From f72ddcc3e4ee7c93fda118b1d4eabd2562b93619 Mon Sep 17 00:00:00 2001 From: Danila Khodjaef Date: Thu, 9 Jul 2026 13:31:47 +0300 Subject: [PATCH] Release: weekly deploy --- Documentation/Plans.md | 8 ++ Documentation/deploy-dev-to-prod.md | 6 +- Documentation/environments.md | 5 + Inputs/artist_influences_web_sources.xlsx | Bin 0 -> 24593 bytes infra/deploy/devtoprod.config.example.json | 4 + infra/docker/.env.prod.example | 2 + infra/docker/compose.prod.yaml | 2 +- infra/docker/compose.truenas.yaml | 4 +- infra/scripts/deploy-dev-to-prod.ps1 | 106 ++++++++++++++++++++- scripts/dev-web.js | 7 +- server/index.js | 15 +++ 11 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 Documentation/Plans.md create mode 100644 Inputs/artist_influences_web_sources.xlsx diff --git a/Documentation/Plans.md b/Documentation/Plans.md new file mode 100644 index 0000000..40c4f12 --- /dev/null +++ b/Documentation/Plans.md @@ -0,0 +1,8 @@ +this file contains draft for future releases and features + +1. Multy language support, russian version at least, search for best implementation, preferably story in db and easily expandable. tool to check and correct translation +2. tool to manage links (influence/influenced by ) import csv's ( define format), edit ,add, delete, visualize, map to pictures/ entities +3. tool to monitor/manage (plan actions) of curator actions, markers to check painting/text ? +4. tool to sync prod /env resources (both ways), db structure, db data, images, users etc +5. curator_audit_log should contain log of actions like fixit, checked, upload etc with details for which entity it was made and details what was the action and outcome + diff --git a/Documentation/deploy-dev-to-prod.md b/Documentation/deploy-dev-to-prod.md index 9d45500..bd7a07b 100644 --- a/Documentation/deploy-dev-to-prod.md +++ b/Documentation/deploy-dev-to-prod.md @@ -31,12 +31,16 @@ For a typical weekly release, use the orchestrator instead of running each step 2. Edit [`infra/deploy/devtoprod.config.json`](../infra/deploy/devtoprod.config.json) (gitignored — safe for SMB password): - `profile`: `full` | `code` | `data` (base step set; individual `steps` overrides win) - `autoConfirm`: `true` skips restore/image-sync prompts (`CONFIRM_PROD=1`, `-SkipConfirm`) + - `autoStartDevStack`: `true` starts `dev:server` (or `dev:web`) automatically when `validateBuild` finds API down - `git.message`, `git.branch` — used when `gitCommitPush` is enabled - `schemaChanged`: `true` enables prod schema migration on `full` profile + - `release.imageTag`: image/version tag shared between Gitea push and runtime (`latest`, `2026-07-09`, `v1.3.0`, etc.) - `smb.user` / `smb.password` — optional; maps `\\host\share` before image sync - `backupFile` — optional fixed path; otherwise uses the newest `gallery_dev_data_*.txt` after backup 3. Prerequisites still apply: **`npm run dev:web` running** (Vite `:5173` + API `:3451`), Docker Desktop running, `docker login gitea.mysuperlab.netcraze.pro`, [`infra/docker/.env.prod`](../infra/docker/.env.prod) present. + - Before migration/restore steps, verify prod DB target in `.env.prod`: `DB_NAME=gallery_prod`. + - Set `IMAGE_TAG=` in prod env (TrueNAS app vars or `.env.prod`) to control which pushed Gitea image tag is active. ### Run @@ -64,7 +68,7 @@ npm run devtoprod:release -- -DryRun | `migrateProdSchema` | Step 4 — `dev:migrate` on `gallery_prod` | | `restoreProd` | Step 5 — `devtoprod:db:restore` | | `syncImages` | Step 6 — `devtoprod:images` | -| `dockerPublish` | Step 7 — `prod:docker:publish` | +| `dockerPublish` | Step 7 — `prod:docker:publish -- -Tag ` | | `truenasRestartPause` | Step 8 — **manual** pause; script waits for Enter after you restart **gallery-web** | | `verify` | Step 9 — curl LAN + public `/api/bounds` | diff --git a/Documentation/environments.md b/Documentation/environments.md index dd1247f..6c089cb 100644 --- a/Documentation/environments.md +++ b/Documentation/environments.md @@ -7,6 +7,11 @@ Gallery uses **one PostgreSQL server** on TrueNAS (`192.168.10.122`) with **two | **Development** | `gallery_dev` | `https://devgallery.mysuperlab.netcraze.pro` | Dev PC `192.168.10.70:5173` | | **Production** | `gallery_prod` | `https://gallery.mysuperlab.netcraze.pro` | TrueNAS container `192.168.10.122:5173` | +Version tracking: +- Runtime version endpoint: `/api/version` (reports `app_env`, `app_version`, `image_tag`, `git_sha`, `db_name`) +- Prod image selection: set `IMAGE_TAG` in TrueNAS app env / `infra/docker/.env.prod` +- Release push tag: set `release.imageTag` in `infra/deploy/devtoprod.config.json` + --- ## Development-first workflow (default) diff --git a/Inputs/artist_influences_web_sources.xlsx b/Inputs/artist_influences_web_sources.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..ff0cc516f6560b33753d1da33288d758df6b6043 GIT binary patch literal 24593 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

