···52523. After bootstrap, fetch a fresh kubeconfig from the node — the one in tofu state will have the wrong CA.
53534. JuiceFS CSI on SELinux (MicroOS) requires `sidecarPrivileged: true` in `juicefs-csi-values.yaml` under `node:`. Without it, the CSI socket has a label mismatch and sidecars can't connect.
54545555-## Spindle CI Runner
5656-5757-Self-hosted [Spindle](https://tangled.org) runner at `spindle.sans-self.org` for pipeline execution on our knot. Runs as a systemd user service with Podman rootless — no privileged containers, no DinD.
5858-5959-### Architecture
6060-6161-Spindle runs outside k8s (needs direct access to the Podman socket) but is exposed via Traefik through a selectorless k8s Service + Endpoints. A CronJob healthcheck (`spindle-healthcheck`) runs every 5 minutes: it heartbeats to a `spindle-leader` ConfigMap and keeps the Endpoints pointed at the active node. If the heartbeat goes stale (>10 min), the healthcheck auto-starts Spindle on whichever node it lands on.
6262-6363-Node provisioning is automatic: `postinstall_exec` in kube.tf creates the spindle user, configures rootless Podman, and pulls the binary from Zot (`zot.sans-self.org/infra/spindle:latest`, anonymous pull). This runs on every new or replaced node.
6464-6565-### Build
6666-6767-```sh
6868-git clone git@tangled.org:tangled.org/core.git ~/Projects/tangled
6969-make build-spindle SPINDLE_CORE=~/Projects/tangled
7070-```
7171-7272-Requires Docker (cross-compiles with `aarch64-linux-gnu-gcc` for CGo/sqlite3).
7373-7474-### Initial setup
7575-7676-```sh
7777-make build-spindle # compile ARM64 binary via Docker
7878-make push-spindle # build OCI image, push to Zot (needs docker login)
7979-make update-spindle # deploy binary to all nodes via k8s Job
8080-make start-spindle # start service on one node (run once)
8181-kubectl apply -f spindle/ingress.yaml
8282-kubectl apply -f spindle/healthcheck-cronjob.yaml
8383-```
8484-8585-Then add the runner in the tangled.org UI with hostname `spindle.sans-self.org`.
8686-8787-### Binary updates
8888-8989-```sh
9090-make build-spindle # recompile from latest source
9191-make push-spindle # push new image to Zot
9292-make update-spindle # rolls out to all nodes, restarts where active
9393-```
9494-9595-### Node replacement
9696-9797-Automatic. The healthcheck CronJob detects the stale heartbeat and starts Spindle on another node within 5 minutes. The Endpoints object is updated to route traffic to the new node.
9898-9999-### Operations
100100-101101-```sh
102102-make logs-spindle # stream journal from active node
103103-make start-spindle # manual start (if healthcheck hasn't kicked in yet)
104104-make update-spindle # redeploy binary + restart
105105-```
106106-10755## Backups
1085610957Daily S3 backups via CronJobs (02:00 PDS, 02:30 knot). See [RESTORE.md](RESTORE.md) for recovery procedures.
+1-1
k8s/knot/network-policy.yaml
···2424 kubernetes.io/metadata.name: traefik
2525 ports:
2626 - port: 22
2727- # Internal: allow Spindle (future) to reach knot events endpoint
2727+ # Internal: knot pods reaching their own API (post-receive hooks → port 5444)
2828 - from:
2929 - namespaceSelector:
3030 matchLabels: