From f200f3d747daaf8d18c2b1f080a60686f9e127bf Mon Sep 17 00:00:00 2001 From: Kody Low Date: Mon, 1 Jul 2024 23:02:44 +0000 Subject: [PATCH] init --- .github/workflows/buildService.yml | 37 +++++ .github/workflows/releaseService.yml | 71 ++++++++++ .gitignore | 4 + .gitmodules | 0 Dockerfile | 37 +++++ LICENSE | 21 +++ Makefile | 39 ++++++ README.md | 131 ++++++++++++++++++ assets/compat/backup.sh | 36 +++++ assets/utils/check-api.sh | 12 ++ docker_entrypoint.sh | 26 ++++ icon.png | Bin 0 -> 59240 bytes instructions.md | 0 manifest.yaml | 199 +++++++++++++++++++++++++++ scripts/.gitignore | 1 + scripts/deps.ts | 1 + scripts/embassy.ts | 6 + scripts/services/dependencies.ts | 165 ++++++++++++++++++++++ scripts/services/getConfig.ts | 63 +++++++++ scripts/services/healthChecks.ts | 5 + scripts/services/migrations.ts | 4 + scripts/services/properties.ts | 3 + scripts/services/setConfig.ts | 10 ++ 23 files changed, 871 insertions(+) create mode 100644 .github/workflows/buildService.yml create mode 100644 .github/workflows/releaseService.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100755 assets/compat/backup.sh create mode 100644 assets/utils/check-api.sh create mode 100644 docker_entrypoint.sh create mode 100644 icon.png create mode 100644 instructions.md create mode 100644 manifest.yaml create mode 100644 scripts/.gitignore create mode 100644 scripts/deps.ts create mode 100644 scripts/embassy.ts create mode 100644 scripts/services/dependencies.ts create mode 100644 scripts/services/getConfig.ts create mode 100644 scripts/services/healthChecks.ts create mode 100644 scripts/services/migrations.ts create mode 100644 scripts/services/properties.ts create mode 100644 scripts/services/setConfig.ts diff --git a/.github/workflows/buildService.yml b/.github/workflows/buildService.yml new file mode 100644 index 0000000..0376b14 --- /dev/null +++ b/.github/workflows/buildService.yml @@ -0,0 +1,37 @@ +name: Build Service + +on: + workflow_dispatch: + pull_request: + paths-ignore: ['*.md'] + branches: ['main', 'master'] + push: + paths-ignore: ['*.md'] + branches: ['main', 'master'] + +jobs: + BuildPackage: + runs-on: ubuntu-latest + steps: + - name: Prepare StartOS SDK + uses: Start9Labs/sdk@v1 + + - name: Checkout services repository + uses: actions/checkout@v3 + + - name: Build the service package + id: build + run: | + git submodule update --init --recursive + start-sdk init + make + PACKAGE_ID=$(yq -oy ".id" manifest.*) + echo "::set-output name=package_id::$PACKAGE_ID" + shell: bash + + - name: Upload .s9pk + uses: actions/upload-artifact@v3 + with: + name: ${{ steps.build.outputs.package_id }}.s9pk + path: ./${{ steps.build.outputs.package_id }}.s9pk + \ No newline at end of file diff --git a/.github/workflows/releaseService.yml b/.github/workflows/releaseService.yml new file mode 100644 index 0000000..427d777 --- /dev/null +++ b/.github/workflows/releaseService.yml @@ -0,0 +1,71 @@ +name: Release Service + +on: + push: + tags: + - 'v*.*' + +jobs: + ReleasePackage: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Prepare StartOS SDK + uses: Start9Labs/sdk@v1 + + - name: Checkout services repository + uses: actions/checkout@v3 + + - name: Build the service package + run: | + git submodule update --init --recursive + start-sdk init + make + + - name: Setting package ID and title from the manifest + id: package + run: | + echo "::set-output name=package_id::$(yq -oy ".id" manifest.*)" + echo "::set-output name=package_title::$(yq -oy ".title" manifest.*)" + shell: bash + + - name: Generate sha256 checksum + run: | + PACKAGE_ID=${{ steps.package.outputs.package_id }} + sha256sum ${PACKAGE_ID}.s9pk > ${PACKAGE_ID}.s9pk.sha256 + shell: bash + + - name: Generate changelog + run: | + PACKAGE_ID=${{ steps.package.outputs.package_id }} + echo "## What's Changed" > change-log.txt + yq -oy '.release-notes' manifest.* >> change-log.txt + echo "## SHA256 Hash" >> change-log.txt + echo '```' >> change-log.txt + sha256sum ${PACKAGE_ID}.s9pk >> change-log.txt + echo '```' >> change-log.txt + shell: bash + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ github.ref_name }} + name: ${{ steps.package.outputs.package_title }} ${{ github.ref_name }} + prerelease: true + body_path: change-log.txt + files: | + ./${{ steps.package.outputs.package_id }}.s9pk + ./${{ steps.package.outputs.package_id }}.s9pk.sha256 + + - name: Publish to Registry + env: + S9USER: ${{ secrets.S9USER }} + S9PASS: ${{ secrets.S9PASS }} + S9REGISTRY: ${{ secrets.S9REGISTRY }} + run: | + if [[ -z "$S9USER" || -z "$S9PASS" || -z "$S9REGISTRY" ]]; then + echo "Publish skipped: missing registry credentials." + else + start-sdk publish https://$S9USER:$S9PASS@$S9REGISTRY ${{ steps.package.outputs.package_id }}.s9pk + fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7892ff4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +jam.s9pk +image.tar +scripts/*.js +docker-images/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6bebc41 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +# Stage 1: Build gatewayd +FROM fedimint/gatewayd:v0.3.0 AS gatewayd + +# Set environment variables for gatewayd +ENV FM_GATEWAY_DATA_DIR=/gateway_data +ENV FM_GATEWAY_LISTEN_ADDR=0.0.0.0:8175 +ENV FM_GATEWAY_API_ADDR=http://127.0.0.1:8175 +ENV FM_GATEWAY_PASSWORD=thereisnosecondbest +ENV FM_GATEWAY_FEES=0,10000 +ENV FM_LND_RPC_ADDR=https://lnd.embassy:10009 +ENV FM_LND_TLS_CERT=/lnd_data/tls.cert +ENV FM_LND_MACAROON=/lnd_data/data/chain/bitcoin/signet/admin.macaroon + +ADD ./docker_entrypoint.sh /usr/local/bin/docker_entrypoint.sh +RUN chmod +x /usr/local/bin/docker_entrypoint.sh + +ENTRYPOINT ["/usr/local/bin/docker_entrypoint.sh"] + +# # Set up volumes for gatewayd +# VOLUME ["/gateway_data"] + +# # Stage 2: Build gateway-ui +# FROM fedimintui/gateway-ui:0.3.0 as gateway-ui + +# # Set environment variables for gateway-ui +# ENV PORT=3001 +# ENV REACT_APP_FM_GATEWAY_API=http://127.0.0.1:8175 +# ENV REACT_APP_FM_GATEWAY_PASSWORD=thereisnosecondbest + +# # Expose ports for gateway-ui +# EXPOSE 3001 + +# # Final stage: Combine both +# FROM fedimint/gatewayd:v0.3.0 + +# # Copy gateway-ui files +# COPY --from=gateway-ui /usr/share/nginx/html /usr/share/nginx/html diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..599ad42 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Start9 Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..705d0fb --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +PKG_ID := $(shell yq e ".id" manifest.yaml) +PKG_VERSION := $(shell yq e ".version" manifest.yaml) +TS_FILES := $(shell find ./ -name \*.ts) +ASSET_PATHS := $(shell find ./assets/compat/*) + +# delete the target of a rule if it has changed and its recipe exits with a nonzero exit status +.DELETE_ON_ERROR: + +all: verify + +verify: $(PKG_ID).s9pk + @start-sdk verify s9pk $(PKG_ID).s9pk + @echo " Done!" + @echo " Filesize: $(shell du -h $(PKG_ID).s9pk) is ready" + +install: $(PKG_ID).s9pk + start-cli package install $(PKG_ID).s9pk + +clean: + rm -rf docker-images + rm -f image.tar + rm -f $(PKG_ID).s9pk + rm -f scripts/*.js + +scripts/embassy.js: $(TS_FILES) + deno bundle scripts/embassy.ts scripts/embassy.js + +docker-images/x86_64.tar: Dockerfile docker_entrypoint.sh assets/utils/* + mkdir -p docker-images + docker buildx build --tag start9/$(PKG_ID)/main:$(PKG_VERSION) --platform=linux/amd64 --build-arg PLATFORM=amd64 -o type=docker,dest=docker-images/x86_64.tar . + +# docker-images/aarch64.tar: Dockerfile docker_entrypoint.sh assets/utils/* +# mkdir -p docker-images +# docker buildx build --tag start9/$(PKG_ID)/main:$(PKG_VERSION) --platform=linux/arm64 --build-arg PLATFORM=arm64 -o type=docker,dest=docker-images/aarch64.tar . + +# $(PKG_ID).s9pk: manifest.yaml instructions.md LICENSE icon.png scripts/embassy.js docker-images/aarch64.tar docker-images/x86_64.tar $(ASSET_PATHS) +# start-sdk pack +$(PKG_ID).s9pk: manifest.yaml instructions.md LICENSE icon.png scripts/embassy.js docker-images/x86_64.tar $(ASSET_PATHS) + start-sdk pack diff --git a/README.md b/README.md new file mode 100644 index 0000000..edd18bb --- /dev/null +++ b/README.md @@ -0,0 +1,131 @@ +# Wrapper for Jam + +[Jam](https://github.com/joinmarket-webui/jam/) is a web UI for JoinMarket with focus on user-friendliness. It aims to provide sensible defaults and be easy to use for beginners while still providing the features advanced users expect. + +## Dependencies + +- [docker](https://docs.docker.com/get-docker) +- [docker-buildx](https://docs.docker.com/buildx/working-with-buildx/) +- [yq](https://mikefarah.gitbook.io/yq) +- [deno](https://deno.land/) +- [start-sdk](https://github.com/Start9Labs/start-os/tree/master/backend) +- [make](https://www.gnu.org/software/make/) + +## Build enviroment + +Prepare your StartOS build enviroment. In this example we are using Ubuntu 20.04. + +1. Install docker + +``` +curl -fsSL https://get.docker.com -o- | bash +sudo usermod -aG docker "$USER" +exec sudo su -l $USER +``` + +2. Set buildx as the default builder + +``` +docker buildx install +docker buildx create --use +``` + +3. Enable cross-arch emulated builds in docker + +``` +docker run --privileged --rm linuxkit/binfmt:v0.8 +``` + +4. Install yq + +Ubuntu: + +``` +sudo snap install yq +``` + +Debian: + +``` +PLATFORM=$(dpkg --print-architecture) +wget -q https://github.com/mikefarah/yq/releases/latest/download/yq_linux_${PLATFORM} && sudo mv yq_linux_${PLATFORM} /usr/local/bin/yq && sudo chmod +x /usr/local/bin/yq +``` + +5. Install essential build packages + +``` +sudo apt-get install -y build-essential openssl libssl-dev libc6-dev clang libclang-dev ca-certificates +``` + +6. Install Rust + +``` +curl https://sh.rustup.rs -sSf | sh +# Choose nr 1 (default install) +source $HOME/.cargo/env +``` + +7. Install toml + +``` +cargo install toml-cli +``` + +8. Build and install start-sdk + +``` +cd ~/ && git clone https://github.com/Start9Labs/start-os.git +#checkout v0.3.5.1 +git checkout 39de098461833e4c56bd3509644ddf7f1a0fc4ca +cd core/ +./install-sdk.sh +start-sdk init +``` + +## Cloning + +Clone the project locally. Note the submodule link to the original project(s). + +``` +git clone https://github.com/Start9Labs/jam-startos +cd jam-startos +git submodule update --init --recursive +``` + +## Building + +To build the project, run the following commands: + +``` +docker run --rm --privileged multiarch/qemu-user-static --reset -p yes +docker buildx create --name multiarch --driver docker-container --use +docker buildx inspect --bootstrap +``` + +You should only run the above commands once to create a custom builder. Afterwards you will only need the below command to make the .s9pk file + +``` +make +``` + +## Installing (on StartOS) + +Sideload from the web-UI: +System > Sideload Service + +Sideload from the CLI: +[SSH](https://docs.start9.com/latest/user-manual/overview/ssh) into your StartOS device. +`scp` the `.s9pk` to any directory from your local machine. +Run the following command to install the package: + +``` +start-cli auth login +#Enter your StartOS server's master password, then run: +start-cli package install /path/to/jam.s9pk +``` + +## Verify Install + +Go to your StartOS Services page, select Jam and start the service. + +#Done diff --git a/assets/compat/backup.sh b/assets/compat/backup.sh new file mode 100755 index 0000000..99471d4 --- /dev/null +++ b/assets/compat/backup.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# Multi-path backup script +set -e + +if [ "$1" = "create" ]; then + shift + backup_type="create" +elif [ "$1" = "restore" ]; then + shift + backup_type="restore" +else + echo "Usage: $0 [create|restore] [dir1] [dir2] ..." + exit 1 +fi + +backup_dir="/mnt/backup" + +for dir in "$@"; do + if [ ! -d "$dir" ]; then + echo "Error: Directory '$dir' does not exist." + exit 1 + fi + + target_dir="$backup_dir/$(basename "$dir")" + + mkdir -p "$target_dir" + + case "$backup_type" in + create) + compat duplicity create "$target_dir" "$dir" + ;; + restore) + compat duplicity restore "$target_dir" "$dir" + ;; + esac +done \ No newline at end of file diff --git a/assets/utils/check-api.sh b/assets/utils/check-api.sh new file mode 100644 index 0000000..02c8cf0 --- /dev/null +++ b/assets/utils/check-api.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +DURATION=$(/dev/null; then + echo "Jam API is unreachable" >&2 + exit 61 + fi +fi diff --git a/docker_entrypoint.sh b/docker_entrypoint.sh new file mode 100644 index 0000000..fd2e938 --- /dev/null +++ b/docker_entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +set -e + +export HOST_IP=$(ip -4 route list match 0/0 | awk '{print $3}') +export GATEWAY_CONFIG_PATH=/root + +rm -rf /root/cl-external-* /root/lnd-external-* + +echo start9/public >/root/.backupignore +echo start9/shared >>/root/.backupignore + +result=$(yq '.nodes.[] | select(.connection-settings.type == "internal" and .type == "lnd")' /root/start9/config.yaml) +if [[ ! -z $result ]]; then + if ! test -d /mnt/lnd; then + echo "LND mountpoint does not exist" + exit 0 + fi + + while ! test -f /mnt/lnd/admin.macaroon; do + echo "Waiting for LND admin macaroon to be generated..." + sleep 30 + done +fi + +exec tini -g -- gatewayd lnd diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..acb16de7c39f2082bf5f2b2ddefcfa7febcccb36 GIT binary patch literal 59240 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 or_H96nI8d z;ArO6@fVMHDQ@8bAjC^%Y%$_0(103Y(gayye?B^CNkT<1GWTSTU!MMyp&ZS!eh6VI zfWT)>c0Et9=D*;Y*P?_r#a!e-;E}*^D`!oPhI;>&W6~7WTSR)=rnXd#^E7^ym~|ZZ z!Q=14j{N|NE%zMm*4FwxQ+9rEI2BKocy$=5b;sh;$D=hD;mV`r5j(z#u z(|@HEc+l9}jA|cuT}?b+Vo%L1^(tRDSjarf0TrE_e%`?f{Ln&!sY7E>fz6d`QY8tv zw#yOW?r^CJ9CJxB66OLS>8nfiT33)(HKjZ}@Sbf@>t^YA(QNSQnwpzcs8xH1$i0xP zwpNPuvo{)WH_&uUL7u-f)?-34Iu&x`K|p!cM2ZWdZK7cNeOTZYs_bnNb_{lFXC?_s zu}?BJULSr-rd#v(dcB_Koo<%VX1n>wl%&Y3%U=tkLd;!>E-632mZ3tnRgZYn4IKTI z{?i~TeKRE)-OssEeyLlNxLw1PhS9zM;2jZ++B{fEp!EamuL$HJ;ge&Uf7ybVT($H% z`5tl?5LQ>NKH3cL=RlE`K&hsI3fniLOF>~@`d&ox8>DLbZ8r6RQD9-VUZlWZi0LzE z{D)2q3*UW?cRb#H| zdB-tXX(G&8`LkHP=IbItpM10)4s`rA0m29y$4bHXU%!ghEQtfsl*E+C$PfGJq|Pe?Z1$V;($j&V+9aGNCFnXG>1G#;>}8y#C8C%!FzkYj5D7o_fP&FJUg zF60Rw--C1 z@_QqK^nmBLh)CE>43O%DuSl+@V?hp_Jm7$4t=R!6B(mBrvf5M{Lod1&4(o&?2@WnX z(lu!O4ZCW71StZbVPjxuW8lpnO4k+Y0Ek4lR_-PDiPs%gyy9RT)OtaDLw;BzZsL5c z$rAr8u%JZgBI-+12liyR@@YSt>E4UP0Ki{z#XhuDlu+rU!^I&W%Rk+9!tW+vwqjP; zt&knKpDOF$Hc(5W=I@tsVEhup>=B}X5mo`F5vnzaqMI1U3(!+I6t%oT5nrw=J+Xt; zik)lZi*4;uc$?Aqp~)dpMlQ~{ntnCqzd4yiAKn5ljjW^OD~bf>}rfYH#I z#1O=0Q?^HnV7EeB>#^YB z8x`|Rfm8t;me)*|qO^++HxMtj-$Bp>-f(JXTI{R21b*Q*+anQe&%Afh4Tgswq>?c5 zD0Fq=v1G{RVe)D5cXj@?C*hN^`259Y1s$`^0$O@)u8PnLcH zo|XKRdJiMp0E!Wx@FV{RL-Tm1uitJKxH~c^s;U6X>k|3!vxJbI8X~Q0o4M{wG55Yx+u_?}kcq;(QzlW2YRUS;IF;K=h%d(GBGiKC$XvO_ z_$;OyVtL!0ac504y}v=08q6w9&)+sPAT=7S-$1&mOlT@t5%O3uQBeAv7$OE=>q#0_ zCp?WQ7@jNT7~)U5eIn}21&Zezx)ckYO%aI^KBxyPwNs9%4Awra!d_dDiwPDcX>|Q@ z0_@ZwO@DFx$wY@#22cafGn zwC($L5rWgQ zZiD$7*fgG0t3INCP_mPz~0oB6H=2Lz_N1@=(AdVBio_0%)X~x1YC#6>$gIlN3l$PKf*JbMe$j zZe%L{|R^!erDhF1k{ObI3>x*R%po*xqx>Atv4 zAjz*A(K5q31#Yq}dTb7WDRXxkzHdDHoV{j9Fkhl`UiFR(KlG#};9DA{p%i4A$`i{P zF9%!Cxep``>Akk;58aVC$$7R;SjJZNq#K+|tB(SyF})7OvwH+x8u@M0f&0|qE;YPQ znMh<*GYu<+9w(bVCF!OdyVI{+HG8|uY>rRu)JwyS@6hzEhrb^Y@$WQqsWAxnb+@3v zZRYGH9c<7l(tpTKF|E|n;Nl_7Z{QDlxogkvN%=p;dK*u6a1** z(7HfVVHXTZ2PYA~k=KK(SJs%Cgo^5nZo@8sImhqEKE?daYI9ViqnSPJ;sgLg-ZoL#>A(Y?dBa)0iHyKvwhAr0CJZKk?r8K?@?*&2%afE5 zzxVQuRXS={uh$ekX;1sJL2U*0YtT;Dz2|N3yIAQl$3lFlz5OLQ@AD;}zQ5XWpj!nd zvQJ^bIDQ=8CSGUme*b5NjL(o*5P$0n;2XaI)6D59VSDNJCuH5H>R2*j_X=loU5|L$C(ze}n@F75$e!NdIuT(Y<}Um9ay5Tm)0T?qleQ!6Vgx-L_Msgd*p?ZcB|!+?k6 zi$(LH*Qw#^n`h0gZyV>GC0uGzIz~5u5B;ipk`P681Y3~E@4i5qE4pK{H}on6VGn5V zKbyH2gWBEv{kb0z)i9R&rrs0wlRm1JwJx8p>A7CG``9W-e*>L@dHKtiOTU2&HNU|8 z{Bf*I-Qi0i0H}m>Wfof+>e*P&*}bNpjQ!318mbM0{ywnh64eIU!#fE$nrztrSdOhG z3)ouxIUx0>Iu)_Jli|Rf&BB2_;JdcE2s&}wmpgkTZIVCNqack!%QDTM(E+jM8Rm0o zrCkH&G|>dz5|`QNrQ?Ep6L#FQHt~kPu4>#lRXa%#=>~l%d8BAQ@H#c<2hY^2wT#jQ z;K6Q~>b&Ng348wd%btF?5qI+&7>>@9EQzh%p8BUAjK{)}pLarmA)~P~IUK!nZThsw z{=5B`=v8(NE=LFtm$;jaQ?oUq%I$^2r&7ADz;QWLYA=7pA{mV;| z4?Ic^$3>1ton&=?`A6mK=t~;^B2+LuST`VfK9Hr8Q&l+f^<&s#*w3&B~K zP2AUyHPNNyi8Ea}k)&<`jvR%g2KVb*X5pA>I9^Tv+&;Mm^Q?iBM6(;TBhu2ga}FqF zf!*EbZH=E|=#+GBHxBQ8YD&6mI*JJ^U4$MnN8|OdsD@WQMjC598C$MxrRe~Kmv{3E8bA3KrCwTOLi;C+?%h5(TwrykQ9htq_yTJydd~7pGc31)4+y<8OsW&m4TblCpkfEc@GmYI%gw?Ic zlk}9`56hy3hxly*wu(qMw|ZAEsG*2`f2{+o!gT>!e4fTmp&Ea(*#+Nw)1NCO2rfzhbuuTT2VspZU zMz>2E6B}k1IZ=*;3Ei$gJ3I_FVxB;SY#MtR?6Vq!A1Dm64l4pGm*x2%8?)esexmq7 zr9g2P!Qe}rw~Kxgw^a-X_5M|6e08l87)Vlb#AV%foELr7Aiu;Z4?0#8voXyb9&5~; zK`MI3_$D}w#_E_w)z;q}ANy1DVMBwcM{-CBKthj;y)gBMya{q5gM77vag~mbWfNy4 zx_DQ6xg(`u)xV2N5+@qBSasOcf6=bJ2z=MxMB43U;IKB^$Ui}d=F)T_)=?NKoF^Ol z9qpz$g3J`^osYJ`l(vlBk&D$GHSr5G0n34&ZXk}_uh90*3Si-V>=5KE3Aq4Uu)JuI zcgzFN7dKLB!Uhhe3Sr4`b$9qb zlX+QBx9wM0axzZ%y+ui65A-0$yAtxFQUv3SWmKz>x%$N-{W-|kQ^GGu5(|$ zNbYK)+qMu{l^s_b6qQjw=3jdm%nnZuam1E~(uWR~jXg!>-@Ctt znQsErq>s5Zp3?u|*3g{U?nRF-EC2qxxl%Cioj+qCtQfyR(A)A0+Hx_?i|6it0rZSL zGA4Zc0<~Xg&x+D^%^EPT7#^XHw*I=HsB@>r_}4w@mL$3WMM)zO6J|x0u zD3M9O9X6L|hB6Ne11Vx+mLTm8BN#J!GVtt!)+z1#wD^a$kf#jP?rP$E`yR3>gnM(O zzV!z((vf@G(fC^umQ#DpY6q)l!Z~W)p>*sFw}wNEL>Xjc^UUe&KD#0@k>cwfATa!D zl3^#X#T}8Uc?z4^Fk9W29^3FKkZ!AXU%Pf+Gu$kvzq)g~Lk$}sa@V7mxOm8W3(mN? zrwx+MV)u1orfl{%!z9MIG~9J%g%V<1f?*&0fMimTgwFfy_le_`TslLk$;Q-A(7=gvM&%$+~fY4p{? zp<<%@=F3fr#gHaK*TAlwHJmxaa(nE_Qs2|b&_ey$r}uN`@tn7OJv7@#Uc1oq!4?+c zt|VCzo^343?FxID1>-q+)_K&q5)w@Af}K>&)^hZI9oLY4$R~d`cTg}}biF(4JmRdu zfp?j;t@yNw^*Qj1c1JI@M@FXFztj|c=x zi~TN5$lfRs4Ha~?bCo(Mz(@+g&`Gj_=fkW!zV{d04~Yb^^IU#e{)b{reW!NwmU?S2 zibGDua9M3GI=5))+25SAkmhhA=VQrP0&l}xjRW~?@`FmaJw^D?dAnmofe0>)oo5Dz zh2@yZB-xKKV1+DjU=ruf2!o zbMRq?&6L?wXv$?GbiQ{QQHVem*Zq-(tV(fPyf$_-@Ak2YH9IG%{vz%2S; zx4U@<3(6S$G2<+Ojp(oNF2H@88aOyKOBr#|yG8H59s3fn@2Un3S*PxVgGO^r*?9 z(pSZYWN~tbGnZt`w6g$A5_Y?tvDUNcs@#h>EWxOhP<)63-j^-KAMMPWImDJmpIaSv5i_(f(4Hmd{-5^V`Yoz2Y9HRi5E2p=Aq|28 zA|VnIqo}B)N~s7Vp#mx`4WkGmA}J*!VbF@Gs0iq&#LyUk2$Ca1C^dxiyAGe{`Ths* zZx7eyb&dP%v-jG)_B!jn*BRr}Pppu@qchyd5~i|;qYHV9HY#?)GOR!bm?@{97?3p0 z@9=MR+@EW&%?$IoXvbLy^%^7Bl=uVsrKF%O&LRL$QhxCpDT{*7_ATodJAPbdG4pAb zCCi?T?ya6U41HhZ;^UB%frd`*zTlYhHz;a~7lx=x>PvgSjlNFO_|oOOO0YQ=SnjbL zWg(?>&zCzj;F{GipGIY;<8M2ovPa3L8g@AXp^>RLL~$r+yPXt(QVmPr#;19)7lD;~ z`1->Qea1E;ekZYepTW`# zIOyC%7Nwe-LEjw6Kl9n*Y3KP}77m7vf-Py*JnV3sn{*~ztNAp#rL$Sy{e6MQ0b5Xp zvZvo{uz1>b{a00Z5c$+mFtAT2RN!3lgeD5PVdg)~vnjTC`UN+aPv$2PL6$d08`GIM ziEoUiRs}p^+sl;eaJF#ug5@{FG9`*RJxkVEUlNf&bAE{Y(eq*Tt^7#12?XccWLi9} zJHPRZ{oQ|YcNpw-AWlevCPBnr7yD-Wy7hj-8R0&UPR}qu9sph7zc|XjMLCuw@rm0( zC$uV)DYOp5N1xDC$DDulH+BkXhVxTGItSK;0DxPE{Z>~uQbASFPS}1%NDE| zPH4^@ivKHod%+A0!f*}ya#6kt4aM!Aiag%)AQzA9=O5k>j-WRdHs-!$SpXyCNT|K!i-#p^;TDxVdGZ<=DV*R z+OK!Ze19kfa0q6JRbqCpyi%X~MdUrKj@cCnRej61Icrh$1Shw7K3j>?J5T;B&aCV< zo5qpX|MVh+flvN@y_!X}kWim>5`#GogpIo`xsgbY!V@%^P@i{_YkpLz>P&&B!sxlz zUQ%=V9#GBMnuFqJOIVdsqBZ+?v21=xu6a>vsstZ)^3)ZIHB}zVu}EPXWn1;go>iWaJE&uC!eP2sp}j3g5TEp-Hpfi+T3O zP*8N)L2d-_Q||7%Gvba4Lue>LjbB*O3ZV+~QpKOI-jymHGE>`EBCLu0021PE*M9Io z{4X9l(_#vi4=%%P7Ef;#w11#fsMIE3UXWyzF=0*(+V@I{RLdW@#$B$88i$_2=fZ+B zY!*)gaV8dOcBmSrT++@-5J=1Do;+|47qshHjGc?jafu#1QR5}Sk1&TG(MRsU(tqa) zwZKJ}Hy0y7ZP&3Fvmu>wYUKCCJx^rOdT!XxWPhoQ<9!jGeb}H)BXPGKcDX8T&p*?9 z@X(1t^Vb@iXSM$%{uRb;hNUuh;wX$nxVodIrdXsEz^W}=$@M4`cgE+$b;ELsAJI&D54F5mHz_e7g@TVfHNpo_Rct{SS1x#MnTsmV@#?KH+m32-su zJ=Yd+;hRfG3FXC$s`MgFsfGm{W~hx$DtjAg%X>jroe(NlORjgjj6)?BPtVJBJOfKj z_k2A&6EP*yZoZ$_wGthc!=!J!D7ex<_Wb@R$=n z#O|<{@`~S}RElK<$j$J%p*I_N@@)&5gbQ8XBfonvPEn0_FMYRHW@zFTEp+A%H3?$; z@?-Z#)#RSXP)lS3nILttMf0+K`8Fv`WXZth zIDS$hWTR7UNQV?eA7#WwW~xoT&Qrc5XD`v#|od+g0q{MHI9T=NbEt0FvLS~WyITN?iW^~noG-&{N%rmq|q=A zXWBw^Y1@7lq|*o&`t(K!CtP@N9C{eM8t`8AwpBAWkw=#Q1g>gdA2}3ldTP@c*edcw zzQ>{p855Yx+DmIPB zTw008(m2VYoZlleePc7Fls-gGxyTKOBKM1zT*BI2IwByBPMjW%ji?MA@KuFhYb%EZ ze7A(s1d{Q{1@~cqtzgqyvsJ(9Ju%W@*n3>fCedW-=tB0VRy^`c7b1vka4+D{JQRa@ zvVxxE+J7qh5=0PcUsg&61-q2GGOCwn#6^Q0(>fe3hcf|kt5Vz2bb>?Ro>=p2*X=Ms z^^l9l@frqm+L8vS5WWSr-z(ctZ%e^qD8&fqi{eP#w18Prb6M^T*(JIxtvSW;E4a9g7DSuHTlj%ExqEMMYcH;cultf(gLApyh> zUnCXSzkf&O)=7blbB6ZPXvQGKvJhvqBhVy%4@nV*L8suP#_rR|YQgCGnX1<*K{^U( zM&HEp5l-_dU)KxzmIbEaWN3b&9E_e}o~{<~0TmE}~E;BPs~z4d%Szb4fSqyEqU%KVeTz z+$r!$_8d2KQ6@&`DSOok@Xx9W8NNpc!3H6jXKvkePTFcw;d;P6V{-YPr9KwaPP<;3 z^4$&spv76yI5D%8c+_zg%kUe5M3W@X8VQii$nM@Y=R>V);-vhpHDhI6y@I*pi+k4 z02k26JePhoACGghUd%4jufM0uZM+N~5N*lAOL>n_v(@fgst5rbcnRhfoJxk?Bt~Rl zvBmZtL{X;Ib$mCalvRBX$h_W-y9r0eAf&GNtW z$9Pnn#Bewd1>Qm$6;Qo$lFr}@X& zWjjgz!6trr`Zj-N%-)>$(EhydaS6LAFD3!~prHKexDBLK+B5ZhXHuC(Wck*RnsYS1 zEcl+1l@P^Zn{?xSeG7Y0p}nYobTNfRoMU0s*hP@UuxQ$yE)$Ewp}lS93Ntag|Hy|N zK@PX$i!DTiajkkhft7V%@z%$yegql?MgLTU;xny!u z@~av;D(-SqzS1(@N^?UbLN zs)zXZ*uMIEtqaieXI=lg{2d%2fe?{RN;exkkl`k2hi}ZtHPJv+Ru}KiwJ>8my|eX> z+0+7~Mb8afAfJ}D103na^cU{~y0{-7!Jls_Nm1JEKbnB9b+5l{bjU(0_79&+D=Vdd z7OkGY8=1^tQ1<;F82kSY4PE%FKXN??;>f}Uc)w2QYQ@T&+uAkYY4Bg_YzcWi*N+Z4 zDNSi`@}aq@u_rw_IVfP0;t@T_s1rC>6g*YU?Iz5?p5yt48N ze2rZL^ttoyX^5lSLtqogb@W*1Sp8||`eCu~!%y7>TH@SDE+Dcr(3dM`z67Zp%lWHIEldilxX$fPesO@Yz@YyHv=?|@}DVGU+s4W{otC=`xPN zOY+tZ##tfE{guSnbsU!(MShW9yu=cDY`MpDoQ-&VnuEd&Rp(@D-2FVsJ^U-DPJStJ z5P3v)F4fHwwhZ{M&BUY4pjf2FNNy$Vs*^AGPv^@D9heE3)?Tk$W(evwG9}(U;DtLR z`R0#o4t_?zS<9%0Ns_N)NhTLHQeb+0V?JbEIfn@LFupnHvm$`A&-qVihm0Wr#Qg@m7ajD?VL0R5Xy7HBaDZXyc(&;~_(I7~t5$}c?4_w7zwn2&tmnj7DS@ulb z!9#gf)%KZoL0qfkz3DV31-2ku@{`uTu{pd{LP%#z*D#`QHmr?L#6f4T*n1MHr9B*z`Uv1h9JVVex6v-vZ*MQ~|O}idKRfTxwTgOqbnE+l%*8hUAofVh)j=8(Cxe&|z!!%#k zX6VJ?SW1QBU5>akVJ~4#9M!~Dz|;h(B=WS&wjggzgq0IYcqM+fy~4HPeAIgNtI@K) zOiJqhtk@L<1)qd~?&5rTgbcx!1a|5XqwS{endRd6+jn#zZy?^uvg5dfhNN4BYl(c3 z&G-Yg@v^wobQ^ptxV@6z?|O_&P1N=hz9TCxH|45r*U4=zsaIt13^)Aaf&lF(2{DiT zc6KDW(C!aXHxrocmprPjWo^>5*O}f(pu<|@4f_<}FLW$RsBB1Mnz>cgt|LU-pO;j& zki40Mxqc3q5%Egu@NkB6;Hn^JFBdulIm^r`tf>O!xmz?hWn{6T;8h1oBW>?LeB!cEp+*2vIiS#Q_P zXBd-!jIY((?Jsaj>Y8*v-^`=~+m#~z1!zxg?YlBvHv_G&g078AIVehcLe;Eg9MO6n zE8rotumO`8)m@1=pvM|VCg|-p-b4wRZg!^eXQ?UAnA`89T=i^tMH415VXtR?5+&`p za=+V)!!7;b6?@7EO&AjyZRv+y1Gah;WGN(WY@Tqz@@z*#v71f zf)|Q{T_^7t-d$JUlr(4^KsIT;%DR zpvS*Tmo!BqGwSxEYS18C?+=aI2+5PaQT1{Y1I4*Z#=Y%DC>~e_9R%Cbj2j1^{H;caP_&A|y9jBCbL;3?ao z4L=Uw(72n!+2`%qvOtrtjup73tIXBsH{Ie-6Ga%viz2b9+IcS#fdDxY{2!OA3Z20= zlN*1_M0OrwMjqsFbX)G1XdKyKH7z2D`P-)S94TF>QCI81GM-`!6G2%2t42l+0Ombg zylK)J53Kj@p4!l-J^j_0CWel{3h3LCP&>8P8PVedbEITHdQ@eOfW?mZh z5rDk@Zy^}&KSw-`d_k539N8E|HnClAh%|Io>isHD(I!!fGcD7fYUn6))yT9F$Og&Yq6=)KV=!P!{Om*8 z_w}#=W5lIU50O(d1aR&`nj_lNJm@7{q=+U7jSG9jp#Q>#B=+znr7DSe;Gny|EO5nHWT=m>j|()xatm*_?*nCe;nRO6YNN?o3LEszzs!RW-%|td4 zy??8_>SuQs!y^=Q09&n0C_)Y^;tuoPjZM<*t^!T?g-tdy>{mkVm&F)7G&rqz0`w_y-1S_nFc-meOn}rATi(L*&T{%;~(Yh;K5-&xhO#Pgt4I) zhr4#!rI6{(zTIvO?kzZB)0q1)+RWHh85c=-aXSaFDkUWcij#R9+|K}V(uqH zhIuT*p|AIobN)AjR`;m}54-=DK>%jWA!&bOQz=f^1ulhnp1L}o{rhbSijgufHdSO2 zU!S|kBx~w^1`Y=u_;8`lSaV}j*}3|h+ZL!c;Zcq(KaMvBfa#GYlIa;k+xj#-zbw;o zguCS%JX#PYNQMnFb~io=MSG z)9ILbXY&m$)tzccOh-q-4niuDe*4=w6?ZK$rCrwaL5oj?VuuoJ&fm3Yvgj);baB;V zSN(-9l77*i=u$0ez5YIH@!@=)u3aj+z)rfiekh11m46JE5$e0*Tk<8&_nc>&-?KK| zx0X0jS<|P6Axz@CL0gG?aOjQVMm5SHE%!au%FQ!)?xPR;q=vssu;&((59`qEx?>01 z#(k77THdO8lhw285c5h_9Cv)-qJ$L=yMQ8#qkb!`m+%$F8Kb7*LBHRPnTC&X_gPO{ z*wN(BSK#OZXZl}aA(tv}w$n>rmJ-$M%4!zxjR@gWZv?f;0%~&;Y&nD-3AVj^2jEOR zy%LpNY-1RF=b8?#@mTX{I+=wa2!m!WHC6ra09#ynK!?LK&!(B;+A&t*Mo&v&Pk!X| zkHZ&uVq!O*+WVKF_P(dmO@}7eaYT4jAWLOIPH};gbP}uMaDpATkYARZze0?rSJIgH zHXly2q@b;t!mI>fg+t~$a6{Q?Y;uV$_NgjXnskr66QCay*w9o_{S+u&1aIHZg2p;# z@x{Ke{i_R_JM1(+l;jNodWfvzg5w!tikoRnC> zN;nOw8G4~Pzgv%#S8-P~)+;G20H1f1yTSM>K5H+`3AwJl#-Z1ET8br*+4aL(3i{`8 z<`3b6Lj5FEPUJO9%2qcE*Jk^6LJuY7Nw_480k9ghIf6xx30uKiv0koE{d-55vzUCC zrbX5%mM%3}l`+0LbddT1$X4uoYwA0WaBUQvwhXZT@-sf^$dC6HS6$$-O=MjELkqqZ zkH__Gg;{}wuc29a3&@jwKbD1AENyk1=Ra-9(EggB@Fy&cqaw0=MRBYWZn z>t`-3t&EyP*yg#zFi{`ZhwS>fQETQc{k?c@@q+SyPMVZ^w8P>mntUkB3&xD}S=unb z-WeU*k?E%P;pvSg=)`KS+&J6L6aVZG|up@NbQpbH^R-*&l@KF#ZhdWbUVu6Azsg`c`5) zI&g^31Yj8=+=A7G=*NRL_`mWgTsgj8Ocy(IPt8;6h8#sm=ic&dfA}AF@vKYyG+5hT@ytrC+yJ0f2=k^1N zwWp9yV#{!KwsqRdnIw6%4s21XEUNP{Qq7pXC@YJX$&q1Y(uwO$4HGsfuBoZ`)A1af zE;iS%CaHn1Sj4rryFv%&8=CwSl)}B9oxm5IsZteBaBxT_TPAiN@wV@9)#XH~n7e|J z+c&PqX99m!Eh$hhMUDN;a`hkJqjV{B560CJxR}tk_4!RWc{+XZRK&IPz))1Y@o6al z_%8act=!bRnh6kD`3AAY0`3q+0Mfn~48`{ab6aG>`03?bTbnj8d;- zGoDeOdMWo(t7?ei^S1yD#n^zR3iT>O-L9T-@Z{_@-mVU0qrq2p3IN}l5?w?0Tv-^%l=nP)OFc=yE zjULz&3^#`qq#VNb&nij&_b7zE9KtvU4@rsVp2FHd^W9>sjv{{WOQ}~y!*!1{0HHbI z7KapM9bWFI6YakZ`EQ23fi&CLNq0Jzfk&Lva10Xt1rsL{49BB0#lU;~g5gYbTHs-x zWc)he22n(C^W^=F!8I-5@+e;LWa)FGPByegp>YC^jU1I%vqDSf);^@&+6mRVjyUJ1 zmraa~c(IaHV0jQl(v!>UCaOQ54{H%7*+vUSHk<{n?2f}%hip2>E^?z0Y@4vQsyQT_ zUDxuj4Bz^}%kyzJ3*~FT#NQY6njXDSbQBD$s69nl(7<^{HDnIWT&%Cy>5Ef(vth+% zE=ZtX7!l4uueIKaX4gFPIde)FJ%6U@m0iI?`TlUmz0-?$kAc>|zZ3LSWPV594Xt4y z%8TU2(CKZ69@YNFsX6#dGYfj`y}oVc9~QnWU=82zjnC@F7EP_O6S(=jj#i0oMjL8i z+iFcIx~=)sYzRJgGm{vHs8>7%fWsp0vuL7D@nM}kD6Y8;NU2c8`ES!aED!xJNA?Tj zzHj!(TbWG#y@&Kpa;RkK1^xLjFteS+#${%g!;NNLUaDU+Drg{=({_`(U;KXF=r7+@ z#zdOLHZKLIOJ!{Mo@qQ)!t2>P(;gEsy&+?sab*3qhe9T+UrZG%sy*SY?Ofz`?>W|&; zkm&Lj35MB*EVk+9h4Ijc>1~uCT2Y4zeLGzcX$`?~IBeJMc!fCmwuru+=sex10sbU0L`QDf88f-3R#>Dncn}UdWCp0@R~z|i8iGVn^bWTw2{#<|Y%P_nZo`InZ-V+P z9%I~~V?-pM)93GCjZ3@kWfS{gza}Tuq({qmXu0n`A`!42n{qVnVdY@d2mC_*RQxL$ zJKRcQ@lc`~+uI9B90RIG?s8>Px3;!zcV!>G^RL;QwwuU4-9G)I#3$CQsBH(C|ulL&~o^JJul7aPBPPy@yHjtnpZPRyI#su`=<@z`n}3a*{Ib z>3hfMU(b}$28`*?zJW~LD7+ZIpoMRiviv4r#_1Pb(Uw^>^|O?9-Eu<+N=q!u)(=&& zbEEhTsK!&znWdB4ii@T?)!5KS&~XXeX`A2m>-VbI1yWq?mD8x_;xa4RJ`|PwC}l(J zHtTDijjM_G;YFSn}=B@0LG1fR17`S#bt)kFN^wna;UxQsHyx zS6kq;k1MyY7jifki{iky4bba4CEJhF;#bauXWV|`E>y0Zqv4|=g z=&7Dc-Ls^|WymXk{W^n$2%U z(qJM(YUW4(MFJ;gWlTN&@s2Bsh7LQ_15TX-I%HW?b}Q1kmEgW6F^nAXKi^r6vI5VEUrO} zlW$E8YhicVe>w#U8pZ@1WfFg0zU-rS@yc1(&k$W`v1IK9;J${x2wzJn9AEvp(yhj& zzs|K>7m`)FQf;?fo#H>Hc=6ZTpknBQMdB|duFQ&Ms}D)#aNxccXWOp1s_5#x*l^)gPnToB5ro*LWTSNq!Ff$I}G@!DpeC z80z`yZIy+sZm)-InMs?lrY3XT?j?i`ub=wk*UE$GZD0H4KPy!*h#r&enW1Y-y=#^I ze+O1!upa3xJ_=o!%q(95c-%rt=oKJ2C>awMTlIod{*FO1k6p!Li~8!K+j5QDvfcdJ zYqqt`kk6-{Eg-FJ)gyk<^V zRJZ5%w6&Hi!A8I z=vtlb(L5Di0HSFeNwVD)*usg(VD?8pWS0op^+bS&6QkvHPR`l#BMpo?+5PC;$Ad{Y z@E)e0GG0Qp;F^WzK{$b{H z2{bVG+~Z>RDj_Vd47lzfVX39o?D|QlE$jGNVr48OgU0|8mS398uIB|nN?zgd-o(mK z7IXo;3lUjziT7c>5~Pb4{t1>x>r6v^=AF0rl7~UpIF^e6 z{BD;0p+uVvHG^#ykss9)AAX915~#*@c%+1p6}u}MdP6JTTZ~*Rn8`($i#m?9%UtlJ zxu6zI0Dtolhk}_Wh|&n@DCU&YKPa@y>PaY8s0C5jLPZCJ+x0y?nmz_H)`kUn{4_SWUXxtqXbi3ycqE|O|4 zngBwT2Hq&>J#!KdZqU`lSD&G;sg_B!jbX@~HkkaEdlDwkyxYuYapTlKI;1%CNnVQom1^ z25SDNG>PA@591G0hpdM;+Lhmetl09EEYK!R1;Uj=JYv*mDW`4x7b0b0&CM&|u`l2m zgjsR1ZESXQMF_3CqVt}fRTnDY`3>m?ZDTX_IzlK8+mzxC{Kgs2f+~+@9tQ;5mZ{^{ z>9)~}Jng)E-Nzt{sCjXN{JbMtr*$UCB(d$&SA7oYrRK%e^7Hm+T@4}ZYhzPyWAlYk zc{Qr$H!I48j;_PP@)l9O5~g5m)Cx&Ch6coASy zSXTGi=flQ5aBPUnmyUPMz&ilO#G{G}muB{b9Y#pyMDKfJF=pi6vVO5?O)Kw@|M=6@ zH#7VrpaM_iPxR)zXM+<2G-4!rF?D92z5(#tO!@qc{WgKtS-8;i^z5%)T;hPrIrwW) zBbXaeL@{ZWuH46cbKrv5k;6&1bbY^d!NGtnI;EHc-@3SVK=NIoSr1VzRwOX8zEU>O zuJ$?Zod5aj;&}l!U@ZGST%O+s`&%DotQ!<0R5o=uGNI?&e1@+E@FS8GW_9b^n|{G; zUce2e(DN;`|E`XDAkyab2i_sEeODvWz`y-iWm&o?3+m&-q@t^41(1t<00-jY(zr4s zgV4okxJaRIZ?~HZ4nY;~@UZ>*%!RmI`5%&QqXx-f^*gX1n`FQ4F}|`9DJk3aLo;~7 zihc$Cg#sM%w$Fd_f{HM^7h+89TMW@xox!8NE2H$4@2y}`#I9kKWv;i^7?yBc&F62N z{|1nTM(h$n6l6WOBzTVreINQ*|9NH$Sn@EdD{7bmD9W#3Mb+e|V9gy<=H1^&+=|5cd5(*| zb`D@cq&95)iyDZ3lrVV4zwh3I!?18HuVH-1X!3VlBf;ZCc}uMs9}?$Q8}}QvK_qVM z`j#cedSOuD6BDY+y1KpA3Lt{s*v!w`!+LS>ABP!4O3rVqIkg7K*pkfBi?}o>*vN!t zJ`XHwjD|oV*v2+fr*uVIA8e+x`E@amdf3n~xKbm+2d=gn#_P?{itp!ZPtl@IiAB4>Ef@+YN0e z@r&&1ylf^)pVaOoGb4dpWi#4Crx1x_1N7jmb=8I6yKcf1JfC*B#Q`Q#UgTL&qrO)U zb&Uim$|8zW<0-o82Rt%A0y}12i{!q6=JSL6fL9pGWu$fDkeoC${Ch)0xBq1(Sb2@1 zy4sm*lN`uQ83+Azb>C34gT4Uja~^ukNCUM+?oGxLjD&m1XPF0go4<)UA zv|TtP)W;0nwg2*u2Q3$rfrEq5>eM)Z&|P<|%XgbR%4~6lW5QslrXf`Xat~s;(=XE= zw9p^HsiMTNX>QzzTE!t$Ci?k%@1oPVp4&bH9LX|uZgtVlv zfj)LrEaub*BCt35d{*heA@AeRJHzR5x(c~pL77SGkGL2Ej`iK1sj%(- z+X$kNauw`fUORda6etd_AAXGvfK)h!6`~0unjU3C84lw@u?w6q;OsE6!um(WdRFLP zFDRm!rf4E)hDYS18mlf1L=9~&an+5JcAYH`OwEI`C$C~DA z4=(guiOsl{3ts7i0YNo=$>ZNPeZvuLFJw5U!A2l(7b`#Y=FG?f87R~u7A^z~0odq> zEQqeeA`u(_gGbS5RAneZk11h;N7}_c!f=-pL5dec(rzul?r;q2na1;c>8)P8UvYhV zhmsajtw03}$BMQA(PO*H@sw|kG7QyBC}cZUNEsCJvsOk8NneQHlzI$uKY-kXT{mYI z-j?o1I;w#XzXbr~%KtUgkHR_{G{?t>hVS^eg^PaFB1lnfCv)5hdE zvtfi0_^-Q*nJvqFDEvh#6qOf&Np$7rjHmXT&<}AoBKOdZ=~PUePrx2Ly*Z;&_Ip1% zrHsEpo*ry)3T8uD zyRd1n+e4`GGAtZQe$j~2OR&DS3S;7I)4Xe|3zOb2O06QO-3>;12Z-M)$5+V>0}^Nb z($&6_6=gZE7vW_%|1NR_1()b{RR4rUz>f(MApW?6-tX%4O*Cs``+(MI08J0CH^{WR zz0&rE79qH(Z1!aj6KWN~D(um?+rxT5B+ID1W1qDZiSdM>rkBC6iV_q zt=Hx7mlb|O&`ciF zfkki>whog2Zpi|7^3ud+50=rs#pZk)pm`u(zzSy%c#L8-LUK`JUOub(rY4q>6nJIAwe$?#wHwP%kSKbFGgA}4`8I9KI8bnVZQiGn@% zvpe^yun^em2MwVz$X08|=AUKvT(_5co`XS%!dhsim-{up({SRnM1k=$i&xZH4upin zv4YN$kLRkjRHjrHyeQ-Tg71)kn61&+@LbvSb>yS+gqw$}@Uz`B&85)2LfFhDblvn0 znEw%I_fPBY7~66q7U5ZIy(qDp-v5U4Gs7|lQ(f`ePwB4k#M=Pt#`haWCSRq5D7xIa z6w=(UQo7OzkMP)}$ud2tMQ(24oH+pOuA~@D)>d^15fmarhAdb|$I;$=A(*jcIoWH1 zg0d5v3VzX9+yp~rP`dhdVp-gym--JFv1M)h1Xl4xN#HjE~lb4n+5{S%Y>53Is6wkiOjpSqbWxtXZ=3L*idVF2(PJmnJu0 z9n$9?dsya#NM5)ZB)K!KCwV{AGLyta={tx?M^Osu(NbcGhXmrG^#l!OAL7o3(94uV zSg|DMZXTk&*5sxySQggxEHm8ajgGR3VjYJrV7d=#1pT0ofQh%0gv~57*32~S6|6;w z15*~zy^$x+WuEKn+=PB19mg7pd;;XC>|;tiSlwATtYE^~j5~eeRNcAY*wqVO|ICsQfsJ~9r_J%*_ZV0-wQt|mokC#%Ada5ja17;bmtxM1LD=y5RM)^RjN|)i+|R6~C?m!E94+&->sd ziK2;X<$vavcsznX^p@{lt_oS6s^vMqyxL)3LQSy?Nt+E=>CoR|_tf&1-30dsJE*L; z!e8nhbh;VydNp!bHA5-ZAuqfBKAT?(zE=Kg_FDOhf=RQ$66zvj!I3 z72gEtH$9^@>KG7Du9jLWY^xh==?(d?Hql`hx{~|b)4$wCal*2&ZK>Km9{b;!r#;1% zg=b~4V7z&lUV@MwgNNp*JP#rulIBx7T+Tt54Hy>|(t1P2)ams<0y{Qm*Z(5d-@|?$ z`+sRivBP8ikFdUltp7|{pN-hhxI@?!!~RIp>yrulDUE4*>wn_6|F6jZ>r3>1lL-Ca k>VW`w|G#b<(hGVXHGX@AzGn$xd(W}MCWoFKB;NS{0NfmLU;qFB literal 0 HcmV?d00001 diff --git a/instructions.md b/instructions.md new file mode 100644 index 0000000..e69de29 diff --git a/manifest.yaml b/manifest.yaml new file mode 100644 index 0000000..f6af2f2 --- /dev/null +++ b/manifest.yaml @@ -0,0 +1,199 @@ +# Example written in yaml (toml and json are also acceptable) + +# The package identifier used by the OS. This must be unique amongst all other known packages +id: + fedimint-gateway + # A human readable service title +title: "Gateway" +# Service version - accepts up to four digits, where the last confirms to revisions necessary for StartOS - see documentation: https://github.com/Start9Labs/emver-rs. This value will change with each release of the service. +version: 0.3.1 +# Release notes for the update - can be a string, paragraph or URL +release-notes: {} +# The type of license for the project. Include the LICENSE in the root of the project directory. A license is required for a Start9 package. +license: MIT +# The repository URL for the package. This repo contains the manifest file (this), any scripts necessary for configuration, backups, actions, or health checks (more below). This key must exist. But could be embedded into the source repository. +wrapper-repo: "https://github.com/kodylow/gateway-start9" +# The original project repository URL. There is no upstream repo in this example +upstream-repo: "https://github.com/fedimint/fedimint" +# URL to the support site / channel for the project. This key can be omitted if none exists, or it can link to the original project repository issues. +support-site: "https://github.com/fedimint/fedimint/issues" +# URL to the marketing site for the project. This key can be omitted if none exists, or it can link to the original project repository. +marketing-site: "https://fedimint.org" +# The series of commands to build the project into an s9pk for arm64/v8. In this case we are using a Makefile with the simple build command "make". +build: ["make"] +# Human readable descriptors for the service. These are used throughout the StartOS user interface, primarily in the marketplace. +description: + # This is the first description visible to the user in the marketplace. + short: A Lightning Gateway for Fedimints + # This description will display with additional details in the service's individual marketplace page + long: | + Fedimint Gateway lets you connect your lightning node to different Fedimints to earn routing fees for swapping ecash <> lightning. + The Gateway runs alongside your LND node and connect to multiple Fedimints and provides a UI for managing liquidity across them. +# These assets are static files necessary for packaging the service for Start9 (into an s9pk). Each value is a path to the specified asset. If an asset is missing from this list, or otherwise denoted, it will be defaulted to the values denoted below. +assets: + # Default = LICENSE.md + license: LICENSE + # Default = icon.png + icon: icon.png + # Default = INSTRUCTIONS.md + instructions: instructions.md + # Default = image.tar + # docker-images: image.tar +# ----- This section commented out until we support long-running containers ----- +# The main action for initializing the service. This can be script to utilize the eOS scripting apis, or docker. +# main: +# type: script +# # Defines the containers needed to run the main and mounted volumes +# containers: +# main: +# # Identifier for the main image volume, which will be used when other actions need to mount to this volume. +# image: main +# # Specifies where to mount the data volume(s), if there are any. Mounts for pointer dependency volumes are also denoted here. These are necessary if data needs to be read from / written to these volumes. +# mounts: +# # Specifies where on the service's file system its persistence directory should be mounted prior to service startup +# main: /root +# ----- END commented section ----- +main: + # Docker is currently the only action implementation + type: docker + # Identifier for the main image volume, which will be used when other actions need to mount to this volume. + image: main + # The executable binary for starting the initialization action. For docker actions, this is typically a "docker_entrypoint.sh" file. See the Dockerfile and the docker_entrypoint.sh in this project for additional details. + entrypoint: "docker_entrypoint.sh" + # Any arguments that should be passed into the entrypoint executable + args: [] + # Specifies where to mount the data volume(s), if there are any. Mounts for pointer dependency volumes are also denoted here. These are necessary if data needs to be read from / written to these volumes. + mounts: + # Specifies where on the service's file system its persistence directory should be mounted prior to service startup + main: /root + gatewayd: /gateway_data/gatewayd + gateway_ui: /gateway_data/gateway-ui + # Specifies whether GPU acceleration is enabled or not. False by default. + gpu-acceleration: false +# Defines what architectures will be supported by the service. This service supports x86_64 and aarch64 architectures. +hardware-requirements: + arch: + - x86_64 + - aarch64 +# This is where health checks would be defined - see a more advanced example in https://github.com/Start9Labs/start9-pages-startos +health-checks: + web-ui: + name: Web Interface + success-message: The Hello World is accessible + type: script +config: ~ +properties: ~ +# type: script +# This denotes any data, asset, or pointer volumes that should be connected when the "docker run" command is invoked +volumes: + # This is the image where files from the project asset directory will go + main: + type: data + compat: + type: assets + lnd: + type: pointer + package-id: lnd + volume-id: main + path: /public + readonly: true + gatewayd: + type: data + gateway_ui: + type: data +# This specifies how to configure the port mapping for exposing the service over TOR and LAN (if applicable). Many interfaces can be specified depending on the needs of the service. If it can be launched over a Local Area Network connection, specify a `lan-config`. Otherwise, at minimum, a `tor-config` must be specified. +interfaces: + # This key is the internal name that the OS will use to configure the interface + main: + # A human readable name for display in the UI + name: User Interface + # A descriptive description of what the interface does + description: A simple user interface that is expected to display the text "Hello Word" + # tor-config: + # # Port mappings are from the external port to the internal container port + # port-mapping: + # 80: "80" + # Port mappings are from the external port to the internal container port + lan-config: + 443: + ssl: true + internal: 3001 + # Denotes if the service has a user interface to display + ui: true + # Denotes the protocol specifications used by this interface + protocols: + - tcp + - http + gatewayd: + name: Gateway Daemon + description: Gateway Daemon + # tor-config: + # port-mapping: + # 8175: "8175" + lan-config: + 8175: + ssl: true + internal: 8175 + ui: false + protocols: + - tcp + - http +dependencies: + lnd: + version: ">=0.17.0 <0.19.0" + description: Used to communicate with the Lightning Network. + requirement: + type: required + config: ~ + +# Specifies how backups should be run for this service. The default StartOS provided option is to use the duplicity backup library on a system image (compat) +backup: + create: + # Currently, only docker actions are supported. + type: docker + # The docker image to use. In this case, a pre-loaded system image called compat + image: compat + # Required if the action uses a system image. The default value is false. + system: true + # The executable to run the command to begin the backup create process + entrypoint: compat + # Arguments to pass into the entrypoint executable. In this example, the full command run will be: `compat duplicity hello-world /mnt/backup /root/data` + args: + - duplicity + - create + - /mnt/backup + # For duplicity, the backup mount point needs to be something other than `/root`, so we default to `/root/data` + - /root/data + mounts: + # BACKUP is the default volume that is used for backups. This is whatever backup drive is mounted to the device, or a network filesystem. + # The value here donates where the mount point will be. The backup drive is mounted to this location. + BACKUP: "/mnt/backup" + main: "/root/data" + gatewayd: "/gateway_data/gatewayd" + gateway_ui: "/gateway_data/gateway-ui" + + # The action to execute the backup restore functionality. Details for the keys below are the same as above. + restore: + type: docker + image: compat + system: true + entrypoint: compat + args: + - duplicity + - restore + - /mnt/backup + - /root/data + mounts: + BACKUP: "/mnt/backup" + main: "/root/data" + gatewayd: "/gateway_data/gatewayd" + gateway_ui: "/gateway_data/gateway-ui" +migrations: + from: + "*": + type: script + args: ["from"] + to: + "*": + type: script + args: ["to"] diff --git a/scripts/.gitignore b/scripts/.gitignore new file mode 100644 index 0000000..a6c7c28 --- /dev/null +++ b/scripts/.gitignore @@ -0,0 +1 @@ +*.js diff --git a/scripts/deps.ts b/scripts/deps.ts new file mode 100644 index 0000000..12d1eb5 --- /dev/null +++ b/scripts/deps.ts @@ -0,0 +1 @@ +export * from "https://deno.land/x/embassyd_sdk@v0.3.3.0.4/mod.ts"; diff --git a/scripts/embassy.ts b/scripts/embassy.ts new file mode 100644 index 0000000..50c491c --- /dev/null +++ b/scripts/embassy.ts @@ -0,0 +1,6 @@ +export { setConfig } from "./services/setConfig.ts"; +export { properties } from "./services/properties.ts"; +export { getConfig } from "./services/getConfig.ts"; +export { dependencies } from "./services/dependencies.ts"; +export { migration } from "./services/migrations.ts"; +export { health } from "./services/healthChecks.ts"; diff --git a/scripts/services/dependencies.ts b/scripts/services/dependencies.ts new file mode 100644 index 0000000..7ac51fa --- /dev/null +++ b/scripts/services/dependencies.ts @@ -0,0 +1,165 @@ +import { types as T, matches } from "../deps.ts"; + +const { shape, arrayOf, string, boolean } = matches; + +const matchProxyConfig = shape({ + users: arrayOf( + shape( + { + name: string, + "allowed-calls": arrayOf(string), + password: string, + }, + ) + ), +}); + +function times(fn: (i: number) => T, amount: number): T[] { + const answer = new Array(amount); + for (let i = 0; i < amount; i++) { + answer[i] = fn(i); + } + return answer; +} + +function randomItemString(input: string) { + return input[Math.floor(Math.random() * input.length)]; +} + +const serviceName = "jam"; +const fullChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; +type Check = { + currentError(config: T.Config): string | void; + fix(config: T.Config): void; +}; + +const checks: Array = [ + { + currentError(config) { + if (!matchProxyConfig.test(config)) { + return "Config is not the correct shape"; + } + if (config.users.some((x) => x.name === serviceName)) { + return; + } + return `Must have an RPC user named "${serviceName}"`; + }, + fix(config) { + if (!matchProxyConfig.test(config)) { + return + } + config.users.push({ + name: serviceName, + "allowed-calls": [], + password: times(() => randomItemString(fullChars), 22).join(""), + }); + }, + }, + ...[ + "createwallet", + "loadwallet", + "listwallets", + "getwalletinfo", + "getblockcount", + "listaddressgroupings", + "listunspent", + "listtransactions", + "importmulti", + "listlabels", + "getaddressesbylabel", + "getinfo", + "getbestblockhash", + "gettxout", + "getblockchaininfo", + "sendrawtransaction", + "getblockhash", + "getblock", + "getblockheader", + "estimatesmartfee", + "getnetworkinfo", + "uptime", + "getrawtransaction", + "getpeerinfo", + "getmempoolinfo", + "getzmqnotifications", + ].map( + (operator): Check => ({ + currentError(config) { + if (!matchProxyConfig.test(config)) { + return "Config is not the correct shape"; + } + if (config.users.find((x) => x.name === serviceName)?.["allowed-calls"]?.some((x) => x === operator) ?? false) { + return; + } + return `RPC user "${serviceName}" must have "${operator}" enabled`; + }, + fix(config) { + if (!matchProxyConfig.test(config)) { + throw new Error("Config is not the correct shape"); + } + const found = config.users.find((x) => x.name === serviceName); + if (!found) { + throw new Error(`Users for "${serviceName}" should exist`); + } + found["allowed-calls"] = [...(found["allowed-calls"] ?? []), operator]; + }, + }) + ), +]; + +const matchBitcoindConfig = shape({ + rpc: shape({ + enable: boolean, + }), + wallet: shape({ + enable: boolean, + }), +}); + +export const dependencies: T.ExpectedExports.dependencies = { + "btc-rpc-proxy": { + // deno-lint-ignore require-await + async check(effects, configInput) { + effects.info("check btc-rpc-proxy"); + for (const checker of checks) { + const error = checker.currentError(configInput); + if (error) { + effects.error(`throwing error: ${error}`); + return { error }; + } + } + return { result: null }; + }, + // deno-lint-ignore require-await + async autoConfigure(effects, configInput) { + effects.info("autoconfigure btc-rpc-proxy"); + for (const checker of checks) { + const error = checker.currentError(configInput); + if (error) { + checker.fix(configInput); + } + } + return { result: configInput }; + }, + }, + bitcoind: { + // deno-lint-ignore require-await + async check(_effects, configInput) { + const config = matchBitcoindConfig.unsafeCast(configInput); + if (!config.rpc.enable) { + return { error: 'Must have RPC enabled' }; + } + if (!config.wallet.enable) { + return { error: 'Must have Bitcoin core wallet loaded and wallet RPC calls enabled' }; + } + return { result: null }; + }, + // deno-lint-ignore require-await + async autoConfigure(_effects, configInput) { + const config = matchBitcoindConfig.unsafeCast(configInput); + config.rpc.enable = true; + config.wallet.enable = true; + return { result: config }; + }, + }, +}; diff --git a/scripts/services/getConfig.ts b/scripts/services/getConfig.ts new file mode 100644 index 0000000..7f955e5 --- /dev/null +++ b/scripts/services/getConfig.ts @@ -0,0 +1,63 @@ +import { compat, types as T } from "../deps.ts"; + +export const getConfig: T.ExpectedExports.getConfig = compat.getConfig({ + "tor-address": { + name: "Tor Address", + description: "The Tor address of the network interface", + type: "pointer", + subtype: "package", + "package-id": "jam", + target: "tor-address", + interface: "main", + }, + "lan-address": { + name: "Network LAN Address", + description: "The LAN address for the network interface.", + type: "pointer", + subtype: "package", + "package-id": "jam", + target: "lan-address", + interface: "main", + }, + username: { + type: "string", + name: "Jam Username", + description: "Administrator username for Jam", + nullable: false, + copyable: true, + masked: false, + default: "jam", + }, + password: { + type: "string", + name: "Jam Password", + description: "Administrator password for Jam", + nullable: false, + copyable: true, + masked: true, + default: { + charset: "a-z,A-Z,0-9", + len: 22, + }, + }, + "bitcoind-user": { + type: "pointer", + name: "RPC Username", + description: "The username for Bitcoin Core's RPC interface", + subtype: "package", + "package-id": "bitcoind", + target: "config", + multi: false, + selector: "$.rpc.username", + }, + "bitcoind-password": { + type: "pointer", + name: "RPC Password", + description: "The password for Bitcoin Core's RPC interface", + subtype: "package", + "package-id": "bitcoind", + target: "config", + multi: false, + selector: "$.rpc.password", + }, +}); diff --git a/scripts/services/healthChecks.ts b/scripts/services/healthChecks.ts new file mode 100644 index 0000000..76286bd --- /dev/null +++ b/scripts/services/healthChecks.ts @@ -0,0 +1,5 @@ +import { types as T, healthUtil } from "../deps.ts"; + +export const health: T.ExpectedExports.health = { + "web-ui": healthUtil.checkWebUrl("http://jam.embassy") +} \ No newline at end of file diff --git a/scripts/services/migrations.ts b/scripts/services/migrations.ts new file mode 100644 index 0000000..bc63eb7 --- /dev/null +++ b/scripts/services/migrations.ts @@ -0,0 +1,4 @@ +import { compat, types as T } from "../deps.ts"; + +export const migration: T.ExpectedExports.migration = + compat.migrations.fromMapping({}, "0.2.0.2"); diff --git a/scripts/services/properties.ts b/scripts/services/properties.ts new file mode 100644 index 0000000..c11acba --- /dev/null +++ b/scripts/services/properties.ts @@ -0,0 +1,3 @@ +import { compat, types as T } from "../deps.ts"; + +export const properties: T.ExpectedExports.properties = compat.properties; \ No newline at end of file diff --git a/scripts/services/setConfig.ts b/scripts/services/setConfig.ts new file mode 100644 index 0000000..7c97f99 --- /dev/null +++ b/scripts/services/setConfig.ts @@ -0,0 +1,10 @@ +import { + compat, + types as T +} from "../deps.ts"; +export const setConfig: T.ExpectedExports.setConfig = async (effects, input ) => { + + const depsBitcoind: T.DependsOn = {bitcoind: ['synced']} + + return await compat.setConfig(effects,input, depsBitcoind) +} \ No newline at end of file