From e516446d03fd39dc96b50b1da0410745a38c92fa Mon Sep 17 00:00:00 2001 From: Benraouane Soufiane <110255764+BenraouaneSoufiane@users.noreply.github.com> Date: Tue, 23 Sep 2025 02:04:24 +0100 Subject: [PATCH] Add Rustdesk module (#266) Closes #79 ## Description This PR add new module, install minimal desktop environment (xfce), virtual display, ,rustdesk package from deb file, init new screen, export DISPLAY environment variable with last created virtual screen, start new xfce session & execute the rustdesk cli, generate new password, change the default password, then log the ID & password to be used within rustdesk client to connect to the host ## Type of Change - [x] New module - [ ] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information Overview/test video: live demo that launch rustdesk with GUI in a docker container https://youtu.be/_rR-l7nARN4 Screenshots: image image image image **Path:** `registry/BenraouaneSoufiane/modules/rustdesk` **New version:** `v1.0.0` **Breaking change:** [ ] Yes [x] No ## Testing & Validation - [x] Tests pass (`bun test`) - [x] Code formatted (`bun run fmt`) - [x] Changes tested locally ## Related Issues /claim #79 (remain asset 150$) --------- Co-authored-by: root Co-authored-by: DevCats Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .icons/rustdesk.svg | 5 + .../BenraouaneSoufiane/.images/avatar.png | Bin 0 -> 28637 bytes registry/BenraouaneSoufiane/README.md | 14 +++ .../modules/rustdesk/README.md | 82 ++++++++++++ .../modules/rustdesk/main.tf | 75 +++++++++++ .../modules/rustdesk/run.sh | 117 ++++++++++++++++++ 6 files changed, 293 insertions(+) create mode 100644 .icons/rustdesk.svg create mode 100644 registry/BenraouaneSoufiane/.images/avatar.png create mode 100644 registry/BenraouaneSoufiane/README.md create mode 100644 registry/BenraouaneSoufiane/modules/rustdesk/README.md create mode 100644 registry/BenraouaneSoufiane/modules/rustdesk/main.tf create mode 100644 registry/BenraouaneSoufiane/modules/rustdesk/run.sh diff --git a/.icons/rustdesk.svg b/.icons/rustdesk.svg new file mode 100644 index 00000000..6c801233 --- /dev/null +++ b/.icons/rustdesk.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/registry/BenraouaneSoufiane/.images/avatar.png b/registry/BenraouaneSoufiane/.images/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..297e89dfcadaf14bc6ef1f60d30e1fce3925098f GIT binary patch literal 28637 zcmbTcWl&sC)IK=4Lx908Ft{bSySqCfSa2WQ34y>cxCReygUetE5MT)I?iwJF;7*p` z`)>VfKkjZ<^{rdCZ=LR^`*@#!i~m*tp8%NX=osi|m>3usSXh|YxIjEy92{HYHC&?HqN&^{QUfMEFu!ZyyDz^{Jcn5SXj6?xa4?vWs#6jU^H3`{I+oL2y3 zBoq{6R1`EcRMeM0Lte%JsDx-l^gJ@?#5z_O44y#Vh~%%BAlZgq65Xl4jC|H!AF;4W z$;c@vnV4Bv+1U971cih}MCIfa6qS@!RQ2=?42_IUOl@pG*x5TcI)S}?eEs|b0wX^~ zMSqTog`}jWrDtSjeaS8=E-5W5uc)kQY-(<4ZG*RW^!?}`7(@*H9G;$;ots}+Tw31R z-r3#TKlpWcbasAmd3Akrdv}inK>1%dFVFu4^#8y^_<{!+6%_>)<3Bt|$bK&!g%A~u zo(G*sMhC;nlbC@w0uv~k{I#JM3&f}Um&Dp@3Y(OXe~antKS=+B=>Hv{kN>|A{U1R8 z2hYD{04@sB%YmT~0;B*}E;LViPGX z1W=l)PhSJxs zVxV;%ghyHSWe#&aBpyXJPX&~hZ<#=CWSvMg2#+)tghvfx3UCzz>PSa#LwtazD4q)3 ztOF=S5FSNVB&-1-ych^bvz<;pQG=i|(NuaWAX%{%PTNkRnv@o0l6w-il9%fJ)n;;R5yXmq5e}*Ca}{1iM4cqn-wWduR#TZ&cq}JOar9pC;*s%x?pJ}{$+fJ zG9*(dpAIqIwK_YgTY{#V4wHxZkqelzGG+7g2#FFsF#H$E6@xVAoc9u~y9lU@UdsJwE zqeZZ&hN{J~O!I{oOn+?BDoA>9RJ63c@yc2-^)S0lqliTRT9D=J! zLD-k4QkL`uTE}M}>g8QNB8?p~seU`=3Nyv%U(9nW@W<|sx$0wMg{IJbo{NN!nN1kF z<9!2FJ+Cl2SJ#HpAQAaw)_qpZPwU_TQ!_EJL&?gZRz|>pt!%h-Bn?o{`=o+BQ(`?M z6=>a_knB)`=;i_dvr3DBnCd!eDlY=~!U1SpKy4%i)_dJEX)HB}*i00FyrlfPP#YOZ zkgq5r)Dt_>BmvB>+Hd*7kIwP_0H_j z=<59)w$}5N9%$FS)NyjGDgArrlCMrFqkYVvW*Bx%PFx|W7%!Q``#s=!;cXOeAjZU= z8I@7FUh*H)!EC2};)?Cx2~OK;S;l=P1`#NG6ay|wrAo>B6XKtR9~NtJo&}gu%C4S$ zW^1>5!X^I!E~}x$4B+Gm@{|pID-rrS?gJQoHr#8%qoJE}#MH@UYPJv1a@z@06H^m~zCNU~67Bzv$2ZG$%lnwKyBV z3e<(50$)U!wY20#L2pXDoO8wtBOs?~1K1pZFK2I^i|*)Y;NC(NBDuDouVA2)uVspE zx&kQ;s5U-4(ir{qnF2+;ps(w~>^2I_eOu#6bb9v=3{P}d-DR2GZ%0=tvGeV@i)Eav z5ClyN(8U-g!_|bBwG2Vz2Q%GX%b(+~g39~Bn7sa&QZO%BJ8NZf7O1{c|3^7K{2wGm zWuKNNN4Dcl6_C{s>-hfee=AP9l&WPnOQ$F#66->pF_{N&2j3F>jXpRV;3=qNB%FcN z%5yZ;d>-r=HH*z{^$;bv$WN~<4_54mO?N9YN%g>6jM%te02CHvd4)Dl?Yj+;(~Prv zPlEYYT)I3<-i1>dMm3@{l8=Yn8!1pX3>OxomRApC+GW+e1+67rZbrxE?DR#1EDtf6Pvcw+d4Z7a! z_9@*+gj5^#L;nF5M{zv&@z<~NT+DmP#vbXWb5;u)Ef2Ti?;{qN>zs5tmXX(l?&oNv zq}(yYFO05u2N!J*Zt~c5yl#`F(nc(I{XX4X*S^SU$v=R%k^Ju+O`FaVmbMn~)WL9K z`5I8!`^LUw8{*5xW6t8S(FZYQMPmKW`c7r*sYs(Cr({InD7-in5RbCyNt6MK{hVwa zX@W6s$D+(qfUsUfSw!}MxWTrux0+(Sc?c}p25`CSb6JRSn>wKV%QLDc!2A_l0$*$) z?dTiBx4BxH^7Jn%4-gM2_K(lcJGyzNwTxV#+?23V`%W7Wy|eLP4}uhsVK?*}%_zfq zltkvHtdsCYG2}4!QbK~m4AKt`ZdhO;XVmknrxuh5FIm z@wWt9ia(~)*sKYa_-jci5xJy0Wo*0;n)865Ss1xFbU(LsU!E~n4}PaQ!Vi(<-BU(@ z+U}4`?Qo^#f7qPX@yEHmYe>-$UTM}kC*c-No%Aco^2*@yq4>>hs2o@9HmHA&7q}25 zZl0Z{;_AP}ErN{zNc{s?ba|S~KjtRfy0!R@|Lt2jAAt9gA0LmgDQ+uGURt+*$M#R* zvK|&LO%9!{j;6sv1RgdV8w2@(S=Z$+u3@+xn{>H#8n(?VY_=ZV?ew0lKZEWE2e)H} zo-yMXPAvid0CY`s*Ao|2=V8?XZE}3^i5UIn2d5HlQ1mUVgYVM~T;t-h2PiQsck{4| zQLbGc>bJXeeH~VRa}6Y!!CoJIk^q3ak;h}$`?vLI{{S(Uv~z>978mrEE@vL zj2K?I(A_CQt*;;Xo;u(^84O^q{_QifLZGqBnu>FQD-_hbq86Whu;mxT{rVh-oqn6* zCfGvG18>`DzMGcFL7x<#7 z??S)$y2He;-?duLPAJi5qxn4! z`9HwikDHv??Crrj{7KAHuo20uFXaxaV4|f{T5RfS+o5S6@&KIPXue2Ig#xaNs3%cX z4vzMcL4auc%19gQ$<2cnWXc^ALHiCozk?RkT#Hw#a20tD_1G)PC^hl}XwsG#nYFsr zIn7yzy__cplU+X5L&3{m+ox^(hfujg(3D!=EK(zVQtXxz)r}TTK&R=(U^G$_3?E)e z`zsNc{h>CVp{Z%}o+zN|y29dT?y^kFxgOV_Z-3P{tm^px;xT=@ z#OMUj>8VWDog+@A)a!LPGUEP$a%e2%+cfAqBpHr0Yu!!< zjZ!Wl&@}N>@L%PAolvJupFg719mG-($~6+aK26!hN80ZI&hU6Ib01QsWe82K4*E6< zyVtb@MSrf5gDv93z(LWp(Ot~KLa&rG=VtxI`rni|(aHr+4)^d9p^MoSsqUI0>aD|B zl`aV1qayP~Y95+E)pZoXimZUrBB-Y{wZhOpfNJK`#p6*|fS`JzWWke-l)u@PN~gF^*B2FbCpsunfRZQR<6enBXFLwVHt{Zg7aD{UXTo8 zjbw5yiT~&LP66YK)d`TAD}N|TYh^FctNg4N{|_+FEzvvXmrv`RsuEvp*^@eRY+=|7 z+)6KUnwnZM3@VM(%sO>RzOb~0a|fzlft8(F3rJ=KtJ+dyOHX^OYi3@{sr=`Ov+=0C zL{#LZr31)B8Bo3OLTe;Vh`Fh$ z#>-0Gv+0JDqqKd80X&QrT>SzV8>ur~=Yq6ah^T2F=U;n`0}iu)T?RyBUJ6&GHcrAh znw&P26O`W`yuK1}e~@GiUxuIL@54+Qv!XG!^d!k249P@oh)M*HiW4Np%w!@y`j?F3 zQ9ZF!@#`?ru$7=_tUBTdIi;jv#E|S~(d$ei>9Vra@h@;CktmSSe8Ot>2I9_yiv=8? zCNxXBYvdo7F6Dmy3*u&-R|EHwBlTkCCHS2>HS zM}ug%1?z~b0Y41kI-I{2W!~S3WeIqOd|#1C1lpVp1QpePz~!50AzB>(9KN`^Ndp7= z?XxGYDn`Ne&xA&RS~;ElXD^<3L^csTKiZ`Y;Ytk+9ev-7N`YBr9V=0W}M(oJG6 zSp*E#zC;dx1Eb{Pb?IH&+s)H6X1MS$7C0@jcv~;4WOp3%nIO(L-33aClsSRB{{R(q z!%aTt8@6%2l4me!YUA@4)6?TkI~OvlH$}alRC8Yfl0rOjxW9{gqTN_G9h~~U%RwM~ zmw5i-o9=U1!T$kjt}4tZwRQw8@ajc&VxBm1{w|-+=GxP-!46G=V|nkMHeijjm zppe^OWr;7VJ35Q{h)nR#(-2jsr#l*nYq>Xy~N8} zH=c)n|i;U!R+hNzkXOig9fq_NS& zac2~SDFNCX!=X%?2m1OU#ZJM6LH7&Q&}V_2e*nU*bDt(?rtK}fxix%p2sKhFdIf>4P&|f>J8I;}IQeFTSA_>ue%KhJ+8+JDE_`?$YJ_l(1AXrqv`q%S29nU4m)AUqY-*mf!ps+#D?*=&8Zz znLoQv;TbW=x~ROXhg3MDQu)c4T9^D6a>=LbGU!3Rf(Z|us?BSi6T{eK9J;gxv06Yw zCW`RMJK*h7?uGe!ZI}l_grvlxj8}=VZqEY5CN6~j?emoVgJG)so2+R!nG2qukH-f} zAdd-~_tO5-bhipFSjT(k5(Hrj0yX1?uG<+Yxy9MxyaIV1jw@ENeydXC5@ZQwT@%Ln z=i5QnH(VWNP+_?!lzidZx{d+3P z@0r*P%u`Wz!uOVjp)rdy=on`rPmZZ2yp|A*s&f%qpQ#gB3Y& zF6$l;r246aN=V|Eicf#cqsO~+!=zzD2^3sHH&|{m*CC+B4{u-v&^q?mrVg?rF$au0Qic?x%!hM%5Y(42dL9~N zgv{=+&@`!IkwaqJkCmAYU;+ga1G@B$d5F_qlRSXVZcNv#nf!3vI`dY!D0LILlG-wO6uWRTFSxS}n1EMFU#U+n z*sM@fC&)afSNl-HJ}tUfOKzwRE!2~)q7wUA3?jcLimXDU4pQA2 zw${EL+rMB5_eeumr)fyuCTE#PgL84m9B)Hdw z=h49KNS1S-DQvF*#%nP&G$r^{<965xJ1UaMSPAO_>tD*nefx69K3{WZrUUhIFe82D z4bdNXN(B#1WbhdCJoR3};!Q3}Rv+PafMes~P#yV?`G_FMkvIK60MY69LFX*Men7X>qxwqNa@eU#c_@VWl7us3%>H z4M_5?6TEFtZ>_i9U*br#PjaAo$7 z>gefw_})dT(?=Bu?`9tavyl_6!ndT6TWig7Lv7}pDxP-AvODQ@^&zpY|>_;ZLHrW_f0gD-kjGv;FQpg5J{#P%i=SIwIQuTKzP zBHqekHI2KxlKd;tR5f=eHB+ct`Fz#b_TrR^fyn&=$WKx#4iy({!nv;~-Z?+@ktglT zs#M$v(s@!yykb5m!w8@p=!p;*8X#oJVv>q7k1xcFMkDw-RK2Gh*XncKvAZ?+l4>Yw zrFE!1x%ql`HdM>7wpM~>9Eg&;Am<1Mu^Q4eB#*IJ@muE*f_I$NWO*4g=!YhZD4wwD ziVReXK6**-=09I;E#q=|o{9dbU>8ch)Kz}0ULDi>1s?H4$1pvgh_&~N^&Y?wUMYAu zYJH|Z(kPF^i+{~Ic4MBX+0t{Qr@~!c);0caT_*P*pxab0uES0*(74+pZ0ZUnLA>`d zN_UFn^Jh1F;R~vJBgJfzxxKs|4G2rfu*$Eu7?)(y|vXJc-Al#+?;55kCjsPjapKpmRg37{rrLIi@ee@D)lv$%rHyeEHi0&hF+; zM_phx3`=iF+o53(X)O&xpW3I~PUM|4N>REZX$o-WSkh=^BR}Z@5WnL>e7gL3N@VYc zhOdu4$YIA3&3K(FtBqq6(hM;mE6gr1{+P;TMv8 zlbf#p4&(~c^)kq#mlCP7ZWP~1F;xZIldI#299y&MTc181SC061Gq%}PMDUXQT~xv)F8-U(W3WPZGc^Di(A6h;BZql zZ$aaxH;Z@(2RsD3Z~Nr5=G$=n3Cixtoq-B}5XGE%GcS8CGScQV|D5rnTUu_d*K%9N zy}{tzPj|208hDCV6|Rqohf%c97nO?}_Sio}4bfly%AK#IXZy*GO*QbpZ z%bf{;MG;Dp=?x51ke4hyH`_29+wpX`u&e2_@l{->NCA@>`;Uhb+feE4#ug3Z@1OoO z$IS=43ZFmsdHoT8<^%!E=Swd>Q->Bm6es)xu-2SN2Xwe>eLEjSTnc>5?D7>=OXf|C z)@I5*2VaatTmrDWy@gNmXC1VEPg`DEbslH83)KGglwVsstfuZhY2D*hyTM>2P_3qU zn!AW|m3+g7Ie7i-)9F{LCYtmfYlG&nHJK?Vif~JM&~tW*1}DE-E~(>VklBta|2bYE zku(d%L8+egnKCKGVkLd3)w_Zi{)mdV9WAyO4WDH_vqxqg#rt$5j%yq~y;Tt5bPlSf zUcVY0$zkIv(a)218x^ITYEmS}sCZI%KkK{dst~&xxSguiYMJHvoA1zg?3a%W)xB#6 zW)BK_8;LU1N=D#IX^&j)y&r5Kac?)2B-x9PTbq}90aWdJfoZYk+|rqd?P;DjSMQq& zf=Paz+&Kj~k`rM~$gIwA!`xhhOKd190)q(j%wCz@`BCjDTI2H_nr3HYWS2r(IwQb@ zcQR7`v_)-k^9Tv~adnAN)KA??pn7qGeX~w~((Qd9ye$m{?lZtu{`m?g=PQ-0jn;OG zT;G#~bA5Yjs~YC-o_7^rk=&1sILHTYgnv!hcxRw9V~HXM$Q`~TN!a%RJTo_1&#aFh zDbG^8wn;X-e(NP%lYby*N|le-tauwf(?HkRz{$6|`{uk(zTka*n{awA?FRjRR|D?k zku4}q#R_{*lNZIFZsZ)6Ih#&z+^f5$c+|a?pd#}qd%IM0Mm-;9msBMiYCZDDIFnLr z4D84Xjsxeq9*;X)I;BQL?EM35@Y87SCVsJ~u@Db&6F=vY?-VY3~=Ro-sjXc8U51n19nz0U3wIZ`RN9d8fT@jm}_vDXXA zot-+)Bx?TBnPgKw2HC~+Dn_?^b@2K=6ANcAk2>SA-`F8X{)rU{d|O=4Qj?cAFdY2G5jxk6-L{ zDaNOaypPRE6|9kKf7hWGIr?ztq%1C_TszvJ+o8#9Mh!@};qW$ngUI0nu zIev{_!hfvaOkiiDhIM^n`(uQwYy!^DZyw3DZZ@CdoQysgxj=7jc#X z^erPGc8bM{cQbPXPZ}zn`RtcwkS=%XrzpuBoA4*f(4&|81ymNVs=sSvKcH!Pca+-T zPb1+xW{I0Q0;lO6RNAqCma4KH_WUuG-m42LF7Jcid4#4%s#acvVE1_1WXD9=m>&mAn!pV*z-ZDVcD@0+ZP=9Nh?3U=N< zVd`I?rGu7NNl^(ka4Idmf%gii5yloQvu^y=2JMl4xF~1Ik;*|-PZcD?#<#=EX8MjM z@&hrfn!;DLAGvhL4LI8f)33&my!IpoOQXXWaNMKuB zNd(fM<*HHh$bR<$vOco^h58H_wKBHynCc7)KIpzK=lXU9qD!}4HPxUZrVUM>J-HGO z)YoOm_G4!uKlH$oRQY)%eXIj(*=bc?CaA1!*}^C!A#Xz^(#PgDnP7_57D_>oc;WDL z0XP)pwcld!KYXBAgHTZ#axJZdcWQ-h*QGB5XxLdj5!42e8uYw*eIG(wKKs~T98ep{ z@p|SOn7k78{>56)Vv~{4W7`N7YB2~3O8;e?u$g@%Ug}n;`;D$!;3wFD3#YgOgbL0dx!wm5Yh@Hll zOw2KHBy^KVR+QP~XOfI*z5!}jF5FAvGdZj?HgrUwD<>?`eP&M}vN}AKQD+Am3uTgc zz)@{SQDCj2A)hQ;ZG+HkS%t5e`%gP~5e0Io(#0R2idzyydGQBx8TK8@IWunLRXa*{ zt2r|eINOxP@bkB*bz8;N2-5Zh_XCptd#@cJH z_N6&3Z81afY=etc5K{?a@3?;cvY-QOXz?5^KmC0fJA;Ly+7NeI4kRQknw?F9qkHjX zdC=zYoU+}q@W``fb4eX|d57n;+)%*5DqCBU74sURLg^F!Dlk87B!^3Jf#BK^r1|yi zJe&WQ56;~qyvQ+@0!c~6BuF8M?05cN91s7Lmh5(~9A$=A&l7)+wp80w~r zfimADuo6&n@K!m>7@X)-(Ce{2RykTvf=SH#rs4z=)2R-Q^JrM)T9(*s#rLanlTyvl z4UWYGPwt%8@}3(-Uu$Ds7^gGRE>#DrpH6r1m()%JFvMT-d``7CSN6`UheaU|Ub~Mi zOos+DWoQlOxnT2_tIH%pNmyizX5Xo3u_5NS{*2x*)TuY-A6#Q;3lq;^^tvN@>}hvj23IvdS{R>;l*ylcW+t%= zI!JJ;%TV1LC#TIjGWdcEdCy2ZM05OcFhlG+mj9RLr7g8G0#Xk(I=DpjH2oP<)Yy+2 zF9J@a&sWS{q&!_jB_GS72}QMe3cvdro1h7AgC?`?vL&$HNx3t(Ib!Cu|2F>)ph02( z2hes7_D|7>fG`sT{=NIN?3lOWyD!2xfBmHn)=~(0nZ@5S08#2t;L$0j;Xe4?e%2)& z+lnSzflf2MIH-0^T#cb2#`T%b^!%OTk=V)|H*)>Yp`T+ur1#BDD>B5j#yc#umunq( zDpvwh&8Ky5K4#NXyZqY0!j{mz=duV@ljCKr(-`((>*H@ld7P zK$*7%k73IE>YLYp=o0Ck0=HcH5RSgE{#@K3zuNRG^z13lFa403TEyImMt(qkS)Htkg3gX4u21RJq%8y!^C!V|ACPK6T})G^3U{!?rv36q zJF0g`e5Q@sr_7!;>%I;7O#r$S$oyMl++hGM)R3p?NV+&{qa!h26*4IiQ%b}G;7 zWAoEahe+KF6C2K2#c|IhAaa=Ke98@!qh%;r>xaufz~3*Y(8i?qs39ER&MaWIoYoL9 zYf`PX?e9Qp$aeEDk~gD&`C7F)c6n;GMqPLnirp!D*{gry4{(4Jz<1-ejzN& zy2_7vEctWzm6olJz>3|wxK4`kWytuv^KsU8te@&CZoz!5 zC<7(866^@(rBhdqA|5;^p8c%?l2@p@!`ld17Z^O7>9Ahs1?1Y}^M_~?~g&H?TKz{o; zsQkT7B&v|D%p@NS)sa&{8cJno1?P+IWOw654KE7ZL#4~n8j&VuV2W0EYhEhUxQ4qB zy`Hbi2Pa~Q@LgS${Py(nrQE#gaOG-4QZm!T>;}<&{&la#?aI-`rPN1&cWq){>$wtF z|8D5-6_JTr{{g`o9KBqJGkR@*YD@hjM8^kNW7{2=0`nHVL}pXH6j+({o4(^H*65h8M1v$c?QfIFCG}m@|%bs28{hARU zP)%~CVS-Gwp|dcO#TmX~H=rc6v;cX#+|atdm=j40 zBNulyUyv#p|Fx0Us+&(u)OOkTlT=P3>qYG7O7AoIp&1k2nAWWvuGCrUTkGf6gSria z_yHJ0)ZtFjYC+-W?ku=k(j3h*En4-971&sttOH*6AJT~4vHyG>kHL96j^=x~wL-x^ z+pTtOrg^Shh&$cVcS5*w8~wg@&^A`yDq5N@&uy4ed3CUd6hirHoE4j&sNK+ywwdA2OgGF|G)izedKK~L+JSJ7g0vmTdS8~4Ma=87*dCiA~7Mz^! z6?xyM$dH|@G8Qep>hH?W)3lO8SG1SE5@iFbNK?9daKX28duV{o*+f^IlhT!M&K+?Zad5Hhc(rpkBN-Z5lLOX zuqV?K*ArFOiR@m_lZAUDDW+><4#g}B07CUwd09D$?Mj2QQoCVCyU8nV2G??uRFWB` zc6k%3*m3sh^=8+H#$t(3(*&$+))8c^RV`^xGd6OsOo211!wg<}3RV={n*p`22t3%V z5VMCeHC{I_2lw;7$w~C1D_&0zNwqC~!Wy7cuw$w+Jj_&>}VosD&#bF{FYQ2J4gz zi-8i1cd&M$1r7%vXMuJSz+*_KxgTowa89Cm!E@1`Mk)6$00yYvrjefO-+gT;m2N8$ zagR;pNn{3zhzzJVxb-)#{wVk7xLX(~o(4aaB$MuYR54QeM>aQkhOPI?--Ig>vM~aG(tK;g9-( zyc?143~Ow9VBGt%(@VZN%ls2{)Ex11bHC*}F-?_o2bQDn-14A#yE&23CrH6@Y`EK! zvcK7Hj=(|^+7Tx(eYw4Bg59`nkR51gNRA~=yrolBZT>tw9*D=Vj@1Uc7pAtX6QG$7 zKVPYF7U--@B8;3gGJ1-$Y4S3^m`T-mMudCADekAQYl3SArn)#>BquYI`?k_h?`~mB zzg-m*nR9%AI*w=IiVHgnce7zj9@PXJ1>?E*?`mQUYE>{Q1S}RcardRR=q68#9BWGK zER5I>=+b!+eb*`cNvkRhwt!L%@7Q9~!4d@?y~7#)AC7x)2E(C0S}P0z6g04+MfR2A z41H>1BfkiPtfT`j_!C>cFM&u6XYiOeB{MMS4zT_GowKa-m%T%AF@hatAcr zNl&n$0xmw2^f;E@GeLb-0~dI8(gv!ei_+w7gHyplEe_%B?A33CYl7lf+cL2E^tz{L zl#e0Gt2~zd1u(IwWu7{&Xhq_JQXPkKM$dP^`p+UD=((o>E%&NTLy02=h4wPv5R9Ae z@Wj-)6v5bYf`K8x?}2K4!00XDB>171C1Saf*X%z}G(y{zbXdo4`;Ri{e@=)YNEUB{ z-gnG$9cakR?MFo~9JXr)KAf2r>qT~AXA0&)R8-8*>u%+{}*Mm%~GlcEI(U+`ADPE<}`Id{hz_YV;uH<=W-X=C| zlG_g@Mcu9&{!Vrg#2AF_ILkQ)X$lX)lX7_=NII*?)cbgiHQgs|(=JtErQ<3 zq4vRDlo82qFm7grqzmRkeDh7ka{&cX-A<&y4me=W&im=vFedZ&q)<`EyT288ghq8l(>)g7*<_@4? z)jn501v=fUu@dk~swC!NDkROv4@3Hio)$sz*JAI+#g9_IDPl-A&jfjXlu3Lk+)3Ec zm`<~k$2z5~FgJE|rdaY-Dm*rUH&WVBZLD(GHi4h!iXUXn!)iTGvNN_>Gxt>F?gehLMQk#eGosA5oZth2yuav2i#Ej?sK;`u_j^& zDxv(0|JNz_Mhi4wk(fo7lj3M#ViryPoQA)5;k^eB4$_T_FF#1bokMCSJF5OV1@2Txbvl_#W~WRVN+bd%xRnLKD7hKr~0J%@>^Fz#OH+jAk8 zJk*m!|GuY{f@vDl5(t>(!TtP`HNM!Y{{sY(80mgbc0*=mX`@YddD;xY6hPa9C;!;# z#ykv6(>-R~sYH-eqdr|gALh-Mm9pj7ginFng|6JYgnRFli_5UaEH;y;Frd)p@KFA0 zKUeIeX?xIod$LWaijJs|L~r$(qtGF_WwO*mIf;4AOgdGA6KUgPXZW8njYgt~xJuJK zCbeIT^d}`C2_>oFA|WzPY)pHU85_MlIu@InpqyYJ{@2egR~6X?R_AaDc&(k7b2}Ep z)K3Hjc}1Q8qZ`*E(geP6aeC|k4az%`wS+v+%c{mBF|2XHe51t$@%#8k#(f6FZw>?&C@bR?B%i-hY|j75&>D5k zLP3}+CgMcmM$YcntpvQDeN%?V=*ebH$+AH0imufqJDV(pq7*Cw?k zQ>xmCxMFqf$n?_!(elt|-^o&y9+JaX4TSX$YnsCQrFgWGAH+I0u_fSMWM(=sUZ{d&JTPQ z9#w)ur~LQy!gmyej`Qc=<~nr|4TnY;(FIf~#&-uamH4q?7>^CB7(OBcI>~Q9QpX>p z31swaJ8;r{6q>d;GOX8aEp6)gi~EeKa{LCBDzUfDwZb#jz5D z)lcGx=ZRjl9m}9I^^|F5DnHi=SRi+QXpVBlhsKXqpQ8xAiIfh}j?o6Y0NE@GZ%Q6; z63``EmVSPfX^uHB7a21M!%I<7i#*BBL}yXo%^q90J(s8u9IwS1-zfFg{UsFB+p9zy zKId284gEMN)Vl6u>X4uaZqw$URC$FLO{D^&HsHW1wAN4BR$wxdEKG8)df<;sX4K?6 zF^_H|Y+BIyd|w@LCrtgEC6#CXrb)19)ud=Ap8&Xa$7JlOztznv0>!lgPn0>1u;jqK z$bHIf+QqVYaH)49b%ExW$2u~sw&OwDWou(-<}fBWr>Y=k#q_>E7G7@PT&Cq}!cKNM zXYJ?Y0n=p1Oq<#7U4_rUor*#VOgb0`s3k4D#MJ_Q^R-lz<7nTR0F~jL=A3qD zx}sWU-jKm|b(o{lIOzV&@O9F#oDIbMhZ~1wLceV%w^l9N-~i?bYjwjWZbJok`JG33 zcE)H9-)@9X4-}ff?tl;(&YiaGrYQ~Kp2ZguQgmaqJlqSgGb3pB=_0ii9b z*Ozi`d!BH7H0NK-PRoux3F|ds_ltB<%ryyt)6+v;_0=?^NC+(_JtDwco{2ym zfI2$M?7iOfDsPgO8#}%=ir^&* zC6_YlL%JDQ?cO>rYOv)ihKJr`gZxVr{Cczg%gf4qTY02d!{4H_mgug%U&7YQJrg!qhvWP2^DlRu!$l4^vXfsT`N2~J z3FUbFE8{(iaWKS<#>wLrRozAlXgXh*8Lv{z$coCA!(g!dy+l+uN|EE;u28$(TtZ00F4`%nz=)wS>MSH zMd8_1Jr`~ou`Zn2E=PPpIOV-z(lsuMSmD1zjQZ*fIP)TB^d#yqi66FFzEp`+O39fA zn}+{i0dX#l(Iaxnj=9HQ!mh`xGA;zH1wM2>WMzF)9h)aa4|lbspm+TzADQv8K+R8=HHge5n=4>w{AewZ~gS z_I7h^9@CykIOd*azm9PJaXe#hS~-Wzb#DU5xGD(xS2^Mj2-@n_t^;{e^^M2yw>bWS zq)l$_Glo<+^%Xb#Bv8l#yd;ow(>&8sLb^PRNi~JTtm;+cZ!O1MAMnjex1M7^I3VLA zp&dW3>05q0wuWhA)om1oakk|G`|#g}f6gnDSyf|IPp{JktCsqj#%D!ueHuzrkPvtE z{{T7s>hZV@AyM0@^!BcD3$&6}kc_~0;%ga~S%xxt z4)lgH!x+wKmh3hIA;HevX0DJ?P8U3JP;R7i_qL46$b-K;RTwN8WhCbXaniaMkhbHF zM{0v`bN6s6(_^ySfnW-Av|^`5C^!`(CfLT_ed;LDOAyVHJ5Wnxo^xQH!lIU4qmmAI zs?*#$NMls#mIKsP87`%n30wi(X9QI!vl&d$TclXX<24z(VB~JdJb{iYH7?z-jta8p z<_4pd;#b5G9nT>E0otuZj&k*+FMoZwvn*&q>5xyS@~+xDq=Gz1>ZL%=SPUFi2AyiK zA|UEHJooQh-;VsteMw}FRvY3+E1nNg{{XW80Gw8o=_<12Y{I?c8=jLD%y%3|EOKL# z6~Q8=X^;cBZEk}->&CR-63uR?o3@2K1>(CcC&bME0PD8Og}DU(04myfc9*8kC00r5 zb)=*8eTS3Qn9SRke2Tch=e=v&>H18%SkzWYM0u3PAs^^$z115Gvu-PxbA5Z+bj%#1;$S~6}~(!#r|vAG3kyd{{Vzl!yH^39y(O} zHeVw#*4`98YOo}91GP8G40&C^cFkxvglC2KM|GV4014?&{{Vz?`Z(~ej$5fGr7lJs ztj+S^Kmf)_>c^UjK+MQeKo>m#&TB=yF?iuis8TztM%0LCEHgMjWipHu7V0 za&j|{^syMDa#|2M^ckz|q3S8hhUwFTii7(LR4u(#i6oL34rt|J%Eqh4$(Z6lF~QA1 zj=o%sfNkh=)~Az0)Sr5^GZpKRfzP0*A<=b+bBkFUCz4oxl=)b!!7(WFHjHDADzwUm zGNMi!i~(09(Y0Nmd2$T!a6iJG29bLdTUr-lPV66AT(S!@3R@eKa3qdWW5xoo=O0?L zC8fwf3n>LgbH_ELY;RgZe$6stjItb5e`LNwH(N~DINS#mSavdAJF)Ucs-Zb2)}Iun zHQPH$zdypc$P>E9jcBEJRj9F?dQGN7?w z&D*!-^r3!USa3!;?@GX5lTajJA>iYsA-LZknS}s#tf?-ZBjz#`Q^3Vq6J{k)gee=d z>S@`H8i{ZkEy~zH25N7#93uxGg<-c6Y~ECp%8yEr%HQh7-Urr%nC@=>0Bc-EFp{w= z*PgWLd_#IrtTN%fO61lHNhm_W=|rABP4&cDw1j?TP3>W9^BP=H3X50 zJL~}U&su6FI*8YMi6V+H2qLRJ-5i1ktCVblF`k&JQce56y`OW|wTw=ANVsoZ z6?VHf827E+BF%{x6Co0}Jf1~m=+-YJqG?p)A1-*uy>+&cZ2$qyMM*7@wJ1M{nilE; zf!?vaX{XJ3VHM;t@B27mC>))K9OKXt`qt!l01SOA@RpyeDVoqnYjxZKgnz(vKH|Ck8^S``@Xx9u;vzOh zxDe;mvqP~WB7qyMDVp@?PIz%pw{L|zt+p2q zp5;w#rm1f0o!d{L6s@Y-f&R2+u#{YPHBS38lbc-EOAwZT1JmR^zO=Tw?ynSo=@19T zdB!tcSN5f$O~zycIO|YJtXY6Iy0JdhKQPz1%uC{n^kT-{dShVi?@qJvP3dUO#Bnl_ zk`QxUJbI>|IUqEA91urJW9k}Ka7-ls0K1w4Ugl(eEw*-W(3LCKnt%KyR<5~77~t}9 zD&ySf?m#iyocf^Vpm)*B1ZZu*$r)7`A6hONk*A4m-as1IsOKkeH1_c&i0X_wb;U;{ zx1p%nf_(^p9a;!R2AH_BbM#U|W`Bj0YXn-*CVLN#} z@ly>KNiKiUV%^a3Mxm)hc%IBicPR>b6N;k#Ft^@zM1%v5TDD(C(cIy!pc(6v{HjQbt1j|bjN!4<&{I|p zV%(^rGK_d{yT^J&S>=d;LasjTJ}FGB1d7Dxm8puPQ~azjho^B=GLqWeJntc3M`4_C zPLdf6hW=ieM7?N(7k)Z%9hPHB=Q0Dwk$C%t0H zJ=znpGFK-UInF8ZiS7`yfTV&-p41BFi{HQ_*ty<1;C7~@_OFaN5^;b9M!HaC+r5+( z%llp6>Hhhs4#3xIpkVU~n2%_9a}05YdOl^M5Ct&&Rd#a8ClW>FLE$jM{KtMMh| z>SDBrN~{K0@E6ybYAw4VXdYh0MTF$HVh>7mPy*l;BOsHW0mW#;V6X&xn~}XoNdExF zm9H$|7+8a2jJGw_3>8Uv5bDJhVzE1a)tomJHlF@SVHn&8M&g-qu1En`lrPe=Wz;Su zIcFxip_X6bW^TIZjWkw?SzM+_{_x_N1K^Nk8OI=V#bj@qL+w+?x*KU-xp&}u3fX0} zqVCxq-b`iu!Q~_zf;a-XPY~{XK1`9*I2F4-)2_C2ZS$0UaLGxy@UiTRziix!q87$bxze*YdfBQvL;1g1=vQTy4 zdRGs7;#eVVtC<{eh4rZX`}??rnOH+~9Zf?~dJmPJm1*K6EEU_2TDvXCY&v|(MnNXL z=F-th{PRi}e;9A+U3QY}Ej*>TSr|9nKQ9>fuRkA$Qin2HqiR#Lwaqr3U|a?T0**TM zs!b&Aag{$WUMcgbg|JHiMg}y2BZrskiR1x!#wpP`cvb)b_6Pr#~@(jiXys| zqicZ*?Ih%$-ol?8K*Z84WCaA{kWC;+jc?u=L}8s|CpkUp`rq74c)4JE^VX=Xl$Q;J z1(=M}V}*;zukwV(2hyF`?#oYoH<~|uLuZpjx}DrFut<7j^`@YJlFYw!{oHq?l1q6d zW^L+8>9(E0wsUS-HwE5f>T%!SHB?Iq?>iVbVmno-?B#DYhWux0k5Ntj&z95|kOpQQ zJw35Oor?2f>NO~=vtyU+DI&Ib*KXz~aU|z8ul8ACTx`O!jDLk8h+t$Dd>za>3S%j5 z@)yr$0Pfq9--?YciOiA$2_rvB(tBtS0NZ-xU}u_Pf+-ZR+Q59HH0~ms-lXzGjexxf z$4ap5S`Dx|ou?x`RClObOMzyUz-9S*ij^#6bl7pi=RG@8<_ocwzH=)ihiVQ(d(^ST zDPRhanfu-r$PPgrtDU$2=ciiBc&(*fvP8K92P!$Pfz@VQsm*3xS~2K^=Deir zdNxeNmrk_}m5F}}jA`&={Zi!Cx7m&W3Ug9LrrHiz=iR^c3L< zP1-8qDaI=7y*0uh>wqgF<5LdB6DQuX<-3yMzIHo(1t9r({43bQ<;p(wO?;0wwlT{{ z7G2rMC9-G_o;_*+{{SyuDMORaYt%_4WO?o1R!*;m0MzRuj1oUyhN=0g5BOAa+@xJw zB>w;i=QPD@nqF`RlaBprwEC2AFDx-x#NSr zDqE39b9s8$i3%t8pZqEK9&gzcU_# zuoW_W7Uf63LHcH#C7DcvD7=&EYo%4%LSSJ>YKqzvw~GTg>T}%G;E~e==QQC36ilVe z#PeN#?@xNM1ndS?$UXh2irL)f7*aEn_|}A0_Hg{tzVqlf6-}ZCTWeSF{GMQSKf>cR zt8b~xaTyZXk_i6*mU$oJS;tVefR839$4ne&if{JR`T1qz(>N8)R}W66@hUTvc4tup z7P4D~Xy=W9;fc--OC`Qz`6NQv!96`aYn4me$w=BAiS(@)VboY;$REzQXO>fY zI~y!Sx|>m23`o0*40ZniKaCOv58? zJ8tWWz_zYp!+je>yB976OD)V%LhR~y?c*HQda*KvRRG|TgvD2xqB5%!B8DYSJAyG+ zVs2O$Vo(Q|T0+>sJt@=Ou>H^(W#U0qB$YQ4ki>SXa?GU!rvkCs(m0)#!~xP=JxI01CyP_Q4E$ichrNYd|Y z@@h!9<+Fj)nyRO!YF*)b{!wl4K<<#ays>>19MV|M&av4tON}n0ndKa&`P%) z0ap&z6<%1zb57$L^5^)vb*Pq3O7XS0=8>0@N9#<+c<<{)fQIcPS%ioSBMq*<*0HXj zmda+hg-$<-J!^1WY$tysJk)cT?h7{@&iEO3{~Bvbbq2wg+Tkk^vdWK9!W}MlD8NOG)mIM#oTVQ{_l=AMlgg+P9;X z9Y#7DEW?IW@kg?r%(oI>k!YqRfgs{ zMnezIpEc|@>$tbzE6Y%A+Q)x7+_*xb2b+Yt_TTlw)dVpE{G9_lb@Wf;v&=4_bgUvknvG z_o)`xuQECOE2PbhF21>LK?9nR65-DgkUdQX;ur_YqcvVlT;T0eO%6$JD>t!mEeSt7 zq!L5;u~hJ8h1jF5hqu60e! zha0o)iqp2!rI%rs?ha|G^K69TmgG@eGpa8IUtR{gD{cI&hNPpGAV77>K$^r==k zWaU(!>s1)oiAwqsAV3KubmttJktU8X3V=QO(k-iFFDfA%p5~-TZKD9T={=7EsF6J9 zm9T-Fp51C?vbmQTLWBLYxB1eG@fh0@qAy>RVyj7f{mIX$?@Lh{QE8AWe*WltgcpOZC#N);Z*iS8y4$G4Y!P~4K&p?a$l2Wcf%oZH zf>z*f{OSC&4CGPBfYCP(AmskE2^%;JPxBR76yy$=?rO?|?~(jir(qK!lb%Yj^{WX9 zBz)uZ#WEBm0!I~k7?2Uc&#g;#5k0M+4gdrx9P6wtcf)b#PIr?#-cEQP}1d z+}qqJ6EaEwKD}wza9SO*yp85(1~wdi6;-tdfrjieIQeqFbk!T{$xcd;;2v4H72sUy zG_G|iuVIQkfU4(d?t6bqh}=!OCshS{hCPi`UombFN70jT^aS-EofE@!;UI1wIKbe3 zbTM6%&n?B0#>eJa0RgkrQ3dYsRFJ`kCp^^hY0V{yZzF#1uNeOT8ijPo;k67w-2M6L zDOdquWL0%itiy2*bHS-i{MLGG_h&g}^5ReLA79S3w7oGM1hO-TRDTiW{f%O4H)88s zw1EH!vVy7x01L?eRq9|cobu34b3J%f7T5y!_ z>TyvsU|{}~#&f$roiHw}huak3;DekBq#d!v23#C^WXvB#35vk+Vg?it_smxvzzQb+Z30(bn{8KRtBEv`pWdVG_Zh`AWwD&NRXh5hdXHfRE@-Vw_L6GNndWJw zJSP?E-v%^=xVY3cSz;NFY!R^?&n=Jqck{13k)TD&rboA0_pgPvvBltPNs$0)-br8F z?koJ1S2i`cQuPSCvaiJbNi6&^Et0<3Z+~*6c31&o$L)GIT~)&mrhdNG*vuKb36@6bs89ohn7Ph;XVfDf>R*&SRyUKip0|O9ShEw>o*9VikyTV0xO>Om?I>)w^XRbsfnIL%v$i32=w z(=|{wcu|j`sUBU5dHU2nK)EHzjBs*&PdKU<7i_s9vMMKX$FI=hvu>v)wyDNFJ!z=e zMU#9p$Lrdq7fe7nKZRz+9(taAOR}ZTtmEB{ zpaI5xO-%Oo?-Mi%#HZdlsjcjmYgr74s{UqBo<=L|WTbFF4<7*+^u<$^xj@roGpptR2 z3}si4N&0pb7N1~{%5Lvd=OYM&4ac6QtZF*C+o@mqXm|j2uS*Mpa`&cTQny4!sofZ2 z@}dDi86b8Yg>f*qov-iCL`cW4azOt88r8kLX(DpX%XjsxjWFiw?o*H9AJ6=2+@}>4 zWOMJSrz?&8(@)LPqu|I`nwdwo0IMWn!RH)$QWMAcQ<(iRP3&<%4nQ-<98kmG6p5bw zXN; zYGF*}i09gXAz*RVkdurPpQTN(3}%yQ&IUM>f#%Ao&*%RDvYO_;9Y#-#t=qE4CV%geD`VpFm$$Kq{nnA&KHzcu&3SdT#l;)L zN^PDx5-AQ!fzN7qwFB=y6`6BXUnBb>$^f^{09F2r$SPRaW!V z_st${!kiFsNsS`UnHyj<03M|5ucJH-8mEEuy`Usi3GPy#5{E(53I((r209xlp0o&yT!Ku7o zsXO??Rob}Qbm{*9e4Kwu#}^Yh&pg+C1eG+5q@B$nxtJ1EdkSgVxcSc>^+*|8lGNpj zLygtB7AzTKP6jxr%%?nhQmgPPChjX$W2!vC^It1?8$O$36c5DhuRb4rm!6AbZ9kjPcHDs+^PW+NV~) z}ESa$qs)c0vT z;Y@pDQG^4z=~{6lGVg^T8-T{(F@f%BRy{t%7nlj9Jx6N!N?3Z8eiU}fYCDi;T zRkdVj&8Zj^$mE$A@$FpQ#q+%7+>+VNCFS{YMrxp7!*Lm}N-;>tn9t^e&mx(oFUxSl zytw@b_|QtI01mWioLkJ<bg7B?Is9t6VVftVPJZVZr1ucQ!=@+$ zJX23!dQij+W|$8Er4mk$mX0_kU%_isJG)d9qB_7 z2g}#BA)VL58*?X!=9rWFt8N7K+bx7Yt}LtV$URV zO!D%2bmpw?UAY`man_54%IgT-!ixG!!|J2L+F)Gm`y5!$Tx=a{=VZb4ucI_SFa8nD zKgr0C?6K_x=L2BJJ?qKMHi@+@+2DRLvsk=cspWWS+CXvYb6D#T7{K6HN8<($;@w2& zIF}zof&LYY6?4;!*P}=HYtYJ1QYBsn2jXeiZ1a!8rNCSh(wa)1m8lzuBLf+r1CG5r zQf_Q>(9=tHAkzWN{PyeA)NJRB^c2$M`- zrPKafBhZs0e>y_S17!aI_33PIY*6vbH&6GC0xrNZdUUHU*cB8VGDjYi=Q%ufrPPu# zPf!Oms!nsC!jN_5o}g~ywJ;ZudsB`(^WKb(x%sIXuyfzIpe`_y-0u7-3Y_|TQ-%pG z`cg3lfsG(>>+EP`9evfpd8|)&&o!5uUy1t3C|o-ay@?vc^qI?0NpRmWQ=^G=GM@4CJ*YFOF%fcXQK>(02Zm#^a3EgxF2P<{a}#+e>kp zU}Ir`I-WaN6(fk6~`XVaWe0)*go>y9bjSQsEv!i;;M(iZ6olskJc=0f82Zoyv(J8%yo_g>Ko>aAT6eGDXaR%**u^N$Fn^r{jQjMaF&JQNEBmj|-O%Z}~ zkJ6Z3mEwe`Cj%y%Gi*>Q+fi}`Pu7!m)sDqnkO(8KG=rXltzEgYxU#u>dzhXXld8Jl zkMXK(er`V#LTT(GGx~~aXTRxA2P}Wak%0hxDU4z0Fe!MXKbJnV?xUuC@jwmR)X}w_ zuooCTszABGI0RMQ9uYphVQ$={w=tpPs4N9c-J;mh?P`gm_+|*4Y)gA^8=jk2pXFaW zT~60`lFs?xBbPZn$*+F#%;FCU-{g_H23|eJE9d20ADcDjWwG~VKIZjzu&TJp&S^pG z=shXJZaeg+DD)Ty-n}yshmrN9&N_cOkf?8`N@-Tb0E7g5r=GR+c8rdm9n-{|I@-n< zd+iyooKZ_S0ZAm_0m&!QzL&OEO*R#8ys-d&xUVZT+9vdRpE>xLgW|nauuuA2qaW}y zRFn*0)vpkrGsN1P4)>Dd{`)mdHqrpEN|(Tl-sG9-$Ky>Y$^M<`LF=5;i*cOuR!Gu- zk%8a6F5WrLDZrnbra$`hZpp#p@TLL<$@zLzoM3eCO;B=3=~8^X0Q%5Pz$o+?pl2hF zezdvBIRp?XP7hySNzo>+eB{j% z`efp+GaL%dxo`B4F_IUx14w(n01`OsiV+FuaZQa&zFVbEMg|8TN(y6?1po}=ulfG~ zKmBV87awj8$?C0bwqu%VVZ0gpM;#o!Fjgq>M?$1*A+LAmC(Hj+@4x zAFea{)?8oe$@Z;VFZl9@{lQF9Erum&2eJPE>(fc#RGq&H2piBG^{FH2$>yUyDoFWi z0C+hk9jaI*k_pymq$?z3GP0ZyMMaV_ed)m94*vjJO zbURs>)qkkz-|5Tvhrze{Gd!>%xw9OJ6lIiuv_G#;rC?jnEHf;zJ4YfMt0}<;>t5TS z-OH$GR<}0<8D1>00~tLtUR7yN2`k0kFQL3rsJzbz3zp3z44& z&Esu0jm&7Ph3r;KtAIi*De^Gd)0^y4%D zjlsw~VxFgp2;&q3+unf=2PXsh(iS-Mz^1mqJ5+>i$oItnFpLrDN(W5z%^3UM+=_5Jy3hnz%V%ll zic#;^INerg6tdvp3zNx;BkLwuxT z^`;j&?bd+`{{SyN@mbP1YnI^tb)e%5yS{N)?0U(87Ce6u~VJfdh$K^s<55R t6!-rC5B|MM+0HZR`p^t?NY5cStHa~~5uc@3wi#UeXY;EJPU;yf|Jjb#9b*6h literal 0 HcmV?d00001 diff --git a/registry/BenraouaneSoufiane/README.md b/registry/BenraouaneSoufiane/README.md new file mode 100644 index 00000000..3067538a --- /dev/null +++ b/registry/BenraouaneSoufiane/README.md @@ -0,0 +1,14 @@ +--- +display_name: "Benraouane Soufiane" +bio: "Full stack developer creating awesome things." +avatar: "./.images/avatar.png" +github: "benraouanesoufiane" +linkedin: "https://www.linkedin.com/in/benraouane-soufiane" # Optional +website: "https://benraouanesoufiane.com" # Optional +support_email: "hello@benraouanesoufiane.com" # Optional +status: "community" +--- + +# Benraouane Soufiane + +Full stack developer creating awesome things. diff --git a/registry/BenraouaneSoufiane/modules/rustdesk/README.md b/registry/BenraouaneSoufiane/modules/rustdesk/README.md new file mode 100644 index 00000000..ae7c2896 --- /dev/null +++ b/registry/BenraouaneSoufiane/modules/rustdesk/README.md @@ -0,0 +1,82 @@ +--- +display_name: RustDesk +description: Run RustDesk in your workspace with virtual display +icon: ../../../../.icons/rustdesk.svg +verified: false +tags: [rustdesk, rdp, vm] +--- + +# RustDesk + +Launches RustDesk within your workspace with a virtual display to provide remote desktop access. The module outputs the RustDesk ID and password needed to connect from external RustDesk clients. + +```tf +module "rustdesk" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/BenraouaneSoufiane/rustdesk/coder" + version = "1.0.0" + agent_id = coder_agent.example.id +} +``` + +## Features + +- Automatically sets up virtual display (Xvfb) +- Downloads and configures RustDesk +- Outputs RustDesk ID and password for easy connection +- Provides external app link to RustDesk web client for browser-based access +- Starts virtual display (Xvfb) with customizable resolution +- Customizable screen resolution and RustDesk version + +## Requirements + +- Coder v2.5 or higher +- Linux workspace with `apt`, `dnf`, or `yum` package manager + +## Examples + +### Custom configuration with specific version + +```tf +module "rustdesk" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/BenraouaneSoufiane/rustdesk/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + rustdesk_password = "mycustompass" + xvfb_resolution = "1920x1080x24" + rustdesk_version = "1.4.1" +} +``` + +### Docker container configuration + +It requires coder' server to be run as root, when using with Docker, add the following to your `docker_container` resource: + +```tf +resource "docker_container" "workspace" { + + # ... other configuration ... + + user = "root" + privileged = true + network_mode = "host" + + ports { + internal = 21115 + external = 21115 + } + ports { + internal = 21116 + external = 21116 + } + ports { + internal = 21118 + external = 21118 + } + ports { + internal = 21119 + external = 21119 + } +} +``` diff --git a/registry/BenraouaneSoufiane/modules/rustdesk/main.tf b/registry/BenraouaneSoufiane/modules/rustdesk/main.tf new file mode 100644 index 00000000..14edcff1 --- /dev/null +++ b/registry/BenraouaneSoufiane/modules/rustdesk/main.tf @@ -0,0 +1,75 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.5" + } + } +} + +variable "log_path" { + type = string + description = "The path to log rustdesk to." + default = "/tmp/rustdesk.log" +} + +variable "agent_id" { + description = "Attach RustDesk setup to this agent" + type = string +} + +variable "order" { + description = "Run order among scripts/apps" + type = number + default = 1 +} + +# Optional knobs passed as env (you can expose these as variables too) +variable "rustdesk_password" { + description = "If empty, the script will generate one" + type = string + default = "" + sensitive = true +} + +variable "xvfb_resolution" { + description = "Xvfb screen size/depth" + type = string + default = "1024x768x16" +} + +variable "rustdesk_version" { + description = "RustDesk version to install (use 'latest' for most recent release)" + type = string + default = "latest" +} + +resource "coder_script" "rustdesk" { + agent_id = var.agent_id + display_name = "RustDesk" + run_on_start = true + + # Prepend env as bash exports, then append the script file literally. + script = <<-EOT + # --- module-provided env knobs --- + export RUSTDESK_PASSWORD="${var.rustdesk_password}" + export XVFB_RESOLUTION="${var.xvfb_resolution}" + export RUSTDESK_VERSION="${var.rustdesk_version}" + # --------------------------------- + +${file("${path.module}/run.sh")} + EOT +} + +resource "coder_app" "rustdesk" { + agent_id = var.agent_id + slug = "rustdesk" + display_name = "Rustdesk" + url = "https://rustdesk.com/web" + icon = "/icon/rustdesk.svg" + order = var.order + external = true +} + diff --git a/registry/BenraouaneSoufiane/modules/rustdesk/run.sh b/registry/BenraouaneSoufiane/modules/rustdesk/run.sh new file mode 100644 index 00000000..f6837ce2 --- /dev/null +++ b/registry/BenraouaneSoufiane/modules/rustdesk/run.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash + +BOLD='\033[0;1m' +RESET='\033[0m' + +printf "${BOLD}๐Ÿ–ฅ๏ธ Installing RustDesk Remote Desktop\n${RESET}" + +# ---- configurable knobs (env overrides) ---- +RUSTDESK_VERSION="${RUSTDESK_VERSION:-latest}" +LOG_PATH="${LOG_PATH:-/tmp/rustdesk.log}" + +# ---- fetch latest version if needed ---- +if [ "$RUSTDESK_VERSION" = "latest" ]; then + printf "๐Ÿ” Fetching latest RustDesk version...\n" + RUSTDESK_VERSION=$(curl -s https://api.github.com/repos/rustdesk/rustdesk/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' || echo "1.4.1") + printf "๐Ÿ“Œ Fetched RustDesk version: ${RUSTDESK_VERSION}\n" +else + printf "๐Ÿ“Œ Using specified RustDesk version: ${RUSTDESK_VERSION}\n" +fi +XVFB_RESOLUTION="${XVFB_RESOLUTION:-1024x768x16}" +RUSTDESK_PASSWORD="${RUSTDESK_PASSWORD:-}" + +# ---- detect package manager & arch ---- +ARCH="$(uname -m)" +case "$ARCH" in + x86_64 | amd64) PKG_ARCH="x86_64" ;; + aarch64 | arm64) PKG_ARCH="aarch64" ;; + *) + echo "โŒ Unsupported arch: $ARCH" + exit 1 + ;; +esac + +if command -v apt-get > /dev/null 2>&1; then + PKG_SYS="deb" + PKG_NAME="rustdesk-${RUSTDESK_VERSION}-${PKG_ARCH}.deb" + INSTALL_DEPS='apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y wget libva2 libva-drm2 libva-x11-2 libgstreamer-plugins-base1.0-0 gstreamer1.0-pipewire xfce4 xfce4-goodies xvfb x11-xserver-utils dbus-x11 libegl1 libgl1 libglx0 libglu1-mesa mesa-utils libxrandr2 libxss1 libgtk-3-0t64 libgbm1 libdrm2 libxcomposite1 libxdamage1 libxfixes3' + INSTALL_CMD="apt-get install -y ./${PKG_NAME}" + CLEAN_CMD="rm -f \"${PKG_NAME}\"" +elif command -v dnf > /dev/null 2>&1; then + PKG_SYS="rpm" + PKG_NAME="rustdesk-${RUSTDESK_VERSION}-${PKG_ARCH}.rpm" + INSTALL_DEPS='dnf install -y wget libva libva-intel-driver gstreamer1-plugins-base pipewire xfce4-session xfce4-panel xorg-x11-server-Xvfb xorg-x11-xauth dbus-x11 mesa-libEGL mesa-libGL mesa-libGLU mesa-dri-drivers libXrandr libXScrnSaver gtk3 mesa-libgbm libdrm libXcomposite libXdamage libXfixes' + INSTALL_CMD="dnf install -y ./${PKG_NAME}" + CLEAN_CMD="rm -f \"${PKG_NAME}\"" +elif command -v yum > /dev/null 2>&1; then + PKG_SYS="rpm" + PKG_NAME="rustdesk-${RUSTDESK_VERSION}-${PKG_ARCH}.rpm" + INSTALL_DEPS='yum install -y wget libva libva-intel-driver gstreamer1-plugins-base pipewire xfce4-session xfce4-panel xorg-x11-server-Xvfb xorg-x11-xauth dbus-x11 mesa-libEGL mesa-libGL mesa-libGLU mesa-dri-drivers libXrandr libXScrnSaver gtk3 mesa-libgbm libdrm libXcomposite libXdamage libXfixes' + INSTALL_CMD="yum install -y ./${PKG_NAME}" + CLEAN_CMD="rm -f \"${PKG_NAME}\"" +else + echo "โŒ Unsupported distro: need apt, dnf, or yum." + exit 1 +fi + +# ---- install rustdesk if missing ---- +if ! command -v rustdesk > /dev/null 2>&1; then + printf "๐Ÿ“ฆ Installing dependencies...\n" + sudo bash -c "$INSTALL_DEPS" 2>&1 | tee -a "${LOG_PATH}" + + printf "โฌ‡๏ธ Downloading RustDesk ${RUSTDESK_VERSION} (${PKG_SYS}, ${PKG_ARCH})...\n" + URL="https://github.com/rustdesk/rustdesk/releases/download/${RUSTDESK_VERSION}/${PKG_NAME}" + wget -q "$URL" 2>&1 | tee -a "${LOG_PATH}" + + printf "๐Ÿ”ง Installing RustDesk...\n" + sudo bash -c "$INSTALL_CMD" 2>&1 | tee -a "${LOG_PATH}" + + printf "๐Ÿงน Cleaning up...\n" + bash -c "$CLEAN_CMD" 2>&1 | tee -a "${LOG_PATH}" +else + printf "โœ… RustDesk already installed\n" +fi + +# ---- start virtual display ---- +echo "Starting Xvfb with resolution ${XVFB_RESOLUTION}โ€ฆ" +Xvfb :99 -screen 0 "${XVFB_RESOLUTION}" >> "${LOG_PATH}" 2>&1 & +export DISPLAY=:99 + +# Wait for X to be ready +for i in {1..10}; do + if xdpyinfo -display :99 > /dev/null 2>&1; then + echo "X display is ready" + break + fi + sleep 1 +done + +# ---- create (or accept) password and start rustdesk ---- +if [[ -z "${RUSTDESK_PASSWORD}" ]]; then + RUSTDESK_PASSWORD="$(tr -dc 'a-zA-Z0-9@' < /dev/urandom | head -c 10)@97" +fi + +echo "Starting XFCE desktop environment..." +xfce4-session >> "${LOG_PATH}" 2>&1 & + +echo "Waiting for xfce4-session to initialize..." +sleep 5 + +printf "๐Ÿ” Setting RustDesk password and starting service...\n" +rustdesk >> "${LOG_PATH}" 2>&1 & +sleep 2 + +rustdesk --password "${RUSTDESK_PASSWORD}" >> "${LOG_PATH}" 2>&1 & +sleep 3 + +RID="$(rustdesk --get-id 2> /dev/null || echo 'ID_PENDING')" + +printf "๐Ÿฅณ RustDesk setup complete!\n\n" +printf "${BOLD}๐Ÿ“‹ Connection Details:${RESET}\n" +printf " RustDesk ID: ${RID}\n" +printf " RustDesk Password: ${RUSTDESK_PASSWORD}\n" +printf " Display: ${DISPLAY} (${XVFB_RESOLUTION})\n" +printf "\n๐Ÿ“ Logs available at: ${LOG_PATH}\n\n" + +echo "Setup script completed successfully. All services running in background." +exit 0