variant: flatcar version: 1.0.0 systemd: units: # Gitea Actions runner. Registers once on first start and keeps its # identity (the .runner file) in /var/lib/oswald_runner on the VM's own # root disk -- not ephemeral, so it does not self-delete after each job # (that caused stale offline entries whenever the container was # interrupted before finishing a job; see flatcar-todo.md). Reusing the # same registration also means the token never needs rotating. # # /var/lib/oswald_runner survives container restarts and VM reboots (it's # on disk, not in the container), but not a VM rebuild (taint): that # recreates the disk from the image, so the runner re-registers fresh # next boot. Old entries from before this change, or from any future # rebuild, still need manual cleanup in Gitea's runner admin page. # # GITEA_RUNNER_REGISTRATION_TOKEN is templated in from Infisical # (GITEA_RUNNER_TOKEN in secrets.tmpl) by main.tf, not stored here. It # still ends up in plaintext in the rendered Ignition snippet on the # node's storage -- see the caveat in flatcar-todo.md. - name: oswald_runner.service enabled: true contents: | [Unit] Description=Gitea Actions runner After=docker.service Requires=docker.service [Service] TimeoutStartSec=0 ExecStartPre=-/usr/bin/docker rm --force oswald_runner ExecStart=/usr/bin/docker run --name oswald_runner \ -e GITEA_INSTANCE_URL=https://git.atlashorizon.net \ -e GITEA_RUNNER_REGISTRATION_TOKEN=${gitea_runner_token} \ -e GITEA_RUNNER_NAME=oswald_runner \ -v /var/lib/oswald_runner:/data \ -v /var/run/docker.sock:/var/run/docker.sock \ docker.io/gitea/runner:3 ExecStop=/usr/bin/docker stop oswald_runner Restart=always RestartSec=5s [Install] WantedBy=multi-user.target # n8n workflow automation, served publicly at n8n.atlashorizon.net. # # Prerequisites, all of which must exist before the VM is rebuilt: # - an "n8n" Postgres database and role on argo.lan.atlashorizon.net # - N8N_DB_PASSWORD and N8N_ENCRYPTION_KEY in Infisical (see # secrets.tmpl); generate the key with `openssl rand -hex 32` # - n8n.atlashorizon.net terminating TLS and proxying to this host's # :5678 (not managed here) # # Workflows, credentials and execution history all live in Postgres, so # a taint loses nothing. The encryption key is what makes that true: # without a fixed one n8n generates its own under /home/node/.n8n, and # the credentials in Postgres become undecryptable the moment the VM # disk goes. Never rotate it without re-entering every credential. - name: n8n.service enabled: true contents: | [Unit] Description=n8n workflow automation After=docker.service Requires=docker.service [Service] TimeoutStartSec=0 ExecStartPre=/usr/bin/docker pull docker.n8n.io/n8nio/n8n:2.40.5 ExecStartPre=-/usr/bin/docker rm --force n8n # /var/lib/n8n holds only what is left in /home/node/.n8n once the # database and key are external: installed community nodes and # n8n's own config file. Losing it to a taint means reinstalling # community nodes, nothing more. # # N8N_PROXY_HOPS=1: requests arrive through the reverse proxy, so # n8n takes the client IP from X-Forwarded-For (rate limiting and # audit logs would otherwise see only the proxy). ExecStart=/usr/bin/docker run --name n8n \ -e DB_TYPE=postgresdb \ -e DB_POSTGRESDB_HOST=argo.lan.atlashorizon.net \ -e DB_POSTGRESDB_PORT=5432 \ -e DB_POSTGRESDB_DATABASE=n8n \ -e DB_POSTGRESDB_USER=n8n \ -e DB_POSTGRESDB_PASSWORD=${n8n_db_password} \ -e N8N_ENCRYPTION_KEY=${n8n_encryption_key} \ -e N8N_HOST=n8n.atlashorizon.net \ -e N8N_PROTOCOL=https \ -e WEBHOOK_URL=https://n8n.atlashorizon.net/ \ -e N8N_PROXY_HOPS=1 \ -v /var/lib/n8n:/home/node/.n8n \ -p 5678:5678 \ docker.n8n.io/n8nio/n8n:2.40.5 ExecStop=/usr/bin/docker stop n8n Restart=always RestartSec=5s [Install] WantedBy=multi-user.target - name: dozzle-agent.service enabled: true contents: | [Unit] Description=Dozzle Agent After=docker.service Requires=docker.service [Service] TimeoutStartSec=0 ExecStartPre=-/usr/bin/docker rm --force dozzle-agent ExecStart=/usr/bin/docker run --name dozzle-agent \ -v /var/run/docker.sock:/var/run/docker.sock \ -p 7007:7007 \ amir20/dozzle:v11.1.1 agent ExecStop=/usr/bin/docker stop dozzle-agent Restart=always RestartSec=5s [Install] WantedBy=multi-user.target storage: directories: - path: /var/lib/oswald_runner mode: 0755 # The n8n image runs as the "node" user (uid 1000), which needs to own # its data directory. - path: /var/lib/n8n mode: 0700 user: id: 1000 group: id: 1000