From 10de99bb06183d4e2e5b971f3c1600d2afa29651 Mon Sep 17 00:00:00 2001 From: Ivan Fontosh Date: Mon, 18 May 2026 10:06:47 +0800 Subject: [PATCH] fix error and new docs --- docs/MANUAL_MAC_UPDATE_UPLOAD.md | 3 +- docs/TTRPG-License-Key-Instructions.docx | Bin 0 -> 38773 bytes docs/~$RPG-Release-Instructions.docx | Bin 162 -> 0 bytes scripts/generate-license-key-docx.py | 298 ++++++++++++++++++++++ scripts/generate-release-docx.py | 7 +- scripts/ttrpg-release/prepare-release.ps1 | 53 +++- scripts/wsl-pack-linux.sh | 2 + 7 files changed, 353 insertions(+), 10 deletions(-) create mode 100644 docs/TTRPG-License-Key-Instructions.docx delete mode 100644 docs/~$RPG-Release-Instructions.docx create mode 100644 scripts/generate-license-key-docx.py diff --git a/docs/MANUAL_MAC_UPDATE_UPLOAD.md b/docs/MANUAL_MAC_UPDATE_UPLOAD.md index f423adc..2bdfd0b 100644 --- a/docs/MANUAL_MAC_UPDATE_UPLOAD.md +++ b/docs/MANUAL_MAC_UPDATE_UPLOAD.md @@ -3,11 +3,12 @@ ## Сборка на Mac ```bash -npm ci +FFMPEG_BINARIES_URL=https://cdn.npmmirror.com/binaries/ffmpeg-static npm ci npm run pack:mac ``` `pack:mac` сам вызывает `build` и `scripts/release-mac-prep.mjs` — подтягивает **оба** набора нативных бинарников sharp (x64 и arm64). Без этого x64-сборка с Apple Silicon падает при старте с ошибкой `Could not load the "sharp" module using the darwin-x64 runtime`. +Переменная `FFMPEG_BINARIES_URL` нужна только чтобы `ffmpeg-static` не падал из-за временных 5xx на GitHub при `npm ci`. В `release/` (имена **без версии**): diff --git a/docs/TTRPG-License-Key-Instructions.docx b/docs/TTRPG-License-Key-Instructions.docx new file mode 100644 index 0000000000000000000000000000000000000000..17d32aa8c81bc11b27694c3681eaf9c27b769084 GIT binary patch literal 38773 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 @ zfJ{XJU+;da)V-LwJWOWSsrcbS z=Dtb$?y|B~KjX0Aw{ZW=mkGEi4_>S^Mt7KD3*AQ%@n2kBU7=2|u4^_NJXJ1k)^8g# zJ^OD1PU*BV_;_#zeAw%EjuE*Xr(Z_tI`>#Wa*uQzJOWRb-wPk<)ZZ+$p8Xbv8$C`_ zOC4WtU-p}iH|;vcJ$AW}4-jRR0&`!4cM&VkJIRKx>4zJy7w9WJ#21F?-nVbNhh>Kk z#@U)X9mpUZGct19PV9nYZM5%}gyQhu)U-T@_sCK4a%i~MG@dPwUpm<{tj5>NJR0gR z#*b^umqzDr@RY#YgygJi64KR6abqnTVg6 z9=ATk!|m=$eZNT!nN+@_+d$&Glmp^m_dWx(TR7+kEx>z(G?ebbbk)lhwZOkz8dOJ) zK7D5;zJfw%bq_tQvOz!#kuhQA!iBPRDA#>0DftCdeZ_L9Ai(Q4u*p4Jn$}O-@6QM( zVqP2=x@Mf;f3OrnFJ5w>!z!J#$?^Q;zkNbbQuWG0IWI5wb@_qaXYi8%?;f{LJBa@T zuC|jY_#{S2 ztXs!tDUsK0yN-1FyWu-g-TgR&qpPATBTui zaFt#qa>G)Oh|ys?Nc-l*cl*M7=(e@ewP7eQe!$OTtuV(a=kUzXzhC3BbjmIpglXpr z0ZTYJ(#`3i&wv#t(>rbUtyy`ETU)G8%%XSm$N}p!Zckoc*j(6WDGuW@JcEMQU;LK+I=nC%g+Jn-ln6O~htH5NOc|s9fa=#_D{#&w7 zb3MKaJ9EW)J*uCw;C588I7Yaor0M*aBd!_oxOL}e`*;>i6LDh%D%Wk^RCL22ny$mA zy6x{RAx2X`iUw@06Ptx@?xStF7J-fanNoukjBh-i97rkr6N`e3$MRX^yMIkw*S8IhS)CT*vH zh7N2caHpn>f?T)Iv!?`GHWi%uf@0)Iv2S2_Xb~1XQpM!tnsLJ1YS&|Zyz83x96DaZ zFwJ;x<=(}Hxe5-mX(7QBcuXr-+lNs}WA!YCcj1J!tavrQY^Rr#9_dS!?G5Y@Wbmgf zZq}c0juQ(r6URk^QnuRazei?HSwpjj70q1*FOL(Or?TdJw;+BR-AahZ26cP#mhU}m z;7)5yc)9#^l-S04dtx@km7_gMY|b88WHlcg@eTWzZtZ&EapLYkls0e9LezRpM7*OG zvJDsFLb;RITiAE%OmjK3=GsSrdI^X*Hdi^sIGygIHwQo{_|ez%!jn@h*zGIj&w=v2 zYG((T2xe|q^l6=G@Vei_CsWs6)4xpMDzfu&V(s;gH}Y{!1eMu%&ejAW9Ey=YQg%Md zuh>b@kA7Yzwf4mQl2T-yAa!DtiU(gURCoF$`3qmcHuW7|s3LeoQVyS&hy%z@ODxQD z6}#)0`}3UpaP@@FUKIXDjP0lX?5zR#q3vN7K@?+zQCUux47J-Z7%M-?pj0wqV49RuCO2#_jky&$X}H+Q5u`n z`MNgqPM;RRIvqzqv{va|>dP>2MGNG7Z=A zB5Qhcc#?p}<3z&irjgDxlrlT$S#XyvI7s+q_)=%JHF-Z_7(0r^Kp6*;iUvjBF=X+) zE;*&A*Qj*&PE@1fipZJ|w_bNfHgE|adTys4dc?GoXT)Y?8H&5XC4-z5YjL54voXr! zHLi)Vu|NFr%&bEL{QBSt7(Vc9s`&HNHt`&N{~~HTRKgO&5V||7vy2P zL}FOb0^_*$aLD`|oY~!7c9GU=@csWz+R94hxCzX6i zK;2L5bCO5xYNlXq)M*1QRch+sbXzzyI*vj7jiPPt#eYar{yro$E{4!Wg?M_||D&gv zdf6J-mKE~NyYueCc@fBOoD=N}2F-7|%C;HV>s$MqD9cI2P}`g)Xj59?ttKOGZM9AP z__0PKIzAVVcTO!_xQvr?-;;+6{pfU~N?h7q>UhJ!6+INsWy|*-lA%m6ywj`KlU-eQ zi=^($+{O_*XQ45MeT(z9G(tEQ=FCo1A4_p8ajfj9cu}fDPanNHlo|+3OKiU=wszyW zY}^5PCH*<03J5!c3l^v-cJzR@>J~bif{4>4nYA^%*LZe+=Wd3~o;>Fz{&oKK;J`8# zbBTLcxZH#&_AcEOO#ik+*2;+ODTH!KZLJ?@R|8XKNCY;PFs=s9TI*r$#YT{Gg+6Nr zuFSU{8|m}mu$^s$miSi?SE3w-`SMaNI=Yzk{%ybzD+2_(UprmvF+{Sz4VPAG!e0$+ zegNUXxb8S=tv6LO<4%^yf));588h9aB`dSjy<7!Wc?@9>VQ&G1(S8VEv zoF0x$js$6Chdt{MwqK!ucbgsMZup9mt3f2$#B3!m{3_lk_FyKskLv#$>2-fjg(WmJ z(z$KVJc=Dc)(^;aM}7#q^7x9Zrd?O1t%4$2)kEFMq~DnEl1eN5Qg(t&V6X&|}v4||nWZUHd`g*r zqyC*t5ffTT7>`t47|v8kt#s(RWA;Rtl8G8i=d1| zS!CdPGG2DJ*Q*(lYqsNQA$^-7)_C)B02q@(5i6Q`IL~2ySloO_tnmlfe3+8OUtn|p zfPF2Q7bf`>q+!82$Nqs5%>h8ku>S|eTKO-OpvEw`wGapI{{=+|fU+z4f#SgVfkMv( zKv{PB2c<;qFBH<|F#nwp#gzXAMGAnD=k|eu{G~U1$Su#+M9Xc62(iB4rn#PIJtWyC zdtRxC^$zZs>8g-=Vv5dfCP0H(ppl(S$%6iDpAAh0vUouU1Q_FDB{|_j-3Ko3UGkdi0-(50w()K>Z@~08v zdW{}Fl$JOQnY}42f%b!O(NZ>E8ny6*43TosHKYO8Opbtk|4=y0a`@ z$*(%SBoqp{uE}4%9)lW_5X2vf|1WDqe_7)R2lQj5uq|r1@W}sZ4f!8yWB_YL29Y1u ziVP|xaNYk{Lsa164+bIxVde&Qkfe;fsm!OD)3q#|Wfufn4uTp}?@{ZfEz|Xfg6lAx z0(Q8MkqLkTf*bD2a+GbSfLW8gpFJ}T27-zcbcnOZB<5c?4hAA%$KS0H0z)thCOF?9 z7S8$_?A^BbWfTSw>Sg3HQ#V=FVwPe8gFNMYL68sX4~0a;(EEu%o3X~320%y)gQ=0W z7=rk5JrGHtQV?vPMz3rWU4=mq3_^$Lw|BF#|idXdLsb~f*HGj^ihl6MfBqlWgQFdH;~Thl5DIX5&c#U?n)yK zLtE{E5e@Fzl!CxzaHc<03I^(mf`j9_XF(t)$5Cl1)D{y0_reB2^Z60;zNlv0$zJy_(60bi z{E|1mYP8oz+Hq2>YsA^ml{8|AK3Cl!1JAm;JX8v&M|Qqz6$*m1q+F>*r|U#B!9S|d z*j=VGwIuYdXflkwQ8jWBOEcg`gs2S2WerYGZ0~=%EKGfc>VJ7m;45;YReoK}g!fAs ziAQndQFW#f<53NQ10-J3M;Y~EzukYklRjm5NKkATYAhPh+ORA~v6E+ny;Ly?L*!SA z9vWEP8CwZnHdZ_ceF@7gGVb-;UvNjweyT-o_^ssiM#xj-X{F?a6!~iRm61xS?daer zVsGf+C~B+g;3!h!w)PHc4soth__mfFdM{NPb|vg7Ton5W9d0dKvHXeN61YUC2O<4vQp)~A$U95M*bCOTW#r1I<`x6at7a}kj*I3e1BKTz9>H6oTWa~& z6Dh$*vY8&XT+iHJfgSNwSQt`fLbs)IuWG67>Zx+C<)v>72;{%=0G>MflMO6yPrVmdYX7gqssN)^(97ditdk)}bKV?6~7kS>1X8(va(@T}_ z{ADV%At?eAI>(s(q+H}#23QXITJ(0O+BPY{MqgrCb#(t*sWTX)L+*7O_Ce-# zt7+f+s;|_u;wt?( zQvecs@}+zUeOYILYZxpL)7wXMl$Cd(a<92%Sa{_CnqR=;3{yEkO3E-mYSHL1vYXCN z3!=3w^9wgF=!ra~0H*PN4{G3NIA-R?P6YmvPv{Y=$$@4Ck zd%ledqX!3=CA?!wGbb4{j6XH^&M!s}RLCnAve3q{DJiBm&s}c*a)JsW*Rs6slTYcjK zDwa{>us6XfNao!~k*+oN_L1&o0fnh!8pq+9krsBfPcjCGVDU49nE6Kpu4&ST2mt2) zM?~TTOY7`$0H7*dfjT&gUW#5&Vy~sUk|=ILax=rn?_*=9m7q~a{sF|!1$rrm26Wvl z77&<2EYi$b{M0jJxy<8qdlesI{mfK=za|+kZjm-4?-U;>S^OMrfct=5`3q<0f5pkt znmG|_v7ks!_7D_LmTq)%E`wSEd5}^rm))d~tEcl`H>@m2I0ZA{uS#mt2%=X#7>#c{ zvuaa6cV0?aq@BLGw-i4=&)9!ov-CMaBj(bcmoUerhnMq0o?zLM_bll~)X)a?c<-{Rta8ZCnejO;X$SY1K zUV)P3vv1FYvu(#_NhaU~3`Xk6Uf0VJLItW_fTH~~u}{{O7G{JxCxn`!azCz7Nu;!@ zd{;(&-zv1qLrz;xo0K1PZ_kjcZ^v>`+V=_sN|M3`HR!uq`qwe0xGTvv63-C}MLBI` z)xSI%UFOAc(d*sL}nU#yX15&7RLS3)aF zBh=?H50sTc0@F#lb z{KG%as|LUyMUKhloByfbmG2c5Fct+p`3xbRNv|gGQTWL%-~zJ0o^ODX)^YZZy_t>s|IH(H3rLRPEcgFIqRtLrXRakQ`3Zodcz7FniG^WGK=)7d9dT-0t2Ca6*K zV<;2T*AYiG>-0|@BRm!DiD2x+8KLW!4nP+N$azM8k&shR7mJR_W3seCz-!qH5mOi~&|qZ@^-(TQ0fcAa zR~`Z=b+c$PWD;weF}#+bE3v83!uSU(bdpU_w+1u*ht)tfgN5+y zlFvsu@o;>%id+eBLKXXP5D^9o7XX3+h3M2sMJlVKB1A2OaX6~(t-<^)n}rLEx?XW_ z*{@tV^f9dRLSW3)QAdt&kw1>*pCjTmbL;TNu+Gw0l|AU-)#CSvES3OutM&ZU*Uw{ENvsOqbCK!`D`i`hcwzYfKSgf$F<8`P zKqRrcC9{4^m`scbvkUd0dqu^@ZHI{@lerh*H^2)v~N$U>O0lwEGD#$77ZkQ_<$<2 z)yNm4S&qI?3#PF*D2#WU9Q)frVMOqohpYlIIpN3KG=3DLDB%cJh?FAIuW8}093(orWJn?(T?!g4ObJB} z(dVL^2QKZ)Mf`UZjG~-4ceJI2KWKLQf!2n|^bDVha{8pU3q$b0l~JRAA}GoKcxx!;!&$KO z&2T9JxLz@k9RQ|qb8#~$jP|$hMIP0K`O%EzL;$`oDI!%4d(};#c3r6JlFkZ2q5qjq zg~B~Sv`v{HaXf~^8B)L31ZL1BZ92SStXH{P&Mc>VHIVE=X?2Z=k^;GN!e4<~@HIS~ zae9^DNCa=oG`!$t*C&#P8b{D=+UnAZz=qA5MBZ9f|2H@b_|MemP*fCk7hr)$-M0NB z`cCkRI>e043w%n9DsTydU9QrAy{{l=&>Va=3bqpnU_c6wFP~!+l6w;gC#6k(j!8r2 zLWq9#5L^TNm(HSA>{5f3rJR{mwZJr!G?hu@+VU{A_=`)_G_Z84jA7@@bOW>jb%m!I z=m2~ecI+NU7&Za;z}Ly{PfyX`9Df?G$yq8K~my&^&ASTXyDSO zxu68di*svN+3Id35sm3>0Z~@j0;XitH$s_dh?syMV=**~0)(UomS}P8JLtQs-M*U?@u~O(i%xyOF-qtQ+!wzOw z*$OME`2gw_s_e)HX2eQ?E0_3oS!_+!P3QxXN!_g}mPx~{zvk9z9O*^dIr#wwB6cUp zkr*FNDS&cNIJTU|ZHm9d-| zZZhKq_Q3e=l*%FV$y9{^Zz@p41H9Q%^k3dA$v86I>XSd26{aj5__xK4?oXmKwvrQ& za8v%jQn1f*3I~bG*VJ(Ei|2yVFp1#-^@Ch6vBz&=Qv9+_^IMI=Jk&rVf+C0rR0xeC^ zTOaOjCM{0@B@tqvE^0NT9Lyn7#z)7UxE9O{v=GeuySn3H2N7N5WlN#&^Ce^-K9U#z zOSZvf5Cy{N6cHU&1ZnaAZi~hx>8sz#{b|#a{zEJA=el~9zX#vyg|~y46OB5 z`%e4@q)GgiHTbk0$;W|u8T7*z=)d?&T?YM`!rJHZ{>JOW@0vvNnhMqY8knJ|_;TDs zisXgV44~eXGUxg~nQ(-9uI7UY`fm#IgZY|1*n2;KZX;@i^Qrx!!Be)L?Jp!k@&e)$ zdJ%xN-+QPfDDrH&5mf#ESa^9twEiuMUVS3^0^B6}A`p4okMDIsv5koLmxsI`gv53b z0ZZ?!KHEgSVlTiiaF>v@L(1HM>!bdyV+ySgU${cO55LOTNgL1<1aG<^G9OJL;2cKu z1|&&{-{Z+p>gr!8`G38X6VB&KOf4ht$D{2XHVGyFT6c%pGN zY|>zWG8^}cs)o_??uuvL6sh&kJk@ruV}{QXTwfhY562gg%;hBsm_SUz5I6>4YOx(M zyiRv^ek2DI)+-Mrl8tuSq336hb5lH2quLaMH;Qhu+}SH4`GC11MEGg}&robsb+Je$ ziK=M*XrZ*3&5E34-_^P}B?th*CQOllIr{h}#B#q!(qQ1etFm#e%u-L7(n_DQwwN4Yva9QD zdRAd-4GN1iu9YGlZ0tZ5qsY?gpQFwn?eHO~f0E^sl8>KhAmBI~m9T_g`^jFhgiMg~ zOqz1~t1tQKe@1ZsKroW|gRqAVEt$yw4~vY5^vf!1F@V`b(+Ep?%!&U=MY$>OiC?A; zf>!=9{ani9OLq68;!B8YUadae<-v^lWdT8Sg+?<>+6`&3lv%BepWH~Qa0cCm@GeeAx{AqRTGmkc5C9R^3hpiJ&2ehChImkv4a}F z88lwELMKSS8f%?$4Qr{3mT+jHM6_Is2E|M<&Q3meeahE#ydRnG{(PNkjHzz(>&QNh zFgB33Gq<{o1#(!;jUEq?QL3)m77dX7emP6{Ti`!!j}|0EU4JgnNB2=m>Rx4DmAn+% zrVuq!hsKX1M}rS+#@wusQqDb7)zS5kXPaeqQ)ZjtX-iDD#V|B%A35sVN)4&7uA(%N z?l+UFY=Z3^g>_s=Iew}#BsVmZlsagGh>*Ze@-xw&x}1jKH*6()BY`4{v0xfxPX_d@ zVZrp!6F>1Leu^BV1s5?v9^`FIKz}`^0VL0GfaKa%=4mgjFP0+g=;(6~C!zYBt;}Xx z(m`6-po;GJkOg5k=dX26%CBZ-oxi#`%|IP&W#G89mw|#U4@m4^fAWzTtC4Y#>v3aG za&JrdI^+a7=+AJH6G_7NaQ<53q>TSe5fwOk!|;VXf5apMM1dyZzIZQJj z^o5hl3(H|ZPC(Z?2)0os3t4TYIrekfeN{ieLRy)Qb+T7bj4Mr*tZTPi-Pq6-yw4F~nM8b{EX(poY3p zOw6}vN*AvD0X$uAe5w>XAI;XyK!6;dM;0rmPS~L+S^_g}cb##ecPHkOwpa};8;<`* z+)nWAjd|yDKM9Rtl82HZn$XnrG^BvkvzjtOAL`%7EP+1OvYuuv)3lN~)>AZgX#cJSW9r@5YsATC%`LNVVy~&L#c9I3)Zp4FB65TZLGO zLB{yB765A@V1_oKa}_lB3!)GW@*fD% zt6>IvbIEkqz?d11umV=_!?uK>!^joKC4Nan{H=qW#}%b+k-fsXg!Gk1e}O!$goIon zU}od&FT05JxNpR5mdMFSBDLaAhMoh6+13*7KgCK|RGo;Oa$Y=Fn>A~(BI8c-N@D?y z+9Wvufh1yv(I@l?I+n`_A1!$nx~=*~;9{WTLUbPttAJU|3k6Kx3Ej$8EFAKe+MOvN z9+OnvY%{VvEZ!1=6Z_dtJi}uc41w}82CPav)K?^Ar~?dL{fR)$P)Ci#p#XM9-EjBm zlb7_5E#2r8xSVPl`=iB-(oi&x>i!?B*)Trm-Kj)?l zn&1ej=k-aUbP;L6pi>fEBB>c3`sqe{=tYl7;7OZAcVGmvF71~57s_((Pv+ZU2jH7O zDAVVmjvt-CLI^aC!ZsLzJw!avj)@Fk7YY}f09gof>DDMW~9znz}}p4RaC_ai2`e4;vipg=$e z(EqV%qJxp6qnWkIpC?P|RjeFx*^u7Wb-FM5b&fC@2rg`I^*T&?LDINy#Ix8`f4%ral%X z7s6k_6m2GrBNUlrR(drbDTEIot()3RQY=tXGkaNtNn^ljSx5WuenNT7_@zMsA({z2 zM}t|Es4^kdO@gU_Bsr#s3@)%8sVBVO7*c2u%t@S&qA@!lsUW|Q*LOKcIV*OB5K~X_ zEpYYbGX@MLCQ;KmO0gEpolY(z!#51W`*ou4VO*VbaJ*HkGDSzqm(f>c{8aKe^Zx8% zEg;aDD1&@pZsQ&*Fl=G zw%nv5jU7+pPL0varNT45^`Iy~%O60%xoMSqg!Clc&*XacF%rIp&0PKd2i+n++(UlS#qRAr zq1J}QjPDJ@SX=kp7RIJ{Rk}H0lSupsSt^Se-7{WIwG&e_H`$1_Pq1Q+gbzWh5=6eh zJ9X4lixg~MMLBx_vBgsMD^b_; z^v0_4b2aNp>!%8WWg$Pp>5gU(2VT5?Dr65%#R`!#6~d9NaDCfTjp8fs*Oi%k+{ebZ>){sL0RMBWKoY^y)SQOmd+*iEA zF*!y#y^)-C{P&lc($*3AGbfR{k(>O^+9%s~bPPX7x9+V;Hg?eb8pE?b&ETQLOCa zvbv!5nsnUh!e%7noa0T(@glbge)<|F`6l4StTLL~(hl!7!97{m_lJ4?s^|p$_a*;Z zV^0wbu%$`^;O}E|?>}3rI2-9J{>N1xJ|$od*h&R_s$G__?UE`1SCDexJ4IZawIJas zFskbb+}s$Z#Lt)&jOjYLmSO9+Z9d+aipkws=pX!B6-A>Za$PDAyT#PJ>+GR}f&r#3 z3^JH4+aTF$_v)!BV9&UH!Q9|ZWP{m+D|H{OYM)BnBR|&~djV%dzFy#PO_ij7;c5ti^yOB#_ydo2oiwFe))z2fwrAQe3FQ>=m zz@I~n5FJR(l#=8Wo2)eFW*Ahau~^VGQ{r!&rt;W!uxZl+|_3qsE`L zf*i~C?xs*g>yEaBF@Lf1@D8bs`&C#}#9{2;p<+l;RJZh8QGoNUAn$WkU)k)m;;cfx z`%e{rtX@N%i-3$S=?bGZU0HF4oOtXAH6eC{Dpz&mE5F06E@!dufsWsbmQ zc3`qezr+Qiz+!RYljW4UB#gvBCydA;=5Ik=5BY#5?o^Zx16c^2wk&Uq^s6!Os^?!! zm6RLy(0nelCoWt15&zA#_lW{Nr~o#Vl_NA5gLB=wZ=;m*R8+h`aV^~C8|zY^lXLez zQUSuJ*(_%kk@NVjD-L@6A4UAoBS`$8e^3ZS^}^D1AHxGZjDL6UEd>W+pX*xLQ6Ali z=W&BgcW|w*`fVO~%`yH+!!hpqNZ$+nVkOhfjZ#KT{$$==d`BV^dB1^w?v)cnpNZe$ z>0UrrhF6%+d~IE$uoQteJwcK!hB;sC5TWaf2#Ms^me3QAHL_4OgTFaeV zUn`on(GB}~{EK0o@v-+Wa}2g+%O?xASDx0^)URHe>(+v|MQ@WY1%WB@=?CoISH&lE zJB@pF`Lr^vD~p`iD>3)YX(=gNX&W&3+JI+`jdYCLA185QsjVkHr7J`W>~10deJRHY z5H;0900Fg90Rf@?b5c5*8d(|9|4AADY}=zDE4wO++IdYW+F>hs#|P~Hb1~rSGQY2G zEY)D+T^Wxax54-rh13{{ZmQF%~_Ri5w>=_^Z}k`>h+wUR;YfPs5EYBj0E z7SPr8i|6@do2!lS#mIQCca8@Q$<|m#rB>wx@fgk-%Kp;zz5AL)=(RYNE}h&&6%SvJ z{oDT0Dhv%ZJ|reCMBK1Z6|GLOZL>mjL?md`f@|=S9zUceSk|r+U0VqCrZ)q9R=it% ziyX8!11uv{mDL(HT(Fz>w_OJZ#c$Ls|2$X|hR!T(qR7kgqFiEJZq~};hP_@{f7!MV|q63^_Wn5nU zE9b4~*3>376xquteQYc(9ofv)Pt7rcuYw^vlz$2|y)HC208hBYw6gPcxL?EI@_2gO zJS66l%SPb6YeICwk4g1zc)ss#(DRCUwLhJm)>ak*8DTv<%j&$`9jhuLQH687?eF1N z>frHtJsluUj@Vj&Jeg@mf~n=SJ^-Cw0I3yAu3{Yc-~Avk`;B!?!Z5YTv>12=zqp1h zM*&(_%?XSMre{sLJ%16sHCsU+7!pT#_Tb~QxSiI}Oo7;iYbDC{U1M^>M~{^FM@b+OhhESY6B$$IG(W@;cLXXo@StzcrYX1w?1UeD<45bXme0XM8n7% zWyU8qXz!k)*}DuxQQ=0sEVjaYn1#y@XyAQdT4mp2M%YH(A_9tg7?6}eOW!Lg+$CJ- z!6c4iFa|`$2sTX`Bg%&i%+GXdz>)=q^XRzXWWp3RsxV7n6Ejgx9SfI)P@}IzW3qm{ z(e7!A{41LAD}&>#Wtp6$T?HnypuTgQ)UzJjkNv|%8*n0r8vUzfgv4AxX$pnd`W20b zo??s1Pg+wL%>mq_p3W~XhnJ1?yfre7Wa5wdlXs29-j=Gzwp%fgxy9wZQC0@~I=6MD z8QZ!&%Vw1I~dEdh)zZVX+>n@ww@KdZQ6Nj+pF9DGj_MM_g%H~%C)?E;C zvNy5|*y9CHU})SyhzL#=z3J6>0v}WT$RDt3LpHl{?Rsi(d|VL7ZSX zEq(q3CDg^eQbIdxa31C8t*=^WMm9Wn_`-YeFpT;&xCD`oceF|A#v=UwOQ;6M$+?ly zHz$n-5%A8e?-m*=YEzU#kPY>-C5<$@{HEVPVMYXHkXjl@oh-nP7+37$KJQ7iZ=N_9 z4tN&3WSe{{*I=>KUnF--4`xGEDEGTD*fv#SYl%biFIhJ~qD;0bqpH2jsaAWIYY)zZ zVZuR#Sdp_VA_<%y1aiWuaAAx%5pCwQ6;>tojI9|PlOu^ zdG_UY^O~(tV#ZMx<-ST5>FhIgWkou|K{Jm=}a!Fuh5~2&I8pTVCWQ|-dfX7Y8q_l zG(TAL$x+gbjx*8?q)UojvnF1c2Ew-)r8~xCgv=+)lK4S zp?SBj%v7UUEdfPh_?XUVcntk&Jg9&XF=b^9;=h&gj_l2~0~C=1l;QnL*8(W7z?V29dZtcf_D(alkqkjno5yu|_f(%b!RIWi;>PN3T%ol7yl~Z!A9W67y zcni0|`4my8Onn8-ENKCQ6@*Nx%SD~{8RqG79RgBv%8t0H1_H(nGOuv@3(Od}GfsN? z);52vGPPfGR}Vhmoj?Os*a8l4r(ni&C5^TF2tr>F$L`usvKrQMx;YYrz0a}6PPD^F5&DPxUG1)lZuR@JzRK%;IZEQ zY1KMP^N#Hj%T9XAaBbH1fBL%apeEX{n_z$-ML_AIbm>I_6-8+(uOI}FUL;YZ7lD9) zNO%D$(wnFtMT(#p2x!1UjS%Tbm4I|fD25JF^y;z%=dh*)y&duTp946lY~LT zs!^)K%^JzU;};T8hy&W!q2;Fb`6;jg1(FuA}AXQ#^HYUQKrW^6iwfxVwz2FJ@c29?m_x>{=Nld!(`&!q6u<-AU?>Rci_&i0@40x7Rf-_>LiL0z6T@b4~` zj_PfAC@62?y%;T2k zoc7W}c0`SZHVgaL3+JPKhaVj;b(}dM+0h`Lb8w{Jiqd|~%|cS+sC1`kn&y_jHZtQe zX9EKGnj|r|f~8Hh(WWgBA3b74%i4D1ZCX60S>>ZdnZtRL-1~zyFgAq2_`&Kryx8i~ zV{V^=^ry|4@VIonorzZhC7eBGQytN1=h^af3Hkd6UN+%l??*^795CQej&j&X)>{ec zVx0DFb*D_O+{~HMf9Eqmuvz8|ppZtpeYW*wUQ&IditF%PB-i}`A1!BllE&$b+9GSWdk~jFJLIU-*?y&xLVqRP7&py(eIlh5U{%htoLcP7NI% z(;_eX)ZIT(R(bq&4sG$V=#IyNQ)bXZv&YtX%fC*qxW_Qs zS6awyAp_C$%PU1~>pWH&<(0gvK4@e+&068?)jc*h_k8lbkl9vQl!LFP)+r0;zm4%I zNUFI{`=zAOxuy%9vTvu8k~5&|zww%qQMM95S-M7P*(ZX5b`8D+rqurx&tcnOjXv z6zz$v%v-(42Zt_`GJ-MEX_)I*v~MJHJmi#levEQT(DuZKgfQ;{DQ@wi=g(n~ws*`k ziwdl<7mln}?O>&bWN57+|Lhs@rMGT{n6pISq_RRG*2gi9#o21`j$sA?P4n^!mJZ8` zA?_E&gU{mz?q1f5c?|1VvSE^q>tZIGLemhLWA3`8k5!*YJ=R_PC#uhGtn7)&7PF1{ zMaMNU629WWq{kZ@Wel`7|7#gEtfNKQvM`zTxl&X!Q%1$$+PEycI;f9msy{X+$A~f{aq4#a= zC-|~FbWHd%xzx&3M;Fa*Td=v@cfUJjPLU{#`kdWlXn#%#BaJzezs~=rmmA}jFmcpI ziau*|PxiFgzVgN+rVAF=F+90hMFuP3rkxeRp>gb9;BFKdQzElB5LW8v;+o*+ufS~GM=#tW?)Iq<`#ox)x^== z6nxCYek(q!M(2y#{x^a1MaFvFdPjMC$cGLwn;uSg2=h{i&9pnpC}qp3oMDXPap9E; zjci4&mELb@l71F{NuOX+&^`5{O>|LOlRL4=5;L}9j`YHP;n zl2o9u&SYCX-JT%Zp3VBilPYGL+ZX5$nuS#!e~9guS&iUF;rk^X=^Z5)jX5rq_eTm5 z<9lKs2-ZGbWz(ey7}whn;k67r*7LQQb+7YLA?|@Dp5gi$7MAGBz0P7HZ&SxpGiCTw zTT@+A4`UyCVvTYhlJz#AL+50!m?ma5(_WG1*f`FK1y)tB$Ta z^Q_{_x5gZE<>sraGj4N=x9^QJWn8lF;i%2!!%B5eQY0(*srvVxmQC+PSE`Vd@NGTe zG!kJcMP51nGwarqoifultcKbaz#A~Gk!MRZ7L@%p@^9U>D=HLcjTcQCntGsEJCSUc z^om1K!VqC-A+jbiPqkf+teQYnyI&(`=gKM4r!6q5?K={PMvklq#GgD#Ic5*lw1Ev2 zRMcJ6nQc8X!6?v=h(Elol*P^3t|fG@0lE=Wlfs-p9JUlnkg|mO^Y#m0c*Tm7SyP61 zeV#Wx>}V^(C=^F0pA@a#pGROS8lWlZW#n91!au++_EW|8x@x4W_rB02OL0(ycADDbFr|N@m?DVGllPP@q54cUBd*?-ad;K=R(` zD50wmKMedpi#Q8!ifJz2>Gr~Q^VEcD|N776o$pSqvfMa$@@#&Vjd>S#r=tZ~U)B)Y ztTe22dvVApUr*{y0l`U1Mhb7*f-STz@#q{=xo7F*uzd3#o#tq2FR<{%ndN!mxaWbh z3rCdQ{HH1Qj{?7Z<$CEdwECeL>k_o`;`p$Cz|<+~OX|ROXXECXk3l0N1(|_<%THsk zHtKsnnpN4nw1yuJ9QOJW&d4RVy+N8B-dSIgdwv%qmb+He3=f7!8{|*Dis)!!I4>64 z)Ws|bxT(qVr6*OZ$UMGT|Cv8vGe*`U{jeeSAmyxg%f$0it_nrtkHrj@OPR@{p`z#v z0_36qcOhGu$4U{8YhB28b}${UL)Kq6Uw3E|^tOPqIh5wWj}kc?5{bZB6&vOyBmwDa z6Hsycu0nqOFu)iDVyOufgYDeBuUz)?u=BG1o~eNe{KRzBHi1Jw0d-`?UufVT10?_y z|CjLp$n(L#h&zl%F2Et30FdnuAQ>3>@9}fh-SbB!8Vp}1k+{A9a2LQto%IiR2r%*= z9O>clx3ebWKC9&ecHpn{!INhK7ve|v;CCx_@Z?|z_h0j4px&JSzn?pp32aZk%QOl+ zg@2sLu30%43|>ob7kuN~A7IeRdSD)K!*rLY+xHjGZ;cc%9Nc`_g*OKLgo9c!Uu|c2}I(z I#P56m1DR~3NB{r; literal 0 HcmV?d00001 diff --git a/docs/~$RPG-Release-Instructions.docx b/docs/~$RPG-Release-Instructions.docx deleted file mode 100644 index 7dab902792afe438157e3873974fc99b4ca1737b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162 zcmd None: + style = "List Bullet" if level == 0 else "List Bullet 2" + doc.add_paragraph(text, style=style) + + +def add_step(doc: Document, n: int, title: str, body: str) -> None: + p = doc.add_paragraph() + p.add_run(f"Шаг {n}. {title}. ").bold = True + p.add_run(body) + + +def add_code(doc: Document, text: str) -> None: + for line in text.strip().splitlines(): + p = doc.add_paragraph(line) + p.style = "No Spacing" + for run in p.runs: + run.font.name = "Consolas" + run.font.size = Pt(9) + + +def build_document() -> Document: + doc = Document() + normal = doc.styles["Normal"] + normal.font.name = "Calibri" + normal.font.size = Pt(11) + + title = doc.add_heading("TTRPG Player — выдача нового лицензионного ключа", level=0) + title.alignment = WD_ALIGN_PARAGRAPH.CENTER + + doc.add_paragraph( + "Инструкция для администратора: добавление нового продуктового ключа в базу " + "сервера лицензий. Пользователь вводит этот ключ в приложении; сервер выдаёт " + "подписанный лицензионный токен при активации." + ) + + doc.add_heading("Термины", level=1) + doc_terms = [ + ( + "Продуктовый ключ", + "строка вида TTRPG-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX — выдаётся покупателю, " + "хранится в data.json на сервере.", + ), + ( + "Лицензионный токен", + "выдаётся автоматически при активации в приложении; вручную создавать не нужно.", + ), + ( + "sub", + "внутренний идентификатор лицензии на сервере (отзыв, учёт устройств).", + ), + ] + for term, desc in doc_terms: + p = doc.add_paragraph() + p.add_run(f"{term} — ").bold = True + p.add_run(desc) + + doc.add_heading("Где лежит сервер (продакшен)", level=1) + srv = doc.add_table(rows=6, cols=2) + srv.style = "Table Grid" + srv.rows[0].cells[0].text = "Параметр" + srv.rows[0].cells[1].text = "Значение" + server_rows = [ + ("Домен", "https://license.mailib.ru/"), + ("VPS", "185.173.94.234"), + ("Папка проекта", "/var/www/license_mailib_ru"), + ("Файл ключей", "/var/www/license_mailib_ru/data.json"), + ("Пользователь Linux", "dndlicense"), + ] + for i, (k, v) in enumerate(server_rows, start=1): + srv.rows[i].cells[0].text = k + srv.rows[i].cells[1].text = v + + doc.add_heading("Редактирование data.json (удобный редактор)", level=1) + doc.add_paragraph( + "Не используйте nano, если нужны выделение мышью и привычное копирование. " + "Рекомендуется Cursor или VS Code с расширением Remote - SSH." + ) + add_step( + doc, + 1, + "Настроить SSH", + "В %USERPROFILE%\\.ssh\\config добавьте хост (ключ ttrpg_updates_root — тот же, " + "что для публикации обновлений):", + ) + add_code( + doc, + """Host mailib-vps + HostName 185.173.94.234 + User root + IdentityFile C:\\Users\\Administrator\\.ssh\\ttrpg_updates_root""", + ) + add_step( + doc, + 2, + "Подключиться", + "F1 → Remote-SSH: Connect to Host… → mailib-vps.", + ) + add_step( + doc, + 3, + "Открыть папку", + "File → Open Folder → /var/www/license_mailib_ru → открыть data.json.", + ) + add_bullet(doc, "Альтернатива: WinSCP (SFTP) → правый клик по data.json → Edit.") + add_bullet( + doc, + "Альтернатива: scp скачать файл на ПК, править в Cursor, scp залить обратно.", + ) + + doc.add_heading("Выдача нового ключа", level=1) + + add_step( + doc, + 1, + "Резервная копия", + "На сервере (SSH или терминал в Remote SSH):", + ) + add_code( + doc, + "cd /var/www/license_mailib_ru\n" + "cp data.json \"data.json.bak.$(date +%F-%H%M%S)\"", + ) + + add_step( + doc, + 2, + "Сгенерировать запись (Node.js на сервере)", + "Выполнить в каталоге /var/www/license_mailib_ru. Скрипт добавит запись в " + "productKeys и выведет новый ключ в консоль. Срок и лимит устройств — в начале скрипта.", + ) + add_code( + doc, + r"""node -e " +const fs = require('node:fs'); +const crypto = require('node:crypto'); + +const dataPath = process.env.DND_LICENSE_DATA_PATH || './data.json'; +const data = JSON.parse(fs.readFileSync(dataPath, 'utf8')); + +const maxDevices = 3; +const expiresAtSec = Math.floor(new Date('2027-12-31T23:59:59Z').getTime() / 1000); + +const key = 'TTRPG-' + crypto.randomUUID().toUpperCase(); +const sub = 'lic_' + crypto.randomUUID().replace(/-/g, ''); + +data.productKeys ??= []; +data.productKeys.push({ + key, + sub, + pid: 'dnd_player', + maxDevices, + expiresAtSec +}); + +fs.writeFileSync(dataPath, JSON.stringify(data, null, 2) + '\n'); +console.log('NEW PRODUCT KEY:', key); +console.log('sub:', sub); +console.log('expiresAtSec:', expiresAtSec); +console.log('maxDevices:', maxDevices); +" +""", + ) + doc.add_paragraph( + "Сохраните выведенный NEW PRODUCT KEY — его передаёте пользователю. " + "Формат ключа должен соответствовать шаблону TTRPG-… (заглавные hex-символы)." + ) + + add_step( + doc, + 3, + "Или добавить вручную в data.json", + "В массив productKeys добавьте объект (пример):", + ) + add_code( + doc, + """{ + "key": "TTRPG-12345678-1234-1234-1234-123456789ABC", + "sub": "lic_unique_id", + "pid": "dnd_player", + "maxDevices": 3, + "expiresAtSec": 1830268799 +}""", + ) + fields = doc.add_table(rows=6, cols=2) + fields.style = "Table Grid" + fields.rows[0].cells[0].text = "Поле" + fields.rows[0].cells[1].text = "Описание" + field_rows = [ + ("key", "Продуктовый ключ для пользователя (уникальный)"), + ("sub", "Уникальный ID лицензии на сервере"), + ("pid", "Обычно dnd_player"), + ("maxDevices", "Сколько разных deviceId можно активировать"), + ("expiresAtSec", "Срок действия (Unix time, секунды)"), + ] + for i, (k, v) in enumerate(field_rows, start=1): + fields.rows[i].cells[0].text = k + fields.rows[i].cells[1].text = v + + add_step( + doc, + 4, + "Права на файл (если правили от root)", + "chown dndlicense:dndlicense /var/www/license_mailib_ru/data.json", + ) + + add_step( + doc, + 5, + "Перезапуск сервиса", + "После изменения data.json:", + ) + add_code(doc, "sudo -u dndlicense pm2 restart dnd-license") + + doc.add_heading("Проверка", level=1) + add_bullet(doc, "Сервер жив: curl https://license.mailib.ru/health → {\"ok\":true}") + add_bullet( + doc, + "В приложении TTRPG Player: «Указать ключ» → вставить продуктовый ключ → активация.", + ) + add_bullet( + doc, + "При ошибке unknown_product_key — ключ не в data.json или опечатка; " + "too_many_devices — превышен maxDevices.", + ) + + doc.add_heading("Отзыв лицензии", level=1) + doc.add_paragraph( + "Чтобы отозвать лицензию по sub, добавьте sub в массив revokedSubs в data.json " + "и перезапустите pm2. Либо POST /v1/admin/revoke с Bearer-токеном администратора " + "(токен хранится только на сервере, в эту инструкцию не включён)." + ) + + doc.add_heading("Локальная разработка", level=1) + add_bullet( + doc, + "Исходники сервера на ПК: D:\\Work\\my_projects\\dnd_project\\DndGamePlayerLicenseServer", + ) + add_bullet(doc, "Локальный data.json: скопировать из data.example.json") + add_bullet(doc, "Запуск: npm start (нужен LICENSE_PRIVATE_KEY_PEM)") + add_bullet(doc, "Порт по умолчанию: 3847") + + doc.add_heading("Частые проблемы", level=1) + problems = [ + ( + "Ключ не принимается в приложении", + "Проверьте формат TTRPG-… (8-4-4-4-12 hex), что запись есть в productKeys, " + "сервер перезапущен после правки data.json.", + ), + ( + "JSON сломан после правки", + "Восстановите из data.json.bak.*; проверьте запятые и кавычки.", + ), + ( + "Нет прав на запись", + "Правьте от root или chown на dndlicense; файл не должен быть только для чтения.", + ), + ] + for prob, fix in problems: + p = doc.add_paragraph() + p.add_run(f"{prob}. ").bold = True + p.add_run(fix) + + p = doc.add_paragraph() + p.alignment = WD_ALIGN_PARAGRAPH.CENTER + p.add_run( + "Репозиторий сервера: git.mailib.ru/ifontosh/DndGamePlayerLicenseServer" + ).italic = True + + return doc + + +def main() -> None: + doc = build_document() + for path in OUT_PATHS: + path.parent.mkdir(parents=True, exist_ok=True) + doc.save(str(path)) + print(f"Wrote {path}") + + +if __name__ == "__main__": + main() diff --git a/scripts/generate-release-docx.py b/scripts/generate-release-docx.py index 0e5686a..e39cc48 100644 --- a/scripts/generate-release-docx.py +++ b/scripts/generate-release-docx.py @@ -53,7 +53,8 @@ def build_document() -> Document: add_step( doc, "Шаг 2. Собрать macOS (только на Mac)", - "npm ci && npm run build && npm run pack:mac", + "FFMPEG_BINARIES_URL=https://cdn.npmmirror.com/binaries/ffmpeg-static npm ci && " + "npm run build && npm run pack:mac", ) add_step( doc, @@ -159,6 +160,10 @@ def build_document() -> Document: "WSL Linux build / Node 18", "Нужен nvm и Node 22 (scripts/wsl-pack-linux.sh).", ), + ( + "npm ci / ffmpeg-static / GitHub 5xx", + "Win-скрипт повторит npm ci через FFMPEG_BINARIES_URL; на Mac используйте эту переменную вручную.", + ), ( "AfterMac: version mismatch", "Версии в package.json и latest-mac.yml должны совпадать до запуска release-all.", diff --git a/scripts/ttrpg-release/prepare-release.ps1 b/scripts/ttrpg-release/prepare-release.ps1 index c16e0ee..9c015f7 100644 --- a/scripts/ttrpg-release/prepare-release.ps1 +++ b/scripts/ttrpg-release/prepare-release.ps1 @@ -45,6 +45,44 @@ function Write-Fail([string]$text) { Write-Host " [!!] $text" -ForegroundColor Red } +$FfmpegStaticMirror = 'https://cdn.npmmirror.com/binaries/ffmpeg-static' + +function Test-IsNpmCi([string[]]$NpmArgs) { + return ($NpmArgs.Count -eq 1 -and $NpmArgs[0] -eq 'ci') +} + +function Invoke-NpmRaw { + param( + [string[]]$NpmArgs, + [string]$WorkingDirectory + ) + Push-Location $WorkingDirectory + try { + & npm @NpmArgs + return $LASTEXITCODE + } finally { + Pop-Location + } +} + +function Invoke-NpmCiWithFfmpegMirror { + param( + [string]$WorkingDirectory + ) + $prevMirror = $env:FFMPEG_BINARIES_URL + $env:FFMPEG_BINARIES_URL = $FfmpegStaticMirror + try { + Write-Host " > npm ci (retry with FFMPEG_BINARIES_URL=$FfmpegStaticMirror)" + return (Invoke-NpmRaw -NpmArgs @('ci') -WorkingDirectory $WorkingDirectory) + } finally { + if ($null -eq $prevMirror) { + Remove-Item Env:\FFMPEG_BINARIES_URL -ErrorAction SilentlyContinue + } else { + $env:FFMPEG_BINARIES_URL = $prevMirror + } + } +} + function Invoke-Npm { param( [string]$Label, @@ -52,14 +90,13 @@ function Invoke-Npm { [string]$WorkingDirectory ) Write-Host " > $Label" - Push-Location $WorkingDirectory - try { - & npm @NpmArgs - if ($LASTEXITCODE -ne 0) { - throw "$Label failed (exit $LASTEXITCODE)" - } - } finally { - Pop-Location + $exitCode = Invoke-NpmRaw -NpmArgs $NpmArgs -WorkingDirectory $WorkingDirectory + if ($exitCode -ne 0 -and (Test-IsNpmCi $NpmArgs)) { + Write-Host " [--] npm ci failed (exit $exitCode). Retrying via ffmpeg-static mirror..." -ForegroundColor Yellow + $exitCode = Invoke-NpmCiWithFfmpegMirror $WorkingDirectory + } + if ($exitCode -ne 0) { + throw "$Label failed (exit $exitCode)" } } diff --git a/scripts/wsl-pack-linux.sh b/scripts/wsl-pack-linux.sh index deec4d0..b827d11 100644 --- a/scripts/wsl-pack-linux.sh +++ b/scripts/wsl-pack-linux.sh @@ -15,5 +15,7 @@ else fi echo "Using $(node -v) ($(command -v node))" +export FFMPEG_BINARIES_URL="${FFMPEG_BINARIES_URL:-https://cdn.npmmirror.com/binaries/ffmpeg-static}" +echo "Using ffmpeg-static mirror: ${FFMPEG_BINARIES_URL}" npm ci npm run pack:linux