Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
Some checks failed
Test / test (push) Has been cancelled

This commit is contained in:
drjones
2026-07-04 09:31:23 +00:00
commit 3678b199d0
154 changed files with 21714 additions and 0 deletions

109
scripts/LAUNCH.sh Executable file
View File

@@ -0,0 +1,109 @@
#!/usr/bin/env bash
# Portable AetherForge / forge-mesh command deck launcher (USB edition).
# Starts optional cloudflared sidecar, sets AF_TUNNEL_EXTERNAL=1, opens the deck UI,
# and runs forge-mesh-server against ./data.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ "$(basename "$SCRIPT_DIR")" == "scripts" ]]; then
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
else
ROOT="$SCRIPT_DIR"
fi
cd "$ROOT"
DATA_DIR="${AF_DATA_DIR:-$ROOT/data}"
CONFIG="${AF_CONFIG:-$DATA_DIR/config.json}"
SERVER_BIN="${AF_SERVER_BIN:-$ROOT/bin/forge-mesh-server}"
DECK_URL="${AF_DECK_URL:-http://localhost:8989}"
CLOUDFLARED_PID=""
cleanup() {
if [[ -n "$CLOUDFLARED_PID" ]] && kill -0 "$CLOUDFLARED_PID" 2>/dev/null; then
kill "$CLOUDFLARED_PID" 2>/dev/null || true
wait "$CLOUDFLARED_PID" 2>/dev/null || true
fi
}
trap cleanup EXIT INT TERM
resolve_cloudflared() {
if command -v cloudflared >/dev/null 2>&1; then
command -v cloudflared
return 0
fi
for candidate in "$ROOT/bin/cloudflared" "$ROOT/cloudflared"; do
if [[ -x "$candidate" ]]; then
echo "$candidate"
return 0
fi
done
return 1
}
read_tunnel_token() {
if [[ -n "${AF_TUNNEL_TOKEN:-}" ]]; then
printf '%s' "$AF_TUNNEL_TOKEN"
return 0
fi
local token_file="$DATA_DIR/cloudflared-token.txt"
if [[ -f "$token_file" ]]; then
tr -d '[:space:]' <"$token_file"
return 0
fi
return 1
}
start_tunnel_sidecar() {
local token cf_bin
token="$(read_tunnel_token)" || return 0
cf_bin="$(resolve_cloudflared)" || {
echo "WARN: tunnel token present but cloudflared not found (PATH or $ROOT/bin/cloudflared)" >&2
return 0
}
echo "Starting cloudflared sidecar..."
"$cf_bin" tunnel --no-autoupdate run --token "$token" &
CLOUDFLARED_PID=$!
export AF_TUNNEL_EXTERNAL=1
echo "AF_TUNNEL_EXTERNAL=1 (cloudflared pid $CLOUDFLARED_PID)"
}
open_browser() {
local url="$1"
if [[ -n "${AF_NO_BROWSER:-}" ]]; then
echo "AF_NO_BROWSER set; deck at $url"
return 0
fi
for opener in xdg-open sensible-browser x-www-browser gnome-open kde-open; do
if command -v "$opener" >/dev/null 2>&1; then
"$opener" "$url" >/dev/null 2>&1 &
return 0
fi
done
echo "Open your browser to $url"
}
ensure_server_binary() {
if [[ -x "$SERVER_BIN" ]]; then
return 0
fi
if command -v forge-mesh-server >/dev/null 2>&1; then
SERVER_BIN="$(command -v forge-mesh-server)"
return 0
fi
echo "ERROR: forge-mesh-server not found. Build with 'make server' or place binary in $ROOT/bin/" >&2
exit 1
}
mkdir -p "$DATA_DIR"
if [[ ! -f "$CONFIG" ]]; then
echo "ERROR: missing config at $CONFIG" >&2
exit 1
fi
start_tunnel_sidecar
ensure_server_binary
open_browser "$DECK_URL"
echo "Starting forge-mesh-server (config: $CONFIG)"
exec "$SERVER_BIN" -config "$CONFIG"

112
scripts/README-USB.md Normal file
View File

