From ea3a47fe20ce5f1254b1d0639881b4095eae4509 Mon Sep 17 00:00:00 2001 From: Vassiliy Yegorov Date: Fri, 7 May 2021 14:04:17 +0700 Subject: [PATCH] init --- .env.example | 86 +++++++++++++++ .gitignore | 5 + README.md | 26 +++++ docker-compose.yml | 234 ++++++++++++++++++++++++++++++++++++++++ fix-unicorn.sh | 4 + ssl-certs/ssl-certs.zip | Bin 0 -> 8679 bytes 6 files changed, 355 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100755 fix-unicorn.sh create mode 100644 ssl-certs/ssl-certs.zip diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6eee92f --- /dev/null +++ b/.env.example @@ -0,0 +1,86 @@ +# Service name +# +SERVICE_NAME=gitlab + +# Container names +# Summary container name in docker-compose.yml will be "${SERVICE_NAME}_${CONTAINER_NAME-*}" +# +CONTAINER_NAME_GITLAB=server +CONTAINER_NAME_PGSQL=pgsql +CONTAINER_NAME_REDIS=redis +CONTAINER_NAME_REGISTRY=registry +CONTAINER_NAME_RUNNER=runner + +# Docker images +# +DOCKER_IMAGE_GITLAB=sameersbn/gitlab:latest +DOCKER_IMAGE_PGSQL=sameersbn/postgresql:latest +DOCKER_IMAGE_REDIS=sameersbn/redis:latest +DOCKER_IMAGE_REGISTRY=registry:latest +DOCKER_IMAGE_RUNNER=vasyakrg/gitlab-runner + +# SMTP settings +SMTP_ENABLED=true +SMTP_DOMAIN= + +SMTP_HOST=smtp.mailgun.org +SMTP_PORT=587 +SMTP_USER= +SMTP_PASS= +SMTP_STARTTLS=true +SMTP_AUTHENTICATION=login + +GITLAB_EMAIL=noreply@ +GITLAB_EMAIL_REPLY_TO=noreply@ +GITLAB_INCOMING_EMAIL_ADDRESS=noreply@ + +# Gitlab domain name +# +GITLAB_HOST=gitlab. + +# Gitlab ssh public port +# +GITLAB_SSH_PORT=10022 + +# Gitlab root user password +# Use only when clear install +# +GITLAB_ROOT_EMAIL= +GITLAB_ROOT_PASSWORD= + +# Docker registry domain name +# +REGISTRY_HOST=docker. +# DB credentials +# +DB_USER=gitlab +DB_PASS= +DB_NAME=gitlab_production + +# Container data path on the host +# Summary container data path will be "${SERVICE_DATA}/${SERVICE_NAME}" +# +SERVICE_DATA=/srv/services/data + +# Email for letsencrypt +# +LETSENCRYPT_EMAIL= + +# Gitlab runner token +# +RUNNER_TOKEN= + +GITLAB_TIMEZONE=Asia/Novosibirsk + +# Runner on the same host with gitlab +# +CI_SERVER_WITH_RUNNER=true + +# Network names +# +#SERVICE_NETWORK=gitlab +WEBPROXY_NETWORK=webproxy + +GITLAB_SECRETS_DB_KEY_BASE= +GITLAB_SECRETS_SECRET_KEY_BASE= +GITLAB_SECRETS_OTP_KEY_BASE= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..06c120c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.env +*.pem +*.crt +*.key +*.csr diff --git a/README.md b/README.md new file mode 100644 index 0000000..a2e74f4 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +## Gitlab in docker + +Полноценная сборка сервера Gitlab, его базы на psql, 4х раннеров и своего docker-registry, разворачеваемая на докер-хосте + +1. переименовываем `.env.example` в `.env` +2. заполняем по максимому внимательно все переменные (кроме `RUNNER_TOKEN=`) +3. распаковываем в папке ssl-certs сертификаты и кладем там же (сертификаты noname и нужны лишь для внутреннего взаимодействия между gitlab и registry компонентами) +4. запускаем сборку `docker-compose up -d` +5. когда сервер запустится, вы войдете в систему под рутом, надо сходить в раздел раннеров (/admin/runners) и подсмотреть там токен, который и нужно будет заполнить в переменной `RUNNER_TOKEN=` и снова запустить `docker-compose up -d`, после чего раннеры перезапустятся и зарегистрируються в системе. + +Подразумевается, что у вас есть `домен` и вы уже создали два поддомена `docker` и `gitlab` +Подразумевается, что и гитлаб и регистри будут работать через один порт 443 +Подразумевается, что у вас уже есть webproxy или traefik, которые возьмут на себя ингрессы контейнеров и выдачу (обновление) им сертификатов +(сеть webpоxy как раз комментирована по этому - ее надо будет раскоментировать по свои условия) + +`labels` у контейнеров подготовлены, если у вас traefik, раскомментите эти поля + +`runner` - костомизирован только тем, что в нем встроена система авторегистрации на сервере. + +## Автор \ Author + +- **Vassiliy Yegorov** [vasyakrg](https://github.com/vasyakrg) +- [youtube](https://youtube.com/realmanual) +- [site](https://vk.com/realmanual) +- [telegram](https://t.me/realmanual) +- [any qiestions for me](https://t.me/realmanual_group) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5f14d46 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,234 @@ +version: '3.7' + +services: + gitlab: + image: ${DOCKER_IMAGE_GITLAB} + container_name: ${SERVICE_NAME}_${CONTAINER_NAME_GITLAB} + restart: always + depends_on: + - postgresql + - redis + ports: + - "${GITLAB_SSH_PORT}:22" + expose: + - 80 + # labels: + # - "traefik.enable=true" + # - "traefik.http.routers.gitlab-server.entrypoints=https" + # - "traefik.http.routers.gitlab-server.rule=Host(`${GITLAB_HOST}`)" + # - "traefik.http.routers.gitlab-server.tls=true" + # - "traefik.http.routers.gitlab-server.tls.certresolver=letsEncrypt" + # - "traefik.http.services.gitlab-server-service.loadbalancer.server.port=80" + # - "traefik.docker.network=webproxy" + volumes: + - ${SERVICE_DATA}/${SERVICE_NAME}/gitlab:/home/git/data:Z + - ${SERVICE_DATA}/${SERVICE_NAME}/certs:/certs + environment: + - DEBUG=false + + - DB_ADAPTER=postgresql + - DB_HOST=${SERVICE_NAME}_${CONTAINER_NAME_PGSQL} + - DB_PORT=5432 + - DB_USER=${DB_USER} + - DB_PASS=${DB_PASS} + - DB_NAME=${DB_NAME} + + - REDIS_HOST=${SERVICE_NAME}_${CONTAINER_NAME_REDIS} + - REDIS_PORT=6379 + + - TZ=UTC + - GITLAB_TIMEZONE=${GITLAB_TIMEZONE} + + - GITLAB_HTTPS=false + - SSL_SELF_SIGNED=false + + - GITLAB_HOST=${GITLAB_HOST} + - GITLAB_PORT=80 + - GITLAB_SSH_PORT=${GITLAB_SSH_PORT} + - GITLAB_SECRETS_DB_KEY_BASE=${GITLAB_SECRETS_DB_KEY_BASE} + - GITLAB_SECRETS_SECRET_KEY_BASE=${GITLAB_SECRETS_SECRET_KEY_BASE} + - GITLAB_SECRETS_OTP_KEY_BASE=${GITLAB_SECRETS_OTP_KEY_BASE} + + - GITLAB_ROOT_PASSWORD=${GITLAB_ROOT_PASSWORD} + - GITLAB_ROOT_EMAIL=${GITLAB_ROOT_EMAIL} + + - GITLAB_NOTIFY_ON_BROKEN_BUILDS=true + - GITLAB_NOTIFY_PUSHER=false + + - GITLAB_EMAIL=${GITLAB_EMAIL} + - GITLAB_EMAIL_REPLY_TO=${GITLAB_EMAIL_REPLY_TO} + - GITLAB_INCOMING_EMAIL_ADDRESS=${GITLAB_INCOMING_EMAIL_ADDRESS} + + - GITLAB_PAGES_ENABLED=false + + - SMTP_ENABLED=true + - SMTP_DOMAIN=${SMTP_DOMAIN} + - SMTP_HOST=${SMTP_HOST} + - SMTP_PORT=${SMTP_PORT} + - SMTP_USER=${SMTP_USER} + - SMTP_PASS=${SMTP_PASS} + - SMTP_STARTTLS=${SMTP_STARTTLS} + - SMTP_AUTHENTICATION=${SMTP_AUTHENTICATION} + + - IMAP_ENABLED=false + - LDAP_ENABLED=false + + - GITLAB_REGISTRY_ENABLED=true + - GITLAB_REGISTRY_HOST=${REGISTRY_HOST} + - GITLAB_REGISTRY_API_URL=http://registry:5000/ + - GITLAB_REGISTRY_KEY_PATH=/certs/registry.key + healthcheck: + test: ["CMD", "/usr/local/sbin/healthcheck"] + interval: 1m + timeout: 5s + retries: 5 + start_period: 2m + networks: + # - webproxy + - service + + registry: + image: ${DOCKER_IMAGE_REGISTRY} + container_name: ${SERVICE_NAME}_${CONTAINER_NAME_REGISTRY} + restart: always + expose: + - 5000 + # labels: + # - "traefik.enable=true" + # - "traefik.http.routers.gitlab-registry.entrypoints=https" + # - "traefik.http.routers.gitlab-registry.rule=Host(`${REGISTRY_HOST}`)" + # - "traefik.http.routers.gitlab-registry.tls=true" + # - "traefik.http.routers.gitlab-registry.tls.certresolver=letsEncrypt" + # - "traefik.http.services.gitlab-registry-service.loadbalancer.server.port=5000" + # - "traefik.docker.network=webproxy" + volumes: + - ${SERVICE_DATA}/${SERVICE_NAME}/gitlab/shared/registry:/registry + - ${SERVICE_DATA}/${SERVICE_NAME}/certs:/certs + environment: + - REGISTRY_AUTH_TOKEN_AUTOREDIRECT=false + - REGISTRY_LOG_LEVEL=debug + - REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/registry + - REGISTRY_AUTH_TOKEN_REALM=https://${GITLAB_HOST}/jwt/auth + - REGISTRY_AUTH_TOKEN_SERVICE=container_registry + - REGISTRY_AUTH_TOKEN_ISSUER=gitlab-issuer + - REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE=/certs/registry.crt + - REGISTRY_STORAGE_DELETE_ENABLED=true + networks: + # - webproxy + - service + + postgresql: + image: ${DOCKER_IMAGE_PGSQL} + container_name: ${SERVICE_NAME}_${CONTAINER_NAME_PGSQL} + restart: always + environment: + - DB_USER=${DB_USER} + - DB_PASS=${DB_PASS} + - DB_NAME=${DB_NAME} + - DB_EXTENSION=pg_trgm + volumes: + - ${SERVICE_DATA}/${SERVICE_NAME}/postgresql:/var/lib/postgresql:Z + networks: + - service + + redis: + restart: always + image: ${DOCKER_IMAGE_REDIS} + container_name: ${SERVICE_NAME}_${CONTAINER_NAME_REDIS} + command: + - --loglevel warning + volumes: + - ${SERVICE_DATA}/${SERVICE_NAME}/redis:/var/lib/redis:Z + networks: + - service + + runner_1: + image: ${DOCKER_IMAGE_RUNNER} + container_name: ${SERVICE_NAME}_${CONTAINER_NAME_RUNNER}_1 + restart: always + depends_on: + - gitlab + volumes: + - ${SERVICE_DATA}/${SERVICE_NAME}/gitlab-runner_1:/etc/gitlab-runner + - /var/run/docker.sock:/var/run/docker.sock + command: --debug run --user=gitlab-runner --working-directory=/home/gitlab-runner + environment: + - CI_SERVER_URL=https://${GITLAB_HOST} + - CI_SERVER_LOCAL_IP=${CI_SERVER_LOCAL_IP} + - CI_SERVER_WITH_RUNNER=${CI_SERVER_WITH_RUNNER} + - RUNNER_TOKEN=${RUNNER_TOKEN} + - RUNNER_DESCRIPTION=gitab-runner_1 + - RUNNER_EXECUTOR=docker + - DOCKER_IMAGE=gitlab/gitlab-runner-helper:x86_64-latest + networks: + - service + + runner_2: + image: ${DOCKER_IMAGE_RUNNER} + container_name: ${SERVICE_NAME}_${CONTAINER_NAME_RUNNER}_2 + restart: always + depends_on: + - gitlab + volumes: + - ${SERVICE_DATA}/${SERVICE_NAME}/gitlab-runner_2:/etc/gitlab-runner + - /var/run/docker.sock:/var/run/docker.sock + command: --debug run --user=gitlab-runner --working-directory=/home/gitlab-runner + environment: + - CI_SERVER_URL=https://${GITLAB_HOST} + - CI_SERVER_WITH_RUNNER=${CI_SERVER_WITH_RUNNER} + - CI_SERVER_LOCAL_IP=${CI_SERVER_LOCAL_IP} + - RUNNER_TOKEN=${RUNNER_TOKEN} + - RUNNER_DESCRIPTION=gitab-runner_2 + - RUNNER_EXECUTOR=docker + - DOCKER_IMAGE=gitlab/gitlab-runner-helper:x86_64-latest + networks: + - service + + runner_3: + image: ${DOCKER_IMAGE_RUNNER} + container_name: ${SERVICE_NAME}_${CONTAINER_NAME_RUNNER}_3 + restart: always + depends_on: + - gitlab + volumes: + - ${SERVICE_DATA}/${SERVICE_NAME}/gitlab-runner_3:/etc/gitlab-runner + - /var/run/docker.sock:/var/run/docker.sock + command: --debug run --user=gitlab-runner --working-directory=/home/gitlab-runner + environment: + - CI_SERVER_URL=https://${GITLAB_HOST} + - CI_SERVER_WITH_RUNNER=${CI_SERVER_WITH_RUNNER} + - CI_SERVER_LOCAL_IP=${CI_SERVER_LOCAL_IP} + - RUNNER_TOKEN=${RUNNER_TOKEN} + - RUNNER_DESCRIPTION=gitab-runner_3 + - RUNNER_EXECUTOR=docker + - DOCKER_IMAGE=gitlab/gitlab-runner-helper:x86_64-latest + networks: + - service + + runner_4: + image: ${DOCKER_IMAGE_RUNNER} + container_name: ${SERVICE_NAME}_${CONTAINER_NAME_RUNNER}_4 + restart: always + depends_on: + - gitlab + volumes: + - ${SERVICE_DATA}/${SERVICE_NAME}/gitlab-runner_4:/etc/gitlab-runner + - /var/run/docker.sock:/var/run/docker.sock + command: --debug run --user=gitlab-runner --working-directory=/home/gitlab-runner + environment: + - CI_SERVER_URL=https://${GITLAB_HOST} + - CI_SERVER_WITH_RUNNER=${CI_SERVER_WITH_RUNNER} + - CI_SERVER_LOCAL_IP=${CI_SERVER_LOCAL_IP} + - RUNNER_TOKEN=${RUNNER_TOKEN} + - RUNNER_DESCRIPTION=gitab-runner_4 + - RUNNER_EXECUTOR=docker + - DOCKER_IMAGE=gitlab/gitlab-runner-helper:x86_64-latest + networks: + - service + +networks: + service: + name: ${SERVICE_NAME} + # webproxy: + # external: + # name: ${WEBPROXY_NETWORK} diff --git a/fix-unicorn.sh b/fix-unicorn.sh new file mode 100755 index 0000000..0128b18 --- /dev/null +++ b/fix-unicorn.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +echo "fix gitlab_server unicorn error" +docker exec -it gitlab_server rm /home/git/gitlab/tmp/pids/unicorn.pid && docker restart gitlab_server diff --git a/ssl-certs/ssl-certs.zip b/ssl-certs/ssl-certs.zip new file mode 100644 index 0000000000000000000000000000000000000000..bdaeb9b8bd0dff1d9b61384e80f40803946181a7 GIT binary patch literal 8679 zcmb7qWl$a4w)IAWySqCC2pS}~Y~0;l0|a-M;J$Hp2=49#C%C)2yL~73d*{A$Ufo;o z-PP4ytAF$uYj#)7HRc>GF9iXK0r)*|^GFr`G5N0x9DoGi;9&jH$i&{!fk{Of5dfVh zC9C)QOq)s2cXEXXfI;4Z0RaDaUj83AFaVgp;W$7609b#+u{SZZaB#GDV>EKG|KBj3 zR2tE050FmX!cJ3sy7Un+v@T-vhDyj&(q%lKj@z{=>Bb1gfDVrN*ZUJp0H)yqJqFv> z8j{+Gk$018&`+K-{taSal1Yb+ItbT0yPu$8#EN@5Y_xY`VmB77> z>w)L0hum1EeIpBWPMPT3C=T~*+|BhVXpbHi;_El$+l|w8kCPdE2_0CeRQO1oj!Aoj zGcucri(+&MP)AS$)eOBT$w!?qK}XC*fE?ItKYx2NP?}VQzBE8G)++|*{}IAxx--r$ zuVKts5S&kJXYLe|g~7Y(=6OS*k{lY>XTLlQRgZv68vT*x;vkj;D&v5!1D#D$66O2P zRK+Z&hw*>~QK$Wiv^50D?kwF2BzlmExC6dgq)}y&KTV$ad<4^aR&FibOH{CXIeLZD zxar}T;XMlN6hkIvv`S)pOOaeM@4yeRoX2oh`Db#BUz(CnNS_fakNBN)#7Xg!l%&(I zau>RQnGmR4KT5-~3@c!|EUqGMW&~ed#ZiURAx7wfqw`@+TXtY-1up7&iOJC0ak16A z#<@H1>2*E&vWhn(ZM1qB?2*NvgXwO;(pXO{C&^>4o!}3VbSW_?-Dd9x2^B9DI%aZX z6Wat9M`~+eJN4Y^QPw~*24O{)#ZM4S3EqNZ5bD|$%DGH|YcnRadEnE;l>@vZ$5e`! zpxAmYh_vUi=n#9=&eg!9zH5XU%H!+c#C^Lj#ri$K;?(@~{QRZ`Ncwuc$fO10jkL-I zHtY`MTtesBBr&lQXa9odo9?XvU6Qz2i}$%eUJ?$vSc{rurjhCy z=>LN7J?FMupSFA7X=sz-OfQEXT#R5Dr3OFec@;(2{yzxt9^# zlc~HeBhNNZs|^KyYR+pKtt+Kw{ex(Ts>IXvQCbRBC!?k;ygiFS0jJ8fZ(@E3+&gaK z>7)33i2mzp63IdrG;0yKd>bO0ZY{CKM_#&W7me4MQrAH`$_%?t`@Ok(u$kNEC;8j1 z$y8Jaad6;2u!<*b5nAZuJ|nGw&_OvYS!t>_1<+{|K^0daV;QMaUj@6 zrN1FL<*VqZRIpwbM3BCz#&mf+Kj-jW!M9pKM`tKgGgSvrBC3jc35)Nxn(=s~=8DT< z+{jaIe*YS}5n1F85eUU&P}Dc*9li{C}hkE^f$)3w2X0R1IdC6hIGES;5S>`w(i%)j2C2iDQFl-K%4j#BmnR`cmC_FT88j%v#OPe+y9nTzb4h! z^esgRIQaCB=rk@NOi7Env5w4`U~EhbQcA2s0kF>px1S7s4fUf~sr4xuXU|LWQ3nNsA2Jj|x? z(ooU71VPfEy-0H)V>4O+Uwb`pu+Ds6ZZ&`4fML7d&;Z2JsZykQY0bN0%~=D58doy<>%!$eXjQ$f?}kdL1zPVX9pCd42ms-z`hXTl8KiW zUxI0t+T!P{Bnkf{$t{=0zT%>`mkSB!*l*khCriMD)RCp7&T`R>l%qjNZ2M`Yw- zawt?e`dQrqoXJDUeL5?V{dCajm7J8DmJ(wkVw`l=`iZe0ty#$xQ@tYKny91R#{zYc znJujJLc9uxpGGnR!uf^2VLhu)7}$hZ3-C!Xp0*4jd_2-PPr^M8h16e~MGJB2wZLD= zdxPUnWuzsFpuE?Lv8!1xL60}{K@(A1YW95O$*2)Ng1>1C^8-}*uI~2RnZ}~MPOK`_TPci-N+oYhOhO`-ZqwBQGzn}sk)PA_@A*p38jciEw2<4o5Snops1ynNKtvX^S} zWZltm4JeM2wP?US3s|~R|42}LQHj?`ca{_@iyZeE?P4gzem=wF}*=>Fd;O1#+-9UGS>H5J${i2utV<+bPtY`B2L>-^wr^L=| z7#y1bayLl)jj&w0L>~^(keI#J!~&6!G{~v9C)QkU2Xo6;c(-eDf(%_@_lK%UtN)xO8NUi z9bbf&)@}2p2R&&qFL38m^7?qiFC$7K38zDB{Hn{Tr)ko)2awuHm(hQ<6%4NtF6Lq= z-Oe5&m!9-yFl;E6X5RLA{PGt)JB(+O@z%xcucc0)G@H)bCRUniDeBdIMp&W|oqm!^ z-&3uEc>}y?Kg}POr2Ycws3$$6;&&F7s)V=r`c3iCy;}5+#D(*l8Q|2LEe?g8k1rF6GW6f6tyaxcZn>75MW=lEav-{tNw1SbMHMbL~di%&G)Cts7cJ!c2^n@Xd ze3fvi0kgtopCVr^UC4k$rFN`kB0Yc2%lLt!{3Tvt9c>FN@+8S?h_FruVe@GniT}gt zET-;Bz-NC4uCx+<^wl+_c5yMkJx`~{Q8hOP;ZM*fN6hoeT32&ur^I-}TJ zxc_FjDW{>FIGj?)epyzD@j7WCa|y=%I1m9Ft3@+&^|@*3B@_eZN)G%wDlvpm6JD*NDghUn*aR}ttCXq#x;aO%8G1_Uu^H) zidm6Qo1RN9cGU1_nq6Mt9KZv$-G6GGE)P#kZyDXEJF@1wMSdgMrwoLoYGu-@g-Wm# zv-fJ1(ZL3j;X;tNOhg{`pi&GlX`+?nt4YrEF>=*r=rnDd1L`G?!Kh_CBPOsNyBZ2g zeP?ArVYi_YxMs_$Ji2s2v@e;ntM=Xzadpl2V!-8xI^>PEG}z^gyqbbb$$@i^Yf*x= zrE2+%0(kZmbnD$a3UG#_DCmUdL?Oq@g-mO(3@@W^7J%lc1F8?2Q7t?TJ5KV>1`;%V z*z|R4^Z6=&N{dHOhcj-17@Q&j-5Fa+F1&^P`ur9+m^1K3f8(AHRllBXB`G`ruiF7% z$NoLyJrOc|Re!nNwq9imc(%WP?m(V2w{rcm1|QJ_d2ia9G0)*mE4$yvx4#xD&;*S{ zrXmeS-vfzknCED6Ky%7>hwN;|o^Ph^13Ph*+P{)p3DVv>SCgr|hc}wm_cI&W$+qE7 zB+Kj@>+vzv$aLG@YN0I_jwgsAy@~EBGSR0N%7kT`p^cI?h9PfMlw{@q-W~Ncru2ymf5mEm(?R_ad}Yw z0Yw_)hYnNHcYLadmrm^-8E?M5&3VQ1(r)7)bhHLX&Du2nE$wCn|js@hMZJvCd<*a~EjzaaN=f@X+1vwppLwMlN8Q2;t= z@K9IRY5Y|Ej3Sl?H|!~+R{YCf?L6@CFtr0g5E0OFDjT=^BOj5 zIKL_^Q-NN+NyQ^=D~#!ho$O2e%)D}IzMvg+U#Ph{(zyjthvN~MaG6DWD%cz)L~GwX zo%?bwW1~{KkH*m+U!%D=?${djk~K*9{(@@mbwB+gBDQDx-IA#6+2e#Qaj2cCK~)$; zq=~4)Be)zl^6PPqXMa___pr-H4yk+N{atCt=MU`%4KrvitXm8V0Q^>b|5^*GA_4%I zf2*+V>@A%Ckb#VLCSU(41%DUp|DtI3N@|=6YypWmWNz)u(6$|JK)IfO(SV4&C(3+# zV_l@kljn(%)SP;{>wJGh{6h?06L;^d^xb`UBVJY!8o+*=4BYVxWY^!iH~GPoyFTjY zD~_#DT*rX#w5F7{KDJQff|Mty9j_c3K1S-%bBJh^p4{d5gfLF(zKW9@=;T0}@F5*7SjYlv4RkVR%q=L-XV|=EG!Y(+`@sApMEG zGHzB`WDRep^6vKzQV!=n32$ZJK839FqQ2;HcsCqEdibZiS02okmywzePNaG<5cICi zYFf=ALDDjHN3`fHct*3{iE0Xx0``mqs^p_WDykXn#1%9F{=q~Mkn`RsZalxyhe|?e+0kD8W&+16`rC-Ep0_#Z zzE2CwSJd#tjZu_XchO9qt*0f>%|27irN0sgv(_fkG+GY5f)J6lij8)Bt*Ye?iXz`3 z0kGUc;00s6i9bd9S?#iq5?%)9+qHC%w1b`If(n~T&mmTU*FX}&+Q3XCT!;Me&)+hL}E{qvfl0n4OaBap8%-xUcqJPvlW&No1iGYNYQ{Wh@QhWNHO#iycsyv<_(=xvAGyARj8UnO;UdW=<$4%wKrz> zX^UH4uTyDWp6;S z;r@JG=JbgC(0CI1Ez7S?{QS!`>(gpOBYb8}?* z_26)K)#b3aI)&)7Q7&&|ohxKXn4kN1z#7{`a5qRV@WS41{0u_Mc$|nFAGW<*iqZ6n zX2(alyYI!Ol=ae87bGEaioH=jxe>Hd{Pf2MLl45um6Ah=y%tyer-|RCxwAf!B|X{p z&}b?VV%fe79q!{Bu}n)ec0Su0^=rC9lW!9|;F{By8-UDoq%XZj4b^`%=xG=w*=BiF z3eybRRVT_eTilRlD55ZFJN&570&S;v`i5ph;fF4I6tz1Q?P7B%W6iX$nj}*d?Gr|V zqnd)AHH-S^$Z;faZR1;u6%9v-^hl_N8+5a}B*p2a8{N+j7%IXBwh+EO=E=ycsLHMK zO8B^%V%CYCMPl6)DSK_t^Wb+S6o01K+_0NSGwn>o%Qoj+G_WLtQ$u(}f6_vaE5RiM z5evNB)z@kAu=RZ_vsoeXZ3fLJNY+9mrXPD@$_b1*82R|K$(%jXh8TL>e-dS&gu8lF zD@}*Hy+zZ#RFN8nBmst16CR#*L4%o)D#WbhlsImsY<0VmT1Kpor#Ty-VIEs6tyP=8 z$AB*l3UL$9V!LLZdk(0kbp`h-=5TJST?NV}u8i7(_OdI7bxi$-Yb6 zmtQ8?0KxS|71<27a0x>QWHe8mwsAgmo-A9V8#81bIO7}vooE>ESD6Sb6@o)9?C;!1 zh|idO-KW)lR735B?Y{*vS6w|bJ32Knd$=h5d`x5%Ws{{>{EAVH%10n!IOU!|_F1aX zRDTm|pz2c;#WE6=XpgFPfYc=7-q+NWoYHHTHWi}j{qZ!mmWdM!O#cZd#H8)a zKdsLrow)!tdN#L534PN|<8~lRs@)Gb_C2=ZWuFpZ z$(#MPQ@}Tsx=!so4EiA>ka#{V$zVlhoj^si3{C%dUua#Hq)1wD|(ZDd25HGwn zZT_$7T-V45zl5M+iov9hh#%?my}k;Jb}9DY3DB-iVrTnR>{EYr_sRHpa&C_qoAnHP zyd7{Y;n6p?m@+tuewvK4bX4+IsF%i0Etb9Hu~*_dtU&J;P1}5ky6IgEh?+Ut?J{>L z?X+zzWqmG-3n?uY1MhvfLl{eAa3n*eFl*(5v3`*^tz@Uok^d>Ha2H0mzMC8{cLfLL z)#zOEZM=FEv8i?}#SJZGw#N!%Sh7ui4qQCuQ2a&d!IV~YFY&9U-9w2z&IuD(`HZCJ zb+>mwqI7qsCuGkxb-$_R;ecY|%|ofLd?M7kllCvI$(#=Bb#C6`oefw__pdAHpb1h; ztA@Cf&iR*+WsiiJ+P(F7Sb;wJx0$`K%BsB{&Ehb(R23f3Jz3dd@d#s34Yj>^(l*8F z_7dT~k3@IL@6Pvra$E4ZHqbN{jQlQ~vuTynGvZvTp14EDX{7M=WKohvtX4x>9q_x4 z9FJM0+stIcZMolHCQ7N92faVr8{V?+Mt8*#%{J|GB5%)a>ZR{uqYdXdU?KZWEev;XVx;aV$ZOXKGi)tL~2hn)nz?H}u zgJf`vOCq!#+xL|R%eTHp>yuZ2%61;M)OY%vA1yI3XBY|EGRKA9YIo@FF+m?fVZ%%?Gj;z@3r#nBXp9>yR6Jg~38K?Ae^6<{tpB znf7v216||7eAy7C^gTLAUlv0m#!J}HDFqqniLopdJYHUCeF955w5D+)lub-EZMmu* z`P)jlVq^;55OjZmQnJ&@;r#*~Li0Sv`xvYSGlioUnN21NTQxv$NDsKb&KB)_S&DwB zl?@z1>B)rv7t-{JU+DnxCf{%=SHzJxwmo|@&*NF1<}%K}68UO1+NfSiWa^p4v9|b9 zH-I~El{n}*?Qhq_8$LW_-8=7{-iMvPRj82B!8$n{0PtID{%aKq3;l09l1BE9|CFKs z#yc`m`H6IWfY^3SYtM;Q3M$wxveal#N^?I~^?G`kut5@ZB{KtnQ-tj3`MgGKib_0V zk7z|v02%Ba_&4-o=p?|Ws~8qwK999Mx?YaPq}H852JwXUjz=;a4SBKb%A-a_BQ2|>N|bmpQ5a?XW{8q*GP zZ%|lDmP8PeiKlg6E>|nVwN$8DigtXrY;BZ9JC<1{TjSP*NhAgffun^d<)E`=cgdGf zk(Em7dPh6chuELM(mQ1cstIPQjbTNWF5(V?#jFVd0SmEdKD<}d<=3jA!OEBe=3up~ zk>9owcVT6Q)iAFU4VSAMUx>1cJO#q$DEX2w6CLN49D&4FdW#vBd^rp26awT)oVf$_ z+lyBT@=8whNOnr}o$c|LK|$r`KWwE7Dc}WF+(DJ6hBHO2od(aH`*4=Ycjqyn`x-{3 z%9)d6rDIS1_~FZ(#tBi+ga_6{*w5_J8ua~}OsHtTSs%JJL*$t#C~(Ej?GB@p;G|hS zmr3m@MxLxDWQpnyEc9O1It`5=5p)jK=K;iwWL|YQ254DTEjQd{*mh`B7*7-Dozp37S z@q|`(Ovq2?oT45flKEkdtCB%-)&K5x}l! zzd3^iN4Xz-Uc|q1&)k(d)#(3t34aymud&i8l=Lh>u-s)Cc-BaU0C;%^5vpzSIch-Q37Vc#UY#ggY_IhkXM zzGPkbOuMd)z?{XdX@-J6WJvwN~P-lX$g2<2QUS2shPLM+7@>`UeSLmj0 zRYkstZ2RsjoCraYEP2GCn&!OCq`u5r;0@`u^UEBZ5L&iS{g z2|TOFad72&&!_Bx$4DEN<70Ua4N;SVpkgbZ(hvJ2`;n)eMl{eh?Vw>=G9dF+B1+>0 zI=|2l-OgnvO|r+e_@7SW5^H*_S&0cz`^Fbbnn1$7lQSa-;SaQknA?TpzOpB5Q=re5 zD;11w(kv4uNJLNQ_|@SvXL9ZsRV|F4HtfuALOE)wUCrQ?Pf-+F4{4mgt;Ii}8S_|0 z^wvgnti8IA1|7gFN%H9iGTs=E;SHe*=3eP5x8yXx$*!(XdTXzLXVauNfp;;Ez;0Bk z+mgyVCPt(*AOZV{TF_Jw2PbVl26d~gYsLm@O}+A+eBE$T>Nny(yl|2KfoAUA4$fIQWQ!UWk*6L~z=4D8QM@=OyUj3GEw2Pg7=oIJ|p>cu`7R#af1> z-Ad!$eDs6IAycZ|MwS~Q#UIV>4K=t$dg1uxMDNI`$Wkbhb zk(NAsf)ng@zxopI{(a(X{deX@!vHVJy{+|6O&j0_% z{nPvZtAF*|`~P#~`vL!k`=9&?@aNk9