Files
ladill-link/deploy/server-bootstrap.sh
T
isaaccladandCursor 9516fcb9f3
Deploy Ladill Link / deploy (push) Successful in 37s
Expand Link app with analytics, settings, and custom domains.
Add Bitly-style branded domain support via Ladill Domains SSL API,
account analytics dashboard, settings page with default domain picker,
and fix SSO/dashboard issues from QR Plus template leftovers.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-27 12:16:34 +00:00

158 lines
6.9 KiB
Bash

#!/usr/bin/env bash
# One-time server bootstrap for Ladill Link on 161.97.138.149
set -Eeuo pipefail
APP_ROOT="/var/www/ladill-link"
MONOLITH="/var/www/ladill.com/current"
QR_PLUS_ENV="/var/www/ladill-qr-plus/shared/.env"
NGINX_SETUP="/var/www/ladill.com/current/deployment/setup-service-subdomain-nginx.sh"
RELEASE_ARCHIVE="${1:-/tmp/ladill-link-release.tgz}"
log() { printf '[%s] %s\n' "$(date '+%F %T')" "$*"; }
[ -f "$RELEASE_ARCHIVE" ] || { echo "Missing release archive: $RELEASE_ARCHIVE" >&2; exit 1; }
[ -f "$QR_PLUS_ENV" ] || { echo "Missing qr-plus env template" >&2; exit 1; }
log "Creating app directories"
mkdir -p "$APP_ROOT"/{releases,shared}
chown -R deploy:www-data "$APP_ROOT" 2>/dev/null || true
if [ ! -f "$APP_ROOT/shared/.env" ]; then
log "Bootstrapping .env from qr-plus template"
cp "$QR_PLUS_ENV" "$APP_ROOT/shared/.env"
sed -i \
-e 's/^APP_NAME=.*/APP_NAME="Ladill Link"/' \
-e 's#^APP_URL=.*#APP_URL=https://link.ladill.com#' \
-e 's/^DB_DATABASE=.*/DB_DATABASE=ladill_link/' \
-e 's/^DB_USERNAME=.*/DB_USERNAME=ladill_link/' \
-e 's/^BILLING_API_KEY_QR=.*/BILLING_API_KEY_LINK=/' \
"$APP_ROOT/shared/.env"
# Generate DB password + billing key if missing
DB_PASS="$(openssl rand -hex 16)"
BILL_KEY="$(openssl rand -hex 24)"
sed -i "s/^DB_PASSWORD=.*/DB_PASSWORD=${DB_PASS}/" "$APP_ROOT/shared/.env"
if ! grep -q '^BILLING_API_KEY_LINK=' "$APP_ROOT/shared/.env"; then
echo "BILLING_API_KEY_LINK=${BILL_KEY}" >> "$APP_ROOT/shared/.env"
else
sed -i "s/^BILLING_API_KEY_LINK=.*/BILLING_API_KEY_LINK=${BILL_KEY}/" "$APP_ROOT/shared/.env"
fi
grep -q '^LINK_PUBLIC_DOMAIN=' "$APP_ROOT/shared/.env" || echo 'LINK_PUBLIC_DOMAIN=ladl.link' >> "$APP_ROOT/shared/.env"
grep -q '^LINK_PRICE_PER_LINK_GHS=' "$APP_ROOT/shared/.env" || echo 'LINK_PRICE_PER_LINK_GHS=0.05' >> "$APP_ROOT/shared/.env"
grep -q '^IDENTITY_API_KEY_LINK=' "$APP_ROOT/shared/.env" || echo "IDENTITY_API_KEY_LINK=$(openssl rand -hex 24)" >> "$APP_ROOT/shared/.env"
chmod 640 "$APP_ROOT/shared/.env"
chown deploy:www-data "$APP_ROOT/shared/.env" 2>/dev/null || true
log "Creating MySQL database ladill_link"
mysql -e "CREATE DATABASE IF NOT EXISTS ladill_link CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -e "CREATE USER IF NOT EXISTS 'ladill_link'@'127.0.0.1' IDENTIFIED BY '${DB_PASS}';"
mysql -e "GRANT ALL PRIVILEGES ON ladill_link.* TO 'ladill_link'@'127.0.0.1';"
mysql -e "FLUSH PRIVILEGES;"
# Save keys for monolith wiring (picked up below)
echo "$BILL_KEY" > /root/.ladill-link-bootstrap-billing-key
echo "$DB_PASS" > /root/.ladill-link-bootstrap-db-pass
fi
log "Deploying release archive"
export LADILL_APP_ROOT="$APP_ROOT"
export LADILL_RELEASE_ARCHIVE="$RELEASE_ARCHIVE"
bash "$APP_ROOT/releases/bootstrap-deploy.sh" 2>/dev/null || true
# deploy.sh lives inside the archive — extract deploy helper first if needed
TMP_EXTRACT="/tmp/ladill-link-deploy-$$"
mkdir -p "$TMP_EXTRACT"
tar -xzf "$RELEASE_ARCHIVE" -C "$TMP_EXTRACT" deploy/deploy.sh
cp "$TMP_EXTRACT/deploy/deploy.sh" /tmp/ladill-link-deploy.sh
LADILL_APP_ROOT="$APP_ROOT" LADILL_RELEASE_ARCHIVE="$RELEASE_ARCHIVE" bash /tmp/ladill-link-deploy.sh
log "Configuring nginx for link.ladill.com"
if [ -x "$NGINX_SETUP" ]; then
bash "$NGINX_SETUP" link --app "$APP_ROOT/current"
else
bash /var/www/ladill.com/current/deployment/setup-service-subdomain-nginx.sh link --app "$APP_ROOT/current" 2>/dev/null || \
echo "WARN: run setup-service-subdomain-nginx.sh link manually"
fi
log "Configuring nginx for ladl.link"
LADL_CONF="/etc/nginx/sites-available/ladl.link.conf"
if [ ! -f "$LADL_CONF" ]; then
cat > "$LADL_CONF" <<'NGINX'
server {
listen 80;
listen [::]:80;
server_name ladl.link www.ladl.link;
root /var/www/ladill-link/current/public;
location ^~ /.well-known/acme-challenge/ { allow all; }
location / { try_files $uri $uri/ /index.php?$query_string; }
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.4-fpm-ladill.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
}
}
NGINX
ln -sf "$LADL_CONF" /etc/nginx/sites-enabled/ladl.link.conf
nginx -t && systemctl reload nginx
certbot certonly --webroot -w /var/www/ladill-link/current/public -d ladl.link -d www.ladl.link --non-interactive --agree-tos -m admin@ladill.com 2>/dev/null || \
certbot certonly --nginx -d ladl.link -d www.ladl.link --non-interactive --agree-tos -m admin@ladill.com 2>/dev/null || \
log "WARN: certbot for ladl.link failed — DNS may not point here yet"
if [ -d /etc/letsencrypt/live/ladl.link ]; then
cat > "$LADL_CONF" <<'NGINX'
server {
listen 80;
listen [::]:80;
server_name ladl.link www.ladl.link;
location ^~ /.well-known/acme-challenge/ { root /var/www/ladill-link/current/public; allow all; }
location / { return 301 https://$host$request_uri; }
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name ladl.link www.ladl.link;
client_max_body_size 64M;
ssl_certificate /etc/letsencrypt/live/ladl.link/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ladl.link/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/ladill-link/current/public;
index index.php;
location / { try_files $uri $uri/ /index.php?$query_string; }
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.4-fpm-ladill.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
}
location ~ /\.(?!well-known).* { deny all; }
}
NGINX
nginx -t && systemctl reload nginx
fi
fi
log "Wiring monolith billing + link DB (if monolith has link registry)"
if [ -f "$MONOLITH/artisan" ]; then
BILL_KEY="$(cat /root/.ladill-link-bootstrap-billing-key 2>/dev/null || grep '^BILLING_API_KEY_LINK=' "$APP_ROOT/shared/.env" | cut -d= -f2-)"
MONO_ENV="/var/www/ladill.com/shared/.env"
if [ -n "$BILL_KEY" ] && [ -f "$MONO_ENV" ]; then
grep -q '^BILLING_API_KEY_LINK=' "$MONO_ENV" || echo "BILLING_API_KEY_LINK=${BILL_KEY}" >> "$MONO_ENV"
grep -q '^LINK_DB_DATABASE=' "$MONO_ENV" || cat >> "$MONO_ENV" <<EOF
LINK_DB_HOST=127.0.0.1
LINK_DB_DATABASE=ladill_link
LINK_DB_USERNAME=ladill_link
LINK_DB_PASSWORD=$(grep '^DB_PASSWORD=' "$APP_ROOT/shared/.env" | cut -d= -f2-)
LINK_PUBLIC_DOMAIN=ladl.link
EOF
(cd "$MONOLITH" && php artisan config:clear) || true
fi
if grep -q "'link'" "$MONOLITH/config/ladill_apps.php" 2>/dev/null; then
(cd "$MONOLITH" && php artisan ladill:onboard-app link --run-dns) || true
(cd "$MONOLITH" && php artisan dns:sync-subdomains) || true
else
log "Monolith missing link registry — deploy monolith code then re-run onboard"
fi
fi
log "Bootstrap complete"
curl -sI "https://link.ladill.com/up" 2>/dev/null | head -3 || curl -sI "http://link.ladill.com/up" 2>/dev/null | head -3 || true