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