Archived
Document manual DDNS update flow
This commit is contained in:
1 parent
f865bc28c7
commit
62e5724318
3 files changed
+61
No files matched your search
@@ -160,6 +160,8 @@ The generated FRITZ!Box URL uses the native placeholders:
|
||||
https://ddns.example.net/u/<random-update-slug>?myip=<ipaddr>&myipv6=<ip6addr>
|
||||
```
|
||||
|
||||
FRITZ!Box replaces `<ipaddr>` and `<ip6addr>` with the current WAN IPv4/IPv6 address and sends an HTTP GET request to that URL whenever it detects an address change. RouterPulse returns DynDNS-style responses such as `good 203.0.113.10`, `nochg 203.0.113.10`, `badauth`, or `nohost`.
|
||||
|
||||
This URL is enough for routers that only expose a single update URL field and do not send separate credentials. RouterPulse also shows a `One-box update URL` that embeds `hostname`, `username`, and `password` as query parameters for clients that require credential-style DynDNS URLs but do not expose separate credential fields. Treat both URLs as secrets.
|
||||
|
||||
The compatibility endpoint is also available:
|
||||
@@ -170,6 +172,27 @@ 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.
|
||||
|
||||
## Manual Updates And Automation
|
||||
|
||||
Every generated hostname can be updated with curl, cron, a shell script, Home Assistant, OpenWrt, pfSense, or any client that can send an HTTP GET request:
|
||||
|
||||
```bash
|
||||
# Explicit IPv4 update
|
||||
curl -sS 'https://ddns.example.net/u/<random-update-slug>?myip=203.0.113.10'
|
||||
|
||||
# Explicit IPv6 update
|
||||
curl -sS 'https://ddns.example.net/u/<random-update-slug>?myipv6=2001:db8::10'
|
||||
|
||||
# Use the request source IP
|
||||
curl -sS 'https://ddns.example.net/u/<random-update-slug>'
|
||||
```
|
||||
|
||||
The `/nic/update` compatibility endpoint also works for clients that expect hostname and credentials in the URL:
|
||||
|
||||
```bash
|
||||
curl -sS 'https://ddns.example.net/nic/update?hostname=home.example.net&username=router&password=<password>&myip=203.0.113.10'
|
||||
```
|
||||
|
||||
## 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:
|
||||
|
||||
@@ -817,6 +817,7 @@ def _credentials_panel(service: DdnsService, created_account: dict[str, str] | N
|
||||
management_url = service.magic_management_url(created_account)
|
||||
cname_record = f"your-subdomain.example.com. CNAME {hostname}."
|
||||
fritzbox_fields = _fritzbox_fields(update_url, hostname, username, password, management_url)
|
||||
manual_commands = _manual_update_commands(update_url)
|
||||
return f"""
|
||||
<section class="section success-section">
|
||||
<div class="container">
|
||||
@@ -838,6 +839,7 @@ def _credentials_panel(service: DdnsService, created_account: dict[str, str] | N
|
||||
{_copy_row("Kennwort (optional):", password, "Secret")}
|
||||
{_copy_row("Private status page:", management_url, "Secret")}
|
||||
</div>
|
||||
{_manual_update_panel(manual_commands)}
|
||||
</div>
|
||||
</section>
|
||||
"""
|
||||
@@ -881,6 +883,31 @@ def _copy_button(label: str, value: str) -> str:
|
||||
return f'<button type="button" class="btn btn-outline-primary rounded-pill" data-copy="{safe_value}">{safe_label}</button>'
|
||||
|
||||
|
||||
def _manual_update_commands(update_url: str) -> str:
|
||||
update_base = update_url.split("?", 1)[0]
|
||||
return "\n".join(
|
||||
[
|
||||
f"curl -sS '{update_base}?myip=203.0.113.10'",
|
||||
f"curl -sS '{update_base}?myipv6=2001:db8::10'",
|
||||
f"curl -sS '{update_base}'",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def _manual_update_panel(commands: str) -> str:
|
||||
safe_commands = html.escape(commands)
|
||||
return f"""
|
||||
<div class="card card-body gap-3 mt-4">
|
||||
<div>
|
||||
<h3>Manual updates</h3>
|
||||
<p class="section-copy">FRITZ!Box replaces <code><ipaddr></code> and <code><ip6addr></code>, then sends a GET request to the Update-URL. Scripts and other routers can do the same with curl. If no IP parameter is sent, RouterPulse uses the request source IP.</p>
|
||||
</div>
|
||||
<pre class="manual-code mb-0"><code>{safe_commands}</code></pre>
|
||||
{_copy_button("Copy curl examples", commands)}
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
def _fritzbox_fields(
|
||||
update_url: str,
|
||||
hostname: str,
|
||||
@@ -935,6 +962,7 @@ def _render_management_page(service: DdnsService, settings: DdnsSettings, manage
|
||||
updated = html.escape(str(account.get("updated_at") or "Never"))
|
||||
fritzbox_fields = _fritzbox_fields(update_url, hostname, username, "unchanged; only shown when generated")
|
||||
cname_record = f"your-subdomain.example.com. CNAME {hostname}."
|
||||
manual_commands = _manual_update_commands(update_url)
|
||||
history_rows = "\n".join(_user_event_row(event) for event in service.store.list_update_events_for_hostname(str(account["hostname"]), 20)) or """
|
||||
<tr><td colspan="5" class="empty">No router updates yet.</td></tr>
|
||||
"""
|
||||
@@ -972,6 +1000,7 @@ def _render_management_page(service: DdnsService, settings: DdnsSettings, manage
|
||||
{_copy_row("Kennwort (optional):", "unchanged; only shown when generated", "Secret")}
|
||||
</div>
|
||||
</div>
|
||||
{_manual_update_panel(manual_commands)}
|
||||
</div>
|
||||
</section>
|
||||
<section class="section">
|
||||
@@ -1363,6 +1392,7 @@ def _page(title: str, body: str) -> str:
|
||||
.copy-field .form-control {{ min-width: 0; }}
|
||||
.copy-field code.form-control {{ width: 1%; flex: 1 1 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }}
|
||||
.copy-field code {{ min-height: 40px; margin: 0; color: var(--rp-ink); font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.6; background: var(--rp-surface-alt); }}
|
||||
.manual-code {{ overflow-x: auto; padding: 16px; border: 1px solid var(--rp-line); border-radius: 12px; color: var(--rp-ink); background: var(--rp-surface-alt); font: 400 13px/1.6 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }}
|
||||
.admin-table-scroll {{ max-height: 560px; overflow: auto; }}
|
||||
.admin-table-scroll thead th {{ position: sticky; top: 0; z-index: 1; background: var(--rp-surface); }}
|
||||
.table-page-size {{ width: auto; min-width: 76px; }}
|
||||
|
||||
@@ -368,6 +368,10 @@ def test_self_service_managed_hostname_generates_random_account(tmp_path: Path)
|
||||
assert "Kennwort (optional):" in response.text
|
||||
assert "Copy complete FRITZ!Box fields" in response.text
|
||||
assert "Copy CNAME record" in response.text
|
||||
assert "Manual updates" in response.text
|
||||
assert "curl -sS" in response.text
|
||||
assert "203.0.113.10" in response.text
|
||||
assert "2001:db8::10" in response.text
|
||||
assert "Public:" in response.text
|
||||
assert "Secret:" in response.text
|
||||
assert ".ddns.example.net" in response.text
|
||||
@@ -398,6 +402,8 @@ def test_magic_hostname_without_login_generates_update_and_management_links(tmp_
|
||||
assert "Private status page:" in response.text
|
||||
assert "Copy complete FRITZ!Box fields" in response.text
|
||||
assert "Copy CNAME record" in response.text
|
||||
assert "FRITZ!Box replaces" in response.text
|
||||
assert "Copy curl examples" in response.text
|
||||
assert response.text.count("Secret") >= 3
|
||||
assert response.text.count("Public") >= 3
|
||||
assert ".ddns.example.net" in response.text
|
||||
@@ -428,6 +434,8 @@ def test_magic_management_link_can_delete_hostname(tmp_path: Path) -> None:
|
||||
assert "Kennwort (optional):" in management.text
|
||||
assert "Copy complete FRITZ!Box fields" in management.text
|
||||
assert "Copy CNAME record" in management.text
|
||||
assert "Manual updates" in management.text
|
||||
assert "curl -sS" in management.text
|
||||
assert "Public:" in management.text
|
||||
assert "Secret:" in management.text
|
||||
assert "Router updates" in management.text
|
||||
|
||||
Reference in new issue
Block a user