This commit is contained in:
parent
1166d2038e
commit
8e6762646a
4 changed files with 275 additions and 13 deletions
30
.drone.yml
Normal file
30
.drone.yml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
kind: secret
|
||||||
|
name: local_username
|
||||||
|
get:
|
||||||
|
path: local
|
||||||
|
name: username
|
||||||
|
---
|
||||||
|
kind: secret
|
||||||
|
name: local_password
|
||||||
|
get:
|
||||||
|
path: local
|
||||||
|
name: password
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: kubernetes
|
||||||
|
name: publish
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: local.io/jcollie/nixos-runner:latest
|
||||||
|
pull: always
|
||||||
|
commands:
|
||||||
|
- nix build .#docker
|
||||||
|
- nix run .#push-container -- result
|
||||||
|
settings:
|
||||||
|
registry: r.ocj.io
|
||||||
|
repository: backup/restic
|
||||||
|
username:
|
||||||
|
from_secret: local_username
|
||||||
|
password:
|
||||||
|
from_secret: local_password
|
176
entrypoint.nu
Normal file
176
entrypoint.nu
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
|
||||||
|
def healthcheck_start [
|
||||||
|
url: record
|
||||||
|
] {
|
||||||
|
if not ($env | get -i HEALTHCHECK_URL | is-empty ) {
|
||||||
|
http get --full --max-time 10 ($url | update path $"($in.path)/start" | url join) | ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def healthcheck_log [
|
||||||
|
url: record
|
||||||
|
log: string
|
||||||
|
] {
|
||||||
|
if not ($url | is-empty ) {
|
||||||
|
http post --max-time 10 ($url | update path $"($in.path)/log" | url join) $log | ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def healthcheck_fail [
|
||||||
|
url: record
|
||||||
|
log: string
|
||||||
|
] {
|
||||||
|
if not ($url | is-empty ) {
|
||||||
|
http post --full --max-time 10 ($url | update path $"($in.path)/fail" | url join) $log | ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def healthcheck_stop [
|
||||||
|
url: record
|
||||||
|
] {
|
||||||
|
if not ($url | is-empty ) {
|
||||||
|
http get --full --max-time 10 ($url | url join) | ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def main [
|
||||||
|
--rclone: string # path to rclone binary
|
||||||
|
--restic: string # path to restic binary
|
||||||
|
--cache-dir: string # path to cache dir
|
||||||
|
] {
|
||||||
|
let rid = (random uuid)
|
||||||
|
|
||||||
|
let healthcheck_url = if not ($env | get -i HEALTHCHECK_URL | is-empty) {
|
||||||
|
$env.HEALTHCHECK_URL | url parse | update params ($in | get params | insert rid $rid)
|
||||||
|
} else {
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
|
||||||
|
healthcheck_start $healthcheck_url
|
||||||
|
|
||||||
|
alias rclone = ^$rclone
|
||||||
|
alias restic = ^$restic --cache-dir $cache_dir --option "b2.connections=128"
|
||||||
|
|
||||||
|
if ($env | get -i BACKUP_PATHS | is-empty) {
|
||||||
|
print "BACKUP_PATHS not set."
|
||||||
|
healthcheck_fail $healthcheck_url "BACKUP_PATHS not set."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
let paths = ($env.BACKUP_PATHS | split row " ")
|
||||||
|
|
||||||
|
let not_exists = ($paths | path exists | enumerate | filter { |r| not $r.item })
|
||||||
|
|
||||||
|
if ($not_exists | length ) > 0 {
|
||||||
|
let error = ("error: some backup paths do not exist:\n\n" + ($not_exists | each { |r| ($paths | get $r.index) } | str join "\n") + "\n")
|
||||||
|
print ($error | str trim --right)
|
||||||
|
healthcheck_fail $healthcheck_url $error
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$paths | each { |r| print $"BACKUP_PATH: ($r)" }
|
||||||
|
|
||||||
|
if ($env | get -i B2_ACCOUNT_ID | is-empty) {
|
||||||
|
print "B2_ACCOUNT_ID not set."
|
||||||
|
healthcheck_fail $healthcheck_url "B2_ACCOUNT_ID not set."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($env | get -i B2_ACCOUNT_KEY | is-empty) {
|
||||||
|
print "B2_ACCOUNT_KEY not set."
|
||||||
|
healthcheck_fail $healthcheck_url "B2_ACCOUNT_KEY not set."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($env | get -i B2_BUCKET | is-empty) {
|
||||||
|
print "B2_BUCKET not set."
|
||||||
|
healthcheck_fail $healthcheck_url "B2_BUCKET not set."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
print $"B2_BUCKET: ($env.B2_BUCKET)"
|
||||||
|
|
||||||
|
if ($env | get -i HOSTNAME | is-empty) {
|
||||||
|
print "HOSTNAME not set."
|
||||||
|
healthcheck_fail $healthcheck_url "HOSTNAME not set."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
print $"HOSTNAME: ($env.HOSTNAME)"
|
||||||
|
|
||||||
|
if ($env | get -i RESTIC_PASSWORD_FILE | is-empty) {
|
||||||
|
print "RESTIC_PASSWORD_FILE not set."
|
||||||
|
healthcheck_fail $healthcheck_url "RESTIC_PASSWORD_FILE not set."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# $env.RESTIC_PASSWORD | save --force --raw "/tmp/password"
|
||||||
|
|
||||||
|
let-env RESTIC_PASSWORD_FILE = "/tmp/password"
|
||||||
|
|
||||||
|
let-env RESTIC_REPOSITORY = $"b2:($env.B2_BUCKET):($env.HOSTNAME)"
|
||||||
|
|
||||||
|
let-env RCLONE_CONFIG = "/dev/null"
|
||||||
|
let-env RCLONE_CONFIG_B2_TYPE = "b2"
|
||||||
|
let-env RCLONE_CONFIG_B2_ACCOUNT = $env.B2_ACCOUNT_ID
|
||||||
|
let-env RCLONE_CONFIG_B2_KEY = $env.B2_ACCOUNT_KEY
|
||||||
|
|
||||||
|
if (rclone lsjson $"b2:($env.B2_BUCKET)/($env.HOSTNAME)/config" | from json | length | $in == 0) {
|
||||||
|
let result = (do {restic init} | complete)
|
||||||
|
if $result.exit_code != 0 {
|
||||||
|
print "restic init failed"
|
||||||
|
print ($result.stderr | str trim --right)
|
||||||
|
healthcheck_fail $healthcheck_url $result.stderr
|
||||||
|
exit 1
|
||||||
|
} else {
|
||||||
|
healthcheck_log $healthcheck_url "restic repository initialized"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = (do {restic backup --host $env.HOSTNAME $paths} | complete)
|
||||||
|
if $result.exit_code == 1 {
|
||||||
|
print "restic backup failed"
|
||||||
|
print ($result.stderr | str trim --right)
|
||||||
|
healthcheck_fail $healthcheck_url $result.stderr
|
||||||
|
exit 1
|
||||||
|
} else if $result.exit_code == 3 {
|
||||||
|
print "restic backup incomplete"
|
||||||
|
print ($result.stderr | str trim --right)
|
||||||
|
healthcheck_log $healthcheck_url $result.stderr
|
||||||
|
} else {
|
||||||
|
print ($result.stdout | str trim --right)
|
||||||
|
healthcheck_log $healthcheck_url $result.stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = (do {restic forget --host $env.HOSTNAME --prune --keep-within 14d} | complete)
|
||||||
|
if $result.exit_code != 0 {
|
||||||
|
print "restic forget failed"
|
||||||
|
print ($result.stderr | str trim --right)
|
||||||
|
healthcheck_fail $healthcheck_url $result.stderr
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
print ($result.stdout | str trim --right)
|
||||||
|
healthcheck_log $healthcheck_url $result.stdout
|
||||||
|
|
||||||
|
let result = (do {restic check} | complete)
|
||||||
|
if $result.exit_code != 0 {
|
||||||
|
print "restic check failed"
|
||||||
|
print ($result.stderr | str trim --right)
|
||||||
|
healthcheck_fail $healthcheck_url $result.stderr
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
print ($result.stdout | str trim --right)
|
||||||
|
healthcheck_log $healthcheck_url $result.stdout
|
||||||
|
|
||||||
|
let result = (do {restic cache --cleanup} | complete)
|
||||||
|
if $result.exit_code != 0 {
|
||||||
|
print "restic cache failed"
|
||||||
|
print ($result.stderr | str trim)
|
||||||
|
healthcheck_fail $healthcheck_url $result.stderr
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
print ($result.stdout | str trim --right)
|
||||||
|
healthcheck_log $healthcheck_url $result.stdout
|
||||||
|
|
||||||
|
healthcheck_stop $healthcheck_url
|
||||||
|
}
|
30
flake.lock
30
flake.lock
|
@ -1,12 +1,15 @@
|
||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1676283394,
|
"lastModified": 1681202837,
|
||||||
"narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=",
|
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073",
|
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -17,11 +20,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1678470307,
|
"lastModified": 1681920287,
|
||||||
"narHash": "sha256-OEeMUr3ueLIXyW/OaFUX5jUdimyQwMg/7e+/Q0gC/QE=",
|
"narHash": "sha256-+/d6XQQfhhXVfqfLROJoqj3TuG38CAeoT6jO1g9r1k0=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "0c4800d579af4ed98ecc47d464a5e7b0870c4b1f",
|
"rev": "645bc49f34fa8eff95479f0345ff57e55b53437e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -35,6 +38,21 @@
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|
52
flake.nix
52
flake.nix
|
@ -23,17 +23,17 @@
|
||||||
let
|
let
|
||||||
pname = "restic";
|
pname = "restic";
|
||||||
version = "0.15.1";
|
version = "0.15.1";
|
||||||
sha256 = "sha256-KdPslVJHH+xdUuFfmLZumP2lHzkDrrAvpDaj38SuP8o=";
|
hash = "sha256-KdPslVJHH+xdUuFfmLZumP2lHzkDrrAvpDaj38SuP8o=";
|
||||||
vendorSha256 = "sha256-oetaCiXWEBUEf382l4sjO0SCPxkoh+bMTgIf/qJTQms=";
|
vendorHash = "sha256-oetaCiXWEBUEf382l4sjO0SCPxkoh+bMTgIf/qJTQms=";
|
||||||
in
|
in
|
||||||
pkgs.buildGoModule {
|
pkgs.buildGoModule {
|
||||||
inherit pname version;
|
inherit pname version vendorHash;
|
||||||
|
|
||||||
src = pkgs.fetchFromGitHub {
|
src = pkgs.fetchFromGitHub {
|
||||||
owner = "restic";
|
owner = "restic";
|
||||||
repo = "restic";
|
repo = "restic";
|
||||||
rev = "v${version}";
|
rev = "v${version}";
|
||||||
sha256 = sha256;
|
hash = hash;
|
||||||
};
|
};
|
||||||
|
|
||||||
patches = [
|
patches = [
|
||||||
|
@ -41,11 +41,12 @@
|
||||||
./0001-Skip-testing-restore-with-permission-failure.patch
|
./0001-Skip-testing-restore-with-permission-failure.patch
|
||||||
];
|
];
|
||||||
|
|
||||||
vendorSha256 = vendorSha256;
|
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
@ -72,6 +73,43 @@
|
||||||
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 {
|
||||||
|
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"
|
||||||
|
];
|
||||||
|
Env = [
|
||||||
|
"TZ=UTC"
|
||||||
|
"SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
|
||||||
|
];
|
||||||
|
ExposedPorts = { };
|
||||||
|
Volumes = {
|
||||||
|
"/cache" = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
) // {
|
) // {
|
||||||
|
|
Loading…
Reference in a new issue