From 96fbc006c3305ea6e407e5ef8dab4ee99822d32a Mon Sep 17 00:00:00 2001 From: trick-1 Date: Fri, 22 May 2026 09:27:28 +0000 Subject: [PATCH 1/2] Add PrivateAI Documentation --- PrivateAI/gpu-cuda-install/images/img01.png | Bin 0 -> 160982 bytes PrivateAI/gpu-cuda-install/images/img02.png | Bin 0 -> 221064 bytes PrivateAI/gpu-cuda-install/readme.md | 618 +++++++++ PrivateAI/localai/docker-compose.yaml | 86 ++ PrivateAI/localai/env | 7 + PrivateAI/localai/readme.md | 730 ++++++++++ PrivateAI/mcphub/Dockerfile | 24 + PrivateAI/mcphub/docker-compose.yaml | 31 + PrivateAI/mcphub/entrypoint-proxy.sh | 62 + PrivateAI/mcphub/env | 6 + PrivateAI/mcphub/mcp_settings.json | 376 +++++ PrivateAI/mcphub/mcphubreadme.md | 1220 +++++++++++++++++ PrivateAI/mcphub/nodefetch.sh | 12 + PrivateAI/mcphub/proxychains.conf | 17 + PrivateAI/mcphub/readme.md | 288 ++++ PrivateAI/mcphub/repo_scan_job_mcp.js | 669 +++++++++ PrivateAI/mcphub/repo_scan_job_mcp.md | 1052 ++++++++++++++ PrivateAI/openwebui/caddy/Caddyfile | 16 + PrivateAI/openwebui/caddy/Caddyfile.orig | 3 + .../caddy/caddy_config/caddy/autosave.json | 1 + .../caddy/caddy_data/caddy/instance.uuid | 1 + .../caddy/caddy_data/caddy/last_clean.json | 1 + PrivateAI/openwebui/caddy/certs/local.crt | 19 + PrivateAI/openwebui/caddy/certs/local.key | 28 + PrivateAI/openwebui/docker-compose-entra.yaml | 88 ++ PrivateAI/openwebui/env | 7 + PrivateAI/openwebui/readme.md | 247 ++++ PrivateAI/private-ai-readme.md | 1103 +++++++++++++++ PrivateAI/semgrep/docker-compose.yaml | 16 + PrivateAI/semgrep/readme.md | 426 ++++++ 30 files changed, 7154 insertions(+) create mode 100644 PrivateAI/gpu-cuda-install/images/img01.png create mode 100644 PrivateAI/gpu-cuda-install/images/img02.png create mode 100644 PrivateAI/gpu-cuda-install/readme.md create mode 100644 PrivateAI/localai/docker-compose.yaml create mode 100644 PrivateAI/localai/env create mode 100644 PrivateAI/localai/readme.md create mode 100644 PrivateAI/mcphub/Dockerfile create mode 100644 PrivateAI/mcphub/docker-compose.yaml create mode 100755 PrivateAI/mcphub/entrypoint-proxy.sh create mode 100644 PrivateAI/mcphub/env create mode 100755 PrivateAI/mcphub/mcp_settings.json create mode 100644 PrivateAI/mcphub/mcphubreadme.md create mode 100644 PrivateAI/mcphub/nodefetch.sh create mode 100644 PrivateAI/mcphub/proxychains.conf create mode 100644 PrivateAI/mcphub/readme.md create mode 100644 PrivateAI/mcphub/repo_scan_job_mcp.js create mode 100644 PrivateAI/mcphub/repo_scan_job_mcp.md create mode 100644 PrivateAI/openwebui/caddy/Caddyfile create mode 100644 PrivateAI/openwebui/caddy/Caddyfile.orig create mode 100644 PrivateAI/openwebui/caddy/caddy_config/caddy/autosave.json create mode 100644 PrivateAI/openwebui/caddy/caddy_data/caddy/instance.uuid create mode 100644 PrivateAI/openwebui/caddy/caddy_data/caddy/last_clean.json create mode 100644 PrivateAI/openwebui/caddy/certs/local.crt create mode 100644 PrivateAI/openwebui/caddy/certs/local.key create mode 100644 PrivateAI/openwebui/docker-compose-entra.yaml create mode 100644 PrivateAI/openwebui/env create mode 100644 PrivateAI/openwebui/readme.md create mode 100644 PrivateAI/private-ai-readme.md create mode 100644 PrivateAI/semgrep/docker-compose.yaml create mode 100644 PrivateAI/semgrep/readme.md diff --git a/PrivateAI/gpu-cuda-install/images/img01.png b/PrivateAI/gpu-cuda-install/images/img01.png new file mode 100644 index 0000000000000000000000000000000000000000..916ebb8d9726803adeb1ba8e0dd67cf690e3dd89 GIT binary patch literal 160982 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 literal 0 HcmV?d00001 diff --git a/PrivateAI/gpu-cuda-install/images/img02.png b/PrivateAI/gpu-cuda-install/images/img02.png new file mode 100644 index 0000000000000000000000000000000000000000..30343a788d19a0dec7d9020f9c68f6f4982be442 GIT binary patch literal 221064 zcmeFZbySsa*EI?VQX(iN4F)M8-JMEGOV=hhAl(QENC*hh-O}A40wN*ZozmS6-?e?- z=lz~@4*bp;XPiIIAFpEoTe$ZfSFAPHoOA5}d07bzG(t2uI5-R`Nzu1(aQ7kL3k~Hy z_)T~45H}p$16flM5qT*Q5psDOD~PFu5geRkK%^?NnqmiDlEzyp6s%7lzV^kFKYj7x zE7m)>H8HHnFhrCWzdokuOW@;b)_u|yQx(FQL~GX<(XuyE*eoQ zO+H-rUVdt$u~|I5=aw&t_2gm6qn`u=Q|+JN!@509qHNVvUlk!=UATO}>v3`vj;p3E%woc|hO(pk^>ke>yN(`tm zIkpJDFolI{_3(Xt8o8ylg%H1Hf|+~0S`-;c(J^*WJKjgJysM@YACw>Wygp!jEza`E zAt#cYs3oeMPpqYSg7Sgb5aE?IvF?3|=SL@!^K1`syagy8-)rrw{&21Riu%Llie7$T z#Dn+idC%IPtSaXG`Ot>7;$+QXuym&h1hgtPA zO4q-*Y)opCcO;k%j@^OS{O7xpbXwl>1=6FU>ZWo1i>nSV?v~N6QL4PojOLn=n%`#; zwTwEv&vYsXoE42&mcqO8-PrLxUj4NAuC&S;xV5mkaR0;lMY=~rL*Uv?A$k5t(Nyy+ z{4Uqb=;+CyD#Iox95U}5-F&-hPt-jDdP2Aao~KHk=tIPXwODiUKJbpo@J#S{Kk$An zB20O4>>>-1KM?hD3VT2)vQsw49qqHG%dQ3&C`6-%I^u;^jAVg4tjoxWLMX_>iRy75 z|Hlh!j48joY;4|pd~+D5pU6>AQ=duygnK2HkA?gbX`cMMB-UfEpHDj^3Bv;zsBg)H>z(hm&_;j6D$*h0gcrYv>#Gl@x-YvP*#6{vf*I8Ow)lZ`NO$Y&V&;6b zyq35*+wb1?y3=E>37=Trvu~rQT2m)|K;JVx)pR4!_EKFuIyJpS^B~)4YQ$PaeCB-SrQKhbA?70($|9b6qy9qQygChI1- z`QnS@{pZNxm^6XU$(JGmrHCY5CEKK;r4ln74KYe-#(jyUWhvfti2Y&Sx7>fVZ?uoS zFA<2>8n_@9koJk1g@%rrhg#-q&{x?`iCp$!zjcw%*$_p1`QJJALo7o)L#l*?gv*3U zgs%u6e?B8zBD`ShF`XK9k{e7NA9N~LiVLqys0?!Ob_hBSl+D%{yjr6=%7;dCEEV zq8olHMeK^M!46*?l8%|TrYBY^lGnrR^6kCtM7e|PDmM}aoZElK;7OAzwrjReHSxA= z4!u~PSlzcTvwvfMvR=7%yt=kpv;JbeyDx8uCwH`|S#9JcKV5W%w|$s>;*s9RJ(O*f zy-%6X*2OhR_f$MHzBhhXYZUcNzGl0^yl>${?nCUOd>`Y!BgT6X*On(OYku(8ip(x_r)J%3okoM_|uM(YU1h#d3~ddlw0Ucv5Yu~z9( zIcSk*Q8e*ri_?M2A%2r#JpI`6Sp7Ki7@tHQ*%3LQh0_06;iE!QdPaJ#__TEfmkkLz zu{p^OPf5blgun#Cgrv|);zVYb;nlMAM$S5}749AD?|kCM(ql1cuVf6gYsczPv}m;o zLo!1#NO`O*7RT@l9~SD5b{BF?$5<{-7gw1)Ts zQ5kU>ogK61K_SLA>U;E9T$`s~u?R_PX%Fwu#xVXF-F0o|ZC?JE_%PeI%~$oKz^CHS zFbUSr5^esUby%)tGn6w}ODWJq_tHm1r$1K)B;j2tK6pjRoyENTeluolyu^4h=Dp&A zLt8@t<|my;o;*$LLa}|}?UK?m`BHB2I5FkiIR?$EsJ4`vlI+q^&pEQf-ab}z$U2mL z5La!fZ=9=P_I~|Hiai1%%`Gr)ZL|F+UGa$`qT<8MM+4yl4%zOHMNxAR8`drlKdw3epG}f(6QU0mbk|{NM$PnQH|13QCF$@NR<;--~Oiq=?F zR>$(~_IF+z?RPVeaza=oTwHQHh6ofVjUJZjtGBThnQmkqwYMzs;C|8%PkW_Q^Ko{PvX4ld$2_?Kv2N4Wz3LxmcdlL>#u(Lay~z598!kg^Nd&GR=CQ6XBjdTY^-6*?F*doqnOlcP|hulBEEc78YRXe5=?Lwzp-yXgYxGU@dJcHHotY%fK)0?#@OpO;BR+i}FDmq45%9V-L4Z0VLBmq1<*FVlu zA=&di1BuCwQjYdYZ7Ruyd*5=#q2;b(^zZWWw!Xv7bCHIgZ>$zKFcI z;S6(_J8#wd-_z#YRJBaq zc1fWjoi0Qnl%9LUv3}QS2X#DPJ0g~ftWw&v#@(zrCokJTwki=-z;=6OE?F*dDT&Xc zXGQbCx#&Q`WBzi$y6H;TJx^m}UT4~aZ)$l)#j}4=fAK}{8=0-eiBPTMI=B7TbCJnK z`$gK-c+Le^F3m^r9B-}crf{a+T)tv3-X@sNJD=V@aVOa( zk=Q|=v2w>cnL8GlZ!r$V3FWtEaFO=ZJpOqo)vXwv-L2K66@49ZDzU;cUKTpt>S=aU zvsbfqKz6f#lUHFZEVqJw7W6&(w>Clj#L>i&{TnO0odynPEB^2F&c?fi=X@(y`a_#-C zxBLp_k}98HB{x%c0%VdV~-kfMl`6!@uVU}I!tX=`F-ca4mq4t{}ZEvaq`2Z#3p_60BX z_W3S2|Cp(gnw^@AG`E2jltItXO5cdV32F^H4;-%(H+Ts(veP4Xf?8PGay#)o{d)vA zcn$kC<5Tj#huE3(JynyDCl|4@F(PMYU}9i;%8y1)PR?s%2;qJ!D*j(@2mj-HYGP++ z&CST@=;+Ac$iiS{W6b!9i;IhqiJ6g^nI0TLZ|iJnr{_d(Y5VM7C;9hzM2&0>Y)q}~ zOsy=*VdvG;x3ah6d-@c1q5uB(ulqD|GX39Ivb6oL#{v(?2>T1;D+VUU|2{W(D=+M~ z-14SQMi%O#rcf|u;2Qj|*jU+l|Gwe>@~8iO$^Uq(+JC&2orUv1-})ba`ai!_+1AKL z#0m;7YRCV-C+xr8{GWgPuQ&2C!k+y<#^PTC{r9(Er1{Z!8UK6M_|Y^ldN0955}S(3 zDS@A0nZf?ugDpntfBgin;rYCH3~^N8-~{2MM1_=`;5VmH6J(BV+V*|px;Ctz9f3??A$nRFLou`M#t8$ z*ltgap6vBaxgH-}&hYS&uJ9%AaO9~Jyqo;&2Zw+{{^71~@^c94h%88!U8q=saPWwC zeWMa&YC(p-`|pTY)X(9)V}-q!S^wc;AK;oEB>&S%1XDoK4?#nX8@&6D!J{CuxZS_& zPGOJWCH$B?hUU4+jj7p4zUfM$W0D4|{kqb{LJ;F+p6wBLWZUcIpZGd%C0pMwwks{C zxn1`=s3OLFq5p8wIZtp|1u|c=;oR{T(VF_uiSiPBZKtLr*X^2b^!qoJ?-zfu>;FV? zOu4?gm}KeYjac!?+E?q){K>OZZ-7i<7e=pDJ7i}`_z$!3oKcVo+i1AK)v@k)!u;7A zB32V!I@QA9db!phB|n-RZk6d;`vRq$x0Ob}@h;~5$@FI^o1~@V+5-*gpYiM>+%>f1 z24GavYF#?N(qzpy)jN!8Io5O%?6}izhKl+nX*(cbw7IM8)sQmyK3$akEyr-`eLJwjg@P{%lCjH(GdB zf^^q23%m*Re#V^hC#$WW?s|)A>nbbGuNoHgbG|8Mp*s6Y>-xtNz0QQ zE(yoL26uSkUK$k;`V;TYrPeC*L3i1c?_H&ZdOW{-?N;?-$nkdF&V>8Pv~jY>d5)2h z(KwhQgR6^^Ui;Z-Mx7a2rEj{wqZxZEt+boJDv{DnwqEYYlx%ouWkbNY3uQ~1 zjWg73E>ym0t>@<+Ss%`WPF6x+^%h(gXx5Z&q;E}DwM{W_(1lk#;D&UBznBiQF(1h< z@w~a>Fu{(ZxN8e$l)yE0-EbFyld)UR37%~4|7NU0#iktl^yrzUSm#1(P=R_Gj!2U2Atsi`v9LSl zRf~8CvFWVmjl1Ej8e<6eI`zgxMIMiMYas441nmWm^#c;_Atb|+V-gO_gh(32U*E5e zH8!T}sz|JVz>lPgAdl+A(Mh)T*ShXBjC!2T`?u?@j~4AzjJbF|tWl&y9krTC5INvO`6RR%q2Up866dB!ur+4WE$JVvMPD=0;er4GCXU zx6HWrm@%YY1@1kFDigl=+E3K+4uzY3PCL$S38$|fp=l`P9GtBrYxE}ZLAzpJRbFh@ zNmxNNDlK05Qbkb7Rhk#p{DQ_Cq~FlXV=ZB(>qA#`lJMVbG4dQWHpe@=`!HnJ%d=VRQ=nBhf&@3i5T~*7o3Zh5CJw)A^%}>I*zPO8dfZhPo~!)-Oo)cZNBX&tPMEYNe*ODea|Xtz-DIYP%kf z8&9`f9c_%(>Ao1LC(AA7JQ`A*wmewwX7R=DpWFtMc08hCRpV@2zle;)lEWM2m?SC? zk%-@l4zc6y?y#A*AFXlSC({vXQIz9bnkv`y3iQVHG@q%j0kN-(e!z{{IN`k?y=HY^ zV*t9Nw&#^S$52e}=N_&VVh+(b&u5G={mj0Cgb%v}aN={NNL^Mb%ts5g467Qi&wuyu zolFty^&gGu?Btr2r{>yigSgkuU*r5GIJ21_13No)bm-D6MW;gkHIpU1ap%Jt)}*+pD9-1 zn6mbvq5fnEM%jLtG)_`Gk}NyXA+9@FK){9Ng#Vnv#f~YW(*Q|ZFxa=_BkII3Sr_ls z;o$L{59WQ+5{In`cGH39RomShvv%+yo+8SD-v|xCYPPIq>6XGC!kIIP;Tm6`nyz!~ zt&A0jQTV}b)cMt7<4Am%>UycfFB9a-gzh-D-^#M4+JwGh&ZZq|W@E3cQ0jKRU-bCO zmZF%U8L)nhpO7GPH-x2s*R3mw`G|9&NEX6Q>10KWX>pOef(=Tt;4frw*c)HRy_`{?hX&S@tHiO1#PTGZ+jrisM<`ZNaPp>*iEdLR&6|sp)vyQOA)1 zgDomLDmc?mN%2E=ma`MwR6lw@9)LY+Sco?WJJi8 z*4VCyv!8g!isCZlvW^23usN#ZS%EFfNm1%~7^JH0+#1Jfy3kCftgw{%!hj3lIS#Ur zkfQlcZ*RPt8aAca+=3x)`}IG|X?sO0Y5fdJs2V!#)4{x1WK)iF(i8o=d|T`MR_$TSw+$T< z#h_*OnTR8VU%K)njw4gA#gEH77Ho}iu!f1pRpwvUFJnq_+pW&|eLko>@g`7C__qIC z<-j{u$K$LO;|$llt{Dxo+!bp;$Su>dLWA;pi1p)S#i;fM`(QmkA!*2+=snB>fq%7?=f2Of^gFt0yv(@nVi?ODENRPT*U@6*+^M&) zJ+xV~lJuq;lju%x{!xxR-6yt<%yUYXYw4@zNuVEnJeMuRurXTOr2`i5XUJSPOOC+- zq9aHRu-Lg((ne~V_piHJohpgKg)rF;E32yS%rs1Ant**zfn8*}jl;M(P|pVPy0PJb z-2;f-9$`p<-P*vmHY=}on#0*r^dtty)Ev8FF`L=YH_^Ic(mRpxM+5?*u1ydZ+1=0W z=rk(xH}I^!m8!XC=+&`i2TttHw`^5TS_WCy{5D|EGL(m-hf>P2#F* z&Z~$agTSuRS7SgA8^Z+Ko)V9%^O`rPgoF$uZ1?HqMs=45s2_RXm zzAdq+GXeZW9!J?H5HW{8cVpsjh|KIU{ksYvxHfJ)@I~}N#tfN!rLL}SX&C`_Z*9N? z?vTYy{rJ{mGRsmEG8wCk3Rw{iA{ZdE{w8VKP~08Dv{BeNwo~}W&>9WRfCV5aiR&)Y zQ4elSJei|gA_Gs$3)gNIOlhHMAqf%}2#DqM>ZQ+3SnkHDA2rDhd(G;s-NGbJSNmY(+rI|x zux7C#nH?Gjn|e?+iQ~iloH$N&oGI~n$lVUxPiWZ-Y`v!-iL?2>x@HF(Jx?Ls4}job z%0%>P#XlOkCpSPOQ$JcCo-PGd*Lapp{L_t=Ctc4CoX^*?CtENDHn$hSce_ZH^OyH4 zKo$HK?4u!rBx%lbv03KBZa{EnfFMLMG-OgNgLJpc%Xi3rFZ_O}>LMM-+QHCpI3S5{ z?o^f@!rj?$vsu=gub3_S#aW^LbFNa3zE$H@C9IsBNh59wR>{~``-1(#X>U2+>Rp4& z(UeM@$@RrfBj(gI-ngXoYHufvE_K@ zY0WPucM0!Y{KNsE#31qgDJNg&L$f2V&}UJO9dKxZ@~nL6H;u*h4Qe1cc{A2o1sx=h5t zCUeIS+QCIMF{uQo6rxd}H)`2%G}2xQo|}8Fux{JT=RQU$$ljtMWQW2c(OT|D!wjo| z+GJpM)!$6NsdLzpwj~y;lbFrUHFr7QG$q7is7-wTivSdIAKDiH-0xAU5V<=+12lrf z61OO3oBWL?Wo@d)w$Pm#16 z@(6TP*8Et%aoXUl8pmAKqU~lvBy|wg9J`p(mzv4!>H}AF3TdJyMO93U!Mc=46ChG{;Qh*&3nRXh{e|<= zr<(I+%>)Fm!%5U8#-pz1bhs6Xv<5ist7I5=UKJkEhAPEc{D!$8+)tO%F&SHtyv%fv z@`NS2uJq1$3Ie1kDs>EvVwUtTL{=DK{=vzL!*c3LEDz<3L;jr!4RH2Ts3tLGAek;( zX0uMk=~4#B4q6)lG*Ai1PO1{N_4e3znT2-0;}u!>Wf^%$8aTg zq9Hgh9CVzObpr2LPBJ{8KyywJChSF#GQF1jfcDi0u6QYa=kah} z-gwjsX&)m2BatGf_^pj926c||E2^O0qq`%&7`gmZGq{~^n2%DWaZs!t-N{T*>w7KW z^k8+J@=JHoEVj)3Lpq^&OwC{m4!ztF5u?UVv%-(|zB7pleh_?*VvGX0-Wa3Ts2l^Z z8lg5u?4L3Wr58X8YB_H+o9(1(YViOaTMg(-tH~;>UR+|LXaA5==F!2DRtKmN{pKeP zw*jCV9_U4-$vyTLx^qy0HZ`v(dn^i+w7nL@z;6U^=Xz`SR{%f$|6En|!Kpk1vIG z_?M<&S|g=bcLPJPj}GzVTZ3th2QrNenaV(UHCbzKUVHdx^q;`XE5z4PFHOM%d@f;k z3u0l8(R)(1>yB0P^zMh*rwx;Bi8az>g5mVZVm%SRxA(9g&X5tce5%O1aY{c$tb zn~`eKJGMKXBmTPoAKsJVgz}X^YvPcRJmI-ug50%}V1XiG8K-P4K05EdC}1Xvcv z-Ght;L;}@U2Rn^7S_+&hH@0u`?6k%U9t|;I4@J-T>4$;836x-K#O_7&A zJx~(|wlno9YeMv>%l;&cq1_ei$%z3lonQ4QRt4o}7Lc190D{*5njRDs*)XQW6%<&| z;ap{|LLGG0lQ$Q~TLpmgsJywpm=w6VGyuh7Nhtq$I|vzg)uDJ`e~u7xSXv;HxfR1S zgXQjcV_1C*5LD_yz&(s)zQK1~0?`<@tg1&3R(h*o<@89tdc;;Cph_SxI@aZAeQ8XhZGEz; z2vDUjC;8)czv(o3FlhD%f(5S*(qcYLQMKPtaRo{hv@bhJoR<=4?5B z3&e}6?diJ7W?ys}JsF@oY{9e;wG@<(78l@-Z5b>SbXg4B!`f@VJd=0_%HPd_H;fgS{HNrlnv6iLG4LmK z`G)TG{d6IyotrU+fX(bH&Cp&|XC&R^P>v#yP;Ewv^;%*?3eQgF@1$?x5RhmDnH0ln z&o~`oyki@0t|vqI&sTrc)ts;AZB3XLRjed?&TIp+;0H7f-^>J17|sTuz9vvFFfbt{ zLJ~yt4mZ_ivIWa|5|sDh2KdVT#5U~|c;@aQBx?VVui(+Jx^T!XJsy%|>53~;&0I3~ zpA6+H)42l133`6G=Fo(MFIwav5;dHo*amYQz{*yb?(ERM!1sF6@0 zfo}zRYFco*9sv0WNZj9l65AfFCT1K?nzNp;kFJxF1VDH#}Cr z^Seweb1S4L!8A{`sFKZmWDJ!mK^GAHnMg=1GbOm?#P&l9mMCmjU7elG7bV))}l zjo0hY<(|A63lzxH=l`JH0a0CAl7~vVsjJ@kU=z?t55KPWrTR?2ZzF*TbmK6Qtg^o{ zRN%Tgr&es37ErX^l~y2>Z$)WXcD(wkUisP0N{KIhakxNZRNH0k1x$i{YMv7lAbrAZ5|D@q>nzkfW{1QU2>w0}hGLRq##0R84oY zDRwX(!j#dMCgu@{Kta;;?Z82h7$Y1@WL4j z;3bRp22hC&wFz$IAwXJtpZW&mY%Qb^1hOL2X`qRi0$gZ!FjBwUj6Or_#~dX24`V^{ zcP!q3v0(KNUbg01P4UqLtQ$f1MOuxa(tMh;&Udh9KN$j+7`i!DQh74tI_ZZ&WC212 zLq?8H<9Fp;r4B$TVQh?*7*qxlY`MX9QZm201qk@;uE!RRU7%X1b%uacMRoy%=1m~+ zmjlsRv`4H}`lC1goxs(U9F`bm(G;0j>{jK8c%6!;?FMBEnX^O#9?L?TypfK7+`}CE zh>C4PP5WUDL_jv^#1Ky5HyVq6H`WUCQHKp6R2%*+m^mV$YrHEs>p zA5a6g{h259ja)34)lTXbdvfns>my`UpW9a|1eop6uu}B1#yswWYy%y&F}U>>G71Fs z?z6yLBKDhO{V?wg&%&o?`Cv<-P6JeI43oPXfUAvNuleK0$uf8Z=SL~}l#FJk#h6Xf zgm#akKizhHK|fR+6{a%aqM2;}@(fzkaNJq83$nX8WBsmh(uuJIqCP5C*Uxa!rr=y7{;Na9Y-f;UCxQuBZ0KDFWjNG<$g{ao z)L03SBje~r**%0kFhvV%8GGE`v8>y?3?#R~NH2X_a7O9hQS{K=xu)ZTUV%v|s>H8U z9RM&+fG`MM{Po!Z$a;U)p7is;gi1@33Q$pwZ;qE~vB@2>*E4d$AEh@}l6hYC1Mxx0 zn(FmJJ0gn|dFCP|BFoyCxhBi457MfU!sH#x(!<$uezIc)6KU*M`zYj6&=dfoUp^kb z=Z?hc3`O$?Y`z(Q5Ndz0K)$R1PPz_Cxg-mKvRNClar1w`@;5*f2m8cM*@R6Tiqa?m*9##S}$5U*v|FJ`$1(7#1b0#_wq ztat#vhPfLpYY;ME&=~~KtGWa3Rxraf_hlBW)qzqwk@VA{6@u*@t3Mbyg>w5CiIWdE zb%Eq*=s5M>Kha^rw1#;C@ObD__a8i&qoq!B9C^>y82k>bt}QTv0W;6%FMx|-q_B3K zwrUz!?jRz54@y7@B6>Xdp3M2rqde_~TA0!29q>5<8ZRJ!Ma&F@iMJcEUHvGKa0Pv$ zTQ@KkjE#Cr&_&Jbyj3xpFPq4{9f+Ipoagx%%<2c^b$@hW*8Ip^mCJ6ju5Sp&07YC6 zqv`-9dveBkg@jJ4w!Go`e8U0Mz{uNWV0lggm)=UQF+j;{$EZ_bzGrx#5arM-!~~?b zIkUrEEYw@Nuufg^NM=Gx3jiEp_;(DTv$(@nS|UJvO9j=B^Xgy^l>jY0=72I}eJpYuV=Elg%jm;ymy3!p%hDG78oRxPlFn|L+K$$fHWJ3jTuuNnp zd#pb=B9JmN2#HlP^IKJWV1$>}GxzrP?D_*WgE{8xbTM@L-XSTU%UJdMMH_leU@TIl zN%y}tPIj+^StY+1{Nac>10Jb-AbV%QEdm!t5HZK9Z}Z1{4>^Av7JD3Tj*qjO!aQVz ztnGlwS&K49Vei&T&*HL!8OMSiV!#Rj{Et(h%yw8!^%wVZ9^B*e^d&;8U|k7`+jaz) ztFU=x%plYr{=-A<9aPDGIAMEl zGmx(E)60_`4(EMC4pB`z`>)&3xgTB(U-ujix7CN07#j}5`Oa6Df*P(4Apm;zoPj~x-w7`o<@WH1prm6#(O_QdI+n< z!P4kfK3lD>-2vRo3heis8~?^6q|sRVE;7t9jn0DDH z`0}Qomov9eAq586C8Aszm7GgQwOwlhzplb{O#Zr2d|34TJa-G!VK66~>%1>MENJy; ztgMZdq!YOm%EYs?dtP6*7uF^NTlgT()anH8kDS_UAeJ(;pIPxcJ5Ehn*3p-SYMbzr zA@LD9^wb$6aihfnMVT94*p#2_NWzZE_U7s&o;Dt4x{dHTmus1(DIT<6Kgc}lr%Aw*PHD!2G zbQTIo5_fb$=B`Qh^X%nlXK>MJeGH*WfJ9ei!d;4J$uY>X0%Ji{Pd&*G$^_ekjUiAg zCAsZOsiH1G=a5)j$XKnqfk9m~qjq9azc|80XNcpnW%lxZ+U=$`6}FZi+; zzcc4y)+H^6@zyN4iC93hkS;&~z?E zRykQaq)+%=CMN94ZisA16omH+tY*Kn1D|nKfG|4An|aAYYX~q-G{oyJO&y-7K-`MWb!Ih;p)A8R%YEFBWt5Beb~tPlxa(U!iD8~BLDOI36@X-4 zP7|{`L0>hs;CEIF3c~3=gBct3RW~$?sF&&s^dV=82g@dEOjlhpt^I7Hng*Z-b3AA; zaVi1+TwGsj!U>s{=p0iNmIf71S_?F@XdRCl(NY3$K^%Ayvq#H>BTQZ0A(IFA!VFD zDwZMhNL0B)tDtt9D=Zyi(jy#^9$iJE)MmmqFML8GJxTXdKJzuOdpvDB$rXWg#pumg zL(rb#%!wisen~{oc4|8Vl-~3d6MQ@(!$7e`>F5+kE;pJP0HW1I4wj%8b!bMVs+I{K zF9Ip%ct}xhsTx!H;9GCFX>N5ahm|&S{I6-C&oRiI7e0@Xn*F}md286DE<{+v>Y`S1 zy4-7{A9;SzaroH}5!MLsvUdM@tb4_0{0B5c3X<4E_0HBxO{vArd4G1j2{3H`T64G7 zbFIr|aXCOr_+9zAnOqswxg{3#BG_Bb;9U{3K%^_71`YicrrU7^8dbIPQ}yOS6%AEW z@zLTfAv6}Kz;&DCac&B0zu5$aW1D!e8A-P7>#uqQZrthswzoxOnbG9W4Fuf>mVGob z$r{QsIcO%}YF?9nAQVl~mgNH`8UQx0zhuC@6*fP3gksKg00_15fy_74_nf-d(&qC3 zT}x&@U0b1%G`YR!4dAVDZNLt${7ria2X%t(e*#1AdD zst`ZAcsBE>_=K6sc65lhFuqkhj68u^S!g9m0dlh{xfrjpHRf0e!Kal;;vILGgmO5N z)Wn(_FZ>Ls?B%~0%gIhM1CA$yO52|<>3WkmPiS7Q0cK?m0;9JM2Hcnk2c%_e*Gy1Y zFetK9`@AOrOJ@q&iS$Br#oPLJO_=+wA-(_wqKVX?^awF2BCaMpzU|535f{)tvc>G_ zl+!*{+Db(WgxN|PY1c+!4M)I`q@e;cS*6zFlOvMo{`~X^P^=HbpS4 zhZ2bmtd9tat43YjE~7YS+ewo>7R2~{wUEYTc2Jh5+gBvlt-sjZ%zk~SWN)#pF6{LM z?aPBIn7;^MDCzHUs>^^Lv(Lli`h&I7V6*gk(AuE?0@eovV<7=Lb_vnV67hf7(<@;7 z`6dDI=XR-Io4=-b>{`WdntR6@W)4{JfK{=<#DIU>*mym_LeXGK@0){x4b0P{AD%$W zI0ho9j{8SwPhrnac$tIZd%Ta~)d z54te|c_B34eD-?-P=tDwnrvW*7kD(u)K`#L-le!I?5142RV?mr^z_8n{`Oij@+q2i z+9b$!<}fcA%u`{W`hyI(K@IV{$~p3MX9+IA(HFVR`4KUklS!7R754Er|0Y8TXq{l%KF z9)d~3`DWijG_hVwFfyKln(VcqFpeIhn+>?hiTwu>sN;ylWLms(yd_TZn1ou=Y3dbp z=d84uSWNoJ!KzD~bodoa!0@5J*n~T~`H3$N8mD($p*p@#=QpaW$iijq>#-XC1cce3pSwlgb#sHK{T-`^LBH>2W&}Yru0P9;r2##Z(n`lj$~vXYQlhQf=M`7)CE`m+-10O-x) zS&lJ~#-&Dc#Sy0Ysetmxu3MWaol75Q;+u`c@YAFQ1X3tA4x;l)RG#0~glTr7wAU|~ z0k)v?NY0lx?nJERYYylv>&APjrc3Az9V^!JYEx`a>yy&!f=4EvD+K%5vkIWkadgId zU9gDIr{vsoC}uz}ReQS&5)WE6&e*E9w>JUQ4TxiP-v_wZ+CnE_G%c`^9{Ek6pu=j;Z<6wgY)lPMibLfQ$|eYw9FzyLA{rVMQ4D>~Yt}G@@#h zTW1<;7Zt$dMIK(p8*ErR=M)w+2#@}?89xCg6g(EQuVxTFVBcSu|IBXlWW z@_kv@AdiuHMeTnpg9_*MFXlAt9?m^Loa^@m2vva*&1h!6{T8TSgQL=hR;KhG0jBkh zT*fIkxbp{{^S2k`6EKpvY zN|(BQ-`_#ZS$=ZbNW-b&5=y+bju!xRtQv77+??e+5Itw8>-w}89=EMnma_kX-wuuQz% zfXVjHy&`f)pkGL8h1{WEfQt+EgClW1yEd0S?t1=Q|K9CO5=NvA$b!eo&=UW*X3_Kf zO~AiD3*au}!~Zk+1vXeXpkqDf?Z_9hM;Z)?abX=>HEx@IR0Jf7mbo zuV-5l+Y7+yX_y*%;4z-B-7o>9_1K}|QD8^_z1_$fbq)nr05PGSz`{Pjh-_^R+$*5u zv3V)Cf&`#(8Q{oa>f#otqhwYzI!-En3z3?gTjVqbng6>}0I)xA-?GoC zM-XPp28w45|As;TtEY<2?rVfy#DFnZDTBxi9yJ05DCfCUoxxu&7T}edjcd?J1KOFK zO&u}MYINQ4fUjypnBe9;tX76WN>7EZQP6i%NhuQtql5JTV;EGEShWzTs{JaFqqaMd zXEct(inM{VU^bM5XM~W=%n;V%Utu-NFLz?oCxl#MV6sv*<3b0$_Zif??E4qXhl6re zy#hD9a-?g;gRo{$P=YrC#NPvY>k}SEcd^}Oh8G1wlc?9nB`9c;JI^};T=&Ek+lg&2 zpD}>8H1e`YWPtg%U2y`|k}V*W)ASuI9nW#hx|JP)h8vjA>74Ea)G&XJu}tf(0Qj@O zrjjiOzNIn1Gj4&qu~TNOO(pX?a9yE3C@+N)FK~d3yiLq-n>mggCf+^_w|*(vrkkZ&r2SXj!F3W~XY@n28m?-;*|(AvKpUvr7&tO|OT=vWDbc2r#xjOpx$P1d zSDb6zm9^L`r^b}KK>Hh44gW7--Pr_PoKIs=!y-07o6-~^l95&Yp7;-ywUSS|lzis_ zdACL6Q%{%*?(uklK88l;dp-J7rPlrPjiO0lj?kJb)q4mj1dclxLkIh)1(=xdoK4`h zmk;g&DrjSiK2XN{B4ue3v^ohNPh3{20vWq=E#=5`yy0xt$B(cw`$| zD{h-sn1;*}X8E@(YZLTul>xhP9mN+`O=Y2~;`^Xi=xYgR9v=Y36y~730x<8;n*{ce zc!DyA(jV67*v+D-gfGd2{j#XWi2ro%Ue!J@kd&l~_}{mRp?rf18OTVlmcA_^fR9wc z9>5glMq+^#cM(u?hES;p%5gD#t*>8mNgtBAtfpq2NwOY}g6G?cxB4FNKn~9x3j^KZ zuwOy~Pqk2U9M=7P0$7gZD)4~`;v>$Chr7-|z?hl*ksOD-^yyhhF-#xmmP^YeQ+~AS z0{Q^m!RI5`&UuvJX*_>8qygm1So7&95;b0d$8Ke{1g@@fLA0yHZlAtu1YF707yRZ9_pT8zkTWb3B=I? zV4m7`2lfeD`|q&Ut@7UerQZcYKKCnu@SAZw3&f9UN0r$~5Fx62+rgwRoU6OzOdpQR zx5i8Wm(a_RUB6lhBxmLj{PlI$T(0*I0Wom_hIe{JQslmwCx&ab18{q_UAnE64TFzM zIMM2Nl*5GrnkW4tdzKGYlebX^->hD^ECf*1a)rUV0zp@IX=@Pvgp?V00IFI>`* zYh1>N^48`7=f%Ii{0enKJoVFZx72Yxnlt~|AM89}9=o84SK9)Hno5`;%$Lu9(8V(C zMg}JPcV!K!Lmr`2Tkt6OXn}`y;3GyfKV={<$IXhhjrzRzRBX>c-gi0Ou(ID6Ve3Xu zpKnWx_bvb?r(xir%3M|6Za`p0;_P5Yl8tku{01`(j(|dhI1fj1wJdTn3iQpws25^l6xTMN8KT^PgJOa@q zzD|NcB0?+()Pq}jkwQ(@lb8wqmH01{XplAu4R#cMF zy5g8z0++iYs-|h3f#4${*tRh?7;BHPDnL zm(y=yKh4i~&Ga4F_%<;?b8QPN($$N`V^ctP@`35fdi{t^s}bS8vdA}mQDwCAJQwB0 zRUtEG8Lv+#yJu6jy%_lmdm)QREaz{|K)YibPUouL4cH&Xxzh5SDX*Ur*Z~g*m+OW2 z%^@;NE=u1$^{qTKBxS_**UArFsv1$uEj1f4cqoKf#Z_fd6R5@8q%N2Qxtb{y-4$1Xn?lvUWIqq!JKCM4NBUNI1 z8`C5WGhzUZ))sSDdHNZhGFiJwC1IK@u1uT+O)X)2-S*}6kg;6bh_PZ*3~l3KqP*A_ zetZ|*qS;4;J#q(UPBR?|C!ou=>J3B9Po(7BsQwj3?ZFe-S@py>GPum=Jq)0E7zrU) zTS2)W7#z2SAKjF^1-1j8%$=w>TBAb(<%%HwF@>YVck)c>>r~2{-*RlEqt+F< zXul2YJc6e8HlBO_Y5!t5>3tfCPzXK*Ws2n8XlidXZ8VNF4fRN|1yDTf8t>s0|NTT0 zwK~vTU){R|^2BED#1KA+wf1$Zj)D9V=Tf&-xhT;fS681uvte6%++~822lR_^Eyrxx z#Hl`=FYzc&^L)C~<)#rZKrsWvNDGa%`Hd7IpDCU26JQYRRtSgQn~lHhdX>&3gjs$R zrq%Sh@Wpx2Z)Iqj|Bee>{(696oZlnG9~sE4ph^v_3j!k4Ghap+tM7o}(wA=r$L9uC zv6WUB&c+A8GJSZ1*~9koE9teF6J^m_(E)BJHFdv_eNhf3>&Mt!Ql-R2Oz2^;6QGqX zSE;qq{oV{PSp~%9e#M!{Ap{0Kj{x@28V|q|!Fcum#on8TQ@OYQ-@A6US|th%LYfRq zAuP%~OIc-FWY$1t3YiHZB`Ji3%nQp{A+rpn%+n$>$vjUP7r*oBzW4Xu-(Tr?{(6q* zIgb0EeY87hUF*6&*XMH%@AvDJ+H1t0)(e(qY{)gIKit6LkCf_9wI>B$!ihSqq*otx zq^q3O;MLfj&>sUu-wrg;wJpsne5virBDfT#Q&Ya8!wE4mu9-Af`}kZf4`Oha$KIwj zyp+1Ulu|;wNtj8O>VUglTl$5v2%k`reZZ*2!h&Ak6$)u0zDdcJ*yv|TyjsxjxpxvA zvAY>>*6%gSj+P>VE=Rn}ejZVBi@C1lRS?}t4g9}2<2|e)W_*|`eo`Zds|+j%x4L-@ zKm?Hkr)1t`&revH;znt6@=!5;(^Q9qmYjkv?>%Y(Nvw9RdmEU!y_}x`s z*gx+V&q_)wIR;r(y;x>t)G+k%Ij%xdRB90tbx~`f!4jbX1<^#&#d9TFo6Z_kF|jH0 zUA9{cK9{8JOnB#IZUl&jU7X5pxmUX$H_^m%ljx*OAzy@|glc2;A9YMG6kGJ$Cb%a_ zwsmWJH)06BpPiD6jGImhil{USY#NRosWm>rk=cB*shblTKWVAWyBSJqW&%Uo@wa4c ze3LmCn{Q9UBC|3X70tQrb(BJZVg4HF1OxF3?xr7J%m&{jUy(AlUUK{M3z@>(+R=TW zF4q7qfUV6L$Dp1&!ev{%BAY9Nmi32<>&$otw_Hzd!QQp$E`P0unhJx64D8?EmaZRt z>kQT)=f$Xfrg{di+E4u974Z32l`)T2Sdj_!yovIc;nvXxnK2&`P1Ep}i;H#VR|{j! zR;Tww^K1lJJ7+GbZY}a$7azp6tsGAvcFN$`KJ#z6(7r|=d~q?r{n(a%xfwHK$7$0| z0|H2pEbE`exI%j$C%VSD9y3gBbX9Ei_VOQM#)YGXy_iub?(X`!X+i&e6m}5-6XCiK z2Qm|H@eF3}SC)t;9^D#gF4RBQqQebwgB?i+bx`%dq+?b4hA`qmKgOlFLow~a42VQ{ zshmU1;j!#-jkZlk(d4+t5oS`*4lj=4xkswc#*%l&$s-ktCQNqVh|ciS*>gkPby^p) zjXwn!eEeIpOo)%;f5mQlcm*msI^+ZS_$*33{3W+)cUscKi5#C!1`!AAZ=JV_8^3j- zWbd`u>R7R7j*{mYYKu|Az#|P?!pZ5*pltweZJOgCeawDece6f;@E94eaVG(AR z&eXZaR)<5zxXW^c+Gw${L!A3^|NbrQ71){!CrCx-3#PBv9ou%2p^k$(!IHrTyJHN(9ma{Qj+YZd@Xish(|sIWPdLOG)>I^cDCze4()LtAgCKgbNX0LJzF4na5CC<8 z4gT-0-pDEG8&`E@!`U59bu3dXa$7`4EsBzsLw=e@B z@uKM09Dv4bR1~DVsa!AmSa>a66{iqq`dX%HHnTlY7utFD)FaP;a<@e^&!p!Xw+T|@ ziQn@Tb36b~Jga8$JbBE0KQx)SvGMp$Y9p+-k5re6Y@VsYsnJr7;l6OCl5$m-Wx{g;p zQ+h75tQH!$cOb2QElh^!`WAkjRUEtA2S7f-v=Axc?Nl}pTu?7TrgGHg!dY)IXuVtM z0D7(IHU9?gjRmxzkQ6&FU-~QbZnab{#1n3GElLRMU8w?{hOeWvVH70$&m9B4_S}(7 zQ>6$kr^P8J^PW>*A5YCR2W{@2trae=KKF##0)`Db0i~yi(`KmVdUvkFz-YQV z-*P$viNN`*0^0d?xc|!Byc=)=p8SFzPKM-^$G`z2bT&(kg>byKHV$>k5ua>9UeR_ zYo^6B(eKtlS*a&4?Re(->UIRgP0peZ^z!c+_Gk1;Pe5_0CL{0b5+e9?l1RAKN67A(Q^X-(qcwMBjje=)~|Mi=MRR2eUQB>g(t9?Bt4W$z)D+yiE!L z-Qk<-H?G97C{?7GT*qK2?h|Kso38CMtA8Ow&xaQ@9Bqu9rg_c$osvnvrpPq?_J(JG z^6Q(j3sQmCPb9k(#TLfFtL&mgY*v-kQc_*=L%(W;zoGb4@Ruf{!OLJcGkOHXwC|wr zR)F&Z!PBBy<53*pMR zk7RR&8riUPnTP+VoakiHw-;BsE9dYo-N#rt!d<;`J0NwNgcIw2>OEo2cY+VwAt&$A zZ_d81lEzK|Nm`s6(LXHM-;N z@H6EnxHEAIO_;?HV$=&)iQ_K~KH4bL2QeE}nGyFj(O5XKZ-N0#b9uUF(Bf6A5VzWb zz$Qd$x?y9*)N6=dV*)II+Vq^4ns$9hVOs%*GE=<#is^3UftSX4LiSn7TMi@xt+WSs zpaZR}ecCN`Cb=XP^?A|N&l0MmHVeJRjzA7GWT>_d)xCdrqz$tRlR1B70=dC{#938U zOhEmCR(_YBp-}y4^)o1=`Q}W?k$qajOQAkms`Oaf_?xU+50`)Lrbw(IQDSyOIV>Jt z*{xjjj+K=juuYI5BK%i#tpDJ~tu@6pn5NN&v z_tQQClE{j-QunQ}LiJ3&9*8i5ZxwIi^*ug-X)7%0;suBkb@=aGvu*j)Ni*Py7wCr#G1TZarC1eS)Ol4?;}DEgoO6>_DJKND?ITE(-?hU{Iqn1`v)^U zniE!aTd@rqX@qJ{iv6$AQQ6VUtBV286?q1!#bIC8ooG&c^3M5Nl15nq1^6g&$&NPc zLxy5!>Y@<_Z%Z+{wl(m^+B78wmo>H-7a>U32elVP97hvLok(K>@`^5iPtKKJvxpJ% zdu4OEJ=v_Ly82$#qy0u|b%e)^6#lQvPqJzZ0RK%guf zda3=!o0(Xt{6{S1I4EE*U-%ZzdZxXy&*^#^Wu6dWSbUCo^CJWP3bjO^<4Dw{rE*}F z-e!oyN@G(@iqU7Y6>Yxms*4|_icYZMH)}iFPA{=(AUDD&U>;_|b=aGe*cs^INi(Ab z$3C%cr?#FK@Z65oD`rwsGf(>5YQZF%%-f09wgsx8SdeP(XSKuLXTqB9;K)fd7MmNZ z_Yr1kR^__RS=|EuF0XqyqtPA3-&@E;@x=!^(#4Kg`Xi|C{A^FfjuYD+k>(H9j$R+P z=MGffGUj4~Mq^CMiz@LAy&?l6$lthXujg6E9+TryPd+%YmZM|TYfpyV*LGythq+}9 zwDw`>OvIHY47G(EcI}0RYYEOx_6nOQj_hcAb8m6UGJp3x35qh5Z_xvl(o@;b)+)`K z%KTrNeaVC>UE$12H_EYM!NbAm9&jbxMpZ(WVPwCmQknm7n+hqwN2xW-v3KTJl$y;S zCZlg4oXj_Sgb_Ja=07+f{)_iT3p_Lrv)9WZ^v*j+b(GL3?Vi60j)7sVYt&-6s5r_l zJ=MTC$||T>@sWoLJa=R2Vq;H4e8tq&@!lxkwS1T?*=4eu&3l(5mfDV9h7l^Q&q)RQ z>%!lH04kqHigQs(1W*B_XPa+ysT1jEEB9hgQa*DUpx`Sgfud*!;ooZ@L>7!5qw)Hz!E-HpvuC)k9#VrYcOzBEn4> z-b&;M^4x8-n6J_w6Fb?uxZV#j@jdDp`WIvPCDn;rP|nf07I~p*${j@Gh5Zf1pNHIS zONnS!Z{O*N?=jk7DEb94qgnO+mjC%$`@p3>ukooH zCGXYrwZC{C6;&fu;6!gn@teFzJ@lzT^qncG+FJz00=MVII3x6f0%}DM6hGZ6W}f{% zPO70ei=suO{h!7i%KQc9(KP&Ncz5yimI`g``}BNW{(V8EW0{=*0^ad18Ip|eQ}6oL zG}x9p_Zq2D#GFW4)L2K|;s5pm&sQk^e=$x)I1h~8e5QTEkn3aonOk{-SH zyJv2uC5J>uH9oX~7n7X&rnuhhga7`xep%XH(B7~fyfn%e8(YR-)PvYWH!d-JtT~3N zb-(BO3g}gLP|6Ja#(}54%v(j&PC&V|Gis}@LIo@gj1dzyL^Co}B=4oQ?8GG+Ul1uR z-r0eaPwQ}r4c8&ot7^2D?vuskI$-J9hy|)cLBkX(pnnNuFBlUh%%n+aWPVn)=?5i| zbb@2Z1!OPnCVo-&j@l9{notgT>BjvD-M01TZX=O%ZolZAjBz3@jFKlyN*T#q?c!`t zn7=y(nT#wusnN%ss8|f0AgVPn((Bntxjl>OA}<}Dpk%au;A}vwK8j;s@nx6~Na~lN zuCS|nJ?t}=`*gYGc&+gx9!#BPGf1HhR~vuHi&>}=Hgap{2yUvlwdA*xQZ$Wg((KcW zXu>^roN+Q@a3N2{22l_0K<700 z4sTU`od6Nl+GvO>z=XDerAWsx678Xe`K2eDy%$6pC#L#%K=}SBdC&W$EIn!Vl*cSV z5uO~lQ^_giG6Ie!P+!&ys1VyNvzOgp85in9)zXdr{P1E^Rln%3l?`<;V{;E%bC@1(4(T$1x5BsEwgC>$pf}ZYfuEX3-#OtH#aC^V^uLSnU-_fZ5h}%K$ z@f#B;6x&#iBa)r>oS$^}7@<@`Z~am^^Fw{3#1B6yJNRlgZ5Q7jzc0V2F8y06;gJn= z<2@#!zq5V*_{#GLHI2H+vpd9qz+!h%;MdO%xoxs=+f@0d_cUDQ2?~5fwapC!1;8a? zizJA_J3&~{WmVImw}+9lbpfP_Te_5W?-`fWBiq~)GxPx+7Be~dxF6{4Z{wC z4rxT)Evp6m2zG0Gr1fqVC6Qg1L%A&0Lie8oEDiKqI>F%G5^I$w_lu(L-&cG|Dh3q1 z>L5ftoqKi-%gYtkQQ}&d4#xG-q5Pe{`$azq5SyRl+Jq}LpA38)jhcrBKimCcNB(!PM5E6VH5}R$1+dG%={y^+ZlBwJmF|A17t_7< z+WvU|RkhWvs?zs~nl#Qk4($Ds6&sVvZlLSjg){&1p6mL7*PL-rDCqY-DvtG};5^BG zsIs~sHO7-dIqN}O+|JPN%QkM#^U<|Y-w3sJSj*lKN0!gGjd&OErj2=j$4m5BuNx%`feE3nJJ8 z_e3M*wz7TjtKaVrIs8&_`D0D5hWJzZx5k@Jq8WXnm^3|F!G#Pcga_lXCbr+nkxv&fMoXiuirfOR-y!8 zVbTEQljF2+wdev2qF6lAd^Y;sfv)XWm(s${M3IN#?I*(K4!ZYn0q~W)_w3hSJEZ@$ zAn80LAkxKdT`q>5HN(KrFfqmHw-l}qCzO|Y?fcY+EKXZ`940@|b#K+drHI9mHzb1F=;-yvE9WNm#9U5|lWzcZ9QIQPu zJG__NiKl3j-6r!7ZmVwhN_q%!#{3}{avpVSe4de)q$}YWKK}5Hi(Z+(Cj)t5+3t!# z(3#Vz+wSJU2@iH2A*-%@wIY*Wh{T3Lubb6K>HaU>d4J!}1RxkGoT<3C-6bKpQgt9%f{CKtmd0wyY@R8 z+_PN(_TvLmK)0##Ig9bA9T%l+)ykKxT7=^Hm|pm0KVEU&D;B>ktvwhI;(&%#gUaJJ^*+ z6}QZN&S!>i;=Fe(@*IorX-cYHDg<0N#)eK0V1G z|LH!vc3lKlA3T9n;1CR7K46T0H=w!AC@c?!ai51j1DT%kB$iP{sZ_2L9PDpzHKAXTJW( z;1}BvAin7!{4G%W;6%zA>&kV~MF1`|0ue#(XV!@8V7N*HIWYpD>i_|-IT(`( zu}#ewix%j%RH9)PTowo_PtIMJ&Nea5c`DHP$@XmL3^ZhoVdS3b%_zlb<5$d6((5XQ zO(3l_03J*Dx}g1Zx8nUWq2Ju0r^p>bh{8-*mwBfAgek(xjsNpoJFCMccxLigR+K@hhCcepX_O*Q&HfN=_VQbG1x)^mE= z9qhID5#S?8lwV!{$h3W2SgjcYnI=rVsTb%ne_NTe1+Ba^5ToUSI;ZlD`OLRtwaF(( z5Uk~`fQzj0p|6DOh#)_2QJbQ*R4%JURI1lRmx5Ex_hcS-5dmzf{XUR3?BlxH-3JzN zUH0rYEweK(6AR)^BS9eE2?pB0PzxgY?e^iEhsr_rE`wpPZP?>Zl|8{g`$A(bE|4|! zr4^4&9j5&Y5NY&=66r0C+224hTHO&wvuJ)_F5T(@deU;jNO@T;hzL!P$#Wp&5H~4S zqeg%`f|S=_=V(U5w|tt~ru>M`TxdUq5NPakNb1_>R)Pz+K(85AYx0{X{T6xB3~dvz z(o^nD(O3I}_d?HQPF67V$9;n-n;E@-u!B&|(5L1HdlH)< zAAJ$sepD(NP7v7@rrEOr2szc;kmU*mAI-c z&||B*l~tWSoTnT!(1nQfp%O2Ei+iZOwMm`=X{@4F5eO2)hHdB0;dp|G_l6K5fK9{7 zlwIno4S!NS^a5y~Y%N4@eF20{m_wSQB{au^9VTat!}wM+vdU3z&k;b9?WfyeT+6xv(0i1(?*z5&v0gpc?YQhC{vo$>ujCIcfm$D;;eeT0~X z=E4%~0C921;s!LS&4D%KXLw3JAR8FXY|Lh(f>iOaZRTZ5;%@#q{cBq`k|-*D+-Z2c zcEv1nApd)>vsFu=0ivHr)XU-_^Y?+-V_6@XJWvPs%o1EI!qC@wp*I2t)Kw=&x5jvR zYHfJ?XrJ7v4SNnaWHp}n8=nz8aHYV2aLu`Qk?DZs4T9292k=y* zvOj>u!2*B~SUC1Nb^(TY0byfhRExdQZ6siiA;8hDvy8PaARNsoNGfpMe_X5^L3CA` zqHyIT5!4-+ucY!$2PZ(nP_4JUy>ksr$9i_D^CH`4=yqCg6;Eoqz`=j){7sn<8d*sn zw2bGu2!mln1`cn)#k_okUOaYpMH2QJq-(D51kTvuN)he>gJH0~+tW)Z9Fq|VyKwhr zGmQy&x(bj!#Q`%s^qgj`Kp&MEVUO^zpHG+!07rZ>>{h_*V-8!8g0@LXC~!Z{m}m-=)k4s82bf95H`fu@y`J+_h(lyelr>MhEBi)M(S;QJ zFe#khM5P%fh>^1qy_G^u9JBGtJ2)&Fb9?SDz)<>|Ny>HG5=k>O^0udvnXy#)x=N!g87SfPq}* zYX~EDX()7y4+X0t=!R7j;Fy{kRkPfyJh3t zahF*ScVjq{5o{owgwGhSDptIJ#f>(J?hJ?8xCo!AS$fXPbnx0umuzZ7$rl?XgqgeG z!X~0$hgbp;-<<=7dE3eqi;}=j!=trnMf26)VagYN*klfI@$q}}!Mkm6^VOlPNXrPI z8A6vN2dIOThYNW?!!6d!5X-8r5#ZU(^JOUvVX` z`<D+7(Xw@dx(i#?T;l31JeCk-{()X&F|`Ar=IOA=2rM7o!3)ve zBEkazi`WJrr40tiW~Sb#@;njJ_hSz4DLMaHYTXb10lJa_R!$ZaT0$S;>(x%_OXqR~ zqcjeUZP9hu)yRkGl`580w9!o=AyUo z$vnGBPN9K=UBw_6FgJ1ex8O(}N~kL@^WVu!k`2SNawfBCt!^?~jN zMA(U#NmJGKaJ7?@4diMN!D=CZq|@T!q^MhR_1(#&^i(3=`UwD+OKbbfyby@up3GN^ zDf42HL^Lc~`JovpFc1 zQs{79{@Xf%uDpE9a!UxQ`Eg_HyVro5D6PFfv;8BU2g<@89eXNq!slr?nsl`dLvX+` zBnAU};tOKQ6F~#ihuZvkk8a&uTx^}%xp;{)x;an3cPA*&9)1P!nvmtyaF|@s#^eii zYpnufkTo|U%82HU@w|nCZbcE&m z{tDm4XSdjT@jsMa>fv_(zgYl3eW%Cjz@;$e#=22LZz1})_HXtjTIhCTVzqXypAO^j zeDeOp_CXL0RLb{ww1)V?r3_uZ$Gq(_uv36f=hCfvpAS?Fp1kdwQ4-G)62fQiN9#O5 zrxgbLmfg*Fr5p24o-zpw{C4AEQF4G&vpizq@wQh1Q5ylio1xRdMT_lY6B_m+kW38J zIm-U-XH?tQC9Vw+wW`-B*I~KLhzXPNcL|UDKWF~WW5DP=zsS! zitu68i{&<*zgr^zT}J<1M*okN(Vcsph|XH}$)S!HXRh#SM%mFM<2*oUsrigqbP~?* z+%hjpWYCJ)l7mXILxh0EBxLyauRS};_fZVg)@rG3`9ol&d;>`8`~@edIUz}Rip|KU zJ&A|dmzFotscAPMvdD&*xg3E!177Pb2#q^?#?x9{DMQxslS+spKi4z!5$sIdq1RZL zMuG5>E<4FFWgxus<~KLpvcUM+0Ye9FK@yk+!C073EM2t=M7W5gu@ey>Be^il&MgkR z=5>ox+5=hpxZFSqOPcqSET7j+h=j`K=EaYa_ zK0=wU<4mZ|O(f$95fud|TN@0#L)Sd9D1)k}IRvIpQwEa(^=Cpo1JQFfL_8f(;kgMx z@0CKXYi&gm05nL4!ql`kG8Z)B{4Ke+V7}&IXA>>g6JJ38!Hw2355J}~JWH6JNPNee;rxN(`e!67c zQZ>iliWt~rt<>kT@YLi2AC-7Ege_r0NiGQ76iMfD4a#p5=*GAN#ZF7MNNdk?!23qI7R19GCENg2qSl z*6W`}$QT0QAZSQded*C~#&tUqE-1HEW{{!!^q0q?VNEn=eHrgP68;8RbAG=piFd4% zt8nt>yFNGPIH%$4maBZYhDK9)I79DuFF4{nj}C`@9wAHf1U=KU?9y`_3PG?Lg-; z`@)P}-Lr!lPt4f)Tmz7aT#==|X5$+O#9nwjIi9h-UU+75xao5nS7?K4xBJK&QKPn> z{JI`^&{2MF7C2IyTp$zVUy5XdQ2NgQT;)~BIc&iCrHLPKkARx9o^5RD)wr{YjF!>1 z$O+M&fsKXlY2k5m*zY{|8f+rfQFtdNe8gLnjK|-rlAiBG;f}(#{h7b$io<$;$%d<4 zfyv5+sXIYffxA&jPL^OYodhSoO)j2JW4!j(5u+>2-9N1E~HVo+- z2_GcWWmX>r0E06Y(GyPgy9 zrwCb8wC6xA;C|%+`7dC}F$OMPZe7^^`LBz~H}66-kKosVX=p@z5WU>E{fn6tLdRXFN&FgNC8bN`nB~`J zmza{)2T4G{^e?`L4i-+b^zu3&QG+&nl7taxSKNJz>z#bnVO%kdqXdzNg@YYWO!x(P8D- zpz>uX`=3(}%=&OhTkYa-zW~E4?7~=r(5wCe4fA;{}H*v7{yd8wYX!)a*K4|JhVDnK-alHfixGjFezi5DGQFECcF=DNqifY)Vj(@+L3NJ-j7o?K9Z*Rcwvo` zPOUL!_9RaA&OG9F6DqElxZNpvte0!$I@7|P$h&gQk=M#b$Yg|N>wQK!dZs_;d4BH` z)SWmyI^uS6AS?0nHz?8C2Q|x!X0p8Zg@|KBJUG0hkZ#lx@_c3P(Chc18l6YO+MS=d zf2%%9xNymq33KNXc%=?@ja|JE_IdejPy?oAgB4ZWjG;Iis&h}F87;&{Q3nB*Q}cZP zpEyx;GA=9>t9|zay>7Brj{s_IW1V}Wx81A{=+j)rk2{}vo^nk}T4T=myZ~csDkh<2 z^;9UWl9z(7Y#9p&=4`$=pz;04E5fUrQ7vwCm zkJNM%>UF*O$B&RQt8%C@FuuXjsyrQfxEa!=2&9RF{8*Q?8=zsfrGIEp5_j_1xe#UR zoM|V+G$^x&+nTs1a*pGslBLaIK+7fDNi*@0DEn8b`5GGupq?hkIo{TgT zdsb^yM>0-~ZJbY$-hx0l~ml>-3855aV z$|T*Ac_6Z%r;EYqyr*$ZXGM|c3GHhOHR~aER3M~y zaXcx!*P^L0t|ROYyK@ZyNN|E8d5w`hxYZe#8QWi_;Z`}<@p%N#5vabBNMO;Zpf>%I zYhWGI@C{14p%<{O`sjqV{N%ABTiwMD^)q;}8L_jWu_da!O!X@3svlku0sLIDQvDpE z_`9-p)4G$B#xIK(>C6^qnk6@sicL86joBnLga%*-^0&m=d1A{J7T43q7FyQz`-Dx2 z?-%IR*N!D#=E1lgT8#g*ui2H7qCrwMIv zn=Y;(`Iau0X#j<3{e0ZKET9G9UI&b-(bINL!%lI3$JLe4#Wh@!kUxp;bjkv67h%7& zH#I8KmaFs5?DcGMyO*$*7A&uy^u~bo+1}yKd_j2q`FIw|k+ktcIPPQyc1Ck!}jghNM3Ry{9h1wiNa9{z1D zfo9;`4ezV$AxzLw{l%wsv3(+Q`4u|06~WUB!=Sxxa=$zAy^?RVDXN){d8qjw z{C#G8;H56~ZInQr2Vps?Ub-xvnNmBpf&0SZoq~%-p3&acBt5(6^$VNzCa<`?gT-1O z6XQ@q_m2NmiZZ||A)5Hvh>#fkPh)A0*?_q)hj9b1p~06JnXGGrNq!-Z zl|ExA6khCp&hF;=fJN*1%$O5IlMH$)O#Gqg3jtcqc444w$3qr;H_s3X)L4ugLMmcHI6>vsyyx{ zN9*2P+o-s^y6$$>#06E>wGZx~b(p0&xNKE8C7|I!LtlVvmyGzL@L0G&z>SNE;smkH z&lDx?vT|tYBDG$GE$K$tAQff)T5pT9&WQw}b{r-a^*WhA3`V9k&Z%($X2L*CGj{nw z`SD`Pa>)#L4;TjZnZm&-tsy(}Y|9{Q2!BZ9*i*C^qoeXN!UN6+85kjs@z}yPjom3X zc6vqMI;Wv(kPz6S;WMh0gio5-%6+sc4k)mhP%d|z3Smu*+U*riH>H}p59R7sR;r9S zP+PfVe^rclsb#qb^2Ooa_A;WvOx4e7hSm}&M=G|*1bZH}Yv!m_YPtQvt=`w1W0BL` zE-l663l{}5JQ-IUnP&4$iT<^B3?*_*)8nVrj?BI*C7+wCvBRnCgBe2U%%ptA?rXCQ z4>{>QnHKPSD=1HvO_5!3H9j0*J&KZS}4 zRX+WzF3AZB;d>RO>fDP<8pesl(-=74B1=ylv6Z1R`6lpqK-~MVSoRS}nVa>+jvQ*%nmKk=!~2Yod8Qg3blqGI;u#HBt)o+>HSFuMby>@5VYtX3u|C_)E_OzelIb?m z=uo#KJwegGwa5pcioWZRYU>0OwE6u^uhu2erPXpSu99qdXxsZP&LlsMOJ=DTZ`k(f zupB|H=$f)0synZ2j*ay?{ujDkLSkeQh_V8aSg;ya@1E*Xh|j*gxr$!lQaW4E3aLT*Kl`uY^zizh-bq0tQq++sOQi}2t()@9 zpX%@KRk{?Av_xIeUhN=EQ}TO|^j$X=>bcj=YB4p@X&Tf6P~0mn-fC z=7cFE@uAM9&{_9e*HRi?NF6g^%_y}|4mkUh*U{rEbb$QE3U&$4Zg4%pgf)q9t=Sc7 zv6c!gkIi)M^fvR!#p(q@6Fr-R)`^>$v}K1x0YIfXJ=;vsn++N!1YEYtDEb|rh_ zu4a+bWoD`CB^-H5EW_H7W&XXYo8+7(xnudab~D40X#JM@{@fSdVu4P%iaq0GhW$pK zJA8Lyj-z+!vpML;KJJ|`%`Y19%1rVb#8fGAoy$A!>Z5|(>rKpjzEP(T_NPlfRpw*9gB#)I z8}38{0_aOJI%ShG9CD>e`O7A12hdXy0x3&z2B%+K@7&RYC{&yC$y*|l#(+D3j!zojaL!>{rD@9PW|?K z`;GX`qus`!p;YrExl^6C%)eZeYA%WBsi=je;B$M$K@m%IxFRU?DnJ!y4uw!#-^dzg zv99(jhCwDgv?F)e0?Y^tDO_R7#uotWs+RQXe!fq9>$-54`X>W|Kz$~`D?D$r-q5+{ z`tQz1{?^dpMI~8ZpAD4<%DTR$VH_)F!_d>l594XGBIg4Kipnt&K2c5pW$02>>_cuA$LgiiOh7Tq<{e~k;Sq!KRiRhd za&l``!g#jkl@;V|H&3;Si7QR?3M66-+h;8bA{Tw`;_R$UfG!Lg` zPi?li2onQTYlcMKp%b1TyFaHGJyx>0X4X?zV8{R`(2rn5CUvjTVsC5Z461>|Z>w)t z=RS=2_6-Kt5w(k}7rgfwnGPb0W{+FI{Hn0VPit%xQIFEGkp^Ji%K~@;4JxaPXkqaeJ^OY@=2KhSZ?7SwlVQW8QJc~Uz4$M zHuf|XZ*pusPbq9>1ILFL-yS2iqf3ylcU|U8|At2MN2($BQBi1Ni6J~g9|aqz7eBU5 z)ci|Uu)kPj3+k>$tq9;~@Wik&s8Zk^dLp_yrLted&y(`>PLE0HKxK$@iX1-P(we+^tSA&t8dXusPFYs(4p?eY4Pwof^0e6ex}!t z1UAtdP_*-LP8)cymY8Kw=2^KgAUyui9O}Kt>N!>>$o*!O=U1Iq?8t-t+>_D3f|4=x zau;(*9r!>el`_<}_!Lu-1>JO&S(g?j9;8kmE<_SSidQ`$HDt-Qrb@%xRwt_hF0=KbWY_4o?SZND)F z*wZ3i=076?MNbDvN!KU8#prmvxPIcJu&z{4c^@RNmYcq66}@R-i?AF`0GZ=QrVmhP z8fAUlrgLZj7Cm`B!m{AAW&Y5iEuOon=v{V<aVWOoJrIQtYTZV*!bA4}u_!vM%4__hC$kkCs!mTiLrF zAybLDuZ^)yfUx+cUR3`$N(-5L*dCic@Ko)5;w(#d+x1&is*ssV)s4iPve!KsjDX$x zNbN3ZL2ozWp*_A&$G??8XF*r?iQp6AFy%+Yfu>fqF~WP^ZvN0GZ40E67=UruVHl&u zs!UNVFi!akTKW!$lJY2VIn*k@WW=jN3=x?UPV5ICIoYx&y^z|D64RL;->FYxVgTBMgieDTnY<4d-4U?v_ z53`u)&VS2Cx{r^Si+-AXNaNpcnVkCF4XA@H3d)Dzi7!-S!#BAa|D$pE_n%@`keHx= z72Y@m*lYn+Z3X!Qn|9XCLFdIAxwq)GRtQ8Us>6i+*MN?qACW4p#bl4~l9YUa_fb|! zeCAfnL~^0r%rAtovA9!f-J@j8t&jTz zv%}F;YCQ{3!e{N($G~+hB*jv>?oO~djx+)FO;<$fjwbGRbPcdebE{8IUJeybG2PUL zzEzL2#;_@IUdbR;LZa^)igpRV>5Qv5U48ZoXp^&bOa7H(%38X;VzMhpEm!GFCz=Np zTQTs~YlO2)1V8J2i%`zi{cGe0vP$|$&A9GWS3&_UFm*Xh>JFy39tdexs2SW`GSBsM z83uCe=x?j^Cx29mJa2SN%;e&U2K1=r-#ur1>Q`ueT@ymt5lycKN@41#twGLrny5~6 z*0uYC_~#bPK)aG}n)PQXjJeE^&IO4SN4rK+4EhE_JI@ag%E$S|oN$3M?g0j(VpMAx z5*jg8;e&Oh1rtrj6`5cxX{tb@Q2u=tGi_`MGp60g=b8E=Jq)@Nbj z*Lm;1+%AdX-m#yaI~P%JQi`2bZ=VB~2L>mYV2a}O^iWbuawfFPWzNCahOpjt)ZC)m zUP13PV$m#9`M}7M!>7O#84LO|&N(h{kad8|NfD=XsI2#zF<5$Gu8pV5*Kmd zySjW)mGCBx?3Ap#MQ^@lt^?xjGFh(o`OqSxjRmcn2PM%~#W=z;?9==4=l1cNFHJtc zxzuaa@M(}zMI*l45@d38OhiXjmSHBvp!;xTFU12D;LWXCDbtQm{nYQ(i6I-SDLWfV zDUp5}%;~rrQN>hioaB9>6cT5lXcwr)Ia57#LMY0|*&^?LJUab0X6R?12RlMeJ(kX^rR-I))4}X>S|(QO22=~N9rvx4A3VIgK^>G6;{iC*Z=#c_RtPd;O5@0HZFH;?(KH1<4PPP##` zjj~Hcv+E9;XUaN64=rAAJ7%8cv9Jlwhl5cK-U8Qec+=}F1!laH3Y>M}%(*K<9B&MC zK>4^NxnP-mOQ`8yjuneo^@_C2`jNbgR++EB{&XQT+$U3I$=?TaWw|G=Uq)_CthvA` z^PB34pn9ctnH%95zQaVc3@2C9FDP5SLkbl7ZvW# zZZ8A-2H?~nHkJr^(7DMJX3_mr?fm<=LIfSv>uc>_I%sQKkDL?URy4>n+1RVwJ!^#D1xiYU{x7k>A2^#{UzUKg-Z^nHT&s{ z|IIZ(bexXxeZFBo?f+>%{zsPOQ42>fug%T>C`*6w?OCkg>lE$j_(_EE&kGC;hv?bp zYTCA}CPX;#4VhpjctZFWuF?NoB`+cfsP)VF&)@v~r+O2B#a>Y z3BUOI&`+WMKV+_f@8G(7A5Q+`ru=*hJ}JVk$j8Ye}G0{-!o0RofofZ7C&MxTu4_VwPyH)I`R*3XDL3aFOf z0k3Xz##qa3Rj0oI&`g*8PNgm6iTCA*@DH)=usqc367u4q;?@2PfM}dy0G>N$z6W4X zvxqiX)$RoRZpx-m`zq!Tx1KpD$m}*nE=vCLbh|A3fQO)#L01+2Od{h179f=1J~q`z zB3tlRnc{Q3Fe#=((qZ(M@$Hg-uY!M`6fsUaq{7FaUXP#N^X*`L@O+>VSQlnQBj=8S z&aoAS+y>6ArEQ~*$FNDTtJ>sF=C4rP+j9pG-YnoPac>6gWr7joyAvXr(ZR|Ww&6Q@ zbV6|=g>3fzKhTBLBDI>zU7s!gd7b}r?T^4Y!OD8+zUqse z&^QumQc1&~2;=zt&UlkjS5wmj2BFrw%TNAtj^~bDaQXxzr%&q+51sOG2qeq^(XJ3B zTBfL=o3XP0JCw-$t?o?j`FlJ0-#2_7!jR8To;{s-gk14eS8!o*a=-!eqCjVkF%4~~ zc7D4r`;`Wi{{a*4m0L3;GGMNRk=jL7^wa;t-gn1S{l0ORXeg?CWMps0YS|n~rR;I+oxO=;kLUXIo8P|qJ+J5g=da>*e9ry3$93QLb-jlk zjJ!%vbunl$0Gm@X$nCz`Ut-a}gH&h;Y`;RT=9+4Rwj@TtK+^+nJ$9;t@YTgCi|3fD zBELNN9{1<&5={uLtRrU2*6S;ED%5uD`T{Ignk+Dr$xmMZF@R2>G+RNkrwB@hY2Em{ z&@sq&ENi54$AEMq`~0(0{YFRxhIHI1p##Uy{i$mnl`tt}=oujdJ47s1qrg0h%K#y5 zLyEJ}#qIiSx^NMpN*Pc_yGBf)gi|}d8rU4F641u6qw4_MhJ+KVsKS1rgy$PtCY{+KcwQ4+g zIE4%zuIQYBiBd8q+0K z6t1avk^L!p5I?#_$OVk9e4yjCB8ZHmE8CO*|to?b*cbn2Jiy4H^RBx9TB{_ zXcihILm*qGjgt3!{Z8W;LCG`X9w88zpy9VSKIT^EK}pC}-1FA(a%t-a_Pw@3_<sWPgN`_*DBcNb4aMVz58G2dw|=^R1?;daHd- zh5r&IxB-B5vz0HHE;k{Mj*Vj|V7W!BF+WSsM3#rbo8&dfo*Z4#$kYLaxIKizfT5q} zO*N~wzXp<1dXR3+nFVIlNijT=8ZAok?V9P@ zJd;EOq}(Go1Vaya!Vj7oP#oz5CUQ3H*9e|d6DCuiEzup2bbD89H-4~S%(9ojfg$pF zt!?cUN*?*(nGn4+arHo_bT~F^)3EM!F?i%m2x+TmloVw zdD5~wZ!AL54Aj{XcygIken=z#^ksz9-sg(Y5`S@7TaOr5fRNogM2rA&?*#aI#1);g zqVDZ?w=CiKTUzdg?1PbBCX^u&k00|~~t5<7AL(=keH=n!u5_G%tQ4;Yv#8s0el$CCxf079jg)sS1J6M)0_CCv&U= z*wG%y2={SXK$+`0z6UYKo6}Wc2-zSjs|>PeW3ro6iWbY(=ol zvu~@4LK&6$iZXy^H1N8a#aUC^GU4xQARz(cKwk76d!NiSo}=Zx1K4ZG%Ssg zjTh|G>dc$m37PE$vR$O6oo#R>LhDCcfo_Tvjg! z5hCgUF(^Z~uWtij_SOj46pzm$@V#Y99K4*m4lI$<>a9P@42lR5*c{|SDnHXA^L?d- ztip+p&@>-bp$w;JNLrT~#hd!r%nP z^_`jNana6C2OS~O*@=Gk%}Tbn^f^T8m>4wB6#pKRiGb7*-J;&sN{-byo9t-KqbvwcjK7LSDxl-bNYnHSjE#QP4J|fD1qmsSEhs zCdh`6^>7m6?55IBI$++$o&&oY_HEV9B#!$G!%_|$MS}#CGZQWhwOE!l#Z$ZBU{U)0 z6E}P6PR2y7rY`TsPejgT7O=cOUl-xjYEf?^8E?5ky`yglcr93M!1S^Qt9}XkTKM-o z5poO(&3j0C@Mm8Le~U19S{A-$*DmR-i{i5D9z|RUF?EOiZ>$q?QOC)&aX_6n$=hG% zxGMTGlEa)HRvt{iis5HXj8U^{<2l%m$~UiI7F0mV^Cxa4SyN%4yOoF=pyP1yO0NG*MR zcFqhIX~i}OZ8-M3#)3XTDeX%HjPE3@x7su1&R(%)%QF@FOnZL}#toN_p_A~4=$?m7 z;0KRQVRWNJqgf}PR|Byqen6YU}EQKI_ zEW=xNAY@h6%N!~FbeiaTG-n-;$MY%ayQ7b#Mto#-Ev2f_X&Ens2i(5|DNQf%Mr^cr z0tXIJodTVTp479p_49$6f4oLu$eyr89y!Pa9JX)8^HY#Pb`&+ww34x_Tsz(C1^Qim z+y{1wrz}z7m4H_oQEfeO|NVo;T+6U;<7lFkp(<|T28hA&AD(T@a92F@T=M`?jAB}& zO$v9;yQ?&@)%{6I!h_B?`g7Gk0L@c05L=3NPCX%wzDSI|W+L2w=r0?&ya`Z(M>};}s{}?U#BJ81hJbSjnsjA9TOmR{qTUED>$Dc+XhND_u_)%24LICi_D}`lTP_gL9Ly zAFYX=kQ#Awzh)ImiiYjBks{{W%|zdoRO?FuFlFr9CZv*^&naOu2P(!^*5z)eM8Jq9dhr#Z6g@TbGb>`lR%gsX)!? z^29aIbkt$y}f(zQj9S-GG#UvqToFGk17|S%b&C znnTs2u9NiPVa1oUpD|4rxM`M{76P^Gt4HwR@Paj{KwGG}DY+lm4@K#i{GB11`$$CSd(bYPA2Q z1#l<$Qp(Vmq-cy9d6Ka)4~cGC8hxF;ZilkIvdRq*XX*VW26`>ynKg_=l2{Fbc&`ZaR7XHAn~j&<~6>OaLJofvXTuzxRWI>WIVczccL@&zRqjv%CT0e zv}9>Q*GMk_OtS!S1kZbw5eZ3`Q0qf~xML<9B$A?!RLrmjHK>%B`$A;rNsbgC01B2P z-r-D9=2;HzoGA(t^s*{TOM@BSqT6oR7gdM2Hk$DeIVkfpvpzN3VTu4P^Pq))s!=68 zl(qqpZnRq}m}qYdi~Vr(^MP}($8wwR2vIlG(`tUkNCs||aZ@|W=u4|8?`DY8w`g@w z&8r~#Aidl6JtlFlfsXY~igS4Ury@GY^V|8hWh}bwCFUVqa6&TB?=(PFf`XDQA^?d= zQVPnlPaF^@I1Uoom{IA2`MKCNbYGHb_yXsBgj1TC!YT0bF2*c*ifUG#?2#aNvN<9p zU)59t#+#LY64-l0aG2bTa|zBgT>!c2BaK0&2m0kj^H{~fmZ4+mQot?5+(ep7arp7( zn)jx%bz*S}YNQKc{>5p)HDVURJx{RTdpIHys>%0u^zu{Z&VYfnZCL9jlRGCFMah~2 zJDe$|;mDCihbXA%&RDkXX7D*}S^v7-fo53!If?6bvtj6LWSp-Pr8mvOf zeIbFV48wLt@_1~CWyAn*6NnfX;n$dC-MZ^vIKJPY_(BE~6OzvC@ zr#{GLgn(=qYcznMym{fc)iBdf*WqBE-ZRg4T)e3`D*uifhCDAlVE`)fNF1ETRb~^3 z0}$=THI=WcJU!-Its!qw{`Qx1IXqbp*B63wkTIo_iKee%WjnaHfE?H3 z?`lgm0zl@Nn{=G*=9`DhX|y;au~mBJMee>TVCyJ@nN`U6C}qrh#%W%y{^bgIpW8lqQlmOj8$$-Q8R5SU2I4k+n&1?3LW9I%fvUKjoT<=@rZ&rFTG< zDB-xL!qK+B)(T;>mSV_*qu)cigU3D+W8}UR z)@Bwp=GOe-5cV9HNg?u+(X8qSF?$o1*S7LgJ@KRV?8N8obxfz&&H}IgD2EC|iquKG zs9z7z|Fi~15Jb`A)oy?9B=cz2kn^q+^dRT|cKRq&do>$*li5NGfKUb0=PYbA%1 zH&tH49&`T(u#vjvBr=)>8Za;6NW;uYa-=<7o>tA~d99F=Me>Q;RkrIU$06x3gi*jb zbro1>@(?_05VaFEGtLE-!NQtcZl(agTgt80dYHlHbyk_CJA%Yku~< zA(;0@tc?>X;=8e%K8u3uLRWc%IE}@9si$qrzUhl+!xPvhj$HSSN;tUsr?p|KL5HA% zntNF{^xyICqPL$*E2jqWkD~HF!jAHymc7L?c^Fs5NX_>EazL^gYNBbtRTdLpsK!-> zhT-h5514ljIW@rXoE!llctDyTNs;?LxoK{*d;k#NJzGwdg9x$?8#J zZscmdtI}Rb?7}@C=*OZA;+2`xG;Jr$Jzf15x2g%u+jWXN66tA{*p968CLM z*gHyKe^(hVQ2>49(-(@9uUYj~_%lAHl#nLWEOECdVc%`{Uu`nc(Hl427s8*U`r%P7xpa*wyxAR^|F)go|+ymh@k%G+y)$G*9A)igvfg}Q-NiPzUSC*xEoWtoT1z#~hEo;%<5O0ZgN%F#+tx4Ne6*f?#2*#*hv*;EV4Ktw;4$@5PF%|SRCJy~jZaHi@ znXAi~C-y~#Q7T*6`jNR%Cy_9(&fiJRaEAc3pC?=Zq;BwRaVENqFf5KFpLV(6!k#dD&UGW&Z_pVFAm=dNwt*Kp{`SiYEbjhYW+W{ z2H{K|@%m8BPD6^#srArEKpLNFiPOAJa@)1y^P0Im0R1$cGpjF5_JX{8_T(bH|JfgY zLA7}GPIg4p$;%K&oOB@mk;7E#lsEWXt)`sNs~}$859-IJaknZrE#Kmlr0dz8EmUR7 z;ek^e^h|$(H7F!Q_88@=&zIv3eMM|wkjf2Mpp-?7P2Hcqul^>;fbeMU~G_7Eb+XdLq9|FoJG39aCy=e+sYIjvq1q(<}b^ z-41vHRx!~(hu(WycKIy%A1gKRwjxpR2}6Q+E{5=E)?ulfKlT8UCvSu4otZ1_)_4JM zMnryJ_WWQbEE`GuXjY#OQ)FhJS#8vx76kIddN4>yvWQ2C%Ez@vA^H?eqE6_G;!j ztTgU;vserlG_!5=hu)seBnHYL%ZY%tXhd<*q`Zq}&t|qF&kW{rV^M-n-r-dJ)1LGz z;@t6tX0oy?FWtBU&2*~gySG;}`H)BYXT^N7yA&xoBsLtnXET2y6OF;Z*A)IjMKso9 zPXRs^h|Fm%pK!V_pb?Vh=8ykfr~fU~hFZfUf2^A@=ITtyRgbT+>)&IkiCDli!mZ$C zXS+_;rL45r4DRv4eFS{4ENllCRPo620HxSHUxyXcGnDx>P1!}EwZ{am|BW8{M>SCr)8-cMG5qU5{0M>%ss07%BpfB-9~4{yj*~Pkv93rN|W+McfZ`&Lg`E zk(rMIzp?zWGKf5v8&;3W?N8p9V7xC4v05M7v(If{bUt5^@<0vX=+2=*@ z%pa#yvHv*zq@3FGgOA~nmu9@Jd3RwW7z+DOAOH40g+SO*@}1FUgE7$B!3(y;d%k!u zw2u1u8@eO#7m`K9XZH@6z3>T}KqcMF@b6nrr6YUBf=}?sQ;s)zV|HOg7bO35(D?Pc z3^AA(8f6%iwHzF2tWWY0?fK&3@SVYT)I$m3FF?@b*`DJ=X9=Gu63gT`4gad2$t3*O zTKT;dbo%hfBc*g_8@!FiPzpv=DQS&l+M^P6&i6PkY#V^8lieI{X#~c3Q zR4zgFZ=>>`A5S2l+}b2X3goOoQV2EN@ zsm-#7+M!@KZw^&@mqMg$X%av56L?15P@Udpi6v zLc^3r)8HwC`N~We0;b``$lO1-aU3B%&2_@n=mDqz%3cTw^ZD%vmVu}=;PduNoB$tL zvPVjTNgxtf2TF}&?Wz|iKp9MR@KtvESwe7c(M249%$&W3d?}-=SRM#gLs7Y_UDwNF zbq8_`o!Pf!d@q7-PXQwD4qSuIj}uS^&j)5FO8qoG@RL6xUR>?~FoK?k`wt|kn!S

=aK=f-64t8|B(+S;+8BGxgtz)?JM3P~GF@rP@yW8(mi+bd%x|`8OVH~XzDUV?p0gjUG0EHW2~|j ztn`915&vOSjWCpdD8#z-d`Msj z3#OyPVRE3N;)Z{wa(W5VX1eU_F;7~k;P<&N)w?w2y#%;O@U5e|E4_B|9m@WCUjw&x z8`E~MJG*hiUSgbN6)dRVcE}V2N8Q(vXA}~rm$ob|&8b|Xonz$!N=H46ugwiWWGl*| zWHL=vS68=LxgQ2)21pQ&o1K7_k`?7=NVNxxCL~ek#ESrP1Q$LYkmUQo$amaV5ik~c z`7540|aD?^l2@ryif!mavh{Eo9{8+s>rO!JL!L-=Yn@sCv)a+ z9+wH?5LHY^ZnbxtfP${4*byVC!mQCNsQIQRColH0fa666LN*GBw|wCf1uVAO0vL=< zXr@eJGnBj8=AOSice!ZLJ8E4E7=-FT#@WO;0vfD&xor(APAe%OeZ=U8P0H!NhM-5? z;NF}WYj#)p!7WbZjZu)1?#q2uSO&>G3lQeFC;O0B*zbBBEU4g%m3*k`uPpW2H5M-k z4+n9XgA*x5;hmP_z^aX{<8%b~rVk)uuR6PM*eGc-YtbWE=!NsEQ+1z*?_BvUJm(`$ z$u~OQr8QjoUKund(FufAL>K-8h%feX$ApYEG;zuT5G>nl?ODkgfU`JONlca_nOd%^ z_RiWQsMPOWsLT}@406vKOPd;IIiq9Mh>RqX=7%DKTRmbk!=us$8P{G{#Ol75i^f;y z@|%wYmOq!!s&`?cnP27k9L~tzz93$)yW?g&>+FRdUOOdyS-&-1-pLXeW!zT~2|$_g zON~Hx?Lxet?5exvmL5;UGlsX1)JEHHLXy+b&VS-TL6X@!s5%rEqCSK%GRC{j6>F0c zpYzYf|3WZAYWU{)*`7kDo+$yO{ECF1-rG&kr#5!Z_isFt;?OmUGMs@|=e~=S_z~x$ z@xIZ4ofV!3Q;lNr)d)rmWGyLv>6sSlnr5cL_bjK zIMWv~|IBxF&Nx(wAS=O?Cfno&Ng@b$%7=3cbVJ4I0Zffjq~Mnpddn{RaqU^VI(=e3 zG$PXFv<+21)av@nrvyYE1Iz0%;FaaPBwjJSltN$XxwRV09#=b9(hLWC$c z8fug5#v4{Z=e7p{7vxF<`j>C@=STg|Ki}ahmNqsoT2a(zIY8m=C7y@jRaoM0N0Udl zVTZr;^PRPVB#9AjBE^OLm6q-IZ-FTXy93!vzJXFEMtoiZ)yb_fnMY*0H3!JDJ^-^I zQIuOF6`iGuFq=C;RR%X9NOWy?qobm?z?Dml^F4x|LDc0yGT2-tgnB3hAY5gMcalj0 z)#;dGt00n(5m3qQl*3-ZopCdcUtIRgc{IF+@D0FTY9F|d+tf&-F9m7V^Dp z?y@~mI2@W~{`Sl1Ij-zR{UT;{-m%Dg7u@cGW`6a~dZU*g&-abePdq5o2W+Mp=r_qa zS7rgq^&wh_7G_-8jY4rNqq_;5=-_cv=eBXF>xyC>ios2+fZr`+u@Be~LZGW@Lm%Iv zR5wc~F64kC73`kO2%dBnB8kJm)m`jv=DZI94Ql3)G~Yl6lC`!3OoyHWpRO*<62xfj zn*+H$YuPbMKBLVzzYdZe9jME3Ze=7tvmKbu2)%Q&8&D;4pGpU@KRk2J>ukiBy2Bc@T8P6YGsW7!aC>rdfOb1~pP*Jvd4&Ho+xRN)ZbQTpPil;v-fy6n zM%$35CdpD*J@KB*=c;0YhPb9I$zD)EoN_fWb~_g+1^9%-Kx#iU?nL%i3tv*#pRg73 zI0X;7dTRv#@YFd~XdAS9bT$3^|A=<-+vCkD4VLLHP7P;Uo>v#Ozlq<(KQIgfE{Dg!2f{M9GhM0>OV`I+x z*2D!7%MwP@v+WPWJH!Ofso<2ws(W9Gl{4T(MT4t}kAaHor$S{@r`y(a%YdP~?Wy(w`%H~K$~8K} zSQ|gmGA`O7doz;yDU?wX)v8tQG_QlpOXb{#=_W{qqHN!TrjS{tf|wv?S-W{$R=;57nnz@M~nitv8KEH54QUhLj);22HMO-1$*pw zl}JzHT@|0C+&l7W(4z;ZC)u;y!vyHFMNr= zp<-!tOO|E)Z0B#@CUocEv^-fPS3k!bcBU@Ibt?0h4;;y%6}7GlH8$f0l*OCj{Ez4 zR`SWAX7NzWBSST1Dk^nI1s723WMl@ozR6$;bhV#Y%6xcHQ;~BO_Np6p+DGpac62*;xhvEJ&ulF$9imDFZs%^qpj;fYv+4m$K9bEwG~P50Z?y7 zQ>*ibybk(MT~T}3oTbLGw94qg-u5krX&ZfanGk~A*4pj+R8rp)VXb~;*Y#J zHY_nt&s>?-&hv(^VJ+WO9=r}{*+Nb2r>P3u)jy@ToS6QQ*hQ09Mj#wk=WZ zfE--`^&N3HZGq*VVF{0frRg(L+pm?T7T^Eob}ixx$IBMGoanaa8($kQ#eVQhRH`bA z>r~@-Vj#^)_=Na~yGOBi6d>bE)05jyWHPO_CDW_p?xd%0Zd2Z1jck5%Z6p0f&Q-p( z$5`s|D#451o0swVRwr;PU4C*u$4txG*Iy~Q`>b`oy=MWUq{a$j3(*r?=?bjom~G5) z3KAmj``P(X3 zfbrco&FJz>_lzdvFBPXjzzb!}#(a!)DCMA)hc1PBCQl>i2%JKPYmN!~Z;XOyvh{vb zdVM7ZY@P{;(MWdxnT@C`+2MJvp%Xfp@1X2j66ZV@5xGNFCd`_>wCb;iC&rFsD;aYt z&#a;yHt8htC~yGjPj;ocn}lWC5z;aRdpKIWi|W3ddG8%@zd-Na!GX{WJ;pLw-2BcB zatDqOjf$6sJ63XMy~d?sq-t#5ll=9*EwVxAzG=}CQuakxw3Ph1oNPic4bEr`98u9Gb5fKOfjA!0g4 zD06#?qfDnIt%(G>Bm5>>$eL{^2k0_fQI}u(86Kq^2+j4gAqk{#za~^>SulW__l}EJ zBPGRaK%7D;&YwjU`33sPDUnjHOW=u{!hZBK^|)ln4P<0A2K${h4M^fU$30n)`{i=c|J?3BDf{h=W=jkQ%tz(^)oaNHY zQqNTS?$#^nI_5!~fHZ5@PVQuX=6QqY0m)RH9%Dy&FZyhi*N?3mx>Ex$&&c_({+5YQ zLiFmr194|bufjmMK8pe3!Mr;I5UFUepg2rw)Dd=_uQ1^kB@%OUe`L2S-!d{-kP|aocoJbpV ztke%|*RqqPWF#6`&BGIx`_Z)aaz{(wtk`__vxcp-pL9d!O8Ih&=i5yxD93wBR4v>N z4-wkITpl|JGkeRF_A#_R8vCELAUUv96=G*SXR^z(a z9Mich>Nb?HO75v+M(^;CTUIJhNG@@C!13mJ8vyL9`X=2#m-q83DhDJ!ot}JpsHZ@) z6J&Z=++$@lSLce&Y5Z7A`K69BwrJR zJ?`BIzb2LM*C`|Wm&yh4{2)&=Rj!5qs`$@=;%)HZ|81aNfd5X|mKpAH2qz2_r@6K=3ut`|~o)t!E2k_xnr&-etTJZgMOjjH=RZFydLUE5~ni_rw1xb{OA;e67>5%nd&XTB}U&$unlzeoP6p*QkAyOb3ZH(;#ocA51RO@ z9uwy>FbyvLGSz%CMwhuLm(&QyB_o(c%o4tVO+!U4mQxYGU7DV-%WG895?ht4Crfb) z`9$wwl@u_hs+f_cbmvzD!($4c?aXCljK__6u@UR;y*H@<`a-oyF zqip9#zr;Be;D`UQILxcR+WwwIIQ6aLRn*J(hRp9%w4O5ZKf830i{AmYfT5R@KWO%2 z(Yo3F%+zS2U#azxUm+`cNi<9&YXl76fdu4< z`~mk1dqKHdRuS7~#U$SR4+q~^m@cR`h9V%)da_|Rn~aD6zl2)Kx|g=H_Q#@~HG|g> zFTkaicNr}J7~-LQ+^;-;-s@aa`xU3Cu@_CPTd_Bs3dZBT`a#$$z6^cP|GskLi(5Vy z>to4gicj<^WLvX%XEioZ^T zw;BsW@tAcdZ0PX4-(Z)Ix}m6>89c3S_ux4Mv%@(|NkGKV=DGZW-~HRh?|s)c zZs4Wza#TmL@tqHVS|eN~J7%}utk*i))dS3NN{3MR zl1Qa*9&14PLei+krd5KJU@{#`MWA#OOE8sj>X^=_N}02(KQlbOzRgzh8n?rL3K}lT zJn+|~JKZ{*#=lpH*Z(}+3Z7{kLEhwoih1-H6Kv#edz&E17oR`Fgt z#hOfK>j=lmzM&jFtc|0<{xATgUoghotUO}+p;47v;I*G8HJ@z?*iCcwm@;s1q}=4} zP%!dLGOuq-5_hWaJBi*>xEBsMIWkefYurvj)c2;Rr<0FYxr?ME&W+5wE#J=78oNc3 zo_b!p#1BB99yjwX06Wi~-C9%ZdY?k2Ygd){AQz3r2gS5QH?jQQRoHME10sKF<$0F+ z9F;q;A%i%R=$KfzUUEKx%t6YW12? zZTVNy$$vUwn+|b;s7vuF^S}Dk;rt*aZJ|{ieIxJ#5m(8+%EYhlQ2We^a)55iHO*-- zm6#OA$_h~W+O12=K-)v1cqJ$Xd_?EPfYu9ao!7&+%1?4@y&Q8s5-}LonRBlYT1HY4LEdk19@YN4<4S^^QWM?bGOPGH8YzAwj3ashEaltB_vu+No?z zj5F$3koP5*eVO}J8ub&y^ulkHwJ@VuGoCgaahBm47stB#2p(Qe!QRSI3UozfoD?2V zup8R&fy`*VLS9BjQojaq1$mvxtu-*j5z854OjP@(A`?P|R(U#a_5y@-)hVv6hPYnn zhYLZ&DxgrEmhOugP!@5X!K}-YpPJ}@oi%aB{&GosNg6f#6)o>oU#&`+%mK9uVd0lQ zfPOe~{#a8}*q4_VLj+p5?GtMtad^Uo-BVpUhYUnm554^4M&Tjv(MVl5=(XDn9HK(| zna<`b5VdW3Ykf8m;W8ScH#U8(-W_aH^8%GJ3sMWzzpao67!LMx{aMi>r$AGeIJ^Hi zoPJCY>eH%YLH~ZhD!-WSOYG-{!ixpY><756)GiwUrjPq^lq*-p#N<69RArK%J_={z zZ5j^pefoaXpH$wFJavRhg^}L`i@n@&HT{#jEIhFDvEoF<%0&@B8*w+%IEhqmDw51) zaG+Ax*4A!M4E83eLEOf_Ry6Ogej_4^%Ulg4pz}S_<+^Ztwnn}w$6|TC<&A4sKwP`p zw=<)2g-5Q3nzA29Eu24s^8-qG`T{DeU*^n!{v2eFU!H)w^AQdI$t{X;Px28W$ZpkcC>oDKpBvUR;PG`){ zf>)?H{dkf;!{3Ks!hCgB$tOy0xNOX*1oH4ST8Qv$)+||0F2Gz%G-)wWp|8et%>0z~tMbtjakMrYi-z9NVgk8ZKW%a0C zttw>H#y@TF z3jx&B4W#kQkl8Mo&+sc}`|#K&3OwX1aUW-d?@^D*W1}}EH`^Eed|C#vQY1$I2%qo4 zYPm?~=DU_v^#KOj58vA=R$f)&Q5}JP*IOUdQ1(@E*Qw{}l=p7S56KoCudswONkPY3 z)_p**5vf|WS5bEX{y;av&T$GuMY61JV$Lx?2FN-_T1?b*%5AdZ9%!G9MmW_%!D4o2 ztcYTFmDXb%HIj(UB4diB!0s8!|U zxja~on_2}?Xu^u=)c#tJzKy$boXT?pE^uHDA4L6SzC08`bj!5(%hXVh;MhmZh1-{% zl>Pxl8ARQQdKv6ZPJ#Mr)_)rHP>e$GZFc(H_C~i(aD(ZLs!x*NxFW}CA_15rQvzVR zo((Q*QB08zXHBzJMc+|3wW()+MInp5mg160|1nv;oFs7pR88joMxKwrgjYix#Zl30 z)fOkJylO~{><^nR9Bo)kSU-)U%V3%^+=5*_iW=9pPc!p87ymQ8i{+kb_j0I%f#Idd z(99r3#|m&ii1EBwk@?$ zeIdlJfk}-sd817vZkENu-b;RMu1wYr)QSwZ?Y!VkQ1 zca{+5(rjR4lU?32t;Y*4i$2bhY*-jc#J!k6N}k%;NvOF z(?iQnfl+ZgRI<73NYNqe9`oe5NuAvJq`5ZMF4wxxi(m#oJW6qRnrlAr(>FMYba}{8 z5NBz@6%WwuS5{XI%G+1(ta=i$L}!fxAG~m=%D;4<%De;-*OY9se%%%d=g_(>PkL&? zDr&BBqclCvhjpj_hv)i$7i7i{O|vq=gxqTfcxa{kJzwrrzuBT%Rn^R;vxlpjxCtbW zL5Po}e$ciy>hEyGbV5=e|FBu>LU9cuoH!<_kQ~l|h+La}R2f#G4N7bB@(puz4-Kd*cxS~s;)-uECiKVm9PLaj+LXKYMOA~+gfV);g2ref`@$gJp1 zDHT2_-Oj>&-O)-6br@5*I1(oBSQ$ty>d{=rM{Ec%dhU*d3>}g1V@4lzQTL}L9qyWllwf<-H`U%b4S>$V-LAbsmmExl0YF@7x9+geb{oNxvui z3*fU9bc}1`+2|DWlJuG#r`A*Z?-66FM9tP6YA3HI^Dk31yL0gy>3+yVgfFl@#JnoY z>uBavmu)Y_NOkGKlPW{cO}3XEG;J(K8Xin0a4-E?2eZaqOr zB%yI4Ir=ZRIOR!HYnB43KjT7EnOgC<;!i$WOV>v-b6Ks}5?-W#VkhBAPjvny$Q(WO z5%(=qTn#UzWSlsy^!;INe1t0_GkFV^{5CT;b0PEL=Cl0o+8$^Nk}lsx1Y~P+N+xF8 zI@#1bR&k=Y%9EE}f=e2t*^5(XR%A$4OcmM})W5v4=ClJeLkX+>G1Dh%2MTw>f5x({ zsC!R$d|E|03yosi%T?l!XjA%LD50HxpJ-hGY3x_Vo@Zy3E8Pp|n4JY6i@dQx;XZLA zYcmO(S3-N*mQ+dp^tNC#Y%5w)RJMw%!XCmnJ4w-$Nmp{^7q%QB!}IP}$LmXe>&AN6 zpVQX0msDbG00^M6&o;DPijMbo9JM=s+0Blf_#UoDillbRMEOp#eJ7hHCz(#jz)5D+ zhx2A{4YEW2y4ODxnFYWVt{;{2lnXFvPiwzPaz7U-wCpnn2Q??RR(JO3`K030_!n?$ zw(GT)LP%ybH5l@vAYg;*Ogph3o9D~?_V{1-1AwiAN}zI22T%S33C^%?>FP%p6@K|t z+feZx`D^9?IVIBl8$VomEFON5kp^c)6u#>Z_*;b@xM<~jcqqF$5^`mh+n?nnK+(Th z`w`d!l8e2?#tqRYFN)(nyV#uit#qV$U&&N0Q0<QWP$~F; zicUedurc&y#n%}6z54Pj(wD<%`ARJzL8K>`WfFkV(GF#wPpY{; zHLkn@fR@H&mix(Q+g0Usx%`cdjkI2d&EMcy{{@pvNFqRVgMqP#wx_j0%W;xL+?Se) z67ndikKyBDkf_!F4Mp@{qZF!}kel1k{lO&6>kDD_P0p}~flV*{IYIroRhW2>H-s3N zf1wHZsI3*;U?a<|m3k(`1{jY(`TKCjEQNKC;e#>6ur&e8&8!WBb|6jqSPlwRhtX_V z^>KndX<$ypmR{xZUrqZD3h&qd^ZECcZAZU3%09WgP^^5s(x)86x7XOBGs!uRNiyN-U$=| zo>?|PL`?X4#j7PbTc4z?U&+YJD+G|;^$N1_pgotG64Sp1YwB4KEoWs=gz=a#RZDi1 z*FIj4J^GN2h`{9hr@AE|;vVtr4x-}f6=wczH2>?zJJBLwyC=Kbq60-rPojH5JCm*H zuU#7z{_8XUwQ`ZLb|*jtaEzISGFMe)BEeLSy z$qGF*ry2m&K033<7d+xVjMHGgXa|Gu{u5&#RL7^x;Ow|(&>kEM%kMn=Jkj;p=tNUum6T2{rBjaYKZI)a{bC2lsU?G|EKnC?I1T z+duE-doBuOivUxe|KCZF!1%v+5}A?z@pIId2kw1hN!A0QMGU_w1o7-|Ds7z;%I62ZM8_B50=#<3e4uA9Jjo`DKW z0Rr_!d@>i(p6+4R(XTUx6Kc5Q^%p4*QIp#i2r{io26?5q(V zJ<&a)GX&6oP@h>Wb4G=2(^utS}z9q*y^tf(_)qVpK zUDqnA7cVq$GBlL@;N_Y6_oDq<6s7x>@Qr=4O8!*Y*bUPjll0hYR)`^biQ}v(mr>of%dJQ8*;L>v;xN;B_5Bv4Hf^Vk zPG6cqVE-22STYw0c)+WrgWgAHVDJjy7>G;{V@Mq{Z#?ONY80q)y@~hS$gC_#eavFh z-ZrSWM)cRzJ+jySbDSSBBG&@~x3z_#0uaV{P~Z%zIx|pjXxuvQZUa}w9;4S5ukJ3# z?`~DAYCE*m%}ctSoFTD?Pjw?&Y*5TW`7#qHT<^M1mX(*+=+=p(SqwiHTt-CV_|b&{1hhoLiZI)frXEo39iL^93+LlC)2tk$sk=!!mslYy{{f!F z_|6Zs20wz#U{0MIq78+gXui1Q)}n*>ULuN`n~0QSIlkSl{2^4KXglo0t1Pk-aGDvA$-YbK_K4{88-P8x01}M7@EJiI5LBA^jPJswU28Cz&zYRyoMWfH zWjR2O8cKE+L%fR43v@^M#UN02YLc9>cE9Jv=5Pd2lC6ndfyV+f9Be6 z|9ixU5Z|LVAzxR32=m;`0Tip5(VaI3tuBp3Aj-ao_~#~w`f7)~7}UB(tV#YdBgVFd z#SwsVJp88k6RwaH?>k`?#w1?=5_~<6kC0L4q!WP|xQ{lVjH6Z|vlioE!h|3M5NNlm zx;_yNsJGBGTz+q-^FO-mGty<=Y@v#rWS7DICH3gVPvsKc(<)2(V>&K?iRq&^esZ;b zKiOqiYSvXzlj(jpltZGeo8N()?7$T)4C71yTkhe!mO&?y=>jn3DlvpgO+$(IjkXh} zl^Kq`m~Or%kb^n_WnZ2H(7FSVSwpa8QSN zdLhIn@JhiO0PcM$cj_{X=t6We^~zzU)I(gGdSwA}!Y)3{zG&k6D!)dh_}l{R-VHc) z2^Rm@mrhtKeP@A_XhM_IhzxMVg@VvNh&Tz^du|N@F$SIe-AyV89y6 zDDeicU<$HwOKQqNybm@&(}BPHoBq4!6A-xqhAB3!rc0~vx`1aXK&m=I07E=wW>Hd) z*w7?PgcKB5^pwPIB-Xq;cM~}vAR^6H1JKuwaLh1N4X*+q;It@6Z23t%B}VvGhQ4;WSbJ}z#Vcc>Gp0f0`LcsY5QvDW--bxaFTnuiMHIKm3!3w?<^D+lD=u(ycs-V* zh}6B?De}Eu?R`4`mBYKRj3_U4y#MS^Tqz4hjP1d^=Xsm>>2W{OqgWS6xc?Gry~~XR zSo>C4+5Hl!h)Apj9Ex%`uHO3^>DoxH1luUu4VZC#7{n0s=*ulf>VESo5+3^sEC2y5 zl!Gep#t2xh4h!31diANJlls;g}a8#Vw5kp@XY1VOq> zNkV1Y%#{L~hB)!-2J`jJaB(^ef5NRL0ykMe`?%@)|D`b=|4L_oGwe7VIOUn%X+1+X|- zz1hHB#m)jGgE)~F)IjASQ_%L(u(7$RQBS}e{150E8#v_sUljuG_E~0*8QU1oHpt2KT%A}Lg8}Pi929=vm zFdxi)230=y2>D&>F2DuOgN1%akH{`N_-oi>yh`2C-|9~Ykm^Gxj$rn^ z{~eIOyn#+g-bl)4`5#?V7<6!Y%KlfL`hU*P5GmjY$KCDz{Rs$IHlhJ=tb>rw{re*V z1R4It4)fb;e`q~0ffdktSKRI&&1MJRZ^}&jTbvE*1s#ag2S0q{_mdZxZ3@1B;*E0f ze}3^V%=XX!w9yrwc3v|8aqlEPi3T^E7vC0QQLhyQk4K}P>hCI!;P zZkmj(JOR-)M-3WR#f@K5OH{w#9k72%Eyb$6BzPaA!%yAg@(!LE6cck?r2Wrn`{$y6 zRp96el$ldd3M5mT)jJ78p!^V^q6ktS*P0#(H+IOm)ldO*0cndo0{ zQdI)3RFKfMoK+psqu;G#0d9Cm&=@MK7W$liaCiR!Ta%<~s(w8}{hQdM<<+I%^ovIh z;;{8D{`;65m$boRxo0u9>W_neM~H}PMoOn;7wyMVNZYK#p~zgpEZ=d-}<0(3$>G1px~(ukUCLE%dUC}bSBopIA< z>BH-1lDxG0s|@Rk;mT{ev`{GU67fec~QqJq8*} zFP5K|{q#)uBXV2%(?9Ds^eJ6HV~IL01ld?1DcmySUjf{lQV_Z6yM%c1QolwzYD5<1=;y8+U@44a%f(G>JAc? z4X&_A@U4+FIEz-&aqK-1lB^r_yWqky;Pwem?Wib!2!4AAIMgNp-7~%qh>mQs7kFOz zFa`95#{O*?{dEufAe2Py83U3xh5*UGUYuD)44{Nn9R+G&b(!CR-#7^}RJ9>w65LLo z?4ISh3sBzzX-1Puu|6#1(bUUBpBBuZjTi80Q~y?AX%fpY=e0frO97W}4Dc!xhhhLX z#{g(f(Ek!xiW2}?L7Wgf7Jc zFbBA1AKs%6=dJ*cqznL3)A@yoph(0;aH<6JmNzoUlqxfynLK1b^?pr>szlwcD{p~+ z6#KgwXbl(pFj~eU24Z)JKb&>mH3Ny@bOiHdL(-){6MYI`TSkD>g4W5IU4S$Sd_-Wg z&{6CQAu!)s6#Vz@hoQqv0d`mch^ogsKap<(E(ZU-t%cl;l5TpNnORVrZ|mb4@G;`r zu_-}h6=56#nktYK2c&Xe4svPvx<4&x)(wi&On{Fw1zSNWNS4It+=nJ^?CamAc( zMF?oJk9OzygP-UYF3w$DR+fXK5z;LmYSq6!ru~GvAG--wP`hIc37bNc8lY6xJyiNR zSr?Q+TcP0Da4S|epgCCxjdxfP=v+zS0QjWDYO#F+IAbP&@+4^8L0k_WD7Vwp0$6J+ zJxa3#*j`n_(=F%BWj1zp5AwMBNJ976dG$lBh-0^?E+JK(iH4GGAhGnDTYqe>07&Q_ z$-SWmDF6ZJwBz4AD5e0GM$KYlI0Dnqz@q+0vG+~{DBv_F1d1h7_C0K4uxfKpVmQ}< z5IKW*yU1<8`eD8f1r0#TZZae-qo|^X14Q1#)Cz8_S1ksYLSoKEYz&LISwN7O9>8}W z;xZo|VZh|Q*s3EiVc7O&YfKPj<8|9bD>=FY<3VNPMNco^x#=dDPBM40PP1`B4YS;i zk`VDma!>W-M>UZ_tzX8%16_8`@DHkHU7V=tx zw%CgfaK_Lh4iqVFlw;C@6hvN2R{)vLPdRFVr2N5Sz<{p`Li3PZ8VAr=Qh;D*=LNsV z>bt*69?avij+7N4ilbAXwow4YETLDc6%Hj1wN zz%&EIKq9dO)}3Qi7fl<)cf`D?CTdm*Vk@cus#1681b*lgK)H25t>TAz089ocq9L$| zuas{CHrd@8xnN?RQGjzYodXFb0<(@=*Io|9F#yZ0%u9S!tYfggCLjSPfQvHx2*IwK z27rtCcq!oP8DO8W9YUyA013p{twbOc0W_9)kH^VZiP-KC+lItDuEZPzL;!+BhSrF1 zLGCbwq)#)NLHU8%GdqBZ+2Jb1X+KL!MyQ4tfN2FB{g5*K1Hh0WNDmajqgnVK5_ zxlrDeu_1T4&4xN_ra!+yyjw+r2y!z>eEsGGi7atirSO970V=M7!T{F2Owc6)0_{yf z#eIc0u4?D7{TYBic!b`GF3>K+m{J#CQ%0@5#2!0P3&Z3m(M8M9e<$XF0iFIPKv=YdQA>j-dl ztYPP!)5J4z zYIjQIOwB|nY%s3vTkYFZ{_&arBo~f9)gu`sl138EF-r>>p|21RH-zQD=-*HAf+=YJ zp~`ZXDt-!t1s9*;HzxBN9A!YlC0iiB+xA?JA^WuqVb0`pAc`VXIc|}ub@;vZb4L&% z-gH{tSWe?-K)e)8GNmX<`l2aNR%PZbtL73EEd#?D53!5c1Hqw5ahm)kr!Pc+aDFFa zNsu1!c0sZ;8fW=MA{@U|mR|B6$pe+8cNEFznZWv54$0Ca0CePDwrb&rnv`az2d8WAPpVCo>qmysX^z{23Kf@3dRW~hsXtTqnPPZl@0gcyhB2lpiM5#cO^N4PVAI9GJ2 zMg)k#R~39}(v|5?+40&KNr_9=%%!4&1ly)xrb8cTW62`dTu>z95kz_SK!ihfCVV9R zm7hz|+DeA4`V98Sav$EDu4uO1+s_g+WzRuM0pQyy1*6Te8Di2v?|PjVLK^ZCP`6Xw z|C|RtpQ2I&lIS!4Kvy1QM1NoF{SiP!C(UDTUTHQY#&*qvGHcshBg7JL?vc$=e%j5& z3-Li4qT-CwbTaa^8`jS}wL6?Y0WGYOz>aBtycTVI-lJC9^&~fX5)<4!H+`fJ$U@&I zK%qZg+6EBnINz9l>8*rZJZvJIEKVlUEI$;#76QJs4BeJTD37S4Pr>A$xgR2< z2{a)CC9EO@PJs5SafE0$YgzQ@NF7s6@mUHt>&Kw>c6oGpF;Kc|4(Tj84XkCvZC^Y) zZsNXn1-$hT2V@J%D)o=Vsbg=ewkUaf#(>kIqX#3Od&Gu<^MORe1y!fa_hhp&Gip7M zBw~5fulcT&13F2Cgq^W1aXGw?vpeWbq0;P*=khY>Dc`E!%TZF5z0yE_-rN!s@3Q}O z$HTIm=SdxP8kN96%R=mx0W6v+h+U?t1cah%W+9oSwm)Q+_CO}UN=6Vg@wdR~WNTrR zMhY*FYKOx0z($!qqWZ$PJoE39FR2%8bM9TA8Ky~U)D*a4HQLf^kl>iH z>9O!pZrj3Do}lL{g{&Bvj-Y1K?{R`PyIM=Jv%+OaUcoUEu1K2$?b z2R7K20?_nRe9rebA&!``wI)+|tcK54?f3;3GH5;~YfM^ik}s%>2INss{a1Qq2-970 zhZe<#__OF_$&rlS7K36+8e|#PvN#_GxdC*2r~J+ABI+C>$E8o^ODUid_dvc6l2)pe zwsem73>MM$cOpD>bbP;weI5(=-I+#TdJBMx%bB|_N=Op!;k6IprHX6JNkRvC&h+p*m}jy3`^wQ*D?An>>&N;KR4 za_N^+8`Hy!b2t{w_lei-1^M^>| zVona9nfYy;rrfE4XdIkD4h&&kA-X89>oAWnnxlyZJT5Z5KQ*!oAX|M5xkNk}-zz}= zpToBhr-mQdq^xttfbUaXEZqzz5?&&X-E0f^Ei9+TnlVt4OMN}auHoJV&R`oWv&>r_ zDdOaH62PbU3Cu{6geWb`-dIo~ADn9`sVzQihghJtW&%{J(v=w$pzR)x!)ifTj*}TU zA*)#orRHzM6C<29DVI9jhn0Od{4)txLDJ#QO5)xRaleZ?X5b8RJo2Wmk)a7vnqLf< zm8)!oO7RI_7i~Uj3RGHnd<`lnAcy#fE?UMgDf`ComqG+LsmI?3opQjN7ynvZ>pxjrL@3o9|B0QW^?bs zi?m@MJ*@$mM)@G?y?{<$Faqrnq^!j8QWE5GsuN~UvMXS}F`6R~mHMU(N;7*Zk48sE zs*^hp(d#ly`ujl8e#R{GSaDkdt;x4i4n4g;7f4>4&wN+ss=0@pfU9aqHs-mOlUvZG z{a8y(yIou-9cp@;*81EQD-l)4{g3WfCGB!*M&(%&@sulr zM9c(>GSmf5Jyf3udNdblb9@8Z1&4c@qqtV0Thc2b-yA~CrS4_KE&h_bO?CppMblNG zU|gm^GF6j3$bGDd^D&Rkk>lX)VM(R;gcM&jj^1|pjZ^HAfezF-WpryFBm=p_ z+)QC=5g`vFu_8d_&~{Wsl^$$5MFlrdr0yE)3CZ3aJ(HPQ29D#d=$8RULvusa9yZ{0 ziYgdMW21U!q2(mr;=Xh^kKJH9D;;RVgIL4JDS00%prRl-I)tLg(_%sm<~8 zF2haSwlHKW`NRHd5>f^V$$oV%WD)GDwWdRm>`--Tf(^^c!V7rN&;w~~a`W-NZ2(2I zK(Ga3a>eUi8^;mL;s|_?DFu%~>tv{LGr+0MP?~UCV!KeI#@4DSio|EEWq$)Ttz!7~ zz)`X)-?fjwwxI}IsPB1W)kM%JbN96B`NgS$hb!tRMfV*rVMfS` z^qSdzjvW&wU!?^(1E`}TAx6kZ!{p>Y80{=6%J-nWz6^W*BLSK5RCIB&;AbDQbdSM( z>Vu47Y68y&-NH7`cfr?#;(tX~l0SJxRf@WOiOZpelRZJY1p&ldgE8(dPqeE2E zHFtoNU}>Up(zp|{iWZs)4XZN1PBebWE6v+C95=n50e6jk_=f8;>;YS<<#`dE#(wLp_~qg>W-fSn*# za_WcnH1dq%Cbh7fd)oF9%TDO#8BPg<6AU}0m0ODb6%4QTD_St|wa!?-B>V!XYX+vW zYTg9gveOIC0$j?BheuoYV|@g8Sy(6P634jaDvaLpKZ&M3u3{S=16)$t(CNBaExUD` zmk7~SSEB7^$bEmTgoj4uOJHnr!=)-hbq4kqTt zHjgq-jnFdi(Derk`)~Z&@Si`3zyJU9dop5YMuhqb>NO)7HdlzUq(I)%N4p=Zu;q7`F?dEO>@Q(Hfyp%m#~ z-}UF~;SlTa&<$n@9aDk@3aMZC27$O4EekDjJfArYjT1F@Brz9OL<8plBE3kXthQFNArtsS;BQQK#p$hSTzVKJS z+hU{ZbL%)M@ZZ$VNKEIzxTaA7cLAcC+LXQY(@W9<(bX;A$**rJ90OgyE3agfv{^F$ zU98XFhxIQbFX6oK-H^z@AcUA$QU5jYe?5VS&PhcIBZ50TVge&@U&(^vKc~pQp7f{& z|ElhmX@Q8yLdeKRdHqAf;Nv%rM(XPrWC-g-Oq z`E`$w3XsOTGlrS#nW!sAtLXM+Tx<9jV_=gp;Y;R1#;`Xs1uw3*=>ZZV%L6ONAG9~M zS7?n^|93`1fAb{sUCO*YZbM2%X1#@n6!Y=29 zcZ~d|H3%?Y_kD(7juRw5wJ&gIv?-_rLPUR!*k2@SL0}N?fa4{+!^JZ8;vrt^al%i+pe`+ zVBXuE@!(cR;R+bTgROYU>qmGI42xC8tcw7cSMIC1zp+UX*i;)^&Oiyvt9Ki^-liO2 zlU?c8RZRU*hW?}9*pv-y3UBi*1wAoAXvuf&EI%{l<*&$|B{X*5N)--Ow;J;%P8kMhRbsM_if+s@Rz*NEGeS-?r{Q- z7ye^SqPwX7*m}}9Fb12Dn4VSwGCq`k65$_OzlmNY3mH$nzcxmH(*Q%C;DpG`;WHK7 zzkcUA(w5d5W_e{pVFM=rHPzfXyyBPsYWrT;F2PUo+`G zXi8TY-toVm2Q9qXLllV4ervLW$>PJfh}rEWSaZYU%MG{vL(9zR1IO^t*p*x4?dXC@ zKQVzhzZnCM6m;;9*kI{QAXdF+TvW`){PeT-?I-&0{B+gT)!8jTRn<^M?|6M@NBUc` z$4`b2&F^o2?Z{+K&oq#;R^=w9BX{Qn#*seE?!P*MP2VJP=gNyn{@BGJVbC&>pwJ6)IV1=-W z+zfM1*v@u(17|jC=Wf5ln}Yk8lhM84!Elgpc+9~$W>(?UnaNk~iHTMZ zXmPj4-NGxjp1ziGUF{SUf5jbV8+@r!*P9~g#(Y-eDqmglI9@_3eeO!bjNbTFCV}1V zc$Z57e~4pdTISI4j4xd)(K}+b4yBV#Nrh)Zu(n2+TCx|34Z=Nt5iq@ri@26WG&K{3 z%gtEWD&9SRIT$l8oJq90Oc4G7CPT}uzq~Y9L+4DdR7tUMr&-eP`!}6y79a1+b1H009T^2F>H9qC{(g>Rhje_ho=K(3G*LP=>`f_~RPU+!{?RdI@$5h*)pt6TT^_Qq^6JX@BWC-cPFZbxI3MsGpD44zkvPHKzy zb+fww%x-F>ZWUL`#jR#ZxY`+l$xM{xm4wXQOXcQSm}CL$%-%$zJV#`(g4fq@YDY+~ zgtW=y()Qwuxp*sjz~^+6%E9&Mw*d{)Gc#Eti#4WFixRo)J7rWfQrh}GD>Jt9oi9cj zObLA9PGxEpgQ!^hmqKYH@(oscwG^_POMUEe^_gQ zK+;37CFoY4q3hE+&fmRm)A(WsjJJv}&v`{|XRn>1P$1Ko6Gj|z2#age^`{zEI-=B`}koBVz$DBw>TWgx`R*l9GOGqGL_16gV%dNa? z&vLl=6NkuR^?gj@rGR=8n5Bi*-W7T$H@Tf2MvC5DPtM6xZj2B9{9!@PN&GJRkiXCJ$)U;O#x_Tte zvf>0RQpr_Sk7^a=7088ft^#4EBMr*#-oEg1*awFvM=R`uV#?|J4&78EIhrDR9qoy! zUv6mS$kjhtw)&Zhm8sja?Z@*a2SYnNGUkQv$2M$&zBTb_!n31DBu8h*0fp_j-0E!} zm-i{UbacyU^M^5SXujh_4)^QHqZQMKdZiMgsn{5}X=g)St#)s)3zMf4bH~f$?8GHF z%Cq~Tc1_qC8Xr+mWageLYZVZ8^R6}74Y8C|Yvx4e^DS2^(lJXCKYo;+!`}StJynS~ zq8bWb6+@y?jEs08zUU3L1{Bq?V4<0PE@;XD%=N^(-0-QWHg%?(vyeL@d#4T#N6=^*QxV z&P?`HDM$jVYVOo(AEfF&)-{}{*%pn8FitSVbkL?rJS)#7tf2WkWKv6czsQojLKEP_-l@&+V9u}p{$A< z!Y8ROE7|31RTVUX*I%i(_GoQHhbWb*!X0hutA@EDCq>wRMW$*v{NekW7o zJXrNjfaQ3F<89-Sv;>i9()`dsz0*lz@v@=)!^APPo%Xn+o3uZp7qC$`r#}5Wb?whQ zSnV4^zP$IDlQ^?a@!2>r`JL=K-a8*2=c3JL z7PxkKVXgV9#o9b5l1QT>!;R0Kb7a_31RC-KJ;A}VAyrG~pWj&Mj7C&RH70G|knxdV zE?GBooOp?zPVF5+NeHV;(Tk^=&fY1`ss%6ZsZeEegmcanV$cUlRr_;S3*;aoQp=j% z*N-ARefgtn>dE|ooP4IJf}WtI&)m;Gy**O@xjMX0f}x)x!jw~{t@D-f9JAi?E}Zo! z5=>`eb<81AU^u!ij`NCUD-1bdH;q!LVs_J4a|F=9XqBr!ahH%%S$Vz*!jzN6@g#L~ zYcpNFOf7TlaLwmA%5Ho)#M#@wu3Yhc9@V)xBxFw_wBqs&nKZ3MUKk~;^|KTy1BvsE zLK$1rTj|9LB8Fn_C7~fEOP4>^6ZLQ-#U3IXHQ#l6VZ@*o`9Z-qFW?b|?UHlNNk5XF zlpIUf8x39CkDBYwEs46q=f}M| z5Vezc*nh`~L;O;Oi)*PR@~KXYe7<;e&Bw<0T4k%zj}koQLv?)RMqM@wc9!)|5{oK+%*P51b*1)D5tGRBXa1!(N6SPm?>E>C?|%{ER4c z(=Z&{j>vaqh+-r+f9G?!k>^8Z9yMwmys>OBIqT$^6@$zNp;||{)t_{noY<7y4Bbvt&K7B{5Wlv}~)URnQZMqbA08*&hn2Y;V5az6K)w#C@ss9gs z8Y7dauei^2(ig=B-LxcxqCfF1&#IId2TY_$-`5yvz(?t28wrWbo6S?-{fgv;?+wR) zor}~Ac~##mZmz=3!~VRhug?`l{lFS4HhdU&ZsV$DT1Blwkk+9-r`3l4b>Zzj{+~x$ z#j$nj&62O&8~uIBJQ6~f{BYf!BD>3Di-kd;Aq7Bs) z9`7XlS!+l5^X!sU;-(+YAxMW;F~!~Fa7tIFHOp8VLZ8` zEJlL<`qZu=i-ipuHWPJU?sDUl64&^~L46k-phl)Ht1UXITxN_FA549}Pq-^DY&6jY z&nrTv4!tKubmQmo?h}*&k;x$x7SVUz@5tUKm@YlgbCBt_+u1p6hH>MD9T$s{zF4?% zv^y(E%&>C$xhwJG3JE1eT>Z*(WmS{e@a|jGMg~b*iM_QsxHH>2*2v4B}im z8U0Z8EEc$16nXeZSTMn@!Uw||I4T_;11Kz`4?RXBr#UJ=-p`_iuh`|1RC9>SV=J@9 zz!F8>%%A35r}HhdJ|!A$MDg~8lT+8h>`^Px)OvHGJau=>;@(ufJIWE0zQ1*R)oJav z&rRcm3T3gmvtuQjetQ!KxmZTsm+nM^l~^J^-nf1Ug;VX#L0@l)-|9fyHo@#T z+;e1&a(Xl3Z+M;{RJZP$B{|HTRAN_tC|5_8g4AK8k90_>U$QNv7+ z$0F`i{4_FCNyXE*-{N_uW>3OsDr944U?NwGh2o4EuDL_!mz(MUpFTIN*%-8_L&SPd z*?idQg*Se^UbM{l+0%EgY@NqW&wlhvwT#mdK2F^Yq=b!QC7120Kj}FBVK*y&zzC*v zDBISvjYAcM3_M&{ts?*4^t5c39ojfUa*(%qbpM zq1LWE*q%FmTy9n%?WR=~+e?x8EP7ym9_zZU=7hTXCxYeZI!`WRsvrDlLA7~xMWKI~ zj+vCqq>G;Ja7RaOKdOZDg(khWXd{N})-b+mg?(IG(V>p#r=;XYS3(rcsV;%rOLo#(;8>bEiS8lDe;1=kz#vdl)Tb`Sx zNJN-U+#&V}_4|VfZw=gzIZM_E`RD<)c2I=h?F+hzADGjAIGxkD7oZI75peW z<=oF!iM>B!hoG%L)O%58GAEPzO=f#Tfm2_aN1@^^TWw!JFyJ>8Lyo!buJ;%AWRI)8 z5_u(+{Jmd*r8LC-21Q_+!5dj?)tas-sjjRKjCQmZ@4MqpLhljtDv*ZMJ)vs)n0s!Q z@g$in$J@eH%}`t6n_=mKtVEuLsYivod&uODg)} z!I18lt81={@3Pu*58+sH@F|m=v~Y*4wZC6xHQnsF*+5poB%HLEw0=Nr`AW(l=PO*X z1m)nZFS6%-dsmIdH%*^^y45w;|b%02* zO7ypLo%M|c6c!Xk^R#Fdq@3*(M3&yS7M+g@${5LY(VNN?!fNE}jlKLGC>&KWzgeQx zu1^_#*I=%tTk6uWi;Rp+*3dsWcmq>$%^*-);`C(H-NjdCV-80+*-Bffw7!iJ(UD39 z8`m(yzV_+_VZ{r1 z4oe-A;O)WuBBR}2laSgRh%2vgrd}E3S@eJvUOUR>MeOU>+%!K|`>tpi&|lYn&)pj9 zrw!4;3N^fx?O3c>Q=^m8&X77l7|u|+O;L66eN0vl9|sd#U<`FDqei{hl_%gG)7cIR z?Ed0*wDEKeco(7>PyU;rsQCS&iZ7Ei>f+Qu5}&vX?Tj>1vj&4-a=FS02T|dN^dy0~s_?Et>x#Zctcx)_NPS1{eY!28| zc_r2CRS-Ub65O72#b&OGXDV-|ke5dZ&%%6t@7$S}PI9}NGBlr*(H6CsIXDR|$4-nk zHBYtHdx?2sp=0)l=~-x~)E*xUXCLi}(s5GGH%gN%3};0r}KQo3g@F% z;{JouM#wB@s9M{o&OL+kKTn@x7K`!)gwkg+rsCb(s!klLEuJa~fS05}K>_>a=k3`L zwy}fq$1XU_msI&R3oCh#Yq`R{Fo_nRkmq{!hiJwtIJ9Bh4{BE-(r#~Qi8rrrdW1Y_ zEtG+G4+TsAn1ow;vvP-xfP`&%)o%6Y>XF4&L0b5*tupL>=rhXe9RH_~<3Af)+Zq@z z5^XeXQx+42yV^2&6ndLZ&6_s^ zFRy5a@M1nv^q-iOIDU=G^9_z)iKgP|(F;ao(IykLm2#7-!zMpbJ2H8mt&B6c=8tL$ zZ#_MZEbNs$Fm|L+FqX2oVDxQYYQ-wNU2wHRD%;3&(*N7Eo05z;(1lGY(}-;jJj{iU zAFB#lIyyc%kZGRtKjQIcW>q+`2B%Y*A4OmlsOa_{t`el%n5>8)NR+ut2=(ROY{;%B zVn(Q`A9BujOiaeKv-(#APpA=7?Z?-S&*nN?0DR913)Wb00mHbP;ERR~8k$IMLEsyxnVq{!tM`no28tAguUWMj<*&L>d- zs%B}=5Eu))(W|yZ>QdvTV(qQO-qfHDr|Y-2wvOO5G=qn>0G3(8Te4X1+nyD2K8tVD z!Tu)P%1-ARncruque>@m>vXc< zx*jS^jIHR4wl3{)d1*_}79kQb0w23OQH?W_t01+E2l^r1k+w^X-{9R36|2Sm1o5=t zLLBuNoG(FluN#gKXgE6c94=q*#4(b`h3XdUX1w#F*K&wlnDKew)9PR-j8fZSyU^Ag4;)r#pmax=j>V! zEHF>x$(k3Bsb!*x+w*r!Lu#|!aa9zD>QN8L`z=%#~xNMh0vLu0#y!AyfWN+xJ~J7Z6_ zre#$yI-jza60)QU?DdkuI)6ZW64TM{K(MiVeo7vrFe|UslVIjkz5!Voy0x-Ri z)O0G?e3n1L{cY0lB;ViqnQhdfy20~sd)&H|vM=hBkW;8j*U=`|@$e@sgJJcu%jHX> z&gLRSPC|#R)E4%<^|XfB5)mCLv+t}|0IJy}ociXB2KrVwvv;ChMN zU9(XzvH6L!F2qjW7AxFU_6B=Xuq(ERCY%1WAWdFl_Iux(T$~3F7`A2EI@&N>&n4e; zy}WnW94O+kBRrj!KU)%Z+g(kQMj_|9_mKMzvHUjid2oMZ{2f~*+^2W7yZtv2G}Dm} zEfVnZRO*O|BR?fA9hoR`aF7z_Q{&?=?QXZEV6l9~ixti~OAiDK(Mvx*9o2B|8&;Wl zH_7(!$|?w$Q8UvHr%ZdUX14d4xbV5PsOFN!O#tj=RJtdTEmtI(&dyXtA%=#Kh}ED; zwv`nl>e1JWkchZc9VR2khx+v2dQ44B@UwSmf303J!JA!DNgohI*E1WVM{b|^12~M|!0K&Oanh87I z=eQQa*mRQ77)l?TMd79o10}n9cwRP{2Bq?{_Z^TNs3iTLn(V>*M{8~%=w%F0W>)e_fDK5^+6EUQL$;Jxs7XoT2} zh6bx#5gLmVr0;x8s93kolc?2S;n-Ydm=h?r@>3mR=YNj6naXaxOn3f3`94kX@`s-G z0~F!Fwc!jTc;(V1>xn8}d>ciT$MKxR1_Sa&YA~_IBa>2^YUzB~1?Q7sL-RQp963d|Slpj$C7bS;UW& zat56td)$DNeWFJs!Q{=boFE>m0J_Q6qf|}oD1-JhGv|@gS0-9~WUw`(`TN&7d-RaA z=dc~`w9-on<7A^H`D|+I;{zAMpw^wXC#o-Q>@!nEcXrx2wIk@!;F6D*zDGFsFz=1Q zRPbg}W5rXFfPP$9T>s5WzVM#7p7nK8UUG}WgSZPZk;n7f8ZjEv@F94$OcIjPN>&1< zNX#M@7yFtX&MmEK!US4c3`ccewH-cWud5hqu;J+h7x*JmJ!JNVI}NRkt97zZPuD9x zN_fZAK5&eZ`xuu+#{;`_?x@vR;h_aU z@#mbVIgs2-=FT-A+{F(*c-bvT!`cU3U)_3w(GWVZs7~NakywHyq2CLC_5bVlKv?;l zq8;v<%$Fn>l$&7Fx|YNV4nS;Qf`XgBQXr^IpzMoS#>qc2BK~73dF;Ogi|ABKH|jH) z7k-pF`&|O)Bf5AYt6lw=1g|+?scfM3h?uyk?t!P;l6_46Kn}ZN-5V1bjy7jS?Lbv5 z8RUX}qsP!~*Ot#hyawM|js^IZb$wj{4;La!L9P9Lu3xuRcP7wiyLt%9HNS!r1VLgI z3-rudAU{MM4$lYany3EqgXbTDj;QK;J`gkgpU|k`_=AAO$5!419a`&&@vuL4tNayv&DasJ~K%V8qufjz7Pp zivcp;!A!$R9~kd+%J}x5TPK8!zu~z>BLNw|{BidGV!RJz{5O9^2{B+i(#-|ye{MeW z5ip)Q3f}WQqo?!VM(|I2|N0^6DU`KCqEt=CI60RenA3zf>pCfQ^*HW*gXAzz2`a9d%7PJ{@#1nZ&?5OMGxy|;3ZrtVv@K2 zX4w~N=p|ixO-bM#B;e7kPPmQX4`~Q?CifpIH<mm!Fkb7a9YY@DeEc9&s6#q8=eASDV{rWowPFB?G%LRlt~0xw?0tnz2bqOx>^Qsb zlKBnS_^-fMmlo+50yn>|5}HhlTDiCkc{b+Ol}QjEd%(-6pY`o&LA?PpMP>$mWW*Ru z#7196@8!o^L+55qPnv z!Gh6ke%}*-i5B|F(uM7@<|uipvgSxgZH%Qwm-?02%*jml=!*4q(pqU9ClKzRzaAvl zm48sldupAHOl9%%otG*{{&SwU^dF^NGg}X;WKnI!9a2BVB(SnSok}Jv>$D`FLxIm- zk$)(DSS9viN7(4+`kEH9xq9*KkAf1shxojGDdQR<1=C^NXkUMGKoQKkyfegvz-CH%9cbH_5LA$q#*mCTPHIWCSSZ1>*` z2oA>lctmjY-L~cITe9HcrZ2<&NUqS+Z_$P4xvOO>Uc&d+;A-3GT7pU!bVoqHM?oA4E{kQBy=NHp@-W(l^^ zr2Amdux}I4Q#(Vqh=jd}on(_?rkX~_K!WyhHo7%a(Jy+0p_H%>BCz%Gw>FIpv_)PB zD#0zI?bW9b`Or;bvR}?5_oXJphi#sY4(byKAJsfQ%p6IN#{Fb8l=JMxR zrT5{~Plb)?2rqoI+egB-L^FF!k~Od3OdcI9b$@NzCPx_LO+}E?oQ1DhF2=`pX3y@A z3975X<8@lK1F1pS`gu1m4%+VO;mfEtOVU&zy!Fk$^+?Li;2xc3^+b(C6qf^zc6*RU z!BI^P+f3J}HtL2v!`$d=$hycA_eq!~QubUgR$;jzgLFE(V{M?auq%vax%V zTC^&vg3MCLe?@A88xTW&czB3Lj9(x(y8gKxmqjIkHCV*ASr7Z6RP?90);(G!!><9; z|A(-*42$F0_J#j(2myiz5AN=s;7)LdV1qNbOA;Ws+YB1q-95o&aCdii9eA64_qliH z-t(N7&(qzD?y6N)>nDr)3ryN^M<_z)mhD=JP&ex4_}1ZYe6K0@hCdA0CR>GeMqDJ? zar)sB+x#JG^)b&})_*H{*5qKGBIJk5W<~JVarzquu+FXdh5R5v15RX*F5lS@*feK& z*tcs=u*=|{US`+JhmLoF&p=aBL$GTIr_GY7qJj@=)I#?=V=h+oy$pBbx>@FjAG!!$ znxZ(uY^lvj4K7yciO$An3#xulN@R->2_}K`O^&lnV)Mo53wKYf-Rb`Rm|_!3NiV-zGJfsB zN*P|W&nr2Fyq$AOfCkD6+lIJOdl|UtMvx&`SUfbqQDc5tg zq1a8>ly_oM$)L4DCbbXN@Jn9v8QIs~|7A*KeN`_>v%lHTYu+Y{GKIw=k=AtW+!*Yi zO2U)64rPjN+DecMsAJGJxA!!8YoKTf&gQpB1P7g|ec~iEsx=ama-nelQiw!36^O_b>Q-#S{$y$Mgvxa%62Omx_;YkyJjrKdv3dRj1`p0m|a-Dp#dgqDe%F z!zOJTjN?VaDBIOSo=&&d*`1+Q3y81Ud@*tna{ucGd?eXtyHRMrk(JI$wIkZ(uv=NO zh~$ch_cN-zYh{^Cu08+ zqW#g|ZBVZj)oDtOB)S$OSJJz9GL*wNN=sLm)R|g;uJC2Vn@Si`{wW0GKekrS-rH&d z$x6q0r|;@_iXsH@Wbl{!=OC4KCjU0i3L46V1o^DjBP|`0eT`;2qrz(4#iL>tSd0(r zH*zonFmE)0jl+nr*JyrxYj4U(Z`FJ0o+eC4OGUSv1-8Aot_LYcEPAD<>Vi1Qi3;1t zu#7SIwyiw^p&-s50j$*L%@uhDwoQo=+(T=!4X7!yNLep>(PHB~dOLp>$IG7a76)gJ z`BI1LM)vM&`={k}r3YcclHP6ArT9vbR2^735yb?_`GILwuVgs6=3@6yil8Nft+U$x zgBu#RN<)2TDpmtHv8&%V`(QnW;(OvhxS8%ZvCwhYFAeLe%oo2cx~~{G&9o1-ic}X! z3}3$it8I}HC-#*%F%D>2&P|%SVS@a+)hoNtuF}!HjtBJ5+w~uD?n7-^Lzct5Qh1PLa-%W{u~PtsBfxqiU3u79L{vnHsN z2UP7bOZDPfm7Ah+J8{vu9DUuLeZCJ`IJBY3oA^07t;ZzV#U8@GKJ?qa)VnI#XKfA@a+e0R)=%(fXO?uaZ(xfdH zn33knA%wB*hP~fdj3^3Kl+U`HcHlb^1)N*=<*|aPXSHH?gmdiaQ`3gxGGb1xTiXup zS`eOrD?@-YmM1s%`o0+{$4f$fWUyIce^Gh%!^RDc;l`wLpVAoH_OjyGkxy~SMJ__h z#I(K2Vf%&lRe`Bb^Q_{&RtZVBbD?*@k%WA7l(P`qkaD;X^A z`WIujgInxceUJXfxrMxvIu6#4V2IA2^jqM&8>H?x_hmn&H7) z4W?U17kply&ozjl%6C1=jtZ4A>qg6$q$DBRzPiYo%jFCor3y(gy~aHYx8aZ+)5!_uCFPnl;V;oSjxAErMx5mg{ovKGip7Us?2Q8eoq zxAweg5Mhzn;|AVnpYFMb( zF(uw?ZOAIy>B`Q}cm6#XS17qpc*n)hMUIj8qt!1;Gjfjz^ZRV>m)L{(w3#8bR)&HO zk74X}>JsR71JqJQ0V}BT>HAi5Nd~A3-V6);MYZU`w+pBA7pvUDF~qHL{PPU0{G{Xp zf)`!^oVXYGxe+bLj0ekdN)pO;K>hmaIzXHzC9Bkmkq9B;Vt?7$}V!Hd4`3b2A}q)4L#u zD%b1Re7X5S5K;w`KC#S1fgcTv#$hGfi4=QqntwQ@L>m6ByNkdXtpN8X-tPRQadzg> zUPu}aZMpoRvbI#EeHG(U`4P1M?+xLeRfq$;QpcOBlx)I zshFM4JkeeLAjR-Mr9{QWDK>8BZf1k9~f#-H4sg z;3q2Uxc};`s5`sB;48{)_>4o7DwfcTKdTHX*%4aR>TFE3&P<(Po!R~AD-Sjk?p7A5 zBE87R0g1kht`Yq`6SpigL*K@ZB%eF`J?C-wNWnf6n#6a;Df(x;b@Mac*z`HJ3$KDH zRqG0F%*wzd7p~Pov`i{LDq7^?jG~NY1E$rVSR)3Q-<`2y+AcJ;d!B=FQT>c$&E!Z&GJ~lA%~_MVvPfZM^xkJ7LbLpo_}$#h zHdtDkoO=1E3fC7t#|LWzV5hw|XA687Dmu`OJ?G)|8T|s%nZOvX@F@qK#BzePt`Wz2 ze<&zyH&Qz?-Vf?NG~k|^UkP1{#Vvg3zEZadT$=(jq*-d!2;uqTI*4Yy`8((1=cv=s zRI{t@+Em#TFn;eLTRg!$J`<41SQu)nWIh4+qMp5}f&BAy%g>7#F*9#1j}&?P;)Onc zZc%AfbE^58U+kBj*Q)#lQb zsg3H0Umh9fB$-+tz4C{a>jS{)f9PYS8g2)Ju;ay1=iQORT53?WcL=iqK?y}{AY{(x zTqDGzeezfE={)d3PIWO99iK2dlPiPMtBVZzeQ7!RNpkq@k+l2WS$$1IHp|pi?q--p zQor^|GsPiq2raB=zD`*a*JgG4v&0NK9V@$EgB<~f+!stm4?rZf=^cs^Ob0VF4ASWL z#I?A^AB<-<8ZTWpHw;z`#f5W>-5k;Q)C{1ho3Gs*3GcQ3>EJcp?Blw{>mxy!UiVL+namX=!zc=*s3n)*8Y?@8_SG^rP=>88klNu8q**6!#L!`GYrlLCe12d%Y|l@N%MSsYX&3XGc=)PyIusn_k&^j2(<8b)rdutqSG% zRtxiu4gw-fQJNR6EAe~-c}s1q%?(zRxv0Ny*?qfr3uS3y;<2;Fv6)!DP_+5N1WW!( zk^}lJc02Vgtd4OH-7WEfSL--T7dN4@8#3~yC_%2|j)(D^6Z)05{s2*Kg{k*B#+z#W z+;9%Nj|oQkJ^1b7@tl*^y>7tbgcgonH2rlvtIu+=3S~hr#S+#D0Y9ce)VG6*3qe*ZcTfB{5ut{O&qQT}7xpUh5fXh-9 z(D~LQ9{>~C9qyY8`usH0=CFiAXK4zU*N(PigvzNY@%M9$)^>JqiOZ9UWw0$t`vz7| zdO(G+qi`zI3TDgZ6iNC|Zez6sLS)Gc5kYllqaI5*9~uL(GE4VzITHcpK#$?|YDt02 zUCw!T8^_PXW&mCO+Jzx~u37F)6(Xvyp#$E_V)w&EllJz0bffqJzaCf$WZUnks-cG63@m}?or#1g;PM@ z7OL?)h$BXQqi-LZC9rsYb{OCvEqfHQBB@KA3t@|dtZFIMW;vsp3Ur?sO|y!%2zi0M zyP7ub!9Kia=5anlm8GNB?Qf_P+(seydXfe#6=f>1TR-(6QO-VzwaJTjr>G7%q&>~N zD}vZMyVN7r=NL>QfVR`2zS*}nSFp|IY!^!k5_KthoP4~CgMCQTc`?^GMxT>$T9|9 z8=g-l4gk5^a#FZMQmYlpv*qM!s(9L_nvHBzitVt}A!xPc9t+k;pR8xvB$LNBS&B_h zbt?`4m2W80SSetiatInb2lT*`zcvcR)R+vxt zt5K!^`h{2s-5ifC$UxkXtBLiJs1m=jbY`Q^R0#V9oB8Iyya1{$BWr^@Q=-U&;iU$N$^&aEz8;m;s*(f>Ol+dGh9MdzrfEmQ|ny31wCjq;>L|Cv8WA$zd8(T)anp!mD&Q!mDgdUb6Tzv|ILB z(FklyEgY%3@tO}0SR=Qs)!I9mJZJlF+bN)Pxs|D#(=-zd^UT4Y&L2X%^8k`umL`LM z^A$3?p)Kmf1fjw}mQgOFX5{Q{EA0|7#@jU#0u(Cj$YR7h(?CBENC{phOc9qYH9Fd58nkk&&cL=CjJC~9tMH_mN zNO6uH;S0?%58uCOI+-joAO!UD@iwL*FBp2OE9~e`sIW_>W!V-}GO=dh^nie5 z=T~#FN~T5KjDMIOWMtE9k5~H!XL>88`O}eoN)w^?(SXRfIDDH*ucmC0*`gLmz;+`S zGexEY6=0rbK)UB*Idqf#qpc%J=Fb) z@01VVs=fwzuln_!afQ-Jx zW-NkCuuWNSaiU5=if&MU%62OcCn`Hej z8-!0gPmpl~|A-b1nk_)(DL#FcOPG%1t%bR-C`D z4Oso+B|sJLzzy0VwKLJ1ABKcyC*HTIdfuEyKj23vJ~Y~*eEjs<8FiGHTVOR=(~{K$WtA!{J_XmP*SiXmN2 zD%N(m)Fm&FWGH|Uv&t>{xaKNqSWiBQ6dw2))uAa>k)T#W$$%wTg`$uc2;1!Ye(eE| zY=nZ_P{EtA{5ppF&|+?KEJ|3F;gt&Vo?0$KI2z{Q5Ibu48;ahP;xwRGo*yoBy~%XD zQu;Vve*YfF|3yPBTNHoB6JmlUwG0yR%tLlaC*3b3)n)vK*RD7-$AMxpdP>CN{ik-(_lpw+q9G zz4cBb00pyR8os+hSE|$8bpSYrq?wOKru-RwF!!kykblN}U>fZSc;|M}KZTg%))%Qm zFypm@Y-BHfbt{GT+&VbmHNf6^~q}xegl=JDkYL|R1ey`LL zyVu#;!1@syY=&4qM1hmNz+w;>sY}ENnf_FG(Ejr`)@1S{M#XaYiIugJD-bTl0*#mH z5qBl(M~)?dmpM$AAGF&fH@0R=bHzz;sK4vedxKQfz0Nj`nwcxlV^O^-OT+4msNX#C zTz`&#Mu{!Xh7}P6uhga*twx)T4Zd*Z8Z0@S66-XNv`mBkDe7Yu=Dz=&6rN>@LdzBTO zvt5H~(h?AMm9&DT%NITd?fn8OLxnG!T(}q8nFy}ZxutxBur~W-Ulxch4XSuygOD@o zrFk*ZL^Fv}ZCKb5i5S6CGVXgJOR*c@#a_Kp=onqE!GHZFuI!g^sjYFl79LFyGudi8 z>XAGtnjb-sT{^OsLo2!AURfiR`3#Cryb2`h(=igzDevrdHaMN!7tP?4sF75SAGBrw zf7rJcC6n^PGT;5u+w&CeIogfl4j^OZ8bWJJsF$;72B$dz^hYdvw-jigqPgfZq7 z^W38^Oa6dnd9*G;h^0U+gMWaRfsZQT^&)S0j(`(Ycdhwt%b+#iS|M0~oI} zr5Tf|_xJ}oXwtAhwrj>_JRa-AzyV}3OC!n?J5q$INrR$XI~_7q6- zcU$uOo=Rb_Q!Dh4iGJg>I?X`pEQ>I}`0@Dybo+BLlj|3hM*-F-{ZoS@Gk;11Hh)s# zSAPROh);lzA(VFUBOo6fnfKW^?hhkAEZP|+%}Tq&I}{zV1^D@A;b+e-!)h_H5<&*k1c}Y z-?qZ^KhN;QgVWVkPY#nUnX~i*X4H-^oU>6fe0R5n`6mK3EP!O1eR@~<45h!nnRK(%Ou$Yb)B-aK*M1K0X0pl&KD}9 z!O`M~Rvk01y|+My7PZypH;uQr1nf-ACIf^tUD5|Brr|%pdWzlRZL$lAsypoosUP#H zY~DX=H>UXauf;-QrDr@DrRJICy-uB-tQde^V|r;8mCG9Av7K#K!p=}rrnnkrGOrzf zatVJ!52Gr^F`-)hWr(42T7!sY_rl3|VJx&4%(I@0L)4mb>&({N^Rd-sUjQli?5Vbc z-o4us^r6@?M8V67UBYMqQv!na*>hVLgW+~kp0SoW^J0!>Axs?PKAQBMXPk8`>~_PQ zTHTm*un9d{4tl61H>vg4YIj_p7P6Snci}1Yw_;>9yKgPzJ=4yY zyp3eAxZI<+P|5j&ioo{zOXRD+@+Tv}T?<8G)Drtp0buV}981#bVkRd(*+GZ8h6D-O z)RrrHK}+%L8cY{I7%TSPlb)=l-0wl4gS4+f(~J@PO$390U2K9xcr) z9w7DELBb|MWad=f{{N?R*p>l4gCRq;#wkH3Fv)0JyL@286k1SD3e%?SBOVNb3h0?u2=yXX>&71&prt2y~{W4C<_ z%77O_)nQ)Qx6MRm2SKI?0CXt2X~-auOb#brOwB5CX}NbJ@j5wc6|0}muu=5GMdWYU zZq!`=bEe;9fh6a1ryWvrPDR{&?^lt?jYcR@D_}4So9eSvFBR5rH9%(+y1xY90y+S# zD3LVu?v2u+92%quFRSwN>)0z~uFTTIDQNMKcs}QoXAY4pvwaiY__ui7OzL?TlN!Ru{~!*1puxx$HthRpzTvq22fYw)(V1hQ*8t{$glE z(K;_p;fFEaiE|b(zir+B@ zb>f9Yzh!f(q#ZFb*VA(F_byraz%I0BI6U0|3_S+cp1%piQ%}}v)suy7p_9N_&T{uK zedR)qQ7~0!;^&~rO>E#JLzv6(@5CMHSZH;&V3@fh#Nze)&Xd$0080C!#`q?mMbg~> z#G>5pb9E$t#{s17pswVqGJ#!UDVwv`KA;C6n8>I2T;8<;iZ>w;M>OeuZ9|FAE6 z{A)&=&muwFbC&@`TL3ic6G3t}-(LvsxAC46{JzA_B@KumBAZeed!x8wn1C6o`Ok;2 z1-;bfIu`UfVR|({H1|X{7}I0JHc5P+QU9WX8YVQ--Dd~vw{1j3GXbF2AgUs|0v&SN zr9P<&+nRNiCWZ04PhGIvODa@H`Cv36%E-&H)#{JGX!fYXKTK0HD|*M^48ulYejtB? zR~zU3u>5Mn(5gKge!d?F# z`0xO~#iEG!g6|pL?svQWw66MhB_^)EfjQGhPrJK+L_v>!;3w)%`jAoyV?ybiO_ZsZ zeSfuAoHjSZQqMrL#?v5wSEF=lJQbe1lUiPCIf3MfeN~^11KQWD>t*{=#KSh7Xxc;A zRnk@TMpe%rx0JueOzKogjZOSSU78m5PR7}^5GV~gql<{^owGAQV;R0|e3YUB5e(hpefVtT=pU%seAH z5u`504UwZnQS%+#R|OU}wOPNo+d_4y7?V4k)C}4b0e{+G|M=YQz_%vEi=2uOaU~R$ zr<@12<4&#$`U&j1**#80##_WhDBdFbd<|aFK5-8LHpu<8;IQ08MOA7ZiWD1nuJXvq48%k9quUv~MG07MQ|CE7KQ4HjeVzDO&74{5=XT>u zJne4mTVM(o0jsX;w!r{rrI7_LWF_S)s;-GbEHV8ikU3%PH{H_a^F^0QSNcJ+Hs77x zt5o&pIdw-37eUZhhDz0IN2~bvDY3>fu`w0V{7)$luwxp7p?b$sGDHU!3AG4m29>{w zNXhW%9;~6Rb+QNK_BR3-`*1F30CxXLnYfSzI`;q+g(#4Yt+_K)hf`oN3C$!_N>nO5-;Y zCC$1KP8?GOn-<1zO=<-bP8P1LK}G5bmQ|SoB~xtE*b^t+sBT=}9~gwhP1QKA*OJUc z;O3?R2Y6jMqluH+J5m5Pd%dgi2<($3A427jw}-jcw%~xAK<;@Fitu(OQ4RN)( zT3QFgB%#8fan$g`W^~JvAc2R$+wQNC=WY1n-Z)0|UPwwp~*!4v$T6v5Jzx=D&-uSxrqyj!18A!v&Iz3+Vz*FGz#QNS8i^Te3y1 z%}Kfk7{Tt_%0WeAYgg}bS9}`^t+dP4USAwaVi2{6P22#*FalZc7ALk%hnZiKA+heY zDj53kA8OX~<|T!M=ewhczS&FZDZB(m;tq6__$%nY=k=OUicdKA#djH;sVI6=#lEX8 z-73=uws^udDO99jzvfaCrM9BOiZ_Zh&8)rPUECYua;bzaf^b^hs@rh0$#+Uil8}** zG_cnRKAh7hZnEkY^U7S6h#q995?|_vT^f=&`S9>trN)+Bv#sFWLIhAb8||>P(jAW~4((Vy`o6XJ zE0bynq8-0A*uQqDTAoY}nh1Q8J5%LK*ZXvMG6ta2o&dm${*brU8h|g^g0{r3BHV`6 z-lzi_d!#1TA~t$hs3B5En^J@d*X*jt_ffeOZI!28hP*R{JO6}CoYz*22tHhTC{7dL zFD62)EIjHZ7~w>)O&(4qYocz9n>CGPsLbQ*nG!m=PmuCw=rez(W5cI(JpDs8c7@lM znXCvupkw6vG63r`rs?r=qea|Nlu9GeRk>d%$bE`mUbhf5a?MdpL(@OiBuiw2V&z@?nqL(sP-u#dyVo@11szPY#YTMHrun%b_c>E%RDJIzXLl#%@%38Ce4 zweO!}nKQ}TWdDmig?9gMJnSGGL<#=kv@*bqM)Pw?sQP!(_-%-;CS;^hA@OD;f*suJ z4j+Fjfb#A*Q(q8LBw939esWIGZN}}*E3Zq1pEH(LAJZoCBkonOF&-jYb7PGnWBG$d zQ+#~UtVKDEX2@E0==ukar{nT$^2Le5`OvD*H$XI8fG(V~Xb>9)OyH=r&041l+G| zXRELB#0UsA$I@;NJnfdqEpNL_Tg&+>ed*OUqI9V6OB5Cbgu|pdWTe8iVA~AuC&3F6 z`tX{-LCVAqrV-xr=TR0AADH1@^`KQJHetf2TET}3^nof?2_e231G+jyO1iqrpx*P* z#-*{Eg*OAFSw+o(nyBGKT_=2$nD6z@53gHpozT2)##(o^&}41!1;2IaEU6=lt-ioT zWK+7nDIEF&pPh(BpdrlxHE*u16|eFE{#H;o-pRI5Owa4lH7gFaB>SPQ5zDlDS-Pns z_7MD2Pmh%#Z5jweOBDx=jDfh0fLcFJl(E(7YsE?%G|RY+JtoZsitMbB)EC`ZiPDBr zC+iDjaeDeP2YQTd-zRxiKJP*&(-z@#*(sHd!h!3X@Vjin> zpi6u>PbttuTgYj-K_=uuYyT0+6jq;33@`~IXtn>GxjkGkO_X0mME|R@UBzIR($#ue zf8!F)pJ`e?k;0$Q@m#)0z(GUOp1eMTnJtdNKr~!?FG|9riSbL(mY78ci@vzA`{@2M zfyK}yo9>VYwIx(J?sR)#Sf`I|=K(}cQ4ZNjSrwn!P*Jn?+D;I;^P(3ccA)ZH?iwn> zMbuj#U^?h1a~h!Mo8+)31dQAS2>vQlf7^Rvd^cFCmq;Aaw}pNp`f~PhVVpUY|5U)& zVhW4^+t~%~W(5L)qL!Jo;c9 za8o_nJ%4DN74jGbt4zHR23@KZkqk%8_Q=oU?vYQoaND!zUXtQu*ia&CFUY-sY$vXj z#``3_pn^9#S$#JSF`W8Klc9rIbIxE@Ex>yu14sMa0u;<*j5m>;X1CvKF^^|dYn@<$ zb(BZm27?>@9D(e~CglztgD%Av$7>SKpY1)70g$k3I{#6}233{!gDwUlnk2UXyBkw(% zk#rt*P|tSov8m3b=tY6=0Oh?)8Y&)+PdJ;g9yW%M82oVa`Tx!F-rO^O*R9ZPzn@LLF$(y_`P`en3iJ5A zUeE(Ln&2g$Zx<8LWv0|yAABl`yFuz5n3gCDgE& z^Jl#mn?1TiOJb8mSVcuN4?Tsv^(rqHY2L^%>=ZtSF)$TuBBImAQ=rF@Jg+J1u>(Ka zN!Kn-AO7`E(!Y|X8Kw4Dv%SRF{>P{GgZ$pQkk5u>vEZOCdPkQLog(1eM@k93i znqtnV2q8vR&1?Nb@5(V_$!(5vnlFmw)MHLrziSBYDw|--cWNolR$@B_QbF_t;Tfz#B2BS?NU)Ki!6{KYu|hE^Zw6-hDaVV7$&7d80&{UV$7~ zLZX>Uk|_o33oEKdcBNbDRJ7_1a0U#^hpFX*#z&}Tj)@(-eDFAm5@r};)fn;b0Ar3^ z@D?NO$79K~_FI<~7E}%-MEew-N~0yT>d*64-f|B2$$+WU=%ABt!^rQ$gM7W>0{U~v zOFA-x%~#GbQwa+jSQAR0ZkeSkf*xUTE~T-b)dPnYRqjuP&?BVD4-FA9yoPb4hjVQr z&#PsqS9A(isY-e`y;&zpn8|R0EQQPgS)hzzYu(A6*NDaKa#E}L&Q)najnMy613q^U zn*AQ=tWf@5tI<6xK3kOBPK5icop|T?gsp0MFF^%b`Iha}s};(i*x@dn>MKseNfPAu zN4TaX%41QFHlLZyI^;bMV5E*MT*hxXS&?`Cw)?#NVxA`KiA+;}(3_BNIfqUE5rtfi zJMJV%gZ-cwYv1KWNzo{L$5jxwC9ZmX zlw1~Kvp(npCm(*r+lf8UI2{dQYh+p@ZCBAT=!($ygnG;G>j$Eg9iM&HSc#V+ocUqu zn6s0etlw;JruyyR4y!G6#h&DECpn#PpqoO1c11ADS5piS`|fHY{V0`x#=WxUw|vnc zs5fT9fr94EXYFeD>>G0iSW!?(W+F~`UW3nD?W31p-f7oib{-@I(s6Ki$v_ldcV@pC zHg8$v<&-xcd}bX7e!fQB0tKvVv*=mHD~^1$!G3(wB%w>JV8C1+jEMnWBxX?h?Y1M* z@lC7Aj&N{Q5@2IzSA2B%$si)15>$)t+ck1BQD4g!C+I#%xZw)iaXg@ZqKSLA6jLJYzQt2oO{3?-%Ni&+|f*?R5aKDt) zC~(Q?!I5C!3l>aUuYth^Jx#$!Hr{88b6&%mm-Ku%czAt>rP3!aozZxmPYJY7l1~%; zJ1+CSkIj6I?ssk@&`%vZlWoOA1(`oPzv@jbTiOU}!07ayEQ|Qp$Tsl zV*_UywQ$3ku!YvUaMuo%4o*8KDr+1%sKA@>dIAacgWi%e!yuL9r6WaxJ34lDzrijY zL(BRP!S%KjDHBR+Rpl>Rhb&YWC30`{9*=qu6Ku{{_v%TEbgGqbX^F6;JZ5NVGs{aV ze&~YqGR>|25r}#(`yvyA?Tod);IO%ssaF;L+4L{L;YTW<-9Su?_zUZDw4VI7(Bki! zbt{sP-Zr3R%g5!&Z?b`mNj#zClwECDh2{?xM}(UL2UMc(qnD3Kwf5JscA6!cQsx@> zao~GX7l>HeJ&K!X}RkPNyUCf|ejO@E^QeL#9Lx;Ts$ZlS06$6)id z4FVgN##g#6G_$rp<449gM$wx-r)0Vfo*Y{otRY(DHeI}S$%s;g57W^`MJ5$_cRZ)8 z&j@O>)wcpvKi(}^p`F4Lk$Eao%v2UBzHNz5hQS%ESjMoi*@&J6Ft4F88vor{BLeWX zKS97@HN^s=;F1KWOMjkg|DX7lk-uwmhs(^l*0G-X0b4%>GZLo1tioKyfr77TQzTyq zaHINXcx^Cb?CIzKa+CO%5*&>1G8@g`&hvNSXM*4FdKmz)Y`)D;_WwNL$-e&S*?I;d z=`g?wVDt4#UKzK0^`f)W*UmroRR=md_*CV80~}C5sVz(SNi6QK+k6Gy-|Jf8a6pnz zDS?87_n+59N&w;YlD!DC`A=U5V{9kL)B~Rqp0R*1EM%@2n7;ql}-2M^2l7GYy zfz8bjG9vZg-Sxko+2a5lt=H>Gt|CxGASV%a!gPNTcK@Hd^8+S%U2J+iDPq8N#%N|b zum5Ha{$Ce-lmTMyRhqAZJUKwr$-KS!{|P<_2;K*;n15Ui2;Qu0MD>3RUgRnG^goxm zCBl%C+!0~z|2g=1;OlnjD)l5}KLt-?qx0YPY53p&!pEoa*h@NG7itRp-n9#<{(rvx zz3Cqi4`IT8 zwW|5=AJXFpOcPc@7RF^{;P;DPL~#H6-~E@@Lb5zCALNPC`Da`9u|Wk(H;zOU5jKCF z(f+r0$+shfeYjW95v_>a)9i_qd3sM2FQ9V`KyRwrFzVYEe_@ILo?($VPh%k@Gh7h3 zYuyu^gGG`&gbjpQ@rsY}?SFU4KhFi9K3OieA}+$WPmF+{C1t`pW1GF#e$aW&jBV7v z(#XH-q@VPjCNEPs?=4`cvK$P25_LaYLG7myvG?n!|9y!u?spHHR_OER&qsBSrh_W0 zu9nLkyB*HHzcrEm5*KVD+ju>9>qE`M(|N9=C!o=w!X8Jf7F)vhZD8(dQ}vx&#S-*n z9svm{+dey(CRIWqCG-0FWq_5huWz3BK;Q0t6h`jO{7pZ~+2#bspZhNQ1V}`J)W-^MH|PI-Ee((OX#?B zsv6NSS0=AYt2}%9Lho%(R@TT4+t$u@ZzZM!iKWjkuGVTntZ-_{{CxBCh20CH3lUZv z2K3LhOG8Cil`B+8;VPSOr$LA@$ z9;w||Z*^N4mA_v~Q92zqSElp2ZrBsR`!MoLlqQ0+^Fh_8v~!1E5@wI-7$w1VUM-?_ zAa@7%oT;&rLc!$CmgH4I3UW#}-K`-Xo6PAt%bX4T@e<*UK1?owt=(S_dW_vim9Ou; zW2(HSp7DC#hnk=K{{4@|#HpIop$^PMtJW>Syagtxk*`Bjpj?^J5XvS9T?kKBdK6(u zRQmGeBcTwkh_>^-%v4btwnF;*)OGe8ETIP}w#`yzqQQwf1)m`O**#2pZOGb1kdE}N zzjATGMjmM5lwMp+QANc-T3$LG!*np;bXB1he7UW8XqSonO86|`OR)u3p58^UN{OT0 z#Z0x=5y`B_GQdkY-FKM%!Xc%9)KHwtyT1)h!yO7sx?$TZR%{-3x(oM;sM((fTP0ez zYK$OgJuFgJ3ppK{o)0MOS#32f1X2^oJ3-7Z`b_c@ReuTioS?%ix8D1t9^@33+YzZU ztSM(_Y}~<29^^?^ZPaoydT-3GH}2W1ZAnwU+I6W0OZG}kV4UV#Nu28EvSsYMM$+2P z80rMXkGh_De)L&diAX5?oH~&`v2tLUV);zvTkE0T7N-@h*+cV`>OyTtLn-J0wOJkq z*{dk^{ULrf zmvp@d`^*6aYVX`)vdw=M+tR_N?-KLiMP~AVD3dArW+R08ssOe$SLM4GU(@T+SW%to zd#@K}cdR#Yr9K3hE!C2Jvj=igJrwZindfitp65bf^X7N8;cS*(=dsYdg!DBV>oYV& zf7k`}hMMld=gGzo(;HR976O{Z0ArSmyit*)*WK_3s4xprH#U zaz^&W(+*--^p5XL1PSUod3dbxvfC{q?aby0MZz%5^h+N?u)SMm8SEXC_q~&gkwwnsPhYcKhHg-P10Dmlmk|c9`65CS&SIR9f7g` zxs6CV*hhyt%QtSJ5b#Jby@Mp0Pn`q=lI-pQp4!yvt=k{9F~vkf^H%NXyXlArcu* zInh3+Yr{!dJ6w3>mHEi|XBBc%osE;KruJX~?v;FIWir8U4c-2Ebe_1;qODk$R>Dp2 z&W{Zoai*hkN~CiWgaZlUbZ>Kq&ELfIP+#;cR>T#Vdc-}9rPUzl3T)N!lH`hAn3 zK=*Cw2IsIK*({mYioNkru7a+uOg+1si-1vU{SD`Ph!Au#AQwmPumSy31f}L-A=XXI zc7$+O>a6-y_)#Wk^3F{D5BcnXpGjK4J2lZL$ot5djHf= zdK1KpCJ_eeUT9{yGcLBZsr~jvmNT~Qx!^Y{HbvAQin^u-SJP}UTn*c_Ol@Mysi^}E z>z+9p1N{Sg*q-(|(DcjFaA2QPdUBdmJs?e(V^gh=pYh_cKxC@~>ofPXcF331VpGP6zATJMB+rF!dtu^0|X5Y!2(t_|Ft2eazd zTeR9M15zz+45LbFG#6q;eq0KSVy#=x4dV05H~MCcyQ)kuoqG4Mk`B#X)VFUlRu$2l zbxeMwPaW6lmh{C*l`(CGW_aG%6rm*5FVkr~z`@Q_lScsAzj8m3oMy&Qjx@$h5$*CS zS5wG%<2XS1&6h7APfO)Zs$x9KWu=--hgGbVmoJY&J1j`P9rV~y!B3JOz@`uP-6vNL2 zUtFJ6Gdzb?U!TWd2le)jw@h)zb*K43-8tw+U@T1e&JjuV5Vgm0P3x@*pE2>vEODy? z@_4_Y%M@92%6O1C=aLuq_co-T8&U>cJsAv4MBD8l2;?`?kQ0z>=s_RN-2*A$cw-j8 z>7rcNn56U&iH~8{HbQ71PVbm|<{>o-bp46THhQP66!?o~$OyjbJHm!N%4Aua-vm7= z-IwdEV`$F%;KSJeJcQ5#b<76aXtxrpf|B%5$n%H)$yIa3F?#>lMx}V3>2DXI(p;KK zA*Vw87FV0`Qe9{cN;N|73(CR&kEX8-i)wq{K4Joj($dm3bhk=(=MYjugLK0IL4l!> z7*aZh8oE(B29Ov!qVjN< z!ui(i<^D$ZN9JN?tr96-REb7(S&oV?9rESQOeW>Zjk4klHB#!NFnMhGJd& ztUR706ow7zkc()cd`~p@d=RDSjqyzwIgnxhxc0LwJo!A+aZ8L0 zG>F(d(WFU8#{$LIzcVqL?^ZLC&shg0mAca;0V_qXO{JxlBFGT?VW*00iB|OL!9%Mm z<1A9o;U$Et=w{KNlCk<_2 z^x@|2I8lBgVD9^@=ZPjWIxXoQQ!e|dIzxd*dNw~aYkzf5;!BOo^1L01y7y)m7h^Rq zYjErH%2^E4o=BYNzsctBd;U(lZzaAD)BU{!VA|u9@YlcKp_m!_Fn{{w*y2{1K4+Rj zM|uafZR*jht~PqVJQc-jGg+lXn+2r5mzu(7T4R;w4@BJ3n{ZMgN!{oa0E>Ho{qkVf zmU>a73&%S<7)u&RX3rjYmB|}3>2Nir^UXK~)_nTBF7m41L#1JH?8n#CAE2QGf_U*O zv~KFz=v193glwhUO6}r&pgXNWuMwt8;be(e(`GaaH%{5+Gh^6aaWTM~Vk-ml=CV^z z{-{(Ne7%vaDK2LvG`>5-KhdV`s%?L`c2&S$YjM1t0PAGR`TRk+yJL~3Lgl#8tB@G7 z2tIwqcC$fT#!B$hTqA?g*LXvI$v~U6XjQ~JxNM{khQl~QpV=8tBB^bRkh3wYPpvxj zzU*G6b6lBM)}C&%69;?CyY#Yj09#G|OS1-#M?a(a?9qDo&Z29wqT`_MmxGE6#ADCXpR$Ji7wQJBYTdS631Pp)oqnVaNHL%Bj1obS7W9e}A2wV5r_5$Uq2 z*UWNvcEPTxTWqGzV<{(qWeNX)n0A=*^=sB`t{=}#JwMEH>2 zUbna<)tTLmRjLry8^zfqNgUsm%`4MS_{V8?yd*REEvEap*w*%+M2NZ+!+~XlZb%4^ zDCQm2$oYWARJ#7KPcDq7anNdh6%9bE{-) z17NZH*gY5GScg_0o=>CRl{h+#Qxi!qa)r?t7|q(z*41ovB@xlYQoNAMwKeph!hUxk zg!>+l-RRoOpTURY6?*Dz&Hky4dX;Al7ep`!!rG!LiD4zx??yXqqiRz}0<8oqYE{ia z`)OS?A+*Fs1_7!j01KhljTATq&)11{9b^^gKq+wLjIE$K4P?7Td+a3LSF(mxx0G>Q zc-YLieqRxZL|Mm7t-~LGypSyPax0J$c~E(6+Cy8@_g_M%>>u#npyVaK>fiXr<`D+)HL8|zH>pp=H zKBPuzeTLL>@%xmPC>9La6!Y|5t|Bj6MT^SYLyU#dJRAqBhdua|V|Dh?KPWBj*nFYEH~H?Z)YIh*jw@Eqry^33R{8n+hVk2*7#zwPqYs-Baozxh2s`c|5W0v^tcY}*0 z?f6j+}?- zIOo#qJi^y+5z3Nx9C85^i(Jf|!PtVVV^CqMuSZtz;t<)>MU3 zVk2VLxV(LWzf0K|c++J7^w+;xDC)mq)9r}VIjKe#KXQ#f<%6oXZ*?)T#k4F2BuW=| zy;!-MwWb)HS(e`h6`igl>!^__>c6=DB0YV=f0t)8U-{ajpmZiA0cNXAA`QD5 zELgY5T8}0pg7v*@h0O0vY_iejc9M^PIttz_lAGiNl4%!Av^4F3P63MQ2 zaFpzuBc!+dTDoUH;)EO~uNz3Kz+HiV9}Nw`U~#IY;nJ#817$?A$~|W%&VHO6ag=CS z*l~qlWNQ(*Bg=cS>Sp+s0?MXz!%m$0BqPf}mQVlM{bPnW3GoN3nWwPRHse#w5=!?V z8gITa`f{s>ZW;iwwQ)=cRYqXw7Q6i-YwNIcS-crAFx2s{X%;k6sPdzpEKiFiEuFT# zY4bbFXXP0(>zNjB3p2cor`QrTuUk$ai4+drS1dI)@PH20%aWCxgE;dHXX|-Fg1Mxxv zKGl2d{7ySb8lQsSS7k&)h&dJY8gP^O)AXmet=@RplkhYwM|099du<%~dMBQI*|Bx{ z{ASgO;t5ygFD@L?!+|~Jxr;Ho)ulq!C9)6n5k%Cg^Fw{oE^tns7FF5IPj*tt6{)b3FUBk-oBZg{<@sC=t$5^-~mqDFd#qm7wRTU1G(=D|&#Tuk4nQ!QS zixzQ1uD;a(z9}sf_=s}+0>Xi$KR2ax(qF%^LMtC|vOd}UGD4yPEUVrZ10)Y&OthJ< zQgKaa*)CE8u_afcSMXi9)_p4A8_}2JOdBkshM_baqp zF;J$1KdmVf`aXtkxgn)JUb#o$kc$@^eu34FB=-u7&&GGToMW^L)UE;gxd;E%yY8jY z_=-vElF8Vz>Qfu+b_skToXl#voZdgs zt3)V#RdoAaGDs_8S^tJwRQ*=Qw9a^We++~%pY#rYRZ6ky1CSxG%eYiuHCPymV<;}P z3m4hE1#C3Xgkti?QUUoCYtCslQIBZ+-GJrqcVUS0<6DBHMMBb7hEbX56SE|#$7!F% z+woMpj@Pp=rfl+1v*wR_nXh_37!m+Gq0#s?Q5f%T#u3o;*f0rX+($LMpF^(v>22~= zyXG+0`5H>*oXo!%u4XYQ;2G1%iu`VFSJ0-*)*de43?5qKa2d*+->{vB4CUBZD7_#w zO}4i4Ui{RkV&Pio50E(7A)^1`$KHQoPfGAa`XdY>AQXYa{vvTnR#itxHHawcSGmA|3p z+WfyP01pg#XChvPB?u<3_{EZjR=RY$EX(3$P=$8GvQj{5J^WFA-nJNvofTU}lmdAn zu^mNjB^lb2EM3$|$VQ@Vc8$I+W1fnt}eJKYc+iZYomq>gzkar5$$vqbn( zJav5PvIkaQDM~U0);_oRehtKbeGiANz9bP$02pLmC+g=Wgcn9D0=J`%xeHj8Xq$TA zCB=m|thdbSjG3f8S?qyp^&dw(cWkZueu_J~=nmU_)wM|NbbTg?vTYdOOn8HrU~wkyYD}nFj|qv=g?f z=f1Q5)a3W((t~MIR9lLOOgHZs!xWM`ygBue=(AaWYb5OmdAL!YC$=>bpOjQVf<3(m z2l_Abs<62hM{^3H0n8nA`ZAMOch3E~WPz*-tA(u>YYgpQ7+T8M3j6fxj~>~G#MM90 zNVYvQ1)Hh4UmZZ8r8)b&-rFA;G5{qeXsD#mFeJ<{Vz+ zZBVnLI+6dj%5l&dfyYVJq2jg6x-rR}raNa1(OZ~BkdTOT&kN5&dm z{b~nSPpADonOOhT838|WHoxM(SSXkW4;(T2#^hITvXfL-qD%#qEe3mqL8V&>k(~r> zj?6@X!V;qzQHm$$M4oEbe%zx=tJ^yK^*PtBvvn^9$2TiJGrhFA^1Iss6WU4pl~P zs`#7evOAA!|M~R@S7rU?69C_f-3{!v$h}JvC4?v>f^5NEIYv$XSx0Wy$fpidr)NYS z>Sy2i-d>glhu28#2?M2%pQE{GJ0cg|VPTJK7~1o>=geg68oJ1Ev0q~H`x4{z>> ze35}KL==-}zGq;c8g9qsOePp~(l_q$w0HaJ%j#*3<+FO8FRb&OqxO8??{ynX#!jh1 zNHjr$|Mdgt1EjN3Qg%7F>1evmCE8dWLQ1>!OQy&XNq@bJk~ra0*ogE(icw#xP0sYU1`UT;^;aSn5~L3Cj> ziVg0++8`n_owZkNmu>Iw8dYW#&zy2YjWLGYU;myDHKKJBkuhZu1_hznDOBZK77d$K6c58zfUi1NMs15a&`17f= zhiSbAR=`j>6ISF5sOZ2H*RLG#G)jpnO`$}vORx3r-tD8CgFl~6Fi#Jmv`R*$4YbI< zCu1>Gsyg_U+(&!XE9z7)?sG>1b6$&IeJIPzdg!qmi2s>!$#O03-LzlMd9wAmUUP{! zQw=q8e+f3YJ{#F)cR$F8vc6QLKsh@PGMbGUfwm%Rv4q9GwJSfMMbgXDXM#$JAOQW6 zaCvmWus`kldtJe_Shu;JuB|hnwPzfq2c`dUlnUIB=~6EJfcOWdmVYBoPI`Ox2Yf(L zRDUcLfF86Yb$A^Grh`FqWQS7mN*qhH21Z4+xZ559c;0ZiL0>wtq2=Mwc}IMWt*oe1 z7r}qEwy->qW4|1M;tia+z&T0HID`aiyMmd{G6eA@(`FP_2T)Hke{)onwya&Q!<{GG z4|%`oD4302Y=lv=5GAe=e{kcsf&BtL2-!3*r%E2q=kvC<8RhHlM(r-LTS{#3gVoA2qG4aB$S;w>ZM#e1ej7nfG zX=LW>{Yf4b*uoJqsH7S%F!Bm_!o(ZxJZDKGw2}5gHC_}bmQ7OR3)oPwM<_q+Wl!6M zPlTCwXjN$Gpz_5;EbUK=M^0iiz(m2_3%MZ2SOVk9fA~tki4n*C0f$>QmR#PUSw`jY z0WVL>d8-vtyuUNH9IRkEA^6|gWW4d9LkjLF>R|H39Ic&YOk&;^H}!kV?jLV70IQt_ z&&_Pg(Y=hzjPCdCFopnH-7^%np^LE{(i0I+pn6j&L*AatwWmv&!dEqS*?xu|@wF>sw%MMVpo?m~bNJhvIr0QOU*%2n z4KdE$M5|ulMC@v^dJ9dBj|_%+NVmS@k2_d(1#zR=h~68O>-T&NDO_bhq}KqBz!y9+ zG}C{rkU^yY70zJiy=v=-*T>v}T(HDZ+ayhzekVcxs&M3D3gHU&(EO!1aj>zV^_{qG zPmoG#yw0Fh@^m2*2bzyi_EdYzu{Ptz5CYk^wh&opSX19I>Rln)TNUfJ9y(&FbDjO0 zUS4YKGmt*M$K6|%jECfhTYp(6WyxvfIRYu}b z(J0gtIXSvHyQn$>)>V(rM8pQ@H!F**SL-@_J@RU&5n;_^jz6h zztETyH<`RRhuZ}U6DQA1ng%5$DR?uTj9DYiI{Eunb&t_*0T*I{XStmA#jAr+s_~6) z{DO4sSUL`Duyz*Ur zY>sb#lWV-KbTPm9UOCg#&ywbqnH>zgqrEA=YG*4A&&BiuKP8#|teY~5F*%q|u1_7c zeFNfi(#aY|bbpwmG}nS#U~H#dnI{qs<#0^?ZpmM2M9oIN;NccxWe-DYyirw&cp5`@ z7xLqNk(+gMZL{kT4vT`9e#^w7_JF(R!}^`HcJcxf_t`h9vpPp4z3M63?TqZ&`VN zlE}Hboe~y%&jsd?`{{$5#n{%&yWme}_H#U@MvXjDN5Ab21h|F;vn3lamtIRDAwWCne68 zIc0$yubB;mryt9b+!`({?o*E^o##yza81khdDVYMSvO$i;3+kEr^R6TL(xHsejN*C zLYjR#5uUaYOrDwbA+uhUCMpF>Sjaler(wY7W zU|h4LPg?jnwK>}*=8SoDj2%(6Ft}|k#e+~bkjq_EW1C-X2t+e>a~$V+sSrX8Dr0}o zve5bcH0#32c$;mN8n>U$fGjm79xs(IqVtiaV|}-B4*%z2IDre?zo{#2Ed;un$AxrV z7jrbf!6*I{&u}uG#g8ksHnsNF_$5fshun2J1D^kd7n+jF0mXbvubLkhh% z%@uQmp(Y<)8Y^t_GAB**%NE|CoOw=y*5Yj`lXA-^L{1#n-6&@POTw4HMJ%2heWWnq zCw9xH$F6E1FFG6aq=oOnf#TXz%>QI!?|}{&AnBFg6(=YiC}yzWGROO0tQ<0sG!3I< z1fgy0@L4M(ttFqkSDHDK-K2A+w@9^hb>~vHS@CxTBMT5KCrIkK>#?r0cTi1z$_{0N z$a5h3rn;x@ODk58TPG2g!(h)cT?axqOx~U^SJi=Kzo3^z-!L=^!$PR{icQciK6P=V#aggu4!XMLHB zi0;-J8??q$Pvtr1uK8HU6-UH#%*f@I`ws#?E>+CLmQxD7!yl{eX)PVzW|XwRyPkX9 zQ=E7kU8-G!sl*Hlay@fMB%HsPAN?0F#VP2sgfUH6{!RlC%vWM)5~J=|hn*$FGsUHp zXYJs&>!&qWS>w7AO$TFgy7_ZyAGi}SR4k%&S?e8KDNE{-fVqbPHlHU zqP&}Xay7sfIQ8WD7Jl2_=J$j z?fIB~n&<{Q%$j84t`O(aWQBFdsz$B(K5Y4CKzvL!og<3KMX93l_nRG_YooeT5$nMu z?GB}(%YSx=tYC6P^;#wbD%on8w*sz~5W93!S~v)Dihp9_Qkt+t{zONBcO-WKI0po6 zZ{;(!Oo%c+_9}I~umbK>BM}=ZAIi)g7iaRx-94kcuxtVf?Cg@(TkcAOfP`Z&?zz$< zi{QlA*sNeZwX3LwAO1Eg&xT-}o!QVLj9znZ8vZZ^gd(pzmFry^MHf8oWXyvkYYfED zU1>--EK3K(L^wXsJl{`tX_G?(^-uW6>M?K-_mQWYJdb{*u=Sf#sW%B;Vq@s`>DEiD zCNFOP2B*XZr@{s&O(nx0LOb8;J0f$hBYUZlZ^J+R7b>|2i2e|pL9OH;-!Z=6d0_Pq zGcNKrz=a)p5?&u@aD8)IY_p>v#A3K!`Sir!|MyCHe@Y{5Z<SdU|m z)O2H_QBuB%f>vzc59H+%giv;KL^GQ=aB9c^br=(BR$&^s_}z*?w+oj9 z1`CXAHQJvkQ%ovT@TjDU4HB8An%v=VsLBa&S?b2QGXlkmKyB)GRJ498HztEQD{!2K z{X^Kd5--3U+G`S+MY{XOboJyyrpUKbU-WHRdPN9{vURL)QAXGYE}~JR=ICbNQOYnp znLDF&vep0N=szMFz~R1q_@k*lE8xt%9{t>WAycVxfM=c2gOzFIThg3Va5>7RP1;oFL|pRFgZ zp%f1)Z*}m<-?w%@637pkrk`pyzbV=w*RSeJ!B)Mpk6SC_%>7Bt=3~Qv z$#CpnJ?`*PXAiy$Bs{@rL%^5I8t>IKl@ZVNkr?kO=Qv=$Ht2>U zM69RdoUA#j)})*ByOZPFH54*kWmEQreUT{oF>NQ^-8?b z4Ik@1c-XO~UKrQ1Mwqqo@<^eyTRVk3;a)jRDQ9qH3WT3voFdejGkyJsOh;k)wz);g z_FuDXzF$mD*1hnq&D=%jq;s;ewo@WgbJ`i7p&@Z^R%ANl(N+%s3xmgH6}PUPlT<(g zk>E5S7W(JWUv=}dchf^8Ph5gI$Lj`EY_*q5FscBgR$Qc9*1rSiB%Hc zhg(`pz11kKpw(Ytg0x&lU901lQK;=h?ZLHR5`WwbO0V@1@b+%4PZP@6#H3LkspPoDQ zhRB_0>r1w$SP=-MuNE` zGV?Hkw*-R|tH)V?TH09ePwEoq%dHPC;1qg}m}V+_1V) z)^RpYH|o*tKmw`)gud|p{Q(o3sbVsl$is=A)+ja)O;PI8TF*(UJE6uA34hNJ7wQJc zz7DB~dE9-?+#Il>lUj@4HqVph>;wcJc_K}tw%t#InxaN1nScMETH`#(>f7u*Zt-Kv zdsf}1w?MsiXoRe^%gZFWfsu0J+KCYJp|GE_GYdG z-v6m@$t~S|<`Xu*=0>y-Lc~q3p10opA}&BJdg_{!PTttKqx01druC~qt0`isl}t<0 z7Qeo#KS{hf)Es7{sey&&)m_S(ZMyqe;u$r#b(d3QV)1x{ip}E+D3k%wrWD283r<;G ztrP7*#Wf{rEJmL>)zg6?LM*!TsD%%wm&N1a^viQ}=AQ4m7e^r`&*L0CCTS&UH+c-C z#Ao_g^x9GUX@++CXce$5_fw9v8P=_;YrnF7FjrIJOtfI$d<(@L7yJqpU+6Ahr>t#C z;(@*basyA-Pv_<^G_1M6&6Tn|_2bpV!9_u+*-c;cjVcIa=>e?T;c|MjDJpx+lgIVU zGn|eZD$oSFoLNqyCOo%~x;AwxK0~${HRbkY8QS?bEat)rRG&s|>py%4l$9XQ!)&EI3^v5LwHPTy@sB?&%txe*i7k8Vr9sR4B~i&v)(1~4QY9=N&wM!Cr6wHHHAT3Q1FfShZLLM1pij= zF;xvnW5w}vz~#%qnr_)q07*Qi)8VQTHi05?ln>>^!$&JU2H9uU`iE$M41-teXh{%F zmqz|l!v%R0l*Nc8HL3Ci&Gsrs00g&tku9Ms?D9|4)Py&W$t{pTwY&-6zJ$5XJ8B>V zQWp%N0}B?;noI~w85QuKiOuGwu(KtCIzgL(B*u7d;Ul}Uv2`eLy!zf(4Y|l+bSbJ` zBy0|k1KJ<2cn9cs_}0iGS-w<$;00AdX6IsEbGcrwpIBhJ%YNP2(&JN>W_9O87bp_YmEzKo?E2a6Ts}*TV74%1H zlOR}5<_8FTfN$!vu6c#}F7em2$>p6-{dnpV&6;FQt4U2BTr|i52eW#q3Zc)6c_V)X zXK7qbMuit*xM4Q)o{bv!J{t<^y{v`ByUpP6=mqqc? zkotH6o@NW#^vF znh`{Qo=%7ONzS(qfF!f;S^9|-WfXB7oJ0K3qBE|M;|!4-mzJKcx7@g%vlo<`$FTJx zZq-b(=p6uffYt9kkIuMAu&Ir@A~`EKBcaDu)N!2`VH?W4jJOu3yBW?j!SmfH2k$a3 zE3dq;CE}yW!Lkc~%IEq^x90X{siwrCLZ2CFO`JH*%{NVxt>W?yRcY|sdb5k$RN9)m z>~%7;<;{2P4&*+0du|zDKyTG=y4g=-L^ny^ZJVzRCw5qsAPrRqHd@-@oG zf2T%ht-*J4m6bD~yXc*^)h>>6J$(`jRR|-j=Ud#ZdTCN*g9QS885dfa_>8*r$r8tx zvLJN3<;W|$m*wk#u1bgKlSc$bpL|OuYQwh_qu!_n8}Z&-fF`tD_|3f*^+Njw=!KXW zVF<+a=HhEXJEQxIe-%)evI3i%@e?Q~8T{g$dBIUBKVFA1l@Vc-Yxa>T-q%0&Ms)n0 zTYpOQs+ahl-M{CE{0U!m^pH8=6YPJJ`(G3BAyCLnQZbtS34+&$*%7&M&l=t?Ftn&X zS%&V(e=^<%r6=Erif`anuGb8Dre3lSr|V48UF+lF_OsB0%P)B45rhpfr3w zQ7qJ~FxsR^IN`B;i@BGBtnzV1A%bh%oW!bEcBWZh@vDE?B2ZZ4Xb}r=W<&Y~{CMH- z+sD1USE=!w@i-V@T~QL&W1n6{6TO{@X_UcqkVpPy?=i6y-4@Jo;kbL{rT!y=E zt%o4fEBGlI zq^X6?(e4MsCLQMyt?dJU1Dt@1Ixg94Z4=aB z(Ye~Jgf!=dk^SVWGx-bi`Nu{?z;S(&RJPXNt$jW2=mfQHy4=J z0!*-YT(ZqZ;|6l1yIed&%?e-f3SHd=hig^7XOb7q z44n2)ZrI{(PTIgy4W9K}eSaDquXhhxd22tmA*9%L(uarAD+DHabA5*;uxStM-{Kf= zfx9mw+k=UAwS%KwK^$ZIQFibFoKzV{cCDF++D@v;iJRv?i&8Z?!#%#~qiI_KRNCXW8X@FY z{!;!Yz5)b@TggJ5(b%dnd%gL~ZeWMOg9NBF9oNM6>qLC7WEUY1loa@;5n>CplZK=X zfH2M@U#XX)=-%9czNncte0LCN4uxfaQjwmTya>Kj0fplm+gR;B;hx|bN6ZOxxhPGH zM7XfCihv zH0Y{)TrK)cgLy?*73*{9bK}YQj-|pLr;`6T(5if26gJP`1M9~n3Vz~`q{)IZt-F-E z>x@ptQoihZPiUNL`7648+*{oXkH^!EUX2?`ig4!i`=Hu1ouwqF8i^p{tLJ_PKg0|& zqK!|2IeFq4Ux5$Tvgw@N*Fb75v?}G%HwnPwTKQdZIHCG16_cmqy@U`~yzcIO-F=pu z^&;g79;(8I-LPN~`F!@A)Q&hyQeqU)>c~;dvP{w8@UsVXErcH|S9Bmx$f-7kjQq%X zzFwP0uS{&6Sk!WAwtHCaEajKAVLCd!UJEZBw_?v5Z96qkYF8eM!?x*mKZAYTF z_;?B1_g2qlGF+`>)7}xn^GBW7T;8@AR*Mf}nfAOZvamvOGz$H@SmM4Pc6#3;+J zDp&tb-nWi>^aqK}s>6L9wQu&P4YrVt&^p)aSFO7Wt#<8?7P9gt*^uppyMtu)-K1Xk zj7wy%Z~RQ~A&X4ua*UGYn$8{w-mV?9etQjJV4p=3e<^Ypg36qMz4OA3Yxu-*J zHSQXjG~z*?z{DrRbG^h`r)dgVz_kr&GR}tIS%u-mfaY=nPCQ3fFv=cnv&M1-=Nyk; z7^zjLdI2rJX4;f6(>0$XZy+5{{>GVG807HZ_VD?;Ju-#Lx1RkSV?_;*Onh37Em5;= zf&4!dF-;w7=ADxO*;3)nKo7GzWFTo^0x|oD1st{odLW{xMR3vU>3$}$1LA+-Gb@&e z$IxUV%lljvJ`fScm-`Y)s|x4!N!}}uXo}9EC4cJJ0()n2bI1Cv7Hkjk zC9Xr=4mwA4MuSImN-~>hkzsSumOS6omp?w0)2V~1`iA@F4*&AzPvHv|Gjo0j({)O_nQ1Ih=_=S>T)R|uNivL4f)Xq zXSah*f!3#|G31t-z10ROqf1~FGKkADtgYf?$HEuR+J7$Lq00wAZ`$FH@HfX~Jjgaw z#;~cQ8CuV1f}1-c?`MzaVaOo#u2#hl91;AGv;BVHk{hTHXjw~pd&d!+c&lK9L5XBtu+wIH#V_;cqeuf(v62kIOdLb z+f)wPSLHtCSZciBJfk!{K&uY2m;VU3_NsCz+yEq5p3)mO&475(dGrFmxm3duOIZq3 zCNXSW`|oDBd_P?|ApbMA764%OM-O~;ZL?iEdJy>sLq05?|3kuD1g?HoOOgki50gFs zxhgu;0IPdaaGF~}IFJWTHn*5M4vYfO*@DqROY+AY{OHtspx+O0AXM3nrq-=G4V$Q# zJX|I_nDC!w#bhDu$~>pLll1Ypt`a=I7Ed#`g|%c@Vd_a}tR4V$gz7F9WFnoTlXA@0SkS#;*#k{PE<-`xQX&gbp+?rH9@eTWPhM(gg= zV2&B?6@f37A>TcdW~=|Wz_mY<3``< ztGmZ6{+YM!D}N+Dy*A!=;=}P>GMnniMo=zWLyL2r$TFFbyggROgvo3E5tMoEvZf zXsBvTGlweWoyLO97nG!PA5jG9Jvl?}>MR0j@6gBIT}# zoAPK;?*pXK_q8e2cM#b~5+_3#AqSCsR;!3cs_N2ROJ)J`UdtivZai2_aqq`B6LJ-1 z^3xgh0b5g<6&yaN9V7D63f+q8?hj)ZvhzBqQjO@rV{&CB&i z(`y; z06jkA$*P=^;KjUuMJW%-d10WD3}&U9h)1m&dyWN#V4{=q@w^8OxOS z>vql#bgNpn5@z(cIWBu)Rritr4(Pt-t=_T!>Y@q|DYO6AXs-8Y6OkaEDlFIdS=8&4 z?v?lIW7OI7^pj?fEpNHf((b)`rK~RQOQgQ59i_kcYTc-r%&_OizqO81YVCkjnR_#-u$ zj-X&vjX}xFKex;{$4mW6l-1n0^{yhNlMHAqo^`wv^(FX~HrXWN#bL3^6YDp`??-yT zXmzV3GCW^P8f?}e5dH07$ADpm2=`xE@#O+JPeBwP1o^QJZ)T@Mp^GXM_QHcoV3xFE zv*HKAHE0a+hX(P5Mc@4SEAvO(%37q1z?R1s6#OrbMgMPf3(@-?dQrw${1Dhb#xpzx z+deG#Dg>Q6Y?6~RP>LR7K&7J2T`@U^O%u;33`*+ZU-)2ZFZ93xE>&6>3u!L_w^1+X z1F*2kNo2{+|0FOyw)QI< z)>(7I_Y9KuI0O_tOfn zP=-6?qL6QKXy((uynu>i_clc#o;C}Zi~DPF*y=T7(O3U_2&nCtGL4GmTy%k~yKf6? zku?qd&b_uhz%!M-qD0?x;7358_}rtq+|z~oA5K{VGs-?E-|Dj=tsPELc8}YfAcuFf z{b_H>4LsUoR|Z?WZ=j z;vGTQpI0Z}ps{+1yyJ~TNkX5&CX5d^{#h@OKNjtDWaKbvD>3RoYnQr3qOtkvwQlr_ zcFkxew&&g@0?%b@Ap)jZLN=@0`jZV8Cw6T zz&UKM5+Kw>k9D!g#;XI;rqoO3tSky+(kyhq)Y5AF5);x|N6ke-e9V$ArggnJY7I5U z$|0^2vlRGkmaive^w?q&?lBUki1ZaPkYbXZipTWRbyPdkAuKN)0$`?(dOz{tTDYBb zkeIHR(RK-Grq~18Zm7)fZ1ZEkG_zTHM1aw-S;53Aa<;3c^2?V@aRFq>5g=e`x{gj$ z2765Re!^#69I=YYc@tp#P!}K)m0tI5!Ls&d{?cD&km+Op7>Q&e{b9NPO6u4=jq0G2 zpHbtXI~MkkYWd2Y_4?^#=q#6~RCAg)#{Cm;QMzTar2#hEDZc6-j#1@5 ziZCPrWKG`H4oI&er7FdkG+8z=UG=U??R2+LI0kGoJrmjyBju1!<)KzaXd%I{7>H$*LGb2-PqA1vcw5@p+oL#;(J8tmb;T=l|9I z|KSJt9>?5|J*9NOUMAUgI-{|&`JDT>WVhEV6Y?w5)=lldY+v?U8a@u+kkmw_AHM=o zg;30=_C>)MW7gKWQCe}U1BUC~SGc>o#p9$jl53 zeqA(^Ip;W8f!l!XeveMmR4K+~A5B!;^RE*rx835H=q9N2NSC3jVws~ECOD0eqhw15f|9~c?^tfxnI<1@?uU>IC{Os zO`lur2&97Kx(FqzG8i!+4fotMz?R6L63QvKai*Y(XP${>74*}}@A@nh?hF$5BKqmR zT}C7){#bF5&$^#&Va<>ZSu}$H_E@^RBs$ia+z0}V4+!wdT5(;I_toomAhEo?Mtc{C z^m|&9SXWi1Kh!66CUDYQj2U+KE#_pc$d#2!WRN0b>ksggsep$wU6kM;%Vgn9Mut8J zub;7>DwM=QsoQA2lOx)#p+o0dH;b%~QgP%Edu+Coh-Oig?&( zI1vDL z%uyNxuK03Kd3w*YefU^PFb`qa?Ky#h0jg$I(rYnM%1XptbHwH}P;5xlL8mj6rAWL_ z>fr|3UN81|ttCvx-`FeN11;X=$gEr9H!A^)HNIbzvka%nqLySBS`ELg-f;u6gskW7 zM%3Nnv?~3 zXX63p>HY|rb#G>;+cDDH-FUH`aDbo?=k=2EkW*RV{95(i%390L)XOQz2y*NNw& zv_uvcyVmdRg0v=QpCiQOCo{wc$jf(Ds^r*wps<qZN^`%i@1~aXo|UdnkK=qL5O)P{Fv}WQt1k%EK16WXkCH zPyW9lR~BNx$*Mm>uIu*Yy$S#?7GESSsP32asKuTseijXM5+h8D`FN#R`GVq66QSB=B~j`5JTbH3IZqn0*EdHXNg5Hj-Hc$JE;(@kV0;eloHwBP@L#5~ zl;fbX{b(-VGF&ord+>4X8EUQ$j_8{xynlpcHAo>pb@^=!*5Ier^xGW^ajv}M=L%*M zrXV)$w>Vw`kQOa}AXZt-s`f3n@y;G!&WAug9?J86fT?z_@0i^Cx?)>ui7EsB(hBI! zl;rh3=N9%DtKC{H!tsm-s-#4`>0=sdyzXm@>-K!G|C#&1Nao-be}AlfURp|${ha1L^;+x7Gq}~6Of!aZ^_)2t#_=~TPh$Y77^UuxV5nS9tm&+56 zb`Dho?F)|PDdjU?@LUVhmK*xpBwV??3jA!T^9~f*cJ-;$<$~eSnlFxPn}V)RTcD5= zNcu2j4|YOaO!3_1|FQShL2)fx-*6x#fuI8fm;k{ocnB~^(7}QQcMlfagF6fkA-Dwz z?(QxD1_-XfC3tX$Z*%Uw&wFpqdCvX*eXHIoirO^;y}Ns@Uiw?B9Y0?KJ=$yBFQ251 z#?eo5Z)JC^=ohBSEFY2Fw@=*H=Q1&T@7^%t*vD4egMK|gRJlpJ4 z7DgXU!3OQ5=4eYgX=)mu?2M@wy^M>t#nd+J$9GN(sS3^-;5i%pPDhem5i3Viboajf z{K64)s#~-Wp|f1VFF?L4W3030y~TWUO9~4G;&;UA#|*vBm6VpGU4~kl*u(kBQi}Z6 zDmeLr2CD!rwsUKIm!rUxQlCaGiBo|B9AW!YSe zP<=*b>cO)vWXWWq=(%!q_Ic5??>MbkC}SgVc}+&9__`b`$Z&YbtR@PE^9d+MaLz_^ zmniIEIP(s@X*0;lHHm89YO?WWOmr-zwZ})r{sh!H5#b!H4`#<6l{1q^)oS3Xn3B7_ zC1ld96S5#_WK+;e=sh|PjGz|BnR}}rM2?4{4s>kxwCX)Ts%}L$bh5mp+`kwe#jmrc zi3b5lp8ig5rTMc@t87|xUAd#4_vG zWXc5^sn5uh-+tQ2=L<+7B4n`%mnNX=Ru=4EAfyV6K@|phcD)i&)ikh?+G7Q=P;rH% zQ#eBlJ-3$DUzWgjdZsQ}_(OK6G8sckR{aVdVdj_iwg3PhbuEmn*Z@cZFSFH^MFzVx zgA$)nx=~B-7Jy^PTyr|%j@!QEgp)4<*?h6JTLW)h)1}Y$8IhUS^Did-z)t;Xi;mLl z$oQiMBm76iHn)uw!|dpUJPPNb1wU|8 zLyhlTO+vtx$dG*X+9n%Ew|=@~6kRBLr~Rw-Kp?yC^GA07oBDlRG?ue?o!UE@5-uK1 zb=zmhe)?|w+iM;&(ZQfceU^@Lr)Sd{#~XRU94baY`Q-`$dk+|PVA;1`Uo zTcu!#Y{&C`1qUlWLaBXoD|4O#d?U(eyw~PA?B>RjKX0pm-7zxdy z{@R(ARZj1!$W0yjia|ES?s@cmdR1sWf8W`(qAmrM;K#x3?zRRa)^~)n>=ylTm$E4FIxKD%b6`#TSe5 z3O4?oTWa`gs|hcf1#WsjlEcnz#E#&PY}J<%WlX;Fl|?d~>*FDW^d8sIMsOWHxPHxD z>knxYaoG@!uXJ{NU-y+uYIsU^#o<9pA=CvwTBg5Bc{(z#Y`+fv!S@(lOVqz#XO}IDWwU7qB@ti_hz>gl(fT&>U>AGqEyOQ z&VP-PY%aJ5o&=6s7(;WiY94EOL_<>C|^r~BPand64%jv|9PuvuDT)fI-VTP>hs?D#tv6A0<`sGY(DuS<8fMWN# z4=EelsYYg>E6m^6eg07xw4DRLQ=|$Nz1Y&cFI;wRbgw#*(_}%{|31&=jW#OBd7IRu63s-Ya z$unl0AXYP6_6Qb&J#mbV-CEEkvf2IpOOGgH&84SelCye2LH0B6ldGPS!c8cD{n7Mu z`u?icQ|lfjEv#>*!1ZpLHqy%EJ5OVtw)7!N@zrDLL=I91DER1ojgMWxFA>#SFNuDp zSVfK(gVEAH!Q=lESEKgh@~LEJ%qyamuO?IM+=MDH8Ea<)b0VEeVu$;#paPbpq)ZjD z*6-e{DZK%j`3ny~g{3Ml{a-YHL>}r|zDY~0FE6>kUb0oImSFUq2^+-)i?;tYn z88wb*)B*Zr7Zf^Rq(G#N2es`l;JxL8&L7iFFcx3!*m}5BSd$Y^cBLMaK7S7st9>&> zI=yYCj5|U8fpA~C7JA?5*d`#C3|ha32N0LAswj{^5XnAwUzWLQDv92t?SZqpDFjn zcv+u0GY8;YMPIT*90RGkJFjIuvLuQ2AJVy^=t`Bx@fYtUwyt2#r|Xw!hDmlo%@-;Xu6R;EW5eL$bD7ohuXU|DT@Ea}-#u&qY3d z;0s1?lp*@WCYHlf^7wmGP^{O%JcqsGQWlXFc-3Oz(6(-a2}y1Z4Vldp%?j5K+@OKn z9!iX{bAOnIfd?9bgy@pLjAjj3r?yF9@1mPW?F2#U4R5J-lt^${PV5to;eoV z1V@4Cl!pPmKh1?BP_2Z%ka` zQ=sE<7{}%R(nR_Gh&@1b4g@CA$Ts0It^o8qW!E%e`1{e5fXUaQJe@9&y`TJASi#?~ z_WgaIX|)cp%0VPBc}#CIp1&Vn2Md@ye#hst=O}vA`(a!ze{|RS$5j9P+u8em@+^HG zQ)0m6AtolT{(f{x;D~6F3kB?uVv&%@PL}8lg-fm^{(0R0_#~1J>2R%IOixcQLB6Jh zen6#6Uj#mKmG}Xpn3;^sdt_w9)*5yA^LNT0BygS61vKoxWrKmxX^REoJG8V@}hF{DZQqv!cO(D~XQ%x0HH- ze9nUadJm01h(prr))DOs+=V8CN!p=G|FfvSe3*XQnzr1JObhfvcWHP()HOOXLQ6(Q z=E@Xa@bB&U|Kh-TVWRj21OVkSUYKU^xqLuPFf9;i@XRHwO{EKV{Ew{q%ZEA2MJr2q zmIkmnM00boj8s(qpFVxE)u{PS^0%Ag%?l-^r1bOg@wv|L!7{r3Q0q21wLMu9tvW#Z z_s7c1s|6eAGfq*Kc#s2ZP?~6ekA<|14E2TZzul1Vm+$tZz#<}&I^3`VQfDn;@tCuy z1^++xRqbsnNs%EEmR|xxleF!87nT|IMi`3v-!tA~vKN1M?63=xo+w3BpbicW4jnr? zJ0_06NZ(@`&b$)6=Ns+FFKhNgi3%fBDtzj*de z7A1v1;#Y?jfHZ|Ci6BW0KOJ3Ar_1AiRJQ)lr2&6>uzl+N^Up>8N))ifHbg=MK(bc;b6Qx-QhP6SjxqkF$^Y~kfL*V>1uSt4{kVYn z)gG1Sg~7O@r5A0~e&4nKT--BRYR=BjtQb^&U<%OAEGY6LkV5kPdvZTOR1*0)=AX7wYWtv|Ynk>isI zwgd@$hrhmb;2(NcV4gZMsp$PN1%M`yeK^a6q6x?+=ob*`bIQM8C)PJwgdhqE%YHz@ z#|`ISXYl%gTL`j~C&fV~omC0Qugk;!+oJcdu|T3oU>mJi6(n#Nx*s?UNxO-fl+^^9 z1(JLYd#!B%75uwNd>coCdV%^0KnAvUXkZmzfs4JgLGrBySQV43r_!{C1@ z@BaDWVhu1x6hBC(QpIA{if64O0Jye;OzOW1ci`U#fu^uFqE9ydQn1~BxOlgdf->hg zUVG=%k@R>(F5qXbHC7aiNC;V)2+YP#n~p*TLg|gqlR^=p5e`(4dmfn%g;9R}WE;6c zYcRilyYF+j>yzjp6zHsG{3jFM!Tr>{7*=d^8zG|4zzVawps4u2MKoJ)}#Euk* zK*BY0@iUdfj`TvJB>m^7e}4QDPLtGW`_s!G0IN3Cb4vZ?C0Be99(mKS%w)&i@}1bfNshGD;y8^69_4RvOrp z#Gkd&z*<|ekd(C?#`VM4Cuo&5EUNT-KT!#PUL>=VUhF2cVu)qZQrC3dt?F`^)Z(Pc zsY!#QtS3ZIb+ggBv3a$+9mgOj*;f3%u?#H0bWsZhn}|@vbv;lDn@(rC63vRaT4H|f zP=L<+b#CP~-A~3TVpxCbUiFd+t^BYgjELJ8dg;z|*(bETR`mzz{%s8cJivgvkh(bF zP2S!}d$w^6S+oh>JoO!&IHWG?PAykIRJTm|!Kf31{xjVvQH(PWyQfv~7!{c{|C5}p zMbJRRV6f`<@7_u`fJEOc1itK{`^F8S(=p5U0E1Sg%)+0vioU_L?hoe zn8r09NF55*a3~J@?y|sTVHy09GHI|*9lz9F?MY@t;CU9l$!hyd|19{Kie_2u#d^|{ecmP4!sg4Zu9Hcf7SdwF zcDW(blRb~K27I2qS_AG4&X$XTW!HtP`Q+4=l4y&vF1)V2hJEep;~aiQ4F2n1V=7lK z8>G4Bwq5s|oXTcC6g<46sjjW=H8*IW^2a zn?o&pR=$zyikFk>^5LZ&D=J%r`;LJ^r7oChv&G}~C@rofwQq=fH>Z4>TWN-4&#%CC zgMCu7;^R+Y67{-WA5qCK2g~>^A7_|4F5mu3 zuKx3YwDRHr@^I<_#Et*7EaKT&)4sa~iSwLI&oe8x6+)Vq>;#+f+6TcZxPU}bX>;AV zyWPKgO70FjY&*ENx;wS%YT9!s(O-*Ek(o^Z1t009Lo`N37&G`$1>@7};#00a#WiNN z4xgRYv^1&K+i%(yozHFX)j(Dwg-4u#;_YqJy_V+CLv4Vj*{)u&x>7km@Lr4S{84I` z`<`9eGC&2VHf~Co1R!trZ_YQ zrxyLpFpig&wMzlGZQ}KHX}}H$r*RXN3FH5Yc-U|n-NU=i+O=!tc{P#q=4{SRp~ckQ zi)RokzJwz+ua$rP`~0z1H{vZQCFG0Ev|${T-1)xeo$Ezp(1lb}1Y{Z3ciFfpZS}JG zq{vFia(i2{TW-&D=aVFpM2m4^pD0w_>4(E8(?+tL%q99*rKfpFb9f{uQKpKkrtE4 z@T)#!{;TO_Jxo{C#P8)N6B;#429G~k{Yl#l-Dk7Yd$qeISG?r!>t+cHXEOc1Gd zaf`Oln>n=$Ky=j#pe5iM#PYBmm+?|5DBaXz6tiXcX`hzIeH~J|y8N*U3<55)&_2mm z`ReK~4|{)}c6X7MfY`u2$7StnXvWiYF<_M{(a>G^F@swip<7)RTKMCn*opD>dV`-c zoY<*;G{tD7E-LodX9byryh*o4!AL>tL`l5>#n8el;Vd-K*-}0{{Q;vSi`a<=E3QU6 zY#cQXtw0GjkN({qXUEO%jI2<+pi~A4+AH#J&38BZm@^hxyTBao)E38XXC|AKiCliB zQ^c@KJQ-3U?C{FvB0s>2DFZYatB4!cX4g^NH>C{YpF_}e8>?|J$REZtCBw6;7&&Ou z9Eu!E5yAT^#e>NYEK~WzarSFvLE{~ugi`#T&&TaDS~*J|z{GgTi_mM&=3SkFkp{1x z3gH^+U7r%3-KaEU@Js$KfsX&!a!2?l&&$y?KD$ofY|bvrMYt%gq3y`JE#q{>YFKhr z?CacJPU=TXb0l2wrdf&e_&torr33zQ;nT{e&-~KWfH=;oGMRc}{3LJsF?diu`K{)a zn_8jBVGsYUO`#I0kk>c=f|6#J)gu7S{ya|>J*nKNYFt^;>~YNYD<-`FkIF&r?X2e4 z<|$$UvCmkFVF_4lLE^nV2r@s$mt;J-PNxivksca3+v&uFvA*vic9P1Q?VDY$Xouf9 z!S>2}UZ9|uJpd_TRlo23^x9D*IMWxCul5{}d&Ef^P3z%P%1_32#;Vf%SUOw`A;~mt zfH|maa?%OcQ|KD8&tU zcULWU4ReFg+EPBW?&n_4$1Aox6K>yn9(ou=3Pmn(!`{I%|3ZDE1L~XO^KBXC@GIxU zS0j&?AO){7-k#%kyMW+>$w(bht>(6&C;Ep~ueP=VDV%RkCS{XK025M8CKH0Ov80f8 zIhIiM>O6INJIg?S*fH6qj%?m-2~~&II#s)jLHc=k4z)oGv&TWmqveNEX?NEfLo+uE zcQ*?PTo4^BE*2zTz;$qa9|K4KB! zs;U>eQn-({KOfVy@TZO(vrrHPf-O`H z0ZzAh%i|x74g&g)$-g81xaj=|bZ)@3VZ9=FnW<+w}pl+Dm*<%(Cw0)BIoC+q$)Y&KMd z*?m)za5Mt_IVPTZC)#BHO~`>;*oJ_D?4mOqGGOm1KaH2^hh;VtIHZ0a!5bC`g!Eh@SiooNY)-2vsH=Snkq8 z;yd51uqk2g2#cB@)ix^7_=gw}-8w}f2uVkRpkM1fi_W~73RieU&-2Zuov6{A!!32C zIiHjqN1SY&Jw@(wm$SHg`J}chm6(;Aj>pZWtY=~;cCv*bg$TN-sWo<>WNLVjMB@dvuO&Pg1q|D&vuSSWlKVlyqSB*21yr5n@Jh`$s#`~V^KUs-*{v8l++gem0wBz zDSo+}fVJ`BgSx zEG^Kc>DLV*XTc+Tuy{e_1v_u{on@E1Zv=4VxiSj3;?IkBU}5_W&5oNipWo!O_~JYl z#A~=d?2n%6Z))eJ#B}-Y`)MWdguNM14%FUkyQ-HRN8WsdqB@yF!zwTiTEdWGPL$rU zAl8s$O~`l9eD1>)!=y?L_p`~|JGUxU918Jh82i`xV3$}O5CMm3EFNp9V_0QgXv~_& zpsL-x^G4TRh4yA<*1OdjglIYzKl#+*?V=j(ODP`rlL8$Jb7RLHiWo569C?O+S-HhA zJtF>@03u}$kKxTum+6=B_y*FYOLN^Or z{N9(aq-dzOWV?zqjLje}`hN)nA z7P-Hp5nnr*c3N;pB`?C_){k}ZAeTSEavpgPm|uY3%G8Cb{mV!3D>Yf*RgeuvN7RfV zFB6Zx0-5mj=7=nd_Si)HlFV@(WIiEk+Pm7#$|6>nNHhYGyUn|s}c&JQnLj2{~ zv+h_`G!f8UD^^Q9)ZJ;Iko3nwxF#h+v^!wor!JM#)qweXx8)Y#(3b}(2(yp zUs^@&4gXUzM1KzC0&HG=jZu1sR-3>!j6g-Maoo)@A#>ez-gd=#60DM+%s}o#h!uyY zdZkvxB%_$6V^dsXEmp@43SXV=DeFda@S$+uCc|_V_ozQ^4(Uj_@|~VjjgE|T%sm&* zLSNP;kP(H7g?e;7e`d&Y{j0;6Ag*RD+;XD&izO@%`eDh7awNcn>5}(`74L{C4kv9x zJ)R6Zik+LqPYeow+0jO1W>5u+KYY~}KINtnccyiTvUW-b$69;zaF0Dhiy$zl7={}I zO%_>I@{oqwtqNhdhw%k^3CwhtAE4#gPEf?Rrx`dmv#fWplehYy56j={^!~0vx4niI z(Kf)EzmGPIeC#=T*;10J?W?_XP}}py(ulMB$;byiXyf<6I@W+LDD3*`V;U;koGh|S zXphvK7s%&{`{Ut#hZ0vU3rd=`fgF~O3$=$>=}5xn>t|rOn(KLQcJgBuKdtkX z7xh^2{S6OQ+&&pxd3gosi^xtN+nLOK_l*^vWRxr!6dUR-F9VVu;&7V`GV9hr81~g_ z3G$Pyly;arnQc~I{P+vGmyjGgIlp~_37+ui@?0TjHxg60b@gh6e8RKl&FmyUt3&CPz;x^3fGe3G-2n)tcbhoKrj?lF-vL*iO~L4vn&{fCN9y`; zMu-R%KlJzmh)j6)`{1$QmgYF-x^VoGHnqQeiZy}En!Vt|+sLP3+dbBPH4<$TimSP1 z@YQfjC^}ICgjIq)jxcXK-u=Z5_d;w-nwegJE4d%t-c9{C{w)$8GC8o8{6!3L*~N*Y z^TOA(cfif!$Y5ykYp{WUGoru?){ZhL*79o%)@SJnWv|JnU^|rg*}b)KVApym5ta?k zCLYKhV*FxP97?jWUMH}MZ1z?uh;OW5|MvMLH;Dxi-CeG53Jcze*%kNJb2c(KDGcYJ z_oL@GvcRm9pBWgH_$zEHBIBKYQ&!FX;4MLWa`ylfESL|0oWa@wqlL*u&Ya3ej$fgz z@koM{QGbrW5XR+?kU2Xt@7J0Tez7t@q;9e9)T7#<17e>P(*2z4SPm)p zWxqnJjqEGOnyPY&>Rp?)oBCs%aCE(FO__1y3G5&TCVf{X_+tDzp8@X>qfUqK6=7R_ zZve;$hWuVrR96VCM7;oZkn|I61_c4vCcDp8FJL$GbQ03zR>?}ou%gyeZ^er*R@LkW z-fCm}ROUMM?UMwS(MP-k4T_P2175@BR$AaV^6oo}eh&=FHPoE^-G>%H^hKtHqmjSX zbxmQ3CJ}x81Os3Gp>%$fP_Q_byeCUV^0&zHC~_25-6?+yn_Ob2Ih(wxmwH~{LP&m} zX&;1C)}TA|M*LA%wI+)$9lvtG5l+$f{#j33z=Ux&H+?&n!&k-8rFv1n&38rz|K6j0dl)yF zl0SZ?+^AO*T$uVT63&KAL=zOG8G0d%)$w_F zWyv~s+4W1dh5)+S5v2_4*R$AE;M?`nD|Tt208 zLMY257&$i|(Iy__ZHTf1(E^A{xQ{gS$_p71dXS%=R%QXjw&OR=LVNCmm>2aiN?iIH z#HurSlKe~Gv5c&FDHQFiG~>w-i;}k(`h2Mj@VC91Hs0(9mC)-nJY_>R{~SUF6?-pO zXPufp#tGHzzJPJV&j$Px_%6*pzag}R;0C72MDN)S!H<%pOX@UOnnm3 zXh@@wRiE8u8%zfrIRZf(aHYoDX&_+kRv{vp$ztXCR|S1cG}C)5M>i3qU+us^nxoLU25HGzYP&knI!n7xV-~A~Ye4eIUAJ>eKbu^BpV+!}?6LNWdrR05}&RBk}3PfuA zSP3w1Txaji8$-anaRXxGFJbeAue$wVx*-l9^G{bAirb}l3Ag$Ru2PMqVoXv|+1!sO zA$M+-HZQums$$a$nw%Hci(R>(}i4D-@Y|j9bW<*_23I=l+n9-JO z)=|>Yn_G30Qipv`zhG^S`%lXjNPT_=4%6qLHF|i9DSG~RpG{~uGD@}XXh9awSm@skWcF696Ip37K1 zR;g%BcojJrR^6^j;s*ndtdCCvM<`1-<4*71+Iu@$08H#REsGSxOrJn_zAk0kE?xrqPic9oXvO_g%LS@;Ba=-@#3^J!NCwKE~wJf z44_(d&-#_aCJoVK4vTOiL#m(>hYY|Ec z5imarxSU4HeC|a#APlvkm=NrbFgt3_FNw_FzvuD~J-amv&C;xUfAVQVl7X~cKZchu zdZ3skq^_MS-gC%xM0LrejSEW@Rvsw)0`^g0hx9~3%Z1!1 z>ecM?0-VQ(C;CF>izh!bBV>BstkKCPtU;)W6(P46JUgY@hsQb5O4oW8NvaTn9J(ku zvcuH|inp4M(}tS`+3ZH55{3bzDB1*hntejLGm$sx^JN^e#CPUrYF0r~$Yr5FspJd8 z^da*gZt-WL!I?nvX>hYSJuOPVm}z$bARA1yP#Bt|9AwS{5Ut1bELf$HJbgtS3+E3z zW8$n9i2sY_$!secNz;%O;cO382Ehd4!+v&Vk4Zx5Kt+DU9_LGsK*knDeW2OXDYq7; z8^0AiAC_n_f0o+{tSj2sKTc9^zgyM8K@Uk<1_UOhr&&aJmXYeKeT8i#jw1e{Rjx3E zMZxJshM*cg#x{1F@K(#s=`1s`*i(M_iv!otTqz_{`S7Tb!Vm2N+__7#btS*dNq1kY z-N$*gnH212rfg(90o5ppL5VkSbc2QTd;(K9n^tHn!8nzxqSkGkeLX`og~f|4_9s$?m8yB_ zYf2asj2Xh0Pb31>^0On9qwNgL=MKFj-nL>nx}7mAk=~5feP`%AQ~J`_g9Na@&9);w zXp>cxPL_C*6{0+f!aaYKxka4ggzw&`Z`t(KMp>n2X+vJ=!E`0KE8+*F>RHI`^UaF^ zL+@S9pzK&lNXHx5U8wzA_~VH3`sVd^v}Luzjp6F#X5t@N{rW|baue{M zZFD$}+o&{WnARx049CRV-mpXK=Zl(@Z!q&3t>1=^if8&oBT;lILfDfAC0tmy0@e^w zxW+-rEJWoX=}*nHH+n`asMq?^?GI+-!IFNU5@dE3e`2%(2E2JHe}X}kWLAfOQrpRf zd3K9#xsM&_oMXG(+gGsXIUs~H!*!kPRNDA*~9xL#+SmUkRO`IX*tq~ zUvU-|0SSJ(L)c0kKlz&O?BS)OJw^DU6tUqzC{sHe;3qB8_>njQdhM&3No8&B6@2%2 zc9Z)g?hxbyy%UqWnqy}yN6qb26_DCu5$;4d6N+=-=DR(JzzdMDe&o;Vi@C9z(B-Y{nE@ z^!yTdo5+TPcuQ<4sGhf&){4qYV!VBrNYa00tCNl{jI~jt!|U1CbD{R9AWkHZMD8_3 z`#1zYJ=}4;H5f{K3U}*S5<7k@M-0Rm=WV|(!CU|Xi>DuK=0U$&dKE3F0Ak1o-6eIGVy-jeFAvqm_> z;g@7O00BRJRCi$1Y8K|r&_tVG^Ozc6N@a%1Zn0c)ZEq`r5t-#orA5`5pxVVX1q!8B zVonedHB|k`k19^mfRoX;m{?Z>Qj;(~4bi#QugjRm(}d-MakZ-F+WPrc!&SQ-$&pwu zt&V>jcX{z^UG-8vWm_6sfAJB33Gi><777VuX@?F$6F;?=_U>5+@+gKc5RC$S;j(HG zI@M!`ZGb2`UO^d7-{NRt5N^zCw6k&%VWNRozCu)}C-j4<<@)ga!vy_y8mo*!Xv3QL zg4&bM%|8|5(|^Lg7`&;uu94ZStRevn*^8z@wtQZ201hjwFY6nRCp$ozx@x0jCi3iA z^$`#GUQvjh!!`ca( zr);agdJgb*JL*MsZnji#_>(_svY|pMIItYT*!2_7IrY8}Wr&g6hUQX3o9zaBBUUY+#a#)s|DX^E8LzrdG@C?xDhG3Q*tYCfJe1R^K_%@;*CLx01 zmn;z{FPE#9zt0o@ICk}u*tuG`^S~A|U}V|??H6oeFE~6i!m?NjlH*%u-9!ZAi_^ns zA1HL=WDu3qE(cdy&GOe##NKwgh!f~*#hK(3-%$F2y9=;l6St%&b(r3gF)b4G{KPK| zQ8o-sfl^@$N~m{I8@L$=A&ByjWs#43KP#Wu47`z-M$0aY)6m2bp-xw1quB2LL{-C~ksy%h+cc}PIX+Oa&cmh{m=n*Afa7E4EE zc&KvtqXoCnh9iT}L+?D#J4l|^9%%~Dq89*Hi=P3A1hTFQyv&kcni!zxWHBg=(i0-_ zOHQcKqP6^W+Jjmud@GQ4{_d$ZtMKaBh$lq81V@blc?dc03_KTV(uSIaI7hr{E(a>C2A zUqmq#RXVR5!1I|=5!rZNH>TfU0(c{^n8J9mwa|lL+9yPO4SV(5whgCc-BP%-n&>lg(!tQ% z9P+$9vb|c}2s=wo<1l*sF@`L8LhB4d^aJ`pZ&eKCS<}J08nX0f%G$iDV zsKX7`Wkh!f$*i*xCkoqVF?Gq*bR|*lI;^+(_gp~=8=;YKRp;mo%0AB78RBNWPqB@p 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 GIsXEyw5JsS literal 0 HcmV?d00001 diff --git a/PrivateAI/gpu-cuda-install/readme.md b/PrivateAI/gpu-cuda-install/readme.md new file mode 100644 index 0000000..dae4976 --- /dev/null +++ b/PrivateAI/gpu-cuda-install/readme.md @@ -0,0 +1,618 @@ +# GPU Drivers, CUDA Toolkit & LocalAGI Setup + +> Converted from `GPU Drivers CUDA tool kit LocalAGI.docx` + + + +GPU Drivers and CUDA tool kit + + +Repaired drivers and installed CUDA 13 using the following outline process + + + +![Image](images/img01.png) + + +Did the following so that the cuda-tools are available to users when they login + +![Image](images/img02.png) + + +Additional software installed + +```bash +apt-get install nvtop (shows GPU usage, graphically, nice and useful tool) +``` + + +Docker install + +Followed the official guide as per the following + + + +```bash +Once installed if you get the following error you need to be added to the docker group in /etc/group. Once added you need to log out and log back in. +``` + + +```bash +richard@capstone-gpu1:~$ docker ps +permission denied while trying to connect to the docker API at unix:///var/run/docker.sock +``` + + +```bash +added the docker proxy config per the document “docker with proxy” (in markup link to the document) +``` + + +then installed the nvidia-container toolkit per the instructins from Nvidia. + + +# Add the NVIDIA container toolkit repo + +distribution=$(. /etc/os-release; echo $ID$VERSION_ID) + + +```bash +curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \ +``` + +| sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg + + +```bash +curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list \ +``` + +| sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#' \ + +| sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list + + +```bash +sudo apt-get update +sudo apt-get install -y nvidia-container-toolkit +``` + + +```bash +Then configure docker to use it +sudo nvidia-ctk runtime configure --runtime=docker +sudo systemctl restart docker +``` + + +localAGI Install + + +```bash +git clone +``` + + +added a + +```bash +docker-compose.local.yaml +services: +``` + +localagi: + +image: localai/localagi:latest + +container_name: localagi + +restart: unless-stopped + +# Internal only — not exposed to host + +expose: + +- "8080" + +environment: + +## - Tz=Utc + +# optional volumes: + +# volumes: + +# - ./models:/app/models + +networks: + +- internal + +build: + +context: . + +```bash +dockerfile: Dockerfile.webui +``` + +args: + +## Http_Proxy: ${Http_Proxy} + +## Https_Proxy: ${Https_Proxy} + +## No_Proxy: ${No_Proxy} + + +nginx: + +image: nginx:latest + +container_name: localagi-proxy + +restart: unless-stopped + +depends_on: + +- localagi + +ports: + +- "80:80" + +- "443:443" + +volumes: + +- ./nginx/conf.d:/etc/nginx/conf.d:ro + +- ./nginx/.htpasswd:/etc/nginx/.htpasswd:ro + +# - ./certs:/etc/letsencrypt:ro # optional if using HTTPS + +networks: + +- internal + +- public + + +networks: + +internal: + +internal: true + +public: + +driver: bridge + + +nginx config + +In the LocalAGI folder that was created with the git clone create the following hierarchy. + +./nginix + +./nginix/conf.d + + +put the following file localagi.conf in ./nginx/conf.d/ + + +localagi.conf + +############################## + +## # Upstream Definitions # + +############################## + + +upstream localagi_upstream { + +server localagi:3000; + +} + + +upstream localai_upstream { + +server localai:8080; + +} + + +upstream localrecall_upstream { + +server localrecall:8080; + +} + + +############################################### + +# 9000 — LocalAGI WEB UI (REQUIRE BASIC AUTH) # + +############################################### + +server { + +listen 9000; + +server_name _; + + +auth_basic "LocalAGI Web UI"; + +auth_basic_user_file /etc/nginx/.htpasswd; + + +location / { + +proxy_pass http://localagi_upstream; + +proxy_set_header Host $host; + +proxy_set_header X-Real-IP $remote_addr; + +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + +proxy_set_header X-Forwarded-Proto $scheme; + +proxy_buffering off; + +} + +} + + +############################################### + +# 9001 — Localrecall (REQUIRE BASIC AUTH) # + +############################################### + +server { + +listen 9080; + +server_name _; + + +auth_basic "Localrecall Web UI"; + +auth_basic_user_file /etc/nginx/.htpasswd; + + +location / { + +proxy_pass http://localrecall_upstream; + +proxy_set_header Host $host; + +proxy_set_header X-Real-IP $remote_addr; + +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + +proxy_set_header X-Forwarded-Proto $scheme; + +proxy_buffering off; + +} + +} + + +############################################################### + +# 80 — LocalAI API + UI # + +# BASIC AUTH for WEB UI ONLY # + +# NO BASIC AUTH for /v1/* (OpenAI API — token required) # + +############################################################### + +server { + +listen 80; + +server_name _; + + +############################################################### + +# SECTION 1 — OpenAI API (/v1/*) — TOKEN ONLY, NO BASIC AUTH # + +############################################################### + +location /v1/ { + +# NO basic auth here — scripts authenticate via Bearer token + +proxy_pass http://localai_upstream; + + +proxy_set_header Host $host; + +proxy_set_header X-Real-IP $remote_addr; + +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + +proxy_set_header X-Forwarded-Proto $scheme; + +proxy_buffering off; + +} + + +##################################################### + +# SECTION 2 — LocalAI WEB CONSOLE — BASIC AUTH REQ # + +##################################################### + +location / { + +auth_basic "LocalAI Web Console"; + +auth_basic_user_file /etc/nginx/.htpasswd; + + +proxy_pass http://localai_upstream; + + +proxy_set_header Host $host; + +proxy_set_header X-Real-IP $remote_addr; + +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + +proxy_set_header X-Forwarded-Proto $scheme; + +proxy_buffering off; + +} + +} + +htpasswd file + +The purpose of the .htpasswd file is to secure the administrative portals of the following applications + +Localai + +Localrecall + +localagi. + + +And in ./nginix create admin users in the .htpasswd file. These are users who need to have permission to create/modify the configuration of the services. Use the following command to add a user + + +htpasswd .htpasswd username + + +make sure that the file has permissions 644 + +LocalAGI Configuration + +```bash +Due to the nature of the deakin environment there are a number of configuration steps required to setup LocalAGI so that it works properly and can reach various external sites for docker images, models and docker builds. +``` + +Docker.webui + +This file needs to be updated to support the Deakin Proxy configuration. + +------------ + +# Use Bun container for building the React UI + +```dockerfile +FROM oven/bun:1 AS ui-builder +``` + +## Arg Http_Proxy + +## Arg Https_Proxy + +## Arg No_Proxy + +```bash +ENV http_proxy=$HTTP_PROXY +ENV https_proxy=$HTTPS_PROXY +ENV no_proxy=$NO_PROXY +``` + + +# Set the working directory for the React UI + +WORKDIR /app + + +# Copy package.json and bun.lockb (if exists) + +COPY webui/react-ui/package.json webui/react-ui/bun.lockb* ./ + + +# Install dependencies + +```bash +RUN bun install --frozen-lockfile +``` + + +# Copy the rest of the React UI source code + +COPY webui/react-ui/ ./ + + +# Build the React UI + +```bash +RUN bun run build +``` + + +# Use a temporary build image based on Golang 1.24-alpine + +```dockerfile +FROM golang:1.24-alpine AS builder +``` + +## Arg Http_Proxy + +## Arg Https_Proxy + +## Arg No_Proxy + +```bash +ENV http_proxy=$HTTP_PROXY +ENV https_proxy=$HTTPS_PROXY +ENV no_proxy=$NO_PROXY +``` + + +# Define argument for linker flags + +ARG LDFLAGS="-s -w" + + +# Install git + +```bash +RUN apk add --no-cache git +RUN rm -rf /tmp/* /var/cache/apk/* +``` + + +# Set the working directory + +WORKDIR /work + + +# Copy go.mod and go.sum files first to leverage Docker cache + +COPY go.mod go.sum ./ + + +# Download dependencies - this layer will be cached as long as go.mod and go.sum don't change + +```bash +RUN go mod download +``` + + +# Now copy the rest of the source code + +## Copy . . + + +# Copy the built React UI from the ui-builder stage + +COPY --from=ui-builder /app/dist /work/webui/react-ui/dist + + +# Build the application + +```bash +RUN CGO_ENABLED=0 go build -ldflags="$LDFLAGS" -o localagi ./ +``` + + +```dockerfile +FROM ubuntu:24.04 +``` + +## Arg Http_Proxy + +## Arg Https_Proxy + +## Arg No_Proxy + +```bash +ENV http_proxy=$HTTP_PROXY +ENV https_proxy=$HTTPS_PROXY +ENV no_proxy=$NO_PROXY +``` + + +```bash +ENV DEBIAN_FRONTEND=noninteractive +``` + + +# Install runtime dependencies + +```bash +RUN apt-get update && apt-get install -y \ +``` + +ca-certificates \ + +tzdata \ + +```bash +docker.io \ +``` + +bash \ + +wget \ + +curl + + +# Copy the webui binary from the builder stage to the final image + +COPY --from=builder /work/localagi /localagi + + +# Define the command that will be run when the container is started + +ENTRYPOINT ["/localagi"] + +Docker-compose.nvidia.yaml + +.env + +HTTP_PROXY=http://proxy1.it.deakin.edu.au:3128 + +HTTPS_PROXY=http://proxy1.it.deakin.edu.au:3128 + +NO_PROXY=localhost,127.0.0.1 + + +Added /etc/profile.d/proxy.sh + +export HTTP_PROXY=http://proxy1.it.deakin.edu.au:3128 + +export HTTPS_PROXY=http://proxy1.it.deakin.edu.au:3128 + +export NO_PROXY="localhost,127.0.0.1,::1" + + + +Setting up so that you can connect to the OpenAI API via Token Auth but still have basic auth for the admin pannels + + +Generate an API_TOKEN + + + +```bash +openssl rand -hex 32 +``` + + +sk-a07de3d7880e0b602068b3eb58fb784b808b724278aa775bac66953c11b3c4ff + + + + + + + diff --git a/PrivateAI/localai/docker-compose.yaml b/PrivateAI/localai/docker-compose.yaml new file mode 100644 index 0000000..0cba78c --- /dev/null +++ b/PrivateAI/localai/docker-compose.yaml @@ -0,0 +1,86 @@ +services: + localai: + container_name: local-ai + hostname: localai + image: localai/localai:latest-gpu-nvidia-cuda-12 + restart: unless-stopped + ports: + - 4000:8080 + #network_mode: host + runtime: nvidia + deploy: {} + + # Compose v2: + gpus: all + + environment: + # Keep core behaviour + #- LOCALAI_SINGLE_ACTIVE_BACKEND=true + # Outbound proxy for model/gallery downloads + - HTTP_PROXY=${HTTP_PROXY} + - HTTPS_PROXY=${HTTPS_PROXY} + # Don't proxy internal Docker traffic + - NO_PROXY=localhost,127.0.0.1,::1,localai,postgres,mcphub,mcp-hub-mcphub-1 + + #- BACKENDS=llama-cpp + #- DISABLE_BACKEND_AUTODETECT=true + - AUTO_LOAD_MODELS=false + #- AUTO_UPDATE_MODELS=true + - DISABLE_TELEMETRY=true + - HEALTHCHECKS=false + - DISABLE_GRAMMAR=true + - DISABLE_TOKENIZER_CHECKS=true + #- DEFAULT_MODEL=llama-3.3-70b-instruct + - MCP_HEADERS={"Accept":"application/json, text/event-stream"} + - NVIDIA_VISIBLE_DEVICES=all + - NVIDIA_DRIVER_CAPABILITIES=compute,utility + - DEBUG=true + - LOCALAI_LOG_LEVEL=debug + #- LOGLEVEL=trace + #- LOCALAI_AUTOLOAD_GALLERIES=false + # - LOCALAI_GALLERIES=[] + #- LOCALAI_DATA_PATH=/data + # PostgreSQL-backed knowledge base + - LOCALAI_AGENT_POOL_VECTOR_ENGINE=postgres + - LOCALAI_AGENT_POOL_DATABASE_URL=postgresql://localrecall:localrecall@postgres:5432/localrecall?sslmode=disable + #- LOCALAI_AGENT_POOL_DEFAULT_MODEL=hermes-3-llama3.1-8b-lorablated + # disabled this and nominated gemma4 so that we don't double up on models that are running, save some GPU memory + - LOCALAI_AGENT_POOL_DEFAULT_MODEL=gemma-4-e4b-it + - LOCALAI_AGENT_POOL_EMBEDDING_MODEL=granite-embedding-107m-multilingual + - LOCALAI_AGENT_POOL_ENABLE_SKILLS=true + - LOCALAI_AGENT_POOL_ENABLE_LOGS=true + logging: + driver: "json-file" + options: + max-size: "20m" + max-file: "5" + volumes: + - /opt/redback/privateai/volumes/models:/models:cached + - /opt/redback/privateai/volumes/images/:/tmp/generated/images/ + - /opt/redback/privateai/volumes/backends:/usr/share/localai/backends + - /opt/redback/privateai/volumes/localai_data:/data + - /opt/redback/privateai/volumes/localai_config:/etc/localai + + + # Make libcuda visible to backends that overwrite LD_LIBRARY_PATH: + #- /usr/lib/x86_64-linux-gnu/libcuda.so.1:/backends/cuda12-stablediffusion-ggml/lib/libcuda.so.1:ro + #- /usr/lib/x86_64-linux-gnu/libcuda.so.1:/backends/cuda12-llama-cpp/lib/libcuda.so.1:ro + # + # + + postgres: + image: quay.io/mudler/localrecall:v0.5.2-postgresql + environment: + - POSTGRES_DB=localrecall + - POSTGRES_USER=localrecall + - POSTGRES_PASSWORD=localrecall + + # Runtime: don't force HTTP(S)_PROXY, just no-proxy for internal services + - NO_PROXY=localhost,127.0.0.1,::1,localai,postgres,mcphub,mcp-hub-mcphub-1 + volumes: + - /opt/redback/privateai/volumes/localai_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U localrecall"] + interval: 10s + timeout: 5s + retries: 5 diff --git a/PrivateAI/localai/env b/PrivateAI/localai/env new file mode 100644 index 0000000..2ccbaae --- /dev/null +++ b/PrivateAI/localai/env @@ -0,0 +1,7 @@ +HTTP_PROXY=http://proxy1.it.deakin.edu.au:3128 +HTTPS_PROXY=http://proxy1.it.deakin.edu.au:3128 + +LOCALAI_API_KEY=sk-changethistobethelocalaiapikeyforclients +MODEL_NAME=gemma-3-4b-it-qat +MULTIMODAL_MODEL=moondream2-20250414 +IMAGE_MODEL=sd-1.5-ggml diff --git a/PrivateAI/localai/readme.md b/PrivateAI/localai/readme.md new file mode 100644 index 0000000..bc08646 --- /dev/null +++ b/PrivateAI/localai/readme.md @@ -0,0 +1,730 @@ +# LocalAI GPU Service with PostgreSQL Knowledge Base + +This compose service runs LocalAI with NVIDIA CUDA 12 GPU support and a PostgreSQL-backed LocalRecall knowledge base. + +It is designed for a private AI stack where LocalAI provides the OpenAI-compatible API, model hosting, agent memory, skills, image outputs, and backend management. + +To setup you will need to copy the env file to .env and then generate a new API key for clients to connect. + +## Service Overview + +```yaml +services: + localai: + container_name: local-ai + hostname: localai + image: localai/localai:latest-gpu-nvidia-cuda-12 + restart: unless-stopped + ports: + - 4000:8080 + #network_mode: host + runtime: nvidia + deploy: {} + + # Compose v2: + gpus: all + + environment: + # Keep core behaviour + #- LOCALAI_SINGLE_ACTIVE_BACKEND=true + # Outbound proxy for model/gallery downloads + - HTTP_PROXY=${HTTP_PROXY} + - HTTPS_PROXY=${HTTPS_PROXY} + # Don't proxy internal Docker traffic + - NO_PROXY=localhost,127.0.0.1,::1,localai,postgres,mcphub,mcp-hub-mcphub-1 + + #- BACKENDS=llama-cpp + #- DISABLE_BACKEND_AUTODETECT=true + - AUTO_LOAD_MODELS=false + #- AUTO_UPDATE_MODELS=true + - DISABLE_TELEMETRY=true + - HEALTHCHECKS=false + - DISABLE_GRAMMAR=true + - DISABLE_TOKENIZER_CHECKS=true + #- DEFAULT_MODEL=llama-3.3-70b-instruct + - MCP_HEADERS={"Accept":"application/json, text/event-stream"} + - NVIDIA_VISIBLE_DEVICES=all + - NVIDIA_DRIVER_CAPABILITIES=compute,utility + - DEBUG=true + - LOCALAI_LOG_LEVEL=debug + #- LOGLEVEL=trace + #- LOCALAI_AUTOLOAD_GALLERIES=false + # - LOCALAI_GALLERIES=[] + #- LOCALAI_DATA_PATH=/data + # PostgreSQL-backed knowledge base + - LOCALAI_AGENT_POOL_VECTOR_ENGINE=postgres + - LOCALAI_AGENT_POOL_DATABASE_URL=postgresql://localrecall:localrecall@postgres:5432/localrecall?sslmode=disable + #- LOCALAI_AGENT_POOL_DEFAULT_MODEL=hermes-3-llama3.1-8b-lorablated + # disabled this and nominated gemma4 so that we don't double up on models that are running, save some GPU memory + - LOCALAI_AGENT_POOL_DEFAULT_MODEL=gemma-4-e4b-it + - LOCALAI_AGENT_POOL_EMBEDDING_MODEL=granite-embedding-107m-multilingual + - LOCALAI_AGENT_POOL_ENABLE_SKILLS=true + - LOCALAI_AGENT_POOL_ENABLE_LOGS=true + logging: + driver: "json-file" + options: + max-size: "20m" + max-file: "5" + volumes: + - /opt/redback/privateai/volumes/models:/models:cached + - /opt/redback/privateai/volumes/images/:/tmp/generated/images/ + - /opt/redback/privateai/volumes/backends:/usr/share/localai/backends + - /opt/redback/privateai/volumes/localai_data:/data + - /opt/redback/privateai/volumes/localai_config:/etc/localai + + + # Make libcuda visible to backends that overwrite LD_LIBRARY_PATH: + #- /usr/lib/x86_64-linux-gnu/libcuda.so.1:/backends/cuda12-stablediffusion-ggml/lib/libcuda.so.1:ro + #- /usr/lib/x86_64-linux-gnu/libcuda.so.1:/backends/cuda12-llama-cpp/lib/libcuda.so.1:ro + # + # + + postgres: + image: quay.io/mudler/localrecall:v0.5.2-postgresql + environment: + - POSTGRES_DB=localrecall + - POSTGRES_USER=localrecall + - POSTGRES_PASSWORD=localrecall + + # Runtime: don't force HTTP(S)_PROXY, just no-proxy for internal services + - NO_PROXY=localhost,127.0.0.1,::1,localai,postgres,mcphub,mcp-hub-mcphub-1 + volumes: + - /opt/redback/privateai/volumes/localai_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U localrecall"] + interval: 10s + timeout: 5s + retries: 5 +``` + +## What This Stack Provides + +This compose file starts two main services: + +- `localai` — the LocalAI inference/API service using the CUDA 12 NVIDIA GPU image. +- `postgres` — a PostgreSQL-backed LocalRecall database used by LocalAI agent memory and knowledge base features. + +LocalAI is exposed on host port `4000`, mapped to container port `8080`. + +The API endpoint is therefore: + +```text +http://localhost:4000 +``` + +From another Docker container on the same network, the service should be reachable as: + +```text +http://localai:8080 +``` + +## LocalAI Service + +### Image + +```yaml +image: localai/localai:latest-gpu-nvidia-cuda-12 +``` + +This uses the LocalAI GPU image built for NVIDIA CUDA 12. + +This is suitable for hosts with NVIDIA GPUs, the NVIDIA driver installed, Docker installed, and NVIDIA Container Toolkit configured. + +### Container Name and Hostname + +```yaml +container_name: local-ai +hostname: localai +``` + +The explicit hostname `localai` is useful for internal Docker service resolution and for other services that need to call the LocalAI API. + +### Port Mapping + +```yaml +ports: + - 4000:8080 +``` + +This maps the LocalAI API to the host on port `4000`. + +Use: + +```bash +curl http://localhost:4000/v1/models +``` + +Or from a remote machine: + +```bash +curl http://:4000/v1/models +``` + +## GPU Configuration + +The service enables NVIDIA GPU access using both the legacy runtime setting and Compose v2 GPU syntax: + +```yaml +runtime: nvidia +gpus: all +``` + +The NVIDIA environment variables are: + +```yaml +- NVIDIA_VISIBLE_DEVICES=all +- NVIDIA_DRIVER_CAPABILITIES=compute,utility +``` + +These allow the container to access all visible GPUs for compute workloads and NVIDIA utility functions such as `nvidia-smi`. + +### GPU Validation + +After startup, test GPU visibility: + +```bash +docker exec -it local-ai nvidia-smi +``` + +If this fails, check the host first: + +```bash +nvidia-smi +``` + +Then verify Docker GPU access: + +```bash +docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi +``` + +## Proxy Configuration + +The LocalAI service passes through outbound proxy settings: + +```yaml +- HTTP_PROXY=${HTTP_PROXY} +- HTTPS_PROXY=${HTTPS_PROXY} +``` + +These are useful for model downloads, gallery downloads, and other outbound network access. + +Internal Docker traffic is excluded from the proxy using: + +```yaml +- NO_PROXY=localhost,127.0.0.1,::1,localai,postgres,mcphub,mcp-hub-mcphub-1 +``` + +This prevents calls to nearby containers from being routed through the external proxy. + +If additional internal services are added, append them to `NO_PROXY`. + +Example: + +```yaml +- NO_PROXY=localhost,127.0.0.1,::1,localai,postgres,mcphub,mcp-hub-mcphub-1,litellm,semantic-router +``` + +## Core LocalAI Behaviour + +### Model Autoloading + +```yaml +- AUTO_LOAD_MODELS=false +``` + +Automatic model loading is disabled. + +This helps avoid loading every available model at startup and gives more direct control over GPU memory usage. + +### Telemetry Disabled + +```yaml +- DISABLE_TELEMETRY=true +``` + +Disables telemetry. + +### Healthchecks Disabled + +```yaml +- HEALTHCHECKS=false +``` + +Disables LocalAI healthchecks. + +This can reduce noisy healthcheck behaviour during debugging or when backends take a long time to initialise. + +### Grammar and Tokenizer Checks Disabled + +```yaml +- DISABLE_GRAMMAR=true +- DISABLE_TOKENIZER_CHECKS=true +``` + +These settings reduce startup and runtime issues with some model/backend combinations. + +### Debug Logging + +```yaml +- DEBUG=true +- LOCALAI_LOG_LEVEL=debug +``` + +Enables verbose debug output from LocalAI. + +This is useful when troubleshooting backend loading, model startup, memory issues, MCP connectivity, or knowledge base behaviour. + +## MCP Headers + +```yaml +- MCP_HEADERS={"Accept":"application/json, text/event-stream"} +``` + +This configures request headers for MCP interactions. + +The `text/event-stream` accept value is important for streamable HTTP MCP servers. + +## PostgreSQL-Backed Agent Pool and Knowledge Base + +LocalAI is configured to use PostgreSQL as the vector engine: + +```yaml +- LOCALAI_AGENT_POOL_VECTOR_ENGINE=postgres +``` + +The connection string points to the `postgres` service: + +```yaml +- LOCALAI_AGENT_POOL_DATABASE_URL=postgresql://localrecall:localrecall@postgres:5432/localrecall?sslmode=disable +``` + +The database credentials are defined in the `postgres` service: + +```yaml +- POSTGRES_DB=localrecall +- POSTGRES_USER=localrecall +- POSTGRES_PASSWORD=localrecall +``` + +## Agent Pool Models + +### Default Agent Pool Model + +```yaml +- LOCALAI_AGENT_POOL_DEFAULT_MODEL=gemma-4-e4b-it +``` + +This selects `gemma-4-e4b-it` as the default agent pool model. + +The intent is to avoid doubling up on GPU-heavy models and reduce unnecessary GPU memory use. + +### Embedding Model + +```yaml +- LOCALAI_AGENT_POOL_EMBEDDING_MODEL=granite-embedding-107m-multilingual +``` + +This model is used for embeddings in the LocalAI agent pool and knowledge base workflows. + +## Skills and Logs + +```yaml +- LOCALAI_AGENT_POOL_ENABLE_SKILLS=true +- LOCALAI_AGENT_POOL_ENABLE_LOGS=true +``` + +These enable LocalAI agent skills and logs. + +This is useful when using LocalAI as part of an agent-oriented private AI stack. + +## Volumes + +The service uses host-mounted volumes under: + +```text +/opt/redback/privateai/volumes +``` + +### Model Storage + +```yaml +- /opt/redback/privateai/volumes/models:/models:cached +``` + +Stores LocalAI models. + +Inside the container, models are available at: + +```text +/models +``` + +### Generated Images + +```yaml +- /opt/redback/privateai/volumes/images/:/tmp/generated/images/ +``` + +Stores generated image outputs. + +### Backend Storage + +```yaml +- /opt/redback/privateai/volumes/backends:/usr/share/localai/backends +``` + +Stores LocalAI backend binaries and backend-related files. + +This allows backends to persist across container restarts. + +### LocalAI Data + +```yaml +- /opt/redback/privateai/volumes/localai_data:/data +``` + +Stores LocalAI data. + +Note that the same host path is also used by the PostgreSQL service as its database directory: + +```yaml +- /opt/redback/privateai/volumes/localai_data:/var/lib/postgresql/data +``` + +If you want stricter separation between LocalAI application data and PostgreSQL database files, consider using separate paths, for example: + +```yaml +- /opt/redback/privateai/volumes/localai_data:/data +- /opt/redback/privateai/volumes/postgres_data:/var/lib/postgresql/data +``` + +### LocalAI Config + +```yaml +- /opt/redback/privateai/volumes/localai_config:/etc/localai +``` + +Stores LocalAI configuration files. + +## Logging + +```yaml +logging: + driver: "json-file" + options: + max-size: "20m" + max-file: "5" +``` + +This limits Docker JSON logs to five files of 20 MB each. + +This prevents LocalAI debug logs from filling the host disk. + +## PostgreSQL Service + +The PostgreSQL service uses the LocalRecall PostgreSQL image: + +```yaml +image: quay.io/mudler/localrecall:v0.5.2-postgresql +``` + +It creates a database called: + +```text +localrecall +``` + +With username: + +```text +localrecall +``` + +And password: + +```text +localrecall +``` + +## PostgreSQL Healthcheck + +```yaml +healthcheck: + test: ["CMD-SHELL", "pg_isready -U localrecall"] + interval: 10s + timeout: 5s + retries: 5 +``` + +This checks that PostgreSQL is accepting connections. + +## Suggested Directory Layout + +```text +/opt/redback/privateai/ +└── volumes/ + ├── models/ + ├── images/ + ├── backends/ + ├── localai_data/ + └── localai_config/ +``` + +Create the directories before starting the stack: + +```bash +sudo mkdir -p /opt/redback/privateai/volumes/models +sudo mkdir -p /opt/redback/privateai/volumes/images +sudo mkdir -p /opt/redback/privateai/volumes/backends +sudo mkdir -p /opt/redback/privateai/volumes/localai_data +sudo mkdir -p /opt/redback/privateai/volumes/localai_config +``` + +Set ownership if running Docker as your user: + +```bash +sudo chown -R "$USER:$USER" /opt/redback/privateai/volumes +``` + +## Starting the Stack + +Start both services: + +```bash +docker compose up -d +``` + +Start only PostgreSQL: + +```bash +docker compose up -d postgres +``` + +Start LocalAI: + +```bash +docker compose up -d localai +``` + +Follow logs: + +```bash +docker compose logs -f localai +``` + +Check PostgreSQL logs: + +```bash +docker compose logs -f postgres +``` + +## Basic API Tests + +List models: + +```bash +curl http://localhost:4000/v1/models +``` + +Check LocalAI root endpoint: + +```bash +curl http://localhost:4000 +``` + +Test from another container on the same Docker network: + +```bash +curl http://localai:8080/v1/models +``` + +## Useful Operational Commands + +Pull the latest LocalAI image: + +```bash +docker compose pull localai +``` + +Recreate the service after pulling: + +```bash +docker compose up -d localai +``` + +Restart LocalAI: + +```bash +docker compose restart localai +``` + +View running containers: + +```bash +docker compose ps +``` + +Stop the stack: + +```bash +docker compose down +``` + +Stop the stack and remove anonymous volumes: + +```bash +docker compose down -v +``` + +## Troubleshooting + +### LocalAI cannot see the GPU + +Check the host: + +```bash +nvidia-smi +``` + +Check Docker GPU support: + +```bash +docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi +``` + +Check inside the LocalAI container: + +```bash +docker exec -it local-ai nvidia-smi +``` + +### LocalAI cannot reach PostgreSQL + +Check the PostgreSQL container: + +```bash +docker compose ps postgres +docker compose logs postgres +``` + +Check name resolution from LocalAI: + +```bash +docker exec -it local-ai sh +getent hosts postgres +``` + +Check the PostgreSQL port from inside LocalAI: + +```bash +docker exec -it local-ai sh +nc -vz postgres 5432 +``` + +If `nc` is not installed in the container, use a temporary debug container on the same Docker network. + +### Internal traffic is going through the proxy + +Make sure all internal service names are included in `NO_PROXY`. + +Current value: + +```text +localhost,127.0.0.1,::1,localai,postgres,mcphub,mcp-hub-mcphub-1 +``` + +Add any additional internal services, such as: + +```text +litellm,semantic-router,openwebui,semgrep-mcp +``` + +### Model does not load automatically + +This is expected because: + +```yaml +- AUTO_LOAD_MODELS=false +``` + +Load or select models explicitly through LocalAI configuration, API calls, model galleries, or mounted model files. + +### Debug logs are very noisy + +Debugging is enabled: + +```yaml +- DEBUG=true +- LOCALAI_LOG_LEVEL=debug +``` + +Once the service is stable, reduce log verbosity by setting: + +```yaml +- DEBUG=false +- LOCALAI_LOG_LEVEL=info +``` + +Or remove those environment variables. + +## Notes on Shared `localai_data` + +This compose file maps the same host path to both: + +```text +/data +``` + +For LocalAI, and: + +```text +/var/lib/postgresql/data +``` + +For PostgreSQL. + +That may work depending on the intended LocalAI layout, but it is usually cleaner to separate application data from database data. + +Recommended alternative: + +```yaml +localai: + volumes: + - /opt/redback/privateai/volumes/localai_data:/data + +postgres: + volumes: + - /opt/redback/privateai/volumes/postgres_data:/var/lib/postgresql/data +``` + +This makes backups, restores, and troubleshooting easier. + +## Security Notes + +The database credentials in this compose file are simple defaults: + +```text +localrecall / localrecall +``` + +For production or shared environments, change the database password and update: + +```yaml +LOCALAI_AGENT_POOL_DATABASE_URL +``` + +Do not expose the LocalAI API port directly to untrusted networks without authentication, firewalling, or a reverse proxy. + +## Summary + +This compose stack runs LocalAI with: + +- NVIDIA CUDA 12 GPU support +- OpenAI-compatible API access on host port `4000` +- Persistent model, backend, image, data, and config volumes +- Debug logging enabled +- Proxy-aware outbound access +- PostgreSQL-backed LocalAI agent pool and knowledge base support +- Skills and agent logs enabled +- Docker log rotation to prevent runaway logs diff --git a/PrivateAI/mcphub/Dockerfile b/PrivateAI/mcphub/Dockerfile new file mode 100644 index 0000000..a6c690b --- /dev/null +++ b/PrivateAI/mcphub/Dockerfile @@ -0,0 +1,24 @@ +FROM samanhappy/mcphub:latest + +ARG HTTP_PROXY +ARG HTTPS_PROXY +ARG NO_PROXY + +ENV HTTP_PROXY=${HTTP_PROXY} +ENV HTTPS_PROXY=${HTTPS_PROXY} +ENV NO_PROXY=${NO_PROXY} +ENV http_proxy=${HTTP_PROXY} +ENV https_proxy=${HTTPS_PROXY} +ENV no_proxy=${NO_PROXY} + +WORKDIR /app + +# Make npm aware of the proxy during build, if provided +RUN if [ -n "$HTTP_PROXY" ]; then npm config set proxy "$HTTP_PROXY"; fi \ + && if [ -n "$HTTPS_PROXY" ]; then npm config set https-proxy "$HTTPS_PROXY"; fi \ + && npx playwright install chromium + +# Copy docker CLI from official image +COPY --from=docker:cli /usr/local/bin/docker /usr/local/bin/docker + +CMD ["node", "dist/index.js"] diff --git a/PrivateAI/mcphub/docker-compose.yaml b/PrivateAI/mcphub/docker-compose.yaml new file mode 100644 index 0000000..b4d55f9 --- /dev/null +++ b/PrivateAI/mcphub/docker-compose.yaml @@ -0,0 +1,31 @@ +services: + mcphub: + build: + context: . + dockerfile: Dockerfile + args: + HTTP_PROXY: ${MCPHUB_HTTP_PROXY} + HTTPS_PROXY: ${MCPHUB_HTTPS_PROXY} + NO_PROXY: ${MCPHUB_NO_PROXY} + http_proxy: ${MCPHUB_HTTP_PROXY} + https_proxy: ${MCPHUB_HTTPS_PROXY} + no_proxy: ${MCPHUB_NO_PROXY} + image: samanhappy/mcphub + ports: + - "3003:3000" + volumes: + - ./mcp_settings.json:/app/mcp_settings.json + - ./entrypoint-proxy.sh:/app/entrypoint-proxy.sh:ro + - ./proxychains.conf:/etc/proxychains.conf:ro + - ./repo_scan_job_mcp.js:/app/repo_scan_job_mcp.js:ro + - /var/run/docker.sock:/var/run/docker.sock + - /opt/redback/repos:/repos + environment: + - HTTP_PROXY=${HTTP_PROXY} + - HTTPS_PROXY=${HTTPS_PROXY} + - NO_PROXY=${NO_PROXY} + restart: unless-stopped + extra_hosts: + - "proxy1.it.deakin.edu.au:10.137.0.162" + entrypoint: ["/app/entrypoint-proxy.sh"] + command: ["pnpm","start"] diff --git a/PrivateAI/mcphub/entrypoint-proxy.sh b/PrivateAI/mcphub/entrypoint-proxy.sh new file mode 100755 index 0000000..5fa3b47 --- /dev/null +++ b/PrivateAI/mcphub/entrypoint-proxy.sh @@ -0,0 +1,62 @@ +#!/bin/sh +set -eu + +# Ensure apt and other tools see proxy envs (many tools expect lowercase) +if [ -n "${HTTP_PROXY:-}" ] && [ -z "${http_proxy:-}" ]; then export http_proxy="$HTTP_PROXY"; fi +if [ -n "${HTTPS_PROXY:-}" ] && [ -z "${https_proxy:-}" ]; then export https_proxy="$HTTPS_PROXY"; fi +if [ -n "${NO_PROXY:-}" ] && [ -z "${no_proxy:-}" ]; then export no_proxy="$NO_PROXY"; fi + +# If we're on Debian/Ubuntu, also configure apt to use the proxy explicitly +if command -v apt-get >/dev/null 2>&1; then + mkdir -p /etc/apt/apt.conf.d + # Prefer HTTPS proxy if set, otherwise HTTP proxy + APT_PROXY="${https_proxy:-${http_proxy:-}}" + if [ -n "$APT_PROXY" ]; then + cat > /etc/apt/apt.conf.d/99proxy </dev/null 2>&1; then + if command -v apk >/dev/null 2>&1; then + apk add --no-cache proxychains-ng + elif command -v apt-get >/dev/null 2>&1; then + apt-get update + # Try common package names + apt-get install -y proxychains4 || apt-get install -y proxychains-ng + else + echo "No supported package manager found to install proxychains." >&2 + exit 1 + fi +fi + +# Generate a proxychains config if none provided +CONF="/etc/proxychains.conf" +if [ ! -f "$CONF" ]; then + PROXY_URL="${https_proxy:-${http_proxy:-}}" + if [ -z "$PROXY_URL" ]; then + echo "HTTP_PROXY/HTTPS_PROXY not set and no $CONF provided." >&2 + exit 1 + fi + + HOSTPORT="$(echo "$PROXY_URL" | sed -E 's#^[a-zA-Z]+://##' | sed -E 's#/.*$##' | sed -E 's#^[^@]*@##')" + HOST="$(echo "$HOSTPORT" | cut -d: -f1)" + PORT="$(echo "$HOSTPORT" | cut -d: -f2)" + + cat > "$CONF" <:3003 +``` + +## Enabled MCP Integrations + +The MCP Hub configuration currently includes the following server integrations: + +```json +{ + "servers": [ + { + "name": "amap", + "tools": "all" + }, + { + "name": "playwright", + "tools": "all" + }, + { + "name": "fetch", + "tools": "all" + }, + { + "name": "sequential-thinking", + "tools": "all" + }, + { + "name": "time", + "tools": "all" + }, + { + "name": "mindmap", + "tools": "all" + }, + { + "name": "playwright-mcp", + "tools": "all" + }, + { + "name": "fetch-mcp", + "tools": "all" + }, + { + "name": "time-mcp", + "tools": "all" + }, + { + "name": "mongodb", + "tools": "all" + }, + { + "name": "git-mcp-server", + "tools": "all" + }, + { + "name": "repo-security-scan", + "tools": "all" + }, + { + "name": "arxiv-mcp", + "tools": "all" + }, + { + "name": "wazuh", + "tools": "all" + }, + { + "name": "postgresql", + "tools": "all" + }, + { + "name": "supabase-postgres", + "tools": "all" + } + ] +} +``` + +## Integration Summary + +| Integration | Purpose | +|---|---| +| `amap` | Map/location-related MCP integration | +| `playwright` | Browser automation | +| `fetch` | HTTP fetching | +| `sequential-thinking` | Structured multi-step reasoning tool | +| `time` | Time/date tools | +| `mindmap` | Mind map generation or manipulation | +| `playwright-mcp` | Additional Playwright MCP server | +| `fetch-mcp` | Additional fetch MCP server | +| `time-mcp` | Additional time MCP server | +| `mongodb` | MongoDB access | +| `git-mcp-server` | Git repository interaction | +| `repo-security-scan` | Job-based Semgrep repository scanning | +| `arxiv-mcp` | arXiv search/research integration | +| `wazuh` | Wazuh security platform integration | +| `postgresql` | PostgreSQL access | +| `supabase-postgres` | Supabase PostgreSQL access | + +## Docker Compose + +```yaml +services: + mcphub: + build: + context: . + dockerfile: Dockerfile + args: + HTTP_PROXY: ${MCPHUB_HTTP_PROXY} + HTTPS_PROXY: ${MCPHUB_HTTPS_PROXY} + NO_PROXY: ${MCPHUB_NO_PROXY} + http_proxy: ${MCPHUB_HTTP_PROXY} + https_proxy: ${MCPHUB_HTTPS_PROXY} + no_proxy: ${MCPHUB_NO_PROXY} + image: samanhappy/mcphub + ports: + - "3003:3000" + volumes: + - ./mcp_settings.json:/app/mcp_settings.json + - ./entrypoint-proxy.sh:/app/entrypoint-proxy.sh:ro + - ./proxychains.conf:/etc/proxychains.conf:ro + - ./repo_scan_job_mcp.js:/app/repo_scan_job_mcp.js:ro + - /var/run/docker.sock:/var/run/docker.sock + - /opt/redback/repos:/repos + environment: + - HTTP_PROXY=${HTTP_PROXY} + - HTTPS_PROXY=${HTTPS_PROXY} + - NO_PROXY=${NO_PROXY} + restart: unless-stopped + extra_hosts: + - "proxy1.it.deakin.edu.au:10.137.0.162" + entrypoint: ["/app/entrypoint-proxy.sh"] + command: ["pnpm","start"] +``` + +## What This Compose File Does + +The `mcphub` service: + +- Builds from a local custom `Dockerfile` +- Uses `samanhappy/mcphub` as the base image +- Publishes MCP Hub on host port `3003` +- Mounts MCP settings from the host +- Mounts a proxy-aware entrypoint script +- Mounts a `proxychains.conf` +- Mounts the repository security scan MCP wrapper +- Mounts the Docker socket so repo scan jobs can launch Semgrep containers +- Mounts `/opt/redback/repos` into the hub at `/repos` +- Runs the hub through `entrypoint-proxy.sh` +- Starts MCP Hub using `pnpm start` + +## Port Mapping + +```yaml +ports: + - "3003:3000" +``` + +This maps container port `3000` to host port `3003`. + +Access MCP Hub at: + +```text +http://localhost:3003 +``` + +Or from another machine: + +```text +http://:3003 +``` + +## Mounted Files and Directories + +### MCP Settings + +```yaml +- ./mcp_settings.json:/app/mcp_settings.json +``` + +This is the main MCP Hub configuration file. + +It defines the enabled MCP servers and how they are launched or connected. + +### Proxy Entrypoint + +```yaml +- ./entrypoint-proxy.sh:/app/entrypoint-proxy.sh:ro +``` + +This script configures proxy environment variables, installs `proxychains4` if needed, and launches MCP Hub through proxychains. + +### Proxychains Configuration + +```yaml +- ./proxychains.conf:/etc/proxychains.conf:ro +``` + +This defines which traffic should go through the proxy and which networks should remain local. + +### Repo Scan MCP Wrapper + +```yaml +- ./repo_scan_job_mcp.js:/app/repo_scan_job_mcp.js:ro +``` + +This mounts the custom job-oriented Semgrep scan MCP server into the hub container. + +The MCP settings can then run this file as a stdio MCP server. + +### Docker Socket + +```yaml +- /var/run/docker.sock:/var/run/docker.sock +``` + +This gives the MCP Hub container access to the host Docker daemon. + +This is required by `repo_scan_job_mcp.js`, because that tool launches Semgrep scan jobs using `docker run`. + +Important: mounting the Docker socket is powerful and should only be used in trusted environments. + +### Repository Mount + +```yaml +- /opt/redback/repos:/repos +``` + +This exposes host repositories to MCP Hub at: + +```text +/repos +``` + +The repo scan MCP uses this path to list repositories and validate scan targets. + +## Custom Dockerfile + +```dockerfile +FROM samanhappy/mcphub:latest + +ARG HTTP_PROXY +ARG HTTPS_PROXY +ARG NO_PROXY + +ENV HTTP_PROXY=${HTTP_PROXY} +ENV HTTPS_PROXY=${HTTPS_PROXY} +ENV NO_PROXY=${NO_PROXY} +ENV http_proxy=${HTTP_PROXY} +ENV https_proxy=${HTTPS_PROXY} +ENV no_proxy=${NO_PROXY} + +WORKDIR /app + +# Make npm aware of the proxy during build, if provided +RUN if [ -n "$HTTP_PROXY" ]; then npm config set proxy "$HTTP_PROXY"; fi \ + && if [ -n "$HTTPS_PROXY" ]; then npm config set https-proxy "$HTTPS_PROXY"; fi \ + && npx playwright install chromium + +# Copy docker CLI from official image +COPY --from=docker:cli /usr/local/bin/docker /usr/local/bin/docker + +CMD ["node", "dist/index.js"] +``` + +## What the Dockerfile Adds + +The custom image extends `samanhappy/mcphub:latest` and adds: + +1. Proxy environment variables during image build +2. npm proxy configuration +3. Playwright Chromium browser installation +4. Docker CLI copied from the official Docker CLI image + +### Why Playwright Chromium Is Installed + +Some MCP tools use Playwright for browser automation. + +The build step installs Chromium: + +```dockerfile +npx playwright install chromium +``` + +This avoids runtime failures when a Playwright MCP server needs a browser binary. + +### Why Docker CLI Is Added + +The repo scan MCP launches Semgrep jobs using: + +```bash +docker run +``` + +Mounting `/var/run/docker.sock` is not enough by itself. The container also needs the `docker` command available. + +The Dockerfile copies the CLI from the official Docker image: + +```dockerfile +COPY --from=docker:cli /usr/local/bin/docker /usr/local/bin/docker +``` + +## Entrypoint Proxy Script + +```sh +#!/bin/sh +set -eu + +# Ensure apt and other tools see proxy envs (many tools expect lowercase) +if [ -n "${HTTP_PROXY:-}" ] && [ -z "${http_proxy:-}" ]; then export http_proxy="$HTTP_PROXY"; fi +if [ -n "${HTTPS_PROXY:-}" ] && [ -z "${https_proxy:-}" ]; then export https_proxy="$HTTPS_PROXY"; fi +if [ -n "${NO_PROXY:-}" ] && [ -z "${no_proxy:-}" ]; then export no_proxy="$NO_PROXY"; fi + +# If we're on Debian/Ubuntu, also configure apt to use the proxy explicitly +if command -v apt-get >/dev/null 2>&1; then + mkdir -p /etc/apt/apt.conf.d + # Prefer HTTPS proxy if set, otherwise HTTP proxy + APT_PROXY="${https_proxy:-${http_proxy:-}}" + if [ -n "$APT_PROXY" ]; then + cat > /etc/apt/apt.conf.d/99proxy </dev/null 2>&1; then + if command -v apk >/dev/null 2>&1; then + apk add --no-cache proxychains-ng + elif command -v apt-get >/dev/null 2>&1; then + apt-get update + # Try common package names + apt-get install -y proxychains4 || apt-get install -y proxychains-ng + else + echo "No supported package manager found to install proxychains." >&2 + exit 1 + fi +fi + +# Generate a proxychains config if none provided +CONF="/etc/proxychains.conf" +if [ ! -f "$CONF" ]; then + PROXY_URL="${https_proxy:-${http_proxy:-}}" + if [ -z "$PROXY_URL" ]; then + echo "HTTP_PROXY/HTTPS_PROXY not set and no $CONF provided." >&2 + exit 1 + fi + + HOSTPORT="$(echo "$PROXY_URL" | sed -E 's#^[a-zA-Z]+://##' | sed -E 's#/.*$##' | sed -E 's#^[^@]*@##')" + HOST="$(echo "$HOSTPORT" | cut -d: -f1)" + PORT="$(echo "$HOSTPORT" | cut -d: -f2)" + + cat > "$CONF" < { + try { + const r = await fetch(\"http://10.137.17.254:4045/health\", { + headers: { Authorization: \"redacted\"} + }); + console.log(\"status\", r.status); + console.log(await r.text()); + } catch (e) { + console.error(\"FETCH_ERR\", e); + } +})();"' +``` + +## What the Node Fetch Test Does + +This command runs a Node.js `fetch()` call from inside the MCP Hub container. + +It tests whether the container can reach: + +```text +http://10.137.17.254:4045/health +``` + +with an `Authorization` header. + +This is useful for debugging MCP servers such as Wazuh or other internal HTTP services. + +## Expected Fetch Test Result + +A healthy result should show an HTTP status and response body, for example: + +```text +status 200 +{"status":"ok"} +``` + +A failure may show: + +```text +FETCH_ERR TypeError: fetch failed +``` + +Common causes include: + +- Target service is down +- Wrong port +- Authentication failure +- Proxy recursion +- Internal IP missing from `NO_PROXY` +- proxychains intercepting traffic that should be local +- Firewall or routing issue +- MCP service bound only to `127.0.0.1` instead of `0.0.0.0` + +## Repo Security Scan Integration + +The file: + +```text +./repo_scan_job_mcp.js +``` + +is mounted into the MCP Hub container as: + +```text +/app/repo_scan_job_mcp.js +``` + +It provides the `repo-security-scan` MCP integration. + +This integration launches Semgrep scans as background Docker jobs and exposes tools such as: + +```text +repo_list +repo_security_scan_start +repo_security_scan_status +repo_security_scan_result +repo_security_scan_list_jobs +``` + +## Docker Requirements for Repo Security Scan + +The repo scan MCP requires: + +1. Docker CLI inside the MCP Hub container +2. Docker socket mounted from the host +3. Repositories mounted at `/repos` +4. Correct `HOST_REPOS_BASE_DIR` +5. Semgrep image available or pullable + +The Dockerfile provides the Docker CLI: + +```dockerfile +COPY --from=docker:cli /usr/local/bin/docker /usr/local/bin/docker +``` + +The compose file provides the Docker socket: + +```yaml +- /var/run/docker.sock:/var/run/docker.sock +``` + +The compose file provides repository access: + +```yaml +- /opt/redback/repos:/repos +``` + +## Recommended Repo Security Scan Environment + +In `mcp_settings.json`, the `repo-security-scan` server should set environment variables similar to: + +```json +{ + "REPOS_BASE_DIR": "/repos", + "HOST_REPOS_BASE_DIR": "/opt/redback/repos", + "JOBS_BASE_DIR": "/repos/logs/security-jobs", + "SEMGREP_IMAGE": "semgrep/semgrep:1.159.0", + "SEMGREP_JOBS": "2", + "SEMGREP_PER_FILE_TIMEOUT": "2", + "SEMGREP_TIMEOUT_THRESHOLD": "1", + "SEMGREP_MAX_TARGET_BYTES": "500000", + "JOB_RETENTION_LIMIT": "100" +} +``` + +If you want `/repos` to be read-only, use a separate job directory: + +```json +{ + "JOBS_BASE_DIR": "/jobs" +} +``` + +And mount: + +```yaml +- /opt/redback/repos:/repos:ro +- /opt/redback/security-jobs:/jobs +``` + +## Suggested Directory Layout + +```text +mcp-hub/ +├── docker-compose.yaml +├── Dockerfile +├── .env +├── mcp_settings.json +├── entrypoint-proxy.sh +├── proxychains.conf +├── nodefetch.sh +└── repo_scan_job_mcp.js +``` + +Host repositories are stored at: + +```text +/opt/redback/repos +``` + +Inside the MCP Hub container they appear as: + +```text +/repos +``` + +## Build and Start + +Build the custom image: + +```bash +docker compose build mcphub +``` + +Start MCP Hub: + +```bash +docker compose up -d +``` + +Follow logs: + +```bash +docker compose logs -f mcphub +``` + +Check status: + +```bash +docker compose ps +``` + +## Rebuild After Dockerfile Changes + +If you change the Dockerfile, rebuild without cache: + +```bash +docker compose build --no-cache mcphub +docker compose up -d mcphub +``` + +## Restart After Settings Changes + +If you change `mcp_settings.json`: + +```bash +docker compose restart mcphub +``` + +Then check logs: + +```bash +docker compose logs -f mcphub +``` + +## Validate Container Basics + +Enter the container: + +```bash +docker exec -it mcp-hub-mcphub-1 sh +``` + +Check proxy environment: + +```bash +env | grep -i proxy +``` + +Check Docker CLI: + +```bash +docker version +docker ps +``` + +Check repository mount: + +```bash +ls -la /repos +``` + +Check MCP settings: + +```bash +ls -la /app/mcp_settings.json +``` + +Check repo scan script: + +```bash +ls -la /app/repo_scan_job_mcp.js +``` + +Check proxychains: + +```bash +which proxychains4 +cat /etc/proxychains.conf +``` + +## Validate Playwright + +Inside the container: + +```bash +npx playwright --version +``` + +Check installed Chromium browser files: + +```bash +ls -la /root/.cache/ms-playwright || true +``` + +If Playwright complains that Chromium is missing, rebuild the image: + +```bash +docker compose build --no-cache mcphub +docker compose up -d mcphub +``` + +## Validate Docker Socket Access + +Inside the MCP Hub container: + +```bash +docker ps +``` + +If this fails, check: + +```bash +ls -la /var/run/docker.sock +``` + +If permission is denied, the container user may not have access to the Docker socket. + +Possible fixes include: + +- Run the container as a user with access to the Docker socket +- Adjust Docker socket group permissions +- Use a Docker socket proxy +- Run the MCP Hub container as root if appropriate for the environment + +## Validate Repo Scan Docker Launch + +Inside the MCP Hub container: + +```bash +docker run --rm -v /opt/redback/repos:/repos:ro semgrep/semgrep:1.159.0 semgrep --version +``` + +If the Semgrep image cannot be pulled, check proxy access. + +If the mount path is wrong, check that `/opt/redback/repos` exists on the Docker host. + +## Validate Internal HTTP Access + +Run the provided Node fetch test: + +```bash +./nodefetch.sh +``` + +Or directly: + +```bash +docker exec -it mcp-hub-mcphub-1 sh -lc 'node -e " +(async () => { + try { + const r = await fetch(\"http://10.137.17.254:4045/health\", { + headers: { Authorization: \"redacted\"} + }); + console.log(\"status\", r.status); + console.log(await r.text()); + } catch (e) { + console.error(\"FETCH_ERR\", e); + } +})();"' +``` + +## Troubleshooting + +### MCP Hub Does Not Start + +Check logs: + +```bash +docker compose logs mcphub +``` + +Common causes: + +- Invalid `mcp_settings.json` +- Entrypoint script not executable +- Proxychains installation failure +- Missing proxy environment variables +- `pnpm start` failing inside the base image + +Check script permissions: + +```bash +chmod +x entrypoint-proxy.sh +``` + +### Proxychains Cannot Connect + +Inspect proxychains config: + +```bash +docker exec -it mcp-hub-mcphub-1 cat /etc/proxychains.conf +``` + +Check the proxy host: + +```bash +docker exec -it mcp-hub-mcphub-1 getent hosts proxy1.it.deakin.edu.au +``` + +Check connection to proxy: + +```bash +docker exec -it mcp-hub-mcphub-1 sh -lc 'nc -vz proxy1.it.deakin.edu.au 3128' +``` + +If `nc` is not installed, use another debug container or install netcat temporarily. + +### Proxy Recursion + +Proxy recursion can happen if the container tries to reach the proxy through the proxy. + +This is why the proxy IP is excluded: + +```conf +localnet 10.137.0.162/32 +``` + +Also ensure it appears in `NO_PROXY`: + +```text +10.137.0.162,proxy1.it.deakin.edu.au +``` + +### Internal MCP Server Connection Fails + +For internal IPs such as: + +```text +10.137.17.254 +``` + +make sure they are in: + +```env +NO_PROXY +MCPHUB_NO_PROXY +``` + +And that proxychains excludes the relevant network: + +```conf +localnet 10.0.0.0/8 +``` + +### Node Fetch Shows `bad port` + +This can happen when proxy environment variables or Node/undici proxy handling are malformed. + +Check: + +```bash +docker exec -it mcp-hub-mcphub-1 env | grep -i proxy +``` + +Make sure proxy values look like: + +```text +http://proxy1.it.deakin.edu.au:3128 +``` + +and not like: + +```text +proxy1.it.deakin.edu.au:3128 +``` + +or values with hidden characters. + +### MCP Server Bound to Localhost Only + +If an HTTP MCP server runs in another container and binds only to `127.0.0.1`, MCP Hub will not be able to reach it. + +The target MCP server should bind to: + +```text +0.0.0.0 +``` + +For FastMCP-based servers, this is often: + +```env +FASTMCP_HOST=0.0.0.0 +``` + +### Docker CLI Missing + +Check: + +```bash +docker exec -it mcp-hub-mcphub-1 which docker +``` + +If missing, rebuild the image: + +```bash +docker compose build --no-cache mcphub +docker compose up -d mcphub +``` + +### Docker Socket Missing + +Check: + +```bash +docker exec -it mcp-hub-mcphub-1 ls -la /var/run/docker.sock +``` + +If missing, confirm the compose volume: + +```yaml +- /var/run/docker.sock:/var/run/docker.sock +``` + +### Repositories Missing + +Check on host: + +```bash +ls -la /opt/redback/repos +``` + +Check inside container: + +```bash +docker exec -it mcp-hub-mcphub-1 ls -la /repos +``` + +### Playwright Browser Missing + +Rebuild the image: + +```bash +docker compose build --no-cache mcphub +docker compose up -d mcphub +``` + +Then check logs for: + +```text +npx playwright install chromium +``` + +## Security Notes + +This deployment is powerful because MCP Hub has: + +- Access to many MCP integrations +- Access to repositories under `/repos` +- Access to the Docker socket +- Access to internal network services +- Proxy-enabled outbound network access + +Important safeguards: + +- Keep MCP Hub on a trusted network. +- Do not expose port `3003` to untrusted clients. +- Protect `mcp_settings.json`, `.env`, and any credentials. +- Treat Docker socket access as equivalent to privileged host access. +- Keep repository mounts read-only where possible. +- Prefer separate writable job storage for scan outputs. +- Avoid logging secrets in test scripts or MCP server output. + +## Recommended Hardening + +Consider the following improvements: + +- Put MCP Hub behind authentication. +- Use a reverse proxy with TLS. +- Restrict Docker socket access using a Docker socket proxy. +- Make `/opt/redback/repos` read-only in MCP Hub if possible. +- Use a separate `/jobs` volume for security scan outputs. +- Split high-risk MCP tools into a separate hub instance. +- Limit exposed tools per integration instead of using `"tools": "all"` everywhere. +- Store secrets in a secret manager or Docker secrets where practical. +- Add healthchecks for MCP Hub and critical MCP servers. +- Pin container image versions instead of using `latest`. +- Keep a known-good backup of `mcp_settings.json`. + +## Useful Commands + +### Start + +```bash +docker compose up -d +``` + +### Stop + +```bash +docker compose down +``` + +### Restart + +```bash +docker compose restart mcphub +``` + +### Logs + +```bash +docker compose logs -f mcphub +``` + +### Rebuild + +```bash +docker compose build --no-cache mcphub +docker compose up -d mcphub +``` + +### Shell + +```bash +docker exec -it mcp-hub-mcphub-1 sh +``` + +### Check Enabled Servers in Settings + +```bash +docker exec -it mcp-hub-mcphub-1 sh -lc 'cat /app/mcp_settings.json' +``` + +### Check Proxy + +```bash +docker exec -it mcp-hub-mcphub-1 env | grep -i proxy +``` + +### Check Docker Access + +```bash +docker exec -it mcp-hub-mcphub-1 docker ps +``` + +### Check Repositories + +```bash +docker exec -it mcp-hub-mcphub-1 ls -la /repos +``` + +## Summary + +This MCP Hub configuration provides a proxy-aware MCP integration layer for a private AI stack. + +It includes: + +- MCP Hub exposed on host port `3003` +- Multiple MCP integrations enabled +- Playwright Chromium support +- Docker CLI support +- Docker socket access for repo scan jobs +- Repository access through `/repos` +- Custom proxychains entrypoint +- Corporate proxy support +- Internal network bypass rules +- A Node fetch test for validating internal HTTP connectivity + +This setup is suitable for an internal lab or trusted private AI environment where MCP tools need access to repositories, browsers, databases, security systems, and internal HTTP services. diff --git a/PrivateAI/mcphub/nodefetch.sh b/PrivateAI/mcphub/nodefetch.sh new file mode 100644 index 0000000..af2195b --- /dev/null +++ b/PrivateAI/mcphub/nodefetch.sh @@ -0,0 +1,12 @@ +docker exec -it mcp-hub-mcphub-1 sh -lc 'node -e " +(async () => { + try { + const r = await fetch(\"http://10.137.17.254:4045/health\", { + headers: { Authorization: \"98bb99ab7db3f86cdaba0276c9b913890cacfa92fa7514a01a52c3792d1753e3\"} + }); + console.log(\"status\", r.status); + console.log(await r.text()); + } catch (e) { + console.error(\"FETCH_ERR\", e); + } +})();"' diff --git a/PrivateAI/mcphub/proxychains.conf b/PrivateAI/mcphub/proxychains.conf new file mode 100644 index 0000000..f5f3649 --- /dev/null +++ b/PrivateAI/mcphub/proxychains.conf @@ -0,0 +1,17 @@ +strict_chain +proxy_dns +tcp_read_time_out 15000 +tcp_connect_time_out 8000 + +# IMPORTANT: do NOT proxy connections to the proxy itself (avoid recursion) +localnet 10.137.0.162/32 + +# Also keep local traffic unproxied +localnet 127.0.0.0/8 +localnet 10.0.0.0/8 +localnet 172.16.0.0/12 +localnet 192.168.0.0/16 + +[ProxyList] +http 10.137.0.162 3128 + diff --git a/PrivateAI/mcphub/readme.md b/PrivateAI/mcphub/readme.md new file mode 100644 index 0000000..413d078 --- /dev/null +++ b/PrivateAI/mcphub/readme.md @@ -0,0 +1,288 @@ +# MCPHub — Install & Configuration Guide + +This folder contains a small **Docker Compose** deployment for **MCPHub** plus supporting configuration files for running behind a corporate proxy (e.g. Deakin). +It is designed so you can unpack the tarball, adjust a few values, and run the service reliably. + +before starting copy env file to .env + +> **Security note:** The provided configs include placeholders and/or example secrets (API keys, bearer tokens, hashed admin password). +> Treat them as **defaults** and rotate/replace before exposing the service beyond a trusted network. + +--- + +## Contents + +After extracting `mcphub.tar`, you should have: + +```text +mcphub/ +├── docker-compose.yaml +├── entrypoint-proxy.sh +├── proxychains.conf +└── mcp_settings.json +``` + +--- + +## Quick install + +### 1) Extract + +```bash +tar -xf mcphub.tar +cd mcphub +``` + +### 2) Create an `.env` file + +Create `mcphub/.env` (recommended) with your proxy settings: + +```env +# Outbound proxy (optional but required in restricted networks) +HTTP_PROXY=http://proxy1.it.deakin.edu.au:3128 +HTTPS_PROXY=http://proxy1.it.deakin.edu.au:3128 + +# Internal destinations that must NOT be proxied +NO_PROXY=localhost,127.0.0.1,::1,mcphub,proxy1.it.deakin.edu.au,10.137.0.162,api.mcprouter.to +``` + +If you’re not behind a proxy, you can omit `HTTP_PROXY` / `HTTPS_PROXY` and rely on direct outbound access. + +### 3) Start MCPHub + +```bash +docker compose up -d +``` + +### 4) Verify + +The Compose file maps container port **3000** to host port **3003**: + +- MCPHub UI/API: `http://:3003` + +Check logs: + +```bash +docker compose logs -f --tail=200 +``` + +--- + +## Configuration files + +## `docker-compose.yaml` + +**Purpose:** Runs the `samahappy/mcphub` image with local configuration injected via bind mounts, plus proxy environment variables. + +Key sections: + +- **`image: samanhappy/mcphub`** + Uses a prebuilt MCPHub container image. + +- **`ports: "3003:3000"`** + Exposes MCPHub on **host port 3003**. + +- **`volumes:`** + Mounts your configuration into the container: + - `./mcp_settings.json` → `/app/mcp_settings.json` *(read-only)* + Main MCPHub configuration (servers, users, routing, providers). + - `./entrypoint-proxy.sh` → `/app/entrypoint-proxy.sh` *(read-only)* + Wrapper entrypoint to ensure proxy support works inside container. + - `./proxychains.conf` → `/etc/proxychains.conf` *(read-only)* + Proxychains config (forces outbound TCP via your proxy). + +- **`environment:`** + - `HTTP_PROXY`, `HTTPS_PROXY` are passed through from `.env` + - `NO_PROXY` ensures internal calls (including Docker DNS names) are not proxied + +- **`extra_hosts:`** + - `"proxy1.it.deakin.edu.au:10.137.0.162"` + Forces resolution of the proxy hostname inside the container (useful if DNS can’t resolve it). + +- **`entrypoint:` / `command:`** + - Entry is overridden to run `/app/entrypoint-proxy.sh` + - Then runs MCPHub via `pnpm start` + +--- + +## `entrypoint-proxy.sh` + +**Purpose:** Makes proxy behaviour reliable inside the container by: + +1. Normalising proxy env vars (`HTTP_PROXY` → `http_proxy`, etc.) +2. Configuring `apt` to use the proxy (if `apt-get` exists) +3. Installing `proxychains4` (or `proxychains-ng`) if missing +4. Ensuring a proxychains config exists (uses `/etc/proxychains.conf` or generates one from proxy env vars) +5. Running the container’s original entrypoint under proxychains: + +```sh +exec proxychains4 -q /usr/local/bin/entrypoint.sh "$@" +``` + +**Why this matters:** Some tools ignore `HTTP_PROXY`/`HTTPS_PROXY` for certain network calls. +Proxychains forces TCP connections through the proxy when needed. + +--- + +## `proxychains.conf` + +**Purpose:** Defines how proxychains routes traffic. + +Notable directives: + +- `strict_chain` + Use the proxies in the listed order. +- `proxy_dns` + Resolve DNS through proxychains (helps in locked-down DNS scenarios). +- Timeouts: + - `tcp_read_time_out 15000` + - `tcp_connect_time_out 8000` + +Local network bypasses (important): + +- `localnet 10.137.0.162/32` + Prevents proxying traffic *to the proxy itself* (avoids recursion). +- Also bypasses: + - `127.0.0.0/8` + - `10.0.0.0/8` + - `172.16.0.0/12` + - `192.168.0.0/16` + +Proxy list: + +- `http 10.137.0.162 3128` + +If your proxy changes, update this file (or rely on auto-generation from env vars by removing the mounted file). + +--- + +## `mcp_settings.json` + +**Purpose:** The main MCPHub configuration file. + +It contains several top-level sections: + +### `mcpServers` +Defines MCP servers MCPHub can launch and route to. This file includes examples such as: + +- `playwright` / `playwright-mcp` (Playwright MCP server) +- `fetch` / `fetch-mcp` (fetch MCP server) +- `time` / `time-mcp` (time MCP server) +- `slack` (Slack MCP server; requires tokens) +- `sequential-thinking` (reasoning helper server) +- `mindmap` (mindmap server) +- `amap` (Amap maps server; requires API key) + +Each server entry generally looks like: + +```json +{ + "command": "npx", + "args": ["-y", "@some/package"], + "env": { "SOME_KEY": "your-value" } +} +``` + +**What to edit:** +- Replace placeholder API keys (e.g. `SLACK_BOT_TOKEN`, `AMAP_MAPS_API_KEY`) +- Remove servers you don’t want MCPHub to expose +- Pin versions if you need reproducible deployments + +### `users` +Defines MCPHub users. The sample includes an `admin` user with a **bcrypt hashed password**. + +**What to edit:** +- Replace the default password hash with your own +- Consider disabling password auth if you are using bearer/OAuth only (depends on your deployment model) + +### `systemConfig` +Controls platform-wide behaviour. Notable subsections include: + +- **`routing`** + - `enableGlobalRoute`: global routing on/off + - `enableGroupNameRoute`: group-based routing on/off + - `enableBearerAuth`: bearer auth on/off + - `bearerAuthKey`: bearer token key (rotate before public exposure) + +- **`install`** + - `baseUrl`: base URL used by MCPHub (ensure it matches your deployment) + - `pythonIndexUrl` / `npmRegistry`: optional private registries + +- **`oauthServer`** + Enables an embedded OAuth server and controls lifetimes and registration behaviour. + If you don’t need OAuth, set `enabled` to `false`. + +- **`mcpRouter`** + Contains settings for the upstream routing API (including API key, base URL, referer/title). + Treat API keys here as secrets. + +### `providers` +Defines LLM providers MCPHub can talk to. The sample includes a LocalAI provider entry (OpenAI-compatible): +- `base_url`: your LocalAI endpoint +- `models`: list of model IDs you want available through this provider + +Update this to match your LocalAI host and model list. + +### `groups` +Defines groups and which servers/tools are available to each group. + +### `bearerKeys` +Defines bearer tokens and access scoping: +- `accessType: all` allows everything (tighten if needed) +- `allowedGroups` / `allowedServers` can restrict access + +--- + +## Recommended hardening (if exposing beyond LAN) + +- Put MCPHub behind a reverse proxy (Nginx/Caddy/Traefik) with TLS +- Rotate/replace: + - bearer auth token(s) + - any `mcpRouter.apiKey` + - admin password hash + - any third-party API tokens (Slack, Amap, etc.) +- Restrict allowed servers/tools to the minimum needed +- Consider network policies/firewall rules to limit who can reach port 3003 + +--- + +## Common changes + +### Change the host port +Edit `docker-compose.yaml`: + +```yaml +ports: + - "3003:3000" +``` + +For example, to run on 8088: + +```yaml +ports: + - "8088:3000" +``` + +### Change the proxy target +Update `proxychains.conf` and/or `.env`, plus `extra_hosts` if required. + +--- + +## Troubleshooting + +### Proxy loops / “connection refused” to proxy +Make sure `proxychains.conf` includes a `localnet` rule for the proxy IP itself (it already does for `10.137.0.162/32`). + +### NPM/Python installs fail +- Confirm `HTTP_PROXY`/`HTTPS_PROXY` are correct +- If you use private registries, set `systemConfig.install.npmRegistry` / `pythonIndexUrl` + +### MCP server packages change unexpectedly +Pin versions in `mcpServers` (avoid `@latest` where reproducibility matters). + +--- + +## License / Attribution + +This repository contains **deployment configuration and documentation**. +MCPHub and MCP servers remain under their respective upstream licenses. diff --git a/PrivateAI/mcphub/repo_scan_job_mcp.js b/PrivateAI/mcphub/repo_scan_job_mcp.js new file mode 100644 index 0000000..675fef3 --- /dev/null +++ b/PrivateAI/mcphub/repo_scan_job_mcp.js @@ -0,0 +1,669 @@ +#!/usr/bin/env node + +// +// This code generated by OpenAI and Claude with the instruction and guidance of ejb (Richard Edwards) +// It should be uses as proof of concept and may require additional hardening/validation/testing for large scale production use +// + +import { spawn } from "node:child_process"; +import { promises as fs } from "node:fs"; +import path from "node:path"; +import process from "node:process"; +import crypto from "node:crypto"; + +import { Server } from "/app/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js"; +import { StdioServerTransport } from "/app/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema +} from "/app/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js"; + +const REPOS_BASE_DIR = process.env.REPOS_BASE_DIR || "/repos"; +const HOST_REPOS_BASE_DIR = process.env.HOST_REPOS_BASE_DIR || "/opt/redback/repos"; +const JOBS_BASE_DIR = process.env.JOBS_BASE_DIR || path.join(REPOS_BASE_DIR, "logs", "security-jobs"); +const SEMGREP_IMAGE = process.env.SEMGREP_IMAGE || "semgrep/semgrep:1.159.0"; +const SEMGREP_JOBS = String(process.env.SEMGREP_JOBS || "2"); +const SEMGREP_PER_FILE_TIMEOUT = String(process.env.SEMGREP_PER_FILE_TIMEOUT || "2"); +const SEMGREP_TIMEOUT_THRESHOLD = String(process.env.SEMGREP_TIMEOUT_THRESHOLD || "1"); +const SEMGREP_MAX_TARGET_BYTES = String(process.env.SEMGREP_MAX_TARGET_BYTES || "500000"); +const JOB_RETENTION_LIMIT = Number(process.env.JOB_RETENTION_LIMIT || "100"); + +function normalizeRepoInput(repo) { + if (!repo || typeof repo !== "string") { + throw new Error("Missing required field: repo"); + } + + let value = repo.trim(); + + if (value === "/repos") { + throw new Error("repo must identify a repository, not /repos itself"); + } + + if (value.startsWith("/repos/")) { + value = value.slice("/repos/".length); + } + + value = value.replace(/^\/+/, "").replace(/\/+$/, ""); + + if (!value) { + throw new Error("repo must not be empty"); + } + + if (value.includes("\\") || value.includes("..") || value.includes("/")) { + throw new Error( + "repo must be a repository directory name under /repos, for example 'redback-smartbike-iot'" + ); + } + + return value; +} + +function normalizeScope(scope) { + if (scope === undefined || scope === null || scope === "") { + return "."; + } + if (typeof scope !== "string") { + throw new Error("scope must be a string"); + } + return scope.trim() || "."; +} + +function profileToConfigs(profile) { + const p = String(profile || "security").toLowerCase(); + + if (p === "security") return ["--config=p/security-audit"]; + if (p === "secrets") return ["--config=p/secrets"]; + if (p === "full") { + return [ + "--config=p/security-audit", + "--config=p/secrets", + "--config=p/owasp-top-ten" + ]; + } + + throw new Error("Unsupported profile. Use one of: security, secrets, full"); +} + +function safeResolve(repo, scope = ".") { + const safeRepo = normalizeRepoInput(repo); + const safeScope = normalizeScope(scope); + + const repoPath = path.resolve(REPOS_BASE_DIR, safeRepo); + const targetPath = path.resolve(repoPath, safeScope); + + if (!targetPath.startsWith(repoPath)) { + throw new Error("Scope escapes repository root"); + } + + const relativeTarget = path.relative(repoPath, targetPath); + const containerTargetPath = + relativeTarget && relativeTarget !== "." + ? path.posix.join("/repos", safeRepo, relativeTarget.split(path.sep).join("/")) + : path.posix.join("/repos", safeRepo); + + return { + safeRepo, + safeScope, + repoPath, + targetPath, + containerTargetPath + }; +} + +async function pathExists(p) { + try { + await fs.access(p); + return true; + } catch { + return false; + } +} + +async function ensureDir(p) { + await fs.mkdir(p, { recursive: true }); +} + +async function readJson(filePath, fallback = null) { + try { + const text = await fs.readFile(filePath, "utf8"); + return JSON.parse(text); + } catch { + return fallback; + } +} + +async function writeJson(filePath, data) { + await fs.writeFile(filePath, JSON.stringify(data, null, 2) + "\n", "utf8"); +} + +function summarizeFindings(findings) { + const summary = { + findings_total: findings.length, + critical: 0, + high: 0, + medium: 0, + low: 0, + info: 0, + unknown: 0 + }; + + for (const finding of findings) { + const severity = String(finding.severity || "").toUpperCase(); + if (severity === "CRITICAL") summary.critical += 1; + else if (severity === "HIGH" || severity === "ERROR") summary.high += 1; + else if (severity === "MEDIUM" || severity === "WARNING") summary.medium += 1; + else if (severity === "LOW") summary.low += 1; + else if (severity === "INFO" || severity === "NOTICE") summary.info += 1; + else summary.unknown += 1; + } + + return summary; +} + +function makeJobId(repo) { + const ts = new Date().toISOString().replace(/[:.]/g, "-"); + const rand = crypto.randomBytes(4).toString("hex"); + return `scan-${ts}-${repo}-${rand}`; +} + +function getJobPaths(jobId) { + const dir = path.join(JOBS_BASE_DIR, jobId); + return { + dir, + meta: path.join(dir, "job.json"), + stdout: path.join(dir, "stdout.json"), + stderr: path.join(dir, "stderr.log") + }; +} + +async function listRepos() { + const entries = await fs.readdir(REPOS_BASE_DIR, { withFileTypes: true }); + const repos = []; + + for (const entry of entries) { + if (!entry.isDirectory()) continue; + if (entry.name.startsWith(".")) continue; + if (entry.name === "logs") continue; + + repos.push({ + repo: entry.name, + path: path.join(REPOS_BASE_DIR, entry.name) + }); + } + + repos.sort((a, b) => a.repo.localeCompare(b.repo)); + + return { + success: true, + repos_base_dir: REPOS_BASE_DIR, + repos + }; +} + +async function cleanupOldJobs() { + await ensureDir(JOBS_BASE_DIR); + const entries = await fs.readdir(JOBS_BASE_DIR, { withFileTypes: true }); + const dirs = []; + + for (const entry of entries) { + if (!entry.isDirectory()) continue; + const full = path.join(JOBS_BASE_DIR, entry.name); + const stat = await fs.stat(full); + dirs.push({ name: entry.name, full, mtimeMs: stat.mtimeMs }); + } + + dirs.sort((a, b) => b.mtimeMs - a.mtimeMs); + + for (const old of dirs.slice(JOB_RETENTION_LIMIT)) { + await fs.rm(old.full, { recursive: true, force: true }); + } +} + +async function startScanJob({ repo, scope = ".", profile = "security" }) { + const { + safeRepo, + safeScope, + repoPath, + targetPath, + containerTargetPath + } = safeResolve(repo, scope); + + if (!(await pathExists(repoPath))) { + throw new Error( + `Repository not found in container view: ${repoPath}. Call repo_list to see available repositories.` + ); + } + + if (!(await pathExists(targetPath))) { + throw new Error(`Target path does not exist in container view: ${targetPath}`); + } + + await ensureDir(JOBS_BASE_DIR); + await cleanupOldJobs(); + + const jobId = makeJobId(safeRepo); + const paths = getJobPaths(jobId); + await ensureDir(paths.dir); + + const configs = profileToConfigs(profile); + + const initialMeta = { + success: true, + job_id: jobId, + status: "queued", + repo: safeRepo, + scope: safeScope, + profile, + created_at: new Date().toISOString(), + started_at: null, + finished_at: null, + repo_path: repoPath, + target_path: targetPath, + container_target_path: containerTargetPath, + jobs_base_dir: JOBS_BASE_DIR, + host_repos_base_dir: HOST_REPOS_BASE_DIR, + image: SEMGREP_IMAGE, + pid: null, + exit_code: null, + summary: null, + error: null + }; + + await writeJson(paths.meta, initialMeta); + await fs.writeFile(paths.stderr, "", "utf8"); + + const dockerArgs = [ + "run", + "--rm", + "-v", + `${HOST_REPOS_BASE_DIR}:/repos:ro`, + "-w", + "/repos", + "-e", + "SEMGREP_ENABLE_VERSION_CHECK=0", + "-e", + "SEMGREP_SEND_METRICS=off", + "-e", + `HTTP_PROXY=${process.env.HTTP_PROXY || ""}`, + "-e", + `HTTPS_PROXY=${process.env.HTTPS_PROXY || ""}`, + "-e", + `NO_PROXY=${process.env.NO_PROXY || ""}`, + SEMGREP_IMAGE, + "semgrep", + "scan", + "--json", + "--disable-version-check", + "--metrics=off", + "--optimizations=all", + "--jobs", + SEMGREP_JOBS, + "--timeout", + SEMGREP_PER_FILE_TIMEOUT, + "--timeout-threshold", + SEMGREP_TIMEOUT_THRESHOLD, + "--max-target-bytes", + SEMGREP_MAX_TARGET_BYTES, + "--exclude=node_modules", + "--exclude=.git", + "--exclude=dist", + "--exclude=build", + "--exclude=.venv", + "--exclude=vendor", + "--exclude=coverage", + "--exclude=.next", + "--exclude=.cache", + "--exclude=target", + ...configs, + containerTargetPath + ]; + + const child = spawn("docker", dockerArgs, { + detached: true, + stdio: ["ignore", "pipe", "pipe"], + env: { ...process.env } + }); + + const stdoutChunks = []; + const stderrChunks = []; + + child.stdout.on("data", (chunk) => { + stdoutChunks.push(chunk); + }); + + child.stderr.on("data", (chunk) => { + stderrChunks.push(chunk); + }); + + const runningMeta = { + ...initialMeta, + status: "running", + started_at: new Date().toISOString(), + pid: child.pid + }; + + await writeJson(paths.meta, runningMeta); + + child.on("close", async (code) => { + const stdout = Buffer.concat(stdoutChunks).toString("utf8"); + const stderr = Buffer.concat(stderrChunks).toString("utf8"); + + await fs.writeFile(paths.stderr, stderr, "utf8"); + + let finalMeta = await readJson(paths.meta, runningMeta); + finalMeta.exit_code = code; + finalMeta.finished_at = new Date().toISOString(); + + if (![0, 1].includes(code)) { + finalMeta.status = "failed"; + finalMeta.error = `Semgrep failed with exit code ${code}`; + await writeJson(paths.meta, finalMeta); + return; + } + + try { + const parsed = stdout.trim() ? JSON.parse(stdout) : {}; + await writeJson(paths.stdout, parsed); + + const findings = (parsed.results || []).map((item) => ({ + rule_id: item.check_id || null, + severity: item.extra?.severity || null, + message: item.extra?.message || null, + path: item.path || null, + start_line: item.start?.line || null, + end_line: item.end?.line || null + })); + + finalMeta.status = "completed"; + finalMeta.summary = { + ...summarizeFindings(findings), + errors_total: (parsed.errors || []).length + }; + finalMeta.error = null; + await writeJson(paths.meta, finalMeta); + } catch (err) { + finalMeta.status = "failed"; + finalMeta.error = `Failed to parse Semgrep JSON output: ${err.message}`; + await writeJson(paths.meta, finalMeta); + } + }); + + child.unref(); + + return { + success: true, + job_id: jobId, + status: "queued", + repo: safeRepo, + scope: safeScope, + profile, + created_at: initialMeta.created_at + }; +} + +async function getJobStatus({ job_id }) { + if (!job_id || typeof job_id !== "string") { + throw new Error("Missing required field: job_id"); + } + + const paths = getJobPaths(job_id); + const meta = await readJson(paths.meta); + + if (!meta) { + throw new Error(`Job not found: ${job_id}`); + } + + return { + success: true, + job_id, + status: meta.status, + created_at: meta.created_at, + started_at: meta.started_at, + finished_at: meta.finished_at, + repo: meta.repo, + scope: meta.scope, + profile: meta.profile, + summary: meta.summary, + error: meta.error, + exit_code: meta.exit_code, + pid: meta.pid + }; +} + +async function getJobResult({ job_id }) { + if (!job_id || typeof job_id !== "string") { + throw new Error("Missing required field: job_id"); + } + + const paths = getJobPaths(job_id); + const meta = await readJson(paths.meta); + + if (!meta) { + throw new Error(`Job not found: ${job_id}`); + } + + if (meta.status !== "completed") { + return { + success: false, + job_id, + status: meta.status, + error: meta.error || "Job is not completed yet" + }; + } + + const parsed = await readJson(paths.stdout, {}); + const findings = (parsed.results || []).map((item) => ({ + rule_id: item.check_id || null, + severity: item.extra?.severity || null, + message: item.extra?.message || null, + path: item.path || null, + start_line: item.start?.line || null, + end_line: item.end?.line || null + })); + + const stderr = await fs.readFile(paths.stderr, "utf8").catch(() => ""); + + return { + success: true, + job_id, + status: meta.status, + tool: "semgrep", + image: meta.image, + repo: meta.repo, + scope: meta.scope, + profile: meta.profile, + created_at: meta.created_at, + started_at: meta.started_at, + finished_at: meta.finished_at, + repo_path: meta.repo_path, + target_path: meta.target_path, + container_target_path: meta.container_target_path, + summary: meta.summary, + findings, + errors: parsed.errors || [], + stderr + }; +} + +async function listJobs() { + await ensureDir(JOBS_BASE_DIR); + const entries = await fs.readdir(JOBS_BASE_DIR, { withFileTypes: true }); + const jobs = []; + + for (const entry of entries) { + if (!entry.isDirectory()) continue; + const meta = await readJson(path.join(JOBS_BASE_DIR, entry.name, "job.json")); + if (!meta) continue; + + jobs.push({ + job_id: entry.name, + status: meta.status, + repo: meta.repo, + scope: meta.scope, + profile: meta.profile, + created_at: meta.created_at, + started_at: meta.started_at, + finished_at: meta.finished_at, + summary: meta.summary, + error: meta.error + }); + } + + jobs.sort((a, b) => String(b.created_at).localeCompare(String(a.created_at))); + + return { + success: true, + jobs + }; +} + +const server = new Server( + { + name: "repo-scan-job-mcp", + version: "1.0.0" + }, + { + capabilities: { + tools: {} + } + } +); + +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "repo_list", + description: "List available repository folders under /repos.", + inputSchema: { + type: "object", + properties: {} + } + }, + { + name: "repo_security_scan_start", + description: + "Start a background Semgrep scan job for a repository under /repos. Returns a job_id immediately.", + inputSchema: { + type: "object", + properties: { + repo: { + type: "string", + description: + "Repository name under /repos, for example 'redback-smartbike-iot'. '/repos/redback-smartbike-iot' is also accepted." + }, + scope: { + type: "string", + description: + "Optional file or subdirectory inside the repository. Use '.' for the whole repository.", + default: "." + }, + profile: { + type: "string", + description: "Scan profile", + enum: ["security", "secrets", "full"], + default: "security" + } + }, + required: ["repo"] + } + }, + { + name: "repo_security_scan_status", + description: "Check the status of a previously started scan job.", + inputSchema: { + type: "object", + properties: { + job_id: { + type: "string", + description: "Job ID returned by repo_security_scan_start" + } + }, + required: ["job_id"] + } + }, + { + name: "repo_security_scan_result", + description: "Fetch the result of a completed scan job.", + inputSchema: { + type: "object", + properties: { + job_id: { + type: "string", + description: "Job ID returned by repo_security_scan_start" + } + }, + required: ["job_id"] + } + }, + { + name: "repo_security_scan_list_jobs", + description: "List recent scan jobs and their statuses.", + inputSchema: { + type: "object", + properties: {} + } + } + ] + }; +}); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + try { + let result; + + if (name === "repo_list") { + result = await listRepos(); + } else if (name === "repo_security_scan_start") { + result = await startScanJob(args || {}); + } else if (name === "repo_security_scan_status") { + result = await getJobStatus(args || {}); + } else if (name === "repo_security_scan_result") { + result = await getJobResult(args || {}); + } else if (name === "repo_security_scan_list_jobs") { + result = await listJobs(); + } else { + return { + content: [ + { + type: "text", + text: JSON.stringify( + { success: false, error: `Unknown tool: ${name}` }, + null, + 2 + ) + } + ], + isError: true + }; + } + + return { + content: [ + { + type: "text", + text: JSON.stringify(result, null, 2) + } + ] + }; + } catch (err) { + return { + content: [ + { + type: "text", + text: JSON.stringify( + { success: false, error: err.message }, + null, + 2 + ) + } + ], + isError: true + }; + } +}); + +const transport = new StdioServerTransport(); +await server.connect(transport); diff --git a/PrivateAI/mcphub/repo_scan_job_mcp.md b/PrivateAI/mcphub/repo_scan_job_mcp.md new file mode 100644 index 0000000..4ed3882 --- /dev/null +++ b/PrivateAI/mcphub/repo_scan_job_mcp.md @@ -0,0 +1,1052 @@ +# Repo Scan Job MCP + +This service is a proof-of-concept Model Context Protocol (MCP) server for launching and managing Semgrep repository scan jobs. + +It exposes MCP tools over stdio and lets an MCP client: + +- List repositories mounted under `/repos` +- Start a Semgrep scan job against a repository +- Check scan job status +- Retrieve completed scan results +- List recent scan jobs + +The implementation is intended for use inside an MCP hub or similar orchestration container where repositories are mounted read-only and Semgrep is executed in a separate Docker container. + +## Important Notice + +This code was generated by OpenAI and Claude with instruction and guidance from ejb / Richard Edwards. + +It should be treated as a proof of concept. + +Additional hardening, validation, testing, access control, and production readiness work may be required before using it at large scale or in security-sensitive environments. + +## What This MCP Server Does + +The server provides an MCP interface around Semgrep scanning. + +Rather than running a long Semgrep scan directly inside the MCP tool call, it starts a background job and immediately returns a `job_id`. + +The user or client can then poll the job status and fetch the result once the scan completes. + +This avoids blocking the MCP client while Semgrep scans larger repositories. + +## Runtime Model + +The MCP server itself runs as a Node.js process. + +When a scan starts, it launches Semgrep using Docker: + +```bash +docker run --rm \ + -v "${HOST_REPOS_BASE_DIR}:/repos:ro" \ + -w /repos \ + semgrep/semgrep:1.159.0 \ + semgrep scan ... +``` + +This means the MCP server container needs access to the Docker CLI and Docker socket if it is running inside Docker. + +## Main Paths + +### Repository Base Directory Inside the MCP Container + +```text +/repos +``` + +Controlled by: + +```env +REPOS_BASE_DIR=/repos +``` + +This is where the MCP server looks for available repositories. + +### Repository Base Directory on the Docker Host + +```text +/opt/redback/repos +``` + +Controlled by: + +```env +HOST_REPOS_BASE_DIR=/opt/redback/repos +``` + +This path is passed into the Semgrep scan container using Docker volume mounting. + +The MCP server sees repositories through `REPOS_BASE_DIR`, while the spawned Semgrep container sees the host path through `HOST_REPOS_BASE_DIR`. + +These must point to the same repository tree from their respective viewpoints. + +### Job Storage Directory + +Default: + +```text +/repos/logs/security-jobs +``` + +Controlled by: + +```env +JOBS_BASE_DIR=/repos/logs/security-jobs +``` + +Each scan job gets its own directory containing: + +```text +job.json +stdout.json +stderr.log +``` + +## Environment Variables + +### `REPOS_BASE_DIR` + +Default: + +```env +REPOS_BASE_DIR=/repos +``` + +The directory inside the MCP server container where repositories are visible. + +### `HOST_REPOS_BASE_DIR` + +Default: + +```env +HOST_REPOS_BASE_DIR=/opt/redback/repos +``` + +The host-side directory passed to the spawned Semgrep Docker container. + +### `JOBS_BASE_DIR` + +Default: + +```env +JOBS_BASE_DIR=/repos/logs/security-jobs +``` + +Where job metadata and results are stored. + +### `SEMGREP_IMAGE` + +Default: + +```env +SEMGREP_IMAGE=semgrep/semgrep:1.159.0 +``` + +The Semgrep container image used when running scan jobs. + +### `SEMGREP_JOBS` + +Default: + +```env +SEMGREP_JOBS=2 +``` + +Controls Semgrep parallelism. + +Passed to Semgrep as: + +```bash +--jobs 2 +``` + +### `SEMGREP_PER_FILE_TIMEOUT` + +Default: + +```env +SEMGREP_PER_FILE_TIMEOUT=2 +``` + +Controls the Semgrep per-file timeout. + +Passed to Semgrep as: + +```bash +--timeout 2 +``` + +### `SEMGREP_TIMEOUT_THRESHOLD` + +Default: + +```env +SEMGREP_TIMEOUT_THRESHOLD=1 +``` + +Controls Semgrep timeout threshold behaviour. + +Passed to Semgrep as: + +```bash +--timeout-threshold 1 +``` + +### `SEMGREP_MAX_TARGET_BYTES` + +Default: + +```env +SEMGREP_MAX_TARGET_BYTES=500000 +``` + +Controls the maximum file size scanned by Semgrep. + +Passed to Semgrep as: + +```bash +--max-target-bytes 500000 +``` + +### `JOB_RETENTION_LIMIT` + +Default: + +```env +JOB_RETENTION_LIMIT=100 +``` + +Limits the number of retained job directories. + +Older job directories beyond this limit are removed when a new job starts. + +### Proxy Variables + +The spawned Semgrep container receives: + +```env +HTTP_PROXY +HTTPS_PROXY +NO_PROXY +``` + +This allows Semgrep to operate in proxy-controlled environments. + +## MCP Server Identity + +The MCP server registers as: + +```json +{ + "name": "repo-scan-job-mcp", + "version": "1.0.0" +} +``` + +It exposes tool capabilities only. + +## MCP Tools + +## `repo_list` + +Lists available repository folders under `/repos`. + +### Input + +```json +{} +``` + +### Output + +```json +{ + "success": true, + "repos_base_dir": "/repos", + "repos": [ + { + "repo": "example-repo", + "path": "/repos/example-repo" + } + ] +} +``` + +### Notes + +The repository list excludes: + +- Hidden directories +- The `logs` directory + +This prevents job logs from appearing as repositories. + +## `repo_security_scan_start` + +Starts a background Semgrep scan job for a repository under `/repos`. + +The tool returns immediately with a `job_id`. + +### Input + +```json +{ + "repo": "example-repo", + "scope": ".", + "profile": "security" +} +``` + +### Required Fields + +```json +{ + "repo": "example-repo" +} +``` + +### Optional Fields + +```json +{ + "scope": ".", + "profile": "security" +} +``` + +### Supported Profiles + +```text +security +secrets +full +``` + +### Profile Behaviour + +#### `security` + +Uses: + +```bash +--config=p/security-audit +``` + +#### `secrets` + +Uses: + +```bash +--config=p/secrets +``` + +#### `full` + +Uses: + +```bash +--config=p/security-audit +--config=p/secrets +--config=p/owasp-top-ten +``` + +### Output + +```json +{ + "success": true, + "job_id": "scan-2026-05-18T06-00-00-000Z-example-repo-a1b2c3d4", + "status": "queued", + "repo": "example-repo", + "scope": ".", + "profile": "security", + "created_at": "2026-05-18T06:00:00.000Z" +} +``` + +## `repo_security_scan_status` + +Checks the status of a previously started scan job. + +### Input + +```json +{ + "job_id": "scan-2026-05-18T06-00-00-000Z-example-repo-a1b2c3d4" +} +``` + +### Output + +```json +{ + "success": true, + "job_id": "scan-2026-05-18T06-00-00-000Z-example-repo-a1b2c3d4", + "status": "completed", + "created_at": "2026-05-18T06:00:00.000Z", + "started_at": "2026-05-18T06:00:01.000Z", + "finished_at": "2026-05-18T06:00:15.000Z", + "repo": "example-repo", + "scope": ".", + "profile": "security", + "summary": { + "findings_total": 3, + "critical": 0, + "high": 1, + "medium": 2, + "low": 0, + "info": 0, + "unknown": 0, + "errors_total": 0 + }, + "error": null, + "exit_code": 1, + "pid": 12345 +} +``` + +## `repo_security_scan_result` + +Fetches the result of a completed scan job. + +### Input + +```json +{ + "job_id": "scan-2026-05-18T06-00-00-000Z-example-repo-a1b2c3d4" +} +``` + +### Output + +```json +{ + "success": true, + "job_id": "scan-2026-05-18T06-00-00-000Z-example-repo-a1b2c3d4", + "status": "completed", + "tool": "semgrep", + "image": "semgrep/semgrep:1.159.0", + "repo": "example-repo", + "scope": ".", + "profile": "security", + "summary": { + "findings_total": 3, + "critical": 0, + "high": 1, + "medium": 2, + "low": 0, + "info": 0, + "unknown": 0, + "errors_total": 0 + }, + "findings": [ + { + "rule_id": "rule.id", + "severity": "HIGH", + "message": "Finding message", + "path": "example.js", + "start_line": 10, + "end_line": 12 + } + ], + "errors": [], + "stderr": "" +} +``` + +### Behaviour When Job Is Not Complete + +If the job is still queued or running, the result tool returns: + +```json +{ + "success": false, + "job_id": "scan-...", + "status": "running", + "error": "Job is not completed yet" +} +``` + +## `repo_security_scan_list_jobs` + +Lists recent scan jobs and their statuses. + +### Input + +```json +{} +``` + +### Output + +```json +{ + "success": true, + "jobs": [ + { + "job_id": "scan-...", + "status": "completed", + "repo": "example-repo", + "scope": ".", + "profile": "security", + "created_at": "2026-05-18T06:00:00.000Z", + "started_at": "2026-05-18T06:00:01.000Z", + "finished_at": "2026-05-18T06:00:15.000Z", + "summary": { + "findings_total": 3, + "critical": 0, + "high": 1, + "medium": 2, + "low": 0, + "info": 0, + "unknown": 0, + "errors_total": 0 + }, + "error": null + } + ] +} +``` + +## Repository Input Validation + +The `repo` parameter is deliberately restricted. + +Accepted examples: + +```text +redback-smartbike-iot +/repos/redback-smartbike-iot +``` + +Rejected examples: + +```text +/repos +../somewhere +repo/subdir +repo\subdir +``` + +The `repo` value must identify a direct repository directory under `/repos`. + +This prevents a client from escaping the repository base directory. + +## Scope Handling + +The `scope` parameter can be used to scan a subdirectory or file inside the repository. + +Examples: + +```json +{ + "repo": "example-repo", + "scope": "." +} +``` + +```json +{ + "repo": "example-repo", + "scope": "src" +} +``` + +```json +{ + "repo": "example-repo", + "scope": "src/server.js" +} +``` + +The server resolves the requested scope and checks that it remains inside the selected repository. + +If the scope escapes the repository root, the request is rejected. + +## Semgrep Scan Command + +The spawned Semgrep command uses: + +```bash +semgrep scan \ + --json \ + --disable-version-check \ + --metrics=off \ + --optimizations=all \ + --jobs "${SEMGREP_JOBS}" \ + --timeout "${SEMGREP_PER_FILE_TIMEOUT}" \ + --timeout-threshold "${SEMGREP_TIMEOUT_THRESHOLD}" \ + --max-target-bytes "${SEMGREP_MAX_TARGET_BYTES}" \ + --exclude=node_modules \ + --exclude=.git \ + --exclude=dist \ + --exclude=build \ + --exclude=.venv \ + --exclude=vendor \ + --exclude=coverage \ + --exclude=.next \ + --exclude=.cache \ + --exclude=target \ + --config=p/security-audit \ + /repos/example-repo +``` + +Additional configs are added depending on the selected profile. + +## Excluded Paths + +The scan excludes common generated or dependency directories: + +```text +node_modules +.git +dist +build +.venv +vendor +coverage +.next +.cache +target +``` + +This reduces noise and improves scan performance. + +## Job Lifecycle + +A job moves through these states: + +```text +queued +running +completed +failed +``` + +### `queued` + +The job metadata has been created. + +### `running` + +The Semgrep Docker process has been spawned. + +### `completed` + +Semgrep exited successfully with code `0` or with findings using code `1`. + +### `failed` + +The Semgrep process failed with an unexpected exit code, or the JSON output could not be parsed. + +## Semgrep Exit Codes + +The wrapper treats exit codes `0` and `1` as non-fatal: + +```javascript +if (![0, 1].includes(code)) { + finalMeta.status = "failed"; +} +``` + +This is important because Semgrep may return exit code `1` when findings are present. + +## Finding Summary + +The wrapper normalises Semgrep results into a compact finding format: + +```json +{ + "rule_id": "check_id", + "severity": "HIGH", + "message": "message", + "path": "file.js", + "start_line": 10, + "end_line": 12 +} +``` + +It also generates a severity summary: + +```json +{ + "findings_total": 3, + "critical": 0, + "high": 1, + "medium": 2, + "low": 0, + "info": 0, + "unknown": 0, + "errors_total": 0 +} +``` + +Severity mapping: + +```text +CRITICAL -> critical +HIGH or ERROR -> high +MEDIUM or WARNING -> medium +LOW -> low +INFO or NOTICE -> info +Anything else -> unknown +``` + +## Suggested File Name + +```text +repo_scan_job_mcp.js +``` + +## Example Docker Compose Service + +This MCP server needs the Node.js runtime, the MCP SDK, access to repositories, and access to the Docker socket. + +Example service: + +```yaml +services: + repo-scan-job-mcp: + image: node:22 + container_name: repo-scan-job-mcp + working_dir: /app + command: ["node", "/app/repo_scan_job_mcp.js"] + environment: + - REPOS_BASE_DIR=/repos + - HOST_REPOS_BASE_DIR=/opt/redback/repos + - JOBS_BASE_DIR=/repos/logs/security-jobs + - SEMGREP_IMAGE=semgrep/semgrep:1.159.0 + - SEMGREP_JOBS=2 + - SEMGREP_PER_FILE_TIMEOUT=2 + - SEMGREP_TIMEOUT_THRESHOLD=1 + - SEMGREP_MAX_TARGET_BYTES=500000 + - JOB_RETENTION_LIMIT=100 + - HTTP_PROXY=${HTTP_PROXY} + - HTTPS_PROXY=${HTTPS_PROXY} + - NO_PROXY=${NO_PROXY} + volumes: + - ./repo_scan_job_mcp.js:/app/repo_scan_job_mcp.js:ro + - /opt/redback/repos:/repos + - /var/run/docker.sock:/var/run/docker.sock + restart: unless-stopped +``` + +Important: the `/repos` mount is not read-only in this example because the job log directory defaults to `/repos/logs/security-jobs`. + +If you want repositories to remain read-only, use a separate writable job directory: + +```yaml +environment: + - JOBS_BASE_DIR=/jobs + +volumes: + - /opt/redback/repos:/repos:ro + - /opt/redback/security-jobs:/jobs +``` + +## Recommended Safer Compose Pattern + +```yaml +services: + repo-scan-job-mcp: + image: node:22 + container_name: repo-scan-job-mcp + working_dir: /app + command: ["node", "/app/repo_scan_job_mcp.js"] + environment: + - REPOS_BASE_DIR=/repos + - HOST_REPOS_BASE_DIR=/opt/redback/repos + - JOBS_BASE_DIR=/jobs + - SEMGREP_IMAGE=semgrep/semgrep:1.159.0 + - SEMGREP_JOBS=2 + - SEMGREP_PER_FILE_TIMEOUT=2 + - SEMGREP_TIMEOUT_THRESHOLD=1 + - SEMGREP_MAX_TARGET_BYTES=500000 + - JOB_RETENTION_LIMIT=100 + - HTTP_PROXY=${HTTP_PROXY} + - HTTPS_PROXY=${HTTPS_PROXY} + - NO_PROXY=${NO_PROXY} + volumes: + - ./repo_scan_job_mcp.js:/app/repo_scan_job_mcp.js:ro + - /opt/redback/repos:/repos:ro + - /opt/redback/security-jobs:/jobs + - /var/run/docker.sock:/var/run/docker.sock + restart: unless-stopped +``` + +## MCP Hub Configuration + +Because this MCP server uses stdio transport, it should be registered as a command-based MCP server. + +Example MCP hub entry: + +```json +{ + "repo-scan-job-mcp": { + "command": "node", + "args": ["/app/repo_scan_job_mcp.js"], + "env": { + "REPOS_BASE_DIR": "/repos", + "HOST_REPOS_BASE_DIR": "/opt/redback/repos", + "JOBS_BASE_DIR": "/jobs", + "SEMGREP_IMAGE": "semgrep/semgrep:1.159.0", + "SEMGREP_JOBS": "2", + "SEMGREP_PER_FILE_TIMEOUT": "2", + "SEMGREP_TIMEOUT_THRESHOLD": "1", + "SEMGREP_MAX_TARGET_BYTES": "500000", + "JOB_RETENTION_LIMIT": "100" + } + } +} +``` + +If running inside an MCP hub container, ensure the hub container also has: + +```yaml +volumes: + - /opt/redback/repos:/repos:ro + - /opt/redback/security-jobs:/jobs + - /var/run/docker.sock:/var/run/docker.sock +``` + +## Testing the MCP Server Manually + +Start the server: + +```bash +node repo_scan_job_mcp.js +``` + +Because it uses stdio transport, it expects MCP JSON-RPC messages on stdin and writes responses to stdout. + +For practical testing, use it through an MCP client or MCP hub. + +## Operational Flow + +A typical workflow is: + +1. Call `repo_list` +2. Pick a repository name +3. Call `repo_security_scan_start` +4. Store the returned `job_id` +5. Call `repo_security_scan_status` until the job is completed +6. Call `repo_security_scan_result` +7. Review findings + +Example request sequence: + +```text +repo_list +repo_security_scan_start(repo="example-repo", profile="security") +repo_security_scan_status(job_id="scan-...") +repo_security_scan_result(job_id="scan-...") +``` + +## Security Considerations + +This service can trigger Docker containers. + +That is powerful and should be treated as sensitive. + +### Docker Socket Risk + +Mounting: + +```text +/var/run/docker.sock +``` + +into a container effectively gives that container significant control over the Docker host. + +Use this only in a trusted environment. + +### Repository Access + +The scanner should normally receive repositories read-only: + +```text +/opt/redback/repos:/repos:ro +``` + +The MCP server itself needs a writable job directory, but that does not need to be inside `/repos`. + +Recommended: + +```text +/opt/redback/security-jobs:/jobs +``` + +### Input Restrictions + +The code includes basic safeguards: + +- Repository must be a direct child of `/repos` +- Repository cannot contain `..` +- Repository cannot include path separators +- Scope must resolve inside the selected repository + +These checks are useful, but production deployments should still apply authentication and authorisation controls at the MCP client or hub layer. + +### Network Access + +The spawned Semgrep container receives proxy variables. + +If outbound access is not required, restrict or remove proxy configuration. + +### Secrets Scanning + +The `secrets` and `full` profiles may detect sensitive material in repositories. + +Ensure scan results are stored securely and not exposed to untrusted users. + +## Limitations + +Current limitations include: + +- No authentication built into this MCP server. +- No per-user authorisation. +- No cancellation tool for running jobs. +- No streaming of scan progress. +- Results are stored as local files. +- Docker socket access is required for scan execution. +- Job retention is count-based, not age-based. +- No central database or multi-node job coordination. +- No separate severity thresholding or policy gate output. + +## Possible Future Improvements + +Useful future enhancements: + +- Add a `repo_security_scan_cancel` tool. +- Add age-based cleanup for old jobs. +- Add result filtering by severity. +- Add SARIF output support. +- Add Git commit metadata to job results. +- Add branch/tag awareness. +- Add allowlist/denylist repository controls. +- Add authentication/authorisation at the MCP wrapper layer. +- Add structured progress updates. +- Add optional direct Semgrep execution without Docker. +- Add support for custom Semgrep rules mounted from a rules directory. +- Add policy modes such as `audit`, `warn`, and `fail`. + +## Troubleshooting + +### `Repository not found` + +Run: + +```text +repo_list +``` + +Confirm the requested repository exists under `/repos`. + +Also check the container mount: + +```bash +ls -la /repos +``` + +### `Scope escapes repository root` + +The requested `scope` resolved outside the selected repository. + +Use a relative path inside the repository, such as: + +```text +. +src +app/server.js +``` + +### Semgrep Job Fails + +Check the job status: + +```text +repo_security_scan_status +``` + +Then inspect the job directory: + +```bash +ls -la /repos/logs/security-jobs/ +cat /repos/logs/security-jobs//stderr.log +cat /repos/logs/security-jobs//job.json +``` + +If using the safer `/jobs` path: + +```bash +ls -la /jobs/ +cat /jobs//stderr.log +cat /jobs//job.json +``` + +### Docker Is Not Available + +The MCP server launches scans using: + +```bash +docker run +``` + +Check that the container has Docker access: + +```bash +docker ps +``` + +If running inside Docker, mount the Docker socket: + +```yaml +- /var/run/docker.sock:/var/run/docker.sock +``` + +The container image must also include the Docker CLI. + +The plain `node:22` image may not include Docker CLI by default, so a custom image may be required. + +### Semgrep Cannot See Repositories + +Check that `HOST_REPOS_BASE_DIR` points to the correct host path. + +The spawned Semgrep container uses: + +```text +${HOST_REPOS_BASE_DIR}:/repos:ro +``` + +If `HOST_REPOS_BASE_DIR` is wrong, the MCP server may see the repo but the Semgrep container will not. + +### Proxy Problems + +Check: + +```env +HTTP_PROXY +HTTPS_PROXY +NO_PROXY +``` + +If the Semgrep image cannot reach rule sources, proxy configuration may be required. + +If local services are incorrectly routed through the proxy, add them to `NO_PROXY`. + +## Summary + +This MCP server provides a job-based Semgrep scanning interface for repositories mounted under `/repos`. + +It is designed to fit into a private AI / MCP hub workflow where an assistant can request repository scans without blocking while the scan runs. + +The key design points are: + +- MCP stdio server +- Background Semgrep scan jobs +- Docker-based Semgrep execution +- Repository input validation +- Scoped scanning inside repositories +- Job metadata and result files +- Summary and finding extraction +- Configurable Semgrep profile, image, timeout, and retention behaviour diff --git a/PrivateAI/openwebui/caddy/Caddyfile b/PrivateAI/openwebui/caddy/Caddyfile new file mode 100644 index 0000000..6dea1e9 --- /dev/null +++ b/PrivateAI/openwebui/caddy/Caddyfile @@ -0,0 +1,16 @@ +{ + # Turn off auto HTTPS completely + auto_https off +} + +# HTTP → redirect to HTTPS +:80 { + redir https://10.137.17.254{uri} +} + +# HTTPS with our self-signed cert +:443 { + tls /etc/caddy/certs/local.crt /etc/caddy/certs/local.key + + reverse_proxy http://openwebui:8080 +} diff --git a/PrivateAI/openwebui/caddy/Caddyfile.orig b/PrivateAI/openwebui/caddy/Caddyfile.orig new file mode 100644 index 0000000..10003b6 --- /dev/null +++ b/PrivateAI/openwebui/caddy/Caddyfile.orig @@ -0,0 +1,3 @@ +10.137.17.254 { + reverse_proxy openwebui:3000 +} diff --git a/PrivateAI/openwebui/caddy/caddy_config/caddy/autosave.json b/PrivateAI/openwebui/caddy/caddy_config/caddy/autosave.json new file mode 100644 index 0000000..bd155e9 --- /dev/null +++ b/PrivateAI/openwebui/caddy/caddy_config/caddy/autosave.json @@ -0,0 +1 @@ +{"apps":{"http":{"servers":{"srv0":{"automatic_https":{"disable":true},"listen":[":443"],"routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"openwebui:8080"}]}]}],"tls_connection_policies":[{"certificate_selection":{"any_tag":["cert0"]}}]},"srv1":{"automatic_https":{"disable":true},"listen":[":80"],"routes":[{"handle":[{"handler":"static_response","headers":{"Location":["https://10.137.17.254{http.request.uri}"]},"status_code":302}]}]}}},"tls":{"certificates":{"load_files":[{"certificate":"/etc/caddy/certs/local.crt","key":"/etc/caddy/certs/local.key","tags":["cert0"]}]}}}} \ No newline at end of file diff --git a/PrivateAI/openwebui/caddy/caddy_data/caddy/instance.uuid b/PrivateAI/openwebui/caddy/caddy_data/caddy/instance.uuid new file mode 100644 index 0000000..ef9eade --- /dev/null +++ b/PrivateAI/openwebui/caddy/caddy_data/caddy/instance.uuid @@ -0,0 +1 @@ +2a8c158e-bd37-4c58-a81b-8dee66267377 \ No newline at end of file diff --git a/PrivateAI/openwebui/caddy/caddy_data/caddy/last_clean.json b/PrivateAI/openwebui/caddy/caddy_data/caddy/last_clean.json new file mode 100644 index 0000000..8fb76d8 --- /dev/null +++ b/PrivateAI/openwebui/caddy/caddy_data/caddy/last_clean.json @@ -0,0 +1 @@ +{"tls":{"timestamp":"2026-01-29T23:59:16.675342759Z","instance_id":"2a8c158e-bd37-4c58-a81b-8dee66267377"}} \ No newline at end of file diff --git a/PrivateAI/openwebui/caddy/certs/local.crt b/PrivateAI/openwebui/caddy/certs/local.crt new file mode 100644 index 0000000..8ac896b --- /dev/null +++ b/PrivateAI/openwebui/caddy/certs/local.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDETCCAfmgAwIBAgIUcmLthbydWk0jTdqODawaCTUIOSswDQYJKoZIhvcNAQEL +BQAwGDEWMBQGA1UEAwwNMTAuMTM3LjE3LjI1NDAeFw0yNTEyMDEwNjA5MzBaFw0z +NTExMjkwNjA5MzBaMBgxFjAUBgNVBAMMDTEwLjEzNy4xNy4yNTQwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDpHqfhlt/KExEDjKyphPuX+Zrpxg4BSdJ+ +h8cBsBqUyRHtpzYjWOIGogCG9lGhKTJCuadahHzDMGgBSpDYVSX7Gj0Mpmz/yPqd +em79qhAt+gJUQ307xLjMkgOckCu9rhSyFwcefGybT/0wecvkxQmDILhweVmqhqc5 +CUo9JJ6AENsaEPP4Yv01YyW3CKcU/aW3CyJl4ILB879qnV1+6BCvNS+lLjJnuu1c +cB4ODOuEHmkEA4l6kugQpNX0dCT3DZzLFQ+4PxXa9qdllRT3+vXEiZfgn9SC+HMz +OPhPfUL3vCMpNjpwy8EXAAIrj4cKUrmOQDpZxA4G5QUEvbYA+qhdAgMBAAGjUzBR +MB0GA1UdDgQWBBTy6B3+IS68wVeXrtbtmDALg8nBWTAfBgNVHSMEGDAWgBTy6B3+ +IS68wVeXrtbtmDALg8nBWTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQAcx9d4t8NmXiIeF02IXNhubfhQPMpsEtdbfqooGlyR3zfS+7R/JlMwVP1w +C2rZfXw3zx+eXK0DzQazUEHpVDLqdrxW7YlzIbOR539V8hOayGgFzQkKG3BC3F/F +4Ygldl8ZZQWMMyc/+Nb/iN+rgQul7VYvW4KK5PORUGuvVWFg3RYhpusgf+8Fk8NG +kGup0miEXKGTCCh86fMElV9GjGdD8ZuQ0McV18dwUIsyC9MDrsDVPcxcGcCAHMb4 +nsyBQB/CUurPf5yFRUJL4T3G3y5FKGM4hku44fKdFHcH9AyEhFYG2JPp/ElVTIl0 +cbN3TkJRWtWIgMaBYe4qUkKmsKRj +-----END CERTIFICATE----- diff --git a/PrivateAI/openwebui/caddy/certs/local.key b/PrivateAI/openwebui/caddy/certs/local.key new file mode 100644 index 0000000..90f6f0c --- /dev/null +++ b/PrivateAI/openwebui/caddy/certs/local.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDpHqfhlt/KExED +jKyphPuX+Zrpxg4BSdJ+h8cBsBqUyRHtpzYjWOIGogCG9lGhKTJCuadahHzDMGgB +SpDYVSX7Gj0Mpmz/yPqdem79qhAt+gJUQ307xLjMkgOckCu9rhSyFwcefGybT/0w +ecvkxQmDILhweVmqhqc5CUo9JJ6AENsaEPP4Yv01YyW3CKcU/aW3CyJl4ILB879q +nV1+6BCvNS+lLjJnuu1ccB4ODOuEHmkEA4l6kugQpNX0dCT3DZzLFQ+4PxXa9qdl +lRT3+vXEiZfgn9SC+HMzOPhPfUL3vCMpNjpwy8EXAAIrj4cKUrmOQDpZxA4G5QUE +vbYA+qhdAgMBAAECggEAARc7aknfXFSeFEMZ4koHEnrlSeCWklSLBjwbsDH59dz4 ++7d4ErxozS/bk8YVamiOJtzF8lRn8C4mTUmH0K694a5lGt/VVN8Ny2yxlhxmzx8e +s9AWdLSi3G9SLbgkZomWn6FEqubrtSvB/7uRA+BeI6tJKshqAH148YuXFR7e0Gnc +6xhsNRV6+Be+rYV9BL4Sq0KNKDE2AE7/E2VOyfMEAPUdhq53jrdbJvu8TgoXYYd/ +yQJarj5GG3dtnR897zw2m+nnBbgpP9Okf8dDLEOWFzwrMQXgwIJcW77Z7NBCjGgv +Y6fBHDWpQkr4Qs4Zxv+s9kos07o+DSy9qhy/g/V++wKBgQDtRGVEtfbzHImgWebV +7LKjUXCOgpjkmPeBrb2rvhoRFUOiFzS2dWTBIYF+/wXv6w0EtF1AZcAg4CgK5a5/ +zEjgrLaT4EZ0re8mL0QEMyqFzUAXrk3IRh3cfIce5Ml/viklYzZ1xidbMO9fuQsy +mn6c1WABDIgwtatLY+hRqDH+vwKBgQD7hm7nyPlSqvLCBpYva/VkZHc5wmndLL5/ +1yZDhIBHEBgmBjLFFNEBT1w5CZF0fY7z5d2Ga76/S9Q0zYkw2B1Mj3s4W5rLhlQ3 +sT0CnS5cFCu/AgUw8ij++xiw99Db5HaZAmfXx2WSe25UwYaG6/ZBIGbVvO0YqR4S +IBn7OMB74wKBgQCjK1wxaqpP+pozKmBzUfpwEnvDpdCbtQ7RobhEudGXWfZPLIJV +0Fnf77jsq1lb61vill9jABanBUDEbbwZq1WbHWvaOmx5pXxH2E2ATee6aLLhFj/r +sTyr+v+5oUFplk8ZpSc4y3MZZYfZXppyzIiyNpN1ZTbruKP6jtSgA3mOZQKBgAsL +pkcrfjdxJmP64hGHDimwd8Pjk76QvnTiv91rLi7wt/7DeutItLz3/TbMAsU41lRD +nezPQnsoG1OOSx4H/5FjI6gf7bZOWdhwQhuhR23nvNwQfKXfnIlGAZmT6GofqE2j +22eQbBd4sCmsrfmy1weZIqr0Nv1EP/vPyRRNM7a9AoGALuJALLb1MMTf2g/OHBLV +69uGu6Lywx6q0P65/jxvyBYteqcFN92/GJ59Qo7I29gUC+43AUHDssE7goewxN0s +blZOZWDGhatKz901GxM8zuYem4IcelRO98k8xCfAQRFsWyv9uhrWrDs63gcdsL/h +7mrruuqPhQCn9qPE6NzL98M= +-----END PRIVATE KEY----- diff --git a/PrivateAI/openwebui/docker-compose-entra.yaml b/PrivateAI/openwebui/docker-compose-entra.yaml new file mode 100644 index 0000000..511a5a6 --- /dev/null +++ b/PrivateAI/openwebui/docker-compose-entra.yaml @@ -0,0 +1,88 @@ +version: "3.9" + +services: + openwebui: + image: ghcr.io/open-webui/open-webui:main + container_name: openwebui + expose: + - "8080" # expose instead of ports (Caddy will proxy) + volumes: + - ./data:/app/backend/data + #- ./caddy/certs/local.crt:/etc/ssl/certs/ca-certificates.crt:ro + environment: + - WEBUI_AUTH=true + - SAFE_MODE=true + - ENABLE_COMMUNITY_SHARING=false + - OPENAI_API_BASE_URL=http://10.137.17.254:9443/v1 + - OPENAI_API_KEY=$LOCALAI_API_KEY + - HTTP_PROXY=${HTTP_PROXY} + - HTTPS_PROXY=${HTTPS_PROXY} + - NO_PROXY=localhost,127.0.0.1,::1,10.137.17.254 + - ENABLE_LOGIN_FORM=true + - ENABLE_SIGNPUT=true + - ENABLE_OIDC=true + - ENABLE_LDAP=false + #- REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt + #- SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt + + # Proxy + session behaviour + - TRUST_PROXY_HEADERS=true + - COOKIE_SECURE=true # HTTPS is now enabled via Caddy + - SESSION_COOKIE_SAMESITE=Lax + + # Public URL of OpenWebUI + - WEBUI_URL=https://10.137.17.254/ + + # OAuth / Microsoft Entra + - ENABLE_OAUTH_SIGNUP=true + - ENABLE_OAUTH_PERSISTENT_CONFIG=false + - OAUTH_MERGE_ACCOUNTS_BY_EMAIL=true + - OAUTH_UPDATE_PICTURE_ON_LOGIN=true + - OAUTH_MICROSOFT_ENABLED=true + - OAUTH_SCOPES=["openid", "profile","email"] + - MICROSOFT_CLIENT_ID=${MICROSOFT_CLIENT_ID} + - MICROSOFT_CLIENT_SECRET=${MICROSOFT_CLIENT_SECRET} + - MICROSOFT_CLIENT_TENANT_ID=${MICROSOFT_CLIENT_TENANT_ID} + - MICROSOFT_REDIRECT_URI=https://10.137.17.254/oauth/microsoft/callback + - OPENID_PROVIDER_URL=https://login.microsoftonline.com/secret/v2.0 # required for logout + #- OPENID_PROVIDER_URL=https://login.microsoftonline.com/${MICROSOFT_CLIENT_TENANT_ID}/v2.0 + - ENABLE_OAUTH_WITHOUT_EMAIL=true #this is a work around and breaks things like password reset and trusts that microsoft sub is stable which it normally isn't + - OAUTH_EMAIL_CLAIM=preferred_username + - OAUTH_PICTURE_CLAIM=picture + + extra_hosts: + - "host.docker.internal:host-gateway" + - "proxy1.it.deakin.edu.au:10.137.0.162" + networks: + - web + restart: unless-stopped + + caddy: + image: caddy:latest + container_name: caddy + ports: + - "3000:443" + - "80:80" + - "443:443" + volumes: + - ./caddy/Caddyfile:/etc/caddy/Caddyfile + - ./caddy/caddy_data:/data + - ./caddy/caddy_config:/config + - ./caddy/certs:/etc/caddy/certs:ro # <— add this + environment: + #- HTTP_PROXY=${HTTP_PROXY} + #- HTTPS_PROXY=${HTTPS_PROXY} + - NO_PROXY=localhost,127.0.0.1,::1,10.137.17.254,openwebui + networks: + - web + restart: unless-stopped + extra_hosts: + - "host.docker.internal:host-gateway" + - "proxy1.it.deakin.edu.au:10.137.0.162" +networks: + web: + +volumes: + caddy_data: + caddy_config: + data: diff --git a/PrivateAI/openwebui/env b/PrivateAI/openwebui/env new file mode 100644 index 0000000..923b99b --- /dev/null +++ b/PrivateAI/openwebui/env @@ -0,0 +1,7 @@ +HTTP_PROXY=http://proxy1.it.deakin.edu.au:3128 +HTTPS_PROXY=http://proxy1.it.deakin.edu.au:3128 +MICROSOFT_CLIENT_ID=insertoauthclientid # Microsoft OAuth client ID +MICROSOFT_CLIENT_TENANT_ID=insertoauthclientsecret # Microsoft OAuth client secret +MICROSOFT_CLIENT_SECRET=nserttenantid # Microsoft tenant ID - use 9188040d-6c67-4c5b-b112-36a304b66dad for personal accounts + +LOCALAI_API_KEY=sk-fromthelocalaienvfile diff --git a/PrivateAI/openwebui/readme.md b/PrivateAI/openwebui/readme.md new file mode 100644 index 0000000..990391e --- /dev/null +++ b/PrivateAI/openwebui/readme.md @@ -0,0 +1,247 @@ +# OpenWebUI (Docker Compose + Caddy + Microsoft Entra ID) + +This bundle runs **OpenWebUI** behind a **Caddy** reverse proxy with **HTTPS** (self‑signed cert) and **Microsoft Entra ID (OAuth/OIDC)** login enabled. +It is also pre-wired to talk to an **OpenAI-compatible API** (e.g. LocalAI) via `OPENAI_API_BASE_URL`. + +> **Security note:** The included `dot.env.example` contains placeholder/example values. +> Treat any secrets as **compromised** if they were ever committed or shared—rotate them in Microsoft Entra and your model backend. + +--- + +## What’s in this tarball + +``` +openwebui/ + docker-compose-entra.yaml + dot.env.example + caddy/ + Caddyfile + Caddyfile.orig + certs/ + local.crt + local.key + caddy_data/... + caddy_config/... +``` + +### File-by-file: what each configuration does + +#### `docker-compose-entra.yaml` +Defines **two services** on the same Docker network: + +- **`openwebui`** + - Image: `ghcr.io/open-webui/open-webui:main` + - Exposes port `8080` **only to the internal Docker network** (`expose:`) — not published to the host. + - Persists OpenWebUI state in `./data` (mounted to `/app/backend/data`). + - Enables: + - local login form (`ENABLE_LOGIN_FORM=true`) + - OAuth/OIDC (`ENABLE_OIDC=true`, `OAUTH_MICROSOFT_ENABLED=true`) + - auth (`WEBUI_AUTH=true`) + - safe mode (`SAFE_MODE=true`) + - Routes model calls to an OpenAI-compatible endpoint: + - `OPENAI_API_BASE_URL=http://10.137.17.254:9443/v1` + - `OPENAI_API_KEY=$LOCALAI_API_KEY` (read from `.env`) + - Supports proxies via `HTTP_PROXY` / `HTTPS_PROXY` from `.env` and a `NO_PROXY` list. + +- **`caddy`** + - Image: `caddy:latest` + - Publishes ports: + - `80:80` (HTTP redirect to HTTPS) + - `443:443` (HTTPS) + - `3000:443` (optional alternate access to the same HTTPS listener) + - Mounts: + - `./caddy/Caddyfile` to `/etc/caddy/Caddyfile` + - `./caddy/certs` to `/etc/caddy/certs` (read-only) for TLS cert/key + - `./caddy_data` and `./caddy_config` for Caddy runtime state + +> ⚠️ **Potential typo:** the compose includes `ENABLE_SIGNPUT=true`. +> OpenWebUI uses `ENABLE_SIGNUP`. If you have issues with signup, change it to `ENABLE_SIGNUP=true`. + +--- + +#### `dot.env.example` +An example environment file you copy to `.env` and edit. It provides: + +- `HTTP_PROXY`, `HTTPS_PROXY` – outbound proxy settings (optional) +- `MICROSOFT_CLIENT_ID` – Entra app registration client ID +- `MICROSOFT_CLIENT_SECRET` – Entra app registration client secret +- `MICROSOFT_CLIENT_TENANT_ID` – Entra tenant ID (or `common`/`organizations` depending on your setup) +- `LOCALAI_API_KEY` – API key for your OpenAI-compatible backend (LocalAI, etc.) + +> ⚠️ The comments in this example file are slightly mismatched (tenant vs secret). +> Use the variable names as the source of truth. + +--- + +#### `caddy/Caddyfile` +Caddy reverse proxy + TLS configuration: + +- Disables Caddy auto-HTTPS (`auto_https off`) so it **only** uses the provided cert. +- HTTP listener `:80` **redirects** to `https://10.137.17.254{uri}`. +- HTTPS listener `:443`: + - Uses the self-signed TLS cert: + - cert: `/etc/caddy/certs/local.crt` + - key: `/etc/caddy/certs/local.key` + - Proxies traffic to OpenWebUI at `http://openwebui:8080`. + +> ⚠️ The redirect + public URL are hard-coded to `10.137.17.254`. +> If your host IP/domain differs, update: +> - `caddy/Caddyfile` (redirect target) +> - `WEBUI_URL` and `MICROSOFT_REDIRECT_URI` in the compose + +--- + +#### `caddy/Caddyfile.orig` +A prior/original version of the Caddyfile kept for reference. + +--- + +#### `caddy/certs/local.crt` and `caddy/certs/local.key` +A **self-signed** TLS certificate and private key used by Caddy for HTTPS. + +- Browsers will show a certificate warning unless you **trust** the certificate on your machine. +- For a production deployment, replace these with a proper certificate (e.g., Let’s Encrypt with a real domain). + +--- + +#### `caddy/caddy_data/*` and `caddy/caddy_config/*` +Caddy’s persisted runtime state: + +- `caddy_data` – instance UUID, lock files, last-clean metadata, etc. +- `caddy_config/caddy/autosave.json` – Caddy’s autosaved config snapshot (generated/maintained by Caddy) + +You normally **do not edit** these manually. + +--- + +## Prerequisites + +- Docker + Docker Compose plugin (`docker compose version`) +- Ports **80** and **443** available on the host (and optionally **3000**) +- If you’ll use Entra login: + - A Microsoft Entra App Registration with a redirect URI matching your deployment URL. + +--- + +## Quick start + +### 1) Extract the tarball +From the directory containing the tar: + +```bash +tar -xf openwebui.tar +cd openwebui +``` + +### 2) Create your `.env` +Copy the example and edit values: + +```bash +cp dot.env.example .env +nano .env +``` + +At minimum, set: + +- `LOCALAI_API_KEY=...` +- `MICROSOFT_CLIENT_ID=...` +- `MICROSOFT_CLIENT_SECRET=...` +- `MICROSOFT_CLIENT_TENANT_ID=...` + +Optionally set proxy variables (or delete them if not needed). + +### 3) Update host/IP references (recommended) +This bundle is hardcoded to `10.137.17.254`. + +Search & replace in: + +- `caddy/Caddyfile` (redirect line) +- `docker-compose-entra.yaml`: + - `WEBUI_URL` + - `MICROSOFT_REDIRECT_URI` + - (optionally) `OPENAI_API_BASE_URL` if your model endpoint differs + +### 4) Start the stack +Run: + +```bash +docker compose --env-file .env -f docker-compose-entra.yaml up -d +``` + +Check logs: + +```bash +docker compose -f docker-compose-entra.yaml logs -f --tail=200 +``` + +Stop: + +```bash +docker compose -f docker-compose-entra.yaml down +``` + +--- + +## Accessing OpenWebUI + +- Primary (standard HTTPS): `https:///` +- Optional alternate mapping: `https://:3000/` + +Because the certificate is self-signed, your browser will warn unless you trust `caddy/certs/local.crt`. + +### Trusting the self-signed cert (quick guidance) + +- **macOS:** Keychain Access → System (or Login) → Certificates → Import `local.crt` → set to “Always Trust”. +- **Windows:** `certmgr.msc` → Trusted Root Certification Authorities → Certificates → Import `local.crt`. +- **Linux:** depends on distro; typically copy to `/usr/local/share/ca-certificates/` and run `update-ca-certificates`. + +--- + +## Microsoft Entra ID (OAuth/OIDC) notes + +In Entra App Registration: + +- Add a **Redirect URI** matching: + - `https:///oauth/microsoft/callback` +- Ensure the app is configured for the correct tenant type: + - Single-tenant: use your tenant ID + - Multi-tenant/personal: you may need `common` and adjust scopes/claims + +This compose sets: +- `OAUTH_SCOPES=["openid","profile","email"]` +- `OAUTH_EMAIL_CLAIM=preferred_username` +- `OAUTH_MERGE_ACCOUNTS_BY_EMAIL=true` + +> The compose includes `ENABLE_OAUTH_WITHOUT_EMAIL=true` (marked as a workaround). +> If you don’t need it, consider disabling it for cleaner account semantics. + +--- + +## Troubleshooting + +### Port 80/443 already in use +- Another service (nginx, Traefik, etc.) may be listening. +- Either stop the conflicting service or change the published ports in the `caddy` service. + +### Redirect goes to the wrong IP +Update the `redir` target in `caddy/Caddyfile`. + +### OAuth login loops or fails +- Confirm the redirect URI matches **exactly** (scheme/host/path). +- Confirm the tenant setting and the `MICROSOFT_CLIENT_*` values in `.env`. +- Check OpenWebUI logs for OAuth errors. + +### OpenWebUI can’t reach the model backend +- Confirm `OPENAI_API_BASE_URL` is reachable **from inside Docker**. +- If the backend runs on the Docker host, `host.docker.internal` is available due to `extra_hosts`. + +--- + +## Data persistence + +- OpenWebUI data is stored in `./data` (in the `openwebui/` folder). +- Caddy state is stored in `./caddy/caddy_data` and `./caddy/caddy_config`. + +Back up those directories if you want to preserve state. + +--- diff --git a/PrivateAI/private-ai-readme.md b/PrivateAI/private-ai-readme.md new file mode 100644 index 0000000..58a1c54 --- /dev/null +++ b/PrivateAI/private-ai-readme.md @@ -0,0 +1,1103 @@ +# PrivateAI Stack Architecture and Capabilities + +This document provides a top-level architecture overview of the PrivateAI stack. + +The stack combines a local/private AI inference layer, a browser-based user interface, an MCP tool integration layer, repository security scanning, knowledge/memory services, and reverse proxy access. + +It is designed as a modular private AI platform that can run local models, expose an OpenAI-compatible API, integrate external tools through MCP, scan repositories, and provide a secure web interface for users. + +## Documented Components + +The current stack documentation covers: + +1. **Semgrep MCP Service** +2. **LocalAI with PostgreSQL / LocalRecall** +3. **OpenWebUI with Caddy HTTPS reverse proxy** +4. **Repo Security Scan Job MCP** +5. **MCP Hub with proxychains and integrations** + +Together, these form the current PrivateAI platform. + +## High-Level Architecture + +```text + Users / Browser + | + v + https://10.137.17.254/ + | + v + Caddy + HTTPS reverse proxy layer + | + v + OpenWebUI + Browser UI / chat interface + | + v + OpenAI-compatible API endpoint / gateway + http://10.137.17.254:9443/v1 + | + v + LocalAI + Local inference, models, agents, memory + | + +--------------+---------------+ + | | + v v + Local models PostgreSQL / + GPU-backed inference LocalRecall + knowledge base + + | + v + MCP Hub + Tool and integration orchestration + | + +------------+-------------+--------------+-------------+ + | | | | | + v v v v v + Playwright Fetch Git tools Wazuh Databases + Browser HTTP tools Repo tools Security PostgreSQL / + automation platform Supabase + | + v + Repo Security Scan MCP + | + v + Semgrep scans + | + v + /opt/redback/repos +``` + +## Core Design Goals + +The stack is intended to provide: + +- A private AI interface for users +- Local model inference using GPU-backed LocalAI +- OpenAI-compatible API access for tools and frontends +- Persistent model, backend, config, image, and data storage +- PostgreSQL-backed agent memory and knowledge base capability +- MCP tool access for browsing, fetching, databases, security tools, and code repositories +- Repository security scanning using Semgrep +- HTTPS access via Caddy +- Corporate proxy compatibility +- Internal service routing without proxy interference +- Docker-based operational simplicity + +## Main User Entry Point + +The main user-facing entry point is OpenWebUI behind Caddy. + +Users access: + +```text +https://10.137.17.254/ +``` + +Caddy handles HTTPS and forwards traffic to OpenWebUI internally: + +```text +openwebui:8080 +``` + +OpenWebUI provides the browser chat interface, authentication, OAuth login, and connection to the OpenAI-compatible backend. + +## Reverse Proxy Layer + +### Component + +```text +Caddy +``` + +### Purpose + +Caddy provides: + +- HTTPS termination +- Reverse proxying to OpenWebUI +- Certificate handling +- Public-facing access on ports `443` and optionally `3000` + +### Published Ports + +```text +80 -> HTTP +443 -> HTTPS +3000 -> alternate HTTPS mapping +``` + +### Why It Matters + +OpenWebUI is not exposed directly to the host. It is only exposed inside Docker using: + +```yaml +expose: + - "8080" +``` + +This keeps the UI behind the reverse proxy and allows secure cookie/session behaviour to work properly. + +## Web UI Layer + +### Component + +```text +OpenWebUI +``` + +### Purpose + +OpenWebUI provides the user-facing AI chat interface. + +It is configured with: + +- Authentication enabled +- Microsoft Entra / Azure AD OAuth +- Login form support +- Community sharing disabled +- Safe mode enabled +- HTTPS-aware cookie/session settings +- OpenAI-compatible backend API integration + +### Backend API + +OpenWebUI is configured to use: + +```text +http://10.137.17.254:9443/v1 +``` + +This means OpenWebUI talks to an OpenAI-compatible API endpoint rather than directly embedding model logic. + +Depending on routing, that endpoint may point to LocalAI directly or to an API gateway such as LiteLLM or another routing layer. + +## Inference Layer + +### Component + +```text +LocalAI +``` + +### Purpose + +LocalAI provides the local model inference backend. + +It is configured using the NVIDIA CUDA 12 GPU image: + +```text +localai/localai:latest-gpu-nvidia-cuda-12 +``` + +LocalAI exposes an OpenAI-compatible API and supports local models, backends, image outputs, agent features, memory integration, and skills. + +### LocalAI API Port + +LocalAI is mapped as: + +```text +host port 4000 -> container port 8080 +``` + +Direct LocalAI endpoint: + +```text +http://localhost:4000 +``` + +Container-internal endpoint: + +```text +http://localai:8080 +``` + +### GPU Capability + +LocalAI is configured for NVIDIA GPUs: + +```yaml +runtime: nvidia +gpus: all +NVIDIA_VISIBLE_DEVICES=all +NVIDIA_DRIVER_CAPABILITIES=compute,utility +``` + +This allows GPU-backed model inference. + +### Model Storage + +Models are persisted at: + +```text +/opt/redback/privateai/volumes/models +``` + +Mounted inside LocalAI as: + +```text +/models +``` + +### Backend Storage + +LocalAI backends are stored at: + +```text +/opt/redback/privateai/volumes/backends +``` + +Mounted inside LocalAI as: + +```text +/usr/share/localai/backends +``` + +### Image Output Storage + +Generated images are stored at: + +```text +/opt/redback/privateai/volumes/images +``` + +Mounted inside LocalAI as: + +```text +/tmp/generated/images +``` + +## Knowledge and Memory Layer + +### Components + +```text +PostgreSQL +LocalRecall +LocalAI Agent Pool +``` + +### Purpose + +The knowledge/memory layer provides persistent storage for LocalAI agent memory and knowledge base workflows. + +LocalAI is configured to use PostgreSQL as the vector engine: + +```env +LOCALAI_AGENT_POOL_VECTOR_ENGINE=postgres +``` + +The database connection is: + +```text +postgresql://localrecall:localrecall@postgres:5432/localrecall?sslmode=disable +``` + +### PostgreSQL Service + +The stack uses: + +```text +quay.io/mudler/localrecall:v0.5.2-postgresql +``` + +Database: + +```text +localrecall +``` + +User: + +```text +localrecall +``` + +### Agent Pool Defaults + +Default agent model: + +```text +gemma-4-e4b-it +``` + +Embedding model: + +```text +granite-embedding-107m-multilingual +``` + +### Capability + +This gives the stack the foundation for: + +- Knowledge bases +- Agent memory +- Embedding-backed retrieval +- Local RAG-style workflows +- Skills-enabled agent behaviour +- Persistent logs for agent operations + +## MCP Integration Layer + +### Component + +```text +MCP Hub +``` + +### Purpose + +MCP Hub acts as the tool orchestration layer. + +It connects AI clients and agents to external tools through the Model Context Protocol. + +It is exposed on: + +```text +host port 3003 -> container port 3000 +``` + +Access: + +```text +http://localhost:3003 +``` + +### Current MCP Integrations + +The hub is currently configured with: + +```text +amap +playwright +fetch +sequential-thinking +time +mindmap +playwright-mcp +fetch-mcp +time-mcp +mongodb +git-mcp-server +repo-security-scan +arxiv-mcp +wazuh +postgresql +supabase-postgres +``` + +### Capability Categories + +#### Browser Automation + +```text +playwright +playwright-mcp +``` + +Provides browser-driven workflows, page inspection, and automation. + +#### HTTP Fetching + +```text +fetch +fetch-mcp +``` + +Provides tool-based HTTP retrieval. + +#### Reasoning Support + +```text +sequential-thinking +mindmap +``` + +Provides structured reasoning and planning style tools. + +#### Time Tools + +```text +time +time-mcp +``` + +Provides time/date utilities. + +#### Repository and Git Tools + +```text +git-mcp-server +repo-security-scan +``` + +Provides repository interaction and security scanning. + +#### Security Platform Integration + +```text +wazuh +``` + +Connects the AI tool layer to Wazuh security data. + +#### Database Integrations + +```text +mongodb +postgresql +supabase-postgres +``` + +Allows MCP-enabled access to database systems. + +#### Research Integration + +```text +arxiv-mcp +``` + +Provides arXiv research search capability. + +## Repository Security Scanning + +Repository scanning is implemented in two related ways. + +## Semgrep MCP Service + +### Component + +```text +semgrep-mcp +``` + +### Purpose + +Runs Semgrep as a streamable HTTP MCP server. + +It exposes Semgrep functionality over MCP and mounts repositories read-only: + +```text +/opt/redback/repos:/repos:ro +``` + +It listens on: + +```text +4004 +``` + +This service is useful when an MCP client wants direct Semgrep MCP access over HTTP. + +## Repo Security Scan Job MCP + +### Component + +```text +repo_scan_job_mcp.js +``` + +### Purpose + +This is a custom job-oriented MCP wrapper around Semgrep. + +Instead of blocking while a scan runs, it starts a background Docker job and returns a `job_id`. + +The client can then: + +1. List repositories +2. Start a scan +3. Check scan status +4. Fetch completed results +5. List recent jobs + +### Main Tools + +```text +repo_list +repo_security_scan_start +repo_security_scan_status +repo_security_scan_result +repo_security_scan_list_jobs +``` + +### Why This Exists + +Large Semgrep scans can take time. + +The job wrapper allows the assistant or MCP client to start a scan and come back for results later, without blocking the MCP tool call. + +### Repository Root + +Host path: + +```text +/opt/redback/repos +``` + +Container path: + +```text +/repos +``` + +### Scan Profiles + +Supported profiles: + +```text +security +secrets +full +``` + +Profile mapping: + +```text +security -> p/security-audit +secrets -> p/secrets +full -> p/security-audit + p/secrets + p/owasp-top-ten +``` + +### Docker Requirement + +The job wrapper launches Semgrep using Docker: + +```bash +docker run --rm ... +``` + +Therefore MCP Hub is configured with: + +```yaml +- /var/run/docker.sock:/var/run/docker.sock +``` + +and the custom MCP Hub image includes the Docker CLI. + +## Proxy and Network Design + +The stack is designed to work in an environment that requires a corporate proxy. + +Proxy host: + +```text +proxy1.it.deakin.edu.au +``` + +Proxy IP: + +```text +10.137.0.162 +``` + +Proxy port: + +```text +3128 +``` + +### HTTP Proxy Variables + +Common proxy variables: + +```env +HTTP_PROXY=http://proxy1.it.deakin.edu.au:3128 +HTTPS_PROXY=http://proxy1.it.deakin.edu.au:3128 +``` + +### NO_PROXY + +Internal traffic is excluded using `NO_PROXY`. + +Typical entries include: + +```text +localhost +127.0.0.1 +::1 +localai +postgres +mcphub +openwebui +semgrep-mcp +mcp-hub-mcphub-1 +proxy1.it.deakin.edu.au +10.137.0.162 +10.137.17.254 +api.mcprouter.to +``` + +### Why NO_PROXY Matters + +Without correct `NO_PROXY`, internal Docker and internal network traffic may be sent through the external proxy. + +That can cause: + +- `fetch failed` +- connection refused +- bad port errors +- proxy recursion +- internal services becoming unreachable +- MCP servers failing to connect +- OAuth/backend API calls behaving unexpectedly + +## Proxychains in MCP Hub + +MCP Hub uses a custom entrypoint that runs the hub under `proxychains4`. + +This helps with tools or dependencies that do not respect normal proxy environment variables. + +### Proxychains Local Network Exclusions + +The configuration excludes: + +```text +10.137.0.162/32 +127.0.0.0/8 +10.0.0.0/8 +172.16.0.0/12 +192.168.0.0/16 +``` + +This prevents internal traffic and the proxy itself from being proxied. + +### Why This Matters + +Some MCP tools make outbound network calls through Node.js, Python, browser tooling, or subprocesses. + +Proxychains gives a broad fallback mechanism for forcing outbound traffic through the proxy when native proxy handling is unreliable. + +## Data and Storage Layout + +The stack uses a host-based storage layout under `/opt/redback`. + +### AI Stack Data + +```text +/opt/redback/privateai/volumes/ +├── models/ +├── images/ +├── backends/ +├── localai_data/ +└── localai_config/ +``` + +### Repository Data + +```text +/opt/redback/repos/ +``` + +This directory is used by: + +- Semgrep MCP +- Repo security scan MCP +- Git MCP tooling +- Code analysis workflows + +### MCP Hub Files + +A typical MCP Hub working directory contains: + +```text +mcp-hub/ +├── docker-compose.yaml +├── Dockerfile +├── .env +├── mcp_settings.json +├── entrypoint-proxy.sh +├── proxychains.conf +├── nodefetch.sh +└── repo_scan_job_mcp.js +``` + +### OpenWebUI Stack Files + +A typical OpenWebUI/Caddy directory contains: + +```text +openwebui-stack/ +├── docker-compose.yml +├── .env +├── data/ +└── caddy/ + ├── Caddyfile + ├── caddy_data/ + ├── caddy_config/ + └── certs/ +``` + +## Authentication and Access Control + +### OpenWebUI + +OpenWebUI authentication is enabled: + +```env +WEBUI_AUTH=true +``` + +Microsoft Entra OAuth is enabled: + +```env +ENABLE_OIDC=true +OAUTH_MICROSOFT_ENABLED=true +ENABLE_OAUTH_SIGNUP=true +``` + +This allows users to authenticate with Microsoft Entra / Azure AD. + +### Caddy + +Caddy provides HTTPS access and certificate handling. + +### MCP Hub + +MCP Hub is powerful and should be treated as sensitive. + +It has access to: + +- MCP tools +- Internal services +- Repositories +- Docker socket +- Databases +- Security systems +- Browser automation +- Outbound network access + +MCP Hub should only be exposed to trusted clients or placed behind authentication and network controls. + +## Capability Overview + +The current PrivateAI stack can provide the following capabilities. + +## 1. Private Chat Interface + +Users can access OpenWebUI through a browser and chat with local or routed models. + +Capability: + +```text +User -> OpenWebUI -> OpenAI-compatible API -> LocalAI / gateway +``` + +## 2. Local GPU Inference + +LocalAI can run local models using NVIDIA GPUs. + +Capability: + +```text +Prompt -> LocalAI -> GPU-backed model -> Response +``` + +## 3. OpenAI-Compatible API + +LocalAI exposes an OpenAI-compatible interface. + +This allows tools like OpenWebUI, LiteLLM, agents, or custom applications to call local models using familiar API patterns. + +## 4. Knowledge Base and Memory + +LocalAI is configured with PostgreSQL-backed vector/memory support. + +Capability: + +```text +Documents / memory -> embeddings -> PostgreSQL / LocalRecall -> retrieval -> model context +``` + +## 5. MCP Tool Use + +MCP Hub exposes external tools to AI clients. + +Capability examples: + +- Fetch webpages +- Use browser automation +- Query databases +- Search arXiv +- Interact with Git repositories +- Check time/date +- Use structured reasoning tools +- Query security systems +- Run repository security scans + +## 6. Repository Security Scanning + +Semgrep can scan repositories under: + +```text +/opt/redback/repos +``` + +Capabilities: + +- Security audit scans +- Secret scans +- OWASP Top Ten scans +- Background scan jobs +- Scan status polling +- JSON result retrieval +- Severity summaries + +## 7. Security Operations Integration + +The Wazuh MCP integration gives the stack a pathway into security monitoring data. + +Potential capabilities: + +- Query alerts +- Investigate endpoints +- Summarise security events +- Connect findings to repository or infrastructure context + +## 8. Database-Aware Assistance + +MCP integrations include: + +```text +mongodb +postgresql +supabase-postgres +``` + +Potential capabilities: + +- Query operational data +- Inspect schemas +- Summarise records +- Support application debugging +- Assist with reporting and analysis + +## 9. Browser and Web Automation + +Playwright tools provide browser automation. + +Potential capabilities: + +- Page testing +- UI validation +- Screenshot-style inspection +- Web workflow automation +- Login/session testing where appropriately configured + +## 10. Research Support + +The arXiv MCP integration provides research discovery capability. + +Potential capabilities: + +- Search papers +- Summarise technical topics +- Support research workflows +- Combine local reasoning with external paper discovery + +## Deployment Boundaries + +The stack has several major trust boundaries. + +## User Boundary + +```text +Users -> Caddy -> OpenWebUI +``` + +Users should interact through HTTPS and authenticated OpenWebUI sessions. + +## API Boundary + +```text +OpenWebUI -> OpenAI-compatible backend +``` + +OpenWebUI sends prompts and receives model responses through the configured backend API. + +## Tool Boundary + +```text +AI client / hub -> MCP tools +``` + +MCP tools can access sensitive systems. This boundary requires careful trust and configuration. + +## Host Boundary + +```text +MCP Hub -> Docker socket -> host Docker daemon +``` + +Docker socket access is effectively privileged host access. + +This is the highest-risk boundary in the current architecture. + +## Data Boundary + +```text +Repositories, models, generated files, databases, configs +``` + +Persistent data is stored on the host and mounted into containers. + +Permissions, backups, and separation of writable/read-only paths matter. + +## Security Considerations + +Important security considerations: + +- Do not expose MCP Hub to untrusted networks. +- Treat Docker socket access as privileged. +- Keep repository mounts read-only where possible. +- Use separate writable job storage for scan outputs. +- Store secrets in `.env`, secret stores, or Docker secrets. +- Do not commit `.env` files. +- Avoid using simple default database passwords in production. +- Keep OAuth redirect URIs exact. +- Use HTTPS for OpenWebUI. +- Limit MCP tools where possible instead of exposing `"tools": "all"` everywhere. +- Pin container image versions for reproducibility. +- Monitor logs for proxy and authentication failures. +- Consider splitting risky tools into separate MCP Hub instances. + +## Operational Validation + +## Validate OpenWebUI + +```bash +curl -k https://10.137.17.254/ +``` + +## Validate LocalAI + +```bash +curl http://localhost:4000/v1/models +``` + +## Validate LocalAI GPU Access + +```bash +docker exec -it local-ai nvidia-smi +``` + +## Validate PostgreSQL + +```bash +docker compose logs postgres +``` + +or: + +```bash +docker exec -it pg_isready -U localrecall +``` + +## Validate MCP Hub + +```bash +curl http://localhost:3003 +``` + +Check logs: + +```bash +docker compose logs -f mcphub +``` + +## Validate MCP Hub Docker Access + +```bash +docker exec -it mcp-hub-mcphub-1 docker ps +``` + +## Validate Repository Mount + +```bash +docker exec -it mcp-hub-mcphub-1 ls -la /repos +``` + +## Validate Internal HTTP Fetch + +```bash +./nodefetch.sh +``` + +## Validate Semgrep Scan Image + +```bash +docker exec -it mcp-hub-mcphub-1 sh -lc \ +'docker run --rm -v /opt/redback/repos:/repos:ro semgrep/semgrep:1.159.0 semgrep --version' +``` + +## Current Strengths + +The current architecture has several strengths: + +- Modular Docker-based design +- Local model support +- GPU-backed inference +- OpenAI-compatible API pattern +- Browser UI with OAuth support +- Caddy-based HTTPS +- MCP tool integration layer +- Repository scanning and security tooling +- Proxy-aware networking +- Persistent host-mounted storage +- Extensible integration approach + +## Current Risks and Gaps + +Areas that may need future hardening: + +- MCP Hub has broad tool access +- Docker socket mount is high risk +- Some images use `latest` +- Some credentials are simple defaults +- Repo scan jobs may write logs under repository mount unless separated +- Tool exposure currently uses `"tools": "all"` for many integrations +- Runtime proxy variables should be checked carefully +- OAuth without email is a workaround and may affect account stability +- More explicit network segmentation may be useful +- Centralised backup/restore documentation is still needed + +## Recommended Next Improvements + +Recommended next steps: + +1. Pin all container image versions. +2. Split high-risk MCP tools into separate MCP Hub instances. +3. Put MCP Hub behind authentication and TLS. +4. Use a Docker socket proxy instead of mounting the Docker socket directly. +5. Move repo scan job logs to a dedicated `/jobs` volume. +6. Make repository mounts read-only wherever possible. +7. Replace default PostgreSQL credentials. +8. Add healthchecks for key services. +9. Add backup and restore procedures. +10. Add a network diagram with actual hostnames/IPs. +11. Add a model inventory document. +12. Add a runbook for proxy troubleshooting. +13. Add a runbook for OAuth troubleshooting. +14. Add a security model for MCP tool exposure. +15. Add a standard onboarding guide for new tools. + +## Architecture Summary + +The PrivateAI stack is a locally controlled AI platform made up of: + +- **Caddy** for HTTPS ingress +- **OpenWebUI** for the user interface +- **LocalAI** for local GPU-backed inference +- **PostgreSQL / LocalRecall** for knowledge base and memory support +- **MCP Hub** for tool orchestration +- **Semgrep and repo scan MCP** for repository security scanning +- **Proxychains and proxy configuration** for reliable operation behind a corporate proxy +- **Host-mounted storage** for models, repositories, configs, generated outputs, and persistent data + +The platform is capable of private chat, local inference, tool use, browser automation, repository analysis, security scanning, database interaction, research assistance, and knowledge-backed workflows. + +The overall design is flexible and powerful, but MCP Hub and Docker socket access should be treated as sensitive infrastructure and hardened before broader production use. diff --git a/PrivateAI/semgrep/docker-compose.yaml b/PrivateAI/semgrep/docker-compose.yaml new file mode 100644 index 0000000..162989b --- /dev/null +++ b/PrivateAI/semgrep/docker-compose.yaml @@ -0,0 +1,16 @@ +services: + semgrep-mcp: + image: returntocorp/semgrep:latest + command: ["semgrep", "mcp", "-t", "streamable-http", "-p", "4004"] + environment: + - FASTMCP_HOST=0.0.0.0 + - SEMGREP_ENABLE_VERSION_CHECK=0 + - HTTP_PROXY=${HTTP_PROXY} + - HTTPS_PROXY=${HTTPS_PROXY} + - NO_PROXY=localhost,127.0.0.1,::1,proxy1.it.deakin.edu.au,10.137.0.162,api.mcprouter.to + volumes: + - /opt/redback/repos:/repos:ro + working_dir: /repos + ports: + - "4004:4004" + restart: unless-stopped diff --git a/PrivateAI/semgrep/readme.md b/PrivateAI/semgrep/readme.md new file mode 100644 index 0000000..1b11da1 --- /dev/null +++ b/PrivateAI/semgrep/readme.md @@ -0,0 +1,426 @@ +# Semgrep MCP Service + +This service runs Semgrep as a Model Context Protocol (MCP) server using the `streamable-http` transport. It is intended to expose Semgrep scanning capability to an MCP hub or MCP-compatible client while mounting local repositories read-only. + +## Service Overview + +```yaml +semgrep-mcp: + image: returntocorp/semgrep:latest + command: ["semgrep", "mcp", "-t", "streamable-http", "-p", "4004"] + environment: + - FASTMCP_HOST=0.0.0.0 + - SEMGREP_ENABLE_VERSION_CHECK=0 + - HTTP_PROXY=${HTTP_PROXY} + - HTTPS_PROXY=${HTTPS_PROXY} + - NO_PROXY=localhost,127.0.0.1,::1,proxy1.it.deakin.edu.au,10.137.0.162,api.mcprouter.to + volumes: + - /opt/redback/repos:/repos:ro + working_dir: /repos + ports: + - "4004:4004" + restart: unless-stopped +``` + +## What This Service Does + +The `semgrep-mcp` container starts Semgrep in MCP server mode and exposes it over HTTP on port `4004`. + +It can be used by an MCP hub, AI assistant, or other MCP-aware client to run Semgrep-based code analysis against repositories mounted into the container. + +The mounted repository path is: + +```text +/opt/redback/repos +``` + +Inside the container, this is available as: + +```text +/repos +``` + +The volume is mounted read-only, so Semgrep can inspect code but cannot modify repository files. + +## Key Settings + +### Image + +```yaml +image: returntocorp/semgrep:latest +``` + +Uses the official Semgrep container image. + +### Command + +```yaml +command: ["semgrep", "mcp", "-t", "streamable-http", "-p", "4004"] +``` + +Starts Semgrep as an MCP server using: + +- `mcp` — run Semgrep in MCP server mode +- `-t streamable-http` — use streamable HTTP transport +- `-p 4004` — listen on port `4004` + +### Host Binding + +```yaml +FASTMCP_HOST=0.0.0.0 +``` + +This makes the MCP server listen on all container interfaces. + +This is important because some MCP servers default to `127.0.0.1`, which would make them reachable only inside the container itself. + +### Version Check Disabled + +```yaml +SEMGREP_ENABLE_VERSION_CHECK=0 +``` + +Disables Semgrep version checking. + +This is useful for repeatable container startup and avoids unnecessary outbound checks during service launch. + +### Proxy Configuration + +```yaml +HTTP_PROXY=${HTTP_PROXY} +HTTPS_PROXY=${HTTPS_PROXY} +NO_PROXY=localhost,127.0.0.1,::1,proxy1.it.deakin.edu.au,10.137.0.162,api.mcprouter.to +``` + +The service supports outbound network access via the host proxy environment. + +The `NO_PROXY` list excludes local services and known internal hosts from being routed through the proxy. + +This is especially important when the Semgrep MCP server is being accessed by nearby containers or internal MCP routing services. + +### Repository Mount + +```yaml +volumes: + - /opt/redback/repos:/repos:ro +``` + +Mounts local repositories into the container at `/repos`. + +The `:ro` suffix makes the mount read-only. + +This is recommended for code scanning services because Semgrep only needs to inspect files, not change them. + +### Working Directory + +```yaml +working_dir: /repos +``` + +Sets `/repos` as the default working directory inside the container. + +This allows Semgrep to operate relative to the mounted repository directory. + +### Port Mapping + +```yaml +ports: + - "4004:4004" +``` + +Maps container port `4004` to host port `4004`. + +The service should be reachable from the host at: + +```text +http://localhost:4004 +``` + +Or from another machine/container using the host IP: + +```text +http://:4004 +``` + +### Restart Policy + +```yaml +restart: unless-stopped +``` + +Docker will restart the service automatically unless it has been manually stopped. + +## Example MCP Hub Configuration + +An MCP hub entry may look similar to this: + +```json +{ + "semgrep": { + "type": "http", + "url": "http://semgrep-mcp:4004/mcp" + } +} +``` + +If the MCP hub is not on the same Docker network, use the host IP instead: + +```json +{ + "semgrep": { + "type": "http", + "url": "http://10.137.0.162:4004/mcp" + } +} +``` + +## Docker Network Notes + +If another container needs to reach this service by name, both containers must be on the same Docker network. + +For example, if your MCP hub is running on a network called `mcp-hub_default`, attach this service to that network: + +```yaml +networks: + default: + external: true + name: mcp-hub_default +``` + +Or define the service like this in a compose file that joins the existing network: + +```yaml +services: + semgrep-mcp: + image: returntocorp/semgrep:latest + command: ["semgrep", "mcp", "-t", "streamable-http", "-p", "4004"] + environment: + - FASTMCP_HOST=0.0.0.0 + - SEMGREP_ENABLE_VERSION_CHECK=0 + - HTTP_PROXY=${HTTP_PROXY} + - HTTPS_PROXY=${HTTPS_PROXY} + - NO_PROXY=localhost,127.0.0.1,::1,proxy1.it.deakin.edu.au,10.137.0.162,api.mcprouter.to + volumes: + - /opt/redback/repos:/repos:ro + working_dir: /repos + ports: + - "4004:4004" + restart: unless-stopped + networks: + - mcp-hub_default + +networks: + mcp-hub_default: + external: true +``` + +## Testing the Service + +Start the service: + +```bash +docker compose up -d semgrep-mcp +``` + +Check logs: + +```bash +docker compose logs -f semgrep-mcp +``` + +Check that the container is running: + +```bash +docker ps | grep semgrep-mcp +``` + +Test from the host: + +```bash +curl -v http://localhost:4004/mcp +``` + +Test from another container on the same Docker network: + +```bash +docker exec -it sh +curl -v http://semgrep-mcp:4004/mcp +``` + +If DNS resolution fails, check that both containers are on the same Docker network: + +```bash +docker network inspect mcp-hub_default +``` + +## Troubleshooting + +### MCP hub cannot connect + +Check that Semgrep is listening on all interfaces: + +```yaml +FASTMCP_HOST=0.0.0.0 +``` + +Without this, the service may only listen on `127.0.0.1` inside the container. + +### Connection refused + +Confirm the container is running: + +```bash +docker compose ps +``` + +Check logs: + +```bash +docker compose logs semgrep-mcp +``` + +Check port binding: + +```bash +docker port semgrep-mcp +``` + +### Host can connect, but another container cannot + +Make sure both containers are on the same Docker network. + +Check networks: + +```bash +docker inspect semgrep-mcp | grep -A20 Networks +docker inspect | grep -A20 Networks +``` + +### Proxy issues + +If the MCP hub or Semgrep service is trying to reach local services through the proxy, expand the `NO_PROXY` list. + +Useful entries usually include: + +```text +localhost,127.0.0.1,::1 +``` + +Docker service names may also need to be added, for example: + +```text +semgrep-mcp,mcp-hub +``` + +Example: + +```yaml +NO_PROXY=localhost,127.0.0.1,::1,semgrep-mcp,mcp-hub,proxy1.it.deakin.edu.au,10.137.0.162,api.mcprouter.to +``` + +### Repository path is empty + +Check that the host path exists: + +```bash +ls -la /opt/redback/repos +``` + +Check that files are visible inside the container: + +```bash +docker exec -it semgrep-mcp sh +ls -la /repos +``` + +## Security Notes + +The repository mount is read-only: + +```yaml +/opt/redback/repos:/repos:ro +``` + +This reduces the risk of accidental file modification by the container. + +If exposing this service beyond the local Docker network, place it behind appropriate authentication, firewalling, or reverse proxy controls. + +Avoid exposing the MCP service directly to untrusted networks. + +## Recommended Directory Layout + +```text +/opt/redback/ +└── repos/ + ├── repo-one/ + ├── repo-two/ + └── repo-three/ +``` + +The Semgrep MCP server will see these as: + +```text +/repos/repo-one +/repos/repo-two +/repos/repo-three +``` + +## Minimal Compose File + +```yaml +services: + semgrep-mcp: + image: returntocorp/semgrep:latest + command: ["semgrep", "mcp", "-t", "streamable-http", "-p", "4004"] + environment: + - FASTMCP_HOST=0.0.0.0 + - SEMGREP_ENABLE_VERSION_CHECK=0 + - HTTP_PROXY=${HTTP_PROXY} + - HTTPS_PROXY=${HTTPS_PROXY} + - NO_PROXY=localhost,127.0.0.1,::1,proxy1.it.deakin.edu.au,10.137.0.162,api.mcprouter.to + volumes: + - /opt/redback/repos:/repos:ro + working_dir: /repos + ports: + - "4004:4004" + restart: unless-stopped +``` + +## Operational Notes + +Useful commands: + +```bash +docker compose pull semgrep-mcp +docker compose up -d semgrep-mcp +docker compose logs -f semgrep-mcp +docker compose restart semgrep-mcp +docker compose down +``` + +To update the image: + +```bash +docker compose pull semgrep-mcp +docker compose up -d semgrep-mcp +``` + +To confirm the image currently in use: + +```bash +docker inspect semgrep-mcp --format '{{.Config.Image}}' +``` + +## Summary + +This service provides a containerised Semgrep MCP endpoint for scanning mounted repositories. + +It is designed to: + +- Run as a long-lived Docker service +- Expose MCP over streamable HTTP +- Listen on port `4004` +- Use `/opt/redback/repos` as the host repository root +- Mount repositories read-only +- Support proxy-aware environments +- Integrate with an MCP hub or AI-assisted code analysis stack From a63e777b6bce0779ef00104428c87ca9bde02251 Mon Sep 17 00:00:00 2001 From: trick-1 Date: Fri, 22 May 2026 10:43:44 +0000 Subject: [PATCH 2/2] Added admintools including Docker Migraiton script --- admintools/createuser | 62 + admintools/migration/allapps.csv | 33 + admintools/migration/containermig.sh | 1974 +++++++++++++++++++++++++ admintools/migration/key.txt | 5 + admintools/migration/mig.csv | 6 + admintools/migration/readme.md | 781 ++++++++++ admintools/migration/replacements.txt | 1 + admintools/migration/trialmigrate.sh | 1 + 8 files changed, 2863 insertions(+) create mode 100755 admintools/createuser create mode 100644 admintools/migration/allapps.csv create mode 100755 admintools/migration/containermig.sh create mode 100644 admintools/migration/key.txt create mode 100644 admintools/migration/mig.csv create mode 100644 admintools/migration/readme.md create mode 100644 admintools/migration/replacements.txt create mode 100644 admintools/migration/trialmigrate.sh diff --git a/admintools/createuser b/admintools/createuser new file mode 100755 index 0000000..323f807 --- /dev/null +++ b/admintools/createuser @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Usage: +# sudo ./create_user.sh username +# +# Example: +# sudo ./create_user.sh richard + +if [[ "${EUID}" -ne 0 ]]; then + echo "Please run as root or with sudo." + exit 1 +fi + +if [[ $# -ne 1 ]]; then + echo "Usage: $0 " + exit 1 +fi + +USERNAME="$1" + +# Basic username validation +if [[ ! "$USERNAME" =~ ^[a-z_][a-z0-9_-]*$ ]]; then + echo "Invalid username: $USERNAME" + echo "Use lowercase letters, numbers, underscores, and hyphens only." + exit 1 +fi + +# Check if user already exists +if id "$USERNAME" >/dev/null 2>&1; then + echo "User '$USERNAME' already exists." + exit 1 +fi + +# Ensure docker group exists +if ! getent group docker >/dev/null 2>&1; then + groupadd docker +fi + +# Generate a random password +PASSWORD="$(openssl rand -base64 18 | tr -d '\n' | cut -c1-20)" + +# Create the user with home directory and bash shell +adduser --disabled-password --gecos "" --shell /bin/bash "$USERNAME" + +# Set password +echo "${USERNAME}:${PASSWORD}" | chpasswd + +# Add to docker group +usermod -aG docker "$USERNAME" + +# Optional: force password change at first login +chage -d 0 "$USERNAME" + +echo "----------------------------------------" +echo "User created successfully" +echo "Username : $USERNAME" +echo "Password : $PASSWORD" +echo "Home dir : /home/$USERNAME" +echo "Groups : $(id -nG "$USERNAME")" +echo "----------------------------------------" +echo "They will be asked to change their password at first login." diff --git a/admintools/migration/allapps.csv b/admintools/migration/allapps.csv new file mode 100644 index 0000000..fdded2b --- /dev/null +++ b/admintools/migration/allapps.csv @@ -0,0 +1,33 @@ +container,include_writable_layer,stop_container +airflow,false,false +bugbox_api,false,false +bugbox_compiler,false,false +bugbox_rdb,false,false +bugbox-streamlit,false,false +competent_shtern,false,false +epic_nobel,false,false +goofy_meitner,false,false +grafana,false,false +great_yonath,false,false +happy_swartz,true,false +kafka,false,false +kafka-ui,false,false +mongodb,false,false +nginx.modsecurity,false,false +nice_goodall,false,false +nifty_blackwell,false,false +peaceful_jepsen,false,false +postgres-todd,false,false +quizzical_blackburn,false,false +recursing_booth,false,false +serverpage,false,false +silly_chandrasekhar,false,false +single-node_wazuh.dashboard_1,false,false +single-node_wazuh.indexer_1,false,false +single-node_wazuh.manager_1,false,false +suspicious_kepler,false,false +test-container,false,false +zabbix-server,false,false +zabbix-server-todd,false,false +zabbix-web,false,false +zabbix-web-todd,false,false diff --git a/admintools/migration/containermig.sh b/admintools/migration/containermig.sh new file mode 100755 index 0000000..aa545c1 --- /dev/null +++ b/admintools/migration/containermig.sh @@ -0,0 +1,1974 @@ +#!/usr/bin/env bash +set -Euo pipefail + +SCRIPT_NAME="$(basename "$0")" +OUTPUT_DIR="${PWD}/docker-migration-output" +RUN_ID="$(date +%Y-%m-%dT%H-%M-%S)" + +DEST_HOST="" +DEST_USER="" +DEST_BASE="" +TRANSFER_METHOD="rsync" + +SYNC_DATA=0 +INCLUDE_WRITABLE=0 +STOP_CONTAINERS=0 +VERBOSE=0 +CSV_FILE="" + +SYNC_COMPOSE_FILES=0 +SYNC_BUILD_CONTEXT=0 +GENERATE_MIGRATED_COMPOSE=0 +FINAL_SYNC=0 +FINAL_SYNC_STOP=0 +ENABLE_LOG_CAP=1 + +SSH_KEY="" +SSH_CONTROL_PERSIST="10m" +REPLACE_FILE="" + +WRITABLE_EXCLUDE_REGEX='^/(dev|proc|sys|run|tmp|var/run|var/tmp|etc/hosts|/etc/hostname|/etc/resolv\.conf|/.dockerenv)($|/)' +CERT_FILE_REGEX='.*\.(crt|cer|pem|key|p12|pfx|jks|keystore|csr|ca-bundle|der)$' +CERT_PATH_HINT_REGEX='(cert|certs|certificate|certificates|ssl|tls|pki|letsencrypt|truststore|keystore)' +CERT_ENV_HINT_REGEX='(CERT|CERTIFICATE|TLS|SSL|KEY|KEYSTORE|TRUSTSTORE|CA_BUNDLE|CA_CERT|CLIENT_CERT|CLIENT_KEY)' + +BUILD_CONTEXT_EXCLUDES=( + ".git" + ".svn" + ".hg" + "node_modules" + "__pycache__" + ".venv" + "venv" + ".mypy_cache" + ".pytest_cache" + ".cache" + "dist" + "build" + ".idea" + ".vscode" +) + +TEXT_FILE_EXTENSIONS_REGEX='(\.ya?ml|\.json|\.conf|\.cfg|\.ini|\.env|\.properties|\.txt|\.xml|\.sh|\.py|\.js|\.ts|\.md|Dockerfile)$' + +declare -A CSV_CONTAINER_INCLUDE_WRITABLE=() +declare -A CSV_CONTAINER_STOP=() +declare -A CSV_SELECTED_CONTAINERS=() +declare -a REPLACEMENTS=() + +usage() { + cat <// + docker-compose.orig.yml + docker-compose.yml + .env + working_dir/ + migration/ + report.txt + certificates-report.txt + docker-compose.migration.override.yml + final-sync-report.txt + data/ + / + volumes/ + binds/ + writable/ + +EOF +} + +log() { echo "[INFO] $*"; } +warn() { echo "[WARN] $*" >&2; } +err() { echo "[ERROR] $*" >&2; } +vlog() { [[ "$VERBOSE" -eq 1 ]] && echo "[DEBUG] $*"; } + +require_cmd() { + command -v "$1" >/dev/null 2>&1 || { + err "Required command not found: $1" + exit 1 + } +} + +trim() { + local s="$1" + s="${s#"${s%%[![:space:]]*}"}" + s="${s%"${s##*[![:space:]]}"}" + echo "$s" +} + +lower() { + echo "$1" | tr '[:upper:]' '[:lower:]' +} + +bool_from_string() { + local v + v="$(lower "$(trim "${1:-}")")" + case "$v" in + 1|true|yes|y|on) echo "1" ;; + 0|false|no|n|off|"") echo "0" ;; + *) warn "Unrecognised boolean value '$1', treating as false"; echo "0" ;; + esac +} + +safe_name() { + local s="$1" + s="${s#/}" + s="${s//\//_}" + s="${s//:/_}" + s="${s// /_}" + s="${s//[^A-Za-z0-9._-]/_}" + echo "$s" +} + +ssh_target() { + if [[ -n "$DEST_USER" ]]; then + echo "${DEST_USER}@${DEST_HOST}" + else + echo "${DEST_HOST}" + fi +} + +build_ssh_opts_array() { + local -n _out="$1" + _out=( + -o ControlMaster=auto + -o "ControlPersist=${SSH_CONTROL_PERSIST}" + -o "ControlPath=${HOME}/.ssh/cm-%r@%h:%p" + ) + if [[ -n "$SSH_KEY" ]]; then + _out+=( + -i "$SSH_KEY" + -o IdentitiesOnly=yes + ) + fi +} + +ssh_cmd() { + local target="$1" + shift + local opts=() + build_ssh_opts_array opts + ssh -n "${opts[@]}" "$target" "$@" +} + +scp_cmd() { + local src="$1" + local dst="$2" + local opts=() + build_ssh_opts_array opts + scp "${opts[@]}" "$src" "$dst" < /dev/null +} + +rsync_rsh() { + local opts=() + build_ssh_opts_array opts + local cmd="ssh" + local o + for o in "${opts[@]}"; do + cmd+=" $(printf '%q' "$o")" + done + echo "$cmd" +} + +container_is_running() { + local c="$1" + docker inspect -f '{{.State.Running}}' "$c" 2>/dev/null | grep -qi '^true$' +} + +container_name() { + local c="$1" + docker inspect -f '{{.Name}}' "$c" | sed 's#^/##' +} + +container_image() { + local c="$1" + docker inspect -f '{{.Config.Image}}' "$c" +} + +container_restart_policy() { + local c="$1" + docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' "$c" +} + +container_network_mode() { + local c="$1" + docker inspect -f '{{.HostConfig.NetworkMode}}' "$c" +} + +container_ports_json() { + local c="$1" + docker inspect "$c" | jq '.[0].HostConfig.PortBindings // {}' +} + +container_env_json() { + local c="$1" + docker inspect "$c" | jq '.[0].Config.Env // []' +} + +container_mounts_json() { + local c="$1" + docker inspect "$c" | jq '.[0].Mounts // []' +} + +container_compose_project() { + local c="$1" + docker inspect "$c" | jq -r '.[0].Config.Labels["com.docker.compose.project"] // empty' +} + +container_compose_service() { + local c="$1" + docker inspect "$c" | jq -r '.[0].Config.Labels["com.docker.compose.service"] // empty' +} + +container_compose_workdir() { + local c="$1" + docker inspect "$c" | jq -r '.[0].Config.Labels["com.docker.compose.project.working_dir"] // empty' +} + +container_compose_files() { + local c="$1" + docker inspect "$c" | jq -r '.[0].Config.Labels["com.docker.compose.project.config_files"] // empty' +} + +resolve_container_identifier() { + local ident="$1" + + if docker inspect "$ident" >/dev/null 2>&1; then + echo "$ident" + return 0 + fi + + local found="" + found="$(docker ps -a --format '{{.ID}} {{.Names}}' | awk -v q="$ident" '$2 == q {print $1; exit}')" + [[ -n "$found" ]] && { echo "$found"; return 0; } + + return 1 +} + +project_root_path() { + local project="$1" + echo "${DEST_BASE}/${project}" +} + +project_data_root() { + local project="$1" + echo "${DEST_BASE}/${project}/data" +} + +project_migration_root() { + local project="$1" + echo "${DEST_BASE}/${project}/migration" +} + +container_output_root() { + local c="$1" + local cname + cname="$(container_name "$c")" + echo "${OUTPUT_DIR}/containers/${cname}/${RUN_ID}" +} + +remote_mkdir() { + local path="$1" + if [[ -n "$DEST_HOST" ]]; then + ssh_cmd "$(ssh_target)" "mkdir -p '$path'" + else + mkdir -p "$path" + fi +} + +remote_test_exists() { + local path="$1" + if [[ -n "$DEST_HOST" ]]; then + ssh_cmd "$(ssh_target)" "test -e '$path'" + else + test -e "$path" + fi +} + +remote_du_bytes() { + local path="$1" + if [[ -n "$DEST_HOST" ]]; then + ssh_cmd "$(ssh_target)" "du -sb '$path' 2>/dev/null | awk '{print \$1}'" + else + du -sb "$path" 2>/dev/null | awk '{print $1}' + fi +} + +copy_dir() { + local src="$1" + local dst="$2" + + [[ "$SYNC_DATA" -eq 1 ]] || { vlog "Skipping copy (no --sync-data): $src -> $dst"; return 0; } + [[ -e "$src" ]] || { warn "Source does not exist, skipping copy: $src"; return 0; } + + if [[ -n "$DEST_HOST" ]]; then + remote_mkdir "$dst" + log "Copying directory: $src -> $(ssh_target):$dst" + case "$TRANSFER_METHOD" in + rsync) + rsync -aHAX --numeric-ids --info=progress2 -e "$(rsync_rsh)" "$src"/ "$(ssh_target):$dst"/ < /dev/null + ;; + scp) + tar -C "$src" -cf - . | ssh_cmd "$(ssh_target)" "tar -C '$dst' -xf -" + ;; + *) + err "Unsupported transfer method: $TRANSFER_METHOD" + return 1 + ;; + esac + else + mkdir -p "$dst" + log "Copying directory locally: $src -> $dst" + case "$TRANSFER_METHOD" in + rsync) rsync -aHAX --numeric-ids "$src"/ "$dst"/ ;; + scp) cp -a "$src"/. "$dst"/ ;; + *) err "Unsupported transfer method: $TRANSFER_METHOD"; return 1 ;; + esac + fi +} + +copy_file() { + local src="$1" + local dst="$2" + + [[ "$SYNC_DATA" -eq 1 ]] || { vlog "Skipping file copy (no --sync-data): $src -> $dst"; return 0; } + [[ -e "$src" ]] || { warn "Source file does not exist, skipping copy: $src"; return 0; } + + if [[ -n "$DEST_HOST" ]]; then + remote_mkdir "$(dirname "$dst")" + log "Copying file: $src -> $(ssh_target):$dst" + case "$TRANSFER_METHOD" in + rsync) + rsync -aHAX --numeric-ids -e "$(rsync_rsh)" "$src" "$(ssh_target):$dst" < /dev/null + ;; + scp) + scp_cmd "$src" "$(ssh_target):$dst" + ;; + *) + err "Unsupported transfer method: $TRANSFER_METHOD" + return 1 + ;; + esac + else + mkdir -p "$(dirname "$dst")" + log "Copying file locally: $src -> $dst" + cp -a "$src" "$dst" + fi +} + +copy_dir_filtered() { + local src="$1" + local dst="$2" + + [[ "$SYNC_DATA" -eq 1 ]] || { vlog "Skipping filtered copy (no --sync-data): $src -> $dst"; return 0; } + [[ -d "$src" ]] || { warn "Build context directory does not exist, skipping: $src"; return 0; } + + if [[ -n "$DEST_HOST" ]]; then + remote_mkdir "$dst" + log "Copying build context: $src -> $(ssh_target):$dst" + case "$TRANSFER_METHOD" in + rsync) + local args=( -aHAX --numeric-ids --info=progress2 ) + local ex + for ex in "${BUILD_CONTEXT_EXCLUDES[@]}"; do args+=( --exclude "$ex" ); done + rsync "${args[@]}" -e "$(rsync_rsh)" "$src"/ "$(ssh_target):$dst"/ < /dev/null + ;; + scp) + warn "scp mode does not support excludes; copying whole working directory" + tar -C "$src" -cf - . | ssh_cmd "$(ssh_target)" "tar -C '$dst' -xf -" + ;; + *) + err "Unsupported transfer method: $TRANSFER_METHOD" + return 1 + ;; + esac + else + mkdir -p "$dst" + log "Copying build context locally: $src -> $dst" + case "$TRANSFER_METHOD" in + rsync) + local args=( -aHAX --numeric-ids ) + local ex + for ex in "${BUILD_CONTEXT_EXCLUDES[@]}"; do args+=( --exclude "$ex" ); done + rsync "${args[@]}" "$src"/ "$dst"/ + ;; + scp) cp -a "$src"/. "$dst"/ ;; + *) err "Unsupported transfer method: $TRANSFER_METHOD"; return 1 ;; + esac + fi +} + +record_inventory_header() { + local f="$1" + cat > "$f" < "$f" </dev/null || true)" + case "$mime" in + text/*|application/json|application/xml|application/x-yaml|application/javascript|application/x-sh) + return 0 + ;; + esac + + local base + base="$(basename "$f")" + if [[ "$base" =~ $TEXT_FILE_EXTENSIONS_REGEX ]]; then + return 0 + fi + + return 1 +} + +escape_sed_replacement() { + printf '%s' "$1" | sed -e 's/[\/&]/\\&/g' +} + +apply_replacements_to_file() { + local f="$1" + local report_file="${2:-}" + + [[ "${#REPLACEMENTS[@]}" -gt 0 ]] || return 0 + [[ -f "$f" ]] || return 0 + file_is_probably_text "$f" || return 0 + + local before_hash after_hash + before_hash="$(sha256sum "$f" | awk '{print $1}')" + + local pair old new old_esc new_esc + for pair in "${REPLACEMENTS[@]}"; do + old="${pair%%=*}" + new="${pair#*=}" + old_esc="$(escape_sed_replacement "$old")" + new_esc="$(escape_sed_replacement "$new")" + sed -i "s/${old_esc}/${new_esc}/g" "$f" + done + + after_hash="$(sha256sum "$f" | awk '{print $1}')" + if [[ "$before_hash" != "$after_hash" && -n "$report_file" ]]; then + { + echo "Text replacements applied:" + echo " file: $f" + local p + for p in "${REPLACEMENTS[@]}"; do + echo " replace: $p" + done + echo + } >> "$report_file" + fi +} + +apply_replacements_to_tree() { + local dir="$1" + local report_file="${2:-}" + [[ "${#REPLACEMENTS[@]}" -gt 0 ]] || return 0 + [[ -d "$dir" ]] || return 0 + + while IFS= read -r -d '' f; do + apply_replacements_to_file "$f" "$report_file" + done < <(find "$dir" -type f -print0) +} + +apply_replacements_remote_file() { + local remote_path="$1" + local report_file="${2:-}" + + [[ "${#REPLACEMENTS[@]}" -gt 0 ]] || return 0 + [[ -n "$DEST_HOST" ]] || return 0 + + local before after + before="$(ssh_cmd "$(ssh_target)" "test -f '$remote_path' && sha256sum '$remote_path' | awk '{print \$1}'" 2>/dev/null || true)" + [[ -n "$before" ]] || return 0 + + local script="" + local pair old new old_esc new_esc + for pair in "${REPLACEMENTS[@]}"; do + old="${pair%%=*}" + new="${pair#*=}" + old_esc="$(printf '%s' "$old" | sed "s/'/'\\\\''/g")" + new_esc="$(printf '%s' "$new" | sed "s/'/'\\\\''/g")" + script+="perl -0pi -e 's/\\Q${old_esc}\\E/${new_esc}/g' '$remote_path'; " + done + + ssh_cmd "$(ssh_target)" "$script" + + after="$(ssh_cmd "$(ssh_target)" "test -f '$remote_path' && sha256sum '$remote_path' | awk '{print \$1}'" 2>/dev/null || true)" + + if [[ -n "$report_file" && "$before" != "$after" ]]; then + { + echo "Remote text replacements applied:" + echo " file: $remote_path" + local p + for p in "${REPLACEMENTS[@]}"; do + echo " replace: $p" + done + echo + } >> "$report_file" + fi +} + +apply_replacements_any_file() { + local path="$1" + local report_file="${2:-}" + + if [[ -n "$DEST_HOST" ]]; then + apply_replacements_remote_file "$path" "$report_file" + else + apply_replacements_to_file "$path" "$report_file" + fi +} + +apply_replacements_remote_tree() { + local remote_dir="$1" + local report_file="${2:-}" + + [[ "${#REPLACEMENTS[@]}" -gt 0 ]] || return 0 + [[ -n "$DEST_HOST" ]] || return 0 + + local files + files="$(ssh_cmd "$(ssh_target)" "find '$remote_dir' -type f 2>/dev/null" || true)" + [[ -n "$files" ]] || return 0 + + local f + while IFS= read -r f; do + [[ -n "$f" ]] || continue + case "$f" in + *.yml|*.yaml|*.json|*.conf|*.cfg|*.ini|*.env|*.properties|*.txt|*.xml|*.sh|*.py|*.js|*.ts|*.md|*/Dockerfile|*Dockerfile) + apply_replacements_remote_file "$f" "$report_file" + ;; + esac + done <<< "$files" +} + +apply_replacements_any_tree() { + local path="$1" + local report_file="${2:-}" + + if [[ -n "$DEST_HOST" ]]; then + apply_replacements_remote_tree "$path" "$report_file" + else + apply_replacements_to_tree "$path" "$report_file" + fi +} + +load_replacements_file() { + [[ -n "$REPLACE_FILE" ]] || return 0 + [[ -f "$REPLACE_FILE" ]] || { err "Replacement file not found: $REPLACE_FILE"; exit 1; } + + while IFS= read -r raw_line || [[ -n "$raw_line" ]]; do + local line + line="$(echo "$raw_line" | tr -d '\r')" + line="$(trim "$line")" + [[ -z "$line" ]] && continue + [[ "$line" =~ ^# ]] && continue + [[ "$line" == *"="* ]] || { warn "Skipping invalid replacement line (missing '='): $line"; continue; } + REPLACEMENTS+=("$line") + done < "$REPLACE_FILE" +} + +generate_bind_mount_line_rw() { + local host_path="$1" + local container_path="$2" + local rw="$3" + + if [[ "$rw" == "false" ]]; then + echo " - ${host_path}:${container_path}:ro" + else + echo " - ${host_path}:${container_path}" + fi +} + +generate_logging_override_block() { + cat <<'EOF' + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" +EOF +} + +render_ports_yaml() { + local c="$1" + local ports + ports="$(container_ports_json "$c")" + [[ "$ports" == "{}" ]] && return 0 + + echo " ports:" + echo "$ports" | jq -r ' + to_entries[] + | .key as $container_port + | (.value // []) + | .[] + | " - \"" + ((.HostIp // "") | if . == "" then "" else . + ":" end) + (.HostPort // "") + ":" + $container_port + "\"" + ' +} + +render_env_yaml() { + local c="$1" + local envj + envj="$(container_env_json "$c")" + [[ "$envj" == "[]" ]] && return 0 + + echo " environment:" + echo "$envj" | jq -r '.[] | " - " + .' +} + +render_restart_yaml() { + local c="$1" + local rp + rp="$(container_restart_policy "$c")" + [[ -n "$rp" && "$rp" != "no" ]] && echo " restart: $rp" +} + +render_network_mode_yaml() { + local c="$1" + local nm + nm="$(container_network_mode "$c")" + [[ -n "$nm" && "$nm" != "default" ]] && echo " network_mode: $nm" +} + +inspect_env_for_cert_hints() { + local c="$1" + local cert_report="$2" + + while IFS= read -r envline; do + [[ -z "$envline" ]] && continue + local k="${envline%%=*}" + local v="${envline#*=}" + + if [[ "$k" =~ $CERT_ENV_HINT_REGEX ]] || [[ "$v" =~ $CERT_PATH_HINT_REGEX ]] || [[ "$v" =~ $CERT_FILE_REGEX ]]; then + { + echo "Environment hint:" + echo " container: $(container_name "$c")" + echo " variable: $k" + echo " value: $v" + echo + } >> "$cert_report" + fi + done < <(container_env_json "$c" | jq -r '.[]') +} + +inspect_mounts_for_cert_hints() { + local c="$1" + local cert_report="$2" + + while IFS= read -r m; do + local type source dest + type="$(jq -r '.Type // ""' <<<"$m")" + source="$(jq -r '.Source // ""' <<<"$m")" + dest="$(jq -r '.Destination // ""' <<<"$m")" + + if [[ "$source" =~ $CERT_PATH_HINT_REGEX ]] || [[ "$dest" =~ $CERT_PATH_HINT_REGEX ]] || [[ "$source" =~ $CERT_FILE_REGEX ]] || [[ "$dest" =~ $CERT_FILE_REGEX ]]; then + { + echo "Mount hint:" + echo " container: $(container_name "$c")" + echo " type: $type" + echo " source: $source" + echo " dest: $dest" + echo + } >> "$cert_report" + fi + done < <(container_mounts_json "$c" | jq -c '.[]') +} + +scan_directory_for_cert_files() { + local dir="$1" + local cert_report="$2" + local heading="$3" + + [[ -d "$dir" ]] || return 0 + + local found=0 + while IFS= read -r f; do + if [[ "$found" -eq 0 ]]; then + echo "$heading" >> "$cert_report" + found=1 + fi + echo " $f" >> "$cert_report" + done < <(find "$dir" -type f \( \ + -iname '*.crt' -o -iname '*.cer' -o -iname '*.pem' -o -iname '*.key' -o \ + -iname '*.p12' -o -iname '*.pfx' -o -iname '*.jks' -o -iname '*.keystore' -o \ + -iname '*.csr' -o -iname '*.der' \ + \) 2>/dev/null) + + [[ "$found" -eq 1 ]] && echo >> "$cert_report" +} + +scan_text_file_for_cert_hints() { + local file="$1" + local cert_report="$2" + local heading="$3" + + [[ -f "$file" ]] || return 0 + + local matches + matches="$(grep -Ein 'cert|certificate|ssl|tls|key|keystore|truststore|letsencrypt|ca_bundle|ca_cert|client_cert|client_key' "$file" 2>/dev/null || true)" + if [[ -n "$matches" ]]; then + { + echo "$heading" + echo "$matches" + echo + } >> "$cert_report" + fi +} + +load_csv_selection() { + [[ -n "$CSV_FILE" ]] || return 0 + [[ -f "$CSV_FILE" ]] || { err "CSV file not found: $CSV_FILE"; exit 1; } + + log "Loading container selection CSV: $CSV_FILE" + + local requested_count=0 + local matched_count=0 + local line_no=1 + + while IFS= read -r raw_line || [[ -n "$raw_line" ]]; do + line_no=$((line_no + 1)) + local line + line="$(echo "$raw_line" | tr -d '\r')" + + [[ -z "$(trim "$line")" ]] && continue + [[ "$(trim "$line")" =~ ^# ]] && continue + + requested_count=$((requested_count + 1)) + + IFS=',' read -r col1 col2 col3 extra <<< "$line" + + local container include_writable stop_container resolved cname + container="$(trim "${col1:-}")" + include_writable="$(trim "${col2:-}")" + stop_container="$(trim "${col3:-}")" + + [[ -n "$container" ]] || { warn "Skipping CSV line $line_no with empty container field"; continue; } + + if ! resolved="$(resolve_container_identifier "$container")"; then + warn "Container from CSV not found, skipping: $container" + continue + fi + + cname="$(container_name "$resolved")" + CSV_SELECTED_CONTAINERS["$resolved"]=1 + CSV_SELECTED_CONTAINERS["$cname"]=1 + matched_count=$((matched_count + 1)) + + [[ -n "$include_writable" ]] && { + CSV_CONTAINER_INCLUDE_WRITABLE["$resolved"]="$(bool_from_string "$include_writable")" + CSV_CONTAINER_INCLUDE_WRITABLE["$cname"]="$(bool_from_string "$include_writable")" + } + + [[ -n "$stop_container" ]] && { + CSV_CONTAINER_STOP["$resolved"]="$(bool_from_string "$stop_container")" + CSV_CONTAINER_STOP["$cname"]="$(bool_from_string "$stop_container")" + } + done < <(tail -n +2 "$CSV_FILE") + + log "CSV requested containers: $requested_count" + log "CSV matched containers: $matched_count" + + [[ "$matched_count" -gt 0 ]] || { err "CSV matched zero containers. Nothing will be processed."; exit 1; } +} + +is_container_selected() { + local c="$1" + [[ -z "$CSV_FILE" ]] && return 0 + local cname + cname="$(container_name "$c")" + [[ -n "${CSV_SELECTED_CONTAINERS[$c]:-}" || -n "${CSV_SELECTED_CONTAINERS[$cname]:-}" ]] +} + +container_include_writable() { + local c="$1" + local cname + cname="$(container_name "$c")" + + [[ -n "${CSV_CONTAINER_INCLUDE_WRITABLE[$c]:-}" ]] && { echo "${CSV_CONTAINER_INCLUDE_WRITABLE[$c]}"; return; } + [[ -n "${CSV_CONTAINER_INCLUDE_WRITABLE[$cname]:-}" ]] && { echo "${CSV_CONTAINER_INCLUDE_WRITABLE[$cname]}"; return; } + echo "$INCLUDE_WRITABLE" +} + +container_should_stop() { + local c="$1" + local cname + cname="$(container_name "$c")" + + [[ -n "${CSV_CONTAINER_STOP[$c]:-}" ]] && { echo "${CSV_CONTAINER_STOP[$c]}"; return; } + [[ -n "${CSV_CONTAINER_STOP[$cname]:-}" ]] && { echo "${CSV_CONTAINER_STOP[$cname]}"; return; } + echo "$STOP_CONTAINERS" +} + +stop_container_if_requested() { + local c="$1" + local stop_flag + stop_flag="$(container_should_stop "$c")" + + if [[ "$FINAL_SYNC" -eq 1 && "$FINAL_SYNC_STOP" -eq 1 ]]; then + stop_flag=1 + fi + + if [[ "$stop_flag" -eq 1 ]] && container_is_running "$c"; then + log "Stopping container for consistent capture: $(container_name "$c")" + docker stop "$c" >/dev/null + fi +} + +is_subpath_of() { + local child="$1" + local parent="$2" + + [[ -n "$child" && -n "$parent" ]] || return 1 + [[ -e "$child" && -e "$parent" ]] || return 1 + + local child_real parent_real + child_real="$(readlink -f -- "$child" 2>/dev/null || true)" + parent_real="$(readlink -f -- "$parent" 2>/dev/null || true)" + + [[ -n "$child_real" && -n "$parent_real" ]] || return 1 + + case "$child_real" in + "$parent_real"|"$parent_real"/*) return 0 ;; + *) return 1 ;; + esac +} + +validate_project_destination() { + local project="$1" + local report_file="$2" + + local project_root migrated_compose original_compose override_file + project_root="$(project_root_path "$project")" + migrated_compose="${project_root}/docker-compose.yml" + original_compose="${project_root}/docker-compose.orig.yml" + override_file="${project_root}/migration/docker-compose.migration.override.yml" + + { + echo "Validation for project: $project" + echo " project_root: $project_root" + } >> "$report_file" + + if remote_test_exists "$project_root"; then + echo " project_root_exists: yes" >> "$report_file" + else + echo " project_root_exists: no" >> "$report_file" + fi + + if remote_test_exists "$original_compose"; then + echo " original_compose_exists: yes" >> "$report_file" + else + echo " original_compose_exists: no" >> "$report_file" + fi + + if [[ "$GENERATE_MIGRATED_COMPOSE" -eq 1 ]]; then + if remote_test_exists "$migrated_compose"; then + echo " migrated_compose_exists: yes" >> "$report_file" + else + echo " migrated_compose_exists: no" >> "$report_file" + fi + else + if remote_test_exists "$override_file"; then + echo " override_exists: yes" >> "$report_file" + else + echo " override_exists: no" >> "$report_file" + fi + fi + + echo >> "$report_file" +} + +validate_mount_copy() { + local source_path="$1" + local dest_path="$2" + local label="$3" + local report_file="$4" + + local src_bytes dst_bytes + src_bytes="" + dst_bytes="" + + if [[ -e "$source_path" ]]; then + src_bytes="$(du -sb "$source_path" 2>/dev/null | awk '{print $1}')" + fi + if remote_test_exists "$dest_path"; then + dst_bytes="$(remote_du_bytes "$dest_path" || true)" + fi + + { + echo "Mount validation:" + echo " label: $label" + echo " source: $source_path" + echo " destination: $dest_path" + echo " source_bytes: ${src_bytes:-unknown}" + echo " destination_bytes: ${dst_bytes:-missing}" + } >> "$report_file" + + if [[ -n "$src_bytes" && -n "$dst_bytes" ]]; then + if [[ "$src_bytes" == "$dst_bytes" ]]; then + echo " size_match: yes" >> "$report_file" + else + echo " size_match: no" >> "$report_file" + fi + else + echo " size_match: unknown" >> "$report_file" + fi + + echo >> "$report_file" +} + +copy_stack_files_for_project() { + local project="$1" + local example_container="$2" + local report_file="$3" + local cert_report="$4" + + [[ "$SYNC_COMPOSE_FILES" -eq 1 || "$SYNC_BUILD_CONTEXT" -eq 1 ]] || return 0 + + local workdir config_files + workdir="$(container_compose_workdir "$example_container")" + config_files="$(container_compose_files "$example_container")" + + local project_root + project_root="$(project_root_path "$project")" + + { + echo "Stack file sync:" + echo " project: $project" + echo " working_dir: ${workdir:-unknown}" + echo " compose_files: ${config_files:-unknown}" + echo + } >> "$report_file" + + if [[ "$SYNC_COMPOSE_FILES" -eq 1 ]]; then + if [[ -n "$config_files" ]]; then + IFS=',' read -ra cfarr <<< "$config_files" + local cf + for cf in "${cfarr[@]}"; do + cf="$(trim "$cf")" + [[ -z "$cf" ]] && continue + + local src_cf="$cf" + if [[ ! -f "$src_cf" && -n "$workdir" && -f "$workdir/$cf" ]]; then + src_cf="$workdir/$cf" + fi + + if [[ -f "$src_cf" ]]; then + local dst_cf="${project_root}/docker-compose.orig.yml" + copy_file "$src_cf" "$dst_cf" || warn "Failed copying compose file: $src_cf" + echo "Copied compose file: $src_cf -> $dst_cf" >> "$report_file" + scan_text_file_for_cert_hints "$src_cf" "$cert_report" "Compose file security hints: $src_cf" + apply_replacements_any_file "$dst_cf" "$report_file" + break + else + echo "Compose file not found: $cf" >> "$report_file" + fi + done + fi + + if [[ -n "$workdir" && -f "$workdir/.env" ]]; then + local dst_env="${project_root}/.env" + copy_file "$workdir/.env" "$dst_env" || warn "Failed copying env file: $workdir/.env" + echo "Copied env file: $workdir/.env -> $dst_env" >> "$report_file" + scan_text_file_for_cert_hints "$workdir/.env" "$cert_report" "Environment file security hints: $workdir/.env" + apply_replacements_any_file "$dst_env" "$report_file" + fi + fi + + if [[ -n "$workdir" && -d "$workdir" ]]; then + scan_directory_for_cert_files "$workdir" "$cert_report" "Certificate-like files under working dir: $workdir" + fi + + if [[ "$SYNC_BUILD_CONTEXT" -eq 1 ]]; then + if [[ -n "$workdir" && -d "$workdir" ]]; then + copy_dir_filtered "$workdir" "${project_root}/working_dir" || warn "Failed copying working dir: $workdir" + echo "Copied working directory: $workdir -> ${project_root}/working_dir" >> "$report_file" + apply_replacements_any_tree "${project_root}/working_dir" "$report_file" + else + echo "Working directory unavailable for build-context copy" >> "$report_file" + fi + fi +} + +capture_mounts_for_container() { + local c="$1" + local project_slug="$2" + local service_slug="$3" + local report_file="$4" + local override_file="$5" + local cert_report="$6" + local project_workdir="$7" + local final_sync_report="${8:-}" + + local mounts_json + mounts_json="$(container_mounts_json "$c")" + + inspect_mounts_for_cert_hints "$c" "$cert_report" + + { + echo " - raw mounts json:" + echo "$mounts_json" | jq . + } >> "$report_file" + + if [[ "$mounts_json" == "[]" ]]; then + echo " - no mounts detected" >> "$report_file" + return 1 + fi + + local tmp_vols + tmp_vols="$(mktemp)" + echo " volumes:" > "$tmp_vols" + + local data_root + data_root="$(project_data_root "$project_slug")" + + while IFS= read -r m; do + local type source dest name rw + type="$(jq -r '.Type // ""' <<<"$m")" + source="$(jq -r '.Source // ""' <<<"$m")" + dest="$(jq -r '.Destination // ""' <<<"$m")" + name="$(jq -r '.Name // ""' <<<"$m")" + rw="$(jq -r '.RW' <<<"$m")" + + [[ -n "$dest" ]] || continue + + local mount_slug rel_dir final_host_path + mount_slug="$(safe_name "$dest")" + + log "Inspecting mount for $(container_name "$c"): type=$type source=$source dest=$dest" + + case "$type" in + bind) + if [[ -n "$project_workdir" && "$SYNC_BUILD_CONTEXT" -eq 1 ]] && is_subpath_of "$source" "$project_workdir"; then + local rel_from_workdir + if [[ "$source" == "$project_workdir" ]]; then + rel_from_workdir="." + else + rel_from_workdir="${source#"$project_workdir"/}" + fi + + final_host_path="$(project_root_path "$project_slug")/working_dir/${rel_from_workdir}" + + { + echo " - bind mount already covered by working_dir copy:" + echo " container: $(container_name "$c")" + echo " dest: $dest" + echo " source: $source" + echo " rw: $rw" + echo " migrated_to: $final_host_path" + } >> "$report_file" + + generate_bind_mount_line_rw "$final_host_path" "$dest" "$rw" >> "$tmp_vols" + + if [[ "$FINAL_SYNC" -eq 1 && -n "$final_sync_report" ]]; then + validate_mount_copy "$source" "$final_host_path" "bind-covered:${dest}" "$final_sync_report" + fi + else + rel_dir="${data_root}/${service_slug}/binds/${mount_slug}" + + if [[ -d "$source" ]]; then + final_host_path="$rel_dir" + { + echo " - bind mount (directory):" + echo " container: $(container_name "$c")" + echo " dest: $dest" + echo " source: $source" + echo " rw: $rw" + echo " migrated_to: $final_host_path" + } >> "$report_file" + + copy_dir "$source" "$final_host_path" || warn "Failed copying bind directory: $source" + generate_bind_mount_line_rw "$final_host_path" "$dest" "$rw" >> "$tmp_vols" + + if [[ "$FINAL_SYNC" -eq 1 && -n "$final_sync_report" ]]; then + validate_mount_copy "$source" "$final_host_path" "bind:${dest}" "$final_sync_report" + fi + elif [[ -f "$source" ]]; then + final_host_path="${rel_dir}/$(basename "$source")" + { + echo " - bind mount (file):" + echo " container: $(container_name "$c")" + echo " dest: $dest" + echo " source: $source" + echo " rw: $rw" + echo " migrated_to: $final_host_path" + } >> "$report_file" + + copy_file "$source" "$final_host_path" || warn "Failed copying bind file: $source" + generate_bind_mount_line_rw "$final_host_path" "$dest" "$rw" >> "$tmp_vols" + + if [[ "$FINAL_SYNC" -eq 1 && -n "$final_sync_report" ]]; then + validate_mount_copy "$source" "$final_host_path" "bind-file:${dest}" "$final_sync_report" + fi + else + { + echo " - bind mount (missing source):" + echo " container: $(container_name "$c")" + echo " dest: $dest" + echo " source: $source" + echo " rw: $rw" + echo " action: left unchanged because source path was not found" + } >> "$report_file" + + generate_bind_mount_line_rw "$source" "$dest" "$rw" >> "$tmp_vols" + fi + fi + ;; + + volume) + rel_dir="${data_root}/${service_slug}/volumes/${mount_slug}" + + if [[ -d "$source" ]]; then + final_host_path="$rel_dir" + { + echo " - docker volume:" + echo " container: $(container_name "$c")" + echo " dest: $dest" + echo " volume_name: $name" + echo " source: $source" + echo " rw: $rw" + echo " migrated_to: $final_host_path" + } >> "$report_file" + + copy_dir "$source" "$final_host_path" || warn "Failed copying volume directory: $source" + generate_bind_mount_line_rw "$final_host_path" "$dest" "$rw" >> "$tmp_vols" + + if [[ "$FINAL_SYNC" -eq 1 && -n "$final_sync_report" ]]; then + validate_mount_copy "$source" "$final_host_path" "volume:${name:-$dest}" "$final_sync_report" + fi + elif [[ -f "$source" ]]; then + final_host_path="${rel_dir}/$(basename "$source")" + { + echo " - docker volume (file):" + echo " container: $(container_name "$c")" + echo " dest: $dest" + echo " volume_name: $name" + echo " source: $source" + echo " rw: $rw" + echo " migrated_to: $final_host_path" + } >> "$report_file" + + copy_file "$source" "$final_host_path" || warn "Failed copying volume file: $source" + generate_bind_mount_line_rw "$final_host_path" "$dest" "$rw" >> "$tmp_vols" + + if [[ "$FINAL_SYNC" -eq 1 && -n "$final_sync_report" ]]; then + validate_mount_copy "$source" "$final_host_path" "volume-file:${name:-$dest}" "$final_sync_report" + fi + else + { + echo " - volume source missing:" + echo " container: $(container_name "$c")" + echo " dest: $dest" + echo " volume_name: $name" + echo " source: $source" + echo " action: not copied because source path was not found" + } >> "$report_file" + fi + ;; + + tmpfs) + { + echo " - tmpfs mount:" + echo " container: $(container_name "$c")" + echo " dest: $dest" + echo " action: skipped" + } >> "$report_file" + ;; + + *) + { + echo " - other mount:" + echo " container: $(container_name "$c")" + echo " type: $type" + echo " dest: $dest" + echo " source: $source" + echo " action: reported only" + } >> "$report_file" + ;; + esac + done < <(echo "$mounts_json" | jq -c '.[]') + + if grep -qE '^[[:space:]]+- ' "$tmp_vols"; then + cat "$tmp_vols" >> "$override_file" + rm -f "$tmp_vols" + return 0 + else + rm -f "$tmp_vols" + return 1 + fi +} + +capture_writable_layer() { + local c="$1" + local project_slug="$2" + local service_slug="$3" + local report_file="$4" + local override_file="$5" + local final_sync_report="${6:-}" + + local include_writable + include_writable="$(container_include_writable "$c")" + [[ "$include_writable" -eq 1 ]] || return 1 + + local cname + cname="$(container_name "$c")" + + local diff_lines + diff_lines="$(docker diff "$c" || true)" + + if [[ -z "$diff_lines" ]]; then + echo " - writable-layer: no changed files detected" >> "$report_file" + return 1 + fi + + local data_root + data_root="$(project_data_root "$project_slug")" + + local tmp_writable + tmp_writable="$(mktemp)" + echo " volumes:" > "$tmp_writable" + + while IFS= read -r line; do + [[ -n "$line" ]] || continue + + local action path + action="$(awk '{print $1}' <<< "$line")" + path="$(cut -d' ' -f2- <<< "$line")" + + [[ -n "${path:-}" ]] || continue + [[ "$action" == "D" ]] && continue + + if [[ "$path" =~ $WRITABLE_EXCLUDE_REGEX ]]; then + continue + fi + + case "$path" in + /var/log/*|/var/cache/*|/root/.cache/*|/tmp/*|/run/*) continue ;; + esac + + local item_slug rel_dir local_stage_parent final_host_path staged_item + item_slug="$(safe_name "$path")" + rel_dir="${data_root}/${service_slug}/writable/${item_slug}" + local_stage_parent="${OUTPUT_DIR}/staging/${project_slug}/${service_slug}/writable" + final_host_path="$rel_dir" + staged_item="${local_stage_parent}/$(basename "$path")" + + mkdir -p "$local_stage_parent" + + if docker cp "${c}:${path}" "$local_stage_parent/" >/dev/null 2>&1; then + { + echo " - writable-layer:" + echo " container: $cname" + echo " path: $path" + echo " recovered_to: $final_host_path" + } >> "$report_file" + + if [[ -d "$staged_item" ]]; then + copy_dir "$staged_item" "$final_host_path" || warn "Failed copying writable dir: $staged_item" + elif [[ -f "$staged_item" ]]; then + copy_file "$staged_item" "$final_host_path" || warn "Failed copying writable file: $staged_item" + fi + + echo " - ${final_host_path}:${path}" >> "$tmp_writable" + + if [[ "$FINAL_SYNC" -eq 1 && -n "$final_sync_report" ]]; then + validate_mount_copy "$staged_item" "$final_host_path" "writable:${path}" "$final_sync_report" + fi + else + { + echo " - writable-layer-skip:" + echo " container: $cname" + echo " path: $path" + echo " reason: docker cp failed" + } >> "$report_file" + fi + done < <(printf '%s\n' "$diff_lines") + + if grep -qE '^[[:space:]]+- ' "$tmp_writable"; then + cat "$tmp_writable" >> "$override_file" + rm -f "$tmp_writable" + return 0 + else + rm -f "$tmp_writable" + return 1 + fi +} + +service_has_logging_in_compose() { + local compose_file="$1" + local service="$2" + + [[ -f "$compose_file" ]] || return 1 + + python3 - "$compose_file" "$service" <<'PY' >/dev/null 2>&1 +import sys, yaml +compose_file = sys.argv[1] +service = sys.argv[2] +with open(compose_file, "r", encoding="utf-8") as f: + data = yaml.safe_load(f) or {} +svc = ((data.get("services") or {}).get(service) or {}) +sys.exit(0 if "logging" in svc else 1) +PY +} + +generate_compose_override_for_service() { + local c="$1" + local project="$2" + local service="$3" + local report_file="$4" + local override_file="$5" + local cert_report="$6" + local project_workdir="$7" + local final_sync_report="${8:-}" + local source_compose_for_logging="${9:-}" + + local tmp_service_file + tmp_service_file="$(mktemp)" + + cat > "$tmp_service_file" <> "$tmp_service_file" + { + echo "Docker log cap applied:" + echo " service: $service" + echo " max-size: 10m" + echo " max-file: 3" + echo + } >> "$report_file" + else + { + echo "Docker log cap skipped because logging already defined:" + echo " service: $service" + echo + } >> "$report_file" + fi + fi + + if grep -qE '^[[:space:]]+(volumes:|logging:)' "$tmp_service_file"; then + cat "$tmp_service_file" >> "$override_file" + else + vlog "No override content for service '$service'; skipping empty block" + fi + + rm -f "$tmp_service_file" +} + +copy_project_migration_artifacts() { + local project="$1" + local report_file="$2" + local cert_report="$3" + local override_file="$4" + + local mig_root + mig_root="$(project_migration_root "$project")" + + copy_file "$report_file" "${mig_root}/report.txt" || true + copy_file "$cert_report" "${mig_root}/certificates-report.txt" || true + copy_file "$override_file" "${mig_root}/docker-compose.migration.override.yml" || true +} + +copy_final_sync_report() { + local project="$1" + local local_report="$2" + local dest_report + dest_report="$(project_migration_root "$project")/final-sync-report.txt" + copy_file "$local_report" "$dest_report" || true +} + +generate_migrated_compose_file() { + local project="$1" + local example_container="$2" + local override_file="$3" + local report_file="$4" + + [[ "$GENERATE_MIGRATED_COMPOSE" -eq 1 ]] || return 0 + + local config_files workdir project_root migrated_compose src_compose + config_files="$(container_compose_files "$example_container")" + workdir="$(container_compose_workdir "$example_container")" + project_root="$(project_root_path "$project")" + migrated_compose="${project_root}/docker-compose.yml" + + src_compose="" + if [[ -f "${project_root}/docker-compose.orig.yml" ]]; then + src_compose="${project_root}/docker-compose.orig.yml" + elif [[ -n "$config_files" ]]; then + IFS=',' read -ra cfarr <<< "$config_files" + local cf + for cf in "${cfarr[@]}"; do + cf="$(trim "$cf")" + [[ -z "$cf" ]] && continue + + if [[ -f "$cf" ]]; then + src_compose="$cf" + break + elif [[ -n "$workdir" && -f "$workdir/$cf" ]]; then + src_compose="$workdir/$cf" + break + fi + done + fi + + if [[ -z "$src_compose" || ! -f "$src_compose" ]]; then + err "Could not locate source compose file for project '$project'" + echo "Migrated compose generation failed: source compose file not found" >> "$report_file" + return 1 + fi + + if [[ ! -f "$override_file" ]]; then + err "Override file missing for project '$project'" + echo "Migrated compose generation failed: override file not found" >> "$report_file" + return 1 + fi + + python3 - <<'PY' >/dev/null 2>&1 +import yaml +PY + if [[ $? -ne 0 ]]; then + err "PyYAML is required for --generate-migrated-compose. Install with: apt-get install -y python3-yaml" + echo "Migrated compose generation failed: PyYAML missing" >> "$report_file" + return 1 + fi + + remote_mkdir "$project_root" + + local tmp_py local_out + tmp_py="$(mktemp)" + local_out="${OUTPUT_DIR}/projects/${project}/docker-compose.yml" + mkdir -p "$(dirname "$local_out")" + + cat > "$tmp_py" <<'PY' +import sys +from pathlib import Path +import yaml + +src_compose = Path(sys.argv[1]) +override_file = Path(sys.argv[2]) +out_file = Path(sys.argv[3]) + +with src_compose.open("r", encoding="utf-8") as f: + base = yaml.safe_load(f) or {} + +with override_file.open("r", encoding="utf-8") as f: + override = yaml.safe_load(f) or {} + +base_services = base.get("services", {}) or {} +override_services = override.get("services", {}) or {} +named_volumes_to_prune = set() + +def is_named_volume_ref(vol_entry): + if not isinstance(vol_entry, str): + return None + parts = vol_entry.split(":") + if not parts: + return None + lhs = parts[0] + if lhs.startswith("/") or lhs.startswith("./") or lhs.startswith("../") or lhs.startswith("~"): + return None + return lhs + +for svc_name, ov_svc in override_services.items(): + if svc_name not in base_services: + base_services[svc_name] = {} + base_svc = base_services[svc_name] + + if "volumes" in ov_svc: + for old_vol in base_svc.get("volumes", []) or []: + named = is_named_volume_ref(old_vol) + if named: + named_volumes_to_prune.add(named) + base_svc["volumes"] = ov_svc["volumes"] + + for key, value in ov_svc.items(): + if key == "volumes": + continue + base_svc[key] = value + +base["services"] = base_services +top_vols = base.get("volumes", {}) or {} + +still_referenced = set() +for svc in base_services.values(): + for vol in svc.get("volumes", []) or []: + named = is_named_volume_ref(vol) + if named: + still_referenced.add(named) + +for v in list(named_volumes_to_prune): + if v not in still_referenced and v in top_vols: + del top_vols[v] + +if top_vols: + base["volumes"] = top_vols +elif "volumes" in base: + del base["volumes"] + +with out_file.open("w", encoding="utf-8") as f: + yaml.safe_dump(base, f, sort_keys=False, default_flow_style=False) +PY + + python3 "$tmp_py" "$src_compose" "$override_file" "$local_out" + local rc=$? + rm -f "$tmp_py" + + if [[ $rc -ne 0 || ! -f "$local_out" ]]; then + err "Failed generating merged docker-compose.yml for project '$project'" + echo "Migrated compose generation failed for project '$project'" >> "$report_file" + return 1 + fi + + copy_file "$local_out" "$migrated_compose" + if [[ $? -ne 0 ]]; then + err "Failed copying merged docker-compose.yml to destination for project '$project'" + echo "Migrated compose copy failed for project '$project'" >> "$report_file" + return 1 + fi + + if ! remote_test_exists "$migrated_compose"; then + err "Merged docker-compose.yml not found on destination for project '$project'" + echo "Migrated compose missing on destination for project '$project'" >> "$report_file" + return 1 + fi + + echo "Generated migrated compose: $local_out -> $migrated_compose" >> "$report_file" + log "Generated merged docker-compose.yml: $migrated_compose" + return 0 +} + +write_container_metadata() { + local c="$1" + local project="$2" + local service="$3" + local image="$4" + local workdir="$5" + local config_files="$6" + local out_file="$7" + + cat > "$out_file" <> "$inventory_file" + fi + + { + echo "Docker log cap enabled: $ENABLE_LOG_CAP" + if [[ "$ENABLE_LOG_CAP" -eq 1 ]]; then + echo "Docker log cap policy: max-size=10m, max-file=3" + fi + echo + } >> "$inventory_file" + + local all_containers + mapfile -t all_containers < <(docker ps -a --format '{{.ID}}') + [[ "${#all_containers[@]}" -gt 0 ]] || { warn "No containers found."; exit 0; } + + declare -A PROJECT_CONTAINERS=() + declare -A PROJECT_FIRST_CONTAINER=() + declare -A PROJECT_WORKDIR=() + declare -A PROJECT_SOURCE_COMPOSE=() + declare -a STANDALONE_CONTAINERS=() + local selected_count=0 + + local c + for c in "${all_containers[@]}"; do + if ! is_container_selected "$c"; then + continue + fi + + selected_count=$((selected_count + 1)) + + local project + project="$(container_compose_project "$c")" + if [[ -n "$project" ]]; then + PROJECT_CONTAINERS["$project"]+="${c} " + [[ -z "${PROJECT_FIRST_CONTAINER[$project]:-}" ]] && PROJECT_FIRST_CONTAINER["$project"]="$c" + [[ -z "${PROJECT_WORKDIR[$project]:-}" ]] && PROJECT_WORKDIR["$project"]="$(container_compose_workdir "$c")" + else + STANDALONE_CONTAINERS+=("$c") + fi + done + + { + echo "Run ID: $RUN_ID" + echo "Destination base: $DEST_BASE" + echo "Destination host: ${DEST_HOST:-local only}" + echo "Destination user: ${DEST_USER:-default ssh user}" + echo "Transfer method: $TRANSFER_METHOD" + echo "Sync enabled: $SYNC_DATA" + echo "Global include writable layer: $INCLUDE_WRITABLE" + echo "Global stop containers: $STOP_CONTAINERS" + echo "Sync compose files: $SYNC_COMPOSE_FILES" + echo "Sync build context: $SYNC_BUILD_CONTEXT" + echo "Generate migrated compose: $GENERATE_MIGRATED_COMPOSE" + echo "Final sync: $FINAL_SYNC" + echo "Final sync stop: $FINAL_SYNC_STOP" + echo "Docker log cap enabled: $ENABLE_LOG_CAP" + echo "SSH key: ${SSH_KEY:-default ssh identity}" + echo "CSV filter: ${CSV_FILE:-none}" + echo "Selected containers: $selected_count" + echo + } >> "$inventory_file" + + [[ "$selected_count" -gt 0 ]] || { err "Zero containers selected for processing."; exit 1; } + + log "Containers selected for processing: $selected_count" + log "Processing compose projects..." + + local project + for project in "${!PROJECT_CONTAINERS[@]}"; do + local project_dir report_file override_file cert_report final_sync_report local_merged_compose project_root + project_dir="${OUTPUT_DIR}/projects/${project}" + report_file="${project_dir}/report.txt" + override_file="${project_dir}/docker-compose.migration.override.yml" + cert_report="${project_dir}/certificates-report.txt" + final_sync_report="${project_dir}/final-sync-report.txt" + local_merged_compose="${project_dir}/docker-compose.yml" + project_root="$(project_root_path "$project")" + + mkdir -p "$project_dir" + record_inventory_header "$report_file" + : > "$cert_report" + cat > "$override_file" <<'EOF' +services: +EOF + + PROJECT_SOURCE_COMPOSE["$project"]="${project_root}/docker-compose.orig.yml" + + if [[ "$FINAL_SYNC" -eq 1 ]]; then + write_final_sync_report_header "$final_sync_report" + validate_project_destination "$project" "$final_sync_report" + fi + + { + echo "Compose project: $project" + echo + } >> "$report_file" + + copy_stack_files_for_project "$project" "${PROJECT_FIRST_CONTAINER[$project]}" "$report_file" "$cert_report" || warn "Stack file copy encountered issues for project: $project" + + declare -A PROJECT_SEEN_SERVICES=() + + local pc + for pc in ${PROJECT_CONTAINERS["$project"]}; do + local cname service workdir config_files image + cname="$(container_name "$pc")" + service="$(container_compose_service "$pc")" + workdir="$(container_compose_workdir "$pc")" + config_files="$(container_compose_files "$pc")" + image="$(container_image "$pc")" + + local service_key="${service:-$cname}" + if [[ -n "${PROJECT_SEEN_SERVICES[$service_key]:-}" ]]; then + warn "Skipping duplicate service entry in override for project '$project': $service_key" + continue + fi + PROJECT_SEEN_SERVICES["$service_key"]=1 + + local container_dir container_report container_cert_report container_final_sync_report container_meta + container_dir="$(container_output_root "$pc")" + mkdir -p "$container_dir" + + container_report="${container_dir}/report.txt" + container_cert_report="${container_dir}/certificates-report.txt" + container_final_sync_report="${container_dir}/final-sync-report.txt" + container_meta="${container_dir}/metadata.txt" + + record_inventory_header "$container_report" + : > "$container_cert_report" + if [[ "$FINAL_SYNC" -eq 1 ]]; then + write_final_sync_report_header "$container_final_sync_report" + fi + + { + echo "Container: $cname" + echo " project: $project" + echo " service: $service_key" + echo " image: $image" + echo " workdir: ${workdir:-unknown}" + echo " compose_files: ${config_files:-unknown}" + echo " include_writable_layer: $(container_include_writable "$pc")" + echo " stop_container: $(container_should_stop "$pc")" + echo " timestamp: $(date -Is)" + } >> "$container_report" + + write_container_metadata "$pc" "$project" "$service_key" "$image" "${workdir:-unknown}" "${config_files:-unknown}" "$container_meta" + + inspect_env_for_cert_hints "$pc" "$container_cert_report" + inspect_mounts_for_cert_hints "$pc" "$container_cert_report" + + stop_container_if_requested "$pc" + generate_compose_override_for_service \ + "$pc" \ + "$project" \ + "$service_key" \ + "$container_report" \ + "$override_file" \ + "$container_cert_report" \ + "${PROJECT_WORKDIR[$project]:-}" \ + "$container_final_sync_report" \ + "${PROJECT_SOURCE_COMPOSE[$project]}" + + echo >> "$container_report" + done + + copy_project_migration_artifacts "$project" "$report_file" "$cert_report" "$override_file" + + if [[ "$GENERATE_MIGRATED_COMPOSE" -eq 1 ]]; then + generate_migrated_compose_file "$project" "${PROJECT_FIRST_CONTAINER[$project]}" "$override_file" "$report_file" + fi + + if [[ "$FINAL_SYNC" -eq 1 ]]; then + validate_project_destination "$project" "$final_sync_report" + copy_final_sync_report "$project" "$final_sync_report" + fi + + for pc in ${PROJECT_CONTAINERS["$project"]}; do + local cdir + cdir="$(container_output_root "$pc")" + copy_file "$override_file" "${cdir}/docker-compose.migration.override.yml" || true + [[ -f "$local_merged_compose" ]] && copy_file "$local_merged_compose" "${cdir}/docker-compose.yml" || true + if [[ "$FINAL_SYNC" -eq 1 && -f "$final_sync_report" ]]; then + copy_file "$final_sync_report" "${cdir}/project-final-sync-report.txt" || true + fi + done + + log "Wrote project report: $report_file" + log "Wrote certificates report: $cert_report" + log "Wrote compose override: $override_file" + [[ "$GENERATE_MIGRATED_COMPOSE" -eq 1 ]] && log "Generated merged docker-compose.yml for project: $project" + [[ "$FINAL_SYNC" -eq 1 ]] && log "Generated final sync report for project: $project" + done + + log "Processing standalone containers..." + for c in "${STANDALONE_CONTAINERS[@]}"; do + local cname standalone_report cert_report compose_file container_dir + cname="$(container_name "$c")" + standalone_report="${OUTPUT_DIR}/standalone/${cname}/report.txt" + cert_report="${OUTPUT_DIR}/standalone/${cname}/certificates-report.txt" + compose_file="${OUTPUT_DIR}/standalone/${cname}/compose.generated.yml" + container_dir="$(container_output_root "$c")" + + mkdir -p "${OUTPUT_DIR}/standalone/${cname}" "$container_dir" + record_inventory_header "$standalone_report" + : > "$cert_report" + + { + echo "Standalone container: $cname" + echo "Image: $(container_image "$c")" + echo "include_writable_layer: $(container_include_writable "$c")" + echo "stop_container: $(container_should_stop "$c")" + echo "timestamp: $(date -Is)" + } >> "$standalone_report" + + write_container_metadata "$c" "standalone" "standalone" "$(container_image "$c")" "n/a" "n/a" "${container_dir}/metadata.txt" + + stop_container_if_requested "$c" + + cat > "$compose_file" <> "$compose_file" || true + render_network_mode_yaml "$c" >> "$compose_file" || true + render_env_yaml "$c" >> "$compose_file" || true + render_ports_yaml "$c" >> "$compose_file" || true + if [[ "$ENABLE_LOG_CAP" -eq 1 ]]; then + generate_logging_override_block >> "$compose_file" + fi + + inspect_env_for_cert_hints "$c" "$cert_report" + inspect_mounts_for_cert_hints "$c" "$cert_report" + + copy_file "$compose_file" "${DEST_BASE}/standalone/${cname}/compose.generated.yml" || true + copy_file "$standalone_report" "${DEST_BASE}/standalone/${cname}/report.txt" || true + copy_file "$cert_report" "${DEST_BASE}/standalone/${cname}/certificates-report.txt" || true + + copy_file "$standalone_report" "${container_dir}/report.txt" || true + copy_file "$cert_report" "${container_dir}/certificates-report.txt" || true + copy_file "$compose_file" "${container_dir}/compose.generated.yml" || true + + log "Wrote standalone output for: $cname" + done + + cat </ + docker-compose.orig.yml # preserved original, with requested text replacements applied + docker-compose.yml # merged migrated compose + .env + working_dir/ + migration/ + data/ + +Per-container review output: + ${OUTPUT_DIR}/containers//${RUN_ID}/ + +Project compose artifacts: + ${OUTPUT_DIR}/projects// + +EOF +} + +main "$@" diff --git a/admintools/migration/key.txt b/admintools/migration/key.txt new file mode 100644 index 0000000..d19e69e --- /dev/null +++ b/admintools/migration/key.txt @@ -0,0 +1,5 @@ +no named volumes +no hidden container data +everything bind-mounted +everything visible on disk +everything reproducible diff --git a/admintools/migration/mig.csv b/admintools/migration/mig.csv new file mode 100644 index 0000000..afbb6db --- /dev/null +++ b/admintools/migration/mig.csv @@ -0,0 +1,6 @@ +container,include_writable_layer,stop_container +airflow,false,false +kafka,false,false +kafka-ui,false,false +mongodb,false,false +nginx.modsecurity,false,false diff --git a/admintools/migration/readme.md b/admintools/migration/readme.md new file mode 100644 index 0000000..e0f701e --- /dev/null +++ b/admintools/migration/readme.md @@ -0,0 +1,781 @@ +# Docker Migration Script + +This repository contains a Bash-based Docker container migration helper. It inventories Docker containers on a source host, copies container data to a destination layout, and generates Docker Compose migration artifacts that make it easier to recreate the workloads on another host. + +The script is designed for two common cases: + +1. **Docker Compose projects** — containers that were started by Compose and have Compose labels. +2. **Standalone containers** — containers started directly with `docker run` or another non-Compose method. + +It can copy Docker volumes, bind mounts, Compose files, `.env` files, build contexts, and optionally changed files from a container writable layer. + +--- + +## What the script does + +At a high level, the script: + +- Scans all Docker containers on the current machine. +- Groups Compose-managed containers by Compose project. +- Detects standalone containers separately. +- Inspects container metadata, including: + - image + - restart policy + - network mode + - environment variables + - port bindings + - mounts + - Docker Compose labels +- Copies Docker volumes and bind mounts into a new destination directory tree. +- Converts volume mounts into explicit host bind mounts in generated Compose output. +- Optionally copies: + - original Compose files + - `.env` files + - Compose working directories / build contexts + - writable-layer changes from `docker diff` +- Generates migration reports for each project and container. +- Generates a certificate/security hints report by looking for certificate-like paths, filenames, and environment variables. +- Adds a default Docker JSON log cap unless disabled: + - `max-size: 10m` + - `max-file: 3` +- Can perform text replacements across copied text files, useful for changing hostnames, IPs, paths, or domains during migration. +- Can copy data either locally or to a remote destination over SSH using `rsync` or `scp`. + +--- + +## Important safety notes + +This script helps prepare a migration, but it does **not** guarantee that the migrated containers will run without review. + +Before starting the migrated stack, review: + +- generated `docker-compose.yml` +- generated `docker-compose.migration.override.yml` +- copied `.env` files +- `report.txt` +- `certificates-report.txt` +- file ownership and permissions on the destination +- application-specific secrets and certificates +- external networks that may need to be recreated manually + +If `--stop-containers` or `--final-sync-stop` is used, containers are stopped for consistency. The script does **not** restart them automatically afterwards. + +Use `--include-writable-layer` carefully. Writable layers can contain runtime cache, temporary files, logs, secrets, or state that should really live in volumes. + +--- + +## Requirements + +Run the script on the source Docker host. + +Required commands: + +```bash +docker +jq +find +grep +awk +readlink +python3 +file +sed +sha256sum +``` + +For remote transfers: + +```bash +ssh +rsync # when using --transfer rsync +scp # when using --transfer scp +``` + +For generated migrated Compose merging: + +```bash +python3-yaml +``` + +On Debian/Ubuntu: + +```bash +sudo apt-get update +sudo apt-get install -y docker.io jq rsync openssh-client python3 python3-yaml file coreutils sed grep gawk +``` + +You must have permission to inspect Docker containers and read Docker volume data. In practice, run as root or a user in the `docker` group. + +--- + +## Basic usage + +```bash +chmod +x containermig.sh + +./containermig.sh \ + --dest-base /opt/redback/migrated \ + --sync-data +``` + +This performs a local migration into: + +```text +/opt/redback/migrated/ +``` + +and writes local review artifacts under: + +```text +./docker-migration-output/ +``` + +--- + +## Remote migration example + +Copy all detected containers to another host: + +```bash +./containermig.sh \ + --dest-host new-docker-host.example.com \ + --dest-user root \ + --dest-base /opt/redback/stacks \ + --transfer rsync \ + --sync-data \ + --sync-compose-files \ + --sync-build-context +``` + +This copies data over SSH and writes project directories under: + +```text +/opt/redback/stacks// +``` + +on the destination host. + +--- + +## Recommended full migration command + +For a Compose-heavy Docker host, this is the most useful starting point: + +```bash +./containermig.sh \ + --dest-host new-docker-host.example.com \ + --dest-user root \ + --dest-base /opt/redback/stacks \ + --transfer rsync \ + --sync-data \ + --sync-compose-files \ + --sync-build-context \ + --final-sync \ + --final-sync-stop \ + --verbose +``` + +This will: + +- copy data +- copy Compose files +- copy build context directories +- stop containers during final sync +- generate final validation reports +- generate merged Compose files where possible + +--- + +## Options + +### Core options + +| Option | Description | +|---|---| +| `--dest-host HOST` | Remote destination host. If omitted, files are copied locally. | +| `--dest-user USER` | SSH username for the destination host. | +| `--dest-base PATH` | Required. Base destination path for migrated projects and standalone containers. | +| `--transfer rsync\|scp` | Transfer method. Default: `rsync`. | +| `--sync-data` | Actually copy files and directories. Without this, copy operations are skipped and the script mainly produces reports/artifacts. | +| `--csv FILE` | Process only containers listed in a CSV file. | +| `--verbose` | Enable debug output. | + +### Migration behavior + +| Option | Description | +|---|---| +| `--include-writable-layer` | Capture changed files from container writable layers using `docker diff` and `docker cp`. | +| `--stop-containers` | Stop selected containers before capturing their data. | +| `--sync-compose-files` | Copy original Compose files and `.env` files. Also automatically enables migrated Compose generation. | +| `--sync-build-context` | Copy the Compose working directory/build context, excluding common heavy/generated directories. | +| `--generate-migrated-compose` | Generate a merged `docker-compose.yml` using the original Compose file plus generated migration override. | +| `--final-sync` | Produce final sync validation reports. | +| `--final-sync-stop` | Stop containers during the final sync phase. | +| `--no-log-cap` | Disable automatic Docker JSON log capping in generated Compose output. | + +### Text replacement options + +| Option | Description | +|---|---| +| `--replace-text OLD=NEW` | Replace text in copied text files. Can be repeated. | +| `--replace-file FILE` | Load replacement pairs from a file, one `OLD=NEW` pair per line. Lines beginning with `#` are ignored. | + +Example: + +```bash +./containermig.sh \ + --dest-base /opt/redback/stacks \ + --sync-data \ + --sync-compose-files \ + --replace-text old.example.com=new.example.com \ + --replace-text 192.168.1.10=10.10.20.10 +``` + +Replacement file example: + +```text +# hostname changes +old.example.com=new.example.com + +# IP changes +192.168.1.10=10.10.20.10 +``` + +Use it with: + +```bash +./containermig.sh \ + --dest-base /opt/redback/stacks \ + --sync-data \ + --sync-compose-files \ + --replace-file replacements.txt +``` + +### SSH options + +| Option | Description | +|---|---| +| `--ssh-key PATH` | SSH private key to use. | +| `--ssh-control-persist DURATION` | SSH ControlPersist value. Default: `10m`. | + +Example: + +```bash +./containermig.sh \ + --dest-host new-docker-host.example.com \ + --dest-user root \ + --ssh-key ~/.ssh/id_ed25519 \ + --dest-base /opt/redback/stacks \ + --sync-data +``` + +### Output options + +| Option | Description | +|---|---| +| `--output-dir PATH` | Local output directory. Default: `./docker-migration-output`. | + +--- + +## CSV container selection + +Use `--csv FILE` to migrate only selected containers. + +The script skips the first line, so include a header row. + +Expected columns: + +```csv +container,include_writable_layer,stop_container +``` + +Example: + +```csv +container,include_writable_layer,stop_container +nextcloud,1,1 +postgres,0,1 +redis,0,0 +``` + +The `container` field can be a container name or ID. + +Boolean values accepted: + +```text +1, true, yes, y, on +0, false, no, n, off +``` + +CSV values override the global `--include-writable-layer` and `--stop-containers` options for the listed containers. + +Run with: + +```bash +./containermig.sh \ + --dest-host new-docker-host.example.com \ + --dest-user root \ + --dest-base /opt/redback/stacks \ + --sync-data \ + --csv containers.csv +``` + +--- + +## Destination layout + +For Compose projects, the destination layout is: + +```text +// + docker-compose.orig.yml + docker-compose.yml + .env + working_dir/ + migration/ + report.txt + certificates-report.txt + docker-compose.migration.override.yml + final-sync-report.txt + data/ + / + volumes/ + binds/ + writable/ +``` + +For standalone containers, the destination layout is: + +```text +/standalone// + compose.generated.yml + report.txt + certificates-report.txt +``` + +Local review output is also written to: + +```text +docker-migration-output/ + migration-inventory.txt + projects/ + / + standalone/ + / + containers/ + / + / + staging/ +``` + +--- + +## Generated Compose behavior + +For Compose projects, the script creates a migration override file: + +```text +docker-compose.migration.override.yml +``` + +This override replaces Docker named volumes and bind mounts with explicit host paths under the destination data directory. + +When `--generate-migrated-compose` is enabled, the script merges: + +```text +docker-compose.orig.yml +docker-compose.migration.override.yml +``` + +into: + +```text +docker-compose.yml +``` + +The merged file: + +- updates services with migrated bind mount paths +- adds log capping unless disabled +- removes unused top-level named volumes where possible + +For standalone containers, the script generates: + +```text +compose.generated.yml +``` + +This includes: + +- image +- restart policy +- network mode +- environment variables +- port bindings +- logging cap, unless disabled + +Standalone generated Compose should be reviewed carefully because not every `docker run` option can be reconstructed from inspection data. + +--- + +## Build context copying + +When `--sync-build-context` is enabled, the script copies the Compose project working directory to: + +```text +//working_dir/ +``` + +The following directories are excluded when using `rsync`: + +```text +.git +.svn +.hg +node_modules +__pycache__ +.venv +venv +.mypy_cache +.pytest_cache +.cache +dist +build +.idea +.vscode +``` + +In `scp` mode, these excludes are not applied; the whole working directory is copied. + +--- + +## Certificate and secret hints + +The script writes a certificate/security hints report: + +```text +certificates-report.txt +``` + +It looks for hints in: + +- environment variable names +- environment variable values +- mount paths +- Compose files +- `.env` files +- certificate-like filenames under the working directory + +Detected hints include terms such as: + +```text +CERT +CERTIFICATE +TLS +SSL +KEY +KEYSTORE +TRUSTSTORE +CA_BUNDLE +CLIENT_CERT +CLIENT_KEY +``` + +and file extensions such as: + +```text +.crt +.cer +.pem +.key +.p12 +.pfx +.jks +.keystore +.csr +.der +``` + +This report is informational only. You still need to review whether secrets should be copied, rotated, or handled through a secrets manager. + +--- + +## Final sync mode + +`--final-sync` writes extra validation information, including source and destination size comparisons for copied mounts. + +Use it with `--final-sync-stop` for a more consistent final copy: + +```bash +./containermig.sh \ + --dest-host new-docker-host.example.com \ + --dest-user root \ + --dest-base /opt/redback/stacks \ + --sync-data \ + --sync-compose-files \ + --sync-build-context \ + --final-sync \ + --final-sync-stop +``` + +The final sync report is written to: + +```text +//migration/final-sync-report.txt +``` + +and local review copies are placed under: + +```text +docker-migration-output/ +``` + +--- + +## Typical migration workflow + +### 1. Run an inventory/report-only pass + +```bash +./containermig.sh \ + --dest-base /tmp/docker-migration-review \ + --verbose +``` + +Review: + +```text +docker-migration-output/migration-inventory.txt +docker-migration-output/projects/ +docker-migration-output/standalone/ +docker-migration-output/containers/ +``` + +### 2. Run the real data sync + +```bash +./containermig.sh \ + --dest-host new-docker-host.example.com \ + --dest-user root \ + --dest-base /opt/redback/stacks \ + --transfer rsync \ + --sync-data \ + --sync-compose-files \ + --sync-build-context +``` + +### 3. Review generated files on the destination + +On the destination host: + +```bash +cd /opt/redback/stacks/ +ls -la +cat migration/report.txt +cat migration/certificates-report.txt +docker compose config +``` + +### 4. Run a final stopped sync + +```bash +./containermig.sh \ + --dest-host new-docker-host.example.com \ + --dest-user root \ + --dest-base /opt/redback/stacks \ + --transfer rsync \ + --sync-data \ + --sync-compose-files \ + --sync-build-context \ + --final-sync \ + --final-sync-stop +``` + +### 5. Start the migrated stack + +On the destination host: + +```bash +cd /opt/redback/stacks/ +docker compose up -d +docker compose ps +docker compose logs --tail=100 +``` + +--- + +## Troubleshooting + +### `--dest-base is required` + +Always provide a destination base path: + +```bash +--dest-base /opt/redback/stacks +``` + +### `Required command not found: jq` + +Install missing dependencies: + +```bash +sudo apt-get install -y jq +``` + +### `PyYAML is required for --generate-migrated-compose` + +Install Python YAML support: + +```bash +sudo apt-get install -y python3-yaml +``` + +### Remote SSH copy fails + +Test SSH manually: + +```bash +ssh user@host 'hostname && mkdir -p /tmp/migration-test' +``` + +If using a key: + +```bash +ssh -i ~/.ssh/id_ed25519 user@host 'hostname' +``` + +Then retry with: + +```bash +--ssh-key ~/.ssh/id_ed25519 +``` + +### Generated Compose references missing paths + +Check whether `--sync-data` was used. Without `--sync-data`, the script does not actually copy directories or files. + +### Containers were stopped and not restarted + +This is expected. Restart source containers manually if needed: + +```bash +docker start +``` + +or for Compose projects: + +```bash +docker compose up -d +``` + +### `scp` copied too much build context + +Use the default `rsync` mode if you want the build context excludes to apply: + +```bash +--transfer rsync +``` + +--- + +## Example: migrate one Compose project by selecting containers + +Create `containers.csv`: + +```csv +container,include_writable_layer,stop_container +nextcloud-app,0,1 +nextcloud-db,0,1 +nextcloud-redis,0,1 +``` + +Run: + +```bash +./containermig.sh \ + --dest-host new-docker-host.example.com \ + --dest-user root \ + --dest-base /opt/redback/stacks \ + --transfer rsync \ + --sync-data \ + --sync-compose-files \ + --sync-build-context \ + --final-sync \ + --final-sync-stop \ + --csv containers.csv +``` + +--- + +## Example: local migration with hostname replacement + +```bash +./containermig.sh \ + --dest-base /opt/redback/stacks \ + --sync-data \ + --sync-compose-files \ + --sync-build-context \ + --replace-text old-hostname.local=new-hostname.local \ + --replace-text 192.168.1.50=10.0.20.50 +``` + +--- + +## Notes and limitations + +- The script relies heavily on Docker inspection metadata. +- It cannot perfectly reconstruct every option used in an original `docker run` command. +- Compose-managed containers produce better migration output than standalone containers. +- The script does not recreate external Docker networks automatically. +- The script does not recreate Docker secrets or Swarm-specific configuration. +- The script does not restart containers it stops. +- Some applications require clean shutdowns or application-level backup/restore instead of filesystem copying. +- Database containers should ideally be migrated using database-native dump/restore or stopped during final sync. +- File ownership, SELinux labels, AppArmor profiles, and custom capabilities may need manual review. +- Writable-layer capture is a recovery aid, not a replacement for proper volume design. + +--- + +## Quick command reference + +```bash +# Help +./containermig.sh --help + +# Local migration +./containermig.sh --dest-base /opt/redback/stacks --sync-data + +# Remote migration with rsync +./containermig.sh \ + --dest-host new-host \ + --dest-user root \ + --dest-base /opt/redback/stacks \ + --sync-data + +# Remote migration with Compose files and build context +./containermig.sh \ + --dest-host new-host \ + --dest-user root \ + --dest-base /opt/redback/stacks \ + --sync-data \ + --sync-compose-files \ + --sync-build-context + +# Select containers by CSV +./containermig.sh \ + --dest-base /opt/redback/stacks \ + --sync-data \ + --csv containers.csv + +# Capture writable layer changes +./containermig.sh \ + --dest-base /opt/redback/stacks \ + --sync-data \ + --include-writable-layer + +# Disable generated Docker log caps +./containermig.sh \ + --dest-base /opt/redback/stacks \ + --sync-data \ + --no-log-cap +``` diff --git a/admintools/migration/replacements.txt b/admintools/migration/replacements.txt new file mode 100644 index 0000000..ce440ff --- /dev/null +++ b/admintools/migration/replacements.txt @@ -0,0 +1 @@ +redback.it.deakin.edu.au=10.137.17.254 diff --git a/admintools/migration/trialmigrate.sh b/admintools/migration/trialmigrate.sh new file mode 100644 index 0000000..96269a9 --- /dev/null +++ b/admintools/migration/trialmigrate.sh @@ -0,0 +1 @@ + ./containermig.sh --dest-host 10.137.17.254 --dest-user ejb --dest-base /opt/redback/ejb --sync-data --csv mig.csv --include-writable-layer --sync-compose-files --sync-build-context --verbose --replace-file replacements.txt