Skip to content

feat: client-only mode (join external headscale) - #32

Merged
kervel merged 10 commits into
mainfrom
feature/client-only-external-server
Jun 10, 2026
Merged

feat: client-only mode (join external headscale)#32
kervel merged 10 commits into
mainfrom
feature/client-only-external-server

Conversation

@kervel

@kervel kervel commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds server.enabled (default true). When false, the chart deploys only the Tailscale client and joins an external headscale — no local server. This covers the use case of joining another cluster's headscale (the Tailscale Kubernetes operator is incompatible with headscale).
  • Client gets --login-server from client.loginServer, its preauth key from client.authKey (chart creates the Secret) or client.authKeySecret (existing Secret), and optional TLS trust via client.caSecretName (empty → system CA bundle, covers Let's Encrypt).
  • All server-side resources (server Deployment/Service/ConfigMaps/Ingress/PVC, UI, key-gen Job/CronJob + its exec RBAC, forbidden-node-name renamer, helm test) are gated behind server.enabled.
  • Render-time validation (fail) enforces the two modes are mutually exclusive: external-mode values are forbidden when server.enabled=true, and required/consistent when false.
  • In external mode the policy ConfigMap is not generated — ACLs and subnet-route approval live on the remote headscale, managed by its admin.

Backward compatibility

Existing installs omit server: → defaults to enabled: true → no change. values.schema.json updated for the new keys.

Test plan

  • helm lint + default and external helm template (new CI step in lint.yaml).
  • New hack/kind-smoke.sh --with-external-client: stands up a server release, mints a real preauth key, then a separate client-only release that joins it over cross-namespace Service DNS — asserts no server resources in the client namespace, client DaemonSet ready, client state secret created, and the server registers the node.
  • Verified on kind: external mode join succeeds end-to-end; in-cluster client regression smoke (--with-client) still passes (init container, policy, idempotency).

Design + plan: docs/superpowers/specs/2026-06-10-client-only-external-server-design.md, docs/superpowers/plans/2026-06-10-client-only-external-server.md.

🤖 Generated with Claude Code

kervel and others added 10 commits June 10, 2026 09:06
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The prose belongs in README.md.gotmpl; helm-docs renders README.md from it,
so the earlier directly-edited section was clobbered on regeneration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Long defaults (JSON objects, paths, URLs) have no spaces and were wrapped in a
backtick code span, so they could not wrap and forced the Default column wide,
starving the Description column on GitHub. Render defaults in <code> with <wbr>
break opportunities (zero-width) so the cell wraps. Done via a chart.valuesSection
override in README.md.gotmpl.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One reusable, long-lived key for the whole cluster (not per node). Explain the
node-key-expiry vs key-expiry distinction and why tagging the key (with a
matching tagOwners ACL entry) keeps nodes enrolled indefinitely.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kervel
kervel merged commit a16507c into main Jun 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant