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:
Piotr Oleszczyk 2026-03-01 13:51:51 +01:00
parent 99584521a1
commit 3428885aaa
2 changed files with 121 additions and 47 deletions

View file

@ -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 <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
# 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