Rebrand app to KarlDNS

This commit is contained in:
StasonJatham committed 2026-05-20 20:03:25 +02:00
1 parent be72fa60ac
commit 2403a40800
17 files changed
+58 -96

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 477 KiB

-35
View File
@@ -1,35 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" role="img" aria-labelledby="title">
<title id="title">router-dyndns logo</title>
<defs>
<linearGradient id="navy" x1="48" x2="208" y1="52" y2="220" gradientUnits="userSpaceOnUse">
<stop stop-color="#17395d"/>
<stop offset="1" stop-color="#071727"/>
</linearGradient>
<linearGradient id="blue" x1="44" x2="218" y1="72" y2="180" gradientUnits="userSpaceOnUse">
<stop stop-color="#14b8ff"/>
<stop offset="1" stop-color="#0066cc"/>
</linearGradient>
</defs>
<path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="30" d="M60 155a78 78 0 0 1 112-70"/>
<path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="30" d="M184 88a78 78 0 0 1 12 81"/>
<path fill="#fff" d="m198 64 33 20-32 22-5-22z"/>
<path fill="none" stroke="url(#blue)" stroke-linecap="round" stroke-linejoin="round" stroke-width="18" d="M60 155a78 78 0 0 1 112-70"/>
<path fill="none" stroke="url(#blue)" stroke-linecap="round" stroke-linejoin="round" stroke-width="18" d="M184 88a78 78 0 0 1 12 81"/>
<path fill="url(#blue)" d="m198 74 21 12-20 14-3-14z"/>
<rect x="46" y="172" width="164" height="44" rx="18" fill="#fff"/>
<rect x="56" y="181" width="144" height="26" rx="11" fill="url(#navy)"/>
<path fill="#fff" d="M80 202h18v19a9 9 0 0 1-18 0zm78 0h18v19a9 9 0 0 1-18 0z"/>
<circle cx="86" cy="194" r="4" fill="#fff"/>
<circle cx="104" cy="194" r="4" fill="#fff"/>
<circle cx="122" cy="194" r="4" fill="#fff"/>
<circle cx="128" cy="119" r="47" fill="#fff"/>
<circle cx="128" cy="119" r="37" fill="url(#navy)"/>
<path fill="none" stroke="#fff" stroke-linecap="round" stroke-width="8" d="M102 94c19 6 38 18 53 35M98 132c22 8 46 6 64-12M122 83c-10 22-10 49 0 72M149 88c8 20 8 44-2 63"/>
<circle cx="108" cy="105" r="10" fill="url(#navy)" stroke="#fff" stroke-width="7"/>
<circle cx="148" cy="128" r="10" fill="url(#navy)" stroke="#fff" stroke-width="7"/>
<circle cx="120" cy="148" r="9" fill="url(#navy)" stroke="#fff" stroke-width="7"/>
<path fill="none" stroke="#fff" stroke-linecap="round" stroke-width="24" d="M91 65a53 53 0 0 1 74 0"/>
<path fill="none" stroke="#fff" stroke-linecap="round" stroke-width="24" d="M109 86a28 28 0 0 1 38 0"/>
<path fill="none" stroke="url(#navy)" stroke-linecap="round" stroke-width="12" d="M91 65a53 53 0 0 1 74 0"/>
<path fill="none" stroke="url(#navy)" stroke-linecap="round" stroke-width="12" d="M109 86a28 28 0 0 1 38 0"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

