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 0000000..acb16de Binary files /dev/null and b/icon.png differ 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