xfnLjk1UZpzEMbLc{7KW15|&D^ z;u=4yCL(dK8#J#e=(ubNtOe>0GsAS+OpwzOl0~%rdcG9WZn%dpMu6F2W;eer^`#pi z@Lkh6^-$k;?1eseAxu!73P#x@@OIEJl(5jM z$!UBP3)h`f?NdK)RV9=>><&c&QOmxs8zaM2k~n}4WCHh-%6wQhA#6w$QR<}E&O7aG2^gPQn#|FmvONj3O1tpRIsE2e*{iCGEk(@HlI zN?DfnU?y#LaR%>_c=@e5ir^MOCMH}bw@0fQ-&fro+;D?k8Z!RN-d%Oh-G=hOhbDqq z#duQ`3Lc48?X<9-v+Xh!$&_yZLR3_ruvCLQD&H7R!-R*4Gz0Toa^Mj*@v>!0yhi3V zfsv0L+1|vd?n{C0y$dK3!JKyp`rpnXtbj3~_bs#%Q%p;8bB}}cPy~fs z|2s(F%gNh0rN&zWA<>EWWRGezD$>Z-rw1G0JEl0Zk3uy}nDK)gAQ*%obXh_2JM`W$ zuEZhr_m#H(gKotA(p6}@=4yCcU3E zBBX_6tIbJzc+hcNFa;T@WfSRvO;Sv&h6vU_8AS4TK}6w{Cr;)rHYTv`M#(5gM_utr zg40C!#{vwf_R;1!^m%7C(p(E2b2C@!dWB){4oQ9_)pmnnf1xHui{U?U=7^ueDS$Ak zL{ST<@ZbG47jEpw?4r#8Lqv1hRiO!iXSC0BLZ!+^9mV5MfOgMB0Rwx`Q|5A6m&lBO z?RwA@D9}P;2rbt^LyKK#8*a|gL``j%C`*6?kuw1d9KzA$)8b+7kHhoZqeQu*b}vI8 zid9r@h@eyAj3gv?s*}L(Q!mOVB07W~H=c#?A?m%q2y^cPNjn-elS2>~*zfDj+eg>>%ylo!=`io_qiKqi}Qa(8I0fsfyaYQmWn5d^vMd8zWcBd!O35?uO1?nepYst|rd zOvZX|DdzFh;(Hi>)E$tZ_z74-B!ZQyjaTx;Et-Q|Nda(FT>xA0IxeMj<^pae>6+ja ziMWECC{;R1|8*}V4CDao7PQzE|2F4?z&%*w>c3idtQ-;9xAiO@j1pweSFU6yP>Jhh zt0VgUPhzw(*xDr98Htax|hNzW)aJs~gMCbc6exu$BICI4ywi}}cUzcyO^%1y!=`yq^MG>j_=SU+; ze!)HS3vIuKuIB~G$dV3WcW-DWIzvTqH~=g~*B{$zHeE6FCFWCZYvkw0|g zP2@^0s;BpFeYrk=&1w7F<)KWhIRSME{kF<{p^yV`JrXH~#~JLZU;w~(;-BaJFgpNoME z*a30o%{9)H8c6#WtPMd!+P&C>zh_R1)Iuk#OFIAsW4m67mHz+(oGA!tPrmVw#{M|4 z?GH+#`R|%IF4*Jz7bB$g!B$eni_aD-y(Vhr$qcPyYN3Sc{cGsS_-cGoL3B>loYf{W zcH6g0v$8t885i1?HhOG+%QBG@?b+snrx=#-WHA9`@y{?F$J+?}?b5Obd>h@EO z$YA56WP0O(-PxM|FQO` z8<+nYw6VA-@am{c)r6|^ka#$K-^iN4-yDeuBQUI?-}@f&@{s?)1+eYG1a8!xu1+`Bnz$lVFYO7vD zbwtWkQ_I-Qjw7C!2X)8GyefAS8VrR+J&Xb_F<3$V{mY49ip;&l z))qHh!ok))cMpIhT1c61T5>}dF>E0e z%j$e zD-@V_ZX_$iW17nbX5=b&)cSLL>(nFrrOD9(zS(#!ZG>3F-Sf^bXpXqvry^8@ODcmW zy_hGOCp0OE{9u*v=TJ;rjIkdSr-&Em+F8NWxk_Q*di^-duhq4>b6$Z)H(QBz<-cxy zyfGbu`_9IdJ)H#vIk&ijc>c$%QG+Jpf&%N6W;MjcYFnmb|1F)o3TV=P$DU4C!Re7FQa++xD0DWALDsn59Vsg8_l-2V+t>;T%qF%XJ!ld> zN|4K3O!8C`xH9eYu2oi*7+4BTllKHA5}4hpyXYJ-vPf>(FOr;b82^wnR-Y2xfR@YX z7wl&C?H$1k5$cdwV)_lH*ZxY?ssjmzRRfdDQlPd#C_qhYrP%rnkktFRENJQD5s);& z1umv^I5CO*qG~lbdRN&od(aK{kDdVK{^~+qoQQv$K5!NUIikD-%ShR^&`sB20Oe-GF94Z}we z!CFzF9r_1O*rrKo*}SUs_}P~m_Z~nScT*%0^B-asj(iY;mAq?p6Ic(7Qi?Ji?FI@F z^A+=K@e%rpk;gYuY8jj%;B0FV=!)n@hvRFP^iH>WY}T}l1;>fGH{$gVLi|_kHqK9& z0`YoJO#se_<49mZlV@R?(JX5Hs5M2vphMj_N0>8qcE96cwo}Y=Jag@|gsawj%f4islos;~@P%8bq7Z`-k;a=!^}ms+2_5SMmP@Tso(4jiWGbJ zVmd(6l#KS$8ys8pq%L~62A)y9W_d*}PIpZ1&&hS&2fps2Qi7AL7-VUgD$!?_>3iK_w4qcgz z-RyNnCMpsu#YswtLeV@pG4%Wm$SF$dgwioE(wgMOE}K@H>*~$(km#vQf*sB*!d?lu zN4uB`{Q$URpj17XYE9-5K_!U#tu@=}yxur9;)}xR{MC5D-V#Ezv!PG$Ys`WRHMYHzW=lehAWdRTheboUX^<`li=JhM%3+4*6S7hZvrJMM;!(7v}dp4;}# ztwR8!4+5*WJ}9C-{}{W0Ep1IFN5P^{^QNxkMYOn)IOMJVD(Pe7qDg6a(VuZ_ekt1( zCha(wPm`vJqWzyJ|C zb<{QApz4x(odM%J8ZR8jM;mSAAoW836neuGE{KQUM6OF?nP$)=BB@mQ?wO;Y=VG(2L01Fj1vP^aoUJrCYM1~P{bgoow!g1^l z=7llJ!yp71qsjglBv0v*(g%int*&2imCpUiO_DcSmG#57ei;<0UuMSY?r^E^a8Wfd z6IkW8X%|?w+?PJ4edmA25MO0Im~HXsmaj`Gk2KGLX-j5|cKdXGA?~!js zycchy7^3SWKCp9+1_>TFF~VzSm%4 zsZyP6dCb|OvRjFqF1L?jMT*JB&!RLJPQu^)B#&if>_yN>iDf5hUW`#5POhnvlH0bM zkb1f2EE>7oOdI%#DR?1(kT+;u@bt}9?}G>U7#iz=%IpsGZoSR7lKu1**hzRG);!|p zQ$mR#kc{Fr$w%n%L5Y8fjzWPR9oM@2M&kjVy+WOFqV4NK@+9k?CSEqwt>*uvowf@+WLiYC2n z@kV~^x}D8{Iik9s_>!?!sd|^fk@t#&ZL$b)!|#dJk&&PG)k#M({F42!B_q&|S{O9+ zfeT*ePl)o4)#b!EZNwqIm$<0jyNvMv9X(7;NbIFLY_&ch&!k89e2Sak(5@pqB+a1@ zF!qDVcR&-~ZFn!-i@6X(G^<3un;of` z1i4Ldsy`)U{}3?^<0B4f&S3`9cbuNXb+`nsUw$s#L3{aU=p+)I>BtL0h%qJa)ACiT zjsoBmW5r~IAG{&(7a|^FMaDWKX|CxHIS@}`Q@=q|tkh)SwKx&)%|o1sIIH?81MAS) zTH(IiKwUyGsR#(pBD8wrXD@r2faZ4z_W+HhT&K}VFCILLRWM5a<-4|aEELmG*k-7u zh5qZ7a8j2pdCm|Xyga73FhC%~PU?MC*g_Z@=>=h>8&-z-)7TwcH;t=N9?9G_E@)xR zX$?Kk6z*_bfUO<7t-v&{cq;UiuRlK9ahE5$?S|8b5#rG+yr~U%;ju`@6vw**@%El++B5bUKZIZ>p|(rdZ2SRFCQ|>Gi@W=Zgf=or}}Y;o-#SVCjnd ztm8FoFIgS>(@G^#u~FD1X!(0pvI_uOn8!6ZKb-oS$$R$JA1%@;90Iw@t3T)}ND7c!J6qrwXv>N zM!1|iS6EkJw@5i*D8S2Ge`@e>!{PB@ip%@8hs)QFCOf*S7r}m0VaLg`&6!X_6sC|; z=0Tn8t27Su642QM1TFi-{_=cVOZM*QpB;e$(SaK?f^@UEwLmE87PCI6`wPWI=D5Uk z#F1#ao5_(ugvWXcbI!u}N|!bL6AlKi88$Mk8OU|rE(*`ptUDS6BwwW<^wc~NE{rny zr-pV+f%s=E+dq}nJ_6E#v9C5Rs{CwQS)1R%`EEewXab8X7z7`E_0<(gN9v{7d72H~ zzdtK<169kYnB~&h(p}~qx+22}Xq=zPK_WY|`pRT^SK+mI*J06ZJ}26%!X2{UQJ*s& z9nniPI&d;<7WKNYyzLJ4?oA?7{E4&k;5x(|s%(~=QyOR5Wbdhi+^}g>1`A*&0{>VW zG=?_xv$U=0XuZT>Vq}CBO}M&D70LTjE&GD|R(m+!WN)i)kB|#L2li-cc@5aMxnpqp zMlAt>gAtkyX$=6W8{!E+pBw>hu@(pBi1r5DJ7F>40AJ{M`~a}dKEs0D*uPcTr540U zxdO6(qQu1OI;4T%41X_KhzA1nlzW?fosaY7*Ap@2==lBW(%WNR>{u`AMy&)p0g*N{ zHYfhRmE$aqIvI8>kl*Tedpm}b`5fP5ZVy(lzSU+p#g@NV)-2sD)VSoD#PGt0R=!VD>T}a*FYzZ$e z5#5IP9vWS_UIE|<2z%OUJh~hh?&wO4i;`MFRkAZE#=^sduuvliiYuOni^}$ZWrLj@ z97;>#7DV8AktH8Ke0%h*YGIPL0il*Dz%*wkfQTHWaElkI`De6m2uKUsS-Vl)?XA!< zn2LsAAzeMY1E+nlAKYvA?~z4ms+KG$6vlCwAvhqKLoTVgOTmF$Wujh`E=bbnXq*5S zYi-ioTAS0I^u_M@(oQYGGP6;5t<;j<=*{$i%_=Xbx!zD%KQR~r_&J?yc-8}k4TSdP~FkR$j-w?Me%X32BaN`i52!B zPrfAFvqD+xsvWeb$>E#Ra{tP?wlVXJ4SkcF1<5ArdCjSI;iDT!--9=tVT+wnNO1eH zy}O}cg6G2`K*(2$tzbzs&s~QX)`f_%O{PzGP{x)2wmpdqJ11qipuC_-$}FQ7sLV1l z>)aYHy()R*=vgplvdLB(7ZZDBBn^wyQ<^COL^t^K8!!~(@yX1lmIigL@)|MTz8`gj zXK@My_fdWT`%!jyu?PH5a;dGPWo1-3;afdHR}Jb11?_X#}~(l*sZsmgaE0 z3>H*DIkfm)pBL5xrmcp1P{K9-q0CxlkEu|VVpu~(kgDKlW99(Pd?ZiP_oDd$iXD`8b=f2S&MUODF`z75NxBn6NinNcY($VoZ8=8XNww`9Gy-bgF0VAgk%hZxpG^gd_vI-No6Ea zGo-^z7_0WW4VC>)@3Ag(tf9M8@7vhdM&4_;wN~hMaC_OGrCl?r#tYssCt?pNyo1Ar z#@J5_!Z;Y$I?H}27rl$!Qd_|k(3p_=EFLT@9%#*@w#x89Q&CAWy}L`0$M|tS!p6KS zWq5iKnIF4tWdF|9LCKLdYj2{3<59^Gds9bhVM1y2IFFx*08GF@Ap=Ng$lHR4dB+G1 z-*yG%n*3jO#0CE~hxjVhlKsd1Z+m$nmW#ovtre<>{U1hxtjr(_=eHCP9I&7|fa3OnXN5nvp&~+MUH`|DsqwwX3<4+Oeqi+a+QfU4= zP4CUSstuN6umyDbZFjOIp{s;ThB%&`qshly;T4olKChJZl)YqQbtz$^X z7eVA-2P@!j_Pjmm(4)O*#dswgdq{O%0ij%0e$-T<0PHOGg zW3M~c_SNfR(goQPk~dxQJ24+D$D~%3`hC?)|8HvddTF^0XEP$7DARdu_Bh!RX|7r# zlPG0o$uQTd;#Yi@ByV-MUAllvlUK0_#+}+BqA9$e9+iL>JUVBMeBS}B>r{)5Kw_O~ z^hZnGqQ8WK;uD4D(18PcOz{ZA4NcC$ffi6AJ13XkS!x%&9YctZt)JMmMR37o!4u}Y zb#JtZlB8#-uKLk8tRYjA0Oj=~rAD^#X;ztEvgSA4Ovqw>ounvBrncQ^Traa@+HAd> zGO}@BBHWtN)*PxhmK}WhV_U+jYXZu(5)&Fp%>%v!M&&h6lO$b2OG2m-@0_AO*VgLl1yK`1{-GTs2T==H z!fI%>J1C#kJ1`-rsGu1mWmw3=Hh&sxfR7q$l0V?WXF-l+1|}ux-WC;=WEbfkF?3XP z&NHk(Sog+_);PRXcB|TOSl#V>(;_RAWXNl9k~}k-e4#rr6P(2DmC25R+>;G6xfc%j zS>oyjm8F^inM`1}Oj~4Njv0yJY0Dw?7Z*YVw;ZCDb`1;V=s#pGE7T0=XzhseJW`@w z(X03*U;Q&E*sVC<=`#9HTu zK5pG6e4o%Rp0bn#2B;R@1oV%C_u}S#jb0sYw&Z#B8Mf{ES(TH5Y|L{wbO6?o6tk6` zFV(Dg=`_XjAjqwN{bLUfV#_j|D9D*f7-pFhhbK^9_Bn32d{H02%1H#_=!Ad!rqSh9 zv7G^;^FBzPn`&LSE?N$ba~p9E_EoWN#!I!XI}gBg^a1PaDth6)hhDuo7G*VG`7BXl zi)DcX;KX;gjS^|dNxrX$?$g&Vv@B?F_sqHcQeFGXw*n1`-PT=z(Jqxb_mEo0G{|%S zY6#&RW1u>9n~BApy^ZrBLF#0;9LKemufJ0fZ{sy{pO4etH2I=A_BN3%&zip6Bi{r{J+Ha`pVg&jt5Yi{!;xYYd*M>LvTfsn!oKVpBNQLD zx^*b!!F-sh>{NMwnEXU@Y}1A8C*YjiPRsio>v4S3#ofYL?Z@lvHdfGrpEL zW4#<>P{}}{`CKGg)}Hjcz0%K`B`BwEhoT4@8UYa)*_UWnJzFgz)!VY$X%bUt4-nVKzJLCzs!Z${l%h^95bT z9KQt?f1GZ|GII7Zn5FQU&v;{s?0InyH_b@}7o5EG=L9j+ci|q=cD5+8H`mD zSk`e+vAh$!lFNm?$<)l;QeI-k4112T{=v16j8t+LsI9RX<&o%3x=8fHIX6G+onGj= z&VBB1l)sb^oIJ`D-5HSyFaOdoflNgx$s7c9^eGNbk{^v@(B-sS8#$DLrph;G*$ST& z&7Kua4Qpjj&H6(CBQKWPy@TAMRZ-S+3Guknh2JUG148km%aKeVy^%)Tji0s#R5aCu zi3gu(y;?U1$?0^dJo%*f&?1UdBKCSI*IgvlC9-U^zBvLi&{)Q{QIN2dC@;p@yDZXi z(wIPM*F7ukmbm)8Jw@{v=vI(O(X z=`L{{sj%xWcq)eow22dqR+}=2xrbDl$(JQsJNVO1v0C^ zHR<;^v)g`kz_r2`F&n{O-|;+iZfjlf1eJq;=v6P3q8Ep4j7ueFwg2k%-14hyw@zB@ z-eQ#|M0+VcRAhcW;~%AkDa0p=?$0ZbHrfhJ8+wyC+E~f z;8=e@AqwR~DKgo)b!iPs|AM*zxZfrGEL6>277-rSQieSnVZ~aCfCjKt?PbomOC2GO z{siR0H;zK)2dcO{T=u@n6JNZ0Y?;tZ>(=TN84X}hof0W@<(AwyHTkwvT ziY@z&34ttvM>-nU(9bS0Bi`oaB3q_g9$9@UN#CY5Ipq(kJq- z(1J)N3|E3Dt9cmMSq+37t&(tF9wv2Ijz8CcBIv#FS83aET*L8S5wh2pUKqFjr4qX7 zMCRdqsOX$7b|Y&=x6kfI2fU~wXyvQ%3SW7Wu$925OP=EDRel=%#t^ao<9Kcsp+T?Y zGQ=O?U`tH1^mt0KXQ#ZkV1|JKO0J~^jnbiYLSB`Nf>x=?I%d8DKebvf>0VWx!X1s0 zU0+1Y{OBZdY-cD==3~9D_4Wz;8mz0BAq5f^#j5K=_jJr~>9kjprcRA0TR5%4iR+h&t(HHGG*^bH58}@Dey;kppF6nJ48t+%_F`IvFerUYsb; zj!Z~T7>*!{+tigzZ*^RA#4?%4QBB8+M5U>7EROO;K}zO?Wryd=$7@Pce)gdT-;KS$ z+6AXHXSOV!P2^!@#tUI~@vUQ^RNu(1hsjb*v|A#3(^(EYf>lw~5_AbIJL6`$H%55B zYAO{)9Kak^M9Sbe*;m^`hx_6={;_w+ZXC?2rC1DDt>^VXJJ)V^j4vaSSZ=mpwRxGva{4*!Mo9h z-2VM)OYX>F<-wLeiEHeH*837zRP9ap&=oQ>$JQzcSS;68{$!*C#141yhpOrpf&}~9 zwh?5mH~-GFCZHseitU4Akb8rFK)a^K#MI+1sNyRQ4?a=RsI@`sE%5~Luu#>^UBrk)QRWr=Kg)w|k7*9Js8A4Ze-A?t4sj-I!YBBdFiD{*ddh+L zcMjLJU)!t&zt&g`ZrQT4|7arwNh#l2h`3(HLjF~E1w$Ju^yK^cN!gat%d%32)6`{Q z4A5O_IC6WvD)}7e+YDrAtz}u+R6=4?_jcXa1U-0p)Nch&wjjVI@&WIg=X)@LlMVBa zIk%RE8IS4Sl%;R+yz!mw5qq?(<{02R#))ThYn+v3WgD3ivoASE=L&26Fl_GeFy`Lc z?T?UL7vNuHai;~Lnrtu_^H^g&Mm3dCRi!KLW|_r#}biaWvt6&NpK9}YEOm`43Wrv zAZVzHI_Po**I(ryrPRc0|~vWdD~J_6=Ptxr;?rv zi#KgX9gOzac>Ep?O+3CI-!-dQZPu#|-yYoA7x;3#$JU?x#->jXKnn3Nhgf30W&~3} z+>@v-Ty)OfuTmja*D=M#Zgd9haM4oj35J`b$8{a%hh6r!1wXRrKAry~k>K+rk5E^V;`0rdzVKx0Zd2sih2qD?%5HO?Tjm#kTPvY7#6x$-UYmFuj&EiJykxP6T@{-Tr`H!%_r8Gs7d%sP^dz*019an5~o^XC&4+_w` zz`7Jd^a^SuQsyS=qED`mcGZ;fLB0HH2XPJXQ)-#rac%cD4wma9XwuSsuc6&FO1n%u zg|c}h3jC*g8%(KU26k(m|Ux(MFM{oa@7ZYxD!SHnX7<5;@+wPnz$PCN{abTLZs-;EO%cqBbYxQR!jH6SL(CQ z*-0OI`Zd}N7gNB}nmub$E2Y4vm`KCeT;-}Fhihw`;^CMw>RgM%G$eTCb|ltmPWID9 ziF~AqOY3F6scxjLFUc$v*?m!D{)87&ngR(t6qIobx)KA<^Xp2B{hlUW4)J}qm~`1- z3#f~AZ6*n%LU`I!|9irENBqe6QW8Q+Kl<=b{j7m^kD0~Nb=b%u!IMeflB@J*6Q6uY z(w%;lH@Oin{!9a_y>|(ZfZp`mJ1)?^&>vQ6&;!T!-D}=Pj=nkc-(>(!+8yNJG@Z7t zGg+Os_%65HELp9{t~1qjz!Ng8qgPmS`~NZh0{esGl4+C_$`!KbGYg>?aEC7U6&5Rn z5EKQkJmK1}(hAor6`S{lcmDKCNXeo+YCczYwm2rXZ z7-`#ROp8ru@hafOnt*rNSY(68k2D+*Y~Jdu-|da2ADp7V!Vz>?h)0wd!z>oF{a!H zdk%CT{OU8TK7Kcbmvb$@@z!1{eqSUVW)dA*gCdU#dg5*~7{l?<0Lr@c zVr93ta?3Pb$ou$`R2pctwns!GPt_sV9M=kBk>pI+)t+%;^z#Z{D zH4@DBn4g%dteHz|NL6h>KPRt+&^C*dys&!W3tEW;#zW;<{{iL)JUv549Xt||Yq%zI zt0rXqU~ps}K$6$xj{|vm9(`!h2rkJ_~r3tNr- z3T$SK&YVYcdqlBj3TXaTYEncjL5lNw)j-iGE`0n2>E(0A7BrM7C;h^kl`J9TF|wt`YBuwwzvwNjaJCc zf(El)7L%MRc&PZvs|}bmxAx=v+D;hZ)WS`JHPu4_Nttl{yCv@o78BEqA8%`l-@__B z3L3=71a8ZmlBt4(&vFL*HJ%?OiVx-=P6dOj1mCt9Xl0(1MRG!7zhKbGk6ZE^fMdy) z@8Lh&e>o5XW+NcNe16zC&w_MavUfsn-&WMPNy z!|3YeNErrLk-K~!UWnVt&R=}2Vyx6!06lJ}s*r9OrQb_;-(2Wx^2n@sdZeCUIOAa3 z9#p_B$DI))bBp^T7uZY+br)kkS?!h;iX7R9vQq6 zAH3X4aV$cZJ+<3bP|i!3GpP{jfI1ROB68d@x(Gv<(~u&L7vZO@mE{P5#btz@;+w=l>i{_g9@^9RLgBBMKkrH# zl(cJXPMg&9Pd5~{TT3j9#vBuFYyHJ=%j*qI8wbh=Sf)NV(^2qi zq66KvXV?p-e0RtpA3s-xc>r>SWh9aA;8hIeKA`gqY93)1ic5$XG7QaDA6baiKAxg# ziN7&r^bcq5@07W+?8!Oqk_JNuAOG69_<+#x`R6X~P!Y0`L?nthV+ypMqM`6`3XqgZ z1MZs@IC{*^8Zojl_OUWfHN{Q|?f?KRUV>xm)!yaZkfE^dkC|*Z9+V;F^|nkj5DP*w z+b^n~9V?ZBEFHMkF}L3PwynuSiD?byr?i`F#Fd7}*C)eQ$f(qGfs(k8Db5@r0E8f? zY0D{EVc;sf0L0V>U(SD({t-85T}a*+ge@gdyQ(SCoiM@5hA%UG^v%A*$TT(b{#M~^ z{4=vv1wp%T2$>2^_jOeUOPm$e41)i58&k#J+pQD##ou2`A`N7vJDLZ?a)nLUo-#}h zyaO&dIoQ_1hb=a&W_V5fXz0BQ(B4)Xa2#=AK@}dlHz)gG1s!gz_sx(;2+V!_cJI3m zuLZ&M0YmQd0vn33zina$eR?KSq(*vwJ#NXcA?Av3wH>(<0wRQBPNi_cGJ zjas;1=nltj-o4EjEl2*^fb<#i;Eg|FK-t}DM_v4uoD@IMo~v*2b~Ld>mSnQ@FIY&_PCL+eG(GSW&-};3W^> z-5@p&wB*xT?>qG^X?xi@C;Fv5-ugebgTjRa+H-frhE<>2?LKmPDtSEdUkcR_pwIbv zSDb3Gu5z-E#F)B4h(q-vspc4)^*Mms3u!-+*(ft17jf3H86y(?$r2v zE=M`nT66TpP#uxYLOr9l3aou&`|crSe}uwr&QLCeyHeG1)}xb$13YPe=72)S8q5=f zjzP7iSTbl0;qcdRowDG=Gd&fL!1RHl+;>Ya*Q2(VS$qN)-^$oGO?|^twr#7N3SJ|>K{ZvHA;bJJg=He32ItQ;I*ZZSu+V^l1FWDvUE@$qh`bJ z=yE2+jR*fhFA#H<_#yZviy5m6aAeu@-h568f6%<}hRo@1MO<^NgWU^&Ay-IRGZ@X7 z$ik2PVf9{{WtImC9N2(Q#BA~_gQ5Q&9?P>MdKa3jJ2~OoEvWFx{pM~52@A64yHT@m z5Ng~tpFpBSHStWvs4bv`ZnltlD5a2gd|?B79&&fO>;!M!T#0R$k2Fy5Q#f}SJs@3& zGB0{XAkv%ssQz6Btcn+pFp9#(BO%N~H$AQrNA{!8hTHq6YFLe|Pg*sa+Wnw<=^u%V z5jTCk0N{KDIl!I|6I^J73huKNy#6!SUFJfk%4&udY+{|MeDS>&pGlq2-RFK>U{oO~ zFw1esvcr&vcqyx9BB>|YsIbjGeCN|*=i(LFdH}7^YpI~Jgqic!PJ^084k4@GLnYPi zd*0IxCR@94rCS^Sr=6K4ll43ZEgO_ zGcH3l#v4^fds+nK=9ux|q14LR@oP zB<-Hq5!3tq&m!rzyVRq-m5aVo8JpcsI_?VRAn~I;fS_ZXY(}v2BH`B1%UM@0@Hx&_ zzl7ku6r@&WN%6@4a#FuDS!bWck1c!6>n8@iA{E^e`5^aHANQ14)YYDVvlU&KNKyC= z$P1IdfbPSsI?FqmO2kuH9U5w+Lq1AJ*~kE5IVj>q-e3{1pakZ?#l=`(-`2(K*2UO? zHH&)%UhV3elY6Jwdr$8?`UN_V^cuRnRA8Jn-If?!tHAB>WT(N4kxyIV!+@`V_*$Krd^HL+j)P43KNH?IW}9^}4v`nhxU(T422)+*x-jUjjZgn6Uix(V+YoO=9Ay zqBFw+ZdSY36iWc1=`F9g5hG>pp)-8GX*-_@)|Bl7&D(l=&f5 zAJ?I}!+5y+g_#w7BE81!LYtCho0+Z)nOaeh*z#}-)ll1;;unVqBiho>C=ab?KcMF( zV0fblX=^tS&lHp;^_`p>X|B8Q=VsD>mr_Hurq^IgH4}wMk|*cV!&Fon%Lb#|RAt z{r1r*yondoLF%_8BFL|BrCjk~Gcl`@MYN_!LOZY_taJt;=>eUtL7Y0oMKR3Y35l-c zaR<6L;yB&=!F0m-QCJZ4f-d2zKa|>Tb?WqwxW@O1RaTA9=o?3MTrez)jUZ%iyQZ3a zT`4lPCL}Air{tOrMJ-q6k<4n+n)CdTy0L7AEYG#1SI$*UwLlOF=({xXBlyGKsCt4O z6B4m|!bsGH)odE%z_c8lZ1c2cSWo}C;e72PdRPo5+j>*sJr#kmpz~oz8d0p#nQVbF zGc937tWKVr;AYT1)NkBMjzn##xe!wmB#qz*$5mg`^vrU;BG1aILOQ*G^8p}d2uzeT zHzXj_NA*~KPIFVZX zXX*M^KUo9V+pe&pKQRo8nE2{R8NMm zdJ>|(3qXCC-?H5WS_ztE01AXyr&KsfP_As0lJe{I4#T(bw&8Z%w?`zZG+L5((1&=Q z=7Xf2QUZ58*rrrFJ9VXLB>KYcLzMxuM*}Y5{9G(qF->uN$zR0Ho~<)f_?VK46`I%j z^uq}*8Iv%JNOq#^q@)y^W1`sRIFwkg=BKbOUhb1f9~peBAP~)fZ&mefUp~OdE9yZ$ z#87ZqhiB&Zvg#CR?^sK|ie91NDbn=qVF{7mkHAj?sP4rbdG6-4WSqBFUt(8_dv+Hp zz=^7!RqJAwdmH+B2VI45E1AE$4{q2XvztOBz(o)B#Xn9H;AZ-=wTJnDD>}Z*&aAi- z-3qZxhsLk+8^C<^y6>Yf_{+GT(=O1hF1bxn$qy+myGRp$#JbhALnfR0f}ahX#xhgT zns3e>9fS_rZGP0#qO9l(wJKRX0a_z}lkHdO4`WxfgJX92s+jM!I{+3{ED!IWy}u|U zfn08u0y+Hf{POs0Cn}BI(dZ7eQMRYg^CCe{;`4fyRMkq+fO2DBSK53v1w><0a* z2*Y)0gwV)yeH6XCDZz(-R36fEAL{UMRtCcrRj1zU>ZBm)roqoSyRtvJLO3T9sFdoW zm~o~MaCFu|SWm0#wM+hA5B)*MF3~yMM&vAu&>qDsS_sV=otSl5Kil38X7QLg0M6e?^U|R?q`5AiJ59U7?X{iw%M7=LF==}@M7wP5M*u~P$m4*4Q zeO5x7!wbk))Ng1en-zYBJdDHg0_V+dXzhHoB}}*osjQxw5fXxV1!SevPM@#-IpBwV2&QOOl^iCR5IVNqs$tClVcoD$bUK$yW| zR*}#Oi;9?|e+|E)v$&0{0tf2(caR_ZuPbCOsa<)>WPO6qbGuI-n{u;x6goH=Qe$)Z z#6^C@Be!vLhc)IDjo$hPzn!f$5o1=(siU0D8lSiV#?VfDq5kuWe&H$-PJ)Jl(te4J zm(=(xGX4T9nmIb!{uLTc>S`*htjJFckO@QO9^V}iByqd7`VJDC?xz+ zgBJopdC*}C7LkG?la=DMixu(5ppP1&80BF*yy=)c2rK6;*}0#4{iz9}dXT_9D6*!r zc|)`_!IMP@DE2*xBeX7R>Br-VY{B2qf(g$tD*Ql;ayu+qA;Ryqyoz*W$WF zX!QIl^P_(;>NUO$T=+|+68v)j|5>d6dl>&wxI+m_h=Hu=(lwYa{xQoSDK)@aE0Yn~ z!Cl~N&}7olsjYk7SLXKk>V6(_2#__Lv=qC=Dz~_aYS;)ZX*2}94pnMh3vgq{3H@En zUJVeG0R(DEm3#{NfKa`8i<~|TEFPte>_J!3YJyu=x;Sy(WHI@Szo-^Cs=hNmKN=pS z=4c+Ui=e`x8&RoPI5Do;-xExs@_}D9>@!F<^Ls|u{nghS!JeV2FX6{C8)LiMQ0vnQ z8~<6X4kc8bBrnAZc=Jz;w7!I+gPWy;o3W;slclTSUj;jqG_CwHJBN*n#@#=$du;kI z82U4u^dbc<7#8g1m^r#R#;!OdiWZpBW2Q<+pF;NUBI|txqzP8XKW(H_vVk1j0|RL zArKdE716|01lDw`UA`A{*^LW~4sYFjlUI#-KRWHNv33lt&W>%NpLHN5Fr}WQB`bsU zp@h2n8t-U1Nj2C7(kf}Dn9E=XHLA-O!CX8x_&($U$df5DstC>Jnkg>n!Ps}&+Ccrq zokSM_GyRa#D2!3)!Dpt!Q0zv5JN6f4p~pGweC=e;1t6B?p69mienfa(!ut8X{8xqB z@*RgU3S?~Wy{ZB<3>NHv2d}@>&p$T*mmm56V%T4+Uo+4DsfB{_haULf>VFZ?U&~)Z z!T*+Ty}bQP5WPl(zczRch5fg|R|++AI^Vcq~mtOyN zftJRTbb~&VE2aA-+6@~ literal 0 HcmV?d00001 diff --git a/infra/deploy/devtoprod.config.example.json b/infra/deploy/devtoprod.config.example.json index 08239e9..a977ab9 100644 --- a/infra/deploy/devtoprod.config.example.json +++ b/infra/deploy/devtoprod.config.example.json @@ -2,6 +2,7 @@ "_comment": "Copy to devtoprod.config.json (gitignored) and edit before running npm run devtoprod:release", "profile": "full", "autoConfirm": true, + "autoStartDevStack": true, "steps": { "validateBuild": true, "gitCommitPush": true, @@ -21,6 +22,9 @@ "stageAll": true }, "schemaChanged": false, + "release": { + "imageTag": "latest" + }, "backupFile": "", "smb": { "host": "192.168.10.122", diff --git a/infra/docker/.env.prod.example b/infra/docker/.env.prod.example index eead4ae..2f6ae89 100644 --- a/infra/docker/.env.prod.example +++ b/infra/docker/.env.prod.example @@ -10,6 +10,8 @@ DB_NAME=gallery_prod PORT=5173 HOST=0.0.0.0 +APP_ENV=prod +IMAGE_TAG=latest PUBLIC_URL=https://gallery.mysuperlab.netcraze.pro TRUST_PROXY=true IMAGE_DIR=/app/data/images diff --git a/infra/docker/compose.prod.yaml b/infra/docker/compose.prod.yaml index b08b34f..9f91bae 100644 --- a/infra/docker/compose.prod.yaml +++ b/infra/docker/compose.prod.yaml @@ -6,7 +6,7 @@ services: build: context: ../.. dockerfile: infra/docker/Dockerfile - image: gitea.mysuperlab.netcraze.pro/danilka/gallery-web:latest + image: gitea.mysuperlab.netcraze.pro/danilka/gallery-web:${IMAGE_TAG:-latest} container_name: gallery-web restart: unless-stopped ports: diff --git a/infra/docker/compose.truenas.yaml b/infra/docker/compose.truenas.yaml index d7e49c4..8647c8e 100644 --- a/infra/docker/compose.truenas.yaml +++ b/infra/docker/compose.truenas.yaml @@ -10,7 +10,7 @@ # See infra/docker/DEPLOY-truenas.md and Documentation/environments.md services: web: - image: gitea.mysuperlab.netcraze.pro/danilka/gallery-web:latest + image: gitea.mysuperlab.netcraze.pro/danilka/gallery-web:${IMAGE_TAG:-latest} container_name: gallery-web restart: unless-stopped pull_policy: always @@ -18,6 +18,8 @@ services: - "5173:5173" environment: NODE_ENV: production + APP_ENV: "prod" + IMAGE_TAG: "${IMAGE_TAG:-latest}" PORT: "5173" HOST: "0.0.0.0" DB_HOST: "192.168.10.122" diff --git a/infra/scripts/deploy-dev-to-prod.ps1 b/infra/scripts/deploy-dev-to-prod.ps1 index 447abde..d550c99 100644 --- a/infra/scripts/deploy-dev-to-prod.ps1 +++ b/infra/scripts/deploy-dev-to-prod.ps1 @@ -203,6 +203,31 @@ function Get-LatestDevBackup { return $latest.FullName } +function Assert-ProdDbTarget { + $prodEnvPath = Join-Path $RepoRoot 'infra\docker\.env.prod' + if (-not (Test-Path $prodEnvPath)) { + throw "Missing production env file: $prodEnvPath" + } + + $prodDbName = $null + foreach ($line in Get-Content -LiteralPath $prodEnvPath) { + if ($line -match '^\s*DB_NAME\s*=\s*(.+?)\s*$') { + $prodDbName = $Matches[1].Trim().Trim('"', "'") + break + } + } + + if (-not $prodDbName) { + throw "DB_NAME is not set in $prodEnvPath" + } + + if ($prodDbName -ne 'gallery_prod') { + throw "Prod DB target is '$prodDbName' in .env.prod. Set DB_NAME=gallery_prod before dev -> prod migration." + } + + Write-Host "Prod DB target confirmed: $prodDbName" -ForegroundColor DarkGray +} + function Get-DevApiPort { $envFile = Join-Path $RepoRoot '.env' $port = '3451' @@ -217,6 +242,72 @@ function Get-DevApiPort { return $port } +function Test-TcpPortListening { + param([int]$Port) + + $matches = netstat -ano | Select-String ":$Port\s" + foreach ($line in $matches) { + if ($line -match 'LISTENING') { + return $true + } + } + return $false +} + +function Start-DevNpmProcess { + param([string]$ScriptName) + + $logPath = Join-Path $RepoRoot "infra\deploy\dev-autostart-$ScriptName.log" + Write-Host "Starting background process: npm run $ScriptName (log: $logPath)" -ForegroundColor Yellow + Start-Process -FilePath 'cmd.exe' -ArgumentList @( + '/c', + "npm run $ScriptName > `"$logPath`" 2>&1" + ) -WorkingDirectory $RepoRoot -WindowStyle Hidden | Out-Null +} + +function Ensure-DevApiReady { + param( + [int]$ApiPort, + [bool]$AutoStart = $true, + [int]$WaitSeconds = 90 + ) + + $apiUrl = "http://127.0.0.1:$ApiPort/api/bounds" + $detail = '' + if (Test-BoundsJson -Url $apiUrl -MaxTimeSeconds 5 -Retries 1 -Detail ([ref]$detail)) { + Write-Host "Dev API already running: $apiUrl" + return $true + } + + if (-not $AutoStart) { + return $false + } + + $viteUp = Test-TcpPortListening -Port 5173 + $apiUp = Test-TcpPortListening -Port $ApiPort + + if ($viteUp -and -not $apiUp) { + Write-Host "Vite is running on :5173 but API is down on :$ApiPort." -ForegroundColor Yellow + Start-DevNpmProcess -ScriptName 'dev:server' + } elseif (-not $viteUp -and -not $apiUp) { + Write-Host 'Dev stack is not running.' -ForegroundColor Yellow + Start-DevNpmProcess -ScriptName 'dev:web' + } else { + Write-Host "API port :$ApiPort is listening but /api/bounds is not ready yet. Waiting..." -ForegroundColor Yellow + } + + for ($elapsed = 3; $elapsed -le $WaitSeconds; $elapsed += 3) { + Start-Sleep -Seconds 3 + if (Test-BoundsJson -Url $apiUrl -MaxTimeSeconds 5 -Retries 1 -Detail ([ref]$detail)) { + Write-Host "Dev API ready: $apiUrl" + return $true + } + Write-Host "Waiting for dev API... (${elapsed}s, $detail)" -ForegroundColor DarkGray + } + + return $false +} + function Test-BoundsJson { param( [string]$Url, @@ -312,6 +403,14 @@ if (Get-StepEnabled $steps 'validateBuild') { # Dev-only endpoints. Do not use verify.lanUrl here (that is prod on TrueNAS). $devApiPort = Get-DevApiPort + $autoStartDev = if ($null -ne $cfg.autoStartDevStack) { [bool]$cfg.autoStartDevStack } else { $true } + if (-not (Ensure-DevApiReady -ApiPort $devApiPort -AutoStart:$autoStartDev)) { + Write-Host "Dev API check failed on all URLs." -ForegroundColor Red + Write-Host "Start the full dev stack before release: npm run dev:web" -ForegroundColor Yellow + Write-Host "Vite on :5173 alone is not enough - the API must listen on :$devApiPort (PORT in .env)." -ForegroundColor Yellow + return 1 + } + $devCandidates = @( "http://127.0.0.1:$devApiPort/api/bounds" 'http://localhost:5173/api/bounds' @@ -395,6 +494,7 @@ if (Get-StepEnabled $steps 'backupDev') { if (Get-StepEnabled $steps 'backupProd') { $ok = Invoke-Step 'backupProd' { + Assert-ProdDbTarget $code = Invoke-Npm 'prod:db:backup' return $code } @@ -403,6 +503,7 @@ if (Get-StepEnabled $steps 'backupProd') { if (Get-StepEnabled $steps 'migrateProdSchema') { $ok = Invoke-Step 'migrateProdSchema' { + Assert-ProdDbTarget $env:DB_NAME = 'gallery_prod' try { $code = Invoke-Npm 'dev:migrate' @@ -416,6 +517,7 @@ if (Get-StepEnabled $steps 'migrateProdSchema') { if (Get-StepEnabled $steps 'restoreProd') { $ok = Invoke-Step 'restoreProd' { + Assert-ProdDbTarget if (-not $script:backupFile) { if ($cfg.backupFile -and $cfg.backupFile.Trim()) { $script:backupFile = (Resolve-Path (Join-Path $RepoRoot $cfg.backupFile)).Path @@ -446,7 +548,9 @@ if (Get-StepEnabled $steps 'syncImages') { if (Get-StepEnabled $steps 'dockerPublish') { $ok = Invoke-Step 'dockerPublish' { - $code = Invoke-Npm 'prod:docker:publish' + $tag = if ($cfg.release.imageTag) { $cfg.release.imageTag } else { 'latest' } + Write-Host "Docker release tag: $tag" + $code = Invoke-Npm 'prod:docker:publish' @('-Tag', $tag) return $code } if (-not $ok) { Write-ReleaseBanner -Success $false -FailedAt $FailedStep; exit 1 } diff --git a/scripts/dev-web.js b/scripts/dev-web.js index 33d0cc1..a749a5b 100644 --- a/scripts/dev-web.js +++ b/scripts/dev-web.js @@ -10,7 +10,12 @@ require('dotenv').config({ path: path.join(__dirname, '..', '.env') }); const apiPort = process.env.DEV_API_PORT || process.env.PORT || '3451'; const vitePort = process.env.DEV_WEB_PORT || '5173'; -const env = { ...process.env, PORT: apiPort }; +const env = { + ...process.env, + PORT: apiPort, + APP_ENV: process.env.APP_ENV || 'dev', + IMAGE_TAG: process.env.IMAGE_TAG || 'dev-local', +}; function run(label, command, args, cwd) { const child = spawn(command, args, { diff --git a/server/index.js b/server/index.js index 8b65a3a..667dbae 100644 --- a/server/index.js +++ b/server/index.js @@ -18,6 +18,10 @@ const app = express(); const HOST = process.env.HOST || '0.0.0.0'; const PORT = Number(process.env.PORT) || 3001; const PUBLIC_URL = process.env.PUBLIC_URL || ''; +const APP_ENV = process.env.APP_ENV || (process.env.DB_NAME === 'gallery_prod' ? 'prod' : 'dev'); +const IMAGE_TAG = process.env.IMAGE_TAG || 'local'; +const GIT_SHA = process.env.GIT_SHA || ''; +const APP_VERSION = process.env.npm_package_version || '0.0.0'; if (process.env.TRUST_PROXY === '1' || process.env.TRUST_PROXY === 'true') { app.set('trust proxy', 1); @@ -1039,6 +1043,17 @@ app.get('/api/bounds', async (req, res) => { } }); +app.get('/api/version', async (_req, res) => { + res.json({ + app_env: APP_ENV, + app_version: APP_VERSION, + image_tag: IMAGE_TAG, + git_sha: GIT_SHA, + db_name: process.env.DB_NAME || '', + public_url: PUBLIC_URL || '', + }); +}); + const CLIENT_DIST = path.join(__dirname, '..', 'client', 'dist'); if (fs.existsSync(CLIENT_DIST)) { app.use(express.static(CLIENT_DIST));