From 82635c895c7730181fc24012d6cf770c4a9386c4 Mon Sep 17 00:00:00 2001 From: Seungmin Kim <8457324+ehfd@users.noreply.github.com> Date: Sun, 27 Sep 2026 21:24:09 +0900 Subject: [PATCH] feat(selkies-project/selkies): add the Selkies desktop module Stream the workspace's desktop to the browser with Selkies behind a coder_app, with the KasmVNC module's variables so a template swaps one for the other. The install script installs what the workspace lacks of Selkies and Xvfb, with PulseAudio where no sound server is installed, from the release's native package and the distribution, as root or with passwordless sudo, or nothing with install_selkies = false; the start script starts selkies-session on the loopback addresses, without a login or TLS of its own, and waits until it answers. --- .icons/selkies.svg | 12 ++ registry/selkies-project/.images/avatar.svg | 12 ++ .../.images/selkies-desktop.png | Bin 0 -> 92190 bytes registry/selkies-project/README.md | 12 ++ .../selkies-project/modules/selkies/README.md | 88 ++++++++ .../modules/selkies/main.test.ts | 168 +++++++++++++++ .../selkies-project/modules/selkies/main.tf | 149 +++++++++++++ .../modules/selkies/main.tftest.hcl | 197 ++++++++++++++++++ .../modules/selkies/scripts/install.sh.tftpl | 99 +++++++++ .../modules/selkies/scripts/start.sh.tftpl | 46 ++++ .../selkies/testdata/selkies-session-mock.sh | 17 ++ 11 files changed, 800 insertions(+) create mode 100644 .icons/selkies.svg create mode 100644 registry/selkies-project/.images/avatar.svg create mode 100644 registry/selkies-project/.images/selkies-desktop.png create mode 100644 registry/selkies-project/README.md create mode 100644 registry/selkies-project/modules/selkies/README.md create mode 100644 registry/selkies-project/modules/selkies/main.test.ts create mode 100644 registry/selkies-project/modules/selkies/main.tf create mode 100644 registry/selkies-project/modules/selkies/main.tftest.hcl create mode 100644 registry/selkies-project/modules/selkies/scripts/install.sh.tftpl create mode 100644 registry/selkies-project/modules/selkies/scripts/start.sh.tftpl create mode 100755 registry/selkies-project/modules/selkies/testdata/selkies-session-mock.sh diff --git a/.icons/selkies.svg b/.icons/selkies.svg new file mode 100644 index 000000000..9bf3875f1 --- /dev/null +++ b/.icons/selkies.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/registry/selkies-project/.images/avatar.svg b/registry/selkies-project/.images/avatar.svg new file mode 100644 index 000000000..9bf3875f1 --- /dev/null +++ b/registry/selkies-project/.images/avatar.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/registry/selkies-project/.images/selkies-desktop.png b/registry/selkies-project/.images/selkies-desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..974afb10ce0656b2d649bb750c2be42d341b9ea1 GIT binary patch literal 92190 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

