mirror of
https://github.com/vale981/phoebe
synced 2025-03-04 09:21:40 -05:00
backups: Add rsync backups using hard links for efficiency
This commit is contained in:
parent
a96b46c4d9
commit
1b62b52dfa
6 changed files with 270 additions and 1 deletions
|
@ -39,6 +39,12 @@ Module List
|
|||
|
||||
Simple backups for PostgreSQL via `pg_dump`.
|
||||
|
||||
* `phoebe.backup.rsync`:
|
||||
|
||||
Sync files from a remote machine creating a set of backups that
|
||||
use hard links for files that don't change from day to day. This
|
||||
is a simple and efficient way to backup a remote host.
|
||||
|
||||
* `phoebe.services.networking.wireguard`:
|
||||
|
||||
Simple way to configure a whole network of WireGuard machines.
|
||||
|
|
|
@ -1,7 +1,53 @@
|
|||
{ config, lib, pkgs, ...}:
|
||||
{ config, lib, pkgs, ...}: with lib;
|
||||
|
||||
let
|
||||
cfg = config.phoebe.backup;
|
||||
user = "backup";
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./postgresql.nix
|
||||
./rsync.nix
|
||||
];
|
||||
|
||||
#### Interface
|
||||
options.phoebe.backup = {
|
||||
user = {
|
||||
enable = mkEnableOption "Backup user and group.";
|
||||
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = user;
|
||||
description = "User to perform backups as.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = user;
|
||||
description = "Group for the backup user.";
|
||||
};
|
||||
};
|
||||
|
||||
directory = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/backup";
|
||||
description = ''
|
||||
Base directory where backups will be stored. Each host to
|
||||
back up will get a directory under this base directory.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
#### Implementation
|
||||
config = mkIf cfg.user.enable {
|
||||
users.users."${cfg.user.name}" = {
|
||||
description = "Backup user.";
|
||||
home = cfg.directory;
|
||||
createHome = true;
|
||||
group = cfg.user.group;
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
users.groups."${cfg.user.group}" = {};
|
||||
};
|
||||
}
|
||||
|
|
162
modules/backup/rsync.nix
Normal file
162
modules/backup/rsync.nix
Normal file
|
@ -0,0 +1,162 @@
|
|||
# Hard-linked backups via rsync.
|
||||
{ config, lib, pkgs, ...}: with lib;
|
||||
|
||||
let
|
||||
cfg = config.phoebe.backup.rsync;
|
||||
plib = config.phoebe.lib;
|
||||
user = "backup";
|
||||
scripts = (import ../../pkgs/default.nix { inherit pkgs; }).backup-scripts;
|
||||
|
||||
##############################################################################
|
||||
# Backup options.
|
||||
backupOpts = {
|
||||
options = {
|
||||
host = mkOption {
|
||||
type = types.str;
|
||||
example = "example.com";
|
||||
description = "Host name for the machine to back up.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.ints.positive;
|
||||
default = builtins.head config.services.openssh.ports;
|
||||
example = 22;
|
||||
description = "SSH port on the remote machine.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "backup";
|
||||
example = "root";
|
||||
description = "User name on the remote machine to use.";
|
||||
};
|
||||
|
||||
directory = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/backup";
|
||||
example = "/var/lib/backup";
|
||||
description = "Remote directory to sync to the local machine.";
|
||||
};
|
||||
|
||||
key = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/home/backup/.ssh/id_ed25519";
|
||||
description = ''
|
||||
Optional SSH key to use when connecting to the remote
|
||||
machine. If the key is provided by NixOps then this backup
|
||||
will wait until the key is available.
|
||||
'';
|
||||
};
|
||||
|
||||
schedule = mkOption {
|
||||
type = types.str;
|
||||
default = "*-*-* 02:00:00";
|
||||
example = "*-*-* *:00/30:00";
|
||||
description = ''
|
||||
A systemd calendar specification to designate the frequency
|
||||
of the backup. You can use the "systemd-analyze calendar"
|
||||
command to validate your calendar specification.
|
||||
|
||||
When increasing the frequency of the backups you should
|
||||
consider changing the number of backups that you keep.
|
||||
'';
|
||||
};
|
||||
|
||||
keep = mkOption {
|
||||
type = types.ints.positive;
|
||||
default = 7;
|
||||
example = 14;
|
||||
description = "Number of backups to keep when deleting older backups.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
##############################################################################
|
||||
# Sanitize the name of a directory.
|
||||
dir = path: replaceStrings ["/"] ["-"] (removePrefix "/" path);
|
||||
|
||||
##############################################################################
|
||||
# Generate a service/timer name (without the suffix):
|
||||
name = opts: "rsync-${opts.host}-${dir opts.directory}";
|
||||
|
||||
##############################################################################
|
||||
# Generate a systemd service for a backup.
|
||||
service = opts:
|
||||
let base = "${config.phoebe.backup.directory}/rsync";
|
||||
localdir = "${base}/${opts.host}/${dir opts.directory}";
|
||||
in rec {
|
||||
description = "rsync backup for ${opts.host}:${opts.directory}";
|
||||
path = [ pkgs.coreutils scripts ];
|
||||
wants = plib.keyService opts.key;
|
||||
after = wants;
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
PermissionsStartOnly = "true";
|
||||
User = cfg.user;
|
||||
};
|
||||
|
||||
preStart = ''
|
||||
mkdir -p "${localdir}"
|
||||
chown -R ${cfg.user}:${cfg.group} "${localdir}"
|
||||
chmod -R 0700 "${localdir}"
|
||||
'';
|
||||
|
||||
script = ''
|
||||
export BACKUP_LIB_DIR=${scripts}/lib
|
||||
export BACKUP_LOG_DIR=stdout
|
||||
export BACKUP_SSH_KEY=${toString opts.key}
|
||||
export BACKUP_SSH_PORT=${toString opts.port}
|
||||
. "${scripts}/lib/backup.sh"
|
||||
backup_via_rsync "${opts.user}@${opts.host}:${opts.directory}" "${localdir}"
|
||||
backup-purge.sh -k "${toString opts.keep}" -d "${localdir}"
|
||||
'';
|
||||
};
|
||||
|
||||
##############################################################################
|
||||
# Generate a systemd timer for a backup.
|
||||
timer = opts: {
|
||||
description = "Scheduled Backup of ${opts.host}:${opts.directory}";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig.OnCalendar = opts.schedule;
|
||||
timerConfig.RandomizedDelaySec = "5m";
|
||||
timerConfig.Unit = "${name opts}.service";
|
||||
};
|
||||
|
||||
##############################################################################
|
||||
# Generate systemd services and timers.
|
||||
toSystemd = f: foldr (a: b: b // {"${name a}" = f a;}) {} cfg.schedules;
|
||||
|
||||
in
|
||||
{
|
||||
#### Interface
|
||||
options.phoebe.backup.rsync = {
|
||||
enable = mkEnableOption "rsync backups";
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = user;
|
||||
description = "User to perform backups as.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = config.phoebe.backup.user.group;
|
||||
description = "Group for the backup user.";
|
||||
};
|
||||
|
||||
schedules = mkOption {
|
||||
type = types.listOf (types.submodule backupOpts);
|
||||
default = [];
|
||||
description = "List of backups to perform.";
|
||||
};
|
||||
};
|
||||
|
||||
#### Implementation
|
||||
config = mkIf cfg.enable {
|
||||
phoebe.backup.user.enable = cfg.user == user;
|
||||
systemd.services = toSystemd service;
|
||||
systemd.timers = toSystemd timer;
|
||||
};
|
||||
}
|
47
test/backup/rsync/default.nix
Normal file
47
test/backup/rsync/default.nix
Normal file
|
@ -0,0 +1,47 @@
|
|||
{ pkgs ? import <nixpkgs> {}
|
||||
}:
|
||||
|
||||
let
|
||||
service = "rsync-localhost-tmp-backup.service";
|
||||
|
||||
in
|
||||
pkgs.nixosTest {
|
||||
name = "rsync-backup-test";
|
||||
|
||||
nodes = {
|
||||
simple = {config, pkgs, ...}: {
|
||||
imports = [ ../../../modules ];
|
||||
phoebe.security.enable = false;
|
||||
services.openssh.enable = true;
|
||||
|
||||
users.users.root.openssh.authorizedKeys.keys = [
|
||||
(builtins.readFile ../../data/ssh.id_ed25519.pub)
|
||||
];
|
||||
|
||||
phoebe.backup.rsync = {
|
||||
enable = true;
|
||||
schedules = [
|
||||
{ host = "localhost";
|
||||
directory = "/tmp/backup";
|
||||
user = "root";
|
||||
key = "/tmp/key";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
$simple->start;
|
||||
$simple->copyFileFromHost("${../../data/ssh.id_ed25519}", "/tmp/key");
|
||||
$simple->succeed("chmod 0600 /tmp/key");
|
||||
$simple->succeed("chown backup:backup /tmp/key");
|
||||
$simple->succeed("mkdir /tmp/backup");
|
||||
$simple->succeed("echo OKAY > /tmp/backup/file");
|
||||
$simple->waitForUnit("sshd.service");
|
||||
$simple->systemctl("start ${service}");
|
||||
$simple->waitForUnit("${service}");
|
||||
$simple->waitUntilFails("systemctl status ${service} | grep -q 'Active: active'");
|
||||
$simple->succeed("cat /var/backup/rsync/localhost/tmp-backup/*/file") =~ /OKAY/ or die;
|
||||
'';
|
||||
}
|
7
test/data/ssh.id_ed25519
Normal file
7
test/data/ssh.id_ed25519
Normal file
|
@ -0,0 +1,7 @@
|
|||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACDvu8YUGTtH+Dr9JM6fk0lvTatPduUZleYMDxIw8ludwAAAAIg8mzDNPJsw
|
||||
zQAAAAtzc2gtZWQyNTUxOQAAACDvu8YUGTtH+Dr9JM6fk0lvTatPduUZleYMDxIw8ludwA
|
||||
AAAEDvidTlFEJvyV9Bn2rY2rMieHx/GMVdm2T8I/noFxdCb++7xhQZO0f4Ov0kzp+TSW9N
|
||||
q0925RmV5gwPEjDyW53AAAAABHRlc3QB
|
||||
-----END OPENSSH PRIVATE KEY-----
|
1
test/data/ssh.id_ed25519.pub
Normal file
1
test/data/ssh.id_ed25519.pub
Normal file
|
@ -0,0 +1 @@
|
|||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO+7xhQZO0f4Ov0kzp+TSW9Nq0925RmV5gwPEjDyW53A test
|
Loading…
Add table
Reference in a new issue