From e7aa43acaf8668fd3f5a9fd45659aca75d9bb3c7 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 19 Nov 2025 22:01:27 +0100 Subject: [PATCH] feat: db & git backups, bots on matrix, proxmox monitors, ... --- config/db-postgres.nix | 9 +++++++++ config/git-gitea.nix | 9 +++++++-- config/matrix-maubot.nix | 23 +++++++++++++++++++++++ config/metrics-prometheus.nix | 22 ++++++++++++++++++++++ containers/matrix.nix | 11 +++++++++++ modules/tools.nix | 11 +++++++++++ secrets/auth-authentik-ldap-secrets.age | Bin 465 -> 465 bytes secrets/auth-authentik-proxy-secrets.age | Bin 417 -> 417 bytes secrets/auth-authentik-secrets.age | Bin 614 -> 614 bytes secrets/db-postgres-initscript.age | Bin 396 -> 396 bytes secrets/finances-app-key.age | 13 ++++++------- secrets/matrix-maubot-cfg.age | Bin 0 -> 776 bytes secrets/metrics-pve.age | 7 +++++++ secrets/power-password-file.age | 12 ++++++------ secrets/proxy-dns-provider-config.age | 14 ++++++++------ secrets/secrets.nix | 6 ++++++ secrets/yarrr-env.age | Bin 1398 -> 1398 bytes 17 files changed, 116 insertions(+), 21 deletions(-) create mode 100644 config/matrix-maubot.nix create mode 100644 secrets/matrix-maubot-cfg.age create mode 100644 secrets/metrics-pve.age diff --git a/config/db-postgres.nix b/config/db-postgres.nix index 1f31774..f66ba3f 100644 --- a/config/db-postgres.nix +++ b/config/db-postgres.nix @@ -15,4 +15,13 @@ checkConfig = true; initialScript = config.age.secrets.db-postgres-initscript.path; }; + + # TODO: Manually add /mnt/backups mountpoint => NAS backup folder (with rotation on the NAS) + services.postgresqlBackup = { + enable = true; + backupAll = true; + compression = "gzip"; + compressionLevel = 6; + location = "/mnt/backups/postgresql"; + }; } diff --git a/config/git-gitea.nix b/config/git-gitea.nix index 77daf04..e7e3110 100644 --- a/config/git-gitea.nix +++ b/config/git-gitea.nix @@ -16,11 +16,16 @@ host = tools.build_ip "db"; createDatabase = false; }; - # TODO: dump ... settings = { server.HTTP_PORT = 3000; }; - # user = "git"; + dump = { + enable = true; + # TODO: Manual mountpoint /mnt/backups => NAS + backupDir = "/mnt/backups/gitea"; + interval = "1:42"; + type = "tar.gz"; + }; }; # gitea-actions-runner.instances.default = { # enable = true; diff --git a/config/matrix-maubot.nix b/config/matrix-maubot.nix new file mode 100644 index 0000000..046b683 --- /dev/null +++ b/config/matrix-maubot.nix @@ -0,0 +1,23 @@ +{ + pkgs, + config, + tools, + ... +}: +{ + nixpkgs.config.permittedInsecurePackages = [ + "olm-3.2.16" + ]; + + environment.etc."maubot/config.base.yaml".source = config.age.secrets.matrix-maubot-cfg.path; + services.maubot = { + enable = true; + plugins = with config.services.maubot.package.plugins; [ + rss + hasswebhookbot + ]; + configMutable = true; + # RIP the auto configuration ... Built a base yaml, written in agenix, and manually copying this to the config.yaml file + adapting as needed... + extraConfigFile = "/etc/maubot/config.yaml"; + }; +} diff --git a/config/metrics-prometheus.nix b/config/metrics-prometheus.nix index 6017973..847c773 100644 --- a/config/metrics-prometheus.nix +++ b/config/metrics-prometheus.nix @@ -15,6 +15,20 @@ in "--web.enable-remote-write-receiver" "--storage.tsdb.retention.time=${config.globals.retention}" ]; + exporters.pve = { + enable = true; + collectors = { + cluster = true; + config = false; + node = true; + replication = false; + resources = true; + status = true; + version = true; + }; + configFile = config.age.secrets.metrics-pve.path; + port = 9221; + }; globalConfig = { scrape_interval = "30s"; }; @@ -30,6 +44,14 @@ in service = "prometheus"; }; } + { + targets = [ "localhost:9221" ]; + labels = { + host = tools.build_hostname "proxmox"; + host_ip = tools.build_ip "proxmox"; + service = "proxmox"; + }; + } ]; } ] diff --git a/containers/matrix.nix b/containers/matrix.nix index 6321c84..ff63767 100644 --- a/containers/matrix.nix +++ b/containers/matrix.nix @@ -18,8 +18,10 @@ in additionalPorts = [ 80 # element web 5173 # synapse admin + 29316 # maubot ]; importConfig = [ + ../config/matrix-maubot.nix ../config/matrix-synapse.nix ../config/matrix-nginx.nix ]; @@ -27,6 +29,9 @@ in db = { enable = true; password = db_pass.matrix; + additionalDB = [ + "maubot" + ]; }; logging = { enable = true; @@ -47,6 +52,12 @@ in private = true; auth = false; } + { + subdomain = "maubot"; + port = 29316; + private = true; + auth = false; + } ]; }; } diff --git a/modules/tools.nix b/modules/tools.nix index 3455491..b6ce0ee 100644 --- a/modules/tools.nix +++ b/modules/tools.nix @@ -19,12 +19,23 @@ let build_ip_cidr = arg: "${build_ip arg}/${toString config.globals.cidr}"; mask_cidr = build_ip_cidr 0; build_hostname = arg: "${arg}${config.globals.domains.external}"; + build_db_uri = + container: base: + let + db_user = container; + db_pass = config.my-lxc.${container}.db.password; + db_host = build_ip "db"; + db_port = "5432"; + db_name = base; + in + "postgresql://${db_user}:${db_pass}@${db_host}:${db_port}/${db_name}"; in { build_ip = build_ip; build_ip_cidr = build_ip_cidr; mask_cidr = mask_cidr; build_hostname = build_hostname; + build_db_uri = build_db_uri; loki_addr = "${build_ip "monitoring"}:3100"; metrics_addr = "${build_ip "metrics"}:9090"; diff --git a/secrets/auth-authentik-ldap-secrets.age b/secrets/auth-authentik-ldap-secrets.age index 1eda0ed774e9f75380231eb48930cb0a61265a79..f3e27eae303e1339b19f4c11fb125a42baa45f1d 100644 GIT binary patch delta 431 zcmcb}e35yAPJOVifq6k@riVqYZ$YT3zrMM)I+w1ULUD11 zZfc5=si~o*LTZsqNmQVMYjCP#o|%)0vrkHzZ%&G-Uww{8rD0ZjWl=`9sZ&H!K}3MQ zXOdfHo|97`mw}PCPl-unlDCtquXm(&L}g?~X{3Q$Mv_HRwx3gIO1`n9kENxTk*}pA zm#(g^LZY8;MyO(#OzqdtFdTwZnk!MLja%PmbwqtQfgm6sMec<}@sR}7l{_51PPW~=Sy^h&>#-_Kj>e86G)ywo3e z;}T@McvnnnpWt4VxOJZQ#|6bPVc+hyo?J6&)6`mDov6kn{V5MTSB9}S_-_<>o|UDX zeecZ{gJqXLDlhA9I^~+6IAt}%(L;RofgA>FUbXVQH%SOut#Rh;iD@-z0lG8@rz7 f*4t>ZWzJV=pLXkb#jfJVHHVpb{IX>acQye4LXD>c diff --git a/secrets/auth-authentik-proxy-secrets.age b/secrets/auth-authentik-proxy-secrets.age index f17a17df5cd30ce414a5c7adddc3d82ccec8eac3..9f00c9cdcee384c38b93c314263256d992946598 100644 GIT binary patch delta 382 zcmZ3;ypVZ)bdYFD@ex{MDMOBc6eqfQIsdh#>*Tf5L zhx^u<-YNHJ%s;Kdcy3yh-qhpKx(64#bbMMl*G(_%jL0?_$tyc2$1#7|x_7JFTJDzm zyNey{x4W>d73Y{ZMOe9GLc6@=oNsxb_OR||qrAK&xgz4`MPPv f^WVJFUOanp{CY!}>s^17>0dmWz9!3CZubKKYo?Q4 diff --git a/secrets/auth-authentik-secrets.age b/secrets/auth-authentik-secrets.age index cdd72293095225fd69f777fb671bf180c607f022..832e461a61063338adc5b06f14c3e2c791ed4ec7 100644 GIT binary patch delta 581 zcmaFH@{DDIPQ6!2iA$t*o^wE&YpSJXa;c+5u3tv5rGbZ+OSWZEUU*cnS4obSpQW#p zBUfa)acN3WiD!v#j(b5sL0G=NcU5?mVYzoij&FWZRiJ)|NqD|tvSC=7Czr0BLUD11 zZfc5=si~o*LTZsqNmQUhM3i>9VMTa&eu$Y{m6=D8QGI5JnY%$iRg|T9q_a_ZT7E^T zXOv~WvA#z-SA>a&Z-HrkiGHqgwoh<=Rd8CWcci0BPGyyQx`~H-u%Sm@NLp@)Z&0N# zm#(g^LT-?orE_RdfMrEeV5+N+pKnN6UP?}=duUEpn!iUyk*9Z{xpBHvZdh(Q*PD5% zNh_2B_SPS`fB4OU>MyYeW@^j zo;?=YcU&(BYK5yRu3ByTElZBi?C^xXe^Vcn7%VPWweiF&`|C%<-fTW0VNqKsQEvwSjq+==ETgvX$f)_dlK0cQmGyTNJ}fv_Al#DW%M?6| zi)nAg}yf8?Avbm_u-mRSexn>A^cwjC3zdj2t(kv!c6>@aP5i9$~XRbUpal* zaCYDCe|c%|#FjXiighb}yrIx3Dd5=N;o!OY(avQ%&jSpb^9M4(dOh^ z_15U|8u={-xk@Yb-re_oWf!oxd)pUZj?Jg5xeeHVOn0>`b?>TaQ$2n1_YwK$S9?ElvL{NgC&==O{N delta 581 zcmaFH@{DDIPJLl{c~YWhs*9U3r;l-jrBP6-eyDk}f4y5-fWC`+Vx*6ycS>%7V~M{{ za%5s@c~wLpSDsOZu}@;Yeq}~vh@oY6nR#hINn}N$xo<_Xzi(KUiGE0EK|zFTl53U) zm#(g^f=5`mNq|{UL6Ng#Qf@|Swr_4_o{_6dVXn5fyN5-QNl92>n6_iKn~!rjS6kfQ z!X-bV#Ou!|$XpN(yK<2~>7LQz#rxIUzpy`C9LsiMmYM&`!(8*Xe6r_iI`_ZssN-ah zAJY^JnK!r#r$qWS^_Z1Rshtp@yY#%YvyS=cFO#!tZZ>}0zKLB_`r2;R8{6(GgtL5} zch6y}S<$9tpE3p4R@@9JOv!27DYRmh!nU`Qu4ey{R&V+pSl`0fDl#$AwMT4`Wk$ch zT;^Bzhdv&8fe!w!|E2V5vb>V`A=N)sr~R2<>?F_6bMGy@Z2fST?B0Uw8owB?1RPtD zWKwW<+6O%r2JLMY>%0DhZb{sF{`M>1)f|QL8+V?6&?Mc&;_)VR(&T@;{O%|HK4UUh zvCS=H;|wA7#}^z!#a4Z^+pyxjBwNnE=+v9%LL9|Umv)qQOq6&w{icM6q1?qYLTxS~ i|E51)@oo3D8aY3gO@1$r`U?1nb%ad6Eo{jfwG;q|-u0&d diff --git a/secrets/db-postgres-initscript.age b/secrets/db-postgres-initscript.age index b667b03d18b63816db1fd50dd5202abe961c5f38..232fc12adc33d79434b8da4488f1f93e63684007 100644 GIT binary patch delta 361 zcmeBS?qQywQ}1YQknfT0lx&%q6<%JMl^bem;_a9b=&$YNEIm0{wehIL zOtG9T3DWiYVt-HS63w;>!HR1id z$&#ikwWkCyV`I#IzrL_8xqi+eX_GFIY8=YP^8(IEN`nxpDrC?eZ!`U-= JG8Xl(Q~=@bg=hc( delta 361 zcmeBS?qQywQ=d{;kzE+<80F%gT~OwmUJ)9R;Th_j?UL!3?h~12lx3I|RahFF9UM{R z$z_pUk{6LwW)xKzW~{BRZ5f(XTwxxOmFpZ9?2~O_lpO5u<5TSCZl32K%B5?kP+Xj$ zo0?)|YHDbyU~XhkTve`+p6Qib>=TgZkyKbwXldl)QJ=xvk zWRRJWWssH4JwAdfTj;g-5po^-tXn)wkYab ztN!6~hsPX)lau*Ag}VwWr8I?H%(h-o_eZaF!)N9`?|1kAb(&<_t5z+na!p((-N@nM Jv4^WD698luhrR#+ diff --git a/secrets/finances-app-key.age b/secrets/finances-app-key.age index f1d609f..a977be2 100644 --- a/secrets/finances-app-key.age +++ b/secrets/finances-app-key.age @@ -1,8 +1,7 @@ age-encryption.org/v1 --> ssh-ed25519 jxhkLg aQoOlZUoNaXXxfkMlkGx9zJDKQh+zlLyYrXuX+LEcFw -9c/dFd+LYdnb2TUm5+lxcPmFW8STMq6UALHlClL85jc --> ssh-ed25519 UJuwpQ hnsSFl7MIkaG0DmCzZKoUtDLj/ey+YZ7Af4gEiPNtkc -2bmkqUGoh2kAW03X//iq/mlzOZeoS1PpmAmLWcAR48k ---- yMItyu2jgirF9YB+u26yykPuqEVz7T46oi6EDZ8rXYs -6v%aKF149<$kHCbv#dܿ$4 -F5k*CtqUH%~E:e:d䁷k \ No newline at end of file +-> ssh-ed25519 jxhkLg cwOIK3+fKR+hwY0ffpXmoRlvEzisaqJKph9KAz1tjgE +M7ZSm185WYRIyVFBtdhqUSSevkPrWUU+oO1pWyvBL6c +-> ssh-ed25519 UJuwpQ Rd52L1o0bCbjgudCzJ0qo209c9WOKxqwnWi9oYbpbXA +6EoyF/9warFja9lKxAAa7M/wIHfFrifJQhg31gNDQeg +--- dkH7UftAnXBiRRK6xf+c/wBTlgREs8fTBNWPXVhfg/I +7:V;/a*޾w.Ր\RCz4ES9P+ l/Q<2GMN kl \ No newline at end of file diff --git a/secrets/matrix-maubot-cfg.age b/secrets/matrix-maubot-cfg.age new file mode 100644 index 0000000000000000000000000000000000000000..c7e5e137f55b50627f7156c72d9bff1dde232971 GIT binary patch literal 776 zcmYdHPt{G$OD?J`D9Oyv)5|YP*Do{V(zR14F3!+RO))YxHMCU7s>sOpNmtMgEO&|U zPf96>Of8ShOf}A`Of=WeweWKDkIbwH3^fR-bn!6AD++T8%I7M`Eh`L*$}~v~uCny9 zNJ-Q-cFcAyDy{VM)DE@`imEUO_VG7%wsedza7DK*#8*GaD_tQgGT+R^+$hm9vmz+l z-ykzECBis8IWe%z%+kx(&^6Di*wZW5IMT?`(16P{wawbCU#7lNZF zJLT6Gyj-|(iEPc~^vnlm_P+R6Ft0pRYU{?C52i2rtkBh!_H%y2o)i8~&%f{3_J5A4 z+D@0*cRx0WF6lC0>pqpW`rwC1_Ct3TKD(q68I$2>=ao6D?OaO!_g~Bx|8})>UHX>H zdM)9a+A{O$K|JR2W$WhdTt8cXzlLkArOYW0{+8D4>5J>jXWsjswQI@$JIVs{WLPVA z9cMJuNsFD^&9L_Glh4)>r>oabo^#D(rP7TXh3%Kk+j>jtV!CHZ_F49OtVj>`Uhw?{ z*H3N#C)TCQ|D9h}9GaK?J!$9Jsllfuo5beL4;Edcb^iRSkAY`*_b)mt%Zgc*KAf0%bGatcIv;=k`HC8 z))_GDo-?_^^}1B~0?z=+EEUcu`@NglG$$Q9{9=ReWTtC7mUYIx%;XfP>5@}luk3Eo z6)TrK_nYXRRY|+vF6DY5xBbQ4Gt1V#U}{oyJ9)pzA$|4BNS_k3S-)+fKeOBo%RBvY zwXm+s!=E=q#m{#Ly)@yGVpgkI18wDo5F Sch7b%*StIbjEs2_Rs#SvwOI@R literal 0 HcmV?d00001 diff --git a/secrets/metrics-pve.age b/secrets/metrics-pve.age new file mode 100644 index 0000000..c528f96 --- /dev/null +++ b/secrets/metrics-pve.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 jxhkLg fMyFt2LR3vCmiEBnsa8l+66q41O6so6vIfwwfR0dXVk ++eW719i/+MlQgJVbM9yP95FK+akVScstte2wWYulBGY +-> ssh-ed25519 hKRBdw DjmDRh5sqxmbSckrYIliu8zFVZDIpzltqK5rCO1qRB8 +8isUMp0G0zE/MK7s3ubTzEZlFh3DSJVYD3hP2cfBODo +--- JOvIpPS8459oTkMN0OqtifBDC3I5ccn/A64k6WLbWbA +udD#«< Zu"UYb]!N6SO;ߋ3WixWY{,żJ\lٻJa^8 \ No newline at end of file diff --git a/secrets/power-password-file.age b/secrets/power-password-file.age index 3ca795b..3a277e4 100644 --- a/secrets/power-password-file.age +++ b/secrets/power-password-file.age @@ -1,7 +1,7 @@ age-encryption.org/v1 --> ssh-ed25519 jxhkLg +kc3WvRZu+M7FPObE9sUEBrRZUjaKQ3uDX01e30bvH4 -jp7GGPCdUHMFYAdZ6eHlb2Rpjbr7fgxO5i5A4JCuBFQ --> ssh-ed25519 DVDL4g u3KhmxBa+ycZKj6g9/p9VfdWJe3sXNIYWqvnxS0LOFk -+6czbSa2PsgCNrsWFYtFJpW6YRttVpC3tlJpvMyKVlo ---- 6giEp6Qr8xXyII1KyBbEtT0a4qUkYtvby2NVshaHvK8 -+& PWtFApOL 'gQ[ \ No newline at end of file +-> ssh-ed25519 jxhkLg oln5ya/9gIVWvlWBE11ZgUQYCN4tZQFa4Fe13q3o81s +69a7qRWUtQ6KAgT9zH6HzPqmoBx5OPMv8mhoc3F+FlQ +-> ssh-ed25519 DVDL4g m2lTL6SD1HxqSJelHrpDli1uOCgM6/cjJApBQ0a4UD0 +QhjMpiWQcovOPMxwX/658PkO0hgppG0rs5wQO5OUH78 +--- IW3/EHx3kYU5kLkRt9x2SQtqx/+krXcT4aPv3zh+u+0 +8PRD37dR3܂w \ No newline at end of file diff --git a/secrets/proxy-dns-provider-config.age b/secrets/proxy-dns-provider-config.age index db8b410..c3e2fce 100644 --- a/secrets/proxy-dns-provider-config.age +++ b/secrets/proxy-dns-provider-config.age @@ -1,7 +1,9 @@ age-encryption.org/v1 --> ssh-ed25519 jxhkLg anodHENUqRaCT66sUwK09KEUOUsApe5VfLioUKylKGM -IyzgkxtINRFeRCa5hdvuUruBrE+07vrjsGsns7Ydwx4 --> ssh-ed25519 FOCPAw DLnVp2X2Nu2wB4/F3R0zfZT5ZcSY9TjY0pKyEeA/AHM -3IQ4Wvl/ei2eOveqXpmk1hZPhgpNn7zb6kjoWXmwZaY ---- oZOshy18oF7M5znbecsTb4np2FAQqU4henZZFTrQUAI -]Yi1/`nEPZ[%S}FU1Q0k9SұI܆v- 2MrWr$Y-(c_&=' g3Ho|8ıc(CױxEWsz'Z/Q 'X]G=lʩ5D'CȏM] \ No newline at end of file +-> ssh-ed25519 jxhkLg TBe8hP/bpnNG/b5h9YeeBruy3znMSWNhjDUWVvNEd04 +TPuNvPhRlvg+wdLCulhBNu+qbXs7pWhFngcrWwcC2Zs +-> ssh-ed25519 FOCPAw Hwq5xX/6uL8uVxudKKkwwS+NSJn69dqabFBDQr5o00A +bz+UUYKhSgrKS4KHFor5XpjZAnuOZrHuNHuvXSP/JR8 +--- VvsmTUj/PZTAwxzT5bFLKhcur6hv7qODo/5L94cW4LY +dnCEBj*n?/6069 +葺W xaO f,övѡyH>7Yr"?إLͽށgc 88Vj +sq<( (~3vfSeդw AFUv?:* A2F@ 70 G娂o^Ϩ \ No newline at end of file diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 684ac63..d661fa1 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -24,6 +24,12 @@ in "finances-app-key.age".publicKeys = users ++ [ keys.finances ]; + "matrix-maubot-cfg.age".publicKeys = users ++ [ + keys.matrix + ]; + "metrics-pve.age".publicKeys = users ++ [ + keys.metrics + ]; "power-password-file.age".publicKeys = users ++ [ keys.power ]; diff --git a/secrets/yarrr-env.age b/secrets/yarrr-env.age index 3608f6a306d335c12f86c9bde14521cd87cc4d31..19109b454878f5e71ff35d96fe03172a43683447 100644 GIT binary patch delta 1371 zcmeyy^^I$SPJK#NVPuJSScbQGqH}~(PI6Y6PgRJKSyFnIhf`XfrIAI6Q*M%3M5#qi zK3BMLdPGV=cA;r@shPj2uWypMTTYUjQ<76XNw|JQuxVgqqLV?DQLw+Cp=F?nX;rzCqlrtguXk=)N^xbGi*v9e zm#(g^LaKA5e`H3UhiP_Bj75q2cV0Kz1BNcMvxzZG~|KgKemYYuARo|qoW%~Wu+%w-idznso z?|EDle%!dkhmj-VHZQC7z84g zz0D5o<#C@LO4k(1i}wFl`_)&Ws&SFk>T%3%?fuDjKTm#J@@>tM*ITFCOWzT!jNZaH z_tvt=f`IHdJ5Bb+2|J#GoTB*E z2dpnnvBezTXbHy86J<*6F>!oYc7|TJFfP5$JxNaQKPsl;eG0syu>?YpY_l?N-)*`zOH9 zYq*Z%h)48&PX6hh%XWN!rF*q*UI+W>W!vgsJbj~m-*vG~__x`-n$|xIUaq{o>fe+`r5C zDa!TqeNp?l#*$X`8+q1n@L0?#ePycGRb0~(JaPNnW`95PP}E~-p2aeA zdKzvh)LXR+8$Xc9FDtk)vuEk5n2(l{|5WAucf8yqGMjDg3(lOENA!NWI@LFDykl{j zYZk`HcvX4o%kYcb8Yco6^y;LyIqToNt)uHyIY-D=W8lPlo1gxtq(wr$1;u8x2$UtfnKkF!$+r4h4jk4`=FbK2S`x(Aj2NpD+qvoI?9mPg@c20d{Fk$C=} z7t>$YFrV0Me3Y1&pS_RGGtRCQCa@RKY5twU1g iC_X&C$VJuGpHHkjmcL1R9d*DOR2Z7c4nAsWVxG{NoJv;XKA7Z zm#(g^g1(2ZX-Y_tkxN06n@^&niAPpZdSG^DM7Cvid9Z0faCU~dftgphfp3m0*UQ9> zzYq9MNUWE(=ctqx)UkcR=xZNxwqlNtpt0K{xeo_){@Xu#`RB{c7bV9hs|2?j`Rx0( zLDD_z`%dY!Q(_MFwR3EzJ`H#mJ-MxFZTvIo@Gr0I9|Zdz(Ks_z>}~Y3r&?vRo_$-@ zvC%+t*F~ESN!O-#Mqd#-jtKkBHoq;@sY`Um z@mbr2o^6g@zmdUj(v+vGYd3!My|6$vWRsGL)h*Mb`wN$7%(%2kN;Y(%3NPkt)0E5-f6$? z$}P5*Vb@I~GQQWGusQShYSSHir{&Iy+MN%$Lf064TU2VtHOEBhrPJYpm_D!GidmL- zYO-c^cBmRh&RH7er+0AA_9d^mFBJdH)4lX&?Yqo}M+L9i^t^a>lKIbtVqbC2ihk28 zOBzl+c;>Wua?S6`UGwtapL4Id(3l(eE~&VFq1G9W51u9o8)`2GT>QWH=DxpPUmXR1 z*gksUBhmgsu}{Hir*G1()Vx<4-zNQLlsfQb()8KK_{4Ra+p7CZ^foS3`hGvQ@avog zZ}$Z?g<0?G$|w6~y>v6NcMMbxVdMVvHOpVbeRfdB@>70#k=LX@@||*;XIge_a%kv- zgl)9~dG*UB4o>F$vEk~B3HLW1nJ=`Z)?ccrM1KLZ)o-JBYA?&g{}jyCS`%T=vA`+f z*rI!W4krH+$~wO_804QV%GTpvB*e}3?E3~@)l*`k^FLP=OjbU-k!{}%k-NVQO`{tR zoteAe)Oh8l8=c#DnYvj^-{h3M-hF@G=hcPAul9=-iRg(J)?dtLIFPq){h||DN={1{ z!(_kS?Pt5F|8^O-=AW6`lXp(ex49W^-^jk$SJA#dk+VaW>CPtaYsSuXoR)i(T6ZS3 z9@xLaQJdLw!-{18E6>B{aFxnO*{5ha#pKDJurl7zrn7#I@SkV zDy;TjSLenWf0XX#~R04qk