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