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:
@@ -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等)
|
||||
|
||||
|
||||
@@ -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等)
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
@@ -61,7 +61,7 @@ server {
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
}
|
||||
location ~ /data/config\.php$ {
|
||||
location ~ /data/config\.json$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ server {
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
}
|
||||
location ~ /data/config\.php$ {
|
||||
location ~ /data/config\.json$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
@@ -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 ""
|
||||
@@ -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`
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user