Add LXC deploy script, Gitea Actions workflow and deploy docs
deploy / deploy (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
name: deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
# Requires a Gitea act runner with network access to the LXC host.
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Configure SSH
|
||||||
|
run: |
|
||||||
|
install -m 600 /dev/null ~/.ssh/id_ed25519
|
||||||
|
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
|
||||||
|
echo "${{ secrets.DEPLOY_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
- name: Deploy
|
||||||
|
env:
|
||||||
|
SSH_HOST: ${{ secrets.DEPLOY_HOST }} # e.g. deploy@10.0.0.42
|
||||||
|
SSH_PORT: ${{ secrets.DEPLOY_PORT }} # e.g. 2222
|
||||||
|
DEPLOY_DIR: ${{ secrets.DEPLOY_DIR }} # e.g. /opt/ai-rendezvous
|
||||||
|
DEPLOY_MODE: ${{ secrets.DEPLOY_MODE }} # docker | systemd
|
||||||
|
SERVICE_NAME: ${{ secrets.DEPLOY_SERVICE }} # systemd mode only
|
||||||
|
HEALTH_URL: ${{ secrets.DEPLOY_HEALTH_URL }} # e.g. http://127.0.0.1:3000/health
|
||||||
|
run: ./scripts/deploy.sh
|
||||||
@@ -4,3 +4,4 @@ data/
|
|||||||
*.db
|
*.db
|
||||||
*.db-*
|
*.db-*
|
||||||
.env
|
.env
|
||||||
|
deploy.env
|
||||||
|
|||||||
@@ -158,7 +158,8 @@ curl -H 'authorization: Bearer <token-a>' http://localhost:3000/api/rooms/<room>
|
|||||||
```
|
```
|
||||||
|
|
||||||
Full API reference: [docs/API.md](docs/API.md). MCP setup:
|
Full API reference: [docs/API.md](docs/API.md). MCP setup:
|
||||||
[docs/MCP.md](docs/MCP.md).
|
[docs/MCP.md](docs/MCP.md). Deploying to your own LXC via Gitea Actions or
|
||||||
|
webhooks: [docs/DEPLOY.md](docs/DEPLOY.md).
|
||||||
|
|
||||||
## Negotiation protocol in one paragraph
|
## Negotiation protocol in one paragraph
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=AI Rendezvous server
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=deploy
|
||||||
|
WorkingDirectory=/opt/ai-rendezvous
|
||||||
|
ExecStart=/usr/bin/node packages/server/dist/src/index.js
|
||||||
|
Environment=PORT=3000
|
||||||
|
Environment=DB_PATH=/opt/ai-rendezvous/data/rendezvous.db
|
||||||
|
# Set to the public URL agents will see in invite links:
|
||||||
|
Environment=BASE_URL=http://localhost:3000
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=3
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# Deploy to LXC
|
||||||
|
|
||||||
|
One script deploys everywhere: [`scripts/deploy.sh`](../scripts/deploy.sh).
|
||||||
|
It SSHes to the LXC host, does `git fetch + reset --hard origin/main`, rebuilds
|
||||||
|
and restarts the service, then hits the health check. It never keeps secrets
|
||||||
|
in the repo — configuration comes from env vars, a gitignored `deploy.env`, or
|
||||||
|
Gitea secrets.
|
||||||
|
|
||||||
|
## Target host (one-time setup on the LXC)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# as root on the LXC container:
|
||||||
|
apt install -y git # plus docker+compose, or node 22 for systemd mode
|
||||||
|
useradd -m -s /bin/bash deploy || true
|
||||||
|
|
||||||
|
# checkout (read-only deploy key tied to this repo, no push rights):
|
||||||
|
sudo -u deploy bash -c '
|
||||||
|
git clone git@git.omniagency.ru:2222/a.andreev/AI-Rendezvous.git /opt/ai-rendezvous'
|
||||||
|
chown -R deploy:deploy /opt/ai-rendezvous
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate a read-only deploy key on the LXC (`sudo -u deploy ssh-keygen -t
|
||||||
|
ed25519`) and add the public key as a **read-only** deploy key in Gitea
|
||||||
|
(repo Settings → Deploy keys, *without* write access).
|
||||||
|
|
||||||
|
Choose how the service runs:
|
||||||
|
|
||||||
|
- **docker mode (default):** `docker compose up -d` is used on every deploy.
|
||||||
|
Prepare `.env` with `BASE_URL=https://rendezvous.example` next to
|
||||||
|
`docker-compose.yml` on the LXC.
|
||||||
|
- **systemd mode:** install
|
||||||
|
[`ai-rendezvous.service`](../contrib/ai-rendezvous.service) and set
|
||||||
|
`DEPLOY_MODE=systemd`.
|
||||||
|
|
||||||
|
## Option A — Gitea Actions (recommended)
|
||||||
|
|
||||||
|
Add repository secrets (Settings → Actions → Secrets):
|
||||||
|
|
||||||
|
| Secret | Example | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `DEPLOY_HOST` | `deploy@10.0.0.42` | user@lxc-container |
|
||||||
|
| `DEPLOY_PORT` | `22` | |
|
||||||
|
| `DEPLOY_DIR` | `/opt/ai-rendezvous` | |
|
||||||
|
| `DEPLOY_MODE` | `docker` | or `systemd` |
|
||||||
|
| `DEPLOY_SERVICE` | `ai-rendezvous` | systemd mode only |
|
||||||
|
| `DEPLOY_HEALTH_URL` | `http://127.0.0.1:3000/health` | checked via SSH on the LXC |
|
||||||
|
| `DEPLOY_SSH_KEY` | *(private key)* | key of a user allowed to deploy (not the read-only one) |
|
||||||
|
| `DEPLOY_KNOWN_HOSTS` | output of `ssh-keyscan -p 22 10.0.0.42` | |
|
||||||
|
|
||||||
|
`.gitea/workflows/deploy.yml` runs `scripts/deploy.sh` on every push to
|
||||||
|
`main`. Needs one act runner registered in Gitea (can run anywhere with SSH
|
||||||
|
access to the LXC — including the LXC itself).
|
||||||
|
|
||||||
|
## Option B — Gitea webhook
|
||||||
|
|
||||||
|
If you don't want an act runner: add a webhook (Settings → Webhooks) pointing
|
||||||
|
to a minimal receiver on the LXC (e.g. `webhook`/` webhookd`/a 20-line HTTP
|
||||||
|
server) that verifies the shared secret and executes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo -u deploy env SSH_HOST=deploy@localhost DEPLOY_MODE=docker \
|
||||||
|
/opt/ai-rendezvous/scripts/deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Gitea → webhook → receiver → `deploy.sh`. Same script, no CI needed.
|
||||||
|
|
||||||
|
## Option C — manual / from a dev machine (or the agent's shell)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/deploy.sh # reads deploy.env (gitignored) if present
|
||||||
|
```
|
||||||
|
|
||||||
|
`deploy.env` example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SSH_HOST=deploy@lxc42.internal
|
||||||
|
SSH_PORT=22
|
||||||
|
DEPLOY_DIR=/opt/ai-rendezvous
|
||||||
|
DEPLOY_MODE=docker
|
||||||
|
HEALTH_URL=http://127.0.0.1:3000/health
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deploy flow for the agent
|
||||||
|
|
||||||
|
1. Make changes, `npm test` locally.
|
||||||
|
2. Commit and push to `main` on Gitea.
|
||||||
|
3. Gitea Actions (or webhook) runs `scripts/deploy.sh`.
|
||||||
|
4. The health check must pass; the action fails loudly otherwise.
|
||||||
|
5. Human verifies on the deployed instance.
|
||||||
Executable
+71
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Deploy AI Rendezvous to the LXC host over SSH.
|
||||||
|
#
|
||||||
|
# Works in three ways:
|
||||||
|
# 1. locally: ./scripts/deploy.sh
|
||||||
|
# 2. from Gitea Actions: .gitea/workflows/deploy.yml calls this script
|
||||||
|
# 3. from a webhook: any tiny receiver on the LXC can exec this script
|
||||||
|
#
|
||||||
|
# Configuration via environment (or a gitignored deploy.env next to this file):
|
||||||
|
# SSH_HOST user@lxc-host (required)
|
||||||
|
# SSH_PORT 22
|
||||||
|
# DEPLOY_DIR /opt/ai-rendezvous checkout on the LXC
|
||||||
|
# BRANCH main
|
||||||
|
# DEPLOY_MODE docker | systemd how to run the service
|
||||||
|
# SERVICE_NAME ai-rendezvous systemd unit name (systemd mode)
|
||||||
|
# HEALTH_URL e.g. http://127.0.0.1:3000/health checked after deploy
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
[ -f deploy.env ] && . ./deploy.env
|
||||||
|
|
||||||
|
: "${SSH_HOST:?Set SSH_HOST (user@lxc-host) via env, deploy.env or CI secrets}"
|
||||||
|
SSH_PORT="${SSH_PORT:-22}"
|
||||||
|
DEPLOY_DIR="${DEPLOY_DIR:-/opt/ai-rendezvous}"
|
||||||
|
BRANCH="${BRANCH:-main}"
|
||||||
|
DEPLOY_MODE="${DEPLOY_MODE:-docker}"
|
||||||
|
SERVICE_NAME="${SERVICE_NAME:-ai-rendezvous}"
|
||||||
|
HEALTH_URL="${HEALTH_URL:-}"
|
||||||
|
|
||||||
|
SSH_CMD=(ssh -p "$SSH_PORT" -o StrictHostKeyChecking=accept-new "$SSH_HOST")
|
||||||
|
|
||||||
|
echo "==> Deploying $BRANCH to $SSH_HOST:$DEPLOY_DIR (mode: $DEPLOY_MODE)"
|
||||||
|
|
||||||
|
"${SSH_CMD[@]}" BRANCH="$BRANCH" DEPLOY_DIR="$DEPLOY_DIR" DEPLOY_MODE="$DEPLOY_MODE" \
|
||||||
|
SERVICE_NAME="$SERVICE_NAME" bash -s <<'REMOTE'
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$DEPLOY_DIR"
|
||||||
|
|
||||||
|
echo "--> fetching $BRANCH"
|
||||||
|
umask 022
|
||||||
|
git fetch --prune origin
|
||||||
|
git reset --hard "origin/$BRANCH"
|
||||||
|
|
||||||
|
if [ "$DEPLOY_MODE" = "docker" ]; then
|
||||||
|
echo "--> rebuilding containers"
|
||||||
|
docker compose up -d --build --remove-orphans
|
||||||
|
elif [ "$DEPLOY_MODE" = "systemd" ]; then
|
||||||
|
echo "--> building"
|
||||||
|
npm ci --no-audit --no-fund
|
||||||
|
npm run build
|
||||||
|
echo "--> restarting $SERVICE_NAME"
|
||||||
|
systemctl restart "$SERVICE_NAME"
|
||||||
|
else
|
||||||
|
echo "unknown DEPLOY_MODE: $DEPLOY_MODE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
REMOTE
|
||||||
|
|
||||||
|
if [ -n "$HEALTH_URL" ]; then
|
||||||
|
echo "==> Health check $HEALTH_URL"
|
||||||
|
if "${SSH_CMD[@]}" curl -sf --max-time 5 --retry 10 --retry-delay 3 --retry-connrefused "$HEALTH_URL" >/dev/null; then
|
||||||
|
echo "==> healthy"
|
||||||
|
else
|
||||||
|
echo "!! health check FAILED after deploy" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> deploy OK"
|
||||||
Reference in New Issue
Block a user