2023-01-26 14:29:12 -06:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
};
|
2023-03-30 15:40:02 -05:00
|
|
|
preCommand = lib.options.mkOption {
|
|
|
|
type = lib.types.lines;
|
|
|
|
default = "";
|
|
|
|
};
|
|
|
|
postCommand = lib.options.mkOption {
|
|
|
|
type = lib.types.lines;
|
|
|
|
default = "";
|
|
|
|
};
|
2023-01-26 14:29:12 -06:00
|
|
|
};
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
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};
|
|
|
|
repositoryConfig = {
|
|
|
|
"b2" = "b2:${bucket}:${directory}";
|
|
|
|
"azure" = "azure:${bucket}:${directory}";
|
|
|
|
}.${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]
|
|
|
|
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
|
2023-03-30 15:40:02 -05:00
|
|
|
''
|
|
|
|
${backup.preCommand}
|
|
|
|
|
|
|
|
${pkgs.restic}/bin/restic backup${one-file-system}${excludes} ${paths}
|
|
|
|
|
|
|
|
${backup.postCommand}
|
|
|
|
''
|
2023-01-26 14:29:12 -06:00
|
|
|
)
|
|
|
|
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" ];
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|