From 1dbe1db576131297c835bc4a31673eba3de43fa0 Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 28 Jan 2026 00:57:46 -0400 Subject: [PATCH] feat: add plugins support --- docker_image/entrypoint.sh | 11 +- docker_image/plugins/Dockerfile.csharp | 81 +++++++++++ docker_image/plugins/Dockerfile.go | 79 +++++++++++ docker_image/plugins/Dockerfile.kotlin | 80 +++++++++++ docker_image/plugins/Dockerfile.python | 80 +++++++++++ docker_image/plugins/Dockerfile.typescript | 80 +++++++++++ docker_image/plugins/README.md | 148 +++++++++++++++++++++ 7 files changed, 553 insertions(+), 6 deletions(-) create mode 100644 docker_image/plugins/Dockerfile.csharp create mode 100644 docker_image/plugins/Dockerfile.go create mode 100644 docker_image/plugins/Dockerfile.kotlin create mode 100644 docker_image/plugins/Dockerfile.python create mode 100644 docker_image/plugins/Dockerfile.typescript create mode 100644 docker_image/plugins/README.md diff --git a/docker_image/entrypoint.sh b/docker_image/entrypoint.sh index 710de72..be8071d 100755 --- a/docker_image/entrypoint.sh +++ b/docker_image/entrypoint.sh @@ -1,12 +1,11 @@ #!/bin/sh # if BIN_PATH its defined we make a link to it from its volume to our system -if [[ -z "${BIN_PATH}" ]]; then - echo "using existing BIN_PATH $BIN_PATH" -# if BIN_PATH it's no provided we sue '/bin/cli' -else +if [ -z "${BIN_PATH}" ]; then echo "BIN_PATH not provided using /bin/cli as default" export BIN_PATH="/bin/cli" +else + echo "using existing BIN_PATH $BIN_PATH" fi # Persisting current version @@ -15,8 +14,8 @@ if [ -f "/root/.canopy/cli" ]; then echo "Found existing persistent cli version" else echo "Persisting build version for current cli" - mv $BIN_PATH /root/.canopy/cli + cp $BIN_PATH /root/.canopy/cli fi -ln -s /root/.canopy/cli $BIN_PATH +ln -sf /root/.canopy/cli $BIN_PATH exec /app/canopy "$@" diff --git a/docker_image/plugins/Dockerfile.csharp b/docker_image/plugins/Dockerfile.csharp new file mode 100644 index 0000000..8982f8b --- /dev/null +++ b/docker_image/plugins/Dockerfile.csharp @@ -0,0 +1,81 @@ +FROM node:18-alpine AS builder + +ARG BRANCH='latest' +ARG BIN_PATH=/bin/cli + +# Install build dependencies +RUN apk add --no-cache git ca-certificates alpine-sdk + +WORKDIR /go/src/github.com/canopy-network/canopy + +# Clone repository +RUN echo "Building from BRANCH=${BRANCH}" && \ + if [ "$BRANCH" = "latest" ]; then \ + echo "Fetching latest tag..."; \ + git clone https://github.com/canopy-network/canopy.git . && \ + LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`) && \ + echo "Checking out tag $LATEST_TAG" && \ + git checkout $LATEST_TAG; \ + else \ + echo "Cloning branch $BRANCH" && \ + git clone -b "$BRANCH" https://github.com/canopy-network/canopy.git .; \ + fi + +# Copy golang +COPY --from=golang:1.24-alpine /usr/local/go/ /usr/local/go/ +ENV PATH="/usr/local/go/bin:${PATH}" + +RUN go version + +# Install build tools +RUN apk update && apk add --no-cache make bash nodejs npm + +# Build wallet and explorer +RUN make build/wallet +RUN make build/explorer + +# Build auto-update coordinator +RUN CGO_ENABLED=0 GOOS=linux go build -a -o bin ./cmd/auto-update/. + +# Build CLI +RUN CGO_ENABLED=0 GOOS=linux go build -a -o "${BIN_PATH}" ./cmd/main/... + +# ============================================================================= +# Final image for C# plugin +# ============================================================================= +# Using Alpine base since C# plugin is now self-contained (includes .NET runtime) +FROM alpine:3.19 +WORKDIR /app + +# Install runtime dependencies +# - bash: required for pluginctl.sh scripts +# - procps: provides pkill for plugin process cleanup +# - ca-certificates: for HTTPS requests to GitHub API +# - pigz: for fast tarball extraction +# - libstdc++, libgcc, icu-libs: required by .NET self-contained apps on Alpine +RUN apk add --no-cache bash procps ca-certificates pigz libstdc++ libgcc icu-libs + +# Copy auto-update coordinator binary +COPY --from=builder /go/src/github.com/canopy-network/canopy/bin ./canopy + +# Copy CLI binary +COPY --from=builder /bin/cli /bin/cli + +# Create plugin directory and copy only pluginctl.sh +# Plugin DLL will be downloaded from upstream release and extracted on first start +RUN mkdir -p /app/plugin/csharp/bin +COPY --from=builder /go/src/github.com/canopy-network/canopy/plugin/csharp/pluginctl.sh /app/plugin/csharp/pluginctl.sh + +# Copy entrypoint +COPY entrypoint.sh /app/entrypoint.sh + +# Set permissions +RUN chmod +x /bin/cli && \ + chmod +x /app/canopy && \ + chmod +x /app/entrypoint.sh && \ + chmod +x /app/plugin/csharp/pluginctl.sh + +# Create plugin temp directory +RUN mkdir -p /tmp/plugin + +ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/docker_image/plugins/Dockerfile.go b/docker_image/plugins/Dockerfile.go new file mode 100644 index 0000000..f285569 --- /dev/null +++ b/docker_image/plugins/Dockerfile.go @@ -0,0 +1,79 @@ +FROM node:18-alpine AS builder + +ARG BRANCH='latest' +ARG BIN_PATH=/bin/cli + +# Install build dependencies +RUN apk add --no-cache git ca-certificates alpine-sdk + +WORKDIR /go/src/github.com/canopy-network/canopy + +# Clone repository +RUN echo "Building from BRANCH=${BRANCH}" && \ + if [ "$BRANCH" = "latest" ]; then \ + echo "Fetching latest tag..."; \ + git clone https://github.com/canopy-network/canopy.git . && \ + LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`) && \ + echo "Checking out tag $LATEST_TAG" && \ + git checkout $LATEST_TAG; \ + else \ + echo "Cloning branch $BRANCH" && \ + git clone -b "$BRANCH" https://github.com/canopy-network/canopy.git .; \ + fi + +# Copy golang +COPY --from=golang:1.24-alpine /usr/local/go/ /usr/local/go/ +ENV PATH="/usr/local/go/bin:${PATH}" + +RUN go version + +# Install build tools +RUN apk update && apk add --no-cache make bash nodejs npm + +# Build wallet and explorer +RUN make build/wallet +RUN make build/explorer + +# Build auto-update coordinator +RUN CGO_ENABLED=0 GOOS=linux go build -a -o bin ./cmd/auto-update/. + +# Build CLI +RUN CGO_ENABLED=0 GOOS=linux go build -a -o "${BIN_PATH}" ./cmd/main/... + +# ============================================================================= +# Final image for Go plugin +# ============================================================================= +FROM alpine:3.19 +WORKDIR /app + +# Install runtime dependencies +# - bash: required for pluginctl.sh scripts +# - procps: provides pkill for plugin process cleanup +# - ca-certificates: for HTTPS requests to GitHub API +# - pigz: for fast tarball extraction +RUN apk add --no-cache bash procps ca-certificates pigz + +# Copy auto-update coordinator binary +COPY --from=builder /go/src/github.com/canopy-network/canopy/bin ./canopy + +# Copy CLI binary +COPY --from=builder /bin/cli /bin/cli + +# Create plugin directory and copy only pluginctl.sh +# Plugin binary will be downloaded from upstream release and extracted on first start +RUN mkdir -p /app/plugin/go +COPY --from=builder /go/src/github.com/canopy-network/canopy/plugin/go/pluginctl.sh /app/plugin/go/pluginctl.sh + +# Copy entrypoint +COPY entrypoint.sh /app/entrypoint.sh + +# Set permissions +RUN chmod +x /bin/cli && \ + chmod +x /app/canopy && \ + chmod +x /app/entrypoint.sh && \ + chmod +x /app/plugin/go/pluginctl.sh + +# Create plugin temp directory +RUN mkdir -p /tmp/plugin + +ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/docker_image/plugins/Dockerfile.kotlin b/docker_image/plugins/Dockerfile.kotlin new file mode 100644 index 0000000..f83aa96 --- /dev/null +++ b/docker_image/plugins/Dockerfile.kotlin @@ -0,0 +1,80 @@ +FROM node:18-alpine AS builder + +ARG BRANCH='latest' +ARG BIN_PATH=/bin/cli + +# Install build dependencies +RUN apk add --no-cache git ca-certificates alpine-sdk + +WORKDIR /go/src/github.com/canopy-network/canopy + +# Clone repository +RUN echo "Building from BRANCH=${BRANCH}" && \ + if [ "$BRANCH" = "latest" ]; then \ + echo "Fetching latest tag..."; \ + git clone https://github.com/canopy-network/canopy.git . && \ + LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`) && \ + echo "Checking out tag $LATEST_TAG" && \ + git checkout $LATEST_TAG; \ + else \ + echo "Cloning branch $BRANCH" && \ + git clone -b "$BRANCH" https://github.com/canopy-network/canopy.git .; \ + fi + +# Copy golang +COPY --from=golang:1.24-alpine /usr/local/go/ /usr/local/go/ +ENV PATH="/usr/local/go/bin:${PATH}" + +RUN go version + +# Install build tools +RUN apk update && apk add --no-cache make bash nodejs npm + +# Build wallet and explorer +RUN make build/wallet +RUN make build/explorer + +# Build auto-update coordinator +RUN CGO_ENABLED=0 GOOS=linux go build -a -o bin ./cmd/auto-update/. + +# Build CLI +RUN CGO_ENABLED=0 GOOS=linux go build -a -o "${BIN_PATH}" ./cmd/main/... + +# ============================================================================= +# Final image for Kotlin plugin +# ============================================================================= +FROM alpine:3.19 +WORKDIR /app + +# Install runtime dependencies +# - bash: required for pluginctl.sh scripts +# - openjdk21-jre: Java runtime for Kotlin plugin +# - procps: provides pkill for plugin process cleanup +# - ca-certificates: for HTTPS requests to GitHub API +# - pigz: for fast tarball extraction +RUN apk add --no-cache bash openjdk21-jre procps ca-certificates pigz + +# Copy auto-update coordinator binary +COPY --from=builder /go/src/github.com/canopy-network/canopy/bin ./canopy + +# Copy CLI binary +COPY --from=builder /bin/cli /bin/cli + +# Create plugin directory and copy only pluginctl.sh +# Plugin JAR will be downloaded from upstream release and extracted on first start +RUN mkdir -p /app/plugin/kotlin/build/libs +COPY --from=builder /go/src/github.com/canopy-network/canopy/plugin/kotlin/pluginctl.sh /app/plugin/kotlin/pluginctl.sh + +# Copy entrypoint +COPY entrypoint.sh /app/entrypoint.sh + +# Set permissions +RUN chmod +x /bin/cli && \ + chmod +x /app/canopy && \ + chmod +x /app/entrypoint.sh && \ + chmod +x /app/plugin/kotlin/pluginctl.sh + +# Create plugin temp directory +RUN mkdir -p /tmp/plugin + +ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/docker_image/plugins/Dockerfile.python b/docker_image/plugins/Dockerfile.python new file mode 100644 index 0000000..47bb78c --- /dev/null +++ b/docker_image/plugins/Dockerfile.python @@ -0,0 +1,80 @@ +FROM node:18-alpine AS builder + +ARG BRANCH='latest' +ARG BIN_PATH=/bin/cli + +# Install build dependencies +RUN apk add --no-cache git ca-certificates alpine-sdk + +WORKDIR /go/src/github.com/canopy-network/canopy + +# Clone repository +RUN echo "Building from BRANCH=${BRANCH}" && \ + if [ "$BRANCH" = "latest" ]; then \ + echo "Fetching latest tag..."; \ + git clone https://github.com/canopy-network/canopy.git . && \ + LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`) && \ + echo "Checking out tag $LATEST_TAG" && \ + git checkout $LATEST_TAG; \ + else \ + echo "Cloning branch $BRANCH" && \ + git clone -b "$BRANCH" https://github.com/canopy-network/canopy.git .; \ + fi + +# Copy golang +COPY --from=golang:1.24-alpine /usr/local/go/ /usr/local/go/ +ENV PATH="/usr/local/go/bin:${PATH}" + +RUN go version + +# Install build tools +RUN apk update && apk add --no-cache make bash nodejs npm + +# Build wallet and explorer +RUN make build/wallet +RUN make build/explorer + +# Build auto-update coordinator +RUN CGO_ENABLED=0 GOOS=linux go build -a -o bin ./cmd/auto-update/. + +# Build CLI +RUN CGO_ENABLED=0 GOOS=linux go build -a -o "${BIN_PATH}" ./cmd/main/... + +# ============================================================================= +# Final image for Python plugin +# ============================================================================= +FROM alpine:3.19 +WORKDIR /app + +# Install runtime dependencies +# - bash: required for pluginctl.sh scripts +# - python3, py3-pip, py3-setuptools: Python runtime for plugin and venv creation +# - procps: provides pkill for plugin process cleanup +# - ca-certificates: for HTTPS requests to GitHub API +# - pigz: for fast tarball extraction +RUN apk add --no-cache bash python3 py3-pip py3-setuptools procps ca-certificates pigz + +# Copy auto-update coordinator binary +COPY --from=builder /go/src/github.com/canopy-network/canopy/bin ./canopy + +# Copy CLI binary +COPY --from=builder /bin/cli /bin/cli + +# Create plugin directory and copy only pluginctl.sh +# Plugin source will be downloaded from upstream release and extracted on first start +RUN mkdir -p /app/plugin/python +COPY --from=builder /go/src/github.com/canopy-network/canopy/plugin/python/pluginctl.sh /app/plugin/python/pluginctl.sh + +# Copy entrypoint +COPY entrypoint.sh /app/entrypoint.sh + +# Set permissions +RUN chmod +x /bin/cli && \ + chmod +x /app/canopy && \ + chmod +x /app/entrypoint.sh && \ + chmod +x /app/plugin/python/pluginctl.sh + +# Create plugin temp directory +RUN mkdir -p /tmp/plugin + +ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/docker_image/plugins/Dockerfile.typescript b/docker_image/plugins/Dockerfile.typescript new file mode 100644 index 0000000..2595dea --- /dev/null +++ b/docker_image/plugins/Dockerfile.typescript @@ -0,0 +1,80 @@ +FROM node:18-alpine AS builder + +ARG BRANCH='latest' +ARG BIN_PATH=/bin/cli + +# Install build dependencies +RUN apk add --no-cache git ca-certificates alpine-sdk + +WORKDIR /go/src/github.com/canopy-network/canopy + +# Clone repository +RUN echo "Building from BRANCH=${BRANCH}" && \ + if [ "$BRANCH" = "latest" ]; then \ + echo "Fetching latest tag..."; \ + git clone https://github.com/canopy-network/canopy.git . && \ + LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`) && \ + echo "Checking out tag $LATEST_TAG" && \ + git checkout $LATEST_TAG; \ + else \ + echo "Cloning branch $BRANCH" && \ + git clone -b "$BRANCH" https://github.com/canopy-network/canopy.git .; \ + fi + +# Copy golang +COPY --from=golang:1.24-alpine /usr/local/go/ /usr/local/go/ +ENV PATH="/usr/local/go/bin:${PATH}" + +RUN go version + +# Install build tools +RUN apk update && apk add --no-cache make bash nodejs npm + +# Build wallet and explorer +RUN make build/wallet +RUN make build/explorer + +# Build auto-update coordinator +RUN CGO_ENABLED=0 GOOS=linux go build -a -o bin ./cmd/auto-update/. + +# Build CLI +RUN CGO_ENABLED=0 GOOS=linux go build -a -o "${BIN_PATH}" ./cmd/main/... + +# ============================================================================= +# Final image for TypeScript plugin +# ============================================================================= +FROM node:18-alpine +WORKDIR /app + +# Install runtime dependencies +# - bash: required for pluginctl.sh scripts +# - procps: provides pkill for plugin process cleanup +# - ca-certificates: for HTTPS requests to GitHub API +# - pigz: for fast tarball extraction +RUN apk add --no-cache bash procps ca-certificates pigz + +# Copy auto-update coordinator binary +COPY --from=builder /go/src/github.com/canopy-network/canopy/bin ./canopy + +# Copy CLI binary +COPY --from=builder /bin/cli /bin/cli + +# Create plugin directory and copy only pluginctl.sh +# Plugin code will be downloaded from upstream release and extracted on first start +# The upstream tarball includes dist/ and node_modules/ +RUN mkdir -p /app/plugin/typescript +COPY --from=builder /go/src/github.com/canopy-network/canopy/plugin/typescript/pluginctl.sh /app/plugin/typescript/pluginctl.sh + +# Copy entrypoint +COPY entrypoint.sh /app/entrypoint.sh + +# Set permissions +RUN chmod +x /bin/cli && \ + chmod +x /app/canopy && \ + chmod +x /app/entrypoint.sh && \ + chmod +x /app/plugin/typescript/pluginctl.sh + +# Create plugin temp directory +RUN mkdir -p /tmp/plugin + +ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/docker_image/plugins/README.md b/docker_image/plugins/README.md new file mode 100644 index 0000000..9669c44 --- /dev/null +++ b/docker_image/plugins/README.md @@ -0,0 +1,148 @@ +# Plugin Dockerfiles for Canopy Auto-Update + +This directory contains Dockerfiles for running Canopy nodes with different plugin implementations. Each Dockerfile is optimized for a specific plugin runtime and supports automatic plugin updates from GitHub releases. + +## Available Dockerfiles + +| Dockerfile | Plugin | Runtime | Description | +|------------|--------|---------|-------------| +| `Dockerfile.go` | Go | Native binary | Compiled Go plugin | +| `Dockerfile.python` | Python | Python 3.x + venv | Python plugin with auto-venv setup | +| `Dockerfile.typescript` | TypeScript | Node.js 18 | TypeScript/JavaScript plugin | +| `Dockerfile.kotlin` | Kotlin | OpenJDK 21 | Kotlin/JVM plugin | +| `Dockerfile.csharp` | C# | .NET 8.0 | C#/.NET plugin | + +## Prerequisites + +For the auto-update system to work with plugins, you must configure the following in your Canopy config file: + +### Required Configuration + +```json +{ + "auto_update": { + "enabled": true, + "plugin": { + "enabled": true, + "owner": "your-github-org", + "repo": "your-plugin-repo", + "asset_name": "plugin-name.tar.gz" + } + } +} +``` + +### Configuration Fields + +| Field | Description | Example | +|-------|-------------|---------| +| `auto_update.enabled` | Enable auto-update for CLI | `true` | +| `auto_update.plugin.enabled` | **Required: Enable plugin auto-update** | `true` | +| `auto_update.plugin.owner` | GitHub repository owner | `"canopy-network"` | +| `auto_update.plugin.repo` | GitHub repository name | `"canopy"` | +| `auto_update.plugin.asset_name` | Release asset filename | See table below | + +### Asset Names by Plugin Type + +| Plugin | Asset Name (x64) | Asset Name (ARM64) | +|--------|------------------|-------------------| +| Go | `go-plugin-linux-amd64.tar.gz` | `go-plugin-linux-arm64.tar.gz` | +| Python | `python-plugin.tar.gz` | `python-plugin.tar.gz` | +| TypeScript | `typescript-plugin.tar.gz` | `typescript-plugin.tar.gz` | +| Kotlin | `kotlin-plugin.tar.gz` | `kotlin-plugin.tar.gz` | +| C# | `csharp-plugin-linux-x64.tar.gz` | `csharp-plugin-linux-arm64.tar.gz` | + +## Usage + +### 1. Update docker-compose.yaml + +Point to the Dockerfile for your desired plugin: + +```yaml +services: + node1: + build: + context: ../docker_image + dockerfile: ./plugins/Dockerfile.python # Change to your plugin + args: + BRANCH: main # or 'latest' for latest tag +``` + +### 2. Set Environment Variables + +Create a `.env` file with required configuration: + +```bash +# Plugin configuration +PLUGIN_TYPE=python +PLUGIN_OWNER=your-org +PLUGIN_REPO=your-repo +PLUGIN_ASSET=python-plugin.tar.gz +``` + +### 3. Build and Run + +```bash +docker-compose build --no-cache node1 +docker-compose up node1 +``` + +## How Auto-Update Works + +1. **On startup**: The Dockerfile only includes `pluginctl.sh` (no plugin code) +2. **First run**: Auto-updater downloads the latest plugin release from GitHub +3. **Extraction**: `pluginctl.sh` extracts the tarball and sets up dependencies +4. **Runtime setup**: Language-specific setup runs (e.g., Python creates venv) +5. **Periodic checks**: Auto-updater checks for new releases every 30 minutes (configurable) +6. **Updates**: When a new version is found, it downloads and restarts the plugin + +## Plugin-Specific Notes + +### Python +- Virtual environment (`.venv`) is created automatically on first start +- Dependencies are installed from `pyproject.toml` +- Dependencies are reinstalled after each update + +### TypeScript +- `node_modules` is included in the release tarball +- `package.json` is required for ES module support + +### Kotlin +- Requires JRE 21 or later +- Uses fat JAR with all dependencies included + +### C# +- Requires .NET 8.0 runtime +- Uses framework-dependent deployment (not self-contained) + +### Go +- Native binary, no runtime dependencies +- Architecture-specific builds (amd64/arm64) + +## Troubleshooting + +### Check plugin logs + +```bash +docker-compose exec node1 cat /tmp/plugin/-plugin.log +``` + +Replace `` with: `go`, `python`, `typescript`, `kotlin`, or `csharp` + +### Check if plugin is running + +```bash +docker-compose exec node1 /app/plugin//pluginctl.sh status +``` + +### Force plugin restart + +```bash +docker-compose exec node1 /app/plugin//pluginctl.sh restart +``` + +### Check extracted files + +```bash +docker-compose exec node1 ls -la /app/plugin// +```