This commit is contained in:
parent
081a664fac
commit
25ddd648bc
2 changed files with 376 additions and 367 deletions
|
@ -20,11 +20,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1697851979,
|
"lastModified": 1698846319,
|
||||||
"narHash": "sha256-lJ8k4qkkwdvi+t/Xc6Fn74kUuobpu9ynPGxNZR6OwoA=",
|
"narHash": "sha256-4jyW/dqFBVpWFnhl0nvP6EN4lP7/ZqPxYRjl6var0Oc=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "5550a85a087c04ddcace7f892b0bdc9d8bb080c8",
|
"rev": "34bdaaf1f0b7fb6d9091472edc968ff10a8c2857",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
737
flake.nix
737
flake.nix
|
@ -9,410 +9,419 @@
|
||||||
url = "github:numtide/flake-utils";
|
url = "github:numtide/flake-utils";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
outputs = { self, nixpkgs, flake-utils, ... }@inputs:
|
outputs = {
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
flake-utils,
|
||||||
|
...
|
||||||
|
} @ inputs:
|
||||||
flake-utils.lib.eachDefaultSystem
|
flake-utils.lib.eachDefaultSystem
|
||||||
(system:
|
(
|
||||||
let
|
system: let
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
};
|
};
|
||||||
in
|
in {
|
||||||
{
|
packages = {
|
||||||
packages = {
|
restic = let
|
||||||
restic =
|
pname = "restic";
|
||||||
let
|
version = "0.16.2";
|
||||||
pname = "restic";
|
hash = "sha256-Qrbg8/f1ne+7c+mnUc/8CoZBjiGLohJXnu0cnc0pT4g=";
|
||||||
version = "0.16.1";
|
vendorHash = "sha256-Ctg6bln5kzGs7gDLo9zUpsbSybKOtHFuHvHG3cxCfac=";
|
||||||
hash = "sha256-sMxOZEnZr2UdhmwLXQnggQzw+pXcoWmqqADlQ0yDhj8=";
|
in
|
||||||
vendorHash = "sha256-Ctg6bln5kzGs7gDLo9zUpsbSybKOtHFuHvHG3cxCfac=";
|
pkgs.buildGoModule {
|
||||||
in
|
inherit pname version vendorHash;
|
||||||
pkgs.buildGoModule {
|
|
||||||
inherit pname version vendorHash;
|
|
||||||
|
|
||||||
src = pkgs.fetchFromGitHub {
|
src = pkgs.fetchFromGitHub {
|
||||||
owner = "restic";
|
owner = "restic";
|
||||||
repo = "restic";
|
repo = "restic";
|
||||||
rev = "v${version}";
|
rev = "v${version}";
|
||||||
hash = hash;
|
hash = hash;
|
||||||
};
|
};
|
||||||
|
|
||||||
env = {
|
env = {
|
||||||
RESTIC_TEST_FUSE = "false";
|
RESTIC_TEST_FUSE = "false";
|
||||||
};
|
};
|
||||||
|
|
||||||
patches = [
|
patches = [
|
||||||
# The TestRestoreWithPermissionFailure test fails in Nix's build sandbox
|
# The TestRestoreWithPermissionFailure test fails in Nix's build sandbox
|
||||||
./0001-Skip-testing-restore-with-permission-failure.patch
|
./0001-Skip-testing-restore-with-permission-failure.patch
|
||||||
];
|
];
|
||||||
|
|
||||||
subPackages = [ "cmd/restic" ];
|
subPackages = ["cmd/restic"];
|
||||||
|
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
pkgs.installShellFiles
|
pkgs.installShellFiles
|
||||||
pkgs.makeWrapper
|
pkgs.makeWrapper
|
||||||
];
|
];
|
||||||
|
|
||||||
passthru.tests.restic = pkgs.nixosTests.restic;
|
passthru.tests.restic = pkgs.nixosTests.restic;
|
||||||
|
|
||||||
postPatch = ''
|
postPatch = ''
|
||||||
# rm cmd/restic/integration_fuse_test.go
|
# rm cmd/restic/integration_fuse_test.go
|
||||||
'';
|
'';
|
||||||
|
|
||||||
postInstall = ''
|
postInstall =
|
||||||
|
''
|
||||||
wrapProgram $out/bin/restic --prefix PATH : '${pkgs.rclone}/bin'
|
wrapProgram $out/bin/restic --prefix PATH : '${pkgs.rclone}/bin'
|
||||||
'' + pkgs.lib.optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) ''
|
''
|
||||||
|
+ pkgs.lib.optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) ''
|
||||||
$out/bin/restic generate \
|
$out/bin/restic generate \
|
||||||
--bash-completion restic.bash \
|
--bash-completion restic.bash \
|
||||||
|
--fish-completion restic.fish \
|
||||||
--zsh-completion restic.zsh \
|
--zsh-completion restic.zsh \
|
||||||
--man .
|
--man .
|
||||||
installShellCompletion restic.{bash,zsh}
|
installShellCompletion restic.{bash,fish,zsh}
|
||||||
installManPage *.1
|
installManPage *.1
|
||||||
'';
|
'';
|
||||||
|
|
||||||
meta = with pkgs.lib; {
|
meta = with pkgs.lib; {
|
||||||
homepage = "https://restic.net";
|
homepage = "https://restic.net";
|
||||||
description = "A backup program that is fast, efficient and secure";
|
description = "A backup program that is fast, efficient and secure";
|
||||||
platforms = platforms.linux ++ platforms.darwin;
|
platforms = platforms.linux ++ platforms.darwin;
|
||||||
license = licenses.bsd2;
|
license = licenses.bsd2;
|
||||||
maintainers = [ maintainers.mbrgm ];
|
maintainers = [maintainers.mbrgm];
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
entrypoint = pkgs.writeTextFile {
|
|
||||||
name = "entrypoint";
|
|
||||||
destination = "/bin/entrypoint";
|
|
||||||
text = pkgs.lib.concatStringsSep "\n" [
|
|
||||||
"#!${pkgs.nushell}/bin/nu"
|
|
||||||
(builtins.readFile ./entrypoint.nu)
|
|
||||||
];
|
|
||||||
executable = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
docker = pkgs.dockerTools.buildLayeredImage {
|
entrypoint = pkgs.writeTextFile {
|
||||||
name = "restic";
|
name = "entrypoint";
|
||||||
tag = "latest";
|
destination = "/bin/entrypoint";
|
||||||
maxLayers = 2;
|
text = pkgs.lib.concatStringsSep "\n" [
|
||||||
contents = [
|
"#!${pkgs.nushell}/bin/nu"
|
||||||
|
(builtins.readFile ./entrypoint.nu)
|
||||||
|
];
|
||||||
|
executable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
docker = pkgs.dockerTools.buildLayeredImage {
|
||||||
|
name = "restic";
|
||||||
|
tag = "latest";
|
||||||
|
maxLayers = 2;
|
||||||
|
contents = [
|
||||||
|
];
|
||||||
|
config = {
|
||||||
|
Cmd = [
|
||||||
|
"${self.packages.${system}.entrypoint}/bin/entrypoint"
|
||||||
|
"--rclone"
|
||||||
|
"${pkgs.rclone}/bin/rclone"
|
||||||
|
"--restic"
|
||||||
|
"${self.packages.${system}.restic}/bin/restic"
|
||||||
|
"--cache-dir"
|
||||||
|
"/cache"
|
||||||
];
|
];
|
||||||
config = {
|
Env = [
|
||||||
Cmd = [
|
"TZ=UTC"
|
||||||
"${self.packages.${system}.entrypoint}/bin/entrypoint"
|
"SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
|
||||||
"--rclone"
|
];
|
||||||
"${pkgs.rclone}/bin/rclone"
|
ExposedPorts = {};
|
||||||
"--restic"
|
Volumes = {
|
||||||
"${self.packages.${system}.restic}/bin/restic"
|
"/cache" = {};
|
||||||
"--cache-dir"
|
|
||||||
"/cache"
|
|
||||||
];
|
|
||||||
Env = [
|
|
||||||
"TZ=UTC"
|
|
||||||
"SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
|
|
||||||
];
|
|
||||||
ExposedPorts = { };
|
|
||||||
Volumes = {
|
|
||||||
"/cache" = { };
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
) // {
|
}
|
||||||
|
)
|
||||||
|
// {
|
||||||
nixosModules = {
|
nixosModules = {
|
||||||
restic = { config, lib, pkgs, ... }:
|
restic = {
|
||||||
let
|
config,
|
||||||
cfg = config.restic;
|
lib,
|
||||||
package = self.packages.${pkgs.system}.restic;
|
pkgs,
|
||||||
in
|
...
|
||||||
{
|
}: let
|
||||||
options = {
|
cfg = config.restic;
|
||||||
restic = lib.options.mkOption {
|
package = self.packages.${pkgs.system}.restic;
|
||||||
type = lib.types.submodule {
|
in {
|
||||||
options = {
|
options = {
|
||||||
enable = lib.options.mkEnableOption "Restic";
|
restic = lib.options.mkOption {
|
||||||
passwordFile = lib.options.mkOption {
|
type = lib.types.submodule {
|
||||||
type = lib.types.path;
|
options = {
|
||||||
};
|
enable = lib.options.mkEnableOption "Restic";
|
||||||
storage = lib.options.mkOption {
|
passwordFile = lib.options.mkOption {
|
||||||
type = lib.types.enum [
|
type = lib.types.path;
|
||||||
"azure"
|
};
|
||||||
"b2"
|
storage = lib.options.mkOption {
|
||||||
];
|
type = lib.types.enum [
|
||||||
};
|
"azure"
|
||||||
healthcheck = lib.options.mkOption {
|
"b2"
|
||||||
type = lib.types.submodule {
|
];
|
||||||
options = {
|
};
|
||||||
enable = lib.options.mkEnableOption "use healthcheck";
|
healthcheck = lib.options.mkOption {
|
||||||
apiUrl = lib.options.mkOption {
|
type = lib.types.submodule {
|
||||||
type = lib.types.str;
|
options = {
|
||||||
};
|
enable = lib.options.mkEnableOption "use healthcheck";
|
||||||
apiKeyFile = lib.options.mkOption {
|
apiUrl = lib.options.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
};
|
};
|
||||||
timeout = lib.options.mkOption {
|
apiKeyFile = lib.options.mkOption {
|
||||||
type = lib.types.int;
|
type = lib.types.str;
|
||||||
default = 86400;
|
};
|
||||||
};
|
timeout = lib.options.mkOption {
|
||||||
grace = lib.options.mkOption {
|
type = lib.types.int;
|
||||||
type = lib.types.int;
|
default = 86400;
|
||||||
default = 14400;
|
};
|
||||||
};
|
grace = lib.options.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 14400;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
b2 = lib.options.mkOption {
|
};
|
||||||
type = lib.types.submodule {
|
b2 = lib.options.mkOption {
|
||||||
options = {
|
type = lib.types.submodule {
|
||||||
bucket = lib.options.mkOption {
|
options = {
|
||||||
type = lib.types.str;
|
bucket = lib.options.mkOption {
|
||||||
};
|
type = lib.types.str;
|
||||||
accountId = lib.options.mkOption {
|
};
|
||||||
type = lib.types.str;
|
accountId = lib.options.mkOption {
|
||||||
};
|
type = lib.types.str;
|
||||||
accountKeyFile = lib.options.mkOption {
|
};
|
||||||
type = lib.types.str;
|
accountKeyFile = lib.options.mkOption {
|
||||||
};
|
type = lib.types.str;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
azure = lib.options.mkOption {
|
};
|
||||||
type = lib.types.submodule {
|
azure = lib.options.mkOption {
|
||||||
options = {
|
type = lib.types.submodule {
|
||||||
accountName = lib.options.mkOption {
|
options = {
|
||||||
type = lib.types.str;
|
accountName = lib.options.mkOption {
|
||||||
};
|
type = lib.types.str;
|
||||||
accountKeyFile = lib.options.mkOption {
|
};
|
||||||
type = lib.types.nullOr lib.types.str;
|
accountKeyFile = lib.options.mkOption {
|
||||||
default = null;
|
type = lib.types.nullOr lib.types.str;
|
||||||
};
|
default = null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
backups = lib.options.mkOption {
|
};
|
||||||
type = lib.types.listOf (
|
backups = lib.options.mkOption {
|
||||||
lib.types.submodule {
|
type = lib.types.listOf (
|
||||||
options = {
|
lib.types.submodule {
|
||||||
oneFileSystem = lib.options.mkOption {
|
options = {
|
||||||
type = lib.types.bool;
|
oneFileSystem = lib.options.mkOption {
|
||||||
default = false;
|
type = lib.types.bool;
|
||||||
};
|
default = false;
|
||||||
excludes = lib.options.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
default = [ ];
|
|
||||||
};
|
|
||||||
paths = lib.options.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
default = [ ];
|
|
||||||
};
|
|
||||||
preCommand = lib.options.mkOption {
|
|
||||||
type = lib.types.lines;
|
|
||||||
default = "";
|
|
||||||
};
|
|
||||||
postCommand = lib.options.mkOption {
|
|
||||||
type = lib.types.lines;
|
|
||||||
default = "";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
excludes = lib.options.mkOption {
|
||||||
);
|
type = lib.types.listOf lib.types.str;
|
||||||
};
|
default = [];
|
||||||
|
};
|
||||||
|
paths = lib.options.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
preCommand = lib.options.mkOption {
|
||||||
|
type = lib.types.lines;
|
||||||
|
default = "";
|
||||||
|
};
|
||||||
|
postCommand = lib.options.mkOption {
|
||||||
|
type = lib.types.lines;
|
||||||
|
default = "";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config =
|
|
||||||
let
|
|
||||||
|
|
||||||
bucket = {
|
|
||||||
"b2" = cfg.b2.bucket;
|
|
||||||
"azure" = builtins.replaceStrings [ "." ] [ "-" ] "${config.networking.hostName}.${config.networking.domain}";
|
|
||||||
}.${cfg.storage};
|
|
||||||
|
|
||||||
directory = {
|
|
||||||
"b2" = "${config.networking.hostName}.${config.networking.domain}";
|
|
||||||
"azure" = "/";
|
|
||||||
}.${cfg.storage};
|
|
||||||
|
|
||||||
in
|
|
||||||
lib.mkIf cfg.enable {
|
|
||||||
|
|
||||||
environment.systemPackages = [
|
|
||||||
package
|
|
||||||
];
|
|
||||||
|
|
||||||
systemd.services.restic =
|
|
||||||
let
|
|
||||||
curlOptions = "--fail --silent --show-error --max-time 10 --retry 5";
|
|
||||||
hcPayload =
|
|
||||||
if cfg.healthcheck.enable
|
|
||||||
then
|
|
||||||
(
|
|
||||||
pkgs.writeText "healthcheck-payload"
|
|
||||||
(
|
|
||||||
builtins.toJSON
|
|
||||||
{
|
|
||||||
name = "${config.networking.hostName}.${config.networking.domain}";
|
|
||||||
timeout = cfg.healthcheck.timeout;
|
|
||||||
grace = cfg.healthcheck.grace;
|
|
||||||
unique = [ "name" ];
|
|
||||||
channels = "*";
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else
|
|
||||||
null;
|
|
||||||
hcSetup =
|
|
||||||
if cfg.healthcheck.enable
|
|
||||||
then ''
|
|
||||||
HC_URL=''$(${pkgs.curl}/bin/curl ${curlOptions} --request POST --header 'Content-Type: application/json' --header "X-Api-Key: ''$(<${cfg.healthcheck.apiKeyFile})" --data @${hcPayload} "${cfg.healthcheck.apiUrl}" | ${pkgs.jq}/bin/jq -r .ping_url)
|
|
||||||
''
|
|
||||||
else
|
|
||||||
"";
|
|
||||||
hcStart =
|
|
||||||
if cfg.healthcheck.enable
|
|
||||||
then ''
|
|
||||||
if [ ! -z "''${HC_URL}" ]
|
|
||||||
then
|
|
||||||
${pkgs.curl}/bin/curl ${curlOptions} --output /dev/null "''${HC_URL}/start" || true
|
|
||||||
fi
|
|
||||||
''
|
|
||||||
else
|
|
||||||
"";
|
|
||||||
hcStop =
|
|
||||||
if cfg.healthcheck.enable
|
|
||||||
then ''
|
|
||||||
if [ ! -z "''${HC_URL}" ]
|
|
||||||
then
|
|
||||||
${pkgs.curl}/bin/curl ${curlOptions} --output /dev/null "''${HC_URL}" || true
|
|
||||||
fi
|
|
||||||
''
|
|
||||||
else
|
|
||||||
"";
|
|
||||||
|
|
||||||
repositoryConfig = {
|
|
||||||
"b2" = "b2:${bucket}:${directory}";
|
|
||||||
"azure" = "azure:${bucket}:${directory}";
|
|
||||||
}.${cfg.storage};
|
|
||||||
|
|
||||||
resticConfig = ''
|
|
||||||
export RESTIC_CACHE_DIR=/var/cache/restic
|
|
||||||
export RESTIC_PASSWORD_FILE=${cfg.passwordFile}
|
|
||||||
export RESTIC_REPOSITORY=${repositoryConfig}
|
|
||||||
''
|
|
||||||
+ {
|
|
||||||
"b2" = ''
|
|
||||||
export B2_ACCOUNT_ID='${cfg.b2.accountId}'
|
|
||||||
export B2_ACCOUNT_KEY="''$(<${cfg.b2.accountKeyFile})"
|
|
||||||
'';
|
|
||||||
"azure" = ''
|
|
||||||
export AZURE_ACCOUNT_NAME='${cfg.azure.accountName}'
|
|
||||||
export AZURE_ACCOUNT_KEY="''$(<${cfg.azure.accountKeyFile})"
|
|
||||||
'';
|
|
||||||
}.${cfg.storage};
|
|
||||||
|
|
||||||
backupCommands = lib.strings.concatStringsSep "\n"
|
|
||||||
(
|
|
||||||
map
|
|
||||||
(
|
|
||||||
backup:
|
|
||||||
let
|
|
||||||
oneFileSystem =
|
|
||||||
if backup.oneFileSystem
|
|
||||||
then
|
|
||||||
[ "--one-file-system" ]
|
|
||||||
else
|
|
||||||
[ ];
|
|
||||||
excludes = map
|
|
||||||
(
|
|
||||||
exclude: ''--exclude="${exclude}"''
|
|
||||||
)
|
|
||||||
backup.excludes;
|
|
||||||
paths = map
|
|
||||||
(
|
|
||||||
path: ''"${path}"''
|
|
||||||
)
|
|
||||||
backup.paths;
|
|
||||||
arguments = lib.strings.concatStringsSep " " (
|
|
||||||
oneFileSystem ++ excludes ++ paths
|
|
||||||
);
|
|
||||||
in
|
|
||||||
''
|
|
||||||
${backup.preCommand}
|
|
||||||
${package}/bin/restic backup ${arguments}
|
|
||||||
${backup.postCommand}
|
|
||||||
''
|
|
||||||
)
|
|
||||||
cfg.backups
|
|
||||||
);
|
|
||||||
initCheck = {
|
|
||||||
"b2" = ''
|
|
||||||
export RCLONE_CONFIG=/dev/null
|
|
||||||
export RCLONE_CONFIG_B2_TYPE=b2
|
|
||||||
export RCLONE_CONFIG_B2_ACCOUNT='${cfg.b2.accountId}'
|
|
||||||
export RCLONE_CONFIG_B2_KEY="''$(<${cfg.b2.accountKeyFile})"
|
|
||||||
|
|
||||||
config=''$(${pkgs.rclone}/bin/rclone lsjson 'b2:${bucket}/${directory}/config' | ${pkgs.jq}/bin/jq -r '. | length | . > 0')
|
|
||||||
|
|
||||||
if [ "''${config}" != 'true' ]
|
|
||||||
then
|
|
||||||
${package}/bin/restic init
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
"azure" = ''
|
|
||||||
export RCLONE_CONFIG=/dev/null
|
|
||||||
export RCLONE_CONFIG_AZURE_TYPE=azureblob
|
|
||||||
export RCLONE_CONFIG_AZURE_ACCOUNT='${cfg.azure.accountName}'
|
|
||||||
export RCLONE_CONFIG_AZURE_KEY="''$(<${cfg.azure.accountKeyFile})"
|
|
||||||
|
|
||||||
container=''$(${pkgs.rclone}/bin/rclone lsjson 'azure:' | ${pkgs.jq}/bin/jq -r 'map(select(.Path=="${bucket}" and .IsBucket)) | length | . > 0')
|
|
||||||
|
|
||||||
if [ "''${container}" != 'true' ]
|
|
||||||
then
|
|
||||||
${pkgs.rclone}/bin/rclone mkdir 'azure:${bucket}'
|
|
||||||
fi
|
|
||||||
|
|
||||||
config=$(${pkgs.rclone}/bin/rclone lsjson 'azure:${bucket}/config' | ${pkgs.jq}/bin/jq -r '. | length | . > 0')
|
|
||||||
|
|
||||||
if [ "''${config}" != 'true' ]
|
|
||||||
then
|
|
||||||
${package}/bin/restic init
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
}.${cfg.storage};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
CacheDirectory = "restic";
|
|
||||||
};
|
|
||||||
script = ''
|
|
||||||
${hcSetup}
|
|
||||||
${hcStart}
|
|
||||||
|
|
||||||
${resticConfig}
|
|
||||||
|
|
||||||
${initCheck}
|
|
||||||
|
|
||||||
${backupCommands}
|
|
||||||
|
|
||||||
${package}/bin/restic forget --prune --keep-daily 14
|
|
||||||
${package}/bin/restic check
|
|
||||||
${package}/bin/restic cache --cleanup
|
|
||||||
|
|
||||||
${hcStop}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.timers.restic = {
|
|
||||||
timerConfig = {
|
|
||||||
OnCalendar = "*-*-* 02:00:00";
|
|
||||||
RandomizedDelaySec = "60m";
|
|
||||||
};
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
config = let
|
||||||
|
bucket =
|
||||||
|
{
|
||||||
|
"b2" = cfg.b2.bucket;
|
||||||
|
"azure" = builtins.replaceStrings ["."] ["-"] "${config.networking.hostName}.${config.networking.domain}";
|
||||||
|
}
|
||||||
|
.${cfg.storage};
|
||||||
|
|
||||||
|
directory =
|
||||||
|
{
|
||||||
|
"b2" = "${config.networking.hostName}.${config.networking.domain}";
|
||||||
|
"azure" = "/";
|
||||||
|
}
|
||||||
|
.${cfg.storage};
|
||||||
|
in
|
||||||
|
lib.mkIf cfg.enable {
|
||||||
|
environment.systemPackages = [
|
||||||
|
package
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.services.restic = let
|
||||||
|
curlOptions = "--fail --silent --show-error --max-time 10 --retry 5";
|
||||||
|
hcPayload =
|
||||||
|
if cfg.healthcheck.enable
|
||||||
|
then
|
||||||
|
(
|
||||||
|
pkgs.writeText "healthcheck-payload"
|
||||||
|
(
|
||||||
|
builtins.toJSON
|
||||||
|
{
|
||||||
|
name = "${config.networking.hostName}.${config.networking.domain}";
|
||||||
|
timeout = cfg.healthcheck.timeout;
|
||||||
|
grace = cfg.healthcheck.grace;
|
||||||
|
unique = ["name"];
|
||||||
|
channels = "*";
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else null;
|
||||||
|
hcSetup =
|
||||||
|
if cfg.healthcheck.enable
|
||||||
|
then ''
|
||||||
|
HC_URL=''$(${pkgs.curl}/bin/curl ${curlOptions} --request POST --header 'Content-Type: application/json' --header "X-Api-Key: ''$(<${cfg.healthcheck.apiKeyFile})" --data @${hcPayload} "${cfg.healthcheck.apiUrl}" | ${pkgs.jq}/bin/jq -r .ping_url)
|
||||||
|
''
|
||||||
|
else "";
|
||||||
|
hcStart =
|
||||||
|
if cfg.healthcheck.enable
|
||||||
|
then ''
|
||||||
|
if [ ! -z "''${HC_URL}" ]
|
||||||
|
then
|
||||||
|
${pkgs.curl}/bin/curl ${curlOptions} --output /dev/null "''${HC_URL}/start" || true
|
||||||
|
fi
|
||||||
|
''
|
||||||
|
else "";
|
||||||
|
hcStop =
|
||||||
|
if cfg.healthcheck.enable
|
||||||
|
then ''
|
||||||
|
if [ ! -z "''${HC_URL}" ]
|
||||||
|
then
|
||||||
|
${pkgs.curl}/bin/curl ${curlOptions} --output /dev/null "''${HC_URL}" || true
|
||||||
|
fi
|
||||||
|
''
|
||||||
|
else "";
|
||||||
|
|
||||||
|
repositoryConfig =
|
||||||
|
{
|
||||||
|
"b2" = "b2:${bucket}:${directory}";
|
||||||
|
"azure" = "azure:${bucket}:${directory}";
|
||||||
|
}
|
||||||
|
.${cfg.storage};
|
||||||
|
|
||||||
|
resticConfig =
|
||||||
|
''
|
||||||
|
export RESTIC_CACHE_DIR=/var/cache/restic
|
||||||
|
export RESTIC_PASSWORD_FILE=${cfg.passwordFile}
|
||||||
|
export RESTIC_REPOSITORY=${repositoryConfig}
|
||||||
|
''
|
||||||
|
+ {
|
||||||
|
"b2" = ''
|
||||||
|
export B2_ACCOUNT_ID='${cfg.b2.accountId}'
|
||||||
|
export B2_ACCOUNT_KEY="''$(<${cfg.b2.accountKeyFile})"
|
||||||
|
'';
|
||||||
|
"azure" = ''
|
||||||
|
export AZURE_ACCOUNT_NAME='${cfg.azure.accountName}'
|
||||||
|
export AZURE_ACCOUNT_KEY="''$(<${cfg.azure.accountKeyFile})"
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
.${cfg.storage};
|
||||||
|
|
||||||
|
backupCommands =
|
||||||
|
lib.strings.concatStringsSep "\n"
|
||||||
|
(
|
||||||
|
map
|
||||||
|
(
|
||||||
|
backup: let
|
||||||
|
oneFileSystem =
|
||||||
|
if backup.oneFileSystem
|
||||||
|
then ["--one-file-system"]
|
||||||
|
else [];
|
||||||
|
excludes =
|
||||||
|
map
|
||||||
|
(
|
||||||
|
exclude: ''--exclude="${exclude}"''
|
||||||
|
)
|
||||||
|
backup.excludes;
|
||||||
|
paths =
|
||||||
|
map
|
||||||
|
(
|
||||||
|
path: ''"${path}"''
|
||||||
|
)
|
||||||
|
backup.paths;
|
||||||
|
arguments = lib.strings.concatStringsSep " " (
|
||||||
|
oneFileSystem ++ excludes ++ paths
|
||||||
|
);
|
||||||
|
in ''
|
||||||
|
${backup.preCommand}
|
||||||
|
${package}/bin/restic backup ${arguments}
|
||||||
|
${backup.postCommand}
|
||||||
|
''
|
||||||
|
)
|
||||||
|
cfg.backups
|
||||||
|
);
|
||||||
|
initCheck =
|
||||||
|
{
|
||||||
|
"b2" = ''
|
||||||
|
export RCLONE_CONFIG=/dev/null
|
||||||
|
export RCLONE_CONFIG_B2_TYPE=b2
|
||||||
|
export RCLONE_CONFIG_B2_ACCOUNT='${cfg.b2.accountId}'
|
||||||
|
export RCLONE_CONFIG_B2_KEY="''$(<${cfg.b2.accountKeyFile})"
|
||||||
|
|
||||||
|
config=''$(${pkgs.rclone}/bin/rclone lsjson 'b2:${bucket}/${directory}/config' | ${pkgs.jq}/bin/jq -r '. | length | . > 0')
|
||||||
|
|
||||||
|
if [ "''${config}" != 'true' ]
|
||||||
|
then
|
||||||
|
${package}/bin/restic init
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
|
"azure" = ''
|
||||||
|
export RCLONE_CONFIG=/dev/null
|
||||||
|
export RCLONE_CONFIG_AZURE_TYPE=azureblob
|
||||||
|
export RCLONE_CONFIG_AZURE_ACCOUNT='${cfg.azure.accountName}'
|
||||||
|
export RCLONE_CONFIG_AZURE_KEY="''$(<${cfg.azure.accountKeyFile})"
|
||||||
|
|
||||||
|
container=''$(${pkgs.rclone}/bin/rclone lsjson 'azure:' | ${pkgs.jq}/bin/jq -r 'map(select(.Path=="${bucket}" and .IsBucket)) | length | . > 0')
|
||||||
|
|
||||||
|
if [ "''${container}" != 'true' ]
|
||||||
|
then
|
||||||
|
${pkgs.rclone}/bin/rclone mkdir 'azure:${bucket}'
|
||||||
|
fi
|
||||||
|
|
||||||
|
config=$(${pkgs.rclone}/bin/rclone lsjson 'azure:${bucket}/config' | ${pkgs.jq}/bin/jq -r '. | length | . > 0')
|
||||||
|
|
||||||
|
if [ "''${config}" != 'true' ]
|
||||||
|
then
|
||||||
|
${package}/bin/restic init
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
.${cfg.storage};
|
||||||
|
in {
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
CacheDirectory = "restic";
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
${hcSetup}
|
||||||
|
${hcStart}
|
||||||
|
|
||||||
|
${resticConfig}
|
||||||
|
|
||||||
|
${initCheck}
|
||||||
|
|
||||||
|
${backupCommands}
|
||||||
|
|
||||||
|
${package}/bin/restic forget --prune --keep-daily 14
|
||||||
|
${package}/bin/restic check
|
||||||
|
${package}/bin/restic cache --cleanup
|
||||||
|
|
||||||
|
${hcStop}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.restic = {
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = "*-*-* 02:00:00";
|
||||||
|
RandomizedDelaySec = "60m";
|
||||||
|
};
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue