The private network

An estate's machines (build Macs, people's laptops and phones, and a proxy inside the cluster) join one private network. They reach each other by name, wherever they are, without opening a port anywhere. The network is the open-source Headscale control server plus the stock Tailscale client; traffic is WireGuard, end to end encrypted, and never passes through the control server.

Status. Built and running: the Headscale chart and image, its node pool, the governed ApplyChart with its DNS step, and the Mac status written from a verified heartbeat (not yet exercised on a joined Mac). In progress: one-time join keys minted by the chart, the tailnet bridge, the portal's sign-in gate for non-local hosts, sign-in with Apple as the default in the Apple apps, Apple Intelligence as a Mac's default model provider, the OpenTelemetry path and the job queue on a Mac. Each section says which.

1. The shape

flowchart TB subgraph Cluster["Cluster (AKS)"] Ingress["Shared HTTPS ingress<br/>cert-manager TLS"] subgraph Pool["Dedicated node pool: network"] HS["Headscale<br/>coordination server<br/>sqlite + Noise key on a kept volume"] Bridge["Tailnet bridge<br/>reverse proxy + userspace client<br/>(in progress)"] end Control["Control instance<br/>(Hosting: records, approvals,<br/>heartbeat inbox, fleet watch)"] end Internet((Internet)) subgraph Devices["Network members"] Mac1["Mac host A<br/>local portal on loopback"] Mac2["Mac host B"] Laptop["Laptop"] Phone["Phone"] end DERP["DERP relays<br/>(public map today)"] Internet -->|"https://#lt;control-server-host#gt;"| Ingress --> HS Internet -->|"https://#lt;mac#gt;.#lt;public-domain#gt;"| Ingress --> Bridge HS -. "keys, policy, MagicDNS" .- Mac1 & Mac2 & Laptop & Phone & Bridge Mac1 <-->|"WireGuard, peer to peer"| Laptop Bridge <-->|WireGuard| Mac1 Mac2 <-.->|"relayed when no direct path"| DERP <-.-> Phone

2. Joining

People sign in and servers present a key. Nobody types a credential on a server.

sequenceDiagram autonumber participant D as Device (Tailscale client) participant H as Headscale participant I as Identity provider (OIDC) participant P as Person (browser) rect rgba(127,127,127,0.08) note over D,P: A person's laptop or phone: OIDC D->>H: register (machine key, no auth) H-->>D: authURL /register/#lt;request#gt; D->>P: open authURL P->>H: GET /register/#lt;request#gt; H-->>P: redirect to the provider (PKCE) P->>I: sign in (the provider's MFA, SSO, passkey) I-->>H: /oidc/callback with code H->>H: allowed domain/group? then register the machine to that user H-->>D: machine authorized, address + MagicDNS name end rect rgba(127,127,127,0.08) note over D,H: A server (Mac host, bridge): one-time pre-auth key (in progress) H->>H: chart-declared key, minted into its own Secret (tagged, short expiry) D->>H: register with --auth-key H-->>D: machine authorized, tagged end

3. Installing and changing the server

Nobody runs helm install by hand. An estate vendors the chart byte-identical into its deployments repository, writes its overlay (host, ingress class and issuer, OIDC issuer, client and allow-list, Key Vault names, node pool) and applies it with the governed ApplyChart instance action from a Hosting/ChartRelease record. The record's optional host and dnsZone make the approved plan also write the public DNS record at the shared ingress address, so the name exists before the certificate is requested. See /Hosting/PrivateNetwork for the Hosting half.

The OIDC client secret is a Key Vault object mounted as a file through the CSI driver and never synced into a Kubernetes Secret. A change to the config, or to WHERE the secret comes from (the SecretProviderClass and the secret reference), rolls the pod through checksum annotations. A new VALUE in the same Key Vault object does not: the checksum covers the source, not the value, and Headscale reads the credential at startup. After rotating the value, roll the pod deliberately through the governed ApplyChart, with a changed podAnnotations entry (for example a rotation date) in the overlay, so the restart is recorded and approved like any other change.

