From 756a752eeec97efbd02d9417650c85000de8a990 Mon Sep 17 00:00:00 2001 From: Tyler W <7053824+tylrw@users.noreply.github.com> Date: Tue, 24 Oct 2023 14:19:18 -0300 Subject: [PATCH] feat: add Kimai (#1390) * feat: add Kimai * fix: address code review comments * fix: adjust hardcoded app port --- README.md | 1 + apps/kimai/config.json | 45 +++++++++++++++ apps/kimai/docker-compose.yml | 86 +++++++++++++++++++++++++++++ apps/kimai/metadata/description.md | 8 +++ apps/kimai/metadata/logo.jpg | Bin 0 -> 42403 bytes 5 files changed, 140 insertions(+) create mode 100644 apps/kimai/config.json create mode 100644 apps/kimai/docker-compose.yml create mode 100644 apps/kimai/metadata/description.md create mode 100644 apps/kimai/metadata/logo.jpg diff --git a/README.md b/README.md index 37854fbb..e4608f20 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ This is the official repository for the Tipi App Store. It contains all the apps - [Kapowarr](https://github.com/Casvt/Kapowarr) - Kapowarr is a software to build and manage a comic book library, fitting in the \*arr suite of software. - [Kasm Workspaces](https://github.com/kasmtech) - Kasm Workspaces is a docker container streaming platform for delivering browser-based access to desktops, applications, and web services - [Kavita](https://github.com/Kareadita/Kavita) - Kavita is a fast, feature rich, cross platform reading server +- [Kimai](https://github.com/kimai/kimai) - Open source time-tracker - [Libreddit](https://github.com/spikecodes/libreddit) - Private front-end for Reddit - [LibrePhotos](https://github.com/LibrePhotos/librephotos) - Complete photo management service - [LibreTranslate](https://github.com/LibreTranslate/LibreTranslate) - Free and Open Source Machine Translation API. 100% self-hosted, offline capable and easy to setup diff --git a/apps/kimai/config.json b/apps/kimai/config.json new file mode 100644 index 00000000..a091ed33 --- /dev/null +++ b/apps/kimai/config.json @@ -0,0 +1,45 @@ +{ + "name": "Kimai", + "available": true, + "port": 8003, + "exposable": true, + "id": "kimai", + "description": "Kimai is a professional grade time-tracking application, free and open-source. It handles use-cases of freelancers as well as companies with dozens or hundreds of users.", + "tipi_version": 1, + "version": "fpm-2.1.0-prod", + "categories": ["utilities"], + "short_desc": "Open source time-tracker", + "author": "Kevin Papst", + "source": "https://github.com/kimai/kimai", + "website": "https://www.kimai.org", + "form_fields": [ + { + "type": "random", + "label": "KIMAI_DATABASE_ROOT_PASSWORD", + "min": 32, + "env_variable": "KIMAI_DATABASE_ROOT_PASSWORD" + }, + { + "type": "random", + "label": "KIMAI_DATABASE_PASSWORD", + "min": 32, + "env_variable": "KIMAI_DATABASE_PASSWORD" + }, + { + "type": "text", + "label": "Admin email", + "max": 254, + "min": 3, + "required": true, + "env_variable": "KIMAI_ADMINMAIL" + }, + { + "type": "password", + "label": "Admin password", + "max": 128, + "min": 10, + "required": true, + "env_variable": "KIMAI_ADMINPASS" + } + ] +} \ No newline at end of file diff --git a/apps/kimai/docker-compose.yml b/apps/kimai/docker-compose.yml new file mode 100644 index 00000000..3272c5d4 --- /dev/null +++ b/apps/kimai/docker-compose.yml @@ -0,0 +1,86 @@ +# Adapted from https://github.com/tobybatch/kimai2/blob/799a534b8d93d18a2d10573457936a31052ce8e0/compose/docker-compose.fpm.prod.yml + +version: "3.5" + +services: + nginx: + container_name: kimai-proxy + image: tobybatch/nginx-fpm-reverse-proxy:latest + ports: + - ${APP_PORT}:80 + volumes: + - ${APP_DATA_DIR}/data/public:/opt/kimai/public:ro + restart: unless-stopped + depends_on: + - kimai + healthcheck: + test: wget --spider http://kimai-proxy/health || exit 1 + interval: 20s + start_period: 10s + timeout: 10s + retries: 3 + networks: + - tipi_main_network + labels: + # Main + traefik.enable: true + traefik.http.middlewares.kimai-proxy-web-redirect.redirectscheme.scheme: https + traefik.http.services.kimai-proxy.loadbalancer.server.port: 80 + # Web + traefik.http.routers.kimai-proxy-insecure.rule: Host(`${APP_DOMAIN}`) + traefik.http.routers.kimai-proxy-insecure.entrypoints: web + traefik.http.routers.kimai-proxy-insecure.service: kimai-proxy + traefik.http.routers.kimai-proxy-insecure.middlewares: kimai-proxy-web-redirect + # Websecure + traefik.http.routers.kimai-proxy.rule: Host(`${APP_DOMAIN}`) + traefik.http.routers.kimai-proxy.entrypoints: websecure + traefik.http.routers.kimai-proxy.service: kimai-proxy + traefik.http.routers.kimai-proxy.tls.certresolver: myresolver + # Local domain + traefik.http.routers.kimai-proxy-local-insecure.rule: Host(`kimai.${LOCAL_DOMAIN}`) + traefik.http.routers.kimai-proxy-local-insecure.entrypoints: web + traefik.http.routers.kimai-proxy-local-insecure.service: kimai-proxy + traefik.http.routers.kimai-proxy-local-insecure.middlewares: kimai-proxy-web-redirect + # Local domain secure + traefik.http.routers.kimai-proxy-local.rule: Host(`kimai-proxy.${LOCAL_DOMAIN}`) + traefik.http.routers.kimai-proxy-local.entrypoints: websecure + traefik.http.routers.kimai-proxy-local.service: kimai-proxy + traefik.http.routers.kimai-proxy-local.tls: true + + kimai: + container_name: kimai + image: kimai/kimai2:fpm-2.1.0-prod + environment: + - ADMINMAIL=${KIMAI_ADMINMAIL} + - ADMINPASS=${KIMAI_ADMINPASS} + - "DATABASE_URL=mysql://kimai:${KIMAI_DATABASE_PASSWORD}@kimai-sqldb/kimai?charset=utf8&serverVersion=5.7" + - TRUSTED_HOSTS=kimai-proxy,localhost,127.0.0.1 + volumes: + - ${APP_DATA_DIR}/data/public:/opt/kimai/public + - ${APP_DATA_DIR}/data/var:/opt/kimai/var + # - ./ldap.conf:/etc/openldap/ldap.conf:z + # - ./ROOT-CA.pem:/etc/ssl/certs/ROOT-CA.pem:z + restart: unless-stopped + networks: + - tipi_main_network + + sqldb: + container_name: kimai-sqldb + image: mysql:5.7 + environment: + - MYSQL_DATABASE=kimai + - MYSQL_USER=kimai + - MYSQL_PASSWORD=${KIMAI_DATABASE_PASSWORD} + - MYSQL_ROOT_PASSWORD=${KIMAI_DATABASE_ROOT_PASSWORD} + volumes: + - ${APP_DATA_DIR}/data/mysql:/var/lib/mysql + command: --default-storage-engine innodb + restart: unless-stopped + healthcheck: + test: mysqladmin -p$$MYSQL_ROOT_PASSWORD ping -h localhost + interval: 20s + start_period: 10s + timeout: 10s + retries: 3 + networks: + - tipi_main_network diff --git a/apps/kimai/metadata/description.md b/apps/kimai/metadata/description.md new file mode 100644 index 00000000..f5a9cf7c --- /dev/null +++ b/apps/kimai/metadata/description.md @@ -0,0 +1,8 @@ +
+ +
+ +Kimai is a professional grade time-tracking application, free and open-source. It handles use-cases of freelancers as well as companies with dozens or hundreds of users. Kimai was build to track your project times and ships with many advanced features, including but not limited to: + +JSON API, invoicing, data exports, multi-timer and punch-in punch-out mode, tagging, multi-user - multi-timezones - multi-language ([over 30 translations existing](https://hosted.weblate.org/projects/kimai/)!), authentication via SAML/LDAP/Database, two-factor authentication (2FA) with TOTP, customizable role and team permissions, responsive design, user/customer/project specific rates, advanced search & filtering, money and time budgets, advanced reporting, support for [plugins](https://www.kimai.org/store/) and so much more. + diff --git a/apps/kimai/metadata/logo.jpg b/apps/kimai/metadata/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e4d41290c4c822a7aaee133dfc433a6bbd4c76ef GIT binary patch literal 42403 zcmeFYcU)85wk{k%q$na_kRn9s5{iJJNQtOa1*ArbL_oTLC`c!Y6seJ}qC}cBAyNW} zKj9r#r9r`C-C&Eh&!F#@#J0?g$w)D}XZtP21$n#+7H(bg zhpw;CYR0B;0PVKJ4H+*|GNJ~Pb3H!=ue_`&SW$$-1`?=U3# _fa--8+W7_b$M_E}Q~ d?I|Ep`}>TGg56dX>8+ zap!Y(QwEVTM~&V3Y+Gx<#D!Ax&!i=$I1~4ev|8>{Gbx@7bB}5QVcVjJIF<3gZ@%FR zc=JZYe+HxMt-sz^dcV)|bL6>7EBZnV9B1?l|DBR?u^6t~lW~XEn@l35%LTZ`F!y}r z4N_r_?Lho;{fStpObhV&IMs@ASd_QB-_iVux1nn*w!QeD!yz8xom T~dUb5caM1Fb>vNBargZ^nfBDD2`> zIFGb^HS }X0$nA2R8SpenwbVkXfdWaoGrSYJ;;YUMxK4f z5TyK#t9J8s?48H-+n>YD$lbG6oKL( z(BDxCeI}nIo>4}6?CU=jGZ9 CMvrKoZdBR%2!V<}-&uQ@i^7u@Ce{ z+PZg63jM8)-pD8y{)Svd(ZvJp=t=HGA*BM2C+UO7WG=obKeml;^B9wuZEy8SFfEO8 z8E^I?rTK7j0nLiq4D?eYu2TY(u>iJd$p}sk4K#9aC>3O#YTWrodk2h@NbZRBj8>1B z>9gpwqwEw5KjlG$q(yW1dvNb6h1Yz;b+W|ZE81ge66&(yYtQ>{%FEp<;i>~y)Pv%G z-bc(UJ`3%z>k-{11l*lIkQzg?43Z!5XD797qUy5`i}5CN2cZsnmIDEBwV?w=gICgv zTyQJu%0R{xKqDTW?|U!f*lB6v4|hnc)6pD4GJKw`-g3CnhIY9E=wd%GaX58!1u;BC z(KTph@=ovRovR7|nAbXAY0rf0LaJJ)PyA})Po4eX{sdTEY@a|3-S>PmDZ_sIX24~C z;=J}(^IeA9gEEb vz8{8w3o6fds^$%U3EE$ni*iUot7WuLfvv)Kk` Re*TwjQYIE#V%L=jr*1T#4-Qg;?-_<;WQ8D-jXPdf%Ou204|HamY zR#S%nXL|>16huDq;=7fr{#QSv@K0gpj1+)~y eq^$oG`F( fGC{uWI2&rd1_luiy5P40?+yPn-#`G 7olD3O6Bvyg@0uf07cplv1gF4Z;I8O(&a;U78$ zGU*IrS(5@uYc2*AsYKyJP?758d^>!*uZGz@hD?2(3L2xl&gf$3ChR-qt5mqSKvCdW zKGd>5HrGkeav(C-uT+j%OVmgKu6C*-`>3KvidoA|TVdGr1U$ s_Gl9lTeMGyQh;zdZP5QRSuQ8*IXAPMtPDil )$)Nrxafy( zRzBSiwI 3H z!Kc;Pz~-C~;6A3wxZwT2zZ+3wvSQ!tFLf%`R$h11v$-M6z74n9D`r1?(h2_;-UuAU zeZkD6kE;yB+%YFwt9Yjtt%7&4Y<7o)hTCsRxPBa&zc)M$K`lyvPbBu2r`1I4N>uUy zY4k`%g30f;zS#ZbMc;)li!J>iRHGHOCMpoz&sqAv6dIUgq)mVxw0)t~39+8H!h`G$ zr*I7s & ~%{c z@1mS#%g5=GNMU|Ijdy|rxo$0ftq+>qEH79O@T9(m=QzJG-%59bODBGIjqh<`c4>P& zlz42}Qp=wML~@-bwl9QKRITTt_Smn8U-uT@QAI98(I QH zZ|D;09%89k)Hi3*C;vwC|3A3N`^L*~zAu%!MdeRT$~={G>iHG*i-!Nih^Wi28kxNK z!0_o~Ok#`QZvrg1fonO>gy=}HiVBR|i=!rL3Ld~^R{JychKjFU{)|(5m2Z5J@LSd) zX0eWwfLmvqVF~rDe?N%#82ondP^^O=O6pakp@)cqt+?^&)+vee%iK)N*~{zO$@ElS zm5t_&SVzk0T*~6J9I39>lp6j$nFH84a@uLzFf&g4Z#RP@AD@He`Ce>~T&-li;(7dj z`t&ubKX(riNqRvJCc@#tX4OaH?=#|m9IsTwR7}Q>Y~wOM+QD-@ElMlUs@S%X)*&CF z^pk`YP`WolSuN{z`bmk7PvPi_3~5hn^ >nzk_kgSGZSt#UnU-!TEP*;C9 znUcFj?TSNVmVd*xPyM2xOd3zq&{(u{*JDYmmY+Y~4twXAjTy+i7ba9;MFaxU-4yH* zQ>BYCb&Nute}86C7`xw+KEJwzl>)s_iKQ2}D6jo^?R{gR&V7KKJS%3 M%M374dWUd&iaaT#Tp9g-UCCG_&OxkY>S1|}ia8frK_+*^5>|Ruvy~1{{ z|2ci=>&zO=usbx@XC~V}J^YsNcna`h^1{ymMjG26x>*(41d>TvyzJodQUm{&c&`B} zxAqAz-=Dt)CM#C+-f<`5UUxNLjR`@Jo7(xBdAnK1yHrj*<;9O2CmknGu&r|*T?&*W z|14#=HlnEHcnzEqd2i=f? 5e`KbaC7`H%S866n8mgPebKlidD|$5+=Y z&tN|G(3RsL4oon+M9Eusx}JV% TtR1{4WhLQ-|+)5yY5}luu zo;@B%N!3@k(Z@;d@U>SCL$ 6qIWM~ zDT|uz93>pX&xz)J?>GiSMil$54@Yjoa|EN{ce&m4Pknaf)zf0(f){Yh6`5Dd6fzKM z=A(LC^jz9Ju^l19Z=7mIQ_2{MPyb+cXi|6OX_GY93Vv{59M?1o&T6YDIF{RuotM3W z$s0UHuE@_S*Od=0cD~0Oj1hDMPdJr4aEFH&1Y=vXm>nLR6(uE;?#TmqGvi-)laR$h z&8NoeqmP3r$A8EA<^R_qhYmQ_r(*l>zTwaj`p3|9kB*RY2J+ >tPpq0$I+HUp()|X@qa0NJz##OcsSDtw%16IkZmrXlJd` z7PpIhL2_ ga)c*x2S*_kPan z4VZ!5np}~z@Uodj8S<60wQ6h+%8T$jPpg=>o^7?}U=en+#r~Rq|3KDKgKEwbzk<&* zjO^ba8k#r=U$0gSqTuXUK0m6U{Wv5`{dLEYy3^PDW)W$ES3;e2hH?zMHr$k*3ibVp z#!@S>!tN%CAoK&wl4UZaK|0`#3Qx_Heo$Pq;j4O8t`Y>tXly3fm|2kQ8Y+QS9O` zN=33{F@ydoQE`(58&*}}?~f%djD3pPqaE@7OW2#jdKOaYZaPlu7Wz-zvQFn1fpij% zt(PoXj)e^T69o)82HBP9Ak=Wm32>Jqj&Azh{-*v^_hFvOZ+n)#VRPWs0h759PA=?!I5_AJGwuP!VVgU7toN}IvPBUD50Bt?^q1mdL `H}mN_OAIr@i=m2#UH)}*LX zJf6lGc17{%k;zUpfLM; atpj%}|uzLblYF-(59!EV(O( zV8 LHM2R`X^M*^72?Wj$`wmXGwa~6 o43bF2QDI(6_4 zQ%Sk7dzpJ^$8jp#7vsHO8-lyp|L$vtuQc=t3VI8FmcV_xeG!psu S z>+`k{HcubDUc)1(yqjsu_oh&|RN*k&p)HcKe{0sz@lLZ;BeQYK&qg`-wujjKHR21C zAgF7{Z;8AswZ{g=pIrQO+kYtMIKMk%@6#S_Q8RgtaxK?$I(uo-Ln-NHHia%(9oOU_ zX`*KNR*-kU1uC@45pRgt?l(i7sbvl_bw=8dT;4K%d6`E}S477exC6z@hl|*3iDx%O z4EBu9N+}ss &+A%dGK<2l);>%-o^F?Bjx?@ zvxo@ATik?6^n^PsMsnxa27b2Hcd&b908ud#SH4vimKS%=P5#G8tG6UjW-)dNb@aY3 zyrqf|ka^ETsJ)Mk&@kC~@aZ0E;%_na!seVhv;?!I85@e6fSwOjC5F*pzg&_E?H{%0 zWq5VlFLTV%P9m$>Mxdy=c+4iZa^enRO?2yh!)bKqxLD|#Ej;hD@Zjg_W{+CBPq2-) zi- {fvKd=-zD` z{a?XR_2t-^ptCoE3cm~Cva3{^VWSf>)2%Gbl!s)!SzILT+-u#BerbA@gT*QO7+)`r zYN*jWsHbfr^7|%*lljP6qRn7*z=--zQPpSB4R*#O@T<4|&aLFYd#~dF&-Xz9a~A%i z$id;4`GknIf#jPejLVAhH}$TkKI~wIG$o4oLp@p$vc{^SDk445llq#oh8C1O^a%8n zz`q>j@NdHGx;*l=U-%zCbUsLcZcwKSMtZgUa7)rO#@;PCzX_lTAZ|8qZF~iMXQhok zz<1s<_{(>;2buuAalrTA23Cjmf_f6ZKA?5!_J})(c^G_HDPU;zs2(=sj%xabn4CUc zNB@x=&`dh4Z&BI&{94N 24%%tF@@h>DVE2vM^Y%H(Hw*(rIQO%#w7AI#GPKj3t7fbr*T zzKTvwxPQ>I-Y8`u7hBGkx>mxM7LP8Q>fg;E#p)}GF5)f(S`c9o-B#Ju7;t2C3<& zP2|(e8u@&2Y!_0NyS2H@JcF)!q(z83JzJE2#^9v9Saf%^=&fz}xgFaReT!+5sd%Ob zr4LZ`oN(szJ=H(ppIi14n1<%vz09@GKh$_4vSxs7k7z~0H!W_eY781P))kw_X^GyI z7ZnI`Yrl${m>xUvd*)Q+9;mT3WYAoqzPN$dkc{%_X3$tqmyzOfCnX1EUi?lh4KG;K zXd^9U?~!m-K-qpJym4-2q@_ngByD;aHWat3DETTkyyZu(tEC960H!7WsJN(jYGYeH zOsH+qm&feRp5P1Qxj<`bd7$j g78PVn$MJr z$jd)W0{eX(i98o)B`)t4a1!-d+OLRmp0|-2xc9yweM6Y(?5;V}se6O!D`JE!*OO+? z-&87?Azn*BF6bCAnWYf*zbQ=*=W9_6x6L#jNx7+oy_WVZINv#H8s81%lKMkg>RIyK z`C6lw^RE;{;VfI3dFycFpE^b%(zj3{;PEARVnImscwXGsAqFHDe3|WhPR}Ufmo22G zvUaOAWcckiC4}qgw? 2y8E=i#hwj?ju)W&d?U$M8k&;fVN|}SI%R##DxwRWrhQD`n zG4pzXyo>GtwU)rY%AJ63V(bAI w(a5^ya-;1-Vk`j@Mq{7|0{8$7s&u|AquB=@ z`G_Xj$4*XYSjXnK(bxOyVWh#n4zKq0$M{otd!^f?CtK?y$wd_Xde&dOTvE$VuQ|NU za8Bqp@QkxG4!`+ED_`8a5(DZ^PGu2t>|910%LR%f(K}2}x0U1laNc7BxquX=nE>&} zU=kB_5z&xXu4w<~f7uwpY+*C6V3i1pWb!(77Rl;Xg>>HPjpU@UPPz%6{xT)?D)-TJ z?OQ(Q(nb6r*EtPM9q_{WeWP3A-WS!qlxq{E-i6IXz$$-XDu1u>&Ei>CtL|bh`RQrA zzzNHHDHXi9*n$~wscgX1f9 wH5q1^p$ZmU{nS_&7R$5MHaB6>I%RDVh`r0$;--#+wUxrg6x^rKh!Uw zZP4qz83Znt%FOi#^-DC(omuwZ*p*$DbF0c%YK|U7JSJ`XPW+7Z{5f(p9Q!k(a9cC` zC;7yB$*SxhItazLaGV;ma*!l-Bk;?)xIL~Bz6(k7`$ck~(rg-vw$MRd5HR-evK45D z iuiyDY7l{Ek48Ena_bieP?Enfeh(S__ Ki>nvR+p9o zLJ4aDMZ&xsl$rL}pI8<1rC(;%Q>8BOTZtw8pEa*JXzaOKT@e+C>ZzVDL`@||a=kNa zi7#_uc1WnOu(%C3#zW*DoK#gaA40TG=BpK*5*2pH52^mN!1_^O@8Taid4IS|()&eE zoo_DBnlFE34@c!LGP)+k7>cEs52}ZnxuPy5RLHO ( zTfEHaPPv1BShbAL!NQ9DT6)?Nc`32#EbaIux%nvPg+IaKejDv<43 z-Lw1+b{??Y-9o4F58R8ldB`hdx)AyFkddsc&3@K!<_K>3*?ggJ$l^`S!|wc{y(%|z z-J=@ZBZvB$+c_zbCHsoQH^T*r?+vB@{-@?ajQt?(6or47ra-aiL$kRs2KMMbF72Ra zy;Ilmd(7|xawJNDJ7UIzaXEvfPV(H#X5FfoDWQ?9936<}au3=W( +%|5J+G3?){n-jz5v6-hnOcwpaz|I1RIS6YEWo|w`isZq4$#T&ztAYUU9B9=1 ~SsN>+QMgwUmKQ$WA zl-wjv`u*5hqcM>a7hGy1p0*g;B>kaNt5ymdV@Ya1`Wz&>m6UNYUX{0p>7w`5e4teR zC?a@LsJb*$9G(gr+X^rqA@owtHPaXw=+_5;8N_gdc%4O^?~Hn+<_K%-rhuun)uesD zOZ#_BLou>^Xh#36L}Gf2Lr#K A=}XI71X!oH;3fHMjx?4{xWP9Cb0lYgd; z)#OpAIn4&|jvQ+@qNXa}v?Z$>rD@a)tm?dt;h60G)w`b!6Q;2EA3O!|O0}VlV9F{} zj?5^KjJs8)2CS|YH3I^Q5AMrHWo+o2V##b=M|M5U&t0)RmG7D5c0;CO@?G?qpVi|X zOOH)sABX}oh|Vb{)(wftZVw5G ?s5Ig=>ho436 zqW)$%3D{0;k^X9#v;3nY+NdU7^R9W*RWVJW=(wx-$IlMMmkxG|>Ah@ApVO@yehgB? z;5N3Ex|r6G|12x*w^7$6_5S91js9b%-z!*?@_CZdEsM6zoRU5`3+5WXx^Oe4;QLg0 zTcDY9e6gDA*=Hhe;!SMzhb+<{t`}byjV^xbqP E}Pu3Y~y2{kkQe|>)6w<5H*d| z!fNzF^byCgaTEW1(%CD2AjOXC|I{@~_~^80dd+`as2tm0Vf1Fk)8ucNQz`0~ndpS- zwQoZ`<5XnK7CnPdGjfGEJ{sC%S@Q)xg2?i!wlwJJQmLh3t49?U;cAtt`zo~E@C9UV zMPOg 9FWJ#uEQIAY(5!kB=n<|BO z&F{WzvIbK-!jDf2)(2@a&u? lEoM zwJ#Q |aRnhKG!h_l5OwII`p%%;zIXYof zwG~Zhp2J_co~j&z#XMuIB@kBs?)w33xS*thb5Br0Dcb5b86ET#F6N_Wxb^5G)RZ5z z5$s<2>$W1RF%b3e?eDM5wB*N 2V^q$yP)@zW~neu G-V%^O|4aW@x4W{dduc#>cMGhhnx$4Wm{Vi*z4z@K748S@ z)NmjtU*LX``Ej~4&xT H2dxf9W zxaPL^w95PIS8bjIjiETF@){G)?5O-Rz;N=0=BYYLuXtW`b1g*K)*!Pzrco85-k;Ik zK@;b7hht^^kLE}R)EEhvvjivfvY*1GgvP_~AroJIuO)EbV_paDV7;F0D9~j`9Q`$^ zg4`2xH7qC^+1rPvJa{~iY1IFzLZeR4(o@DxV^k)aiG)+oT5uVS{^DtAq;1|YR#@`) zjJM 5)cG=UJ;Puy`@F^+)WCDucBQj^IuiM9_Y(c&w8=~- z68o{|*N6(?{i)Svg~4&X5lWf725p-=tj59tvKS!C*7f67(OR3%Uix~1L0p;HmFJg> z&!2de>)#t!`dCDpzu?g*q|V5}xKggDGowE*aaBR1m=G3sq00Y9;#ad2u%hJ;9rXoR zXcaj 0}lsCY2=iL71)e5xoc1#gHtoH=(r=VWn}STA|*At?|t=^i~fvX3v9;K zPgsh_12g;5SABe(rYHY(Ykqvv4zXTiYXhv>q8Tx;Tj;bGjjBxQ#?L{pEnNv?!3G-4 zg77E184_c$8*^X!cMG41Z)`Z-why~(TK1K@(bNgweVfMEdK9>d*)qhBZ(5K`I6I-C z<=>anIm^+C#meU&F9ay|q(E2b?U4OY3Rfc-XUij*UHE7Wn_KpVK#w%oCmx9OJnjW4 zX;ycP``Ovt1*@sRyc!|0*?wk>PQI;3F|%q=yJ)2D`_2xGa_RrKHO;?ER93-4gniIL zF$H?$P&BtrQfh50GAGpo9DR ucp#p3~Z`RH#PqR4mrjK&oNhMmuUmg)7}y>P vlwu&Fsfz5LUbj2C?)Q~sO%6FcmBqcPR(Ji-k)~{6Pn&ysUP1% $(*rBl+1QpyPenGSxAT|(dODVB1Srt5V-k19XzFL0lE3C^0a~x- z0&u{7n5fBSpeIlX%x}mC0MRaiq4qPz5hQ2g@3r>1F-1|rr|+Z-@rOZ&;=DDlFoMvQ z`i8f-45N}BwEU~)A5oJcaplRXJE`Tl`ugvNQg3Bpbn5cBu&psVeK9(OH}{zRatiw@ zHxjYY*nNp$<#Wh$tMnEP*5jkE? -I|0Wpjq#=x|^MJ_cSSd(2)8bqA$X_6mafkl&N8DSf*R 3V9q0 z|FH~b &A_a2P~xgpw58|6>c?2w8;84) z$gS9WRrrzl`gg}kYQM6nA7{{JyFXS)rE9eio^_GHxarVl4Wq@lS&qCW&W@{6uCjNX zkuyFM8JRfF+L!?O@!{n&Pc)vE#=SfZ`1*+6X?E`kKXEZ1zc6n1HrkOVwy0S=?{t8U zztsn#oJ*VHPq?B>r`{AQKPg*kB)sd{LRNFVTK_S#dku-lJ594r0>4wmhQwn}P?tQ% zi+OUC(u-VE
`7oo;Bz`;cX)1sND7$ z(F3JdbR=12&$^MQzuLV|kRGj)Nw^&Lvf+kiCJVEVA7dXytIO?hn-QIxkia-5WBitO z+SeNvo qT)7Y0sO+?HTtg(Mf^ z=DO_MC`EYI{QIRF5Vs=|a?(nj3KZpx`-g}}oGc1u(IQ55=@FcLxgzm~FTXnB)-sN+ zldDMC @(G-Y#p8i6Dws}7A-K@JLr*GbO8LY2>yke#;<$K1=;6?o3` z`k&27Uu#cNu87_mnaz6vbM6f%Wt?f?oOoqd#U8KvJ-!3I8_&Hq{b9z_GAI6t8%ZD9 zk(t~-Vj1guD8aYuo+lD>J{`|FO?eG6@Gkvl$lr miC5gpL`*A;3pIUS5rYxWD_%`Do|>Ax=c`zDZz3gj?x*+h7p Ty#&N4Yag&?3+&MFAsY};E4s0v+#)qb{^5W9xXDJo=V+TMQZWTm z^0t^0pFX|2nBCPC^3JA@)}wH$KFhCAwTvhc_L+bm*qqgkbbEVQ(YW>MnW88;5slNl z6|3aGq)hNXYD7(wJchjg(2Y)$@&+UQ=ndbhXQK9*GuB+^6;*j)+bin;3hoy8L+9`g z6L4$)(*DzBAJi(pv5 irm zcT|QSIoXAu^w`j%H=|VJmPgN@)x4Y^s0WVSQ-P*ixxL`35laZ(AjDr!5b9ZOsp+ACsYwaKVGc`(11M^-d5~mw zG3rxqymm&eStI2x@eTUiPfPPxM}Ses&$PL^3z;#b{Y3NX8eOb>e6zZZc85~fT`_$T zMH9j+#FR+9B{#ij`m}qz!j~X@`iUig+2h6#5YB)QCxW(vRF|$aSA0MM+&-z4bz7vV zLG*X3VrBH}=K6b|)gAd85+6EVS(Rnwh!8WkIO! Mv%qW5-?-+sd$4Z>F6VZM*_S*14QT}Q4F zm7)aR#5o7~g|{tBegs)#M1y9wHWBUT+eT_j1CeKAbztTM-Pq>7MqAGQJs@$hnWrG# zQNv*u_eP6Yqw7dT@$Q*-VncHa7Oq4}h#{V#N?xbFswy^q)ydwuY*-fhDOG>SY`&Q9 zoY=L&%I)G7=8~Ub# STB>aDoO?wh zr{mY-;C^}3dU$pWr+NHEG4|7^pV8BM`+SAWq4}1m_t&2K{a*WeBowGZ{S;_@bMhE- zdZf!LiK;!a? Z?6!k+iUXlAw*L(jC(Jb9HePols*`6?+kr2I--BR( za5fk_wgrft>|3~|q-ho!hk0wBD@nKHJkN^_uc5d5_C0Faf&ySuK{YxF$yAS0nqI1l z{kdKAG5x&kR1iR@L?!KOY6Rke`?P_zsG#jgxdD?)GWr_v+8~?*ox$|W`j>u_TozWc zCgttSNkb_EJ4-gUbC!4Z5&B>!Q |S zPv(7pFya&Nw4KyKOt*`QJ@C1!0MZQ{I(b~7FKb1o<43Og$)|zIAQuCH)yo1|2{KOa3$7b=M zQvP>G4Ceo1l_0GeKNFg 9M7#V{7kQS9T`u(|`02VO!L~QYWnn9a z&}2?b+_ 2gowBy?@h!_&j}Q8GNHv{fWb^Dbj)T;qwyDpy2MG as zEF 6Y6vZ9rec2ixoh&9RcMrpX$>A(=c5`zt7vG#eOrP zrI{NdepiDVFRvrxVHG&a1@Q?JtBeR_nX!=5_p+swrVNuu1_p HJpV<<+^=~RpeS0W-WIg{dxvU~(K7@4??PnQ4PU}T@f z{w6#7Y^wop+Qka09_=Tl6xkR%o~Gxz*8wY~+>LnUBYGcfK{_52FIX}U2!9#V gqM zuf^;{Fc29}0;tJ JGT;+PWpaP$QgGR&Zi|@b8~AaKSgKd-LFysm$;f~sU<)}%tj=OW!skaq1D`ys zwF#&Ccr4h?)w?w|+^z*aSAbZ)H}r?@bzyruHC+|#3SiEVPjL;?DP9!QTpa0sk}mI1 zapMcbEcesB3Z1LgQggX*S5SavUe~j{qrd&MV16HL6IC#CPAXQ*J^wUetKf;Qkz%5G zRk2H!;iCJHuP4^6LxC}8+tc#X^JV_r0q-6Y+riAfa$Q%@gPLnzypiwwROz2j^CJV$ zvfi~|Vge~{khM H;eHPHyelquU)*-DCp(gT&Y-NtY~nbbYzoGqaJJPhh*J zmv2qyB)@XbJ0d7tIcH>GzD_!#-4ec#H%W`c%?DUK(zSxP2Hx1D0lU~xj6()lz^37C z<@9-stEq8!#Qi?$ZCqwGACBt9<$s7+4Vub7^E5wasZXpTM}(D?&Jg1FcOTMIhz@88 zYX==Bfy@%lca?F4w1&COFXkL_=1+{l@-{@))03*6o+ooye7sjL z8VNf; 04k&T)ZEI!uhw# z0YO-A2^P;CW~`MQajsnWyLfK+u;DA~-^FvmTuO|Vhr6yp$-%`OQlHkMR!j{M&Y74- zP#4YAJ|x;zAV)_?GD8-MQc=SVyhKRh{go4shljtz7Q&r9X=NV{(EHo6>X7yRTn^8L zhz0#ekJbPS?dNkK`K0@wU&sHF- pczdG9RM>TcHtF5nvFK0qN<* zHeC3n9bPiou&VRxS5at$i15MohTtD*2ka+AO=c1%yHFYc@5Ub|Ns4ta@}WP;vl$ zgTgW*R;Mz%ZYJC8 0QCMDgN$n8!BGmn+mBEyiLt(Yul%7r`{W5tmarcr}(xeCU-85T=e)e1)0ay0m` zz2;trs@znL363Q&`r&vRE}8CGaVK)D`1`>0!P{R@IB9IU;K$cr1uI{d5oNgPaeksb zz*S)_j^A*lD?lp!bdiWq)t!=q ?;N*gW{KSv9O zQ35_T=9~YxRcF%1uL>A`d0+w&N#Iv@dOz@Nz)|G z#1G}Vk8H}e#geau11Wuk&*qENl%J2%#7OcxGMUQHspLO_G6r`hn1uvpk}r>Ddp z!8giOUj(=-9zB3f8+`pk_fIR@E9LQqEPb)1Z>&!N5QOiP5Xs-OFNBfj@_ec~X(_ zAdZej!3%2(iPRtLq*_&w{DY&VOv (Vf&Uq$z=Z`B*MIHiseQu+O77A 2X0eVoQ9FE@^->C<5 ypM$XQ#pla)&w{PaGTwL z|ALeNW63Yy9 YMBsIZ^rCUs7^C~N%s?1 zA>0?8alR$vfAa$I4_(YO_y61+>?08c 2Ky}$9N1lEQ)AF$Xr7GNcS&LYwgBehu&Q_8JH zZjbCh`bzIyyz4=nsrb4ldC>DftUkPsKX-7*f5vQ;yW-X-p$qmlC#E!vyX#?2B_!}R zO=i @Y%f-5hS~^1iTZ!uzuf*}{e~>8)Btn0DU#bFNW^A^ap!6?qD{R5AFFakPz1 zJM@F^nqT|XUKCjWXjhw9e(>>A!uu2NBeZ}VoCkmk^bq@+%UKC2&oYPAjj;EFi;}aF z^z%J6Nn7304`T}xTF&1|t9_u+t*N-i-bGS&w !iR&P>sj=U~R#OgH2p*83R-qpEseH2KplUv@_#ZGrX$WjKC z7AmNIZ<)udDJ5V=tjH(GtMZcCt8x}^Z Xv{NPRA!Sjl@c3v5&lOs)S=A{(YnPdj|7UYbPEf9>@O5(H<;Wr&y_j)*BUeyp3 zS@T(M=cDP$pSWeDV!AtOp8zG|$KN {YOUbFNL>Cg#C!8cSx zE3N0$bCp7%ANCrQuD>iB$FGAK`jMlZ1*?TWt)rf wh1 zryvqEOS$S _M*`J?-8WtlM$sdhYv>UnI`1?u0+gt%0F;)=qaJeyO zsu~4%2I%>LJ1>CD`lDb^*+CBy8 mfI~f05 zkZo+%!D_7OrGNS2V+OCm7t1Uacov~VN^b*k53o5Ov|poCmlaYnnGBED49w%@- 7@%Y)52Vsu69KIrEkgV7_MU+$hojG~o*E}VDQ2o(GcofF1 zxT5Eg6|=&3NFWx4Ba1?pY;+B*01fYxVuDl6V0QZtm3R#_m!vLm7F|UARtWId%8kc- zvzAwk0vj3a@Oa|HODU=|cJXro$`gC938ciNiuq(Iw5(I31i(7wt?ihUY|n$6O%cPF zEjCa`Bl48`F8H^_uvk4&QuT!A>>lJSwMRMevrM(eObt+?uCMN4p6K^$Gl^U$%6s); z{S^&V0kZRke0^YQ28~91{lsJ7WHiyV>3Co9-4B5Fh;Jn5h}1SW9T`}|>~<}_+j73# z?%m fu}87zdtv+iF>?)clgZ1{Wc8Ze0TXKQhaF!tw_m8RbujM91<&eCnQB;t#(#hfOa^ zp4$n0df`|9aQ&C|-`{*XI6ME#zw2s&JeLlI^`|$M0{*asJVRzs1m`fbWn#?=N&+tI zGVuIZMwIwS2&=RFEGIQ0CAQJ$oE7$l%iwB>VNub!)STkv)Sn`g(ECs|M)Dm~q#~sm zJo}Bbi?>5SNx{fyuY#~)yDmt~UODGj*0v3!uc*u;d(^eDl9QPumMY+UU|ycTfy#sj znWzFRCZlb3%sqm+WUtl&bsFFq{E(E$(eD!wzl_Z7-WTP?_Df+`AIg@i4>_y#9t2_! zG1%pM(oC?cj#CEv`1&j)R&=Y6N>&fEIHkj7H1-ZP?CYWa2$mKZr#$DeU@oZRW15I* zjhZQKx~n9Fhsa&W+h5VF0y;05f1L8itQzDn6(=2ZRjjy2Es*1rZZ7)+J%QY~j9V$L z2QE8L-}|MHOrMj#Yx;*ycKVJG65$C|n6UU&)#E~?Op sO5_@O%0spsO)NPydQSg((U}P6xv>o|@ z=Bp$9>Ebjy>DT_t`<|z2Y8!iB+IFzfKYhu7G4rmqpl~w!QQC z;AnQcVc(WY#Lnzk^3E}gG595_r$I`{AG&37|1kcimf7D-{x8%2w{ZdVhlDgxQ;DKb zi)?~e^5UGEKHWaOHsYZV;fy58TK<&?=W)ly;RU>({uGjEftne)705R2#&lu8MS4=q z8PB7UI~zM H};!6cZimox9sSTze+sy%d+U+QIg2 z*mUuqWt<0qBq_vK(8{vod3ruD1-jBysx7F{VeH0-Q{w~FxN5DYD#2ARd*$cGve@aL z0`!8x-p2Yp7m}2Z?ChHOEaR<#m5hJ}jEvV5fh+q)yDIHwO@&%(m3{nyvWEu@H^Q_t zr|?N{$O$AHkUm#~Zed-HAh{v<61I{op(NCTMR`o{zeCncJSlMhuB}Y<5XzpQjyW@6 zL6q7|dg&~rE!ibJoB`itHPmn<7DA7{0xj^Od|+;Qto~3|<$a!wUb^7@q!I#PjPb_u z0*xZ^=Hv$3@dds8 ?2j@qmpHf? zZU~6Mi$)#|b}z$UH56MvSnNucq*71?;B{A0;sMjCKxP1BfbIJ%Q@hv#$cH~)OR#I^ zSr3QL5+C~F$HbZT)s%U31?b Ok d(Hb;%u-M(x)!5DY^UAcDaegAk*+mi4*J=KQV{?gm57x7b5__0yL=D83YK zsPMn~Sdu#OeLlWdZdiY>;WZ$NhKZLWr3q;wu`~rAw56Dsrn|1*=DwqvSBR;9*7&U^ z`3^@!jyf+!7+)+@T8rAMK`E5=^@Q?fjHe+z%NiChsvrEK^sYxR%-)q7xb;mtCMzs@ z?6XW~XGtpR1{Xe>1w84hgJ?5d>AN^