a1276f38ad
新增文件: - 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>
718 lines
35 KiB
HTML
718 lines
35 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN" x-data="installWizard()" x-init="init()">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Nexus 6.0 安装向导</title>
|
||
<script src="/app/vendor/tailwindcss-browser.js"></script>
|
||
<script defer src="/app/vendor/alpinejs.min.js"></script>
|
||
<style type="text/tailwindcss">
|
||
@theme {
|
||
--color-brand: oklch(55% 0.2 250);
|
||
--color-brand-light: oklch(75% 0.15 250);
|
||
--color-brand-dark: oklch(35% 0.18 250);
|
||
}
|
||
</style>
|
||
<style>
|
||
.installer-card { max-width: 780px; }
|
||
.code-block {
|
||
background: #1e293b; color: #e2e8f0; border-radius: 6px; padding: 12px;
|
||
font-size: 12px; overflow-x: auto; white-space: pre-wrap; word-break: break-all;
|
||
position: relative; margin: 8px 0;
|
||
}
|
||
.copy-btn {
|
||
position: absolute; top: 6px; right: 6px;
|
||
background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.15);
|
||
color: #94a3b8; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px;
|
||
}
|
||
.copy-btn:hover { background: rgba(255,255,255,0.2); color: #fff; }
|
||
.copy-btn.copied { background: #22c55e; color: #fff; border-color: #22c55e; }
|
||
</style>
|
||
</head>
|
||
<body class="bg-slate-950 min-h-screen flex items-center justify-center p-4">
|
||
|
||
<div class="installer-card w-full bg-white rounded-2xl shadow-2xl overflow-hidden">
|
||
|
||
<!-- Header -->
|
||
<div class="bg-gradient-to-br from-blue-700 to-violet-600 text-white px-8 py-6 text-center">
|
||
<h1 class="text-2xl font-bold mb-1">Nexus 6.0 安装向导</h1>
|
||
<p class="text-violet-200 text-sm">服务器运维管理平台 — 心跳监控 + 智能告警 + 3层守护</p>
|
||
</div>
|
||
|
||
<!-- Step Indicator -->
|
||
<div class="flex items-center justify-center gap-1 py-4 px-8">
|
||
<template x-for="i in 5" :key="i">
|
||
<div class="flex items-center">
|
||
<div class="w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold transition-all"
|
||
:class="step > i ? 'bg-green-500 text-white' : step === i ? 'bg-blue-500 text-white' : 'border-2 border-slate-200 text-slate-400'"
|
||
x-text="step > i ? '✓' : i"></div>
|
||
<div x-show="i < 5" class="w-8 h-0.5 mx-0.5 transition-all"
|
||
:class="step > i ? 'bg-green-500' : 'bg-slate-200'"></div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- Body -->
|
||
<div class="px-8 pb-8">
|
||
|
||
<!-- Error / Success alerts -->
|
||
<div x-show="error" x-transition class="bg-red-50 border border-red-200 text-red-800 px-4 py-3 rounded-lg mb-4 text-sm" x-text="error"></div>
|
||
<div x-show="success" x-transition class="bg-green-50 border border-green-200 text-green-800 px-4 py-3 rounded-lg mb-4 text-sm" x-text="success"></div>
|
||
|
||
<!-- Already installed -->
|
||
<template x-if="installed">
|
||
<div class="text-center py-8">
|
||
<div class="text-5xl mb-4">⚠️</div>
|
||
<h2 class="text-xl font-bold text-red-600 mb-2">系统已安装</h2>
|
||
<p class="text-slate-500 mb-4">检测到 .env 配置文件,系统已完成安装。</p>
|
||
<a href="/app/login.html" class="inline-flex items-center gap-2 bg-blue-500 hover:bg-blue-600 text-white px-5 py-2.5 rounded-lg font-medium transition">前往登录 →</a>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ======== Step 1: Welcome ======== -->
|
||
<template x-if="step === 1 && !installed">
|
||
<div class="text-center py-4">
|
||
<h2 class="text-xl font-bold text-slate-800 mb-4">欢迎使用 Nexus 6.0</h2>
|
||
<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.json</code> (共享配置) +
|
||
<code class="bg-slate-100 px-1.5 py-0.5 rounded text-sm">MySQL settings表</code> (共享配置)<br>
|
||
整个过程大约 3 分钟。
|
||
</p>
|
||
<div class="bg-slate-50 rounded-lg p-5 text-left mb-6">
|
||
<h3 class="font-semibold text-slate-700 mb-3">📋 安装前准备</h3>
|
||
<ul class="text-slate-600 text-sm ml-5 list-disc space-y-1">
|
||
<li>Python 3.12+ (FastAPI + uvicorn)</li>
|
||
<li>MySQL 8.0+ (需提前创建数据库和用户)</li>
|
||
<li>Redis 6+ (心跳缓冲和实时数据)</li>
|
||
<li>Web 目录和根目录可写权限</li>
|
||
</ul>
|
||
</div>
|
||
<button @click="step = 2; checkEnv()" class="bg-blue-500 hover:bg-blue-600 text-white px-6 py-2.5 rounded-lg font-semibold transition">
|
||
开始安装 →
|
||
</button>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ======== Step 2: Environment Check ======== -->
|
||
<template x-if="step === 2 && !installed">
|
||
<div>
|
||
<h2 class="text-lg font-bold text-slate-800 mb-4">🔍 环境检测</h2>
|
||
|
||
<div x-show="envLoading" class="text-center py-8 text-slate-400">
|
||
<div class="animate-spin inline-block w-6 h-6 border-2 border-blue-500 border-t-transparent rounded-full mb-2"></div>
|
||
<p>正在检测环境...</p>
|
||
</div>
|
||
|
||
<div x-show="!envLoading && envChecks.length" class="bg-slate-50 rounded-lg overflow-hidden mb-4">
|
||
<template x-for="c in envChecks" :key="c.name">
|
||
<div class="flex items-center justify-between px-4 py-3 border-b border-slate-100 last:border-0">
|
||
<div>
|
||
<span class="font-medium text-slate-700" x-text="c.name"></span>
|
||
<span class="text-slate-400 text-xs ml-2" x-text="'需要: ' + c.required"></span>
|
||
</div>
|
||
<span class="font-semibold text-sm" :class="c.pass ? 'text-green-500' : 'text-red-500'"
|
||
x-text="c.pass ? '✓ 通过' : '✗ ' + c.current"></span>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
|
||
<div x-show="!envLoading && envAllPass" class="bg-green-50 border border-green-200 text-green-800 px-4 py-3 rounded-lg mb-4 text-sm">
|
||
✓ 所有环境检测通过,可以继续安装。
|
||
</div>
|
||
<div x-show="!envLoading && !envAllPass && envChecks.length" class="bg-red-50 border border-red-200 text-red-800 px-4 py-3 rounded-lg mb-4 text-sm">
|
||
部分环境检测未通过,请先解决上述问题。
|
||
</div>
|
||
|
||
<div x-show="!envLoading" class="flex gap-3 mt-4">
|
||
<button @click="step = 3" class="bg-blue-500 hover:bg-blue-600 text-white px-5 py-2.5 rounded-lg font-semibold transition">
|
||
下一步:数据库配置 →
|
||
</button>
|
||
<button @click="checkEnv()" class="bg-slate-200 hover:bg-slate-300 text-slate-700 px-5 py-2.5 rounded-lg font-semibold transition">
|
||
重新检测
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ======== Step 3: DB + Redis + API Config ======== -->
|
||
<template x-if="step === 3 && !installed">
|
||
<div>
|
||
<h2 class="text-lg font-bold text-slate-800 mb-4">⚙ 数据库 + Redis + API 配置</h2>
|
||
|
||
<div class="bg-blue-50 border border-blue-200 text-blue-800 px-4 py-3 rounded-lg mb-4 text-sm">
|
||
<b>说明:</b>请提前创建好数据库和用户,安装向导将自动建表并写入配置。
|
||
SECRET_KEY 和 API_KEY 将自动生成,安装后不可修改(加密一致性)。
|
||
</div>
|
||
|
||
<form @submit.prevent="initDb()">
|
||
<!-- MySQL -->
|
||
<div class="border-t-2 border-slate-100 pt-4 mt-4">
|
||
<h3 class="font-bold text-slate-700 mb-3">🗄 MySQL 数据库</h3>
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<label class="block font-semibold text-sm text-slate-700 mb-1">主机</label>
|
||
<input x-model="form.db_host" class="w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:border-blue-500 focus:outline-none transition">
|
||
</div>
|
||
<div>
|
||
<label class="block font-semibold text-sm text-slate-700 mb-1">端口</label>
|
||
<input x-model="form.db_port" class="w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:border-blue-500 focus:outline-none transition">
|
||
</div>
|
||
</div>
|
||
<div class="grid grid-cols-2 gap-4 mt-3">
|
||
<div>
|
||
<label class="block font-semibold text-sm text-slate-700 mb-1">数据库名</label>
|
||
<input x-model="form.db_name" class="w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:border-blue-500 focus:outline-none transition">
|
||
</div>
|
||
<div>
|
||
<label class="block font-semibold text-sm text-slate-700 mb-1">用户名</label>
|
||
<input x-model="form.db_user" class="w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:border-blue-500 focus:outline-none transition">
|
||
</div>
|
||
</div>
|
||
<div class="mt-3">
|
||
<label class="block font-semibold text-sm text-slate-700 mb-1">密码</label>
|
||
<input x-model="form.db_pass" type="password" required placeholder="数据库密码"
|
||
class="w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:border-blue-500 focus:outline-none transition">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Redis -->
|
||
<div class="border-t-2 border-slate-100 pt-4 mt-4">
|
||
<h3 class="font-bold text-slate-700 mb-3">⚡ Redis 配置</h3>
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<label class="block font-semibold text-sm text-slate-700 mb-1">Redis 主机</label>
|
||
<input x-model="form.redis_host" class="w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:border-blue-500 focus:outline-none transition">
|
||
</div>
|
||
<div>
|
||
<label class="block font-semibold text-sm text-slate-700 mb-1">Redis 端口</label>
|
||
<input x-model="form.redis_port" class="w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:border-blue-500 focus:outline-none transition">
|
||
</div>
|
||
</div>
|
||
<div class="grid grid-cols-2 gap-4 mt-3">
|
||
<div>
|
||
<label class="block font-semibold text-sm text-slate-700 mb-1">Redis 数据库号</label>
|
||
<input x-model="form.redis_db" class="w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:border-blue-500 focus:outline-none transition">
|
||
</div>
|
||
<div>
|
||
<label class="block font-semibold text-sm text-slate-700 mb-1">Redis 密码(可选)</label>
|
||
<input x-model="form.redis_pass" type="password" placeholder="无密码留空"
|
||
class="w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:border-blue-500 focus:outline-none transition">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- API + Site + Timezone -->
|
||
<div class="border-t-2 border-slate-100 pt-4 mt-4">
|
||
<h3 class="font-bold text-slate-700 mb-3">🌐 网站地址 + API 服务 + 时区</h3>
|
||
<div class="mt-3">
|
||
<label class="block font-semibold text-sm text-slate-700 mb-1">网站地址</label>
|
||
<p class="text-xs text-slate-400 mb-1">自动检测 — 用于 Agent 上报和前端 API 调用,可手动修改</p>
|
||
<input x-model="form.site_url" class="w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:border-blue-500 focus:outline-none transition">
|
||
</div>
|
||
<div class="grid grid-cols-2 gap-4 mt-3">
|
||
<div>
|
||
<label class="block font-semibold text-sm text-slate-700 mb-1">Python API 端口</label>
|
||
<p class="text-xs text-slate-400 mb-1">uvicorn 监听端口</p>
|
||
<input x-model="form.api_port" class="w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:border-blue-500 focus:outline-none transition">
|
||
</div>
|
||
<div>
|
||
<label class="block font-semibold text-sm text-slate-700 mb-1">时区</label>
|
||
<select x-model="form.timezone" class="w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:border-blue-500 focus:outline-none transition">
|
||
<option value="Asia/Shanghai">Asia/Shanghai (中国标准时间)</option>
|
||
<option value="Asia/Tokyo">Asia/Tokyo</option>
|
||
<option value="America/New_York">America/New_York</option>
|
||
<option value="Europe/London">Europe/London</option>
|
||
<option value="UTC">UTC</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<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.json + settings表 ④ 自动计算连接池大小
|
||
</div>
|
||
|
||
<div class="flex gap-3 mt-5">
|
||
<button type="submit" :disabled="loading" class="bg-blue-500 hover:bg-blue-600 disabled:bg-blue-300 text-white px-5 py-2.5 rounded-lg font-semibold transition flex items-center gap-2">
|
||
<span x-show="loading" class="animate-spin inline-block w-4 h-4 border-2 border-white border-t-transparent rounded-full"></span>
|
||
<span x-text="loading ? '初始化中...' : '初始化数据库 →'"></span>
|
||
</button>
|
||
<button type="button" @click="step = 2" class="bg-slate-200 hover:bg-slate-300 text-slate-700 px-5 py-2.5 rounded-lg font-semibold transition">← 上一步</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ======== Step 4: Admin + Brand ======== -->
|
||
<template x-if="step === 4 && !installed">
|
||
<div>
|
||
<h2 class="text-lg font-bold text-slate-800 mb-4">👤 管理员账号 + 系统名称</h2>
|
||
|
||
<!-- Step 3 summary -->
|
||
<div class="bg-slate-50 rounded-lg p-4 mb-4 text-sm">
|
||
<div class="font-semibold text-green-600 mb-2">✓ 步骤 3 配置完成</div>
|
||
<div class="grid grid-cols-2 gap-1">
|
||
<span>数据库: <code class="bg-slate-200 px-1 rounded" x-text="form.db_name"></code></span>
|
||
<span>用户: <code class="bg-slate-200 px-1 rounded" x-text="form.db_user"></code></span>
|
||
<span>连接池: <code class="bg-slate-200 px-1 rounded" x-text="initResult?.pool_size || '—'"></code></span>
|
||
<span>溢出池: <code class="bg-slate-200 px-1 rounded" x-text="initResult?.max_overflow || '—'"></code></span>
|
||
</div>
|
||
</div>
|
||
|
||
<form @submit.prevent="createAdmin()">
|
||
<!-- Brand -->
|
||
<div class="border-t-2 border-slate-100 pt-4">
|
||
<h3 class="font-bold text-slate-700 mb-3">系统品牌</h3>
|
||
<div class="mt-3">
|
||
<label class="block font-semibold text-sm text-slate-700 mb-1">系统名称</label>
|
||
<p class="text-xs text-slate-400 mb-1">显示在浏览器标题和后台标题栏</p>
|
||
<input x-model="adminForm.system_name" class="w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:border-blue-500 focus:outline-none transition">
|
||
</div>
|
||
<div class="mt-3">
|
||
<label class="block font-semibold text-sm text-slate-700 mb-1">系统标题</label>
|
||
<input x-model="adminForm.system_title" class="w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:border-blue-500 focus:outline-none transition">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Admin account -->
|
||
<div class="border-t-2 border-slate-100 pt-4 mt-4">
|
||
<h3 class="font-bold text-slate-700 mb-3">管理员账号</h3>
|
||
<div class="mt-3">
|
||
<label class="block font-semibold text-sm text-slate-700 mb-1">管理员用户名</label>
|
||
<input x-model="adminForm.admin_username" required class="w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:border-blue-500 focus:outline-none transition">
|
||
</div>
|
||
<div class="mt-3">
|
||
<label class="block font-semibold text-sm text-slate-700 mb-1">管理员密码</label>
|
||
<p class="text-xs text-slate-400 mb-1">至少6位字符</p>
|
||
<input x-model="adminForm.admin_password" type="password" required minlength="6" placeholder="请输入安全密码"
|
||
class="w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:border-blue-500 focus:outline-none transition">
|
||
</div>
|
||
<div class="mt-3">
|
||
<label class="block font-semibold text-sm text-slate-700 mb-1">邮箱(可选)</label>
|
||
<input x-model="adminForm.admin_email" type="email" placeholder="admin@example.com"
|
||
class="w-full px-3 py-2 border-2 border-slate-200 rounded-lg text-sm focus:border-blue-500 focus:outline-none transition">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex gap-3 mt-5">
|
||
<button type="submit" :disabled="loading" class="bg-blue-500 hover:bg-blue-600 disabled:bg-blue-300 text-white px-5 py-2.5 rounded-lg font-semibold transition flex items-center gap-2">
|
||
<span x-show="loading" class="animate-spin inline-block w-4 h-4 border-2 border-white border-t-transparent rounded-full"></span>
|
||
<span x-text="loading ? '创建中...' : '创建账号 →'"></span>
|
||
</button>
|
||
<button type="button" @click="step = 3" class="bg-slate-200 hover:bg-slate-300 text-slate-700 px-5 py-2.5 rounded-lg font-semibold transition">← 上一步</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ======== Step 5: Complete ======== -->
|
||
<template x-if="step === 5 && !installed">
|
||
<div class="text-center">
|
||
<div class="w-16 h-16 rounded-full bg-green-100 text-green-500 text-3xl flex items-center justify-center mx-auto mb-4">✓</div>
|
||
<h2 class="text-xl font-bold text-slate-800 mb-2">安装完成!</h2>
|
||
<p class="text-slate-500 mb-1">Nexus 6.0 已成功安装。</p>
|
||
<p class="text-slate-400 text-xs">安装向导已锁定 — 防止重复安装(如需重装请删除 .env 文件后重启服务)</p>
|
||
|
||
<!-- Guardian results -->
|
||
<div x-show="initResult?.guardian_results?.length" class="text-left mt-6 mb-4 rounded-lg p-4"
|
||
:class="guardianAllOk ? 'bg-green-50 border-l-4 border-green-500' : 'bg-amber-50 border-l-4 border-amber-500'">
|
||
<div class="font-semibold text-sm mb-2" :class="guardianAllOk ? 'text-green-700' : 'text-amber-700'"
|
||
x-text="guardianAllOk ? '✓ 进程守护已自动配置' : '⚠ 进程守护部分配置失败'"></div>
|
||
<template x-for="r in (initResult?.guardian_results || [])" :key="r">
|
||
<div class="text-xs leading-relaxed" x-text="r"></div>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- Post-install checklist -->
|
||
<div class="text-left mt-6 space-y-3">
|
||
<h3 class="text-sm font-bold text-slate-700 text-center mb-3">━━━ 后续配置清单(必须完成)━━━</h3>
|
||
|
||
<!-- 1. Supervisor -->
|
||
<div class="bg-slate-50 border border-slate-200 rounded-lg p-4">
|
||
<div class="font-bold text-sm text-slate-800 mb-2">
|
||
<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">1</span>
|
||
Supervisor 进程守护
|
||
</div>
|
||
<div x-show="!guardianAllOk" class="text-xs text-slate-500 space-y-1">
|
||
<!-- 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>
|
||
|
||
<!-- 2. Python venv -->
|
||
<div class="bg-slate-50 border border-slate-200 rounded-lg p-4">
|
||
<div class="font-bold text-sm text-slate-800 mb-2">
|
||
<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">2</span>
|
||
Python 虚拟环境 + 依赖
|
||
</div>
|
||
<div 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">cd <span x-text="installDir"></span></code></p>
|
||
<p><code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">python3.12 -m venv venv</code></p>
|
||
<p><code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">source venv/bin/activate</code></p>
|
||
<p><code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">pip install -r requirements.txt</code></p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 3. Nginx -->
|
||
<div class="bg-slate-50 border border-slate-200 rounded-lg p-4">
|
||
<div class="font-bold text-sm text-slate-800 mb-2">
|
||
<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">3</span>
|
||
Nginx 反向代理 (API + WebSocket)
|
||
</div>
|
||
<div class="text-xs text-slate-500">
|
||
<!-- 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;
|
||
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:<span x-text="form.api_port"></span>;
|
||
}
|
||
location ^~ /ws/ {
|
||
proxy_pass http://127.0.0.1:<span x-text="form.api_port"></span>;
|
||
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;
|
||
}</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>
|
||
|
||
<!-- 4. Nginx Rewrite -->
|
||
<div class="bg-slate-50 border border-slate-200 rounded-lg p-4">
|
||
<div class="font-bold text-sm text-slate-800 mb-2">
|
||
<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">4</span>
|
||
Nginx 伪静态
|
||
</div>
|
||
<div class="text-xs text-slate-500">
|
||
<p class="mb-1">宝塔 → 网站 → 伪静态 → 粘贴:</p>
|
||
<div class="code-block"><button class="copy-btn" @click="copyCode($event, 'nginxRewrite')">复制</button><pre id="nginxRewrite">location /app/ {
|
||
try_files $uri $uri/ /app/index.html;
|
||
}
|
||
location / {
|
||
try_files $uri $uri/ =404;
|
||
}</pre></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 5. SSL -->
|
||
<div class="bg-slate-50 border border-slate-200 rounded-lg p-4">
|
||
<div class="font-bold text-sm text-slate-800 mb-2">
|
||
<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">
|
||
<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 -->
|
||
<div class="bg-slate-50 border border-slate-200 rounded-lg p-4">
|
||
<div class="font-bold text-sm text-slate-800 mb-2">
|
||
<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">6</span>
|
||
Shell 健康检查 (外部守护 + Telegram告警)
|
||
</div>
|
||
<div x-show="!guardianAllOk" class="text-xs text-slate-500">
|
||
<p>配置 crontab:</p>
|
||
<div class="code-block"><button class="copy-btn" @click="copyCode($event, 'crontab')">复制</button><pre id="crontab">* * * * * <span x-text="installDir"></span>/deploy/health_monitor.sh</pre></div>
|
||
</div>
|
||
<div x-show="guardianAllOk" class="text-xs text-green-600">✓ 已自动配置 — health_monitor.sh INSTALL_DIR 已更新,Crontab 已添加</div>
|
||
<div class="text-xs text-slate-500 mt-2">
|
||
<b>3层守护机制:</b><br>
|
||
Layer 1: Supervisor — Python崩溃自动重启<br>
|
||
Layer 2: Python self_monitor — 每30s检查Redis/MySQL/WebSocket<br>
|
||
Layer 3: Shell脚本 — 每分钟HTTP检查,连续3次失败→Supervisor重启+Telegram告警
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 7. Security -->
|
||
<div class="bg-slate-50 border border-slate-200 rounded-lg p-4">
|
||
<div class="font-bold text-sm text-slate-800 mb-2">
|
||
<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">7</span>
|
||
安全收尾
|
||
</div>
|
||
<div 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">.env</code> 文件可重新安装</p>
|
||
<p>✓ <code class="bg-slate-800 text-slate-200 px-1.5 py-0.5 rounded">.env</code> 包含 SECRET_KEY/API_KEY — <b>安装后不可修改</b>(加密一致性)</p>
|
||
<p>✓ 防火墙限制端口 <span x-text="form.api_port"></span> (仅允许本机和子服务器IP)</p>
|
||
<p>✓ 修改管理员默认密码</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex justify-center mt-6">
|
||
<a href="/app/index.html" class="bg-green-500 hover:bg-green-600 text-white px-8 py-3 rounded-lg font-semibold text-lg transition">
|
||
进入管理面板 →
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
function installWizard() {
|
||
const API = window.location.origin;
|
||
|
||
return {
|
||
step: 1,
|
||
loading: false,
|
||
installed: false,
|
||
error: '',
|
||
success: '',
|
||
envLoading: false,
|
||
envChecks: [],
|
||
envAllPass: false,
|
||
initResult: null,
|
||
installDir: '/opt/nexus',
|
||
btPanel: false,
|
||
|
||
form: {
|
||
db_host: 'localhost',
|
||
db_port: '3306',
|
||
db_name: 'Nexus',
|
||
db_user: 'Nexus',
|
||
db_pass: '',
|
||
redis_host: '127.0.0.1',
|
||
redis_port: '6379',
|
||
redis_db: '0',
|
||
redis_pass: '',
|
||
api_port: '8600',
|
||
timezone: 'Asia/Shanghai',
|
||
site_url: '',
|
||
},
|
||
|
||
adminForm: {
|
||
admin_username: 'admin',
|
||
admin_password: '',
|
||
admin_email: '',
|
||
system_name: 'Nexus',
|
||
system_title: 'Nexus — 服务器运维管理平台',
|
||
},
|
||
|
||
get guardianAllOk() {
|
||
const results = this.initResult?.guardian_results || [];
|
||
if (!results.length) return false;
|
||
return results.every(r => !r.includes('✗') && !r.includes('⚠'));
|
||
},
|
||
|
||
async init() {
|
||
// Auto-detect site URL
|
||
const proto = location.protocol === 'https:' ? 'https' : 'http';
|
||
this.form.site_url = proto + '://' + location.host;
|
||
|
||
// Check install status
|
||
try {
|
||
const r = await fetch(API + '/api/install/status');
|
||
if (r.ok) {
|
||
const d = await r.json();
|
||
this.installed = d.installed;
|
||
if (d.installed) return;
|
||
}
|
||
} catch(e) {}
|
||
|
||
// Try to restore state
|
||
try {
|
||
const r = await fetch(API + '/api/install/state');
|
||
if (r.ok) {
|
||
const d = await r.json();
|
||
if (d.step > 1) this.step = d.step;
|
||
if (d.db_host) Object.assign(this.form, {
|
||
db_host: d.db_host, db_port: d.db_port, db_name: d.db_name,
|
||
db_user: d.db_user, db_pass: d.db_pass, api_port: d.api_port || '8600',
|
||
});
|
||
if (d.install_dir) this.installDir = d.install_dir;
|
||
if (d.pool_size) this.initResult = { pool_size: d.pool_size, max_overflow: d.max_overflow, guardian_results: d.guardian_results || [] };
|
||
}
|
||
} catch(e) {}
|
||
},
|
||
|
||
async checkEnv() {
|
||
this.envLoading = true;
|
||
this.error = '';
|
||
try {
|
||
const r = await fetch(API + '/api/install/env-check');
|
||
if (r.ok) {
|
||
const d = await r.json();
|
||
this.envChecks = d.checks;
|
||
this.envAllPass = d.all_pass;
|
||
}
|
||
} catch(e) {
|
||
this.error = '环境检测请求失败: ' + e.message;
|
||
}
|
||
this.envLoading = false;
|
||
},
|
||
|
||
async initDb() {
|
||
this.loading = true;
|
||
this.error = '';
|
||
this.success = '';
|
||
try {
|
||
const r = await fetch(API + '/api/install/init-db', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify(this.form),
|
||
});
|
||
const d = await r.json();
|
||
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 || '初始化失败';
|
||
}
|
||
} catch(e) {
|
||
this.error = '请求失败: ' + e.message;
|
||
}
|
||
this.loading = false;
|
||
},
|
||
|
||
async createAdmin() {
|
||
this.loading = true;
|
||
this.error = '';
|
||
try {
|
||
const body = {
|
||
...this.adminForm,
|
||
db_host: this.form.db_host,
|
||
db_port: this.form.db_port,
|
||
db_name: this.form.db_name,
|
||
db_user: this.form.db_user,
|
||
db_pass: this.form.db_pass,
|
||
};
|
||
const r = await fetch(API + '/api/install/create-admin', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify(body),
|
||
});
|
||
const d = await r.json();
|
||
if (r.ok && d.success) {
|
||
// Lock the installer
|
||
await fetch(API + '/api/install/lock', { method: 'POST' });
|
||
this.step = 5;
|
||
} else {
|
||
this.error = d.detail || '创建管理员失败';
|
||
}
|
||
} catch(e) {
|
||
this.error = '请求失败: ' + e.message;
|
||
}
|
||
this.loading = false;
|
||
},
|
||
|
||
copyCode(event, id) {
|
||
const el = document.getElementById(id);
|
||
if (!el) return;
|
||
const text = el.textContent;
|
||
const btn = event.target;
|
||
navigator.clipboard.writeText(text).then(() => {
|
||
btn.textContent = '已复制 ✓';
|
||
btn.classList.add('copied');
|
||
setTimeout(() => { btn.textContent = '复制'; btn.classList.remove('copied'); }, 2000);
|
||
});
|
||
},
|
||
};
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|