← back to Homesonspec
deploy-kamatera.sh
116 lines
#!/usr/bin/env bash
# One-shot Kamatera self-host deploy for HomesOnSpec. Run from Mac: ! bash ~/Projects/homesonspec/deploy-kamatera.sh
# Idempotent — safe to re-run. Web :9975, admin :9976, workers daemon; all behind nginx.
#
# Verified working 2026-07-28 (TK-10005). Notes baked in from the failures that cost us a day:
# • project is "homesonspec" (renamed from spechomes) — package scope @homesonspec/*, DB "homesonspec".
# • pm2 MUST get an explicit PORT env or Next binds its default 3100/3101 and the :9975 vhost 502s.
# • restore is a CLEAN drop+create (--no-owner) then GRANT on BOTH public AND pgboss schemas,
# else the pg-boss workers crash-loop on aclcheck_error.
# • homesonspec.com is grey-cloud (DNS-only → origin), so the origin serves the real cert:
# keep the Let's Encrypt cert on the public :443 block (NOT self-signed + "CF Full").
set -euo pipefail
KH=root@45.61.58.125
REMOTE=/root/Projects/homesonspec
WEB_PORT=9975
ADMIN_PORT=9976
SRC_DB="postgresql://macstudio3@localhost/homesonspec?host=/tmp"
EK=~/Projects/homesonspec/.env.kamatera
echo "──────── 1/6 creds (gitignored .env.kamatera) ────────"
grep -q '^HOMESONSPEC_DB_PW=' "$EK" 2>/dev/null || echo "HOMESONSPEC_DB_PW=$(openssl rand -hex 16)" >> "$EK"
grep -q '^HOMESONSPEC_ADMIN_AUTH=' "$EK" 2>/dev/null || echo "HOMESONSPEC_ADMIN_AUTH=admin:$(openssl rand -hex 12)" >> "$EK"
chmod 600 "$EK"
DBPW=$(grep '^HOMESONSPEC_DB_PW=' "$EK" | tail -1 | cut -d= -f2)
ADMIN_AUTH=$(grep '^HOMESONSPEC_ADMIN_AUTH=' "$EK" | tail -1 | cut -d= -f2-)
echo "──────── 2/6 Postgres role + CLEAN db ────────"
# Drop+recreate for a faithful mirror (restoring onto an existing DB duplicates rows / fails PKs).
ssh $KH "sudo -u postgres psql -v ON_ERROR_STOP=1 \
-c \"DO \\\$\\\$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='homesonspec') THEN CREATE ROLE homesonspec LOGIN PASSWORD '$DBPW'; ELSE ALTER ROLE homesonspec PASSWORD '$DBPW'; END IF; END \\\$\\\$;\" \
-c \"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='homesonspec' AND pid<>pg_backend_pid();\" \
-c 'DROP DATABASE IF EXISTS homesonspec;' \
-c 'CREATE DATABASE homesonspec OWNER homesonspec;'; \
sudo -u postgres psql -d homesonspec -c 'CREATE EXTENSION IF NOT EXISTS pg_trgm;' >/dev/null"
echo "──────── 3/6 Migrate data (pg_dump Mac → Kamatera) + GRANTS ────────"
pg_dump --no-owner --no-acl -d "$SRC_DB" | gzip | ssh $KH "gunzip | sudo -u postgres psql -q -d homesonspec >/tmp/hos_restore.log 2>&1; \
sudo -u postgres psql -d homesonspec -v ON_ERROR_STOP=1 \
-c 'GRANT ALL ON DATABASE homesonspec TO homesonspec;' \
-c 'GRANT ALL ON SCHEMA public TO homesonspec;' \
-c 'GRANT ALL ON ALL TABLES IN SCHEMA public TO homesonspec;' \
-c 'GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO homesonspec;' \
-c \"DO \\\$\\\$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name='pgboss') THEN EXECUTE 'GRANT ALL ON SCHEMA pgboss TO homesonspec'; EXECUTE 'GRANT ALL ON ALL TABLES IN SCHEMA pgboss TO homesonspec'; EXECUTE 'GRANT ALL ON ALL SEQUENCES IN SCHEMA pgboss TO homesonspec'; EXECUTE 'GRANT ALL ON ALL FUNCTIONS IN SCHEMA pgboss TO homesonspec'; END IF; END \\\$\\\$;\" >/dev/null; \
echo -n ' homes='; sudo -u postgres psql -d homesonspec -tAc 'SELECT count(*) FROM \"InventoryHome\"'"
echo "──────── 4/6 Ship code + remote env + build ────────"
ssh $KH "mkdir -p $REMOTE"
rsync -az --delete --exclude node_modules --exclude .git --exclude '.env*' --exclude var/snapshots \
--exclude '.next' --exclude 'test-results' --exclude 'playwright-report' --exclude tmp \
~/Projects/homesonspec/ $KH:$REMOTE/
ssh $KH "cat > $REMOTE/.env <<EOF
DATABASE_URL=postgresql://homesonspec:$DBPW@localhost/homesonspec
BASIC_AUTH=$ADMIN_AUTH
BUILDER_IMAGES_ENABLED=1
SNAPSHOT_DIR=$REMOTE/var/snapshots
NODE_ENV=production
EOF
ln -sf $REMOTE/.env $REMOTE/apps/web/.env
ln -sf $REMOTE/.env $REMOTE/apps/admin/.env
ln -sf $REMOTE/.env $REMOTE/apps/workers/.env
mkdir -p $REMOTE/var/snapshots
cd $REMOTE; export PATH=\$PATH:/root/.npm-global/bin:/usr/local/bin; corepack enable 2>/dev/null || true
pnpm install --config.confirmModulesPurge=false 2>&1 | tail -2
pnpm --filter @homesonspec/database db:generate 2>&1 | tail -1
pnpm --filter @homesonspec/web build 2>&1 | tail -3
pnpm --filter @homesonspec/admin build 2>&1 | tail -3"
echo "──────── 5/6 pm2 (explicit PORT — the bug that 502'd) ────────"
ssh $KH "cd $REMOTE; export PATH=\$PATH:/root/.npm-global/bin:/usr/local/bin; set -a; . $REMOTE/.env; set +a
pm2 delete homesonspec-web homesonspec-admin homesonspec-workers 2>/dev/null || true
PORT=$WEB_PORT pm2 start 'pnpm --filter @homesonspec/web start' --name homesonspec-web --time
PORT=$ADMIN_PORT pm2 start 'pnpm --filter @homesonspec/admin start' --name homesonspec-admin --time
cd $REMOTE/apps/workers; pm2 start src/index.ts --name homesonspec-workers --interpreter node --interpreter-args='--env-file=$REMOTE/.env --import tsx' --time
pm2 save; sleep 8
echo -n ' web '; curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:$WEB_PORT/
echo -n ' admin(401 expected) '; curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:$ADMIN_PORT/"
echo "──────── 6/6 nginx vhost (LE cert on public block; loopback proxy) ────────"
# Assumes the LE cert already exists (see cert-homesonspec.sh). Keep LE — do NOT swap to self-signed.
ssh $KH "
cat > /etc/nginx/sites-available/homesonspec.com <<'NGINX'
server {
listen 45.61.58.125:80;
server_name homesonspec.com www.homesonspec.com admin.homesonspec.com;
location /.well-known/acme-challenge/ { root /var/www/certbot; }
location / { return 301 https://\$host\$request_uri; }
}
server {
listen 45.61.58.125:443 ssl;
server_name homesonspec.com www.homesonspec.com;
ssl_certificate /etc/letsencrypt/live/homesonspec.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/homesonspec.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:$WEB_PORT;
proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme;
}
}
server {
listen 45.61.58.125:443 ssl;
server_name admin.homesonspec.com admin.spechomes.com;
ssl_certificate /etc/letsencrypt/live/homesonspec.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/homesonspec.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:$ADMIN_PORT;
proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme;
}
}
NGINX
ln -sf /etc/nginx/sites-available/homesonspec.com /etc/nginx/sites-enabled/homesonspec.com
nginx -t && systemctl reload nginx"
echo ""
echo "✅ homesonspec.com self-hosted on Kamatera (web :$WEB_PORT, admin :$ADMIN_PORT, workers). Admin creds in $EK."