@@ -0,0 +1,112 @@
# Forge Mesh — Portable Deck (USB Edition)
Self-contained command deck you can run from a USB stick or extracted tarball without a system install.
## Quick start
```bash
tar -xzf forge-mesh-portable-*.tar.gz
cd forge-mesh-portable-*
# Edit credentials and wallet before production use:
${EDITOR:-nano} data/config.json
./LAUNCH.sh
```
`LAUNCH.sh` starts the control plane on **http://localhost:8989**, opens your default browser, and uses `./data` for SQLite, artifacts, and secrets.
Default login (change in `data/config.json`):
- Username: `admin`
- Password: `changeme`
## Cloudflare Tunnel (optional sidecar)
Linux uses an **external** cloudflared process (`AF_TUNNEL_EXTERNAL=1`), not in-process tunneling.
1. Place your tunnel token in `data/cloudflared-token.txt` (single line, no quotes), **or**
2. Export `AF_TUNNEL_TOKEN` before launch.
`LAUNCH.sh` looks for `cloudflared` on `PATH` or in `bin/cloudflared`. When a token is present it starts the sidecar and sets `AF_TUNNEL_EXTERNAL=1` for agents that honor that flag.
```bash
# Example
echo 'YOUR_CF_TUNNEL_TOKEN' > data/cloudflared-token.txt
./LAUNCH.sh
```
Skip browser auto-open (headless / SSH):
```bash
AF_NO_BROWSER=1 ./LAUNCH.sh
```
## WireGuard mesh (optional, operator-managed)
When you control the network, WireGuard is preferred over Cloudflare: agents dial the deck on a private mesh without a public tunnel.
Forge Mesh does **not** auto-install WireGuard. Configure it on the deck host and enrolled agents yourself:
1. Install WireGuard (`wireguard`, `wireguard-tools`) on deck and agents.
2. Generate keys: `wg genkey | tee privatekey | wg pubkey > publickey`
3. Create `/etc/wireguard/forge-mesh.conf` (paths may vary):
```ini
[Interface]
PrivateKey = <deck-private-key>
Address = 10.66.0.1/24
ListenPort = 51820
[Peer]
# Example agent
PublicKey = <agent-public-key>
AllowedIPs = 10.66.0.2/32
```
4. Enable: `sudo systemctl enable --now wg-quick@forge-mesh`
5. Point agents at the deck **WireGuard IP** (e.g. `http://10.66.0.1:8989`) in summon URL or agent env — not `localhost`.
Triple-onion tier **T9** (mTLS mesh join via WireGuard) assumes this overlay exists. See `docs/PROBLEMS.md` for limits.
## Summon agents from this deck
With the deck listening (and reachable on your LAN or tunnel):
```bash
curl -fsSL http://localhost:8989/install.sh | sudo bash
```
Replace `localhost` with your tunnel hostname or WireGuard address when summoning remote hosts.
## Layout
```
.
├── LAUNCH.sh # Entry point (symlink to scripts/LAUNCH.sh)
├── scripts/LAUNCH.sh
├── bin/
│ ├── forge-mesh-server # Included if built before pack-usb.sh
│ └── cloudflared # Optional
├── data/
│ ├── config.json
│ ├── cloudflared-token.txt # Optional (gitignore in real deployments)
│ └── forge-mesh.db # Created on first run
└── README.md
```
## Environment variables
| Variable | Purpose |
|----------|---------|
| `AF_TUNNEL_TOKEN` | Cloudflare tunnel token (overrides file) |
| `AF_TUNNEL_EXTERNAL` | Set to `1` automatically when sidecar runs |
| `AF_NO_BROWSER` | Skip opening a browser |
| `AF_CONFIG` | Override config path (default `./data/config.json`) |
| `AF_DATA_DIR` | Data directory (default `./data`) |
| `AF_SERVER_BIN` | Path to `forge-mesh-server` binary |
| `AF_DECK_URL` | Browser URL (default `http://localhost:8989`) |
## Security notes
- Change `auth.basic_password` and `auth.fleet_secret` before exposing the deck.
- Do not commit `data/cloudflared-token.txt` or `data/signing.key` to version control.
- USB copies carry your fleet secret — treat the stick like a key.

25
scripts/ci-test.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# CI entrypoint — mirrors .github/workflows/test.yml locally
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
export GOROOT="${GOROOT:-/usr/local/go}"
export PATH="${GOROOT}/bin:${PATH}"
export CGO_ENABLED=1
echo "==> [ci] environment"
go version
node --version 2>/dev/null || echo "node: not installed (frontend may skip)"
npm --version 2>/dev/null || true
echo "==> [ci] go mod tidy + build"
go mod tidy
go build ./...
echo "==> [ci] full test suite"
SKIP_E2E="${SKIP_E2E:-0}" SKIP_FRONTEND="${SKIP_FRONTEND:-0}" \
CONTINUE_ON_FAIL=0 ./scripts/test-all.sh
echo "✓ ci-test complete"

92
scripts/e2e-lib.sh Executable file
View File

