mirror of
https://git.meshkee.com/Meshkee/backend.git
synced 2026-08-11 22:30:59 +04:30
Persist deploy_slug on domains, call the websites agent /provision endpoint, and drop the hard-coded host map so Deploy appears from the UI. Co-authored-by: Cursor <cursoragent@cursor.com>
164 lines
5.0 KiB
Bash
Executable File
164 lines
5.0 KiB
Bash
Executable File
#!/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).
|
|
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 <slug> <host> <gitRepoUrl>" >&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" <<NGINX
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name ${HOST} www.${HOST};
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:${PORT};
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade \$http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
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;
|
|
proxy_cache_bypass \$http_upgrade;
|
|
}
|
|
}
|
|
NGINX
|
|
ln -sfn "$NGINX_AVAILABLE" "$NGINX_ENABLED"
|
|
nginx -t
|
|
systemctl reload nginx
|
|
echo "nginx site created for $HOST → :$PORT"
|
|
else
|
|
echo "nginx site already exists: $NGINX_AVAILABLE"
|
|
fi
|
|
|
|
if command -v certbot >/dev/null 2>&1; then
|
|
certbot --nginx -d "$HOST" -d "www.$HOST" --non-interactive --agree-tos --register-unsafely-without-email --redirect \
|
|
|| echo "certbot skipped/failed (non-fatal)"
|
|
fi
|
|
|
|
if [[ -f "$ENV_FILE" ]]; then
|
|
CURRENT="$(grep -E '^ALLOWED_SLUGS=' "$ENV_FILE" | head -1 | cut -d= -f2- || true)"
|
|
if [[ -z "$CURRENT" ]]; then
|
|
if grep -qE '^ALLOWED_SLUGS=' "$ENV_FILE"; then
|
|
sed -i -E "s|^ALLOWED_SLUGS=.*|ALLOWED_SLUGS=${SLUG}|" "$ENV_FILE"
|
|
else
|
|
echo "ALLOWED_SLUGS=$SLUG" >>"$ENV_FILE"
|
|
fi
|
|
elif [[ ",$CURRENT," != *",$SLUG,"* ]]; then
|
|
sed -i -E "s|^ALLOWED_SLUGS=.*|ALLOWED_SLUGS=${CURRENT},${SLUG}|" "$ENV_FILE"
|
|
fi
|
|
fi
|
|
|
|
echo "provision ok: slug=$SLUG host=$HOST port=$PORT"
|