#!/usr/bin/env bash # Provision a new storefront site on the websites VM (clone + nginx + pm2 entry + allowlist). # Does NOT run npm ci/build — first build happens via deploy.sh (Super Admin Deploy). # Does NOT run certbot — use ssl.sh / Super Admin Issue SSL (certbot blocked Edit Domain saves). set -euo pipefail SLUG="${1:-}" HOST="${2:-}" GIT_REPO_URL="${3:-}" if [[ -z "$SLUG" || -z "$HOST" || -z "$GIT_REPO_URL" ]]; then echo "usage: provision.sh " >&2 exit 1 fi if [[ ! "$SLUG" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then echo "invalid slug: $SLUG" >&2 exit 1 fi if [[ ! "$HOST" =~ ^[a-z0-9.-]+$ ]]; then echo "invalid host: $HOST" >&2 exit 1 fi ROOT="/var/www/sites/$SLUG" ECOSYSTEM="/var/www/sites/ecosystem.config.cjs" AGENT_DIR="/opt/websites-agent" ENV_FILE="$AGENT_DIR/.env" LOG_DIR="/var/log/websites" NGINX_AVAILABLE="/etc/nginx/sites-available/$HOST" NGINX_ENABLED="/etc/nginx/sites-enabled/$HOST" SSH_KEY="/root/.ssh/websites_deploy" mkdir -p "$LOG_DIR" /var/www/sites export GIT_SSH_COMMAND="ssh -i $SSH_KEY -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" if [[ ! -d "$ROOT/.git" ]]; then echo "cloning $GIT_REPO_URL → $ROOT" rm -rf "$ROOT" git clone "$GIT_REPO_URL" "$ROOT" else echo "site already cloned: $ROOT" fi PORT="$( SLUG="$SLUG" ECOSYSTEM="$ECOSYSTEM" node <<'NODE' const fs = require('fs'); const path = process.env.ECOSYSTEM; const slug = process.env.SLUG; let cfg = { apps: [] }; try { delete require.cache[require.resolve(path)]; cfg = require(path); if (!Array.isArray(cfg.apps)) cfg.apps = []; } catch (_) { cfg = { apps: [] }; } const existing = cfg.apps.find((a) => a.name === slug); if (existing) { const fromEnv = Number(existing.env && existing.env.PORT); const m = String(existing.args || '').match(/--port\s+(\d+)/); const port = Number.isFinite(fromEnv) && fromEnv > 0 ? fromEnv : m ? Number(m[1]) : 3005; process.stdout.write(String(port)); process.exit(0); } const used = new Set(); for (const app of cfg.apps) { const fromEnv = Number(app.env && app.env.PORT); if (Number.isFinite(fromEnv) && fromEnv > 0) used.add(fromEnv); const args = String(app.args || ''); const m = args.match(/--port\s+(\d+)/); if (m) used.add(Number(m[1])); } let port = 3005; while (used.has(port)) port += 1; cfg.apps.push({ name: slug, cwd: '/var/www/sites/' + slug, script: 'node_modules/next/dist/bin/next', args: 'start --hostname 127.0.0.1 --port ' + port, env: { NODE_ENV: 'production', PORT: String(port) }, error_file: '/var/log/websites/' + slug + '-error.log', out_file: '/var/log/websites/' + slug + '-out.log', time: true, }); const lines = ['module.exports = {', ' apps: [']; cfg.apps.forEach((app, idx) => { lines.push(' {'); lines.push(` name: ${JSON.stringify(app.name)},`); lines.push(` cwd: ${JSON.stringify(app.cwd)},`); lines.push(` script: ${JSON.stringify(app.script)},`); lines.push(` args: ${JSON.stringify(app.args)},`); lines.push(' env: {'); lines.push(` NODE_ENV: ${JSON.stringify(app.env.NODE_ENV)},`); lines.push(` PORT: ${JSON.stringify(app.env.PORT)},`); lines.push(' },'); lines.push(` error_file: ${JSON.stringify(app.error_file)},`); lines.push(` out_file: ${JSON.stringify(app.out_file)},`); lines.push(' time: true,'); lines.push(idx === cfg.apps.length - 1 ? ' }' : ' },'); }); lines.push(' ],'); lines.push('};'); lines.push(''); fs.writeFileSync(path, lines.join('\n')); process.stdout.write(String(port)); NODE )" echo "using port $PORT for $SLUG" if [[ ! -f "$NGINX_AVAILABLE" ]]; then cat >"$NGINX_AVAILABLE" <