#!/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/torpanel" PANEL_PUBLIC="$PANEL_ROOT/public" STATE_DIR="/var/lib/torpanel" LOG_DIR="/var/log/torpanel" ETC_APP="/etc/torpanel" TOR_ETC="/etc/tor" TOR_TORRC_D="$TOR_ETC/torrc.d" TOR_PANEL_CONF="$TOR_TORRC_D/99-torpanel.conf" NGX_SITE_AVAIL="/etc/nginx/sites-available/torpanel" NGX_SITE_ENABL="/etc/nginx/sites-enabled/torpanel" SUDOERS_FILE="/etc/sudoers.d/torpanel" COLLECTOR_SRC="$SCRIPT_DIR/bin/torpanel-collect.py" COLLECTOR_BIN="/usr/local/bin/torpanel-collect.py" SVC="/etc/systemd/system/torpanel-collector.service" TIMER="/etc/systemd/system/torpanel-collector.timer" export DEBIAN_FRONTEND=noninteractive ensure_torrc_d_include() { local main="$TOR_ETC/torrc" install -d -m 755 "$TOR_TORRC_D" if [[ ! -f "$main" ]]; then echo "# Created by TorPanel installer" > "$main" fi if grep -Eq '^[[:space:]]*%include[[:space:]]+/etc/tor/torrc\.d/\*\.conf' "$main"; then return 0 fi if sed -n 's/^[[:space:]]*#\s*%include[[:space:]]\+\/etc\/tor\/torrc\.d\/\*\.conf/%include \/etc\/tor\/torrc.d\/\*\.conf/p' "$main" | grep -q .; then sed -i 's/^[[:space:]]*#\s*%include[[:space:]]\+\/etc\/tor\/torrc\.d\/\*\.conf/%include \/etc\/tor\/torrc.d\/\*\.conf/' "$main" else printf '\n%%include /etc/tor/torrc.d/*.conf\n' >> "$main" fi } echo -e "${C_BOLD}Installing TorPanel...${C_RESET}" info "Updating apt and installing packages" apt-get update -y >/dev/null apt-get install -y --no-install-recommends \ tor tor-geoipdb nginx rsync \ php-fpm php-cli php-json php-curl php-zip php-common php-opcache \ python3 >/dev/null ok "Packages installed" PHPV=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;') 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" echo -n '{"data":[]}' > "$STATE_DIR/stats.json" rsync -a --delete "$SCRIPT_DIR/web/" "$PANEL_PUBLIC/" chown -R www-data:www-data "$PANEL_ROOT" "$STATE_DIR" "$LOG_DIR" chmod 750 "$PANEL_ROOT" "$STATE_DIR" "$LOG_DIR" chown root:www-data "$ETC_APP"; chmod 770 "$ETC_APP" ok "Web files deployed" info "Writing Nginx site" cat > "$NGX_SITE_AVAIL" <<'NGINX' server { listen 80 default_server; server_name _; root /var/www/torpanel/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 "Writing torrc defaults" install -d "$TOR_TORRC_D" cat > "$TOR_PANEL_CONF" <<'TORRC' ## --- Managed by TorPanel --- SocksPort 0 ORPort 9001 ExitRelay 0 ExitPolicy reject *:* Nickname RaspberryRelay ContactInfo contact@admin.com BandwidthRate 5 MB BandwidthBurst 10 MB AccountingMax 100 GB AccountingStart month 1 00:00 ControlPort 0 ControlSocket /run/tor/control CookieAuthentication 1 CookieAuthFileGroupReadable 1 # --- End TorPanel block --- TORRC ok "torrc written" info "Ensuring main torrc includes torrc.d/*.conf" ensure_torrc_d_include ok "torrc.d include active" info "Setting permissions for Tor managed config" chown root:www-data "$TOR_TORRC_D"; chmod 775 "$TOR_TORRC_D" chown root:www-data "$TOR_PANEL_CONF"; chmod 664 "$TOR_PANEL_CONF" ok "Permissions applied" info "Granting www-data access to Tor cookie" usermod -aG debian-tor www-data || true ok "Permissions set" info "Allowing www-data to control tor (limited)" cat > "$SUDOERS_FILE" <<'SUD' www-data ALL=NOPASSWD:/bin/systemctl reload tor, /bin/systemctl restart tor, /bin/systemctl start tor, /bin/systemctl stop tor SUD chmod 440 "$SUDOERS_FILE" ok "Sudoers entry created" info "Installing collector" install -m 0755 "$COLLECTOR_SRC" "$COLLECTOR_BIN" chown www-data:www-data "$COLLECTOR_BIN" cat > "$SVC" <<'UNIT' [Unit] Description=TorPanel minute collector After=tor.service Wants=tor.service ConditionPathExists=/run/tor/control.authcookie [Service] Type=oneshot User=www-data Group=www-data SupplementaryGroups=debian-tor ExecStart=/usr/bin/env python3 /usr/local/bin/torpanel-collect.py NoNewPrivileges=yes ProtectSystem=strict ProtectHome=yes ProtectClock=yes ProtectHostname=yes ProtectKernelLogs=yes ProtectKernelModules=yes ProtectKernelTunables=yes PrivateTmp=yes PrivateDevices=yes PrivateUsers=no LockPersonality=yes MemoryDenyWriteExecute=yes RestrictRealtime=yes RestrictSUIDSGID=yes UMask=0077 ReadWritePaths=/var/lib/torpanel ReadOnlyPaths=/run/tor /etc/tor RestrictAddressFamilies=AF_UNIX SystemCallFilter=@system-service CapabilityBoundingSet= StandardOutput=journal StandardError=journal Environment=PYTHONUNBUFFERED=1 Restart=on-failure RestartSec=15s [Install] WantedBy=timers.target UNIT cat > "$TIMER" <<'TIMER' [Unit] Description=Run TorPanel collector every minute [Timer] OnCalendar=*-*-* *:*:00 AccuracySec=15s Persistent=true Unit=torpanel-collector.service [Install] WantedBy=timers.target TIMER ok "Systemd units installed" info "Restarting services" systemctl daemon-reload systemctl enable --now tor systemctl enable "$PHP_FPM_SVC" nginx >/dev/null systemctl restart "$PHP_FPM_SVC" systemctl restart nginx systemctl start torpanel-collector.service systemctl enable --now torpanel-collector.timer 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." echo -e " Tip: forward ${C_BOLD}TCP/9001${C_RESET} to your Pi for a publicly reachable relay."