This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

Shell 53.1%
HCL 28.6%
Just 13.1%
Other 5.2%
4 1 0

Clone this repository

https://git.vm.fail/zzstoatzz.io/tangled-infra https://git.vm.fail/did:plc:frh5llq33lmpjomlte3ruiq5
ssh://git@knot.zzstoatzz.io:2222/zzstoatzz.io/tangled-infra ssh://git@knot.zzstoatzz.io:2222/did:plc:frh5llq33lmpjomlte3ruiq5

For self-hosted knots, clone URLs may differ based on your setup.


README.md

tangled-infra#

self-hosted knot + spindle for tangled.org, on a single hetzner box.

  • knot hostname: knot.zzstoatzz.io (git hosting)
  • spindle hostname: spindle.zzstoatzz.io (CI runner)
  • platform: hetzner cloud, cpx31 (4 vCPU / 8GB / 160GB), ubuntu-24.04
  • provisioning: terraform + cloud-init
  • reverse proxy: caddy (auto letsencrypt)
  • tangled core pinned to v1.13.0-alpha

knot and spindle are colocated on one box because (a) it's cheap and (b) consolidating personal tangled infra in one place makes ops simpler. spindle needs a real docker daemon to spawn step containers, which is why this lives on a VPS instead of fly.io.

layout#

tangled-infra/
├── infra/              # terraform — hcloud_server, firewall, ssh_key
├── deploy/             # cloud-init + systemd + Caddyfile (templated by terraform)
├── scripts/            # ops helpers (e.g. listing repos on a knot)
├── justfile            # all the verbs
├── NOTES.md            # ops notes + history
└── .env (gitignored)   # HCLOUD_TOKEN + TF_VAR_owner_did

first-time setup#

  1. get a hetzner cloud API token: console.hetzner.cloud → your project → security → API tokens → generate (read+write).
  2. find your AT proto DID — from https://tangled.org/settings, or via DNS:
    dig +short TXT _atproto.<your-handle> | tr -d '"' | sed 's/^did=//'
    
  3. copy env: cp deploy/env.example .env and fill in HCLOUD_TOKEN + TF_VAR_owner_did.
  4. terraform init: just init.
  5. plan: just plan — verify what's about to happen.
  6. apply: just apply — stands up the box. takes ~3–5 min including cloud-init.
  7. point DNS: just outputs shows the IP. add A records for both hostnames at your DNS provider.
  8. verify on tangled:

daily ops#

just status              # systemctl status for knot/spindle/caddy/docker
just ssh                 # ssh as root
just knot-logs           # journalctl follow knot
just spindle-logs        # journalctl follow spindle
just caddy-logs          # journalctl follow caddy
just bootstrap-log       # tail the initial cloud-init bootstrap log

just restart-knot        # systemctl restart knot
just restart-spindle     # systemctl restart spindle
just restart-caddy       # systemctl reload caddy

just redeploy            # scp deploy/* to box (no terraform re-apply)

migrating from an existing knot#

a knot's identity is its hostname — every sh.tangled.repo record on a PDS references it. that means changing hostname = standing up a new knot record + per-repo cutover. there's no built-in "rename knot" or "move repo" operation today.

rough shape of a migration (do these AFTER the new knot is verified):

  1. inventory the repos on the old knot. if you control the old host, sshing in and lsing the repos dir is the simplest path. scripts/list-knot-repos.sh does this for fly-hosted knots.
  2. for each repo:
    • clone from old: git clone git@<old-host>:<did>/<name> /tmp/repo
    • create the repo on the new knot (via the tangled.org UI — it'll mint a fresh per-repo DID)
    • push: cd /tmp/repo && git remote set-url origin git@<new-host>:<repo-did> && git push --all && git push --tags
  3. retire the old knot only after all repos are confirmed on the new one:
    • delete the old sh.tangled.knot record from the owner PDS
    • tear down the old host

upgrading the knot/spindle version#

bump TF_VAR_tangled_core_tag in .env (or infra/variables.tf default), then:

just redeploy
just ssh
# on the box:
/opt/tangled-infra/bootstrap.sh   # idempotent — re-clones at new tag, rebuilds, restarts

per the migration guide, hit "retry" on the tangled.org settings page for both services after a major bump.

why not nix?#

the docs-prescribed path is NixOS via @tangled.org/infra. that's idiomatic but a different workflow from the rest of my hetzner infra (my-prefect-server is terraform + ubuntu + cloud-init). matching the existing pattern wins over idiom here. if nix proves useful later, the migration is contained — same box, swap the bootstrap.