first
This commit is contained in:
parent
c07fe53f56
commit
2fb69448b2
4 changed files with 394 additions and 282 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/result*
|
24
0001-Skip-testing-restore-with-permission-failure.patch
Normal file
24
0001-Skip-testing-restore-with-permission-failure.patch
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
From 8e6186be04e2819b6e3586e5d1aeb8a824e1979f Mon Sep 17 00:00:00 2001
|
||||||
|
From: Simon Bruder <simon@sbruder.de>
|
||||||
|
Date: Thu, 25 Feb 2021 09:20:51 +0100
|
||||||
|
Subject: [PATCH] Skip testing restore with permission failure
|
||||||
|
|
||||||
|
The test fails in sandboxed builds.
|
||||||
|
---
|
||||||
|
cmd/restic/integration_test.go | 1 +
|
||||||
|
1 file changed, 1 insertion(+)
|
||||||
|
|
||||||
|
diff --git a/cmd/restic/integration_test.go b/cmd/restic/integration_test.go
|
||||||
|
index 7d198d33..1588ccb1 100644
|
||||||
|
--- a/cmd/restic/integration_test.go
|
||||||
|
+++ b/cmd/restic/integration_test.go
|
||||||
|
@@ -1170,6 +1170,7 @@ func TestRestoreLatest(t *testing.T) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRestoreWithPermissionFailure(t *testing.T) {
|
||||||
|
+ t.Skip("Skipping testing restore with permission failure")
|
||||||
|
env, cleanup := withTestEnvironment(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
--
|
||||||
|
2.29.2
|
42
flake.lock
Normal file
42
flake.lock
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1676283394,
|
||||||
|
"narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1678470307,
|
||||||
|
"narHash": "sha256-OEeMUr3ueLIXyW/OaFUX5jUdimyQwMg/7e+/Q0gC/QE=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "0c4800d579af4ed98ecc47d464a5e7b0870c4b1f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
609
flake.nix
609
flake.nix
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
description = "openlens";
|
description = "Restic";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs = {
|
nixpkgs = {
|
||||||
|
@ -16,318 +16,363 @@
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
};
|
};
|
||||||
arch = {
|
|
||||||
"x86_64-linux" = "amd64";
|
|
||||||
}.${system};
|
|
||||||
|
|
||||||
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
packages = { };
|
packages = {
|
||||||
|
restic =
|
||||||
|
let
|
||||||
|
pname = "restic";
|
||||||
|
version = "0.15.1";
|
||||||
|
sha256 = "sha256-KdPslVJHH+xdUuFfmLZumP2lHzkDrrAvpDaj38SuP8o=";
|
||||||
|
vendorSha256 = "sha256-oetaCiXWEBUEf382l4sjO0SCPxkoh+bMTgIf/qJTQms=";
|
||||||
|
in
|
||||||
|
pkgs.buildGoModule {
|
||||||
|
inherit pname version;
|
||||||
|
|
||||||
|
src = pkgs.fetchFromGitHub {
|
||||||
|
owner = "restic";
|
||||||
|
repo = "restic";
|
||||||
|
rev = "v${version}";
|
||||||
|
sha256 = sha256;
|
||||||
|
};
|
||||||
|
|
||||||
|
patches = [
|
||||||
|
# The TestRestoreWithPermissionFailure test fails in Nix’s build sandbox
|
||||||
|
./0001-Skip-testing-restore-with-permission-failure.patch
|
||||||
|
];
|
||||||
|
|
||||||
|
vendorSha256 = vendorSha256;
|
||||||
|
|
||||||
|
subPackages = [ "cmd/restic" ];
|
||||||
|
|
||||||
|
nativeBuildInputs = [ pkgs.installShellFiles pkgs.makeWrapper ];
|
||||||
|
|
||||||
|
passthru.tests.restic = pkgs.nixosTests.restic;
|
||||||
|
|
||||||
|
postPatch = ''
|
||||||
|
rm cmd/restic/integration_fuse_test.go
|
||||||
|
'';
|
||||||
|
|
||||||
|
postInstall = ''
|
||||||
|
wrapProgram $out/bin/restic --prefix PATH : '${pkgs.rclone}/bin'
|
||||||
|
'' + pkgs.lib.optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) ''
|
||||||
|
$out/bin/restic generate \
|
||||||
|
--bash-completion restic.bash \
|
||||||
|
--zsh-completion restic.zsh \
|
||||||
|
--man .
|
||||||
|
installShellCompletion restic.{bash,zsh}
|
||||||
|
installManPage *.1
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = with pkgs.lib; {
|
||||||
|
homepage = "https://restic.net";
|
||||||
|
description = "A backup program that is fast, efficient and secure";
|
||||||
|
platforms = platforms.linux ++ platforms.darwin;
|
||||||
|
license = licenses.bsd2;
|
||||||
|
maintainers = [ maintainers.mbrgm ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
) // {
|
) // {
|
||||||
nixosModules.restic = { config, lib, pkgs, ... }:
|
nixosModules = {
|
||||||
let
|
restic = { config, lib, pkgs, ... }:
|
||||||
cfg = config.restic;
|
let
|
||||||
in
|
cfg = config.restic;
|
||||||
{
|
package = self.packages.${pkgs.system}.restic;
|
||||||
options = {
|
in
|
||||||
restic = lib.options.mkOption {
|
{
|
||||||
type = lib.types.submodule {
|
options = {
|
||||||
options = {
|
restic = lib.options.mkOption {
|
||||||
enable = lib.options.mkEnableOption "Restic";
|
type = lib.types.submodule {
|
||||||
password = lib.options.mkOption {
|
options = {
|
||||||
type = lib.types.str;
|
enable = lib.options.mkEnableOption "Restic";
|
||||||
};
|
password = lib.options.mkOption {
|
||||||
storage = lib.options.mkOption {
|
type = lib.types.str;
|
||||||
type = lib.types.enum [
|
|
||||||
"azure"
|
|
||||||
"b2"
|
|
||||||
];
|
|
||||||
# default = "b2";
|
|
||||||
};
|
|
||||||
healthcheck = lib.options.mkOption {
|
|
||||||
type = lib.types.submodule {
|
|
||||||
options = {
|
|
||||||
enable = lib.options.mkEnableOption "use healthcheck";
|
|
||||||
api_url = lib.options.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
};
|
|
||||||
api_key = lib.options.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
};
|
|
||||||
timeout = lib.options.mkOption {
|
|
||||||
type = lib.types.int;
|
|
||||||
default = 86400;
|
|
||||||
};
|
|
||||||
grace = lib.options.mkOption {
|
|
||||||
type = lib.types.int;
|
|
||||||
default = 14400;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
storage = lib.options.mkOption {
|
||||||
b2 = lib.options.mkOption {
|
type = lib.types.enum [
|
||||||
type = lib.types.submodule {
|
"azure"
|
||||||
options = {
|
"b2"
|
||||||
bucket = lib.options.mkOption {
|
];
|
||||||
type = lib.types.str;
|
# default = "b2";
|
||||||
};
|
|
||||||
account_id = lib.options.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
};
|
|
||||||
account_key = lib.options.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
healthcheck = lib.options.mkOption {
|
||||||
azure = lib.options.mkOption {
|
type = lib.types.submodule {
|
||||||
type = lib.types.submodule {
|
|
||||||
options = {
|
|
||||||
account_name = lib.options.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
};
|
|
||||||
account_key = lib.options.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
backups = lib.options.mkOption {
|
|
||||||
type = lib.types.listOf (
|
|
||||||
lib.types.submodule {
|
|
||||||
options = {
|
options = {
|
||||||
one-file-system = lib.options.mkOption {
|
enable = lib.options.mkEnableOption "use healthcheck";
|
||||||
type = lib.types.bool;
|
api_url = lib.options.mkOption {
|
||||||
default = false;
|
type = lib.types.str;
|
||||||
};
|
};
|
||||||
excludes = lib.options.mkOption {
|
api_key = lib.options.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.str;
|
||||||
default = [ ];
|
|
||||||
};
|
};
|
||||||
paths = lib.options.mkOption {
|
timeout = lib.options.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.int;
|
||||||
|
default = 86400;
|
||||||
|
};
|
||||||
|
grace = lib.options.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 14400;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
);
|
};
|
||||||
|
b2 = lib.options.mkOption {
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
bucket = lib.options.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
account_id = lib.options.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
account_key = lib.options.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
azure = lib.options.mkOption {
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
account_name = lib.options.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
account_key = lib.options.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
backups = lib.options.mkOption {
|
||||||
|
type = lib.types.listOf (
|
||||||
|
lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
one-file-system = lib.options.mkOption {
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
config =
|
||||||
config =
|
let
|
||||||
let
|
bucket = {
|
||||||
bucket = {
|
"b2" = cfg.b2.bucket;
|
||||||
"b2" = cfg.b2.bucket;
|
"azure" = builtins.replaceStrings [ "." ] [ "-" ] "${config.networking.hostName}.${config.networking.domain}";
|
||||||
"azure" = builtins.replaceStrings [ "." ] [ "-" ] "${config.networking.hostName}.${config.networking.domain}";
|
}.${cfg.storage};
|
||||||
}.${cfg.storage};
|
directory = {
|
||||||
directory = {
|
"b2" = "${config.networking.hostName}.${config.networking.domain}";
|
||||||
"b2" = "${config.networking.hostName}.${config.networking.domain}";
|
"azure" = "/";
|
||||||
"azure" = "/";
|
}.${cfg.storage};
|
||||||
}.${cfg.storage};
|
in
|
||||||
in
|
lib.mkIf cfg.enable {
|
||||||
lib.mkIf cfg.enable {
|
environment.systemPackages = [
|
||||||
environment.systemPackages = [
|
package
|
||||||
pkgs.restic
|
];
|
||||||
];
|
|
||||||
|
|
||||||
environment.etc."restic/password" = {
|
environment.etc."restic/password" = {
|
||||||
text = cfg.password;
|
text = cfg.password;
|
||||||
user = "root";
|
|
||||||
group = "root";
|
|
||||||
mode = "0400";
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.etc."restic/environment" =
|
|
||||||
let
|
|
||||||
hcConfig =
|
|
||||||
if cfg.healthcheck.enable
|
|
||||||
then ''
|
|
||||||
HC_API_KEY=${cfg.healthcheck.api_key}
|
|
||||||
HC_API_URL=${cfg.healthcheck.api_url}
|
|
||||||
''
|
|
||||||
else
|
|
||||||
"";
|
|
||||||
storageConfig = {
|
|
||||||
"b2" = ''
|
|
||||||
B2_ACCOUNT_ID=${cfg.b2.account_id}
|
|
||||||
B2_ACCOUNT_KEY=${cfg.b2.account_key}
|
|
||||||
'';
|
|
||||||
"azure" = ''
|
|
||||||
AZURE_ACCOUNT_NAME=${cfg.azure.account_name}
|
|
||||||
AZURE_ACCOUNT_KEY=${cfg.azure.account_key}
|
|
||||||
'';
|
|
||||||
}.${cfg.storage};
|
|
||||||
# if cfg.storage == "b2"
|
|
||||||
# then
|
|
||||||
# else
|
|
||||||
# "";
|
|
||||||
repositoryConfig = {
|
|
||||||
"b2" = "b2:${bucket}:${directory}";
|
|
||||||
"azure" = "azure:${bucket}:${directory}";
|
|
||||||
}.${cfg.storage};
|
|
||||||
|
|
||||||
# if cfg.storage == "b2"
|
|
||||||
# then
|
|
||||||
# "b2:${cfg.b2.bucket}:${config.networking.hostName}.${config.networking.domain}"
|
|
||||||
# else
|
|
||||||
# "";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
text = ''
|
|
||||||
${hcConfig}
|
|
||||||
RESTIC_CACHE_DIR=/var/cache/restic
|
|
||||||
RESTIC_PASSWORD_FILE=/etc/restic/password
|
|
||||||
RESTIC_REPOSITORY=${repositoryConfig}
|
|
||||||
${storageConfig}
|
|
||||||
'';
|
|
||||||
user = "root";
|
user = "root";
|
||||||
group = "root";
|
group = "root";
|
||||||
mode = "0400";
|
mode = "0400";
|
||||||
};
|
};
|
||||||
|
|
||||||
environment.etc."restic/rclone" = {
|
environment.etc."restic/environment" =
|
||||||
text = {
|
let
|
||||||
"b2" = ''
|
hcConfig =
|
||||||
[b2]
|
if cfg.healthcheck.enable
|
||||||
type = b2
|
then ''
|
||||||
account = ${cfg.b2.account_id}
|
HC_API_KEY=${cfg.healthcheck.api_key}
|
||||||
key = ${cfg.b2.account_key}
|
HC_API_URL=${cfg.healthcheck.api_url}
|
||||||
'';
|
''
|
||||||
"azure" = ''
|
else
|
||||||
[azure]
|
"";
|
||||||
type = azureblob
|
|
||||||
account = ${cfg.azure.account_name}
|
|
||||||
key = ${cfg.azure.account_key}
|
|
||||||
'';
|
|
||||||
}.${cfg.storage};
|
|
||||||
user = "root";
|
|
||||||
group = "root";
|
|
||||||
mode = "0400";
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.restic =
|
storageConfig = {
|
||||||
let
|
"b2" = ''
|
||||||
curlOptions = "--fail --silent --show-error --max-time 10 --retry 5";
|
B2_ACCOUNT_ID=${cfg.b2.account_id}
|
||||||
hcPayload =
|
B2_ACCOUNT_KEY=${cfg.b2.account_key}
|
||||||
if cfg.healthcheck.enable
|
'';
|
||||||
then
|
"azure" = ''
|
||||||
builtins.toJSON
|
AZURE_ACCOUNT_NAME=${cfg.azure.account_name}
|
||||||
{
|
AZURE_ACCOUNT_KEY=${cfg.azure.account_key}
|
||||||
name = "${config.networking.hostName}.${config.networking.domain}";
|
'';
|
||||||
timeout = cfg.healthcheck.timeout;
|
}.${cfg.storage};
|
||||||
grace = cfg.healthcheck.grace;
|
|
||||||
unique = [ "name" ];
|
|
||||||
channels = "*";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
"";
|
|
||||||
hcSetup =
|
|
||||||
if cfg.healthcheck.enable
|
|
||||||
then ''
|
|
||||||
HC_PAYLOAD='${hcPayload}'
|
|
||||||
HC_URL=$(${pkgs.curl}/bin/curl ${curlOptions} --request POST --header "X-Api-Key: $HC_API_KEY" --data "$HC_PAYLOAD" $HC_API_URL | ${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
|
|
||||||
"";
|
|
||||||
backupCommands = lib.strings.concatStringsSep "\n" (
|
|
||||||
map
|
|
||||||
(
|
|
||||||
backup:
|
|
||||||
let
|
|
||||||
one-file-system =
|
|
||||||
if backup.one-file-system
|
|
||||||
then
|
|
||||||
" --one-file-system"
|
|
||||||
else
|
|
||||||
"";
|
|
||||||
excludes = lib.strings.concatMapStrings
|
|
||||||
(
|
|
||||||
arg: " --exclude=${arg}"
|
|
||||||
)
|
|
||||||
backup.excludes;
|
|
||||||
paths = lib.strings.concatStringsSep " " backup.paths;
|
|
||||||
in
|
|
||||||
"${pkgs.restic}/bin/restic backup${one-file-system}${excludes} ${paths}"
|
|
||||||
)
|
|
||||||
cfg.backups
|
|
||||||
);
|
|
||||||
initCheck = {
|
|
||||||
"b2" = ''
|
|
||||||
config=$(${pkgs.rclone}/bin/rclone --config /etc/restic/rclone lsjson "b2:${bucket}/${directory}/config" | ${pkgs.jq}/bin/jq -r '. | length | . > 0')
|
|
||||||
|
|
||||||
if [ "$config" != "true" ]
|
repositoryConfig = {
|
||||||
then
|
"b2" = "b2:${bucket}:${directory}";
|
||||||
${pkgs.restic}/bin/restic init
|
"azure" = "azure:${bucket}:${directory}";
|
||||||
fi
|
}.${cfg.storage};
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
text = ''
|
||||||
|
${hcConfig}
|
||||||
|
RESTIC_CACHE_DIR=/var/cache/restic
|
||||||
|
RESTIC_PASSWORD_FILE=/etc/restic/password
|
||||||
|
RESTIC_REPOSITORY=${repositoryConfig}
|
||||||
|
${storageConfig}
|
||||||
'';
|
'';
|
||||||
|
user = "root";
|
||||||
|
group = "root";
|
||||||
|
mode = "0400";
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.etc."restic/rclone" = {
|
||||||
|
text = {
|
||||||
|
"b2" = ''
|
||||||
|
[b2]
|
||||||
|
type = b2
|
||||||
|
account = ${cfg.b2.account_id}
|
||||||
|
key = ${cfg.b2.account_key}
|
||||||
|
'';
|
||||||
"azure" = ''
|
"azure" = ''
|
||||||
container=$(${pkgs.rclone}/bin/rclone --config /etc/restic/rclone lsjson "azure:" | ${pkgs.jq}/bin/jq -r 'map(select(.Path=="${bucket}" and .IsBucket)) | length | . > 0')
|
[azure]
|
||||||
if [ "$container" != "true" ]
|
type = azureblob
|
||||||
then
|
account = ${cfg.azure.account_name}
|
||||||
${pkgs.rclone}/bin/rclone --config /etc/restic/rclone mkdir "azure:${bucket}"
|
key = ${cfg.azure.account_key}
|
||||||
fi
|
|
||||||
config=$(${pkgs.rclone}/bin/rclone --config /etc/restic/rclone lsjson "azure:${bucket}/config" | ${pkgs.jq}/bin/jq -r '. | length | . > 0')
|
|
||||||
if [ "$config" != "true" ]
|
|
||||||
then
|
|
||||||
${pkgs.restic}/bin/restic init
|
|
||||||
fi
|
|
||||||
'';
|
'';
|
||||||
}.${cfg.storage};
|
}.${cfg.storage};
|
||||||
in
|
user = "root";
|
||||||
{
|
group = "root";
|
||||||
serviceConfig = {
|
mode = "0400";
|
||||||
Type = "oneshot";
|
};
|
||||||
CacheDirectory = "restic";
|
|
||||||
EnvironmentFile = "/etc/restic/environment";
|
systemd.services.restic =
|
||||||
|
let
|
||||||
|
curlOptions = "--fail --silent --show-error --max-time 10 --retry 5";
|
||||||
|
hcPayload =
|
||||||
|
if cfg.healthcheck.enable
|
||||||
|
then
|
||||||
|
builtins.toJSON
|
||||||
|
{
|
||||||
|
name = "${config.networking.hostName}.${config.networking.domain}";
|
||||||
|
timeout = cfg.healthcheck.timeout;
|
||||||
|
grace = cfg.healthcheck.grace;
|
||||||
|
unique = [ "name" ];
|
||||||
|
channels = "*";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
"";
|
||||||
|
hcSetup =
|
||||||
|
if cfg.healthcheck.enable
|
||||||
|
then ''
|
||||||
|
HC_PAYLOAD='${hcPayload}'
|
||||||
|
HC_URL=$(${pkgs.curl}/bin/curl ${curlOptions} --request POST --header "X-Api-Key: $HC_API_KEY" --data "$HC_PAYLOAD" $HC_API_URL | ${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
|
||||||
|
"";
|
||||||
|
backupCommands = lib.strings.concatStringsSep "\n" (
|
||||||
|
map
|
||||||
|
(
|
||||||
|
backup:
|
||||||
|
let
|
||||||
|
one-file-system =
|
||||||
|
if backup.one-file-system
|
||||||
|
then
|
||||||
|
" --one-file-system"
|
||||||
|
else
|
||||||
|
"";
|
||||||
|
excludes = lib.strings.concatMapStrings
|
||||||
|
(
|
||||||
|
arg: " --exclude=${arg}"
|
||||||
|
)
|
||||||
|
backup.excludes;
|
||||||
|
paths = lib.strings.concatStringsSep " " backup.paths;
|
||||||
|
in
|
||||||
|
"${package}/bin/restic backup${one-file-system}${excludes} ${paths}"
|
||||||
|
)
|
||||||
|
cfg.backups
|
||||||
|
);
|
||||||
|
initCheck = {
|
||||||
|
"b2" = ''
|
||||||
|
config=$(${pkgs.rclone}/bin/rclone --config /etc/restic/rclone lsjson "b2:${bucket}/${directory}/config" | ${pkgs.jq}/bin/jq -r '. | length | . > 0')
|
||||||
|
|
||||||
|
if [ "$config" != "true" ]
|
||||||
|
then
|
||||||
|
${package}/bin/restic init
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
|
"azure" = ''
|
||||||
|
container=$(${pkgs.rclone}/bin/rclone --config /etc/restic/rclone lsjson "azure:" | ${pkgs.jq}/bin/jq -r 'map(select(.Path=="${bucket}" and .IsBucket)) | length | . > 0')
|
||||||
|
if [ "$container" != "true" ]
|
||||||
|
then
|
||||||
|
${pkgs.rclone}/bin/rclone --config /etc/restic/rclone mkdir "azure:${bucket}"
|
||||||
|
fi
|
||||||
|
config=$(${pkgs.rclone}/bin/rclone --config /etc/restic/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";
|
||||||
|
EnvironmentFile = "/etc/restic/environment";
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
${hcSetup}
|
||||||
|
${hcStart}
|
||||||
|
|
||||||
|
${initCheck}
|
||||||
|
|
||||||
|
${backupCommands}
|
||||||
|
|
||||||
|
${package}/bin/restic forget --prune --keep-daily 14
|
||||||
|
${package}/bin/restic check
|
||||||
|
${package}/bin/restic cache --cleanup
|
||||||
|
|
||||||
|
${hcStop}
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
script = ''
|
|
||||||
${hcSetup}
|
|
||||||
${hcStart}
|
|
||||||
|
|
||||||
${initCheck}
|
systemd.timers.restic = {
|
||||||
|
timerConfig = {
|
||||||
${backupCommands}
|
OnCalendar = "*-*-* 02:00:00";
|
||||||
|
RandomizedDelaySec = "60m";
|
||||||
${pkgs.restic}/bin/restic forget --prune --keep-daily 14
|
};
|
||||||
${pkgs.restic}/bin/restic check
|
wantedBy = [ "multi-user.target" ];
|
||||||
${pkgs.restic}/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