From 6a0b61a502de10a27c1a456f24c49a2f48b90ca6 Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Tue, 30 Jun 2026 23:43:49 -0500 Subject: [PATCH] feat: add immich module to yumeami tsuki host --- modules/nixos/bundles/common/default.nix | 1 + modules/nixos/bundles/common/networking.nix | 8 +++ .../nixos/services/apps/immich/default.nix | 58 ++++++++++++++++++ .../services/storage/postgresql/default.nix | 46 +++++++++++--- secrets/crypt/postgresql.json | Bin 3508 -> 3739 bytes secrets/sops/secrets.yaml | 5 +- 6 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 modules/nixos/services/apps/immich/default.nix diff --git a/modules/nixos/bundles/common/default.nix b/modules/nixos/bundles/common/default.nix index 21ed664..fd3f2d6 100644 --- a/modules/nixos/bundles/common/default.nix +++ b/modules/nixos/bundles/common/default.nix @@ -88,6 +88,7 @@ in { "forgejo" "grafana" "asciinema" + "immich" "ntfy" "terraform" "vaultwarden" diff --git a/modules/nixos/bundles/common/networking.nix b/modules/nixos/bundles/common/networking.nix index f15fd70..8414ce9 100644 --- a/modules/nixos/bundles/common/networking.nix +++ b/modules/nixos/bundles/common/networking.nix @@ -78,6 +78,14 @@ in { port = 5125; internal = true; }; + immich = { + port = 2283; + internal = true; + websocket = { + enable = true; + path = ""; + }; + }; jellyfin = { port = 8096; internal = true; diff --git a/modules/nixos/services/apps/immich/default.nix b/modules/nixos/services/apps/immich/default.nix new file mode 100644 index 0000000..825c2c6 --- /dev/null +++ b/modules/nixos/services/apps/immich/default.nix @@ -0,0 +1,58 @@ +{ + config, + lib, + namespace, + ... +}: let + inherit (lib) mkIf; + inherit (lib.${namespace}) getAttrByNamespace resolveDatabaseHost resolveDatabaseIP hostHasService resolveServicePort; + inherit (config.networking) hostName; + + pgCfg = getAttrByNamespace config "${namespace}.services.storage.postgresql"; + networkCfg = getAttrByNamespace config "${namespace}.services.networking"; + + isEnabled = hostHasService networkCfg.network-services hostName "immich"; + port = resolveServicePort networkCfg.network-services "immich" 2283; +in { + config = mkIf isEnabled { + services.immich = { + enable = true; + host = "0.0.0.0"; + port = port; + openFirewall = true; + + database = { + enable = false; + host = resolveDatabaseIP networkCfg.devices pgCfg.databases "immich"; + port = 5600; + name = "immich"; + user = "immich"; + }; + + secretsFile = config.sops.secrets."environment-file/immich".path; + + settings.server.externalDomain = "https://photos.yumeami.sh"; + }; + + systemd.services.immich-server = let + dbHost = resolveDatabaseHost pgCfg.databases "immich"; + in + mkIf (dbHost == hostName) { + after = ["postgresql.service" "pgbouncer.service"]; + requires = ["postgresql.service" "pgbouncer.service"]; + }; + + sops.secrets."environment-file/immich" = {}; + + ${namespace}.services.storage.impermanence.folders = let + immichUser = config.users.users.immichUser; + in [ + { + directory = "/var/lib/immich"; + user = immichUser.name; + group = immichUser.group; + mode = "700"; + } + ]; + }; +} diff --git a/modules/nixos/services/storage/postgresql/default.nix b/modules/nixos/services/storage/postgresql/default.nix index d501256..b09c096 100644 --- a/modules/nixos/services/storage/postgresql/default.nix +++ b/modules/nixos/services/storage/postgresql/default.nix @@ -6,7 +6,7 @@ pkgs, ... } @ args: let - inherit (lib) types mkIf concatStringsSep hasAttr getAttr filter; + inherit (lib) types mkIf mkMerge concatStringsSep hasAttr getAttr filter optionalString head splitString; inherit (lib.${namespace}) getAttrByNamespace mkOptionsWithNamespace readJsonOrEmpty getIn mkOptAttrset; inherit (config.networking) hostName; base = "${namespace}.services.storage.postgresql"; @@ -26,7 +26,7 @@ in { config = mkIf (hasAttr hostName cfg.databases) (let hostDatabases = getAttr hostName cfg.databases; mainServices = filter (db: !(lib.hasSuffix "-log" db)) hostDatabases; - logDatabases = filter (lib.hasSuffix "-log") hostDatabases; + auxDbs = filter (lib.hasSuffix "-log") hostDatabases; in { services = { postgresql = { @@ -34,9 +34,21 @@ in { enableTCPIP = true; package = pkgs.postgresql_17; - settings = { - port = port; - }; + settings = mkMerge [ + { + port = port; + } + (mkIf (builtins.elem "immich" mainServices) { + shared_preload_libraries = ["vchord.so"]; + search_path = "\"$user\", public, vectors"; + }) + ]; + + extensions = mkIf (builtins.elem "immich" mainServices) (ps: + with ps; [ + pgvector + vectorchord + ]); authentication = let permissionEntries = @@ -105,14 +117,28 @@ in { ALTER FUNCTION public.pgbouncer_lookup(text) SET search_path = pg_catalog; GRANT EXECUTE ON FUNCTION public.pgbouncer_lookup(text) TO pgbouncer_auth; - ${concatStringsSep "\n" (map (logDb: let - mainUser = lib.removeSuffix "-log" logDb; + ${ + auxDbs + |> map (db: let + user = head (splitString "-" db); in '' - GRANT ALL PRIVILEGES ON DATABASE "${logDb}" TO "${mainUser}"; - ALTER DATABASE "${logDb}" OWNER TO "${mainUser}"; + GRANT ALL PRIVILEGES ON DATABASE "${db}" TO "${user}"; + ALTER DATABASE "${db}" OWNER TO "${user}"; '') - logDatabases)} + |> concatStringsSep "\n" + } SQL + + ${ + optionalString (builtins.elem "immich" mainServices) '' + ${ + ["unaccent" "uuid-ossp" "cube" "earthdistance" "pg_trgm" "vector" "vchord"] + |> map (ext: "psql -d immich -v ON_ERROR_STOP=1 -c 'CREATE EXTENSION IF NOT EXISTS \"${ext}\";'") + |> concatStringsSep "\n" + } + psql -d immich -v ON_ERROR_STOP=1 -c 'ALTER SCHEMA public OWNER TO immich;' + '' + } ''; }); } diff --git a/secrets/crypt/postgresql.json b/secrets/crypt/postgresql.json index 5eab98092bb4c36c4c50ad7fd3ab0e2d90e8d8d1..1a30e7d62643591039ef4e78651bda55743ea839 100644 GIT binary patch literal 3739 zcmZQ@_Y83kiVO&02s`(-l#L^{*xaz!;*R{j{E0ITcQQQWzj<)GLg3f#4_bkDPRS&j zA3il%w*HuL;y)FqRZ1dTKiA%fd|~Pqy%QR*Avnv-F;#{o-_bl-V zo%!<7C408j=K4_wKWv0>;qUOgv*oLJ>-JWrzcUE=GAYl^HJN|Tu@uMJ^gFB< zAF|!u(9Iod`E>c+nR73s?|#qGc&S)T&o$vES{emU^(?$g;;s@x4L;(f!T z;_v&3F;?X1@c5fO*x1Hrv*I#OkYU^HZ_QSR)c7p5TjE%^9QE)F-!_5OY1y90#Wh~j zwGu*f`?tM4H+h;myG&l}%(!T0hTAW`i|E{`yW5|jb#AA9<;&^8jhnybOkB6h`0N?y zvqJpU&$wpp*>?1YyzBP-cek{>tJr_)e$0 z{rf_;6>)C(7Id{ltL~$lF58FAM($i*Z$j7)KKz+m@o`x}*Wy*p5lfy#h3j5CfB)~( z$Je%esCJuqY3H^Vw?6)6I$OQxby`b>=7n4D_dnBq`}55zzVa9TdmB}G&)q({`^r^D z+3Ch*|Nk*?vlsK-xHh$5qwkpm%#uN|>z{LJNK~9!$l}=$WV=zW=a0v8V_BDKy`86u zI*w#c-f`)qwR5z&@NvDP z`LgUp;)Pmdeg>(CuFo|5vU7uI-)`$Jg8&aRna3{MY+A3VYb52VE|LwO+VcEXyT@wn z83xR68#35CZhtuvy7C^cp!w3!$-7Q`WH)#<@3%qS^LaNdAMN2fa(%b<-q5E0{M;wa z2?C|&kF`E)W=YS?{f$|2p{OiPQg2?XnBa zp8xBua=pNXmh0I+U#@vN$13h?V#AW<%is67^Y04twlJ3Bn%*@nCA5t9UpG_7q0R5} z1GPDbn?) PYR1|z?nV^?zmtbxvsupy+Juou-xz|d&y=+V$gs*d5 zp2N6uu~OSLlds~&(-u#NxLxf0e?~T_zF9GZ4r zpVDcx@bQBz?%UEi;r{EFxnDHfte&L9xgzbU;svev4latk%Qo?UyvCh*EhTy~K56%NePZ z-mCJzU*1%_HgQ_P)5_|$9S)U;nmKp;_c-%HLo&ZAxR_b&yLJ6c17jtIcB7+@#r65( z=ZDU@G3!f)CEu&Vb5jnLepX{-{V_|Y`{Ml=HKo{Z|K~mb)zmt#i#zM_x`$WhZ&}VO zZIEdxcvpFOS0Hor^Hk}e3yHCEeU>p=Pj5W7cQv_?wIWFSxy|cCr$j@Ge)(Pf+rxh? zKz?`btc9nPAIrWhy|VwG+jQw|9Cle{`u}w%J}}YH+Pg4l^82c*`FrlA{+k=rX6Ekt zVt2Cq>AivvSigN)7(V&oL~XS$rjNHIX8ruQo_Fr)gh`DJZiklkTwd?(SFX}fy7F{X zkn$AUl`<|f#dO&c?BtjFWNuhKY3uJ7OV*qJDcQ7iVYy+IT+>MpnVzoJsT>Dv?q%&^ zDxG#Sb^V4jzp`ifzwP%OXSb-lY276{>FS>x?iFmywf*O{FKo6x5Zdwg(}XwdQ>F<- zvT<#%iT|9fNsJZq)!QszmF0>sk*EZldy_e@|P4$Y7 z8IoIzwAXK$S&9z01kM$7!Gm(Shj$KAU@$wps()xxV6e z@i$uBy|*QPVbIKy7Zd#}=9#F!{nT_(dhsc9-kI_bwBIee^dZit#HuEVmt_&di(KWJ zO1WhIZ3+Ql&HjJ7Z%sQA%nmJ|yObyI9h9zh1}N<~?PYmjUUF{hK7;00?aw-*G-h&iUUYiC z{`ywQ69HQcl0Q20beTJuF(_x)14q8$f*TJRq+ zoa?Jnyi$JQ%1P(mCMe8W!?xdWpO(DKYN1mh@2~%QawbVQ$#VAxCA-C+*f_J>)gww4 zPd1o+>$q$78guQ0yKb&Gm+<^+F|OY}RbXo83jK%n6U48YaMqRaODKj}zcqc;x<~Cm zCWqO-Rd!E`*Zg^2$eidX@>kc~RB~sbPpfHj@WNe;SGHW}3ffjBGf{qXr)qY`#+*~} zZ}*9BUhko4%X4P;*L%965_LKLswSrI4sM+Oz)QBjJD$(>R^*!SS++k{=LP$2-q@qf zx+mjkqwM*;*9y*br@m(4iAK$Bx*~cjV`1vaPiC*t^MJo8lX|aUtQK1ZF<;l@v-A# zvX@)jUtYd;zdH8!qGL|sX>!IH-Cq?>eKSdonz-wUp6~lvu`*3R(xoT-zZ)i#`Qz%3 zpc}QDY(l-PJxoI1Kd(ONlbQMctl6cS3{~yDQxndg^JR}X@cf1sQ+Qu`$g8zIC#EH5 zh5co2d9k1SAIsmgYsI_8-r5*-#&w=Z59T_y=oMgKpgF3s%AeD3n}vp{h-hnUb$u5JBalY&EDFMge#(p&r7 zdZVyW>AlH$=d>rft=u-fal72jRoWIOThr=8&Azgx)Se4r+bOm2MgP+&U&US+yRlD{ ztN;ARZ;szT^T`3ZI->}r>EkySogY*w!As~a0w zohv7O_>IhayVAD1XMP5VuM-sG3=rt+Y!0_yW_z#wr%2F=DEU;QJez`?rt0(!8(&E< zK2!Kox70A)VfNX;16sCUn|YUq$9(vo|M+?Jix!sJJAtc@zg5dzz4YPqk9YkOrTds; zm>6y>{h+kU;>||2?9B%jo!1og-YA!{{QvKxGIb2Ci`8A6QuNiOkG?a{bNqbzOf-Lo z-(5D&Lz~3*{tTX9@v&3+rLq5KH;Y#lZ@;Oi{_r;Xw=VO>!X=l(^{#)oKpA!PTKbSvaE4D&L8pA`Qw`9i?a4^uMtrB;j%d~aP4uvPnJ%R-%D?} zUeRUPJ)ue{_H@5ifXU>tOVb`)`|GEn7Tt1DyZ&sQac#mbvAOpZ%W94#F)}+ju6S{$ zZkgkynSKkVO}&|CRukL$*3j&&oBXah$6|}wmRPl?9Q|-S zdZPNI3OoP*TZOo}wB84>Nri-kbFCEXw+r=p;9KCbz3jK~`TB1^_OtCbyvXuWEhf(%IYdBQA$cj4Zq`YtD})8Op}@H012!`gXH5&iN#3t`lgVz4^|{2T=!Q&emTS zwihsUxmr^ewm|P;XMa?h!0j84da43jpFT9Vf1!3To@w(Di>k}>>n_y=a?dMQow-Uz rIlJhN@%g`Pzh&*RnU~%*(d~@C&*r=`CSF77YuqOlw&zo%_Du4D@re60#m6|A~er?7Iwy-5RpF!aIbd zKST!4IvSc^#<0QH&_pWZ#ED(8A0oDjUb%ZqTwgon-lg*EB^TQ@l}_fg%q`ux@Onfz z&*AKXMQm?fl;XY2%s>KmL=4 zt_BP0I|-Q2lR9w4f5G;uy@3;tYo6~572IXI;f$NT?XNeFOr(+$!o9z2`RzFILCk%X zBI}Rp6Mvqp;t+qJe9XH2vLb61L&Rn~?nftoYdl}UD^)DjyF+;CDOV;HeX~ux?hcPz zX61`@s;C+*wd3f!D8{lSE9m8ShG&)c-+bK=#$j+cQza?xIzz#|>M8xx9Gz6=@U3=V z%l!Y>GI=?>qWBemca~pL`>~_+osQ?hu4C>T(jVqE^&Yt^$33e*;bOZct;#(SFaERpN?x@3pvi?S46e)e-tb%{yE z%O1nC$+u*+Gi^P8JBQBHknQ1O73h4q`r=O4gS!9rUHty)wAD%Jn(jCC>O2#DJYM zw?#-&aQX5qd&V}UHEX_Gh4Zs7Kf_i(B~WB8+hxYe>W0jnFN8C7RwzCfGwVygAC_8D{atj!ex|GY*zpR-)c1xtyU%2Pdd7e1ner8(=r?wA+NXOQO#T$p(-q`# zgFAY4?o_ko_b$ERySnNUhw%K_YoA4LGjHhWeY@7I-R$qS=TFOC@_)|RvGRTGo1Q)w z+bLG(1B(nRR!#A^Eg`%2cE=L)Y?4uU^)h zy`ITQNP1#~cb;#sn!-!@gY(7Ieyd$oT6@4_gZ!tD3cQ=3;a z3!Bz)7CzIwBD7YcDe-4C^A>Lh``5njY%}{Rr*iG>EOlYn+H$9DJLisP`#3(dG`gL; zeyu!qvdW@k#-UPQY*XCdy|}kKw!K_<);b}VHOs4_6*@eg%O5e%UFUT`YDXmdPv?(Z zPbY_5RJ*-M@UtsR^;%xN2~S=GOrKjSobqLrZrTPbeG9g&$;B(=YraG~I=`Flv8d|G z6wV3E%)d6+e0tgG!uEPnb>W&VY}$DjcsVAj$8CA;xUxM$H>PRBAAXI)PyPjqO!VoT zx%Knw?#pGr`=*~g9@})L{#=8fzs1UoCAz8CZ!t}9dC0L{dE-m*l)`*JwcJCtrnfTQ zrMhhAeU-j1PPG2?uM^+1?+B$AMk&^8S}^6U-TgZ~qy0t~v2hVMX_b|7-i=PjTl&i+W8tvtVCR^@Or$ zCM(NcYaUmnlaY$OY2v!C;%y~k%QT+fTqu_%^;dh+hV7|&2TpSC74*Kge8rN_A8%iH zXf@+@()@|u8Vl^~R3|0<@$WJI{>k0BG+N*-%eFT$+#mhaE~S;M+FtFup;zQx6bH{l zM%Sn3T}}oBuFIUp_-5MXfXT6&mC7FXC%tyOmGHMZeXGBU)9FW%D}KJx$zODZnek%i z?tmA&nUZFUuldw8m)%EY;=DQE=7-EO_+(b5A#BYs-}ctO90#o}VvJw-_b)WNVp05E zqdN(s`lQ@!josNu#jSYou3r7#`xxds;B*?70TpEzBoo>qIR7bN}&dc1gFx9gY=q zis#sJP=OZKv_Eql+9kb5<1MTD#( zo0asDtdu`j9Qr;u+>~y8zdewvGw4Qa;(DIT6Mk`go7%MN*t`nI{kt3rcjo-KGF|zg z!qK}zM(gF*?)lomd3c)W`D?!J3V-*pT&UpSy(-w&W#88Msb>Dh$XCtmo444v?O4mn z#&(v!yxy4H!hgS8fK!|O z?mwR7mbvG?@l}5^IaJo(6WbZ=w&%vV3HIBb$F_5ECs>%cT)p>OvN6Zbjf<;#&yEz+ zkW+^#=7jRfB(3eHAUg0!-a15Qx|4-Pc=E{Rp57& z-T3OX&pdq1H>y{iYnylQON)sm^H0r*t`$4Y`g7EeT+W_jS;u;*f1~D#zVNp%{wi_A z`4!_;KF*HNH*Gr}p|;c{4p@Up?3Km#YSUuf5&My?f?Y@Vfne$-K){ zDBAXp_<{1ZlMWsCxSP=CIXR5^lcHupT+K3uy4?D5=VxDvSCHObh&`oc=1{?DlKR7mLk3{I+7!o%PkfTQg7E@mxMJ zXF;95%4*FjuD_u!2Xm+C|Jfxj#a%LyA?lA(ZOY_7nXT(8@=_N}zRY}Ut%b@j#%*PM zzTv!gxeBDY1E=4yUgmi97GvV|!zu-Rx{DV6Rd9Utd3IZV>13bn3CUcG=O{f=Z+5h{ z=|7lzFeiNXGxi_*dP-Q+kL0R-+ht$0smx*W@$*|>7azG8IJ-JdTf%hm>faXUbRWnx zhkaRi!aVPEh4GK)Q>K4RyLHqp|K9g0yGxl#{uNnF6`XVYJ8R{a~_ zb@}pgHOa$Q*2IS53|rx8}~Bp#E1t z>cPqv8#bs(dN?xZ>YX^C|8rhnXTSev7pcI?Sc%x_pSKi7vRq#EK6$0^>XjBQ@@Hn5 zaA!pSd2#sTw8E84lg)br?mT*MaoKrIhM84j9iJQLZcFZ2d8On^C70zMS-o?vM`{>v z`T4WFxUnH9C^5@Vk}t8Izb#oRDbL_ez$}K>GMB!s4Zp%>^=Qs|FV6D0Q#8EKKKYn@ ze6yOW-IJ41esO)z*YRkHXU6=E&_2xDxgqzQ-xdXhiz?G@Y!EX1o>0WXQ=w4AW}JWH zc2=iJp?S<3?dn~oVGEv=`5ex7yZ)B_RnMM@?U!C2R6?9?s)7C<1^qAKmgvc;imdT!St!cCA>())537)^biU@~T$Uv;U6xb{IUu4e>E*=ZYk|KBKPr