From 99a41d2a740288fc062ed42d4dc6286f3adaea8d Mon Sep 17 00:00:00 2001 From: Bastian de Byl Date: Wed, 28 Sep 2022 00:49:14 -0400 Subject: [PATCH] added photopsirm cloud and other fixes --- ansible/deploy_home.yml | 1 + ansible/roles/podman/defaults/main.yml | 2 + ansible/roles/podman/meta/main.yml | 1 - .../podman/tasks/configuration-nginx-http.yml | 2 + .../tasks/configuration-nginx-https.yml | 2 + .../roles/podman/tasks/container-cloud.yml | 108 ++++++++++++++++++ ansible/roles/podman/tasks/main.yml | 1 + ansible/roles/podman/tasks/podman.yml | 10 ++ .../nginx/sites/cloud.bdebyl.net.conf.j2 | 21 ++++ .../sites/cloud.bdebyl.net.https.conf.j2 | 47 ++++++++ ansible/roles/ssl/tasks/certbot.yml | 5 +- ansible/vars/vault.yml | Bin 7699 -> 8477 bytes 12 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 ansible/roles/podman/tasks/container-cloud.yml create mode 100644 ansible/roles/podman/templates/nginx/sites/cloud.bdebyl.net.conf.j2 create mode 100644 ansible/roles/podman/templates/nginx/sites/cloud.bdebyl.net.https.conf.j2 diff --git a/ansible/deploy_home.yml b/ansible/deploy_home.yml index 5570dc5..126b711 100644 --- a/ansible/deploy_home.yml +++ b/ansible/deploy_home.yml @@ -6,3 +6,4 @@ - role: common - role: git - role: podman + - role: ssl diff --git a/ansible/roles/podman/defaults/main.yml b/ansible/roles/podman/defaults/main.yml index 3dc5804..7a4b779 100644 --- a/ansible/roles/podman/defaults/main.yml +++ b/ansible/roles/podman/defaults/main.yml @@ -5,6 +5,7 @@ graylog_path: "{{ podman_volumes }}/graylog" hass_path: "{{ podman_volumes }}/hass" nginx_path: "{{ podman_volumes }}/nginx" partkeepr_path: "{{ podman_volumes }}/partkeepr" +cloud_path: "{{ podman_volumes }}/cloud" pihole_path: "{{ podman_volumes }}/pihole" drone_server_proto: "https" @@ -15,6 +16,7 @@ ci_server_name: ci.bdebyl.net pi_server_name: pi.bdebyl.net assistant_server_name: assistant.bdebyl.net bookstack_server_name: wiki.skudakrennsport.com +cloud_server_name: cloud.bdebyl.net home_server_name: home.bdebyl.net parts_server_name: parts.bdebyl.net video_server_name: video.bdebyl.net diff --git a/ansible/roles/podman/meta/main.yml b/ansible/roles/podman/meta/main.yml index d00d780..fdda41b 100644 --- a/ansible/roles/podman/meta/main.yml +++ b/ansible/roles/podman/meta/main.yml @@ -1,4 +1,3 @@ --- dependencies: - role: common - - role: ssl diff --git a/ansible/roles/podman/tasks/configuration-nginx-http.yml b/ansible/roles/podman/tasks/configuration-nginx-http.yml index e0b2936..212ceac 100644 --- a/ansible/roles/podman/tasks/configuration-nginx-http.yml +++ b/ansible/roles/podman/tasks/configuration-nginx-http.yml @@ -68,6 +68,7 @@ - "{{ bookstack_server_name }}.conf" - "{{ video_server_name }}.conf" - "{{ parts_server_name }}.conf" + - "{{ cloud_server_name }}.conf" - "{{ logs_server_name }}.conf" notify: - restorecon podman @@ -86,6 +87,7 @@ - "{{ ci_server_name }}.http.conf" - "{{ pi_server_name }}.conf" - "{{ parts_server_name }}.conf" + - "{{ cloud_server_name }}.conf" - "{{ home_server_name }}.conf" - "{{ assistant_server_name }}.conf" - "{{ bookstack_server_name }}.conf" diff --git a/ansible/roles/podman/tasks/configuration-nginx-https.yml b/ansible/roles/podman/tasks/configuration-nginx-https.yml index 5189f75..9f703a1 100644 --- a/ansible/roles/podman/tasks/configuration-nginx-https.yml +++ b/ansible/roles/podman/tasks/configuration-nginx-https.yml @@ -36,6 +36,7 @@ loop: - "{{ ci_server_name }}.https.conf" - "{{ parts_server_name }}.https.conf" + - "{{ cloud_server_name }}.https.conf" - "{{ bookstack_server_name }}.https.conf" notify: - restorecon podman @@ -53,6 +54,7 @@ loop: - "{{ ci_server_name }}.https.conf" - "{{ parts_server_name }}.https.conf" + - "{{ cloud_server_name }}.https.conf" - "{{ bookstack_server_name }}.https.conf" notify: - restorecon podman diff --git a/ansible/roles/podman/tasks/container-cloud.yml b/ansible/roles/podman/tasks/container-cloud.yml new file mode 100644 index 0000000..84a4237 --- /dev/null +++ b/ansible/roles/podman/tasks/container-cloud.yml @@ -0,0 +1,108 @@ +--- +- name: create required cloud volumes + become: true + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ podman_subuid.stdout }}" + group: "{{ podman_user }}" + mode: 0755 + notify: restorecon podman + loop: + - "{{ cloud_path }}/mysql" + - "{{ cloud_path }}/storage" + tags: cloud + +- name: flush handlers + ansible.builtin.meta: flush_handlers + tags: cloud + +- name: mount cloud cifs + become: true + ansible.posix.mount: + src: "{{ cloud_cifs_src }}" + path: "{{ cloud_path }}/storage" + fstype: cifs + opts: "username=cloud,password={{ cloud_cifs_pass }},uid={{ podman_subuid.stdout }},gid={{ podman_subuid.stdout }}" + state: mounted + tags: cloud + +- name: create cloud-db container + become: true + become_user: "{{ podman_user }}" + containers.podman.podman_container: + name: cloud-db + image: docker.io/mariadb:10.8 + recreate: false + restart: false + restart_policy: on-failure + log_driver: journald + network: + - shared + env: + MARIADB_AUTO_UPGRADE: "1" + MYSQL_RANDOM_ROOT_PASSWORD: "yes" + MYSQL_DATABASE: cloud + MYSQL_USER: cloud + MYSQL_PASSWORD: "{{ cloud_db_pass }}" + volumes: + - "{{ cloud_path }}/mysql:/var/lib/mysql" + tags: cloud + +- name: create systemd startup job for cloud-db + include_tasks: systemd-generate.yml + vars: + container_name: cloud-db + tags: cloud + +- name: create cloud container + become: true + become_user: "{{ podman_user }}" + containers.podman.podman_container: + name: cloud + image: docker.io/photoprism/photoprism:220901-bookworm + recreate: false + restart: false + restart_policy: on-failure + log_driver: journald + network: + - shared + env: + PHOTOPRISM_ADMIN_PASSWORD: "{{ cloud_user_pass }}" + PHOTOPRISM_AUTH_MODE: "password" + PHOTOPRISM_SITE_URL: "http://localhost:2342/" + PHOTOPRISM_ORIGINALS_LIMIT: 5000 + PHOTOPRISM_HTTP_COMPRESSION: "gzip" + PHOTOPRISM_LOG_LEVEL: "info" + PHOTOPRISM_READONLY: "false" + PHOTOPRISM_EXPERIMENTAL: "false" + PHOTOPRISM_DISABLE_CHOWN: "false" + PHOTOPRISM_DISABLE_WEBDAV: "false" + PHOTOPRISM_DISABLE_SETTINGS: "false" + PHOTOPRISM_DISABLE_TENSORFLOW: "false" + PHOTOPRISM_DISABLE_FACES: "false" + PHOTOPRISM_DISABLE_CLASSIFICATION: "false" + PHOTOPRISM_DISABLE_RAW: "false" + PHOTOPRISM_RAW_PRESETS: "false" + PHOTOPRISM_JPEG_QUALITY: 85 + PHOTOPRISM_DETECT_NSFW: "false" + PHOTOPRISM_UPLOAD_NSFW: "true" + PHOTOPRISM_DATABASE_DRIVER: "mysql" + PHOTOPRISM_DATABASE_SERVER: "cloud-db:3306" + PHOTOPRISM_DATABASE_NAME: "cloud" + PHOTOPRISM_DATABASE_USER: "cloud" + PHOTOPRISM_DATABASE_PASSWORD: "{{ cloud_db_pass }}" + PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App" + PHOTOPRISM_SITE_DESCRIPTION: "" + PHOTOPRISM_SITE_AUTHOR: "Bastian D." + volumes: + - "{{ cloud_path }}/storage:/photoprism/" + ports: + - "8088:2342" + tags: cloud + +- name: create systemd startup job for cloud + include_tasks: systemd-generate.yml + vars: + container_name: cloud + tags: cloud diff --git a/ansible/roles/podman/tasks/main.yml b/ansible/roles/podman/tasks/main.yml index a3468ea..6032ceb 100644 --- a/ansible/roles/podman/tasks/main.yml +++ b/ansible/roles/podman/tasks/main.yml @@ -9,4 +9,5 @@ - import_tasks: container-graylog.yml - import_tasks: container-pihole.yml - import_tasks: container-bookstack.yml +- import_tasks: container-cloud.yml - import_tasks: container-nginx.yml diff --git a/ansible/roles/podman/tasks/podman.yml b/ansible/roles/podman/tasks/podman.yml index a6119c3..a633146 100644 --- a/ansible/roles/podman/tasks/podman.yml +++ b/ansible/roles/podman/tasks/podman.yml @@ -60,6 +60,16 @@ - podman - selinux +- name: selinux allow podman samba + become: true + ansible.posix.seboolean: + name: virt_use_samba + state: true + persistent: true + tags: + - podman + - selinux + - name: create podman system directories become: true become_user: "{{ podman_user }}" diff --git a/ansible/roles/podman/templates/nginx/sites/cloud.bdebyl.net.conf.j2 b/ansible/roles/podman/templates/nginx/sites/cloud.bdebyl.net.conf.j2 new file mode 100644 index 0000000..73e6b18 --- /dev/null +++ b/ansible/roles/podman/templates/nginx/sites/cloud.bdebyl.net.conf.j2 @@ -0,0 +1,21 @@ +geo $whitelisted { + default 0; + 192.168.1.0/24 1; +} + +server { + modsecurity on; + modsecurity_rules_file /etc/nginx/modsec_includes.conf; + + listen 80; + server_name {{ cloud_server_name }}; + + location '/.well-known/acme-challenge' { + default_type "text/plain"; + root /srv/http/letsencrypt; + } + + location / { + return 302 https://$host$request_uri; + } +} \ No newline at end of file diff --git a/ansible/roles/podman/templates/nginx/sites/cloud.bdebyl.net.https.conf.j2 b/ansible/roles/podman/templates/nginx/sites/cloud.bdebyl.net.https.conf.j2 new file mode 100644 index 0000000..bc6e1ed --- /dev/null +++ b/ansible/roles/podman/templates/nginx/sites/cloud.bdebyl.net.https.conf.j2 @@ -0,0 +1,47 @@ +geo $whitelisted { + default 0; + 192.168.1.0/24 1; +} + +upstream cloud { + server 127.0.0.1:8088; +} + +server { + modsecurity on; + modsecurity_rules_file /etc/nginx/modsec_includes.conf; + + resolver 127.0.0.1 127.0.0.53 9.9.9.9 valid=60s; + + listen 443 ssl http2; + server_name {{ cloud_server_name }}; + client_max_body_size 500M; + + ssl_certificate /etc/letsencrypt/live/{{ cloud_server_name }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ cloud_server_name }}/privkey.pem; + ssl_trusted_certificate /etc/letsencrypt/live/{{ cloud_server_name }}/fullchain.pem; + ssl_dhparam /etc/nginx/ssl/dhparam.pem; + + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_session_timeout 1d; + ssl_stapling on; + ssl_stapling_verify on; + + location / { + add_header Referrer-Policy "same-origin" always; + # add_header Strict-Transport-Security "max-age=630720000; includeSubDomains" always; + add_header X-Content-Type-Options "nosniff" always; + + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Upgrade $http_upgrade; + + proxy_buffering off; + proxy_http_version 1.1; + proxy_pass http://cloud; + } +} \ No newline at end of file diff --git a/ansible/roles/ssl/tasks/certbot.yml b/ansible/roles/ssl/tasks/certbot.yml index fca95c6..1ea298f 100644 --- a/ansible/roles/ssl/tasks/certbot.yml +++ b/ansible/roles/ssl/tasks/certbot.yml @@ -8,9 +8,10 @@ args: creates: "/etc/letsencrypt/live/{{ item }}" loop: - - "{{ ci_server_name }}" - - "{{ parts_server_name }}" - "{{ bookstack_server_name }}" + - "{{ ci_server_name }}" + - "{{ cloud_server_name }}" + - "{{ parts_server_name }}" tags: ssl - name: set group ownership for /etc/letsencrypt/ diff --git a/ansible/vars/vault.yml b/ansible/vars/vault.yml index c7a84929376af18449666b412d16373508ab4168..fd36954e4383b8f1b23c380ae96428c7e1c14bb9 100644 GIT binary patch literal 8477 zcmV+&A>!TuM@dveQdv+`0B!W~nATP6sa>Zgzyo7|=>tgt$Dg8^Ni?!(EqQb? zxAc5f!-0Ey-5~XBDYL!z!O(axTS&KA`M1zJY~RwsWl>rWutleCADB0J+V?SH;TBAV zM^H9(=Tv93gH|KPCxOt~a7D&M$N2~0MA3-Cy{UrJsJ+qZ1BrrG%fQQH*R0}sv747| z*t7&Cu(lv;?*u9;WQ+1^sA0j)&h@<&yxm6Dj&YPodIJpQP$P9ZQ-=hVVlmA1)JE*) zW2-gk7?OCr4;;BDmqQ@Ue+LK}rHaM2c+F?62K6`MtIdDWRT?BWETtLg>ST2Nnx8*c4+T8TbNrv<1yqz`+3S&(mD! zY$-+PCjA|A+nIf+n_iDCcjX~zrPe-nX_mC82Y=YSrwO<2<&1`nAj7w=YeZy;kI8s(P*lIiei5KpxZNssSO>(YjHs?Y|kWxW=A@f?WtZ(82s-P4oj1)d%7#ib4m7 zItxkH0m*|Dz@DYe`Al7y?|QJpHrF{w>mNmEmdAaNVfY^=rw#-c;^6p0S%GX zHS+tM>70pT)E;x^&vi=xi(oY5MuSLuktTwgI%t3ZU1A~)KJ)xAC^~gUlS~xGcjnz! zv+Z+7NvdJtTU)ZxemYLr!-S85 z@=dNK4t5{W-Z)FXInK*@R*YNkf-J}%1pkwo2&4f(_lqdNsK`-GpQPQKx}xLzE5iXJ zzSz)cT~?L0I=;SSHBCIHkvQ$`Ki7a{SC7(Z%bsg*hG(ch%|n@NT|Mcyj29ADi{RS> zw|P$w<{2Da{TgSq^SRz2j2WFVWKugSUOM(WFIzY{$<(7D*w5`7E>w)hSDgsYT;c6Z z?8Qd0AX?l#g*r7$K~8SKHo*}7o1rS}`BD#3K`SBXsy#)k+dnU7y z+16tcw=yTsYCt_A)^sKJ7(JwZDi`vYBif(na~q}LR)BRhzitdE?+N_NJuwhh?~Vp| zEWA#o=VPdUzAj)cv63?7M<+RxA@_yRw?>&!ncJ?{5=ZapZ}RKu)2^>92h6R2pl-Mi zJ|-<&Pa(^w%^`OUZlxaveq?gmOl~=j{BV=}(5Z7GXY*$17iXY&gPY(ek4XSgEZ9~l z`2$+}nt>uuTnvbyl;=o+HZ33^1W+RHQt6u)TpmFb33!0yw zo46TXY)~WXA%wD}&dIhmY{!`;zsiKO25Yv10c}Tt%jf1qh|-@|(rHiO8zHs$seMnp z-v<{pNnJ0O$LsB>ZD05}8=A2ccO-=ebnL`31HxJ4UG}D2L?QQycv!a79gz!t;=vz`X^X`{aVic57IbXf2Xck*- zeEDc#x?ilgs#BmIZvFw)F(OGWHqi5v#6jUc&Z29IB5N=0Up5-2*X0T{tDs3p{ick? zc3|HzCH2Y_Z3y`yw3V}kazCm|(zyO6?I`cc1Mb2zMcFWZvFc>Gp$;?qD%r@vVR%EC9Fv6C=lE@~<%gm3fu; z16(K%wx-|AF7WF#Y=v!we9F+esLquN)N}fIFQZMhzmK)#=W`<+?c!Xdvlt$>PY!UE zkyW?Gm#h3qiZ_oNv!3IR6LG-`m+)3`oGIKwRTcf2DIR}lxn~cg>j%X!MXaZ<0m`e% zO|}bk`o!=q%)1!KJy}E5TWN`i5L^QklD7GAda?v?*|1$Y`*TJe?H0dhpX+Gt=DgE~ zM(*QPm6P~7{H7NwqrbayPwX~oYtg#}qfR05+MU(mU7u4}{H&Pc!Sf_JZrVyTTgh*0 zZtYCALeR%Rl0dBOw9FgXHSpm%Tu(7uN36 z8C}zmS4SI0x>pM*(0S_Nlhd928lxGiADLbr=rIm}&;d^aze)1u;C`i66|OUQWvyJ|K#TRK#Y{$PrRJyVDG-liJ z1Iv}G%gP5O%j{Hdtf(nBFb%}!+``~yM@E(~v&sVduAvNejp}3ubc__)Gab$6p>=&+ zk`h2>ID#_ZhE{gcX5TTNNOM(|6Dbu^DANe6o@(K{NRYBX4a-69czu4)&$qD zH^K*@+ZqEZvN1JWTPq!djbBFWdBc{|7$Pc5BjZ>}ejFoz62WSR6f`<5$Tzl91y1Kg zGYyI+UduHv~s`6-|>4Rm)lSRlj>l*}A|0XN#>{w9W1i34)LGDZ>q* zMeu#Rg23+4^eYMViL6!?rS=~P_^rc3Jr98=k0J@T7d(EJ-90mxM!&Y$JT){L@r(KR z@_FXarF%|+0KY*@Aln^`Eh(E5rTcb#LU4t#(t^F8%?a#P2eZ~fsr%$9>=i2o17w=+ zUS;ld4tJtWP{as-06Xh%bWbUP7rQY|KyJyN4j5!DS#QH|$GE#8|$#UVx)vdNe7I8NiK*El*-GxV)ir&lYU7;5kdKxX`ey{HAnc);}C*je1+Bt{XF@E%KPp+5!%+TPN zAKlGD0k!r<_s2}%7A)w--&&5&zK$+(;yh!51S6(seT`^3bH@(VTI{CgvFVgVyd5z; z+LPbQ(?&A)3j46v zVcX6M>kxXA#RUG?>nNhGLsZOt$vrQ3_?NYs7+!q1?uiFE)b9)eU&L*0;U+mIi_Bc>}rCd-my^*_Ltq0wp6uu?HQ%gh0|!M%FGo+us^5|$Qq4BjsX zwwyo_)VefsbJk8;Zaaw|m^^e^F{+36whH;wk#2?R8kkZm_QWpp*p*>m;T{g(en>Qj z)UE{z3-Ty&9;@Y>#nwkV{CaKL&&1^|Sl~8aPf5uRm_`I(m3vRh6?*Qmq7e+tBqvxt z;~>rf@4YgVb>Oajo|6KXEzfZe(=1X2@9enUN7xSrfQo)KGqQQTC%GudLL3m765DHl z1V9irXd(RV&lG3%zFg_A0T$gIPDk8d!wVVU5Yn998v_0p)cm*!p^seuuS(?T?YD!39SmY=IR(%o(~0_&`YZ=14(oXHlN zMw9ochYo*};K4U;$Luck;jpK8rh5qZr1j$5%{Umrgz(E+*lQ0K{1XPp%VVrmS`6vn zep!~Z5iiu98WtfErBFKBamp>?nTk0M97JG)VcvlEG7?&G1;W`8R20Sy1Ieu~H@5s0 zg!S4_O?Dd7qwBRsoSM2h*Ql@DjJSSI9O3SlHkp1gEMLyK5$xtAz`X}2Q3zH&(@C^! zgPnwvzgif$^9!6Nu@UCIu}%x#E*bm@=4u&j6hmm)-ZB}%r#f7YkA<7a_?3kOYg7W* zK8qRmwWP*zfbQ{Fn7g#z>$%g9B>68mEJz8Fxz7XEXwsQNaiAQ4=1Oa^KCzgJlbOa2 z&zUs?>sVZJ5vs`bHkO=kuz=cAy>&RoO&x-y5;-7%zZmr@+&scMQ)vAD0W+)d;!LzF z!DK4cSMO*Hy{RFb>i^RePb`?_8(I}n$RTOSS$b1s*x_P{`CU<&T7C~N9i5hrK1+#- z#z}^JHR?ZPR4LXen>^qYn24q;kaq!Hb$rsnY=UO@M^4$G3e|{9H;aJxNWt$@w+*dL z4w9vW&1caPmv#%F&jEDc5kTTRn+g`R?3*J~7p7(8r%~(w0~DzZozWKHJeIx{!E_2q zv4V}iqy@MAcuBw(Of&oZw--Jkm3SKh;$=9<#c{vDA~h*TMO?~HLj@LOCP(l3247A4 zx0qneWM4=LHy=R0d7eHa(WJ*1io!&cBd4;veFaN=VaveDe)5303+`kMo7Av=_`IZW z=ilCz_%hnJA!5BMKy=puPQ?`*)XWi}U;HzRy$;A$>k8^Zc%BGDByOq3$=}zP)F~zl zp&r#`GAl7Zx)gn`m%MC|M2*~+7rUz2TM}?9lb+lG5r*J70m!$@pe54$M+fTsU2M{2 zkoOf?d~&z6E-NI2)ZQPIQaC6ifL;>-OicxAUupNxb-P?Ln@1Op*CqU2^*wo}wym!d zR)UMy`n3LKzqQ_fS(ffV=q}qhL!M+0XL(b_S!%~E9#8se{d*LcH&FjCq@wBn_1#IA z6kBPauQz)iDzak?W{kjt_o|QR8P{Ue_FVqwT`GSBYl?5bC9dKUG3f6Fb8G%wp10bG z<;%f%e>il+oleGW;IbqX6YFg!)U4+zTxhcd`aU#5e&+QI(gf+5<}eFn1GcgMQ}*uW zv1Xw{iWSRGTxm;((RXmRmEVl&vRGNpF|X?y!=SIuktiXEhr7RJnEd>Z1L5H;&)zx< zpP^Q@M2D_A|GhpvaUe6vH;Omv>_tJ;9b%?>6}K(eaDW>bTrdZX%7AYfvuL$S<3UWy zy3E!?vuE9uJu(s7pED%K(_W0}tlN&n*Eh)B9p1XtkukSON_2PKmqLLN@<;fP%I z4Vx_TvVgM|wEb#5{3W+GTnSJKr*D8}V#R8Tp_%LSXo8U3y#Cg#tZ}>`>@yX&lGt{3 zsX$R#5-Jb~m}u(>>ASiz1H%dXO>kGg&hJe0&xQ4QF27MxWT0bR@MHRev^K!qJVbk8 z;E?|4N0LXekO<;gRbD2sh=-;*+wx*S2RB5RtNl#Hn2|*4y)KkiDN6oa`yz|-`*%kY z&}81?e7ClJBCBs1F~QAAd(^RLZ30Fs9*7ZUQ7}g6DajPLaA{v}%aeKty@fA;q=1vH zBNz3}b)N(EyO05!aANA$r-flHo@gSnm}jJJ2(=-9_X88cc3Z}X?@Z>+;L$f0Q3z8v zHPq&(7N_m25-$uN+mm5^0%!Z%%g53=VyYeMp>t(at$p{aY?TZ;wek~!RABTYTmF^ztEO}<@HkC2TgH6rRVhhrlymDfrGCC z%JHo6N%hQw1QuGph?-{D&5#IWHx?PQo>sQOff-swVt&PVGzQW1-x5HYyA(5Xs%%0`?&;xSkmCh2A}aCAoz2Tkcg%^Mcdnw z{C-3-)z6bUQ8|PhYAXpDD{YUB!0OzHmUmNQ@$7vr$tKJDfAi z+sNF}_=9Q;VwqW6Ut07bqQg~fj?RQH>{4)m#}M{=@}Ij}f&E;~HtI|@rE^Pl>|qX> z*}%0+qm<6#r`;qm#y&1FZyorocHsO|l2HS#h83v9q?DU#t%15W30SS{68TCUw|Yc@ z8g6jw0bYCPVZdHp(;iA!Ty;92h9p3H?(xH{{;ol;k9VXLAHuE9QK`{i!>6!FU(MI7r z95V<&NYeSCvFYK;WLgzKgT7&HJ+{Fiv29~5AE5+p7S6w*LAp+$FugL)$mo1yc0!sJ z3a;oO1^I}5YmKv!57U>atO;CZhhdtyoa}^3JFsU^bBIT;lfHX$ZdFAK zqb2KP6dAgbyP8<<(fNA7UbPU*LFbFRH>J+=G$HYucjOBF~zH9KmB_zZG(}B4+{a+iRao zQIYJcyW`qJbedV|&9gPZwh+`uryCjjtN?az3Qknt_A~uQ;+oW(4jmb{vy4fGV1{Gu z7fE@mrp({E{?BQiL)slin~~;=gl4OVTsXKTgETpOn+eMo;+XdaoMk4SGLG|at|UKn z4YR)M)Ni}&gDRlYd;WV4IGT}o>0T5!KOzadPEIjr&x0ekEU+h(rcHosru3d-#;_;N z{xla>A7U<}sa9)R7s&PIh_2LIwJv@q*0wQC$jjlep$+nKse$DT)FD61Le!Y+wf0;? z8V6n`%%yp>S4XvgqOeTIhH(PpUnJdQ30{4Ge_;_g7i zRS=u@*~7po=H552i`MMTvTlyD&7zo#*;E>O>1w-5b}z0u7xA3cN++l^a#2nZC@=Pw zk$gEj+hzcx(V%=-Ft<;qy=8+%a>lVCZ5atRlpM478H_JJzR&B=wRWH(!r|)lmDbK6 zI|ef-pL+4r+}@wuChW#G=r=BFKH`_64qiQA4IZfE;DqWPS}&*Aofy5ni|oB2OkqiI zC8H2iI|sdCzB$=lyVYk6;|{mmuR>a*6@ak4GF6i^q|~@ioGBu+f`?;w2_MuR{2!h9 zu4x59K}!`8pRkr?9V}!Iyf{up^E_$;J;A21=a~qx{Gu6*h)`a7);#y!>2D&+IBP!X z2e9pIhWRh_BLpyp>{g(-eR4n@S1NzOF^wf|v=tee>?*uX;4Ml-Fx;<+!|)+05sXuF zR0Hy|>K(ivq*Kzt_-i16P77)VoCiK%)Yt$Ci|6W!y%dN1DL7(0*#`CB0F>zQ3{9;d zCJ#L5MFohm$!k5pWsAhDKxRQfQQcH<1NVlGsJ{CpL)R3=c78z83vHxFQ@j_A`6br- z+>3dcJ{gEAQ?ne)*!!RMuddMPxU5>a-nzrpZinoal)|S@QcHUSSIp{zC+Cimlt5Sg zuwZTt+Ifbv)r4w*v;v1#E}e^8^m_X}*)LI&C6zi5i%Up-AEYwQ%5RU`6xcRL?ag{9 z-C#4&az~gJ?H*n2Ek2@QS?|!QCMqc8KsXc5m;E4e{ZUM)6J2gjF|%kV{*LIkl-{r8O4;9m&zCUq1dh z*{P}%k3S1f76FOw_*Oq&FvKGCQo>i}qAYeCc*VaW89T0Yr5ye%33l8r*y?J=U1(^w zuvg7TExRooZvWQ2mKgdvPH=1~k;kP;{HiX%J0Uv!E%=J7h93_lnHTGzH7=GiPK{nQ zgc9A!@yw_epR()bOJ6EjF(>m=5CUG9Q{Hn_sohQ=Dsl?PNd+9pB4i79@wE9R6>_RC zL2PJ-xXg0ap4LJox=KfL&=(#|IB zP;Yu)fB3q!Vh;Az4BP)I*&HG{&*!-(9WE2?=CkE&*Cj*ln9`#0n**dpwILQ!50N~ z60#woj|~IbdAh#(wMr*}cWN_PZpigkrCk5B3}cO<(hp{1$%HdSBL9`Q3UR=k!quuB zV9BNLi^V6hpV_K3W>P&FuLMR$aWM*Q4v4>RHQ8s>^7YzD;V=+LyNfA zWZ}d?Q#N5@3(&m`2)~<@_#lmFxEsS}u84zFi(=>s(iwxR{`cEH#eR(~7Fqw=?usk_ zZJMUC3@>YNh>3<~q_)ZJUn4?@!=;Il*`f}i1YMy-gOjF6-kEo6DqXII4Bt%^D}PQ$ z=qy5r&HNafc`mP-8ZGH$_CB51Q88TxE$o*#V|G0XnfZEl8`T*TCEM@Z=l3T2{rN9kX1J#35WaBXo1QtOqpdxl`rr|4BVwzUcQL zKR#9Yg8*IA>wtoBX4~qc1Wsi)I)HroJ3YFaBi|BwpzCaGs@wX?!-c-aSDL=GW3H=k zj*28)RLtSC2`9j12^Q9On>s;UisW_c8+P^nVLka1PP*zQK@Rl-04bIqp!*a)R(=U@ z$~D5?R|ee(0M7tJ=Hy+94trk`NW$*Hi@_!#VZlx2jqF>_3ULJ)!H;$njQj3=A@lhM zrAu?7C-zc=zY{$SeZmx4Bk$I5AM}8W3DGwp(EjSrLBJ3wT$)s}7GuY>kX+!DxVX+vPil5g z`C2N6&ZdtT-B)CU`Gc*;m%JM_5MU1MbRCL=(7m52<|H#`0U-^278N-)V#Z+_uAiYh zPBDe7>bmV~#}Uy>1P0A|A=ThVM|cEoKzyd5rb^^+} zC)TBlq-+?Xt$qE34Y<|WXqJe=(zA6Z(1~-JNH#@jZOMPz78?sh5NzkM7R=2pXi|Xi zM$&@Iq@IQjHr2K)<4W4?5s-sBl{XB`ma)v*O9Pd&%6F?u7P*54+7@HPB-BeK*^0J; zj0@|!GN*t{v&DZcI2mLBGtV50JpqwZTTbHBM-rarcoO6qNAI6GanAu`nPO1Q+m#ka zuPv5fE&Rq>Uq@p~P!E~5QoZGDa8f{O@NCZ*G&H3GKHNv$x&TgP9X%Q`5=U1q%^?Zf zUyAwj`?w;xKnDSr1>lMM1i({Jcu58|+V-Y>h?(F0JJRbA0a!l9cVI6Hu0p7XBKj>t zcp;V=fc#7LIRBb4k1l$W=-3L!doMq@QVyCVQ$uhDoQ9 zGe4oV8uV6?yBq7^U#8DjGbsEiMo>X>CCJc~d9MafF=2D{LvJ)a9|DF$b7+ij7|MPM zwHmcc93NagCmCNH#lT?$s&U`W0-6DYxMZRPgqd8OhIL|;@ zW&x0+L>3>^(Ifqbf2RC-Dtz})(SN|LAREae7hH$#^11sA^iBV#PJow%gChp!rq+i$ z!Ay{cbpJH<$V`#7`Jv)!6{4P0_q^G`;I5N?R!$0%q-?e}4|@)~#U{0dL$#dKpjU%@y?B{ZCMdk_kv)N-oEZbHI(U8nEXx z{C^ZV5nbGkTA2ACN=f88?Yd zU(|%-iPixtR#sK@8GvSOlaFYZhTa*qepabvl(-6)kuCGczo$!T0gIoP8-NW*axA=hCt?@Q)&69cc|V@UC!i41RauDp*2dd(Y0&Ev z8m)TQp^6pIR%qD5c`lbHpLza1pQYTlF2ZG_fPsKrKP%)6Dhli!<~CT3?5Uagk`RNp zD~FMMV=Q4@GTfAv{IhVZs?hxeA=(N#lgN!eVF<2aB?3X~MvfyfYoX>NT0>fQ^m7LZ z&~A!tgqarpfl6LDu-T;>*g-?co~oL7y^5sYJ%g+(J_%XqaUZGgEKFEei{>sS$E#P2 zt?ocN<(Ry^u~`DRjy6i3sv?&Jziba@jNG{8qbVu!KtU}%RLqbwor7{kvMiI6Cva9t zO~-(^>`c;rWb9Q{njRTK{L6>hP&nfeZMiln&hu7tp*f6BFUvg)lw+^5ch5-ON-dMg zoI`C*MvtcQCUmpDB=+vG!i!W2#K1jBe0WqGTl(|@*$(V?>s2H@v4pjBi(fDjbZ5D&+;sX)L^?Z{k z8dV*E6A}|#!~(+ya9>@?vq|O}d0uhvoS(9T7K(5a;Bu3oFgf;=UiHR8C%UGBfd# z?xx1pk;U7N8}MNarNza>A^y*3OV$b*}p;AHo9T3~LpqZWxkUd&x62>tZsRnigPFlM3!dgkW(HzMRi{uiAY3 zylF-N2(htESWn~{nz;|+e=>{JY|87TBF9r4F`J_x*3+mhokTkP!j=COTM#DOGl9Ch zD*Qu{gbfHR=Cj}@H`ELRl_Idzp=oK{io%WGhlTMaM>(QP-4ItzU#C)nVp9pe3&#Wo zbZF$MT|T3YY^H$<{W0iz^h&r&t?yW9$dgee5N1AU{-MRgOQKd4*#TPTd%JxRKLmSI7)2hyLD`ts%>PGn`Xlu-?Wev>fBG{;Nlj+`cA(rube*$zZ@a-{%RB1jVo z)mJ(4NKFEA1eqz5Kp(pi_QgM`)PYHI0LPpPOgHb*B-*tPRcu-vYjOb`2f6LtH*T~0q-ZkDYG}a1O#u&p51)Ps5oRW2 zf85HA>^0+A0ghH57NP_F|5nmyEnZWm>0-xZih?XVqJ#v33_S7eNVF3?57~`ouyWRg zTE1-}1{p-|0vSaGHaP9!$Le~uRh!Zf^FSuk=-SK4;C6o)1XkLeF>y3h{#9~&?6!hlmL^qMy5 z8IdX%0IY6zVB4NoZM?_OsYRrnGvMjR&ZMgE)r?O6!?Yg10lMn&5TMS=CjweKj#RG- z<}0XJk<%g6-WxOOL09=V|CWQw}=5#JnIvSJ!W2mh!bo}FVl!HG zC0>!bSa|vzeTUy3GFmDR&n(xxM@G$GwFV$5>W?wJSf5Dh_FVSjezC|frMc330^BWXyq z(TuCmBX=l-LAUe!`#XW<8TP|a+vZO2XHpFBigxosZK|iVx@@)Dw#}wml~A5)V(KP> zL(+|RJXM09Sv= zPKgaL3@W22uL9V7BRvL>@jNNN4nx9>@&N7lQ>swGDAS5tDf6*+AT%}lUT-XiUhB5U zai{|6c8uCPib~hu>10+z#(<@pdII_ji==WvYU(GqX-E4c(viuld|+IVs(%RmOaHs_ zRcmQmowUt%p!W>wT+HA=JC*PLzRrGI3Ls8RVlau-O3AvwKCiPm!PkmZya#giz3Ffg zMJVx6js+iIg5l=K0JL)q7$uD%W_cQ#$(Q`~*0#vit4o*T*Gg2_VE&PD3eD+4MiLqg z@&xg1(a;$pIskxKkT!GNd<(&5;?VSD_uu;cZa(r-8<+O>`tVHV)j=cf!5mJ9sugTh zb~Bi$yCDm#9jNu}oDHbPjj4J;IyVOqONZYG@~1O8@Lhcq4BDBfI4gr%s4e#XyGWgG zzvBBs?>Cp!@ckw4 zQYP_XtvlP>{eu5D1`~Qyel_4;oO7kPNiuC!CoAunV^)`c%#matAego%{dGx>T|T~x zxrmnVrP;JSH50YAWC0aXB*#4rNLlhJ)}4=I=2vMfd6V}VfE!l41`RS)8IBA`f>lh8 z?=&kr@BXQ!`42{S)W2)c?UHN4x-0P)BGmfS;%iaFP{$70Sncv!#1LjQxp~0F!DOSp zX$~G1te^!y)-N!(|4ecS$s~kqW)AHzZATqSi)ur&B`_{D+2FiAqYcSziiHlc~l(9YXy5KYc-!KBHaS?(U3HhTMGh}#WDm+&4i)4W(T9bs_sK(eN; zoY!N@v~{X0TBpBAZ8~zv(fyZ~$c@nu$nWbodAdv+cX`;*pDs8)$+q4xe|JMeW6Z8-eHSJ=7XAC&lSK5=G6Cfw?@#!k&>!{7-d=VX z2Mew8a%?P+T#*eBTOVPn&T%jMMC=K)W4j_sf1jiz)?0Z(tN5WJa;OAo&baFYn{dnl zb>j`4wO~lNXqoZq_U=$;sWd z=|1$ct=b#pAcgD^i}DgL)CZSu3RO~0Ym)+-^<}h+zrI-moBt-r1Cqc^zov{8sCnKI zB-^2(@eE)=10PUP)!DDgW@j$!%vSJ$-_Kr7kHTkIeE?y^0*~=#fJS)Eg!v*3cz}DL zpd9f6V?DLaXH_d7&}*v_yTYgmaxXU}N|>B~ZsO$8sXMwur;Zz$clZWLniyhmmPGx- z)zp;hFl^UpGbd@`r1h<5KmB`S#gCKFD&Z;*1+vdV&y?XX4;aWl?$lYkIWIOGj}E%J z1Adtq=Uz zjBO`}f@ICs@O~t4x9AWpXPCOB=0*%yd)I#K9zyJrcY<0 zmSUnAf)B_qIWGL_ZS4g2%AyUo&|*NJ3GTm7&I_?e*uGpSZOyb3aL91Sia7;VTeuSY zl^yu?P^g0lE3GZ?tS(td((&4}aXBC)LT!!ih$3~grsdl{tGWOD&Y@o8h92(aotAiM z!Xa7`FftBcC}Ap9aWK9tl;@Em9E#E(%HJGROjw4Q`t2l1vMR=r3hk(QRRf~|!oK{f z)XJs=A$E-N_W#^*;0fx;w-2g$o_Pgg+$u2wm_UkQM|YZKK`uyi@>)*@N%ncwSWDdj zTG_M8{H_v>-^>Y1Hu>(s6x~%eU%z{HWymqu_tnQMg%dRFpgifk;srVcSMnfWs-6b( zn%I>47M>Q?UM_xC%6Y(Ncmk^?D>0_WJn+xNEW0>Uc_26EWLqugy3BgHC|Em^<6dUh z(*o34s!w?BouF=(4phE^om0Go?>ak7)fJT>ml}|Yw1iFFR4GH|o|5=`=c}TA(*d)rdbH^&7Us4_ zM$eGoMl`RaPD&q>b`sWGU8V*WEI$#NC?)yV5PY^UFMN!Dmp3Tez=%5Ofjc@Eilx8y z&~yfgCXhYw{l#5Ex`Mu3vIiy_G3X*G`G5=r2G(r}me%}OmedKI$@-4zcMhFLKQ<_f zi7|;fbQj;4P*cLG$w$Dy`%99HBb_q}s=K(%Z`hH-zg#!VVAU}Y2yo(A>#IE84<=M? z&o5b5$`a6XqZUO{6Gg9uxzycw&TL=K|Ily7%_#>z|0;{wC>aQfEE8Fx!I-@$TtWh@ z4{?rdLM+GLoQ0)~%j?Q1J}DI&D<Gs6=^hZxvZ~V<-hi zML0XaJ|)cdGWpq?{#fv1nBkW^(snO|w)r)E1wLy`eB0w z9X|XxO0QASl8U3qP%|^#1O7LYs~XoyqTO}1Of7*(sQzb9x0|(uqo5Mn462x0l-wIr z!Z{o*dL}=3WZ%z?0q4jYEn<1;gzraHrEBWKvVQ4Om~3<25B1U`#!3BIq>>?s_3a^g z3e@`*!N;3~1j6b6(OAE^$+3cr5gjPf$Pe`mLqzy_e_s?bg7RW)`uJEr5wvJ?9xUAj zBi&*b$ZwgHXdaf2oEze~6<(8(T#MkXM`fv-Hoy=a%WCPV)nP9D(WVRR+6Sg8MD+~& z^hFZ*mnxR6AoHn6bZVW>Anm)&n~8lN zUBTWXB@CfrS?Ybi%eJD9k1nB5^x-DtU^ z#9OF=6WoFBlD4{`LspIFtnv$~C(a)*u7V;8Tq{L zxEKSwlnqP&#C=Av0IjFx)*@Yz#|cL)AHA^`3W~plTo;%|WFR$N@bx!3rhj?Z%;fUG%C#Yo z=qm_(dyuhOb!Pk6Gi}b7lwBLSh;7J0In5cFuO4BnFy-N zKgwOc68ImCs4j$Oj(p*oNL(m03O)zlD<5zx9%RoN{uO*P^aUC{N7LLpOsokMm@G=j z5?L)5AINYMEfu(eVFsk-c|0_Tcl(K((IPh=3C+SL@#lHGBAXs`JoP^v5WiGV`5wtl zsSFy?(IpF?9HUhz3x`I?7wV>em)Hd&v4VwX<&p#60SRGq#fZ8}R%D?Kmq64qKZHsM zglwvzWz;Rgl#D$XWjqEN%TUV9h~pX$&Yg_<$gFc z&z+}DfsdA17}-a_k^ocWT`;Y^oQ*QH%X*!`4liH;yErDq%=zY~x|eL#KJl4?jj^WW zE?It{&psz5X}!Y#ob-qph6cv#icUvXu9I|hKTd8BZe~AMbiH`obHu^(IO;K*8@far zEcf|B2;ACVLR@FbzAD5@Lnv+Nq1wr^3SBE9hx|j%sy+$^u7u$uhKFz%VM7`Wk2&rM zMagMmu>>QiI9`Yz>Q?XB#LT^;2nUnFy_Qj%jM22pFe9I8iN+4jxk7v@STLg7XtVSN zHyDQVWlHar`VNuXU+`gD{?^C7GHIrfFP9Ib4h559nFC%rMIa8HTX~G_=wj5QZe$T%V&d&fMdxhPEh1DiAhsThC{mF9AK&_O14Z`$92Q&VSZrY%57GiFK;SmNnOfr-Yv?N-)lU@o<3U4 zR5((YS3RsAK*Kr_ih{V)eHttVW~#E$4(^h-+OmyP+3en*z!*Wl>D~$|-2>lD2 zxLlV0baj=PsBn)s$|wKT;Xj4HcvW!|PNV2`nV%l^XUo0?SjG?>eN|dnvzWr*|E{SK z{{47=G1zhn(CNqDoFTs3Q+@D!{VHL%$~jO~+1>zk1FT01hr#N6`He_QT&ulqBKz+b NNOb1q_~~~sW}pYKzmWg{