#!/usr/bin/env bash set -euo pipefail C_RESET="\033[0m"; C_DIM="\033[2m"; C_BOLD="\033[1m" C_RED="\033[31m"; C_GRN="\033[32m"; C_BLU="\033[34m"; C_YEL="\033[33m" info(){ echo -e "${C_BLU}➜${C_RESET} $*"; } ok(){ echo -e "${C_GRN}✓${C_RESET} $*"; } warn(){ echo -e "${C_YEL}!${C_RESET} $*"; } fail(){ echo -e "${C_RED}✗${C_RESET} $*"; } if [[ $EUID -ne 0 ]]; then fail "Run as root (sudo)."; exit 1; fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PANEL_ROOT="/var/www/snowpanel" PANEL_PUBLIC="$PANEL_ROOT/public" STATE_DIR="/var/lib/snowpanel" LOG_DIR="/var/log/snowpanel" ETC_APP="/etc/snowpanel" NGX_SITE_AVAIL="/etc/nginx/sites-available/snowpanel" NGX_SITE_ENABL="/etc/nginx/sites-enabled/snowpanel" SUDOERS_FILE="/etc/sudoers.d/snowpanel" COLLECTOR_SRC="$SCRIPT_DIR/bin/snowpanel-collect.py" COLLECTOR_BIN="/usr/local/bin/snowpanel-collect.py" LOGDUMP_SRC="$SCRIPT_DIR/bin/snowpanel-logdump" LOGDUMP_BIN="/usr/local/bin/snowpanel-logdump" SHAPER_SRC="$SCRIPT_DIR/bin/snowpanel-shaper" SHAPER_BIN="/usr/local/bin/snowpanel-shaper" SHAPER_SVC="/etc/systemd/system/snowpanel-shaper.service" SHAPER_PATH="/etc/systemd/system/snowpanel-shaper.path" SVC="/etc/systemd/system/snowpanel-collector.service" TIMER="/etc/systemd/system/snowpanel-collector.timer" SF_DROPIN_DIR="/etc/systemd/system/snowflake-proxy.service.d" SF_ACCOUNTING="$SF_DROPIN_DIR/10-accounting.conf" export DEBIAN_FRONTEND=noninteractive echo -e "${C_BOLD}Installing SnowPanel...${C_RESET}" info "Updating apt and installing packages" apt-get update -y >/dev/null apt-get install -y --no-install-recommends snowflake-proxy nginx rsync php-fpm php-cli php-json php-curl php-zip php-common php-opcache python3 iproute2 iptables >/dev/null ok "Packages installed" PHPV=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;' 2>/dev/null || echo "8.2") PHP_FPM_SVC="php${PHPV}-fpm" PHP_SOCK="/run/php/php${PHPV}-fpm.sock" ln -sf "$PHP_SOCK" /run/php/php-fpm.sock || true info "Preparing directories" install -d "$PANEL_PUBLIC" "$STATE_DIR" "$LOG_DIR" "$ETC_APP" install -d -m 0755 /usr/local/bin chown -R www-data:www-data "$PANEL_ROOT" || true chown -R www-data:www-data "$STATE_DIR" "$LOG_DIR" || true chmod 750 "$PANEL_ROOT" "$STATE_DIR" "$LOG_DIR" ok "Directories ready" info "Deploying web" rsync -a --delete "$SCRIPT_DIR/web/" "$PANEL_PUBLIC/" chown -R www-data:www-data "$PANEL_PUBLIC" ok "Web deployed" info "Seeding state" echo -n '{"data":[]}' > "$STATE_DIR/stats.json" chown www-data:www-data "$STATE_DIR/stats.json" chmod 640 "$STATE_DIR/stats.json" ok "State seeded" info "Writing Nginx site" cat > "$NGX_SITE_AVAIL" <<'NGINX' server { listen 80 default_server; server_name _; root /var/www/snowpanel/public; index index.php index.html; location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php-fpm.sock; } location ~ /\. { deny all; } } NGINX rm -f /etc/nginx/sites-enabled/default || true ln -sf "$NGX_SITE_AVAIL" "$NGX_SITE_ENABL" ok "Nginx site enabled" info "Installing helpers" install -m 0755 "$COLLECTOR_SRC" "$COLLECTOR_BIN" install -m 0755 "$LOGDUMP_SRC" "$LOGDUMP_BIN" install -m 0755 "$SHAPER_SRC" "$SHAPER_BIN" install -d -m 0750 -o www-data -g www-data "$STATE_DIR" ok "Helpers installed" info "Granting sudoers" cat > "$SUDOERS_FILE" < "$SF_ACCOUNTING" < "$SVC" <<'UNIT' [Unit] Description=SnowPanel minute collector After=snowflake-proxy.service [Service] Type=oneshot User=www-data Group=www-data ExecStart=/usr/bin/env python3 /usr/local/bin/snowpanel-collect.py UNIT cat > "$TIMER" <<'TIMER' [Unit] Description=Run SnowPanel collector every minute [Timer] OnCalendar=*-*-* *:*:00 Persistent=true Unit=snowpanel-collector.service [Install] WantedBy=timers.target TIMER ok "Collector units created" info "Creating shaper units" cat > "$SHAPER_SVC" <<'UNIT' [Unit] Description=SnowPanel traffic shaper for snowflake-proxy After=network-online.target snowflake-proxy.service Wants=network-online.target [Service] Type=oneshot ExecStart=/usr/local/bin/snowpanel-shaper apply ExecStop=/usr/local/bin/snowpanel-shaper clear RemainAfterExit=yes [Install] WantedBy=multi-user.target UNIT cat > "$SHAPER_PATH" <<'PATHUNIT' [Unit] Description=Watch SnowPanel limits and reapply shaper [Path] PathChanged=/var/lib/snowpanel/app.json PathChanged=/etc/snowpanel/app.json PathChanged=/etc/snowpanel/limits.json PathChanged=/var/lib/snowpanel/limits.json [Install] WantedBy=multi-user.target PATHUNIT ok "Shaper units created" info "Restarting services" systemctl daemon-reload systemctl enable "$PHP_FPM_SVC" nginx >/dev/null 2>&1 || true systemctl restart "$PHP_FPM_SVC" || true systemctl restart nginx systemctl enable --now snowflake-proxy >/dev/null 2>&1 || true systemctl restart snowflake-proxy || true systemctl enable --now snowpanel-collector.timer systemctl start snowpanel-collector.service || true systemctl enable --now snowpanel-shaper.service snowpanel-shaper.path systemctl restart snowpanel-shaper.service || true ok "Services running" IP=$(hostname -I 2>/dev/null | awk '{print $1}') echo echo -e "${C_BOLD}All set!${C_RESET}" echo -e "URL: ${C_GRN}http://$IP/${C_RESET}" echo -e " First run: you'll see a setup page to create the admin user."