feat(deploy): local chess.local instance for VDJ-RIG
A second, LAN-only deploy alongside the CT 690 / chess.sethpc.xyz instance. Runs on VDJ-RIG as a persistent systemd daemon, served on port 80 and reachable at http://chess.local via an mDNS alias. - blind-chess-local.service: server unit; binds port 80 as the non-root blindchess user via CAP_NET_BIND_SERVICE. - chess-mdns-alias{,.service}: publishes the chess.local mDNS name with avahi-publish -a -R (-R skips the reverse PTR, which would otherwise collide with the host's own <hostname>.local record). - install-local.sh: idempotent root-side installer (Node 22 via NodeSource, avahi-utils, blindchess user, /opt/blind-chess, units). - CLAUDE.md: documents the local instance under Operations. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,17 @@ The system's most distinctive property: highlighting in blind mode reveals **zer
|
|||||||
- **Health:** `curl https://chess.sethpc.xyz/api/health`
|
- **Health:** `curl https://chess.sethpc.xyz/api/health`
|
||||||
- **Deploy update:** `pnpm -r build` → `pnpm --filter @blind-chess/server deploy --prod --legacy .deploy-server` → rsync server bundle to `/opt/blind-chess/server/` and client `dist/` to `/opt/blind-chess/client/dist/` → `chown -R blindchess:blindchess /opt/blind-chess` → `systemctl restart blind-chess`. Server restart drops in-memory games (acceptable for MVP).
|
- **Deploy update:** `pnpm -r build` → `pnpm --filter @blind-chess/server deploy --prod --legacy .deploy-server` → rsync server bundle to `/opt/blind-chess/server/` and client `dist/` to `/opt/blind-chess/client/dist/` → `chown -R blindchess:blindchess /opt/blind-chess` → `systemctl restart blind-chess`. Server restart drops in-memory games (acceptable for MVP).
|
||||||
|
|
||||||
|
### Local instance — `chess.local` on VDJ-RIG
|
||||||
|
|
||||||
|
A second, **LAN-only** deploy on VDJ-RIG (192.168.0.143), fully independent of the CT 690 / chess.sethpc.xyz instance (separate in-memory state). Serves on **port 80**, reached at `http://chess.local` via an mDNS alias — no Caddy, no TLS.
|
||||||
|
|
||||||
|
- **Artifacts** (`deploy/`): `blind-chess-local.service` (server unit; binds port 80 as the non-root `blindchess` user via `CAP_NET_BIND_SERVICE`), `chess-mdns-alias` + `chess-mdns-alias.service` (publishes the `chess.local` mDNS name with `avahi-publish -a -R` — `-R` avoids a reverse-PTR collision with the host's own `.local` name), `install-local.sh` (root-side installer).
|
||||||
|
- **On the rig:** tree at `/opt/blind-chess/{server,client/dist}`, units `blind-chess.service` + `chess-mdns-alias.service`, Node 22 via NodeSource.
|
||||||
|
- **Logs:** `ssh vdj-rig 'journalctl -u blind-chess -f'`
|
||||||
|
- **Restart:** `ssh vdj-rig 'sudo systemctl restart blind-chess'`
|
||||||
|
- **Health:** `curl http://chess.local/api/health`
|
||||||
|
- **Redeploy:** on steel141 `pnpm -r build` + `pnpm --filter @blind-chess/server deploy --prod --legacy .deploy-server`; rsync `.deploy-server/` → rig `~/blind-chess-stage/server/`, `packages/client/dist/` → `~/blind-chess-stage/client-dist/`, and the `deploy/` files → `~/blind-chess-stage/`; then `sudo bash ~/blind-chess-stage/install-local.sh`.
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
- Inherits global homelab conventions from `~/bin/CLAUDE.md` (gitea CLI, conventional commits, `.claude/handoffs/` for session state).
|
- Inherits global homelab conventions from `~/bin/CLAUDE.md` (gitea CLI, conventional commits, `.claude/handoffs/` for session state).
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=blind_chess server — local LAN instance (chess.local)
|
||||||
|
Documentation=https://git.sethpc.xyz/Seth/blind_chess
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=blindchess
|
||||||
|
Group=blindchess
|
||||||
|
WorkingDirectory=/opt/blind-chess/server
|
||||||
|
ExecStart=/usr/bin/node /opt/blind-chess/server/dist/server.js
|
||||||
|
Environment=NODE_ENV=production
|
||||||
|
Environment=PORT=80
|
||||||
|
Environment=HOST=0.0.0.0
|
||||||
|
Environment=STATIC_DIR=/opt/blind-chess/client/dist
|
||||||
|
Environment=PUBLIC_BASE=http://chess.local
|
||||||
|
Environment=LOG_LEVEL=info
|
||||||
|
Restart=always
|
||||||
|
RestartSec=2s
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
# Bind privileged port 80 as a non-root user
|
||||||
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
|
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||||
|
|
||||||
|
# Hardening
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ReadWritePaths=/opt/blind-chess
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Publish "chess.local" as an mDNS alias for this host's primary IPv4 address.
|
||||||
|
# Invoked by chess-mdns-alias.service (blind_chess local deploy).
|
||||||
|
#
|
||||||
|
# avahi-daemon already advertises the host's own <hostname>.local; this adds
|
||||||
|
# the friendly "chess.local" name pointing at the same machine. Runs in the
|
||||||
|
# foreground holding the registration until the service is stopped.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
IP="$(hostname -I | awk '{print $1}')"
|
||||||
|
if [ -z "$IP" ]; then
|
||||||
|
echo "chess-mdns-alias: no IPv4 address found" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "chess-mdns-alias: publishing chess.local -> $IP"
|
||||||
|
# -R/--no-reverse: skip the reverse (PTR) record. avahi-daemon already owns the
|
||||||
|
# PTR for this IP via the host's own <hostname>.local, so publishing chess.local
|
||||||
|
# for the same address *with* a reverse entry collides ("Local name collision").
|
||||||
|
# Clients only need the forward A record, which -a still publishes.
|
||||||
|
exec avahi-publish -a -R chess.local "$IP"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Publish chess.local mDNS alias for the blind_chess local deploy
|
||||||
|
Requires=avahi-daemon.service
|
||||||
|
After=avahi-daemon.service network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/local/bin/chess-mdns-alias
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5s
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# blind_chess — local (chess.local) deploy installer.
|
||||||
|
#
|
||||||
|
# Run as root ON THE TARGET HOST. Expects a staging directory containing:
|
||||||
|
# server/ pnpm-deploy bundle (dist/ + node_modules/)
|
||||||
|
# client-dist/ vite build output
|
||||||
|
# chess-mdns-alias mDNS alias helper script
|
||||||
|
# blind-chess-local.service systemd unit for the server
|
||||||
|
# chess-mdns-alias.service systemd unit for the mDNS alias
|
||||||
|
#
|
||||||
|
# Usage: sudo bash install-local.sh [STAGE_DIR]
|
||||||
|
# (STAGE_DIR defaults to the directory containing this script)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
STAGE="${1:-$(cd "$(dirname "$0")" && pwd)}"
|
||||||
|
echo "=== blind_chess local install (staging: $STAGE) ==="
|
||||||
|
|
||||||
|
# --- Node.js 22 (Debian trixie ships only 20; blind_chess needs >=22) ---
|
||||||
|
need_node=1
|
||||||
|
if command -v node >/dev/null 2>&1; then
|
||||||
|
major="$(node -e 'process.stdout.write(String(process.versions.node.split(".")[0]))' 2>/dev/null || echo 0)"
|
||||||
|
if [ "${major:-0}" -ge 22 ] 2>/dev/null; then need_node=0; fi
|
||||||
|
fi
|
||||||
|
if [ "$need_node" -eq 1 ]; then
|
||||||
|
echo "--- installing Node.js 22 via NodeSource ---"
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
||||||
|
apt-get install -y -o DPkg::Lock::Timeout=600 nodejs
|
||||||
|
fi
|
||||||
|
echo "node: $(node --version)"
|
||||||
|
|
||||||
|
# --- avahi-utils provides avahi-publish ---
|
||||||
|
command -v avahi-publish >/dev/null 2>&1 || \
|
||||||
|
apt-get install -y -o DPkg::Lock::Timeout=600 avahi-utils
|
||||||
|
|
||||||
|
# --- dedicated unprivileged service user ---
|
||||||
|
getent passwd blindchess >/dev/null 2>&1 || \
|
||||||
|
useradd --system --user-group --no-create-home --shell /usr/sbin/nologin blindchess
|
||||||
|
|
||||||
|
# --- deploy tree under /opt/blind-chess ---
|
||||||
|
install -d /opt/blind-chess
|
||||||
|
rm -rf /opt/blind-chess/server /opt/blind-chess/client
|
||||||
|
cp -a "$STAGE/server" /opt/blind-chess/server
|
||||||
|
install -d /opt/blind-chess/client
|
||||||
|
cp -a "$STAGE/client-dist" /opt/blind-chess/client/dist
|
||||||
|
chown -R blindchess:blindchess /opt/blind-chess
|
||||||
|
|
||||||
|
# --- mDNS alias helper ---
|
||||||
|
install -m 0755 "$STAGE/chess-mdns-alias" /usr/local/bin/chess-mdns-alias
|
||||||
|
|
||||||
|
# --- systemd units (the server unit installs under the canonical name) ---
|
||||||
|
install -m 0644 "$STAGE/blind-chess-local.service" /etc/systemd/system/blind-chess.service
|
||||||
|
install -m 0644 "$STAGE/chess-mdns-alias.service" /etc/systemd/system/chess-mdns-alias.service
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now chess-mdns-alias.service
|
||||||
|
systemctl enable --now blind-chess.service
|
||||||
|
|
||||||
|
echo "=== install complete ==="
|
||||||
|
systemctl --no-pager --lines=0 status blind-chess.service chess-mdns-alias.service || true
|
||||||
Reference in New Issue
Block a user