From b17c96f6c48a1e438a53160a5d00f2b4d09dea7d Mon Sep 17 00:00:00 2001 From: Anton Gushcha Date: Tue, 5 Apr 2022 23:34:51 +0300 Subject: [PATCH] First commit --- .gitignore | 4 + Cargo.toml | 6 + Dockerfile | 11 ++ default.nix | 42 ++++ docker-compose.yml | 23 +++ docker/setup-build-postgresql.sh | 14 ++ docker/wait-for-it.sh | 182 ++++++++++++++++++ docs/diagrams/hotwallet_components.dia | Bin 0 -> 1983 bytes docs/diagrams/hotwallet_components.png | Bin 0 -> 55069 bytes hexstody-db/Cargo.toml | 14 ++ hexstody-db/build.rs | 3 + hexstody-db/migrations/0001_create_scheme.sql | 0 hexstody-db/src/main.rs | 47 +++++ hexstody-hot/Cargo.toml | 27 +++ hexstody-hot/src/api/mod.rs | 1 + hexstody-hot/src/api/public.rs | 0 hexstody-hot/src/db.rs | 0 hexstody-hot/src/main.rs | 87 +++++++++ make-docker.sh | 1 + nix/containers.nix | 40 ++++ nix/overlay.nix | 4 + nix/pkgs.nix | 7 + nix/sources.json | 50 +++++ nix/sources.nix | 174 +++++++++++++++++ shell.nix | 21 ++ 25 files changed, 758 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 Dockerfile create mode 100644 default.nix create mode 100644 docker-compose.yml create mode 100755 docker/setup-build-postgresql.sh create mode 100755 docker/wait-for-it.sh create mode 100644 docs/diagrams/hotwallet_components.dia create mode 100644 docs/diagrams/hotwallet_components.png create mode 100644 hexstody-db/Cargo.toml create mode 100644 hexstody-db/build.rs create mode 100644 hexstody-db/migrations/0001_create_scheme.sql create mode 100644 hexstody-db/src/main.rs create mode 100644 hexstody-hot/Cargo.toml create mode 100644 hexstody-hot/src/api/mod.rs create mode 100644 hexstody-hot/src/api/public.rs create mode 100644 hexstody-hot/src/db.rs create mode 100644 hexstody-hot/src/main.rs create mode 100755 make-docker.sh create mode 100644 nix/containers.nix create mode 100644 nix/overlay.nix create mode 100644 nix/pkgs.nix create mode 100644 nix/sources.json create mode 100644 nix/sources.nix create mode 100644 shell.nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e310ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +target/ +result +*.dia~ +docker-image-hexstody.tar.gz \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5169d59 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] + +members = [ + "hexstody-hot", + "hexstody-db", +] \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cf8a4c6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM rust:1 as builder +RUN apt-get update && apt-get install -y clang postgresql sudo +COPY . . +RUN ./docker/setup-build-postgresql.sh + +FROM debian:bullseye-slim +COPY --from=builder target/release/hexstody-hot /hexstody-hot +VOLUME /data +WORKDIR /data +RUN apt update && apt install -y libssl1.1 ca-certificates && rm -rf /var/lib/apt/lists/* +COPY ./docker/wait-for-it.sh /wait-for-it.sh diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..b341ae5 --- /dev/null +++ b/default.nix @@ -0,0 +1,42 @@ +let + sources = import ./nix/sources.nix; + nixpkgs-mozilla = import sources.nixpkgs-mozilla; + pkgs = import sources.nixpkgs { + overlays = + [ + nixpkgs-mozilla + (self: super: + let chan = self.rustChannelOf { date = "2022-01-25"; channel = "nightly"; }; + in { + rustc = chan.rust; + cargo = chan.rust; + } + ) + ]; + }; + naersk = pkgs.callPackage sources.naersk {}; + merged-openssl = pkgs.symlinkJoin { name = "merged-openssl"; paths = [ pkgs.openssl.out pkgs.openssl.dev ]; }; +in +naersk.buildPackage { + name = "hexstody"; + root = pkgs.lib.sourceFilesBySuffices ./. [".rs" ".toml" ".lock" ".html" ".css" ".png" ".sh" ".sql"]; + buildInputs = with pkgs; [ openssl pkgconfig clang llvm llvmPackages.libclang zlib cacert curl postgresql ]; + LIBCLANG_PATH = "${pkgs.llvmPackages.libclang}/lib"; + OPENSSL_DIR = "${merged-openssl}"; + preBuild = '' + echo "Deploying local PostgreSQL" + initdb ./pgsql-data --auth=trust + echo "unix_socket_directories = '$PWD'" >> ./pgsql-data/postgresql.conf + pg_ctl start -D./pgsql-data -l psqlog + psql --host=$PWD -d postgres -c "create role \"hexstody\" with login password 'hexstody';" + psql --host=$PWD -d postgres -c "create database \"hexstody\" owner \"hexstody\";" + cp -r ${./hexstody-db/migrations} ./hexstody-db/migrations + for f in ./hexstody-db/migrations/*.sql + do + echo "Applying $f" + psql --host=$PWD -U hexstody -d hexstody -f $f + done + export DATABASE_URL=postgres://hexstody:hexstody@localhost/hexstody + echo "Local database accessible by $DATABASE_URL" + ''; +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..84bf49b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +version: "3.9" +services: + postgres: + image: postgres:latest + environment: + - POSTGRES_PASSWORD=hexstody + - POSTGRES_USER=hexstody + - POSTGRES_DB=hexstody + + service: + image: hexstody:latest + ports: + - "8081:8081" + hostname: kolliderhedge + environment: + - HEXSTODY_POSTGRES=postgres://hexstody:hexstody@postgres:5432/hexstody + - HEXSTODY_PORT=8081 + - RUST_LOG=hexstody::api,hexstody=debug,hexstody_domain=debug + # Set it via .env file + - HEXSTODY_API_KEY=$HEXSTODY_API_KEY + command: /wait-for-it.sh postgres:5432 -- /hexstody-hot serve + links: + - postgres \ No newline at end of file diff --git a/docker/setup-build-postgresql.sh b/docker/setup-build-postgresql.sh new file mode 100755 index 0000000..a0cf9cc --- /dev/null +++ b/docker/setup-build-postgresql.sh @@ -0,0 +1,14 @@ +# Designed for debian +echo "Deploying local PostgreSQL" +pg_ctlcluster 13 main start +sudo -u postgres psql -d postgres -c "create role \"hexstody\" with login password 'hexstody';" +sudo -u postgres psql -d postgres -c "create database \"hexstody\" owner \"hexstody\";" +for f in ./hexstody-hedge-db/migrations/*.sql +do + echo "Applying $f" + sudo -u postgres psql -d hexstody -f $f +done +sudo -u postgres psql -d hexstody -c "GRANT ALL PRIVILEGES ON TABLE updates TO hexstody;" +export DATABASE_URL=postgres://hexstody:hexstody@localhost/hexstody +echo "Local database accessible by $DATABASE_URL" +cargo build --release \ No newline at end of file diff --git a/docker/wait-for-it.sh b/docker/wait-for-it.sh new file mode 100755 index 0000000..3974640 --- /dev/null +++ b/docker/wait-for-it.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# Check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) + +WAITFORIT_BUSYTIMEFLAG="" +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + # Check if busybox timeout uses -t flag + # (recent Alpine versions don't support -t anymore) + if timeout &>/dev/stdout | grep -q -e '-t '; then + WAITFORIT_BUSYTIMEFLAG="-t" + fi +else + WAITFORIT_ISBUSY=0 +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi \ No newline at end of file diff --git a/docs/diagrams/hotwallet_components.dia b/docs/diagrams/hotwallet_components.dia new file mode 100644 index 0000000000000000000000000000000000000000..4e220149cad9555fc475e9ee3b435292a78f43ab GIT binary patch literal 1983 zcmV;w2SE5AiwFP!000021MOX1Z<|OIeb28z?rVY>VEAa7ROzm&U1@h)sa;j-OEfkU zn+;|x7$@;Q>~G&Yn8Y>)8ykki$sm==WVm-`m~+p$aIrspe3(Sq9Zk}Z#TPxK8$FH2 zLl%Vb=%V-c_s?fu@8jjkhae>H;om4B6AjLYQ}OAdH|Bi$zTdyUzt^LAN;pgOD4gjj z?f*xji1eXRzjt|}X{!bT!ihMRA0?b8;b6w87Ly6R=ncs5c9gJL9P}2g@^(WOu|&Hg z(M9jwO}_Q|xuO1=r7hN{WJCuECAZba4cL7DFym926b()V z?xJqpCEdCU)9WcqctS$H>2knWL`l5x%99yA+)p|r5iGH-!#dphO~^Uh(f=C~rF90d zIQ)dReUUp#!eBda*IHCq3Fh#BIFx!x?!$nOuOGHu64ccCT$1&>FbxM0E$e+4^VZDI z+c95U0U1XuPj9&57S<|BDDWtz_z)5r&BB1DI~MbLn+i+gyjg$OycgRwoSGm^rxBTN zA40KFg)I{E5MmffY0y%nsS1@bnVxZWR)`Z4Vsh??4gR4+$>D#@?mHNe@G@j^48s8x zdNyT%*0dd$bywozP(h$(USW;VB=6~`r8L#@V3YSm%ll7O4iOm6-Q%Z~<-WLZ`7-_- zvHRheBwRbw{v@NA@^GmA8m70s6;=1Feuti#E|1rM05L=?w=yqo!dx9rzY$U^ERQJ^mc^fAcWO_kaZf-$@P#W@um`Q zXR0b^}R4G>3kXIPGcKH5At>KlL{n z-HE7ARxjTEJ=ll|Wotl3rYANV+qUJJ*lFf-ooz~ksH@{sy~YLJiKwN5IjuK?J$b)z{zO7)u+vS5Q>8Y??N` z1x+L0Ry19MrrY5Jvkgu_@;SlCIROcJkWUFnA|>cXPX77kDULX1j`)iOR6Dr}$dt1r zRb0^(R|uGzx~tftL$&P#7h2vI>;YX`k<(xoEpANX(9@e6e)C;KxFyDD&!R< zv_%PDXYp`M1MNGR(vl~Gc2jOZJ`lP@a)8_tJRsda2bzF90(OpxkN4qVgZZk%Jy;_5 z05`+buLK8QJuOsE3)R!YZCR#vHREHYBBZMbByK}mx@lrdsR;G7@C7_AZ295f4jqTv z&~dJG9p^mhIR6#sI9KU7rQ?*2d;L1@7shTwsKU)hIab~#7y`sp(jedbtd0j9lO}{m3&k3?e)pG@+up* z@jI55?mM<~YFd(Cv_!&*DavhZq#Kz_S8~Bk)3K=%&9_&F^9xI;_Jbw2fHXzh9lUR6 zdp;l(t6Kg}t24c(ykT2%-|w=`<@;UO=;Zyb218N>ny;Q1evl}lT>G3r%%*XmxIs2I z*p7P1t9xSb+aLxcpBRiKF-U*QtHF>IF}$71eMJr3QiG+a;d#pUQR8p+!boo27Z5{U z_l2F;Q1{hfzEs_pdUmLu9jdx7B_+xutEK9`)Put}T=#`g8)6Pg7jwpwn6r<(_N&2s zsp2ms;FN$<#a~LpZE84ERDUTAr!?Fv(Qw~D0|g`rK*IeDM?cBv4~Wra0NY;NZ1`llBJ(=**$qqh_ z^(JEju&jrNoqSvoHmc9tr%zN(f}G1Z%f6XdTmBUW z>THa(k&o1}v6Zw!&>hdN+iC|=MbWq|hN_U- zK2rT7Mk;O%sa_XI&D8G`Qe~^(vaxN!pr?@f!jbA7F;W}9VgB^CK(?x7){h8JmhdFX z3=Q*K{4NmA{Q3v`ttW!3hPb6E;L^oATH-yLL7Hw#1a3`mwsqs_g#gz*V&J-M++eaS z^Hl2nC0O4c_;4SSR^)h{B(24fm1UVb|6V6ib@BndJyPA{UhP5Pw4N87i_h6Fw_cvC R{j}@l$^Z6t;jZ$4002R7=o|n5 literal 0 HcmV?d00001 diff --git a/docs/diagrams/hotwallet_components.png b/docs/diagrams/hotwallet_components.png new file mode 100644 index 0000000000000000000000000000000000000000..de516116007cb8d85cb18a7e11c4933d5c41ce4a GIT binary patch literal 55069 zcmeFYWmKF&^CwCKLU0KXJb3Wn4oPqZcO4)E*Wfx30>L4;yL)iAU_rt#z(DZe3~qzV zk_58v?m4@A&prRWpKkL3c%JI&uIj4l>gwMUq$n?mjzWZjfPjE5EhVOmfPe@=KzO8p zjCA{i3l{h2_7~DSfTS40&F`O#mi%Z0gck_XVsBJkQ?_PZJf9ga_U`XmNz>rH_3gcJ!X1Y;Ilm@Fb#GNeupTu|D|dMmb!v0r-&6!iH{|YEiR^%^Hg&^llCKZ z&W>=K$iwGRsqcCGw`kK(Hfy|Rj7?V)(K$9u$$(DtJ32O08gYBOmf5o#(> zFge0y_Z87^YDL?63l9WjJ}yFwfoYzO-@@Fs_79R`lH$I`&JM5-SkMQ5ACB=-^2?yc znE|o0`2RlfA(9jeP?cMlNhsRxsVo}n0Fb3*loj0wBm4?vm`nfD57}Jo=)8Euzb0?7 zWERATZHv6|w=+T!B6bVtX+7vkr z(lv0>de_lXh$|WO!-Q0fJO1G)%N<#Q?<-cnz6W4Y<=xE@uJHo9h1w)v19Oq@FCoI? zihhYBsz6w*sNkJ&K3Ix2VweVmsB!Lv3G?D9u6H~UIy%Y|*J-kC5ZNj3IH-3zh^r3AQp%#-KAsEHllW2^+pX_ zWLA>@y>kMDNghQNVU0!XWKAKd$RXczRCFPo2m~s_PIdbVKr`4s?rbCDx}YWysQp#K zDeIpM`fqR{fJ3};Oa2nv!8?`FO@Yu2I{k%H9t4%*Dz=e!EH z>8^+7ZV^e0Xde1LI)ee|Ve!PT3?wT>*lsaF?-vp@4&lIX0Ofwb#4J= zQZfO`qIW0a{Z~@*`^GRw$tH%=wqtR~>Ek;~k0Z)e`wa27wKC}y=1Kryi_*NO5R?eB zjL*zd#l63t9Hn>b#I8A@-7UDH$6I^a#u>xzP0wUOQo#gPwUeUxIlc2=X{|(;8xj3% zQ7fUUp32q1wX74NZ2GVkH~o#81)#`rbXhl*$Hr8fceboiAIurx9}rV}?I1@A_ZL1r z(qh(r^){6%Vp2+{7gOcLqfPNca@}%hpqlQZy2@i)NRa}!B{YAOYaw-w^Tr#q0C2Rc z!%tMF0TynlvEC-oj#YnI{K3O%_lxc#>f<{8+Q5SACnfH+o8EOP*&=d3GiI{qQDZK% z)pAJ5g|mu3w`8oqfP@240_^e?5HjOk-F%weGH8@mq0ChqeG*4mn{tY9_`NZ_X^-(l zWZc~eGItm;GLgf7`Vv~11(u#2CL zRqs4%_n#Y~9G7!BNE~xYV-RX7<@FAY3avtS^7`|_in|{*98PK1ila}ahlvP2E`{7JA+V}+f*_D}ulOu7T^$=($Kf9MV zZX&1USQ3|oa83FoF&D8FJc^fY@J>&9wYh2jReH&^apfVZ+|UkuwF&(qY~s0tU7zUc zk<`Fqo^~xAHZmrQx4XjZR3OF4*uqNhJ;#ddDHbZdBZk?H6=%IrI3lj)Gzg* z2YzWIC-K_N;6J)l+ zA;Oa=?9J+LGTL^Ypb6-KEYot#I}g_s-wFVjFmrpme2$1^Av3RUu`m25q_lq^h_cKA zMDia(Fpv5yy~h#hv7^>}Kvx#w*B6&kvmiNrC!v}OoTQX8z_p;dgLgm5dfTx>{NPx# z9fz%$fiM-DCkZ9A)cYM|0Zbc{H?yu zbj7oI51+xsrQG((}Q}q(OI7nt)SElq+y*+T=K?z78bT??Tt zT6lQn@@$h7xtGqx+T%N)RW2wCVv$z&9ctd@^7?RCrG0ZIsl7KDuU&EQ$0;Ja74)v~P1Un*ZDkvq&+G>@{^S?om!tqLc#Z0hbJs+5>#3raC-!RVY`q`~ zgIsfuBuR#3SOlX+FB$*5Dd~yDK&A22YUHwBwno6wh-#B9ne1n7e3^qSXqzyaI>t_-}BJ602tkXBgYQatcH<%F^*R{I4ZScx#ZkxPuvP-q99ikF-QuXx)2T%NM zoaU)IkMiay5;^6uF6XpX6*=Xk?}Z1Hjk#Zc@=lULRv51LF`r<}=j*&ZiY3+24F0#$JluX+ERYZ{40{(yke7OTaN0z>c^Xdw!nJ zzuY@6Y3i;6{bI*hff+3}W9`etqu6|87uC z5viAs?V{>g_~>>e`W(=9e*$etM6jS7H7vAnJvJZnCPwV_-TsI5Y{NiI9I)K`C%6Ypo)no@!M%tvhu){!M37Jl+n`UHORSrwMEI3ak z(qAn{O&Cm-8KAeNAoQ+Z7H(e3%A!m@admoiXwU!{j{#>coAh<3uZ44ICl6Jv-%0Hk z$4>%Ldsq0ZuaZfG_OpPKef}uk?iazPwy~&mEA|GL{5Y0L)koN~#VymdjPp`MdDc+w zjtQTz5dzjRlxb62ZF_!_ZjOeqigf9nK#jM}qEV9O)6VC@1kstcai#Hk@5&QY=+Z~k zi;g$9V^{co=9Nguv)Zn2S?WVRZ?7@#T?#D2< z{;#EwxOa>1jHM4u@)$>Vyws_jc!k8}-D*rbteE>)F5*gz!Im?UlWP3ny+hmauzdeM zb}enr^9L<0xtG-G@sXJ`3?!I$Dk@Tul5pIubt8h*g=yEeY1RqDlT5tMTR_qP4QJ3t zd(542Fr)DBCMK}Rp**>SnXE5Z7~nEDC%SOGC74%E2C5HOWQwR&Xwtgc1pydO@Sok3 zycFHMJ}E!~9+KyZ^Avo<1S`LbXx$((?|biEAo*D&8!_1-n5vd9hg3gzV+gf;cpy3Z z8G#K?>0GYLkXb%5fI*iJpyFMv6W(1~^%-rPo&}J`yyP^E&T6qAN8(1~hH;%ov$Q)R z{z!jhsNSzA)l`%23LbmmC=o*g4o_?>)Da0wXjW|_y%fqLFtHi$$L!tgBOcO7M%5qr z+^^tuXO%ug#xkwGY6}OFvQ2gaF=V!r@x~-iO_Q2TZQ1;LwTS8LrOF&53DsUsUKSfqgfJMusPIv(-(%B2-6UM z-p5|ixtb)H?C|~O2qSv&i;}AdhoN`hgXILB}Nt>^(Lz-c$(NE-g zAOT>NxX${{&d|}Q$Q5=umh*tKU;ZNa>3=rWSYB`^dBJ3ZK@Kc(i+y}yK5PYz2OWnv zsVf0_sTy>n*T^qu2ztrnVp`MFn_gjni!;IMGpurctsHXl_Y@M|^d%)|hyOy$tIaxA zIO(*RtP#czU%!09B^RikFdEfKn(Jqhzdnds4s8L0hwF&rVd?SdimMALEB4%@?Hcj0 zX~JyIW(@(ZpyQ49)=!m5skz1Ei6!N6*DAA=!SQ?*i@13D3)-6>1C`?gL`Ej6BrILQ z&K4u>BblL(4!Y8d(nQmTtIONQlRs&H*BT-#>3ZO9yx=IrRuV67NmlypN}$?l0N;AD zQoN}u7*tNK?&~hnrU_6cAB)w~p{JjsF`^vH=@f6=CP#&DUwT04i};873iAlA=N*@wl=_z$TZfA-MR+krHp=?cZ>yM)!+P0I*e}fRd;K~ zOIVxxhaHexshOVL2bb=#W1ltb20a+~asxp@H+v?u1O{#7}% zSKO9^ZF2Cf`HLlQ78;j^IyPp$HurjDl|B6!*ab(;knn52`V&Qbh(yJnTt0h<9rQo3 z<@z5^oQf{bwnlhtBOrPj@G-*MQ=XNdPTAL&G&$8u6Cj?A$Q!2NJSLNMf>ls3yT z6$Mx#tXNHPy0ft4o9&UEP94)g)Jh;Ux6r)klxGXl*yf~^V2o$$aAc)3G%Kd8I!+ww(c4LnT@+!(E-h!n1@)h_Sa-eVps7MqUC3)Y9qiT2o{ z%}!3dG+{rwK24m0eJF&Zdn?U9r}sV+le@0?e&gkLqOq}~yRwSkW{Hy(pGRW;FyN00eB46)scG*mrV_b2GjfHa zYiO!wCDrYg8Y;++2Jhymw!>0%&aiTUo3lb^)mI!1W?&RKw*wPBcXi9(LCRI^5tkJN zD1;=Eal;#<20oSviijClP`~Ka*!V7JX?v{BoPK311loMw=h*~IZTi-rS)fGUJ9^y% ziR&Rio@oYM)amC&l|Opx(hX9gA~zimmy5pPhzPvT)5kNU9_Yew>uCM}f7)ZM)nm`| zN?ML6x!x|If|_c;kO=NR&Lr{+UUBob>grQlk^@de^91`&f$QiiwjMu$lEwK5s<{-m zT@MovC73mU76lM2*d98g{_$Q){V#7E*;vv^uJ-JVrt=WrZ$ z`{bz@hjfLFBO)qD7UmmQ#ff{9%4`Vx%(Z84a_Nx`rW^B)u5$5rZ2y?y<1lxEg^gC6 z2bMo|qcx^GpBm+Q_{H<=G9RGq(t?FxX6^ev#(kvE`^`SbPvAp35~GZ zcO}d7w7eJAt(sUS1u1k~*O*)S8n*AE`0fY$b8X=_7b=#U7&HHtQ(o_*~0o^tgld zM`mTZZ#pJEJmE*-BiX2184~zDv{$WOp(l3tGW5UNTobZBPqm}&!Q{l{j=!d*`r>e) zzq8mh7$i@~xMR;@42~>C{oxXkW};gu`@UR5HZhZAMIV{<^zud)kRQ6totg&pIz6Px zV&vWR{-a0|9yrT4wGiJG6EWh61;Qk@y!q4A2TwBF@e;$T(m51_&T`z&Q_1C8w=V+M zp}Du`T95d`Qg`Po{6utcsvZf4UVc(Q+rBqTl?ymI&$d2v`Bj+hSwR=vVcDG?qlZ+& z)3|cLD%E!UDmx&k;yu%Z`6^^rhNR9emT;DDM)Ov_?Q5`H9+>z|=H$_B>s{&+`8ab=ZJBtJ5_E(Gmv3sF6k!(t>)GYMI{1E3T+8IXHL%N4-{9 z+N)`de}F4fig+y?TA-ADROg~z?7Ncqk^g(~kr%9c(^(*DtUmIlj)Sqbf=E&`s+qrD zM9;r<4%eaL+Wm$vnB_3~Rn665C(+0lg-Z1sj`^$Wb@w^{0Xp-=B2W6P_#L;e$%~#W zLp#cwhCGEXF*Lh#;^*r+`jYV^PaknEURQaN%ZJuTDkkw);ph!Jc%-2aEk51+AD3-LtjlK|DDMRN$$`rV|jLA8OfZ+ z2O7|-;EFc724+hLULPjzPGM-rj5HtfMDgbxdrvB zT$lHFl6q^WD4QKe4}a?^W9s*%4F&l9`qe&1&FGTyCb6wr@l8u!M4ZCdGWm@{Hvhr? z!`_hDPO)uO)ybLs6XiMyZjUfwjg}?}i_0w%Yv9?Z{|v8cOw z(QL+l9OrY4*zMT<9QvNK7>*K*z+jotW6lU_7yk=^ zILWrs%Qu9;zTl?U-h#a>)RM5~kjBCeV%yEvJ(${O#gbR@4(jJA{cF%wSm8PEz<0L; z1N2=&r>zK5W+ynj1-`zvyBTkeTYcK+Fjm)WkuIXoLnpamk}gvH3^ekh<=|M#X@7s^ zZ-t?Nyr5a_{fLSAw+q-oBY61hg+#I592&EyX=zbG(CdsL`+Zovg7EH&MPAs@*xgsm<7^RI=@v zS{0x)t#^Pr^W*|pN>x+wMfQmP!$0MOPJ4x3Gw`>7G4JoF=iH-=MtpeC0)K5MDP>*N zgp@ey=tBKgy?i}H;K~}F%!B#J=BXyj+v-M2A9DDqTU43jeR!Vm*(RC>cpd z@1Z`-w3;OL1;!P^_$fUO*|iwL=-lOjvI!dRD@BeG69)JdTcep>R=R$rW+e%Wcn=Rm zIzP1(_$XjAd>X09+Dffc^g@ zy;>{QjoR4){(Y6-#^Afe%ll`v*?Q&r1;_(Yo68sQl?CYYgEcg@5~{aXba?|({mC4v zY;kJMDWS%xXG)s@T zo0rnWF!H*hgIExni7l%-cX1Pu`lCwOfn=mBODjT#3g>z~H7WEgGTN&`P8MY%1X zEW}QzWTaf_)R^?iUTv1gV-No|EbJDUV9?mIG-FKpODA$+F*<>qb>M{MUXZ-o$+PlF z9bBy`NPWv~m7z3TZniS~dRKVE;Rvdesv{N?rZ(ruOg5=vs&^cC-DFndY=T$xayMgS z+`FtW8)>r{WK=U^!ff>7xp<04W+Ts6>gfDjjx~#XeJ~|&PHxqlMv`PyaoG8h#ln(= z*|PL>nO^OMKu)Sa@Pv^IHJ4J9XcUQzO&n7I_3rN)4zNmJ#+cb%=g`^1xLdb~Et*`d zO=W4olhAs9}mm2ckF;tA4TI^KKMKSpGMhTVd45SO=63Ky?@$2tbr;AXsj)d_~~Sx$d9#;|y^ zfo5I$@kwYS*hB14`1&>YC@hlNKlLPVPN!NbB)nC4to~zIzpDR?KNaVZKc^EZpB`ha zap4OQ{f3Kiv!U;4GWM>q6^_+A-iI2=gj=uoDu~d>m^HYfMJ4#sL!lhxrmCmiY8{Q< zn}vNkE0MYDhc`J3q@G|=R^1v+!13f3vuPqeD>yb1K))sV1O%*?lspKK1snDJ3J;7-FTP3OkzulFklDVUSlB$Er*kqIjrmxCZ1 z6W=LRjzZ?@L0{8uR(-6cSWlfAuRT|m&e^BGqwHoh_0D9ebGy8WZc$`yuGi%=KFNZY zi{!)1fm&tUjT;;ClBPZZ>E2Z~g3PjUeIr>A%=K;NmHg@EP&Bn2R&UKTYn7^2VR}}`m)QQiF#$DIp}Ut#fj%_m*HvrJpCr+ak>!0bGMn;#>+mpbJ*+sctcv# z5Mj3kZS?02Vp=_gF_sdr9eEm)$^fSkefmB{Wut#HEF!szG5F~V7WSK(~eU(W@cpZvwHAH`X&`vTEUasu4=C857@`2H#xG{o^%&9PnMB<4EaV8)j6e=k~&IAhoM5jW$dL#Gg zT%;&h>-KPZ#dRJj99%X?uhvziq(bMJscS@E)N6olr42zLvvF#5|04fEI<1r=rt#Nn zxzN9MIwqR8jOr}7IuY-YAO+}|+z>t#2g1@lzG?`|mB^&#X z4YsRb+KDw#xGrm45a~e{q4%~l?O@bx9g();1J!7|HQw&LeS(BgI+s5A-uX|-$h&$S zKR^Bep$!o=H)jwVc8|H1Wbe|(p5IyK@apMpWs$uu&gj3_62JcMYl#fgWTdUZv&b^L*Z97Q9kce*J!LdYO%(#bLVZ#ks2^Q3w3wS;o4 zwP<|ACjHOH7pH%M2t{b(dhCK$F+Fapa@vQ&A6%V|@~$u*O_|m$cfF`zU+Ap!l;;&N zeQ&brp5YVIO{?MSmMzlI%ii9MSa?^zHelisJQ4n(S@TA}1s-~Be}-t`{Z>@GiPXNH z^4WAw%}}#h0-3vUtGex}E-RWvZeXx8Ojt`a-?J19Y*Kh6Kr_(aui7f`hM6Bey3GnI z*(DInDywKU5fy+QWwpUZ;f!1kTY8NLzN0ZXn~Fe_yh@=384IYAP?Uc$($g=wLiB&!=IumI>?+b;?!VSKor*aqPmMwe*{{1?sGcqI@8u;=P zxSj5>>QxBCOq-}qofYI(@znyw>n9g+u^ESCS{YmebesFcEXuvOILrM5%B=!Q%mTn1 z;W)XeGYfu;X~qpX3<< znn4DB57(a?xsbCob*s{gNIjd%*d;az$RCrxObKeOnlj0X7eVE`85NKX7L{1I8uYsY z{n#&Y@w^0mLZ7gWRRfopXOz)QYo|F`AS@6UcMhTSaC$XrlKSq?IwK&w4x`F?@_oxgp|HxO&r-Z-B4jf*Eix0W zX<|;iF~3HI*4v*}UOU%Fxq%A!bRR>SEZ2IPK3#6^{d%(T=88at&hL8{0MYZ#YJoeB z1rNlId|Yd;X7|rujeZZ_%CkgDFY-He?VfHSa++}Zq&<*JdBy4E8@C$!GZrdEDz6Z! z0^HVmr30uOAY-aU=?U3Sz@1x$!trCh)PQe3FrUS0C&YdCJ%`}og>9kL6=6$D4 zj*#~FY7Ul;)%fKRdedr^-|_o*z^mN4FCDA$P`V?av?Uiv&l7ytglurR`b05WtVijZ z>gK+&){Sy0Uky*so~6l)4M>H50XF1+%2LaizjkSJPk&Y%7-;9;n_faz*IM>NugC15 zGj-*-!4wdNG?Q1HERb6Q=Bjjv!bz$g4x&pdz*P_wpOByH#$kQkPRVsTt4p{9{Yd`^ z4>;ZVHw}DUN|kl7{Y4m%S|leP1?waUuXfF7{|>3|T&X%YO5>9dm}m6)rt(*IpKKJfl7PEkBpV2^DU`kbA~ z&d)gZY^tk#S1FvTOdo-Za%{&NrFH+$+}=Lt_#djqcgz2O5vTNDfq?pbW-Sn48Ymh$ z?n$s9x(6kxVp8%yRE8=7C%tfg{#8A~7!oCazgjdz5G%L$jq05V+6iJoK7<+st>RG> z-Q6GZk_E084A}vpq-u?R@q$l$?}3RMw=lu{qUS@F7LARwphrcEkDcz61oaODMyRr< zJn1}VY3=!?K}uI}$E*N_(w$Tz$c_vRGDDu*t7S;>?-lYh?3QqpS7`wFzk!Vkd(dC>_*`T zcFQ+SP4-iqvEt9JWcGpoFpfq%xU=P&-?xtXDbfN2L3dMmEu;joXL-==7?fc{=VQl$ zZGN|KFJ7KD(Yp5)3~U`CyxJtW)A`%@c}#rfkCQMwgZ~aZM2`(xmpr}t(tVCK_6Tha zXaAns7pO*gjtydkQ$kBKFmWGl-B9P6T8&$dG zp8! z*RIq-0`a1-zcVj{aAHYo%==+|48;Gxz!(0X{dVCN8jMgt7-tMU#`-%spD(?#_jdfh zL_}I20|X<@Cf9p%?XLF0?VH=;jiw@Aek?eqngCXxq$KZ0Fw){|vfcLq%`I zf%zc@q412Ljep?S0uf)DBn@h_EPia#g6zBwE3(3f@3N-lgaPPOp7o20o zb~beF8NiiE*gp}FZMqzYC5P&0Z!zEhD0$D+x_*b`+^DM2tKk12Ci6RFKzUJcQ8R{m z4|OdN2`yT96H@RyFk`jbv({FP-Yf0(m2#m|rjk+MTJIaRcegtF>kkF#fv}q5Zw&Ih z<)8;F@ORSplgVhM$|Jvx%`X;qw;ccJp${Cul%=d$NfW5{>>q0c86#(R_=~iO2lt&3 zod3YU%SG=!>9x?xZqo3(LIUqoy{@3OO#l1!9dq~(7`PM3{j}PZ2IWsstA;kQ?}zqb ze;5c;zCzx30JU3moBfA=HehVe1?Dv3jXhoa=h%A+K2@W+!)O~1;(yah+^-eZ7nwo% z`2HT4LC{7l!!N(X^M3&#V}yZljAnknF|X4-wx4IA8AN%%j0N~=$Q!pY+4@r(o>Nc6Vj?2j8n3iFlau>TM0v!>Ck6vr_w~ZtJEBEipjzutDN5Bi zU?)`T?vsk%&k-4eK=DUOzqQ|&gvc)jkyBu-a1UYOt04;5?;|3S?% z($iqXW$~`@8>@);2@b?k*UcLg__^kj8Hgn2I)#A{+CckPUJKv<*VuE(S={gOuf*U< zZ#Xd@LhrwHQIu=e$%CyVnjd^GjN_3XC0Ke=pcJILBrmHp?Vvn%oC0bz?BX@v0V~OF zqZ2sks*y4Ei#}(}mr2(t5Ip4=+voec0gk0*!HcVFho!=M8c!QrdmkTF>IU0Br(0vY zNf*Jy{{i_t3106J-i8WN$QUkESd6Sb)Z?%(Qa5&wWi$7=g@F&n2ppNe>D#vFTH27( ziWB|Nxkj(waPD^P&WK)VDCHivO3 z*cHK+ztqRCnYH14CZ*?&jkL{&DXTZT51fLoy#Tj&>>HE$b+x5eZ;MAzjJa&ecSxfE zo*#dy_u^wi$xVc&;cYrYkaSur#T|&Q7CSS6d2c|(dXXWknOmbqPvvrWsESp!7+rS7X;F(Ao1AH8hi2j-{Jn`bl!O5hW zCQHgDB%ynK$`gc3aSL>UFZXzQSmw$LF(YQvh`EZ-_B zp8D7P|0fmp*rENMoFO8?MZ6!{jp{ll#*897Di@&G;SI)> zxS5@1NzUCR%p=+P#cs=QLmpzg%-T8PlKSKpGwl*kG6<&EX~H!-pI+*YyXY0JvH0tw^W&9G z$8EsovQd$^tyjH8>X{WXQ|H?a#FQI4*8O^k2y(TTE8<{I?m94ZI8 zc8v(}@(2o+e)_xb!aDdlXtXh;ceUtYEVr+ZjNNqxem@%^t*0>Bd;yTGM@p-4W6P1`$H^A61y;+MLU-%)>d^+x( zhj2y{D{{G=L>CZ0f+{Du%# zf5RkxajLa&Ae~gtpdY-R>p`}a$D2-`1Sk!d&6!h*YxW?3W!qZbq&!q&w#>Rf>3~*) z9KREoQ1bay3TmI!RYCVFVi{uI`=@61wrZC_^uAfowF1G;LZT5Mjhvr`4%MdpuP2*v ze|v>bW2ek;VqHrfs|`qfQ{+YpRro@gQT;lD9HdzqX0FvgU8POl!uA%Gwyo^Vji-W& zjdgw?ipvJ1oKb^*l#wr4FJs5r3V+QxXWe(l`^#Y!bY)1w#YIq3kJ9U^OZk%teQ-_A z%nX|GG9dTHNZ7kqSmSIF{KEXgs=0xyaO39kbKsv= zFa!Gq-NOh?I7a_AF7zqS{dJ`R#ZguI7n|OhiL{o1tPb0!aTNQT`v(dBykBw@n|70b zdTyXZv7~qQb+#Gp+8QFGqKf`3LtNifV#BTy(()UUw&mF4PG&AAbi8pxzpzI!$wL%L z{CS{h5c_oaBMJtjl-?>pom-NH7+Vdqzls>?_~;|MUe`e(`LCB3#B_hPTN&*O*316j zZ6t)ifpf#}|58Qi&eN7VM&L+b<|$yOv0DL%%4Fd4wE1rO^8fM(CtDf@`qrl(tWQKp^8`%^Mnt|F$#&X z%0g_J`%gs0RClV+UEd$yWQK8kfv;U8G*V{y5wPGcVwARre(-{1Dq(r*qT9irzFi8x z+FK`P{J>jQFaYAeMPy<|z->OcgKSklJJYsQ5HQ#+t%;eili7$#QnPR zgYRde?jWMn!{edd0QFP}qw%OxUQc0+#338Iq0r6-QIU81gG%+KPTtKmtdMhms3+mH zehVrol7hr~xKW@i(#+M2VdyETdjL`#E;Q3YVkx9HEg3a}VYdFeS@0 zVcHJSNS6G&lRDJ>!C7kvJa-pci>!Pv=&wOev87H2sR@87ONX6rp_WsZeB&uR4~DUR z)-rEQMLIXm!4Z%io4+1n^Q` zGt%J~l}2$MbVt+=ARy~ArKNt6{cN)s4Gj`>>p=(NH?U|(C!oU-^fb@Yk%U{Ho4`Qg zG>YxHps~O^S^0YNU%FP;j2Pz9Po-p%+*mhSbxP0nJ^Y@th6olKTwA=3p zF`5X30)#)!NygOK!fHnwgn(D;G}TTjuf)qb>dUU!47HvQ(da86*yDhghU3H&*z!=5 z;2`q+VprZ5ZX#lide?{9IA9eW*K2h`2|l)_PrMJZm`GBa?y&&Pk^?=m*pXZCW&4{c-VHZ~p)Q=6@8;28Fc)M=GjxvLVbGtD!W4vkiF?DE@(e=os6QtH>zZoLel zPAavw4Np1y8t+OUh%YGLszBdUMi~&*mR8ujQHCJNOn{Be$2}*4tJLsazfJX>kSQRx z;Ku$~AqpSK*xGyb;w8k>HgavkA25Ut_#U;b0L4eg5^WR5NxpG|KBp&6v!Su+uO!KJ z!M&NPp=qC$eil<-rrEUuvFf4r_^a2d0JOmC;AxVI}pcr!6+;54?Cas{YZmcsuv@`$y09B)otKWY>5 zevJ3XQRP+dPJ4v+&-tOWzuTY%?Ul8|htoyu%lc*OW?`FGjZUYSzXP^begT{+?g5Jewxbi~{=-YW@z7P*iZe8uq zACU%bZVu;xW;cthQVtEIhnPD(n?K+_uxG2z79sRc!uE)2$l~KM`>))z=T<`7lfK73 zU$pTGGoKd<{qm$$BC1trPS6uUMQ6i)ZP(9P1@qgp1GL)otLH+cetSOH0r@qZ)>R7a zx@+cEA}7*2jwY9fL#bx_Q^4ow6lSA^LEFhF((UsX;S~JSJ|73(ef?1FLVrS`hV-QD z$)Sd8xH1bRrM*?NH*J>vLi@=Njf(3k019`gjUR=6cgNMoa_s$WJ6AR5$by+ke(z$< zRRuBCRM_zQOArj&I;t%2K_ypBv%WC6A&8 zD*}VK9u2Pyc~7!?Z#Nbg9pcqt)NDdOV$dDHD@@*wb-zq_Af-+nz7<|=M2yHcIT^=u z<}53B7nup|ug`IItph)4H7pIax^z@kqKd@^&o*FT^WjVg?41v0B99H$S6-tx2D6dILLKNj^#`rOR5mF@1T!|2J(=V_7ovJQxJH+TNBEbAaS4 z%9Sij;2PUst*y8f0*H1N2QPudz+`X*7m27NC1pSs7Ox{Mux%FoRa(cIig4#Vtbl}pW!da3*e`Qi#iT}E0w>dfWa;S7-Vsr!UPz!Q_jfIX7+&7*E#+tsW9Nn?UI z2IAlIkj!o(RQN#{LF@ivyRL4p$EAXs$ai!2tC7H|xlhh#!`@c3VwDH5pH15uImj7j zwW7TejSml#S+{%2a3waYk~{Yg%e+hc*7-R%W}bHNzh?jT)!j?1bqDy@n*&K=T4^M} zUbw8G^~Br6h~K_AAx1`4e0RhUThh5$s7Pe|y1P!>{IjFymyv4rtKaeG^^S{NSLmDT z;lcR*?IXmW$%aA_F5CPhClk;IdB`ORhlJPtEWC2#`w?uG;?%OknLA$s?BX}W4(AE7 z9);5FugpLL8NT?UF4hiq23%B@Af0_?iFjuPFnuZA5w2DKYtxSSjc3`Q-{YUb=YJEO zK!#A1_EIXkg{#=<59{e>)H%i-X6%$4d`PCCn+N*xn@+m?3R@QP6S1p~rH_bj5RWC+j!~mYLqr*YuWuj#thF8UE8m3~vU1Q7 z&Qv3P0_B8(?!>0^c^iqs_t@sq@5lAZvN*-*z%SY%qvZl0J+2 zMjKPVHWN~<=#c~R5O3EnqkjxuAIAq?Bzs(OTfr?Pu=^Kv-0zBQ}Q{Mt|p;l!K^6;h~Ej>5??TY!lhO$ zF#hvkmsm~`8nV?4#w@ujhC{qfZ={|IL|s2v@9nKGtI|Bh0o`CxO>uQ^V=DCYT6%Xt z4lAVk*uTX@f9xt4J@pz;9`w#5*XG*%_>_3)Pzz1svc>R$$xuoV-~8TW^})U?meLRP z>@2iC;ouv2z0bWo5t1K!{u_7i9N%{kEQ&@A8YfL-H@0mxwi?^EZQE{aTa6l{u^Zdw z`z7hQ_n!AY=bZcZ`~DFuW@lzKGdnw*3`hFBD^td*=P&n}m)tSXGon^cOL)_k%Ize; zMwAogmZ>kSN3ExsQ&~iTa35ff;nSBUa}}e~r)2Jrs5eUi#um|;jg?JS%%yM68O$Dp z#AaN0$Qm%`-%0+J1XvsTqvyDLqvymFfB>pvuBn){!9eMr-K!gGI=Kur?8Ia7+}Uz) z$KG-owfmJxllzVI)6+_l^JBXr>(^`ORnKgSmlt~yB`(0-Z9T<=92#!6dMtR?#k-{%TGq|KC2u?^$>X=-ug*+Ncaq9DvPKm17Ja3uRV1Kc=$%AUomjz~F> ziQm=N67mt$#J}e;r<>L#yrnbHm!90d<)>U>e2q-PR5YOJD^D_wiqhe!9Y_#x6}`!6 z8bEqr%$zJ(wKs*zj_1Wc;~6-gK@bqMH#bkAZ!u=dVuFiIOX=`iE{+0yxID5YP^;Y! zAXpF9JGo-_>Q&mkD@KCWdUsP-~c{Cyp7!@RV;OJ)0Z=6{KuPsL0jPU%F+m0Q*jxE)^zGgw zxDBx23mmGolN4%;j0E1x1mGOhJ_YTIIQqi8I3Te$VT))9%;7fMGl~@M8yu6hszm2j z*03-qDwFA)%J8&memD)fMA7C~&qqf(QldcW(;@V@?+1wk`H|q0zWu-Zw|*INkuxl@ zL9Ksl9DNlpOX{bb%vz-ai1V;ou~p{7uZ9(FDx5PBYd)AfV^f=XrH@n^N?hx)Mq2s9 zDUz34=c^V!Gdo++)Z6nNFD>b9`!U!ObgBJ3U{)b^x4qQ!2HR2Uw?$qtO z!}JUxr|V>1
+ZEaVwND)kGl?YqB?|1yZA`IU@{aEAjne|uPec`?1H&6G z&%HpwO1mZtDF)*<eICZ(X7)wn`v`!@bBt`$7j2Tevw{jKi=jcF0J1A=k(;}OZ4K>T1NeY zht+0I*scnmpnWdRUP&zx*YSDM!Z=F#TDE2 zMnx8*+_W07q7p`yO=>o!e&1f6B=u8^Wpm~qa*$jE47kjmZc=DI@HTJWj~na`qKS?U z#ng!xZmsV(Egv(A_J*SBPy0vdTg*c>TN52GUYa=mLSMLE_%k&qjZ8{n^0H_pcNRk@ z-+_Bb@);rNQR#c=XuCn|WezBjj2|!Cuq5d2(9WNbL(~zv3%w-0L1gzliC~+#0&kutuc1fqiE8KT1T0H`8Q6dW- z17b>2dFjt(RwpjI_!c;iayNZj1sn^?TjMCNBpL1w8-MrlF$C;)AZEl~`YROV?#H7r zY>~F1CmDn!cDZ9(+v9ngf0x@uir&D+p?=Lxy74vW4A&SRlw=RS=#Wm->ToR{i}92H z5sD+G?&kiD8C5G3Vdd8|+LM5$<<64Tuq)11kJryAqQATG;Tc7sO_v=Q>t^S6gP zzvs^AvxlO*UxtMp4%F|55u+` zOt|~^-Nl%f2#APwb(Gj!gr5<10Si|r3uo_=3+K=Uhf=IcrX5a@j1mLQ z*jAwxH1d?8TzE9#XYe?=KOlQX6-uZ;EY}FXxNW(==nqG~*kKXryO~SCcU2wq@>J(C zF_p;LQ#N6V=t(zH`vsN4+GLH<5zW%D3R5OY(hk?L*Ecs(f=t9{1rG#TYG(G_2SBy!}QxC2WW~;*!c{Z z+H<-hBcGHz^CH!JprjRC4;B8&1Q6?eeb@Se)@BAUw@hqgd+FRJXDc=#L5XFkGjCZ{ zR6StHl-({pIux*}#5EWRzoR%$^vtd2o&H?)damX&!%v0!sVjV|$$3j9t-qE=)1#ST z8TzRYeKadQ_-{Q^@HykGyW8CluSK@UB;COn%C39}P3LlowsOa@3XwS3&51vf-B_)l zJTE#H$KE>}2*{cpGeZx@`Rjk|@J=oiXi1LxDhJ9c3rW#iJG)!HBWIU}#mHyz+wJ=P zLHofKWD2h>dVl_~7 zXhx3KE@opcC))XK;HP9ZJK`O8I~X|bUcGvyO@8~XPmLY)&^>-k$(zhNJ8fFgrUU8* zF5Tqe>OZ0;V5@1(>gn{axX#8dSv3A2qIm>CN85o7C*oKqBNuYgbbTn2Gki9nt+{fZ zh0I(NOkMqQD^?=oQWY|^8&5erh$KJ&E9+`|lQD=kcVtx1E${8gO1T#}P1g$+P=SiS zmPBLAXOC9PJzXyRc0MJ2H2y-aP&W=15e+XVv@49dQc$@TxPIjjkmNi>M2*-$UE*|( zD-xs;?~5j6Cnv{xx(v*dy+0^8pJ_>+LIjM;D~DGtYQo;6LCmmu6|*c-bJrSAAKw0( zhhayKj70}wklVY9_3K$>61}s zy03KRYQ$U`Y6HcEdR5zTleWhTQ>-nG%Cp+n!XM0=PbD(u(ujK)uswSxmMkx-X5J9E z`yphV=KY-%sCskZV@-uMnlZaN;{jBg2LYop-2sFjlIa(WCy=LXVA>QtoBdF9V`D82 zX3@yL1PS2m?%}mcZ9gK?J=3>Ss*Pot!U`V7pWRrPeku$IKN(x6Z8t!|LwU|S6?Lkw!!~lUip1lkYniqcIf@hj#5{5l-CDxCewl(d>cl(69~*1 zmCI!EkY?%F{^laETmjUzorn~PFudPaq{G_a5u3u3Z8SKd*?d8|{xCOklvOxr@%n%_ z2zecTXHXNV{c9M@K+n(>4>e^hN`TTJCc(h;(_Lv}7 zuug$Gm1#}_B0ltB;)xv1FvGKHpk(UxaY7qMo=yW?4*yMIpou(_;Q}JlXT$y3YhlCM zZFI%kthp*05j?qR5@aI_{!*k)9GWQAU{KM|^^YAPqlFcC`xk8KoKGA# zFJA+zs%ZU>e|UG`pv)8perbLUTP(OxVm_pC<;WSbq8J7&$BVXKTX4~&WvY|U#)o8{ z;=$1@F(ZQ1K4q^ZKO&Z;AiPMDHWYqgKBOBlJ%!0npWHA1Q}yzQz&B#l{x|o_RfJ~0 zRWlcxemO1_&FCl4M3sxqk!!-gF4sw%VtizvuAW#rn+7uAFZZ6@{kJAz55PFqHkW)E zUsvc3BI@rb5=eMOrza4wy$`XSJ$7WkObyjlU%^*D*S=ilY}9W08VJmG5UF@lLVyrT zjqF1fVoJdbi(KMH&YT-|rF-M@&;44ZUV%=mfl1i>lpGw4m1-LekBF!Uo8Zx2baogt zeB5r66a?}OS;veSZ>VCo(cZs&CEOSp$b;ePKz4k{ueIyT+ri+lxQN2fd>%(CS~MRF zAY;f9WH4B7whLCN-XyEw*qTi<^EUm!i#F8DL`lBaG>^}1FwwoFf`thMR8@gp>pv1_ zUy`T^YNAg>Y6xgHYM{K8pu2q(sXiBzFR50q(U~1~He}@zYPSaAA?Fh0n){~+DxpQ` zE=+;`_QgEG;KGVv>}nq%M01RT+Zj%@Oc~x>s+chiIB9py)vzEbGJ8D<(46Dun#{=* z?}kog!)YaU8YuT8`8$j~fRGRis%J=_EB)+?!VvT~@&($u2uwp}&R+3&NH*{bNYGgC zVPh+BKf|5-kkQ?eA?y!C)Ust@sH2L$PbNGx{X<@t@_q9at zlRnq>CkXi8bBF_8KRkLU;d8SB#vG~vdERXDEQ-_%DPx_Txi zZq;U9;9#zdM-y;>i=B`> z2`F#!v$-#1oFw~4EO~Vuxp4vmjX6pDD!}Hb+FUe(UPb>RgC{sX&kRh6DxCLcOOe%@ z?$d&*>hC67tV0qfk!(X@{&~u_r#{sk-#!ID0ehg9wDalobK_iyDNi?OD|e&AQQ3Q~ zs;wbn|B5gl)#ls*w?c%hXm2pJta)tboDjHJ2O#l8oNm~pPooeW+!#Q}xipK;wd7ED z9;aj~o$2#-I^RAxpX-vdcBQ(9o6Q&gx^XEI`1g1%&YauGAWI{FBuJvisu;w;IAV8~ zE3%(CbCJja^J_;VuF0n)U-_)jff8ee_zrX&=c?!Wuj~IWYvS22h~8m|@4SN;6y(m2 z_x$qSWCvhUy(r7NqKgsDR+fUkqhCb2|11qp{c+IcM6!_BU36eQsVoJRKvbdIWq3-^ zuud)8#MPS({hpjHu!0Z&mpbh^hP`5FP3kdHq|alZ%&qR|`Bv@&et=jeUY@67Aes$kPru>#fjmvexvFU)+p zYdiZnK;5&yVY?|;4!9KT^~u!&SpWaAyS8IctwATEBd$^ZN)+_5Jq`^DT!GQT#l4$< z09y_S3S#!b5q_%IrTayHNh$Uz+aV2MPrHA+{IH6netl8yrX#}1Q=;CcyXy4UcZEyH z|I;=(4g4->+?&Am<6o8TPV{SZySqRTJDT}%)3^s;KaSzC)4`&XDI<8=LnMx3`KYmT zhvyeE0DEA=+^u7_*|erbM3QL?~nr>0(Tr zX_Zw-)N1F}_An`Kd|03zs_ti_@u>H!hBjTi1IK>nTjoFxRT!a)Vvl z-A+ObaINGS4NtC@&Rx5ssbXPogoHI(jXZou2v=0~NR2KW2uP?LDHS4n*~j^@dr!(N^Wi>jt-OdMX~_->sN z#s!T>(A#h7jOc!+hhc~oJui&6u|sBJhN^+g7`VBGedaU*-Ev!6OnY^)A*#>Cqi_kq_|x+^EaOV%; zjlVxBhm}*o?6W8yW6M_1(TPkC_4mL2>{R-i&k!7x)p_UA5-g?R#XQ@XN$s^Wui}M2 z?(@~;VxX;JqjbAMfGx~OJNnLzHAD_!dr<;M?LMCq<0sTQfnTQ>b<^Ai^U%C2yKM8N z+ozcXa9E7*e&DY$L5adzou>=~!&Y`~qS4fiM`9!utDYOjxx*{ML=Wq$^j6Ys32uEG z=JK*)wv{U3*UDXvUvFTnZqJLnPgQbM{q|$c-SYFCOO_qXz|Y}ezTl!}kdoS%UDr{j zt#ztr9harAMF!scXh_)J_*rh0;I;JA3|4;Xh&Bv?Yggt(5k$T4e@ZqrKug!=9 zlCoWT>ZD}T1vU(Mxw53opQ;Ajd;EAKKxK5Fe*3Z7GZ)CF*(%`YJmukx9mEE`b@8|P zWOB15WTcvpu>F+X4-4%GKeyM99-#K- zrr|TWU(U`Uc`tqBtJ#(zHLR^hR6Eh4rW?KHcCgfa^Mh*fhp@Q{m zue{m5Vty?j$mdk7xh${T%tQUrgT26dyE44DFab?X4UXoHk+XC+Zr!oTNtrgEvyMpy zE-|J@n6k^FWT2eC!p`SUQXg#>P0=_#~_;pdw4r-^J0-0+kR ztj8#;?%pMj(wpF|gtiPv+^Elwkqg-~{HIQin-dRgXmh0~2hJwnr6q#o+ohbXr)-bz zuL)rw4Y7nBX*0Sm`rM+)Tp8Vrzp!IoNM|E3-XUIH&S-~f`b~_U!$qf%vAVhR81ACE zw&#b@0KEFjo~D!xW{xD|J*Xyy1SJr|46?J7zKdTS4u?PMHA`Ur5m{t{M3DB2YjOr3 zl01I+oxHC#*iA9J2CyCyFJ+cdu=B?6;;A|=tlreH+3E{pPJ-sDw+=!WUDl(r3=DSn zRSrf=MG#H&s9ud}B25)UaaNy^g#2bkxdlXL=K`%&1==Xscspv(B)_4T zQ5X=ds?+>&QF=N70aK(o9N9 zS+fjF3y4>>^VE)(y)M}nVL?7LQw|zEEW$?b_=h3yfNXNT{ujf920-{pmaCU5g7B=b zPTX9JZ8l(RR`YkY7Xm@m&{d8{ActOFwjA_Xb?x@Ih63mP7dch+&Rr-y9iv@U1Y-Nk zn|gxD+|;_{xiy57X4TGioFh^ebO-sW#xczpOC4+>+u}FcQ;$w4E4e`>;q+{?Vt#Sm zpQx2d9BugW)WA1qwlgLXu<6`|zR?42e7!C)0cWlCSOK&az$ZHz{U~nKIslFG^SKR; z;??6z#Qt8$p~qxGqgjz&%%eSe?MvC2S(q^3G+=AXpL&R?dvg4L(HQMaL(g}fAWLBK zKx!9XAG_hi?qt7E8pWp-xhTZ;+9R;?duX^B9Ce815yAsy_uTOYVDve^w09s#u zvvwEK@OVG{T$F#IT;hpjyssSi)e|ehTHm~XlK=&+p1PL3@fR>$Wl(hw--#wY9?pk; zF|5d1hotDr^w?k?WvQOuC*=qmo1fcv{FVN|{arK^qnGkz!Ig$P3OB^>ux4TH9_+*J za7fM8XzX@2zaY6Is`=RnmCrRRB*TYx)hjbS15B&cjSKM-kQZJr^9H1pW~vLVHxu=lq&(Ab1R%2U30u zZ`=`nTzNY%5daZG;62s+i(~@3KX|Qrr@Z~nQxA>XrB^B3v_C*7&w4JbbLU2+BHQlV zw=oD=ZsV7Y{;mtwy$Q-!F9zH-uiMY;EvPlHY_GZtJ>#1TOsG23t%O}`s1;Dk)_*4q z-EEzrl`69$Mh{6g8kS$E>VK3^v@1)ibDmQ~3hwd(bja2p5Zn7|Q&7hT&FX80qFt+v zfqK8|u!HG7XWP>{yZ(ws53&pL>|GF0tA4u7Pkvq@Z(Cg$aMTJdfONlgHsWl!W27&8 zZW_c2aM*NP|J&z9C=Zlx=i|hS;65_gWV=rpDfk*j*l8(ZIxsL{VeAvR9jcV7XF*c# zr~zii+cX%y<^L_&hoFB9??baG84nM(Le&w^xFtd!P~PICJDH%Id9T&?hY zF0>CaZ4r2-@+DgJLrQl{@B0wbSx#=6Y)<0atu`}ovHmMYUPF(Bh?*5z zch^~cSX($bI2WFL97pTAgQNeY26jKKaI?=r*gmTR*h@q7*Y=+H_h6o&4CtWT>e}_8 z3KO)4BX;$%_7?UPIw;J>SMP;eXAq$D(`0{14oB^Q`th%A45WB063m3|gJ>|BHQ02- zB+HD})Xc9aL3KaDzU6Cl4@Ow!dUGmfXFXzZNcZD0 zQ9zg^Md~aV595ud@QindiN6XbDxrezSoN))7k>{9;oaKS*Z)bfzVY|;yfcK*MqGQ4 zT7ZaW_J%=31g{?k2&2ut-MRZi2`zz=@Y3ts(2BwTY`GGsBXgXaYQ&b)<0mkw0G~`< zCcHQM6Z6IK@Jz?JM42{#<`=TwYEQ)(V={1Z?6}Tt?(T%q#AEQqP5t>uvB;kWs`Xs& zbOE#K4boN*n=e=@-oFE%c14IX3mc}-E?Gopqbi6n7@^SaGq$i#C!I|zmJiAV*&N~* z2FhVJV_Fn5i%OAs#Uoiq1bZP0#VwdCS&Nv%RiW z!U2H*6oXmhoA0JR2Ks}8POTw@zN8-FDYmqy>nVzk2ExL~_Lq{iI z=gs~!ys0*IoYjai43NT=42%N)w-g?@2JqDUkNpe!u#;c^Gz)L^3V{?Jz3MJ@p6SVO z3_oR7%*tgIr%6!vVoX0WDWkFu(J7&IJb_pW+-Q;vGG5d7v5kUS-8s57|dmaxu$&7sx4>#mGPXn!?G`? z+Kq`>Fa^j1Iy{RW3;&a;@Tb?8xO1-zSfV2w!S2Fc-sSCLZK?S%t*8alaX8ZP4(IFb zG1B?XFoskfHpCt?GlWf(viqB=h?gnz;rVCSS)o)Q4UO>hhcBrdsOkL^2dWa7=pfh< z!Roh6p4F4)nwQwYP&8|%`RPE}>*iJVle&@Z-oJmPt|sxCL^^Z_q{PG}gp5cGlMrry z9>ZM@cEa6{u+b9v1@=kc@Z7ni&}_jH?87gCE!+MHs|Ca;-1}lLMCW#KL9(cW2?uQv z#1&G`k+Q$!(q%D>LhH)i4b?HIBM?6WgK$Cv9uXC+@x+g0btRCe!wd0v#hm8HJ#4SY zl9J9t5stCy95$OxOcMf5e%mRf46w^eSi`EP(4x8Y(c4$JK&`JWUC&E`}h z_AAYE1q6-X$QPTiqlFQ+x{e|?AcD{?1<_x3*ldkW#V1jxkhmB&lvNoW(yvaZCHS9~ zdsPbsw_jIDS9Xl~zxwKO+cajbFAcG!zm&Tz{P&a`P{% zU-efP6qK8bM6mkgBk$&N892M_GPH21M?rOSS$4daHr1UCCWd_a)o;7cN$K}Z4=T}u z(x!E5!j6g-#me>UR)A+b8U{C>R{BU4H;q~@haA`5Zv(&~)gV^8WkvVxxS&Gj#T-q7 z%J{;shp4w{IWQ9!NHrN!binYRtTIaB^t$^3Ap)Q%JipGkPX zz|t8_5nOR?rgGoMT6eAVox2(Mw38jFeQ7SJQktu-(}=Rby#v(fPni6RdOrKCkgQB9 z^77L`j=@}x^U1LT>(j0&=jw9)p6Wlhx}rthU@1XG&HrR>Cdtpf@L+uXtAcF>J_2=z zZ%>tL0X7i;UcvlA`hR!1rDjaqQYA@2kPw`V-g%a7KkQC44VWcL)8B7bO8aob#rZzQ zZL+zTCw+?We77M#`B5LY&iopPnzOKS_wVHjtQWUNDV! zAW1H{nFs z7qVgToIVVQ1zQRq^+D=GWtXmCje3;VtAFi1T?_6sgV;BMds zewSmigAl#SRl=*5GX>RK;RZbL*}I&Pw8k_dia@CHKG~@4{O$2jRRS^nWW}Rp{FzoL zqbsDzSN{8y<%owJA<~ht4OQ_{)mi)VcZ#zl^YjOEZY80#oU75ZF2Cc;I!EQRs~cYs zZK-gP+=%NYN9@SQ!vi3BFr|@u7%v0VbmGIif#iAuB;q|8r{4li!}WXgYI-ANlnqMO zmsd@zooyOi)A6I#9}Z^(1mx6#dx8oMDKGt@eVqgXUzS^7d$JdWuC$} zO7M!+Xicun&AGA+2Lp((W8C=9%}*cCyIokEF+<6-d{XJ7X!2SBa>w zI8YX@iU?JMLy0xRV@A~4O4_WsEl-5j+rU_M6_pqk=GT^4kVgB&W`q+xqgKg8* zMPl&IY4;J0=NaN>n6F5Z=iY(A0KxEq3i7`Znh?A7j4~}u1&()yRVScRHlo*vwWzfJB5knXCB_i!q@8`&v?s_HqE=dH zu<-Pv3nW;2S$8xt)H=a^m=-r{77^8Wms zce270H$h6h4dh#sV>#yR=F}a&sw=>Xkf+oz@FN_su|e2?fKmJDyX0yJk@~rmcDy+l z%{a`m+AahKo6?I^rkO22GaxF41ALo^X6+iPUI=UOU+-iSr!l{36+5S)3(TglheqX2(xY?5~%7VWF32WzTpai^QTtK zv)wF;^Y6$c9Er;P|9d3?MDj&oX~VAdKNV~sQ@ltnr;WT@sjVsBu01CjxNIC6>NRBdd&D=&p#A+z)uC%z&*GM7+-bbZUeNk|AoekLjEXu zsp&X;K;WK>lbGU4S4m)wO8}WY=bK;eht8Q=#OY>4YQ7Me&aBJOznXA&oakh%FNS3(hK{k4{(sXdTfa$~<06Yi<~4?!yGx~7Ew=p~bNf45 zsxl_b%ztpJNQ}H^y{!(K+@?S&MI#Dz7OB10hh6)*OW{^~#it`&;l(-p-yRbDTPK0O zl(O<@c-B^YIlGmB-pH-hjx0{8X@*RNW;2V6Hu~)Zwgd<3cqj2T<&6bQ-=P@9MVgH2 zl)zh0$|(qBXiT#93})*C2%~=ghKYvP=rt{1+E8)1H5ysQ~}SfRY+I{=h!@ z8#?oRH}fq2RmW8|U~e{Fyy|4Vq$eo`&`wcH@yTsA%ININRy*w*2)0t6?q(`Shy#O3 z_I6GId$SVxD$R^e|2O0n74uislpLM>?-kEzILa+JPgCBwHU!jj$*-}pYHOCEIf>uG z6o5TF(6gOge={c&6n_7RU+dI%hTr? zLjS#hzuj*`JhyF#ai-z`-$j+e)GMCv_n6Gv;3|TI85{Q3@M&p(T%3HnLjvII!qB&E zLlsSC^Wysc3}O1&k;_4;6*I^H=qB$VP+Q$;|9aU`W{;Ms%%(IB57^QHpMqxSb#EU8 zV@pZA4?8X|fa6Dc$}C^aGc@U8#!K5UBmYrKE)FwRERldGW0VY75)?T5KK}t;?OBi; zq0@4%lnZ#f<^sT@M*BAI3vNr%&rtND+EP?N%Eq8uyZ+&ou6F6=0pR8afB?jVKn-ar z_paO;rC+=m@D%_gZgdW>2&!gWMt^_5t5L%01&I zDLkj(4LP4OjXNnoE~?S026gCqU}|nVWe`V9)>0ExjrS}0`oUCtlpebh#3S>JF+q< z;f=&7xqOfccvCz8gj~{clGOO>0I-)007QnUL>?xiH$eRFiW+(%=;{gT8m6FGT ztlv6h;E;k1>mN+e9=lxK8bS%9(zg+wi}ArG+3WtZ^rCpQf_DJsZ#R*HgG)#PcLfb3 z1n0Hh*dz~hGkWJk~RB&j>KltDP_)G{N@NM0X#sc050IU!sC4cAp-Yx*e==~$x#hiEx zfVL?PM~^-D+mIGS31`s;{8E@v?BkwXct{WatSI>)=5)pV&i$ibX=raqAC$-ikDa9l zhvtU{?Y}d-=*PMw3;?vEgCdcj(Hxbag07JNex%AS@JDv)#6SMcqdtYvwf~U^L*bHU>kC9dh$aa2#&g`a*mqF7xPPt=SRq;%wTzXl zd35E*xrd;S8ys=xxy-oryadEj%k@Tp;gytmi*##>OZXPNE%)a)7(E4c?-GQ!H>(BV zKN7>OVR#LS5CO#|`@RFB7p#SF9WcK;Fg%4`tQEM?)j+E|AmJ48Rcl2 z3Xm3zBm5T8tQWGt8TbITz!HFBFuea;NIkkhGEMK6h+;Ls-y6_>ngPFh@!m?|e3X(7 ze+!@%tRry_G7T%S0*F$K(D~l(29SIk(0Ry5Mcj{yP=JaX5b})xAk*0I;B5kFv4b_< z@p6FwC{$eXixp7L6crc{bdh(Z_ni~0{YCb850G!qyB;vVbAsI)K$ORO&1+Enx1!km z$#8-=e*tgW@jm}be?#6uhW0X_!H8XktGf;pv= z0Jzf{?-)5?=>Q%TdxsnSPRIf5FMB-J_XN@~1VDFYXW~mhJQ2P2HdnXJ*b_P8|^W4M#$F;a5Kg8JE2p9Yy#8k*Z zxbV>pSI1;U(E5!lDkAU9`GIPht!aZDjLY;K!ZCmes4&U@o>5dHntgKG?q60-7Vo0N zRshHe{`Ah_V00$fY0&m8ga*feO+HgB20&IX#yfevKSOm)B|#Q>hY_X&iLrECpd#O1 zOw+}FXXXY!EC022BGEJz(pYvI<;`21*bUf^EIC$z>(nw5&7Sq|0h>O(hn{a?xWy3O zCQG)|8S1PsJ#ED?EjtcE@>>cu!8_wJ6qmYPJ@~cX=y~|)$!jsizcl!6i2SyI`q_~0 zz-5H1{@Sz!9{X z=j19-FIU)vYX)RF>IWBi&kj6ZM1muS1WSDU4{J9IasadT?V}5RqJi|^i_TqA;YT!X z)yuW{j||@@8ZiI8Yw0%Tn<$*8p^U*A>)BVZE;@GaOR$46h!$mG1`78Z{|j%}i+Ry$>h9k;GpjG;zjsb$Zsc5t zAujo8aTNg*HE-sl5d`tyujIkP0E}_G__>!h%exhVI#0jy_mH>JgZu+D4SvxnCP?^N z3fRjr`ZJ@)?pl_$eNhSm`$#`Z$e>2Q_TgM~A_%d+mYRQK&(X1;FgE4udsgTX7D{`^ z7U(?uiH806t0^DrW#@s!E`MGq42C2Oq(I)=SOrWEr!B7-z86$b{=rxN6yywHMFz%H zR^ETK^1dhwYB=A>k|MyQ1anda@a|6IBnP- z0WXox3YM~}^mE34QIFrKFa?wf^2G?j=`W@_kn8!plp>Y)!3SQ(nAdxu7YI?N^f985 zOCe=PaG_Qop{=8W$@eyZ@DgKQ1DfP6I@yUR?Q$PE$bK<;*)L~VFrt0r_i|ixcKv=m z=&p58Pplm;1g*B61_T1G}w<9 zB7q=b?{POYrUkBstdT&0yfJc-fwQ@DZ-9X|-~bsu6pL@`%=^b%_CAx}|HcD)Uqjyi z280=9d)rh^@mi#b7ct{FL5irR0WTMMLu!Bf;25A7@W)JktAGIXKGWap{eNxOa<$WS$9xAaBQ7mBf9IGy?oW)ZSOIXnUBdyBUiX*f^XX##GIR;)}d?Q6L1zD&(qSpd znC#l0*J7e&TF@dR^FN2|L_I_UqA~+&ow7RFEMG?^u8+BdJo5eFS`4JRoJ!UYG3&Di zVm61SR9_=@rDBykJ8qp;>~2?DUdwU$?+6XDxeK$BjI=a3%JIV2@TY#&M|nSlK2tXR3HeBy~-hxQ+6>_Lw>;c zsWL#QUYO%*JDKHLB*M(C4Hp?G_NPA~qw9`sxq7qhxY-Q5#cH#_VUl~Xmlg{z6>{hw z`%BZ~pP1K4B3?3_La0CpgfxV&m-n!LJ9yClcoZ`Jd^}aa(pX-LAU4nkuiNJp9oB%Yet07k-Ftu;t=Q zkBWxP^vi+!GK%Z^HvH9fuEEekW~uKl_v7U@DKF~eTqv65a`DKMY)6ss3Fym)nP1)I zu+@mh36NG*D3sJ&!8W2KrXwvYi|ws@^>&Bs&)m0MT(&RWL)H?_o$dJ)<8I&A2Kolm zvhz+d3vj(2M4`1fB~_(n#QifYW<_W^^JX-oZaViqW5?ldkD`|m>MO7MQo(wqEWP7<(&Gp~*V5!0N(D+588K zM~Nq;Jz2Ypok7#*dg}vwi>t@k>r`%s>d!(3hLd|({Z&8puAgw(h6bU|e%!X> z93>zemxFCU-s9eDt^9t?{gRu;?8PJ|@Su^4+A$b2w<5SQj-8LXmQ{?Kv}=5LW3kxu z0RK$u;hTx+kmFhph;-R@?rJk7HWR_~y^l9rkpQdvs|+d|HPJUB3by9GgwzFvV|X4_ zmiC)wO}5H}WqLq}6Ta7Uq$I#AO6+P3e3kBXq8IZ zAkzXk_1w>NT-LTdQ&OcgyU6W43}3z0p@3pGWD9+sXMRzso?uC4HV==xHJAYc;10>h zKBzloJbV*(u`4j%k>ashp_7;2*%{nYO7jG@_vV;d!Zk`4G+-Q3a&+`o-V78Ab3_8~ zg}vap=xiY*$8WLfbB^HL)frvbh@)ujJb7@B*=e)MqUPEsbuf)9AGv~IGM$vtct|fO zJ;5xj^mXUwB!_WLVRYqM{8D5CpVXBX~}cp z()x}Lg$jq-rm$AorW;oFS6I3Dgy3{s#u>%{Q zvBTK%O=@<$SW+fanaU=RNFn6Ny@} z?ugu<z%GehGGB|_PQ5(2Y`Fy7 zi*+*=skcZh-s=5kz-UrvDFQ!tTsbn^0%=GUaJYL=KZ$ziPk5so7HA?SCZ)W|tTFvs zhIu_sHhfz`&EVzu`e;p19`vJ16ZzQ*mY zk_5*k_^4kBuF_qhVIhgS&Bs8|4_Z8~oYilq1xyo%#^CsX$?By`RgHV)fD~cdq8TZN0m{x(0bmfU&G-VLK43b z(ZzM!fVWBz-h*=YHK|)LEjc*F#z{&U)FVQVaqL0rBtvm)T6=1QJEqaF7^b`;$$E45YpujO4kYYtT|-|VIW7n)#++G#7F@AcQX zu2mzO<1kh70!F>j)%vXoKRN0kXL(!;oPcz>U5GZRd)YodEB4Ze)k6X@vwvrCZxj8Ev^=xPy*-gI< zm24>wGH4FHiQ}M~TqYV2p*EYp)p@j3CVeb293YrJrrg#r%37IkXQZ)SCY!Btu`^3Q zpc9GAw{Mx&blGDSj~$u05PPJjN#6W|jdCbnuH0BM!~ab93eZND?zMU`W2|}(9mNXt zV)gb1UY3*;J=PQkhxnTj0$SV+B7U2h@pR5an(ooFK{bpIjxRnP8@%$+X9Sx<6U~3l zK~y~D#|ebCNd{XA&L#D<1kqKp&e(mbtf+3VOo`VHTD=vRyvOZFSc%KDEY3`1*1vkZ zxtz@<*k5WMA?ERHIZoP2-fEcbA(?NztTHL1j8U zk!*MhqB)*<6kb%Md6vsV@VJdHwa{uDF|iJ9Z|Khb?-$yF{PkZkj)l?Ae z3u8q^0UIb?QBbO)^k$)mbm?8`Ekt@xR79EtrAiIbdxy}YD81JJA@mvuk&-|NB=4a2 zyWjiPUEf;opZETG_pr`7Va`4?`|R1XXJ+sD&3UG8vOZafytEqljeQ{NS?#8y_0c9A zgg)uv5s-{5bCQlTYkO$ms3TqGZ^7y|73MYXs%%0{GXu?hQd#D!d-y`GG9WLx;m33> zxGEB8RejiKtx1HMv~z7Fi|nKl@ZrCsvL>>7FyA`bJ_d}{mmBxGQuKzrIR@@<6|16N z_Il!Fxm9mAw)l;AVY_84{2Cd)36{njR^tNEdOM7P z=qhSu$$eO7oJ|PKXNX6zt9)vHZxL+j%qizlfJ@l;mU3~OygGjYs?b}_KppB}>x|aC z2}T#^Jfjx~;-`Mc`!{9G+gifE=dL<)hi6FCpAXf0>X8<>zLC0Pl4v4QmxS(6 zX^oS1zU8N_F0~(@2vJg5o`{MfATBB|`$(bLZLUTpP1i2=R->hPzj3!m)7Q5(kKfot zuDVq33E4Xe)d`RfsYVlK#D&(tZky9&`jS#W2C+Z6a=e$U&l_1cBbV`eQ%_qhOWqSjg!GN-t~_n@et+IHPd+Axy*N=q$VxWjEA?iq9S_lH1TQh zs*q}bU(Ge;2VSFHZxn!!&4!O8A&hQ9&ugPm)Os#^Se(Gwv^cwBN1cQ2&Vsh8s zi$G}zLWKeDNan@Y%{jPj8=5qu=IHk*B^G>R36nxDPeXz3!t;zfrJsJcW6^pt5gfNR zzArYho|~1tkTs#Q5U%8RAojU3W{AyQL=z$2-Q?Mr8b4<^cyG;jv^HUPVmV4_QqCoj z-@mr00hQOvLR6$<8gB}@o49btc!jmOLJ-oBafZ15C`fSlYh6-Q8ACTlMu|(IbT`gE z5Pn$MlGm9Luu!J$en{sgMZO6-Ieh9>&)0VPMqmARTKE4`aoHLz zFA?f=FFo_XU+~`=|16ssivew5@3&rR`eWx`<=vI`M11F7ga6pryJ`XNPI=r%+ma!x zxoJF0vhnxFiF*wGjoy~7_o82iU@BkZn?vN~EZ+4OzX?r~?xvJx=00ZBlv*u4q3o7S za$YZLD6;b-4etL8UcE~$$&E~E&JSPzp-&+6Z#0Xfo%l85Qt0urWb~?V`H?E* z?yQRyxBRp*Mexnc3_-Q^;8kIqpu}J^hwl3I9WTv_uf1kGbwx`{aJ<7z1MkG$Uelc4 z=0wBJ-Ya zp7NQssL5nGi_J>7BtJ_J48drqxQCaXu#uMrVm=0$l^cRoaca%_LPT+XVcuDDl4APz z7-?K(cSKIpTT%8moN@l>w0>7a-Oyoaiydn_)6vff2cNHjh9h^S}sOy5B(B~ilV zwnB+AX3isxG)7+m4vV}8g%i#VK4+RcP^K7sJS-Jjf>s~C)`p8MIrI712lJb#S2|I) z8gh@6@g>_YH^-!)BiFI9wR`^2^@n=iX)`Zfe5sOi_OGJ;xy%xa?MT=OmLt_v+cyUHvXe1hor+*Fh<;2e0-hZib-!I!%OuONi=9qe^@Q4>YGs`P?eL@^;;4NRx zt$W^#wgBw)ZvLxR>{<52+seVlQs8E>Pb2I*j%z94sxf{^ha=CSbb;W#H*<1=fli<0 zW?bA5(yGiCUkFlXN@}EbTAsf2zCa+emfCmq@#!?sE?m47d+hNP1J|2X$aw77D&{>Fu0b_TcC3}vaxFgLfu zCjIrG?!5{A7h4|drPC1bbkl+RbO4B`qT$EMj(K$VM@UWqYM}Y0_nnz#Qqo=s^(c9u zIJcf`7M!^oUmUykqQ<{cjMh)X^U0hn)96fJ#!*!#Z(R~$_(vZHHF?dk@cpSh;rbk| zwy#vZ;6vdu!(Qr1-JD779T+%~PCJN8NI|$czqpk-V?ny%S6P5>4Nh7%@L;rl;n($T zTlQ=d3b}+sqTc8d)> z!I4di&^NlR&1gE;SAt+Rtb{VGJ2SRu*%4J2a+bc~iGi7zTB-=QD|xd6lM$ChE5McS zI8k42I(grs&v%Lv@JB|RNA9k;_ERW`D@g@)N3to zqLUe0o6NI@bebOg7R50%P|0$}7nIj)buwMw=(`ulD02o02Yj0{8N4$_S;fusRCke4 z>N4wNF2(2zyGOqns;cflL2o}$yj0rxq!2>Y5<60<-Y8l>+kAtH;)D67&Pm8-VwZq# zL`=bM)x5D~a<%mq9|`7S(9Ny6{dnb9e^v9ozdwEblLUfhsb!)9yIbcA0;OFvDaoV> zStga(zE;XjUt~E$+Fki5P9SV%cs7{83)lNWW(0_9#bb}(HywAdI>uO0Q1U@o^K@Xd zhR_#Y_4vvQM>Nt0U=-(KMgnw;H}s8?`eel1OkgukKG3U8PKC8`{7K z+`aEAJ45mV@hduglWaicBv)$}zTDJ_F~(;uyEqgID?7Od44G?}Xa4RvdtuyW&nFyP zd%d8ntls}#N_z$mdJTAvph(VFbgI7S16|OM5EEP)vm~8tI`c5>u=Msevg@}a#i09E zy=r9A<7m}r6yoW+*nXCm=-TZNU}(A$+*5UQjR!Q4I}F24PP~pjo+X-3&$Z+C!y#tZ$lJD2qfYYII_?NU(i^UDQr6fQcOf~MsZWe32?fUZ;m76XMlQQ zvk?lnKD?Vxr@NZ9mUlw7%jUd_?Abg!?2RUQXt=F&y*Un4pCfSZ%i`eAQ{?=OC==&l z|N6O%c2V!b%=q@{T&TQn^(U_mKF%A9(4xzN8bjmib7Uns3)gJvS=+#)1$hr^-4N1f ziuBK`bk#1-|Ib>0Ptsb22j)M+OwBH?m!ET$=EpE>x$>XhM)G~3o?&VrSqNHWOgRqc zXNs83*k^htE(=fHkNH(gR;q(a7Yxz|UsJO$DoVu4z2I zqj$2t`w5AHM@@wU4dy1ZjhjudH%Gv$LFLaaV7@vhAYs@Lhm{e7-8MY)dd{i+_qJ^5 zT$xOgZlbGM!!G_B{Zo&h^s2u6=$#KCS7vq8R`>r*)zhOly`!C4?-i@O9xL35IPgBI zGx2}sk?pWLno!t{W^jB{(un{yn>RPY+ol^!VQezGr^p0t{B+YurHQ}jMU&p59}Ul- zCF467;(D3ITX{2YgigrOmBk+6JSEPa?4l!L;ep?){X!;cibbQj@_Byw;mkcM<5UMf zzSP(p*qD92$}iP`@BEa_VYaxNit3eeFN8e`$Ri2`bV^}rTl25j}E?cr?c}$PKbHa^LBEg zTvtueSW@a78+H9vWqj}A(1q(I##yb&YRY1f1f86EQ^%o3he2;s#IwTfYY&1uO{$-* zH$}^+kIGbOKrShe4L2qDRf zrQfj$l%#1)eBn*J+6eY?LV;d2gGL^6#vk*JDO}&^_?BJX&vIsL9DI5p^k-dH>TbQz znl1*87;OvYH~pz!d-8cY?5$1yn|Re%@5hP^1xuyy>^|eRfsdTRF6fa?-M_%3Hdu8D zm7j_vBHiP2dL{&8lyUUyIc>M%&LJiVGOq-uX6#9QrMr{_6OZNmxkP4+SU~zid`Vot z%HWUo6^9m!BwBOuoOeAr&uVuB>@Mmxb<)h6`ercI>EaWd>y@}fjMjad@Qj7pC6$+Y zK|IAH%{t-)wzrKM(x5UaTfMx3=HAKL?5u>jS6-DHT+o;6z?G*}=jsdninJbCRo=Z? zRWPzl?NVvytS*c^AYwGrZX)g5M(PgQa-1SG z-9&M@??Yli3UW(DhWRovQ-V_YF*6o~Z(`~sHTPdq)FKm{KS%!ZCb-&i@8Z#3Q`|wf z4X8j;@B=rZTTl0wzzB`tl_^J5Q!-0k^Kd=;qutgGj1;I0xfR#&c)fCM(j~g^jkqV6 zv+wEMpBtHP*&?)JCp6b{9J=P8U6LB(00ut(0}VKl#&%KNyY{b!37>2%t8-FQNA>;Um%)Bci4eT7rO zg@hwbgNu?n{enfCr!-#*nMPl?sBH;68BbS}W|*O#Ebio0rn1fh>!OI@Reesl7r3#p1S>@5mvp9W9JtzVWs=!Eh0d!x4!IiQv> zBBal@Ill2v*|hOO%)(mUqdu9(i)E7KD8>3e>8B-|*_s|XR`Gx@C_C*sETM&+hL~bv zh749xMxS1)tcbn78i&zbNo_{T@Qr9LSPXuidf=X*Uy#nn!z_~x-9qwMRd z(9w)K9Rj6L6HAN;*wjc(*aNA3B1C=GRq%2ng7J@gB|gZ)8gG29poU(H9R<~>j1 z(W^BQ_w_Csm;$!X6SU*}eyTn-GuIXzj;4I>zTTs5(!AZWTb18rcel`j%z93}!0!?^ z-&%QF2wnt5$`}Z?Iigf-3Ym$GFOS+X*_mSkZpj~(wN zaQhkvk6FrVmLQ6UcV`3fPVV2pS}}eWMMw{gSSvK4wMiV?YxDkO((Srr@xnt)L+0nR ziVU$t&_tHt!*8=$yRs&^HD0RIaV*pybO)W9g1siYDVyxbm*#b&2UYGP!bSaj7hE`l zAp*~0hW4COhq?rTfNBEAVW~d8IyEOUhGXbae^}}JQ$Rc#>-i7{+&Vd1G&XLYu zvx*k^-JxK+)#*|I^^Fav{qS>`_jp#4c;4H z5R1JUj){cWgh@zViSNOwUW-a?ex`tYL|TI~RN6bVzFJMkVbfU-MBWm_yxq9(=key- zD_h>@mqcF5{m2zeMx6Dv(n}XiE~Hq2dTa7GC>kU0?BctfcnUYW5h@EByYjajMu6do z0h{Qi_=}J`FZfK;sg2byf;H;>F3C*cD@UXKE)Hnt!!9sWl&PfeT5eZ7#(&5;&c<5U zWm4K#JFY3$SVg-u%t8X|t7>y5g^H6Tm7wfy1!=}f1!q;>aNJMy$#d}|`msl2I~uU; zk*ofVF20_hC{r~na<#9!U-c&MRw!AFn6QV)C3M>NJHaK+8g^x)jLdBE zW=*QGW;gE5*zdbBG4?o3QiJ+|r_&&n*`)?9S39F&pZpqTU6R9#9=DmkfM&%0f1;0q zWSp2o5_HeH$kjCO3+&_wRIJU)oUZY~#V*(+y*=4!7%ppMWst zE5SnToV#@?^X>(fm*@qceS%|y+%YjOyv-GT9~{4)+1cn5O3T1QhYU?j>b35*-}$`| z_6l%Jj}(|5X2>OMPlBU3Y{Kr6xyz$uw?e2KPvR~#3zx8pblRvZL#~v;wcb2UhN6Bh z-=994I~PoR`j&2U4sFA4M{VDf-QnuKmrrsNSg+cGI69Z8OYj!gHrE$*c4!Ke77&;z z)(m?u?wOaOi6pdccd6seN0lv{8`Cc5v(YhLeW$79-w9U2oC&)1lp}DG5#fMt8NFJP zHe@q-==pMVhTF8D8o8bjPRJ&9n@mOYADBKsxva0=$GGr1e33=mej-Q^s^%s0Y?9K_ zO2qt{k*po5MBMYh;_rn%sTvtT^xrme_-c=3hQ-K)jd@$4m}uN03ZZ8x7u#(A zy_mA=wLa6MW6Ey>lkOa16|GUa*}3eMGKAY>3rNtL3Yclz!wG|?mal=mZ7avdYCtAV zXd;cY-Bi--L53(+{H%AWan;x4jdfaDlSkfL-mX*eL+M{dUw9=Dk+QuZ|kls>Ega<=$OEd8x4s<-03tGEUglD!adUs z1ZoHuUy}~ZN}6Pjmjdaseoaj5uMzZ_5R#Qa%k-JE8c>$*7*?hwOnf7uyWyX2nT0+x zYu*l8M~ifG-1uTP@}sdFK(fL(4idoq~*&S&kSai z$eVG=xGL_wN}jc_WtE;PeY22Bn<&kiIFdOKpXYkEJ9kwO@iw(LV9rmP6zVl5(Q0)* zKhhcgJPN@N2ll(##|eCLTX=N`igRZ5U$NMz-NY3e1zl5p%WG`B5Ll#&tlR*$6049b z{IHh2S=BED2Dx;me11d39=t|dy%)TJ8I_5aynlqtAJU3slx5N_64owq-wHo4<@xlPbF3$o}Do7i4DqLPs5 zhsl3lWb%^WW~)PBUv+=iNa#nGz44ZJ-0T;QgPjSKxdF_wWAc@yb~41-sSIq;VZydMWDT0D~6_*z@ zX}D0O;NEamK(sKiTG~2271G)ywpo~6!#xYhUkkoBvJ@myi$+$qM>)8-C@)*H*`lBo zHT(0|NE*MVTiBhi7rMSS%)-+79*Sn%3m*nn1DBWl4GKpXjI6wxyEqtFbI@ufP@VM% zzh;eq=nybfdwaaa;Fq13!rG-~wo-$m&)}2Z_N5IDsM};pP*Ce;W4w3l(rO0;BZ|OY%L3$Jcou^P|c@_*@DNA%0;)lxk5V z%a}r>_ost!48N=ds7*KWKl4yhDHIN+N#8naO*H-7W^<3V&wWXv7DrvkbQPEhg zODbEwl$vBrlnp+hlsq3@XjbHljU8BcbM#<`$ukej?QVz7d48~7ZgJtRIYeZI=9Gcq8&Z!{>VEoHzsL9Huhaf~9aj*5z6%l=r< zyt%nnKlkOS-!|%5BHLZB`j?yOlkrJ9v3uuu zy)$D%N{-zs5$UNn=)M^+x!oGc;Djs2>t8wd0oY0E13Ll={U_w z&<@n2bueL-6ECEtalOBIw}h;_CnIO%%cBB&&~VQtZd>W-4P_C)RacDldpT{M=vn*f{U)GzPP_5q$xhGV^=M zi8<&XIWBnJN!(62)UmsGR5-m~UJ#P#S4}_&6XqjxSAUb6wVx(Z&r>O9fmdmX`nvk$ z%f70}y8}LYtVL0#T*3wX3lEi{68!G$m!~YE?j~C>ultq5=UH60ZnF5iFcI~@4Pfs+ z+MVVXc52y^ea}`V*NXr1Mvc0ar)Br$1Brg^XPNX^@2PSu)Y{tX@!;kXBPfGNWszXq6%lYnDs+zo@ufkzP>*h|3zv&NNAga{wrpN3Kb!Dthxn>tA zuKXIw23cjtTJLMrpT#`XN3ogP-e>}iFpFOt>C`+*H_JZNb9E%|=$4cZHRoj?Iz2mr zc{L$LCegHu;~-B70Z~A~jJV-!HYwB+H*#@{&tQLr9LrNlCfVF z`p0gz6JH@t85h2(xbKZ-0-2ISASv>imB<4%9p)mv65Nr|<%t=>IfA71h#k%4%d~{V z1KQJ!c}Hgt%K49#g8Zssy(SEQf3K>NF#DO!w>KB0DmS1b4o%NJ6q>!aF#FZdcV5T; z%<59&1+k+n;dUx;8Z@8Rc;%nEwL#r|oBl+6;Ra^!&?d=q6Itbx+ZoVMbr5pX_~`jW z++*D!)kZ#)tT)U0YP%eYoVc>~Atc9M{k@8Qx;heCSPBtFoN)A?4?4g*893~yGbBLz zN2*5t%#=$fN?}V>4l=f0r22PDA9yBoZljzi{{8mA`LS&N!R9f91=xJQ?|1>eYi}n< zo9bn!=SqQR885qP!SkU{Xx>i> z-;!dyBlLj=TPTqSEr%U^HZI4-bdN0khB^fsxV<{NUS}aU99K-5b1+j1q`!G=wtqhj z%%Pk(aoQ2NTL8cv-bt*OP|IEFASvN#X4re~e@If<*UbWtE0=B6=vW{R?_0nzGKO{- zVg`?(X{ihpyyY?$brp|VYw91MlV9q-BN=AqF`DN-a(k?;@ff|~8op-wj%&+ZfQF#Q zHi9%%4ph3dw3c~U&=c@3i?kPy@ZdZLo{|goyiR5NUZ?^ci3lblwV=*jGw#uK6{7ot z!n;2SA@}YkCBuxL{uj1o5&;AoO{fZt?K)wyw{fzXVi4E>oUF4=X*|lSL+$2++{|nW z?~VaKA1`wczUSIXu>OBJi6nUmp~T?<>6$rxBjPjEyqWltfyrKUwKPIYM-bK{Wkno0b?;j7pnSda`sNXR* zYN2Jo9_to#UVog0>EBI8jYa62%Y%odLyS!uC#)SKCZA^Ji(cQm%6kle0YFLK0N4o_ zr5B$!p)l{h@K}z30f46-F5Q39Be0MxtO3;2XO zIPfc7&Xr9$+S*oo`U=ffK^E*d$?Gp9OyR^Esdw1h$H3uG`W$z_3Wf^g?k`d0&$rT? z{vK|u?T`wjJ+Z$L2DBtCX*Lx|Na`a#_9t9V55GqqLx;Iq{(?UpHC4tHztN|^`iuQ7 z4}vjkd4H*;$rIe=Y~g*4<9R`gE#=B_dFrEOe`18mQJitb{`V8iPjvt-{)~F7k*|2> zpQ}5@%J0u;ndHqpNy)r?%9B?71dSuzQAh`u8ZGvI%TC_+vSWCgD_37#M$@s$ z{-WF3tH*eY^_|3D!^ucq{Q@Na=1|H$<;nIJcYT;{1*kq_!@1%2QlI{EOc{6zSe8)G z>9r^9Pmj$lBYZXM_$gWdRju(4+Ac2T1mwOI3_!uJ{(U9SQ>kJ=ue1OB3;l12lYepB zK`;QE_vG-l8m;BUzeQo5(*n71Q7-44(pK_07MSgoX`pc9e<^!|-;YyF3e>vJ z55FHcb2&|Q3JerJa^B;(tz&rr9!~xPm2*mY11X-Y0BHS9%U?`9;cBW3z{J6O0B3&d z7y$k5AhM4GVB!eCnV$~(3+29&DtDY7N*a3q@&O(|p3`(?ECAK31qIl$`3vshZu-mAyRL!Dr-59~w|I>3|9#tYXW5?t6uQ@RUumSne6Cr)6>=>FNZwg`Tm+b9 zJO{wS{~@qPuCXf|Qv#B5jrhyJ_l!)y7`=OaIfvWjZ*g3vs}F#-4nWFBF1w%WJAVGW z2gmW{LW_q2`?2U~Zii{y0WK}KR%271u+M2l&YI3Q9~;kG>5t9>xxMAqQdVG00Yt~e zvT6wA#`UTo?#tD@UvS5>D_8c8t?;==Qs00}Xq98`!f7ml_2+TEmgiH|fy)TbNzZ+~ zARto+pbWEAbi)pp^t#C4LchO;@I>dc7ltP*&Plxcr1VZRM~)l8^>Q1}1ULW?iLu$8J-It<9U3cD8vO^@rmu%{z|e)7NO;F`s;T zboU7WbDwSP=mdHr;m2s*wqOGDC(ya&e)bIMW#IimOTUjPD_ri+w;T{hFa12Md+v#P z4@k8^LgftbWR{6jZEdqn{lj>9?~YA5E*csSao%DYn#OM+)CJ0`hz<0uXz(s0nheN2 zy+JbU3{ATD#P?BhFcZd~D_kCbPTCKU)40Bd;|ZEooVlS*gZFmxNt$=&-JL=JOM3GM zV_O`+{|VQ4f6&kjR3|uE?;Y3S*uv?XG&J@7bb^i2GqeDsi&-o*H0Bawpa1lsPSCuj zEyS$?WWX^S{PN#m;@!#1$2$qo&|FYB2Kv*`++#cjumXHN`QJ`(rF{}8h$&n};i>Nb zf#UwVi2q|X`(H&}z%C0IHQ{(bvuE$N`^(V0b6I}*|D$mqJ{=SPP*u!{dO=%nphr6A zLGupR8@GC_mo!>j$Ga;4nY|AMtU7RsUoFxeIIi_!-kHD`|mlpJf+q%DdB>2S>))PJM!X4ph<%AD-T++o$wK zwN=2_W$pL6TNB#vp8^7;k*>435Kd5baPg!0U*yNYZfx439E|YB%32X)@R9unT1!!n zl?{Bfrh~R|p`m#_d~8bVpSD%VA3d0M--Wmitj}DvXxvC}T@Q4({8n@@5?sim^Gqqz%~4cfEHgKCBsfmV}q+zOmCKi}2qkG$Fjzn*Xid0K@E zX`jPOhzm6lYkX#f>E`ooG}|+fa!k-9%1-V6)FaCtmrLb7%hz4kF70F(n0+xv%krqn zaA7YGdVy8dET%@I&WWU@KvZQ0^dnAiE4TeHHktD8-ZdOq8bY@2nrueZgxX8kTYl!$ z2F;L8(7Y20J7yvVR%h^g0!($As;lC5l-3O00oMy}Hg4~1mGZK)m)@v$zsU07!Kcl7 zZ#EgC;8t_LW|#>lbe8!ZZNDAaB-^R*H?7^ zd^8YZ=?o9m^RD>~>h~(&O+;rbmFRudj?!M>ZR=h~`$K?EjQ_ae(XWlBe10NZMKwJX zM$0r)jxyoxc08L`k~f+H_rg)o<%BJ&+%B+Gw7NYb(crfo4^oI8ZKrT?6U`P#8xTl zP`DRi7P1?)EO*LsJTJ(q#YCQE#(|_-G!>4qm$H2InN?TOLm={|;G;~>UuO!bjDiTE z2xlJ~HvjG9`Edg=g?61s88*vG{8j`` zVhmXRj{hhg7`_5#wDjvbl%T{;C)}@dR_~_v(mPb3_zD8t{gY`0WFd7+Sxnc7SX5xQ zlm1gOuP#kbK}mO_Y(0#$gif$-SMoMbGe{lde4$PC_pY(E zwRP$vkG5~;eXka6FD`K25SIwK#Qv?fx;n~?HAOjKH9@izqzeCam4rbNdiid%G8_d^ z9D>LQ%2uOH?X8ODnf7hsXt8g5yG0tozGo2LATBKo>NQ&7jJ|Z)h#)VSD`1Mhxi>CX zj3B0>7P~as9r}4)4s55&$#(E+gaJWL(&zVP z9s1AviQT>S{DWl^XpP%1+BH$JfYh}))L%csI8T#YhA@)&nN8p1DxU?liMJuNCb>|S zlS)yMY-GBrr_;QVMYnSh5?^w@8>G73kAld}l-XJ7Up7K7Egt?{U>!T~oU4BG=L_4& z1DW=K*`Y(sDb2-4g~mG*ywntL`Ko);B0Fmi@D2lFv4~^P&Oj&g(xpAYjyNY;i>6gd zUNsqYcBywXo*{5abmGQWy&YE)&Ud|kA}xx#vhQ5rC>gNrSts)- z!9rd#1XbWHN1RVVJFKM~E`Q7^x?SP0LG5gxL`f?#;B+K2N;_zC8z1fAb!>D`ZR_F5 zihV{Hl6DO!N*vmD9qDheX%tn2^;lqKJ>B@jYC=aiEo+iRLF zg{Y&6b*h3Q*v#!Fq?x%P!Fk%JkHaXcV^Syv*qg9>EEqL8fLPXJjl)!5?caZ~sS{On z&@3i=1Xni|M=oy5WJBY$Ipgr)iI&1|22)?`qKa%31ihtKay$**me$_tXVLDqXNj?Z zdf?2f<)bZFhhw$TqBDUD;hrJ;gbB$CrkmBW+kr?EgD!tZxKybb>!3k@A3W9l1%2M_ z!e;VH17ugLWOd#g-Q1omZRIa_$f;~+r6AUL7$jMJIQ-5W^b3{9POzk<8Aul0IG!J@ zvKJIL+xhpTN%F^gDCkN@MlM8QO$<@OZ7C8$XH5@cd>lKAzL5<58|Ts^sgE6gt)6%7 z(J)gVzU=OTlwGb-R%j1&-9p16`CmR?iQdPLh1y#%x-Sbwg{F#8*LEfOcp}$06U>?f zC}K@_H=U&A=)s}(4Y_x7gt4KYj(C3W%Q`uYjS<7Q7KpGi(i8^sa!)sP@H2Gb~^kmw?9QFS3@iJk)wYpuds>rb6$EbtAt23Q-cg zJt@u97DFd}n>Z~|D2Id@{`vi3H8k`fRJ+{Y-PdM^!@sS=@3#&+-F(;?_RHmO?iXjc z?swc6Te^YULe)VdVWAy*I}2$JJK|O~pT|ZExvf+oFCN@$(~y!$g1D*{`B^)vC8IBg z;8P#^PgZ@y=trcstA_@`rRl(-&v{^xZ>U>ZGE$z#J!=xTiII{IL6aNy5qt-1zRqlq zX7NryRf|mr*`UlB;lR#{QQ@8P^>RWokQZ;{>)oT^(6)nHoKgHpY3XSqGWT~AgXvY~ zz>TEZdj)w6z$ALF`>eTcnI+m>#uP}Lu1 zv?sLrzCXHSCGfMK)$@lw-X0!BMmyV4?d-)j6Z`k7MO-AD_LrS_rm=n7Rtmqe}SAe=UblUF6&>*+xsl$BYCYTDd9-C2H3!Et~0=@@B`L zr4UZ>w(VoEm+NkC+ShUG3m^rJH%+e(`q-<<*yd`ysbvXJQ@eq=QMV1NB@vg(4`K1O zRyIl_d*SD=?wR3Pw$mnah0^+`@u8fQ&Fyaiui=JAt8ouWwb1Zx%9P%>%{tl5$1}61 z=08G7x&eV*p04EQ(1_L@+qP=v)a%L@^DUP)3KxFio9=L63h;mn<#>udes;7hW&s06 zb7Q}5ZGe$%%!jsvw&sy(<$j*)lhl&->5wSc_v+99TctxH?0PU+APNmm;pi605VQ7R zYwnMSta{24C9$FpD>%OL+y2(1I+;5oN+x}L)L0NYBN^OGn&7a8&BnX70=Uov%2ms$ zP1ckTD5cg~SF+Oc_ULb}-DX?l__gvX?hG;K05)<#o5s#wkDOK# zC<9Ib;pnSQXqrB2AhfS1DL-_qi>X-0-!JR z5edJ8%mR!%3dlk*x{`04IPECe>22Z-i-U%wf4TND80!b2gbb&KssqQFY=_VW9?J z^D5=*M{R21nPd<12_3TTs~KF@{+K#4G>YxvgUJ=(lc_RuvHO#lZmFZ$tQDd}MzN<( zdr8L3j}z-9i}=Kd`C|vmJHao&xXYZqZa-dg<*W3l$n6Tp8)+fbVP6tveY1mhMX)Ou zZWbRwwey-5Kf)gN`PwZ$Y!J7*9_c&IyxCu~0viN6|Jg-0^N7y{_xB{1_gR`bsJWFDPLl{)bQV#$IbK;@QAw}cuPI(n^qwZt z(E+6+*nE3NCcUdbzu&L8D=*3I5Gt3(D%YKc&Q&kP8TzQEN!z{{yIle32ZS^JI~EB#vxu&AinbM z`!BcEA96;e1uM27iN(W|>bT3YCipI>blF^;m0oYm_IzD%G@9aL{e!y(!zBCq2& z%kl4R{rn-W^Lu~nliIp@sRWE&zKa}VvuUvKCHJ4ubvD~)t=&et)^KKZaf5jrrTiBU@3amQ+ zXHuJaCRv_}CGQAYF?7j(+U@;hESr3Qi{;*ve$1Ojq%;B_$MdDOfWk`ociP;&0)@84 zU=-776Bc=solU6xJ=U?3;wV38P0{2?xxXb`%nDc0u)|u)o=D)UVDT}8?4vY2>ir<= zM4ugK;p|!nxQ`QxyXrfZ%)9)Zj7th4i`h0!#~y{?5^1BPMdkkxoOXWNw1R8Q-yDxU zh>pP6BSP=k>G{sQNN2!!xPhi#QxCKo-}lc^#1Qc}JoVompa1rJ9nU!a*FAtwt22SP y#cHdYG6PNHqE8f{XT7G!(KF!qFK)v>)DvKb3LS;Pw~WX4RTMQ8N}j#`@IL@kG>|d? literal 0 HcmV?d00001 diff --git a/hexstody-db/Cargo.toml b/hexstody-db/Cargo.toml new file mode 100644 index 0000000..10ef219 --- /dev/null +++ b/hexstody-db/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "hexstody-db" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "3.0.0-rc.4", features = ["derive", "env"] } +dotenv = "0.15.0" +env_logger = { version = "0.9.0" } +log = "0.4.14" +sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "migrate", "macros", "postgres", "json", "chrono" ] } +tokio = { version = "1", features = ["full"] } \ No newline at end of file diff --git a/hexstody-db/build.rs b/hexstody-db/build.rs new file mode 100644 index 0000000..3a8149e --- /dev/null +++ b/hexstody-db/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rerun-if-changed=migrations"); +} diff --git a/hexstody-db/migrations/0001_create_scheme.sql b/hexstody-db/migrations/0001_create_scheme.sql new file mode 100644 index 0000000..e69de29 diff --git a/hexstody-db/src/main.rs b/hexstody-db/src/main.rs new file mode 100644 index 0000000..b57f348 --- /dev/null +++ b/hexstody-db/src/main.rs @@ -0,0 +1,47 @@ +use clap::Parser; +use std::error::Error; +use sqlx::postgres::PgPoolOptions; +use log::*; + +#[derive(Parser, Debug)] +#[clap(about, version, author)] +struct Args { + /// PostgreSQL connection string + #[clap( + long, + short, + default_value = "postgres://hexstody:hexstody@localhost/hexstody", + env = "HEXSTODY_POSTGRES" + )] + dbconnect: String, + #[clap(subcommand)] + subcmd: SubCommand, +} + +#[derive(Parser, Debug)] +enum SubCommand { + /// Apply migrations to the given database + Migrate, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let args = Args::parse(); + + env_logger::init(); + + match args.subcmd { + SubCommand::Migrate => { + info!("Connecting to database"); + let pool = PgPoolOptions::new() + .max_connections(1) + .connect(&args.dbconnect) + .await?; + + info!("Applying migrations"); + sqlx::migrate!("./migrations").run(&pool).await?; + info!("Done"); + } + } + Ok(()) +} diff --git a/hexstody-hot/Cargo.toml b/hexstody-hot/Cargo.toml new file mode 100644 index 0000000..354357f --- /dev/null +++ b/hexstody-hot/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "hexstody-hot" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = { version = "0.4.19", features = [ "serde" ] } +clap = { version = "3.0.0-rc.4", features = ["derive", "env"] } +dotenv = "0.15.0" +env_logger = { version = "0.9.0" } +futures = "0.3.19" +futures-channel = "0.3" +futures-util = "0.3.19" +log = "0.4.14" +rweb = { version = "0.15.0", features = ["openapi", "chrono"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "migrate", "macros", "postgres", "json", "chrono" ] } +thiserror = "1.0" +tokio = { version = "1", features = ["full"] } +uuid = { version = "0.8.2", features = ["v4"]} + +[dev-dependencies] +maplit = "1.0.2" +sqlx-database-tester = { version = "0.2.0", features = [ "runtime-tokio" ] } \ No newline at end of file diff --git a/hexstody-hot/src/api/mod.rs b/hexstody-hot/src/api/mod.rs new file mode 100644 index 0000000..3244898 --- /dev/null +++ b/hexstody-hot/src/api/mod.rs @@ -0,0 +1 @@ +pub mod public; \ No newline at end of file diff --git a/hexstody-hot/src/api/public.rs b/hexstody-hot/src/api/public.rs new file mode 100644 index 0000000..e69de29 diff --git a/hexstody-hot/src/db.rs b/hexstody-hot/src/db.rs new file mode 100644 index 0000000..e69de29 diff --git a/hexstody-hot/src/main.rs b/hexstody-hot/src/main.rs new file mode 100644 index 0000000..5862871 --- /dev/null +++ b/hexstody-hot/src/main.rs @@ -0,0 +1,87 @@ +mod api; +mod db; + +#[cfg(test)] +#[macro_use] +extern crate maplit; + +use clap::Parser; +use log::*; +use std::error::Error; +use std::time::Duration; +use tokio::time::{sleep, timeout}; + +#[derive(Parser, Debug, Clone)] +#[clap(about, version, author)] +struct Args { + // #[clap(long, env = "KOLLIDER_API_KEY", hide_env_values = true)] + // api_key: String, + /// PostgreSQL connection string + #[clap( + long, + short, + default_value = "postgres://hexstody:hexstody@localhost/hexstody", + env = "HEXSTODY_POSTGRES" + )] + dbconnect: String, + #[clap(subcommand)] + subcmd: SubCommand, +} + +#[derive(Parser, Debug, Clone)] +enum SubCommand { + /// Start listening incoming API requests + Serve { + /// Host name to bind the service to + #[clap( + long, + default_value = "0.0.0.0", + env = "HEXSTODY_HOST" + )] + public_host: String, + /// Port to bind the service to + #[clap(long, short, default_value = "8480", env = "HEXSTODY_PORT")] + public_port: u16, + }, + /// Output swagger spec for public API + SwaggerPublic, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let args = Args::parse(); + env_logger::init(); + + match args.subcmd.clone() { + SubCommand::Serve { + public_host, + public_port, + } => loop { + let args = args.clone(); + + info!("Connecting to database"); + let pool = create_db_pool(&args.dbconnect).await?; + info!("Connected"); + + info!("Serving API"); + let public_api_fut = serve_public_api(&public_host, public_port, pool); + match Abortable::new(public_api_fut, abort_api_reg).await { + Ok(mres) => mres?, + Err(Aborted) => { + error!("API thread aborted"); + } + } + + let restart_dt = Duration::from_secs(5); + info!("Adding {:?} delay before restarting logic", restart_dt); + sleep(restart_dt).await; + }, + SubCommand::Swagger => { + let pool = create_db_pool(&args.dbconnect).await?; + let specs = public_api_specs(pool).await?; + let specs_str = serde_json::to_string_pretty(&specs)?; + println!("{}", specs_str); + } + } + Ok(()) +} diff --git a/make-docker.sh b/make-docker.sh new file mode 100755 index 0000000..918ef89 --- /dev/null +++ b/make-docker.sh @@ -0,0 +1 @@ +nix-build ./nix/containers.nix -o docker-image-hexstody.tar.gz "$@" diff --git a/nix/containers.nix b/nix/containers.nix new file mode 100644 index 0000000..522ed65 --- /dev/null +++ b/nix/containers.nix @@ -0,0 +1,40 @@ +{ containerTag ? "latest" +, prefixName ? "" +}: +let + sources = import ./sources.nix; + pkgs = import sources.nixpkgs {}; + hexstody = import ../default.nix; + + baseImage = pkgs.dockerTools.pullImage { + imageName = "debian"; + imageDigest = "sha256:7d8264bf731fec57d807d1918bec0a16550f52a9766f0034b40f55c5b7dc3712"; + sha256 = "sha256-PwMVlEk81ALRwDCSdb9LLdJ1zr6tn4EMxcqtlvxihnE="; + }; + + # As we place all executables in single derivation the derivation takes them + # from it and allows us to make thin containers for each one. + takeOnly = name: path: pkgs.runCommandNoCC "only-${name}" {} '' + mkdir -p $out + cp ${path} $out/${name} + ''; + takeFolder = name: path: innerPath: pkgs.runCommandNoCC "folder-${name}" {} '' + mkdir -p $out/${innerPath} + cp -r ${path}/* $out/${innerPath} + ''; + + mkDockerImage = name: cnts: pkgs.dockerTools.buildImage { + name = "${prefixName}${name}"; + fromImage = baseImage; + tag = containerTag; + contents = cnts; + }; + + hexstody-container = mkDockerImage "hexstody" [ + (takeOnly "hexstody" "${hexstody}/bin/hexstody") + (takeOnly "wait-for-it.sh" "${hexstody.src}/docker/wait-for-it.sh") + ]; +in { inherit + hexstody-container + ; +} diff --git a/nix/overlay.nix b/nix/overlay.nix new file mode 100644 index 0000000..59956a1 --- /dev/null +++ b/nix/overlay.nix @@ -0,0 +1,4 @@ +self: super: +rec { + eclair-tortoise = import ../default.nix; +} diff --git a/nix/pkgs.nix b/nix/pkgs.nix new file mode 100644 index 0000000..0eb8e8e --- /dev/null +++ b/nix/pkgs.nix @@ -0,0 +1,7 @@ +# To update nix-prefetch-git https://github.com/NixOS/nixpkgs +import ((import {}).fetchFromGitHub { + owner = "NixOS"; + repo = "nixpkgs"; + rev = "7ec99ea7cf9616ef4c6e835710202623fcb846e7"; + sha256 = "1cp4sb4v1qzb268h2ky7039lf1gwkrs757q6gv2wd2hs65kvf1q7"; +}) diff --git a/nix/sources.json b/nix/sources.json new file mode 100644 index 0000000..558c83e --- /dev/null +++ b/nix/sources.json @@ -0,0 +1,50 @@ +{ + "naersk": { + "branch": "master", + "description": "Build rust crates in Nix. No configuration, no code generation, no IFD. Sandbox friendly.", + "homepage": "", + "owner": "nmattia", + "repo": "naersk", + "rev": "b3b099d669fc8b18d361c249091c9fe95d57ebbb", + "sha256": "156fbnr5s2n1xxbbk2z9xa7c5g2z5fdpqmjjs6n9ipbr038n0z3s", + "type": "tarball", + "url": "https://github.com/nmattia/naersk/archive/b3b099d669fc8b18d361c249091c9fe95d57ebbb.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + }, + "niv": { + "branch": "master", + "description": "Easy dependency management for Nix projects", + "homepage": "https://github.com/nmattia/niv", + "owner": "nmattia", + "repo": "niv", + "rev": "af958e8057f345ee1aca714c1247ef3ba1c15f5e", + "sha256": "1qjavxabbrsh73yck5dcq8jggvh3r2jkbr6b5nlz5d9yrqm9255n", + "type": "tarball", + "url": "https://github.com/nmattia/niv/archive/af958e8057f345ee1aca714c1247ef3ba1c15f5e.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + }, + "nixpkgs": { + "branch": "release-20.03", + "description": "Nix Packages collection", + "homepage": "", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "6d1a044fc9ff3cc96fca5fa3ba9c158522bbf2a5", + "sha256": "07a3nyrj3pwl017ig0rbn5rbmbf14gl3vqggvkyrdby01726p5fg", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/6d1a044fc9ff3cc96fca5fa3ba9c158522bbf2a5.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + }, + "nixpkgs-mozilla": { + "branch": "master", + "description": "mozilla related nixpkgs (extends nixos/nixpkgs repo)", + "homepage": "", + "owner": "mozilla", + "repo": "nixpkgs-mozilla", + "rev": "8c007b60731c07dd7a052cce508de3bb1ae849b4", + "sha256": "1zybp62zz0h077zm2zmqs2wcg3whg6jqaah9hcl1gv4x8af4zhs6", + "type": "tarball", + "url": "https://github.com/mozilla/nixpkgs-mozilla/archive/8c007b60731c07dd7a052cce508de3bb1ae849b4.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + } +} diff --git a/nix/sources.nix b/nix/sources.nix new file mode 100644 index 0000000..1938409 --- /dev/null +++ b/nix/sources.nix @@ -0,0 +1,174 @@ +# This file has been generated by Niv. + +let + + # + # The fetchers. fetch_ fetches specs of type . + # + + fetch_file = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true then + builtins_fetchurl { inherit (spec) url sha256; name = name'; } + else + pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; + + fetch_tarball = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true then + builtins_fetchTarball { name = name'; inherit (spec) url sha256; } + else + pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; + + fetch_git = name: spec: + let + ref = + if spec ? ref then spec.ref else + if spec ? branch then "refs/heads/${spec.branch}" else + if spec ? tag then "refs/tags/${spec.tag}" else + abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; + in + builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; }; + + fetch_local = spec: spec.path; + + fetch_builtin-tarball = name: throw + ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. + $ niv modify ${name} -a type=tarball -a builtin=true''; + + fetch_builtin-url = name: throw + ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. + $ niv modify ${name} -a type=file -a builtin=true''; + + # + # Various helpers + # + + # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 + sanitizeName = name: + ( + concatMapStrings (s: if builtins.isList s then "-" else s) + ( + builtins.split "[^[:alnum:]+._?=-]+" + ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) + ) + ); + + # The set of packages used when specs are fetched using non-builtins. + mkPkgs = sources: system: + let + sourcesNixpkgs = + import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; + hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; + hasThisAsNixpkgsPath = == ./.; + in + if builtins.hasAttr "nixpkgs" sources + then sourcesNixpkgs + else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then + import {} + else + abort + '' + Please specify either (through -I or NIX_PATH=nixpkgs=...) or + add a package called "nixpkgs" to your sources.json. + ''; + + # The actual fetching function. + fetch = pkgs: name: spec: + + if ! builtins.hasAttr "type" spec then + abort "ERROR: niv spec ${name} does not have a 'type' attribute" + else if spec.type == "file" then fetch_file pkgs name spec + else if spec.type == "tarball" then fetch_tarball pkgs name spec + else if spec.type == "git" then fetch_git name spec + else if spec.type == "local" then fetch_local spec + else if spec.type == "builtin-tarball" then fetch_builtin-tarball name + else if spec.type == "builtin-url" then fetch_builtin-url name + else + abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; + + # If the environment variable NIV_OVERRIDE_${name} is set, then use + # the path directly as opposed to the fetched source. + replace = name: drv: + let + saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; + ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; + in + if ersatz == "" then drv else + # this turns the string into an actual Nix path (for both absolute and + # relative paths) + if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; + + # Ports of functions for older nix versions + + # a Nix version of mapAttrs if the built-in doesn't exist + mapAttrs = builtins.mapAttrs or ( + f: set: with builtins; + listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) + ); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 + range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 + stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 + stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); + concatMapStrings = f: list: concatStrings (map f list); + concatStrings = builtins.concatStringsSep ""; + + # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 + optionalAttrs = cond: as: if cond then as else {}; + + # fetchTarball version that is compatible between all the versions of Nix + builtins_fetchTarball = { url, name ? null, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchTarball; + in + if lessThan nixVersion "1.12" then + fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) + else + fetchTarball attrs; + + # fetchurl version that is compatible between all the versions of Nix + builtins_fetchurl = { url, name ? null, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchurl; + in + if lessThan nixVersion "1.12" then + fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) + else + fetchurl attrs; + + # Create the final "sources" from the config + mkSources = config: + mapAttrs ( + name: spec: + if builtins.hasAttr "outPath" spec + then abort + "The values in sources.json should not have an 'outPath' attribute" + else + spec // { outPath = replace name (fetch config.pkgs name spec); } + ) config.sources; + + # The "config" used by the fetchers + mkConfig = + { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null + , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) + , system ? builtins.currentSystem + , pkgs ? mkPkgs sources system + }: rec { + # The sources, i.e. the attribute set of spec name to spec + inherit sources; + + # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers + inherit pkgs; + }; + +in +mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..253fd8f --- /dev/null +++ b/shell.nix @@ -0,0 +1,21 @@ +with import ./nix/pkgs.nix {}; +let merged-openssl = symlinkJoin { name = "merged-openssl"; paths = [ openssl.out openssl.dev ]; }; +in stdenv.mkDerivation rec { + name = "rust-env"; + env = buildEnv { name = name; paths = buildInputs; }; + + buildInputs = [ + rustup + clang + llvm + llvmPackages.libclang + openssl + cacert + #podman-compose + docker-compose + ]; + shellHook = '' + export LIBCLANG_PATH="${llvmPackages.libclang}/lib" + export OPENSSL_DIR="${merged-openssl}" + ''; +}