@@ -0,0 +1,92 @@
#!/usr/bin/env bash
# Shared helpers for e2e-test.sh
set -euo pipefail
e2e_log() { echo "==> [e2e] $*"; }
e2e_pass() { echo " ✓ $*"; }
e2e_wait_health() {
local base="$1" tries="${2:-60}"
for _ in $(seq 1 "$tries"); do
if curl -fsS "${base}/api/v1/health" >/dev/null 2>&1; then
return 0
fi
sleep 0.25
done
echo "server did not become healthy at ${base}" >&2
return 1
}
e2e_wait_port() {
local host="$1" port="$2" tries="${3:-40}"
for _ in $(seq 1 "$tries"); do
if (echo >/dev/tcp/"${host}"/"${port}") 2>/dev/null; then
return 0
fi
sleep 0.25
done
echo "port ${host}:${port} not open" >&2
return 1
}
e2e_json_field() {
local json="$1" field="$2"
echo "${json}" | sed -n "s/.*\"${field}\":\"\([^\"]*\)\".*/\1/p" | head -1
}
e2e_curl_auth() {
curl -fsS -u "${E2E_USER}:${E2E_PASS}" "$@"
}
e2e_curl_fleet() {
curl -fsS -H "Authorization: Bearer ${FLEET_SECRET}" "$@"
}
e2e_write_config() {
local path="$1" port="$2" clearance="$3" xmr_port="$4" rvn_port="$5"
mkdir -p "$(dirname "${path}")" "/tmp/forge-mesh-e2e"
cat > "${path}" <<EOF
{
"listen_addr": ":${port}",
"data_dir": "/tmp/forge-mesh-e2e",
"database_path": "/tmp/forge-mesh-e2e/test-${port}.db",
"operator_clearance": ${clearance},
"auth": {
"basic_username": "${E2E_USER}",
"basic_password": "${E2E_PASS}",
"fleet_secret": "${FLEET_SECRET}"
},
"wallet_policy": {
"default_wallet": "e2e-test-wallet",
"currency": "XMR"
},
"stratum": {
"xmr_listen": ":${xmr_port}",
"rvn_listen": ":${rvn_port}",
"upstream_xmr": "",
"upstream_rvn": ""
},
"forge": {
"signing_key_path": "/tmp/forge-mesh-e2e/signing-${port}.key",
"artifacts_dir": "/tmp/forge-mesh-e2e/artifacts-${port}"
},
"court": { "enabled": false, "ollama_url": "http://127.0.0.1:11434" },
"telegram": { "enabled": false, "bot_token": "", "chat_id": "" }
}
EOF
}
e2e_start_server() {
local config="$1"
./bin/forge-mesh-server -config "${config}" -install-tmpl scripts/install.sh.tpl &
SERVER_PID=$!
e2e_wait_health "${BASE}"
}
e2e_stop_server() {
if [[ -n "${SERVER_PID:-}" ]] && kill -0 "${SERVER_PID}" 2>/dev/null; then
kill "${SERVER_PID}" 2>/dev/null || true
wait "${SERVER_PID}" 2>/dev/null || true
fi
SERVER_PID=""
}

201
scripts/e2e-test.sh Executable file
View File