4. A Mac's local portal at a public name (in progress)

A Mac's local portal listens on the Mac's loopback only. To answer at https://<mac>.<public-domain> it is reached through the network, and the Mac opens no port:

sequenceDiagram autonumber participant B as Browser or app participant G as Cluster ingress (TLS) participant R as Tailnet bridge pod participant M as Mac host (network member) participant L as Mac's local front door (127.0.0.1) participant Pt as Local portal B->>G: https://#lt;mac#gt;.#lt;public-domain#gt;/... G->>R: HTTP, Host = public name R->>R: block /dev/* (open sign-in) as defense in depth R->>M: over WireGuard to #lt;mac#gt;.#lt;base-domain#gt; M->>L: tailnet entry point (ACL: only tag:bridge), marks the request forwarded L->>Pt: Host = public name, provenance = tailnet Pt->>Pt: sign-in gate (section 5) Pt-->>B: page, or sign-in with a configured provider

The bridge is a small reverse proxy plus a userspace Tailscale client. It runs on the network node pool with Headscale and joins with its own pre-auth key. Its public host is a ChartRelease with host and dnsZone, so the same approved ApplyChart writes its DNS record.

5. The sign-in gate

The default configuration accepts any account, but only on localhost. Any other host must have a real sign-in provider configured, or the open sign-in is refused (in progress in the portal; the bridge blocks it as well).

flowchart TD Req["Sign-in request"] --> Fwd{"Arrived through the tailnet entry point?"} Fwd -- yes --> Cfg Fwd -- no --> Local{"Host is localhost or *.localhost,<br/>and, where reached directly, the peer is loopback?"} Local -- yes --> Open["Open local sign-in allowed<br/>(development login)"] Local -- no --> Cfg{"At least one provider configured<br/>for this host?"} Cfg -- no --> Refuse["Refused: configure a provider"] Cfg -- yes --> Pick["Person chooses among the configured providers<br/>Microsoft · Google · Apple · LinkedIn"] Pick --> First{"A human administrator exists?"} First -- no --> Admin["This person becomes global admin<br/>(bootstrap)"] First -- yes --> Own{"Own directory gates sign-in?<br/>(Microsoft only, one tenant,<br/>assignment required per Graph)"} Own -- "yes: assigned users admitted" --> In Own -- no --> Inv{"Invitation-only?"} Inv -- yes --> Invited{"Invited?"} Invited -- yes --> In["Signed in"] Invited -- no --> Closed["Closed: invitation required"] Inv -- no --> In Admin --> In

6. Telemetry over the network

flowchart LR subgraph Mac["Mac host"] HB["Heartbeat sender<br/>every 30 s, HMAC with the Mac's own secret"] Col["OpenTelemetry Collector<br/>hostmetrics + kubeletstats<br/>(in progress)"] end subgraph Ctl["Control instance"] Inbox["Heartbeat inbox<br/>verify, then publish"] Status["Ops/Status/#lt;mac#gt;<br/>DeploymentStatus + Host section<br/>staleAfter = 3 × interval"] FW["Fleet watch<br/>does not sample mac-host records,<br/>reads the pushed status"] OC["Collector → metrics store<br/>(in progress)"] end HB -->|"over the private network"| Inbox --> Status --> FW Col -->|"OTLP/HTTP over the private network,<br/>per-record credential"| OC

Built: a verified heartbeat from a mac-host record, signed with that record's own secret, writes the Mac's status with a host section (CPU, memory, disk, thermal state, VM usage, scheduled runs), and fleet watch reads that status instead of sampling the cluster. A Mac silent past staleAfter reads Unobserved, never green. In progress: the OpenTelemetry collector for metric history, its per-record credential (the receiving collector overwrites the claimed identity with the record the credential belongs to), and the job queue and leasing monitor on a Mac.

Reconnecting…
The connection to the server was interrupted. Trying to restore it…
Trying again…
The connection could not be restored. Reloading the page…
The server was updated. Reloading the page to pick up the latest version.