Ctrl K
ComunidadGuías…

setup-sudo.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/bin/bash
# =============================================================================
# OpenVoiceUI — generic sudo setup script
# Creates: nginx config, Let's Encrypt SSL, systemd service, prestart script
# Run as: sudo bash setup-sudo.sh
# =============================================================================
set -e

# ── Configure these before running ──────────────────────────────────────────
INSTALL_DIR="$(cd "$(dirname "$0")" && pwd)"
DOMAIN="your-domain.com"        # ← EDIT: your actual domain
PORT=5001                        # ← match PORT in your .env (default: 5001)
EMAIL="your@email.com"           # ← EDIT: for Let's Encrypt notifications
SERVICE_NAME="openvoiceui"
RUN_USER="${SUDO_USER:-$(whoami)}"
WWW_DIR="/var/www/${SERVICE_NAME}"          # canvas pages + any web assets
OPENCLAW_TESTED_VERSION="2026.5.7"        # pinned: the openclaw version tested with this release
# ────────────────────────────────────────────────────────────────────────────

# Guard: refuse to run with placeholder values
if [ "$DOMAIN" = "your-domain.com" ] || [ "$EMAIL" = "your@email.com" ]; then
    echo "ERROR: Edit DOMAIN and EMAIL at the top of this script before running."
    echo "       Open setup-sudo.sh in a text editor and set your real domain and email."
    exit 1
fi

# Check .env exists
if [ ! -f "${INSTALL_DIR}/.env" ]; then
    echo "ERROR: No .env file found at ${INSTALL_DIR}/.env"
    echo "       Run: cp ${INSTALL_DIR}/.env.example ${INSTALL_DIR}/.env"
    echo "       Then edit .env and set your API keys before running this script."
    exit 1
fi

# ── OpenClaw gateway setup ────────────────────────────────────────────────────
# Helper: is OpenClaw currently listening on its default port?
_openclaw_running() {
    ss -tlnp 2>/dev/null | grep -q ':18791'
}

# Helper: is the openclaw binary installed anywhere?
_openclaw_installed() {
    command -v openclaw >/dev/null 2>&1 \
        || [ -f "/usr/local/bin/openclaw" ] \
        || [ -f "${HOME}/.local/bin/openclaw" ]
}

