Files
homey/modules/backup.nix
T
Aner Zakobar 2f0d0b5e4c Port to NixOS: replace Helm chart with flake-based NixOS config
Replaces the Helm/k3s setup with a declarative NixOS configuration targeting
a Raspberry Pi 4. Services run as podman containers under systemd, with data
on an external HD at /mnt/data. Key components:

- flake.nix: multi-host flake with pi-main (aarch64) and a placeholder for a
  second machine
- modules/common.nix: shared system config (nix, podman, sops, SSH)
- modules/storage.nix: external HD mount with per-service subdirs
- modules/caddy.nix: Caddy with cloudflare DNS-01 ACME + authelia forward_auth
- modules/cloudflared.nix: Cloudflare tunnel for remote access
- modules/backup.nix: restic daily backups with NC maintenance mode pre-hook
- modules/services/{openldap,authelia,gitea,nextcloud,phpldapadmin}.nix: core services
- modules/services/{jellyfin,transmission}.nix: media services (disabled by default)
- secrets/: sops-nix scaffold with .sops.yaml age key config
- hosts/pi-main/: hardware config + service selection for the Pi
- PORTING.md: step-by-step migration guide (SD card → data restore → verify)
2026-04-15 17:18:12 +03:00

151 lines
4.8 KiB
Nix

{ config, lib, pkgs, homeyConfig, ... }:
# Restic backup module.
#
# Backs up all service data directories from the external HD.
# Schedule: daily at 03:00, keep 7 daily / 4 weekly / 6 monthly snapshots.
#
# Before a backup, Nextcloud is put into maintenance mode and postgres is
# pg_dump'd to a file. This ensures consistent DB backups.
#
# Secrets consumed from sops:
# restic/password
#
# The backup repository URL is set per-host in default.nix:
# homey.backup.repository = "sftp:user@nas:/backups/homey";
#
# Restore:
# restic -r <repo> restore latest --target /mnt/data
# (or restore a single path: --include /mnt/data/openldap)
let
cfg = config.homey.backup;
dataDir = config.homey.storage.mountPoint;
in
{
options.homey.backup = {
enable = lib.mkEnableOption "Restic backup jobs";
repository = lib.mkOption {
type = lib.types.str;
example = "sftp:user@nas.local:/backups/homey";
description = ''
Restic repository URL. Examples:
sftp:user@host:/path
b2:bucket-name:prefix
rclone:remote:path
/local/path (for testing)
'';
};
schedule = lib.mkOption {
type = lib.types.str;
default = "03:00";
description = "systemd OnCalendar expression for the daily backup.";
};
pruneRetention = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = {
daily = "7";
weekly = "4";
monthly = "6";
};
};
};
config = lib.mkIf cfg.enable {
# -----------------------------------------------------------------------
# Secrets
# -----------------------------------------------------------------------
sops.secrets."restic/password" = { owner = "root"; };
# -----------------------------------------------------------------------
# Pre-backup hook: pg_dump + nextcloud maintenance mode
# -----------------------------------------------------------------------
systemd.services."homey-backup-pre" = {
description = "Pre-backup hooks (pg_dump, NC maintenance mode)";
serviceConfig = {
Type = "oneshot";
ExecStart = pkgs.writeShellScript "backup-pre" ''
set -euo pipefail
# Put Nextcloud into maintenance mode (if running)
if systemctl is-active --quiet podman-nextcloud.service; then
podman exec nextcloud php occ maintenance:mode --on || true
fi
# Dump postgres (if running)
if systemctl is-active --quiet podman-nextcloud-postgres.service; then
install -d -m 700 ${dataDir}/nextcloud/db-dump
podman exec nextcloud-postgres \
pg_dump -U postgres nextcloud_db \
> ${dataDir}/nextcloud/db-dump/nextcloud.sql
fi
'';
};
};
systemd.services."homey-backup-post" = {
description = "Post-backup hooks (take NC out of maintenance mode)";
serviceConfig = {
Type = "oneshot";
ExecStart = pkgs.writeShellScript "backup-post" ''
set -euo pipefail
if systemctl is-active --quiet podman-nextcloud.service; then
podman exec nextcloud php occ maintenance:mode --off || true
fi
'';
};
};
# -----------------------------------------------------------------------
# Restic backup service
# -----------------------------------------------------------------------
services.restic.backups.homey = {
repository = cfg.repository;
passwordFile = config.sops.secrets."restic/password".path;
cacheDir = "${dataDir}/restic-cache";
paths = [
"${dataDir}/openldap"
"${dataDir}/authelia"
"${dataDir}/gitea"
"${dataDir}/nextcloud"
# media and transmission config included when those services are enabled:
"${dataDir}/jellyfin"
"${dataDir}/transmission"
# Deliberately excluded: media/* (large, can be re-downloaded)
];
# Exclude Nextcloud's raw DB directory in favour of the pg_dump file
exclude = [
"${dataDir}/nextcloud/db"
"${dataDir}/restic-cache"
];
timerConfig = {
OnCalendar = cfg.schedule;
Persistent = true; # run on next boot if missed
};
pruneOpts = [
"--keep-daily ${cfg.pruneRetention.daily}"
"--keep-weekly ${cfg.pruneRetention.weekly}"
"--keep-monthly ${cfg.pruneRetention.monthly}"
];
};
# Wire the pre/post hooks around the restic job
systemd.services."restic-backups-homey" = {
requires = [ "homey-backup-pre.service" ];
after = [ "homey-backup-pre.service" ];
};
systemd.services."homey-backup-post" = {
after = [ "restic-backups-homey.service" ];
wantedBy = [ "restic-backups-homey.service" ];
};
};
}