from __future__ import annotations from ..ddns_service import DdnsService from .html_utils import SafeHtml, attr, escape, trusted from .templates import ( _copy_button, _copy_row, _fritzbox_fields, _manual_update_commands, _manual_update_panel, _one_time_secret_notice, _secret_notice, ) def _linux_installer_section() -> SafeHtml: system_install = "curl -fsSL https://karldns.de/install.sh | sudo sh" system_uninstall = "curl -fsSL https://karldns.de/uninstall.sh | sudo sh" user_install = "curl -fsSL https://karldns.de/install.sh | sh -s -- --mode user" clean_uninstall = "curl -fsSL https://karldns.de/uninstall.sh | sh -s -- --mode user --delete-config" all_commands = "\n".join([system_install, system_uninstall, user_install, clean_uninstall]) return trusted( f"""

Linux cron updater

Install and uninstall should be boring.

For a Linux server, Proxmox host, NAS, or home-lab box that is always on, the installer creates a hostname if needed, installs the updater, schedules cron, runs the first update, and prints the private dashboard URL.

{escape(system_install)} {escape(system_uninstall)}
{_copy_button("Copy install", system_install)} {_copy_button("Copy uninstall", system_uninstall)} {_copy_button("Copy all commands", all_commands)}
Uninstall keeps the private config by default because it may contain your only local copy of the update and dashboard URLs. Use --delete-config for a clean wipe.

Without sudo

User mode writes to ~/.local/bin, ~/.config/karldns, and your user crontab.

{escape(user_install)} {escape(clean_uninstall)}
{_copy_button("Copy user install", user_install)} {_copy_button("Copy clean user uninstall", clean_uninstall)}
""" ) def _custom_domain_flow(service: DdnsService, challenge: dict[str, str | None] | None, message: str | None) -> SafeHtml: if not challenge: return trusted("") domain = attr(challenge["domain"]) token = str(challenge["token"]) claim_secret = attr(challenge.get("claim_secret") or "") verification_name = service.verification_name(str(challenge["domain"])) claim_url = service.domain_claim_url(challenge) status = escape(message or "Add this TXT record at your DNS provider, then check when it has propagated.") txt_bundle = f"TXT name: {verification_name}\nTXT value: {token}" challenge_html = trusted( f"""
1

Add the TXT record

{status}

TXT name and value are public DNS records. The private claim link is secret.
{_copy_button("Copy TXT record", txt_bundle)} {_copy_row("TXT name", verification_name, "Public")} {_copy_row("TXT value", token, "Public")} {_copy_row("Private claim link", claim_url, "Secret")}
""" ) create_form = _custom_hostname_form(claim_secret) if challenge.get("verified_at") else _locked_custom_hostname_card() return trusted( f"""

Direct custom-domain publishing

Verify DNS ownership

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.

{challenge_html} {create_form}
""" ) def _credentials_panel(service: DdnsService, created_account: dict[str, str] | None) -> SafeHtml: if not created_account: return trusted("") hostname = created_account["hostname"] username = created_account["username"] password = created_account["password"] update_url = service.fritz_update_url(created_account) credentialed_update_url = service.credentialed_update_url(created_account) management_url = service.magic_management_url(created_account) cname_record = f"your-subdomain.example.com. CNAME {hostname}." fritzbox_fields = _fritzbox_fields(update_url, hostname, username, password, management_url) manual_commands = _manual_update_commands(update_url) return trusted( f"""

Hostname generated

{escape(hostname)}

Use the Update-URL directly. The separate Benutzername and Kennwort values are optional compatibility fields for routers that require them.

{_one_time_secret_notice()} {_secret_notice()}
{_copy_button("Copy complete FRITZ!Box fields", fritzbox_fields)} {_copy_button("Copy one-box update URL", credentialed_update_url)} {_copy_button("Copy CNAME record", cname_record)}
{_copy_row("Update-URL:", update_url, "Secret")} {_copy_row("One-box update URL:", credentialed_update_url, "Secret")} {_copy_row("Domainnamen:", hostname, "Public")} {_copy_row("CNAME target:", hostname, "Public")} {_copy_row("Benutzername (optional):", username, "Public")} {_copy_row("Kennwort (optional):", password, "Secret")} {_copy_row("Private status page:", management_url, "Secret")}
{_manual_update_panel(manual_commands)}
""" ) def _custom_hostname_form(claim_secret: str) -> SafeHtml: return trusted( f"""
2

Create router hostname

Router compatibility credentials are generated automatically after creation.

""" ) def _locked_custom_hostname_card() -> SafeHtml: return trusted( """
2

Create router hostname

This unlocks after the TXT record is visible in public DNS.

""" )