From a6ef1eaf7c4adac04f8f7d911466ec54980e90e7 Mon Sep 17 00:00:00 2001 From: evangineer <53523+evangineer@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:11:02 +0100 Subject: [PATCH 1/3] chore(deploy): remove obsolete scripts and spike MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - delete deploy/scripts/bootstrap-harvester.sh — superseded by `hdeploy bootstrap-harvester`; referenced old file-based joining config approach and JOINING_SERVICE_* env vars - delete deploy/spike/ — labeled spike, stale tofu state and provider binaries committed to history - update apply.sh next-step hint to reference hdeploy instead of the deleted shell script Co-Authored-By: Claude Sonnet 4.6 --- deploy/scripts/apply.sh | 2 +- deploy/scripts/bootstrap-harvester.sh | 172 --------------------- deploy/spike/.gitignore | 5 - deploy/spike/README.md | 82 ---------- deploy/spike/build.sh | 103 ------------ deploy/spike/tofu/main.tf | 132 ---------------- deploy/spike/tofu/outputs.tf | 19 --- deploy/spike/tofu/terraform.tfvars.example | 3 - deploy/spike/tofu/variables.tf | 15 -- 9 files changed, 1 insertion(+), 532 deletions(-) delete mode 100755 deploy/scripts/bootstrap-harvester.sh delete mode 100644 deploy/spike/.gitignore delete mode 100644 deploy/spike/README.md delete mode 100755 deploy/spike/build.sh delete mode 100644 deploy/spike/tofu/main.tf delete mode 100644 deploy/spike/tofu/outputs.tf delete mode 100644 deploy/spike/tofu/terraform.tfvars.example delete mode 100644 deploy/spike/tofu/variables.tf diff --git a/deploy/scripts/apply.sh b/deploy/scripts/apply.sh index 3d23b4a..1e2782c 100755 --- a/deploy/scripts/apply.sh +++ b/deploy/scripts/apply.sh @@ -121,5 +121,5 @@ else log "Deployment complete. Outputs:" tofu output log "" - log "Next: run deploy/scripts/bootstrap-harvester.sh to install the Unyt hApp on the harvester." + log "Next: run 'hdeploy bootstrap-harvester --deployment $DEPLOYMENT' to install the hApp on the harvester." fi diff --git a/deploy/scripts/bootstrap-harvester.sh b/deploy/scripts/bootstrap-harvester.sh deleted file mode 100755 index 7abbd73..0000000 --- a/deploy/scripts/bootstrap-harvester.sh +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env bash -# -# One-time harvester bootstrap: installs the Unyt hApp on the harvester -# conductor and registers the agent key with the joining service. -# -# Run this AFTER `tofu apply` has provisioned the harvester VM and the -# conductor container has had time to start (~1-2 minutes). -# -# Prerequisites: -# source deploy/.env.staging # or .env.production -# wrangler authenticated # wrangler whoami -# docker available in PATH -# jq available in PATH -# -# Required env vars (set via .env.*): -# SSH_PUBLIC_KEY # used by tofu — SSH key must be added to agent -# HARVESTER_NETWORK_SEED # network seed for the Unyt hApp cell -# JOINING_SERVICE_CONFIG_PATH # path to joining-config.json -# JOINING_SERVICE_DEPLOY_SCRIPT # path to the joining service deploy.sh -# -# Optional env vars: -# BOOTSTRAP_IMAGE # override bootstrap container image -# HARVESTER_ADMIN_PORT # override conductor admin port (default: 4444) -# SSH_USER # SSH user on harvester VM (default: root) -# -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -TOFU_DIR="$SCRIPT_DIR/../tofu" -BOOTSTRAP_IMAGE="${BOOTSTRAP_IMAGE:-ghcr.io/holo-host/edgenode-bootstrap:latest}" -HARVESTER_ADMIN_PORT="${HARVESTER_ADMIN_PORT:-4444}" -LOCAL_TUNNEL_PORT="${LOCAL_TUNNEL_PORT:-14444}" -SSH_USER="${SSH_USER:-root}" - -log() { echo -e "\033[0;32m[bootstrap]\033[0m $*"; } -warn() { echo -e "\033[0;33m[bootstrap]\033[0m $*"; } -err() { echo -e "\033[0;31m[bootstrap]\033[0m $*" >&2; } - -# ── Validate env ───────────────────────────────────────────────────────────── - -for var in HARVESTER_NETWORK_SEED JOINING_SERVICE_CONFIG_PATH JOINING_SERVICE_DEPLOY_SCRIPT; do - if [[ -z "${!var:-}" ]]; then - err "$var is not set. Did you source deploy/.env.-?" - exit 1 - fi -done - -if [[ ! -f "$JOINING_SERVICE_CONFIG_PATH" ]]; then - err "JOINING_SERVICE_CONFIG_PATH not found: $JOINING_SERVICE_CONFIG_PATH" - exit 1 -fi - -if [[ ! -f "$JOINING_SERVICE_DEPLOY_SCRIPT" ]]; then - err "JOINING_SERVICE_DEPLOY_SCRIPT not found: $JOINING_SERVICE_DEPLOY_SCRIPT" - exit 1 -fi - -# ── Get harvester IP from tofu output ──────────────────────────────────────── - -log "Reading harvester IP from tofu output..." -HARVESTER_IP=$(cd "$TOFU_DIR" && tofu output -raw harvester_ip) -if [[ -z "$HARVESTER_IP" ]]; then - err "Could not read harvester_ip from tofu output. Has tofu apply been run?" - exit 1 -fi -log "Harvester IP: $HARVESTER_IP" - -# ── SSH tunnel ──────────────────────────────────────────────────────────────── - -SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR" -TUNNEL_PID="" - -cleanup() { - if [[ -n "$TUNNEL_PID" ]] && kill -0 "$TUNNEL_PID" 2>/dev/null; then - log "Closing SSH tunnel (pid $TUNNEL_PID)..." - kill "$TUNNEL_PID" 2>/dev/null || true - wait "$TUNNEL_PID" 2>/dev/null || true - fi -} -trap cleanup EXIT - -log "Opening SSH tunnel (local:$LOCAL_TUNNEL_PORT -> remote:$HARVESTER_ADMIN_PORT)..." -# shellcheck disable=SC2086 -ssh $SSH_OPTS -N \ - -L "${LOCAL_TUNNEL_PORT}:localhost:${HARVESTER_ADMIN_PORT}" \ - "${SSH_USER}@${HARVESTER_IP}" & -TUNNEL_PID=$! - -for i in $(seq 1 10); do - nc -z localhost "$LOCAL_TUNNEL_PORT" 2>/dev/null && break - [[ "$i" -eq 10 ]] && { err "SSH tunnel failed to open within 10 seconds"; exit 1; } - sleep 1 -done -log "SSH tunnel ready (pid $TUNNEL_PID)" - -# ── Run bootstrap container ─────────────────────────────────────────────────── - -log "Running bootstrap container..." -INSTALL_RESULT=$(docker run --rm \ - --network host \ - -e HC_ADMIN_WS="ws://localhost:${LOCAL_TUNNEL_PORT}" \ - -e APP_ID="edgenode-harvester" \ - -e NETWORK_SEED="$HARVESTER_NETWORK_SEED" \ - "$BOOTSTRAP_IMAGE") - -AGENT_KEY=$(echo "$INSTALL_RESULT" | jq -r '.agent_key') -if [[ -z "$AGENT_KEY" || "$AGENT_KEY" == "null" ]]; then - err "Bootstrap container did not return an agent key" - err "Output: $INSTALL_RESULT" - exit 1 -fi -log "Agent key: $AGENT_KEY" - -# ── Update joining service config ───────────────────────────────────────────── - -log "Updating joining service config..." -TMP_CONFIG=$(mktemp) -jq --arg key "$AGENT_KEY" ' - .allowed_agents = ((.allowed_agents // []) + [$key] | unique) | - .auth_methods = ( - [.auth_methods[] | - if . == "agent_allow_list" then - empty - elif type == "string" then - { any_of: [., "agent_allow_list"] } - elif type == "object" and .any_of then - .any_of = (.any_of + ["agent_allow_list"] | unique) - else . - end - ] | - if length == 0 then ["agent_allow_list"] else . end - ) -' "$JOINING_SERVICE_CONFIG_PATH" > "$TMP_CONFIG" - -mv "$TMP_CONFIG" "$JOINING_SERVICE_CONFIG_PATH" -log "Updated: $(jq -r '.allowed_agents | length' "$JOINING_SERVICE_CONFIG_PATH") allowed agent(s)" - -log "Redeploying joining service..." -DEPLOY_ARGS=("deploy" "--config-file" "$JOINING_SERVICE_CONFIG_PATH") -if [[ -n "${JOINING_SERVICE_SIGNING_KEY_FILE:-}" && -f "$JOINING_SERVICE_SIGNING_KEY_FILE" ]]; then - DEPLOY_ARGS+=("--signing-key-file" "$JOINING_SERVICE_SIGNING_KEY_FILE") -fi -bash "$JOINING_SERVICE_DEPLOY_SCRIPT" "${DEPLOY_ARGS[@]}" - -log "Waiting 5s for Cloudflare propagation..." -sleep 5 - -# ── Save results ────────────────────────────────────────────────────────────── - -RESULTS_DIR="$SCRIPT_DIR/../results" -mkdir -p "$RESULTS_DIR" -RESULTS_FILE="$RESULTS_DIR/bootstrap-result.json" - -echo "$INSTALL_RESULT" | jq \ - --arg ip "$HARVESTER_IP" \ - --arg bootstrapped_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ - '. + { server_ip: $ip, bootstrapped_at: $bootstrapped_at }' \ - > "$RESULTS_FILE" - -HISTORY_DIR="$RESULTS_DIR/history" -mkdir -p "$HISTORY_DIR" -cp "$RESULTS_FILE" "$HISTORY_DIR/bootstrap-result-$(date +%Y%m%d-%H%M%S).json" - -log "" -log "=========================================" -log " Bootstrap Complete" -log "=========================================" -log "" -log "Harvester: ${SSH_USER}@${HARVESTER_IP}" -log "Agent Key: $AGENT_KEY" -log "Results: $RESULTS_FILE" -log "" diff --git a/deploy/spike/.gitignore b/deploy/spike/.gitignore deleted file mode 100644 index ce4ec0c..0000000 --- a/deploy/spike/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -dist/ -tofu/.terraform/ -tofu/.terraform.lock.hcl -tofu/terraform.tfstate* -tofu/terraform.tfvars diff --git a/deploy/spike/README.md b/deploy/spike/README.md deleted file mode 100644 index 695c8da..0000000 --- a/deploy/spike/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# Spike: cloudflare_worker_script Feasibility - -Tests whether the OpenTofu Cloudflare provider can deploy both Workers without -falling back to `wrangler deploy`. Three specific questions: - -1. Can `cloudflare_worker_script` handle a TypeScript Worker that requires a - wrangler/esbuild build step? (both Workers) -2. Can it handle the joining service Worker's cross-directory imports - (`../../../joining-service/src/...`)? -3. Do KV namespace bindings (joining service) and D1 database bindings - (log-collector) wire up correctly via the provider? - -## Prerequisites - -- Node.js 20+ -- OpenTofu installed (`tofu` in PATH) -- A Cloudflare account with API token (Workers, KV, D1, Pages: Edit) -- `esbuild` installed globally or via npx: `npm install -g esbuild` - -## Steps - -### 1. Build both Workers - -```bash -cd deploy/spike -./build.sh -``` - -Inspect `dist/` — both `joining.js` and `log-collector.js` should be present -and non-empty. If the joining service build fails, that answers question 2 -immediately (cross-directory imports not resolvable by esbuild alone — wrangler -would be required). - -### 2. Deploy via OpenTofu - -```bash -cd deploy/spike/tofu -cp terraform.tfvars.example terraform.tfvars -# edit terraform.tfvars with your Cloudflare credentials -tofu init -tofu apply -``` - -### 3. Verify - -After apply succeeds, check the Cloudflare dashboard: - -- [ ] `spike-joining` Worker exists and is deployed -- [ ] `spike-joining` has `SESSIONS` KV namespace bound -- [ ] `spike-log-collector` Worker exists and is deployed -- [ ] `spike-log-collector` has `DB` D1 database bound -- [ ] Hit `https://spike-joining..workers.dev/health` — expect a - response (even a 404 is fine; a 500 with "could not route" suggests - Worker deployed but config missing) -- [ ] Hit `https://spike-log-collector..workers.dev/health` — - same check - -### 4. Clean up - -```bash -tofu destroy -``` - -## What success looks like - -All three questions answered yes → use OpenTofu for Workers in the full -deployment, no wrangler step needed. - -Any question answered no → use `wrangler deploy` for Workers, OpenTofu for -Hetzner VMs, Cloudflare DNS, KV, and D1 only. - -## Known risks going in - -- The Cloudflare provider's `cloudflare_worker_script` expects pre-bundled JS. - esbuild handles this for straightforward Workers but wrangler adds its own - transforms. If the Worker uses wrangler-specific features (e.g. `__STATIC_CONTENT` - or service bindings), esbuild alone may not suffice. -- D1 binding support in the provider was added in v4.x. Verify the provider - version in `tofu/main.tf` matches what's available. -- The joining service imports from outside its own directory tree. esbuild - resolves this at bundle time, but path resolution depends on running the - build from the correct working directory (the repo root). diff --git a/deploy/spike/build.sh b/deploy/spike/build.sh deleted file mode 100755 index 6b3a786..0000000 --- a/deploy/spike/build.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env bash -# -# Build both Cloudflare Workers into deploy/spike/dist/ for the spike. -# Run from the repo root or from deploy/spike/. -# -# Both Workers are bundled with esbuild (the same bundler wrangler uses -# internally). Cross-directory imports in the joining service entry point -# are resolved at bundle time. -# -# If either build fails, note the error — it directly answers one of the -# spike's three questions. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PIONEER_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" -DIST_DIR="$SCRIPT_DIR/dist" - -# The joining service worker entry point lives in the mewsfeed repo and -# imports from the joining-service repo (a sibling of mewsfeed). -# Override these if your checkouts are elsewhere. -MEWSFEED_DIR="${MEWSFEED_DIR:-$(cd "$PIONEER_DIR/../src/mewsfeed" && pwd 2>/dev/null || echo "")}" -JOINING_SERVICE_DIR="${JOINING_SERVICE_DIR:-$(cd "$PIONEER_DIR/../src/joining-service" && pwd 2>/dev/null || echo "")}" - -if [ -z "$MEWSFEED_DIR" ] || [ ! -d "$MEWSFEED_DIR" ]; then - echo "ERROR: mewsfeed repo not found. Set MEWSFEED_DIR to its path." - echo " e.g. MEWSFEED_DIR=/path/to/mewsfeed ./build.sh" - exit 1 -fi - -if [ -z "$JOINING_SERVICE_DIR" ] || [ ! -d "$JOINING_SERVICE_DIR" ]; then - echo "ERROR: joining-service repo not found. Set JOINING_SERVICE_DIR to its path." - echo " e.g. JOINING_SERVICE_DIR=/path/to/joining-service ./build.sh" - exit 1 -fi - -# @holo-host/lair is a file: dependency with no pre-built dist/. -# Resolve it to holo-web-conductor/packages/lair and build if needed. -HOLO_WEB_CONDUCTOR_DIR="${HOLO_WEB_CONDUCTOR_DIR:-$(cd "$PIONEER_DIR/../src/holo-web-conductor" && pwd 2>/dev/null || echo "")}" -LAIR_PACKAGE_DIR="$HOLO_WEB_CONDUCTOR_DIR/packages/lair" - -if [ -z "$HOLO_WEB_CONDUCTOR_DIR" ] || [ ! -d "$LAIR_PACKAGE_DIR" ]; then - echo "ERROR: holo-web-conductor repo not found. Set HOLO_WEB_CONDUCTOR_DIR to its path." - echo " e.g. HOLO_WEB_CONDUCTOR_DIR=/path/to/holo-web-conductor ./build.sh" - exit 1 -fi - -if [ ! -d "$LAIR_PACKAGE_DIR/dist" ]; then - echo "Building @holo-host/lair (from $LAIR_PACKAGE_DIR)..." - (cd "$HOLO_WEB_CONDUCTOR_DIR" && npm install && node_modules/.bin/tsc --build packages/lair/tsconfig.json) -fi - -mkdir -p "$DIST_DIR" - -# ── Joining service Worker ────────────────────────────────────────────────── -# -# Entry point imports from ../joining-service/src/ relative to its own -# location. esbuild resolves these at bundle time. -# If this fails, cross-directory imports require wrangler rather than -# esbuild alone. - -echo "Building joining service Worker (from $MEWSFEED_DIR)..." -NODE_PATH="$JOINING_SERVICE_DIR/node_modules" npx esbuild \ - "$MEWSFEED_DIR/deploy/cloudflare/worker-entry.ts" \ - --bundle \ - --format=esm \ - --platform=browser \ - --outfile="$DIST_DIR/joining.js" \ - --external:__STATIC_CONTENT_MANIFEST \ - --external:"node:*" \ - --log-level=info - -echo " → $DIST_DIR/joining.js ($(wc -c < "$DIST_DIR/joining.js") bytes)" - -# ── Log-collector Worker ──────────────────────────────────────────────────── -# -# Standard TypeScript Worker with its own package.json and dependencies. -# Install deps first if node_modules is absent. - -LOG_COLLECTOR_DIR="$PIONEER_DIR/docker/log-collector" - -if [ ! -d "$LOG_COLLECTOR_DIR/node_modules" ]; then - echo "Installing log-collector dependencies..." - (cd "$LOG_COLLECTOR_DIR" && npm install) -fi - -echo "Building log-collector Worker..." -NODE_PATH="$LOG_COLLECTOR_DIR/node_modules" npx esbuild \ - "$LOG_COLLECTOR_DIR/src/index.ts" \ - --bundle \ - --format=esm \ - --platform=browser \ - --outfile="$DIST_DIR/log-collector.js" \ - --external:__STATIC_CONTENT_MANIFEST \ - --external:"node:*" \ - --external:buffer \ - --log-level=info - -echo " → $DIST_DIR/log-collector.js ($(wc -c < "$DIST_DIR/log-collector.js") bytes)" - -echo "" -echo "Build complete. Both bundles in $DIST_DIR/" -echo "Next: cd deploy/spike/tofu && tofu apply" diff --git a/deploy/spike/tofu/main.tf b/deploy/spike/tofu/main.tf deleted file mode 100644 index 1d3bbe7..0000000 --- a/deploy/spike/tofu/main.tf +++ /dev/null @@ -1,132 +0,0 @@ -terraform { - required_providers { - cloudflare = { - source = "cloudflare/cloudflare" - version = "~> 5.0" - } - } -} - -provider "cloudflare" { - api_token = var.cloudflare_api_token -} - -# ── KV namespace (joining service) ───────────────────────────────────────── - -resource "cloudflare_workers_kv_namespace" "sessions" { - account_id = var.cloudflare_account_id - title = "spike-sessions" -} - -# ── D1 database (log-collector) ──────────────────────────────────────────── - -resource "cloudflare_d1_database" "log_collector" { - account_id = var.cloudflare_account_id - name = "spike-log-collector-db" - read_replication = { mode = "disabled" } -} - -# ── Joining service Worker ───────────────────────────────────────────────── -# Tests: -# - Does the current cloudflare_worker + cloudflare_worker_version model -# accept an esbuild-bundled ES module via content_file? -# - Do KV namespace bindings wire up via the unified bindings array? - -resource "cloudflare_worker" "joining" { - account_id = var.cloudflare_account_id - name = "spike-joining" - subdomain = { enabled = true } -} - -resource "cloudflare_worker_version" "joining" { - account_id = var.cloudflare_account_id - worker_id = cloudflare_worker.joining.id - compatibility_date = "2024-12-01" - compatibility_flags = ["nodejs_compat"] - main_module = "joining.js" - - modules = [{ - name = "joining.js" - content_type = "application/javascript+module" - content_file = "${path.module}/../dist/joining.js" - }] - - bindings = [ - { - type = "kv_namespace" - name = "SESSIONS" - namespace_id = cloudflare_workers_kv_namespace.sessions.id - }, - { - type = "plain_text" - name = "CONFIG_JSON" - text = jsonencode({ - happ = { - id = "spike" - name = "Spike" - happ_bundle_url = "https://example.com/spike.happ" - } - auth_methods = ["invite_code"] - invite_codes = ["spike-test"] - session = { store = "cloudflare-kv" } - }) - } - ] -} - -resource "cloudflare_workers_deployment" "joining" { - account_id = var.cloudflare_account_id - script_name = cloudflare_worker.joining.name - strategy = "percentage" - versions = [{ - percentage = 100 - version_id = cloudflare_worker_version.joining.id - }] -} - -# ── Log-collector Worker ─────────────────────────────────────────────────── -# Tests: -# - Does content_file work for a larger bundle (1.2mb uncompressed)? -# - Do D1 database bindings wire up via the unified bindings array? - -resource "cloudflare_worker" "log_collector" { - account_id = var.cloudflare_account_id - name = "spike-log-collector" - subdomain = { enabled = true } -} - -resource "cloudflare_worker_version" "log_collector" { - account_id = var.cloudflare_account_id - worker_id = cloudflare_worker.log_collector.id - compatibility_date = "2024-10-01" - main_module = "log-collector.js" - - modules = [{ - name = "log-collector.js" - content_type = "application/javascript+module" - content_file = "${path.module}/../dist/log-collector.js" - }] - - bindings = [ - { - type = "d1" - name = "DB" - id = cloudflare_d1_database.log_collector.id - }, - { - type = "secret_text" - name = "ADMIN_SECRET" - text = "spike-admin-secret" - } - ] -} - -resource "cloudflare_workers_deployment" "log_collector" { - account_id = var.cloudflare_account_id - script_name = cloudflare_worker.log_collector.name - strategy = "percentage" - versions = [{ - percentage = 100 - version_id = cloudflare_worker_version.log_collector.id - }] -} diff --git a/deploy/spike/tofu/outputs.tf b/deploy/spike/tofu/outputs.tf deleted file mode 100644 index 2aff03e..0000000 --- a/deploy/spike/tofu/outputs.tf +++ /dev/null @@ -1,19 +0,0 @@ -output "joining_worker_url" { - description = "Joining service Worker URL" - value = "https://spike-joining.${var.cloudflare_workers_subdomain}.workers.dev" -} - -output "log_collector_worker_url" { - description = "Log-collector Worker URL" - value = "https://spike-log-collector.${var.cloudflare_workers_subdomain}.workers.dev" -} - -output "kv_namespace_id" { - description = "SESSIONS KV namespace ID" - value = cloudflare_workers_kv_namespace.sessions.id -} - -output "d1_database_id" { - description = "Log-collector D1 database ID" - value = cloudflare_d1_database.log_collector.id -} diff --git a/deploy/spike/tofu/terraform.tfvars.example b/deploy/spike/tofu/terraform.tfvars.example deleted file mode 100644 index 5c8df02..0000000 --- a/deploy/spike/tofu/terraform.tfvars.example +++ /dev/null @@ -1,3 +0,0 @@ -cloudflare_account_id = "" -cloudflare_api_token = "" -cloudflare_workers_subdomain = "" diff --git a/deploy/spike/tofu/variables.tf b/deploy/spike/tofu/variables.tf deleted file mode 100644 index 2337b62..0000000 --- a/deploy/spike/tofu/variables.tf +++ /dev/null @@ -1,15 +0,0 @@ -variable "cloudflare_account_id" { - description = "Cloudflare account ID" - type = string -} - -variable "cloudflare_api_token" { - description = "Cloudflare API token (Workers, KV, D1, Pages: Edit)" - type = string - sensitive = true -} - -variable "cloudflare_workers_subdomain" { - description = "Cloudflare Workers subdomain (e.g. 'myaccount' for myaccount.workers.dev)" - type = string -} From 6337024f6ad0527174b6bed01e8a6ed4919742cd Mon Sep 17 00:00:00 2001 From: evangineer <53523+evangineer@users.noreply.github.com> Date: Wed, 20 May 2026 17:32:15 +0100 Subject: [PATCH 2/3] docs(deploy): replace mewsfeed-config with generic joining-service-config example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename mewsfeed-config.json.example → joining-service-config.example.json - Update to JoiningServiceConfigInput schema (happ.id/name/happ_bundle_url, auth_methods) — the old file used an obsolete format - Show all auth method variants (open, invite_code, membrane_proof, any_of) as commented alternatives rather than baking in open-auth-only assumption - Document that network_seed and linker_registrations are auto-injected by hdeploy — operators must not set them in the config file - Update DEPLOYMENT.md deploy-joining-service steps to include --joining-config flag and explain network seed auto-injection Co-Authored-By: Claude Sonnet 4.6 --- deploy/DEPLOYMENT.md | 42 ++++++++++++++-------- deploy/joining-service-config.example.json | 41 +++++++++++++++++++++ deploy/mewsfeed-config.json.example | 12 ------- 3 files changed, 69 insertions(+), 26 deletions(-) create mode 100644 deploy/joining-service-config.example.json delete mode 100644 deploy/mewsfeed-config.json.example diff --git a/deploy/DEPLOYMENT.md b/deploy/DEPLOYMENT.md index fa87a62..a7c9bb3 100644 --- a/deploy/DEPLOYMENT.md +++ b/deploy/DEPLOYMENT.md @@ -141,16 +141,30 @@ existing seed. ## Deploy the Joining Service +Copy and fill in `deploy/joining-service-config.example.json` for your hApp: + ```bash -source deploy/.env.acme-staging -hdeploy deploy-joining-service --deployment acme-staging \ - --tofu-dir deploy/tofu \ - --joining-service-dir ../joining-service +cp deploy/joining-service-config.example.json deploy/acme-mewsfeed-joining-config.json +$EDITOR deploy/acme-mewsfeed-joining-config.json ``` -This deploys the joining service Worker via wrangler and writes -`linker_registrations` to the sessions KV namespace. Safe to re-run after -infrastructure changes that affect linker URLs. +The config needs at minimum: `happ.id`, `happ.name`, `happ.happ_bundle_url`, and +`auth_methods`. See `joining-service-config.example.json` for membrane proof and +invite code variants. + +`network_seed` and `linker_registrations` are injected automatically — do not set +them in the config file. + +```bash +source deploy/.env.acme-mewsfeed-staging +hdeploy deploy-joining-service -d acme-mewsfeed-staging \ + --joining-service-dir ../joining-service \ + --joining-config deploy/acme-mewsfeed-joining-config.json +``` + +This deploys the joining service Worker via wrangler and writes `joining_config` +(including the network seed from deployment KV and linker URLs from tofu outputs) +to the sessions KV namespace. Safe to re-run after infrastructure changes. --- @@ -220,10 +234,10 @@ harvester_image = "ghcr.io/holo-host/edgenode-harvester:v1.2.3" ### Joining service update ```bash -source deploy/.env.acme-staging -hdeploy deploy-joining-service --deployment acme-staging \ - --tofu-dir deploy/tofu \ - --joining-service-dir ../joining-service +source deploy/.env.acme-mewsfeed-staging +hdeploy deploy-joining-service -d acme-mewsfeed-staging \ + --joining-service-dir ../joining-service \ + --joining-config deploy/acme-mewsfeed-joining-config.json ``` ### Staging → production @@ -239,9 +253,9 @@ hdeploy provision --deployment acme-prod \ --tofu-dir deploy/tofu \ --log-collector-src docker/log-collector hdeploy init-deployment --deployment acme-prod --tofu-dir deploy/tofu -hdeploy deploy-joining-service --deployment acme-prod \ - --tofu-dir deploy/tofu \ - --joining-service-dir ../joining-service +hdeploy deploy-joining-service -d acme-mewsfeed-prod \ + --joining-service-dir ../joining-service \ + --joining-config deploy/acme-mewsfeed-joining-config.json hdeploy bootstrap-harvester --deployment acme-prod \ --tofu-dir deploy/tofu \ --bootstrap-image ghcr.io/holo-host/bootstrap:latest diff --git a/deploy/joining-service-config.example.json b/deploy/joining-service-config.example.json new file mode 100644 index 0000000..5ac4c09 --- /dev/null +++ b/deploy/joining-service-config.example.json @@ -0,0 +1,41 @@ +{ + "_comment": "Joining service config passed to: hdeploy deploy-joining-service -d --joining-config . Copy, rename, and fill in values for your hApp. network_seed and linker_registrations are injected automatically by hdeploy — do not set them here.", + + "happ": { + "id": "mewsfeed", + "name": "Mewsfeed", + "happ_bundle_url": "https://github.com/GeekGene/mewsfeed/releases/download/v0.14.0/mewsfeed.webhapp", + "icon_url": "" + }, + + "auth_methods": ["open"], + + "_auth_method_variants": { + "_comment": "Replace auth_methods above with one of these for non-open deployments.", + "invite_code_only": ["invite_code"], + "membrane_proof_only": ["membrane_proof"], + "invite_code_or_membrane_proof": [{ "any_of": ["invite_code", "membrane_proof"] }] + }, + + "_membrane_proof": { + "_comment": "Required when auth_methods includes 'membrane_proof'. The signing key is generated once via: cd ../joining-service && npm run gen-signing-key. Its derived public key must be baked into the DNA properties as the progenitor before the hApp bundle is compiled — see ../joining-service/DEPLOYMENT.md.", + "enabled": true, + "signing_key_path": "/path/to/signing-key.hex" + }, + + "_dna_hashes": { + "_comment": "Required when membrane_proof.enabled is true. Base64-encoded DNA hashes the signing key is authorised to sign membrane proofs for.", + "example": ["uhCkk..."] + }, + + "_invite_codes": { + "_comment": "Invite codes are NOT stored here. They are stored in deployment KV via the INVITE_CODES env var and read by the joining service Worker at runtime." + }, + + "_network": { + "_comment": "Optional. Exposes bootstrap and relay URLs in /v1/info. Off by default — enabling increases DDoS surface area for the listed services.", + "bootstrap_url": "", + "relay_url": "", + "reveal_in_info": false + } +} diff --git a/deploy/mewsfeed-config.json.example b/deploy/mewsfeed-config.json.example deleted file mode 100644 index 5f42e41..0000000 --- a/deploy/mewsfeed-config.json.example +++ /dev/null @@ -1,12 +0,0 @@ -{ - "_comment": "Reference hApp configuration for a Mewsfeed edgenode deployment. Copy and adapt for your own hApp.", - "happ_id": "mewsfeed", - "happ_name": "Mewsfeed", - "happ_bundle_url": "https://github.com/holochain-apps/mewsfeed/releases/download/v0.15.1/mewsfeed.happ", - "network_seed": "REPLACE_WITH_NETWORK_SEED", - "joining_service": { - "auth_methods": ["invite_code"], - "invite_codes": ["REPLACE_WITH_INVITE_CODES"], - "session": { "store": "cloudflare-kv" } - } -} From 8bf9d4a691307b3d7a6f6e12742f000a0219bba5 Mon Sep 17 00:00:00 2001 From: evangineer <53523+evangineer@users.noreply.github.com> Date: Thu, 21 May 2026 13:34:48 +0100 Subject: [PATCH 3/3] fix(deploy): remove empty icon_url from joining-service-config example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit z.string().url() rejects empty strings — omit the field since it's optional. Co-Authored-By: Claude Sonnet 4.6 --- deploy/joining-service-config.example.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deploy/joining-service-config.example.json b/deploy/joining-service-config.example.json index 5ac4c09..b7fc58d 100644 --- a/deploy/joining-service-config.example.json +++ b/deploy/joining-service-config.example.json @@ -4,8 +4,7 @@ "happ": { "id": "mewsfeed", "name": "Mewsfeed", - "happ_bundle_url": "https://github.com/GeekGene/mewsfeed/releases/download/v0.14.0/mewsfeed.webhapp", - "icon_url": "" + "happ_bundle_url": "https://github.com/GeekGene/mewsfeed/releases/download/v0.14.0/mewsfeed.webhapp" }, "auth_methods": ["open"],