Self-hosted spindle #
Tangled runs no shared spindle; a repo can only run workflows on a spindle registered to an account. The mirror workflows run on one hosted on the same machine as the rest of this project, as two systemd user units: the spindle itself and a Cloudflare tunnel that gives it a public HTTPS name.
Machine-specific values (hostname, owner DID, tunnel id) are placeholders here;
the live copies stay in ~/.config/spindle/spindle.conf and
~/.cloudflared/config.yml on the host.
Restoring on a fresh host #
-
git ≥ 2.49. Ubuntu 24.04's stock 2.43 is refused; the
git-corePPA supplies a new enough one. -
Build spindle from
tangled.org/corewith a current Go toolchain and put the binary at~/.local/bin/spindle. The source clone doubles as the local reference for how the appview and spindle behave. -
Rootless Podman, with its user socket enabled (
systemctl --user enable --now podman.socket). -
Cloudflare tunnel. Install
cloudflaredat~/.local/bin/cloudflared, thencloudflared tunnel login,cloudflared tunnel create spindle, andcloudflared tunnel route dns spindle <spindle hostname>. Write~/.cloudflared/config.ymlfromcloudflared-config.yml.example. -
Config. Copy
spindle.conf.exampleto~/.config/spindle/spindle.confand fill in the hostname and owner DID. -
Units. Copy both
.servicefiles to~/.config/systemd/user/, then:systemctl --user daemon-reload systemctl --user enable --now spindle.service cloudflared-spindle.service loginctl enable-lingerLingering keeps user units running with nobody logged in.
-
Register the spindle on Tangled under the owner account, and point each repo that should use it at it in that repo's settings (web UI).
-
Repo secrets (Tangled repo Settings → Secrets, web UI) live in the spindle's database, so a rebuild loses them; re-enter each one.
Checking it #
systemctl --user is-active spindle.service cloudflared-spindle.servicecurl -s https://<spindle hostname>/xrpc/sh.tangled.ownerreturns the owner DID when the tunnel is up.- Recent runs:
curl -s 'https://<spindle hostname>/xrpc/sh.tangled.ci.queryPipelines?repo=<repo did>&limit=5'. The repo DID appears in anypipeline triggeredline ofjournalctl --user -u spindle.service. - Per-run detail is one JSON-lines file per workflow run under
~/.local/share/spindle/logs/;"kind":"control"lines mark step boundaries. The tail is post-run housekeeping, so reading only the last lines makes a passing run look truncated.
Behaviour worth knowing #
- Secrets are withheld from runs whose source repo differs from the repo itself, so a fork's pull request cannot read them. A same-repo pull request does.
- Tailscale Funnel was tried as the public ingress and abandoned: the ingress closed every external connection without presenting a certificate. Requests from the host itself resolve the tailnet name over MagicDNS and go through Serve instead, so they report success while nothing external can connect.