7X>qxwqNa@eU#c_@VWl7us3%>H z4M_5?6TEFtZ>_i9U*br#PjaAo$7 z>gefw_})dT(?=Bu?`9tavyl_6!ndT6TWig7Lv7}pDxP-AvOD Q@^&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_1aFh 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@Y87SCVs J~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$n gUI0nu zIev{_!hfvaOkiiDhIM^n`(uQwYy!^DZyw3 DZZ@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!Ip oOQXXWaNMKuB 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;N 2-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?05cN91s7L mh5(~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%pN m y izX5Xo3u_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^z13lFa403TE yImMt(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&`C 7F)c6n;GMqPLnirp!D*{gry4{(4Jz<1-ejzN& zy2_7vEctWzm6olJz>3|wxK4`kWytuv^KsU8te@&CZoz!5 zC<7(866^@(rBhdqA|5;^p8c%?l 2@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*65h 8M1v$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@&uy4ed3CUd6hi rHoE4j&sNK+ywwdA2OgGF|G)izedKK~L+JSJ7g0vmTdS8~4Ma=87*dCiA~7Mz^! z6?xyM$dH|@G8Qep>hH?W)3lO8SG1SE5@iFbNK?9daKX28duV{o*+f^I lhT!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~!Wy7c uw$w+Jj_&>}VosDbF{FYQ2J4gz zi-8i1cd&M$1r7%vXMuJSz+*_KxgTowa89Cm!E@1`Mk)6$00yYvrjefO-+gT;m2N8$ zagR ;pNn{3zh zzJVxb-)#{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&u6 XYiOeB{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)<`EyT 288 ghq8l(>)g 7*<_@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?g ehLMQk#eGosA5oZth2yuav2i#Ej?sK;`u_j^ & zDxv(0|JNz_Mhi4wk(fo7lj3M#ViryPoQA)5;k^eB4$_T_FF#1bokMCSJ F5OV1@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#(f6F Zw> ?& C@bR?B%i-hY|j75&>D5k zLP3}+CgMcmM$YcntpvQ DeN%?V=*ebH$+A H0imufqJDV(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{LCBDz UfDwZb#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*#VOg b0`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-PRou x3F|ds_ltB<%ryyt)6+v;_0=?^NC+(_JtDwco{2ym zfI2$M?7iOfDsPgO8#}%=i r^&* zC6_YlL%JDQ?cO>rYOv)ihKJr`gZxVr{ Cczg%gf4qTY02d!{4H_mgug%U&7YQJrg!qhvWP2^DlRu!$l4^vXfsT`N2~J z3FUbFE8{(iaW KS<#>wLrRozAlXgXh*8Lv{z$co CA!(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>WM zF)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%SkzWYM0
u3PAs^^$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?iuis8Tzt M%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!l01SO A@RpyeDVoqnYjxZKgnz(vKH|Ck8^S``@Xx9u;vzOh zxDe;m vqP~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;{ zx 1p%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 zPLdf6h W=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$0U aLGxy@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&W3Ug9Lr rHiz=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#NS r zDqE39b9s8$i3%t8pZqE K9&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*40ZniKaC Ov58? 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#7D EW?IW@kg?r%(oI>k!YqRfgs{ zMnezIpEc|@>$tbzE6Y%A+Q)x7+_*xb 2b+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?2Ucg;#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|cobu 34b3J%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+~*6c3 1&o$L)GIT~)&mrhdNG*vuKb36@6bs89ohn7Ph;XVfDf>R*&SRyUKip0|O 9ShEw>o*9VikyT V0xO>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(taAO R}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 zX N; zYGF*}i09gXAz*RVkdurPpQTN(3}%yQ&I UM>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`U si3GPy#5{E(53I((r209xlp0o&yT!Ku7o zsXO??Rob}Qbm{*9e4Kwu#}^Yh&pg+C1eG+5q@B$nxtJ1EdkSgVxcSc>^+*|8lGNpj zLygtB7AzTKP6jxr%%?nhQmg PPChjX$W2!vC^It1?8$O$36c5DhuRb4rm!6Ab Z9kjPcHDs+^PW+NV~) z }ESa$qs)c0vT z;Y@pDQG^4z=~{6lGVg^T8-T{(F@f%BRy{t%7nlj9Jx6N!N?3Z8eiU}fYCD i;T zRkdVj&8Zj^$mE$A@$FpQ#q+%7+>+VNCFS{YMrxp7!*Lm}N-;>tn9t^e&mx(oFUxSl zytw@b_|QtI01mWioLkJ< bg7B?Is9t6VVftVPJZVZr1ucQ!=@+$ zJX23!dQij+W|$8E r4mk$mX0_kU%_isJG)d9qB_7 z2g}#BA)VL58*?X!=9rWFt8 N7K+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`9e vfpd8|)&&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?XP7hySN zo>+eB{j% z`efp+GaL%dxo`B4F_IUx14w(n01`OsiV+FuaZQa&zFVbEMg|8TN(y6?1po}=ulfG~ zKmBV87aw j8$?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&E su0jm&7Ph3r;KtAIi*De^Gd)0^y4%D zjlsw~VxFgp2;&q3+unf=2PXsh(iS-Mz^1 mqJ5+>i$oItnFpLrDN(W5z%^3UM+=_5Jy3hnz%V%ll zic#;^IN erg6tdvp3zNx;BkLwuxT z^`;j&?bd+`{{SyN@mbP1YnI^tb)e%5yS{N)?0 U(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