# Helper: get installed openclaw version string (e.g. "2026.3.2")
_openclaw_version() {
    local ver=""
    if command -v openclaw >/dev/null 2>&1; then
        ver=$(openclaw --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
    elif [ -f "/usr/local/bin/openclaw" ]; then
        ver=$(/usr/local/bin/openclaw --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
    elif [ -f "${HOME}/.local/bin/openclaw" ]; then
        ver=$(${HOME}/.local/bin/openclaw --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
    fi
    echo "$ver"
}

# Helper: compare two version strings (format: YYYY.M.D)
# Returns: 0 = match, 1 = first is newer, 2 = first is older
_version_compare() {
    local a="$1" b="$2"
    if [ "$a" = "$b" ]; then return 0; fi
    local older
    older=$(printf '%s\n%s' "$a" "$b" | sort -V | head -n1)
    if [ "$older" = "$a" ]; then return 2; else return 1; fi
}

# Helper: display the requirements checklist for existing openclaw installs
_show_requirements() {
    echo ""
    echo "  ┌────────────────────────────────────────────────────────────────────┐"
    echo "  │  OpenVoiceUI Gateway Requirements                                  │"
    echo "  │                                                                    │"
    echo "  │  Your openclaw.json must include these settings for voice to work. │"
    echo "  │  OpenVoiceUI will NOT modify your config — these are for you to    │"
    echo "  │  review and apply manually.                                        │"
    echo "  │                                                                    │"
    echo "  │  VERSION                                                           │"
    echo "  │    Tested: openclaw@${OPENCLAW_TESTED_VERSION}                               │"
    echo "  │    Other versions may work but are not guaranteed.                  │"
    echo "  │                                                                    │"
    echo "  │  GATEWAY (global — affects all agents on this instance)            │"
    echo "  │    gateway.auth.mode: \"token\"                                      │"
    echo "  │    gateway.controlUi.dangerouslyDisableDeviceAuth: true            │"
    echo "  │                                                                    │"
    echo "  │  AGENT (can be scoped to the openvoiceui agent only)              │"
    echo "  │    agents.defaults.thinkingDefault: \"off\"                          │"
    echo "  │    agents.defaults.blockStreamingDefault: \"on\"                     │"
    echo "  │    agents.defaults.timeoutSeconds: 300                             │"
    echo "  │    agents.defaults.compaction.reserveTokensFloor: 120000           │"
    echo "  │                                                                    │"
    echo "  │  Full details: docs/openclaw-requirements.md                       │"
    echo "  └────────────────────────────────────────────────────────────────────┘"
    echo ""
}

echo ""
echo "── OpenClaw Gateway ──────────────────────────────────────────────────────"
echo ""
echo "  OpenVoiceUI uses OpenClaw as its AI gateway for voice conversations."
echo "  Tested version: openclaw@${OPENCLAW_TESTED_VERSION}"
echo ""

OPENCLAW_GATEWAY_URL=$(grep -E "^CLAWDBOT_GATEWAY_URL=" "${INSTALL_DIR}/.env" 2>/dev/null | cut -d= -f2 | tr -d '"' | tr -d "'")
OPENCLAW_GATEWAY_URL="${OPENCLAW_GATEWAY_URL:-ws://127.0.0.1:18791}"
OPENCLAW_TOKEN=$(grep -E "^CLAWDBOT_AUTH_TOKEN=" "${INSTALL_DIR}/.env" 2>/dev/null | cut -d= -f2 | tr -d '"' | tr -d "'")
OPENCLAW_CONFIGURED=false

# Detect current state
HAS_RUNNING=false
HAS_INSTALLED=false
INSTALLED_VERSION=""
if _openclaw_running; then HAS_RUNNING=true; HAS_INSTALLED=true; fi
if _openclaw_installed; then HAS_INSTALLED=true; fi
if [ "$HAS_INSTALLED" = "true" ]; then
    INSTALLED_VERSION=$(_openclaw_version)
fi

# Show options based on current state
if [ "$HAS_RUNNING" = "true" ] || [ "$HAS_INSTALLED" = "true" ]; then
    echo "  Detected: OpenClaw is installed on this system."
    [ -n "$INSTALLED_VERSION" ] && echo "  Installed version: ${INSTALLED_VERSION}"
    [ "$HAS_RUNNING" = "true" ] && echo "  Status: running (port 18791 active)"
    echo ""
    echo "  How would you like to set up the gateway?"
    echo ""
    echo "    1) Install a fresh OpenClaw for OpenVoiceUI (recommended)"
    echo "       Installs openclaw@${OPENCLAW_TESTED_VERSION} alongside your existing install."
    echo "       This will replace the globally installed version."
    echo ""
    echo "    2) Use my existing OpenClaw"
    echo "       You must configure it yourself to meet OpenVoiceUI's requirements."
    echo "       Voice features may not work if settings don't match."
    echo ""
    echo "    3) Skip — I'll set up the gateway later"
    echo ""
    printf "  Choice [1]: "
    read -r CHOICE
    CHOICE="${CHOICE:-1}"
else
    echo "  OpenClaw is not installed on this system."
    echo ""
    echo "  How would you like to set up the gateway?"
    echo ""
    echo "    1) Install OpenClaw now (recommended)"
    echo "       Installs openclaw@${OPENCLAW_TESTED_VERSION} configured for voice conversations."
    echo ""
    echo "    2) Skip — I'll install and configure OpenClaw later"
    echo ""
    printf "  Choice [1]: "
    read -r CHOICE
    CHOICE="${CHOICE:-1}"
    # Map choice 2 to "skip" (choice 3 in the full menu)
    if [ "$CHOICE" = "2" ]; then CHOICE="3"; fi
fi

case "$CHOICE" in
    1)
        # ── Fresh install ──────────────────────────────────────────────────
        echo ""
        echo "  Installing openclaw@${OPENCLAW_TESTED_VERSION}..."

        # Check for npm/pnpm
        if command -v pnpm >/dev/null 2>&1; then
            PKG_MGR="pnpm"
            INSTALL_CMD="pnpm add -g openclaw@${OPENCLAW_TESTED_VERSION}"
        elif command -v npm >/dev/null 2>&1; then
            PKG_MGR="npm"
            INSTALL_CMD="npm i -g openclaw@${OPENCLAW_TESTED_VERSION}"
        else
            echo "  ⚠  Neither npm nor pnpm found. Install Node.js 22+ first:"
            echo "     curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -"
            echo "     sudo apt install -y nodejs"
            echo "  Then re-run this script."
            exit 1
        fi

        echo "  Using ${PKG_MGR}..."
        if $INSTALL_CMD; then
            echo "  ✓ openclaw@${OPENCLAW_TESTED_VERSION} installed."
        else
            echo "  ⚠  Installation failed. Check the output above for errors."
            echo "     You can install manually: ${INSTALL_CMD}"
            printf "  Continue setup without the gateway? [y/N] "
            read -r REPLY
            if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1; fi
        fi

        if _openclaw_installed; then
            # Run onboard wizard if no config exists yet
            OPENCLAW_HOME="$(eval echo "~${RUN_USER}")/.openclaw"
            if [ ! -f "${OPENCLAW_HOME}/openclaw.json" ]; then
                echo ""
                echo "  ── OpenClaw First-Time Setup ───────────────────────────────────"
                echo "  OpenClaw needs to know which AI model to use."
                echo "  The setup wizard will ask for your LLM provider and API key."
                echo "  ────────────────────────────────────────────────────────────────"
                echo ""
                # Run onboard as the service user, not root
                sudo -u "${RUN_USER}" openclaw onboard 2>&1 || true
            fi

            # Start the gateway
            echo ""
            echo "  Starting OpenClaw gateway..."
            if ! _openclaw_running; then
                sudo -u "${RUN_USER}" openclaw gateway &
                WAIT_I=0
                while [ $WAIT_I -lt 10 ]; do
                    sleep 1
                    _openclaw_running && break
                    WAIT_I=$((WAIT_I+1))
                done
            fi

            if _openclaw_running; then
                echo "  ✓ OpenClaw gateway is running on port 18791."
                OPENCLAW_CONFIGURED=true
            else
                echo "  ⚠  Gateway did not start. You can start it manually later:"
                echo "     openclaw gateway"
            fi
        fi
        ;;

    2)
        # ── Use existing install ───────────────────────────────────────────
        echo ""
        echo "  Using your existing OpenClaw installation."

        # Version check
        if [ -n "$INSTALLED_VERSION" ]; then
            _version_compare "$INSTALLED_VERSION" "$OPENCLAW_TESTED_VERSION"
            VERSION_CMP=$?
            if [ $VERSION_CMP -ne 0 ]; then
                echo ""
                echo "  ┌────────────────────────────────────────────────────────────────┐"
                echo "  │  ⚠  VERSION MISMATCH                                          │"
                echo "  │                                                                │"
                printf "  │  %-62s │\n" "Your version:   openclaw@${INSTALLED_VERSION}"
                printf "  │  %-62s │\n" "Tested version: openclaw@${OPENCLAW_TESTED_VERSION}"
                echo "  │                                                                │"
                if [ $VERSION_CMP -eq 1 ]; then
                    echo "  │  Your version is NEWER and may include breaking changes       │"
                    echo "  │  (especially around auth and config validation).              │"
                else
                    echo "  │  Your version is OLDER and may be missing required features.  │"
                fi
                echo "  │  Voice conversations may not work correctly.                  │"
                echo "  │                                                                │"
                printf "  │  %-62s │\n" "To install the tested version:"
                printf "  │  %-62s │\n" "  npm i -g openclaw@${OPENCLAW_TESTED_VERSION}"
                echo "  └────────────────────────────────────────────────────────────────┘"
                echo ""
                printf "  Continue with openclaw@${INSTALLED_VERSION} anyway? [y/N] "
                read -r REPLY
                if [[ ! $REPLY =~ ^[Yy]$ ]]; then
                    echo "  Stopping. Install the tested version and re-run this script."
                    exit 1
                fi
            else
                echo "  ✓ Version matches (${INSTALLED_VERSION})."
            fi
        fi

        # Show requirements checklist
        _show_requirements

        echo "  Please review the requirements above and confirm your OpenClaw"
        echo "  is configured to meet them. OpenVoiceUI will NOT modify your"
        echo "  OpenClaw config."
        echo ""
        printf "  I've reviewed the requirements and my OpenClaw is configured [y/N]: "
        read -r REPLY
        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
            echo ""
            echo "  No problem. Configure your OpenClaw to meet the requirements,"
            echo "  then re-run this script. Or choose option 1 for a fresh install."
            exit 1
        fi

        # Try to use it
        if [ "$HAS_RUNNING" = "true" ]; then
            OPENCLAW_CONFIGURED=true
        elif _openclaw_installed; then
            echo "  OpenClaw is not currently running."
            printf "  Start it now? [Y/n] "
            read -r REPLY
            if [[ ! $REPLY =~ ^[Nn]$ ]]; then
                echo "  Starting..."
                sudo -u "${RUN_USER}" openclaw gateway &
                sleep 5
                if _openclaw_running; then
                    echo "  ✓ OpenClaw started."
                    OPENCLAW_CONFIGURED=true
                else
                    echo "  ⚠  Could not start. Start it manually, then restart OpenVoiceUI."
                fi
            fi
        fi

        # Custom gateway URL
        if [ "$OPENCLAW_CONFIGURED" = "true" ]; then
            printf "  Gateway URL [${OPENCLAW_GATEWAY_URL}]: "
            read -r CUSTOM_URL
            if [ -n "$CUSTOM_URL" ]; then
                sed -i "s|^CLAWDBOT_GATEWAY_URL=.*|CLAWDBOT_GATEWAY_URL=${CUSTOM_URL}|" "${INSTALL_DIR}/.env"
                OPENCLAW_GATEWAY_URL="$CUSTOM_URL"
                echo "  Updated CLAWDBOT_GATEWAY_URL in .env"
            fi
        fi
        ;;

    3|*)
        # ── Skip ───────────────────────────────────────────────────────────
        echo ""
        echo "  ┌────────────────────────────────────────────────────────────────┐"
        echo "  │  OpenVoiceUI needs an AI gateway for voice conversations.      │"
        echo "  │  Without one the server will start but the voice agent will    │"
        echo "  │  not respond to any input.                                     │"
        echo "  │                                                                │"
        echo "  │  To set up later:                                              │"
        echo "  │    1. Install: npm i -g openclaw@${OPENCLAW_TESTED_VERSION}              │"
        echo "  │    2. Run: openclaw onboard                                    │"
        echo "  │    3. Set CLAWDBOT_AUTH_TOKEN in .env                          │"
        echo "  │    4. Restart: sudo systemctl restart openvoiceui              │"
        echo "  │                                                                │"
        echo "  │  Or use Docker instead (includes everything):                  │"
        echo "  │    docker compose up --build                                   │"
        echo "  └────────────────────────────────────────────────────────────────┘"
        echo ""
        printf "  Continue setup without a gateway? [y/N] "
        read -r REPLY
        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
            echo "Stopping."
            exit 1
        fi
        echo "  Continuing — voice conversations will not work until a gateway is configured."
        ;;
