Retire gateway.tls=internal; host routes use a bare subdomain label

acme mode fully replaced the internal-CA path (which required installing a private
CA on every device — the exact pain, esp. on Android, that acme avoids). Remove
`internal` entirely and simplify how services declare host routes.

- Generator (caddyfile.py): drop the `tls internal` branch — modes are now
  `off` | `acme`. In acme mode the published subdomain is the **first DNS label**
  of `proxy.caddy.host` (a bare `claw`, or a legacy `claw.civil.lan`, both →
  `claw.<domain>`), so services stay domain-agnostic and the declared value is
  authoritative again (no more silent service-name override). Shared
  `_host_matcher_block` reused by off-mode and the acme wildcard site.
- castle-api: delete `GET /gateway/ca.crt`, `_gateway_ca_pem`, `_ca_fingerprint`
  and the now-unused imports; drop `ca_fingerprint` from `GatewayInfo` (keep
  `tls`).
- Dashboard: remove the CA-cert download button + unused imports; drop
  `ca_fingerprint` from the `GatewayInfo` type.
- Tests: replace TestCaddyfileTlsInternal with an off-mode class (keeps the
  runner-agnostic host-route coverage); acme tests assert first-label derivation
  incl. label-wins-over-service-name; drop the castle-api CA-endpoint test.
- Docs: registry.md + dns-and-tls.md — two-mode tables (off|acme), remove the
  internal sections/CA-download, document the bare-label host convention; note a
  domain-less node stays on `off`.
This commit is contained in:
2026-06-30 19:55:17 -07:00
parent fa3a646064
commit a022a136fe
9 changed files with 113 additions and 257 deletions

View File

@@ -1,9 +1,8 @@
import { useMemo, useState } from "react"
import { Link } from "react-router-dom"
import { Globe, RefreshCw, FileText, ShieldCheck } from "lucide-react"
import { Globe, RefreshCw, FileText } from "lucide-react"
import type { GatewayInfo, HealthStatus } from "@/types"
import { useGatewayReload, useCaddyfile } from "@/services/api/hooks"
import { apiClient } from "@/services/api/client"
import { HealthBadge } from "./HealthBadge"
interface GatewayPanelProps {
@@ -34,20 +33,6 @@ export function GatewayPanel({ gateway, statuses }: GatewayPanelProps) {
</span>
</div>
<div className="flex items-center gap-1">
{gateway.tls === "internal" && (
<a
href={apiClient.streamUrl("/gateway/ca.crt")}
download="castle-root.crt"
className="flex items-center gap-1 text-xs px-2.5 py-1 rounded bg-[var(--border)] hover:bg-[var(--border)]/80 text-[var(--muted)] hover:text-[var(--foreground)] transition-colors"
title={
"Download the gateway's root CA — install on other devices to trust *.lan HTTPS" +
(gateway.ca_fingerprint ? `\nSHA-256: ${gateway.ca_fingerprint}` : "")
}
>
<ShieldCheck size={12} />
CA cert
</a>
)}
<button
onClick={() => reload()}
disabled={reloading}