diff --git a/AGENTS.md b/AGENTS.md index f5a87fdf..55299ff8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -80,8 +80,8 @@ Nexus/ | 心跳数据流 | Agent→Redis→前端直读→10min→MySQL | 前端实时, MySQL只存历史 | | 告警推送 | WebSocket→前端 + Telegram | 即时感知 | | Python守护 | 3层: Supervisor + Python自检(30s) + Shell(cron 1min) | 全覆盖 | -| 安装向导 | install.html + FastAPI API | D2: 替代install.php | -| 配置源 | 安装向导→config.php(PHP兼容)+settings表(MySQL)+.env(Python) | 三写一致 | +| 安装向导 | install.html + FastAPI API | 纯Python,无PHP依赖 | +| 配置源 | 安装向导→config.json+settings表(MySQL)+.env(Python) | 三写一致 | | Agent告警心跳 | CPU/内存>80% 或变化>10% 时主动上报 | 智能监控 | | 仓库统一 | 所有代码统一在Nexus仓库 | 不再用两个仓库 | @@ -111,7 +111,7 @@ Nexus/ | E12 /health端点 | server/api/health.py | Shell health_monitor.sh checks this | | **技术债务** | | | | D1 ✅ paramiko删除 | — | pool.py已删除, 全部迁移asyncssh | -| D2 ✅ install迁移 | server/api/install.py | install.php→install.html+API | +| D2 ✅ install迁移 | server/api/install.py | PHP→install.html+API | | D3 ✅ WebSSH前端 | web/app/terminal.html | xterm.js+Koko协议+全屏 | | **安全增强** | | | | S1 JWT全局保护 | server/api/*.py | 所有业务API都有JWT验证 | @@ -159,7 +159,7 @@ Nexus/ ## 配置三写机制 安装向导 Step 3 POST处理同时写入: -1. `web/data/config.php` — PHP兼容配置 (API_BASE_URL, DB_*, API_KEY) +1. `web/data/config.json` — 共享配置 (API_BASE_URL, DB_*, API_KEY) 2. `.env` — Python后端启动必需 (NEXUS_前缀) 3. `settings` MySQL表 — 共享动态配置 (api_key, thresholds, Telegram等) diff --git a/CLAUDE.md b/CLAUDE.md index b03f5fe7..cc622ba6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,8 +50,8 @@ Nexus/ | 心跳数据流 | Agent→Redis→前端直读→10min→MySQL | 前端实时, MySQL只存历史 | | 告警推送 | WebSocket→前端 + Telegram | 即时感知 | | Python守护 | 3层: Supervisor + Python自检(30s) + Shell(cron 1min) | 全覆盖 | -| 安装向导 | install.html + FastAPI API | D2: 替代install.php | -| 配置源 | 安装向导→config.php(PHP兼容)+settings表(MySQL)+.env(Python) | 三写一致 | +| 安装向导 | install.html + FastAPI API | 纯Python,无PHP依赖 | +| 配置源 | 安装向导→config.json+settings表(MySQL)+.env(Python) | 三写一致 | | Agent告警心跳 | CPU/内存>80% 或变化>10% 时主动上报 | 智能监控 | | 仓库统一 | 所有代码统一在Nexus仓库 | 不再用两个仓库 | @@ -81,7 +81,7 @@ Nexus/ | E12 /health端点 | server/api/health.py | Shell health_monitor.sh checks this | | **技术债务** | | | | D1 ✅ paramiko删除 | — | pool.py已删除, 全部迁移asyncssh | -| D2 ✅ install迁移 | server/api/install.py | install.php→install.html+API | +| D2 ✅ install迁移 | server/api/install.py | PHP→install.html+API | | D3 ✅ WebSSH前端 | web/app/terminal.html | xterm.js+Koko协议+全屏 | | **安全增强** | | | | S1 JWT全局保护 | server/api/*.py | 所有业务API都有JWT验证 | @@ -129,7 +129,7 @@ Nexus/ ## 配置三写机制 安装向导 Step 3 POST处理同时写入: -1. `web/data/config.php` — PHP兼容配置 (API_BASE_URL, DB_*, API_KEY) +1. `web/data/config.json` — 共享配置 (API_BASE_URL, DB_*, API_KEY) 2. `.env` — Python后端启动必需 (NEXUS_前缀) 3. `settings` MySQL表 — 共享动态配置 (api_key, thresholds, Telegram等) diff --git a/deploy/install.sh b/deploy/install.sh new file mode 100644 index 00000000..87af45e4 --- /dev/null +++ b/deploy/install.sh @@ -0,0 +1,526 @@ +#!/bin/bash +# ================================================================ +# Nexus 6.0 — One-Click Install Script +# +# Usage: +# curl -fsSL https://YOUR_DOMAIN/deploy/install.sh | sudo bash -s -- --domain nexus.example.com +# sudo bash install.sh --domain nexus.example.com +# +# Supports: Ubuntu 20.04+, Debian 11+, CentOS 7/8, Rocky 8/9, AlmaLinux 8/9 +# Auto-detects BT Panel (宝塔面板) and adapts paths accordingly. +# ================================================================ + +set -euo pipefail + +# ── Colors ── +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*"; } + +# ── Helper: Idempotent config write ── +write_config() { + local target="$1" content="$2" + if [ -f "$target" ]; then + local existing + existing=$(cat "$target" 2>/dev/null || true) + if [ "$existing" = "$content" ]; then + info " [skip] $target (unchanged)" + return 0 + fi + info " [update] $target (content changed)" + else + info " [create] $target" + fi + echo "$content" > "$target" +} + +# ── Defaults ── +DOMAIN="" +DEPLOY_DIR="" +PORT=8600 +SKIP_NGINX=false +SKIP_SUPERVISOR=false +BT_MODE=false +NEXUS_REPO="" + +# ── Parse Arguments ── +while [[ $# -gt 0 ]]; do + case $1 in + --domain) DOMAIN="$2"; shift 2 ;; + --deploy-dir) DEPLOY_DIR="$2"; shift 2 ;; + --port) PORT="$2"; shift 2 ;; + --skip-nginx) SKIP_NGINX=true; shift ;; + --skip-supervisor) SKIP_SUPERVISOR=true; shift ;; + --repo) NEXUS_REPO="$2"; shift 2 ;; + -h|--help) + echo "Usage: sudo bash install.sh --domain DOMAIN [options]" + echo "" + echo "Options:" + echo " --domain DOMAIN Required. Domain or IP for Nginx server_name" + echo " --deploy-dir PATH Installation directory (auto-detected)" + echo " --port PORT uvicorn port (default: 8600)" + echo " --skip-nginx Skip Nginx configuration" + echo " --skip-supervisor Skip Supervisor configuration" + echo " --repo URL Git repository URL (optional, for cloning)" + exit 0 ;; + *) shift ;; + esac +done + +# ── Banner ── +echo "" +echo "==========================================" +echo " Nexus 6.0 Install Script" +echo "==========================================" +echo "" + +# ================================================================ +# Phase 1: Pre-flight Checks +# ================================================================ + +info "Phase 1/7: Pre-flight checks..." + +# 1.1 Must run as root +if [ "$(id -u)" -ne 0 ]; then + error "This script must be run as root. Use: sudo bash install.sh" + exit 1 +fi + +# 1.2 Domain is required +if [ -z "$DOMAIN" ]; then + error "--domain is required. Usage: sudo bash install.sh --domain nexus.example.com" + exit 1 +fi + +# 1.3 OS detection +if [ ! -f /etc/os-release ]; then + error "Cannot detect OS. /etc/os-release not found." + exit 1 +fi + +OS_ID=$(grep '^ID=' /etc/os-release | head -1 | cut -d'=' -f2 | tr -d '"' | cut -d'-' -f1) +OS_VERSION=$(grep '^VERSION_ID=' /etc/os-release | head -1 | cut -d'=' -f2 | tr -d '"') + +case "$OS_ID" in + ubuntu|debian) PKG_MANAGER="apt" ;; + centos|rocky|almalinux|rhel) PKG_MANAGER="yum" ;; + *) + warn "Unsupported OS: $OS_ID. Script may still work but is untested." + PKG_MANAGER="apt" + ;; +esac +info " OS: $OS_ID $OS_VERSION (pkg: $PKG_MANAGER)" + +# 1.4 BT Panel detection +if [ -d "/www/server/panel" ] && [ -f "/www/server/panel/class/common.py" ]; then + BT_MODE=true + info " BT Panel: detected" +else + info " BT Panel: not detected (standard mode)" +fi + +# 1.5 Set deploy directory +if [ -z "$DEPLOY_DIR" ]; then + if [ "$BT_MODE" = true ]; then + DEPLOY_DIR="/www/wwwroot/$DOMAIN" + else + DEPLOY_DIR="/opt/nexus" + fi +fi +VENV_DIR="$DEPLOY_DIR/venv" + +# Set log paths early (used in multiple phases) +if [ "$BT_MODE" = true ]; then + LOG_DIR="/www/wwwlogs" +else + LOG_DIR="/var/log/nexus" +fi + +info " Deploy dir: $DEPLOY_DIR" +info " Domain: $DOMAIN" +info " Port: $PORT" + +# ================================================================ +# Phase 2: System Dependencies +# ================================================================ + +info "Phase 2/7: Installing system dependencies..." + +if [ "$PKG_MANAGER" = "apt" ]; then + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y -qq \ + python3 python3-venv python3-pip python3-dev \ + build-essential libssl-dev libffi-dev default-libmysqlclient-dev \ + git curl >/dev/null 2>&1 || warn "Some apt packages failed to install (may already exist)" +elif [ "$PKG_MANAGER" = "yum" ]; then + if command -v dnf >/dev/null 2>&1; then + dnf install -y -q \ + python3 python3-devel python3-pip \ + gcc openssl-devel libffi-devel mysql-devel \ + git curl >/dev/null 2>&1 || warn "Some dnf packages failed to install" + else + yum install -y -q \ + python3 python3-devel python3-pip \ + gcc openssl-devel libffi-devel mysql-devel \ + git curl >/dev/null 2>&1 || warn "Some yum packages failed to install" + fi +fi + +# ================================================================ +# Phase 3: Python 3.10+ Resolution +# ================================================================ + +info "Phase 3/7: Resolving Python 3.10+..." + +PYTHON="" +CANDIDATES="python3.12 python3.11 python3.10 /usr/bin/python3.12 /usr/bin/python3.11 /usr/bin/python3.10 python3" + +# Add BT Panel Python paths +if [ "$BT_MODE" = true ]; then + for p in /www/server/python-manager/versions/*/bin/python3.*; do + [ -x "$p" ] && CANDIDATES="$CANDIDATES $p" + done +fi + +for candidate in $CANDIDATES; do + if command -v "$candidate" >/dev/null 2>&1 || [ -x "$candidate" ]; then + ver=$("$candidate" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null || true) + if [ -n "$ver" ]; then + major=$(echo "$ver" | cut -d'.' -f1) + minor=$(echo "$ver" | cut -d'.' -f2) + if [ "$major" -eq 3 ] && [ "${minor:-0}" -ge 10 ] 2>/dev/null; then + PYTHON="$candidate" + break + fi + fi + fi +done + +# Fallback: install Python 3.12 on Ubuntu via deadsnakes PPA +if [ -z "$PYTHON" ] && [ "$OS_ID" = "ubuntu" ]; then + warn " Python 3.10+ not found. Adding deadsnakes PPA..." + export DEBIAN_FRONTEND=noninteractive + apt-get install -y -qq software-properties-common >/dev/null 2>&1 + add-apt-repository -y ppa:deadsnakes/ppa >/dev/null 2>&1 + apt-get update -qq + apt-get install -y -qq python3.12 python3.12-venv python3.12-dev >/dev/null 2>&1 + if command -v python3.12 >/dev/null 2>&1; then + PYTHON="python3.12" + fi +fi + +if [ -z "$PYTHON" ]; then + error "Python 3.10+ is required but not found." + error "Install Python 3.10+ manually and re-run." + exit 1 +fi + +PYTHON_VERSION=$("$PYTHON" --version 2>&1) +info " Using: $PYTHON ($PYTHON_VERSION)" + +# ================================================================ +# Phase 4: Application Deployment +# ================================================================ + +info "Phase 4/7: Deploying application..." + +# 4.1 Create deploy directory +mkdir -p "$DEPLOY_DIR" + +# 4.2 Download / Clone Nexus +if [ -d "$DEPLOY_DIR/server" ] && [ -d "$DEPLOY_DIR/web" ]; then + info " Application files already exist in $DEPLOY_DIR (skipping download)" + if [ -d "$DEPLOY_DIR/.git" ]; then + info " Git repo detected, pulling latest..." + (cd "$DEPLOY_DIR" && git pull --ff-only 2>/dev/null || warn " git pull failed (may have local changes)") + fi +elif [ -n "$NEXUS_REPO" ]; then + info " Cloning from $NEXUS_REPO..." + git clone "$NEXUS_REPO" "$DEPLOY_DIR" 2>/dev/null || { + error "git clone failed. Check the repository URL." + exit 1 + } +else + error "No application files found in $DEPLOY_DIR and no --repo URL provided." + error "Either:" + error " 1. Copy Nexus files to $DEPLOY_DIR manually and re-run" + error " 2. Use --repo https://your-gitea-url/Nexus.git" + exit 1 +fi + +# 4.3 Create Python venv +if [ -d "$VENV_DIR/bin" ] && "$VENV_DIR/bin/python" -c 'import sys; v=sys.version_info; assert v.major==3 and v.minor>=10' 2>/dev/null; then + info " venv already exists with Python 3.10+ (skipping)" +else + info " Creating Python venv..." + rm -rf "$VENV_DIR" + "$PYTHON" -m venv "$VENV_DIR" +fi + +# 4.4 Install pip dependencies +info " Installing Python dependencies (this may take a few minutes)..." +"$VENV_DIR/bin/pip" install -q --upgrade pip +"$VENV_DIR/bin/pip" install -q -r "$DEPLOY_DIR/requirements.txt" 2>/dev/null || { + warn " pip install had some warnings, but continuing..." +} + +# 4.5 Create log directory +mkdir -p "$LOG_DIR" + +# 4.6 Create backup directory +mkdir -p "$DEPLOY_DIR/backups" + +# ================================================================ +# Phase 5: Supervisor Configuration +# ================================================================ + +if [ "$SKIP_SUPERVISOR" = true ]; then + warn "Phase 5/7: Skipping Supervisor (per --skip-supervisor)" +else + info "Phase 5/7: Configuring Supervisor..." + + # Calculate worker count + WORKERS=$(nproc 2>/dev/null || echo 2) + [ "$WORKERS" -gt 4 ] && WORKERS=4 + [ "$WORKERS" -lt 1 ] && WORKERS=1 + + # Set Supervisor config path + if [ "$BT_MODE" = true ]; then + SUPERVISOR_CONF_DIR="/www/server/panel/plugin/supervisor/profile" + CONF_FILE="$SUPERVISOR_CONF_DIR/nexus.ini" + else + SUPERVISOR_CONF_DIR="/etc/supervisor/conf.d" + CONF_FILE="$SUPERVISOR_CONF_DIR/nexus.conf" + fi + + # Generate Supervisor config + SUPERVISOR_CONF="[program:nexus] +command=$VENV_DIR/bin/uvicorn server.main:app --host 0.0.0.0 --port $PORT --workers $WORKERS --loop uvloop --http httptools --log-level info +directory=$DEPLOY_DIR +user=root +autostart=true +autorestart=true +startretries=10 +startsecs=3 +stopwaitsecs=10 +stopsignal=INT +environment=PATH=\"$VENV_DIR/bin:%(ENV_PATH)s\",HOME=\"/root\",NEXUS_WORKERS=\"$WORKERS\" +stderr_logfile=$LOG_DIR/nexus_error.log +stdout_logfile=$LOG_DIR/nexus_access.log +stderr_logfile_maxbytes=50MB +stdout_logfile_maxbytes=50MB +stderr_logfile_backups=5 +stdout_logfile_backups=5" + + # Write config (idempotent) + mkdir -p "$SUPERVISOR_CONF_DIR" + write_config "$CONF_FILE" "$SUPERVISOR_CONF" + + # Ensure Supervisor is running + if ! supervisorctl status >/dev/null 2>&1; then + if [ "$BT_MODE" = true ]; then + if [ -f /etc/init.d/supervisor ]; then + /etc/init.d/supervisor start >/dev/null 2>&1 || true + fi + else + systemctl start supervisor >/dev/null 2>&1 || true + fi + sleep 2 + fi + + # Reload Supervisor config + supervisorctl reread >/dev/null 2>&1 || true + supervisorctl update >/dev/null 2>&1 || true + info " Supervisor config written to $CONF_FILE (workers=$WORKERS)" +fi + +# ================================================================ +# Phase 6: Nginx Configuration +# ================================================================ + +if [ "$SKIP_NGINX" = true ]; then + warn "Phase 6/7: Skipping Nginx (per --skip-nginx)" +else + info "Phase 6/7: Configuring Nginx..." + + if [ "$BT_MODE" = true ]; then + NGINX_CONF_FILE="/www/server/panel/vhost/nginx/$DOMAIN.conf" + NGINX_RELOAD_CMD="/www/server/nginx/sbin/nginx -s reload" + NGINX_TEST_CMD="/www/server/nginx/sbin/nginx -t" + NGINX_ACCESS_LOG="/www/wwwlogs/$DOMAIN.log" + NGINX_ERROR_LOG="/www/wwwlogs/$DOMAIN.error.log" + else + NGINX_CONF_FILE="/etc/nginx/sites-available/nexus" + NGINX_RELOAD_CMD="systemctl reload nginx" + NGINX_TEST_CMD="nginx -t" + NGINX_ACCESS_LOG="/var/log/nginx/nexus_access.log" + NGINX_ERROR_LOG="/var/log/nginx/nexus_error.log" + fi + + NGINX_CONF="# Nexus 6.0 — Nginx Reverse Proxy +# Auto-generated by install.sh +upstream nexus_api { + server 127.0.0.1:$PORT; + keepalive 32; +} + +server { + listen 80; + server_name $DOMAIN; + + # Reverse proxy to Nexus (uvicorn) + location / { + proxy_pass http://nexus_api; + 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_read_timeout 300s; + proxy_send_timeout 300s; + } + + # WebSocket support (terminal, alerts) + location /ws/ { + proxy_pass http://nexus_api; + 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_read_timeout 3600s; + } + + # Static assets caching + location /app/vendor/ { + proxy_pass http://nexus_api; + expires 7d; + add_header Cache-Control \"public, immutable\"; + } + + # Block sensitive files + location ~ ^/(\\.env|\\.git|\\.svn) { + return 404; + } + + # Block config.json access + location ~ /data/config\\.json$ { + return 404; + } + + # Upload limit + client_max_body_size 100m; + + # Well-known for SSL cert validation + location ~ \\.well-known { + allow all; + } + + access_log $NGINX_ACCESS_LOG; + error_log $NGINX_ERROR_LOG; +}" + + # Write Nginx config + if [ "$BT_MODE" = true ]; then + write_config "$NGINX_CONF_FILE" "$NGINX_CONF" + else + mkdir -p /etc/nginx/sites-available + write_config "$NGINX_CONF_FILE" "$NGINX_CONF" + if [ ! -L /etc/nginx/sites-enabled/nexus ]; then + ln -sf "$NGINX_CONF_FILE" /etc/nginx/sites-enabled/nexus + fi + fi + + # Test and reload Nginx + if $NGINX_TEST_CMD >/dev/null 2>&1; then + $NGINX_RELOAD_CMD >/dev/null 2>&1 || warn " Nginx reload failed" + info " Nginx config written to $NGINX_CONF_FILE" + else + warn " Nginx config test failed. Please check $NGINX_CONF_FILE manually" + fi +fi + +# ================================================================ +# Phase 7: Start Application + Verify +# ================================================================ + +info "Phase 7/7: Starting application..." + +# Start via Supervisor +if [ "$SKIP_SUPERVISOR" = false ]; then + supervisorctl start nexus >/dev/null 2>&1 || supervisorctl restart nexus >/dev/null 2>&1 || true +fi + +# Wait for health check +HEALTH_OK=false +for i in $(seq 1 15); do + if curl -sf "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; then + HEALTH_OK=true + break + fi + sleep 2 +done + +# ── Print Results ── +echo "" +echo "==========================================" + +if [ "$HEALTH_OK" = true ]; then + echo -e " ${GREEN}Nexus installed successfully!${NC}" +else + echo -e " ${YELLOW}Nexus process started but health check pending.${NC}" + echo -e " ${YELLOW}It may need a few more seconds to start up.${NC}" +fi + +echo "==========================================" +echo "" +echo " Deploy: $DEPLOY_DIR" +echo " Domain: $DOMAIN" +echo " Port: $PORT" +echo " Workers: ${WORKERS:-N/A}" +echo " BT Panel: $BT_MODE" +echo "" + +if [ "$HEALTH_OK" = true ]; then + echo -e " ${GREEN}Next step:${NC} Open your browser and complete the web install wizard:" + echo "" + echo " http://$DOMAIN/app/install.html" + echo "" + echo " The wizard will guide you through:" + echo " Step 1: Welcome & prerequisites" + echo " Step 2: Environment check" + echo " Step 3: MySQL + Redis configuration" + echo " Step 4: Admin account + brand" + echo " Step 5: Complete" +else + echo " Troubleshooting:" + echo " supervisorctl status nexus" + echo " tail -f $LOG_DIR/nexus_error.log" + echo " curl http://127.0.0.1:$PORT/health" +fi + +echo "" +echo " Post-install:" +echo " 1. Configure SSL (HTTPS)" +if [ "$BT_MODE" = true ]; then + echo " BT Panel -> Website -> $DOMAIN -> SSL" +else + echo " certbot --nginx -d $DOMAIN" +fi +echo " 2. Set up health monitor crontab:" +echo " * * * * * $DEPLOY_DIR/deploy/health_monitor.sh" +echo " 3. Set up database backup crontab:" +echo " 0 3 * * * $DEPLOY_DIR/deploy/db_backup.sh" +echo "" +echo " Management:" +echo " supervisorctl status nexus" +echo " supervisorctl restart nexus" +echo " supervisorctl stop nexus" +echo "" diff --git a/deploy/nginx_http.conf b/deploy/nginx_http.conf index ae8218ce..d56a302e 100644 --- a/deploy/nginx_http.conf +++ b/deploy/nginx_http.conf @@ -61,7 +61,7 @@ server { location ~ /\. { deny all; } - location ~ /data/config\.php$ { + location ~ /data/config\.json$ { deny all; } diff --git a/deploy/nginx_https.conf b/deploy/nginx_https.conf index 3b9fa257..e931796d 100644 --- a/deploy/nginx_https.conf +++ b/deploy/nginx_https.conf @@ -89,7 +89,7 @@ server { location ~ /\. { deny all; } - location ~ /data/config\.php$ { + location ~ /data/config\.json$ { deny all; } diff --git a/deploy/uninstall.sh b/deploy/uninstall.sh new file mode 100644 index 00000000..42ac002d --- /dev/null +++ b/deploy/uninstall.sh @@ -0,0 +1,145 @@ +#!/bin/bash +# ================================================================ +# Nexus 6.0 — Uninstall Script +# +# Usage: +# sudo bash uninstall.sh # interactive +# sudo bash uninstall.sh --deploy-dir /opt/nexus # specify deploy dir +# sudo bash uninstall.sh --purge # also remove deploy dir +# +# Safe: stops services, removes configs. Database is NEVER deleted automatically. +# ================================================================ + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*"; } + +DEPLOY_DIR="" +PURGE=false + +while [[ $# -gt 0 ]]; do + case $1 in + --deploy-dir) DEPLOY_DIR="$2"; shift 2 ;; + --purge) PURGE=true; shift ;; + -h|--help) + echo "Usage: sudo bash uninstall.sh [--deploy-dir PATH] [--purge]" + echo "" + echo " --deploy-dir PATH Specify Nexus installation directory" + echo " --purge Also remove the deploy directory and venv" + echo "" + echo " Note: MySQL database is NEVER deleted automatically." + exit 0 ;; + *) shift ;; + esac +done + +# Auto-detect deploy directory +if [ -z "$DEPLOY_DIR" ]; then + for d in /opt/nexus /www/wwwroot/*/; do + if [ -d "$d/server" ] && [ -d "$d/web" ]; then + DEPLOY_DIR="$d" + break + fi + done +fi + +if [ -z "$DEPLOY_DIR" ] || [ ! -d "$DEPLOY_DIR/server" ]; then + error "Cannot find Nexus installation. Use --deploy-dir to specify." + exit 1 +fi + +# Detect BT Panel +BT_MODE=false +if [ -d "/www/server/panel" ] && [ -f "/www/server/panel/class/common.py" ]; then + BT_MODE=true +fi + +echo "" +echo "==========================================" +echo " Nexus 6.0 Uninstall" +echo "==========================================" +echo " Deploy: $DEPLOY_DIR" +echo " BT Panel: $BT_MODE" +echo " Purge files: $PURGE" +echo "" + +# Confirm +read -rp "Are you sure you want to uninstall Nexus? [y/N] " confirm +if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then + echo "Cancelled." + exit 0 +fi + +# 1. Stop Supervisor process +info "Step 1/5: Stopping Nexus process..." +supervisorctl stop nexus >/dev/null 2>&1 || true + +# 2. Remove Supervisor config +info "Step 2/5: Removing Supervisor config..." +if [ "$BT_MODE" = true ]; then + SUPERVISOR_CONF="/www/server/panel/plugin/supervisor/profile/nexus.ini" +else + SUPERVISOR_CONF="/etc/supervisor/conf.d/nexus.conf" +fi +if [ -f "$SUPERVISOR_CONF" ]; then + rm -f "$SUPERVISOR_CONF" + supervisorctl reread >/dev/null 2>&1 || true + supervisorctl update >/dev/null 2>&1 || true + info " Removed $SUPERVISOR_CONF" +else + info " Supervisor config not found (already removed)" +fi + +# 3. Remove Nginx config +info "Step 3/5: Removing Nginx config..." +if [ "$BT_MODE" = true ]; then + # Find the domain from deploy dir + DOMAIN=$(basename "$DEPLOY_DIR") + NGINX_CONF="/www/server/panel/vhost/nginx/$DOMAIN.conf" +else + NGINX_CONF="/etc/nginx/sites-available/nexus" + NGINX_SYMLINK="/etc/nginx/sites-enabled/nexus" +fi + +if [ -f "$NGINX_CONF" ]; then + rm -f "$NGINX_CONF" + [ -L "${NGINX_SYMLINK:-}" ] && rm -f "$NGINX_SYMLINK" + if [ "$BT_MODE" = true ]; then + /www/server/nginx/sbin/nginx -s reload >/dev/null 2>&1 || true + else + nginx -t >/dev/null 2>&1 && systemctl reload nginx 2>/dev/null || true + fi + info " Removed $NGINX_CONF" +else + info " Nginx config not found (already removed)" +fi + +# 4. Remove crontab entries +info "Step 4/5: Removing crontab entries..." +crontab -l 2>/dev/null | grep -v "health_monitor.sh" | grep -v "db_backup.sh" | crontab - 2>/dev/null || true +info " Crontab entries removed" + +# 5. Optionally remove deploy directory +if [ "$PURGE" = true ]; then + info "Step 5/5: Removing deploy directory (--purge)..." + rm -rf "$DEPLOY_DIR" + info " Removed $DEPLOY_DIR" +else + info "Step 5/5: Keeping deploy directory (use --purge to remove)" + info " To remove manually: rm -rf $DEPLOY_DIR" +fi + +echo "" +echo -e " ${GREEN}Nexus uninstalled successfully.${NC}" +echo "" +echo " Note: MySQL database was NOT deleted." +echo " To drop the database manually:" +echo " mysql -u root -e 'DROP DATABASE IF EXISTS nexus;'" +echo "" diff --git a/deploy/upgrade.sh b/deploy/upgrade.sh new file mode 100644 index 00000000..39bec7df --- /dev/null +++ b/deploy/upgrade.sh @@ -0,0 +1,130 @@ +#!/bin/bash +# ================================================================ +# Nexus 6.0 — Upgrade Script +# +# Usage: +# sudo bash upgrade.sh # auto-detect deploy dir +# sudo bash upgrade.sh --deploy-dir /opt/nexus # specify deploy dir +# +# Safe: git pull + pip install + restart. Database is NOT touched. +# ================================================================ + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*"; } + +DEPLOY_DIR="" + +while [[ $# -gt 0 ]]; do + case $1 in + --deploy-dir) DEPLOY_DIR="$2"; shift 2 ;; + -h|--help) + echo "Usage: sudo bash upgrade.sh [--deploy-dir PATH]" + exit 0 ;; + *) shift ;; + esac +done + +# Auto-detect deploy directory +if [ -z "$DEPLOY_DIR" ]; then + # Check common locations + for d in /opt/nexus /www/wwwroot/*/; do + if [ -d "$d/server" ] && [ -d "$d/web" ]; then + DEPLOY_DIR="$d" + break + fi + done + # Try from Supervisor config + if [ -z "$DEPLOY_DIR" ]; then + DEPLOY_DIR=$(supervisorctl status nexus 2>/dev/null | grep -oP 'cwd=\K[^ ,]+' || true) + fi +fi + +if [ -z "$DEPLOY_DIR" ] || [ ! -d "$DEPLOY_DIR/server" ]; then + error "Cannot find Nexus installation. Use --deploy-dir to specify." + exit 1 +fi + +VENV_DIR="$DEPLOY_DIR/venv" +ENV_FILE="$DEPLOY_DIR/.env" + +echo "" +echo "==========================================" +echo " Nexus 6.0 Upgrade" +echo "==========================================" +echo " Deploy: $DEPLOY_DIR" +echo "" + +# 1. Pull latest code +info "Step 1/4: Updating source code..." +if [ -d "$DEPLOY_DIR/.git" ]; then + cd "$DEPLOY_DIR" + OLD_REV=$(git rev-parse HEAD 2>/dev/null || echo "unknown") + git pull --ff-only 2>/dev/null || { + error "git pull failed. You may have local changes." + error "Resolve conflicts manually: cd $DEPLOY_DIR && git status" + exit 1 + } + NEW_REV=$(git rev-parse HEAD 2>/dev/null || echo "unknown") + if [ "$OLD_REV" = "$NEW_REV" ]; then + info " Already up to date (no new commits)" + else + info " Updated: ${OLD_REV:0:8}..${NEW_REV:0:8}" + fi +else + warn " Not a git repo. Skip git pull (update files manually)" +fi + +# 2. Update Python dependencies +info "Step 2/4: Updating Python dependencies..." +if [ -d "$VENV_DIR/bin" ]; then + "$VENV_DIR/bin/pip" install -q --upgrade pip + "$VENV_DIR/bin/pip" install -q -r "$DEPLOY_DIR/requirements.txt" 2>/dev/null || { + warn " pip install had some warnings, but continuing..." + } +else + error " venv not found at $VENV_DIR. Run install.sh first." + exit 1 +fi + +# 3. Restart application +info "Step 3/4: Restarting Nexus..." +supervisorctl restart nexus >/dev/null 2>&1 || { + warn " supervisorctl restart failed. Try manually: supervisorctl restart nexus" +} + +# 4. Health check +info "Step 4/4: Verifying health..." + +# Read port from .env or default +PORT=8600 +if [ -f "$ENV_FILE" ]; then + PORT_FROM_ENV=$(grep '^NEXUS_PORT=' "$ENV_FILE" 2>/dev/null | cut -d'=' -f2 || true) + [ -n "$PORT_FROM_ENV" ] && PORT="$PORT_FROM_ENV" +fi + +HEALTH_OK=false +for i in $(seq 1 15); do + if curl -sf "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; then + HEALTH_OK=true + break + fi + sleep 2 +done + +echo "" +if [ "$HEALTH_OK" = true ]; then + echo -e " ${GREEN}Upgrade complete! Nexus is healthy.${NC}" +else + echo -e " ${YELLOW}Health check pending. Verify manually:${NC}" + echo " curl http://127.0.0.1:$PORT/health" + echo " supervisorctl status nexus" +fi +echo "" diff --git a/docs/install-guide.md b/docs/install-guide.md new file mode 100644 index 00000000..70b87751 --- /dev/null +++ b/docs/install-guide.md @@ -0,0 +1,410 @@ +# Nexus 6.0 安装指南 + +## 系统要求 + +| 组件 | 最低版本 | 推荐 | +|------|----------|------| +| 操作系统 | Ubuntu 20.04 / Debian 11 / CentOS 7 | Ubuntu 22.04+ | +| Python | 3.10 | 3.12 | +| MySQL | 8.0 | 8.4 | +| Redis | 6.0 | 7.0 | +| Nginx | 1.18 | 最新稳定版 | +| 内存 | 1GB | 2GB+ | +| 磁盘 | 5GB | 20GB+ | + +--- + +## 快速安装 + +### 一键脚本安装(推荐) + +```bash +# 1. 下载并运行安装脚本 +sudo bash install.sh --domain nexus.example.com + +# 2. 打开浏览器完成 Web 安装向导 +# http://nexus.example.com/app/install.html +``` + +脚本会自动检测环境、安装依赖、配置 Supervisor 和 Nginx。 + +### 参数说明 + +``` +--domain DOMAIN 必填,域名或 IP +--deploy-dir PATH 安装目录(默认自动检测) +--port PORT uvicorn 端口(默认 8600) +--skip-nginx 跳过 Nginx 配置 +--skip-supervisor 跳过 Supervisor 配置 +--repo URL Git 仓库地址(可选) +``` + +--- + +## 宝塔面板安装指南 + +### 1. 安装宝塔面板(如未安装) + +```bash +curl -sSO https://raw.githubusercontent.com/zhucaidan/btpanel-v7.0.3/main/install_panel.sh && bash install_panel.sh +``` + +### 2. 通过宝塔安装 MySQL 和 Redis + +1. 宝塔面板 → 软件商店 → 搜索 **MySQL 8.0** → 安装 +2. 软件商店 → 搜索 **Redis** → 安装 +3. 数据库 → 添加数据库: + - 数据库名:`nexus` + - 用户名:`nexus` + - 密码:自动生成或自定义 + +### 3. 创建网站 + +1. 网站 → 添加站点 + - 域名:`nexus.example.com` + - 根目录:`/www/wwwroot/nexus.example.com` + - PHP版本:纯静态(不需要 PHP) +2. 记录网站根目录路径 + +### 4. 运行安装脚本 + +```bash +# 将 Nexus 文件上传到网站根目录后运行 +cd /www/wwwroot/nexus.example.com +sudo bash deploy/install.sh --domain nexus.example.com --deploy-dir /www/wwwroot/nexus.example.com +``` + +或者如果文件还没上传: + +```bash +sudo bash deploy/install.sh --domain nexus.example.com --repo https://your-gitea-url/Nexus.git +``` + +脚本会自动检测宝塔面板,配置写入正确的路径。 + +### 5. 配置 Supervisor + +如果脚本未自动配置: + +1. 宝塔面板 → 软件商店 → 搜索 **Supervisor管理器** → 安装 +2. Supervisor管理器 → 添加守护进程: + - 名称:`nexus` + - 启动命令:`/www/wwwroot/nexus.example.com/venv/bin/uvicorn server.main:app --host 0.0.0.0 --port 8600` + - 运行目录:`/www/wwwroot/nexus.example.com` + +### 6. 配置 Nginx + +1. 网站 → nexus.example.com → 设置 → 配置文件 +2. 在 `#PHP-INFO-END` 后面粘贴: + +```nginx + # Nexus Python API + WebSocket + location ^~ /api/ { + proxy_pass http://127.0.0.1:8600; + 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; + } + location ^~ /health { + proxy_pass http://127.0.0.1:8600; + } + location ^~ /ws/ { + proxy_pass http://127.0.0.1:8600; + 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; + } +``` + +3. 保存并重载 + +### 7. 配置 SSL + +1. 网站 → nexus.example.com → SSL → Let's Encrypt +2. 申请证书 → 开启强制 HTTPS + +### 8. 完成 Web 安装向导 + +打开浏览器访问 `https://nexus.example.com/app/install.html`,按向导完成: + +- Step 2:环境检测 +- Step 3:填写 MySQL 和 Redis 连接信息 +- Step 4:创建管理员账号 +- Step 5:安装完成 + +--- + +## 手动安装指南 + +### 1. 安装系统依赖 + +**Ubuntu / Debian:** + +```bash +sudo apt update +sudo apt install -y python3 python3-venv python3-pip python3-dev \ + build-essential libssl-dev libffi-dev default-libmysqlclient-dev \ + git curl nginx supervisor +``` + +**CentOS / Rocky / AlmaLinux:** + +```bash +sudo dnf install -y python3 python3-devel python3-pip \ + gcc openssl-devel libffi-devel mysql-devel \ + git curl nginx supervisor +``` + +### 2. 安装 MySQL 8.0 + +```bash +# Ubuntu +sudo apt install -y mysql-server +sudo mysql_secure_installation + +# 创建数据库 +sudo mysql -e "CREATE DATABASE nexus CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" +sudo mysql -e "CREATE USER 'nexus'@'localhost' IDENTIFIED BY 'YOUR_PASSWORD';" +sudo mysql -e "GRANT ALL PRIVILEGES ON nexus.* TO 'nexus'@'localhost';" +sudo mysql -e "FLUSH PRIVILEGES;" +``` + +### 3. 安装 Redis + +```bash +# Ubuntu +sudo apt install -y redis-server +sudo systemctl enable redis-server +sudo systemctl start redis-server + +# 验证 +redis-cli ping # 应返回 PONG +``` + +### 4. 部署 Nexus + +```bash +# 创建目录 +sudo mkdir -p /opt/nexus +cd /opt/nexus + +# 克隆代码(或从发布包解压) +sudo git clone https://your-gitea-url/Nexus.git . + +# 创建 Python 虚拟环境 +python3.12 -m venv venv +source venv/bin/activate + +# 安装依赖 +pip install -r requirements.txt +``` + +### 5. 配置 Supervisor + +```bash +sudo tee /etc/supervisor/conf.d/nexus.conf << 'EOF' +[program:nexus] +command=/opt/nexus/venv/bin/uvicorn server.main:app --host 0.0.0.0 --port 8600 --workers 2 --loop uvloop --http httptools --log-level info +directory=/opt/nexus +user=root +autostart=true +autorestart=true +startretries=10 +startsecs=3 +stopwaitsecs=10 +stopsignal=INT +environment=PATH="/opt/nexus/venv/bin:%(ENV_PATH)s",HOME="/root" +stderr_logfile=/var/log/nexus/error.log +stdout_logfile=/var/log/nexus/access.log +stderr_logfile_maxbytes=50MB +stdout_logfile_maxbytes=50MB +EOF + +# 创建日志目录 +sudo mkdir -p /var/log/nexus + +# 重载 Supervisor +sudo supervisorctl reread +sudo supervisorctl update +sudo supervisorctl start nexus +``` + +### 6. 配置 Nginx + +```bash +sudo tee /etc/nginx/sites-available/nexus << 'EOF' +upstream nexus_api { + server 127.0.0.1:8600; + keepalive 32; +} + +server { + listen 80; + server_name nexus.example.com; + + location / { + proxy_pass http://nexus_api; + 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_read_timeout 300s; + } + + location /ws/ { + proxy_pass http://nexus_api; + 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_read_timeout 3600s; + } + + location ~ ^/(\.env|\.git) { + return 404; + } + + client_max_body_size 100m; + + access_log /var/log/nginx/nexus_access.log; + error_log /var/log/nginx/nexus_error.log; +} +EOF + +# 启用站点 +sudo ln -sf /etc/nginx/sites-available/nexus /etc/nginx/sites-enabled/ +sudo nginx -t && sudo systemctl reload nginx +``` + +### 7. 完成 Web 安装向导 + +打开浏览器访问 `http://nexus.example.com/app/install.html` + +### 8. 配置 SSL + +```bash +sudo apt install -y certbot python3-certbot-nginx +sudo certbot --nginx -d nexus.example.com +``` + +--- + +## 安装后配置 + +### 健康检查 Crontab(第3层守护) + +```bash +# 编辑 crontab +sudo crontab -e + +# 添加(每分钟检查一次) +* * * * * /opt/nexus/deploy/health_monitor.sh +``` + +### 数据库备份 Crontab + +```bash +# 每天凌晨3点自动备份,保留30天 +0 3 * * * /opt/nexus/deploy/db_backup.sh +``` + +### 防火墙配置 + +```bash +# 只开放必要端口 +sudo ufw allow 22/tcp # SSH +sudo ufw allow 80/tcp # HTTP +sudo ufw allow 443/tcp # HTTPS +sudo ufw enable + +# 8600 端口不需要对外开放(Nginx 反向代理) +``` + +### 安全收尾清单 + +- [ ] 安装向导已自动锁定 +- [ ] `.env` 文件包含 SECRET_KEY/API_KEY/ENCRYPTION_KEY — 安装后不可修改 +- [ ] SSL 证书已配置,强制 HTTPS +- [ ] 8600 端口未对外开放 +- [ ] 管理员密码已修改(非默认弱密码) +- [ ] Telegram 告警已配置(Settings 页面) + +--- + +## 升级 + +```bash +sudo bash /opt/nexus/deploy/upgrade.sh +``` + +升级脚本会:git pull → pip install → supervisorctl restart → 健康检查。 + +--- + +## 卸载 + +```bash +sudo bash /opt/nexus/deploy/uninstall.sh # 保留文件 +sudo bash /opt/nexus/deploy/uninstall.sh --purge # 同时删除部署目录 +``` + +> 注意:MySQL 数据库不会被自动删除。需手动 `DROP DATABASE nexus;` + +--- + +## 故障排查 + +### 端口 8600 无响应 + +```bash +supervisorctl status nexus # 检查进程状态 +tail -f /var/log/nexus/error.log # 查看错误日志 +curl http://127.0.0.1:8600/health # 本地健康检查 +``` + +### Nginx 502 Bad Gateway + +- 检查 uvicorn 是否运行:`supervisorctl status nexus` +- 检查端口匹配:Nginx upstream 端口和 uvicorn 端口是否一致 +- 检查 Nginx 错误日志:`tail -f /var/log/nginx/nexus_error.log` + +### 数据库连接失败 + +```bash +# 检查 MySQL 是否运行 +sudo systemctl status mysql + +# 测试连接 +mysql -u nexus -p -h 127.0.0.1 nexus +``` + +### Redis 连接失败 + +```bash +# 检查 Redis 是否运行 +sudo systemctl status redis-server +redis-cli ping +``` + +### Supervisor 启动失败 + +```bash +# 检查配置语法 +supervisorctl reread + +# 查看详细日志 +tail -f /var/log/nexus/error.log + +# 手动启动测试 +cd /opt/nexus && source venv/bin/activate +uvicorn server.main:app --host 0.0.0.0 --port 8600 +``` + +### 安装向导无法访问 + +- 检查 `.env` 文件是否存在:如果存在,安装向导会被锁定 +- 删除 `.env` 可重新进入安装模式:`rm /opt/nexus/.env && supervisorctl restart nexus` diff --git a/mcp/firefox_server.py b/mcp/firefox_server.py index 07844a67..b14ae397 100644 --- a/mcp/firefox_server.py +++ b/mcp/firefox_server.py @@ -26,7 +26,7 @@ def _get_driver(): # T1: 导航 # ================================================================ @server.tool() -async def firefox_navigate(url: str = "https://192.168.124.10/login.php"): +async def firefox_navigate(url: str = "https://192.168.124.10/app/login.html"): """Firefox 打开指定 URL,返回页面标题和 URL""" try: d = _get_driver() diff --git a/server/api/install.py b/server/api/install.py index 4045e3f8..47753a74 100644 --- a/server/api/install.py +++ b/server/api/install.py @@ -29,10 +29,9 @@ router = APIRouter(prefix="/api/install", tags=["install"]) ROOT_DIR = Path(__file__).resolve().parent.parent.parent ENV_FILE = ROOT_DIR / ".env" -LOCK_FILE = ROOT_DIR / "web" / "install.php.locked" # Legacy compat — primary lock is .install_locked INSTALL_LOCK = ROOT_DIR / ".install_locked" -CONFIG_PHP_DIR = ROOT_DIR / "web" / "data" -CONFIG_PHP = CONFIG_PHP_DIR / "config.php" +CONFIG_DIR = ROOT_DIR / "web" / "data" +CONFIG_JSON = CONFIG_DIR / "config.json" STATE_FILE = ROOT_DIR / ".install_state.json" @@ -41,7 +40,7 @@ def _is_installed() -> bool: def _is_locked() -> bool: - return INSTALL_LOCK.exists() or LOCK_FILE.exists() + return INSTALL_LOCK.exists() # ── Pydantic Models ── @@ -76,16 +75,6 @@ class CreateAdminRequest(BaseModel): # ── Helpers ── -def _escape_php_string(s: str) -> str: - """Escape a string for safe insertion into a PHP single-quoted string. - - In PHP single-quoted strings, only ``\\`` and ``'`` are special escapes. - A user value containing ``'`` could break out of the define() and inject - arbitrary PHP code (RCE). This function neutralises that vector. - """ - return s.replace("\\", "\\\\").replace("'", "\\'") - - def _make_db_url(req: InitDbRequest | CreateAdminRequest) -> str: return ( f"mysql+aiomysql://{req.db_user}:{quote_plus(req.db_pass)}" @@ -171,42 +160,48 @@ def _write_env(req: InitDbRequest, secret_key: str, api_key: str, encryption_key logger.info(f".env written to {ENV_FILE}") -def _write_config_php(req: InitDbRequest, api_key: str, site_url: str) -> None: - CONFIG_PHP_DIR.mkdir(parents=True, exist_ok=True) +def _write_config_json(req: InitDbRequest, api_key: str, site_url: str) -> None: + """Write shared configuration as JSON (replaces legacy config.php).""" + import json + CONFIG_DIR.mkdir(parents=True, exist_ok=True) install_dir = str(ROOT_DIR) - now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") - lines = [ - " bool: + """Detect if BT Panel (宝塔面板) is installed on this system.""" + return Path("/www/server/panel/class/common.py").exists() def _configure_guardian(install_dir: str, api_port: str) -> list[str]: """Best-effort: configure Supervisor, crontab, health_monitor.sh""" results: list[str] = [] + bt_panel = _detect_bt_panel() # 1. Log directory - log_dir = Path("/var/log/nexus") + if bt_panel: + log_dir = Path("/www/wwwlogs") + else: + log_dir = Path("/var/log/nexus") try: log_dir.mkdir(parents=True, exist_ok=True) results.append(f"✓ 日志目录已创建 {log_dir}") @@ -214,6 +209,11 @@ def _configure_guardian(install_dir: str, api_port: str) -> list[str]: results.append(f"✗ 日志目录创建失败(需 root 权限)") # 2. Supervisor config + if bt_panel: + conf_path = Path("/www/server/panel/plugin/supervisor/profile/nexus.ini") + else: + conf_path = Path("/etc/supervisor/conf.d/nexus.conf") + supervisor_conf = ( f"[program:nexus]\n" f"command={install_dir}/venv/bin/uvicorn server.main:app --host 0.0.0.0 --port {api_port}\n" @@ -226,18 +226,18 @@ def _configure_guardian(install_dir: str, api_port: str) -> list[str]: f"stopwaitsecs=10\n" f"stopsignal=INT\n" f"environment=PATH=\"{install_dir}/venv/bin:/usr/local/bin:%(ENV_PATH)s\",HOME=\"/root\"\n" - f"stderr_logfile=/var/log/nexus/error.log\n" - f"stdout_logfile=/var/log/nexus/access.log\n" + f"stderr_logfile={log_dir}/nexus_error.log\n" + f"stdout_logfile={log_dir}/nexus_access.log\n" f"stderr_logfile_maxbytes=50MB\n" f"stdout_logfile_maxbytes=50MB\n" f"stderr_logfile_backups=5\n" f"stdout_logfile_backups=5\n" ) - conf_path = Path("/etc/supervisor/conf.d/nexus.conf") try: conf_path.parent.mkdir(parents=True, exist_ok=True) conf_path.write_text(supervisor_conf) - results.append(f"✓ Supervisor 配置已写入 {conf_path}") + mode_label = "宝塔" if bt_panel else "标准" + results.append(f"✓ Supervisor 配置已写入 {conf_path}({mode_label}模式)") except OSError: results.append("✗ Supervisor 配置写入失败(需 root 权限)") @@ -379,7 +379,7 @@ async def env_check(): }) # Write permissions - data_writable = CONFIG_PHP_DIR.exists() and os.access(CONFIG_PHP_DIR, os.W_OK) or \ + data_writable = CONFIG_DIR.exists() and os.access(CONFIG_DIR, os.W_OK) or \ os.access(ROOT_DIR / "web", os.W_OK) root_writable = os.access(ROOT_DIR, os.W_OK) checks.append({ @@ -498,8 +498,8 @@ async def init_db(req: InitDbRequest): site_url = req.site_url.rstrip("/") if req.site_url else f"http://127.0.0.1:{req.api_port}" _write_env(req, secret_key, api_key, encryption_key, pool_size, max_overflow, redis_url, site_url) - # Write config.php - _write_config_php(req, api_key, site_url) + # Write config.json + _write_config_json(req, api_key, site_url) # Configure process guardian (best-effort) install_dir = str(ROOT_DIR) @@ -530,6 +530,7 @@ async def init_db(req: InitDbRequest): "max_overflow": max_overflow, "tables_created": 14, "guardian_results": guardian_results, + "bt_panel": _detect_bt_panel(), } @@ -588,16 +589,15 @@ async def create_admin(req: CreateAdminRequest): except Exception as e: raise HTTPException(400, f"创建管理员失败: {e}") - # Update config.php with brand - if CONFIG_PHP.exists(): - import re - content = CONFIG_PHP.read_text() - content = re.sub( - r"define\('APP_NAME', '.*?'\);", - f"define('APP_NAME', '{_escape_php_string(req.system_name)}');", - content, - ) - CONFIG_PHP.write_text(content) + # Update config.json with brand + if CONFIG_JSON.exists(): + import json + try: + config = json.loads(CONFIG_JSON.read_text()) + config["app_name"] = req.system_name + CONFIG_JSON.write_text(json.dumps(config, indent=2, ensure_ascii=False)) + except Exception: + pass # Update .env with brand if ENV_FILE.exists(): @@ -654,17 +654,11 @@ async def lock_install(): logger.warning("Failed to verify admin account before locking: %s", e) # If DB is unreachable, allow lock (admin may have been created but DB is temporarily down) - # Create lock marker files + # Create lock marker file INSTALL_LOCK.write_text( f"Nexus installation locked at {datetime.now(timezone.utc).isoformat()}\n" ) - # Also create the legacy lock file (install.php.locked) for compatibility - try: - LOCK_FILE.write_text("locked\n") - except OSError as e: - logger.warning("Failed to write legacy lock file: %s", e) - # Clean up state file if STATE_FILE.exists(): try: diff --git a/server/config.py b/server/config.py index d5e92d90..204aea2e 100644 --- a/server/config.py +++ b/server/config.py @@ -39,10 +39,10 @@ class Settings(BaseSettings): # Database (immutable — must be set in .env) DATABASE_URL: str = "mysql+aiomysql://root:password@127.0.0.1:3306/nexus" - # Pool params: install.php auto-calculates from MySQL max_connections + # Pool params: install wizard auto-calculates from MySQL max_connections # Formula: pool_size = max(20, maxConn * 0.4), overflow = max(20, maxConn * 0.3) # MySQL max_connections=400 → pool=160, overflow=120, max=280 - # These defaults match install.php; override via .env or MySQL settings table + # These defaults match install wizard; override via .env or MySQL settings table DB_POOL_SIZE: Optional[int] = 160 DB_MAX_OVERFLOW: Optional[int] = 120 diff --git a/server/infrastructure/database/crypto.py b/server/infrastructure/database/crypto.py index 9ad9ab94..8bd6d614 100644 --- a/server/infrastructure/database/crypto.py +++ b/server/infrastructure/database/crypto.py @@ -1,12 +1,9 @@ -"""Nexus — Encryption Utilities (Fernet + AES) +"""Nexus — Encryption Utilities (Fernet) Shared crypto module — used by SSH pool, DB credentials, password presets. """ -import base64 -import hashlib import logging from cryptography.fernet import Fernet -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from server.config import settings @@ -39,14 +36,6 @@ def get_fernet() -> Fernet: return _fernet_instance -def _derive_aes_key() -> bytes: - """Unified key derivation: must match PHP _encryptPw() - PHP uses hash('sha256', API_KEY, true). - This uses API_KEY first, falls back to SECRET_KEY.""" - key = settings.API_KEY or settings.SECRET_KEY - return hashlib.sha256(key.encode()).digest() - - def encrypt_value(plaintext: str) -> str: """Encrypt a string using Fernet""" if not plaintext: @@ -55,29 +44,11 @@ def encrypt_value(plaintext: str) -> str: def decrypt_value(ciphertext: str) -> str: - """Decrypt a string — supports both Fernet and PHP AES format""" + """Decrypt a Fernet-encrypted string""" if not ciphertext: return ciphertext - # PHP-compatible AES format - if ciphertext.startswith("aes:"): - try: - key = _derive_aes_key() - data = base64.b64decode(ciphertext[4:]) - iv = data[:16] - enc = data[16:] - cipher = Cipher(algorithms.AES(key), modes.CBC(iv)) - decryptor = cipher.decryptor() - plain = decryptor.update(enc) + decryptor.finalize() - pad_len = plain[-1] - if 1 <= pad_len <= 16: - plain = plain[:-pad_len] - return plain.decode() - except Exception as e: - logger.warning(f"AES decryption failed (key mismatch?): {e}") - raise ValueError("Failed to decrypt credential (AES)") from e - # Fernet format try: return get_fernet().decrypt(ciphertext.encode()).decode() except Exception as e: logger.warning(f"Fernet decryption failed (key mismatch?): {e}") - raise ValueError("Failed to decrypt credential (Fernet)") from e \ No newline at end of file + raise ValueError("Failed to decrypt credential") from e \ No newline at end of file diff --git a/server/infrastructure/database/migrations.py b/server/infrastructure/database/migrations.py index 223140c2..0f0e6a7b 100644 --- a/server/infrastructure/database/migrations.py +++ b/server/infrastructure/database/migrations.py @@ -144,7 +144,7 @@ async def run_schema_migrations(): "ALTER TABLE servers ADD COLUMN connectivity VARCHAR(20) DEFAULT 'unknown' COMMENT '连接状态'", "ALTER TABLE servers ADD COLUMN last_checked_at DATETIME NULL COMMENT '上次连接测试时间'", "ALTER TABLE servers ADD COLUMN ssh_key_configured TINYINT(1) DEFAULT 0 COMMENT '是否已配置SSH Key'", - # Fix: ssh_key columns were VARCHAR(500) in install.php — change to TEXT for large encrypted keys + # Fix: ssh_key columns were VARCHAR(500) in legacy schema — change to TEXT for large encrypted keys "ALTER TABLE servers MODIFY COLUMN ssh_key_private TEXT NULL COMMENT '加密后的私钥内容(Fernet)'", "ALTER TABLE servers MODIFY COLUMN ssh_key_public TEXT NULL COMMENT '公钥内容'", ] diff --git a/server/main.py b/server/main.py index 38fa7493..b6b01c1c 100644 --- a/server/main.py +++ b/server/main.py @@ -14,7 +14,7 @@ Lifespan startup (no .env — install mode): - All other API routes return 503 "System not configured" - User completes the install wizard at /app/install.html -D2: install.php → install.html migration (conditional lifespan) +D2: install wizard (install.html + FastAPI API) D7: DB session leak fix — middleware auto-manages session lifecycle per request. """ diff --git a/web/app/install.html b/web/app/install.html index 71f35f26..c1a332b6 100644 --- a/web/app/install.html +++ b/web/app/install.html @@ -76,7 +76,7 @@
本向导将引导您完成系统安装配置。
安装完成后将自动生成:.env (Python后端) +
- config.php (PHP兼容) +
+ config.json (共享配置) +
MySQL settings表 (共享配置)
整个过程大约 3 分钟。
安装: sudo apt install -y supervisor
配置文件复制到: /etc/supervisor/conf.d/nexus.conf
[program:nexus] -command=/venv/bin/uvicorn server.main:app --host 0.0.0.0 --port -directory= -user=root -autostart=true -autorestart=true -startretries=10 -startsecs=3 -stopwaitsecs=10 -stopsignal=INT -environment=PATH="/venv/bin:%(ENV_PATH)s" -stderr_logfile=/var/log/nexus/error.log -stdout_logfile=/var/log/nexus/access.log -stderr_logfile_maxbytes=50MB -stdout_logfile_maxbytes=50MB
启动: sudo supervisorctl reread && sudo supervisorctl update && sudo supervisorctl start nexus
宝塔面板 → 软件商店 → Supervisor管理器 → 添加守护进程
+或手动写入配置: /www/server/panel/plugin/supervisor/profile/nexus.ini
[program:nexus] + command=/venv/bin/uvicorn server.main:app --host 0.0.0.0 --port + directory= + user=root + autostart=true + autorestart=true + startretries=10 + startsecs=3 + stopwaitsecs=10 + stopsignal=INT + environment=PATH="/venv/bin:%(ENV_PATH)s" + stderr_logfile=/www/wwwlogs/nexus_error.log + stdout_logfile=/www/wwwlogs/nexus_access.log + stderr_logfile_maxbytes=50MB + stdout_logfile_maxbytes=50MB
重载: supervisorctl reread && supervisorctl update && supervisorctl start nexus
安装: sudo apt install -y supervisor
配置文件复制到: /etc/supervisor/conf.d/nexus.conf
[program:nexus] + command=/venv/bin/uvicorn server.main:app --host 0.0.0.0 --port + directory= + user=root + autostart=true + autorestart=true + startretries=10 + startsecs=3 + stopwaitsecs=10 + stopsignal=INT + environment=PATH="/venv/bin:%(ENV_PATH)s" + stderr_logfile=/var/log/nexus/error.log + stdout_logfile=/var/log/nexus/access.log + stderr_logfile_maxbytes=50MB + stdout_logfile_maxbytes=50MB
启动: sudo supervisorctl reread && sudo supervisorctl update && sudo supervisorctl start nexus
宝塔 → 网站 → 配置文件 → #PHP-INFO-END 后面粘贴:
# Nexus Python API + WebSocket + + ++proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }宝塔 → 网站 → 配置文件 →
+#PHP-INFO-END后面粘贴:} location ^~ /health { proxy_pass http://127.0.0.1:; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; } location ^~ /ws/ { proxy_pass http://127.0.0.1:; @@ -402,6 +431,43 @@ stdout_logfile_maxbytes=50MB# Nexus Python API + WebSocket location ^~ /api/ { proxy_pass http://127.0.0.1:; proxy_set_header Host $host; @@ -391,8 +422,6 @@ stdout_logfile_maxbytes=50MB
将以下配置写入 /etc/nginx/sites-available/nexus 并创建符号链接到 sites-enabled:
upstream nexus_api {
+ server 127.0.0.1:;
+ keepalive 32;
+}
+server {
+ listen 80;
+ server_name YOUR_DOMAIN;
+
+ location / {
+ proxy_pass http://nexus_api;
+ 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_read_timeout 300s;
+ }
+ location /ws/ {
+ proxy_pass http://nexus_api;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ proxy_set_header Host $host;
+ proxy_read_timeout 3600s;
+ }
+ location ~ ^/(\.env|\.git) {
+ return 404;
+ }
+}启用: sudo ln -sf /etc/nginx/sites-available/nexus /etc/nginx/sites-enabled/ && sudo nginx -t && sudo systemctl reload nginx
sudo apt install -y certbot python3-certbot-nginxsudo certbot --nginx -d YOUR_DOMAIN系统已完成安装,install.php 已重命名为 install.php.locked。
-如需重新安装,请删除 install.php.locked 文件。
服务器运维管理平台 — 心跳监控 + 智能告警 + 3层守护
-
- 本向导将引导您完成系统安装配置。
- 安装完成后将自动生成:config.php (PHP前端) + .env (Python后端) + MySQL settings表 (共享配置)
- 整个过程大约 3 分钟。
-
= htmlspecialchars($_SESSION['db_name'] ?? '-') ?>
- 用户: = htmlspecialchars($_SESSION['db_user'] ?? '-') ?>
- 连接池: = htmlspecialchars($_SESSION['pool_size'] ?? '-') ?>
- 溢出池: = htmlspecialchars($_SESSION['max_overflow'] ?? '-') ?>
- Nexus 6.0 已成功安装。
-install.php 已重命名为 install.php.locked — 防止重复安装
- - - -sudo apt install -y supervisor/etc/supervisor/conf.d/nexus.conf
- [program:nexus] -command== htmlspecialchars($installDir) ?>/venv/bin/uvicorn server.main:app --host 0.0.0.0 --port = htmlspecialchars($apiPort) ?> - -directory== htmlspecialchars($installDir) ?> - -user=root -autostart=true -autorestart=true -startretries=10 -startsecs=3 -stopwaitsecs=10 -stopsignal=INT -environment=PATH="= htmlspecialchars($installDir) ?>/venv/bin:%(ENV_PATH)s" -stderr_logfile=/var/log/nexus/error.log -stdout_logfile=/var/log/nexus/access.log -stderr_logfile_maxbytes=50MB -stdout_logfile_maxbytes=50MB-
sudo supervisorctl reread && sudo supervisorctl update && sudo supervisorctl start nexuscd = htmlspecialchars($installDir) ?>python3.12 -m venv venvsource venv/bin/activatepip install -r requirements.txt
- #PHP-INFO-END 后面粘贴: # Nexus Python API + WebSocket
- location ^~ /api/ {
- proxy_pass http://127.0.0.1:= htmlspecialchars($apiPort) ?>;
- 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;
- }
- location ^~ /health {
- proxy_pass http://127.0.0.1:= htmlspecialchars($apiPort) ?>;
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- }
- location ^~ /ws/ {
- proxy_pass http://127.0.0.1:= htmlspecialchars($apiPort) ?>;
- 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;
- }
- location /app/ {
- try_files $uri $uri/ /app/index.html;
-}
-location / {
- try_files $uri $uri/ =404;
-}
- = htmlspecialchars($installDir) ?>/deploy/* * * * * = htmlspecialchars($installDir) ?>/deploy/health_monitor.sh-
install.php 已自动重命名为 install.php.locked.env 包含 SECRET_KEY/API_KEY — 安装后不可修改(加密一致性)