From c07fe53f5694fd4dc65f6aebf35cc5402474a592 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Thu, 26 Jan 2023 14:29:12 -0600 Subject: [PATCH] first --- flake.nix | 333 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 flake.nix diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..f7f6387 --- /dev/null +++ b/flake.nix @@ -0,0 +1,333 @@ +{ + description = "openlens"; + + inputs = { + nixpkgs = { + url = "nixpkgs/nixos-unstable"; + }; + flake-utils = { + url = "github:numtide/flake-utils"; + }; + }; + outputs = { self, nixpkgs, flake-utils, ... }@inputs: + flake-utils.lib.eachDefaultSystem + (system: + let + pkgs = import nixpkgs { + inherit system; + }; + arch = { + "x86_64-linux" = "amd64"; + }.${system}; + + + in + { + packages = { }; + } + ) // { + nixosModules.restic = { config, lib, pkgs, ... }: + let + cfg = config.restic; + in + { + options = { + restic = lib.options.mkOption { + type = lib.types.submodule { + options = { + enable = lib.options.mkEnableOption "Restic"; + password = lib.options.mkOption { + type = lib.types.str; + }; + storage = lib.options.mkOption { + 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; + }; + }; + }; + }; + 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 = + 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 = [ + pkgs.restic + ]; + + environment.etc."restic/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"; + 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] + type = azureblob + account = ${cfg.azure.account_name} + key = ${cfg.azure.account_key} + ''; + }.${cfg.storage}; + user = "root"; + group = "root"; + mode = "0400"; + }; + + 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 + "${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" ] + then + ${pkgs.restic}/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 + ${pkgs.restic}/bin/restic init + fi + ''; + }.${cfg.storage}; + in + { + serviceConfig = { + Type = "oneshot"; + CacheDirectory = "restic"; + EnvironmentFile = "/etc/restic/environment"; + }; + script = '' + ${hcSetup} + ${hcStart} + + ${initCheck} + + ${backupCommands} + + ${pkgs.restic}/bin/restic forget --prune --keep-daily 14 + ${pkgs.restic}/bin/restic check + ${pkgs.restic}/bin/restic cache --cleanup + + ${hcStop} + ''; + }; + + systemd.timers.restic = { + timerConfig = { + OnCalendar = "*-*-* 02:00:00"; + RandomizedDelaySec = "60m"; + }; + wantedBy = [ "multi-user.target" ]; + }; + }; + }; + }; +}