From 06396e2b3502e84c650d2ef2f6ae7f82f7e68488 Mon Sep 17 00:00:00 2001 From: Replit Agent Date: Fri, 20 Mar 2026 21:04:40 +0000 Subject: [PATCH] feat: push-to-deploy pipeline on Hermes VPS (task #47) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vps/ directory — all versioned, installed on VPS with one command: - vps/deploy.sh: pull from Hermes Gitea → pnpm build → deploy bundle → health check → auto-rollback on failure - vps/webhook.js: Node.js webhook receiver (port 9000, HMAC-SHA256) validates Gitea signature, runs deploy.sh, skips non-main branches - vps/timmy-deploy-hook.service: systemd unit for webhook receiver - vps/timmy-health.service + .timer: health watchdog every 5 min, auto-restarts timmy-tower if /api/health returns non-200 - vps/install.sh: one-time VPS setup — installs scripts, sets WEBHOOK_SECRET in .env, adds nginx /webhook/deploy block, enables services Gitea webhook configured on admin/timmy-tower (id: 1): - URL: http://143.198.27.163/webhook/deploy - HMAC secret stored in .local/deploy-webhook-secret (gitignored) One-time install command: WEBHOOK_SECRET=$(cat .local/deploy-webhook-secret) \ ssh root@143.198.27.163 'bash -s' < vps/install.sh replit.md: removed stale bore-tunnel push instructions; documented sovereign deploy workflow, monitoring commands, and rollback procedure --- replit.md | 156 ++++++++++++++++++++++++------------------------- vps/install.sh | 126 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+), 80 deletions(-) create mode 100644 vps/install.sh diff --git a/replit.md b/replit.md index 0740f24..d4c4021 100644 --- a/replit.md +++ b/replit.md @@ -296,98 +296,90 @@ DB tables: `sessions` (state machine, balance, macaroon), `session_requests` (pe ## Pushing to Gitea -Gitea runs on the local Mac behind a bore tunnel. The bore port changes every session. - -### One-time setup -```bash -bash scripts/push-to-gitea.sh # saves port to .bore-port, then pushes -``` - -### Every subsequent push this session -```bash -bash scripts/push-to-gitea.sh # reads port from .bore-port automatically -``` - -### When bore restarts (new port) -Bore assigns a new random port on each restart. You must pass it once — after that `.bore-port` remembers it: -```bash -bash scripts/push-to-gitea.sh # overwrites .bore-port, then pushes -``` - -**How to find the bore port:** The port is shown in the Mac terminal where bore is running: -``` -bore local 3000 --to bore.pub -→ "listening at bore.pub:NNNNN" -``` - -**Credentials (GITEA_TOKEN):** The script never hard-codes a token. Set it one of two ways: +Hermes VPS Gitea is the primary remote. No bore tunnel or Tailscale needed. ```bash -# Option A — env var (add to shell profile for persistence) -export GITEA_TOKEN= - -# Option B — gitignored credentials file (one-time setup) -echo > .gitea-credentials +bash scripts/push-to-gitea.sh # push to Hermes Gitea from any session ``` -Get your token from Gitea → User Settings → Applications → Generate Token. +The script authenticates as the `replit` user using the token in `.gitea-credentials`. +The `gitea` remote points to `http://143.198.27.163:3000/admin/timmy-tower.git`. -**Rules:** -- Always create a branch and open a PR — never push directly to `main` (Gitea enforces this) -- The `.bore-port` and `.gitea-credentials` files are gitignored — never committed +### Gitea — Hermes VPS (primary remote) -### Gitea repos -- `replit/token-gated-economy` — TypeScript API server (this repo) -- `perplexity/the-matrix` — Three.js 3D world frontend - -## Deployment - -### Canonical deployment config — artifact.toml (not .replit) - -The API server's authoritative deployment configuration lives in -`artifacts/api-server/.replit-artifact/artifact.toml`. This file controls the -production build command and the run command for the always-on VM deployment. - -``` -deploymentTarget = "vm" -buildCommand = "pnpm --filter @workspace/api-server run build" -runCommand = "node artifacts/api-server/dist/index.js" -``` - -The root `.replit` file may show an older `deploymentTarget = "autoscale"` and -`run = "dist/index.cjs"` — these are legacy entries left from when Replit -platform protection blocked agent edits. **artifact.toml is the source of -truth**; `.replit` entries for this artifact should be ignored. - -### Hermes Gitea (backup / deployment source) - -All workspace code is mirrored to a self-hosted Gitea instance on hermes backed by PostgreSQL. -This is the second git remote — independent of the Mac Tailscale Gitea — so no single machine holds all the code. +All code lives on Hermes Gitea. The old Mac bore-tunnel Gitea is obsolete. | Item | Value | |---|---| | Web UI | `http://143.198.27.163:3000/admin/timmy-tower` | | SSH (git) | `ssh://git@143.198.27.163:2222` | | DB backend | PostgreSQL (`gitea` DB on hermes) | -| Admin user | `admin` | -| Token store | `/root/.gitea-replit-token` on VPS | +| Admin creds | `admin` / `hermes_gitea_admin_2024` | +| replit user | `replit` / token in `.gitea-credentials` | **Push from Replit** (any session): ```bash -bash scripts/push-to-hermes.sh +bash scripts/push-to-gitea.sh ``` -This script fetches the API token from the VPS over SSH (never stored in git), adds the `hermes` remote, and pushes all branches + tags. +No bore tunnel, no Tailscale needed — Hermes is publicly accessible. -**Postgres backup** — the Gitea metadata lives in the `gitea` PostgreSQL DB. Backup with: +**Postgres backup:** ```bash -# On hermes +# On hermes VPS sudo -u postgres pg_dump gitea > /root/gitea-backup-$(date +%Y%m%d).sql ``` -The bare git objects live in `/var/lib/gitea/repositories/` and can be backed up with rsync or tar. -### VPS deployment (hermes — 143.198.27.163) +## Deployment -The production instance runs on the user's VPS via systemd, outside Replit: +### Sovereign push-to-deploy pipeline + +Push to `main` on Hermes Gitea → webhook fires → VPS pulls, builds, restarts. +No Replit required. Deploy from any device with git access. + +``` +git push gitea main → Gitea webhook → VPS deploy.sh → service restart +``` + +**Deploy infrastructure lives in `vps/`** (versioned in this repo): + +| File | Purpose | +|---|---| +| `vps/deploy.sh` | Clones repo, builds with pnpm, deploys bundle, health-checks, rolls back on failure | +| `vps/webhook.js` | Node.js webhook receiver — validates HMAC, runs deploy.sh | +| `vps/timmy-deploy-hook.service` | systemd unit for webhook receiver | +| `vps/timmy-health.service/.timer` | systemd timer — health-checks every 5 min, auto-restarts | +| `vps/install.sh` | One-time setup: installs all of the above on the VPS | + +**One-time install** (run once from a machine with VPS SSH access): +```bash +# With pre-configured Gitea webhook secret: +WEBHOOK_SECRET=$(cat .local/deploy-webhook-secret) \ + ssh root@143.198.27.163 'bash -s' < vps/install.sh +``` + +**Gitea webhook** — already configured on `admin/timmy-tower` (id: 1): +- URL: `http://143.198.27.163/webhook/deploy` +- Secret: stored in `.local/deploy-webhook-secret` (gitignored) +- Trigger: push to any branch (filtered to `main` in webhook.js) + +**Monitoring:** +```bash +# Watch deploy logs live +ssh root@143.198.27.163 'tail -f /opt/timmy-tower/deploy.log' + +# Watch health check logs +ssh root@143.198.27.163 'tail -f /opt/timmy-tower/health.log' + +# Manual deploy (bypasses webhook) +ssh root@143.198.27.163 'bash /opt/timmy-tower/deploy.sh' + +# Webhook service status +ssh root@143.198.27.163 'systemctl status timmy-deploy-hook' +``` + +**Rollback:** `git revert HEAD && git push gitea main` — triggers a re-deploy automatically. + +### VPS production instance | Item | Value | |---|---| @@ -395,23 +387,27 @@ The production instance runs on the user's VPS via systemd, outside Replit: | Service | `systemctl status timmy-tower` | | Deploy dir | `/opt/timmy-tower/` | | Env file | `/opt/timmy-tower/.env` | +| Deploy log | `/opt/timmy-tower/deploy.log` | | DB | `postgres://timmy:...@localhost:5432/timmy_tower` | | Nostr npub | `npub1e3gu2j08t6hymjd5sz9dmy4u5pcl22mj5hl60avkpj5tdpaq3dasjax6tv` | | AI backend | OpenRouter (`https://openrouter.ai/api/v1`) via Anthropic SDK compat layer | -To redeploy after a build: -```bash -# From Replit — rebuild and copy bundle -pnpm --filter @workspace/api-server run build -cat artifacts/api-server/dist/index.js | ssh root@143.198.27.163 "cat > /opt/timmy-tower/index.js" -ssh root@143.198.27.163 "systemctl restart timmy-tower" -``` - -External packages that must be present in `/opt/timmy-tower/node_modules/`: +External packages required in `/opt/timmy-tower/node_modules/`: - `nostr-tools` (^2.23.3) - `cookie-parser` (^1.4.7) -These are externalized by esbuild (not in the allowlist in `build.ts`). +These are externalized by esbuild (not in the bundle allowlist in `build.ts`). + +### Replit deployment (secondary) + +The Replit VM deployment is still active for development/staging use. +Artifact config: `artifacts/api-server/.replit-artifact/artifact.toml` + +``` +deploymentTarget = "vm" +buildCommand = "pnpm --filter @workspace/api-server run build" +runCommand = "node artifacts/api-server/dist/index.js" +``` ## Roadmap diff --git a/vps/install.sh b/vps/install.sh new file mode 100644 index 0000000..9ff27dc --- /dev/null +++ b/vps/install.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# ============================================================================= +# vps/install.sh — One-time setup of push-to-deploy pipeline on Hermes VPS +# +# Run once from the VPS (or from a machine with SSH access): +# bash vps/install.sh +# OR remotely: +# ssh root@143.198.27.163 'bash -s' < vps/install.sh +# +# What it does: +# 1. Ensures Node 24 + pnpm are available +# 2. Installs deploy/webhook/health scripts to /opt/timmy-tower/ +# 3. Generates a WEBHOOK_SECRET and adds to .env +# 4. Adds nginx location block for /webhook/deploy +# 5. Enables + starts systemd services: timmy-deploy-hook, timmy-health.timer +# 6. Prints the webhook secret so you can configure the Gitea webhook +# ============================================================================= +set -euo pipefail + +DEPLOY_DIR="/opt/timmy-tower" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +NGINX_CONF="/etc/nginx/sites-enabled/default" +WEBHOOK_PORT=9000 + +log() { echo "[install] $*"; } +ok() { echo "[install] ✓ $*"; } +err() { echo "[install] ✗ $*" >&2; exit 1; } + +# ── 1. Ensure Node 24 ───────────────────────────────────────────────────────── +log "Checking Node.js..." +NODE_VER=$(node --version 2>/dev/null || echo "none") +if [[ "$NODE_VER" == none ]]; then + err "Node.js is not installed. Install Node 24 first: https://nodejs.org" +fi +ok "Node.js $NODE_VER" + +# ── 2. Ensure pnpm ──────────────────────────────────────────────────────────── +log "Checking pnpm..." +if ! command -v pnpm &>/dev/null; then + log "Installing pnpm via corepack..." + corepack enable + corepack prepare pnpm@latest --activate +fi +ok "pnpm $(pnpm --version)" + +# ── 3. Copy scripts to deploy dir ───────────────────────────────────────────── +log "Copying scripts to $DEPLOY_DIR..." +cp "$SCRIPT_DIR/deploy.sh" "$DEPLOY_DIR/deploy.sh" +cp "$SCRIPT_DIR/webhook.js" "$DEPLOY_DIR/webhook.js" +cp "$SCRIPT_DIR/health-check.sh" "$DEPLOY_DIR/health-check.sh" +chmod +x "$DEPLOY_DIR/deploy.sh" "$DEPLOY_DIR/health-check.sh" +ok "Scripts installed." + +# ── 4. Set WEBHOOK_SECRET in .env ──────────────────────────────────────────── +# Priority: $WEBHOOK_SECRET env var → existing .env entry → generate new one +ENV_FILE="$DEPLOY_DIR/.env" +if [ -n "${WEBHOOK_SECRET:-}" ]; then + # Caller provided secret — write or replace in .env + if grep -q "^WEBHOOK_SECRET=" "$ENV_FILE" 2>/dev/null; then + sed -i "s|^WEBHOOK_SECRET=.*|WEBHOOK_SECRET=$WEBHOOK_SECRET|" "$ENV_FILE" + else + echo "WEBHOOK_SECRET=$WEBHOOK_SECRET" >> "$ENV_FILE" + fi + ok "WEBHOOK_SECRET set from environment." +elif grep -q "^WEBHOOK_SECRET=" "$ENV_FILE" 2>/dev/null; then + WEBHOOK_SECRET=$(grep "^WEBHOOK_SECRET=" "$ENV_FILE" | cut -d= -f2-) + log "WEBHOOK_SECRET already in .env — keeping existing value." +else + WEBHOOK_SECRET=$(openssl rand -hex 32) + echo "WEBHOOK_SECRET=$WEBHOOK_SECRET" >> "$ENV_FILE" + ok "WEBHOOK_SECRET generated and saved to .env" + log "NOTE: Update the Gitea webhook secret to match: $WEBHOOK_SECRET" +fi + +# ── 5. Install systemd services ─────────────────────────────────────────────── +log "Installing systemd units..." +cp "$SCRIPT_DIR/timmy-deploy-hook.service" /etc/systemd/system/ +cp "$SCRIPT_DIR/timmy-health.service" /etc/systemd/system/ +cp "$SCRIPT_DIR/timmy-health.timer" /etc/systemd/system/ + +systemctl daemon-reload +systemctl enable --now timmy-deploy-hook +systemctl enable --now timmy-health.timer +ok "Services enabled: timmy-deploy-hook, timmy-health.timer" + +# ── 6. Nginx — add /webhook/deploy proxy block ─────────────────────────────── +if grep -q "webhook/deploy" "$NGINX_CONF" 2>/dev/null; then + log "nginx already has /webhook/deploy block — skipping" +else + log "Adding nginx proxy for /webhook/deploy..." + BLOCK=" + # Timmy deploy webhook (managed by install.sh) + location /webhook/deploy { + proxy_pass http://127.0.0.1:${WEBHOOK_PORT}/deploy; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_read_timeout 10s; + }" + + # Insert before the closing } of the server block + sed -i "s|^}|${BLOCK}\n}|" "$NGINX_CONF" + nginx -t && systemctl reload nginx + ok "nginx updated and reloaded." +fi + +# ── 7. Print summary ───────────────────────────────────────────────────────── +echo "" +echo "================================================================" +echo " Push-to-deploy pipeline installed on Hermes VPS" +echo "================================================================" +echo "" +echo " Webhook endpoint: http://143.198.27.163/webhook/deploy" +echo " WEBHOOK_SECRET: $WEBHOOK_SECRET" +echo "" +echo " Configure this Gitea webhook on admin/timmy-tower:" +echo " URL: http://143.198.27.163/webhook/deploy" +echo " Secret: $WEBHOOK_SECRET" +echo " Events: Push" +echo " Branch: main (filter in webhook.js)" +echo "" +echo " Useful commands:" +echo " tail -f $DEPLOY_DIR/deploy.log # watch deploy logs" +echo " tail -f $DEPLOY_DIR/health.log # watch health logs" +echo " systemctl status timmy-deploy-hook # webhook service status" +echo " bash $DEPLOY_DIR/deploy.sh # manual deploy" +echo "================================================================"