Add parked alias domains with Arvan or Cloudflare DNS and websites SSL.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
5813e9fc53
commit
d1f60b5313
@@ -7,6 +7,8 @@
|
||||
# GET /deploy-status?slug=… — last deploy status JSON for slug
|
||||
# POST /provision { slug, host, gitRepoUrl } — clone + nginx + ecosystem + allowlist (no certbot)
|
||||
# POST /ssl { host, slug? } — certbot for apex + www (nginx must exist)
|
||||
# POST /park { host, canonicalHost } — nginx 301 apex+www → https://canonical
|
||||
# POST /unpark { host } — remove parked nginx site
|
||||
# GET /health
|
||||
#
|
||||
# Scripts on the VM (same folder):
|
||||
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env bash
|
||||
# Parked alias: nginx 301 from parked apex+www to the canonical storefront.
|
||||
# SSL is issued separately via ssl.sh (certbot --nginx) so the cert is in
|
||||
# certbot's auto-renewal list.
|
||||
set -euo pipefail
|
||||
|
||||
HOST="${1:-}"
|
||||
CANONICAL="${2:-}"
|
||||
|
||||
if [[ -z "$HOST" || -z "$CANONICAL" ]]; then
|
||||
echo "usage: park.sh <parked-host> <canonical-host>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "$HOST" =~ ^[a-z0-9.-]+$ || ! "$CANONICAL" =~ ^[a-z0-9.-]+$ ]]; then
|
||||
echo "invalid host" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NGINX_AVAILABLE="/etc/nginx/sites-available/$HOST"
|
||||
NGINX_ENABLED="/etc/nginx/sites-enabled/$HOST"
|
||||
SSL_DIR="/etc/nginx/ssl/$HOST"
|
||||
LIST_FILE="/opt/websites-agent/parked-hosts.txt"
|
||||
|
||||
mkdir -p "$SSL_DIR" /opt/websites-agent /var/www/certbot
|
||||
|
||||
if [[ ! -f "$SSL_DIR/fullchain.pem" || ! -f "$SSL_DIR/privkey.pem" ]]; then
|
||||
openssl req -x509 -nodes -newkey rsa:2048 -days 825 \
|
||||
-keyout "$SSL_DIR/privkey.pem" \
|
||||
-out "$SSL_DIR/fullchain.pem" \
|
||||
-subj "/CN=$HOST" \
|
||||
-addext "subjectAltName=DNS:$HOST,DNS:www.$HOST" \
|
||||
>/dev/null 2>&1
|
||||
echo "origin self-signed cert created: $SSL_DIR"
|
||||
fi
|
||||
|
||||
# If certbot already issued a live cert, prefer it.
|
||||
LE_LIVE="/etc/letsencrypt/live/$HOST"
|
||||
if [[ -f "$LE_LIVE/fullchain.pem" && -f "$LE_LIVE/privkey.pem" ]]; then
|
||||
CERT="$LE_LIVE/fullchain.pem"
|
||||
KEY="$LE_LIVE/privkey.pem"
|
||||
else
|
||||
CERT="$SSL_DIR/fullchain.pem"
|
||||
KEY="$SSL_DIR/privkey.pem"
|
||||
fi
|
||||
|
||||
cat >"$NGINX_AVAILABLE" <<NGINX
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name ${HOST} www.${HOST};
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://${CANONICAL}\$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name ${HOST} www.${HOST};
|
||||
|
||||
ssl_certificate ${CERT};
|
||||
ssl_certificate_key ${KEY};
|
||||
|
||||
return 301 https://${CANONICAL}\$request_uri;
|
||||
}
|
||||
NGINX
|
||||
|
||||
ln -sfn "$NGINX_AVAILABLE" "$NGINX_ENABLED"
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
|
||||
touch "$LIST_FILE"
|
||||
if ! grep -qxF "$HOST" "$LIST_FILE" 2>/dev/null; then
|
||||
echo "$HOST" >>"$LIST_FILE"
|
||||
fi
|
||||
|
||||
echo "park ok: $HOST → https://$CANONICAL (apex+www)"
|
||||
@@ -265,6 +265,74 @@ const server = http.createServer(async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (req.method === 'POST' && req.url === '/park') {
|
||||
if (!assertAuth(req, res)) return;
|
||||
|
||||
let body;
|
||||
try {
|
||||
body = await readJson(req);
|
||||
} catch {
|
||||
return send(res, 400, { error: 'invalid json' });
|
||||
}
|
||||
|
||||
const host = String(body.host || '').trim().toLowerCase();
|
||||
const canonicalHost = String(body.canonicalHost || body.canonical_host || '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
|
||||
if (!host || !/^[a-z0-9.-]+$/.test(host)) {
|
||||
return send(res, 400, { error: 'valid host is required' });
|
||||
}
|
||||
if (!canonicalHost || !/^[a-z0-9.-]+$/.test(canonicalHost)) {
|
||||
return send(res, 400, { error: 'valid canonicalHost is required' });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await runScript('/opt/websites-agent/park.sh', [host, canonicalHost]);
|
||||
return send(res, 200, {
|
||||
status: 'parked',
|
||||
host,
|
||||
canonicalHost,
|
||||
log: (result.stdout || '').slice(-2000),
|
||||
});
|
||||
} catch (err) {
|
||||
return send(res, 500, {
|
||||
error: 'park failed',
|
||||
detail: err instanceof Error ? err.message.slice(0, 2000) : String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (req.method === 'POST' && req.url === '/unpark') {
|
||||
if (!assertAuth(req, res)) return;
|
||||
|
||||
let body;
|
||||
try {
|
||||
body = await readJson(req);
|
||||
} catch {
|
||||
return send(res, 400, { error: 'invalid json' });
|
||||
}
|
||||
|
||||
const host = String(body.host || '').trim().toLowerCase();
|
||||
if (!host || !/^[a-z0-9.-]+$/.test(host)) {
|
||||
return send(res, 400, { error: 'valid host is required' });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await runScript('/opt/websites-agent/unpark.sh', [host]);
|
||||
return send(res, 200, {
|
||||
status: 'unparked',
|
||||
host,
|
||||
log: (result.stdout || '').slice(-2000),
|
||||
});
|
||||
} catch (err) {
|
||||
return send(res, 500, {
|
||||
error: 'unpark failed',
|
||||
detail: err instanceof Error ? err.message.slice(0, 2000) : String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return send(res, 404, { error: 'not found' });
|
||||
});
|
||||
|
||||
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
# Remove a parked-alias nginx site. Does not delete Arvan DNS or LE certs.
|
||||
set -euo pipefail
|
||||
|
||||
HOST="${1:-}"
|
||||
|
||||
if [[ -z "$HOST" ]]; then
|
||||
echo "usage: unpark.sh <parked-host>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "$HOST" =~ ^[a-z0-9.-]+$ ]]; then
|
||||
echo "invalid host" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NGINX_AVAILABLE="/etc/nginx/sites-available/$HOST"
|
||||
NGINX_ENABLED="/etc/nginx/sites-enabled/$HOST"
|
||||
LIST_FILE="/opt/websites-agent/parked-hosts.txt"
|
||||
|
||||
rm -f "$NGINX_ENABLED"
|
||||
rm -f "$NGINX_AVAILABLE"
|
||||
|
||||
if [[ -f "$LIST_FILE" ]]; then
|
||||
grep -vxF "$HOST" "$LIST_FILE" >"${LIST_FILE}.tmp" || true
|
||||
mv "${LIST_FILE}.tmp" "$LIST_FILE"
|
||||
fi
|
||||
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
echo "unpark ok: $HOST"
|
||||
Reference in New Issue
Block a user