From 25ddd648bcdd0531c0be682d9a5add7b44585ef3 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Fri, 3 Nov 2023 12:19:05 -0500 Subject: [PATCH] update to 0.16.2 --- flake.lock | 6 +- flake.nix | 737 +++++++++++++++++++++++++++-------------------------- 2 files changed, 376 insertions(+), 367 deletions(-) diff --git a/flake.lock b/flake.lock index 84a1bde..d6de0cc 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1697851979, - "narHash": "sha256-lJ8k4qkkwdvi+t/Xc6Fn74kUuobpu9ynPGxNZR6OwoA=", + "lastModified": 1698846319, + "narHash": "sha256-4jyW/dqFBVpWFnhl0nvP6EN4lP7/ZqPxYRjl6var0Oc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5550a85a087c04ddcace7f892b0bdc9d8bb080c8", + "rev": "34bdaaf1f0b7fb6d9091472edc968ff10a8c2857", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 45c7f7c..8c5055b 100644 --- a/flake.nix +++ b/flake.nix @@ -9,410 +9,419 @@ url = "github:numtide/flake-utils"; }; }; - outputs = { self, nixpkgs, flake-utils, ... }@inputs: + outputs = { + self, + nixpkgs, + flake-utils, + ... + } @ inputs: flake-utils.lib.eachDefaultSystem - (system: - let - pkgs = import nixpkgs { - inherit system; - }; - in - { - packages = { - restic = - let - pname = "restic"; - version = "0.16.1"; - hash = "sha256-sMxOZEnZr2UdhmwLXQnggQzw+pXcoWmqqADlQ0yDhj8="; - vendorHash = "sha256-Ctg6bln5kzGs7gDLo9zUpsbSybKOtHFuHvHG3cxCfac="; - in - pkgs.buildGoModule { - inherit pname version vendorHash; + ( + system: let + pkgs = import nixpkgs { + inherit system; + }; + in { + packages = { + restic = let + pname = "restic"; + version = "0.16.2"; + hash = "sha256-Qrbg8/f1ne+7c+mnUc/8CoZBjiGLohJXnu0cnc0pT4g="; + vendorHash = "sha256-Ctg6bln5kzGs7gDLo9zUpsbSybKOtHFuHvHG3cxCfac="; + in + pkgs.buildGoModule { + inherit pname version vendorHash; - src = pkgs.fetchFromGitHub { - owner = "restic"; - repo = "restic"; - rev = "v${version}"; - hash = hash; - }; + src = pkgs.fetchFromGitHub { + owner = "restic"; + repo = "restic"; + rev = "v${version}"; + hash = hash; + }; - env = { - RESTIC_TEST_FUSE = "false"; - }; + env = { + RESTIC_TEST_FUSE = "false"; + }; - patches = [ - # The TestRestoreWithPermissionFailure test fails in Nix's build sandbox - ./0001-Skip-testing-restore-with-permission-failure.patch - ]; + patches = [ + # The TestRestoreWithPermissionFailure test fails in Nix's build sandbox + ./0001-Skip-testing-restore-with-permission-failure.patch + ]; - subPackages = [ "cmd/restic" ]; + subPackages = ["cmd/restic"]; - nativeBuildInputs = [ - pkgs.installShellFiles - pkgs.makeWrapper - ]; + nativeBuildInputs = [ + pkgs.installShellFiles + pkgs.makeWrapper + ]; - passthru.tests.restic = pkgs.nixosTests.restic; + passthru.tests.restic = pkgs.nixosTests.restic; - postPatch = '' - # rm cmd/restic/integration_fuse_test.go - ''; + postPatch = '' + # rm cmd/restic/integration_fuse_test.go + ''; - postInstall = '' + postInstall = + '' 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 \ --bash-completion restic.bash \ + --fish-completion restic.fish \ --zsh-completion restic.zsh \ --man . - installShellCompletion restic.{bash,zsh} + installShellCompletion restic.{bash,fish,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 ]; - }; + 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]; }; - - 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 { - name = "restic"; - tag = "latest"; - maxLayers = 2; - contents = [ + 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 { + 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 = { - Cmd = [ - "${self.packages.${system}.entrypoint}/bin/entrypoint" - "--rclone" - "${pkgs.rclone}/bin/rclone" - "--restic" - "${self.packages.${system}.restic}/bin/restic" - "--cache-dir" - "/cache" - ]; - Env = [ - "TZ=UTC" - "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" - ]; - ExposedPorts = { }; - Volumes = { - "/cache" = { }; - }; + Env = [ + "TZ=UTC" + "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" + ]; + ExposedPorts = {}; + Volumes = { + "/cache" = {}; }; }; }; - } - ) // { + }; + } + ) + // { nixosModules = { - restic = { config, lib, pkgs, ... }: - let - cfg = config.restic; - package = self.packages.${pkgs.system}.restic; - in - { - options = { - restic = lib.options.mkOption { - type = lib.types.submodule { - options = { - enable = lib.options.mkEnableOption "Restic"; - passwordFile = lib.options.mkOption { - type = lib.types.path; - }; - storage = lib.options.mkOption { - type = lib.types.enum [ - "azure" - "b2" - ]; - }; - healthcheck = lib.options.mkOption { - type = lib.types.submodule { - options = { - enable = lib.options.mkEnableOption "use healthcheck"; - apiUrl = lib.options.mkOption { - type = lib.types.str; - }; - apiKeyFile = 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; - }; + restic = { + config, + lib, + pkgs, + ... + }: let + cfg = config.restic; + package = self.packages.${pkgs.system}.restic; + in { + options = { + restic = lib.options.mkOption { + type = lib.types.submodule { + options = { + enable = lib.options.mkEnableOption "Restic"; + passwordFile = lib.options.mkOption { + type = lib.types.path; + }; + storage = lib.options.mkOption { + type = lib.types.enum [ + "azure" + "b2" + ]; + }; + healthcheck = lib.options.mkOption { + type = lib.types.submodule { + options = { + enable = lib.options.mkEnableOption "use healthcheck"; + apiUrl = lib.options.mkOption { + type = lib.types.str; + }; + apiKeyFile = 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; }; }; }; - b2 = lib.options.mkOption { - type = lib.types.submodule { - options = { - bucket = lib.options.mkOption { - type = lib.types.str; - }; - accountId = lib.options.mkOption { - type = lib.types.str; - }; - accountKeyFile = lib.options.mkOption { - type = lib.types.str; - }; + }; + b2 = lib.options.mkOption { + type = lib.types.submodule { + options = { + bucket = lib.options.mkOption { + type = lib.types.str; + }; + accountId = lib.options.mkOption { + type = lib.types.str; + }; + accountKeyFile = lib.options.mkOption { + type = lib.types.str; }; }; }; - azure = lib.options.mkOption { - type = lib.types.submodule { - options = { - accountName = lib.options.mkOption { - type = lib.types.str; - }; - accountKeyFile = lib.options.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - }; + }; + azure = lib.options.mkOption { + type = lib.types.submodule { + options = { + accountName = lib.options.mkOption { + type = lib.types.str; + }; + accountKeyFile = lib.options.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; }; }; }; - backups = lib.options.mkOption { - type = lib.types.listOf ( - lib.types.submodule { - options = { - oneFileSystem = 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; - default = [ ]; - }; - preCommand = lib.options.mkOption { - type = lib.types.lines; - default = ""; - }; - postCommand = lib.options.mkOption { - type = lib.types.lines; - default = ""; - }; + }; + backups = lib.options.mkOption { + type = lib.types.listOf ( + lib.types.submodule { + options = { + oneFileSystem = 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; + 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"]; + }; + }; + }; }; }; }