💡 Idea
So basically at home I have a fritzbox router, and I have a home server, and a homeassistant that runs on a raspberry pi. And since i don’t have a static IP, i want to somehow have the ability to access the server/pi from outside via a dns query. The problem is that for that to work i need to have a dynamic dns record which updates whenever the ip of the router changes. Thankfully my router comes with a built-in dyndns function, sadly it doesn’t work with cloudflare. But since all it does is fire a get request to a specific url, I can just use a serverless function to handle the request and update the dns record. The code for this is quite simple, and can be seen here fritz-dns
I copied the idea from the old php code I used before, to host on my server, but since I didn’t want to pay for a server, I decided to use vercel serverless functions. Original idea
The only thing I had to change was migrate it to js, and then use the cloudflare library to update the dns record. I use a random token, to validate that only I can send the request, and then I just update the dns record with the new ip.
That’s it. This week was kinda short, but that’s because I didn’t do that much, since it took like 2 hours to find out what the problem was 😂
🛠️ Code
import { NextRequest, NextResponse } from "next/server";import Cloudflare from "cloudflare";
const client = new Cloudflare({ apiEmail: process.env["CLOUDFLARE_EMAIL"], apiKey: process.env["CLOUDFLARE_API_KEY"],});
function isValidIPv4(ip: string) { return /^(\d{1,3}\.){3}\d{1,3}$/.test(ip);}function isValidIPv6(ip: string) { return /^[a-fA-F0-9:]+$/.test(ip);}
export async function GET(req: NextRequest) { const params = Object.fromEntries(req.nextUrl.searchParams.entries()); const { cf_key, domain, ipv4, ipv6, log, proxy } = params;
if (cf_key !== process.env["CLOUDFLARE_TOKEN"]) { return NextResponse.json( { error: "Invalid Cloudflare token" }, { status: 403 }, ); }
// Logging helper (console only) function wlog(level: string, msg: string) { if (log === "true") { // Optionally, write to a file or external log here console.log(`${new Date().toISOString()} - ${level} - ${msg}`); } }
wlog("INFO", "===== Starting Script =====");
if (!cf_key || !domain) { wlog("ERROR", "Parameter(s) missing or invalid"); wlog("INFO", "Script aborted"); return NextResponse.json( { error: "Parameter(s) missing or invalid" }, { status: 400 }, ); }
let validIPv4 = ipv4 && isValidIPv4(ipv4) ? ipv4 : null; let validIPv6 = ipv6 && isValidIPv6(ipv6) ? ipv6 : null;
if (!validIPv4) { wlog("ERROR", "Neither IPv4 nor IPv6 available."); wlog("INFO", "Script aborted"); return NextResponse.json( { error: "Neither IPv4 nor IPv6 available." }, { status: 400 }, ); }
const proxied = proxy === "true"; wlog("INFO", `Record will${proxied ? "" : " not"} be proxied by Cloudflare`);
// Automatically fetches more pages as needed. for await (const zone of client.zones.list()) { if (domain.includes(zone.name)) { const list = await client.dns.records.list({ zone_id: zone.id }); const record = list.result.find((r: any) => r.name === domain); if (!record) { wlog("ERROR", `Record ${domain} not found`); wlog("INFO", "Script aborted"); return NextResponse.json( { error: `Record ${domain} not found` }, { status: 404 }, ); } await client.dns.records.update(record.id, { zone_id: zone.id, type: "A", name: domain, content: validIPv4, ttl: 2 * 60, }); break; } }
wlog("INFO", "===== Script completed ====="); return NextResponse.json( { message: "IP updated successfully" }, { status: 200 }, );}