backup: init as a reusable module

This commit is contained in:
Katherina Walshe-Grey 2025-03-04 14:43:03 +00:00
parent 691c180ac1
commit fefc7bd20d
5 changed files with 144 additions and 31 deletions

View file

@ -102,6 +102,21 @@
"qenya@shaw".imports = [ ./hosts/shaw/home.nix ];
};
fountain.backup = {
keys = {
elucredassa = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOFa3hjej6KGmS2aQ4s46Y7U8pN4yyR2FuMofpHRwXNk syncoid@elucredassa" ];
};
sync = {
"orm-state" = {
dataset = "state";
sourceHost = "orm";
targetHost = "elucredassa";
source = "rpool_orm";
target = "rpool_elucredassa/backup/orm";
};
};
};
flake.colmena = {
meta = {
nixpkgs = import nixpkgs-unstable {

126
flake/backup.nix Normal file
View file

@ -0,0 +1,126 @@
{ config, lib, pkgs, ... }:
let
cfg = config.fountain.backup;
syncOptions = {
dataset = lib.mkOption {
type = lib.types.str;
description = ''
The name of the dataset to be synced (not including its parent
datasets, if any). This will be the same on the source and target.
It must already exist on the source, defined with the
{option}`randomcat.services.zfs` module, and not exist on the target.
'';
};
sourceHost = lib.mkOption {
type = lib.types.str;
description = ''
The host from which the dataset should be synced. Must be an entry in
{option}`flake.colmena`.
'';
};
targetHost = lib.mkOption {
type = lib.types.str;
description = ''
The host to which the dataset should be synced. Must be an entry in
{option}`flake.colmena`.
'';
};
source = lib.mkOption {
type = lib.types.str;
description = ''
The path to the synced dataset in the ZFS namespace on the source host,
excluding the component that is the name of the dataset itself.
'';
};
target = lib.mkOption {
type = lib.types.str;
description = ''
The path to the synced dataset in the ZFS namespace on the target host,
excluding the component that is the name of the dataset itself. It must
already exist, defined with the {option}`randomcat.services.zfs`
module.
'';
};
};
in
{
options.fountain.backup = {
keys = lib.mkOption {
type = lib.types.attrsOf (lib.types.listOf lib.types.singleLineStr);
default = { };
description = ''
Lists of verbatim OpenSSH public keys that may be used to identify the
syncoid user on each target host. The key to each list must be the
host's hostname, as listed in {option}`flake.colmena`.
'';
example = {
host = [ "ssh-rsa AAAAB3NzaC1yc2etc/etc/etcjwrsh8e596z6J0l7 example@host" ];
bar = [ "ssh-ed25519 AAAAC3NzaCetcetera/etceteraJZMfk3QPfQ foo@bar" ];
};
};
sync = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule { options = syncOptions; });
default = { };
description = ''
Details of ZFS datasets whose snapshots should be synced from machine
to machine using syncoid. Syncoid will run hourly at 15 past the hour
and copy all ZFS snapshots from the source dataset to the target
dataset (recursing into child datasets).
See descriptions for the individual options for more details. The name
of each attribute in this set is arbitrary and used to generate systemd
unit names.
This module does not actually cause snapshots to be taken; sanoid must
be configured separately to do this.
'';
example = {
"orm-state" = {
dataset = "state";
sourceHost = "orm";
targetHost = "elucredassa";
source = "rpool_orm";
target = "rpool_elucredassa/backup/orm";
};
};
};
};
# TODO: add some assertions to verify the options
config.flake.colmena = lib.mkMerge (lib.mapAttrsToList
(name: sync: {
${sync.sourceHost} = { pkgs, ... }: {
randomcat.services.zfs.datasets."${sync.source}/${sync.dataset}".zfsPermissions.users.backup = [ "hold" "send" ];
users.users.backup = {
group = "backup";
isSystemUser = true;
useDefaultShell = true;
openssh.authorizedKeys.keys = cfg.keys.${sync.targetHost};
packages = with pkgs; [ mbuffer lzop ]; # syncoid uses these if available but doesn't pull them in automatically
};
users.groups.backup = { };
};
${sync.targetHost} = {
randomcat.services.zfs.datasets."${sync.target}".zfsPermissions.users.syncoid = [ "mount" "create" "receive" "recordsize" ];
services.syncoid = {
enable = true;
interval = "*-*-* *:15:00";
commonArgs = [ "--no-sync-snap" ];
commands = {
${name} = {
source = "backup@${config.flake.nixosConfigurations.${sync.sourceHost}.config.networking.fqdn}:${sync.source}/${sync.dataset}";
target = "${sync.target}/${sync.dataset}";
recursive = true;
recvOptions = "ux recordsize o compression=lz4";
};
};
};
};
})
cfg.sync
);
}

View file

@ -1,5 +1,6 @@
{
imports = [
./backup.nix
./colmena.nix
];
}

View file

@ -23,26 +23,10 @@ in
console.keyMap = "uk";
services.xserver.xkb.layout = "gb";
# TODO: modularise this
# These are populated by fountain.backup
randomcat.services.zfs.datasets = {
"rpool_elucredassa/backup" = { mountpoint = "none"; };
"rpool_elucredassa/backup/orm" = {
mountpoint = "none";
zfsPermissions.users.syncoid = [ "mount" "create" "receive" "recordsize" ];
};
};
services.syncoid = {
enable = true;
interval = "*-*-* *:15:00";
commonArgs = [ "--no-sync-snap" ];
commands = {
"testing1" = {
source = "backup@10.127.1.2:rpool_orm/state";
target = "rpool_elucredassa/backup/orm/state";
recursive = true;
recvOptions = "ux recordsize o compression=lz4";
};
};
"rpool_elucredassa/backup/orm" = { mountpoint = "none"; };
};
qenya.services.distributed-builds = {

View file

@ -31,19 +31,6 @@
useTemplate = [ "production" ];
recursive = "zfs";
};
# TODO: modularise this
randomcat.services.zfs.datasets."rpool_orm/state".zfsPermissions.users.backup = [ "hold" "send" ];
users.users.backup = {
group = "backup";
isSystemUser = true;
useDefaultShell = true;
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOFa3hjej6KGmS2aQ4s46Y7U8pN4yyR2FuMofpHRwXNk syncoid@elucredassa"
];
packages = with pkgs; [ mbuffer lzop ]; # syncoid uses these if available but doesn't pull them in automatically
};
users.groups.backup = { };
qenya.services.actual = {
enable = true;