forked from clan/clan-core
add postgresql backup hooks
This commit is contained in:
parent
f71295e640
commit
6dec2a9222
@ -44,6 +44,7 @@
|
||||
zt-tcp-relay = import ./zt-tcp-relay nixosTestArgs;
|
||||
borgbackup = import ./borgbackup nixosTestArgs;
|
||||
syncthing = import ./syncthing nixosTestArgs;
|
||||
postgresql = import ./postgresql nixosTestArgs;
|
||||
wayland-proxy-virtwl = import ./wayland-proxy-virtwl nixosTestArgs;
|
||||
};
|
||||
|
||||
|
22
checks/postgresql/default.nix
Normal file
22
checks/postgresql/default.nix
Normal file
@ -0,0 +1,22 @@
|
||||
(import ../lib/container-test.nix) (
|
||||
{
|
||||
name = "postgresql";
|
||||
|
||||
nodes.machine = { self, ... }:
|
||||
{
|
||||
imports = [
|
||||
self.nixosModules.clanCore
|
||||
self.clanModules.postgresql
|
||||
self.clanModules.localbackup
|
||||
];
|
||||
clan.postgresl.databases = [ "test" ];
|
||||
clan.localbackup.targets.hdd.directory = "/mnt/external-disk";
|
||||
};
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.succeed("systemctl status postgresql")
|
||||
machine.wait_for_unit("postgresql")
|
||||
machine.succeed("localbackup-create")
|
||||
machine.succeed("ls -la /var/backups/postgresql")
|
||||
'';
|
||||
})
|
@ -51,7 +51,9 @@ in
|
||||
|
||||
config = lib.mkIf (cfg.destinations != { }) {
|
||||
services.borgbackup.jobs = lib.mapAttrs (_: dest: {
|
||||
paths = lib.flatten (map (state: state.folders) (lib.attrValues config.clanCore.state));
|
||||
paths = lib.unique (
|
||||
lib.flatten (map (state: state.folders) (lib.attrValues config.clanCore.state))
|
||||
);
|
||||
exclude = [ "*.pyc" ];
|
||||
repo = dest.repo;
|
||||
environment.BORG_RSH = dest.rsh;
|
||||
@ -60,6 +62,23 @@ in
|
||||
persistentTimer = true;
|
||||
preHook = ''
|
||||
set -x
|
||||
declare -A preCommandErrors
|
||||
${lib.concatMapStringsSep "\n" (state: ''
|
||||
echo "Running pre-backup command for ${state.name}"
|
||||
if ! ${state.preBackupCommand} then
|
||||
preCommandErrors["${state.name}"]=1
|
||||
fi
|
||||
'') (lib.attrValues config.clanCore.state)}
|
||||
'';
|
||||
postPrune = ''
|
||||
# report any preBackupCommand errors
|
||||
if [[ ''${#preCommandErrors[@]} -gt 0 ]]; then
|
||||
echo "PreBackupCommand failed for the following services:"
|
||||
for state in "''${!preCommandErrors[@]}"; do
|
||||
echo " $state"
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
|
||||
encryption = {
|
||||
|
@ -12,6 +12,7 @@
|
||||
localsend = ./localsend;
|
||||
matrix-synapse = ./matrix-synapse;
|
||||
moonlight = ./moonlight;
|
||||
postgresql = ./postgresql;
|
||||
root-password = ./root-password;
|
||||
sshd = ./sshd;
|
||||
sunshine = ./sunshine;
|
||||
|
@ -6,7 +6,8 @@
|
||||
}:
|
||||
let
|
||||
cfg = config.clan.localbackup;
|
||||
rsnapshotConfig = target: states: ''
|
||||
uniqueFolders = lib.unique (lib.flatten (lib.mapAttrsToList (name: state: state.folders) config.clanCore.state));
|
||||
rsnapshotConfig = target: ''
|
||||
config_version 1.2
|
||||
snapshot_root ${target.directory}
|
||||
sync_first 1
|
||||
@ -21,6 +22,13 @@ let
|
||||
cmd_preexec ${pkgs.writeShellScript "preexec.sh" ''
|
||||
set -efu -o pipefail
|
||||
${target.preBackupHook}
|
||||
|
||||
# FIXME: we currently fail the backup if the pre-backup command fails
|
||||
# This is not ideal, but at least most of the time we run backup commands in foreground.
|
||||
${lib.concatMapStringsSep "\n" (state: ''
|
||||
echo "Running pre-backup command for ${state.name}"
|
||||
${state.preBackupCommand}
|
||||
'') (lib.attrValues config.clanCore.state)}
|
||||
''}
|
||||
''}
|
||||
|
||||
@ -31,11 +39,9 @@ let
|
||||
''}
|
||||
''}
|
||||
retain snapshot ${builtins.toString config.clan.localbackup.snapshots}
|
||||
${lib.concatMapStringsSep "\n" (state: ''
|
||||
${lib.concatMapStringsSep "\n" (folder: ''
|
||||
${lib.concatMapStringsSep "\n" (folder: ''
|
||||
backup ${folder} ${config.networking.hostName}/
|
||||
'') state.folders}
|
||||
'') states}
|
||||
'') uniqueFolders}
|
||||
'';
|
||||
in
|
||||
{
|
||||
@ -132,8 +138,8 @@ in
|
||||
(
|
||||
${mountHook target}
|
||||
echo "Creating backup '${target.name}'"
|
||||
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target (lib.attrValues config.clanCore.state))}" sync
|
||||
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target (lib.attrValues config.clanCore.state))}" snapshot
|
||||
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target)}" sync
|
||||
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target)}" snapshot
|
||||
)
|
||||
'') (builtins.attrValues cfg.targets)}
|
||||
'')
|
||||
|
@ -8,13 +8,14 @@ let
|
||||
cfg = config.clan.matrix-synapse;
|
||||
nginx-vhost = "matrix.${config.clan.matrix-synapse.domain}";
|
||||
element-web =
|
||||
pkgs.runCommand "element-web-with-config" { nativeBuildInputs = [ pkgs.buildPackages.jq ]; } ''
|
||||
cp -r ${pkgs.element-web} $out
|
||||
chmod -R u+w $out
|
||||
jq '."default_server_config"."m.homeserver" = { "base_url": "https://${nginx-vhost}:443", "server_name": "${config.clan.matrix-synapse.domain}" }' \
|
||||
> $out/config.json < ${pkgs.element-web}/config.json
|
||||
ln -s $out/config.json $out/config.${nginx-vhost}.json
|
||||
'';
|
||||
pkgs.runCommand "element-web-with-config" { nativeBuildInputs = [ pkgs.buildPackages.jq ]; }
|
||||
''
|
||||
cp -r ${pkgs.element-web} $out
|
||||
chmod -R u+w $out
|
||||
jq '."default_server_config"."m.homeserver" = { "base_url": "https://${nginx-vhost}:443", "server_name": "${config.clan.matrix-synapse.domain}" }' \
|
||||
> $out/config.json < ${pkgs.element-web}/config.json
|
||||
ln -s $out/config.json $out/config.${nginx-vhost}.json
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.clan.matrix-synapse = {
|
||||
@ -71,7 +72,8 @@ in
|
||||
};
|
||||
systemd.tmpfiles.settings."synapse" = {
|
||||
"/run/synapse-registration-shared-secret.yaml" = {
|
||||
C.argument = config.clanCore.facts.services.matrix-synapse.secret.synapse-registration_shared_secret.path;
|
||||
C.argument =
|
||||
config.clanCore.facts.services.matrix-synapse.secret.synapse-registration_shared_secret.path;
|
||||
z = {
|
||||
mode = "0400";
|
||||
user = "matrix-synapse";
|
||||
|
2
clanModules/postgresql/README.md
Normal file
2
clanModules/postgresql/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
A free and open-source relational database management system (RDBMS) emphasizing extensibility and SQL compliance.
|
||||
---
|
52
clanModules/postgresql/default.nix
Normal file
52
clanModules/postgresql/default.nix
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
createDatatbaseState =
|
||||
db:
|
||||
let
|
||||
folder = "/var/backup/postgresql/${db}";
|
||||
curFile = "${folder}/dump.sql.zstd";
|
||||
prevFile = "${folder}/dump.sql.prev.zstd";
|
||||
inProgressFile = "${folder}/dump.sql.in-progress.zstd";
|
||||
in
|
||||
{
|
||||
folders = [ folder ];
|
||||
preBackupCommand = ''
|
||||
(
|
||||
umask 0077 # ensure backup is only readable by postgres user
|
||||
if [ -e ${curFile} ]; then
|
||||
mv ${curFile} ${prevFile}
|
||||
fi
|
||||
pg_dump -C ${db} | \
|
||||
${pkgs.zstd}/bin/zstd --rsyncable | \
|
||||
> ${inProgressFile}
|
||||
mv ${inProgressFile} ${curFile}
|
||||
)
|
||||
'';
|
||||
postRestoreCommand = ''
|
||||
if [[ -f ${prevFile} ]]; then
|
||||
zstd --decompress --stdout ${prevFile} | psql -d ${db}
|
||||
fi
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
options.clan.postgresl = {
|
||||
databases = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "clan" ];
|
||||
};
|
||||
};
|
||||
config = {
|
||||
services.postgresql.enable = true;
|
||||
clanCore.state = lib.listToAttrs (
|
||||
builtins.map (
|
||||
db: lib.nameValuePair "postgresql-${db}" (createDatatbaseState db)
|
||||
) config.clan.postgresl.databases
|
||||
);
|
||||
};
|
||||
}
|
@ -60,6 +60,7 @@ nav:
|
||||
- reference/clanModules/localsend.md
|
||||
- reference/clanModules/matrix-synapse.md
|
||||
- reference/clanModules/moonlight.md
|
||||
- reference/clanModules/postgresql.md
|
||||
- reference/clanModules/root-password.md
|
||||
- reference/clanModules/sshd.md
|
||||
- reference/clanModules/static-hosts.md
|
||||
|
@ -17,6 +17,15 @@
|
||||
Folder where state resides in
|
||||
'';
|
||||
};
|
||||
preBackupCommand = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
script to run before backing up the state dir
|
||||
This is for example useful for services that require an export of their state
|
||||
e.g. a database dump
|
||||
'';
|
||||
};
|
||||
preRestoreCommand = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
|
@ -17,7 +17,7 @@ def restore_service(machine: Machine, name: str, provider: str, service: str) ->
|
||||
folders = backup_folders[service]["folders"]
|
||||
env = {}
|
||||
env["NAME"] = name
|
||||
env["FOLDERS"] = ":".join(folders)
|
||||
env["FOLDERS"] = ":".join(set(folders))
|
||||
|
||||
if pre_restore := backup_folders[service]["preRestoreCommand"]:
|
||||
proc = machine.target_host.run(
|
||||
@ -58,12 +58,23 @@ def restore_backup(
|
||||
name: str,
|
||||
service: str | None = None,
|
||||
) -> None:
|
||||
errors = []
|
||||
if service is None:
|
||||
backup_folders = json.loads(machine.eval_nix("config.clanCore.state"))
|
||||
for _service in backup_folders:
|
||||
restore_service(machine, name, provider, _service)
|
||||
try:
|
||||
restore_service(machine, name, provider, _service)
|
||||
except ClanError as e:
|
||||
errors.append(f"{_service}: {e}")
|
||||
else:
|
||||
restore_service(machine, name, provider, service)
|
||||
try:
|
||||
restore_service(machine, name, provider, service)
|
||||
except ClanError as e:
|
||||
errors.append(f"{service}: {e}")
|
||||
if errors:
|
||||
raise ClanError(
|
||||
"Restore failed for the following services:\n" + "\n".join(errors)
|
||||
)
|
||||
|
||||
|
||||
def restore_command(args: argparse.Namespace) -> None:
|
||||
|
Loading…
Reference in New Issue
Block a user