esac

# Check auth token is set and not placeholder
if [ "$OPENCLAW_CONFIGURED" = "true" ]; then
    if [ -z "$OPENCLAW_TOKEN" ] || [ "$OPENCLAW_TOKEN" = "your-openclaw-gateway-token" ]; then
        echo ""
        echo "  ⚠  CLAWDBOT_AUTH_TOKEN is not set in .env."
        echo "     OpenClaw is running but OpenVoiceUI cannot authenticate to it."
        echo "     Copy your agent auth token from your OpenClaw workspace and set:"
        echo "       CLAWDBOT_AUTH_TOKEN=your-token-here"
        echo "     in ${INSTALL_DIR}/.env, then restart the service."
    else
        echo "  ✓ Auth token configured. Gateway: ${OPENCLAW_GATEWAY_URL}"
    fi
    echo ""
    echo "  Note: The AI model (LLM) is configured inside your OpenClaw agent"
    echo "  workspace — OpenVoiceUI works with any model OpenClaw is connected to."
    echo "  Voice settings (TTS, profile, wake words) are in the admin dashboard:"
    echo "  https://${DOMAIN}/src/admin.html"
fi
echo ""

echo "=== OpenVoiceUI setup: ${DOMAIN} on port ${PORT} ==="
echo "    Install dir : ${INSTALL_DIR}"
echo "    Service user: ${RUN_USER}"
echo "    WWW dir     : ${WWW_DIR}"
echo ""

