2026-05-21 17:23:08 +02:00
from __future__ import annotations
from ..settings import DdnsSettings
from .components import _code_window , _example_update_ips , _message_band
from .html_utils import escape , trusted
from .layout import _page , _top_nav
2026-05-21 17:58:20 +02:00
from .templates_public_sections import _linux_installer_section
2026-05-21 17:23:08 +02:00
def _render_public_home (
settings : DdnsSettings ,
message : str | None ,
request_ip : str | None = None ,
) -> str :
base_url = settings . public_base_url . rstrip ( "/" )
hostname_suffix = settings . hostname_suffix or "your KarlDNS suffix"
suffix = escape ( hostname_suffix )
demo_update_url = escape ( f " { base_url } /api/v1/router/update/<secret>?myip=<ipaddr>" )
example_ipv4 , _ = _example_update_ips ( request_ip )
create_demo_command = f "curl -sS ' { base_url } /api/v1/router/update/<secret>?myip= { example_ipv4 } '"
create_demo = f " { create_demo_command } \n good { example_ipv4 } "
manual_demo = " \n " . join (
[
"# explicit IPv4" ,
f "curl -sS ' { base_url } /api/v1/router/update/<secret>?myip= { example_ipv4 } '" ,
"# source IP fallback" ,
f "curl -sS ' { base_url } /api/v1/router/update/<secret>'" ,
]
)
message_html = _message_band ( message ) if message else ""
description = "KarlDNS is a free self-hosted Dynamic DNS service for FRITZ!Box routers, generated KarlDNS hostnames, Linux cron jobs, and home networks."
return _page (
"KarlDNS" ,
trusted ( 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.webp?v=3" alt="" width="80" height="80" decoding="async" fetchpriority="high">
<h1>Dynamic DNS for home routers.</h1>
<div class="install-pill mx-auto">
<code> { demo_update_url } </code>
</div>
<p class="lead">KarlDNS gives you a generated DynDNS hostname under { suffix } . Add the private update URL to your router, or use it from a script.</p>
<div class="d-flex gap-2 flex-wrap justify-content-center mt-2">
<form method="post" action="/" class="hero-action-form" aria-label="Create a generated DynDNS hostname">
<button type="submit" class="btn btn-primary rounded-pill">Create hostname</button>
</form>
<a class="btn btn-outline-primary rounded-pill" href="#manual-updates">Manual updates</a>
</div>
</div>
</section>
{ message_html }
<section class="section section-compact">
<div class="container narrow text-center">
<p class="eyebrow">How it works</p>
<h2>No account needed.</h2>
<p class="section-copy mx-auto">Create a hostname, save the private values, register it, done.</p>
<ol class="flow-steps" aria-label="KarlDNS setup flow">
<li><strong>Create</strong><span>Random hostname and private links.</span></li>
<li><strong>Save</strong><span>Copy the secrets while they are shown.</span></li>
<li><strong>Register</strong><span>Activate the hostname when you are ready.</span></li>
<li><strong>Update</strong><span>Router, cron, or CNAME can use it.</span></li>
</ol>
{ _code_window ( create_demo , copy_label = "update example" , class_name = "mt-4" , copy_value = create_demo_command ) }
</div>
</section>
<section class="section" id="manual-updates">
<div class="container narrow text-center">
<h2>Update from a router or script.</h2>
<p class="lead mx-auto mt-3">Works with FRITZ!Box, curl, cron, Home Assistant, OpenWrt, pfSense, OPNsense, UniFi, MikroTik, and custom tools.</p>
2026-05-21 19:09:37 +02:00
{ _code_window ( manual_demo , copy_label = "manual update examples" , class_name = "manual-code text-start mt-4" ) }
2026-05-21 17:23:08 +02:00
</div>
</section>
{ _linux_installer_section ( base_url ) }
</main>
""" ),
description = description ,
preload_logo = True ,
canonical_url = f " { base_url } /" ,
image_url = f " { base_url } /social-preview.png" ,
image_alt = "KarlDNS landing page with private Dynamic DNS update URL" ,
structured_data = {
"@context" : "https://schema.org" ,
"@type" : "SoftwareApplication" ,
"name" : "KarlDNS" ,
"applicationCategory" : "NetworkingApplication" ,
"operatingSystem" : "Linux" ,
"url" : f " { base_url } /" ,
"description" : description ,
"image" : f " { base_url } /social-preview.png" ,
"offers" : { "@type" : "Offer" , "price" : "0" , "priceCurrency" : "EUR" },
"featureList" : [
"Dynamic DNS for FRITZ!Box routers" ,
"Accountless private update URLs" ,
"CNAME target for external DNS names" ,
"Linux cron updater" ,
],
},
)
def _render_privacy_page ( settings : DdnsSettings ) -> str :
base_url = settings . public_base_url . rstrip ( "/" )
description = "Privacy and legal information for KarlDNS, including stored hostnames, update links, IP history, and cleanup behavior."
return _page (
"Legal and Privacy" ,
trusted ( f """
<main id="main">
{ _top_nav () }
<section class="hero-band compact">
<div class="container text-center d-grid justify-items-center gap-3">
<p class="eyebrow">Legal and privacy</p>
<h1>Privacy basics.</h1>
<p class="lead">KarlDNS stores only the data needed to keep your hostname updated.</p>
</div>
</section>
<section class="section">
<div class="container narrow d-grid gap-4">
<div>
<h2>Data processed</h2>
<p class="section-copy">KarlDNS stores hostnames, protected dashboard tokens, private update links, recent IP history, timestamps, and service events.</p>
</div>
<div>
<h2>Secrets</h2>
<p class="section-copy">Dashboard links, registration URLs, and update URLs are secrets. Anyone with them may update or manage the hostname. Dashboard lookup values are not stored as readable text.</p>
</div>
<div>
<h2>Logs and retention</h2>
<p class="section-copy">KarlDNS keeps recent update events for troubleshooting. Cleanup removes unused hostnames. Server logs may also include IP addresses and request details.</p>
</div>
<div>
<h2>Contact</h2>
<p class="section-copy">For the public KarlDNS instance, contact Karl through <a href="https://karl.fail" rel="noopener">karl.fail</a> or <a href="https://karlcom.de" rel="noopener">karlcom.de</a>.</p>
</div>
</div>
</section>
</main>
""" ),
description = description ,
canonical_url = f " { base_url } /privacy" ,
image_url = f " { base_url } /social-preview.png" ,
image_alt = "KarlDNS legal and privacy information" ,
structured_data = {
"@context" : "https://schema.org" ,
"@type" : "WebPage" ,
"name" : "KarlDNS Legal and Privacy" ,
"url" : f " { base_url } /privacy" ,
"description" : description ,
"isPartOf" : {
"@type" : "WebSite" ,
"name" : "KarlDNS" ,
"url" : f " { base_url } /" ,
},
},
)
def _render_api_docs_page ( settings : DdnsSettings , request_ip : str | None = None ) -> str :
base_url = settings . public_base_url . rstrip ( "/" )
example_ipv4 , _ = _example_update_ips ( request_ip )
create_example = f "curl -sS -X POST { base_url } /api/v1/hostnames"
2026-05-21 18:36:06 +02:00
register_example = f "curl -sS -X POST ' { base_url } /api/v1/router/register/<karl_dashboard_secret>?myip= { example_ipv4 } '"
update_example = f "curl -sS ' { base_url } /api/v1/router/update/<karl_update_secret>?myip= { example_ipv4 } '"
status_example = f "curl -sS ' { base_url } /api/v1/hostnames/<karl_dashboard_secret>?history_limit=10'"
rotate_example = f "curl -sS -X POST { base_url } /api/v1/hostnames/<karl_dashboard_secret>/rotate-update-url"
2026-05-21 17:23:08 +02:00
schema_example = f "curl -sS { base_url } /openapi.json"
description = "Small KarlDNS API guide for creating, activating, updating, reading, and deleting generated Dynamic DNS hostnames."
return _page (
"KarlDNS API" ,
trusted ( f """
<main id="main">
{ _top_nav () }
<section class="hero-band compact">
<div class="container text-center d-grid justify-items-center gap-3">
<p class="eyebrow">API</p>
<h1>Small API surface.</h1>
2026-05-21 17:58:20 +02:00
<p class="lead">Create a hostname, register it, update it, and read its status.</p>
2026-05-21 17:23:08 +02:00
</div>
</section>
<section class="section section-compact">
<div class="container narrow d-grid gap-4">
<div class="api-endpoints">
<h2>Endpoints</h2>
<ol class="simple-steps text-start">
2026-05-21 17:58:20 +02:00
<li><strong>POST /api/v1/hostnames.</strong> Create a pending hostname and private setup values. The response includes <code>registration_url</code>.</li>
2026-05-22 16:14:59 +02:00
<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>
2026-05-21 18:36:06 +02:00
<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>
<li><strong>DELETE /api/v1/hostnames/<karl_secret>.</strong> Delete hostname and DNS records.</li>
2026-05-21 17:23:08 +02:00
</ol>
</div>
<div>
<h2>Create</h2>
{ _code_window ( create_example , copy_label = "create command" , class_name = "mt-3" ) }
</div>
<div>
<h2>Register</h2>
{ _code_window ( register_example , copy_label = "register command" , class_name = "mt-3" ) }
</div>
<div>
<h2>Update</h2>
{ _code_window ( update_example , copy_label = "update command" , class_name = "mt-3" ) }
</div>
<div>
<h2>Status</h2>
<p class="section-copy">Use the private dashboard secret to read current DNS state and recent updates for only that hostname.</p>
{ _code_window ( status_example , copy_label = "status command" , class_name = "mt-3" ) }
</div>
<div>
<h2>Rotate update URL</h2>
<p class="section-copy">Use the private dashboard secret to replace the router update URL. The old update URL stops working immediately.</p>
{ _code_window ( rotate_example , copy_label = "rotate command" , class_name = "mt-3" ) }
</div>
<div>
<h2>Schema</h2>
2026-05-21 17:58:20 +02:00
<p class="section-copy">Machine-readable OpenAPI is available for the public hostname and router endpoints.</p>
2026-05-21 17:23:08 +02:00
{ _code_window ( schema_example , copy_label = "schema command" , class_name = "mt-3" ) }
</div>
</div>
</section>
</main>
""" ),
description = description ,
canonical_url = f " { base_url } /docs" ,
image_url = f " { base_url } /social-preview.png" ,
image_alt = "KarlDNS API documentation" ,
)