feat(rsync-backup): fix rsync backup quirks and rsyncd port bug
Still dont know what is using port 873 on shan... Rsync backup testing continues
This commit is contained in:
parent
9bad8d8484
commit
9d096472a5
7 changed files with 266 additions and 30 deletions
|
@ -46,7 +46,11 @@
|
||||||
"/home/ny/Downloads"
|
"/home/ny/Downloads"
|
||||||
"/home/ny/notes"
|
"/home/ny/notes"
|
||||||
];
|
];
|
||||||
target = "/home/ny/test";
|
target = {
|
||||||
|
location = "backup";
|
||||||
|
type = "rsyncd";
|
||||||
|
host = "shan";
|
||||||
|
};
|
||||||
incremental.enable = true;
|
incremental.enable = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -121,6 +121,15 @@ mkIf config.services.headscale.enable {
|
||||||
acls = [
|
acls = [
|
||||||
(mkAcl [ "tag:client" ] [ "tag:client:*" ]) # client -> client
|
(mkAcl [ "tag:client" ] [ "tag:client:*" ]) # client -> client
|
||||||
(mkAcl [ "tag:client" ] [ "tag:server:*" ]) # client -> server
|
(mkAcl [ "tag:client" ] [ "tag:server:*" ]) # client -> server
|
||||||
|
{
|
||||||
|
action = "accept";
|
||||||
|
src = [
|
||||||
|
"tag:client"
|
||||||
|
"tag:server"
|
||||||
|
];
|
||||||
|
proto = "rsync"; # optional
|
||||||
|
dst = [ "tag:backup" ];
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
ssh = [
|
ssh = [
|
||||||
|
@ -133,6 +142,7 @@ mkIf config.services.headscale.enable {
|
||||||
tags = [
|
tags = [
|
||||||
"tag:client"
|
"tag:client"
|
||||||
"tag:server"
|
"tag:server"
|
||||||
|
"tag:backup"
|
||||||
];
|
];
|
||||||
|
|
||||||
tagOwners =
|
tagOwners =
|
||||||
|
|
|
@ -68,6 +68,24 @@
|
||||||
isExitNode = true;
|
isExitNode = true;
|
||||||
tags = [ "server" ];
|
tags = [ "server" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
services.rsyncd = {
|
||||||
|
enable = true;
|
||||||
|
port = 9523;
|
||||||
|
settings = {
|
||||||
|
globalSection = {
|
||||||
|
address = "100.64.0.4";
|
||||||
|
};
|
||||||
|
sections = {
|
||||||
|
backup = {
|
||||||
|
path = "/var/backups/test";
|
||||||
|
comment = "ftp export area";
|
||||||
|
"write only" = true;
|
||||||
|
"read only" = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
|
@ -78,8 +96,11 @@
|
||||||
openFirewall = true;
|
openFirewall = true;
|
||||||
host = "0.0.0.0";
|
host = "0.0.0.0";
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = [ 9523 ];
|
||||||
|
|
||||||
environment.systemPackages = map lib.lowPrio [
|
environment.systemPackages = map lib.lowPrio [
|
||||||
pkgs.curl
|
pkgs.curl
|
||||||
];
|
];
|
||||||
|
|
|
@ -6,5 +6,7 @@
|
||||||
./system
|
./system
|
||||||
./container
|
./container
|
||||||
./server
|
./server
|
||||||
|
|
||||||
|
./fixes
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
1
options/fixes/default.nix
Normal file
1
options/fixes/default.nix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{ imports = [ ./rsyncd.nix ]; }
|
134
options/fixes/rsyncd.nix
Normal file
134
options/fixes/rsyncd.nix
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
cfg = config.modules.services.rsyncd;
|
||||||
|
settingsFormat = pkgs.formats.iniWithGlobalSection { };
|
||||||
|
configFile = settingsFormat.generate "rsyncd.conf" cfg.settings;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.modules.services.rsyncd = {
|
||||||
|
|
||||||
|
enable = lib.mkEnableOption "the rsync daemon";
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
default = 873;
|
||||||
|
type = lib.types.port;
|
||||||
|
description = "TCP port the daemon will listen on.";
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = lib.mkOption {
|
||||||
|
type = lib.types.oneOf [
|
||||||
|
settingsFormat.type
|
||||||
|
(pkgs.formats.ini { }).type # Retrocompatibility
|
||||||
|
];
|
||||||
|
default = { };
|
||||||
|
example = {
|
||||||
|
globalSection = {
|
||||||
|
uid = "nobody";
|
||||||
|
gid = "nobody";
|
||||||
|
"use chroot" = true;
|
||||||
|
"max connections" = 4;
|
||||||
|
};
|
||||||
|
sections = {
|
||||||
|
ftp = {
|
||||||
|
path = "/var/ftp/./pub";
|
||||||
|
comment = "whole ftp area";
|
||||||
|
};
|
||||||
|
cvs = {
|
||||||
|
path = "/data/cvs";
|
||||||
|
comment = "CVS repository (requires authentication)";
|
||||||
|
"auth users" = [
|
||||||
|
"tridge"
|
||||||
|
"susan"
|
||||||
|
];
|
||||||
|
"secrets file" = "/etc/rsyncd.secrets";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
description = ''
|
||||||
|
Configuration for rsyncd. See
|
||||||
|
{manpage}`rsyncd.conf(5)`.
|
||||||
|
'';
|
||||||
|
apply =
|
||||||
|
val:
|
||||||
|
if (lib.typeOf val == "ini") then
|
||||||
|
{
|
||||||
|
sections = lib.removeAttrs val [ "global" ];
|
||||||
|
globalSection = lib.mkIf (lib.hasAttrs "global") val.global;
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
val;
|
||||||
|
};
|
||||||
|
|
||||||
|
socketActivated = lib.mkOption {
|
||||||
|
default = false;
|
||||||
|
type = lib.types.bool;
|
||||||
|
description = "If enabled Rsync will be socket-activated rather than run persistently.";
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
|
||||||
|
modules.services.rsyncd.settings.globalSection.port = toString cfg.port;
|
||||||
|
|
||||||
|
systemd =
|
||||||
|
let
|
||||||
|
serviceConfigSecurity = {
|
||||||
|
ProtectSystem = "full";
|
||||||
|
PrivateDevices = "on";
|
||||||
|
NoNewPrivileges = "on";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
services.rsync = lib.mkForce {
|
||||||
|
enable = !cfg.socketActivated;
|
||||||
|
aliases = [ "rsyncd.service" ];
|
||||||
|
|
||||||
|
description = "fast remote file copy program daemon";
|
||||||
|
after = [ "network.target" ];
|
||||||
|
documentation = [
|
||||||
|
"man:rsync(1)"
|
||||||
|
"man:rsyncd.conf(5)"
|
||||||
|
];
|
||||||
|
|
||||||
|
serviceConfig = serviceConfigSecurity // {
|
||||||
|
ExecStart = "${pkgs.rsync}/bin/rsync --daemon --no-detach --config=${configFile}";
|
||||||
|
RestartSec = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
services."rsync@" = lib.mkForce {
|
||||||
|
description = "fast remote file copy program daemon";
|
||||||
|
after = [ "network.target" ];
|
||||||
|
|
||||||
|
serviceConfig = serviceConfigSecurity // {
|
||||||
|
ExecStart = "${pkgs.rsync}/bin/rsync --daemon --config=${configFile}";
|
||||||
|
StandardInput = "socket";
|
||||||
|
StandardOutput = "inherit";
|
||||||
|
StandardError = "journal";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sockets.rsync = lib.mkForce {
|
||||||
|
enable = cfg.socketActivated;
|
||||||
|
|
||||||
|
description = "socket for fast remote file copy program daemon";
|
||||||
|
conflicts = [ "rsync.service" ];
|
||||||
|
|
||||||
|
listenStreams = [ (toString cfg.port) ];
|
||||||
|
socketConfig.Accept = true;
|
||||||
|
|
||||||
|
wantedBy = [ "sockets.target" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
|
@ -9,8 +9,6 @@ let
|
||||||
mkIf
|
mkIf
|
||||||
mkEnableOption
|
mkEnableOption
|
||||||
mkOption
|
mkOption
|
||||||
stringToCharacters
|
|
||||||
head
|
|
||||||
concatStringsSep
|
concatStringsSep
|
||||||
;
|
;
|
||||||
inherit (lib.types)
|
inherit (lib.types)
|
||||||
|
@ -19,6 +17,7 @@ let
|
||||||
package
|
package
|
||||||
enum
|
enum
|
||||||
str
|
str
|
||||||
|
port
|
||||||
;
|
;
|
||||||
cfg = config.modules.services.rsync-backup;
|
cfg = config.modules.services.rsync-backup;
|
||||||
in
|
in
|
||||||
|
@ -32,9 +31,10 @@ in
|
||||||
# - [ ] support
|
# - [ ] support
|
||||||
# - [ ] nfs
|
# - [ ] nfs
|
||||||
# - [ ] sftp
|
# - [ ] sftp
|
||||||
# - [x] ssh
|
# - [ ] ssh
|
||||||
# - [x] local
|
# - [x] local
|
||||||
# - retention limit for incremental backups
|
# - [x] rsyncd
|
||||||
|
# - [ ] retention limit for incremental backups
|
||||||
|
|
||||||
options.modules.services.rsync-backup = {
|
options.modules.services.rsync-backup = {
|
||||||
enable = mkEnableOption "rsync backups";
|
enable = mkEnableOption "rsync backups";
|
||||||
|
@ -42,7 +42,6 @@ in
|
||||||
sources = mkOption {
|
sources = mkOption {
|
||||||
type = listOf nonEmptyStr;
|
type = listOf nonEmptyStr;
|
||||||
default = [ ]; # TODO: validate if at least 1 element
|
default = [ ]; # TODO: validate if at least 1 element
|
||||||
apply = map (s: "\"${s}\"");
|
|
||||||
description = ''
|
description = ''
|
||||||
A list of absolute paths to the files or folders that should get synchronized.
|
A list of absolute paths to the files or folders that should get synchronized.
|
||||||
It isn't necessary to wrap them in escaped double quotation marks.
|
It isn't necessary to wrap them in escaped double quotation marks.
|
||||||
|
@ -54,7 +53,8 @@ in
|
||||||
default = "";
|
default = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
target = mkOption {
|
target = {
|
||||||
|
location = mkOption {
|
||||||
type = nonEmptyStr;
|
type = nonEmptyStr;
|
||||||
description = ''
|
description = ''
|
||||||
The target to where the data should be backed up.
|
The target to where the data should be backed up.
|
||||||
|
@ -63,17 +63,34 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
targetType = mkOption {
|
finalLocation = mkOption {
|
||||||
|
type = nonEmptyStr;
|
||||||
|
default =
|
||||||
|
if cfg.target.type == "local" then
|
||||||
|
cfg.target.location
|
||||||
|
else if cfg.target.type == "rsyncd" then
|
||||||
|
"rsync://${cfg.target.host}/${cfg.target.location}"
|
||||||
|
else
|
||||||
|
throw "Unable to create location.";
|
||||||
|
};
|
||||||
|
|
||||||
|
type = mkOption {
|
||||||
type = enum [
|
type = enum [
|
||||||
"local"
|
"local"
|
||||||
"ssh"
|
"rsyncd"
|
||||||
];
|
];
|
||||||
default = if head (stringToCharacters cfg.target) == "/" then "local" else "ssh";
|
default = "local";
|
||||||
description = ''
|
description = ''
|
||||||
Guesses if the given target is local or ssh, very rudimentary therefore it might be necessary to specify manually (or rewrite the logic).
|
Guesses if the given target is local or ssh, very rudimentary therefore it might be necessary to specify manually (or rewrite the logic).
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
host = mkOption {
|
||||||
|
type = str;
|
||||||
|
default = "";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
package = mkOption {
|
package = mkOption {
|
||||||
type = package;
|
type = package;
|
||||||
default = pkgs.rsync;
|
default = pkgs.rsync;
|
||||||
|
@ -87,6 +104,7 @@ in
|
||||||
default = [
|
default = [
|
||||||
"--archive"
|
"--archive"
|
||||||
"--verbose"
|
"--verbose"
|
||||||
|
"--mkpath"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -96,13 +114,14 @@ in
|
||||||
(mkIf cfg.incremental.enable "--delete")
|
(mkIf cfg.incremental.enable "--delete")
|
||||||
(mkIf (cfg.exclude != "") "--exclude=\"${cfg.exclude}\"")
|
(mkIf (cfg.exclude != "") "--exclude=\"${cfg.exclude}\"")
|
||||||
(mkIf cfg.incremental.enable "--backup-dir=\"${cfg.incremental.finalIncrementPath}\"")
|
(mkIf cfg.incremental.enable "--backup-dir=\"${cfg.incremental.finalIncrementPath}\"")
|
||||||
|
"--port=${toString cfg.port}"
|
||||||
];
|
];
|
||||||
apply = concatStringsSep " ";
|
apply = concatStringsSep " ";
|
||||||
};
|
};
|
||||||
|
|
||||||
interval = mkOption {
|
interval = mkOption {
|
||||||
type = nonEmptyStr;
|
type = nonEmptyStr;
|
||||||
default = "1d";
|
default = "daily";
|
||||||
};
|
};
|
||||||
|
|
||||||
incremental = {
|
incremental = {
|
||||||
|
@ -117,7 +136,15 @@ in
|
||||||
};
|
};
|
||||||
finalIncrementPath = mkOption {
|
finalIncrementPath = mkOption {
|
||||||
type = nonEmptyStr;
|
type = nonEmptyStr;
|
||||||
default = "${cfg.target}/increment/$(date +${cfg.incremental.format})/";
|
default = "../${cfg.target.location}/increment/$(date +${cfg.incremental.format})/";
|
||||||
|
description = ''
|
||||||
|
The directory in which the changes will be stored, the .. at the start is necessary because it is relative to
|
||||||
|
the target and we append `backup` to the target.
|
||||||
|
|
||||||
|
-- cfg.location
|
||||||
|
| - backup/
|
||||||
|
| - increment/<increment format>
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
# TODO: add retention
|
# TODO: add retention
|
||||||
};
|
};
|
||||||
|
@ -126,34 +153,54 @@ in
|
||||||
type = nonEmptyStr;
|
type = nonEmptyStr;
|
||||||
default = "rsync-backup";
|
default = "rsync-backup";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
type = str;
|
||||||
|
default = "rsync-backup";
|
||||||
|
description = "User account under which Rsync runs.";
|
||||||
|
};
|
||||||
|
|
||||||
|
group = mkOption {
|
||||||
|
type = str;
|
||||||
|
default = "rsync-backup";
|
||||||
|
description = "Group under which Rsync runs.";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = port;
|
||||||
|
default = 9523;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
|
|
||||||
systemd.timers.${cfg.unitName} = {
|
systemd.timers.${cfg.unitName} = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
unitConfig = {
|
unitConfig = {
|
||||||
Description = "Triggers rsync-backup regularly.";
|
Description = "Triggers rsync-backup regularly.";
|
||||||
};
|
};
|
||||||
timerConfig = {
|
timerConfig = {
|
||||||
Unit = "${cfg.unitName}.service";
|
Unit = "${cfg.unitName}.service";
|
||||||
OnBootSec = cfg.interval;
|
OnCalendar = cfg.interval;
|
||||||
|
Persistent = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.${cfg.unitName} = {
|
systemd.services.${cfg.unitName} = {
|
||||||
script =
|
script =
|
||||||
let
|
let
|
||||||
destination = if cfg.incremental.enable then "${cfg.target}/backup" else cfg.target;
|
destination =
|
||||||
|
if cfg.incremental.enable then "${cfg.target.finalLocation}/backup" else cfg.target.finalLocation;
|
||||||
|
sources = concatStringsSep " " (map (s: "\"${s}\"") cfg.sources);
|
||||||
in
|
in
|
||||||
''
|
''
|
||||||
${cfg.package}/bin/rsync \
|
${cfg.package}/bin/rsync \
|
||||||
${cfg.flagsFinal} \
|
${cfg.flagsFinal} \
|
||||||
${concatStringsSep " " cfg.sources} \
|
${sources} \
|
||||||
"${destination}"
|
"${destination}"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
requires = mkIf (cfg.targetType == "ssh") [ "network.target" ];
|
requires = mkIf (cfg.target.type == "rsyncd") [ "network.target" ];
|
||||||
unitConfig = {
|
unitConfig = {
|
||||||
Description = "Backs up files from a source location to a specified destination.";
|
Description = "Backs up files from a source location to a specified destination.";
|
||||||
};
|
};
|
||||||
|
@ -161,7 +208,24 @@ in
|
||||||
Type = "simple";
|
Type = "simple";
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
RestartSec = 300;
|
RestartSec = 300;
|
||||||
|
PrivateDevices = true;
|
||||||
|
PrivateTmp = true;
|
||||||
|
ProtectSystem = "full";
|
||||||
|
ReadOnlyPaths = concatStringsSep " " cfg.sources;
|
||||||
|
IPAddressAllow = "any";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
users.users = mkIf (cfg.user == "rsync-backup") {
|
||||||
|
rsync-backup = {
|
||||||
|
useDefaultShell = true;
|
||||||
|
group = cfg.group;
|
||||||
|
isSystemUser = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
users.groups = mkIf (cfg.group == "rsync-backup") {
|
||||||
|
rsync-backup = { };
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue