Archived
Publish placeholder DNS on registration
This commit is contained in:
1 parent
99c55405bc
commit
c0d82eaf26
4 files changed
+21
-9
No files matched your search
@@ -47,7 +47,7 @@ KarlDNS lets you host your own managed DynDNS service:
|
||||
3. KarlDNS shows the exact router settings and private values.
|
||||
4. The user confirms they saved the values, which registers the hostname.
|
||||
5. The router periodically calls the update URL with its current public IPv4/IPv6 address.
|
||||
6. KarlDNS publishes the matching DNS records through your configured DNS backend.
|
||||
6. KarlDNS publishes the matching DNS records through your configured DNS backend. If no public IP is visible during registration, it publishes a temporary `192.0.2.1` A record until the router sends its first real update.
|
||||
7. 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.
|
||||
@@ -335,7 +335,7 @@ Example:
|
||||
curl -sS -X POST https://karldns.de/api/v1/hostnames
|
||||
```
|
||||
|
||||
The create response includes `registration_url`, `update_url`, and `dashboard_url`. Save the returned secrets, then `POST` the registration URL before using the update URL. Registration publishes an initial DNS record when a public request IP or explicit `myip`/`myipv6` value is available; otherwise the first router update publishes the record. Pending hostnames that are never registered are cleaned up automatically.
|
||||
The create response includes `registration_url`, `update_url`, and `dashboard_url`. Save the returned secrets, then `POST` the registration URL before using the update URL. Registration publishes an initial DNS record when a public request IP or explicit `myip`/`myipv6` value is available. If no public IP is visible, KarlDNS publishes a temporary `192.0.2.1` A record and replaces it on the first router update. Pending hostnames that are never registered are cleaned up automatically.
|
||||
|
||||
Use the dashboard secret to rotate the update URL:
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ from ..settings import DdnsSettings
|
||||
from ..store import DdnsStore
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
INITIAL_PLACEHOLDER_IPV4 = "192.0.2.1"
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@@ -79,7 +80,12 @@ class DdnsService:
|
||||
source_fallback = source_ip if _is_global_ip(source_ip) else ""
|
||||
supplied_ip = any(value and value.strip() for value in (myip, ipaddr, myipv6, ip6addr))
|
||||
if not supplied_ip and not source_fallback:
|
||||
return False
|
||||
ipv4 = INITIAL_PLACEHOLDER_IPV4
|
||||
ipv6 = None
|
||||
update_ipv4 = True
|
||||
update_ipv6 = False
|
||||
detail = "initial placeholder dns registered"
|
||||
else:
|
||||
ipv4, ipv6, update_ipv4, update_ipv6 = parse_update_ips(
|
||||
source_fallback,
|
||||
myip,
|
||||
@@ -87,6 +93,7 @@ class DdnsService:
|
||||
myipv6,
|
||||
ip6addr,
|
||||
)
|
||||
detail = "initial dns registered"
|
||||
lock = self._lock_for(hostname)
|
||||
with lock:
|
||||
planned = self.store.preview_update(hostname, ipv4, ipv6, update_ipv4=update_ipv4, update_ipv6=update_ipv6)
|
||||
@@ -114,7 +121,7 @@ class DdnsService:
|
||||
self.store.log_update_event(hostname, ipv4, ipv6, DNS_FAILED, f"{type(exc).__name__}: {exc}", source_ip)
|
||||
raise
|
||||
result = self.store.commit_update_result(planned)
|
||||
self.store.log_update_event(hostname, result.ipv4, result.ipv6, UPDATED, "initial dns registered", source_ip)
|
||||
self.store.log_update_event(hostname, result.ipv4, result.ipv6, UPDATED, detail, source_ip)
|
||||
return True
|
||||
|
||||
def apply_update(
|
||||
|
||||
@@ -183,7 +183,7 @@ def _render_api_docs_page(settings: DdnsSettings, request_ip: str | None = None)
|
||||
<h2>Endpoints</h2>
|
||||
<ol class="simple-steps text-start">
|
||||
<li><strong>POST /api/v1/hostnames.</strong> Create a pending hostname and private setup values. The response includes <code>registration_url</code>.</li>
|
||||
<li><strong>GET or POST /api/v1/router/register/<karl_secret>.</strong> Register the hostname after secrets are saved. Publishes an initial DNS record when a public request IP or explicit <code>myip</code>/<code>myipv6</code> value is available; otherwise the first router update publishes it.</li>
|
||||
<li><strong>GET or POST /api/v1/router/register/<karl_secret>.</strong> Register the hostname after secrets are saved. Publishes an initial DNS record from a public request IP or explicit <code>myip</code>/<code>myipv6</code> value. If no public IP is visible, publishes a temporary <code>192.0.2.1</code> A record until the first router update.</li>
|
||||
<li><strong>GET or POST /api/v1/router/update/<karl_secret>.</strong> Router-friendly update endpoint. Supports <code>myip</code>, <code>ipaddr</code>, <code>myipv6</code>, and <code>ip6addr</code>.</li>
|
||||
<li><strong>GET /api/v1/hostnames/<karl_secret>.</strong> Read hostname status and recent history.</li>
|
||||
<li><strong>POST /api/v1/hostnames/<karl_secret>/rotate-update-url.</strong> Replace the private router update URL.</li>
|
||||
|
||||
+9
-4
@@ -273,7 +273,9 @@ def test_registration_publishes_initial_dns_when_provider_is_configured(tmp_path
|
||||
assert published[0].ipv4 == "203.0.113.77"
|
||||
|
||||
|
||||
def test_registration_without_public_source_ip_waits_for_first_router_update(tmp_path: Path, monkeypatch) -> None:
|
||||
def test_registration_without_public_source_ip_publishes_placeholder_until_first_router_update(
|
||||
tmp_path: Path, monkeypatch
|
||||
) -> None:
|
||||
published = []
|
||||
monkeypatch.setattr(ddns_module, "publish_dns", lambda result, settings: published.append(result))
|
||||
app = make_app(
|
||||
@@ -298,11 +300,14 @@ def test_registration_without_public_source_ip_waits_for_first_router_update(tmp
|
||||
assert registered.status_code == 200
|
||||
assert registered.text == "good registered"
|
||||
assert status.json()["status"] == "active"
|
||||
assert status.json()["ipv4"] is None
|
||||
assert status.json()["ipv4"] == "192.0.2.1"
|
||||
assert update.status_code == 200
|
||||
assert update.text == "good 203.0.113.80"
|
||||
assert len(published) == 1
|
||||
assert published[0].ipv4 == "203.0.113.80"
|
||||
assert len(published) == 2
|
||||
assert published[0].ipv4 == "192.0.2.1"
|
||||
assert published[0].update_ipv4 is True
|
||||
assert published[0].update_ipv6 is False
|
||||
assert published[1].ipv4 == "203.0.113.80"
|
||||
|
||||
|
||||
def test_api_update_publishes_before_recording_ip(tmp_path: Path, monkeypatch) -> None:
|
||||
|
||||
Reference in new issue
Block a user