diff options
author | tv <tv@krebsco.de> | 2018-04-24 19:28:09 +0200 |
---|---|---|
committer | tv <tv@krebsco.de> | 2018-04-24 19:28:09 +0200 |
commit | 21053de317e838c06a20425bdb3e81b7ac132d83 (patch) | |
tree | d722f9c2a525d6d66310da5e86dbcff73c79672a | |
parent | 0fe9b28302c905523f2ecefadfd167e1547785f9 (diff) | |
parent | c99e8256b223761eb50cf5d6841ab64f989851c3 (diff) |
Merge remote-tracking branch 'prism/master'
31 files changed, 650 insertions, 87 deletions
diff --git a/krebs/1systems/onebutton/config.nix b/krebs/1systems/onebutton/config.nix new file mode 100644 index 000000000..c634d73ce --- /dev/null +++ b/krebs/1systems/onebutton/config.nix @@ -0,0 +1,44 @@ +{ config, pkgs, lib, ... }: +{ + imports = [ + <stockholm/krebs> + <stockholm/krebs/2configs> + { # minimal disk usage + environment.noXlibs = true; + nix.gc.automatic = true; + nix.gc.dates = "03:10"; + programs.info.enable = false; + programs.man.enable = false; + services.journald.extraConfig = "SystemMaxUse=50M"; + services.nixosManual.enable = false; + } + ]; + krebs.build.host = config.krebs.hosts.onebutton; + # NixOS wants to enable GRUB by default + boot.loader.grub.enable = false; + # Enables the generation of /boot/extlinux/extlinux.conf + boot.loader.generic-extlinux-compatible.enable = true; + + # !!! If your board is a Raspberry Pi 1, select this: + boot.kernelPackages = pkgs.linuxPackages_rpi; + + nix.binaryCaches = [ "http://nixos-arm.dezgeg.me/channel" ]; + nix.binaryCachePublicKeys = [ "nixos-arm.dezgeg.me-1:xBaUKS3n17BZPKeyxL4JfbTqECsT+ysbDJz29kLFRW0=%" ]; + + # !!! Needed for the virtual console to work on the RPi 3, as the default of 16M doesn't seem to be enough. + # boot.kernelParams = ["cma=32M"]; + + fileSystems = { + "/boot" = { + device = "/dev/disk/by-label/NIXOS_BOOT"; + fsType = "vfat"; + }; + "/" = { + device = "/dev/disk/by-label/NIXOS_SD"; + fsType = "ext4"; + }; + }; + + swapDevices = [ { device = "/swapfile"; size = 1024; } ]; + services.openssh.enable = true; +} diff --git a/krebs/1systems/onebutton/source.nix b/krebs/1systems/onebutton/source.nix new file mode 100644 index 000000000..8f25881c9 --- /dev/null +++ b/krebs/1systems/onebutton/source.nix @@ -0,0 +1,16 @@ +with import <stockholm/lib>; +let + pkgs = import <nixpkgs> {}; + nixpkgs = pkgs.fetchFromGitHub { + owner = "nixos"; + repo = "nixpkgs-channels"; + rev = "6c064e6b"; # only binary cache for unstable arm6 + sha256 = "1rqzh475xn43phagrr30lb0fd292c1s8as53irihsnd5wcksnbyd"; + }; +in import <stockholm/krebs/source.nix> { + name = "onebutton"; + override.nixpkgs = mkForce { + file = toString nixpkgs; + }; + +} diff --git a/krebs/3modules/krebs/default.nix b/krebs/3modules/krebs/default.nix index 1e626f0a0..a916c1873 100644 --- a/krebs/3modules/krebs/default.nix +++ b/krebs/3modules/krebs/default.nix @@ -91,6 +91,37 @@ in { ssh.privkey.path = <secrets/ssh.id_ed25519>; ssh.pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICxFkBln23wUxt4RhIHE3GvdKeBpJbjn++6maupHqUHp"; }; + onebutton = { + cores = 1; + owner = config.krebs.users.krebs; + nets = { + retiolum = { + ip4.addr = "10.243.0.101"; + ip6.addr = "42:0:0:0:0:0:0:101"; + aliases = [ + "onebutton.r" + ]; + tinc.pubkey = '' + -----BEGIN PUBLIC KEY----- + MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA11w6votRExwE0ZEiQmPa + 9WGNsMfNAZEd14iHaHCZH7UPQEH+cH/T6isGPpaysindroMnqFe9mUf/cdYChb6N + aaFreApwGBQaJPUcdy4cfphrFpzmOClpOFuFbnV7ZvAk/wefBad3kUzsq/lK4HvB + 7nPKeOB9kljphLrkzuLL/h2yOenMpO2ZdvwxyWN8HKmUNgvpBQjIr+Hka6cgy7Gp + pBVFHfSnad/eHeEvq91O/bHxrAxzH5N5DVagPDpkbiWYGl+0XVGP/h0CApr15Ael + +j2pJYc0ZlaXIp4KmNRqbd/fLe52JLrWbnFX4rRuY/DhoMqK8kjECEZ7gLiNSpCC + KlnlJ2LXX9c+d79ubzl5yLAJ3d6T4IJqkbAWJDuCrj821M9ZDk/qZwerayhrrvkF + tMYkQoGSe8MvSOU0rTEoH5iSRwDC7M0XzUe4l8/yZLFyD4Prz/dq6coqANfk/tlE + DnH3vDu9lmFvYrLcd6yDWzFfI3mWDJoUa6AKKoScCOaCkRfIM4Aew0i73+h1nJLO + 59AAbZIkDYyWs53QniIG4EQteI9y/9j/628nPAVj68V5oIN76RDXfFHWDWq4DxmU + PpGVmoIKcKZmnl7RrDomRVpuGMdyQ+kCzIGH3XYe12v8Y5beHZBrd3OajgHZ/Tfp + jP873cT6h0hsGm9glgOYho8CAwEAAQ== + -----END PUBLIC KEY----- + ''; + }; + }; + ssh.privkey.path = <secrets/ssh.id_ed25519>; + ssh.pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAcZg+iLaPZ0SpLM+nANxIjZC/RIsansjyutK0+gPhIe "; + }; puyak = { ci = true; owner = config.krebs.users.krebs; diff --git a/krebs/5pkgs/simple/Reaktor/plugins.nix b/krebs/5pkgs/simple/Reaktor/plugins.nix index bcfcbf76b..f3b771190 100644 --- a/krebs/5pkgs/simple/Reaktor/plugins.nix +++ b/krebs/5pkgs/simple/Reaktor/plugins.nix @@ -120,11 +120,24 @@ rec { url-title = (buildSimpleReaktorPlugin "url-title" { pattern = "^.*(?P<args>http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+).*$$"; path = with pkgs; [ curl perl ]; - script = pkgs.writeDash "lambda-pl" '' - if [ "$#" -gt 0 ]; then - curl -SsL --max-time 5 "$1" | - perl -l -0777 -ne 'print $1 if /<title.*?>\s*(.*?)\s*<\/title/si' - fi + script = pkgs.writePython3 [ "beautifulsoup4" "lxml" ] "url-title" '' + import sys + import urllib.request + from bs4 import BeautifulSoup + + try: + soup = BeautifulSoup(urllib.request.urlopen(sys.argv[1]), "lxml") + title = soup.find('title').string + + if title: + if len(title) > 512: + print('message to long, skipped') + elif len(title.split('\n')) > 5: + print('to many lines, skipped') + else: + print(title) + except: # noqa: E722 + pass ''; }); diff --git a/krebs/5pkgs/simple/generate-secrets/default.nix b/krebs/5pkgs/simple/generate-secrets/default.nix new file mode 100644 index 000000000..a800ff543 --- /dev/null +++ b/krebs/5pkgs/simple/generate-secrets/default.nix @@ -0,0 +1,46 @@ +{ pkgs }: +pkgs.writeDashBin "generate-secrets" '' + HOSTNAME="$1" + TMPDIR=$(${pkgs.coreutils}/bin/mktemp -d) + PASSWORD=$(${pkgs.pwgen}/bin/pwgen 25 1) + HASHED_PASSWORD=$(echo $PASSWORD | ${pkgs.hashPassword}/bin/hashPassword -s) > /dev/null + + ${pkgs.openssh}/bin/ssh-keygen -t ed25519 -f $TMPDIR/ssh.id_ed25519 -P "" -C "" >/dev/null + ${pkgs.openssl}/bin/openssl genrsa -out $TMPDIR/retiolum.rsa_key.priv 4096 2>/dev/null > /dev/null + ${pkgs.openssl}/bin/openssl rsa -in $TMPDIR/retiolum.rsa_key.priv -pubout -out $TMPDIR/retiolum.rsa_key.pub 2>/dev/null > /dev/null + cat <<EOF > $TMPDIR/hashedPasswords.nix + { + root = "$HASHED_PASSWORD"; + } + EOF + + cd $TMPDIR + for x in *; do + ${pkgs.coreutils}/bin/cat $x | ${pkgs.brain}/bin/brain insert -m krebs-secrets/$HOSTNAME/$x > /dev/null + done + echo $PASSWORD | ${pkgs.brain}/bin/brain insert -m hosts/$HOSTNAME/root > /dev/null + + cat <<EOF + $HOSTNAME = { + cores = 1; + owner = config.krebs.users.krebs; + nets = { + retiolum = { + ip4.addr = "10.243.0.changeme"; + ip6.addr = "42:0:0:0:0:0:0:changeme"; + aliases = [ + "$HOSTNAME.r" + ]; + tinc.pubkey = ${"''"} + $(cat $TMPDIR/retiolum.rsa_key.pub) + ${"''"}; + }; + }; + ssh.privkey.path = <secrets/ssh.id_ed25519>; + ssh.pubkey = "$(cat $TMPDIR/ssh.id_ed25519.pub)"; + }; + EOF + + rm -rf $TMPDIR +'' + diff --git a/lass/1systems/mors/config.nix b/lass/1systems/mors/config.nix index cd259d0fe..c59494e4d 100644 --- a/lass/1systems/mors/config.nix +++ b/lass/1systems/mors/config.nix @@ -141,6 +141,15 @@ with import <stockholm/lib>; dnsutils generate-secrets + (pkgs.writeDashBin "btc-coinbase" '' + ${pkgs.curl}/bin/curl -Ss 'https://api.coinbase.com/v2/prices/spot?currency=EUR' | ${pkgs.jq}/bin/jq '.data.amount' + '') + (pkgs.writeDashBin "btc-wex" '' + ${pkgs.curl}/bin/curl -Ss 'https://wex.nz/api/3/ticker/btc_eur' | ${pkgs.jq}/bin/jq '.btc_eur.avg' + '') + (pkgs.writeDashBin "btc-kraken" '' + ${pkgs.curl}/bin/curl -Ss 'https://api.kraken.com/0/public/Ticker?pair=BTCEUR' | ${pkgs.jq}/bin/jq '.result.XXBTZEUR.a[0]' + '') ]; #TODO: fix this shit @@ -177,4 +186,34 @@ with import <stockholm/lib>; programs.adb.enable = true; users.users.mainUser.extraGroups = [ "adbusers" "docker" ]; virtualisation.docker.enable = true; + + lass.restic = genAttrs [ + "daedalus" + "icarus" + "littleT" + "prism" + "shodan" + "skynet" + ] (dest: { + dirs = [ + "/home/lass/src" + "/home/lass/work" + "/home/lass/.gnupg" + "/home/lass/Maildir" + "/home/lass/stockholm" + "/home/lass/.password-store" + "/home/bitcoin" + "/home/bch" + ]; + passwordFile = (toString <secrets>) + "/restic/${dest}"; + repo = "sftp:backup@${dest}.r:/backups/mors"; + #sshPrivateKey = config.krebs.build.host.ssh.privkey.path; + extraArguments = [ + "sftp.command='ssh backup@${dest}.r -i ${config.krebs.build.host.ssh.privkey.path} -s sftp'" + ]; + timerConfig = { + OnCalendar = "00:05"; + RandomizedDelaySec = "5h"; + }; + }); } diff --git a/lass/1systems/prism/config.nix b/lass/1systems/prism/config.nix index c0e4620cc..983604f8e 100644 --- a/lass/1systems/prism/config.nix +++ b/lass/1systems/prism/config.nix @@ -8,11 +8,15 @@ in { imports = [ <stockholm/lass> { - networking.interfaces.et0.ip4 = [ + networking.interfaces.et0.ipv4.addresses = [ { address = ip; prefixLength = 27; } + { + address = "46.4.114.243"; + prefixLength = 27; + } ]; networking.defaultGateway = "46.4.114.225"; networking.nameservers = [ @@ -110,29 +114,13 @@ in { }; # TODO write function for proxy_pass (ssl/nonssl) - services.nginx.virtualHosts."hackerfleet.de" = { - serverAliases = [ - "*.hackerfleet.de" - ]; - locations."/".extraConfig = '' - proxy_pass http://192.168.122.92:80; - ''; - }; - services.nginx.virtualHosts."hackerfleet.de-s" = { - serverName = "hackerfleet.de"; - listen = [ - { - addr = "0.0.0.0"; - port = 443; - } - ]; - serverAliases = [ - "*.hackerfleet.de" - ]; - locations."/".extraConfig = '' - proxy_pass http://192.168.122.92:443; - ''; - }; + + krebs.iptables.tables.filter.FORWARD.rules = [ + { v6 = false; precedence = 1000; predicate = "-d 192.168.122.92"; target = "ACCEPT"; } + ]; + krebs.iptables.tables.nat.PREROUTING.rules = [ + { v6 = false; precedence = 1000; predicate = "-d 46.4.114.243"; target = "DNAT --to-destination 192.168.122.92"; } + ]; } { users.users.tv = { diff --git a/lass/2configs/monitoring/node-exporter.nix b/lass/2configs/monitoring/node-exporter.nix new file mode 100644 index 000000000..8c27e90d4 --- /dev/null +++ b/lass/2configs/monitoring/node-exporter.nix @@ -0,0 +1,13 @@ +{ config, lib, pkgs, ... }: +{ + networking.firewall.allowedTCPPorts = [ 9100 ]; + + services.prometheus.exporters = { + node = { + enable = true; + enabledCollectors = [ + "systemd" + ]; + }; + }; +} diff --git a/lass/2configs/monitoring/prometheus-server.nix b/lass/2configs/monitoring/prometheus-server.nix new file mode 100644 index 000000000..d56d7e552 --- /dev/null +++ b/lass/2configs/monitoring/prometheus-server.nix @@ -0,0 +1,179 @@ +{ pkgs, lib, config, ... }: +{ + #networking = { + # firewall.allowedTCPPorts = [ + # 3000 # grafana + # 9090 # prometheus + # 9093 # alertmanager + # ]; + # useDHCP = true; + #}; + + services = { + prometheus = { + enable = true; + extraFlags = [ + "-storage.local.retention 8760h" + "-storage.local.series-file-shrink-ratio 0.3" + "-storage.local.memory-chunks 2097152" + "-storage.local.max-chunks-to-persist 1048576" + "-storage.local.index-cache-size.fingerprint-to-metric 2097152" + "-storage.local.index-cache-size.fingerprint-to-timerange 1048576" + "-storage.local.index-cache-size.label-name-to-label-values 2097152" + "-storage.local.index-cache-size.label-pair-to-fingerprints 41943040" + ]; + alertmanagerURL = [ "http://localhost:9093" ]; + rules = [ + '' + ALERT node_down + IF up == 0 + FOR 5m + LABELS { + severity="page" + } + ANNOTATIONS { + summary = "{{$labels.alias}}: Node is down.", + description = "{{$labels.alias}} has been down for more than 5 minutes." + } + ALERT node_systemd_service_failed + IF node_systemd_unit_state{state="failed"} == 1 + FOR 4m + LABELS { + severity="page" + } + ANNOTATIONS { + summary = "{{$labels.alias}}: Service {{$labels.name}} failed to start.", + description = "{{$labels.alias}} failed to (re)start service {{$labels.name}}." + } + ALERT node_filesystem_full_90percent + IF sort(node_filesystem_free{device!="ramfs"} < node_filesystem_size{device!="ramfs"} * 0.1) / 1024^3 + FOR 5m + LABELS { + severity="page" + } + ANNOTATIONS { + summary = "{{$labels.alias}}: Filesystem is running out of space soon.", + description = "{{$labels.alias}} device {{$labels.device}} on {{$labels.mountpoint}} got less than 10% space left on its filesystem." + } + ALERT node_filesystem_full_in_4h + IF predict_linear(node_filesystem_free{device!="ramfs"}[1h], 4*3600) <= 0 + FOR 5m + LABELS { + severity="page" + } + ANNOTATIONS { + summary = "{{$labels.alias}}: Filesystem is running out of space in 4 hours.", + description = "{{$labels.alias}} device {{$labels.device}} on {{$labels.mountpoint}} is running out of space of in approx. 4 hours" + } + ALERT node_filedescriptors_full_in_3h + IF predict_linear(node_filefd_allocated[1h], 3*3600) >= node_filefd_maximum + FOR 20m + LABELS { + severity="page" + } + ANNOTATIONS { + summary = "{{$labels.alias}} is running out of available file descriptors in 3 hours.", + description = "{{$labels.alias}} is running out of available file descriptors in approx. 3 hours" + } + ALERT node_load1_90percent + IF node_load1 / on(alias) count(node_cpu{mode="system"}) by (alias) >= 0.9 + FOR 1h + LABELS { + severity="page" + } + ANNOTATIONS { + summary = "{{$labels.alias}}: Running on high load.", + description = "{{$labels.alias}} is running with > 90% total load for at least 1h." + } + ALERT node_cpu_util_90percent + IF 100 - (avg by (alias) (irate(node_cpu{mode="idle"}[5m])) * 100) >= 90 + FOR 1h + LABELS { + severity="page" + } + ANNOTATIONS { + summary = "{{$labels.alias}}: High CPU utilization.", + description = "{{$labels.alias}} has total CPU utilization over 90% for at least 1h." + } + ALERT node_ram_using_90percent + IF node_memory_MemFree + node_memory_Buffers + node_memory_Cached < node_memory_MemTotal * 0.1 + FOR 30m + LABELS { + severity="page" + } + ANNOTATIONS { + summary="{{$labels.alias}}: Using lots of RAM.", + description="{{$labels.alias}} is using at least 90% of its RAM for at least 30 minutes now.", + } + ALERT node_swap_using_80percent + IF node_memory_SwapTotal - (node_memory_SwapFree + node_memory_SwapCached) > node_memory_SwapTotal * 0.8 + FOR 10m + LABELS { + severity="page" + } + ANNOTATIONS { + summary="{{$labels.alias}}: Running out of swap soon.", + description="{{$labels.alias}} is using 80% of its swap space for at least 10 minutes now." + } + '' + ]; + scrapeConfigs = [ + { + job_name = "node"; + scrape_interval = "10s"; + static_configs = [ + { + targets = [ + "localhost:9100" + ]; + labels = { + alias = "prometheus.example.com"; + }; + } + ]; + } + ]; + alertmanager = { + enable = true; + listenAddress = "0.0.0.0"; + configuration = { + "global" = { + "smtp_smarthost" = "smtp.example.com:587"; + "smtp_from" = "alertmanager@example.com"; + }; + "route" = { + "group_by" = [ "alertname" "alias" ]; + "group_wait" = "30s"; + "group_interval" = "2m"; + "repeat_interval" = "4h"; + "receiver" = "team-admins"; + }; + "receivers" = [ + { + "name" = "team-admins"; + "email_configs" = [ + { + "to" = "devnull@example.com"; + "send_resolved" = true; + } + ]; + "webhook_configs" = [ + { + "url" = "https://example.com/prometheus-alerts"; + "send_resolved" = true; + } + ]; + } + ]; + }; + }; + }; + grafana = { + enable = true; + addr = "0.0.0.0"; + domain = "grafana.example.com"; + rootUrl = "https://grafana.example.com/"; + security = import <secrets/grafana_security.nix>; # { AdminUser = ""; adminPassword = ""} + }; + }; +} diff --git a/lass/2configs/reaktor-coders.nix b/lass/2configs/reaktor-coders.nix index 5fa1611ae..5a39f7115 100644 --- a/lass/2configs/reaktor-coders.nix +++ b/lass/2configs/reaktor-coders.nix @@ -4,7 +4,7 @@ with import <stockholm/lib>; { krebs.Reaktor.coders = { nickname = "Reaktor|lass"; - channels = [ "#coders" "#germany" ]; + channels = [ "#coders" "#germany" "#panthermoderns" ]; extraEnviron = { REAKTOR_HOST = "irc.hackint.org"; }; @@ -87,6 +87,19 @@ with import <stockholm/lib>; exec /run/wrappers/bin/ping -q -c1 "$1" 2>&1 | tail -1 ''; }) + (buildSimpleReaktorPlugin "google" { + pattern = "^!g (?P<args>.*)$$"; + script = pkgs.writeDash "google" '' + exec ${pkgs.ddgr}/bin/ddgr -C -n1 --json "$@" | \ + ${pkgs.jq}/bin/jq '@text "\(.[0].abstract) \(.[0].url)"' + ''; + }) + (buildSimpleReaktorPlugin "blockchain" { + pattern = ".*[Bb]lockchain.*$$"; + script = pkgs.writeDash "blockchain" '' + exec echo 'DID SOMEBODY SAY BLOCKCHAIN? https://paste.krebsco.de/r99pMoQq/+inline' + ''; + }) ]; }; } diff --git a/lass/2configs/syncthing.nix b/lass/2configs/syncthing.nix index cef43d1e6..17debf822 100644 --- a/lass/2configs/syncthing.nix +++ b/lass/2configs/syncthing.nix @@ -3,7 +3,6 @@ with import <stockholm/lib>; { services.syncthing = { enable = true; - useInotify = true; }; krebs.iptables.tables.filter.INPUT.rules = [ { predicate = "-p tcp --dport 22000"; target = "ACCEPT";} diff --git a/lass/2configs/websites/util.nix b/lass/2configs/websites/util.nix index 62055d0fd..441b7af90 100644 --- a/lass/2configs/websites/util.nix +++ b/lass/2configs/websites/util.nix @@ -16,7 +16,7 @@ rec { in { services.nginx.virtualHosts.${domain} = { enableACME = true; - enableSSL = true; + onlySSL = true; extraConfig = '' listen 80; listen [::]:80; @@ -34,7 +34,7 @@ rec { in { services.nginx.virtualHosts."${domain}" = { enableACME = true; - enableSSL = true; + onlySSL = true; serverAliases = domains; extraConfig = '' listen 80; @@ -148,7 +148,7 @@ rec { in { services.nginx.virtualHosts."${domain}" = { enableACME = true; - enableSSL = true; + onlySSL = true; serverAliases = domains; extraConfig = '' listen 80; diff --git a/lass/3modules/default.nix b/lass/3modules/default.nix index 0c10e1ec2..5e7e6dff3 100644 --- a/lass/3modules/default.nix +++ b/lass/3modules/default.nix @@ -8,6 +8,7 @@ _: ./mysql-backup.nix ./news.nix ./pyload.nix + ./restic.nix ./screenlock.nix ./umts.nix ./usershadow.nix diff --git a/lass/3modules/restic.nix b/lass/3modules/restic.nix new file mode 100644 index 000000000..c720793b1 --- /dev/null +++ b/lass/3modules/restic.nix @@ -0,0 +1,119 @@ +{ config, lib, pkgs, ... }: + +with import <stockholm/lib>; + +{ + options.lass.restic = mkOption { + type = types.attrsOf (types.submodule ({ config, ... }: { + options = { + name = mkOption { + type = types.str; + default = config._module.args.name; + }; + passwordFile = mkOption { + type = types.str; + default = toString <secrets/restic-password>; + description = '' + read the repository password from a file. + ''; + example = "/etc/nixos/restic-password"; + + }; + repo = mkOption { + type = types.str; + default = "sftp:backup@prism.r:/backups/${config.name}"; + description = '' + repository to backup to. + ''; + example = "sftp:backup@192.168.1.100:/backups/${config.name}"; + }; + dirs = mkOption { + type = types.listOf types.str; + default = []; + description = '' + which directories to backup. + ''; + example = [ + "/var/lib/postgresql" + "/home/user/backup" + ]; + }; + timerConfig = mkOption { + type = types.attrsOf types.str; + default = { + OnCalendar = "daily"; + }; + description = '' + When to run the backup. See man systemd.timer for details. + ''; + example = { + OnCalendar = "00:05"; + RandomizedDelaySec = "5h"; + }; + }; + user = mkOption { + type = types.str; + default = "root"; + description = '' + As which user the backup should run. + ''; + example = "postgresql"; + }; + extraArguments = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Extra arguments to append to the restic command. + ''; + example = [ + "sftp.command='ssh backup@192.168.1.100 -i /home/user/.ssh/id_rsa -s sftp" + ]; + }; + initialize = mkOption { + type = types.bool; + default = false; + description = '' + Create the repository if it doesn't exist. + ''; + }; + }; + })); + default = {}; + }; + + config = { + systemd.services = + mapAttrs' (_: plan: + let + extraArguments = concatMapStringsSep " " (arg: "-o ${arg}") plan.extraArguments; + connectTo = elemAt (splitString ":" plan.repo) 1; + resticCmd = "${pkgs.restic}/bin/restic ${extraArguments}"; + in nameValuePair "backup.${plan.name}" { + environment = { + RESTIC_PASSWORD_FILE = plan.passwordFile; + RESTIC_REPOSITORY = plan.repo; + }; + path = with pkgs; [ + openssh + ]; + restartIfChanged = false; + serviceConfig = { + ExecStartPre = mkIf plan.initialize (pkgs.writeScript "rustic-${plan.name}-init" '' + #! ${pkgs.bash}/bin/bash + ${resticCmd} snapshots || ${resticCmd} init + ''); + ExecStart = pkgs.writeDash "rustic-${plan.name}" ( + " |