#!/bin/bash
# ═══════════════════════════════════════════════════════════════════════
#  AliothPress — Cloud Installer
#  Paste this into "User Data" / "Cloud-Init" when creating your server.
#  Works on: Hetzner Cloud, DigitalOcean, Linode, Vultr, AWS, GCP, Azure
#  Requires: Ubuntu 22.04 or 24.04
# ═══════════════════════════════════════════════════════════════════════
#
#  ╔══════════════════════════════════════════════════════════╗
#  ║  CHANGE THIS — your AliothPress download link:          ║
#  ╚══════════════════════════════════════════════════════════╝
DOWNLOAD_URL="https://updates.aliothpress.com/releases/aliothpress-latest.zip"
#
#  After the server is created (2–3 minutes), open:
#    http://YOUR-SERVER-IP
#  The Setup Wizard will guide you through the rest.
# ═══════════════════════════════════════════════════════════════════════

set -e

APP_USER="aliothpress"
APP_DIR="/home/$APP_USER/app"
SERVICE_NAME="aliothpress"
LOG_DIR="/var/log/$SERVICE_NAME"

log() { echo "[AliothPress] $1"; }

# ── 1. System packages ──────────────────────────────────────────────
log "Installing system packages..."
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq \
    python3 python3-venv python3-dev \
    build-essential \
    nginx \
    certbot python3-certbot-nginx \
    unzip curl

# ── 2. Create application user ─────────────────────────────────────
log "Creating application user..."
if ! id "$APP_USER" &>/dev/null; then
    useradd -r -m -s /bin/bash "$APP_USER"
fi

# Allow nginx (www-data) to access the Gunicorn socket inside the home dir
usermod -a -G "$APP_USER" www-data
chmod 750 /home/$APP_USER

# ── 3. Download AliothPress ─────────────────────────────────────────
log "Downloading AliothPress..."
mkdir -p "$APP_DIR"
cd /tmp
curl -fsSL -o aliothpress.zip "$DOWNLOAD_URL"
unzip -qo aliothpress.zip -d aliothpress_extract

# Handle zip with or without top-level folder
EXTRACTED=$(ls aliothpress_extract)
if [ "$(echo "$EXTRACTED" | wc -l)" -eq 1 ] && [ -d "aliothpress_extract/$EXTRACTED" ]; then
    cp -r aliothpress_extract/$EXTRACTED/* "$APP_DIR/"
else
    cp -r aliothpress_extract/* "$APP_DIR/"
fi
rm -rf aliothpress.zip aliothpress_extract

# ── 4. Virtual environment & dependencies ───────────────────────────
log "Setting up Python environment..."
cd "$APP_DIR"
python3 -m venv venv
venv/bin/pip install --upgrade pip --quiet
venv/bin/pip install -r requirements.txt --quiet
venv/bin/pip install gunicorn --quiet

# ── 5. Create .env ──────────────────────────────────────────────────
log "Creating configuration..."
if [ ! -f .env ]; then
    SECRET=$(python3 -c "import secrets; print(secrets.token_hex(32))")
    cat > .env << ENVEOF
SECRET_KEY=$SECRET
DATABASE_URI=sqlite:///aliothpress.db
UPDATE_SERVER_URL=https://updates.aliothpress.com
SERVICE_NAME=$SERVICE_NAME.service
ENVEOF
fi

# ── 6. Create version.txt if missing ───────────────────────────────
if [ ! -f version.txt ]; then
    echo "1.0.0" > version.txt
fi

# ── 7. Fix ownership ───────────────────────────────────────────────
chown -R "$APP_USER:$APP_USER" "$APP_DIR"

# ── 8. Gunicorn + systemd ──────────────────────────────────────────
log "Configuring background service..."
WORKERS=$(python3 -c "import os; print(max(2, min(4, (os.cpu_count() or 1) * 2 + 1)))")
SOCKET="$APP_DIR/$SERVICE_NAME.sock"

mkdir -p "$LOG_DIR"
chown "$APP_USER:$APP_USER" "$LOG_DIR"

cat > /etc/systemd/system/$SERVICE_NAME.service << SVCEOF
[Unit]
Description=AliothPress CMS
After=network.target

[Service]
Type=notify
User=$APP_USER
Group=$APP_USER
UMask=0002
WorkingDirectory=$APP_DIR
Environment="PATH=$APP_DIR/venv/bin:/usr/local/bin:/usr/bin:/bin"
ExecStart=$APP_DIR/venv/bin/gunicorn \
    --workers $WORKERS \
    --preload \
    --bind unix:$SOCKET \
    --timeout 120 \
    --access-logfile $LOG_DIR/access.log \
    --error-logfile $LOG_DIR/error.log \
    app:app
ExecReload=/bin/kill -s HUP \$MAINPID
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
SVCEOF

# ── 9. Nginx ────────────────────────────────────────────────────────
log "Configuring web server..."

# Auto-detect server IP
SERVER_IP=$(curl -4 -s --max-time 5 https://api.ipify.org || curl -4 -s --max-time 5 https://ifconfig.me || echo "_")

cat > /etc/nginx/sites-available/$SERVICE_NAME << NGXEOF
server {
    listen 80 default_server;
    server_name $SERVER_IP _;

    client_max_body_size 20M;

    location /static/ {
        alias $APP_DIR/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    location /favicon.ico {
        alias $APP_DIR/static/uploads/favicon/favicon.ico;
        expires 30d;
    }

    location / {
        proxy_pass http://unix:$SOCKET;
        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_read_timeout 120;
    }
}
NGXEOF

ln -sf /etc/nginx/sites-available/$SERVICE_NAME /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default

# ── 10. SSL helper script (used by admin panel "Enable HTTPS") ──────
log "Installing SSL helper..."
cat > /usr/local/bin/aliothpress-ssl << 'SSLEOF'
#!/bin/bash
# ─────────────────────────────────────────────────────────────────────
# AliothPress SSL Helper — called by the admin panel, runs as root.
# Usage: aliothpress-ssl <domain> <email>
# Only the aliothpress user can call this via sudoers.
# ─────────────────────────────────────────────────────────────────────
set -e

DOMAIN="$1"
EMAIL="$2"
SERVICE_NAME="aliothpress"
NGINX_CONF="/etc/nginx/sites-available/$SERVICE_NAME"

# ── Validate inputs (defense in depth — Flask also validates) ───────
if [ -z "$DOMAIN" ] || [ -z "$EMAIL" ]; then
    echo "ERROR: Usage: aliothpress-ssl <domain> <email>"
    exit 1
fi

if ! echo "$DOMAIN" | grep -qP '^[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?)*\.[a-z]{2,}$'; then
    echo "ERROR: Invalid domain format"
    exit 1
fi

if [ ${#DOMAIN} -gt 253 ]; then
    echo "ERROR: Domain too long"
    exit 1
fi

if [ ! -f "$NGINX_CONF" ]; then
    echo "ERROR: Nginx config not found at $NGINX_CONF"
    exit 1
fi

# ── Backup nginx config ────────────────────────────────────────────
cp "$NGINX_CONF" "$NGINX_CONF.bak"

# ── Update server_name ──────────────────────────────────────────────
sed -i "s/server_name[[:space:]]\+[^;]*/server_name $DOMAIN/" "$NGINX_CONF"