# 0. Per-instance www directory (canvas pages, isolated from other users)
echo "[0/6] Creating www directory for ${RUN_USER}..."
mkdir -p "${WWW_DIR}/canvas-pages"
chown -R "${RUN_USER}:${RUN_USER}" "${WWW_DIR}"
chmod -R 755 "${WWW_DIR}"

# 0b. OpenVoiceUI agent workspace (dedicated agent — does NOT touch user's main agent)
OPENCLAW_DIR="$(eval echo "~${RUN_USER}")/.openclaw"
AGENT_TEMPLATE_DIR="${INSTALL_DIR}/setup/openvoiceui-agent"
AGENT_DEST_DIR="${OPENCLAW_DIR}/agents/openvoiceui"

if [ -d "${AGENT_TEMPLATE_DIR}" ] && [ -d "${OPENCLAW_DIR}" ]; then
    if [ -d "${AGENT_DEST_DIR}" ]; then
        echo "  OpenVoiceUI agent already exists at ${AGENT_DEST_DIR} — skipping (not overwriting)"
    else
        echo "  Creating OpenVoiceUI agent workspace at ${AGENT_DEST_DIR}..."
        mkdir -p "${AGENT_DEST_DIR}/memory"
        for f in SOUL.md TOOLS.md AGENTS.md IDENTITY.md MEMORY.md USER.md; do
            if [ -f "${AGENT_TEMPLATE_DIR}/${f}" ]; then
                # Fill in instance-specific placeholder values
                sed "s|{{CANVAS_PAGES_DIR}}|${WWW_DIR}/canvas-pages|g" \
                    "${AGENT_TEMPLATE_DIR}/${f}" > "${AGENT_DEST_DIR}/${f}"
            fi
        done
        chown -R "${RUN_USER}:${RUN_USER}" "${AGENT_DEST_DIR}"
        echo "  Agent workspace created. Register it in ~/.openclaw/openclaw.json:"
        echo "    { id: \"openvoiceui\", workspace: \"${AGENT_DEST_DIR}\", model: { primary: \"your-model\" } }"
        echo "  Then set VOICE_SESSION_PREFIX=voice-openvoiceui in your .env"
    fi

    # transcripts: let the agent READ its own voice transcripts. They are written by the
    # OpenVoiceUI app (its runtime transcripts dir) but the agent only sees its workspace —
    # so without this link a transcript/note "sent to the AI" is invisible to it. Runs whether
    # the agent workspace is fresh or pre-existing (idempotent). Added 2026-06-11.
    if [ -d "${AGENT_DEST_DIR}" ]; then
        mkdir -p "${WWW_DIR}/transcripts"
        chown -R "${RUN_USER}:${RUN_USER}" "${WWW_DIR}/transcripts" 2>/dev/null || true
        ln -sfn "${WWW_DIR}/transcripts" "${AGENT_DEST_DIR}/transcripts"
        echo "  Linked transcripts into agent workspace (${AGENT_DEST_DIR}/transcripts → ${WWW_DIR}/transcripts)"
    fi
