Skip to content

Preflight validation + destroy teardown (open-source lifecycle) - #6

Merged
oz6un merged 1 commit into
mainfrom
feat/lifecycle-preflight-destroy
Jul 14, 2026
Merged

Preflight validation + destroy teardown (open-source lifecycle)#6
oz6un merged 1 commit into
mainfrom
feat/lifecycle-preflight-destroy

Conversation

@oz6un

@oz6un oz6un commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Why

Open-sourcing means strangers will clone this. The two things they hit that I never did (I had all the context): 'will this even work for me?' and 'how do I cleanly undo it?'. Today I personally hit the first — a stopped Tailscale client surfaced only as a cryptic Could not resolve hostname, 15 minutes into a provision that had already created a billable server.

What

Two lifecycle bookends, small shell, no new deps:

  • preflight.sh (make preflight, auto-run by provision) — validates CLIs, Hetzner token (401 vs unreachable), authkey format, tailscale up + MagicDNS, free name. Fails in seconds, before spending money. Replaces provision's inline existing-server check.
  • destroy.sh (make destroy, FORCE=1/DRY_RUN=1) — deletes the server (billing stops), clears local host key, prints the one manual tailnet step.

Deliberately NOT added (kept the repo's single purpose): multi-cloud, CONTRIBUTING scaffolding, optional-Claude, monitoring stacks.

Verified

preflight run live: 9 ✓ + correct ✗ on the taken name. destroy DRY_RUN: found the server, deleted nothing, box untouched. Adversarial review caught a money-losing false-safe (revoked token → 'nothing to delete') — fixed and reproduced: a bad token in secrets.env now makes destroy abort (HTTP 401, exit 1) and preflight ✗, verified with the real API then restored. make check green (shellcheck+bash -n+residue over all scripts); cloud-init schema + devproxy compile in CI.

🤖 Generated with Claude Code

Complete the create-lifecycle with the two bookends a stranger cloning this
hits first: 'will it work for me' and 'how do I undo it'.

preflight.sh (make preflight; auto-run by provision): validates local CLIs,
Hetzner token (401 vs unreachable), TS authkey format, tailscale up + MagicDNS,
and a free server name — failing in seconds instead of a 15-min blind timeout
after a billable box exists. Replaces provision's inline existing-server check.

destroy.sh (make destroy; FORCE=/DRY_RUN=): deletes the Hetzner server and
clears local SSH host-key state; prints the manual tailnet-node step.

Adversarial-audit fixes before ship: destroy distinguishes API/auth error from
'0 servers' (a REVOKED token — which the example tells you to revoke — no longer
reads as 'nothing to delete' on a billing box); DRY_RUN/FORCE normalized so
DRY_RUN=true can't delete; preflight name-check validates HTTP status (no
fail-open free-name); refuses ambiguous duplicate names. CI now runs 'make
check' as the single source of truth for the file list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@oz6un
oz6un merged commit 80803d3 into main Jul 14, 2026
1 check passed
@oz6un
oz6un deleted the feat/lifecycle-preflight-destroy branch July 14, 2026 03:08
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