# ── Test nginx config ──────────────────────────────────────────────
if ! nginx -t 2>/dev/null; then
    cp "$NGINX_CONF.bak" "$NGINX_CONF"
    echo "ERROR: Nginx config test failed — rolled back"
    exit 1
fi

# ── Reload nginx (domain must be active before certbot runs) ───────
systemctl reload nginx

# ── Run certbot ─────────────────────────────────────────────────────
CERTBOT_OUTPUT=$(certbot --nginx -d "$DOMAIN" \
    --non-interactive --agree-tos --email "$EMAIL" \
    --redirect 2>&1) || {
    echo "ERROR: Certbot failed: $CERTBOT_OUTPUT"
    exit 1
}

echo "OK: HTTPS enabled for $DOMAIN"
exit 0
SSLEOF
chmod 755 /usr/local/bin/aliothpress-ssl

# ── 10b. Update-prep helper (used by updater before applying updates) ──
# Restores ownership of app files to the service user. Needed because the
# updater runs as $APP_USER (via gunicorn) but can't chown files that may
# have been edited by root via SSH/SFTP. Paths are hardcoded — the helper
# accepts no arguments, so it can't be used to chown arbitrary directories.
log "Installing update-prep helper..."
cat > /usr/local/bin/aliothpress-update-prep << 'UPDEOF'
#!/bin/bash
# ─────────────────────────────────────────────────────────────────────
# AliothPress Update-Prep Helper — called by the updater, runs as root.
# Usage: aliothpress-update-prep  (no arguments)
# Only the aliothpress user can call this via sudoers.
# ─────────────────────────────────────────────────────────────────────
set -e

APP_DIR="/home/aliothpress/app"
APP_USER="aliothpress"

# Hardcoded paths — no argument injection possible.
if [ ! -d "$APP_DIR" ]; then
    echo "ERROR: App directory not found at $APP_DIR"
    exit 1
fi

if ! id -u "$APP_USER" >/dev/null 2>&1; then
    echo "ERROR: Service user $APP_USER not found"
    exit 1
fi

chown -R "$APP_USER:$APP_USER" "$APP_DIR"
echo "OK: Ownership restored on $APP_DIR"
exit 0
UPDEOF
chmod 755 /usr/local/bin/aliothpress-update-prep

# ── 11. Sudoers — allow aliothpress user to run specific commands ───
cat > /etc/sudoers.d/$SERVICE_NAME << SUDEOF
$APP_USER ALL=(ALL) NOPASSWD: /bin/systemctl restart $SERVICE_NAME.service
$APP_USER ALL=(ALL) NOPASSWD: /bin/systemctl reload $SERVICE_NAME.service
$APP_USER ALL=(postgres) NOPASSWD: /usr/bin/psql
$APP_USER ALL=(ALL) NOPASSWD: /usr/local/bin/aliothpress-ssl
$APP_USER ALL=(ALL) NOPASSWD: /usr/local/bin/aliothpress-update-prep
$APP_USER ALL=(ALL) NOPASSWD: /usr/bin/apt-get update *
$APP_USER ALL=(ALL) NOPASSWD: /usr/bin/apt-get install *
$APP_USER ALL=(ALL) NOPASSWD: /bin/systemctl start postgresql
$APP_USER ALL=(ALL) NOPASSWD: /bin/systemctl enable postgresql
SUDEOF
chmod 440 /etc/sudoers.d/$SERVICE_NAME

# ── 12. Start everything ───────────────────────────────────────────
log "Starting services..."
systemctl daemon-reload
systemctl enable $SERVICE_NAME
systemctl start $SERVICE_NAME
systemctl restart nginx

log "═══════════════════════════════════════════════════════"
log "  AliothPress is ready!"
log "  Open in your browser: http://$SERVER_IP"
log "═══════════════════════════════════════════════════════"
