From 3428885aaa879aea4dd2e51de739e62fc864c0c6 Mon Sep 17 00:00:00 2001 From: Piotr Oleszczyk Date: Sun, 1 Mar 2026 13:51:51 +0100 Subject: [PATCH] docs: add deploy.sh and rewrite DEPLOYMENT.md for local-build workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add deploy.sh: builds frontend locally, rsyncs build/ to server, restarts services via passwordless sudo - DEPLOYMENT.md: remove pnpm build from server setup (frontend is never built on the LXC — esbuild hangs on low-resource containers), add rsync to apt packages, document deploy.sh setup (SSH config, sudoers), replace manual update steps with ./deploy.sh invocation Co-Authored-By: Claude Sonnet 4.6 --- deploy.sh | 59 ++++++++++++++++++++++++ docs/DEPLOYMENT.md | 109 ++++++++++++++++++++++++++------------------- 2 files changed, 121 insertions(+), 47 deletions(-) create mode 100755 deploy.sh diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..c1378a6 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# Usage: ./deploy.sh [frontend|backend|all] +# default: all +# +# SSH config (~/.ssh/config) — recommended: +# Host innercontext +# HostName +# User innercontext +# +# The innercontext user needs passwordless sudo for systemctl only: +# /etc/sudoers.d/innercontext-deploy: +# innercontext ALL=(root) NOPASSWD: /usr/bin/systemctl restart innercontext, /usr/bin/systemctl restart innercontext-node, /usr/bin/systemctl is-active innercontext, /usr/bin/systemctl is-active innercontext-node +set -euo pipefail + +SERVER="${DEPLOY_SERVER:-innercontext}" # ssh host alias or user@host +REMOTE="/opt/innercontext" +SCOPE="${1:-all}" + +# ── Frontend ─────────────────────────────────────────────────────────────── +deploy_frontend() { + echo "==> [frontend] Building locally..." + (cd frontend && pnpm run build) + + echo "==> [frontend] Uploading build/..." + rsync -az --delete frontend/build/ "$SERVER:$REMOTE/frontend/build/" + + echo "==> [frontend] Restarting service..." + ssh "$SERVER" "sudo systemctl restart innercontext-node && echo OK" +} + +# ── Backend ──────────────────────────────────────────────────────────────── +deploy_backend() { + echo "==> [backend] Uploading source..." + rsync -az --delete \ + --exclude='.venv/' \ + --exclude='__pycache__/' \ + --exclude='*.pyc' \ + --exclude='.env' \ + backend/ "$SERVER:$REMOTE/backend/" + + echo "==> [backend] Syncing dependencies..." + ssh "$SERVER" "cd $REMOTE/backend && uv sync --frozen" + + echo "==> [backend] Restarting service (alembic runs on start)..." + ssh "$SERVER" "sudo systemctl restart innercontext && echo OK" +} + +# ── Dispatch ─────────────────────────────────────────────────────────────── +case "$SCOPE" in + frontend) deploy_frontend ;; + backend) deploy_backend ;; + all) deploy_frontend; deploy_backend ;; + *) + echo "Usage: $0 [frontend|backend|all]" + exit 1 + ;; +esac + +echo "==> Done." diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 36084bd..f33d219 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -14,6 +14,10 @@ Reverse proxy (existing) innercontext LXC (new, Debian 13) FastAPI + MCP SvelteKit Node ``` +> **Frontend is never built on the server.** The `vite build` + `adapter-node` +> esbuild step is CPU/RAM-intensive and will hang on a small LXC. Build locally, +> deploy the `build/` artifact via `deploy.sh`. + ## 1. Prerequisites - Proxmox VE host with an existing PostgreSQL LXC and a reverse proxy @@ -53,7 +57,7 @@ pct enter 200 # or SSH into the container ```bash apt update && apt upgrade -y -apt install -y git nginx curl ca-certificates gnupg lsb-release libpq5 +apt install -y git nginx curl ca-certificates gnupg lsb-release libpq5 rsync ``` ### Python 3.12+ + uv @@ -65,7 +69,10 @@ curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR=/usr/local/bin sh Installing to `/usr/local/bin` makes `uv` available system-wide (required for `sudo -u innercontext uv sync`). -### Node.js 24 LTS + pnpm +### Node.js 24 LTS + +The server only needs Node.js to **run** the pre-built frontend bundle. +`pnpm` is **not** needed on the server — the frontend is always built locally. ```bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash @@ -75,23 +82,12 @@ nvm install 24 Copy `node` to `/usr/local/bin` so it is accessible system-wide (required for `sudo -u innercontext` and for systemd). -Symlinking into `/root/.nvm/` won't work — other users can't traverse `/root/`. Use `--remove-destination` to replace any existing symlink with a real file: ```bash cp --remove-destination "$(nvm which current)" /usr/local/bin/node ``` -Install pnpm as a standalone binary from GitHub releases — self-contained, -no wrapper scripts, works system-wide. Do **not** use `corepack enable pnpm` -(the shim requires its nvm directory structure and breaks when copied/linked): - -```bash -curl -fsSL "https://github.com/pnpm/pnpm/releases/latest/download/pnpm-linux-x64" \ - -o /usr/local/bin/pnpm -chmod 755 /usr/local/bin/pnpm -``` - ### Application user ```bash @@ -194,11 +190,10 @@ systemctl status innercontext --- -## 7. Frontend build and setup +## 7. Frontend setup -```bash -cd /opt/innercontext/frontend -``` +The frontend is **built locally and uploaded** via `deploy.sh` — never built on the server. +This section only covers the one-time server-side configuration. ### Create `.env.production` @@ -211,25 +206,24 @@ chmod 600 /opt/innercontext/frontend/.env.production chown innercontext:innercontext /opt/innercontext/frontend/.env.production ``` -### Install dependencies and build +### Grant `innercontext` passwordless sudo for service restarts ```bash -sudo -u innercontext bash -c ' - cd /opt/innercontext/frontend - pnpm install - PUBLIC_API_BASE=http://innercontext.lan/api pnpm build -' +cat > /etc/sudoers.d/innercontext-deploy << 'EOF' +innercontext ALL=(root) NOPASSWD: \ + /usr/bin/systemctl restart innercontext, \ + /usr/bin/systemctl restart innercontext-node +EOF +chmod 440 /etc/sudoers.d/innercontext-deploy ``` -The production build lands in `/opt/innercontext/frontend/build/`. - ### Install systemd service ```bash cp /opt/innercontext/systemd/innercontext-node.service /etc/systemd/system/ systemctl daemon-reload -systemctl enable --now innercontext-node -systemctl status innercontext-node +systemctl enable innercontext-node +# Do NOT start yet — build/ is empty until the first deploy.sh run ``` --- @@ -276,7 +270,40 @@ Reload your reverse proxy after applying the change. --- -## 10. Verification +## 10. First deploy from local machine + +All subsequent deploys (including the first one) use `deploy.sh` from your local machine. + +### SSH config + +Add to `~/.ssh/config` on your local machine: + +``` +Host innercontext + HostName + User innercontext +``` + +Make sure your SSH public key is in `/home/innercontext/.ssh/authorized_keys` on the server. + +### Run the first deploy + +```bash +# From the repo root on your local machine: +./deploy.sh +``` + +This will: +1. Build the frontend locally (`pnpm run build`) +2. Upload `frontend/build/` to the server via rsync +3. Restart `innercontext-node` +4. Upload `backend/` source to the server +5. Run `uv sync --frozen` on the server +6. Restart `innercontext` (runs alembic migrations on start) + +--- + +## 11. Verification ```bash # From any machine on the LAN: @@ -290,30 +317,18 @@ The web UI should be accessible at `http://innercontext.lan`. --- -## 11. Updating the application +## 12. Updating the application ```bash -cd /opt/innercontext -git pull - -# Sync backend dependencies if pyproject.toml changed: -cd backend && sudo -u innercontext uv sync && cd .. - -# Apply any new DB migrations (runs automatically via ExecStartPre, but safe to run manually first): -sudo -u innercontext bash -c 'cd /opt/innercontext/backend && uv run alembic upgrade head' - -# Rebuild frontend: -cd frontend && sudo -u innercontext bash -c ' - pnpm install - PUBLIC_API_BASE=http://innercontext.lan/api pnpm build -' - -systemctl restart innercontext innercontext-node +# From the repo root on your local machine: +./deploy.sh # full deploy (frontend + backend) +./deploy.sh frontend # frontend only +./deploy.sh backend # backend only ``` --- -## 12. Troubleshooting +## 13. Troubleshooting ### 502 Bad Gateway on `/api/*` @@ -328,7 +343,7 @@ journalctl -u innercontext -n 50 ```bash systemctl status innercontext-node journalctl -u innercontext-node -n 50 -# Verify /opt/innercontext/frontend/build/index.js exists (pnpm build ran successfully) +# Verify /opt/innercontext/frontend/build/index.js exists (deploy.sh ran successfully) ``` ### MCP endpoint not responding