2026-05-20 23:55:27 +02:00
from __future__ import annotations
from ..ddns_models import DdnsSettings
from ..ddns_service import DdnsService
from .html_utils import escape , trusted
2026-05-21 14:18:39 +02:00
from .templates import _code_window , _message_band , _page , _top_nav
from .templates_public_sections import _credentials_panel , _linux_installer_section
2026-05-20 23:55:27 +02:00
def _render_public_home (
service : DdnsService ,
settings : DdnsSettings ,
created_account : dict [ str , str ] | None ,
message : str | None ,
) -> str :
2026-05-21 10:05:40 +02:00
base_url = settings . public_base_url . rstrip ( "/" )
2026-05-21 14:18:39 +02:00
hostname_suffix = settings . hostname_suffix or "your KarlDNS suffix"
suffix = escape ( hostname_suffix )
2026-05-21 10:05:40 +02:00
demo_update_url = escape ( f " { base_url } /u/<secret>?myip=<ipaddr>" )
2026-05-21 14:18:39 +02:00
cname_example = f "home.example.com. CNAME your-name. { hostname_suffix } ."
create_demo = "$ curl -sS 'https://karldns.de/u/<secret>?myip=203.0.113.10' \n good 203.0.113.10"
manual_demo = " \n " . join (
[
"# explicit IPv4" ,
"curl -sS 'https://karldns.de/u/<secret>?myip=203.0.113.10'" ,
"# source IP fallback" ,
"curl -sS 'https://karldns.de/u/<secret>'" ,
]
)
2026-05-20 23:55:27 +02:00
created_html = _credentials_panel ( service , created_account )
2026-05-21 14:18:39 +02:00
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."
2026-05-20 23:55:27 +02:00
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">
2026-05-21 10:05:40 +02:00
<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>
2026-05-20 23:55:27 +02:00
<div class="install-pill mx-auto">
<code> { demo_update_url } </code>
</div>
2026-05-21 14:18:39 +02:00
<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>
2026-05-20 23:55:27 +02:00
<div class="d-flex gap-2 flex-wrap justify-content-center mt-2">
2026-05-21 10:05:40 +02:00
<a class="btn btn-primary rounded-pill" href="#create">Create hostname</a>
<a class="btn btn-outline-primary rounded-pill" href="#manual-updates">Manual updates</a>
2026-05-20 23:55:27 +02:00
</div>
</div>
</section>
{ message_html }
{ created_html }
<section class="section" id="create">
<div class="container narrow">
<div class="text-center mb-4">
2026-05-21 10:05:40 +02:00
<p class="eyebrow">Hostname</p>
<h2>No account needed.</h2>
2026-05-21 14:18:39 +02:00
<p class="section-copy mx-auto">Create a KarlDNS hostname. Use it directly, or point your own DNS name at it with a CNAME.</p>
2026-05-20 23:55:27 +02:00
</div>
<div class="row g-4 align-items-stretch">
<div class="col-lg-6">
<div class="card card-body h-100 gap-3">
2026-05-21 14:18:39 +02:00
<p class="eyebrow">KarlDNS hostname</p>
2026-05-21 10:05:40 +02:00
<h3>Create a hostname</h3>
<p class="section-copy mb-0">You get a hostname, a private update URL, and router fields if your router needs them.</p>
2026-05-20 23:55:27 +02:00
<form method="post" action="/magic" class="d-grid gap-3" aria-label="Create a generated DynDNS hostname">
2026-05-21 10:05:40 +02:00
<button type="submit" class="btn btn-primary rounded-pill">Create hostname</button>
2026-05-20 23:55:27 +02:00
</form>
</div>
</div>
<div class="col-lg-6">
<div class="card card-body h-100 gap-3">
2026-05-21 14:18:39 +02:00
<p class="eyebrow">Optional DNS alias</p>
<h3>Add a CNAME</h3>
<p class="section-copy mb-0">After creation, add a CNAME at your DNS provider:</p>
{ _code_window ( cname_example , copy_label = "CNAME example" ) }
<p class="small text-secondary mb-0">KarlDNS updates only the generated hostname. The external DNS name follows it through CNAME.</p>
2026-05-20 23:55:27 +02:00
</div>
</div>
</div>
2026-05-21 14:18:39 +02:00
{ _code_window ( create_demo , copy_label = "update example" , class_name = "mt-4" ) }
2026-05-20 23:55:27 +02:00
</div>
</section>
<section class="section" id="manual-updates">
<div class="container narrow text-center">
2026-05-21 10:05:40 +02:00
<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 14:18:39 +02:00
{ _code_window ( manual_demo , copy_label = "manual update examples" , class_name = "text-start mt-4" ) }
2026-05-20 23:55:27 +02:00
</div>
</section>
{ _linux_installer_section () }
</main>
""" ),
2026-05-21 10:05:40 +02:00
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" ,
2026-05-21 14:18:39 +02:00
"CNAME target for external DNS names" ,
2026-05-21 10:05:40 +02:00
"Linux cron updater" ,
],
},
2026-05-20 23:55:27 +02:00
)
2026-05-21 10:05:40 +02:00
def _render_privacy_page ( settings : DdnsSettings ) -> str :
base_url = settings . public_base_url . rstrip ( "/" )
2026-05-21 14:18:39 +02:00
description = "Privacy and legal information for KarlDNS, including stored hostnames, update links, IP history, and cleanup behavior."
2026-05-20 23:55:27 +02:00
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>
2026-05-21 10:05:40 +02:00
<h1>Privacy basics.</h1>
<p class="lead">KarlDNS stores only the data needed to keep your hostname updated.</p>
2026-05-20 23:55:27 +02:00
</div>
</section>
<section class="section">
<div class="container narrow d-grid gap-4">
<div>
<h2>Data processed</h2>
2026-05-21 14:18:39 +02:00
<p class="section-copy">KarlDNS stores hostnames, update links, protected dashboard tokens, protected router passwords, recent IP history, timestamps, and service events.</p>
2026-05-20 23:55:27 +02:00
</div>
<div>
<h2>Secrets</h2>
2026-05-21 14:18:39 +02:00
<p class="section-copy">Dashboard links, update URLs, router passwords, and full update URLs are secrets. Anyone with them may update or manage the hostname. Passwords and dashboard lookup values are not stored as readable text.</p>
2026-05-20 23:55:27 +02:00
</div>
<div>
<h2>Logs and retention</h2>
2026-05-21 14:18:39 +02:00
<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>
2026-05-20 23:55:27 +02:00
</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>
""" ),
2026-05-21 10:05:40 +02:00
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 } /" ,
},
},
2026-05-20 23:55:27 +02:00
)