diff --git a/deploy/dokploy/agent.Dockerfile b/deploy/dokploy/agent.Dockerfile deleted file mode 100644 index f669a6d..0000000 --- a/deploy/dokploy/agent.Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM oven/bun:1.3.14 AS build - -WORKDIR /app - -COPY . . -RUN bun install --frozen-lockfile -RUN bun run --filter @code/agents build - -FROM node:24-bookworm-slim - -ENV NODE_ENV=production -ENV PORT=3000 - -WORKDIR /app - -COPY --from=build /app /app - -EXPOSE 3000 - -CMD ["node", "packages/agents/dist/server.mjs"] diff --git a/deploy/dokploy/backend.compose.yml b/deploy/dokploy/backend.compose.yml deleted file mode 100644 index 7541348..0000000 --- a/deploy/dokploy/backend.compose.yml +++ /dev/null @@ -1,31 +0,0 @@ -services: - backend: - build: - context: ../.. - dockerfile: packages/agents/Dockerfile - environment: - AGENT_BACKEND_URL: ${AGENT_BACKEND_URL} - AGENT_MODEL_API: ${AGENT_MODEL_API} - AGENT_MODEL_API_KEY: ${AGENT_MODEL_API_KEY} - AGENT_MODEL_BASE_URL: ${AGENT_MODEL_BASE_URL} - AGENT_MODEL_CONTEXT_WINDOW: ${AGENT_MODEL_CONTEXT_WINDOW} - AGENT_MODEL_MAX_TOKENS: ${AGENT_MODEL_MAX_TOKENS} - AGENT_MODEL_NAME: ${AGENT_MODEL_NAME} - AGENT_MODEL_PROVIDER: ${AGENT_MODEL_PROVIDER} - CONVEX_URL: ${CONVEX_URL} - DAEMON_ID: zopu-agent-backend - FLUE_DB_TOKEN: ${FLUE_DB_TOKEN} - GITEA_TOKEN: ${GITEA_TOKEN:-} - GITEA_URL: ${GITEA_URL:-https://git.openputer.com} - PORT: "3000" - RIVET_ENDPOINT: ${RIVET_ENDPOINT} - RIVET_PUBLIC_ENDPOINT: ${RIVET_PUBLIC_ENDPOINT} - RIVET_WORKSPACE_TOKEN: ${RIVET_WORKSPACE_TOKEN} - networks: - - default - - dokploy-network - restart: unless-stopped - -networks: - dokploy-network: - external: true diff --git a/deploy/dokploy/frontend.compose.yml b/deploy/dokploy/frontend.compose.yml deleted file mode 100644 index f2df55c..0000000 --- a/deploy/dokploy/frontend.compose.yml +++ /dev/null @@ -1,15 +0,0 @@ -services: - frontend: - build: - context: ../.. - dockerfile: apps/web/Dockerfile - args: - VITE_AUTH_URL: ${VITE_AUTH_URL} - VITE_CONVEX_URL: ${VITE_CONVEX_URL} - environment: - HOST: 0.0.0.0 - NODE_ENV: production - PORT: "3000" - VITE_AUTH_URL: ${VITE_AUTH_URL} - VITE_CONVEX_URL: ${VITE_CONVEX_URL} - restart: unless-stopped diff --git a/deploy/dokploy/runner.Dockerfile b/deploy/dokploy/runner.Dockerfile deleted file mode 100644 index 9e96f73..0000000 --- a/deploy/dokploy/runner.Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -FROM oven/bun:1.3.14 AS bun - -FROM node:24-bookworm-slim - -COPY --from=bun /usr/local/bin/bun /usr/local/bin/bun - -RUN apt-get update \ - && apt-get install -y --no-install-recommends ca-certificates g++ git make python3 \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /opt/zopu-source - -COPY . . -RUN bun install --frozen-lockfile - -ENV AGENT_WORKSPACE_ROOT=/var/lib/zopu/workspaces -ENV BUN_EXECUTABLE=/usr/local/bin/bun -ENV DAEMON_ID=zopu-agentos-runner -ENV ZOPU_SOURCE_REPOSITORY=/opt/zopu-source - -VOLUME ["/var/lib/zopu/workspaces"] - -CMD ["bun", "packages/agents/src/runner.ts"] diff --git a/deploy/dokploy/runner.compose.yml b/deploy/dokploy/runner.compose.yml deleted file mode 100644 index fe000c9..0000000 --- a/deploy/dokploy/runner.compose.yml +++ /dev/null @@ -1,26 +0,0 @@ -services: - runner: - build: - context: . - dockerfile: deploy/dokploy/runner.Dockerfile - environment: - AGENT_MODEL_API: ${AGENT_MODEL_API} - AGENT_MODEL_API_KEY: ${AGENT_MODEL_API_KEY} - AGENT_MODEL_BASE_URL: ${AGENT_MODEL_BASE_URL} - AGENT_MODEL_CONTEXT_WINDOW: ${AGENT_MODEL_CONTEXT_WINDOW} - AGENT_MODEL_MAX_TOKENS: ${AGENT_MODEL_MAX_TOKENS} - AGENT_MODEL_NAME: ${AGENT_MODEL_NAME} - AGENT_MODEL_PROVIDER: ${AGENT_MODEL_PROVIDER} - CONVEX_URL: ${CONVEX_URL} - DAEMON_ID: zopu-agentos-runner - FLUE_DB_TOKEN: ${FLUE_DB_TOKEN} - RIVET_ENDPOINT: ${RIVET_ENDPOINT} - RIVET_PUBLIC_ENDPOINT: ${RIVET_PUBLIC_ENDPOINT} - RIVET_WORKSPACE_TOKEN: ${RIVET_WORKSPACE_TOKEN} - ZOPU_SOURCE_REPOSITORY: /opt/zopu-source - volumes: - - runner-workspaces:/var/lib/zopu/workspaces - restart: unless-stopped - -volumes: - runner-workspaces: diff --git a/deploy/dokploy/web.Dockerfile b/deploy/dokploy/web.Dockerfile deleted file mode 100644 index f8eac6a..0000000 --- a/deploy/dokploy/web.Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM oven/bun:1.3.14 AS build - -WORKDIR /app - -COPY . . -RUN bun install --frozen-lockfile -RUN bun run --filter web build - -FROM node:24-bookworm-slim - -ENV HOST=0.0.0.0 -ENV NODE_ENV=production -ENV PORT=3000 - -WORKDIR /app - -COPY --from=build /app /app - -EXPOSE 3000 - -CMD ["node", "apps/web/node_modules/.bin/react-router-serve", "apps/web/build/server/index.js"] diff --git a/deploy/zopu-runtime/.env.template b/deploy/zopu-runtime/.env.template deleted file mode 100644 index 10f67d8..0000000 --- a/deploy/zopu-runtime/.env.template +++ /dev/null @@ -1,86 +0,0 @@ -############################################################################### -# Zopu Runtime Deployment — Environment Template -# -# Copy to .env and fill in real values. Never commit .env to the repository. -# This file documents every environment group required by the single-node -# execution plane. Lines marked REQUIRED must be set before first start. -############################################################################### - -# --------------------------------------------------------------------------- -# 1. Convex (control plane) — REQUIRED -# Public URLs for the self-hosted Convex deployment. -# --------------------------------------------------------------------------- -CONVEX_URL=https://your-deployment.convex.cloud -CONVEX_SITE_URL=https://your-deployment.convex.site -SITE_URL=http://localhost:13100 -VITE_AUTH_URL=http://localhost:13100 -VITE_CONVEX_URL=https://your-deployment.convex.cloud -VITE_FLUE_URL=http://localhost:3583 -VITE_ZOPU_SERVER_URL=http://localhost:3590 - -# Self-hosted Convex origins used by convex/docker-compose.yml -CONVEX_CLOUD_ORIGIN=https://your-deployment.convex.cloud -CONVEX_SITE_ORIGIN=https://your-deployment.convex.site -CONVEX_INSTANCE_NAME=zopu-production -CONVEX_INSTANCE_SECRET= - -# --------------------------------------------------------------------------- -# 2. Model gateway — REQUIRED -# All model calls route through this OpenAI-compatible endpoint. -# --------------------------------------------------------------------------- -AGENT_MODEL_PROVIDER=cheaptricks -AGENT_MODEL_NAME=glm-5.2 -AGENT_MODEL_API=openai-completions -AGENT_MODEL_BASE_URL=https://ai.example.invalid/v1 -AGENT_MODEL_API_KEY=replace-with-model-gateway-key -AGENT_MODEL_CONTEXT_WINDOW=262000 -AGENT_MODEL_MAX_TOKENS=131072 - -# --------------------------------------------------------------------------- -# 3. AgentOS / Rivet Engine — REQUIRED for the execution runner -# The runner creates isolated worktrees from ZOPU_SOURCE_REPOSITORY and -# connects to AgentOS through the public engine endpoint. -# --------------------------------------------------------------------------- -RIVET_ENDPOINT=https://default:@rivet.example.com -RIVET_PUBLIC_ENDPOINT=https://default@rivet.example.com -RIVET_RUNNER_VERSION=1 -RIVET_WORKSPACE_TOKEN=replace-with-a-long-random-workspace-token -ZOPU_SOURCE_REPOSITORY=/opt/zopu-source -# --------------------------------------------------------------------------- -# 4. Zopu agent service (Flue) -# FLUE_DB_TOKEN authenticates the Flue persistence adapter. -# zopu-agent.service pins the Flue Node server to port 3583. -# --------------------------------------------------------------------------- -FLUE_DB_TOKEN=replace-with-long-random-token -AGENT_BACKEND_URL=https://zopu-agent.example.com - -# --------------------------------------------------------------------------- -# 5. Daemon identity -# --------------------------------------------------------------------------- -DAEMON_ID=zopu-dedicated -DAEMON_NAME=Zopu-Dedicated-Server -DAEMON_VERSION=0.0.0 -DAEMON_HEARTBEAT_MS=15000 -DAEMON_COMMAND_LEASE_MS=60000 - -# --------------------------------------------------------------------------- -# 7. Docker sandbox -# The zopu service user is added to the docker group during bootstrap. -# Orb sandboxes will use Docker for full-system isolation, but the Orb -# lane contract has not landed yet. Docker access is provisioned now so -# the boundary is ready; no Docker-backed sandbox code is wired today. -# --------------------------------------------------------------------------- -# No env vars required; Docker socket access is via group membership. - -# --------------------------------------------------------------------------- -# 8. Service authentication / secrets -# These tokens authenticate inter-service calls. Generate strong randoms. -# --------------------------------------------------------------------------- -# Better Auth / Convex JWT secret (if the agent service needs to mint tokens): -#AUTH_SECRET=replace-with-64-char-hex - -# --------------------------------------------------------------------------- -# 9. Private networking (Tailscale) — OPTIONAL -# When Tailscale is available, set the hostname for private DNS. -# --------------------------------------------------------------------------- -#TAILSCALE_HOSTNAME=zopu-runtime diff --git a/deploy/zopu-runtime/README.md b/deploy/zopu-runtime/README.md deleted file mode 100644 index 305909e..0000000 --- a/deploy/zopu-runtime/README.md +++ /dev/null @@ -1,306 +0,0 @@ -# Zopu Single-Node Runtime Deployment - -Deployment artifacts for the complete Zopu stack on a single Debian dedicated server: the React Router web app, a persistent self-hosted Convex control plane, the daemon (Bun/Effect + AgentOS/RivetKit), the Flue agent service, Docker Engine, and supporting infrastructure. - -## Architecture - -``` -┌─────────────────────────────────────────────────────────────┐ -│ Debian Dedicated Server │ -│ ~12 CPU cores · ~40 GB RAM · single-node │ -│ │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ -│ │ zopu-web │ │ zopu-agent │ │ Docker │ │ -│ │ (systemd) │ │ (systemd) │ │ Engine │ │ -│ │ │ │ │ │ │ │ -│ │ React Router │ │ Flue Node 22 │ │ Convex │ │ -│ │ :13100 │ │ server.mjs │ │ backend │ │ -│ │ │ │ :3583 │ │ :3210/:3211 │ │ -│ └──────┬───────┘ └──────┬───────┘ └──────────────┘ │ -│ │ │ │ -│ ┌──────▼───────┐ │ -│ │ zopu-daemon │ Bun + Effect + RivetKit :6420 │ -│ └──────────────┘ │ -│ │ -│ systemd timers: health-check (60s), docker-cleanup (daily) │ -│ cron: disk-monitor (daily 06:00) │ -│ ufw: deny-incoming, SSH + tailscale0 │ -│ Tailscale: optional private overlay │ -└─────────────────────────────────────────────────────────────┘ -``` - -### Single-node RivetKit topology - -The daemon calls `registry.start()` from `rivetkit`, which boots an **in-process RivetKit engine** (envoy mode) backed by a **native Rust sidecar** binary (`@rivet-dev/agentos-sidecar`, platform-resolved). The engine listens on `RIVET_ENDPOINT` (default `http://localhost:6420`). The daemon then calls `createClient(RIVET_ENDPOINT)` to connect back to its own in-process engine for actor dispatch. - -Evidence: RivetKit source `chunk-YDUQHING.js` line 4751 — `DEFAULT_ENDPOINT = "http://localhost:6420"`. The `Registry.start()` method calls `#startEnvoy()` → `runtime.serveRegistry()` for serverful mode (Mode A). The `createClient()` function reads `RIVET_ENDPOINT` env or defaults to the same `http://localhost:6420`. - -**No separate Rivet Engine process is required.** The engine, actor envoy, and sidecar all run inside the daemon process. A future multi-node deployment would externalize the engine, but that is out of scope. - -### Docker / Orb boundary - -Docker Engine is installed and the `zopu` service user is in the `docker` group. The daemon's systemd unit includes `SupplementaryGroups=docker`. However, **no Docker-backed sandbox code is currently wired**. The Orb sandbox lane contract has not landed; Docker access is provisioned now so the boundary is ready. The current agent uses the in-process AgentOS VM (Wasm/V8) sandbox, not Docker. - -## Files - -``` -deploy/zopu-runtime/ -├── bootstrap.sh # One-shot Debian installer -├── .env.template # Environment template (all groups documented) -├── README.md # This file (runbook) -├── systemd/ -│ ├── zopu-daemon.service # Daemon systemd unit -│ ├── zopu-agent.service # Agent systemd unit -│ ├── zopu-web.service # React Router web systemd unit -│ ├── zopu-health.service # Health check oneshot -│ ├── zopu-health.timer # Health check every 60s -│ ├── zopu-docker-cleanup.service -│ └── zopu-docker-cleanup.timer -├── scripts/ -│ ├── health-check.sh # TCP/process health probes -│ ├── update.sh # Update to branch or commit -│ ├── rollback.sh # Roll back to previous commit -│ ├── docker-cleanup.sh # Prune stopped containers/images/networks -│ └── disk-monitor.sh # Disk usage alerting -└── caddy/ - └── Caddyfile # Optional reverse proxy config (documentation) -``` - -## Fresh install - -```bash -# 1. SSH into the fresh Debian 12 server as root. - -# 2. Set environment overrides (optional): -export ZOPU_REPO_URL="ssh://git@git.openputer.com:2222/puter/zopu-code.git" -export ZOPU_REPO_BRANCH="dogfood/v0" -# export TAILSCALE_AUTHKEY="tskey-..." -# export TAILSCALE_HOSTNAME="zopu-runtime" - -# 3. Run the bootstrap script: -bash bootstrap.sh - -# 4. Edit .env with real values: -nano /opt/zopu/.env - -# 5. Start services: -systemctl start zopu-web zopu-daemon -sleep 3 -systemctl start zopu-agent - -# 6. Enable timers: -systemctl enable --now zopu-health.timer zopu-docker-cleanup.timer - -# 7. Verify: -/opt/zopu/deploy/zopu-runtime/scripts/health-check.sh -``` - -## Start / stop / restart - -```bash -# Start all services -systemctl start zopu-web zopu-daemon zopu-agent - -# Stop all services -systemctl stop zopu-agent zopu-daemon zopu-web - -# Restart (daemon first — it owns the RivetKit engine) -systemctl restart zopu-web zopu-daemon && sleep 3 && systemctl restart zopu-agent - -# Enable on boot -systemctl enable zopu-web zopu-daemon zopu-agent - -# Disable on boot -systemctl disable zopu-web zopu-daemon zopu-agent -``` - -## Log inspection - -All service logs go to journald with `SyslogIdentifier` tags. - -```bash -# Daemon logs (live follow) -journalctl -u zopu-daemon -f - -# Agent logs (live follow) -journalctl -u zopu-agent -f - -# Last 100 lines of daemon -journalctl -u zopu-daemon -n 100 - -# Logs since boot -journalctl -u zopu-daemon -b - -# Health check timer logs -journalctl -u zopu-health.service -n 50 - -# Docker cleanup logs -journalctl -u zopu-docker-cleanup.service -n 50 - -# Disk monitor logs (cron → file) -tail -100 /var/log/zopu/disk-monitor.log - -# All Zopu syslog identifiers -journalctl -t zopu-daemon -t zopu-agent --since "1 hour ago" -``` - -## Health checks - -```bash -# Manual health check (prints all probes) -/opt/zopu/deploy/zopu-runtime/scripts/health-check.sh - -# Quiet mode (exit code only) -/opt/zopu/deploy/zopu-runtime/scripts/health-check.sh --quiet - -# Check systemd timer is running -systemctl status zopu-health.timer -systemctl list-timers zopu-health.timer -``` - -The health check probes: - -1. `zopu-daemon` systemd unit is active -2. `zopu-agent` systemd unit is active -3. RivetKit engine port (default 6420) accepts TCP connections -4. Flue agent port (default 3583) accepts TCP connections -5. Docker daemon responds to `docker info` - -No HTTP health endpoints are assumed. Flue does not expose one by design (per Flue docs: "Flue does not add a health endpoint"). RivetKit's health route is internal to the registry runtime and not documented as publicly addressable on the engine endpoint. - -## Update to commit - -```bash -# Update to latest of dogfood/v0 (default) -/opt/zopu/deploy/zopu-runtime/scripts/update.sh - -# Update to a specific branch -/opt/zopu/deploy/zopu-runtime/scripts/update.sh dogfood/runtime-deploy - -# Update to a specific commit -/opt/zopu/deploy/zopu-runtime/scripts/update.sh abc123def456 -``` - -The update script: - -1. Records current HEAD to `.last-deployed-sha` -2. Fetches, resolves branch-or-commit, checks out -3. `bun install`, builds daemon and agent -4. Restarts daemon, waits, restarts agent -5. Runs health check; reports failure and rollback instructions - -## Rollback - -```bash -# Roll back to the previously deployed commit -/opt/zopu/deploy/zopu-runtime/scripts/rollback.sh - -# Roll back to a specific commit -/opt/zopu/deploy/zopu-runtime/scripts/rollback.sh abc123def456 -``` - -Rollback reads `.last-deployed-sha` (written by `update.sh`), checks out that commit, rebuilds, and restarts services. The pre-rollback SHA is saved to `.pre-rollback-sha` for re-rollback if needed. - -## Docker cleanup - -```bash -# Manual cleanup -/opt/zopu/deploy/zopu-runtime/scripts/docker-cleanup.sh - -# Check Docker disk usage -docker system df - -# Timer runs daily; check its schedule -systemctl list-timers zopu-docker-cleanup.timer -``` - -Cleanup prunes: - -- Stopped containers older than 24 hours -- Dangling (untagged) images -- Unused networks - -Named volumes and running containers are never removed. - -## Disk-space monitoring - -```bash -# Manual check -/opt/zopu/deploy/zopu-runtime/scripts/disk-monitor.sh - -# Custom threshold (90%) -/opt/zopu/deploy/zopu-runtime/scripts/disk-monitor.sh --warn-percent 90 -``` - -A cron job runs at 06:00 daily and writes to `/var/log/zopu/disk-monitor.log`. Default alert threshold is 80%. - -## Firewall and private networking - -The firewall (`ufw`) is deny-by-default: - -- SSH (port 22) is allowed on all interfaces -- All traffic on `tailscale0` is allowed (Tailscale private overlay) -- All other incoming traffic is denied - -The RivetKit engine (`:6420`) and Flue agent (`:3583`) ports are **not** exposed on public interfaces. Reachability options: - -1. **Tailscale** (recommended): bootstrap runs `ufw allow in on tailscale0` so all ports are reachable over the private overlay. Set `TAILSCALE_AUTHKEY` before running bootstrap to configure automatically. Other Tailscale-connected machines can reach the agent at `http://zopu-runtime:3583` and the engine at `http://zopu-runtime:6420`. -2. **Custom private interface**: if you have a non-Tailscale private network (e.g. a VLAN or wireguard interface), add an explicit rule: - ```bash - ufw allow in on eth1 # or your private interface name - ``` - Do NOT assume direct private IP access works by default — the deny-incoming policy blocks it until an interface-specific rule is added. -3. **Caddy** (optional): install Caddy and use the annotated Caddyfile in `caddy/` if you need TLS termination or a public ingress point. - -## Environment groups - -See [`.env.template`](./.env.template) for the full annotated template. The eight required groups: - -| Group | Variables | -| --- | --- | -| Convex | `CONVEX_URL`, `CONVEX_SITE_URL`, `SITE_URL` | -| Gitea | `GITEA_URL`, `GITEA_TOKEN` | -| Model gateway | `AGENT_MODEL_*` | -| AgentOS/RivetKit | `RIVET_ENDPOINT` (optional) | -| Zopu agent | `FLUE_DB_TOKEN` (`zopu-agent.service` sets port 3583) | -| Daemon | `DAEMON_ID`, `DAEMON_NAME`, `DAEMON_VERSION`, `DAEMON_HEARTBEAT_MS`, `DAEMON_COMMAND_LEASE_MS` | -| Docker sandbox | group membership (no env vars) | -| Service auth | `AUTH_SECRET` (if needed) | - -## Public single-node routes - -The checked-in Caddy example assumes Cloudflare Tunnel terminates TLS and sends the four Zopu hosts to Caddy on loopback: - -- `zopu.sai-onchain.me` → React Router web app on `127.0.0.1:13100` -- `zopu-api.sai-onchain.me` → Convex API on `127.0.0.1:3210` -- `zopu-site.sai-onchain.me` → Convex HTTP actions on `127.0.0.1:3211` -- `zopu-agent.sai-onchain.me` → Flue on `127.0.0.1:3583` - -Self-hosted Convex state lives in the `zopu-convex-data` Docker volume. Generate the CLI admin key after the backend is healthy: - -```bash -docker compose \ - --env-file /opt/zopu/.env \ - -f /opt/zopu/deploy/zopu-runtime/convex/docker-compose.yml \ - exec backend ./generate_admin_key.sh -``` - -## What is NOT deployed - -- **Kubernetes**: no container orchestration. -- **PostgreSQL for Rivet**: the in-process RivetKit engine uses its own storage; no external PostgreSQL is required. -- **Multi-node coordination**: single-node only. -- **Public administration endpoints**: no admin HTTP surface. -- **Secrets in source**: `.env` is never committed; `.env.template` contains only placeholder values. -- **Docker-backed Orb sandboxes**: Docker is installed and access is provisioned, but no Orb sandbox code is wired. This is a boundary prepared for the Orb lane, not a working feature. - -## Reproducibility - -The deployment does not require the developer's MacBook to remain online. Once bootstrap completes and `.env` is filled in: - -1. Services run under systemd with `Restart=always`. -2. Logs persist in journald. -3. Health checks run every 60 seconds via systemd timer. -4. Docker cleanup runs daily. -5. Disk usage is monitored daily. -6. Unattended-upgrades handles Debian security patches. diff --git a/deploy/zopu-runtime/bootstrap.sh b/deploy/zopu-runtime/bootstrap.sh deleted file mode 100755 index ec9d30e..0000000 --- a/deploy/zopu-runtime/bootstrap.sh +++ /dev/null @@ -1,290 +0,0 @@ -#!/usr/bin/env bash -# -# bootstrap.sh — One-shot installer for the Zopu single-node execution plane. -# -# Run as root on a fresh Debian 12 host: -# -# bash bootstrap.sh -# -# Environment overrides (set before running): -# ZOPU_REPO_URL — SSH clone URL (default: ssh://git@git.openputer.com:2222/puter/zopu-code.git) -# ZOPU_REPO_BRANCH — branch to deploy (default: dogfood/v0) -# ZOPU_INSTALL_DIR — install path (default: /opt/zopu) -# ZOPU_SERVICE_USER — system user (default: zopu) -# TAILSCALE_AUTHKEY — if set, configure Tailscale -# TAILSCALE_HOSTNAME — Tailscale hostname (default: zopu-runtime) -# -# Installs: Docker Engine, Node.js 22, Bun, clones the repo, runs bun install, builds the -# web app, daemon, and agent, creates a non-root service user, installs systemd -# units, and configures firewall/Tailscale defaults. - -set -euo pipefail - -# --------------------------------------------------------------------------- -# Configuration -# --------------------------------------------------------------------------- -REPO_URL="${ZOPU_REPO_URL:-ssh://git@git.openputer.com:2222/puter/zopu-code.git}" -REPO_BRANCH="${ZOPU_REPO_BRANCH:-dogfood/v0}" -INSTALL_DIR="${ZOPU_INSTALL_DIR:-/opt/zopu}" -SERVICE_USER="${ZOPU_SERVICE_USER:-zopu}" -DEPLOY_DIR="${INSTALL_DIR}/deploy/zopu-runtime" - -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' - -log() { echo -e "${GREEN}[bootstrap]${NC} $*"; } -warn() { echo -e "${YELLOW}[bootstrap]${NC} $*"; } -err() { echo -e "${RED}[bootstrap]${NC} $*" >&2; } - -# runuser is part of util-linux (essential on Debian) and always available. -# sudo is NOT assumed on minimal Debian installs. -run_as_service() { - runuser -u "$SERVICE_USER" -- "$@" -} - -# --------------------------------------------------------------------------- -# Pre-flight -# --------------------------------------------------------------------------- -if [[ "$EUID" -ne 0 ]]; then - err "This script must be run as root." - exit 1 -fi - -if [[ -f /etc/debian_version ]]; then - log "Detected Debian $(cat /etc/debian_version)" -else - warn "This script targets Debian 12. Other distributions may need manual adjustments." -fi - -# --------------------------------------------------------------------------- -# 1. System packages -# --------------------------------------------------------------------------- -log "Updating apt and installing base packages..." -apt-get update -y -apt-get install -y \ - ca-certificates \ - curl \ - gnupg \ - ufw \ - git \ - jq \ - netcat-openbsd \ - openssh-client \ - unattended-upgrades \ - rsyslog - -# --------------------------------------------------------------------------- -# 2. Docker Engine -# --------------------------------------------------------------------------- -if ! command -v docker &>/dev/null; then - log "Installing Docker Engine..." - install -m 0755 -d /etc/apt/keyrings - curl -fsSL https://download.docker.com/linux/debian/gpg \ - -o /etc/apt/keyrings/docker.asc - chmod a+r /etc/apt/keyrings/docker.asc - - echo \ - "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \ - https://download.docker.com/linux/debian \ - $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ - > /etc/apt/sources.list.d/docker.list - - apt-get update -y - apt-get install -y \ - docker-ce \ - docker-ce-cli \ - containerd.io \ - docker-buildx-plugin \ - docker-compose-plugin -else - log "Docker Engine already installed: $(docker --version)" -fi - -systemctl enable --now docker - -# --------------------------------------------------------------------------- -# 3. Node.js 22 (required by the Flue Node target) -# --------------------------------------------------------------------------- -NODE_MAJOR=$(node --version 2>/dev/null | sed -n 's/^v\([0-9][0-9]*\).*/\1/p') -if [[ -z "$NODE_MAJOR" || "$NODE_MAJOR" -lt 22 ]]; then - log "Installing Node.js 22..." - curl -fsSL https://deb.nodesource.com/setup_22.x -o /tmp/nodesource_setup.sh - bash /tmp/nodesource_setup.sh - apt-get install -y nodejs -else - log "Node.js already installed: $(node --version)" -fi - -# --------------------------------------------------------------------------- -# 4. Bun -# --------------------------------------------------------------------------- -if ! command -v bun &>/dev/null; then - log "Installing Bun..." - curl -fsSL https://bun.sh/install | bash - install -m 0755 /root/.bun/bin/bun /usr/local/bin/bun -else - log "Bun already installed: $(bun --version)" -fi - -# --------------------------------------------------------------------------- -# 5. Service user -# --------------------------------------------------------------------------- -if ! id "$SERVICE_USER" &>/dev/null; then - log "Creating service user: $SERVICE_USER" - useradd -r -m -d "/home/$SERVICE_USER" -s /bin/bash "$SERVICE_USER" -fi - -if ! id -nG "$SERVICE_USER" | grep -qw docker; then - usermod -aG docker "$SERVICE_USER" - log "Added $SERVICE_USER to docker group" -fi - -# --------------------------------------------------------------------------- -# 6. Clone or update repository -# --------------------------------------------------------------------------- -if [[ -d "$INSTALL_DIR/.git" ]]; then - log "Repository exists at $INSTALL_DIR, fetching latest..." - cd "$INSTALL_DIR" - git fetch origin - git checkout "$REPO_BRANCH" - git reset --hard "origin/$REPO_BRANCH" -else - log "Cloning $REPO_URL (branch $REPO_BRANCH) into $INSTALL_DIR..." - git clone --branch "$REPO_BRANCH" "$REPO_URL" "$INSTALL_DIR" - cd "$INSTALL_DIR" -fi - -# --------------------------------------------------------------------------- -# 6b. Hand ownership of the checkout to the service user -# --------------------------------------------------------------------------- -log "Setting ownership of $INSTALL_DIR to $SERVICE_USER..." -chown -R "$SERVICE_USER":"$SERVICE_USER" "$INSTALL_DIR" - -# --------------------------------------------------------------------------- -# 7. Install dependencies and build -# --------------------------------------------------------------------------- -log "Running bun install..." -run_as_service bun install - -log "Building web app..." -run_as_service bun run --cwd apps/web build - -log "Validating daemon production build..." -run_as_service bun run build:daemon - -log "Building agent service..." -run_as_service bun run build:agents - -# --------------------------------------------------------------------------- -# 8. Environment file -# --------------------------------------------------------------------------- -ENV_FILE="$INSTALL_DIR/.env" -if [[ ! -f "$ENV_FILE" ]]; then - log "Copying .env.template to .env — EDIT BEFORE STARTING SERVICES" - cp "$DEPLOY_DIR/.env.template" "$ENV_FILE" - chown "$SERVICE_USER":"$SERVICE_USER" "$ENV_FILE" - chmod 600 "$ENV_FILE" - warn "Edit $ENV_FILE with real values before starting services." -else - log ".env already exists at $ENV_FILE" - # Ensure correct ownership and permissions on existing .env - chown "$SERVICE_USER":"$SERVICE_USER" "$ENV_FILE" - chmod 600 "$ENV_FILE" -fi - -# --------------------------------------------------------------------------- -# 9. Persistent log directory -# --------------------------------------------------------------------------- -LOG_DIR="/var/log/zopu" -mkdir -p "$LOG_DIR" -chown "$SERVICE_USER":"$SERVICE_USER" "$LOG_DIR" - -# --------------------------------------------------------------------------- -# 10. Install systemd units (substitute placeholders) -# --------------------------------------------------------------------------- -log "Installing systemd units..." -for unit in zopu-web.service zopu-daemon.service zopu-agent.service \ - zopu-health.timer zopu-health.service \ - zopu-docker-cleanup.timer zopu-docker-cleanup.service; do - SRC="$DEPLOY_DIR/systemd/$unit" - DST="/etc/systemd/system/$unit" - if [[ -f "$SRC" ]]; then - sed \ - -e "s|__INSTALL_DIR__|$INSTALL_DIR|g" \ - -e "s|__SERVICE_USER__|$SERVICE_USER|g" \ - "$SRC" > "$DST" - log " installed $unit" - fi -done -systemctl daemon-reload - -# --------------------------------------------------------------------------- -# 11. Firewall (deny-by-default, explicit allow for Tailscale) -# --------------------------------------------------------------------------- -log "Configuring firewall..." -if ! ufw status 2>/dev/null | grep -q "Status: active"; then - ufw allow 22/tcp - ufw default deny incoming - ufw default allow outgoing - - # Allow all traffic on the Tailscale interface (if present) - # This lets the agent and engine ports be reached over the private overlay. - ufw allow in on tailscale0 || warn "tailscale0 not present yet; rule will activate when interface appears" - - ufw --force enable - log "Firewall enabled: SSH (22) allowed, tailscale0 allowed." - warn "Agent and RivetKit ports are NOT exposed on public interfaces." - warn "Reachability is via Tailscale (tailscale0) only." -else - log "Firewall already active. Ensuring tailscale0 rule..." - ufw allow in on tailscale0 2>/dev/null || true -fi - -# --------------------------------------------------------------------------- -# 12. Tailscale (optional) -# --------------------------------------------------------------------------- -if [[ -n "${TAILSCALE_AUTHKEY:-}" ]]; then - log "Installing and configuring Tailscale..." - if ! command -v tailscaled &>/dev/null; then - curl -fsSL https://tailscale.com/install.sh | sh - fi - tailscale up --authkey "$TAILSCALE_AUTHKEY" \ - --hostname "${TAILSCALE_HOSTNAME:-zopu-runtime}" \ - --accept-routes - log "Tailscale configured: $(tailscale ip -4 2>/dev/null || echo 'waiting for IP')" - - # Re-apply the tailscale0 firewall rule now that the interface exists - ufw allow in on tailscale0 2>/dev/null || true -else - warn "TAILSCALE_AUTHKEY not set — skipping Tailscale setup." - warn "Without Tailscale, services are reachable only via localhost." - warn "To use a private network interface, add an explicit UFW rule:" - warn " ufw allow in on " -fi - -# --------------------------------------------------------------------------- -# 13. Disk-space monitoring cron -# /etc/cron.d format REQUIRES a username field. -# --------------------------------------------------------------------------- -log "Installing disk-space monitor (daily at 06:00)..." -CRON_LINE="0 6 * * * ${SERVICE_USER} ${DEPLOY_DIR}/scripts/disk-monitor.sh --warn-percent 80 >> /var/log/zopu/disk-monitor.log 2>&1" -echo "$CRON_LINE" > /etc/cron.d/zopu-disk-monitor -chmod 644 /etc/cron.d/zopu-disk-monitor - -# --------------------------------------------------------------------------- -# Done -# --------------------------------------------------------------------------- -log "Bootstrap complete." -echo "" -echo "Next steps:" -echo " 1. Edit $ENV_FILE with real values" -echo " 2. Start services:" -echo " systemctl start zopu-web zopu-daemon zopu-agent" -echo " 3. Enable health monitoring:" -echo " systemctl enable --now zopu-health.timer zopu-docker-cleanup.timer" -echo " 4. Verify health:" -echo " $DEPLOY_DIR/scripts/health-check.sh" -echo "" -warn "Services are NOT started automatically. Edit .env first." diff --git a/deploy/zopu-runtime/caddy/Caddyfile b/deploy/zopu-runtime/caddy/Caddyfile deleted file mode 100644 index 152573e..0000000 --- a/deploy/zopu-runtime/caddy/Caddyfile +++ /dev/null @@ -1,23 +0,0 @@ -# Caddyfile — Public reverse proxy for the Zopu web + API. -# -# The web frontend (port 5173) is served at the root, Better Auth is proxied -# first-party under /api/auth, and Flue is mounted under /api/flue. - -zopu.cheaptricks.puter.wtf { - bind 135.181.82.179 2a01:4f9:c013:4a64::1 - encode zstd gzip - - handle /api/auth/* { - reverse_proxy https://befitting-dalmatian-161.convex.site { - header_up Host befitting-dalmatian-161.convex.site - header_up X-Forwarded-Host {host} - header_up X-Forwarded-Proto {scheme} - } - } - - handle_path /api/flue/* { - reverse_proxy 127.0.0.1:3585 - } - - reverse_proxy 127.0.0.1:5173 -} diff --git a/deploy/zopu-runtime/caddy/Caddyfile.production b/deploy/zopu-runtime/caddy/Caddyfile.production deleted file mode 100644 index 1a6e9b1..0000000 --- a/deploy/zopu-runtime/caddy/Caddyfile.production +++ /dev/null @@ -1,25 +0,0 @@ -{ - admin 127.0.0.1:2019 - auto_https off - http_port 8080 -} - -http://zopu.sai-onchain.me { - bind 127.0.0.1 - reverse_proxy 127.0.0.1:13100 -} - -http://zopu-api.sai-onchain.me { - bind 127.0.0.1 - reverse_proxy 127.0.0.1:3210 -} - -http://zopu-site.sai-onchain.me { - bind 127.0.0.1 - reverse_proxy 127.0.0.1:3211 -} - -http://zopu-agent.sai-onchain.me { - bind 127.0.0.1 - reverse_proxy 127.0.0.1:3583 -} diff --git a/deploy/zopu-runtime/cloudflared/config.production.yml b/deploy/zopu-runtime/cloudflared/config.production.yml deleted file mode 100644 index d3a9ac6..0000000 --- a/deploy/zopu-runtime/cloudflared/config.production.yml +++ /dev/null @@ -1,13 +0,0 @@ -tunnel: 9e54ac9c-c1a1-4583-be08-3b5f1acc7b65 -credentials-file: /root/.cloudflared/9e54ac9c-c1a1-4583-be08-3b5f1acc7b65.json - -ingress: - - hostname: zopu.sai-onchain.me - service: http://127.0.0.1:8080 - - hostname: zopu-api.sai-onchain.me - service: http://127.0.0.1:8080 - - hostname: zopu-site.sai-onchain.me - service: http://127.0.0.1:8080 - - hostname: zopu-agent.sai-onchain.me - service: http://127.0.0.1:8080 - - service: http_status:404 diff --git a/deploy/zopu-runtime/convex/docker-compose.yml b/deploy/zopu-runtime/convex/docker-compose.yml deleted file mode 100644 index 460ea49..0000000 --- a/deploy/zopu-runtime/convex/docker-compose.yml +++ /dev/null @@ -1,32 +0,0 @@ -services: - backend: - image: ghcr.io/get-convex/convex-backend:latest - container_name: zopu-convex - restart: unless-stopped - stop_grace_period: 10s - stop_signal: SIGINT - ports: - - "127.0.0.1:3210:3210" - - "127.0.0.1:3211:3211" - volumes: - - zopu-convex-data:/convex/data - environment: - CONVEX_CLOUD_ORIGIN: ${CONVEX_CLOUD_ORIGIN} - CONVEX_SITE_ORIGIN: ${CONVEX_SITE_ORIGIN} - DISABLE_BEACON: "true" - DISABLE_METRICS_ENDPOINT: "true" - DOCUMENT_RETENTION_DELAY: "172800" - INSTANCE_NAME: ${CONVEX_INSTANCE_NAME:-zopu-production} - INSTANCE_SECRET: ${CONVEX_INSTANCE_SECRET:-} - REDACT_LOGS_TO_CLIENT: "true" - RUST_LOG: ${CONVEX_RUST_LOG:-info} - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3210/version"] - interval: 5s - timeout: 3s - retries: 12 - start_period: 10s - -volumes: - zopu-convex-data: - name: zopu-convex-data diff --git a/deploy/zopu-runtime/runner/Dockerfile b/deploy/zopu-runtime/runner/Dockerfile deleted file mode 100644 index 9e96f73..0000000 --- a/deploy/zopu-runtime/runner/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -FROM oven/bun:1.3.14 AS bun - -FROM node:24-bookworm-slim - -COPY --from=bun /usr/local/bin/bun /usr/local/bin/bun - -RUN apt-get update \ - && apt-get install -y --no-install-recommends ca-certificates g++ git make python3 \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /opt/zopu-source - -COPY . . -RUN bun install --frozen-lockfile - -ENV AGENT_WORKSPACE_ROOT=/var/lib/zopu/workspaces -ENV BUN_EXECUTABLE=/usr/local/bin/bun -ENV DAEMON_ID=zopu-agentos-runner -ENV ZOPU_SOURCE_REPOSITORY=/opt/zopu-source - -VOLUME ["/var/lib/zopu/workspaces"] - -CMD ["bun", "packages/agents/src/runner.ts"] diff --git a/deploy/zopu-runtime/runner/docker-compose.yml b/deploy/zopu-runtime/runner/docker-compose.yml deleted file mode 100644 index 890fd6f..0000000 --- a/deploy/zopu-runtime/runner/docker-compose.yml +++ /dev/null @@ -1,26 +0,0 @@ -services: - zopu-agentos-runner: - build: - context: ../../.. - dockerfile: deploy/zopu-runtime/runner/Dockerfile - environment: - AGENT_MODEL_API: ${AGENT_MODEL_API} - AGENT_MODEL_API_KEY: ${AGENT_MODEL_API_KEY} - AGENT_MODEL_BASE_URL: ${AGENT_MODEL_BASE_URL} - AGENT_MODEL_CONTEXT_WINDOW: ${AGENT_MODEL_CONTEXT_WINDOW} - AGENT_MODEL_MAX_TOKENS: ${AGENT_MODEL_MAX_TOKENS} - AGENT_MODEL_NAME: ${AGENT_MODEL_NAME} - AGENT_MODEL_PROVIDER: ${AGENT_MODEL_PROVIDER} - CONVEX_URL: ${CONVEX_URL} - DAEMON_ID: zopu-agentos-runner - FLUE_DB_TOKEN: ${FLUE_DB_TOKEN} - RIVET_ENDPOINT: ${RIVET_ENDPOINT} - RIVET_PUBLIC_ENDPOINT: ${RIVET_PUBLIC_ENDPOINT} - RIVET_WORKSPACE_TOKEN: ${RIVET_WORKSPACE_TOKEN} - ZOPU_SOURCE_REPOSITORY: /opt/zopu-source - volumes: - - zopu-agentos-workspaces:/var/lib/zopu/workspaces - restart: unless-stopped - -volumes: - zopu-agentos-workspaces: diff --git a/deploy/zopu-runtime/scripts/disk-monitor.sh b/deploy/zopu-runtime/scripts/disk-monitor.sh deleted file mode 100755 index 225adb3..0000000 --- a/deploy/zopu-runtime/scripts/disk-monitor.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env bash -# -# disk-monitor.sh — Check disk usage and alert when above threshold. -# -# Usage: -# disk-monitor.sh # print usage, warn at 80% -# disk-monitor.sh --warn-percent 90 # custom threshold -# -# Requires GNU coreutils df (standard on Debian). Uses --output for -# deterministic column ordering regardless of locale. -# -# Exit code 0 if under threshold, 1 if at or above. - -set -euo pipefail - -WARN_PERCENT=80 - -while [[ $# -gt 0 ]]; do - case "$1" in - --warn-percent) - WARN_PERCENT="$2" - shift 2 - ;; - *) - echo "Unknown argument: $1" >&2 - exit 2 - ;; - esac -done - -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' - -EXIT_CODE=0 - -# Partitions to check: install root and /var (Docker data-root is often here) -PARTITIONS="${PARTITIONS:-/ /var}" - -for partition in $PARTITIONS; do - if [[ ! -d "$partition" ]]; then - continue - fi - # GNU df --output columns: pcent (Use%), size, avail, target (Mounted on) - read -r USAGE_PCT SIZE AVAIL MOUNT <<< "$(df -h --output=pcent,size,avail,target "$partition" | awk 'NR==2 {gsub(/%/,"",$1); print $1, $2, $3, $4}')" - - if [[ -z "${USAGE_PCT:-}" || ! "${USAGE_PCT:-}" =~ ^[0-9]+$ ]]; then - echo " [skip] Could not read usage for ${partition}" - continue - fi - - if [[ "$USAGE_PCT" -ge "$WARN_PERCENT" ]]; then - echo -e " ${RED}WARN${NC} ${MOUNT}: ${USAGE_PCT}% used (${AVAIL} avail of ${SIZE}) — threshold ${WARN_PERCENT}%" - EXIT_CODE=1 - elif [[ "$USAGE_PCT" -ge $((WARN_PERCENT - 10)) ]]; then - echo -e " ${YELLOW}NOTE${NC} ${MOUNT}: ${USAGE_PCT}% used (${AVAIL} avail of ${SIZE}) — approaching threshold" - else - echo -e " ${GREEN}OK${NC} ${MOUNT}: ${USAGE_PCT}% used (${AVAIL} avail of ${SIZE})" - fi -done - -exit "$EXIT_CODE" diff --git a/deploy/zopu-runtime/scripts/docker-cleanup.sh b/deploy/zopu-runtime/scripts/docker-cleanup.sh deleted file mode 100755 index 0c24207..0000000 --- a/deploy/zopu-runtime/scripts/docker-cleanup.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash -# -# docker-cleanup.sh — Remove stopped containers, dangling images, and unused networks. -# -# Safe to run via systemd timer (daily). Uses Docker's built-in pruning -# commands with conservative scope. -# -# Does NOT prune volumes. Named volumes may hold durable data and cannot be -# safely auto-pruned in a deployment that mixes stateful workloads. When the -# Orb lane lands with labeled resources, volume pruning can be scoped to -# Orb-managed labels (e.g. --filter label=org.openputer.orb). Until then, -# manage volumes manually. - -set -euo pipefail - -echo "[docker-cleanup] $(date -u +%Y-%m-%dT%H:%M:%SZ)" - -# Remove stopped containers older than 24 hours -echo "[docker-cleanup] Pruning stopped containers (>24h old)..." -docker container prune -f --filter "until=24h" - -# Remove dangling images (untagged intermediate layers only) -echo "[docker-cleanup] Pruning dangling images..." -docker image prune -f - -# Remove unused networks -echo "[docker-cleanup] Pruning unused networks..." -docker network prune -f - -# Volumes are intentionally NOT pruned. See header comment. - -# Show remaining disk usage -echo "[docker-cleanup] Docker disk usage:" -docker system df - -echo "[docker-cleanup] Done." diff --git a/deploy/zopu-runtime/scripts/health-check.sh b/deploy/zopu-runtime/scripts/health-check.sh deleted file mode 100755 index e3bb0b8..0000000 --- a/deploy/zopu-runtime/scripts/health-check.sh +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env bash -# -# health-check.sh — Probe the Zopu execution plane. -# -# Checks (TCP/process-level; no assumed HTTP health routes): -# 1. zopu-daemon systemd unit is active -# 2. zopu-agent systemd unit is active -# 3. RivetKit engine TCP port (RIVET_ENDPOINT, default 6420) accepts connections -# 4. Flue agent TCP port (PORT, default 3583) accepts connections -# 5. Docker daemon is reachable -# -# Flue does not expose a health endpoint by design. RivetKit's health route -# is internal to the registry runtime. We use TCP connection checks only. -# -# Usage: -# health-check.sh # print results -# health-check.sh --quiet # suppress output, exit 0 only if all healthy -# -# Exit codes: 0 = all healthy, 1 = one or more unhealthy - -set -euo pipefail - -QUIET=false -[[ "${1:-}" == "--quiet" ]] && QUIET=true - -GREEN='\033[0;32m' -RED='\033[0;31m' -NC='\033[0m' - -pass() { $QUIET || echo -e " ${GREEN}PASS${NC} $*"; } -fail() { echo -e " ${RED}FAIL${NC} $*" >&2; FAILURES=$((FAILURES + 1)); } - -FAILURES=0 - -# --------------------------------------------------------------------------- -# Load environment -# --------------------------------------------------------------------------- -ENV_FILE="${ENV_FILE:-/opt/zopu/.env}" -if [[ -f "$ENV_FILE" ]]; then - # shellcheck source=/dev/null - set -a - . "$ENV_FILE" - set +a -fi - -RIVET_PORT="6420" -if [[ -n "${RIVET_ENDPOINT:-}" ]]; then - RIVET_PORT=$(echo "$RIVET_ENDPOINT" | sed -n 's|.*://[^:]*:\([0-9]*\).*|\1|p') - [[ -z "$RIVET_PORT" ]] && RIVET_PORT="6420" -fi - -AGENT_PORT="${PORT:-3583}" - -# --------------------------------------------------------------------------- -# TCP probe helper: works on Debian (nc from netcat-openbsd) and macOS. -# Falls back to bash /dev/tcp if nc is unavailable. -# --------------------------------------------------------------------------- -tcp_probe() { - local host="$1" port="$2" - if command -v nc &>/dev/null; then - nc -z -w 5 "$host" "$port" 2>/dev/null - else - timeout 5 bash -c "echo > /dev/tcp/${host}/${port}" 2>/dev/null - fi -} - -# --------------------------------------------------------------------------- -# 1. Daemon systemd unit -# --------------------------------------------------------------------------- -if systemctl is-active --quiet zopu-daemon 2>/dev/null; then - pass "zopu-daemon service is active" -else - fail "zopu-daemon service is not active" -fi - -# --------------------------------------------------------------------------- -# 2. Agent systemd unit -# --------------------------------------------------------------------------- -if systemctl is-active --quiet zopu-agent 2>/dev/null; then - pass "zopu-agent service is active" -else - fail "zopu-agent service is not active" -fi - -# --------------------------------------------------------------------------- -# 3. RivetKit engine TCP port -# --------------------------------------------------------------------------- -if tcp_probe localhost "$RIVET_PORT"; then - pass "RivetKit engine port ${RIVET_PORT} is accepting connections" -else - fail "RivetKit engine port ${RIVET_PORT} is not accepting connections" -fi - -# --------------------------------------------------------------------------- -# 4. Flue agent TCP port -# --------------------------------------------------------------------------- -if tcp_probe localhost "$AGENT_PORT"; then - pass "Flue agent port ${AGENT_PORT} is accepting connections" -else - fail "Flue agent port ${AGENT_PORT} is not accepting connections" -fi - -# --------------------------------------------------------------------------- -# 5. Docker daemon -# --------------------------------------------------------------------------- -if docker info &>/dev/null; then - pass "Docker daemon is reachable" -else - fail "Docker daemon is not reachable" -fi - -# --------------------------------------------------------------------------- -# Summary -# --------------------------------------------------------------------------- -$QUIET || echo "" -if [[ "$FAILURES" -eq 0 ]]; then - $QUIET || echo -e "${GREEN}All checks passed.${NC}" - exit 0 -else - echo -e "${RED}${FAILURES} check(s) failed.${NC}" >&2 - exit 1 -fi diff --git a/deploy/zopu-runtime/scripts/rollback.sh b/deploy/zopu-runtime/scripts/rollback.sh deleted file mode 100755 index 6bd46eb..0000000 --- a/deploy/zopu-runtime/scripts/rollback.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env bash -# -# rollback.sh — Roll back to the previously deployed commit. -# -# Usage: -# rollback.sh # roll back to .last-deployed-sha -# rollback.sh # roll back to a specific commit -# -# .env is gitignored and is never touched by git operations. It survives -# updates and rollbacks unchanged. -# -# Must be run as root (uses runuser to build as the service user). - -set -euo pipefail - -INSTALL_DIR="${ZOPU_INSTALL_DIR:-/opt/zopu}" -SERVICE_USER="${ZOPU_SERVICE_USER:-zopu}" - -GREEN='\033[0;32m' -RED='\033[0;31m' -NC='\033[0m' - -log() { echo -e "${GREEN}[rollback]${NC} $*"; } -err() { echo -e "${RED}[rollback]${NC} $*" >&2; } - -run_as_service() { - runuser -u "$SERVICE_USER" -- "$@" -} - -cd "$INSTALL_DIR" - -# Determine target -ROLLBACK_SHA="${1:-}" -if [[ -z "$ROLLBACK_SHA" ]]; then - LAST_SHA_FILE="${INSTALL_DIR}/.last-deployed-sha" - if [[ ! -f "$LAST_SHA_FILE" ]]; then - err "No previous deployment recorded in ${LAST_SHA_FILE}." - err "Pass a commit SHA explicitly: rollback.sh " - exit 1 - fi - ROLLBACK_SHA=$(cat "$LAST_SHA_FILE") -fi - -# Validate commit exists -if ! git rev-parse --verify "${ROLLBACK_SHA}^{commit}" &>/dev/null; then - err "Commit ${ROLLBACK_SHA} does not exist in the local repository." - exit 1 -fi - -CURRENT_SHA=$(git rev-parse HEAD) -log "Current: ${CURRENT_SHA:0:12}" -log "Rolling back to: ${ROLLBACK_SHA:0:12}" - -# Save current state before rolling back (enables re-rollback) -echo "$CURRENT_SHA" > "${INSTALL_DIR}/.pre-rollback-sha" - -# Checkout target -git checkout "$ROLLBACK_SHA" - -# Restore ownership of the checkout to the service user after git operations -log "Setting ownership of checkout to $SERVICE_USER..." -chown -R "$SERVICE_USER":"$SERVICE_USER" "$INSTALL_DIR" - -# Confirm .env is intact and has correct permissions -if [[ -f "${INSTALL_DIR}/.env" ]]; then - log ".env preserved." - chmod 600 "${INSTALL_DIR}/.env" -else - err ".env is missing! Restore it from backup before starting services." -fi - -log "Running bun install..." -run_as_service bun install - -log "Building web app..." -run_as_service bun run --cwd apps/web build - -log "Validating daemon production build..." -run_as_service bun run build:daemon - -log "Building agent service..." -run_as_service bun run build:agents - -log "Restarting services..." -systemctl restart zopu-daemon -sleep 3 -systemctl restart zopu-agent -systemctl restart zopu-web -sleep 5 - -# Health check -HEALTH_SCRIPT="${INSTALL_DIR}/deploy/zopu-runtime/scripts/health-check.sh" -if [[ -x "$HEALTH_SCRIPT" ]]; then - log "Running health check..." - if "$HEALTH_SCRIPT"; then - log "Rollback complete and healthy." - else - err "Health check failed after rollback!" - err " journalctl -u zopu-daemon -n 50" - err " journalctl -u zopu-agent -n 50" - err " journalctl -u zopu-web -n 50" - exit 1 - fi -fi diff --git a/deploy/zopu-runtime/scripts/update.sh b/deploy/zopu-runtime/scripts/update.sh deleted file mode 100755 index df5cec1..0000000 --- a/deploy/zopu-runtime/scripts/update.sh +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env bash -# -# update.sh — Update the Zopu runtime to a specific branch or commit. -# -# Usage: -# update.sh # update to latest dogfood/v0 -# update.sh dogfood/v0 # update to latest of branch dogfood/v0 -# update.sh # checkout and build a specific commit -# -# The argument is treated as a branch name first; if no matching remote -# tracking branch exists it is treated as a commit SHA. -# -# .env is gitignored and is never touched by git operations. It survives -# updates and rollbacks unchanged. -# -# Must be run as root (uses runuser to build as the service user). - -set -euo pipefail - -INSTALL_DIR="${ZOPU_INSTALL_DIR:-/opt/zopu}" -SERVICE_USER="${ZOPU_SERVICE_USER:-zopu}" -TARGET="${1:-dogfood/v0}" - -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' - -log() { echo -e "${GREEN}[update]${NC} $*"; } -warn() { echo -e "${YELLOW}[update]${NC} $*"; } -err() { echo -e "${RED}[update]${NC} $*" >&2; } - -run_as_service() { - runuser -u "$SERVICE_USER" -- "$@" -} - -cd "$INSTALL_DIR" - -# Record current commit for rollback -CURRENT_SHA=$(git rev-parse HEAD) -log "Current HEAD: ${CURRENT_SHA:0:12}" -echo "$CURRENT_SHA" > "${INSTALL_DIR}/.last-deployed-sha" - -# Fetch all remotes -log "Fetching from origin..." -git fetch origin - -# Resolve target: branch first, then commit -if git show-ref --verify --quiet "refs/remotes/origin/${TARGET}"; then - log "Target is a branch: ${TARGET}" - git checkout -B "$TARGET" "origin/${TARGET}" -elif git rev-parse --verify "${TARGET}^{commit}" &>/dev/null; then - log "Target is a commit: ${TARGET:0:12}" - git checkout "$TARGET" -else - err "'${TARGET}' is not a known branch or valid commit." - err "Available branches: $(git branch -r | tr '\n' ' ')" - exit 1 -fi - -NEW_SHA=$(git rev-parse HEAD) -log "Now at: ${NEW_SHA:0:12}" - -# Restore ownership of the checkout to the service user after git operations -log "Setting ownership of checkout to $SERVICE_USER..." -chown -R "$SERVICE_USER":"$SERVICE_USER" "$INSTALL_DIR" - -# Confirm .env is intact and has correct permissions -if [[ -f "${INSTALL_DIR}/.env" ]]; then - log ".env preserved." - chmod 600 "${INSTALL_DIR}/.env" -else - err ".env is missing! Restore it from backup before starting services." -fi - -# Install and build -log "Running bun install..." -run_as_service bun install - -log "Building web app..." -run_as_service bun run --cwd apps/web build - -log "Validating daemon production build..." -run_as_service bun run build:daemon - -log "Building agent service..." -run_as_service bun run build:agents - -# Graceful restart: daemon first (owns RivetKit engine), then agent and web -log "Restarting services..." -systemctl restart zopu-daemon -sleep 3 -systemctl restart zopu-agent -systemctl restart zopu-web -sleep 5 - -# Health check -HEALTH_SCRIPT="${INSTALL_DIR}/deploy/zopu-runtime/scripts/health-check.sh" -if [[ -x "$HEALTH_SCRIPT" ]]; then - log "Running health check..." - if "$HEALTH_SCRIPT"; then - log "Update complete and healthy." - else - err "Health check failed after update!" - err " journalctl -u zopu-daemon -n 50" - err " journalctl -u zopu-agent -n 50" - err " journalctl -u zopu-web -n 50" - err "To rollback: ${INSTALL_DIR}/deploy/zopu-runtime/scripts/rollback.sh" - exit 1 - fi -else - log "Update complete. Verify manually." -fi diff --git a/deploy/zopu-runtime/systemd/zopu-agent.service b/deploy/zopu-runtime/systemd/zopu-agent.service deleted file mode 100644 index 39cc638..0000000 --- a/deploy/zopu-runtime/systemd/zopu-agent.service +++ /dev/null @@ -1,40 +0,0 @@ -[Unit] -Description=Zopu Agent Service (Flue Node server) -After=network-online.target zopu-daemon.service -Wants=network-online.target - -[Service] -Type=simple -User=__SERVICE_USER__ -Group=__SERVICE_USER__ -WorkingDirectory=__INSTALL_DIR__/packages/agents - -EnvironmentFile=__INSTALL_DIR__/.env -Environment=PORT=3583 - -# Flue's Node target requires Node built-ins such as node:sqlite. -# Listens on the service-pinned port 3583. -ExecStart=/usr/bin/node dist/server.mjs - -Restart=always -RestartSec=10 - -StandardOutput=journal -StandardError=journal -SyslogIdentifier=zopu-agent - -# Resource limits -MemoryMax=8G -CPUWeight=80 - -# Security hardening -NoNewPrivileges=true -ProtectSystem=full -ProtectHome=true -PrivateTmp=true - -# Docker socket access for Orb sandbox runtime (lives in agent process) -SupplementaryGroups=docker - -[Install] -WantedBy=multi-user.target diff --git a/deploy/zopu-runtime/systemd/zopu-daemon.service b/deploy/zopu-runtime/systemd/zopu-daemon.service deleted file mode 100644 index cf677f9..0000000 --- a/deploy/zopu-runtime/systemd/zopu-daemon.service +++ /dev/null @@ -1,42 +0,0 @@ -[Unit] -Description=Zopu Daemon (Bun/Effect + AgentOS/RivetKit) -After=network-online.target docker.service -Wants=network-online.target - -[Service] -Type=simple -User=__SERVICE_USER__ -Group=__SERVICE_USER__ -WorkingDirectory=__INSTALL_DIR__ - -EnvironmentFile=__INSTALL_DIR__/.env -Environment=HOME=/var/lib/zopu -StateDirectory=zopu - -# RivetKit in-process engine: envoy mode (serverful). -# Run the Bun source entrypoint so AgentOS software assets retain their real -# node_modules paths; Bun compiled executables do not embed .aospkg files. -ExecStart=/usr/local/bin/bun --env-file=__INSTALL_DIR__/.env apps/daemon/src/index.ts - -Restart=always -RestartSec=10 - -StandardOutput=journal -StandardError=journal -SyslogIdentifier=zopu-daemon - -# Resource limits (12-core, 40 GB host) -MemoryMax=8G -CPUWeight=100 - -# Security hardening -NoNewPrivileges=true -ProtectSystem=full -ProtectHome=true -PrivateTmp=true - -# Docker socket access (for future Orb sandboxes; group membership required) -SupplementaryGroups=docker - -[Install] -WantedBy=multi-user.target diff --git a/deploy/zopu-runtime/systemd/zopu-docker-cleanup.service b/deploy/zopu-runtime/systemd/zopu-docker-cleanup.service deleted file mode 100644 index 0c37668..0000000 --- a/deploy/zopu-runtime/systemd/zopu-docker-cleanup.service +++ /dev/null @@ -1,7 +0,0 @@ -[Unit] -Description=Zopu Docker Cleanup -After=docker.service - -[Service] -Type=oneshot -ExecStart=__INSTALL_DIR__/deploy/zopu-runtime/scripts/docker-cleanup.sh diff --git a/deploy/zopu-runtime/systemd/zopu-docker-cleanup.timer b/deploy/zopu-runtime/systemd/zopu-docker-cleanup.timer deleted file mode 100644 index 674a3ec..0000000 --- a/deploy/zopu-runtime/systemd/zopu-docker-cleanup.timer +++ /dev/null @@ -1,9 +0,0 @@ -[Unit] -Description=Zopu Docker Cleanup (daily) - -[Timer] -OnCalendar=daily -Persistent=true - -[Install] -WantedBy=timers.target diff --git a/deploy/zopu-runtime/systemd/zopu-health.service b/deploy/zopu-runtime/systemd/zopu-health.service deleted file mode 100644 index eeaca41..0000000 --- a/deploy/zopu-runtime/systemd/zopu-health.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Zopu Health Check - -[Service] -Type=oneshot -User=__SERVICE_USER__ -EnvironmentFile=__INSTALL_DIR__/.env -# health-check.sh reads ENV_FILE to find the .env to source. Set it to the -# install path so custom install dirs work correctly. -Environment=ENV_FILE=__INSTALL_DIR__/.env -ExecStart=__INSTALL_DIR__/deploy/zopu-runtime/scripts/health-check.sh --quiet diff --git a/deploy/zopu-runtime/systemd/zopu-health.timer b/deploy/zopu-runtime/systemd/zopu-health.timer deleted file mode 100644 index a2092af..0000000 --- a/deploy/zopu-runtime/systemd/zopu-health.timer +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=Zopu Health Check (every 60s) - -[Timer] -OnBootSec=30 -OnUnitActiveSec=60 -AccuracySec=5 - -[Install] -WantedBy=timers.target diff --git a/deploy/zopu-runtime/systemd/zopu-web.service b/deploy/zopu-runtime/systemd/zopu-web.service deleted file mode 100644 index 1f74557..0000000 --- a/deploy/zopu-runtime/systemd/zopu-web.service +++ /dev/null @@ -1,26 +0,0 @@ -[Unit] -Description=Zopu Web -After=network-online.target docker.service -Wants=network-online.target - -[Service] -Type=simple -User=__SERVICE_USER__ -Group=__SERVICE_USER__ -WorkingDirectory=__INSTALL_DIR__/apps/web -EnvironmentFile=__INSTALL_DIR__/.env -Environment=HOST=127.0.0.1 -Environment=PORT=13100 -ExecStart=/usr/local/bin/bun run start -Restart=always -RestartSec=5 -StandardOutput=journal -StandardError=journal -SyslogIdentifier=zopu-web -NoNewPrivileges=true -ProtectSystem=full -ProtectHome=true -PrivateTmp=true - -[Install] -WantedBy=multi-user.target