Files
Nexus/web/app/install.html
T

742 lines
34 KiB
HTML
Raw Normal View History

<!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 src="/app/vendor/theme-init.js"></script>
2026-05-31 02:55:45 +08:00
<link rel="stylesheet" href="/app/vendor/theme.css">
<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-[var(--bg-page)] 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-[var(--text-secondary)]'"
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-[var(--text-muted)] 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-[var(--text-muted)] 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-[var(--text-dim)] 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-[var(--text-secondary)]">
<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-[var(--text-secondary)] 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"
:disabled="!envAllPass"
:class="envAllPass ? 'bg-blue-500 hover:bg-blue-600' : 'bg-slate-300 cursor-not-allowed'"
class="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>
<p class="text-xs text-slate-500 mb-3">请先在宿主机或 1Panel 安装 MySQL 并创建库与用户。Docker 部署填主机 <code class="bg-slate-100 px-1 rounded">host.docker.internal</code>,端口 3306。</p>
<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>
<p class="text-xs text-slate-500 mb-3">请先在宿主机或 1Panel 安装 Redis。Docker 部署填主机 <code class="bg-slate-100 px-1 rounded">host.docker.internal</code>,端口 6379。</p>
<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-[var(--text-secondary)] 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-[var(--text-secondary)] 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-[var(--text-secondary)] 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-[var(--text-secondary)] 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-[var(--text-muted)] mb-1">Nexus 6.0 已成功安装。</p>
<p class="text-[var(--text-secondary)] 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-[var(--text-muted)] space-y-1">
<!-- BT Panel mode -->
<template x-if="btPanel">
<div>
<p>宝塔面板 → 软件商店 → Supervisor管理器 → 添加守护进程</p>
<p>或手动写入配置: <code class="bg-[var(--bg-surface)] 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-[var(--bg-surface)] 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-[var(--bg-surface)] text-slate-200 px-1.5 py-0.5 rounded">sudo apt install -y supervisor</code></p>
<p>配置文件复制到: <code class="bg-[var(--bg-surface)] 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-[var(--bg-surface)] 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-[var(--text-muted)] space-y-1">
<p><code class="bg-[var(--bg-surface)] text-slate-200 px-1.5 py-0.5 rounded">cd <span x-text="installDir"></span></code></p>
<p><code class="bg-[var(--bg-surface)] text-slate-200 px-1.5 py-0.5 rounded">python3.12 -m venv venv</code></p>
<p><code class="bg-[var(--bg-surface)] text-slate-200 px-1.5 py-0.5 rounded">source venv/bin/activate</code></p>
<p><code class="bg-[var(--bg-surface)] 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-[var(--text-muted)]">
<!-- BT Panel mode -->
<template x-if="btPanel">
<div>
<p class="mb-1">宝塔 → 网站 → 配置文件 → <code class="bg-[var(--bg-surface)] 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-[var(--bg-surface)] 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-[var(--bg-surface)] 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-[var(--text-muted)]">
<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-[var(--text-muted)]">
<template x-if="btPanel">
<div>宝塔 → 网站 → SSL → Let's Encrypt → 一键申请 → 开启强制HTTPS</div>
</template>
<template x-if="!btPanel">
<div>安装certbot: <code class="bg-[var(--bg-surface)] text-slate-200 px-1.5 py-0.5 rounded">sudo apt install -y certbot python3-certbot-nginx</code><br>
申请证书: <code class="bg-[var(--bg-surface)] 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-[var(--text-muted)]">
<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-[var(--text-muted)] 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-[var(--text-muted)] space-y-1">
<p>✓ 安装向导已锁定 — 删除 <code class="bg-[var(--bg-surface)] text-slate-200 px-1.5 py-0.5 rounded">.env</code> 文件可重新安装</p>
<p><code class="bg-[var(--bg-surface)] 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,
installToken: '',
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;
if (d.docker_defaults) {
Object.assign(this.form, d.docker_defaults);
}
}
} 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.installToken = d.install_token || '';
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 = {
install_token: this.installToken,
...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) {
if (d.locked) {
this.step = 5;
this.success = '安装完成!请使用刚创建的管理员账户登录。';
return;
}
const lr = await fetch(API + '/api/install/lock', { method: 'POST' });
let ld = {};
try { ld = await lr.json(); } catch (_) {}
if (lr.ok || ld.already_locked) {
this.step = 5;
this.success = '安装完成!请使用刚创建的管理员账户登录。';
} else {
this.error = (ld.detail || '管理员已创建,但锁定安装向导失败,请再次点击完成或刷新后重试。');
}
} 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>