head-vps/README.md

3.3 KiB

head-vps

The HEAD VPS (185.143.103.191, Ubuntu 24.04, 4 vCPU, 3.8 GB) as code. Everything it runs is in this repository; a git push to the bare repo on the VPS deploys it. No CI, no external forge.

Piece Role
doco-cd Watches the bare repos in /srv/git, runs docker compose up (with build:) on every push, updates itself
caddy-docker-proxy Reverse proxy with automatic HTTPS, configured by caddy.* labels on containers
RustFS (rustfs/) The S3 of every app: API at s3.fuckingtech.ch, public media at assets.fuckingtech.ch
sharp (sharp/) Image transformer at sharp.fuckingtech.ch (from nestarz/remove-background, without background removal)
Forgejo The forge at git.fuckingtech.ch: account and repos declared in site.env + forgejo/, hydrated on every deploy

Layers

  1. bootstrap.sh: only to rebuild the host from scratch (ssh fuckingtech.ch 'sudo bash -s -- <app repos>' < bootstrap.sh, push, run again) or to create a new app's bare repo; doco-cd manages everything else, itself included.
  2. .doco-cd.yml + */compose.yaml: the base stacks (doco-cd, caddy, gitore). doco-cd applies them on every push to main.
  3. Apps: each app repository carries its own .doco-cd.yml and compose file, and is listed in doco-cd/compose.yaml's POLL_CONFIG.

Use

git remote add forge ssh://git@git.fuckingtech.ch:2222/headpoolnumerique/head-vps.git
git push forge main                     # deploys; follow with: ssh fuckingtech.ch 'docker logs -f doco-cd-doco-cd-2'

Repositories belong to the organisation headpoolnumerique (public to read); Forgejo stores them in /srv/git/headpoolnumerique/<name>.git, where doco-cd reads them.

A site is two labels on a container that joins the external caddy network:

labels:
  caddy: storytellurx.fuckingtech.ch
  caddy.reverse_proxy: "{{upstreams 8000}}"

Certificates

Caddy keeps its certificates and ACME account in the caddy_data volume: restarts and redeploys reuse them and never count against Let's Encrypt, and renewal is automatic. Let's Encrypt allows 5 failed validations per name per hour, so a new name goes in this order: DNS record first, Caddy label after. A label published while the name points elsewhere fails on every retry and locks the name out.

DNS

dns/ (OpenTofu, official Infomaniak/infomaniak provider) declares only the records that point at the VPS: A and AAAA for storytellurx, v1.storytellurx, git in fuckingtech.ch. Each infomaniak_record is a single record, so nothing else in a zone is read or changed.

It is a workflow run by the VPS: the dns stack (in .doco-cd.yml) builds dns/Dockerfile (OpenTofu + fnox + the provider) and runs apply.sh once whenever dns/ changes. The token (scopes dns:read dns:write) and the state passphrase are in dns/fnox.toml (recipients: Elias, the VPS); the state lives in the VPS's dns_state volume, encrypted (OpenTofu state encryption, AES-GCM).

ssh fuckingtech.ch 'docker logs dns-apply-1'          # the last run's plan and apply

Mirror

github.com/bureaudouble/head-vps (private) is a copy, pushed alongside: git push forge main && git push github main. Deployments only ever come from Forgejo's repositories on the VPS.