from __future__ import annotations from dataclasses import dataclass from ..settings import DdnsSettings from .components import _code_window, _example_update_ips, _message_band from .html_utils import trusted from .layout import PageOptions, _page, _top_nav from .sections.public_home import _manual_updates_section, _public_home_hero, _setup_flow_section from .templates_public_sections import _linux_installer_section @dataclass(frozen=True, slots=True) class _ApiDocsExamples: create: str register: str update: str status: str rotate: str schema: str 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" demo_update_url = f"{base_url}/api/v1/router/update/?myip=" example_ipv4, _ = _example_update_ips(request_ip) create_demo_command = f"curl -sS '{base_url}/api/v1/router/update/?myip={example_ipv4}'" create_demo = f"{create_demo_command}\ngood {example_ipv4}" manual_demo = "\n".join( [ "# explicit IPv4", f"curl -sS '{base_url}/api/v1/router/update/?myip={example_ipv4}'", "# source IP fallback", f"curl -sS '{base_url}/api/v1/router/update/'", ] ) 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"""
{_top_nav()} {_public_home_hero(suffix=hostname_suffix, demo_update_url=demo_update_url)} {message_html} {_setup_flow_section(create_demo=create_demo, create_demo_command=create_demo_command)} {_manual_updates_section(manual_demo=manual_demo)} {_linux_installer_section(base_url)}
"""), PageOptions( 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"""
{_top_nav()}

Legal and privacy

Privacy basics.

KarlDNS stores only the data needed to keep your hostname updated.

Data processed

KarlDNS stores hostnames, protected dashboard tokens, private update links, recent IP history, timestamps, and service events.

Secrets

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.

Logs and retention

KarlDNS keeps recent update events for troubleshooting. Cleanup removes unused hostnames. Server logs may also include IP addresses and request details.

Contact

For the public KarlDNS instance, contact Karl through karl.fail or karlcom.de.

"""), PageOptions( 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) examples = _api_docs_examples(base_url, example_ipv4) description = ( "Small KarlDNS API guide for creating, activating, updating, reading, and deleting " "generated Dynamic DNS hostnames." ) return _page( "KarlDNS API", trusted(f"""
{_top_nav()} {_api_docs_hero()} {_api_docs_content(examples)}
"""), PageOptions( description=description, canonical_url=f"{base_url}/docs", image_url=f"{base_url}/social-preview.png", image_alt="KarlDNS API documentation", ), ) def _api_docs_examples(base_url: str, example_ipv4: str) -> _ApiDocsExamples: return _ApiDocsExamples( create=f"curl -sS -X POST {base_url}/api/v1/hostnames", register=f"curl -sS -X POST '{base_url}/api/v1/router/register/?myip={example_ipv4}'", update=f"curl -sS '{base_url}/api/v1/router/update/?myip={example_ipv4}'", status=f"curl -sS '{base_url}/api/v1/hostnames/?history_limit=10'", rotate=f"curl -sS -X POST {base_url}/api/v1/hostnames//rotate-update-url", schema=f"curl -sS {base_url}/openapi.json", ) def _api_docs_hero() -> str: return """

API

Small API surface.

Create a hostname, register it, update it, and read its status.

""" def _api_docs_content(examples: _ApiDocsExamples) -> str: return f"""
{_api_endpoints_overview()} {_api_example_block("Create", examples.create, "create command")} {_api_example_block("Register", examples.register, "register command")} {_api_example_block("Update", examples.update, "update command")} {_api_status_block(examples.status)} {_api_rotate_block(examples.rotate)} {_api_schema_block(examples.schema)}
""" def _api_endpoints_overview() -> str: return """

Endpoints

  1. POST /api/v1/hostnames. Create a pending hostname and private setup values. The response includes registration_url.
  2. GET or POST /api/v1/router/register/<karl_secret>. Register the hostname after secrets are saved. Publishes an initial DNS record from a public request IP or explicit myip/myipv6 value. If no public IP is visible, publishes a temporary 192.0.2.1 A record until the first router update.
  3. GET or POST /api/v1/router/update/<karl_secret>. Router-friendly update endpoint. Supports myip, ipaddr, myipv6, and ip6addr.
  4. GET /api/v1/hostnames/<karl_secret>. Read hostname status and recent history.
  5. POST /api/v1/hostnames/<karl_secret>/rotate-update-url. Replace the private router update URL.
  6. DELETE /api/v1/hostnames/<karl_secret>. Delete hostname and DNS records.
""" def _api_example_block(title: str, command: str, copy_label: str) -> str: return f"""

{title}

{_code_window(command, copy_label=copy_label, class_name="mt-3")}
""" def _api_status_block(command: str) -> str: return f"""

Status

Use the private dashboard secret to read current DNS state and recent updates for only that hostname.

{_code_window(command, copy_label="status command", class_name="mt-3")}
""" def _api_rotate_block(command: str) -> str: return f"""

Rotate update URL

Use the private dashboard secret to replace the router update URL. The old update URL stops working immediately.

{_code_window(command, copy_label="rotate command", class_name="mt-3")}
""" def _api_schema_block(command: str) -> str: return f"""

Schema

Machine-readable OpenAPI is available for the public hostname and router endpoints.

{_code_window(command, copy_label="schema command", class_name="mt-3")}
"""