@@ -0,0 +1,201 @@
#!/usr/bin/env bash
# AetherForge Linux end-to-end operator flow tests
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
# shellcheck source=scripts/e2e-lib.sh
source "${ROOT}/scripts/e2e-lib.sh"
PORT="${E2E_PORT:-18989}"
STRATUM_XMR_PORT="${E2E_STRATUM_XMR:-33333}"
STRATUM_RVN_PORT="${E2E_STRATUM_RVN:-33388}"
BASE="http://127.0.0.1:${PORT}"
CONFIG="${E2E_CONFIG:-/tmp/forge-mesh-e2e-config.json}"
FLEET_SECRET="${E2E_FLEET_SECRET:-e2e-fleet-secret-test}"
E2E_USER="${E2E_USER:-admin}"
E2E_PASS="${E2E_PASS:-changeme}"
SERVER_PID=""
AGENT_PID=""
MOCK_OLLAMA_PID=""
MOCK_TG_PID=""
cleanup() {
[[ -n "${AGENT_PID}" ]] && kill "${AGENT_PID}" 2>/dev/null || true
[[ -n "${MOCK_OLLAMA_PID}" ]] && kill "${MOCK_OLLAMA_PID}" 2>/dev/null || true
[[ -n "${MOCK_TG_PID}" ]] && kill "${MOCK_TG_PID}" 2>/dev/null || true
e2e_stop_server
}
trap cleanup EXIT
start_mock_ollama() {
if curl -fsS http://127.0.0.1:11434/api/tags >/dev/null 2>&1; then
e2e_pass "Ollama already available — using live instance"
return 0
fi
python3 - <<'PY' &
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
class H(BaseHTTPRequestHandler):
def log_message(self, *a): pass
def do_GET(self):
if self.path.startswith("/api/tags"):
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({"models": [{"name": "mock"}]}).encode())
else:
self.send_response(404)
self.end_headers()
HTTPServer(("127.0.0.1", 11434), H).serve_forever()
PY
MOCK_OLLAMA_PID=$!
sleep 0.5
e2e_pass "mock Ollama listening on :11434"
}
start_mock_telegram() {
# Telegram is disabled in e2e config; mock not required unless enabled.
e2e_pass "Telegram disabled in test config (mock skipped)"
}
e2e_log "building server + agent"
make server agent
e2e_log "writing L4 test config"
e2e_write_config "${CONFIG}" "${PORT}" 4 "${STRATUM_XMR_PORT}" "${STRATUM_RVN_PORT}"
e2e_log "starting server (start/stop lifecycle)"
e2e_start_server "${CONFIG}"
e2e_pass "server started pid=${SERVER_PID}"
e2e_log "health check"
HEALTH="$(curl -fsS "${BASE}/api/v1/health")"
echo "${HEALTH}" | grep -q '"status":"ok"' || { echo "health failed: ${HEALTH}"; exit 1; }
e2e_pass "GET /api/v1/health"
e2e_log "basic auth rejection/acceptance"
CODE="$(curl -s -o /dev/null -w '%{http_code}' "${BASE}/api/v1/fleet")"
[[ "${CODE}" == "401" ]] || { echo "expected 401 without auth, got ${CODE}"; exit 1; }
e2e_pass "unauthenticated fleet → 401"
FLEET="$(e2e_curl_auth "${BASE}/api/v1/fleet")"
echo "${FLEET}" | grep -q '"hosts"' || { echo "fleet list failed: ${FLEET}"; exit 1; }
e2e_pass "authenticated fleet → 200"
e2e_log "public install.sh render + bash syntax"
INSTALL="$(curl -fsS "${BASE}/install.sh")"
echo "${INSTALL}" | grep -q 'forge-mesh' || { echo "install.sh missing marker"; exit 1; }
TMP_INSTALL="$(mktemp)"
echo "${INSTALL}" > "${TMP_INSTALL}"
bash -n "${TMP_INSTALL}"
rm -f "${TMP_INSTALL}"
e2e_pass "GET /install.sh valid bash"
e2e_log "forge build + public download"
BUILD_RESP="$(e2e_curl_auth -X POST "${BASE}/api/v1/forge/builds/trigger" \
-H "Content-Type: application/json" -d '{"public":true}')"
echo "${BUILD_RESP}" | grep -q '"ok":true' || { echo "forge trigger failed: ${BUILD_RESP}"; exit 1; }
LATEST="$(curl -fsS "${BASE}/api/v1/public/builds/latest?os=linux&arch=amd64")"
BUILD_ID="$(e2e_json_field "${LATEST}" id)"
[[ -n "${BUILD_ID}" ]] || { echo "no build id in ${LATEST}"; exit 1; }
DOWNLOAD_BYTES="$(curl -fsS "${BASE}/api/v1/public/download/${BUILD_ID}" | wc -c | tr -d ' ')"
[[ "${DOWNLOAD_BYTES}" -gt 100 ]] || { echo "download too small: ${DOWNLOAD_BYTES}"; exit 1; }
e2e_pass "forge build + public download (${BUILD_ID})"
e2e_log "agent register via REST + WS subprocess"
REGISTER="$(e2e_curl_fleet -X POST "${BASE}/api/v1/fleet/register" \
-H "Content-Type: application/json" \
-d '{"hostname":"e2e-rest-host","fingerprint":"127.0.0.2","arch":"amd64"}')"
HOST_ID="$(e2e_json_field "${REGISTER}" host_id)"
[[ -n "${HOST_ID}" ]] || { echo "register failed: ${REGISTER}"; exit 1; }
e2e_pass "POST /api/v1/fleet/register"
AGENT_HOST="e2e-ws-agent"
FORGE_FLEET_SECRET="${FLEET_SECRET}" FORGE_HOST_ID="${AGENT_HOST}" \
./bin/forge-mesh-agent -deck "${BASE}" -secret "${FLEET_SECRET}" -host-id "${AGENT_HOST}" &
AGENT_PID=$!
sleep 3
e2e_log "fleet appears in API"
FLEET2="$(e2e_curl_auth "${BASE}/api/v1/fleet/hosts")"
echo "${FLEET2}" | grep -q "${AGENT_HOST}" || echo "${FLEET2}" | grep -q 'e2e-rest-host' || {
echo "fleet missing enrolled hosts: ${FLEET2}"; exit 1
}
WS_HOST_ID="$(echo "${FLEET2}" | sed -n 's/.*"hostname":"\('"${AGENT_HOST}"'\)".*"id":"\([^"]*\)".*/\1/p')"
[[ -z "${WS_HOST_ID}" ]] && WS_HOST_ID="${HOST_ID}"
e2e_pass "fleet hosts visible"
e2e_log "mining profile push"
PROFILE_RESP="$(e2e_curl_auth -X POST "${BASE}/api/v1/fleet/${WS_HOST_ID}/mining-profile" \
-H "Content-Type: application/json" \
-d '{"wallet_address":"e2e-wallet-addr","name":"E2E profile"}')"
echo "${PROFILE_RESP}" | grep -q '"ok":true' || { echo "profile push failed: ${PROFILE_RESP}"; exit 1; }
e2e_pass "POST /api/v1/fleet/{id}/mining-profile"
e2e_log "stratum port open"
e2e_wait_port 127.0.0.1 "${STRATUM_XMR_PORT}"
e2e_wait_port 127.0.0.1 "${STRATUM_RVN_PORT}"
# Send minimal stratum-ish payload (accept-only mode)
(printf '{"id":1,"method":"mining.subscribe","params":[]}\n' | nc -w 2 127.0.0.1 "${STRATUM_XMR_PORT}") >/dev/null 2>&1 || true
e2e_pass "stratum XMR:${STRATUM_XMR_PORT} RVN:${STRATUM_RVN_PORT} listening"
e2e_log "/spread/ lander serves"
SPREAD="$(curl -fsS "${BASE}/spread/?c=e2e-campaign")"
echo "${SPREAD}" | grep -qi 'spread\|summon\|install' || { echo "spread lander unexpected: ${SPREAD}"; exit 1; }
e2e_pass "GET /spread/"
e2e_log "policy snapshot"
SNAP="$(e2e_curl_auth -X POST "${BASE}/api/v1/policy/snapshot")"
SNAP_TOKEN="$(e2e_json_field "${SNAP}" token)"
[[ -n "${SNAP_TOKEN}" ]] || { echo "snapshot create failed: ${SNAP}"; exit 1; }
SNAP_BODY="$(curl -fsS "${BASE}/api/v1/public/policy-snapshot/${SNAP_TOKEN}")"
echo "${SNAP_BODY}" | grep -q 'policy_from_server' || { echo "snapshot fetch failed: ${SNAP_BODY}"; exit 1; }
e2e_pass "policy snapshot create + public fetch"
e2e_log "crucible endpoint (L4)"
CRUC="$(e2e_curl_auth -X POST "${BASE}/api/v1/crucible/batch" \
-H "Content-Type: application/json" \
-d "{\"command\":\"status\",\"host_ids\":[\"${WS_HOST_ID}\"]}")"
echo "${CRUC}" | grep -q '"status":"completed"' || echo "${CRUC}" | grep -q '"status":"running"' || {
echo "crucible failed: ${CRUC}"; exit 1
}
e2e_pass "POST /api/v1/crucible/batch"
e2e_log "ws ticket"
TICKET="$(e2e_curl_auth -X POST "${BASE}/api/v1/ws/ticket")"
echo "${TICKET}" | grep -q 'ticket' || { echo "ws ticket failed: ${TICKET}"; exit 1; }
e2e_pass "POST /api/v1/ws/ticket"
start_mock_ollama
start_mock_telegram
e2e_log "clearance gate blocks L0 actions — restart with operator_clearance=0"
kill "${AGENT_PID}" 2>/dev/null || true
AGENT_PID=""
e2e_stop_server
L0_CONFIG="/tmp/forge-mesh-e2e-l0.json"
e2e_write_config "${L0_CONFIG}" "${PORT}" 0 "${STRATUM_XMR_PORT}" "${STRATUM_RVN_PORT}"
e2e_start_server "${L0_CONFIG}"
ME="$(e2e_curl_auth "${BASE}/api/v1/operator/me")"
echo "${ME}" | grep -q '"clearance_level":0' || { echo "operator/me: ${ME}"; exit 1; }
DENIED_CODE="$(e2e_curl_auth -X POST "${BASE}/api/v1/fleet/${HOST_ID}/command" \
-H "Content-Type: application/json" \
-d '{"action":"shell","args":{"command":"id"}}' \
-o /dev/null -w '%{http_code}')"
[[ "${DENIED_CODE}" == "403" ]] || { echo "expected 403 for L0 shell, got ${DENIED_CODE}"; exit 1; }
CRUC_CODE="$(e2e_curl_auth -X POST "${BASE}/api/v1/crucible/batch" \
-H "Content-Type: application/json" \
-d "{\"command\":\"status\",\"host_ids\":[\"${HOST_ID}\"]}" \
-o /dev/null -w '%{http_code}')"
[[ "${CRUC_CODE}" == "403" ]] || { echo "expected 403 for L0 crucible, got ${CRUC_CODE}"; exit 1; }
e2e_pass "L0 operator blocked from shell + crucible"
echo ""
echo "✓ e2e full operator flow passed ($(grep -c 'e2e_pass\|✓' "$0" || echo 18) checks)"

112
scripts/install.sh.tpl Normal file
View File

@@ -0,0 +1,112 @@
#!/bin/bash
# forge-mesh agent installer — rendered by deck at GET /install.sh
set -euo pipefail
DECK_URL="{{.DeckURL}}"
PUBLIC_KEY="{{.PublicKey}}"
FLEET_SECRET="{{.FleetSecret}}"
PIN="{{.Pin}}"
CAMPAIGN="{{.Campaign}}"
INSTALL_DIR="${FORGE_MESH_INSTALL_DIR:-/opt/forge-mesh}"
AGENT_BIN="${INSTALL_DIR}/agent"
SERVICE_NAME="forge-mesh-agent"
ARCH_RAW="$(uname -m)"
case "${ARCH_RAW}" in
x86_64|amd64) ARCH="amd64" ;;
aarch64|arm64) ARCH="arm64" ;;
*) echo "unsupported arch: ${ARCH_RAW}" >&2; exit 1 ;;
esac
echo "[forge-mesh] deck=${DECK_URL} arch=${ARCH}"
META_URL="${DECK_URL}/api/v1/public/builds/latest?os=linux&arch=${ARCH}"
META="$(curl -fsSL "${META_URL}")"
BUILD_ID="$(echo "${META}" | sed -n 's/.*"id":"\([^"]*\)".*/\1/p')"
CHECKSUM="$(echo "${META}" | sed -n 's/.*"checksum":"\([^"]*\)".*/\1/p')"
SIGNATURE="$(echo "${META}" | sed -n 's/.*"signature":"\([^"]*\)".*/\1/p')"
if [[ -z "${BUILD_ID}" || -z "${CHECKSUM}" ]]; then
echo "failed to fetch build metadata from ${META_URL}" >&2
exit 1
fi
TMP="$(mktemp)"
trap 'rm -f "${TMP}"' EXIT
curl -fsSL "${DECK_URL}/api/v1/public/download/${BUILD_ID}" -o "${TMP}"
ACTUAL="$(sha256sum "${TMP}" | awk '{print $1}')"
if [[ "${ACTUAL}" != "${CHECKSUM}" ]]; then
echo "checksum mismatch: expected ${CHECKSUM}, got ${ACTUAL}" >&2
exit 1
fi
if [[ -n "${SIGNATURE}" && -n "${PUBLIC_KEY}" ]]; then
echo "[forge-mesh] signed build (sig verified by agent on self-update with pubkey ${PUBLIC_KEY:0:16}...)"
fi
SECRET="${FORGE_MESH_FLEET_SECRET:-${FORGE_FLEET_SECRET:-${FLEET_SECRET}}}"
if [[ -z "${SECRET}" ]]; then
echo "set FORGE_MESH_FLEET_SECRET before install" >&2
exit 1
fi
if [[ "$(id -u)" -ne 0 ]]; then
echo "[forge-mesh] not root — installing user systemd unit"
mkdir -p "${HOME}/.local/bin"
install -m 0755 "${TMP}" "${HOME}/.local/bin/forge-mesh-agent"
UNIT_DIR="${HOME}/.config/systemd/user"
mkdir -p "${UNIT_DIR}"
cat > "${UNIT_DIR}/${SERVICE_NAME}.service" <<UNIT
[Unit]
Description=Forge Mesh Agent
After=network-online.target
[Service]
Type=simple
ExecStart=${HOME}/.local/bin/forge-mesh-agent -deck-url ${DECK_URL} -secret ${SECRET} -pubkey ${PUBLIC_KEY}
Restart=always
RestartSec=10
Environment=FORGE_MESH_DECK_URL=${DECK_URL}
Environment=FORGE_MESH_FLEET_SECRET=${SECRET}
Environment=FORGE_MESH_PUBKEY=${PUBLIC_KEY}
[Install]
WantedBy=default.target
UNIT
systemctl --user daemon-reload
systemctl --user enable --now "${SERVICE_NAME}.service"
echo "[forge-mesh] started user unit ${SERVICE_NAME}"
exit 0
fi
mkdir -p "${INSTALL_DIR}"
install -m 0755 "${TMP}" "${AGENT_BIN}"
cat > "/etc/systemd/system/${SERVICE_NAME}.service" <<UNIT
[Unit]
Description=Forge Mesh Agent
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=${INSTALL_DIR}
ExecStart=${AGENT_BIN} -deck-url ${DECK_URL} -secret ${SECRET} -pubkey ${PUBLIC_KEY}
Restart=always
RestartSec=10
Environment=FORGE_MESH_DECK_URL=${DECK_URL}
Environment=FORGE_MESH_FLEET_SECRET=${SECRET}
Environment=FORGE_MESH_PUBKEY=${PUBLIC_KEY}
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable --now "${SERVICE_NAME}.service"
echo "[forge-mesh] agent installed to ${AGENT_BIN} (build ${BUILD_ID})"
[[ -n "${CAMPAIGN}" ]] && echo "[forge-mesh] campaign=${CAMPAIGN}"
[[ -n "${PIN}" ]] && echo "[forge-mesh] pin accepted"

84
scripts/pack-usb.sh Executable file
View File

@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# Build a portable USB tarball: LAUNCH.sh, data template, README, optional binaries.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
OUT_DIR="${1:-$ROOT/dist}"
STAMP="$(date -u +%Y%m%d)"
PKG_NAME="forge-mesh-portable-${STAMP}"
STAGE="$OUT_DIR/$PKG_NAME"
ARCHIVE="$OUT_DIR/${PKG_NAME}.tar.gz"
echo "==> Staging portable deck at $STAGE"
rm -rf "$STAGE"
mkdir -p "$STAGE"/{bin,data,scripts}
cp scripts/LAUNCH.sh "$STAGE/scripts/LAUNCH.sh"
chmod +x "$STAGE/scripts/LAUNCH.sh"
ln -sf scripts/LAUNCH.sh "$STAGE/LAUNCH.sh"
if [[ -f data/config.json ]]; then
cp data/config.json "$STAGE/data/config.json"
else
cat >"$STAGE/data/config.json" <<'EOF'
{
"listen_addr": ":8989",
"data_dir": "./data",
"database_path": "./data/forge-mesh.db",
"auth": {
"basic_username": "admin",
"basic_password": "changeme",
"fleet_secret": "change-me-fleet-secret"
},
"wallet_policy": {
"default_wallet": "",
"currency": "XMR"
},
"stratum": {
"xmr_listen": ":3333",
"rvn_listen": ":3388",
"upstream_xmr": "",
"upstream_rvn": ""
},
"forge": {
"signing_key_path": "./data/signing.key",
"artifacts_dir": "./data/artifacts"
},
"court": {
"ollama_url": "http://127.0.0.1:11434",
"enabled": false
}
}
EOF
fi
# Optional: include cloudflared token placeholder (empty = no tunnel until operator fills in).
touch "$STAGE/data/cloudflared-token.txt.example"
cat >"$STAGE/data/cloudflared-token.txt.example" <<'EOF'
# Paste your Cloudflare Tunnel token on a single line, then rename to cloudflared-token.txt
# Or set AF_TUNNEL_TOKEN in the environment before running LAUNCH.sh
EOF
cp scripts/README-USB.md "$STAGE/README.md"
if [[ -x bin/forge-mesh-server ]]; then
cp bin/forge-mesh-server "$STAGE/bin/forge-mesh-server"
echo " included bin/forge-mesh-server"
else
echo " WARN: bin/forge-mesh-server missing — run 'make server' before packing for a self-contained bundle"
fi
if [[ -x bin/cloudflared ]]; then
cp bin/cloudflared "$STAGE/bin/cloudflared"
echo " included bin/cloudflared"
fi
mkdir -p "$OUT_DIR"
tar -C "$OUT_DIR" -czf "$ARCHIVE" "$PKG_NAME"
rm -rf "$STAGE"
echo "==> Created $ARCHIVE"
echo " Extract anywhere, edit data/config.json, optionally data/cloudflared-token.txt, then:"
echo " ./LAUNCH.sh"

16
scripts/sync-webroot.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Copy Vite build output into cmd/server/webroot for go:embed.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
DIST="$ROOT/web/dist"
WEBROOT="$ROOT/cmd/server/webroot"
if [[ ! -f "$DIST/index.html" ]]; then
echo "ERROR: $DIST/index.html missing — run: cd web && npm run build" >&2
exit 1
fi
rm -rf "$WEBROOT"
mkdir -p "$WEBROOT"
cp -a "$DIST"/. "$WEBROOT/"
echo "Synced web/dist -> cmd/server/webroot ($(find "$WEBROOT" -type f | wc -l) files)"

78
scripts/test-all.sh Executable file
View File

@@ -0,0 +1,78 @@
#!/usr/bin/env bash
# Run full AetherForge Linux test suite with summary
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
export GOROOT="${GOROOT:-/usr/local/go}"
export PATH="${GOROOT}/bin:${PATH}"
SUITE=(
"unit:scripts/test-unit.sh"
"integration:scripts/test-integration.sh"
"cli:scripts/test-cli.sh"
"frontend:scripts/test-frontend.sh"
"e2e:scripts/e2e-test.sh"
)
PASSED=()
FAILED=()
SKIPPED=()
run_suite() {
local name="$1" script="$2"
echo ""
echo "════════════════════════════════════════"
echo " Running ${name} tests"
echo "════════════════════════════════════════"
if [[ ! -x "${script}" ]]; then
chmod +x "${script}"
fi
if "${script}"; then
PASSED+=("${name}")
else
FAILED+=("${name}")
return 1
fi
}
CONTINUE_ON_FAIL="${CONTINUE_ON_FAIL:-0}"
OVERALL=0
for entry in "${SUITE[@]}"; do
name="${entry%%:*}"
script="${entry#*:}"
if [[ "${SKIP_E2E:-0}" == "1" && "${name}" == "e2e" ]]; then
SKIPPED+=("${name}")
echo "⊘ skipping e2e (SKIP_E2E=1)"
continue
fi
if [[ "${SKIP_FRONTEND:-0}" == "1" && "${name}" == "frontend" ]]; then
SKIPPED+=("${name}")
echo "⊘ skipping frontend (SKIP_FRONTEND=1)"
continue
fi
if run_suite "${name}" "${script}"; then
:
else
OVERALL=1
[[ "${CONTINUE_ON_FAIL}" == "1" ]] || break
fi
done
echo ""
echo "════════════════════════════════════════"
echo " Test summary"
echo "════════════════════════════════════════"
echo " Passed : ${#PASSED[@]} (${PASSED[*]:-none})"
echo " Failed : ${#FAILED[@]} (${FAILED[*]:-none})"
echo " Skipped: ${#SKIPPED[@]} (${SKIPPED[*]:-none})"
echo "════════════════════════════════════════"
if [[ "${OVERALL}" -ne 0 ]]; then
echo "✗ test-all FAILED"
exit 1
fi
echo "✓ test-all PASSED"
exit 0

48
scripts/test-cli.sh Executable file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# CLI smoke tests for forge-mesh forge and agent binaries
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
export GOROOT="${GOROOT:-/usr/local/go}"
export PATH="${GOROOT}/bin:${PATH}"
CLI_CONFIG="/tmp/forge-mesh-cli-config.json"
CLI_DIR="/tmp/forge-mesh-cli"
mkdir -p "${CLI_DIR}"
cat > "${CLI_CONFIG}" <<EOF
{
"listen_addr": ":0",
"data_dir": "${CLI_DIR}",
"database_path": "${CLI_DIR}/cli.db",
"auth": {
"basic_username": "admin",
"basic_password": "changeme",
"fleet_secret": "cli-test-secret"
},
"forge": {
"signing_key_path": "${CLI_DIR}/signing.key",
"artifacts_dir": "${CLI_DIR}/artifacts"
}
}
EOF
echo "==> [cli] building forge CLI + agent"
make -C "${ROOT}" agent
go build -o "${CLI_DIR}/forge" ./cmd/forge
echo "==> [cli] forge pubkey"
PUBKEY="$("${CLI_DIR}/forge" pubkey --config "${CLI_CONFIG}")"
[[ -n "${PUBKEY}" ]] || { echo "empty pubkey"; exit 1; }
echo " pubkey: ${PUBKEY:0:16}..."
echo "==> [cli] forge build (public artifacts)"
BUILD_OUT="$("${CLI_DIR}/forge" build --config "${CLI_CONFIG}" --public 2>&1)"
echo "${BUILD_OUT}" | grep -q 'built linux/amd64' || { echo "${BUILD_OUT}"; exit 1; }
echo "==> [cli] agent --help exits cleanly"
./bin/forge-mesh-agent -h 2>&1 | grep -q 'deck' || ./bin/forge-mesh-agent 2>&1 | grep -q 'fleet secret'
echo "✓ cli tests passed"

18
scripts/test-frontend.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# Frontend unit tests (Vitest + Testing Library)
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
WEB="${ROOT}/web"
cd "${WEB}"
if [[ ! -d node_modules ]]; then
echo "==> [frontend] installing dependencies"
npm ci 2>/dev/null || npm install
fi
echo "==> [frontend] vitest run"
npm run test
echo "✓ frontend tests passed"

15
scripts/test-integration.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Integration tests — HTTP router, stratum, fleet store against temp SQLite
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
export GOROOT="${GOROOT:-/usr/local/go}"
export PATH="${GOROOT}/bin:${PATH}"
export CGO_ENABLED="${CGO_ENABLED:-1}"
echo "==> [integration] api router + stratum + fleet store"
go test -count=1 -v ./internal/api/... ./internal/stratum/... ./internal/fleet/...
echo "✓ integration tests passed"

14
scripts/test-unit.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Go unit tests (fast, no network)
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
export GOROOT="${GOROOT:-/usr/local/go}"
export PATH="${GOROOT}/bin:${PATH}"
echo "==> [unit] go test ./internal/..."
go test -count=1 -short ./internal/...
echo "✓ unit tests passed"