# Hetzner single-node bootstrap This repo’s canonical infrastructure path is: 1. provision one Hetzner VM with Terraform 2. let cloud-init install k3s (and optionally Tailscale) 3. run `scripts/hetzner/bootstrap.sh` from the operator workstation 4. apply repo-managed platform + project manifests 5. bootstrap Forgejo, the runner, repo secrets/variables, and the first CI-driven deploy ## Source of truth Use these docs first: - `docs/hetzner-k3s-bootstrap.md` — bootstrap + destroy + required env - `docs/hetzner-self-hosted-ci-runbook.md` — Forgejo/runner/CI flow - `docs/k8s-observability.md` — Grafana, Loki, Promtail, Headlamp - `deploy/k8s/README.md` — Kubernetes layout - `deploy/k8s/overlays/hetzner-single-node/README.md` — overlay details ## Current architecture Infrastructure under `infra/terraform/hetzner/` provisions: - one Hetzner VM - one firewall - one private network attachment - cloud-init for unattended k3s install Kubernetes platform services deployed from this repo: - Forgejo - Forgejo runner - private registry - cert-manager - Traefik via k3s bundled ingress controller - Grafana - Loki - Promtail - Headlamp Project services deployed from this repo: - Redpanda - `near-intents-ingest` - `dummy-reactor` - `dummy-executor` - `dummy-consumer` ## Bootstrap model The current bootstrap is workstation-driven after Terraform. cloud-init does **not** clone this repo onto the node. `scripts/hetzner/bootstrap.sh` now: - loads config and secrets from `scripts/hetzner/bootstrap-secrets.env` - resolves `*_PASS` values through `pass` - runs Terraform - configures DNS through Cloudflare or Porkbun when credentials are present - fetches kubeconfig from the node - renders `.state/hetzner/generated-overlay/` - applies platform + project manifests - bootstraps Forgejo admin/user/repo/runner state - seeds the repo into Forgejo - lets Forgejo Actions perform the routine image build + deploy path by default Legacy local-image bootstrap still exists, but the default/steady-state path is Forgejo Actions. ## Required operator inputs Create and source: ```bash cp scripts/hetzner/bootstrap-secrets.env.example scripts/hetzner/bootstrap-secrets.env source scripts/hetzner/bootstrap-secrets.env ``` At minimum you need: - Hetzner credentials - SSH public key path - public domain settings - registry credentials - app secret(s) - Forgejo admin credentials - Grafana admin credentials Recommended: - Tailscale auth key for private admin/control-plane access - DNS provider credentials - `pass`-backed secret refs instead of raw env values ## Current live/public surfaces - Forgejo: `https://git.doran.133011.xyz/` - Registry: `https://registry.doran.133011.xyz/` - Grafana: `https://grafana.doran.133011.xyz/` - Headlamp: `https://headlamp.doran.133011.xyz/` ## Notes - The Forgejo runner no longer reads a pre-seeded `runner_registration_token` from a secret. Bootstrap generates a one-time token in-cluster and persists the runner config on the Forgejo PVC. - Registry auth is created imperatively during bootstrap from `REGISTRY_USERNAME` and `REGISTRY_PASSWORD`; manual overlay applies must provide `registry.htpasswd` themselves. - Headlamp login uses a generated Kubernetes service-account token; bootstrap stores it in `pass` when `HEADLAMP_ADMIN_TOKEN_PASS` is configured. - Ingress is Traefik-based. The old `ingress-nginx` path is obsolete. ## Status This path has been rebuilt successfully and the cluster is operational, but if you want the strongest reproducibility guarantee after any new platform addition, do one more full destroy/rebuild rehearsal.