feat: 一键安装脚本 + PHP完全移除 + config.php→config.json

新增文件:
- deploy/install.sh: 7步一键安装(自动检测宝塔/标准模式)
- deploy/upgrade.sh: git pull + pip install + restart + 健康检查
- deploy/uninstall.sh: 停服务+删配置, 可选--purge删部署目录
- docs/install-guide.md: 完整中文安装教程(宝塔+手动)

核心改动:
- install.py: 宝塔Supervisor路径自适应, init-db返回bt_panel标记,
  config.php→config.json, 移除PHP锁文件, 生成Fernet加密密钥
- install.html: Step 5根据btPanel显示不同Supervisor/Nginx/SSL指引
- crypto.py: 移除PHP AES-CBC兼容层, 纯Fernet加密
- 删除 web/install.php (1192行PHP, 功能已完全迁移到Python)

PHP清理:
- Nginx配置: config\.php→config\.json deny规则
- config.py/main.py/migrations.py: install.php注释→install wizard
- CLAUDE.md/AGENTS.md: config.php→config.json, 移除PHP引用
- firefox_server.py: login.php→login.html默认URL

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-05-25 08:23:37 +08:00
parent 400dcae781
commit a1276f38ad
16 changed files with 1388 additions and 1396 deletions
+4 -4
View File
@@ -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等)
+4 -4
View File
@@ -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等)
+526
View File
@@ -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 ""
+1 -1
View File
@@ -61,7 +61,7 @@ server {
location ~ /\. {
deny all;
}
location ~ /data/config\.php$ {
location ~ /data/config\.json$ {
deny all;
}
+1 -1
View File
@@ -89,7 +89,7 @@ server {
location ~ /\. {
deny all;
}
location ~ /data/config\.php$ {
location ~ /data/config\.json$ {
deny all;
}
+145
View File
@@ -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 ""
+130
View File
@@ -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 ""
+410
View File
@@ -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`
+1 -1
View File
@@ -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()
+58 -64
View File
@@ -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 = [
"<?php",
"// Nexus Configuration — Auto-generated by installer",
f"// {now}",
"",
f"define('API_BASE_URL', '{_escape_php_string(site_url)}');",
f"define('API_KEY', '{_escape_php_string(api_key)}');",
f"define('DB_HOST', '{_escape_php_string(req.db_host)}');",
f"define('DB_PORT', '{_escape_php_string(req.db_port)}');",
f"define('DB_NAME', '{_escape_php_string(req.db_name)}');",
f"define('DB_USER', '{_escape_php_string(req.db_user)}');",
f"define('DB_PASS', '{_escape_php_string(req.db_pass)}');",
"define('APP_NAME', 'Nexus');",
"define('APP_VERSION', '6.0.0');",
"define('SESSION_LIFETIME', 28800);",
f"define('DEPLOY_ROOT', '{_escape_php_string(install_dir)}');",
"",
f"date_default_timezone_set('{_escape_php_string(req.timezone)}');",
"",
]
config = {
"_generated": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC"),
"_generator": "Nexus 6.0 Installer",
"api_base_url": site_url,
"api_key": api_key,
"db_host": req.db_host,
"db_port": req.db_port,
"db_name": req.db_name,
"db_user": req.db_user,
"db_pass": req.db_pass,
"app_name": "Nexus",
"app_version": "6.0.0",
"session_lifetime": 28800,
"deploy_root": install_dir,
"timezone": req.timezone,
}
CONFIG_PHP.write_text("\n".join(lines))
logger.info(f"config.php written to {CONFIG_PHP}")
CONFIG_JSON.write_text(json.dumps(config, indent=2, ensure_ascii=False))
logger.info(f"config.json written to {CONFIG_JSON}")
def _detect_bt_panel() -> 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:
+2 -2
View File
@@ -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
+3 -32
View File
@@ -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
raise ValueError("Failed to decrypt credential") from e
+1 -1
View File
@@ -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 '公钥内容'",
]
+1 -1
View File
@@ -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.
"""
+101 -25
View File
@@ -76,7 +76,7 @@
<p class="text-slate-500 mb-6 leading-relaxed">
本向导将引导您完成系统安装配置。<br>
安装完成后将自动生成:<code class="bg-slate-100 px-1.5 py-0.5 rounded text-sm">.env</code> (Python后端) +
<code class="bg-slate-100 px-1.5 py-0.5 rounded text-sm">config.php</code> (PHP兼容) +
<code class="bg-slate-100 px-1.5 py-0.5 rounded text-sm">config.json</code> (共享配置) +
<code class="bg-slate-100 px-1.5 py-0.5 rounded text-sm">MySQL settings表</code> (共享配置)<br>
整个过程大约 3 分钟。
</p>
@@ -232,7 +232,7 @@
<div class="bg-amber-50 border border-amber-200 text-amber-800 px-4 py-3 rounded-lg mt-4 text-sm">
<b>⚠ 注意:</b>初始化将自动:① 连接数据库并建表 ② 生成 SECRET_KEY/API_KEY
③ 写入 .env + config.php + settings表 ④ 自动计算连接池大小
③ 写入 .env + config.json + settings表 ④ 自动计算连接池大小
</div>
<div class="flex gap-3 mt-5">
@@ -337,24 +337,52 @@
Supervisor 进程守护
</div>
<div x-show="!guardianAllOk" class="text-xs text-slate-500 space-y-1">
<p>安装: <code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">sudo apt install -y supervisor</code></p>
<p>配置文件复制到: <code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">/etc/supervisor/conf.d/nexus.conf</code></p>
<div class="code-block"><button class="copy-btn" @click="copyCode($event, 'supervisorConf')">复制配置</button><pre id="supervisorConf">[program:nexus]
command=<span x-text="installDir"></span>/venv/bin/uvicorn server.main:app --host 0.0.0.0 --port <span x-text="form.api_port"></span>
directory=<span x-text="installDir"></span>
user=root
autostart=true
autorestart=true
startretries=10
startsecs=3
stopwaitsecs=10
stopsignal=INT
environment=PATH="<span x-text="installDir"></span>/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</pre></div>
<p>启动: <code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">sudo supervisorctl reread && sudo supervisorctl update && sudo supervisorctl start nexus</code></p>
<!-- BT Panel mode -->
<template x-if="btPanel">
<div>
<p>宝塔面板 → 软件商店 → Supervisor管理器 → 添加守护进程</p>
<p>或手动写入配置: <code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">/www/server/panel/plugin/supervisor/profile/nexus.ini</code></p>
<div class="code-block"><button class="copy-btn" @click="copyCode($event, 'supervisorConf')">复制配置</button><pre id="supervisorConf">[program:nexus]
command=<span x-text="installDir"></span>/venv/bin/uvicorn server.main:app --host 0.0.0.0 --port <span x-text="form.api_port"></span>
directory=<span x-text="installDir"></span>
user=root
autostart=true
autorestart=true
startretries=10
startsecs=3
stopwaitsecs=10
stopsignal=INT
environment=PATH="<span x-text="installDir"></span>/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</pre></div>
<p>重载: <code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">supervisorctl reread && supervisorctl update && supervisorctl start nexus</code></p>
</div>
</template>
<!-- Standard mode -->
<template x-if="!btPanel">
<div>
<p>安装: <code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">sudo apt install -y supervisor</code></p>
<p>配置文件复制到: <code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">/etc/supervisor/conf.d/nexus.conf</code></p>
<div class="code-block"><button class="copy-btn" @click="copyCode($event, 'supervisorConf2')">复制配置</button><pre id="supervisorConf2">[program:nexus]
command=<span x-text="installDir"></span>/venv/bin/uvicorn server.main:app --host 0.0.0.0 --port <span x-text="form.api_port"></span>
directory=<span x-text="installDir"></span>
user=root
autostart=true
autorestart=true
startretries=10
startsecs=3
stopwaitsecs=10
stopsignal=INT
environment=PATH="<span x-text="installDir"></span>/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</pre></div>
<p>启动: <code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">sudo supervisorctl reread && sudo supervisorctl update && sudo supervisorctl start nexus</code></p>
</div>
</template>
</div>
<div x-show="guardianAllOk" class="text-xs text-green-600">✓ 已自动配置 — Supervisor 配置已写入,服务已重载</div>
</div>
@@ -380,8 +408,11 @@ stdout_logfile_maxbytes=50MB</pre></div>
Nginx 反向代理 (API + WebSocket)
</div>
<div class="text-xs text-slate-500">
<p class="mb-1">宝塔 → 网站 → 配置文件 → <code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">#PHP-INFO-END</code> 后面粘贴:</p>
<div class="code-block"><button class="copy-btn" @click="copyCode($event, 'nginxConf')">复制配置</button><pre id="nginxConf"> # Nexus Python API + WebSocket
<!-- BT Panel mode -->
<template x-if="btPanel">
<div>
<p class="mb-1">宝塔 → 网站 → 配置文件 → <code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">#PHP-INFO-END</code> 后面粘贴:</p>
<div class="code-block"><button class="copy-btn" @click="copyCode($event, 'nginxConf')">复制配置</button><pre id="nginxConf"> # Nexus Python API + WebSocket
location ^~ /api/ {
proxy_pass http://127.0.0.1:<span x-text="form.api_port"></span>;
proxy_set_header Host $host;
@@ -391,8 +422,6 @@ stdout_logfile_maxbytes=50MB</pre></div>
}
location ^~ /health {
proxy_pass http://127.0.0.1:<span x-text="form.api_port"></span>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location ^~ /ws/ {
proxy_pass http://127.0.0.1:<span x-text="form.api_port"></span>;
@@ -402,6 +431,43 @@ stdout_logfile_maxbytes=50MB</pre></div>
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}</pre></div>
</div>
</template>
<!-- Standard mode -->
<template x-if="!btPanel">
<div>
<p class="mb-1">将以下配置写入 <code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">/etc/nginx/sites-available/nexus</code> 并创建符号链接到 sites-enabled</p>
<div class="code-block"><button class="copy-btn" @click="copyCode($event, 'nginxConf2')">复制配置</button><pre id="nginxConf2">upstream nexus_api {
server 127.0.0.1:<span x-text="form.api_port"></span>;
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;
}
}</pre></div>
<p>启用: <code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">sudo ln -sf /etc/nginx/sites-available/nexus /etc/nginx/sites-enabled/ && sudo nginx -t && sudo systemctl reload nginx</code></p>
</div>
</template>
</div>
</div>
@@ -428,7 +494,15 @@ location / {
<span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-blue-500 text-white text-xs font-bold mr-2">5</span>
SSL证书 (强制HTTPS)
</div>
<div class="text-xs text-slate-500">宝塔 → 网站 → SSL → Let's Encrypt → 一键申请 → 开启强制HTTPS</div>
<div class="text-xs text-slate-500">
<template x-if="btPanel">
<div>宝塔 → 网站 → SSL → Let's Encrypt → 一键申请 → 开启强制HTTPS</div>
</template>
<template x-if="!btPanel">
<div>安装certbot: <code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">sudo apt install -y certbot python3-certbot-nginx</code><br>
申请证书: <code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">sudo certbot --nginx -d YOUR_DOMAIN</code></div>
</template>
</div>
</div>
<!-- 6. Health Monitor -->
@@ -491,6 +565,7 @@ function installWizard() {
envAllPass: false,
initResult: null,
installDir: '/opt/nexus',
btPanel: false,
form: {
db_host: 'localhost',
@@ -582,6 +657,7 @@ function installWizard() {
if (r.ok && d.success) {
this.success = '数据库初始化成功!已创建 ' + (d.tables_created || 14) + ' 张表。';
this.initResult = d;
this.btPanel = !!d.bt_panel;
this.step = 4;
} else {
this.error = d.detail || '初始化失败';
-1260
View File
File diff suppressed because it is too large Load Diff