n%{ zzJrPi)?DSUHt*f1MVVm(ua+nPu(TGLdR#^rUIyn(h0?ofEga`uWzk1&Ze5$eZ zOEA8Qd+$Bu#MAfa25RpbdKDL3fgbx1p`Z@O#~<$VZfxnbJJot_j_ry6}^L++qvwuS>r78G{sX*Z90HPd*MN9~|HH z<3r9=r_3(*seLBft~Nnb(QK3#Qq)w`#u7 z_mbHhuYjZmCWb4;I`OU{BQ}_(YN7oA(GM6DR-w=nIJcWT|g)0>O06x%l7>I z!M7w)mQG@dLXw4%Kg~Fb*di`sY%f(bdd}@%1WRki1lUo{Oiu%GHw*3ypwsicfsS(l zIL&{#EAr!4F=M;%w}ntd5!)@|KZ`KGW*E%Q&b~;KPh>%9WGjlnWOzQr51cuW!#?=>0#|B23Q)7K)e@rpi*FbZ`oEQW>a0rEbwWCQd~uJZ44 zoU{!-;U56Yo^xx8>ec`S49^R&Vh5zgYGf>CUU|i$IpH|YfF20MgrX8T#^E#BR9<3s z#vOc?Z`+>?AIHzbP*#Mx+FTi_oQ{a8phW03QoTL;^3W@6BVjt^Z$sDcBOFqnPo*l= zxOnJy~*;SX` zj6LaoMCHSIx7Y#_7Fty%a)99~?uSv()#@i>Pl0`8jV-X3K_$N&$Z7}*bYh_=Vipy+ zii_r}m(3ys1^7cifFd;kvBhl%V@%kixHa9NES{k)c5N^3DX3+W zExR-0-v9}b2UJ(nzcq3m$1R}B3kokm0|ZKHDDm@gAuF?-c${5Q9YEI0r()-zv1LGD z521posS$d_vE8~co6J$!NrfuF?PLI@gX*&Sd~#bS7wbi81DJj#C|)9;=_ zw{gJNZta;nVO|d;rM>8$G6vX_a}R%oZJ?m;#%w&4#dF#kkLvI9_2#MJ{uU!_DhJxub$2K;74-rIn# zjvjSz;WUAH&RmYxcTjt)o-0kvFo?bp|B^N-9+S(s3e5`LxDAK{&PS3NQ4#xRFH2CuZh&?m$b3nA!v7 z`q~Lv;z71Kf}Rx_8N0IEqjPI#pB}yctK7Kiv6c~FwgmmaXTM7i8{jf++-cl&?o^B& zFiW|)`sbyr8>^>O?MUCa#mUJa`zyniGKars+&GV}rpe4>W322oGzQG6lxE+#h6Pds zZ$S>_kuID+(n#XqTV^OcOl;kC6TZkn=gcczzf-?lHCQ+xZZN>8S`j!hSEBe>{yPMfM}AX;=3 z7Zxw_)vfpQOZ+_lH#MPhX~Gvb5HN*dP(?Mj^OIU??&=)!+?&`@a5%R)Y^u?7-hH~= zh`~OI!-j-}G*U^ghb-528$W`mL{I2#I+f<$Y0nB2cuc1wlFPxBH}>Wxu!TJ zUNw1bjaS45<_Aisve1VsZe_1SfV)E`{Bl+?;NpQ1O?M~>`^LK|m|gmaWcuZCj2vm{ zN3rpK%S4fR&Hnp8F2Dqgl!pz686Z)4yMD8cNOmggW{c3VoAQ^rMD(XTE}oS0)$ z5ehzF#{tudXqkeP8yt&Z4JqNLFX36xW(5-%NbM~=Ru80Bdn^_F9UVopdV$y(s9mqC zP5s9%OSpx(Lo$V-Gxo!BkZyeINS}Vk7AO)}L_UE(+8Zrz1aoXJRg<)l#4CL2aqobE zb~q1YC}1tuAj}}?9ZAFe<#u7u`FNag4+K~a)wHxoJZR&NXiInR=S0EJ_2>q@5Ovt- zvD?;5Ez_g~pE;(w=}O@HvaHJ#l)h)Z4h;#(Vqshs?@`>@3^@DP)TXbuOt|&ya{-oo zmq_qJHIqtGuK&P~%uLD?iQR3Mh%NOXJte^$E!y8x;AHhhyFb*-)>>X38K*-v0}*{` zS6&;%&k)bgi=jwqYvEnmCHuw90t?Ijf`f34C-dqBR45_*D;tT`x+>OAD-QZIa(0S+ z_C6eC-(Wu!cgv9y()&Nf#5dx!clIOf^wqZc2L%s?OfjC;k^Xw6XXhyIxa)MLiIn&J zcmG{Zp5(UW>DFIk6aP*SR@Leda9FLnOB@nzf0#T2g>Uf<0tq?6K1}G_1RJrfW4(q@ zp4#EPeQR&b=IYTy+-eLJ?Um&#nl$%lmKP~X851sOV{Eb#JRZpkb~84JXPB?6iq(!O z=8rd^NZp?1@9E7DtRD6UX=|EZO5h5(&`$7u2Ko@IzJuG;urNf4ASw?(_%WC&y`U7~ zb4qS*SVf3w;1ZaWRD&=;F$y92D6ym?ri=yL31O{u9ipQ4#(t9FQ* zmlLb*omV*m6^M48KWDH^`wD~$@C4J7&gB%wS|IAeFoR>05EQUQmWEVILK)UJZ5~4w zAhki7A#KF5Q+HbOE4Di&a*LYpq;Fm(7w;Zu&$-@^TOE)r%pbNrZFWcW8q6EZCPgXb z@H~#6oaZgrzdVCjiz3SB)fP~X_w;YhiTv%f_)-m04A!?Sg56^kQ{Fv>$LM4pFmQOO zoG+e|n7FRfu)Rg>Dy7_bLZE+-##GI|so0HSIv~JJu^QRO<7KU#B45`Cyd&`&KXY2C zgx#WZIb=#3^LS-tvheUser^xQ4?QRFHNS-GoNjjbM`$RP>qLPv&EzAnJLJ)=pXOP4 zjjh}{1=qpnR(sEw4-NL0Rp>}aABKt+*wZRA6$}iJqB;iH&rVBO!X$YM_p~S#xESOXcu!I6T}`DTD>sRghOua&dux zYoB;U^ro(qt?jU3V3$EI%)k6$qj%9FwhY3z>!s_qAx{#L-agRM%y63pV-V zIZ0lxv%P#76-RAVmEC1h$f_KeQ?tMmaE!H$rbVo`oM^tN9}L_`I&|YCGAkkIn%JX54c4qN!{xGp|2Pe|liF52M@K z;u@(O_zBk%P4en?pouGlxmBa=;cX3sMK5>1NA0Rr!EyKdKvnIR|n`}9D=!zj;doC}Oe%Q5rWeFKQFq>DHo-1a z`jUefDmG@TYj7aR9w@0nH{Bz;+(xHBoRDCUgW4RiPl>k;T|yGO?_AUs_1$ zE6`EXP2oL%5q@wo>YbyEy~CQJ32&JEzpUik-U4~(47Td5X{SFOekm*Uxvv?jqbva; zG#h@PzuCVbeTcv*nyT;b$gtQPJP^fcTB5DUmk_vu|DT$)&7qwa6qm%Rmo==A=!Iv^ zU4b|}Y@DF`%5h7>HRJ)Z|E*v>l~y}u@1Al5cmi!mT1>vMPm%6(#Oh!IGOR0)y#nK@ z5Le_7Rf~-A)OIaY87XGM7U=_!6JZ>XH=$8I*P$l?D&&)`q@m$FoKE7M4Gj&&OR4iy z@0+%Pj>)?fOv}bs@=wn9U)Ofj?$^l$wsp<_{`J4N{RfD@uK+bc>r`?k~OATRgj z6V%_hK$rof^W>grpn%e~HdWPiRX6TuT?D^BaZdWI7TqtSEzu7=4pW(D6^r^1H-UK` z?z2hj>JGNMEwI7VdSG`{UH^A@Z|{!6Mta9HW6#33O8$wNR9#}TM7YdFL(%4sl_-*g z|H5od7cwNV8((K0ciMcIGtdlqj9HLMT%r=|%$8MFQfiis-G@i1Ie#C)<$*{wz`&&q z+5fhK#DNzn66wrQm?N;?#}zt@Qm(}3kVg&9oDShHmrLbMY^9n+MC6m6dJrjkcmmLX zG5Ij;aA)R%+3QTk#3a{F%EwOKrGSN{WfyI-%5?H&^`#r5{&TaVYF;{F?a4lQPGTY3)9a;+nSw^g1x?xdTP zUbJYUH2D+tuBYc%dz(y`8~Rom^|AF?#P%d#Qqcw|cYSI73c!}8yNc?cI`iFC|654u zIzac#pIi?JNP%6FU58y#2w%2|Jpz(iLA@KdGN4)ElX;+zWIrsW908im*Gtbr?NILk z0|`50>6NaYU6!9z7u$9G@J3}5?i)x?VvZ`+Nar;?8Ge7~^U1wU*%eV-Q`c0zWVfK1 z&BgciKcuQI?NZiLNT|XxisjeGf_irT>Z(mxu9(ChhAoBj?Hq236G!Vm(xH#q{}mL$ z!W@?EEM=^!c2;VPqW-4OU61}pLL*yUCdyFKK={Hvr=U{t!W7MEyx6O0i1aJijIVPh zHPGkm*mB$)YbKt%6x{yVGJlw1FYWJy0t*NPd=1DajBN^D^t#z*uG4WPG;Qx(>F@B< z(bA?rVAG#c(xN+IB)=gE2!T>vvyL z>=*Bus@S4w%nt?CTb2f5iOpLwsegK@9{KZo=DZ(v=;M4bHq%T{^2u%aETPX$?D;5W zHP7qDlV~?<^rs0~Ja<%pfbK-;2`%>0l_9#nwb(Lcn>fbc$l$5#I zExwMD9xAF0X(nAn&r~vK(p!*wbV-*)Tu2+NZ0HA1xK(b|OnfiMT+*+V_ToKY;i+l- z{^+R(_TAF!c$wcF^lJnKTRZee(I=^0^qi6!E@*AT5$3HqKBR&}$NGb85UFUER&@aIdJ6GSA!dH(5DzBPAsJ?GFT zs#|sW+x`_^OlJM-%~j>xJGcLoyt~ime3P$edf>GB`-|5<4>hVZI=Y`XPK$Gsb?@df zU8+O^Dr?_QokMC2lBC63^z3E>e{;WF|7lXUPMd3TS&TOy&KtHEIE7q1`@6}J#I2D2 zyyo4ZUk6@^%C(9UUws2w_j_cjYje8iZrVjaB00I))Vq#Wd(%5^(^q@hXtZ2Xcb(F9 zG!he$=Cbm4)B1F3vZjjz2kK#rzU@{eaagBDCsn$Q?ZmZ}xUT0UoLQH>%#zS`9`vu) zCB8T*JGUA|d+gEd)USsM_%c7!nUa6Ge?jL;+y5f@ohUm;T3G{6B{JMzqN$U2+f9>g zxO3*3o0|{MkI)YPwVCZLET(ZN;GfPp&9UdF&927ILY`rLwqwgKqgoIi=Ki)`kkSN5Q-nfim5? z=S)mi;{z1WT3@s_387FVSnu)QZ5*Bw^MFrQt_ zS?72|wHfox(O5VcH=D)y$GkbH?Bm;QZ!Ykd*5ZOhVldq zG3R`H1WQ8!w3_v1MevFejJ91JeVY1L|EondxF^<=E^^M{gm2lCmcWza{p34Iyk=0we@a2& z9lon1_f$QsiQuW}+lkyS@9@f!;u{4TOjKjNHSbxmi|KQxeNIYp?R{1N1Srx*sTO8? z#mkDTi=(NuL@wi=gfSh5<#uH>=jPcDKRtB516Au0`{;Wp`DmWCHD{c_i zR{g0hD)-ywKW#eaJ7V!KVwH_GU5*hL4>N1F&7P~e{(MSw;eP!OYLr?%&_dor+|zod z)SgC9JKw(5<+iW1?VRV&sRoBS#iizK(jxOD&7naCYvPbNw!r0HTX?w;bpzmYWcv-T z`z8F7U+4;s7Xiq~@~1_pwz7blu*y(cZv;U^pya0D-(W|LS>nz@o_XCf-S@uQAPO`> zI0y~vq2R#|Y_x^hGzEe$b9$}&@I~H%S}qthjNZIKK)TtGp}~{5g9hMa@}3SqSZmWs zasp9jOM}UJ?~5dupk05nj|~JU{e@1cU({c!+)Y!4hlb2NPPv<$NZQCcXPP3aSk1G{ z=Ap5O-bzJBXIhS9M$`-Icl1i-%w~6+-0B~rNw*u|8BRdQySVbK%H}g&jbBtc;C7ob zr=D)%@9S<<|BMQrPeHVHm|#%U)1-3Vr#TX?xl@)P>S5QdIk&AQGXY8{s($jFh`s())&NbX_R@#^m&LfI z_ZB&()&3wD6)t}M)@dia1-#EGF)1&3o0M8Oo<8l8>YDbwKN!)1R^TiXaqkhc*HV-O zu;c5^GB3Z}FV?K8MZH}<_s{KYS)DFl|HId_IS-C7AKwEtX=xU|e=~0!#N2>P2_s`o zin#ejpbm?Et-3@1Jz}@8A5~RXP(2G~c#b&A_|Mny2)k;nr&uhPejJl=bG><0tk3gq z1q(IqyGR+Hz_dCM50~HULdq~_L)Q#{RTXK(_X8l)5E=#4EO9I|I)vXkcOOQzWdwa9ZPJjh#J6#BInlaWL{yOEga@WaK(v~@31uB~sLAzvOVKLK_ z?nB5@d?(YLcs$GfVDKR%AXr{n8t~G`H=3y63<^g+fDsnO83T4&;)g-^InniD}yd z$R3g?-=Q(eVCJKD-tb~xnn^_x zmCQcGe)v-l3&s{34#L|4Is<%Pwe*3T9uWIkJPd03CoUDqKc8x^Kc3s z930S9*Oy_g7;4b2zdEMfq-q@a1#4*X;p9Y!A&3zla(vQNY_jZpC->SfF_$XGP=_^3 zA*`rymAFC%TJCR|vw%O5rp+L0NT=faB6=e$7Z+9Z=>(N>Go6p2l_+-O)9Tt6an@P* zl2xl!6I8pphvb*9Cy!-qLnGoc5TN^TW+E8YIjFyTkQo2JV zB^2rI?vw@*kd95aba%ssb8XP)_q^|U-*e9R#y7t4{c-j`Iv6|dxbADMHP@VT*|3dw zNmK54S{);AGkp%Ld0;*3xSsdru>DL$}#i3%B zRFbi*yn1~PiAPJ!O{)^ie_Cvw2D0=dIkdO!7^JY9jjF6ge$X(}5d5e}5A6T>$(&AP ziH!!nGv(3&Ao6!`Jh!njXk<4LRq4jtZqmaB*T>V*(zhbD$+j^!gEog!U+pfbTlPk? zg!rhJo3*FJe)Ih5mfL>UR-x7vl(8po#2(_38T2N&_6rmAcLMV)IJ^mZ5Qv#$J@?5Ps|y(EnKES2cIK5v=6^u%f$GKpu2Je_ThkD@|#8Cxt{)6t!?U*;Ci!R z=Hd>OW5*)7eG?J^oLt&-0JN_OOnS(?XwOJ#p%r}=@JPRMffU~|%WeftF=_{zm15^< zke2q{R?u8tJsUSIOy4d8^c&DhXqW~K==91s|7>J&XE(sYPzXs!7*7E`eG3G7jFyeS zz(C8Z$;jx_#@9!fSvn zB{Wu{M7fdgH>y&FEr48B2r)a`Sd+_MtIisKG*=la}Op}jf&QkE$WFBZ-s(lEm-+sg7Hi?!yaqB zfDhhqN*KfO{P_~-+|1AP1Ft60mtj`4n@oJ$s&?u=55nt#E=p=hp}vMP5fF17fM6Jm zmO!Rk1~3}vmswR>v?yo00M_w=3t)i^X!FGQxZ|6b7;7_2if7E#f-!CuVM?KxX+V>< zPZhs@Cyq|P?MGynhPe$C;%yfIjWyYdzz6wdmbJ+f2o{mmbKc$r?hzsuFd`YX@c_s7 z)_cka_3H&SmQ|-2)2>cf{HCKz-Res{vFyN7Z|m8K-UUIN`*cM9M13rmE1)UZr&BvxGrAU>pq9(7a6qBLNjmid(f|d8g;UR*8hHDdDsjgyoW5p*Sxgm| z`?R|fR9&hyc69MlXT9#1ESA$enQALPm8}4act^<(h+SW%{{RT!jPXVGVO#+8Kkn?`5@RKVj zdlX|{`6nAw>ZXdP4r*M+uj+ms9orLh}R|1{YDKHBrvi=G2C`gTg7ju?TZ$oNo zBJM2SB^)p7_;FzCP&W;NIoy(`R+g#$Ho*Q*z~qOYW3QLSuQJv@nkY>h-#NBL$O|<( zElVt{UdsVp=(rv;)RlDr!9G+QdJU=HD4`JxI<;71wMSD46DD_`%eGnu#EC1AAe5r^ zY3LiKrNh=QQAUeps!qn&FV(ixBQ>lXg{|vA{5YWGc54q!Paoc|+0KYC>8jeOka^2c zlXbdJ(3iS#g2i{?M}|Io`~3{27#N;r=$XhK?BOW}bZ6_-LucgUyNKD1=deyRzFI(>ADqORivwL)ViD z^^}eH&``OGCy$qzb#j!UwP5E-o@3$W?5mc=h!|J`zIc>#d$5fHTjRnK=G!Z z5B6`$><_>^e+bN?feS^sO-Ntn4{jH)7 z0$~b1het61m8=4a8eDF-mF0&Nj2YN-6FDx5B;LF^0o@8?e*VKj`HfGuib&vbqi9ew zxs3*qGT5TerXu74YMn4QcR;!T9f4NO2GA{-0UfP3!U{2YV1Wa%Sc=0^n6%@M4;IhZ z#GMo#^I(6g=VgcF^sjuycF&OS}0|M!f3aRZL@q%v$~rmD_$9ZSU|7- zbyL@S+YcKFc^kgf+bz7P6eFB8^OIjY3+dQ9*9-lI1E>eegyF^FibZvd>C*ZIyzwdN zyffQW%XDiQErkk1{%*^*blqvLu>IsN+YuUX6GL(D(2A0&WnxNCZsjM?pS8)BT!aq6 z>0isBB_$;%^y&6f-LAXXWl$~FZY>l0=x46_oO&nedp#8#n&i8OHmEH&U&3O2Pc<$q zHmxY`Y2<(8eqszC{*+r*#yZ|>Gn{1BLIRdf3hFIJ$ODU3f4L?=l@RW)1Vsv{j<$eN zT^aB%0^xT)z@2nLIBf^G}QELv%z z)0gxM6245a?6p&-f=*|I)x-1^xpeX6=@B_P7Ea|;IghldH{}*N={1qk%{F9kY+A?% zJ()V!nPNxc*VtN2X}5yYJTS)$%WRZFZS&=GTZ~2AjEu^v3XBz{Y}36vo5UhR7u-@dvr6s zTfEK3Pd5$6?Uqa)puw*;4!GKkna3l5umhVXzxuwGlM6-$?SU4Ue7g;p1CSYKB_<_> z8N#j*ls4%+qG=Dz?iPSxO-G;FnCC2lc(pc7U(2ffS=kq$qx2MuRFF0}YN>QV>JZ+S z6Lta*FMOCn%N~&Z?K=Q3xC_wXRtkLqYDBRXla945h=l2nWmh2RH-=xSydXVr35uAFZyg;!sFk0Q!J&JC8S)WzYl8P}F2r20C9m(sZ$$ zxP3>*z7)q9K)YG&jFgUj(Y;#iUkm6~AVs?bq7botIgaIS8TpL`-KZCz-m=VS#dwVH z5YVap+-@^y%h8hi5LMxan!x&`MQ}U?6lJpgQPXGL7F-GSbMWdMB*CCe%l)Pn&kJWY zYg>1-2@4A!=*zxKhqPO=z?-L%`ygh*J^IrfEvr|bAD0hH9S*rFI%eagx;ivtuxr>) z*#dfU1`(i%b#NzXRhSW(6S#^<^TSxvXSlbDs^8g*7@cVu@k5F3If>2?AQ>Fnzrq%P>EW1sEqd_Io;P(`PEXFtd9vU|h(T1)bg|J3r zyJ__h>*Nh^8Zf|~<-FHryB;i zuo5wtG3P?}WC4FEDY6W>?o>opQAgn3qJl&Q)NfJemJXE(13%k^M__*2?tz4>D ztu>H?TIa0PqC7D?j1!NavKhh z-q@^<@i=0MpPlVYwoSrt1nAxh_nbB*!;hN6NA?B}7ZD++If zXYL1OT7G%F-Y=~lrg@bbxDaV5ZtxJ9Rf+>il9~f(K|H2@2F&n zR$%9cB+IzzLj~p#Q!uY%CV@SXIO7rOV_yEl1$nT9(J$L`R2B0e>oL#8Lfzg;A|`F6 z%&Q8y{}5TMkA`ZFS z1W+sPKU0;z?;&oDqRDQ5IK9)bN0k{arPhhX^jBV*rm5bBNqb^^AQ~}_@04F}Ia^Exk&xDN zjTc>}WnC!gP_Kw~r3n_jd8V76(xQYaTqs8{6QPz!O{;pJO>!Agf(^-TAtK`0gRzkU zmI11Wh?uV^JZk%Ln&%2-F2~5#k=wN0d3GXv1}zwDJTR&3WVI6~MQH+|M?2wsUE-X48O!6Bo zbN%+7gXef?4AjAfY$l-duT^*qHc!r;!&gHD;CE(!W-CE5$W2szk22HJQP9w2QGB>W z!rp01nAsWlC{_hp=#CX#60Vd?d4zYMpJ}(SV&u{pwr{n(q2B8B(@vlndhIVl`v~Q7z%Hs6Ztm4=pNx!Du9DRrij< zgkklgF;lAXdDe9`!{#(YXqvBB?zHm>6(S$8z~|mz&Nsc&g?gheo`2eS7Dnrjzg6$2 zuH)7`&Qh&o+ec6mLfUj@WNP)Tuu7f`ND_2G$O6pZbX>LkB zl;EjnaR*vfin4~qnff>mX4u1YL?yY z^5NHI`^i5Uj5CDsu$W$A=PN2~v3(xzp8b^M5U2$71kD~-! zqzlY*902qOH#r7!+S+gG@P&4Q14#N^{nB-ld58+4`)7J9?1@940t<-j)Y;Hy(_W%v zQi>`ZmOqX&{&cHJrf4UPK7HtM!~FRq*w4}{$+xu(^uRcX4Q+}UT|cLP&0KXMjylv zEkOAKkV=K=NYDH(oL>ea-COuI-^tAJgmfW&Ms05L$a8qtBHkOC7{H5-$YCtQCI?lJ zRBZNqJhaw^<~_3)fBs_;QJVDoi`9^n6am=TA*in>`JdCPUF=(B6uD&{5p%d@(RNOx zb@L?__O2jIIUk(3?3|w>3FG2fUMwH}?wntFC6u>O1wNy42gglOI%87Yao4nIE}7~lT$*T$Xg};a20wU z=H?1CSB3#8Flqj}UOm^ZJ)nB7sqdgY3g1at0Y8$p@y|zg@ngn&Uj{ zFFF@78Tj4~4oob38StaXuM}B8K7;Bl2=E>V?U>>UIK;#SWd_h!gS@IGMr-Vfo5zxuQ`F>*QJ*so8!x5*h^^9nj zOds&jiplFw9XC$)=jZ3%wO0>Cu;~H)+XNLDZn86*o$3tz@q)A?^l23s>y;Rr`1#27 zXSIcls$2WOvkAU23Gq9oO`J1Vg5-n6dcL;nBgKqx0D^ttL6pO)DQ#?!A3%*N4?Z| zY+C~GKj|&1b8aaR;OBL0j@z07AYx;d*kG8hRf*xJ=&qd6}uE75X&<%m8^c-IC1BD0Tlq7uJUO47V-NAXRtVW8Zru>vseZv}RJvYXc`%*t3gOeL|zHDK%l`L0+ zSm@c%kK1$+oBCohU#%B@z&OQJ?yTF@bYW1H87%)?&^n&A72+op35jjhh>hE}#EyOa znOS8N_XkBz|IWO$qlD1B<;Tdfqk9DM!hwH-Fd^G%Ac@^%D;aLo zBei_)&lyJS*yfd`d1LB}Z?#G2+LZ+90SQd7U<$v&NR2>(#Q!b^qVSJ34DtdE-rtRv zGP(G?Qk%~5%XX2AO9s%To#A^4j!V(tzQC6GuK<9%2_FLr6sdqEzoRdXy>$cb2%Q&1y)_2#2YXf_8*T>bR>m_7I#bQ#zxz+XAOKLPqV!|s5neY`A_3h^6 zOMd-O@)ebPNccrR`cesWrCBW|M^PtG#l^*;nY;SgwRdG@p|Hyn=nIc1 zTz;f(y!B1O;Eo-I^!d@&M7d#PJE|lNA3+Pv-PJ?&<#)>usAG&J4aT8pf)DS3Q5&5= z)(i~H1P}q=eFor>23kS}*AMRcJr9J&HK|_#$&Xk}+oz)@>PV~EwH-5hE{i%+lYN}b zUcUXkr4bNQ-!&S(>24T#HZ~6Np5A&=vmME-7aH<>ld~t@Bk9D=?pN=1o|&1?=`yl1_{PXl2=WO7}ZXJrp zlSv1tb{m7nRrNj9mJ!UXEn^SQUG|pax3;YJo9(IU7I>YH(_RFb&eX%mXA0c6LnHg4^FxYA_hAMLkx$U-Q>$s*I zI{G(r|yCZEI*IEC|a=qLdHXn-TfV+5dK{q2Zy4m zRr9xARG@W2KOSi*Jo&g{F1&?_ssqG`7+W7ISxKI+gnJe<1ROy*%C3YP&}M!Te>Iq5Z{gdMnqeUyyyWo|%GLNG$gkCLA}YXXf$PpNi>Mu?@fb+^sr{dkdN`QEKfO9o#;H% z+TC#knzM;BUw7?FtGcLr6NNKWp=1gCuAYZftjfIH^!r$nrOO>`(Qg}|1}F-5Ggg#0@`)px$tcQT^tN$?w=niqkIncbpR0i z7;2RCKNF}IO!283Ap)}0k#`MbuvFZ?ARf)LXX0pZW1vcuX+^z$QL0uvZZS39xji(5 z6h9q~u`XRz)Lc!ZQfXT0qZ4#rlFzKIQ|M@bdDD>l=WZ3NK~@g60ht#0W$#CWtC06! z%$*#^+D^wRuV6>?EZlt!q*`?Tm)Tzoy_b8{9-{Cb^m>)G#VFwYD5KR+e2X=4S00*g zi+(38Xi-tAxif}$dK&*Za=tDd^pb^g%v^WuP}4u0wLgy~KA>Kyv={K<6?$w!GjS}$ zk1|&W16C8cB%za~-oK+;M}iYTPp7aZW)_46)Ksk-wYKqy%~C`8r;nmg@$7aN^C<; z+Qqp|2*4GJ74D@kbNH zEu~4crJ7DYbmh1FTwTU}u)}-aLZ;wt-)i;S{hTGao-sN1)FM-HN{8Y!8=cH3w@e{&3X4{`+lW@U=-MCJ!ONG0(h$&=b`|y0f6l+c* zsHI>fxP~ zsIZ6vAEDp(1{emP+5F5&mXTBQTDww)zX1ZV6=ZWi`z)HhDtubyQ^4d=?Gvodvw$3Q$yfjdd8-r@C&k5qNA%TC`JrlM-Y!U%7>7R&;Bgvh~?T@>o%G!7|jGY zo!1|Fn4-u8`J9sFP-}FE`WTV4J)n&u;5!jq?*n-ls<=zgkmk^jh4KnJH{@wc^UX)B zz(;5T+WKsf?@^%(DdS$3Xb_KIiY*XY8dM}?_@6aXQ)A$p9YkOuXYE;ENgyiYD{QWW z)vW8h1#+({=j5|C?lUMWkw99~d~~qM^>u)JMeCl1M;7oo1pyG9;V+LY4?xAge#s&L zIl=WSMUaloc>-8wtryl*=w)Iy3A_hB8Q&<}>)$SRoHlfQO&jRO#318g#l-}wxU*EO znaoJgL~z?l_BGa-ZA9qhKQ_zuA1Ty!-?VjC#62wp3O_IbcQ9AkvMpE>G(6nSL?Uzw3_7-Ad9gUu_hRY=qV*b;8hF7v1phy9eN5J5p^R~4i zR=HBIbvR3ilYzl<*5eYeguR8m0Rl^PyfOnq^?zJ!pZu<<*MfDX0Sc0xd}aj@N%@IHk6-|5lEi;->Imf&Jmr)#xOeoj@}B`<^iP=bh&ANCDP zSTp;p`pT17kc8mWo?{L>3(Cofw-TEHg5W~wZLPLA zH*hHr*N4Ci0nTv8)x|H#Lcx46Kt@?P1*YtECn=wS8k+Ex@8(2Bnc0xZLKahxm)5|W zoBJx0Fo1B9h|$ACQDq&2EsjN;6qz>;NTOtJ2mQ6R7sYz7L&&f8=0mO~wvWm6&p^W6 zw;!4=PBpeQRa$8nvZb)c+i_4>f7B{n>SocO(h_ff2?K;Hj0cP!FLC>9HmBINCW_qX z+(i)&LSHjq-$#$VFwDlxwhEq(PU=_|=dBf{WuDUoD1c>~eK4yoO2lbm2IdbjAB-j< zSKDmafabsVP|;ISFz1S^qvYs;6jp&8OH+la(Q!+FNSIElPg4rUUx7(kCXD$Y<9wN}l_WAm#Y zwZE3H7PNHEFKw?byShM)>11q-o`QHyxRSz3)4o1-j_Prv+onJLO0yQY-J5=V{P?8bfrpM}>N5*Z}apvZs zV5()Y^)l#O628Jvl)sJ{kj7%ajh_=Gj;4&b#4JvORr5W`=oek}e7b+JfG+ohcrI6` zD*23z3_#`)f|hL?UN*KxWmM@jJtih(TiXbTxLOaugEPPv<~}HpH>dWh0Q^}K`bzZe^`9|d zcds|aS7raH1#|tVJyr#pS1|;YR}i0Hj{;KgG2xvQ01|63=VEJO#AdXER?2nXqydcl4QbOiRQe;Aew#pvaRUhon(PW zH+!<5QoftVehJpu+qXmlnoB`Jot>ONZ$3GoNCSz@h_*FyHxqok)+j^ZANl|%F)BW~ z>AesjYJj~FH%kdpLHPQ}6TM~q@Z>!|BRpVGlSvPIqI3EEIc-nktVuai4)%2qhUdfU zA@^^;9I+J|LDv*v>3=Vm>?I%eH0Mk#^j*wr%6sTYXh=8DWPeI4y(T6;o(~v~y8A02 zGT~W6VDQJ^X>a@di6n;<*d9>I<-66&oWS`q5I=f0$L#`L!rwAq$o|}y*{AZG&K@_r z7G&+#Z5{agz&lYvulM?O4-b?K*Z+G+{Sb0}DgL)x{XcgYDSGIEEUtbF4NX9v!&6m! zVSj`;R&=2eRz+n+NsL^RQC9Rn=aJTWtBB#b((cLamy9;?w_bA)GD>~}Z(faX&iryoq)!nl?H+Q$AmvPsk4~QT3P#ucOy_U2x z8@30izF*vu>64!*VR(lQ5YI|Ez`xFeQnows@~xbSldM}ovMG8}LGpY=@O0SOs?BrX z>ysBq%Ti%{whWZ+0&~ zBVcThLP<@=pjP+smw-sg+-|?@b$$eSXcd0hl33^+yf^TOO6z5#`L}zVd3>R`ST~F9 zmm(MBO78n6B?D=|9Ft8v!YkC~KD?GcDS%b@7Rt$3;8;vDR-oz2W;C>lhA*6j7h|}> z^3CWE55(^|y;s?2CNk6w_r!r|3-_2clFPEJTL{_r*fLvhuyp8vm@95+kfri5_V8y$Htpe`)mty{J$Vvrj zcy}Iby?59RP4B%}9*BnJa~X4#bH%Hl7j_|kNA_Soa$YGOogWW`GJ23&)F!n_rll5*=Bpm&9v<( z9EuMn{YI%x{e#9dr?ZuSh1Z*t}ux26irCjexFi;w4a(EgO~xsINq@S=uO z#NrdB%aqRyv|#8Fw1yo6NUmu4O6&`@4lYg!S}K*j1@6EpkhrKXjY$<|GZp%yzrKQb@K&v6Dh+nS5|jQnDePiy zka$vQY5q~x+`P%`-Yh^A4_1eqyJO06p4T6@r-^P=7Sx|rm-cX-&xG*y*Q%B}Z`H?! zboDYMEvGF!H7p4Wd*=j@^mN0J5$^Xo?!+qtRZyUE!B=VyeHui6cK|Na0<$;6k(YY4 zzXK|9fD$bj^{5nDkZfmEeY8bVD7&)AVC?a4=^sdHfo=~I7fB8(hl0Tsuh@H{tj%w| zqRKvP(^<1Eg^v6&eVUGv_GGhCk@a5WJ!#npWH-%XFvmUK^+lqbtr7fQ$FV?lO$haIs!OxavcQ~@|P zWa~QRq~W5T8;2fvaI-im_J8j;MfMn(RDVf0~fUyU_=F-#&@GR=k5Kw zZ!az<#K+C2t8FLjXC%|J#va!3)ol!wQV93WSLuSJmtl}C-f+Vk4S(-4VA|FGN8=?7 zFi<9PY&-MSD{ME%(}5LxSX{%|fQhQpXkYRHnD{c!AA7-BI=}l*>Zb4x|F(10M}exd zmPjqPIsY~?FkmUSuqv1#n$@y@W2(%gp%PL5y$?TP1(w}&ygQ8(`WK1IWqfw*1xyR#b)eA3c# z_>!Gq$>4wz%6h!nd7|HR!9L^gIb^a}Z<*8{HXl4;30J4^MrroDE>GR9 zvD+4tlmzSBH^Ued8J^M-r3@-XMh7dUB0*fSw~Cy9m%1hJI`z*XAfC|h+@UNcI=S0X7>-0@mxdi zr@X2CJ{tFNFMa6R+91fCIfm^ z0&>ytTSa~l^shp=!gLP|uJ`48RqvFZRTIAeiOVZgt~fS~o8lLg7r|_um zA|Z*4wD$WaC3zvqvfg(&`svl6XEHtZGtTFHOx!G9zkTfB$KydMlMgXt3fw>>3IIKo z-)=ft;wyfNJPCP-L^k*Soc1j?nW}WI_ZGtl+7fvq zzJ7h(zL!W&oZ1^tgwggtI-~lK@one<38(tjp*8%0;vm9rvqK5&Z z#;YbAxR{S%f|`ONdBmvC(%Bm0cdoxqHYJ81){-`OkaD3RG(sq#Dbz+sT1RyVU6sJR zNdw;~=Yu#AN=!@r4q~fXRK)n`S%)b4RXgF5z9m>K@2rZ{3xExj$c3 z1-?pOIJvPA{5je5-=3Aa+iDTA2tIEF+gM14ZZ76yWjy!Y&nfS~1@yzU;eq}u(@h?` zrnda=tBS0214|0&guy&x`Q=55L5r8n&2ce%=>$M3`{$c_Bd5$bj(OTJi_^tPB=8@bacf9? zId}uTr1o`c4?ybDzW)7g_a5FxkntLX1DfMV6@%w~3i@AJ+Vx@; zXo0_b0yyA;n2d3-HfFs$_e_dg1$G`USExly4tHHX-k$08jd%7?&R1hI?5Vx~q^BAn zKg$d%Z=Oh#a(J`?L?!O!f$YBmQD-8N2T<9}fLR{No@fj_?%J(g^Cx@-Z8BWm zVCy6A&-j3^=ly4a&2kKy(DeOzw>$nn8_<8XqbV>!NPOt;-1<*X2f$|GY*y^+Kbx*d zUq6u7D6gprAp+rJf6DflfEySEz!!ubil~i`Z!x~Yl*KK7AN41OgsO(GfLdA-0J$sg zhgolq!{d$8;_D)GHrxXT7<$sy{>jB}na02M_Sf9CsfS32-YHMTWu8_91{~2#pVp%Kj-oMZ+C>^&d22I?udtR z%wKxn!4B1~7W9sfEiju_8g<9>|~cb5rG)xY!()xRH!PX_xvWt z@r{AWi#n#l^+D9~licR^+yk18&J@xRhJB^&r5`8xHpN0nwLr@3-$2kT6*gpUmx3f#4X# z!bJBQ)GbNNR{SgN6l9qjQ$U*pMpky6FA|`RlJ)Ku02>AH63`bd8A9_sdM-hjQ|DCC+>V=Y3y=#H>M_Ni@rq+1NNC)gN)P=c^XgDKYt;7UkP}g<4 zO|hWKk`MDs-6S_TlmHRvIkDWdLqI9+yb;xZaAG_RY{U)&h4CnACGQDz4R!-Ub?lRaf94|=$9g2}Xj$J*7Vy?igX{bYXSu-Q!VHx`5Dv+`1d9-ZsE z16j;YS4vv$O5k?t;soMV8!4$CfZ5SEY{)I=1HUL_be?$r>G&O>KLT2)yC0LXiNpQ4 zZPo*2^P&k0s+-ED_z#_kN?Bx%xY6Ohz${9!Bv2MwlHp}U<=!9M( z?&nlg{t-t#pYEcs-TJpS&rs{Lz9+BagrCiTM+zSoH;(H)BuKTw=9Fo94O*iNjQS== z9%o-c_8ut@-5A+H`9yA&qy0-Eid z1Cgz3M{8{B8o z9gT2!E*qLjZe4I6-ZleT(u@*a{*Ii6pO&tJDl*L4S{4Kj)EncvLRXVg|BinlBWdDtY zBLBy(finp@gs(L;9>8}RkC>Y(T}YVwtcA^XO`%!Q3-?tM>k6s$e?$Jzn>oA7mJvbR zbkik(%Z5(1<4Vu$9EwoR)`x_IEEa*Z(cl|8JrGe+%{h zZwvK019-rOo10cx48bQ{gPbT|tMtqZi*LNzmc(88-ly$)*mE{EeRqSUs%5R&F19}n z7z~#er~AMor&k2D6jp#@GR+gTR(8LNkf(b7$%EpnhHq#d>7F+(g<;6IggrO8E5Pi6 zD`1ha$-&>x7zn&6%x5a*BTx4;r4xTkFd7_Wp)ph!3GW*a{YRU@n;1w@sg?tQTP%i0 zY7@S&c>~%VA^hFrkzf03l((k}CLLOdGUf3B@G1^|Tj1(aKeDeuo8Qo3hsZGOoC?tA zGvg#ez>&7|x`Be$dbu00FUNqiXC&9KcmDJzDYw1U>0W0SEZPe>{`Klv4oxBDI`pN44juz^n{l)MnDg*n&bkrT*tM3!b`?B{ya+BE^uXCbm*uT%cEmeD7 z!2LS-LA}xL+v6Hw(`^YPLf|041dIYoxMC5|1;B;Ql|xgleYXnGkqFHb0E&!#H^xby z0Yr>Mr!@~e8fCJ59DB2wQ<@r4(5b!$5qP8Ln;Y=i zGg^S#d0(QCGo8o-w~PN0uNcpQfH@~AlCi_W-ojU|Ti#X_enI}}x3&QZf=a#~XaxGM z&GfX6Z+E(vKVJXxNNB|o_`>`9`**s-$~Uaej=2|)CM;9iq75jgrjxgAc=u$Jm(f3;-YH zc+hj$yThxOP(emzXCg;YpFhiThA&dfiiy|hcx#c&9V~50hvQAqR0#1g>`idyNnEkp znSJCWCGjR{nTVy{-TsF&Q*kl4Xp3#+uJ0Ga-F0^;`u`ge%iwFsG)OCXGt-_z_rX2n zi@iAY2_l)oK_}%JOnB%WP$?=Z%E|*W+qq*2sG7K}O3`~+IC=twN^`@?Kx2ZaTK34R z6(C{Pu3}YB#Jf{P5~q@0dI8B3a36*?z$(@V6IEV2xzaRlkJrUE#4+m5{rZ~9U&P>9 zf0{u8fSlG@>A810f5|Bsy93~|ukrCL4m%G!2Ec3rxT$rAo6MD4=3s8*YeYcXqB2%4<7+#4rM{2fy;Z;7 z18#+kC$JNB*gRzak$f+x0W?Zt*i0*cm6H4j@EavS^;Wt(BJtu6&&j?iGiFsxvPsgS zS=_&`t(k&GcS2~WOHULN5a=kdO_i9_l*UwWeM>OxjbE1l13-C{1{T66z!3Bm$EJ_M z8LpZ(ssL02?bBQb&ER#MSa#DFPM`(;5@*KwY7&k9VlTJw^km3APgGpAZs+5E4>6zF zVGP^V$+GK2<@Ud}4IE-t^EPS*wz>~5EnXn{J^bL`?t8SY8!mFinWw5%)ww5)jK#A9 zpA14Fij6_ybDw~q4Ss=>GU!xGe$Ma87zerCG+Blo0=YGYQ`-Y}i@0B`#4CURr%Rmj zaA03vn(y!c+N&UQ?nRxG4xdT`e>Ene{fo}`PebM3>S<7@`y`{)dOh~=6Zj9nFk(@G zamthft-!maYq|FI*WJ$#Kv0oyzSDS5^p)ta!oN@}pn{fz;NSOnWaXEV<=uR9;U7nK z<-AgEeqQ~~=Z>InX~v%{5_DqNKk8>CC$+5uC1_&XGM4YHf59voirdIkgp|0?WJ7aQ zgpfHz`qDVkCtkiuEOgtue`Kg$m~U^8GT9_# z>PiKao^Gz$&;EyJdbeNp*-rJb361niu*NA|6I*iXL!U-x+(*4F)FkM61Ll=w6^loD z348z65m4eOnM*fpXGl%e;m9VmdHY(PO%VenKp~f$0rv)MO%a!zt}!Ki&4HVSa~5|<6{ixD;mAYg z6dV$=ZI+5IK^2)&Xpve{WTaPH`IIE28{Gu+7NbDeTqa&TfA-eaYMk*7%C(ln#KCT{ z@=z`;ohi)f6pL>8u_0f=^Hc<<$X0p53|%dh45kw%rFkH;gA)9UnN7j?6qpt#H9U&R zSj)-D)>Xuaeb$WyDZP5Ya?Y=-VFtegGV%^*ygd(0y{S~~GG0AVp?LGR#iVNMqM7M& z9}{_pg;klHe1kpN?cT2#-e`h2`~gE!EMUjgk}O+NdHPg`Ka)AM98Av zsS4lCKW>_;_1~Q9v=|s(JlMBhZ6jp3V&>2_`OAqe{4B_T$f4Swh)sJSQD}ezpJ`N(;n`SNp#Ouoh@}|JhU;5%3Ks0c6|k{c zv7}KW0B(H{j_liBO`tPzKd3bB4qSGjqWCOh5Y1N*d@xfql-9Y)P6W zC2Rp0^RK6AFCT@kmE8(*{#5jDhtjr}+X}04fg9y0;R-7%BC}<#A&e5>WIEJ--zIqM z@ju#otGFoN@826i3{q)938h0q8UZPRfgxw;?(RlFP(VNghi<7Mht46TyBTsoq@+Q* z_s#eF|2@zDIe7NLe(i&OusP&_VXphSuXU}pKI{GIUe|2@X{1V51QVwG_#&6J^-=48 zmipfnXL302zOBE)v6&~;t1R>)j~pCNad`CnGSufW|x z2uLpyXNI3*D1_I-mE18>@%}&RF5rk`rd}2luEfk|`(IaN16sz)6N?K0(OPl?7>&ed& z0lu7p{{{nxPPG-_!Lh5~AYeaxgqqv4Ff-ft{I3=sWlwMKOu65BY-uxqeAPbsix4o} z0|@2??sd0;p#b<&3JVbNK6_)~23RMUTyX;x(sW__*$L}$pdEDi^C2++@N%A6)!g53 zY+8l4T&?~E{Fu)o1r~+-F#FNG|DLfxNgq(99B<|}on5&G(GDbu0C-;WU#&NW0f&PJ z1JjM(l?eu3uNnC*fKn15z?wn$)&jz0;3Ibmi%K}yS=|_c&jrdGdZh-U)B)S0MqGjc zH$gz9r_lqA1fWrU$cF^aQ;!dac>x)7!-n^t=r@4XlPaZFos+78fdOz8*9G)p1AGNm zfzX9JcQD{5-yA4${aXZ8|8;e0stmE|qH?H9AKL>IR=ahF4&%J0rX~PGXb`}z>iNtt z02U{1jF(r3ocs>6N3|_zryV}cjIlgI>|dPCiIU5g$0EUhZ7eL-`Z=RK8JiEfT2u{y z=pF_Lfn+(a$rib%a7`*3A~qX#)VW z1XMs>p(0g|d;UfLkH#va9ddlV+hARb_E1<9YeL^koDr(A)!ZUl#R?^$HV4lFExJ3T zv8b!kGfjpq%uVv`Fi} ziLtCQG10 znPBKqaEQv3(#Cp_K18#pF<9}l!<{!$P{p<-4GHGm=c`W%f7ewtP3S^&;g9si5e8~%&I@a7NpW$5O095-NGBeyv45Id7|+!KHJzwQhc<*f zt;NU~aEvJcXpxEL3JEmBU-z0tjE!z@00x}>ByttSF$Wl1jTJ)sjcCs%6FNCK)alo- z0*Vbpfp^w*=$Y^OPC&p#k|moCt<1djo8}ha=1C3oh5=yg;Bdbqs(1#l%LXK8VjL$x zD?QD3SBNIy=x0`E#9WobWag*I%F%71%9+R0y!k97K1_}0!Zb$zyagDfMUfbd=7%UF zW?mpUH{z-TI;@sAhuaey<58Af0QO?Rc}g76N!9=ji;+)V>;{c1X`W2mvrR?Y9n0M< z{+q3Vz$h8u_e0Gv9Ugzad8T>z*JW6IZ+=`!G;W66Xm7qgrsa74kQ10rw>sN5w#27G zS`HhqZmQ*rIsN(-2K+}7h4B9xBrtvYKf5)HrTp_GTYC}z>y=6+nnsMLAOBC+v9JB* zm;FyI;N$<*w6p$iZvy|{KM1`2|BisEJd}fVJiHIgcQ}nbk@3j|*!|GSQ^JH~Yx8H2?i#i_~4_g{Z-hg)Ai zpTgd%Q0y$bNwf|6sYE|oC8j`*Q2xHHOgKXx+-D(^p<8dk$l7wUI!p(RmKeaykwZu_|K7mKIQ&+7E7BCmWN+i6>^!|)Rx zKGp2?vX?f=@1H43l!Eg?D}H z)E_7ooPeHC5!+e_!JEW`%dRCo;*B8tF^n43T@P1jMT<_-k7jS1ARaqx!u}RRsn!Sr zKd3Me2;RN;XV6aNAqAY}OO{nAJ2gnAVZ|!fP+pC`K|1Uaa2ard{Ieo>r5j<4r6$^> z2+3Zb_(9@JCVleu68AE(e%pOk-lAu1CPdOVcAxfbEq)^b2GDH}Y)-l;Q(g=r{tp~49I3W|yhDx?QdzcLzR z(;8;_z!<;s?}kz~DMm|h9++); z*9&;}$2UbG2-nuhNj6wTzP}AhjgH=z(=`MJ>Mw1vi*wSu6pKy)aWP6(HCBar<@D0M z|7o1)x`vwe>)QFlggWac3uYc;{Da;YvkVW_A9PJFlxnkCtB>hrM+You&8 zS5c`&C*G)QP45-Azx*Vt6Hn`ucO4^^`y(!6%Q# z7RZ9>eWsB0)219>YEI#k^-H8wbDbT0KOi(AJw zNX9UODc&P|b$O9*@w9T7V0QT|W>AQ8U8Z2*`5*1fG&6lphedV!#W)T~#b0(DKG#kl z=3UkQvyxn(7J!Y&T(}rDvlNr2lAM%~AmF;Dr%EwXs?E;UTlMx@jWPQ+n;XUs%-6kS z+wF=emdx#z8vXndBooOBy*Qb~iI*yf^uYEDk4uvdd+~7gGurn=<>K!&hh32*l#-O~ zaqG#(V_Wea@YWRDH@#K={4JQ#_++$}a}UWjg^vM^=lZ}LY%EF#Wsaw2or z2L=YVx3}kWFHKEM)O6|Hw5F?{@VkAo$mClZt-3lcH7gY- z0r$UfOpdi?pstTfG>HCFR<2gwMBt`3dY|MMnu$+(G-prv`ItBEfZNwm_S|^oq$F75 z6w_8A>-1pFfoPNYcqiB)gNvDEE1Y?0WidqPCei~EeJzcARbS&jw@7cIMf zHzn}d+D4Y?vJ#}w6{_h>@?D6dSI!3u;jE!-?vRG;KCadEit~ z-NZEH$}~35Ws}B?ey2D2rkjCpw3>X#i!3=3wH1JpG$P7mvE42aRXhIG++%nC{HI?7 zFpA>!;n@|}ICIYWN6|Uu^t$SlzXprZDKRM>JOa5poD}orm03OVNNC{HHl_QQ&I069 zoQ+tbQ{QFgYs+x4zHryti{%pq@U>)YKhW}!pG)IIwznu z;cA?`~RFo6uF?S zaFcNSSmGnl16ZG$w1uY!h-Q--9A_xmyV4Dy%yCE}sRX{k5=q7M&x1B&AW1pAtB2>T zeXVi34+?@5V$C~aQc@ITHYO$@AaH1cp~O!ZP7dT!zs97##F~!brw&&ozY004W(mjV6h;FLS+PEmbXwF{PPsFqn$m$|sNR^y~D{fzpJ%j$w z)M($jglwDHid==xDzaFLq;BO<3FjGlN9^2e%qKUdnE523pn>g{njcc)5=C!Cb*N{s203Q2%eO~d9ur3$9cRQWGhV+BAsh>= zq&ocx;jawObF~VOD6<@?6ku;nMs74!`L`F`b|R9Xmr}e2X=?|=D;f5j;{;1Qi)v*3 zpo?v^IeZgo3=-4d9~)x6b;WPqq4r(CoVDg&pwORs6H7vQXl-Eypms0c%3F*J(s91) zEn_Yai%{Seg&aV@4WgD&M*0g^vft}_k2YX_r)6mQN?Pp=?drYv3ow-c(`Ju@fqKB` z993-)_e2dWxKQ#N{uy`K z#5Cf5!AB))FH?kyy%mg|?=i`R=~YPn*lEhDaz#m`K(T2%%>Y&^=xMi`7&tk+#rKp!TV=A@p@>6uz;$%c51Yv{W74hj%4dCBed3RXHMQfMOo0EOyzbqjp~`>wu}i zWeSHX#Ct_SDxQ>;Ud(BVV@IS)Kv5scCsQ7za`2*tRIkm3qBFrUMe^W9Lx^aTm3Tt& zk0P5s-`}gIiegPoULq<7uJ=4NSS=Dsc~PASdW{a9JE5vQIoQQoaiU=4LDMVl9g_8lT*RvuOqT3zZ`5=1z8GdN)vcayT3)@}6ABMt`+2z{?nys5oMP zG>VA=Z*6585K}eLZJg8^iE93>G1(fk0`&sO8I5+$l4EjxCRviGftD|THO%!vD5_k_ z*I%=2Md(DCR^kbQ_!&G?4rekMk>f~#I-ZPc)&s(#64~8h<N$%sSMH(oJg=+YG026ZDp)Cy`qqf4Ym9W-W|qf-Q69eXhR zxk!GNlQI^v18ePG@zfCgToU{Y(Okam>Y{=Vd{)MAmEtVaCvV60dm_iSuok%~Jmr_G zt&ivaN^yg+khEdxTH!j#q^e4aQ)Qc&&8lY5Zta!?G2>(x&$G?IrQp$ zGJV}AJl==;X;Mi571@zBg#-hi0~-BVC`&j%a|mH?LYk5K{@5UGGe4Bm6cs^r{aY08 z5Cua_d2>4l1J0j0@e<<$;kC(R3X|bPeg?|4iS?GJ0{xn1+jHc7_}oPbCaMSi3rKmr zJf$=w@0PS7s3c9{x}y85a{M)SXyaoG8)AR_#$S~uXA+JDx}B7TxrQN1j^n6>O)j)$;eg-#zV}Lb4)|Y?{G2E{upG%2&9;FpE?_A>CZc+ zP0GU9wFr;gn~z5^UVN2|9Fms1q7eKk@B-*WJ^ttBTP?xN@2W(Z` z({`-U$9i$l7L5gR)P6dV?^|oH7RgJYSZ-QbEkN*Gu*|u=>ssErNrZOyex~`a;D@-OG975;hRP7@GUWSf@(C ztX*~L+J`EY4}k&!VvSpWQZdsIzesf`!QSbtJ5?q$;BWq}BMj;8uXb_)D(QymQGQ%(a zdN8r>6$QqR-Mgu%kHnJ9-^@-bnYA>@=Z@d{$mjOSQhS?SBYnk@o&Tu?xbHRHHXG}Y z*w*^fLn~CXcoN7&WF#;3i=-Ge@IP!I{h0Q%U0T-pZwE?5u*ClTpz*K~whPo-&m7L*&pr`~ zO-oOIoqVsbnT%lyH)$q=Q+vF?H19>`4^*9~x*G3Rv-uWszRWH-K>LSVf%)oZ*TcgE zhebuxp%+{#B<6)WTLY=~j1EJ_Q(jl7)rAT_@p|jYF|Yw-^=Wdi22EqguIgKV{gV*v zaq@VEnbKe@>y(@6{mCiAEla(zW>eYl>CDfcw}0#8Gw1LTRBgxm+i)F3b;gLDxoNee zzk2^wP>%GElggI^QpZ{=_a?=nrHRYw#uI=1H{LLE?@k5WK-^S52ueLu z0LE!u7mz$Hj!8=Ifl1kMtH}YJh|bGs&df6dBj4#}hS_G3ty&2zM0ygwx;k`bS3hjV@_jyQ z|1iP+FPl}Z@-rLv%iCZztt5pX_)`r-lohasx`-D^U#$D;&gy`pJCCW-G>cPXJpX}1 zby@Ri6d(}Zo|+x?q{{?)BPyPEb%u9{EZB?A!CSw>mN+)e1@HB=(yufDd*XY5yn8jy zUQ@34hbo(hn~se5WdLHocaOwe@?2u#TLJ7kKtrdxivsm#QWfEiUF2|nuxYsA-O!!4 zB{6#Os314_r-$ViuL2PO;I%DzfH^>uz}7?Ue_q=Zrjs9y z%Jvsbgr&Z<8nD`Gvz{yyepq2@JiTBnQN-uy*VU|_Cd}(d`u4-HYwyjvyHSxD=WH0i zJo9`)t$++i4uTt}O2)A%#_PoVJWT|bgb+J3&@ejE=&K!Cfqm9q`t&5?p6YDEY**|u zQvXBe5ofbiT(R39G}DYJHO`p!=NI8=?!YQveTt--8h2lBb8oNTPt;aL&C-j!+ESuJ zCx1W18(?ca0i&+QJgL7%u(g)x@A4roYnOM_F^o;6Dj(oK^iU-KEUC`fLbCxwb?)@N z_49UX`-EBA{dF&)*>8?t{%f@`jL2lW2{K9qB>gMFcm1qt5R8G(YL%g|fLhS$<4gQW zP3_e^OawCqxqxM7cuGwLyed0Z`=@9Z0~kswUJiRAlMC@hY~NUrb{2B`ko1F;w^hfJ{tO+#I128=Ny)oi3_2fP?7?UM1iWkpz0qSu@^7m5g7^f}PZca8GX~ zNpz~iR2;253-b04Zxa(R)_*)`yWRNIL85}HFY9q}N!~_4f&Xi2Z;mFDQ;O%74}bZP z)NL`S@TD~i7Gx>xp66nogzp#Ox%mdmXoEnY-=~sq3UoB5umIJ^Bj6VL!{HaXyPAgT zcA$#0Yke$+^vvc`s0k{p>9smPf{K$+vpc`03+O}D6n$|lBerTe`KA_r->xp9j-v`*WOzv#X64JhTph+ijqg^-RmPyU?P^}t z+{12t$86@6uYt&+oUuNT)aj=PFXcwXsy#i_X^?qTq^NpKVe3jS0p0P*kv9C+$IGFL zKk`@jESN$@9Z%oP;^~f@dPJuDhTg{#d6-_#L+et9Lq0Xa@)~)@PPdHhYTUBW+O{8i zNjS4G>7rPs4#&m%ooG$=o?*jA^L6tn!;Ga$VJ9X0;9^9r&LN6EV1DU^QDGtd+I?62 z;9zaNgnO3TXWpAN3@y3GBIa`^h$Lt00fKlEy-G|WU|3D{xQuSZc8k%b^XA*xJ#!i^ z4-sp({b|CkN~C`2TA(fuWL7+^3y$JYo8zmV`nfQ-drRbXJIz>nn{s3*AD=mM?2?`3 zJLK-+^%V6PCR%D}d2G52wdjKl+)}X<^mgVE0?EY0MRoI&-?;Nq%PpdCNgp z3V`4Z?_NHN=}^5`MOe;Y#ce;YA((CG#+lIIRwu&OWUBPf7zV9Dq-z|A;t5^|mYJVQ zWGrfKHft14sEvG+2NkYI#vnX$w?$&kpWRngk?wq?%E&Qv;t`{Y(rfUv>?tU>IiSeQ z!fAx-#HjeW{|q|6nIK`E+RKxydg;4u2Re_zK`V6+4Qvh-j<7N+`0L;kPI_WzDkPKu zGWeTt^^3nQwN{>q9pXv?ZW{QI&T6DN2y8uC!7yG}%?v1Zi2nInbNLu&ZMd9f<}YwU z+exPI(2qi)GD26%F!b9PKmBq)y4ND#+p_X;P*UX5?IrUeJHlmlkX_gIMk>1Ri?*z4 z-@nJpv-F7W0+-bhq?jZ+oi_F>v|CTPM4P|9(K5!YOMPSKpP_w zyL7WMH9WG{fBS*U%8MkU`|a%UdT_|QDCx;c@7_%^`v#v6VZF(Uz91Dh>J)?{2=@hG zoS1#Uf}G^3Y{ipNH4&~!2TT{26ZL4B>9m&wRC0J0!yVV^>+6C6uRg+={QRbnx zJ*igI&Fx8sKWS5^6&Qd+G$-VrL$nXn7~;P_BGEwpJV~|`IAvmEA*W{E-rh1Y!5$qZ zPlrvXT4j2bk9Dt|xF?aZI&X@b6L`VsDqCeeJG)vX{VB)kUpG^8&5t!EplL&lf0EhR zK$#kKPD^d`Wzo^mPNUSRGBPlENF&cX2Z~p|*@_v} z^5`QR6Bg5`sEA+n@{B|7%G~V#?rOIN$^4M^#;{ig8vsY`kgc?FMNz8!4?rEb0UUj7 zGt22Drlh4}cHT$viRAR2q&0=8)Z>)dX_70%8&OoflgbXR#Bty|mgkW{4cs?h?m{CI ze^3@q?#8z&N{}WoKSl+22N%lEc)5Cv0`v?{E>B=e>Uc4<=`T$np9{}UR+;q1C|)pP z!HsVwrNQ!c0ufwTNa|gU$-@1b#n@2wSNCxP<%y8#Mn$e6r?%+t35lHx0&vfql=()P z@$s7~2Sej2AMo;|o_z7X^d~F7a56S4ZpGtHU;36`Q%U(f25S>F44&Wkwh6wOH%w3x zwPn#?EHAfPI8zN7zTKF~c$enb$`o&Ru!oM)8Vg7J${wyHBBa52ys}O&F|`(!65FNwk*|xFbqDKC+eH z_kF$a9r2QxRph+{%O=2HK=;rCXA*BQ>i@P=2uBqe-v0Xe3Gmh~ zF>LXV;#IaOgxcj1{-`bPk45ZrQkz5F+il{K#TY26gm21)rQ#V>-344Ukc;{Yr!n2l zRQ37!J>e%oa<&dK8fY751(@`OAvcc0K9Ksv+!=jgTvy>4ZKQ+l$Nb1GkYD;}XESIT*LGZ#a=O=~j7OC$v5uQ+t$66`klD4j3)0SJ=6gYy9@uc&v z&izZdUBt5*=Tnwd9p!!aAB9&6I!qQ!B%G2u`h%=N8uQu&rC6G~Jer?|Z8^dG5h82V zrK21fe%uH;Rt*XF4OlKPFvws0zOJ~zdjkz5$AAGa!(=wT@bXuM*FuJ>iDp^}iv8h+ z@lj{By45}}VDvH$Xk=XR`r#oqWR~=P(Ok~w&-ZQlBv}XJ0ALMTTVG2$qetXRmUo0^ zI;euQLo#D{3J#`{APG(NUeg6KKo4xKad&Me_mq|9`?KGg;Z10gA?xlNKvs^>sm=lM zaWE;%GcSBlRfHTCfAio24hI$6e+&A9udS@#mS}X8U_qH^P?_I#B{Nx+Cw!@iX6Aakj2Q7T%(%1tHxYB;GZ0?o?;9v1DX8tQ{TP$mb}YI+BgR_C);>cktny?>t*5n-5h zRS+o`V{(jV5DfoYsrfhbE3IF}OD$2>e(GM{wn#N%g54SJ0C)c=YEkuKk5X(fk(Gqv zS8{}JCZS;^$83r`;ZHLvKJ%^kG+z_qs-e6NKQZU_wvC-3t0{7d}ivCp#oIgwYRP20rfmAskYVA&)HWvd=KYBydy?(pI?gz z=p|@{g3~Hm2JXuXKu5Jb^9I+)T6$gW;3uA~)#62T4g67f{}}G`LayNlbB?U z_-jy~VU}?<0l3<*;mTHvP#rF-RYqNCH6oR>$rl#{9`u2zsSt7npSQD*KB*)g`wB;T$Urr+y-pEXz{y z8jbp6jAJY%`1^rHA2K{^Iw4sB>(cEgEO4XW!>s?^^= zP}6luop}o7;bg?E{S$UY91=dV;EV7?#&VMJAF^2OK4-aQ%6NRdxRkguhl~C>)(gnn z1y3-nJR!{}$oow7s%bjT?&qxi5jf5tI32%$?t&*q0OnSoar+aQcv!Lx)DQfo#25=6 z0yx)A)T!Fk?xOR**r4r;HX2LO+3cY<9RU7;{4>ri`1Jg$amsw*yYl)_TBU1+fZe=g zfz~gD{6y$P^=ln|K7IAK=`FX13xJoh*!c?aIre!KbH=c2oIw=0oaQQE197lysNHlC z1-SUm0Eo+y#j^B&o{AG87tr5sZwEP}9*4*fI}uKhSzGHBAN>NwJy zH!KOH1lrs*%2!r1$^KE`8(c;W11r~WJ;VDfN^K}dWHwVNds=w>`*q_n{4p?Z?0LG_ z{gwL7+591qsVV&>ze50Xj%u7x2N`?f?NY*CO33-oMJnFMes2#iZya0roX3&+ng-qs z2)s^jaM3wtaf7E}U;sHIjicZ)__F9pIHBGI@L02+0uk_?{`=l`9GWaM@k(NO)?J-0 z>nqmqqdzn1@${V}EbIy%3s9M&H*-lThuYB5EH6>i)^S&NCn*_OUy#d?kY9tUN#!D| zZXy)Q!LO8gVc%T3&;?GR*Cuk4nEGUpF}!sO%-@!WJyTLvKJ8Wex~LqHD@aLTE6Yzg z$lE}*G=1iE@nvPjtPp3b&;lC8CYT|?fChM|lAW@F^RbOp8jl}8E^RuQ^;|Z*+T1D? zOBY7S^pgP@QCL*eDK-``yRuN`!Nws}ZhW=B)NVOYS8iPicf4}swjTxDXCct0r4TAN zG|9*{ppCnd|L1+CU%+{d8;q|!UE(iqu-u{X5kX+d(#`cr4DvN-?jv?XK)TjoVT1+8 zCC76OO|2>1asMZH`g^&_Vl9U9RjjQtSx|-=*6KK&W)}58K_#3=Sxz!`3*WFrsHrC4 zflu~BygSt@kdKJGQ@W^Ql0x9BCrYE3#{-Q7Ae(*^SPcE$+=eygiViw4mdvfZkh=5C zuHPYp%weW*^4U&`8eQ_x*AbU_=QpzQdexh!X4(#b_my7iFt2-(KOW>NnE!kASxB!; zc(uDRx8l7z?`(?$*m`=yO%l-eTtx$IAp7YmnW$jGZKbU!-ZusNj)oH@jTMGz% zKgUc;4O8<{B_>Kmb=BBt|2lh)%a``7N4SlLW`urw$rjM%0xvd|7t<;*n5fQEa3;y> z-gNmZCQ&gK7Woq(fGLy|VF9frf99>|q}cgFb021sABi^Z`=$^tFpu8`z{z8eZK~Rl zu+XR9#s7T8jhIkzy1n_T&b)oHL!|*_70jb}4>S|~EWV>V%M%vut}eDp7kw^YKrTrY zZ5IHlbl#)usg|2NLfO@`fA$6Cr2%-f#1>yyV6r)Rf>J|yKe!jvi|#%_UoN`A?+uFU8uh-xZ{;m>^U4f1JSfL<-$-Ce1F&S z94Z)lsCmZg@Sjy{B%FgC(bkEFH`Bsyf6C}zb+}1nO|ypwtGy%nY?o1-%MdmC=CxxF z3E#VvA6wKuhU5gLdB)IGd^9hM%iid9l8K}eNOT=$2Nj`Z?VZtC$V~LnejIi}&BYSW zWNra!zR-`TnKQJF*2d?Fz#rdgI_Qh2op*`&6_H!^g7;{qg`Hh3GQ|;@^o?Tcx1ZD- zH=hwyPmt}4;@vb*vq?j@)z5_`6KBF!Kw@5;-8E~nrLTEAklQksoPRp|LL4TNF;)?-@^@TOitSNIyYZmIlF*%bWK-6pKU9j&?oWt`q4}WgC7lKN z2$)c`h6FqQ_9R8Gr3vlgjxF-?wZ`fBfJ$KX{lVyBHI{LAl-?9^qPP9>%I6={kOHFN zHj9yxCC#IlkDE)$cIa|DYU)>3jl+P^-x>^mypTHumH2860xeWhZv%rsK7gUg&0g2- zAv3i$oP_Epbw~kL2*3!YrE)PoLsC2M+cYJ zRrjuD-FpkMGV=fQmMX*7tE#GM7kxyEL!dgZz%l0L3yt2HDsmNUBObPES=w$heb#aPHzxQ+QNYR82nv>k#-9i_k4Bd+RVozvjfUF>8I&r!g z?ZeD?m=ZK)guPIwDB9J1g8a$Uwuks$!2VTCG`nL+f~}aC)ul^rFmyy+}<}4f3<0ui!8hgCP&;0cXd(br)Xd@kO3teogTZt5qN(Y-9R{PhhhG{rqA z-o3jRtpQV=x6@cfg@!6O1arX?c)ys*&YPBgE$*pO2HZ{f0RLyP7d~o3+0V?ANXa%c z6qDVSu}!VV-L!$-q;mz3XSCb-yA9y%ochQrprLj3-Vi)4`qh56Zi6!~BMUGVXBo0K z=tYienRy6|l?>(*lQz28>ce&p#?mtDn@HP}g3Nj(HQQ)mvoXjoV9*KEn)35}y1+55-+yTkAk6i>|p8 z`*5#1Z9UMGI+;eio-5ZQXZ3`Z897Gu=+Y6WrwUk?yL3)U-aJ^e;IPAPAw%X_HO-WQmKhDZoYv5OFHi096 zLz|w?q2MxBSTEqVIl&-f_VZ&m+XFr+O=!_W4!VJ_V_WicjrnC?HpleaFB^D)29Wxc z6w+aY}#A^Yp+*O!+0BfBSGrm|#$p*W7O`UqO7i#qdv<7^%b*zi@D z|M5sekrc<5Nw-fhYuy0W+!gHhXk?^D>3QpGZtn7`U_h0%;QYKl$HiHYZ`J61*YC~< zSvSXe=ee#T;4>zRD)WOkcmwrEN%KK=$1}D(4eGh=m*X~#AQ&zVE;ELDc|u=f#pE^D zwZ+KxRw_aruMz8I^Yh@xkb5Wc3+=w8UKw@7KF$F$$#B;2JbFUiQ?+qPt2iqhnbt|5 zOYLNGw=pKTgN&fK(1z|=FV);Ez1AlzJAb;uI5OmuNwv*I-+dn_`xI_sSjO5b1|E0Q z=M4KE@{%pYLauXWI6Qc8Ox<$u%ws*?9iNKVthvJ_!Ei?72OE-O=Fy6|nVE{u+ry)A zkORJ`PH|RGiY3LkFC)7yl6S6bHbQNO@B7Y8e3YS9)k#SAO{=v2t|zDk6HbPsV}d;% z;Su%oq^W9P0;&xQvI(5p!!I;O|DfHTsrKE2m*rWfP>V!faWwqxn5+k&{{3TG-%7ZI zLYlYBq#4u)Eq$=TRUS3(u_4J*Pt4gS*T+k~9CXt&S(=$MaiqH+wg$G(@|}dV(UHOK z7tOy8Q~P3#ik#8t)va)GdYmD*N7>8Wtgp*|PP{dqh&k>wBskKBAZygS6gLwmCv9PE zVMj!S2DsGikrAwz!>5!_zvMa|DR(Z9)h&km z<-SVJ%yRNys9(SS+kXd6WY7O0o|}x*T3(Tqo-XFKZv?1ru&2XkU&BbX@k8CmDY%S` zzMFszuQ9f#ipQ*a9*sH}%bwu5k;GP8DU%790kHU}^{j~37uToa$@C0re#cSbH$OO7 z6&)=&D?dOmrrM2*@)eGuMKE@Hro2?2&4PrS5H)qvCY|ZRrU*uCOM|o$tBjF6^$DPX zeF)6C@$d8)F+>X8iu+oQ&%J-zbug1O<8HDZEbJT+I}$C-{Wz~mPVa}D9PN|ZaN@;4 zEV&OKAvTWlzW16jf~fQaoqh++st2nON{~js2T709`-gdYT6m>fKDRWr+hlkQo>(Vm z0co57GS~M*0XM@DnxAe$e~zNBv7KPfXXb0o?2sbYg!NyKM?s%n%!{V`m4xTMEPkKb zQIV1Cw>+5Z>q!$r-bWB&9LVWsf;rt451<4hzhiIcPL&EJ@TLqhTRLI1bK$vqJTmxD zz(^4y^gunq!oU79+bB`}0X^20=IvmuVZi>4FD5QqCeFqA&8N9q2Xdxd0;rH3)r7DTJRnggui$46MGf{4B-zOp?XQWh@*W_xcSz`2_?5C=qtz3neX1<&!pi^t%#PbsI*c*{N{`9`Sf3 z#$T4k*V4LZ4mC?YT-bQq_{`3d%tOI%6i?=a@%*~n3nj(BwO+b=8NOrsG`t~PI*N|F zANVZ^8B16E&_jum{v%;;NU3dvYY0>KVqN_xS1!|Qr;@Uxch66 zl3!U(17H|gU5zbU0gM;@wKp^4u1Y8UeLQ<#(UJFEMv6_Y`&)nlb++3TNyrkS&xO~{ zWl7`l@mYxR5H}keTTxMwXY-kj{Z4KCARFx=H_;cJow#AKb1!f2XIDeJ2lo$UpxsDF zT2`MyfW;yKCmRXQL`+RfncGXpqTHKO&HK#l=_K2v|G?oP&RBj46nc_7u z1b8sL6y@r)!lem9w!6(9YrTIazwc`dnxLJE2X)q#8v3Y}ie895_~ceJ>0@KyT%e_= zrFZt{@8kh25o8iWiG?{Gh!O7;8O)s3H(bTQ?d9O*K*#cC7Ux1jMWpDPM;r>DT?cjR z#YKBRc_=uy?yFE1t+A$z{OUjpM+Usl(l|$PlZ?DyLBTw*5NbBKVR{2S%ltL-?0X$n zr#X@WSO5|Bf7anybA9oiPB!eKZ>--_tc9VBV`M|PwIIh#4kCTYBbXYdc5|8OxgOb& zZUr4-&EFk)x6LW9H+0T674Uq&OWo$Jh?%37r6~hC|4FBb@!M<-;-pg6<6y=2`9$}Q zrCb6PLcBROObCM13huk?_MTW!$k3ruaZKvovl!U!{QR;v#8%H0a9etEadFX7 zWiP>B`D<}HJk04+6BFx%ODrrcZR4D6b=x{5-pCb8I6tlt5kzYIykMGG3*+(9mvm^% zmR>2&*wQ64{=w7SsFC|t-B`p$CC_kEkfXc+mQu%*-CsD!#3=S_+>`W(Mb z?&u{hHI|vD6r05&VL z2%l&qUj{vcs^#aGb}w4LgUl=R(oi=h>a%T zIOw8mft@$tN{DL|Vw~vu)uTXQ1H*S#sP&<75dpaT_6E3NHEM=`xEQ$4boeyh=x@a# z-Oq*fqi5-v{p4@A{vfaT1fLXXwNP|Jr1Z#qmMcti1hDX22qI6 [!IMPORTANT] +> The workspace needs a desktop installed, as the [`codercom/example-desktop`](https://hub.docker.com/r/codercom/example-desktop) image has. + +![Selkies desktop in a Coder workspace](../../.images/selkies-desktop.png) + +`desktop_environment` names a session installed in the workspace (`xfce`, `kde`, `lxqt`, `gnome`, `mate`) or gives a command to run; empty starts the workspace's default desktop. The module's variables are those of the [KasmVNC module](https://registry.coder.com/modules/coder/kasmvnc), so a template swaps one for the other or offers both. + +## Selkies in the Image + +Where the image carries Selkies and Xvfb, the module installs nothing and needs neither `sudo` nor network access, and `install_selkies = false` keeps it that way. Selkies publishes native packages, a Python wheel, and an AppImage; see its [native install guide](https://github.com/selkies-project/selkies/blob/main/docs/native.md). + +```tf +module "selkies" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/selkies-project/selkies/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + desktop_environment = "xfce" + install_selkies = false +} +``` + +## Installing at Start + +Otherwise, the module installs the release's native package, the distribution's Xvfb, and PulseAudio where the workspace has no sound server, as root or with passwordless `sudo`, on the distributions a release publishes packages for (for 2.0.0: Ubuntu 24.04 and 26.04, Debian 12 and 13, Fedora, RHEL 9 and its rebuilds, Alpine, and Arch Linux). `selkies_version` pins a release, and `release_url` points at a mirror laid out like GitHub's releases. + +```tf +module "selkies" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/selkies-project/selkies/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + desktop_environment = "xfce" + selkies_version = "2.0.0" + release_url = "https://artifacts.example.com/selkies/releases" +} +``` + +## Wayland + +`wayland = true` streams through Selkies' Wayland backend instead of an Xvfb. The desktop is then a Wayland compositor, or a desktop that starts one, nested in Selkies' own. + +```tf +module "selkies" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/selkies-project/selkies/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + desktop_environment = "labwc" + wayland = true +} +``` + +## Network Access + +The module contacts the network only when it installs: + +- `/latest`, to resolve the latest release without GitHub's rate-limited API, unless `selkies_version` is set. +- `/download//selkies--`, the package. +- The distribution's package repositories, for Xvfb, PulseAudio, and the package's dependencies. + +Once it runs, the browser reaches Selkies through Coder's proxy on the workspace's loopback addresses; no WebRTC, STUN, or TURN server is involved. + +## Troubleshooting + +The logs are in `~/.coder-modules/selkies-project/selkies/logs/`: `install.log`, `start.log`, and Selkies' own `selkies-session.log`. `coder port-forward --tcp 8080:8080` reaches the same desktop at `http://localhost:8080`, which browsers treat as a secure context for the clipboard, gamepads, and the microphone. diff --git a/registry/selkies-project/modules/selkies/main.test.ts b/registry/selkies-project/modules/selkies/main.test.ts new file mode 100644 index 000000000..321975515 --- /dev/null +++ b/registry/selkies-project/modules/selkies/main.test.ts @@ -0,0 +1,168 @@ +import { afterEach, describe, expect, it, setDefaultTimeout } from "bun:test"; +import path from "node:path"; +import { + execContainer, + readFileContainer, + removeContainer, + runContainer, + runTerraformApply, + runTerraformInit, + type TerraformState, + testRequiredVariables, + writeCoder, + writeFileContainer, +} from "~test"; + +setDefaultTimeout(120_000); + +const IMAGE = "node:22-bookworm-slim"; + +type Variables = Readonly<{ + agent_id: string; + desktop_environment?: string; + port?: number; + install_selkies?: boolean; +}>; + +interface Scripts { + install: string; + start: string; +} + +const scriptsOf = (state: TerraformState): Scripts => { + const scripts: Partial = {}; + for (const resource of state.resources) { + if (resource.type !== "coder_script") { + continue; + } + for (const instance of resource.instances) { + const { display_name: name, script } = instance.attributes as Record< + string, + unknown + >; + if (typeof script !== "string") { + continue; + } + if (name === "Selkies: Install Script") { + scripts.install = script; + } else if (name === "Selkies: Start Script") { + scripts.start = script; + } + } + } + if (!scripts.install || !scripts.start) { + throw new Error("the module must create an install and a start script"); + } + return scripts as Scripts; +}; + +let containers: string[] = []; + +afterEach(async () => { + for (const id of containers) { + await removeContainer(id); + } + containers = []; +}); + +// A workspace with the coder CLI stubbed and, as an image carrying Selkies +// would have them, a launcher and an Xvfb on PATH +const workspace = async (launcher?: string): Promise => { + const id = await runContainer(IMAGE); + containers.push(id); + await writeCoder(id, "#!/bin/sh\nexit 0\n"); + if (launcher !== undefined) { + await writeFileContainer(id, "/usr/local/bin/selkies-session", launcher, { + user: "root", + }); + await writeFileContainer(id, "/usr/local/bin/Xvfb", "#!/bin/sh\n", { + user: "root", + }); + await execContainer( + id, + ["chmod", "755", "/usr/local/bin/selkies-session", "/usr/local/bin/Xvfb"], + ["--user", "root"], + ); + } + return id; +}; + +const mockLauncher = () => + Bun.file( + path.join(import.meta.dir, "testdata", "selkies-session-mock.sh"), + ).text(); + +const run = (id: string, script: string) => + execContainer(id, ["bash", "-c", script]); + +describe("selkies", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { agent_id: "foo" }); + + it("installs nothing where the image has Selkies, and starts it behind the app's port", async () => { + const scripts = scriptsOf( + await runTerraformApply(import.meta.dir, { + agent_id: "foo", + desktop_environment: "xfce", + }), + ); + const id = await workspace(await mockLauncher()); + + const install = await run(id, scripts.install); + expect(install.exitCode).toBe(0); + expect(install.stdout).toContain( + "Selkies and its display server are installed", + ); + + const start = await run(id, scripts.start); + expect(start.exitCode).toBe(0); + expect(start.stdout).toContain("Selkies is ready on port 8080"); + const args = await readFileContainer(id, "/tmp/selkies-session.args"); + expect(args.trim().split("\n")).toEqual([ + "--port=8080", + "--enable-basic-auth=false", + "--enable-https=false", + "--session=xfce", + ]); + + const again = await run(id, scripts.start); + expect(again.exitCode).toBe(0); + expect(again.stdout).toContain("Selkies already answers on port 8080"); + }); + + it("installs nothing when install_selkies is false, and says what is missing", async () => { + const scripts = scriptsOf( + await runTerraformApply(import.meta.dir, { + agent_id: "foo", + install_selkies: false, + }), + ); + const id = await workspace(); + + const install = await run(id, scripts.install); + expect(install.exitCode).not.toBe(0); + expect(install.stdout).toContain( + "The workspace lacks selkies-session Xvfb and install_selkies is false", + ); + }); + + it("reports a Selkies that exits before it answers", async () => { + const scripts = scriptsOf( + await runTerraformApply(import.meta.dir, { + agent_id: "foo", + port: 8081, + }), + ); + const id = await workspace( + "#!/bin/sh\necho 'Xvfb did not come up' >&2\nexit 1\n", + ); + + // The install script runs first, as Coder orders them, and lays out the module's directory + expect((await run(id, scripts.install)).exitCode).toBe(0); + const start = await run(id, scripts.start); + expect(start.exitCode).not.toBe(0); + expect(start.stdout).toContain("Selkies exited before it answered"); + expect(start.stdout).toContain("Xvfb did not come up"); + }); +}); diff --git a/registry/selkies-project/modules/selkies/main.tf b/registry/selkies-project/modules/selkies/main.tf new file mode 100644 index 000000000..81c7591c5 --- /dev/null +++ b/registry/selkies-project/modules/selkies/main.tf @@ -0,0 +1,149 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.13" + } + } +} + +variable "agent_id" { + description = "The ID of a Coder agent." + type = string +} + +variable "port" { + description = "The port Selkies listens on, on the workspace's loopback addresses." + type = number + default = 8080 + + validation { + condition = var.port >= 1 && var.port <= 65535 && floor(var.port) == var.port + error_message = "port must be an integer between 1 and 65535." + } +} + +variable "desktop_environment" { + description = "The desktop to start: a session installed in the workspace, by name (xfce, kde, lxqt, gnome, mate), or a command. Empty starts the workspace's default desktop." + type = string + default = "" +} + +variable "wayland" { + description = "Stream through Selkies' Wayland backend instead of an Xvfb." + type = bool + default = false +} + +variable "install_selkies" { + description = "Install what the workspace lacks of Selkies and Xvfb, as root or with passwordless sudo. Set to false when the image carries both." + type = bool + default = true +} + +variable "selkies_version" { + description = "The Selkies release to install, such as 2.0.0. Empty installs the latest." + type = string + default = "" + + validation { + condition = can(regex("^([0-9A-Za-z][0-9A-Za-z._-]*)?$", var.selkies_version)) + error_message = "selkies_version must be a release tag such as 2.0.0, or empty for the latest." + } +} + +variable "release_url" { + description = "Where Selkies' releases are downloaded from: GitHub's, or a mirror laid out the same way (/download//). A mirror without a /latest redirect needs selkies_version." + type = string + default = "https://github.com/selkies-project/selkies/releases" + + validation { + condition = can(regex("^https?://[^\\s'\"]+[^/\\s'\"]$", var.release_url)) + error_message = "release_url must be an http(s) URL without quotes or a trailing slash." + } +} + +variable "order" { + description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)." + type = number + default = null +} + +variable "group" { + description = "The name of a group that this app belongs to." + type = string + default = null +} + +variable "subdomain" { + description = "Is subdomain sharing enabled in your cluster?" + type = bool + default = true +} + +variable "share" { + description = "Who can open the app: the workspace's owner, any authenticated user, or anyone." + type = string + default = "owner" + + validation { + condition = var.share == "owner" || var.share == "authenticated" || var.share == "public" + error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'." + } +} + +locals { + icon = "/icon/selkies.svg" + module_directory = "$HOME/.coder-modules/selkies-project/selkies" + + install_script = templatefile("${path.module}/scripts/install.sh.tftpl", { + ARG_INSTALL = tostring(var.install_selkies) + ARG_WAYLAND = tostring(var.wayland) + ARG_VERSION = var.selkies_version + ARG_RELEASE_URL = var.release_url + }) + + start_script = templatefile("${path.module}/scripts/start.sh.tftpl", { + ARG_PORT = tostring(var.port) + ARG_WAYLAND = tostring(var.wayland) + ARG_DESKTOP_B64 = base64encode(var.desktop_environment) + }) +} + +module "coder_utils" { + source = "registry.coder.com/coder/coder-utils/coder" + version = "0.0.2" + + agent_id = var.agent_id + module_directory = local.module_directory + display_name_prefix = "Selkies" + icon = local.icon + install_script = local.install_script + start_script = local.start_script +} + +# Selkies derives its path prefix from the URL it is loaded from, so a path app works as well as a subdomain. +resource "coder_app" "selkies" { + agent_id = var.agent_id + slug = "selkies" + display_name = "Selkies" + url = "http://localhost:${var.port}" + icon = local.icon + subdomain = var.subdomain + share = var.share + order = var.order + group = var.group + + healthcheck { + url = "http://localhost:${var.port}/api/health" + interval = 5 + threshold = 6 + } +} + +output "scripts" { + description = "Ordered list of coder exp sync names for the scripts this module runs: the install script, then the start script." + value = module.coder_utils.scripts +} diff --git a/registry/selkies-project/modules/selkies/main.tftest.hcl b/registry/selkies-project/modules/selkies/main.tftest.hcl new file mode 100644 index 000000000..4745486f4 --- /dev/null +++ b/registry/selkies-project/modules/selkies/main.tftest.hcl @@ -0,0 +1,197 @@ +mock_provider "coder" {} + +run "defaults" { + command = plan + + variables { + agent_id = "agent" + } + + assert { + condition = coder_app.selkies.url == "http://localhost:8080" + error_message = "The app must reach Selkies on the default port." + } + + assert { + condition = coder_app.selkies.slug == "selkies" && coder_app.selkies.display_name == "Selkies" && coder_app.selkies.icon == "/icon/selkies.svg" + error_message = "The app must be named and branded Selkies." + } + + assert { + condition = coder_app.selkies.subdomain && coder_app.selkies.share == "owner" + error_message = "The app must default to a subdomain its owner alone opens, as the KasmVNC module's does." + } + + assert { + condition = local.module_directory == "$HOME/.coder-modules/selkies-project/selkies" + error_message = "The module must keep its data under the standard per-module root." + } + + assert { + condition = strcontains(local.start_script, "--enable-basic-auth=false --enable-https=false") && !strcontains(local.start_script, "--public") + error_message = "Selkies must listen on the loopback addresses alone, leaving the login and TLS to Coder." + } + + assert { + condition = strcontains(local.start_script, "ARG_DESKTOP=$(echo -n '' | base64 -d)") + error_message = "An empty desktop_environment must start the workspace's default desktop." + } + + assert { + condition = strcontains(local.install_script, "ARG_INSTALL='true'") && strcontains(local.install_script, "ARG_VERSION=''") + error_message = "The module must install the latest release where the workspace lacks Selkies." + } + + assert { + condition = strcontains(local.install_script, "ARG_RELEASE_URL='https://github.com/selkies-project/selkies/releases'") && !strcontains(local.install_script, "api.github.com") + error_message = "The latest release must be resolved from GitHub's releases redirect, not its rate-limited API." + } +} + +run "health_check_and_scripts" { + command = apply + + variables { + agent_id = "agent" + } + + assert { + condition = one(coder_app.selkies.healthcheck).url == "http://localhost:8080/api/health" + error_message = "The health check must use Selkies' /api/health endpoint." + } + + assert { + condition = length(output.scripts) == 2 + error_message = "The module must run an install script and then a start script." + } +} + +run "kasmvnc_module_variables" { + command = plan + + variables { + agent_id = "agent" + desktop_environment = "xfce" + port = 6800 + subdomain = false + share = "authenticated" + order = 3 + group = "Desktops" + } + + assert { + condition = coder_app.selkies.url == "http://localhost:6800" && !coder_app.selkies.subdomain && coder_app.selkies.share == "authenticated" + error_message = "The app must follow the port, subdomain, and share variables." + } + + assert { + condition = coder_app.selkies.order == 3 && coder_app.selkies.group == "Desktops" + error_message = "The app must keep its order and group." + } + + assert { + condition = strcontains(local.start_script, base64encode("xfce")) && strcontains(local.start_script, "ARG_PORT='6800'") + error_message = "The start script must start the named desktop on the configured port." + } +} + +run "desktop_command" { + command = plan + + variables { + agent_id = "agent" + desktop_environment = "startxfce4 --replace 'now'" + } + + assert { + condition = strcontains(local.start_script, base64encode("startxfce4 --replace 'now'")) + error_message = "A desktop command must reach the start script intact, quotes included." + } +} + +run "wayland" { + command = plan + + variables { + agent_id = "agent" + wayland = true + } + + assert { + condition = strcontains(local.start_script, "ARG_WAYLAND='true'") && strcontains(local.install_script, "ARG_WAYLAND='true'") + error_message = "The Wayland backend must reach both scripts, so neither starts nor installs an Xvfb." + } +} + +run "image_carries_selkies" { + command = plan + + variables { + agent_id = "agent" + install_selkies = false + } + + assert { + condition = strcontains(local.install_script, "ARG_INSTALL='false'") + error_message = "install_selkies = false must keep the install script from installing." + } +} + +run "pinned_release_from_a_mirror" { + command = plan + + variables { + agent_id = "agent" + selkies_version = "2.0.0" + release_url = "https://artifacts.example.com/selkies/releases" + } + + assert { + condition = strcontains(local.install_script, "ARG_VERSION='2.0.0'") && strcontains(local.install_script, "ARG_RELEASE_URL='https://artifacts.example.com/selkies/releases'") + error_message = "The install script must download the pinned release from the mirror." + } +} + +run "rejects_an_invalid_share" { + command = plan + + variables { + agent_id = "agent" + share = "everyone" + } + + expect_failures = [var.share] +} + +run "rejects_a_version_that_is_not_a_tag" { + command = plan + + variables { + agent_id = "agent" + selkies_version = "2.0.0'; true" + } + + expect_failures = [var.selkies_version] +} + +run "rejects_a_release_url_with_a_trailing_slash" { + command = plan + + variables { + agent_id = "agent" + release_url = "https://github.com/selkies-project/selkies/releases/" + } + + expect_failures = [var.release_url] +} + +run "rejects_a_port_out_of_range" { + command = plan + + variables { + agent_id = "agent" + port = 70000 + } + + expect_failures = [var.port] +} diff --git a/registry/selkies-project/modules/selkies/scripts/install.sh.tftpl b/registry/selkies-project/modules/selkies/scripts/install.sh.tftpl new file mode 100644 index 000000000..4cf2acf36 --- /dev/null +++ b/registry/selkies-project/modules/selkies/scripts/install.sh.tftpl @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +# Install what the workspace lacks of Selkies and its display server: the +# release's native package and the distribution's Xvfb, with PulseAudio where +# no sound server is installed, as root or with passwordless sudo. A workspace +# that has Selkies and its display server installs nothing. +if [ -r /etc/os-release ]; then + # shellcheck source=/dev/null + . /etc/os-release +fi + +set -euo pipefail + +ARG_INSTALL='${ARG_INSTALL}' +ARG_WAYLAND='${ARG_WAYLAND}' +ARG_VERSION='${ARG_VERSION}' +ARG_RELEASE_URL='${ARG_RELEASE_URL}' + +missing=() +command -v selkies-session > /dev/null || missing+=(selkies-session) +[ "$ARG_WAYLAND" = true ] || command -v Xvfb > /dev/null || missing+=(Xvfb) +if [ $${#missing[@]} -eq 0 ]; then + echo "Selkies and its display server are installed" + exit 0 +fi +if [ "$ARG_INSTALL" != true ]; then + echo "The workspace lacks $${missing[*]} and install_selkies is false: install them in the image" >&2 + exit 1 +fi + +sudo=() +if [ "$(id -u)" -ne 0 ]; then + if ! sudo -n true 2> /dev/null; then + echo "Installing $${missing[*]} needs root or sudo without a password: install them in the image instead" >&2 + exit 1 + fi + sudo=(sudo) +fi + +if command -v apt-get > /dev/null; then + platform="$${ID:-}$([ "$${ID:-}" = ubuntu ] && echo "$${VERSION_ID:-}" || echo "$${VERSION_CODENAME:-}")-$(dpkg --print-architecture).deb" + apt=("$${sudo[@]}" env DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=300) + "$${apt[@]}" update -qq + install_file=("$${apt[@]}" install -y) + install_repo=("$${apt[@]}" install -y --no-install-recommends) + xvfb=xvfb +elif command -v dnf > /dev/null; then + platform="$([ "$${ID:-}" = fedora ] && echo fc || echo "el$${VERSION_ID%%.*}")-$(uname -m).rpm" + install_file=("$${sudo[@]}" dnf install -y) + install_repo=("$${sudo[@]}" dnf install -y) + xvfb=xorg-x11-server-Xvfb +elif command -v apk > /dev/null; then + platform="$(uname -m).apk" + install_file=("$${sudo[@]}" apk add --allow-untrusted) + install_repo=("$${sudo[@]}" apk add) + xvfb=xvfb +elif command -v pacman > /dev/null; then + platform="$(uname -m).pkg.tar.zst" + install_file=("$${sudo[@]}" pacman -U --noconfirm) + install_repo=("$${sudo[@]}" pacman -S --noconfirm --needed) + xvfb=xorg-server-xvfb +else + echo "No package manager Selkies publishes packages for (apt, dnf, apk, pacman): install Selkies in the image instead" >&2 + exit 1 +fi + +if ! command -v selkies-session > /dev/null; then + command -v curl > /dev/null || "$${install_repo[@]}" curl + version=$ARG_VERSION + if [ -z "$version" ]; then + # The latest release's tag, from the redirect GitHub answers without its rate-limited API + latest=$(curl -fsSLI -o /dev/null -w '%%{url_effective}' "$ARG_RELEASE_URL/latest") || latest= + version=$${latest##*/} + if [ -z "$version" ] || [ "$version" = latest ]; then + echo "Could not resolve the latest release at $ARG_RELEASE_URL: set selkies_version" >&2 + exit 1 + fi + fi + file="selkies-$version-$platform" + work=$(mktemp -d) + trap 'rm -rf "$work"' EXIT + # The package manager reads the file as an unprivileged user of its own + chmod 755 "$work" + echo "Installing Selkies $version ($file)" + if ! curl -fsSL -o "$work/$file" "$ARG_RELEASE_URL/download/$version/$file"; then + echo "Selkies $version publishes no $file for $${PRETTY_NAME:-this workspace}: install Selkies in the image instead" >&2 + exit 1 + fi + "$${install_file[@]}" "$work/$file" +fi + +if [ "$ARG_WAYLAND" != true ] && ! command -v Xvfb > /dev/null; then + echo "Installing Xvfb" + "$${install_repo[@]}" "$xvfb" +fi +if ! command -v pulseaudio > /dev/null && ! command -v pipewire-pulse > /dev/null; then + echo "Installing PulseAudio" + "$${install_repo[@]}" pulseaudio +fi +echo "Selkies and its display server are installed" diff --git a/registry/selkies-project/modules/selkies/scripts/start.sh.tftpl b/registry/selkies-project/modules/selkies/scripts/start.sh.tftpl new file mode 100644 index 000000000..f020bcf44 --- /dev/null +++ b/registry/selkies-project/modules/selkies/scripts/start.sh.tftpl @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Start Selkies with the workspace's desktop behind the app's port, unless it +# already answers. Coder authenticates the app and terminates TLS, so Selkies +# listens on the loopback addresses without a login or TLS of its own. +set -euo pipefail + +ARG_PORT='${ARG_PORT}' +ARG_WAYLAND='${ARG_WAYLAND}' +ARG_DESKTOP=$(echo -n '${ARG_DESKTOP_B64}' | base64 -d) +LOG_PATH="$HOME/.coder-modules/selkies-project/selkies/logs/selkies-session.log" + +# A GET of /api/health over bash's own TCP, so the check needs no curl +healthy() { + ( + exec 3<> "/dev/tcp/127.0.0.1/$ARG_PORT" + printf 'GET /api/health HTTP/1.0\r\nHost: localhost\r\n\r\n' >&3 + read -r -t 5 _ status _ <&3 + [ "$status" = 200 ] + ) 2> /dev/null +} + +if healthy; then + echo "Selkies already answers on port $ARG_PORT" + exit 0 +fi + +mkdir -p "$(dirname "$LOG_PATH")" +args=(--port="$ARG_PORT" --enable-basic-auth=false --enable-https=false) +[ -z "$ARG_DESKTOP" ] || args+=(--session="$ARG_DESKTOP") +SELKIES_WAYLAND="$ARG_WAYLAND" nohup selkies-session "$${args[@]}" >> "$LOG_PATH" 2>&1 & +pid=$! +echo "Starting Selkies on port $ARG_PORT; its log is $LOG_PATH" +for _ in $(seq 1 60); do + if healthy; then + echo "Selkies is ready on port $ARG_PORT" + exit 0 + fi + if ! kill -0 "$pid" 2> /dev/null; then + echo "Selkies exited before it answered; the end of its log:" >&2 + tail -n 20 "$LOG_PATH" >&2 + exit 1 + fi + sleep 1 +done +echo "Selkies did not answer on port $ARG_PORT within 60 seconds; see $LOG_PATH" >&2 +exit 1 diff --git a/registry/selkies-project/modules/selkies/testdata/selkies-session-mock.sh b/registry/selkies-project/modules/selkies/testdata/selkies-session-mock.sh new file mode 100755 index 000000000..174cee8ac --- /dev/null +++ b/registry/selkies-project/modules/selkies/testdata/selkies-session-mock.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Stands in for selkies-session: records how it was started, and answers +# /api/health on the port it was given, as Selkies does +printf '%s\n' "$@" > /tmp/selkies-session.args +printf 'SELKIES_WAYLAND=%s\n' "${SELKIES_WAYLAND:-}" > /tmp/selkies-session.env +port=8080 +for arg in "$@"; do + case "$arg" in --port=*) port=${arg#--port=} ;; esac +done +exec node -e ' + require("node:http") + .createServer((req, res) => { + res.writeHead(req.url === "/api/health" ? 200 : 404); + res.end(); + }) + .listen(Number(process.argv[1]), "127.0.0.1"); +' "$port"