From 8f985da61f381b6e8abec89b71a2ef5ab6cf3e59 Mon Sep 17 00:00:00 2001 From: smueller Date: Thu, 28 May 2026 17:32:21 +0200 Subject: [PATCH] Enhance obfuscation workflow and asset processing: Updated the Gitea workflow to skip CI for specific commit messages, improving efficiency. Refactored the obfuscation script to include better asset handling, validation, and cleanup processes, ensuring only valid files are processed. Introduced temporary directories for intermediate files during obfuscation, enhancing reliability and reducing errors. --- .gitea/workflows/obfuscate-main.yml | 13 +- .../obfuscate_release.cpython-314.pyc | Bin 13123 -> 20166 bytes scripts/obfuscate_release.py | 194 +++++++++++++++--- 3 files changed, 170 insertions(+), 37 deletions(-) diff --git a/.gitea/workflows/obfuscate-main.yml b/.gitea/workflows/obfuscate-main.yml index 1aa8f32..a0b081c 100644 --- a/.gitea/workflows/obfuscate-main.yml +++ b/.gitea/workflows/obfuscate-main.yml @@ -7,13 +7,12 @@ on: workflow_dispatch: env: + # Gitea liefert intern oft eine IP; das SSL-Zertifikat gilt für den Hostnamen. GITEA_HOST: git.hexahost.dev REPO_PATH: smueller/HexaHost-Frontend jobs: obfuscate: - # Kein erneuter Lauf nach dem Bot-Commit - if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} runs-on: ubuntu-latest steps: @@ -21,9 +20,17 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - # Gitea liefert intern oft eine IP; Zertifikat gilt für git.hexahost.dev repository-url: https://git.hexahost.dev/smueller/HexaHost-Frontend + - name: Skip loop commits + run: | + msg="$(git log -1 --pretty=%B)" + echo "Last commit message: $msg" + if echo "$msg" | grep -q "\[skip ci\]"; then + echo "Skip CI commit detected." + exit 0 + fi + - name: Setup Python uses: actions/setup-python@v5 with: diff --git a/scripts/__pycache__/obfuscate_release.cpython-314.pyc b/scripts/__pycache__/obfuscate_release.cpython-314.pyc index 475c68f4eac09741fed4f2a823e0f1a7aa39348a..6ee1bb96aa678be90ed286afb375b8b48581ac15 100644 GIT binary patch literal 20166 zcmdUXdvH@%n&-WGzpR&K`EAP&>X504gvFsgK>l-hhR_}OBe-N(p<>_Jb5_1 z-PMMW#&pt+GM$|$Go6XEwOj2>_f)d8wNs=!v%NEaY}v>l<;F9es?P3ISM49rgi4Cq zt=->uuC62-@#u7FYWI|V&bjBF_xax6xmsf`GjIr+qgBs#_Hf*9=|K)EDZ}6VDbI0J z+)>WU@8kCJ3BHl?ihYWNf~CrYlBKGIilyp=nx&eAhNaqsmZiFc4ykgVey<^6V120e z8TXnJrbccp=Up?-C(4ef2I}PD3uzPQRpU)Vp+wGYP)NCi)vKxGyxK}GY)#m_I^^{% zZ}%FIH?n-W*Mxi-%R9Vg3%U64=kgsO> z8gC8qwJcxjtwX+^Ka`Te=M2mOA*f)?fYkJH&t zabI$MJYRK##i`NjES(*do^PTC$Cf}D3Ye<2gf=W@itFTJ{828Z7?ziaMalaYQ+oNB z3jb>SYw)kdzYhQUp_a{dj_c(EpV7jH6dg6xwG?6opW-OrAwMGZDXDhAD%D;7uDIoX zY+lUh)3xW?2HL2F&)`+Evv$xE&A`VG&?;60pFy5?BgbY?R;UnD^c}`L^!c8*=XzEZ zdiEK;YW9S7Y#cp$QFJWpWq(tCHdqnnR&pj@Hx|YY7+K#2udb*LPJDlhFF&7r9sRO8>isrf zVUD1d?3@g&4nqc&y#zGh$lfWAsX;(b^2 zKREEL0X26=@bNMXgMu0bB4oQoPUH(U{o>EkXgAq|P5{m^R z0a5jAG!${01b{LpDx(t#kq?P{MAU~O{&*-d77ns(AUbgjStCjl!SPraG*8q7LHlAs z(SpKoC=&FKM#sm4k%VYP!Le|3^klw7F?vE&MouTxO;EHV}s+^E4Q=x;#wrns!ZG_^CU(@*VK(ix|By@E{ES~0axitLqBec3Ym)b6aa zcIrU3vTk1G_{?B@byc>^_Uh2IB3s{(vi;kqQcu2lXgVJlQdv*8pj!#UEe=*ilt%K*!BoC%`zWGeH<)L}2 zD{HmQJozKdMgFt$ilpQ9Em=q9rN=Kmo=hw{T66Dac-GqSmj2ztS(D|$<7XeA5zhDj z#!`{3Zn&t)+RA4Vuh(R&>o014W2;FDse^Bx$<{Sp)USN1OW9NLpH^oZT4s#-nTaOH zXU4Lb^79WT%TvlXYqAZ^(+7&*W|zHfY?(LJ6~E3k&Q{e;?<<;Jag|&T4^pM3VY=@w zy?mxKUND?B%ygeO;{d0hd{y%bkHIf&I=g9R{VUt%Rh0txj>H0G76)SZ;9OkLqP9RV z=H>+=2ol3)K*l&Ri$>^$%#L?3c1&^d;B}7D)WI7pEzAY=Ss+G+fBxn(h`$ay&a>j@ ziMXv`#Y7UnJ}Wj9k$8y}>sPSiyAZ7VFhF9(1$HH|V!gMR70V1lX2se&SprzGJezNd z6-%tQ!1{cQJ(VV`dYL>z*eD1r5}SpHvfqQ*EoZ$tu+==<&#_hg9jw<@#Cj#Rs`p9^ z2k^nzy1FD=1qsK9k^m#IRe29(_DJk_g*XG2*2LOLY^b>Aoy-r4&>bTzXTT*+2_tw} zyJ8VT3lT$)zKYkSjopor7VmT^ru>~hgoO!!6;2@1FF>5+gbIqVU)-QjN!cnyBHtpA zJPWo*984gz79h58f{P;T7$-DSw2C4i1}C&qL;{%b5TZEbs$z~LF(kXhkYXnaOiA#d zA|o9?WS!ULsKg5{M z|0As99fBBwH`3%(q{tykLXSg|f*Ob9PPRc38)F+u*07}Z#toCy#=-yy)5-w*0kkP- zd+3Kk?Edwk+7{RMaRbC0R|HuLhuo;TtsIW85qa07eK@}Qp~HXr!j`Qa+t<8!#Er+> zis5zdshB7xDuf^uBd?r*>tHmJ3_XZu!>} z68=4AdFM$(M4*#~gz*3KNegSSIAI;4$@;=sKjeO7&9G;6*S76VZO?y;9lOv?71vX= z2~qJG+jD39Y|#-z?c>n zZIq3LF}@Fn;^Tp~JmZpXrD)!}6)xD^5&iKvQ< zj0fXHY1&*8syaqTBGE`_bR_H_iN}Kp|1nsxgvU^W?L`+dP`Qj2Af=%yV`yA3G^SJ; z!>TKWRZEuojHPM8(v)gW1=5x^=Tua$udqR2lPu&n$c4wLyBp$EWQcRCb4M40flr`) zU`Hpajzy#4`ySxmqJI7WHN5QxhbXz>Iwh%H?5Qw6)MbJ8gB{@CAw%;UkY_HvXR%Bv zOC6~EF2>`KxKi}{Ve~Q9A_;}}sKJC|uj1}CB;%!5*}{2Y7olE@O4Qgr&7`^$nAwR0 z#>vh}O=ln&J=QrWnkda#D+9X$FS?PqKNgLLPK)Yr^i)s~cAz%^$}S|LmK6m9%V5PC ziu>ayL$DhJBw!KxP?v4_K4btVX8VQlv*WKsGv<~Bb4%L1YHCkbYo2MnUVz3`c zF`2H!nLRfW4h8)2=!7szHX3PD4xl1iDWH;$PTM-GUDS4cVsR`P&C{nYym0n~S6)gR z8&lm2##QsGRfQ%0QHNrXsEiPeCJM+s*-lpd!-3Ug@WNDtI=4P4)IQpz}u^0u<`**H_h z0CWe@{sPG`F=%B!MqCz4V5Y@t1IC_jx2*yq%2Be9N2!xX4B6DkmC#jug zOo&dz;-Zo~FZ$!5aL`Y*9~2*~NK~E-o{4KvPqG@yTq_z52S*b*o+Wi497Qb#0B4Ya zgxRy>slj=B*YwV;$^L3z#?-K2YDk{BW^&&& zay5_KP;%DB>t@bmpW0slFBwrt6k*^+`sTg(pI7Zwc{N^bmkM~VllY*T_9A}UP{0C# z4)jFP2)i)n^}4`a5{4Er$JUHa4%`s{-}fe^u` zMjvaak6O;BDT(Y#uWD27#ADiFGCr~}sFizLzC(R(*fBH{ibm`djX*o*;)V%USQsX$ zm4)>R4Hv+E*Kv(pJ=X^w7UH%l>$wo`R`=iGE-WYeWCt1+(=pJe+*aiJWQja602|*A zlxBQn2VTM9$;XPhh;CbWWc*lQWV_fL3&E_P<0XWvo!emO9|^~|BO}%MXY|1A{8QX7 z%IBW^!1uoI7qtw6z@Lub^7_E>|J}a}&tb6t83;wfyI2ug$ndg$D z$){$HUNyB$^=518Na`_t_r0DoJTtMVt6!?9P4+IdZ_89XGOf=V9oLL4SyTCi z{*9 z&aXg9Oaut7C`D?>0Fy`X2jSg&KwuODMMMpgWI(i|Dqn=E`s9fth44)>IEM1G1uB^#=q3>+p5BA@*aW>~y zUl^7FFxpm~+e08+eLHT$NblHpxD+3`%6IgrKibG6{zf2JgqAQq4!SU@XB-xC=Mhmk zdMZ!=WFd~x@5@uaLZx^c+Yy4}!#u(4VSeTif$(8ol0gsivjM66PGBZ{VmN5#8S%!W z6iSiqCUDmxe#gtC96$;%1?MBwK!_;-f_Y$)$|yd{ZN@PMAcn&1P!K~E19`4fs5_A7 zWrZ>sgtzi}1X?8lDaI=ygg#&&Om+aE=fz=M(ji$$J-8b{JBjfnKeZWtXWY#R`r)sR z!DX!;JrNu|Icd(FrR(?zI8&hCtrHC}RK|qpXfO@|Q<#V_xk)rgS9^$yh$inuBoP{y z*a^s30?a~S>J)px2yUf75+xg7R8ennOq6g%LKQ9g^^cARgzur0!2zKhnK&yhY(KXh zY+%V`nNhy77e450Wo_16{)JIv(oF4w#HcfU|D~5-N;W||UNYAtH@vm=jjfs5j)mHe zbZuv*_RxIop}E!%*1W$a)4g+{duO`4S1O#}cPMRsW?uIUL{D9v1WkaO{N*P)^9P~m z3P_x4qUi5wB#OQZ4JFuT+fC%_!4N=&;o}gIu<$Rr$9TRE7{HPls9!9T0y$@ySrBxV zxf2VZ@JRFiUzq*fY%$A7v0|7Gg9Tq+)yhL<6NB&^Hjwkl$C&_Mr4=?As!>DLt(I}> z1YeNKO5q3H?UU=u;w!xk-bPuc z0hM%-j#X+F!!7bK1&p}Y*whom50~5u`i4rP=Jniul`L-De};oAX86QFu|`vRRV&-b zqq&<3!p)>BCxp17f@?G?2oo`w7+jAZbV)+z7T08}>meZ+Peg^FE7&>K>1rPfC0vXv zfPlLK!RK5ak84uV?#4Bw*y%}KC<49#bH=1zVj;-hR(m{Wf^ks;{Qx%b+X_!~(%|uc zr^L{8d{XD}Kv2Mis9Y&sb&88>IJn}$$%>LDI%*^JKdF~`8W~OCj;SyaoUEn-m_njq z&v95=5sf5Zu7rp+S>f>np#9)|Am|B2o`t0Y*I*~j9uIlkB4eHrK^QqRS-Je#BP&y) zhAbyN8))>zh%gpROj?c!(NkEICld7pAk(3T18SW|4WxnNavfxiVNMzOt-s7so= zi}bw7#-cj8@^CbIGA^GDbJPz)sVfVOMQ{#&u+gZ1ySsKt7G)Bs!8a0~$Z2kS0d8Qi zCngf1u&6$DA_Qjz5o%FKH_T{EQ9FJzKo6qfln}x#TT%xZmzB&P6R0h`Oi>T(OBhcG zLEKmCJv|zX(Uu8YQ4U>&F*V|6qAn_g#zK*iFphaV76=J&WeTJ0+5lbnjpK%KT#Jg! z)Ivi}g7Cxq_2+oWSl#C!EwGzf$}hZl_Qlkx=@-+MuBp9C#^%)KjIra2v17?xl{I(H zCT5Rc=lG4?+3ubXJnwsc-Zj7OvCGX6Y|D1@Hwq0?yrJWC@ZSHB!bw!W9Cu^@tZn?c6(W-J7kbx^(2?k=Z>nN75A=rgtxuub%D6ly_e#?`G9@rJcR=)?U&S z4xBxZ-2K)QZ#?mn{@KB+b?ZLdGksvuvRs*GvlXDZ$I54?O} z(b1MVop!99+i=yf>2mwjforPAzxu++S*uAuwtZrYvUn_aatCW;)s@bzu`Lz_1yZ|PAALHCt&DaO1 z%c~i+{1XteD4q#iXKBhIrL(P4`JDn53HVGf{A&mY4=7;eb?#EGEr>sNh+TodV@#P4 ziG}+KP%?v**=tVtQ7#j8VBhwtmX(n3lNYe$u}JG)&RT~p@;lu3hXG@l)JYbKgdc5D zX+cigD8;7bReKHh6blNw?~vzRNSRmw5v@opC}UzliG3N) zi&I5n!9B+Iq9-tL63JP(t5|TK8G^+xn;{bm{=hbQvA|pq3xuL3o`a;z`1f zDEcu)mnixkMTAO>^9V`Gy+P4tL~v~68IJI0RQeN&-lPcjWKMEtFuS9qKpJKKLKA-* zH5iuHmu4{9vOJr(au+t?l-NX$Ie<-UyJ~O0C!1)=llTIgXvsRNuiNw>i8+gh$Dk;CMrq{WgGOMt` zi~_olAqsex?}Cb*$v%0uY9z@7dnzdIsKx?~!De?H znFpfJe31&1Hw3}(!LQj0=;(JE+K(PE@UggoFzt58=AV)ZQF8%ruMqWtUWaNSe<=B}7 zbQ=AM;ORhU3>p%4VHa8ydw2BI(oX`n@E`Ds?S*8?Tr%3pf|YE2tLu%fbj=#v*UK1t z7K}Y<`uNohjS3OeL7x~m4)b<3+(=u;@sLEdpfaH(wQ=sbHHnXb%`Rf4EJ+23` z$o>hPX%iMi2B|WB+YO46Fh(Jn?Ptk{z(PDpODH}xl3WEch{_MFFp$kK|y48f>LA*LKu##9AK zXpk>;2S6UltAzU$$FnK78mtc*)+y|e;2SJya606ZWu%y@bPca&P)!s;4tUlSKNs5f zfr*k!^w3T+Qs>;Ye+_r3VjyqEl6yfZK6)&;df`*~bv$2u2|<*LtwIp~ zN!-X0g2&;WjKD;Fr!RF9$CvY^eq4Te@V_?w{m{pfwRaE>TXLp~S|RuxGg#2x^y41< z5G%#KAErWU;uFV@hfWLosMIpp`}Ck6|GoVO_Z{dzD3SUwUP(?@HJd4Q$)+aJf35H{ zsxU`Ul%oBJMD0Y3JgEUD)`-dgd`^Tk%k*aZol6v;8E_0U{~I#k4i(jxp1Jr;O8H|y z{GyhsjK#HJab+ycS1iq`P^PVCp{*y=wr!zpTe@xg$I->Mr*gifQXbOl8m_!%TDw%$ zkUaI)3vayelb04Y?4KVxoT(Z%^;uIz#^hQsxl)zz#F|{!Oj~bObIw)duCg|l^0eCQJL?s{syFWJ zQ2%NTk9e6{NGSh@ax~Ex!^d}_QGcF1N>uQD^mSi_@nwP_SqQ1;%WrUqQs8VzW{X-)(EGkJqC9%|a}@n2L~gTinevQ~JxRGk6g^E*;pWJ<>5)9+qDB&!U?^lZRHBMuene&G-X>>C$(v- z>xPztYG}Z9w?+CQwt_B;UU=!;OIfS)QrX3_*DcWfm_f2<(bOz;Yq9^}g;e>wHE-9v zTldqt-1oIVHr-UCryHxd_KmO>n#!m83$#dvDH6*V#l#gZ>{)4btqb%8iVFGA8Eq7m zA(+;Q_uotEe;Z@O!QIjAm)vT;>;^^C_>2dMG!eYZ2O>OdB2E>xAdrzlX$So>H`85& zfA;$mMRFFiFO?vNbSaSBcCtLi>g5TcJdXlG;|qj>Q=c#8w3Nx>J;@qKJZw`%k|iV8 z_dwn}(TAA>7Yp)sHm`A*?x(OY;E;2LwAg-i6h{e5U)i#8Rj~9uz_@{b$Hpz;+>EJ< ztY7K@lFU{FnHb7D19JCr5$hQf&JO2YdqbCex;*Ov>p1=>jXMNiq))fv(h`5+HSjSpt4ZldA3YdJlNF;8wAVT>{JbTTxYTO?Gy=yg8TaaUnWRPSy_QeBI(I z<$M(mQjhA@ov;y!TKXgqKR+>9UuwI^ruC9ZIfDG_Xqa9G5J%Kcf|cXUC&q=tm?l;6f<8r}XPSXX3(lP#{SVKFSruh>z(J zdMC$ReasxoR4QiI5{-GpWyq*17gUu= z-E7PJhF<)%!lJ6E)G}w9-+7po4nuN~#E!JFVQSasR>#cXr9&4Fy=KW++ZU|uX{#GQ zu3@iDo`A1$s_!$SGh?h@FxF>`jaQ6~OS-ao^Xgxee^B#&%~jpDPb}8Y3i6EBmeE!% zXsh7SWTxs>sb|w9f-J3GJLmkM?)|!-H(Xo&=z^(j!PGeIORit4Sd|LStxs2Mn%)hu z;@peb%KB8>Z27D@WxiV3Daj)r_Q7{Jqg=8=16()PGFJg3-KL#iOJSo7{cP+Igd%TeE}z z!o^vgGbc07_62AAtTydjd(E;ApAG2BXLerGRnZmy?$0e1GiOo{U#;xOIM**Y*U!O^ zx$T%C4LwVGhGTKV~&1)-{a-tR#Q(aB+;YD5N=PLcY!M&(j{fW`UDN%CCEKE)g z=31A&?C7J%0~8&lC`J)Opzl%c&nbGFqIW1FDV^brggpevw0fdfe!plqc!oaN>^&Wl zq&K!Z65#8$jpJfMIJjN7fi?hpF8&cB968Vbcdq%joaIx_{3)mZlq>s`vwg}LKIJU` z#I^hrSM__C1^W0Y3w~zhQq2!*=Ic7=6d$PGSEX$mr_8rB^n+Zth86VFAGh`@@LlgM zb1t&pGUL0!TXuZ-b<2b=h;CKk%ZOW@xS4#biY|5Ds$#d;ZauCbAMCAduiNA>a;6~YwQc$%-HU^c0$lJ z=#Qk3p7-P2U%erGFz(ptuv-Zf#~=M?Z*RZks1yR1nx30&BatmrLar>L z_mhahPg1U&En)=T#Cdzf47`Q&j))cbGR`|AH4z)M?AS^PIakC1Ehm?`BQD_GoG*`* z1MlIyC*lRZg7e;p4|uv8li)h0SO7z29<)#Z>4tZ#x*#Ju?}k!O!!Dwbt7+`5NF*e zDM#W#;zd#cWQ6*V^#f6b23iGmsEnGiZwrzdB(+HDkkljDhGaXE1|*G0nt&*Hs>Zrf z;b&6Il^m75*;sZm8%t!SrlfREp)D{nb|d5X_W$&&-eeh91N^G@_ExbUy51Ldu(aD} z!^3hVG*UZTa90^Rq2o<<$+L_7*6sgawDvZvU%}Glj?y0AHPKzL8f|0Uo`&5|A={2* zFOcG%z^dg_a#|k05Id{H#^b5fSUmAXx(@_z!!eG)YAo1eH%3em^DaF=z(P%|XWN!p zJx;aC>1;k1%SqF@87t_j?n`w;nY2XF3aACilP>!m7^EOh=Ey%tnc%;VWZ{K%tgeml z>iP_?e*G+$Q6~&eh`OhQr=RiybewXmle8)x_6Sfi3lX7ag^p7ubK&!@d8(BljR_1f6jSf>Hd1e0Tl zsU$rP-CS2OVCKkLS;fWP*LqjY&NZ8JL418+)ls=>cdz=ZR~?=Qc9YFGcWB*3%(ho& zUzxpJf6G+8<_NHl8r{LZrRu(=_P&Cnf63gxTIRgi{k85Ty^pJvz24S4`<9WKwBvx4 zQ#_7#M!Zv1&#(7Mm-s-S7%rM3!#!hKE`;6AZ+O|R99UaeodQXPzkg4at3UKGF5 zm)J}Dwy-zatt|f3p1~7%OVwl)56DX^i5Q24r~vmZcESWKS@BbH6PJsR$n{Zj(!$&} zkDzB)w+3|)Jv+F&dY7Srpg>#XQ97!f3A&=XqC?c?jKQ2bGK#K@7b8i6!h)ohY#}Gw zWzsEfA+iuMZy55q@o?8$f@+r1iA+*XPpD!#JN=fxubw-%p^%9_3#)7x!r=>&q8f7& zRV2zzw}cu(9xkHKfqbUoZ2VkYNl-bP3ung0^GYI~%TV@%mX^H~961aKhm%q~8HSCd zu$(?CC312mt;rnWF#5FgL^w`q`~vS}8!g9Y=_#CQ4u=7oVAFH}nt)^rg_XF018^tv zIXR^o&QHpTNsdU>dY;NTiOVTRx8?A3Ldx>}(1(D5JPnm*CFTs3C**WIrJA|Rk)@<+ z-TVd3IVq_aU?@PpZWH|0O%npoM8`nHVf8j)YaI=<>$+x(d(B_-vCix+UoR(a?`n0! z1HY}(F+cDiKmzq2>%1Un-W2Sr3^?YGueTGs`{MA|hA$sq3AQW;TMEI}>)zYJJ@1+3 zhi};i?%K-Njl^Jib@-LxTi(WN(*FX>|TO7ofjbVziePduazK&k{_B*qAxP<|iD!l8AnE<>n?iaOaByQ+j~ zi0XNL7Q8&$=;|EP@l&hDq&y)hIR(Q;FtFx#ylPhQU2-*EZXT76)l2ljCtvHBH5{ zS@4}3j-;H(y$C96sRMFa%XdE0-MCrxzUFG4!)yjTI3FRn6OVTvLcH5ydKaRYE~;Z$ zUyxlpXl6I|&FDr1_VVE%le(RPo8`Ofw5Je`!2@tmZZ5}9-k(r?w2&tgYX6&0FL#UwxgmB+(I5w+!; zOU9@)E>VcMl0v@%gWQO@o9yTNU$CrJ)?T$=v47XentEFy89&v#-|o_V;415{HGEJb z^w+ZQ_4dwk0F-6(V<|ZiE8#%9?sIs|Cz)dXqbPR>p8EvIH9$F3i)VOCbLg8c8`gXT z47xi4d+GB5U&P=dM^<=dQyK^8Xft}@J|R`T8u0JD0b+eb7uBCcZ}C|99JD9EsRl_z zjOy7PUrltdi9C`=MfJtRCToBt@LBR$u>nY;1{(n>dZNdQjkN2tMp*jg_6obPw3Z2b z4trfuQ?aWS7jR?p#bO7nY_YxCm^DXDpYEE?T25VxnlXbM{E3Fp!Mj==6(I9As>ZmY zNbnZ(z>pb3E2I;xP(d!ystTUY4*k=2^IxudJ@;N|Jt?xwlpY+JBycGc;-9J<8TcfNe92T{QNCjNr>54SUJRjMebb5FFY zcC;$IdW;>_g8e>{`2?>ta{^hZl-?4Dz{AcF%oMb8vgm=?8(sDrR}S?ETSxsxAw2u= zgTiAaVrPFl>OZI-tu6{q^>uvWPF)&EpSX$xR~a^p;%5q8PDTFfD!PcZ1zpW%`Hv@a z@->a4wZ&1W?;*b`p!`oi&YaFZP~l`s6o_ zmV#BpT^w6W&JZx>a&~c`LwJpSIFJy&$95dsWuMVEgZa)Fn$dpQkB+qo9`^fVd-x{@ z9v#qfluYUz7^^fbLj+I_kextq5S0rGJqib)-#~%}&tDEapX2$9TE-1((|w>l@yGx# z9b-0*?=4jyrxOakd$JkL<)gW)y0c2VD#CeG5mO-yPYQZTc&zZErAzv$xSXaxg?`{Poevf`w{k=&`+i0G41vL zCG5vfH2ljKPpJ9#AuZh=Ljk7CT)jN+;u-~C1%ZhEl)d+KU7N#(~z;u#d?ob13*<%xed;0w{i zAbwAHI40nW>0yf~v^}&z;`q>N7kVDnL5zL46+FXw57FD^XKsmAcdgz_BP?352AA|f KP@_J;PxUX^=(()` diff --git a/scripts/obfuscate_release.py b/scripts/obfuscate_release.py index 9f0fa6e..95c222c 100644 --- a/scripts/obfuscate_release.py +++ b/scripts/obfuscate_release.py @@ -3,15 +3,17 @@ from __future__ import annotations import argparse import hashlib -import os import re import shutil import subprocess import sys +import tempfile +from collections import defaultdict from pathlib import Path TEXT_EXTENSIONS = {".php", ".html", ".htm", ".xml", ".txt", ".js", ".css"} +HASH_SUFFIX_RE = re.compile(r"\.[a-f0-9]{12}$", re.I) def strip_comments_keep_strings(text: str) -> str: @@ -174,42 +176,124 @@ def minify_js_fallback(text: str) -> str: return text.strip() -def run_cmd(command: list[str], cwd: Path, input_text: str | None = None) -> str: +def canonical_asset_base(stem: str) -> str: + name = stem + while HASH_SUFFIX_RE.search(name): + name = HASH_SUFFIX_RE.sub("", name) + return name + + +def is_skipped_asset(path: Path) -> bool: + lowered = path.as_posix().lower() + if ".min." in path.name or ".obf." in path.name or ".deob." in path.name: + return True + if "deobfuscated" in lowered: + return True + return False + + +def is_valid_source_content(content: str) -> bool: + if "[javascript-obfuscator-cli]" in content: + return False + return len(content.strip()) >= 20 + + +def collect_asset_groups(asset_root: Path) -> dict[tuple[Path, str, str], list[Path]]: + groups: dict[tuple[Path, str, str], list[Path]] = defaultdict(list) + for ext in (".js", ".css"): + for file_path in sorted(asset_root.rglob(f"*{ext}")): + if is_skipped_asset(file_path): + continue + base = canonical_asset_base(file_path.stem) + key = (file_path.parent, base, ext) + groups[key].append(file_path) + return groups + + +def pick_source_file(paths: list[Path], base: str, ext: str) -> Path | None: + if not paths: + return None + + parent = paths[0].parent + plain = parent / f"{base}{ext}" + ordered: list[Path] = [] + if plain in paths: + ordered.append(plain) + for candidate in sorted(paths, key=lambda p: len(p.name)): + if candidate not in ordered: + ordered.append(candidate) + + for candidate in ordered: + try: + content = candidate.read_text(encoding="utf-8") + except (OSError, UnicodeDecodeError): + continue + if is_valid_source_content(content): + return candidate + return None + + +def cleanup_invalid_siblings(paths: list[Path], source: Path) -> None: + for path in paths: + if path == source or not path.exists(): + continue + try: + content = path.read_text(encoding="utf-8") + except (OSError, UnicodeDecodeError): + content = "" + if not is_valid_source_content(content): + path.unlink() + + +def run_cmd(command: list[str], cwd: Path) -> None: proc = subprocess.run( command, cwd=str(cwd), - input=input_text, text=True, capture_output=True, check=False, ) if proc.returncode != 0: - raise RuntimeError(proc.stderr.strip() or "command failed") - return proc.stdout + raise RuntimeError(proc.stderr.strip() or proc.stdout.strip() or "command failed") def process_js(path: Path, cwd: Path) -> None: original = path.read_text(encoding="utf-8") + if not is_valid_source_content(original): + raise ValueError( + f"invalid or corrupted JS source: {path} " + f"(restore e.g. 'git checkout dev -- {path.as_posix()}')" + ) + if shutil.which("npx"): + tmpdir = Path(tempfile.mkdtemp()) try: - minified = run_cmd( + src = tmpdir / "input.js" + out = tmpdir / "output.js" + src.write_text(original, encoding="utf-8") + run_cmd( [ "npx", "--yes", "terser", + str(src), + "-o", + str(src), "--compress", "--mangle", "--comments", "false", ], cwd, - input_text=original, ) - obfuscated = run_cmd( + run_cmd( [ "npx", "--yes", "javascript-obfuscator", + str(src), + "--output", + str(out), "--compact", "true", "--control-flow-flattening", @@ -224,38 +308,51 @@ def process_js(path: Path, cwd: Path) -> None: "browser-no-eval", "--source-map", "false", - "--output", - "stdout", ], cwd, - input_text=minified, ) - path.write_text(obfuscated.strip() + "\n", encoding="utf-8") + if not out.exists(): + raise RuntimeError("obfuscator produced no output file") + result = out.read_text(encoding="utf-8") + if not is_valid_source_content(result): + raise RuntimeError("obfuscator output looks invalid") + path.write_text(result.strip() + "\n", encoding="utf-8") return except Exception: pass + finally: + shutil.rmtree(tmpdir, ignore_errors=True) + path.write_text(minify_js_fallback(original) + "\n", encoding="utf-8") def process_css(path: Path, cwd: Path) -> None: original = path.read_text(encoding="utf-8") if shutil.which("npx"): + tmpdir = Path(tempfile.mkdtemp()) try: - minified = run_cmd( + src = tmpdir / "input.css" + out = tmpdir / "output.css" + src.write_text(original, encoding="utf-8") + run_cmd( [ "npx", "--yes", "clean-css-cli", + str(src), + "-o", + str(out), "--skip-rebase", "-O2", ], cwd, - input_text=original, ) - path.write_text(minified.strip() + "\n", encoding="utf-8") + path.write_text(out.read_text(encoding="utf-8").strip() + "\n", encoding="utf-8") return except Exception: pass + finally: + shutil.rmtree(tmpdir, ignore_errors=True) path.write_text(minify_css_fallback(original) + "\n", encoding="utf-8") @@ -266,8 +363,7 @@ def process_php(path: Path) -> None: def hash_file(path: Path) -> str: - digest = hashlib.sha256(path.read_bytes()).hexdigest()[:12] - return digest + return hashlib.sha256(path.read_bytes()).hexdigest()[:12] def replace_references(root: Path, mapping: dict[str, str]) -> None: @@ -279,7 +375,7 @@ def replace_references(root: Path, mapping: dict[str, str]) -> None: except UnicodeDecodeError: continue updated = content - for src, dst in mapping.items(): + for src, dst in sorted(mapping.items(), key=lambda item: len(item[0]), reverse=True): updated = updated.replace(src, dst) updated = updated.replace("/" + src, "/" + dst) if updated != content: @@ -289,17 +385,32 @@ def replace_references(root: Path, mapping: dict[str, str]) -> None: def build_hash_mapping(public_root: Path) -> dict[str, str]: mapping: dict[str, str] = {} asset_root = public_root / "assets" - for ext in (".js", ".css"): - for file_path in sorted(asset_root.rglob(f"*{ext}")): - if ".min." in file_path.name or ".obf." in file_path.name: - continue - digest = hash_file(file_path) - new_name = f"{file_path.stem}.{digest}{file_path.suffix}" - new_path = file_path.with_name(new_name) - file_path.rename(new_path) - rel_old = file_path.relative_to(public_root).as_posix() - rel_new = new_path.relative_to(public_root).as_posix() - mapping[rel_old] = rel_new + if not asset_root.exists(): + return mapping + + groups = collect_asset_groups(asset_root) + for (parent, base, ext), paths in groups.items(): + source = pick_source_file(paths, base, ext) + if source is None: + continue + digest = hash_file(source) + target = parent / f"{base}.{digest}{ext}" + rel_new = target.relative_to(public_root).as_posix() + + for old in paths: + rel_old = old.relative_to(public_root).as_posix() + if rel_old != rel_new: + mapping[rel_old] = rel_new + + if source != target: + if target.exists(): + target.unlink() + source.replace(target) + + for old in paths: + if old != target and old.exists(): + old.unlink() + return mapping @@ -316,11 +427,26 @@ def main() -> int: print("public directory not found", file=sys.stderr) return 1 - for js in sorted(public_root.rglob("*.js")): - process_js(js, repo_root) - for css in sorted(public_root.rglob("*.css")): - process_css(css, repo_root) - for php in sorted((repo_root / "public").rglob("*.php")): + asset_root = public_root / "assets" + if asset_root.exists(): + groups = collect_asset_groups(asset_root) + for (parent, base, ext), paths in sorted(groups.items()): + source = pick_source_file(paths, base, ext) + if source is None: + rel = (parent / f"{base}{ext}").relative_to(public_root) + print( + f"ERROR: No valid source for {rel}. " + f"Restore from dev, e.g.: git checkout dev -- {rel}", + file=sys.stderr, + ) + return 1 + cleanup_invalid_siblings(paths, source) + if ext == ".js": + process_js(source, repo_root) + else: + process_css(source, repo_root) + + for php in sorted(public_root.rglob("*.php")): process_php(php) for php in sorted((repo_root / "backend").rglob("*.php")): process_php(php)