This commit is contained in:
nora 2025-08-03 00:41:37 +02:00
parent f456a5c626
commit 0949cba7be
92 changed files with 19 additions and 58 deletions

62
nix/modules/backup/backup.sh Executable file
View file

@ -0,0 +1,62 @@
#!/usr/bin/env bash
set -euo pipefail
time="$(date --iso-8601=s --utc)"
echo "Starting backup procedure with time=$time"
dir=$(mktemp -d)
echo "Setting workdir to $dir"
cd "$dir"
export HOME="$dir"
# Delete the temporary directory afterwards.
# Yes, this variable should expand now.
# shellcheck disable=SC2064
trap "rm -rf $dir" EXIT
echo "Logging into garage"
export MC_CONFIG_DIR="$dir"
mc alias set garage "$S3_ENDPOINT" "$S3_ACCESS_KEY" "$S3_SECRET_KEY" --api S3v4
mc ls garage/backups
files=$(jq -c '.files[]' "$CONFIG_FILE")
pg_dumps=$(jq -c '.pg_dumps[]' "$CONFIG_FILE")
echo "$files"
echo "$pg_dumps"
IFS=$'\n'
for file_config in $files; do
filepath=$(echo "$file_config" | jq -r ".file")
app=$(echo "$file_config" | jq -r ".app")
echo "Backing up app $app FILE $filepath..."
tmppath="$dir/file"
xz < "$filepath" > "$tmppath"
echo "Uplading file"
mc put "$tmppath" "garage/$S3_BUCKET/$app/$time/$(basename "$filepath").xz"
echo "Uploaded file"
done
for pg_config in $pg_dumps; do
app=$(echo "$pg_config" | jq -r ".app")
containerName=$(echo "$pg_config" | jq -r ".containerName")
dbName=$(echo "$pg_config" | jq -r ".dbName")
userName=$(echo "$pg_config" | jq -r ".userName")
echo "Backing up app $app POSTGRES $containerName/$dbName..."
tmppath="$dir/file"
podman exec "$containerName" pg_dump --format=custom --file /tmp/db.bak \
--host "127.0.0.1" --dbname "$dbName" --username "$userName"
podman cp "$containerName:/tmp/db.bak" "$tmppath"
xz -f "$tmppath" > "$tmppath.xz"
echo "Uplading file"
mc put "$tmppath.xz" "garage/$S3_BUCKET/$app/$time/$dbName.bak.xz"
echo "Uploaded file"
podman exec "$containerName" rm "/tmp/db.bak"
done

View file

@ -0,0 +1,83 @@
{ config, lib, pkgs, ... }: with lib;
let
jobOptions = { ... }: {
options = {
app = mkOption {
type = types.str;
description = "The app name, used as the directory in the bucket";
};
environmentFile = mkOption {
type = types.nullOr types.path;
default = null;
};
file = mkOption {
type = types.nullOr types.str;
default = null;
};
pgDump = mkOption {
type = types.nullOr (types.submodule ({ ... }: {
options = {
containerName = mkOption {
type = types.str;
};
dbName = mkOption {
type = types.str;
};
userName = mkOption {
type = types.str;
};
};
}));
default = null;
};
#mongo_dump = { };
};
};
in
{
options.services.custom-backup = {
jobs = mkOption {
default = [ ];
type = types.listOf (types.submodule jobOptions);
description = "Backup jobs to execute";
};
};
config =
let
cfg = config.services.custom-backup;
backupConfig = {
files = builtins.map (job: { app = job.app; file = job.file; })
(builtins.filter (job: job.file != null) cfg.jobs);
pg_dumps = builtins.map (job: { app = job.app; } // job.pgDump)
(builtins.filter (job: job.pgDump != null) cfg.jobs);
};
backupScript = pkgs.writeShellApplication {
name = "backup";
runtimeInputs = with pkgs; [ podman jq minio-client getent xz ];
text = builtins.readFile ./backup.sh;
};
in
{
age.secrets.backup_s3_secret.file = ../../secrets/backup_s3_secret.age;
systemd.services.custom-backup = {
startAt = "daily";
serviceConfig = {
# TODO: can we use a dynamic user?
#DynamicUser = true;
ExecStart = "${backupScript}/bin/backup";
Environment = [
"CONFIG_FILE=${pkgs.writeText "backup-config.json" (builtins.toJSON backupConfig)}"
"S3_BUCKET=backups"
"S3_ENDPOINT=http://localhost:3900"
];
EnvironmentFile = (builtins.filter (file: file != null)
(builtins.map (job: job.environmentFile) cfg.jobs)) ++ [
config.age.secrets.backup_s3_secret.path
];
};
};
};
}