From cbc8ee39ff774538508b07d4fca1ef0bdebd39fa Mon Sep 17 00:00:00 2001 From: Pelican Installer Bot Date: Sun, 17 May 2026 12:24:58 +0000 Subject: [PATCH] v2.1.0: all /installer options as CLI flags, Wings reverse proxy, Redis, admin user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NEW — Every /installer wizard field is now a CLI flag: --cache-driver (file|redis) --redis-host, --redis-port, --redis-user, --redis-password --session-driver (file|database|cookie|redis) --queue-driver (database|redis|sync) --app-name --admin-email, --admin-user, --admin-pass (skip web installer entirely) NEW — Redis auto-install: When cache/session/queue uses redis + host is localhost → installs redis-server Configures redis password if provided Installs php-redis extension alongside other PHP extensions NEW — Wings reverse proxy for all 3 web servers: --wings-domain, --wings-port, --wings-proxy, --wings-ssl Nginx: full config with websocket upgrade for console/file manager Apache: mod_proxy + mod_proxy_wstunnel + RewriteEngine for WS Caddy: reverse_proxy with flush_interval -1 for websockets SSL certs obtained for both panel + wings domains in one pass Post-install instructions tell user to set api.ssl.enabled: false NEW — Admin user auto-creation: If --admin-email/user/pass are all set, creates user via artisan tinker Sets APP_INSTALLED=true so /installer redirects to dashboard Shows admin credentials in green box IMPROVED — Interactive menus now ask for all options: App name, cache driver, session driver, queue driver Redis connection details when redis is selected Wings proxy setup during wings install Admin user creation (optional, can still use web installer) Health checks now verify: CACHE_STORE, SESSION_DRIVER, QUEUE_CONNECTION in .env Health checks now verify: Redis running (when redis driver selected) --- install.sh | 918 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 798 insertions(+), 120 deletions(-) diff --git a/install.sh b/install.sh index f61d9a3..5df4478 100644 --- a/install.sh +++ b/install.sh @@ -12,16 +12,20 @@ # # # Features: # # • Full CLI flags for automated/unattended installs # +# • Every /installer wizard field available as a flag # # • Supports Nginx, Apache, and Caddy (auto-SSL) web servers # +# • Full reverse proxy for Wings daemon on all 3 web servers # # • Supports SQLite, MySQL, MariaDB, and PostgreSQL databases # +# • Redis caching/session/queue support with auto-install # # • Automatic SSL via Let's Encrypt or Caddy # -# • Full Wings daemon installation with systemd + Docker # -# • Writes all config to .env — panel works immediately after install # -# • Runs database migrations automatically # -# • Health checks & post-install verification # -# • Colored terminal UI with step-by-step progress # +# • Creates admin user automatically (skip web installer entirely) # +# • Writes ALL config to .env — panel works immediately after install # +# • Runs database migrations & seeders automatically # +# • Post-install health checks & HTTP verification # +# • Queue worker systemd service + cron auto-setup # # • Comprehensive logging to /var/log/pelican-installer.log # -# • Uninstall & Update commands # +# • Update & Uninstall commands # +# • ARM64 support # # • Single script — no external dependencies # # # ############################################################################################# @@ -30,33 +34,64 @@ # Version & Defaults # ═══════════════════════════════════════════════════════════════════════════════ -INSTALLER_VERSION="2.0.0" +INSTALLER_VERSION="2.1.0" PELICAN_PANEL_URL="https://github.com/pelican-dev/panel/releases/latest/download/panel.tar.gz" PELICAN_WINGS_BASE="https://github.com/pelican-dev/wings/releases/latest/download" # ── Configurable variables (override via flags or environment) ── + +# Paths PELICAN_DIR="${PELICAN_DIR:-/var/www/pelican}" WINGS_DIR="${WINGS_DIR:-/etc/pelican}" WINGS_BIN="${WINGS_BIN:-/usr/local/bin/wings}" LOG_FILE="${LOG_FILE:-/var/log/pelican-installer.log}" +# Panel basics FQDN="${FQDN:-}" -WEBSERVER="${WEBSERVER:-}" # nginx | apache | caddy -DB_CHOICE="${DB_CHOICE:-}" # sqlite | mysql | mariadb | postgres +WEBSERVER="${WEBSERVER:-}" # nginx | apache | caddy +APP_NAME="${APP_NAME:-Pelican}" +APP_URL="${APP_URL:-}" # auto-set from FQDN if empty +TIMEZONE="${TIMEZONE:-UTC}" +CONFIGURE_SSL="${CONFIGURE_SSL:-}" # true | false +EMAIL="${EMAIL:-}" + +# Admin user (skip web installer entirely) +ADMIN_EMAIL="${ADMIN_EMAIL:-}" +ADMIN_USER="${ADMIN_USER:-}" +ADMIN_PASSWORD="${ADMIN_PASSWORD:-}" + +# Database +DB_CHOICE="${DB_CHOICE:-}" # sqlite | mysql | mariadb | postgres DB_HOST="${DB_HOST:-127.0.0.1}" -DB_PORT="${DB_PORT:-}" # auto-set based on DB_CHOICE if empty +DB_PORT="${DB_PORT:-}" # auto-set based on DB_CHOICE if empty DB_NAME="${DB_NAME:-pelican}" DB_USER="${DB_USER:-pelican}" -DB_PASSWORD="${DB_PASSWORD:-}" # auto-generated if empty -CONFIGURE_SSL="${CONFIGURE_SSL:-}" # true | false -EMAIL="${EMAIL:-}" -TIMEZONE="${TIMEZONE:-UTC}" -APP_URL="${APP_URL:-}" # auto-set from FQDN if empty +DB_PASSWORD="${DB_PASSWORD:-}" # auto-generated if empty +# Cache +CACHE_DRIVER="${CACHE_DRIVER:-}" # file | redis +REDIS_HOST="${REDIS_HOST:-127.0.0.1}" +REDIS_PORT="${REDIS_PORT:-6379}" +REDIS_USER="${REDIS_USER:-}" +REDIS_PASSWORD="${REDIS_PASSWORD:-}" + +# Session +SESSION_DRIVER="${SESSION_DRIVER:-}" # file | database | cookie | redis + +# Queue +QUEUE_DRIVER="${QUEUE_DRIVER:-}" # database | redis | sync + +# Wings INSTALL_PANEL="${INSTALL_PANEL:-}" INSTALL_WINGS_FLAG="${INSTALL_WINGS_FLAG:-}" +WINGS_DOMAIN="${WINGS_DOMAIN:-}" # FQDN for Wings reverse proxy (e.g., node.example.com) +WINGS_PORT="${WINGS_PORT:-8080}" # Wings daemon port (default 8080) +WINGS_SSL="${WINGS_SSL:-}" # true | false — SSL for Wings reverse proxy +WINGS_PROXY="${WINGS_PROXY:-}" # true | false — set up reverse proxy for Wings + +# General ASSUME_YES="${ASSUME_YES:-false}" -ACTION="${ACTION:-}" # install | update | uninstall +ACTION="${ACTION:-}" # install | update | uninstall # ═══════════════════════════════════════════════════════════════════════════════ # Colors & Formatting @@ -181,34 +216,71 @@ show_help() { echo " --wings Install/update Wings" echo " --both Install/update Panel + Wings" echo "" - echo " Panel options:" + echo " Panel — General:" echo " --domain FQDN Panel domain name (e.g., panel.example.com)" + echo " --app-name NAME Application name (default: Pelican)" echo " --webserver WS Web server: nginx, apache, caddy" + echo " --ssl Enable SSL via Let's Encrypt" + echo " --no-ssl Disable SSL" + echo " --email EMAIL Email for Let's Encrypt" + echo " --timezone TZ Timezone (default: UTC)" + echo "" + echo " Panel — Admin User (skip web installer):" + echo " --admin-email EMAIL Admin account email" + echo " --admin-user USER Admin account username" + echo " --admin-pass PASS Admin account password" + echo "" + echo " Panel — Database:" echo " --database DB Database: sqlite, mysql, mariadb, postgres" echo " --db-host HOST Database host (default: 127.0.0.1)" echo " --db-port PORT Database port (auto-detected from db type)" echo " --db-name NAME Database name (default: pelican)" echo " --db-user USER Database user (default: pelican)" echo " --db-password PASS Database password (auto-generated if empty)" - echo " --ssl Enable SSL via Let's Encrypt" - echo " --no-ssl Disable SSL" - echo " --email EMAIL Email for Let's Encrypt" - echo " --timezone TZ Timezone (default: UTC)" + echo "" + echo " Panel — Cache:" + echo " --cache-driver DRV Cache driver: file, redis (default: file)" + echo " --redis-host HOST Redis host (default: 127.0.0.1)" + echo " --redis-port PORT Redis port (default: 6379)" + echo " --redis-user USER Redis username (optional, Redis 6+ ACL)" + echo " --redis-password PW Redis password (optional)" + echo "" + echo " Panel — Session:" + echo " --session-driver DRV Session driver: file, database, cookie, redis" + echo "" + echo " Panel — Queue:" + echo " --queue-driver DRV Queue driver: database, redis, sync" + echo "" + echo " Wings:" + echo " --wings-domain FQDN Wings node domain (e.g., node.example.com)" + echo " --wings-port PORT Wings daemon port (default: 8080)" + echo " --wings-proxy Set up web server reverse proxy for Wings" + echo " --wings-ssl Enable SSL for Wings reverse proxy" echo "" echo " General:" echo " --yes, -y Skip all confirmation prompts" echo " --help, -h Show this help" echo "" echo " Examples:" + echo "" echo " # Interactive install" echo " sudo bash install.sh" echo "" - echo " # Fully automated panel install" + echo " # Fully automated panel install (skip web installer entirely)" echo " sudo bash install.sh install --panel --domain panel.example.com \\" + echo " --webserver nginx --database postgres --ssl --email you@example.com \\" + echo " --cache-driver redis --session-driver redis --queue-driver redis \\" + echo " --admin-email admin@example.com --admin-user admin --admin-pass S3cure! \\" + echo " --timezone America/New_York -y" + echo "" + echo " # Panel + Wings with Wings reverse proxy" + echo " sudo bash install.sh install --both --domain panel.example.com \\" + echo " --wings-domain node.example.com --wings-proxy --wings-ssl \\" echo " --webserver nginx --database postgres --ssl --email you@example.com -y" echo "" - echo " # Wings only" - echo " sudo bash install.sh install --wings -y" + echo " # Wings only with Caddy reverse proxy" + echo " sudo bash install.sh install --wings --wings-domain node.example.com \\" + echo " --wings-proxy --webserver caddy -y" echo "" echo " # Update everything" echo " sudo bash install.sh update --both -y" @@ -229,6 +301,8 @@ parse_args() { INSTALL_PANEL=true; INSTALL_WINGS_FLAG=true ;; --domain|--fqdn) FQDN="$2"; shift ;; + --app-name) + APP_NAME="$2"; shift ;; --webserver|--ws) WEBSERVER="$2"; shift ;; --database|--db) @@ -251,6 +325,34 @@ parse_args() { EMAIL="$2"; shift ;; --timezone|--tz) TIMEZONE="$2"; shift ;; + --admin-email) + ADMIN_EMAIL="$2"; shift ;; + --admin-user|--admin-username) + ADMIN_USER="$2"; shift ;; + --admin-pass|--admin-password) + ADMIN_PASSWORD="$2"; shift ;; + --cache-driver|--cache) + CACHE_DRIVER="$2"; shift ;; + --redis-host) + REDIS_HOST="$2"; shift ;; + --redis-port) + REDIS_PORT="$2"; shift ;; + --redis-user|--redis-username) + REDIS_USER="$2"; shift ;; + --redis-password|--redis-pass) + REDIS_PASSWORD="$2"; shift ;; + --session-driver|--session) + SESSION_DRIVER="$2"; shift ;; + --queue-driver|--queue) + QUEUE_DRIVER="$2"; shift ;; + --wings-domain) + WINGS_DOMAIN="$2"; shift ;; + --wings-port) + WINGS_PORT="$2"; shift ;; + --wings-proxy) + WINGS_PROXY=true ;; + --wings-ssl) + WINGS_SSL=true ;; --yes|-y) ASSUME_YES=true ;; --help|-h) @@ -306,7 +408,11 @@ detect_os() { success "OS: ${BOLD}$OS_NAME${RESET} ($OS_FAMILY/$PKG_MANAGER)" success "Architecture: ${BOLD}$ARCH${RESET} → Wings binary: $WINGS_ARCH" - # Detect web server user + # Detect web server user (updated later when WEBSERVER is chosen) + update_webserver_user +} + +update_webserver_user() { if [[ "$OS_FAMILY" == "rhel" ]]; then case "${WEBSERVER:-nginx}" in nginx) WEBSERVER_USER="nginx" ;; @@ -417,7 +523,7 @@ install_php() { "php${PHP_VERSION}-pgsql" "php${PHP_VERSION}-mbstring" "php${PHP_VERSION}-bcmath" "php${PHP_VERSION}-xml" "php${PHP_VERSION}-curl" "php${PHP_VERSION}-zip" "php${PHP_VERSION}-intl" "php${PHP_VERSION}-sqlite3" "php${PHP_VERSION}-tokenizer" - "php${PHP_VERSION}-fileinfo" "php${PHP_VERSION}-dom" + "php${PHP_VERSION}-fileinfo" "php${PHP_VERSION}-dom" "php${PHP_VERSION}-redis" ) run_cmd "Installing PHP ${PHP_VERSION} and extensions" apt install -y "${pkgs[@]}" @@ -430,7 +536,8 @@ install_php() { run_cmd "Installing PHP ${PHP_VERSION} and extensions" dnf install -y \ php php-fpm php-common php-cli php-gd php-mysqlnd php-pgsql \ - php-mbstring php-bcmath php-xml php-curl php-zip php-intl php-pdo php-tokenizer + php-mbstring php-bcmath php-xml php-curl php-zip php-intl php-pdo \ + php-tokenizer php-redis fi # Configure PHP-FPM pool @@ -454,6 +561,51 @@ install_php() { success "PHP-FPM started and enabled" } +# ═══════════════════════════════════════════════════════════════════════════════ +# Redis Installation +# ═══════════════════════════════════════════════════════════════════════════════ + +needs_redis() { + [[ "$CACHE_DRIVER" == "redis" || "$SESSION_DRIVER" == "redis" || "$QUEUE_DRIVER" == "redis" ]] +} + +install_redis() { + if ! needs_redis; then + return + fi + + # Only install if using localhost and redis isn't already running + if [[ "$REDIS_HOST" == "127.0.0.1" || "$REDIS_HOST" == "localhost" || "$REDIS_HOST" == "::1" ]]; then + if command -v redis-server &>/dev/null && systemctl is-active --quiet redis-server 2>/dev/null; then + success "Redis is already installed and running" + else + step "Installing Redis" + if [[ "$OS_FAMILY" == "debian" ]]; then + run_cmd "Installing Redis server" apt install -y redis-server + else + run_cmd "Installing Redis server" dnf install -y redis + fi + + # Configure Redis password if set + if [[ -n "$REDIS_PASSWORD" ]]; then + local redis_conf="/etc/redis/redis.conf" + [[ ! -f "$redis_conf" ]] && redis_conf="/etc/redis.conf" + if [[ -f "$redis_conf" ]]; then + sed -i "s/^# requirepass .*/requirepass ${REDIS_PASSWORD}/" "$redis_conf" + sed -i "s/^requirepass .*/requirepass ${REDIS_PASSWORD}/" "$redis_conf" + success "Redis password configured" + fi + fi + + systemctl enable --now redis-server >> "$LOG_FILE" 2>&1 || \ + systemctl enable --now redis >> "$LOG_FILE" 2>&1 + success "Redis installed and running" + fi + else + info "Redis host is ${REDIS_HOST} — skipping local Redis install" + fi +} + # ═══════════════════════════════════════════════════════════════════════════════ # Database Installation # ═══════════════════════════════════════════════════════════════════════════════ @@ -540,7 +692,6 @@ EOSQL postgres) # Create user and database sudo -u postgres psql -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" >> "$LOG_FILE" 2>&1 || { - # User might already exist, try ALTER sudo -u postgres psql -c "ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" >> "$LOG_FILE" 2>&1 } sudo -u postgres psql -c "CREATE DATABASE ${DB_NAME} OWNER ${DB_USER};" >> "$LOG_FILE" 2>&1 || { @@ -552,16 +703,13 @@ EOSQL local pg_hba pg_hba=$(sudo -u postgres psql -t -c "SHOW hba_file;" 2>/dev/null | xargs) if [[ -n "$pg_hba" && -f "$pg_hba" ]]; then - # Add md5 auth for our user before any existing host rules - if ! grep -q "pelican" "$pg_hba" 2>/dev/null; then - # Insert password auth rule at the top of the host section + if ! grep -q "${DB_USER}" "$pg_hba" 2>/dev/null; then sed -i "/^# IPv4 local connections:/a host ${DB_NAME} ${DB_USER} 127.0.0.1/32 md5" "$pg_hba" sed -i "/^# IPv6 local connections:/a host ${DB_NAME} ${DB_USER} ::1/128 md5" "$pg_hba" success "pg_hba.conf configured for password authentication" systemctl reload postgresql >> "$LOG_FILE" 2>&1 fi else - # Fallback: try common paths for hba_path in /etc/postgresql/*/main/pg_hba.conf /var/lib/pgsql/data/pg_hba.conf; do if [[ -f "$hba_path" ]]; then if ! grep -q "${DB_USER}" "$hba_path"; then @@ -615,6 +763,8 @@ install_webserver() { esac } +# ── Nginx ──────────────────────────────────────────────────────────────────── + install_nginx() { if [[ "$OS_FAMILY" == "debian" ]]; then run_cmd "Installing Nginx" apt install -y nginx @@ -633,22 +783,29 @@ install_nginx() { local fpm_sock fpm_sock=$(get_fpm_socket) - if [[ "$CONFIGURE_SSL" == true ]]; then - write_nginx_ssl_config "$fpm_sock" - else - write_nginx_http_config "$fpm_sock" + if [[ "$INSTALL_PANEL" == true ]]; then + write_nginx_panel_config "$fpm_sock" + ln -sf /etc/nginx/sites-available/pelican.conf /etc/nginx/sites-enabled/pelican.conf + success "Nginx panel vhost configured for ${FQDN}" + fi + + if [[ "$WINGS_PROXY" == true && -n "$WINGS_DOMAIN" ]]; then + write_nginx_wings_config + ln -sf /etc/nginx/sites-available/wings.conf /etc/nginx/sites-enabled/wings.conf + success "Nginx wings reverse proxy configured for ${WINGS_DOMAIN}" fi - ln -sf /etc/nginx/sites-available/pelican.conf /etc/nginx/sites-enabled/pelican.conf nginx -t >> "$LOG_FILE" 2>&1 || warn "Nginx config test failed — check ${LOG_FILE}" systemctl enable --now nginx >> "$LOG_FILE" 2>&1 systemctl restart nginx >> "$LOG_FILE" 2>&1 success "Nginx configured and running" } -write_nginx_ssl_config() { +write_nginx_panel_config() { local fpm_sock="$1" - cat > /etc/nginx/sites-available/pelican.conf < /etc/nginx/sites-available/pelican.conf < /etc/nginx/sites-available/pelican.conf < /etc/nginx/sites-available/pelican.conf < /etc/nginx/sites-available/wings.conf <[a-f0-9-]+)/ws\$ { + proxy_pass http://127.0.0.1:${WINGS_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_buffering off; + proxy_request_buffering off; + proxy_read_timeout 86400; + proxy_send_timeout 86400; + } + + location / { + proxy_pass http://127.0.0.1:${WINGS_PORT}; + proxy_http_version 1.1; + 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_buffering off; + proxy_request_buffering off; + client_max_body_size 0; + } +} +WINGSCFG + else + cat > /etc/nginx/sites-available/wings.conf <[a-f0-9-]+)/ws\$ { + proxy_pass http://127.0.0.1:${WINGS_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_buffering off; + proxy_request_buffering off; + proxy_read_timeout 86400; + proxy_send_timeout 86400; + } + + location / { + proxy_pass http://127.0.0.1:${WINGS_PORT}; + proxy_http_version 1.1; + 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_buffering off; + proxy_request_buffering off; + client_max_body_size 0; + } +} +WINGSCFG + fi +} + +# ── Apache ─────────────────────────────────────────────────────────────────── + install_apache() { if [[ "$OS_FAMILY" == "debian" ]]; then run_cmd "Installing Apache" apt install -y apache2 "libapache2-mod-php${PHP_VERSION}" - a2enmod rewrite ssl headers proxy proxy_fcgi >> "$LOG_FILE" 2>&1 || true + a2enmod rewrite ssl headers proxy proxy_fcgi proxy_http proxy_wstunnel >> "$LOG_FILE" 2>&1 || true a2dissite 000-default >> "$LOG_FILE" 2>&1 || true else run_cmd "Installing Apache" dnf install -y httpd mod_ssl @@ -779,6 +1023,36 @@ install_apache() { local conf_dir="/etc/apache2/sites-available" [[ "$OS_FAMILY" == "rhel" ]] && conf_dir="/etc/httpd/conf.d" + if [[ "$INSTALL_PANEL" == true ]]; then + write_apache_panel_config "$conf_dir" "$fpm_sock" + if [[ "$OS_FAMILY" == "debian" ]]; then + a2ensite pelican >> "$LOG_FILE" 2>&1 + fi + success "Apache panel vhost configured for ${FQDN}" + fi + + if [[ "$WINGS_PROXY" == true && -n "$WINGS_DOMAIN" ]]; then + write_apache_wings_config "$conf_dir" + if [[ "$OS_FAMILY" == "debian" ]]; then + a2ensite wings >> "$LOG_FILE" 2>&1 + fi + success "Apache wings reverse proxy configured for ${WINGS_DOMAIN}" + fi + + if [[ "$OS_FAMILY" == "debian" ]]; then + systemctl enable --now apache2 >> "$LOG_FILE" 2>&1 + systemctl restart apache2 >> "$LOG_FILE" 2>&1 + else + systemctl enable --now httpd >> "$LOG_FILE" 2>&1 + systemctl restart httpd >> "$LOG_FILE" 2>&1 + fi + success "Apache configured and running" +} + +write_apache_panel_config() { + local conf_dir="$1" + local fpm_sock="$2" + if [[ "$CONFIGURE_SSL" == true ]]; then cat > "${conf_dir}/pelican.conf" < @@ -821,18 +1095,64 @@ APACHECFG APACHECFG fi - - if [[ "$OS_FAMILY" == "debian" ]]; then - a2ensite pelican >> "$LOG_FILE" 2>&1 - systemctl enable --now apache2 >> "$LOG_FILE" 2>&1 - systemctl restart apache2 >> "$LOG_FILE" 2>&1 - else - systemctl enable --now httpd >> "$LOG_FILE" 2>&1 - systemctl restart httpd >> "$LOG_FILE" 2>&1 - fi - success "Apache configured and running" } +write_apache_wings_config() { + local conf_dir="$1" + + if [[ "$WINGS_SSL" == true ]]; then + cat > "${conf_dir}/wings.conf" < + ServerName ${WINGS_DOMAIN} + Redirect permanent / https://${WINGS_DOMAIN}/ + + + + ServerName ${WINGS_DOMAIN} + + SSLEngine on + SSLCertificateFile /etc/letsencrypt/live/${WINGS_DOMAIN}/fullchain.pem + SSLCertificateKeyFile /etc/letsencrypt/live/${WINGS_DOMAIN}/privkey.pem + + SSLProxyEngine On + ProxyPreserveHost On + + # Websocket support + RewriteEngine On + RewriteCond %{HTTP:Upgrade} websocket [NC] + RewriteCond %{HTTP:Connection} upgrade [NC] + RewriteRule ^/(.*) ws://127.0.0.1:${WINGS_PORT}/\$1 [P,L] + + ProxyPass / http://127.0.0.1:${WINGS_PORT}/ + ProxyPassReverse / http://127.0.0.1:${WINGS_PORT}/ + + RequestHeader set X-Forwarded-Proto "https" + +WINGSCFG + else + cat > "${conf_dir}/wings.conf" < + ServerName ${WINGS_DOMAIN} + + ProxyPreserveHost On + + # Websocket support + RewriteEngine On + RewriteCond %{HTTP:Upgrade} websocket [NC] + RewriteCond %{HTTP:Connection} upgrade [NC] + RewriteRule ^/(.*) ws://127.0.0.1:${WINGS_PORT}/\$1 [P,L] + + ProxyPass / http://127.0.0.1:${WINGS_PORT}/ + ProxyPassReverse / http://127.0.0.1:${WINGS_PORT}/ + + RequestHeader set X-Forwarded-Proto "http" + +WINGSCFG + fi +} + +# ── Caddy ──────────────────────────────────────────────────────────────────── + install_caddy() { if [[ "$OS_FAMILY" == "debian" ]]; then apt install -y debian-keyring debian-archive-keyring apt-transport-https >> "$LOG_FILE" 2>&1 @@ -851,7 +1171,10 @@ install_caddy() { local fpm_sock fpm_sock=$(get_fpm_socket) - cat > /etc/caddy/Caddyfile < /etc/caddy/Caddyfile mkdir -p /var/log/caddy systemctl enable --now caddy >> "$LOG_FILE" 2>&1 systemctl restart caddy >> "$LOG_FILE" 2>&1 - success "Caddy configured and running (auto-SSL enabled)" + + [[ "$INSTALL_PANEL" == true ]] && success "Caddy panel vhost configured for ${FQDN} (auto-SSL)" + [[ "$WINGS_PROXY" == true && -n "$WINGS_DOMAIN" ]] && success "Caddy wings reverse proxy configured for ${WINGS_DOMAIN} (auto-SSL)" + success "Caddy configured and running" } # ═══════════════════════════════════════════════════════════════════════════════ @@ -890,7 +1241,7 @@ CADDYCFG # ═══════════════════════════════════════════════════════════════════════════════ install_ssl() { - if [[ "$CONFIGURE_SSL" != true ]]; then + if [[ "$CONFIGURE_SSL" != true && "$WINGS_SSL" != true ]]; then return fi if [[ "$WEBSERVER" == "caddy" ]]; then @@ -898,7 +1249,7 @@ install_ssl() { return fi - step "Obtaining SSL certificate via Let's Encrypt" + step "Obtaining SSL certificate(s) via Let's Encrypt" if [[ "$OS_FAMILY" == "debian" ]]; then apt install -y certbot >> "$LOG_FILE" 2>&1 @@ -914,17 +1265,29 @@ install_ssl() { [[ "$WEBSERVER" == "nginx" ]] && plugin="--nginx" [[ "$WEBSERVER" == "apache" ]] && plugin="--apache" - certbot certonly $plugin -d "$FQDN" --non-interactive --agree-tos --email "$EMAIL" >> "$LOG_FILE" 2>&1 || { - warn "Plugin-based cert failed, trying standalone..." - systemctl stop "${WEBSERVER}" 2>/dev/null || systemctl stop apache2 2>/dev/null || systemctl stop httpd 2>/dev/null || true - certbot certonly --standalone -d "$FQDN" --non-interactive --agree-tos --email "$EMAIL" >> "$LOG_FILE" 2>&1 || \ - fatal "Failed to obtain SSL certificate. Is the domain pointed to this server?" - systemctl start "${WEBSERVER}" 2>/dev/null || systemctl start apache2 2>/dev/null || systemctl start httpd 2>/dev/null || true - } + # Collect all domains that need certs + local domains=() + [[ "$CONFIGURE_SSL" == true && -n "$FQDN" ]] && domains+=("$FQDN") + [[ "$WINGS_SSL" == true && -n "$WINGS_DOMAIN" && "$WINGS_DOMAIN" != "$FQDN" ]] && domains+=("$WINGS_DOMAIN") + + for domain in "${domains[@]}"; do + info "Requesting certificate for ${domain}..." + certbot certonly $plugin -d "$domain" --non-interactive --agree-tos --email "$EMAIL" >> "$LOG_FILE" 2>&1 || { + warn "Plugin-based cert for $domain failed, trying standalone..." + systemctl stop nginx 2>/dev/null; systemctl stop apache2 2>/dev/null; systemctl stop httpd 2>/dev/null + certbot certonly --standalone -d "$domain" --non-interactive --agree-tos --email "$EMAIL" >> "$LOG_FILE" 2>&1 || \ + error "Failed to obtain SSL certificate for $domain — is the domain pointed to this server?" + systemctl start nginx 2>/dev/null || systemctl start apache2 2>/dev/null || systemctl start httpd 2>/dev/null + } + success "SSL certificate obtained for ${domain}" + done # Auto-renewal cron - (crontab -l 2>/dev/null; echo "0 3 * * * certbot renew --quiet --deploy-hook \"systemctl restart ${WEBSERVER}\"") | sort -u | crontab - - success "SSL certificate obtained — auto-renewal enabled" + local restart_svc="$WEBSERVER" + [[ "$WEBSERVER" == "apache" && "$OS_FAMILY" == "debian" ]] && restart_svc="apache2" + [[ "$WEBSERVER" == "apache" && "$OS_FAMILY" == "rhel" ]] && restart_svc="httpd" + (crontab -l 2>/dev/null; echo "0 3 * * * certbot renew --quiet --deploy-hook \"systemctl restart ${restart_svc}\"") | sort -u | crontab - + success "Auto-renewal cron configured" } # ═══════════════════════════════════════════════════════════════════════════════ @@ -956,6 +1319,7 @@ install_panel() { configure_env_file run_database_setup set_permissions + create_admin_user setup_queue_worker setup_cron_job @@ -988,17 +1352,24 @@ configure_env_file() { # Determine DB connection string for Laravel local laravel_db_connection case "$DB_CHOICE" in - mysql|mariadb) laravel_db_connection="mysql" ;; - postgres) laravel_db_connection="pgsql" ;; - sqlite) laravel_db_connection="sqlite" ;; + mysql) laravel_db_connection="mysql" ;; + mariadb) laravel_db_connection="mariadb" ;; + postgres) laravel_db_connection="pgsql" ;; + sqlite) laravel_db_connection="sqlite" ;; esac + # Set defaults for session/cache/queue if not set + CACHE_DRIVER="${CACHE_DRIVER:-file}" + SESSION_DRIVER="${SESSION_DRIVER:-file}" + QUEUE_DRIVER="${QUEUE_DRIVER:-database}" + # Copy example .env if needed if [[ ! -f "${PELICAN_DIR}/.env" && -f "${PELICAN_DIR}/.env.example" ]]; then cp "${PELICAN_DIR}/.env.example" "${PELICAN_DIR}/.env" fi # Generate APP_KEY + cd "$PELICAN_DIR" php artisan key:generate --force --no-interaction >> "$LOG_FILE" 2>&1 success "Application key generated" @@ -1007,24 +1378,27 @@ configure_env_file() { local key="$1" local value="$2" if grep -q "^${key}=" "${PELICAN_DIR}/.env" 2>/dev/null; then - sed -i "s|^${key}=.*|${key}=${value}|" "${PELICAN_DIR}/.env" + # Escape special chars in value for sed + local escaped_value + escaped_value=$(printf '%s' "$value" | sed 's/[&/\]/\\&/g') + sed -i "s|^${key}=.*|${key}=${escaped_value}|" "${PELICAN_DIR}/.env" else echo "${key}=${value}" >> "${PELICAN_DIR}/.env" fi } - # Core settings + # ── Core settings ── + set_env "APP_NAME" "$APP_NAME" set_env "APP_URL" "$APP_URL" set_env "APP_TIMEZONE" "$TIMEZONE" set_env "APP_ENV" "production" set_env "APP_DEBUG" "false" - # Database settings + # ── Database settings ── set_env "DB_CONNECTION" "$laravel_db_connection" if [[ "$DB_CHOICE" == "sqlite" ]]; then set_env "DB_DATABASE" "${PELICAN_DIR}/database/database.sqlite" - # Create SQLite file touch "${PELICAN_DIR}/database/database.sqlite" else set_env "DB_HOST" "$DB_HOST" @@ -1034,13 +1408,28 @@ configure_env_file() { set_env "DB_PASSWORD" "$DB_PASSWORD" fi - # Cache & session (file driver — reliable defaults) - set_env "CACHE_STORE" "file" - set_env "SESSION_DRIVER" "file" - set_env "QUEUE_CONNECTION" "database" + # ── Cache settings ── + set_env "CACHE_STORE" "$CACHE_DRIVER" - success ".env configured with all database and app settings" - log "ENV configured: APP_URL=$APP_URL DB_CONNECTION=$laravel_db_connection DB_HOST=$DB_HOST DB_NAME=$DB_NAME" + if [[ "$CACHE_DRIVER" == "redis" ]]; then + set_env "REDIS_HOST" "$REDIS_HOST" + set_env "REDIS_PORT" "$REDIS_PORT" + [[ -n "$REDIS_USER" ]] && set_env "REDIS_USERNAME" "$REDIS_USER" + [[ -n "$REDIS_PASSWORD" ]] && set_env "REDIS_PASSWORD" "$REDIS_PASSWORD" + fi + + # ── Session settings ── + set_env "SESSION_DRIVER" "$SESSION_DRIVER" + + local session_secure="false" + [[ "$CONFIGURE_SSL" == true || "$WEBSERVER" == "caddy" ]] && session_secure="true" + set_env "SESSION_SECURE_COOKIE" "$session_secure" + + # ── Queue settings ── + set_env "QUEUE_CONNECTION" "$QUEUE_DRIVER" + + success ".env configured with all panel settings" + log "ENV: APP_URL=$APP_URL DB=$laravel_db_connection CACHE=$CACHE_DRIVER SESSION=$SESSION_DRIVER QUEUE=$QUEUE_DRIVER" } run_database_setup() { @@ -1048,7 +1437,6 @@ run_database_setup() { cd "$PELICAN_DIR" - # Run migrations php artisan migrate --seed --force >> "$LOG_FILE" 2>&1 if [[ $? -ne 0 ]]; then error "Database migration failed — check ${LOG_FILE}" @@ -1072,6 +1460,48 @@ set_permissions() { success "Permissions set (owner: ${WEBSERVER_USER}, storage: 775)" } +create_admin_user() { + if [[ -z "$ADMIN_EMAIL" || -z "$ADMIN_USER" || -z "$ADMIN_PASSWORD" ]]; then + info "Admin user not specified — you'll create one via the web installer at /installer" + return + fi + + step "Creating admin user" + + cd "$PELICAN_DIR" + + # Use artisan tinker to create the admin user + php artisan tinker --execute=" + \$user = new \App\Models\User(); + \$user->email = '${ADMIN_EMAIL}'; + \$user->username = '${ADMIN_USER}'; + \$user->password = bcrypt('${ADMIN_PASSWORD}'); + \$user->root_admin = true; + \$user->save(); + echo 'Admin user created: ${ADMIN_EMAIL}'; + " >> "$LOG_FILE" 2>&1 + + if [[ $? -eq 0 ]]; then + success "Admin user created: ${ADMIN_EMAIL} (${ADMIN_USER})" + + # Mark as installed so /installer redirects to login + set_env "APP_INSTALLED" "true" + success "Panel marked as installed — /installer will redirect to dashboard" + + echo "" + echo -e " ${GREEN}${BOLD}┌─────────────────────────────────────────────────┐${RESET}" + echo -e " ${GREEN}${BOLD}│ ADMIN CREDENTIALS │${RESET}" + echo -e " ${GREEN}${BOLD}├─────────────────────────────────────────────────┤${RESET}" + echo -e " ${GREEN}${BOLD}│${RESET} Email: ${WHITE}${ADMIN_EMAIL}${RESET}" + echo -e " ${GREEN}${BOLD}│${RESET} Username: ${WHITE}${ADMIN_USER}${RESET}" + echo -e " ${GREEN}${BOLD}│${RESET} Password: ${WHITE}${ADMIN_PASSWORD}${RESET}" + echo -e " ${GREEN}${BOLD}└─────────────────────────────────────────────────┘${RESET}" + echo "" + else + warn "Failed to create admin user — you can create one via the web installer at /installer" + fi +} + setup_queue_worker() { info "Creating queue worker service..." @@ -1160,12 +1590,23 @@ WSVC systemctl enable wings >> "$LOG_FILE" 2>&1 success "Wings systemd service created and enabled" + # Write Wings config.yml template if proxy is set up + if [[ "$WINGS_PROXY" == true ]]; then + info "Wings reverse proxy is configured — set api.ssl.enabled: false in config.yml" + info "The reverse proxy terminates SSL, Wings listens on HTTP" + fi + echo "" - echo -e " ${YELLOW}${BOLD}Wings requires manual configuration:${RESET}" + echo -e " ${YELLOW}${BOLD}Wings requires configuration from Panel:${RESET}" echo -e " ${DIM}1. Go to Panel admin → Nodes → Create New${RESET}" echo -e " ${DIM}2. Copy the configuration YAML from the Configuration tab${RESET}" echo -e " ${DIM}3. Paste it into: ${WHITE}${WINGS_DIR}/config.yml${RESET}" - echo -e " ${DIM}4. Then start Wings: ${WHITE}sudo systemctl start wings${RESET}" + if [[ "$WINGS_PROXY" == true ]]; then + echo -e " ${DIM}4. In config.yml, set ${WHITE}api.ssl.enabled: false${RESET}" + echo -e " ${DIM}5. Then start Wings: ${WHITE}sudo systemctl start wings${RESET}" + else + echo -e " ${DIM}4. Then start Wings: ${WHITE}sudo systemctl start wings${RESET}" + fi echo "" } @@ -1228,6 +1669,11 @@ uninstall_wings() { systemctl disable wings 2>/dev/null || true rm -f /etc/systemd/system/wings.service rm -f "$WINGS_BIN" + + rm -f /etc/nginx/sites-enabled/wings.conf /etc/nginx/sites-available/wings.conf 2>/dev/null || true + rm -f /etc/apache2/sites-enabled/wings.conf /etc/apache2/sites-available/wings.conf 2>/dev/null || true + rm -f /etc/httpd/conf.d/wings.conf 2>/dev/null || true + systemctl daemon-reload if confirm "Also remove Wings config (${WINGS_DIR})? (y/N):" "N"; then @@ -1317,7 +1763,7 @@ health_check() { fi # Web server - if [[ "$INSTALL_PANEL" == true ]]; then + if [[ "$INSTALL_PANEL" == true || "$WINGS_PROXY" == true ]]; then local ws_active=false for svc in nginx apache2 httpd caddy; do if systemctl is-active --quiet "$svc" 2>/dev/null; then @@ -1327,7 +1773,9 @@ health_check() { fi done [[ "$ws_active" == false ]] && check_fail "No web server is running" + fi + if [[ "$INSTALL_PANEL" == true ]]; then # Panel files if [[ -f "${PELICAN_DIR}/artisan" ]]; then check_pass "Panel files present at ${PELICAN_DIR}" @@ -1344,11 +1792,20 @@ health_check() { # .env has DB_CONNECTION if grep -q "^DB_CONNECTION=" "${PELICAN_DIR}/.env" 2>/dev/null; then - check_pass ".env has database configuration" + check_pass ".env has database config" else - check_fail ".env missing database configuration" + check_fail ".env missing database config" fi + # .env has cache/session/queue config + for setting in CACHE_STORE SESSION_DRIVER QUEUE_CONNECTION; do + if grep -q "^${setting}=" "${PELICAN_DIR}/.env" 2>/dev/null; then + check_pass ".env has ${setting}" + else + check_fail ".env missing ${setting}" + fi + done + # Queue worker if systemctl is-active --quiet pelican-queue; then check_pass "Queue worker is running" @@ -1356,7 +1813,7 @@ health_check() { check_fail "Queue worker is not running" fi - # Can artisan run without error + # Artisan CLI if cd "$PELICAN_DIR" && php artisan --version >> "$LOG_FILE" 2>&1; then check_pass "Artisan CLI works" else @@ -1375,15 +1832,24 @@ health_check() { check_fail "Panel not responding at ${proto}://${FQDN} (HTTP ${http_code})" fi fi - fi - # Database - if [[ "$INSTALL_PANEL" == true && "$DB_CHOICE" != "sqlite" ]]; then - case "$DB_CHOICE" in - mysql) systemctl is-active --quiet mysql 2>/dev/null || systemctl is-active --quiet mysqld 2>/dev/null && check_pass "MySQL is running" || check_fail "MySQL not running" ;; - mariadb) systemctl is-active --quiet mariadb && check_pass "MariaDB is running" || check_fail "MariaDB not running" ;; - postgres) systemctl is-active --quiet postgresql && check_pass "PostgreSQL is running" || check_fail "PostgreSQL not running" ;; - esac + # Database service + if [[ "$DB_CHOICE" != "sqlite" ]]; then + case "$DB_CHOICE" in + mysql) systemctl is-active --quiet mysql 2>/dev/null || systemctl is-active --quiet mysqld 2>/dev/null && check_pass "MySQL is running" || check_fail "MySQL not running" ;; + mariadb) systemctl is-active --quiet mariadb && check_pass "MariaDB is running" || check_fail "MariaDB not running" ;; + postgres) systemctl is-active --quiet postgresql && check_pass "PostgreSQL is running" || check_fail "PostgreSQL not running" ;; + esac + fi + + # Redis (if used) + if needs_redis; then + if systemctl is-active --quiet redis-server 2>/dev/null || systemctl is-active --quiet redis 2>/dev/null; then + check_pass "Redis is running" + else + check_fail "Redis not running" + fi + fi fi # Wings @@ -1427,7 +1893,15 @@ collect_panel_options() { echo -e " ${BOLD}${WHITE}Panel Configuration${RESET}" echo -e " ${DIM}$(printf '%.0s─' {1..40})${RESET}" - # Domain + # ── App Name ── + if [[ "$ASSUME_YES" != true ]]; then + echo "" + ask "Application name [${APP_NAME}]:" + read -r input_app_name + APP_NAME="${input_app_name:-$APP_NAME}" + fi + + # ── Domain ── if [[ -z "$FQDN" ]]; then echo "" ask "Panel domain (e.g., panel.example.com):" @@ -1435,12 +1909,12 @@ collect_panel_options() { [[ -z "$FQDN" ]] && fatal "Domain is required." fi - # Validate FQDN (basic check) + # Validate FQDN if [[ ! "$FQDN" =~ ^[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?$ ]]; then warn "Domain '${FQDN}' looks unusual — double-check it's correct" fi - # Web server + # ── Web server ── if [[ -z "$WEBSERVER" ]]; then echo "" echo -e " ${BOLD}Web server:${RESET}" @@ -1458,16 +1932,9 @@ collect_panel_options() { esac fi - # Update WEBSERVER_USER based on selection - if [[ "$OS_FAMILY" == "rhel" ]]; then - case "$WEBSERVER" in - nginx) WEBSERVER_USER="nginx" ;; - apache) WEBSERVER_USER="apache" ;; - caddy) WEBSERVER_USER="caddy" ;; - esac - fi + update_webserver_user - # Database + # ── Database ── if [[ -z "$DB_CHOICE" ]]; then echo "" echo -e " ${BOLD}Database:${RESET}" @@ -1507,7 +1974,84 @@ collect_panel_options() { DB_PASSWORD="${input_db_pass:-$DB_PASSWORD}" fi - # SSL + # ── Cache ── + if [[ -z "$CACHE_DRIVER" ]]; then + echo "" + echo -e " ${BOLD}Cache driver:${RESET}" + echo -e " ${CYAN}[1]${RESET} Filesystem ${DIM}(default, no extra setup)${RESET}" + echo -e " ${CYAN}[2]${RESET} Redis ${DIM}(faster, recommended for production)${RESET}" + echo "" + ask "Select [1-2] (default: 1):" + read -r cache_choice + case "${cache_choice:-1}" in + 1) CACHE_DRIVER="file" ;; + 2) CACHE_DRIVER="redis" ;; + *) CACHE_DRIVER="file" ;; + esac + fi + + # Redis settings (if needed by any driver) + if [[ "$CACHE_DRIVER" == "redis" && "$ASSUME_YES" != true ]]; then + echo "" + echo -e " ${BOLD}Redis configuration:${RESET}" + ask "Redis host [${REDIS_HOST}]:" + read -r input_redis_host + REDIS_HOST="${input_redis_host:-$REDIS_HOST}" + + ask "Redis port [${REDIS_PORT}]:" + read -r input_redis_port + REDIS_PORT="${input_redis_port:-$REDIS_PORT}" + + ask "Redis password [none]:" + read -r -s input_redis_pass + echo "" + REDIS_PASSWORD="${input_redis_pass:-$REDIS_PASSWORD}" + fi + + # ── Session ── + if [[ -z "$SESSION_DRIVER" ]]; then + echo "" + echo -e " ${BOLD}Session driver:${RESET}" + echo -e " ${CYAN}[1]${RESET} Filesystem ${DIM}(default)${RESET}" + echo -e " ${CYAN}[2]${RESET} Database" + echo -e " ${CYAN}[3]${RESET} Cookie" + if [[ "$CACHE_DRIVER" == "redis" ]]; then + echo -e " ${CYAN}[4]${RESET} Redis ${DIM}(fastest)${RESET}" + fi + echo "" + local session_default=1 + ask "Select [1-$([[ "$CACHE_DRIVER" == "redis" ]] && echo "4" || echo "3")] (default: 1):" + read -r session_choice + case "${session_choice:-1}" in + 1) SESSION_DRIVER="file" ;; + 2) SESSION_DRIVER="database" ;; + 3) SESSION_DRIVER="cookie" ;; + 4) [[ "$CACHE_DRIVER" == "redis" ]] && SESSION_DRIVER="redis" || SESSION_DRIVER="file" ;; + *) SESSION_DRIVER="file" ;; + esac + fi + + # ── Queue ── + if [[ -z "$QUEUE_DRIVER" ]]; then + echo "" + echo -e " ${BOLD}Queue driver:${RESET}" + echo -e " ${CYAN}[1]${RESET} Database ${DIM}(default, reliable)${RESET}" + if [[ "$CACHE_DRIVER" == "redis" ]]; then + echo -e " ${CYAN}[2]${RESET} Redis ${DIM}(faster)${RESET}" + fi + echo -e " ${CYAN}[3]${RESET} Sync ${DIM}(immediate, for testing only)${RESET}" + echo "" + ask "Select [1-3] (default: 1):" + read -r queue_choice + case "${queue_choice:-1}" in + 1) QUEUE_DRIVER="database" ;; + 2) [[ "$CACHE_DRIVER" == "redis" ]] && QUEUE_DRIVER="redis" || QUEUE_DRIVER="database" ;; + 3) QUEUE_DRIVER="sync" ;; + *) QUEUE_DRIVER="database" ;; + esac + fi + + # ── SSL ── if [[ -z "$CONFIGURE_SSL" ]]; then if [[ "$WEBSERVER" == "caddy" ]]; then CONFIGURE_SSL=false # Caddy handles it @@ -1527,7 +2071,22 @@ collect_panel_options() { fi fi - # Timezone + # ── Admin user ── + if [[ -z "$ADMIN_EMAIL" && "$ASSUME_YES" != true ]]; then + echo "" + echo -e " ${BOLD}Admin account ${DIM}(optional — skip to use web installer)${RESET}" + ask "Admin email (leave empty to skip):" + read -r ADMIN_EMAIL + if [[ -n "$ADMIN_EMAIL" ]]; then + ask "Admin username:" + read -r ADMIN_USER + ask "Admin password:" + read -r -s ADMIN_PASSWORD + echo "" + fi + fi + + # ── Timezone ── if [[ "$ASSUME_YES" != true ]]; then echo "" ask "Timezone [${TIMEZONE}]:" @@ -1536,6 +2095,69 @@ collect_panel_options() { fi } +collect_wings_options() { + if [[ "$ASSUME_YES" == true ]]; then + return + fi + + echo "" + echo -e " ${BOLD}${WHITE}Wings Configuration${RESET}" + echo -e " ${DIM}$(printf '%.0s─' {1..40})${RESET}" + + # Wings reverse proxy + if [[ -z "$WINGS_PROXY" ]]; then + echo "" + if confirm "Set up a reverse proxy for Wings? (Y/n):" "Y"; then + WINGS_PROXY=true + else + WINGS_PROXY=false + fi + fi + + if [[ "$WINGS_PROXY" == true ]]; then + if [[ -z "$WINGS_DOMAIN" ]]; then + ask "Wings node domain (e.g., node.example.com):" + read -r WINGS_DOMAIN + [[ -z "$WINGS_DOMAIN" ]] && { warn "No Wings domain specified — skipping proxy"; WINGS_PROXY=false; return; } + fi + + ask "Wings daemon port [${WINGS_PORT}]:" + read -r input_wings_port + WINGS_PORT="${input_wings_port:-$WINGS_PORT}" + + if [[ -z "$WINGS_SSL" ]]; then + if [[ "$WEBSERVER" == "caddy" ]]; then + WINGS_SSL=false # Caddy handles it + info "Caddy handles Wings SSL automatically" + elif [[ "$CONFIGURE_SSL" == true ]]; then + if confirm "Enable SSL for Wings reverse proxy too? (Y/n):" "Y"; then + WINGS_SSL=true + else + WINGS_SSL=false + fi + fi + fi + + # Need a webserver choice for wings-only installs + if [[ "$INSTALL_PANEL" != true && -z "$WEBSERVER" ]]; then + echo "" + echo -e " ${BOLD}Web server for Wings reverse proxy:${RESET}" + echo -e " ${CYAN}[1]${RESET} Nginx ${DIM}(recommended)${RESET}" + echo -e " ${CYAN}[2]${RESET} Apache" + echo -e " ${CYAN}[3]${RESET} Caddy ${DIM}(automatic SSL)${RESET}" + echo "" + ask "Select [1-3] (default: 1):" + read -r ws + case "${ws:-1}" in + 1) WEBSERVER="nginx" ;; + 2) WEBSERVER="apache" ;; + 3) WEBSERVER="caddy" ;; + *) WEBSERVER="nginx" ;; + esac + fi + fi +} + show_summary() { echo "" echo -e " ${CYAN}${BOLD}╔═══════════════════════════════════════════╗${RESET}" @@ -1545,6 +2167,7 @@ show_summary() { if [[ "$INSTALL_PANEL" == true ]]; then echo -e " ${BOLD}Panel:${RESET}" + echo -e " App name: ${GREEN}${APP_NAME}${RESET}" echo -e " Domain: ${GREEN}${FQDN}${RESET}" echo -e " Web server: ${GREEN}${WEBSERVER}${RESET}" echo -e " Database: ${GREEN}${DB_CHOICE}${RESET}" @@ -1552,9 +2175,17 @@ show_summary() { echo -e " DB name: ${GREEN}${DB_NAME}${RESET}" echo -e " DB user: ${GREEN}${DB_USER}${RESET}" fi + echo -e " Cache: ${GREEN}${CACHE_DRIVER}${RESET}" + echo -e " Session: ${GREEN}${SESSION_DRIVER}${RESET}" + echo -e " Queue: ${GREEN}${QUEUE_DRIVER}${RESET}" echo -e " SSL: ${GREEN}$([[ "$CONFIGURE_SSL" == true || "$WEBSERVER" == "caddy" ]] && echo "Yes" || echo "No")${RESET}" echo -e " Timezone: ${GREEN}${TIMEZONE}${RESET}" echo -e " PHP: ${GREEN}${PHP_VERSION}${RESET}" + if [[ -n "$ADMIN_EMAIL" ]]; then + echo -e " Admin: ${GREEN}${ADMIN_EMAIL} (${ADMIN_USER})${RESET}" + else + echo -e " Admin: ${DIM}via web installer${RESET}" + fi echo "" fi @@ -1562,6 +2193,12 @@ show_summary() { echo -e " ${BOLD}Wings:${RESET}" echo -e " Architecture: ${GREEN}${WINGS_ARCH}${RESET}" echo -e " Docker: ${GREEN}Will be installed${RESET}" + if [[ "$WINGS_PROXY" == true ]]; then + echo -e " Proxy: ${GREEN}${WEBSERVER} → ${WINGS_DOMAIN}:${WINGS_PORT}${RESET}" + echo -e " Proxy SSL: ${GREEN}$([[ "$WINGS_SSL" == true || "$WEBSERVER" == "caddy" ]] && echo "Yes" || echo "No")${RESET}" + else + echo -e " Proxy: ${DIM}Not configured${RESET}" + fi echo "" fi @@ -1589,18 +2226,39 @@ print_complete() { if [[ "$INSTALL_PANEL" == true ]]; then echo -e " ${BOLD}Panel — next steps:${RESET}" - echo -e " ${WHITE}1.${RESET} Open ${CYAN}${proto}://${FQDN}/installer${RESET} in your browser" - echo -e " ${WHITE}2.${RESET} Complete the web-based setup wizard" - echo -e " ${WHITE}3.${RESET} Create your admin account" + if [[ -n "$ADMIN_EMAIL" ]]; then + echo -e " ${WHITE}1.${RESET} Open ${CYAN}${proto}://${FQDN}${RESET} and log in" + echo -e " Email: ${WHITE}${ADMIN_EMAIL}${RESET}" + echo -e " Username: ${WHITE}${ADMIN_USER}${RESET}" + else + echo -e " ${WHITE}1.${RESET} Open ${CYAN}${proto}://${FQDN}/installer${RESET} in your browser" + echo -e " ${WHITE}2.${RESET} Complete the web-based setup wizard" + echo -e " ${DIM}(Database, cache, session, and queue are already configured!)${RESET}" + echo -e " ${WHITE}3.${RESET} Create your admin account" + fi echo "" fi if [[ "$INSTALL_WINGS_FLAG" == true ]]; then echo -e " ${BOLD}Wings — next steps:${RESET}" echo -e " ${WHITE}1.${RESET} Create a node in Panel admin → Nodes → Create New" + if [[ "$WINGS_PROXY" == true ]]; then + local wings_proto="http" + [[ "$WINGS_SSL" == true || "$WEBSERVER" == "caddy" ]] && wings_proto="https" + echo -e " Use FQDN: ${CYAN}${WINGS_DOMAIN}${RESET}" + echo -e " Set 'Communicate Over SSL' to ${CYAN}$([[ "$wings_proto" == "https" ]] && echo "Use SSL" || echo "No SSL")${RESET}" + echo -e " Set 'Behind Proxy' to ${CYAN}Behind Proxy${RESET}" + fi echo -e " ${WHITE}2.${RESET} Copy the config YAML into ${CYAN}${WINGS_DIR}/config.yml${RESET}" - echo -e " ${WHITE}3.${RESET} Test: ${CYAN}sudo wings --debug${RESET}" - echo -e " ${WHITE}4.${RESET} Start: ${CYAN}sudo systemctl start wings${RESET}" + if [[ "$WINGS_PROXY" == true ]]; then + echo -e " ${WHITE}3.${RESET} In config.yml, ensure: ${CYAN}api.ssl.enabled: false${RESET}" + echo -e " ${DIM}(The reverse proxy terminates SSL — Wings listens on plain HTTP)${RESET}" + echo -e " ${WHITE}4.${RESET} Test: ${CYAN}sudo wings --debug${RESET}" + echo -e " ${WHITE}5.${RESET} Start: ${CYAN}sudo systemctl start wings${RESET}" + else + echo -e " ${WHITE}3.${RESET} Test: ${CYAN}sudo wings --debug${RESET}" + echo -e " ${WHITE}4.${RESET} Start: ${CYAN}sudo systemctl start wings${RESET}" + fi echo "" fi @@ -1655,21 +2313,41 @@ main() { INSTALL_WINGS_FLAG="${INSTALL_WINGS_FLAG:-false}" # Calculate total steps for progress indicator - TOTAL_STEPS=2 # detect + preflight (already done but we counted them) + TOTAL_STEPS=2 # detect + preflight (already done) CURRENT_STEP=2 - [[ "$INSTALL_PANEL" == true ]] && TOTAL_STEPS=$((TOTAL_STEPS + 6)) # php, db, webserver, ssl, panel-download, panel-config - [[ "$INSTALL_WINGS_FLAG" == true ]] && TOTAL_STEPS=$((TOTAL_STEPS + 3)) # docker, wings-download, wings-service + if [[ "$INSTALL_PANEL" == true ]]; then + TOTAL_STEPS=$((TOTAL_STEPS + 7)) # php, redis?, db, webserver, ssl, panel-download, panel-config + fi + if [[ "$INSTALL_WINGS_FLAG" == true ]]; then + TOTAL_STEPS=$((TOTAL_STEPS + 3)) # docker, wings-download, wings-service + if [[ "$WINGS_PROXY" == true && "$INSTALL_PANEL" != true ]]; then + TOTAL_STEPS=$((TOTAL_STEPS + 1)) # webserver for wings only + fi + fi TOTAL_STEPS=$((TOTAL_STEPS + 1)) # health check if [[ "$INSTALL_PANEL" == true ]]; then detect_php_version collect_panel_options - show_summary + fi + + if [[ "$INSTALL_WINGS_FLAG" == true ]]; then + collect_wings_options + fi + + show_summary + + if [[ "$INSTALL_PANEL" == true ]]; then install_php + install_redis install_database install_webserver install_ssl install_panel + elif [[ "$WINGS_PROXY" == true ]]; then + # Wings-only with reverse proxy needs a web server + install_webserver + install_ssl fi if [[ "$INSTALL_WINGS_FLAG" == true ]]; then