From 04c127e14481279f549efd47a918ca6fc0a52b5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Gro=C3=9Fmann?= Date: Sat, 27 Jan 2024 23:21:42 +0100 Subject: [PATCH] feat: forgejo backups feat: maddy --- hosts/elisabeth/default.nix | 2 - hosts/elisabeth/net.nix | 32 +++++++++++++++ .../secrets/cloudflare_api_token.age | 0 .../secrets/ddclient/cloudflare_api_token.age | 13 ++++++ .../gitea/generated/forgejoHetznerSsh.age | Bin 0 -> 1136 bytes .../secrets/gitea/generated/resticpasswd.age | Bin 0 -> 797 bytes hosts/maddy/default.nix | 1 + hosts/maddy/fs.nix | 1 - hosts/maddy/net.nix | 31 +++++++++++++++ hosts/maddy/secrets/cloudflare_api_token.age | 15 +++++++ modules/config/users.nix | 1 + modules/services/acme.nix | 37 ------------------ modules/services/ddclient.nix | 2 +- modules/services/gitea.nix | 32 +++++++++++++++ modules/services/maddy.nix | 7 ++++ secrets/secrets.nix.age | Bin 5151 -> 5224 bytes 16 files changed, 133 insertions(+), 41 deletions(-) rename secrets/cloudflare/api_token.age => hosts/elisabeth/secrets/cloudflare_api_token.age (100%) create mode 100644 hosts/elisabeth/secrets/ddclient/cloudflare_api_token.age create mode 100644 hosts/elisabeth/secrets/gitea/generated/forgejoHetznerSsh.age create mode 100644 hosts/elisabeth/secrets/gitea/generated/resticpasswd.age create mode 100644 hosts/maddy/secrets/cloudflare_api_token.age delete mode 100644 modules/services/acme.nix create mode 100644 modules/services/maddy.nix diff --git a/hosts/elisabeth/default.nix b/hosts/elisabeth/default.nix index 8989a61..7af29ee 100644 --- a/hosts/elisabeth/default.nix +++ b/hosts/elisabeth/default.nix @@ -19,8 +19,6 @@ ../../modules/hardware/physical.nix ../../modules/hardware/zfs.nix - ../../modules/services/acme.nix - ./net.nix ./fs.nix ] diff --git a/hosts/elisabeth/net.nix b/hosts/elisabeth/net.nix index 29717c9..d6134d4 100644 --- a/hosts/elisabeth/net.nix +++ b/hosts/elisabeth/net.nix @@ -39,4 +39,36 @@ interface = "lan01"; mode = "bridge"; }; + + age.secrets.cloudflare_token_acme = { + rekeyFile = ./secrets/cloudflare_api_token.age; + mode = "440"; + group = "acme"; + }; + security.acme = { + acceptTerms = true; + defaults = { + email = config.secrets.secrets.global.devEmail; + dnsProvider = "cloudflare"; + dnsPropagationCheck = true; + reloadServices = ["nginx"]; + credentialFiles = { + "CF_DNS_API_TOKEN_FILE" = config.age.secrets.cloudflare_token_acme.path; + "CF_ZONE_API_TOKEN_FILE" = config.age.secrets.cloudflare_token_acme.path; + }; + }; + }; + security.acme.certs.web = { + domain = config.secrets.secrets.global.domains.web; + extraDomainNames = ["*.${config.secrets.secrets.global.domains.web}"]; + }; + users.groups.acme.members = ["nginx"]; + environment.persistence."/state".directories = [ + { + directory = "/var/lib/acme"; + user = "acme"; + group = "acme"; + mode = "0755"; + } + ]; } diff --git a/secrets/cloudflare/api_token.age b/hosts/elisabeth/secrets/cloudflare_api_token.age similarity index 100% rename from secrets/cloudflare/api_token.age rename to hosts/elisabeth/secrets/cloudflare_api_token.age diff --git a/hosts/elisabeth/secrets/ddclient/cloudflare_api_token.age b/hosts/elisabeth/secrets/ddclient/cloudflare_api_token.age new file mode 100644 index 0000000..88ecbfb --- /dev/null +++ b/hosts/elisabeth/secrets/ddclient/cloudflare_api_token.age @@ -0,0 +1,13 @@ +age-encryption.org/v1 +-> X25519 j1vdwUZ+o5coMFAaOCyiS42rLq7FPX6xwuWmoHcN61U +m1QEYj4NW5IdNsFh26Uhwe2Sg1ggkvTYB92S4B2lC8M +-> piv-p256 XTQkUA AhjsxoVBz3h/1Sj+cwnT7gpcE6SDMhNOBMU9nP+gfC5G +a7E3dolF4QaxTVpJBOKA314INK32eTdDykDyRT+/8XQ +-> piv-p256 ZFgiIw Ah49xwjTzvroi4R90URbbE0yY15w+OvUsWZ2cQdYHs/w +4i6XZ8lwOeWinlU1IiCgUBTSWMzxuPyvYKRbz6GqNUk +-> piv-p256 ZFgiIw A49Cv751h0WJYL6qPceFVwjbGVpF668SGKVjHq/lQ4Rs +AAGD0jOCHIOAIBk872SJwe2mCx69xn/1ZjiswebgU0w +-> K("0$@8-grease z`/W }"_xiVH <~Bj._ + +--- /NUrs98fD72LqCIYVOzrUhFNhxGivAEOZ9pob65I2fI +8:(#a[8@B4|C7!>?`5or By`AIJ;)&)@BϡQ \ No newline at end of file diff --git a/hosts/elisabeth/secrets/gitea/generated/forgejoHetznerSsh.age b/hosts/elisabeth/secrets/gitea/generated/forgejoHetznerSsh.age new file mode 100644 index 0000000000000000000000000000000000000000..92ff4aee9a1403af32e798906cdaeb87d57e6026 GIT binary patch literal 1136 zcmY+<`)?Bk0KoALH>O3|<^+QXX+#*KD|dZd9}yS(x?Ugade`gglA(Rvde>`j_h_%} zWy&K4To6QEn7|0&%o33dB4b&iD7pawd?aLX8<0(>IGBP#kPHE%{NNA#0pEP`jipF6 z$tHvnm!sR+we3O*kTeQ)lQN=(AdOjBY)L~xyGBJcC#-%|9(MCNM!--z5JxOl*pW?> zK9kk!=Pih+NMZ?A&>>h7^crz0>=%uNJR)VYu!SKu!|O& zQ>LV!#iWQK0wJ1+){V9K1tfyG*dk{!M!n!876GSdS3zmf0Hqv5fGu{TrX=TL?dCY- z5Xp!ga=Z0iWV$Br`eH@QUI?bn5@Q|!TE4Z&l^lSDks`f3Ijsn z<{}i&hDfrM%Y>31ypUrvggzbAgcY@QTW)TRvqkHRi828vbDL2alI3}P5bhF~m{iX>UikvDTt%;8AV z66q?~(1%v62~ej5GA5GBgaQy@z?xu;Ei7)(>XSB|9bwuXd>aOu7*?`EFscRoJVD?n z!G`g;-UNZL!B9}B)oLXvNrq_2>r7B>k`p$OreaI8ON;QukVPk^@@%551xcZxIj9m9 zyLx`zzuQ;wKAxm*lm_Qt`=q1s=!-Mk<^u2L{<{12R=MK%LYwVi|H}iLb6rO|mp1Y1 zw9Y$q(R<op*`MZ_XU&uR8O$>sqJ=B5KH?IuTuf*)Lho@&d_Kj7KzQGM0`TgMZo+ah;zCRoJ zvVI+Mj~&mvx%{pD!#iWcWz$b6t5HtBqVsq|Z@H?Ys{R+>ExAr;h|e7glx>o>KX|8x zH9Y0qv1P;gZ#LfO`=jck&D(q65%an3%zgPlY_w`(!J+k=?u?xg^6PGcyI?@4_-A0K zK6Jio-!sv#o@_e#_a3>day0q*j>cQV(Weian2yWJfs?QAJ$nB{bMN)RiScRBRI7Pu L?Bl|m@4J5i%#pkR literal 0 HcmV?d00001 diff --git a/hosts/elisabeth/secrets/gitea/generated/resticpasswd.age b/hosts/elisabeth/secrets/gitea/generated/resticpasswd.age new file mode 100644 index 0000000000000000000000000000000000000000..bae6ae44dbb933c68346f031cb33c7c461215339 GIT binary patch literal 797 zcmY+=yNlBR0Dy5p1j*n7T$N0sgkGAaNmInbq<2l)JesCSnq&~#Bu&#io1{ro4@5i= zQE_y*!Og)%?*SI9Jm71wd(2Qt(M`wBsADS$9dNv(%NGC3d z&I!wCJ8q`{NkoK*NXJcdQz%4vY`JRLca3&w_^n(*`6x{mSR6Up1cL#FZJ0)rP1yQO zG(v%&rMgWTbXSt>dEx6OLNYTVKzl1gIbmUWAZC4#M2898jW9~FJBjMVRmdvLNEnS= zEhyxFJ87z0*yXYDHq|frT~ntD>$r8y8bGzN|S-dF>qE3 zLV43z@+&>tSPqk-XO0{xJ+_#n;w1JcwZUtqWqPf)4w6BOUT-wPLB0ky*TvgFza@9) ziIhd0hUlvq*Bi)8x`2|jC)bA3a7xutbeV9RSZ~neigY}^fzSQLGPSK>wL-gqq7$}i zW?|VQfJ&tTmc*RSa<`Y&+hd5`R9&W|W~jLIv^I-7g`c>!E@2|VJQu=PW_ipFe_St(c ce%-jdvwNQnj{Y1S-q|{H>YMT7;inIO0VMDcNdN!< literal 0 HcmV?d00001 diff --git a/hosts/maddy/default.nix b/hosts/maddy/default.nix index 25381d0..711c119 100644 --- a/hosts/maddy/default.nix +++ b/hosts/maddy/default.nix @@ -7,6 +7,7 @@ [ ../../modules/config ../../modules/optional/initrd-ssh.nix + ../../modules/services/maddy.nix ../../modules/hardware/zfs.nix diff --git a/hosts/maddy/fs.nix b/hosts/maddy/fs.nix index 0951732..dc67797 100644 --- a/hosts/maddy/fs.nix +++ b/hosts/maddy/fs.nix @@ -28,7 +28,6 @@ fileSystems."/state".neededForBoot = true; fileSystems."/persist".neededForBoot = true; - boot.initrd.luks.devices.enc-rpool.allowDiscards = true; boot.loader.grub.devices = [ "/dev/disk/by-id/${config.secrets.secrets.local.disko.drive}" ]; diff --git a/hosts/maddy/net.nix b/hosts/maddy/net.nix index f4750fc..843738b 100644 --- a/hosts/maddy/net.nix +++ b/hosts/maddy/net.nix @@ -30,4 +30,35 @@ linkConfig.RequiredForOnline = "routable"; }; }; + age.secrets.cloudflare_token_acme = { + rekeyFile = ./secrets/cloudflare_api_token.age; + mode = "440"; + group = "acme"; + }; + security.acme = { + acceptTerms = true; + defaults = { + email = config.secrets.secrets.global.devEmail; + dnsProvider = "cloudflare"; + dnsPropagationCheck = true; + reloadServices = ["nginx"]; + credentialFiles = { + "CF_DNS_API_TOKEN_FILE" = config.age.secrets.cloudflare_token_acme.path; + "CF_ZONE_API_TOKEN_FILE" = config.age.secrets.cloudflare_token_acme.path; + }; + }; + }; + security.acme.certs.mail = { + domain = config.secrets.secrets.global.domains.mail; + extraDomainNames = ["*.${config.secrets.secrets.global.domains.mail}"]; + }; + users.groups.acme.members = ["maddy"]; + environment.persistence."/state".directories = [ + { + directory = "/var/lib/acme"; + user = "acme"; + group = "acme"; + mode = "0755"; + } + ]; } diff --git a/hosts/maddy/secrets/cloudflare_api_token.age b/hosts/maddy/secrets/cloudflare_api_token.age new file mode 100644 index 0000000..5306a75 --- /dev/null +++ b/hosts/maddy/secrets/cloudflare_api_token.age @@ -0,0 +1,15 @@ +age-encryption.org/v1 +-> X25519 uhnRibm92XSz2UcJWT43CrsZfOrSzUyqVFU8nWiYEXs +QNxh6YGDCgSSoCWLthZlou7F7i9OJpunB+/6J4ogk2k +-> piv-p256 XTQkUA AzTDTMXLU5jTp54ysvnVIDo5lIb5ED1zkP8659tTH2JJ +VLO6rtfY5poFGVH/eeD+T/xrlNdPGnlLQ6mK1HytT8A +-> piv-p256 ZFgiIw AnwL/t0GNZI3/y7KlatHLebToW1pJLfOasODGQ7ogriz +Wl7xm6+a1qmqLeTZszpO0XG96BcDRO5l8wvpc0atW0Y +-> piv-p256 5vmPtQ AzC3t9sPdKF/IPkJSqhldnx3Mnkc84DCD13l8tYqZIWd +GaNzRxPoSOy/kEuLzbXpiRDo5F2hZT8KriXpgqZkQ5Y +-> piv-p256 ZFgiIw ApFdJVoW4zoWq38fE27TR/OFEDs4Wub1g3q6RiF+fDTR +IypnQqeluntk31gez5I6eYtlKiY/8sy+dXNkpWhdwPs +-> wX-grease +neAQttCOcpQWsfSpI38jdOjODJYK8uOhqjWsZOLWlHZaRUQtoyXI +--- r44AgWizs6H92oY6hKMs67ARXqr8Je0Z0cIJr9xidBg +Ѩ␟̪Ph\dv _]Ӛ܊ژEʃewIt.W6ZFi \ No newline at end of file diff --git a/modules/config/users.nix b/modules/config/users.nix index aa27fa0..e803184 100644 --- a/modules/config/users.nix +++ b/modules/config/users.nix @@ -25,6 +25,7 @@ vaultwarden = uidGid 215; redis-paperless = uidGid 216; microvm = uidGid 217; + maddy = uidGid 218; paperless = uidGid 315; systemd-oom = uidGid 300; systemd-coredump = uidGid 301; diff --git a/modules/services/acme.nix b/modules/services/acme.nix deleted file mode 100644 index 47b78ef..0000000 --- a/modules/services/acme.nix +++ /dev/null @@ -1,37 +0,0 @@ -{ - config, - lib, - ... -}: { - age.secrets.cloudflare_token_acme = { - rekeyFile = ../../secrets/cloudflare/api_token.age; - mode = "440"; - group = "acme"; - }; - security.acme = { - acceptTerms = true; - defaults = { - email = config.secrets.secrets.global.devEmail; - dnsProvider = "cloudflare"; - dnsPropagationCheck = true; - reloadServices = ["nginx"]; - credentialFiles = { - "CF_DNS_API_TOKEN_FILE" = config.age.secrets.cloudflare_token_acme.path; - "CF_ZONE_API_TOKEN_FILE" = config.age.secrets.cloudflare_token_acme.path; - }; - }; - }; - security.acme.certs = lib.flip lib.mapAttrs config.secrets.secrets.global.domains (_: value: { - domain = value; - extraDomainNames = ["*.${value}"]; - }); - users.groups.acme.members = ["nginx"]; - environment.persistence."/state".directories = [ - { - directory = "/var/lib/acme"; - user = "acme"; - group = "acme"; - mode = "0755"; - } - ]; -} diff --git a/modules/services/ddclient.nix b/modules/services/ddclient.nix index 46e84ba..ca7a78c 100644 --- a/modules/services/ddclient.nix +++ b/modules/services/ddclient.nix @@ -1,6 +1,6 @@ {config, ...}: { age.secrets.cloudflare_token_dns = { - rekeyFile = ../../secrets/cloudflare/api_token.age; + rekeyFile = "${config.node.secretsDir}/cloudflare_api_token.age"; mode = "440"; }; # So we only update the A record diff --git a/modules/services/gitea.nix b/modules/services/gitea.nix index 8af0d89..95452d1 100644 --- a/modules/services/gitea.nix +++ b/modules/services/gitea.nix @@ -5,6 +5,38 @@ }: let giteaDomain = "git.${config.secrets.secrets.global.domains.web}"; in { + age.secrets.resticpasswd = { + generator.script = "alnum"; + }; + age.secrets.forgejoHetznerSsh = { + generator.script = "ssh-ed25519"; + }; + services.restic.backups = { + main = { + user = "root"; + timerConfig = { + OnCalendar = "06:00"; + Persistent = true; + RandomizedDelaySec = "3h"; + }; + initialize = true; + passwordFile = config.age.secrets.resticpasswd.path; + hetznerStorageBox = { + enable = true; + inherit (config.secrets.secrets.global.hetzner) mainUser; + inherit (config.secrets.secrets.global.hetzner.users.forgejo) subUid path; + sshAgeSecret = "forgejoHetznerSsh"; + }; + paths = [config.services.gitea.stateDir]; + pruneOpts = [ + "--keep-daily 10" + "--keep-weekly 7" + "--keep-monthly 12" + "--keep-yearly 75" + ]; + }; + }; + # Recommended by forgejo: https://forgejo.org/docs/latest/admin/recommendations/#git-over-ssh services.openssh.settings.AcceptEnv = "GIT_PROTOCOL"; networking.firewall.allowedTCPPorts = [3000 9922]; diff --git a/modules/services/maddy.nix b/modules/services/maddy.nix new file mode 100644 index 0000000..9388200 --- /dev/null +++ b/modules/services/maddy.nix @@ -0,0 +1,7 @@ +{config, ...}: { + services.maddy = { + enable = true; + hostname = "mx1" + config.secrets.secrets.global.domains.mail; + primaryDomain = config.secrets.secrets.global.domains.mail; + }; +} diff --git a/secrets/secrets.nix.age b/secrets/secrets.nix.age index 1d066a76a4d5546b7cda0aba23be28a3ed5c5ade..ac61579f270bef7285fd6ec512a461ea441d9c67 100644 GIT binary patch delta 5223 zcmV-t6qxIuDCj7VAb)ptT4+cyaz$5KK{Z55WOz_|X;4;iSY>HpcQIFIM^RTVWq37g zRzp`-FbYIkL2YhYc`IUWb8j{^cvvrZVP{HXMrcD?L`PFsXk>SHZaFY?Lt{d7NeV4K zAaH4REpRe5HXvA3QEOE}AVFtrIdw}>NpCn(G;L09NP1^sV}EgOc5-M?M{+l8b9GNa zWoR{GPjqcfMK=m?d1EzCXizp{d1Nv&IAmrtWLH>7PEbZ!SwUkLTqy|Sw&Sv zFEm05LsL&`Rc|&(QAT($HBVD=X){DmQ89Q+WLIQbOLb3YHbF8+Wl?EFWpG6bEj}P{ zX?87eGBq|JT1IDSNp~PYGfXj6M^|b}R$6CqLU%%EWiL~ISVL(sXlF=zH$hNQX+%e2 zN=`#WbXamR3RXCJb~#B)F=aw@Ok;IXOiXE0bZL20FKaJOGjmsRS41~fFl=E#R$@0e z3N1b$S}kXCWnpt=AWD$a!G7!F;+7QEiEk|Z*?y>ctJvIbt`agI7V(Z zW-&%|FK%vsRY*5TFHK`@LRv;LbT>FMXhd{P3XuF((}yee8WQ7YQc`2Rgw-TCw!yIR zV~XMT8S6kJ_$a?{LyD*BtAG9GB>F;|O}Gme50d_;Jfa#eZN4o87j^&gi5`%#t@yvU zKDh*>MT9aihb@k==qf~i;M+vYIjELXCH=B%?ppDGq4HBsYSy@|U~!xKwd5UK*qt8N z>>=@cp;Zw)rN@ljNVkNYGDY1~nm7a(Nvr7|;7~xxdLD{GkxS!;5N5`yaj(W57;mi{ zIXpt_W1@PxwhwF@S1`3ORR8y?43wvA?)B=k#?ZM8)?P~is_iG1ZjwwjY=x%}ULr-Vu~(&oyJ zky!pZZrqwk(x+H!sI6{d=40Kyr4#m-cpS{1m{%^EGU@Rj(Xzl#Srg$slhB@XQg&E> z)C*pqkuV$J&XnhF7-CEZJrl1BL?d_U(p=7XiugGqPjtLc8E_Bg_MzB${0R~J(9}MX z(m@w;xHNM0$l`P@4{2nWRAb7)Lc5wvXQ^pg2=?T-55j5}6^X zEqK6ut8ef+ULqP{ti*3(HtF@<_L9x`Lua143~g;tSgojM#&?TCBwK;wqCH|m7|Bbd zFDI?Hya~ZsFp~sasy=?wza3QwxUbYf3}NPvuWQMqj+9A#pOHYXs545Z$1PlctkX$b z!Ehf58%ZBRF&^s9?F|mp)n`y4V z#??z-{S?lbH;JGAB4>UadnrAYdb~Z{w_CQYOzNk)_Ijg5&dgJFbD^!8`NLphAXGWR zAZK%ssmwGc6HdF;^Fuh?JGIe&oU-kgP`0@!c-&-7rzc{cG)=Ae8Q!Wn{5WV9V48pW z6m}rkQ`|TU;k#hiWp!H^rVlx`Fo%^h=iqN~a+M+zAgFMG!c?4LG~q6Yj;?Yzf9X

qRhuWzRqazj>;|ZCu*c$RW8QKo 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 LNg3?cRp#QM1ACjX