2026-04-04 15:14:01 -07:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# @analytics — Deploy to vps-0 (1984 hosting)
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# Usage: ./scripts/deploy.sh
|
2026-04-05 15:07:10 -07:00
|
|
|
# or via: ./run deploy
|
2026-04-04 15:14:01 -07:00
|
|
|
#
|
|
|
|
|
# Requires: quinn-vps SSH alias configured in ~/.ssh/config
|
2026-04-05 15:07:10 -07:00
|
|
|
#
|
|
|
|
|
# Strategy:
|
|
|
|
|
# - Services are built locally (turbo) — dist/ files are pre-compiled.
|
|
|
|
|
# - dist/ is rsynced to VPS alongside Dockerfiles; no build step needed on VPS.
|
|
|
|
|
# - Docker images are built on VPS from pre-compiled dist/ via docker compose --build.
|
|
|
|
|
# - @lilith/* workspace deps are compiled into dist/ by SWC — stripped from
|
|
|
|
|
# package.json in each Dockerfile so npm install only fetches registry packages.
|
2026-04-04 15:14:01 -07:00
|
|
|
# =============================================================================
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
|
|
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
|
|
REMOTE="quinn-vps"
|
|
|
|
|
REMOTE_DIR="~/analytics"
|
|
|
|
|
|
2026-04-07 18:01:58 -07:00
|
|
|
echo "==> [1/5] Building services..."
|
2026-04-04 15:14:01 -07:00
|
|
|
cd "$ROOT_DIR" && bun run build:services
|
|
|
|
|
|
2026-04-07 18:01:58 -07:00
|
|
|
echo "==> [2/5] Staging @lilith registry packages for Docker builds..."
|
|
|
|
|
# SWC transpiles but doesn't bundle — registry @lilith/* packages (non-workspace)
|
|
|
|
|
# still need to exist in node_modules at runtime. The VPS can't reach Verdaccio,
|
|
|
|
|
# so we resolve them locally and stage into .vendor-lilith/ per service. The
|
|
|
|
|
# Dockerfile copies these into node_modules/ before npm install.
|
|
|
|
|
for svc_dir in "$ROOT_DIR"/services/*/; do
|
|
|
|
|
svc_name="$(basename "$svc_dir")"
|
|
|
|
|
vendor_dir="${svc_dir}.vendor-lilith"
|
|
|
|
|
rm -rf "$vendor_dir"
|
|
|
|
|
mkdir -p "$vendor_dir"
|
|
|
|
|
# Recursively resolve @lilith registry deps (non-workspace) and their transitive
|
|
|
|
|
# @lilith deps into .vendor-lilith/ so the Docker image has everything it needs.
|
|
|
|
|
# Uses require.resolve from the svc dir to follow bun's hoisting chain.
|
|
|
|
|
node -e "
|
|
|
|
|
const fs = require('fs');
|
|
|
|
|
const path = require('path');
|
|
|
|
|
const svcDir = '${svc_dir}';
|
|
|
|
|
const vendorDir = '${vendor_dir}';
|
|
|
|
|
const svcName = '${svc_name}';
|
|
|
|
|
|
|
|
|
|
function stagePackage(name) {
|
|
|
|
|
const dst = path.join(vendorDir, ...name.split('/'));
|
|
|
|
|
if (fs.existsSync(dst)) return; // already staged
|
|
|
|
|
// Find the package by walking up from svcDir checking:
|
|
|
|
|
// 1. node_modules/@scope/pkg (standard symlink)
|
|
|
|
|
// 2. node_modules/.bun/@scope+pkg@*/node_modules/@scope/pkg (bun store)
|
|
|
|
|
const parts = name.split('/');
|
|
|
|
|
const bunKey = parts.join('+'); // @lilith/foo → @lilith+foo
|
|
|
|
|
let real = null;
|
|
|
|
|
let search = path.resolve(svcDir);
|
|
|
|
|
while (search !== '/') {
|
|
|
|
|
// Standard location
|
|
|
|
|
const candidate = path.join(search, 'node_modules', ...parts);
|
|
|
|
|
if (fs.existsSync(candidate)) { real = fs.realpathSync(candidate); break; }
|
|
|
|
|
// Bun store — glob for versioned directory
|
|
|
|
|
const bunDir = path.join(search, 'node_modules', '.bun');
|
|
|
|
|
if (fs.existsSync(bunDir)) {
|
|
|
|
|
const match = fs.readdirSync(bunDir).find(d => d.startsWith(bunKey + '@'));
|
|
|
|
|
if (match) {
|
|
|
|
|
const storePkg = path.join(bunDir, match, 'node_modules', ...parts);
|
|
|
|
|
if (fs.existsSync(storePkg)) { real = fs.realpathSync(storePkg); break; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
search = path.dirname(search);
|
|
|
|
|
}
|
|
|
|
|
if (!real) {
|
|
|
|
|
console.warn(' WARN: ' + name + ' not found in any node_modules up from ' + svcName);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
|
|
|
fs.cpSync(real, dst, { recursive: true });
|
|
|
|
|
console.log(' Staged ' + name + ' → .vendor-lilith/ (' + svcName + ')');
|
|
|
|
|
// Recurse into this package's @lilith deps
|
|
|
|
|
const child = JSON.parse(fs.readFileSync(path.join(real, 'package.json'), 'utf8'));
|
|
|
|
|
for (const [dep] of Object.entries(child.dependencies || {})) {
|
|
|
|
|
if (dep.startsWith('@lilith/')) stagePackage(dep);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const p = JSON.parse(fs.readFileSync(svcDir + 'package.json', 'utf8'));
|
|
|
|
|
for (const [name, ver] of Object.entries(p.dependencies || {})) {
|
|
|
|
|
if (name.startsWith('@lilith/') && typeof ver === 'string' && !ver.startsWith('workspace:')) {
|
|
|
|
|
stagePackage(name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
echo "==> [3/5] Syncing to $REMOTE:$REMOTE_DIR ..."
|
2026-04-05 15:07:10 -07:00
|
|
|
# Include dist/ — Docker images copy from pre-built dist, no VPS build needed
|
2026-04-04 15:14:01 -07:00
|
|
|
rsync -avz --delete \
|
|
|
|
|
--exclude=node_modules \
|
|
|
|
|
--exclude=.env \
|
|
|
|
|
--exclude=.env.* \
|
|
|
|
|
"$ROOT_DIR/services/" "$REMOTE:$REMOTE_DIR/services/"
|
|
|
|
|
|
|
|
|
|
rsync -avz \
|
|
|
|
|
"$ROOT_DIR/infrastructure/docker-compose.prod.yaml" \
|
|
|
|
|
"$ROOT_DIR/infrastructure/init.sql" \
|
|
|
|
|
"$REMOTE:$REMOTE_DIR/infrastructure/"
|
|
|
|
|
|
2026-04-07 18:01:58 -07:00
|
|
|
echo "==> [4/5] Rebuilding and restarting Docker stack..."
|
2026-04-04 15:14:01 -07:00
|
|
|
ssh "$REMOTE" "cd $REMOTE_DIR && docker compose -f infrastructure/docker-compose.prod.yaml --env-file infrastructure/.env.prod up -d --build"
|
|
|
|
|
|
2026-04-07 18:01:58 -07:00
|
|
|
echo "==> [5/5] Health check..."
|
2026-04-05 15:07:10 -07:00
|
|
|
sleep 8
|
|
|
|
|
ssh "$REMOTE" "curl -sf http://localhost:4001/health && echo 'collector OK' || echo 'collector NOT READY'"
|
|
|
|
|
ssh "$REMOTE" "curl -sf http://localhost:4003/health && echo 'api OK' || echo 'api NOT READY'"
|
|
|
|
|
ssh "$REMOTE" "curl -sf http://localhost:4005/health && echo 'website-bff OK' || echo 'website-bff NOT READY'"
|
2026-04-04 15:14:01 -07:00
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
echo "Deployed at $(date '+%Y-%m-%d %H:%M:%S %Z')"
|
2026-04-05 15:07:10 -07:00
|
|
|
echo "Collector: https://data.transquinnftw.com/analytics/track/"
|
|
|
|
|
echo "API: https://data.transquinnftw.com/api/"
|