NixOs server configuration
Find a file
Felix Meuer 7ea7305666 feat: add Ghost blog module for felix.meuer.xyz
Runs Ghost via podman/oci-containers (nixpkgs has no native services.ghost
module), reverse-proxied through nginx with ACME. Uses sqlite3 as the
database, with an explicit connection filename and useNullAsDefault - the
image defaults to MySQL on 127.0.0.1:3306 in production mode otherwise.
Provisions /var/lib/ghost via tmpfiles since podman won't auto-create a
missing bind-mount source. Wires the content directory into the existing
encrypted backup flow and the pre-upgrade/post-upgrade healthcheck in
auto-update.nix, matching the other services.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 00:50:55 +02:00
modules feat: add Ghost blog module for felix.meuer.xyz 2026-07-31 00:50:55 +02:00
scripts refactor: use ssh multiplexing in deploy script 2026-07-30 23:33:36 +02:00
secrets fix: repair HedgeDoc realtime editing 2026-07-30 23:16:51 +02:00
.gitignore feat: publish repo with secrets removed and services reorganized 2026-07-30 22:24:24 +02:00
configuration.nix feat: publish repo with secrets removed and services reorganized 2026-07-30 22:24:24 +02:00
disk-config.nix feat: publish repo with secrets removed and services reorganized 2026-07-30 22:24:24 +02:00
flake.lock feat: publish repo with secrets removed and services reorganized 2026-07-30 22:24:24 +02:00
flake.nix feat: add Ghost blog module for felix.meuer.xyz 2026-07-31 00:50:55 +02:00
README.md feat: added README 2026-07-30 22:33:27 +02:00

nix-server

NixOS flake configuration for a single Contabo VPS (meuer.xyz) hosting a handful of self-hosted services behind an nginx reverse proxy, deployed with nixos-rebuild --target-host and secrets managed via agenix.

Structure

.
├── flake.nix           # entrypoint: nixosConfigurations.contabo-server
├── configuration.nix   # host-wide base config (bootloader, SSH, ACME defaults, agenix identity)
├── disk-config.nix     # disko partitioning/LVM layout
├── modules/            # one file per service, imported from flake.nix
├── scripts/            # deploy + operational helper scripts
└── secrets/            # agenix-encrypted secrets (*.age) and secrets.nix key registry

Each service lives in its own modules/*.nix file and is wired into the system via the modules list in flake.nix. Services that need a public hostname declare their own services.nginx.virtualHosts.<domain> block with enableACME = true; forceSSL = true;, so TLS certs are issued automatically per-domain by the ACME module configured in configuration.nix.

Modules

  • nginx.nix — enables the shared nginx instance used as the reverse proxy for every other service, plus the Diffie-Hellman params it uses for TLS.
  • postgres.nix — shared PostgreSQL instance. Services connect over the local Unix socket using peer authentication (the connecting system user must match the Postgres role name), so no DB passwords are needed. Runs services.postgresqlBackup.
  • vaultwarden.nix — Bitwarden-compatible password manager at vault.meuer.xyz. Signups disabled; SMTP + admin token supplied via the vaultwarden-env.age secret.
  • hedgedoc.nix — collaborative Markdown editor at pad.meuer.xyz.
  • jitsi.nix — self-hosted video conferencing at jitsi.meuer.xyz.
  • minecraft.nix — declarative Minecraft server on port 43000.
  • forgejo.nix — self-hosted git forge (the software Codeberg.org runs on) at git.meuer.xyz. Uses Postgres via peer auth, LFS enabled, public registration disabled (admin accounts are created via scripts/forgejo-create-admin.sh), git-over-SSH on port 2222 (its own built-in SSH server, separate from the host's OpenSSH on port 22). Reuses the smtp-password secret declared in auto-update.nix for outgoing mail.
  • backup.nix — nightly-triggered oneshot service that GPG-encrypts a pg_dump of the Vaultwarden database (symmetric, passphrase from backup-passphrase.age) and prunes backups older than 14 days.
  • auto-update.nixsystem.autoUpgrade at 03:00 daily, gated on a successful Vaultwarden backup first. After a successful upgrade+switch, a health-check service verifies core services came back up and emails felix@meuer.xyz (via smtp-password.age) if any failed to restart.

Secrets

Secrets are encrypted with agenix and committed as secrets/*.age. secrets/secrets.nix lists which public keys (the server's own SSH host key, plus a local personal key for editing) each secret is encrypted for:

  • vaultwarden-env.age — Vaultwarden SMTP_PASSWORD / ADMIN_TOKEN env file.
  • smtp-password.age — shared SMTP password, referenced by auto-update.nix and forgejo.nix.
  • backup-passphrase.age — GPG passphrase for encrypted Vaultwarden backups.

Edit a secret with agenix -e <name>.age (run from secrets/, requires your local SSH key to be one of the listed public keys). The server decrypts secrets at activation time using its own SSH host key (/etc/ssh/ssh_host_ed25519_key), configured via age.identityPaths in configuration.nix.

Scripts

  • deploy.sh — rsyncs the repo to /etc/nixos-server-config/ on the server and runs nixos-rebuild switch --target-host against it.
  • backup-vaultwarden.sh — one-off, on-demand GPG-encrypted Vaultwarden backup, downloaded to the local machine.
  • forgejo-create-admin.sh — creates a Forgejo admin user via the Forgejo CLI on the server (registration is disabled, so this is the only way to provision accounts).
  • post-upgrade-alert-email.tmpl — email template used by the post-upgrade-healthcheck service in auto-update.nix.

Deploying

./scripts/deploy.sh

This builds and switches the contabo-server flake output on the remote host over SSH. Requires SSH access to root@meuer.xyz.