docs: add deploy.sh and rewrite DEPLOYMENT.md for local-build workflow
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
99584521a1
commit
3428885aaa
2 changed files with 121 additions and 47 deletions
59
deploy.sh
Executable file
59
deploy.sh
Executable file
|
|
@ -0,0 +1,59 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Usage: ./deploy.sh [frontend|backend|all]
|
||||||
|
# default: all
|
||||||
|
#
|
||||||
|
# SSH config (~/.ssh/config) — recommended:
|
||||||
|
# Host innercontext
|
||||||
|
# HostName <IP_LXC>
|
||||||
|
# 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."
|
||||||
|
|
@ -14,6 +14,10 @@ Reverse proxy (existing) innercontext LXC (new, Debian 13)
|
||||||
FastAPI + MCP SvelteKit Node
|
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
|
## 1. Prerequisites
|
||||||
|
|
||||||
- Proxmox VE host with an existing PostgreSQL LXC and a reverse proxy
|
- 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
|
```bash
|
||||||
apt update && apt upgrade -y
|
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
|
### 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`).
|
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
|
```bash
|
||||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | 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
|
Copy `node` to `/usr/local/bin` so it is accessible system-wide
|
||||||
(required for `sudo -u innercontext` and for systemd).
|
(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:
|
Use `--remove-destination` to replace any existing symlink with a real file:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp --remove-destination "$(nvm which current)" /usr/local/bin/node
|
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
|
### Application user
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -194,11 +190,10 @@ systemctl status innercontext
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Frontend build and setup
|
## 7. Frontend setup
|
||||||
|
|
||||||
```bash
|
The frontend is **built locally and uploaded** via `deploy.sh` — never built on the server.
|
||||||
cd /opt/innercontext/frontend
|
This section only covers the one-time server-side configuration.
|
||||||
```
|
|
||||||
|
|
||||||
### Create `.env.production`
|
### Create `.env.production`
|
||||||
|
|
||||||
|
|
@ -211,25 +206,24 @@ chmod 600 /opt/innercontext/frontend/.env.production
|
||||||
chown innercontext:innercontext /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
|
```bash
|
||||||
sudo -u innercontext bash -c '
|
cat > /etc/sudoers.d/innercontext-deploy << 'EOF'
|
||||||
cd /opt/innercontext/frontend
|
innercontext ALL=(root) NOPASSWD: \
|
||||||
pnpm install
|
/usr/bin/systemctl restart innercontext, \
|
||||||
PUBLIC_API_BASE=http://innercontext.lan/api pnpm build
|
/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
|
### Install systemd service
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp /opt/innercontext/systemd/innercontext-node.service /etc/systemd/system/
|
cp /opt/innercontext/systemd/innercontext-node.service /etc/systemd/system/
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable --now innercontext-node
|
systemctl enable innercontext-node
|
||||||
systemctl status 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 <innercontext-lxc-ip>
|
||||||
|
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
|
```bash
|
||||||
# From any machine on the LAN:
|
# 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
|
```bash
|
||||||
cd /opt/innercontext
|
# From the repo root on your local machine:
|
||||||
git pull
|
./deploy.sh # full deploy (frontend + backend)
|
||||||
|
./deploy.sh frontend # frontend only
|
||||||
# Sync backend dependencies if pyproject.toml changed:
|
./deploy.sh backend # backend only
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 12. Troubleshooting
|
## 13. Troubleshooting
|
||||||
|
|
||||||
### 502 Bad Gateway on `/api/*`
|
### 502 Bad Gateway on `/api/*`
|
||||||
|
|
||||||
|
|
@ -328,7 +343,7 @@ journalctl -u innercontext -n 50
|
||||||
```bash
|
```bash
|
||||||
systemctl status innercontext-node
|
systemctl status innercontext-node
|
||||||
journalctl -u innercontext-node -n 50
|
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
|
### MCP endpoint not responding
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue