Clarify managed hostnames as CNAME targets

This commit is contained in:
StasonJatham committed 2026-05-20 19:00:21 +02:00
1 parent 3ce992ae9e
commit 840d3281d1
3 files changed
+34 -10

No files matched your search

+21 -2
View File
@@ -2,7 +2,7 @@
**Self-hosted DynDNS for FRITZ!Box, OpenWrt, pfSense, OPNsense, UniFi, MikroTik, and any router that can call a custom DDNS update URL.**
RouterPulse is the friendly name for `router-dyndns`: a small FastAPI Dynamic DNS provider you can run on your own VPS. It gives users FRITZ!Box-ready DynDNS credentials, keeps public A/AAAA records updated through Cloudflare or RFC 2136, and supports verified custom domains with DNS TXT ownership checks.
RouterPulse is the friendly name for `router-dyndns`: a small FastAPI Dynamic DNS provider you can run on your own VPS. It gives users FRITZ!Box-ready DynDNS credentials, keeps public A/AAAA records updated through Cloudflare or RFC 2136, provides generated hostnames that work as CNAME targets, and supports verified custom domains with DNS TXT ownership checks.
If you want a lightweight DuckDNS / Dynu / No-IP style service that you control yourself, RouterPulse is built for that use case.
@@ -12,6 +12,7 @@ If you want a lightweight DuckDNS / Dynu / No-IP style service that you control
- **Self-hosted Dynamic DNS provider** for home labs, small ISPs, communities, and private infrastructure.
- **FRITZ!Box compatible**: copy the generated `Update-URL`, `Domainnamen`, `Benutzername`, and `Kennwort` straight into the German FRITZ!Box DynDNS form.
- **CNAME-friendly hostnames**: users can use the generated hostname directly, or point `home.example.com` to it with a CNAME.
- **No registration required**: provider hostnames and custom-domain setup both use private magic links.
- **Custom domain support**: users enter a domain, save the private claim link, add a DNS TXT record, then press a button to verify ownership before credentials are issued.
- **Real DNS publishing** through **Cloudflare** or **RFC 2136** with A and AAAA support.
@@ -37,6 +38,7 @@ RouterPulse lets you host your own managed DynDNS service:
3. RouterPulse shows the exact router settings for the DynDNS form.
4. The router periodically calls the update URL with its current public IPv4/IPv6 address.
5. RouterPulse publishes the matching DNS records through your configured DNS backend.
6. If the user wants their own DNS name, they can create a CNAME to the generated hostname.
That means users always have a current hostname for VPN, remote access, self-hosted apps, home servers, NAS devices, and lab networks.
@@ -46,6 +48,7 @@ That means users always have a current hostname for VPN, remote access, self-hos
- Free DDNS service for friends, customers, a community, or a home lab.
- Dynamic DNS for IPv4 and IPv6 home internet connections.
- Custom domain DDNS with DNS TXT verification.
- CNAME target hostnames for users who want `home.example.com` without sharing DNS API access.
- Lightweight Cloudflare DDNS provider with a web UI.
- RFC 2136 / TSIG DynDNS frontend for BIND, Knot, PowerDNS, or compatible DNS servers.
@@ -56,6 +59,7 @@ That means users always have a current hostname for VPN, remote access, self-hos
| Router update URLs | FRITZ!Box custom provider URL, `/u/<slug>`, and `/nic/update` compatibility |
| DNS records | A and AAAA |
| DNS providers | Cloudflare API and RFC 2136 dynamic DNS |
| Generated hostnames | Random provider-owned names that can be used directly or as CNAME targets |
| Custom domains | TXT challenge verification before hostname creation |
| Persistence | SQLite with WAL mode |
| API | FastAPI, OpenAPI, Swagger UI, ReDoc |
@@ -144,6 +148,7 @@ Create a hostname in the web UI. RouterPulse shows the exact values for:
- `Update-URL`
- `Domainnamen`
- `CNAME target`
- `Benutzername`
- `Kennwort`
@@ -161,9 +166,23 @@ https://ddns.example.net/nic/update?hostname=<domain>&myip=<ipaddr>&myipv6=<ip6a
Prefer `/u/<slug>` for managed service use because the router request cannot change the hostname.
## Use Your Own Subdomain With CNAME
Most users do not need domain verification. Generate a RouterPulse hostname first, then create a CNAME at your DNS provider:
```dns
home.example.com. CNAME a1b2c3d4.ddns.example.net.
```
Put the RouterPulse `Update-URL`, `Domainnamen`, `Benutzername`, and `Kennwort` into the router. The router updates `a1b2c3d4.ddns.example.net`; `home.example.com` follows through the CNAME.
Use a subdomain such as `home.example.com`. Root/apex domains like `example.com` usually cannot be plain CNAME records unless your DNS provider supports CNAME flattening or ALIAS/ANAME records.
## Custom Domain Flow
Custom domains are intentionally simple:
This flow is only needed when RouterPulse should publish A/AAAA records directly inside a user-owned domain instead of using a CNAME target.
Custom-domain publishing is intentionally simple:
1. Enter the domain.
2. Save the private claim link.
+10 -8
View File
@@ -649,10 +649,10 @@ def _render_public_home(
<div class="container text-center d-grid justify-items-center gap-3">
<p class="eyebrow">FRITZ!Box compatible</p>
<h1>DynDNS without signups.</h1>
<p class="lead">Create a secure update endpoint under {suffix}. Provider hostnames are anonymous; custom domains require DNS proof.</p>
<p class="lead">Create a secure hostname under {suffix}. Use it directly, or point your own subdomain at it with a CNAME.</p>
<div class="d-flex gap-2 flex-wrap justify-content-center mt-2">
<a class="btn btn-primary rounded-pill" href="#create">Get a hostname</a>
<a class="btn btn-outline-primary rounded-pill" href="#custom-domain">Verify a domain</a>
<a class="btn btn-outline-primary rounded-pill" href="#custom-domain">Publish inside my domain</a>
</div>
</div>
</section>
@@ -663,8 +663,8 @@ def _render_public_home(
<div class="row g-4 align-items-start">
<div class="col-lg-7">
<p class="eyebrow">Free DynDNS hostname</p>
<h2>Create a router update URL</h2>
<p class="section-copy">Generate a random hostname, paste the update URL into your router, and keep the management link private.</p>
<h2>Create your DynDNS target</h2>
<p class="section-copy">Generate a random hostname, paste the update URL into your router, and keep the private status page. Your own DNS name can point to this hostname with a CNAME.</p>
</div>
<form method="post" action="/magic" class="col-lg-5 card card-body gap-3" aria-label="Create a generated DynDNS hostname">
<label>Router username
@@ -773,9 +773,9 @@ def _custom_domain_flow(service: DdnsService, challenge: dict[str, str | None] |
<div class="container">
<div class="d-flex align-items-md-center justify-content-between gap-4 flex-column flex-md-row">
<div>
<p class="eyebrow">Custom domain</p>
<p class="eyebrow">Direct custom-domain publishing</p>
<h2>Verify DNS ownership</h2>
<p class="section-copy">We only issue router credentials after the DNS record proves you control the domain.</p>
<p class="section-copy">Use this only when RouterPulse should publish A/AAAA records directly inside your domain. If you just want your own subdomain, generate a hostname above and create a CNAME to it.</p>
</div>
</div>
<div class="row g-3 mt-4">
@@ -812,10 +812,11 @@ def _credentials_panel(service: DdnsService, created_account: dict[str, str] | N
<div class="container">
<p class="eyebrow">Credentials generated</p>
<h2>{html.escape(hostname)}</h2>
<p class="section-copy">Save this now. The password and management link are only shown on this screen.</p>
<p class="section-copy">Save this now. The password and private status page are only shown on this screen. To use your own DNS name, create a CNAME from that name to this hostname.</p>
<div class="d-grid gap-2">
{_copy_row("Update-URL:", update_url)}
{_copy_row("Domainnamen:", hostname)}
{_copy_row("CNAME target:", hostname)}
{_copy_row("Benutzername:", username)}
{_copy_row("Kennwort:", password)}
{_copy_row("Private status page:", management_url)}
@@ -898,11 +899,12 @@ def _render_management_page(service: DdnsService, settings: DdnsSettings, manage
<div class="col-lg-6">
<p class="eyebrow">Router settings</p>
<h2>FRITZ!Box fields</h2>
<p class="section-copy">Use these values in the FRITZ!Box custom DynDNS provider fields.</p>
<p class="section-copy">Use these values in the FRITZ!Box custom DynDNS provider fields. If you use your own DNS name, point it at this hostname with a CNAME.</p>
</div>
<div class="col-lg-6 d-grid gap-2">
{_copy_row("Update-URL:", update_url)}
{_copy_row("Domainnamen:", hostname)}
{_copy_row("CNAME target:", hostname)}
{_copy_row("Benutzername:", username)}
{_copy_row("Kennwort:", "unchanged; only shown when generated")}
</div>
+3
View File
@@ -289,6 +289,7 @@ def test_self_service_managed_hostname_generates_random_account(tmp_path: Path)
assert response.status_code == 200
assert "Update-URL:" in response.text
assert "CNAME target:" in response.text
assert ".ddns.example.net" in response.text
assert "/u/" in response.text
assert "/m/" in response.text
@@ -309,6 +310,7 @@ def test_magic_hostname_without_login_generates_update_and_management_links(tmp_
assert response.status_code == 200
assert "Update-URL:" in response.text
assert "CNAME target:" in response.text
assert "Private status page:" in response.text
assert ".ddns.example.net" in response.text
@@ -323,6 +325,7 @@ def test_magic_management_link_can_delete_hostname(tmp_path: Path) -> None:
management = client.get(f"/m/{account['management_slug']}")
assert management.status_code == 200
assert "home.ddns.example.net" in management.text
assert "CNAME target:" in management.text
assert "Router updates" in management.text
deleted = client.post(f"/m/{account['management_slug']}/delete")