+1 -1
View File
@@ -6,7 +6,7 @@ from .ddns import DdnsSettings, make_app
def main() -> None:
parser = argparse.ArgumentParser(prog="router-dyndns")
parser = argparse.ArgumentParser(prog="karldns")
subparsers = parser.add_subparsers(dest="command", required=True)
render_url = subparsers.add_parser("render-fritz-url", help="Render a FRITZ!Box custom DynDNS update URL.")
+10 -15
View File
@@ -229,7 +229,7 @@ def make_app(settings: DdnsSettings | None = None) -> FastAPI:
await cleanup_task
app = FastAPI(
title="router_dyndns DDNS",
title="KarlDNS",
summary="A small self-hosted DynDNS provider for FRITZ!Box routers.",
description=(
"Generate cryptographically random DynDNS endpoints, verify custom domains with DNS TXT "
@@ -301,7 +301,7 @@ def make_app(settings: DdnsSettings | None = None) -> FastAPI:
raise HTTPException(
status_code=401,
detail="admin authentication required",
headers={"WWW-Authenticate": 'Basic realm="router_dyndns-ddns"'},
headers={"WWW-Authenticate": 'Basic realm="karldns"'},
)
def apply_update(
@@ -521,10 +521,6 @@ def make_app(settings: DdnsSettings | None = None) -> FastAPI:
def favicon() -> FileResponse:
return FileResponse(ASSET_DIR / "favicon.ico", media_type="image/x-icon")
@app.get("/logo.svg", include_in_schema=False)
def logo_svg() -> FileResponse:
return FileResponse(ASSET_DIR / "logo.svg", media_type="image/svg+xml")
@app.get("/logo.png", include_in_schema=False)
def logo_png() -> FileResponse:
return FileResponse(ASSET_DIR / "logo.png", media_type="image/png")
@@ -647,13 +643,13 @@ def _render_public_home(
message_html = _message_band(message) if message and not challenge else ""
challenge_html = _custom_domain_flow(service, challenge, message)
return _page(
"router-dyndns",
"KarlDNS",
f"""
<main id="main">
{_top_nav()}
<section class="hero-band">
<div class="container text-center d-grid justify-items-center gap-3">
<img class="hero-logo" src="/logo.svg" alt="" width="92" height="92">
<img class="hero-logo" src="/logo.png" alt="" width="92" height="92">
<p class="eyebrow">Dynamic DNS for FRITZ!Box and any router</p>
<h1>The simplest way to keep a home IP updated.</h1>
<div class="install-pill mx-auto">
@@ -677,7 +673,7 @@ def _render_public_home(
<p class="section-copy">The generated URL is the secret. FRITZ!Box replaces <code>&lt;ipaddr&gt;</code> and calls it when your public IP changes.</p>
<div class="terminal-card mt-4" aria-hidden="true">
<div class="terminal-dots"><span></span><span></span><span></span></div>
<code>$ curl -sS 'https://ddns.example.net/u/&lt;secret&gt;?myip=203.0.113.10'</code>
<code>$ curl -sS 'https://karldns.de/u/&lt;secret&gt;?myip=203.0.113.10'</code>
<code>good 203.0.113.10</code>
</div>
</div>
@@ -698,9 +694,9 @@ def _render_public_home(
<div class="terminal-card text-start mt-4">
<div class="terminal-dots"><span></span><span></span><span></span></div>
<code># explicit IPv4</code>
<code>curl -sS 'https://ddns.example.net/u/&lt;secret&gt;?myip=203.0.113.10'</code>
<code>curl -sS 'https://karldns.de/u/&lt;secret&gt;?myip=203.0.113.10'</code>
<code># source IP fallback</code>
<code>curl -sS 'https://ddns.example.net/u/&lt;secret&gt;'</code>
<code>curl -sS 'https://karldns.de/u/&lt;secret&gt;'</code>
</div>
</div>
</section>
@@ -727,7 +723,7 @@ def _top_nav(label: str | None = None, links: str = "") -> str:
<a class="skip-link" href="#main">Skip to content</a>
<nav class="navbar navbar-expand-sm sticky-top navbar-blur border-bottom" aria-label="Main navigation">
<div class="container">
<a class="navbar-brand brand" href="/"><img src="/logo.svg" alt="" width="28" height="28">router-dyndns</a>
<a class="navbar-brand brand" href="/"><img src="/logo.png" alt="" width="28" height="28">KarlDNS</a>
<div class="navbar-nav ms-auto flex-row align-items-center gap-2 gap-sm-3">
{links}
{account_html}
@@ -806,7 +802,7 @@ def _custom_domain_flow(service: DdnsService, challenge: dict[str, str | None] |
<div>
<p class="eyebrow">Direct custom-domain publishing</p>
<h2>Verify DNS ownership</h2>
<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>
<p class="section-copy">Use this only when KarlDNS 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">
@@ -924,7 +920,7 @@ def _manual_update_panel(commands: str) -> str:
<div class="card card-body gap-3 mt-4">
<div>
<h3>Manual updates</h3>
<p class="section-copy">FRITZ!Box replaces <code>&lt;ipaddr&gt;</code> and <code>&lt;ip6addr&gt;</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>
<p class="section-copy">FRITZ!Box replaces <code>&lt;ipaddr&gt;</code> and <code>&lt;ip6addr&gt;</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, KarlDNS uses the request source IP.</p>
</div>
<pre class="manual-code mb-0"><code>{safe_commands}</code></pre>
{_copy_button("Copy curl examples", commands)}
@@ -1316,7 +1312,6 @@ def _page(title: str, body: str) -> str:
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{html.escape(title)}</title>
<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" href="/logo.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/logo.png">
<script src="/app.js" defer></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
+2 -2
View File
@@ -16,7 +16,7 @@ class DdnsSettings(BaseModel):
public_base_url: str = Field(default="http://localhost:8080")
trusted_hosts: set[str] = Field(default_factory=set)
hostname_suffix: str = Field(default="")
verification_prefix: str = Field(default="_router_dyndns-ddns")
verification_prefix: str = Field(default="_karldns")
rate_limit_per_minute: int = Field(default=60, ge=1, le=10_000)
admin_rate_limit_per_minute: int = Field(default=20, ge=1, le=10_000)
max_request_body_bytes: int = Field(default=16_384, ge=1024, le=1_048_576)
@@ -85,7 +85,7 @@ class DdnsSettings(BaseModel):
public_base_url=os.getenv("DDNS_PUBLIC_BASE_URL", "http://localhost:8080"),
trusted_hosts=trusted_hosts,
hostname_suffix=os.getenv("DDNS_HOSTNAME_SUFFIX", ""),
verification_prefix=os.getenv("DDNS_VERIFICATION_PREFIX", "_router_dyndns-ddns"),
verification_prefix=os.getenv("DDNS_VERIFICATION_PREFIX", "_karldns"),
rate_limit_per_minute=_env_int("DDNS_RATE_LIMIT_PER_MINUTE", 60),
admin_rate_limit_per_minute=_env_int("DDNS_ADMIN_RATE_LIMIT_PER_MINUTE", 20),
max_request_body_bytes=_env_int("DDNS_MAX_REQUEST_BODY_BYTES", 16_384),
+1 -1
View File
@@ -160,7 +160,7 @@ class DdnsService:
return None
def verification_name(self, domain: str) -> str:
prefix = self.settings.verification_prefix.strip().strip(".") or "_router_dyndns-ddns"
prefix = self.settings.verification_prefix.strip().strip(".") or "_karldns"
return f"{prefix}.{domain}"
def hostname_is_publishable(self, hostname: str) -> bool: