| 1 | { |
| 2 | config, |
| 3 | lib, |
| 4 | ... |
| 5 | }: |
| 6 | |
| 7 | let |
| 8 | metricsPort = 9811; |
| 9 | in |
| 10 | { |
| 11 | age.secrets."zrepl-ssh-key" = { |
| 12 | file = ../secrets/zrepl-ssh-key.age; |
| 13 | mode = "0400"; |
| 14 | }; |
| 15 | |
| 16 | programs.ssh = { |
| 17 | knownHosts = { |
| 18 | rsync-net = { |
| 19 | hostNames = [ |
| 20 | "zh4461b.rsync.net" |
| 21 | "2001:1620:2019::336" |
| 22 | ]; |
| 23 | publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILtF46LwRn+hC9vuw0vedXBKGNPMSIqrXdxl+EQOI/8J"; |
| 24 | }; |
| 25 | }; |
| 26 | }; |
| 27 | |
| 28 | services.zrepl = |
| 29 | let |
| 30 | defaultBackupJob = { |
| 31 | type = "push"; |
| 32 | filesystems."zroot/pg<" = true; |
| 33 | snapshotting = { |
| 34 | type = "periodic"; |
| 35 | interval = "30m"; |
| 36 | prefix = "zrepl_snap_"; |
| 37 | hooks = [ |
| 38 | { |
| 39 | # https://zrepl.github.io/configuration/snapshotting.html#postgres-checkpoint-hook |
| 40 | # CREATE ROLE zrepl LOGIN; |
| 41 | # GRANT pg_checkpoint TO zrepl; |
| 42 | type = "postgres-checkpoint"; |
| 43 | dsn = "host=/run/postgresql dbname=hydra user=zrepl sslmode=disable"; |
| 44 | filesystems."zroot/pg" = true; |
| 45 | } |
| 46 | ]; |
| 47 | }; |
| 48 | |
| 49 | # The current pruning setup is an exponentially growing scheme, at both sides. |
| 50 | pruning = { |
| 51 | keep_sender = [ |
| 52 | { type = "not_replicated"; } |
| 53 | { |
| 54 | type = "grid"; |
| 55 | regex = "^zrepl_snap_.*"; |
| 56 | grid = lib.concatStringsSep " | " [ |
| 57 | "1x1h(keep=all)" |
| 58 | "1x1h" |
| 59 | "1x2h" |
| 60 | "1x4h" |
| 61 | # "grid" acts weird if an interval isn't a whole-number multiple |
| 62 | # of the previous one, so we jump from 8h to 24h |
| 63 | "2x8h" |
| 64 | "1x1d" |
| 65 | "1x2d" |
| 66 | "1x4d" |
| 67 | "1x8d" |
| 68 | # At this point we keep ~10 snapshots spanning 8--16 days (depends on moment), |
| 69 | # with exponentially increasing spacing (almost). |
| 70 | ]; |
| 71 | } |
| 72 | ]; |
| 73 | keep_receiver = [ |
| 74 | { |
| 75 | type = "grid"; |
| 76 | regex = "^zrepl_snap_.*"; |
| 77 | grid = lib.concatStringsSep " | " [ |
| 78 | "2x1h(keep=all)" |
| 79 | "2x1h" |
| 80 | "2x2h" |
| 81 | "2x4h" |
| 82 | "4x8h" |
| 83 | # At this point the grid spans 2 days by ~13 snapshots. |
| 84 | # (See note above about 8h -> 24h.) |
| 85 | "2x1d" |
| 86 | "2x2d" |
| 87 | "2x4d" |
| 88 | "2x8d" |
| 89 | "2x16d" |
| 90 | "2x32d" |
| 91 | "2x64d" |
| 92 | "2x128d" |
| 93 | # At this point we keep ~29 snapshots spanning 384--512 days (depends on moment), |
| 94 | # with exponentially increasing spacing (almost). |
| 95 | ]; |
| 96 | } |
| 97 | ]; |
| 98 | }; |
| 99 | }; |
| 100 | in |
| 101 | { |
| 102 | enable = true; |
| 103 | settings = { |
| 104 | global = { |
| 105 | logging = [ |
| 106 | { |
| 107 | type = "syslog"; |
| 108 | level = "info"; |
| 109 | format = "human"; |
| 110 | } |
| 111 | ]; |
| 112 | |
| 113 | # https://zrepl.github.io/configuration/monitoring.html |
| 114 | monitoring = [ |
| 115 | { |
| 116 | type = "prometheus"; |
| 117 | listen = ":${toString metricsPort}"; |
| 118 | } |
| 119 | ]; |
| 120 | }; |
| 121 | |
| 122 | jobs = [ |
| 123 | # Covers 20240629+ |
| 124 | ( |
| 125 | defaultBackupJob |
| 126 | // { |
| 127 | name = "rsyncnet"; |
| 128 | connect = { |
| 129 | identity_file = config.age.secrets."zrepl-ssh-key".path; |
| 130 | type = "ssh+stdinserver"; |
| 131 | host = "zh4461b.rsync.net"; |
| 132 | user = "root"; |
| 133 | port = 22; |
| 134 | }; |
| 135 | } |
| 136 | ) |
| 137 | /* |
| 138 | rsync.net provides a VM with FreeBSD |
| 139 | - almost nothing is preserved on upgrades except this "data1" zpool |
| 140 | $ scp ./zrepl.yml root@zh4461b.rsync.net:/usr/local/etc/zrepl/zrepl.yml |
| 141 | # pkg install zrepl |
| 142 | # service zrepl enable |
| 143 | # service zrepl start |
| 144 | */ |
| 145 | ]; |
| 146 | }; |
| 147 | }; |
| 148 | |
| 149 | networking.firewall.extraInputRules = '' |
| 150 | ip6 saddr $prometheus_inet6 tcp dport ${toString metricsPort} accept |
| 151 | ip saddr $prometheus_inet4 tcp dport ${toString metricsPort} accept |
| 152 | ''; |
| 153 | } |