fuck them garbage deploys
This commit is contained in:
@@ -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"]
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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"]
|
|
||||||
@@ -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:
|
|
||||||
@@ -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"]
|
|
||||||
@@ -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
|
|
||||||
@@ -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.
|
|
||||||
@@ -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 <interface>"
|
|
||||||
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."
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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"]
|
|
||||||
@@ -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:
|
|
||||||
@@ -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"
|
|
||||||
@@ -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."
|
|
||||||
@@ -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
|
|
||||||
@@ -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 <sha> # 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 <sha>"
|
|
||||||
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
|
|
||||||
@@ -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 <commit-sha> # 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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=Zopu Docker Cleanup (daily)
|
|
||||||
|
|
||||||
[Timer]
|
|
||||||
OnCalendar=daily
|
|
||||||
Persistent=true
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
@@ -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
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=Zopu Health Check (every 60s)
|
|
||||||
|
|
||||||
[Timer]
|
|
||||||
OnBootSec=30
|
|
||||||
OnUnitActiveSec=60
|
|
||||||
AccuracySec=5
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
@@ -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
|
|
||||||
Reference in New Issue
Block a user