elif [ ! -d "${OPENCLAW_DIR}" ]; then
    echo "  OpenClaw not configured yet (~/.openclaw missing) — skipping agent setup"
    echo "  Run setup again after configuring OpenClaw, or manually copy setup/openvoiceui-agent/"
fi

# 0c. Bundled agent skills (./skills) -> OpenClaw's managed skills dir, in a subfolder the
# installer owns: ~/.openclaw/skills/openvoiceui/<skill>/SKILL.md. OpenClaw scans that dir by
# default (no openclaw.json change). Re-running is safe: unchanged files update, files you
# edited are kept, nothing is deleted. After `git pull`, refresh with:
#   node install-skills.js
# Copied, not symlinked: OpenClaw skips skill paths that resolve outside their skills root.
SKILLS_TARGET="${OPENCLAW_DIR}/skills/openvoiceui"
if [ ! -d "${OPENCLAW_DIR}" ]; then
    echo "  OpenClaw not configured yet — skipping bundled skills."
    echo "  Install them later (as ${RUN_USER}): node ${INSTALL_DIR}/install-skills.js"
elif ! command -v node >/dev/null 2>&1; then
    echo "  ⚠  node not found — skipping bundled skills."
    echo "     Install them later (as ${RUN_USER}): node ${INSTALL_DIR}/install-skills.js"
else
    echo "  Installing bundled agent skills into ${SKILLS_TARGET}..."
    if ! sudo -u "${RUN_USER}" -H node "${INSTALL_DIR}/install-skills.js" --target "${SKILLS_TARGET}"; then
        echo "  ⚠  Skill install failed (non-fatal). Retry as ${RUN_USER}: node ${INSTALL_DIR}/install-skills.js"
    fi
fi

# 1. Prestart script (kills stale process on port before service starts)
echo "[1/6] Creating prestart script..."
cat > /usr/local/bin/prestart-${SERVICE_NAME}.sh << PRESTART
#!/bin/bash
PORT=${PORT}
LOG=/var/log/${SERVICE_NAME}.log
PID=\$(fuser \${PORT}/tcp 2>/dev/null)
if [ -n "\$PID" ]; then
    echo "\$(date): Found stale process \$PID on port \$PORT, killing..." | tee -a \$LOG
    kill \$PID 2>/dev/null
    sleep 2
    if kill -0 \$PID 2>/dev/null; then
        kill -9 \$PID 2>/dev/null
        sleep 1
    fi
fi
exit 0
PRESTART
chmod +x /usr/local/bin/prestart-${SERVICE_NAME}.sh

# 2. Systemd service
echo "[2/6] Creating systemd service..."
cat > /etc/systemd/system/${SERVICE_NAME}.service << SERVICE
[Unit]
Description=OpenVoiceUI Voice Agent (${DOMAIN})
After=network.target

[Service]
Type=simple
User=${RUN_USER}
WorkingDirectory=${INSTALL_DIR}
ExecStartPre=/usr/local/bin/prestart-${SERVICE_NAME}.sh
ExecStart=${INSTALL_DIR}/venv/bin/python3 ${INSTALL_DIR}/server.py
Restart=on-failure
RestartSec=10
Environment=PATH=/usr/bin:/usr/local/bin
EnvironmentFile=${INSTALL_DIR}/.env

[Install]
WantedBy=multi-user.target
SERVICE

# 3. Nginx config
# 3a. Write HTTP-only nginx config so certbot can serve ACME challenges
echo "[3/6] Creating nginx config..."
if [ ! -f "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" ]; then
    echo "  No SSL cert yet -- writing HTTP-only config for certbot..."
    cat > /etc/nginx/sites-available/${DOMAIN} << NGINX
server {
    listen 80;
    listen [::]:80;
    server_name ${DOMAIN};

    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;
        proxy_read_timeout 86400;
    }

    client_max_body_size 25M;
}
NGINX
    ln -sf /etc/nginx/sites-available/${DOMAIN} /etc/nginx/sites-enabled/${DOMAIN}
    nginx -t
    systemctl reload nginx

    # 4. Obtain SSL cert (nginx can now serve ACME challenge on port 80)
    echo "[4/6] Obtaining SSL certificate..."
    certbot certonly --nginx -d ${DOMAIN} --non-interactive --agree-tos -m ${EMAIL}
else
    echo "  SSL cert already exists, skipping certbot."
fi

# 3b. Write full HTTPS config (cert now exists)
echo "  Writing HTTPS nginx config..."
cat > /etc/nginx/sites-available/${DOMAIN} << NGINX
server {
    listen 80;
    listen [::]:80;
    server_name ${DOMAIN};
    return 301 https://\$host\$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name ${DOMAIN};

    ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    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;
        proxy_read_timeout 86400;
    }

    client_max_body_size 25M;
}
NGINX

ln -sf /etc/nginx/sites-available/${DOMAIN} /etc/nginx/sites-enabled/${DOMAIN}

# 5. Enable and start service
echo "[5/6] Enabling and starting service..."
nginx -t
systemctl reload nginx
systemctl daemon-reload
systemctl enable ${SERVICE_NAME}.service
systemctl restart ${SERVICE_NAME}.service

sleep 3
systemctl status ${SERVICE_NAME}.service --no-pager

echo "[6/6] Setup complete."
echo ""
echo "=== Done! OpenVoiceUI running at https://${DOMAIN} ==="
echo ""
echo "Useful commands:"
echo "  sudo systemctl status ${SERVICE_NAME}"
echo "  sudo systemctl restart ${SERVICE_NAME}"
echo "  sudo journalctl -u ${SERVICE_NAME} -f"