geshu-nginx-management 是一个基于 Next.js 16、Better Auth、Prisma + SQLite 和 Ant Design 的 Nginx 管理后台。
当前项目支持:
- 用户登录、初始化管理员、用户管理
- 操作日志、异常日志
- 反向代理与 TCP / UDP 端口转发
- 自签证书生成、Nginx 配置测试与重载
- 运行时系统设置
- SQLite 自动备份与可选的 S3 / 兼容对象存储上传
项目将启动、构建、认证核心配置、格数账号平台与 geshu-agent OAuth Client 配置,以及首次登录前就必须可用的默认邮箱域名和短信通道配置放在 .env 或部署平台环境变量中。验证码日志打印、限流、用户资料开关、自动备份和 Nginx 运行控制等配置请登录后台后在“系统设置”页面维护。
说明:
- 以
NEXT_PUBLIC_开头的变量会暴露给浏览器,仅放置浏览器也需要读取的非敏感配置 NODE_ENV由运行命令和框架控制,一般不需要手动设置- 开发服务读取进程环境变量
PORT,未设置时使用 Next.js 默认端口3000 - 开发环境的
BETTER_AUTH_URL和NEXT_PUBLIC_BETTER_AUTH_URL自动使用http://localhost:<PORT>,无需手动同步端口 - 开发环境未设置
GESHU_OAUTH_ISSUER时,默认使用http://localhost:<PORT>/api/auth BETTER_AUTH_SECRET在生产环境是强制项,未配置会导致服务启动失败;开发环境会使用仅本地可用的兜底值- 格数账号平台与 geshu-agent 登录由环境变量配置,因为 Better Auth 的
genericOAuthProvider 配置在当前版本中是启动时静态配置 NEXT_PUBLIC_TIME_ZONE用于页面展示和自动备份窗口,留空时默认Asia/Shanghai;该变量会进入浏览器构建产物,修改后需要重新构建镜像或前端资源,避免服务端 HTML 和浏览器 hydration 使用不同配置- 默认邮箱域名、短信通道和密钥不会进入系统设置,避免首次进入系统时因无法登录而无法配置基础能力
- 未填写任何短信发送配置时,发送验证码不会报错或调用短信通道,验证码默认打印到服务端系统日志
- 系统设置中的“短信设置”控制是否在服务端系统日志中打印验证码,默认开启
- 系统设置中的配置不读取同名环境变量,首次初始化时只写入代码默认值
| 变量名 | 必填 | 说明 | 示例 / 默认值 |
|---|---|---|---|
COOKIE_PREFIX |
是 | 登录相关 Cookie 前缀 | geshu |
BETTER_AUTH_SECRET |
是 | Better Auth 签名密钥 | your_better_auth_secret |
PORT |
否 | 开发服务端口 | 3000 |
BETTER_AUTH_URL |
按需 | 生产环境服务端 Better Auth 基础地址 | https://example.com |
NEXT_PUBLIC_BETTER_AUTH_URL |
按需 | 生产环境客户端 Better Auth 基础地址 | https://example.com |
GESHU_OAUTH_LOGIN_ENABLED |
否 | 是否启用格数账号平台登录 | 0 |
GESHU_OAUTH_ISSUER |
按需 | 格数账号平台地址或 OpenID Configuration | https://auth.example.com |
GESHU_OAUTH_CLIENT_ID |
按需 | 格数账号平台 OAuth Client ID | your_client_id |
GESHU_OAUTH_CLIENT_SECRET |
按需 | 格数账号平台 OAuth Client Secret | your_client_secret |
GESHU_OAUTH_ALLOW_CREATE_USER |
否 | 手机号未匹配本地用户时是否创建普通用户 | 0 |
GESHU_AGENT_OAUTH_LOGIN_ENABLED |
否 | 是否启用 geshu-agent 登录与绑定入口 | 0 |
GESHU_AGENT_OAUTH_ISSUER |
按需 | geshu-agent issuer,必须以 /api/auth 结尾 |
https://agent.example.com/api/auth |
GESHU_AGENT_OAUTH_CLIENT_ID |
按需 | geshu-agent OAuth Client ID | your_agent_client_id |
GESHU_AGENT_OAUTH_CLIENT_SECRET |
按需 | geshu-agent OAuth Client Secret | your_agent_client_secret |
NEXT_OUTPUT |
否 | Next 构建输出模式 | standalone / export |
NEXT_TELEMETRY_DISABLED |
否 | 是否关闭 Next 遥测上报 | 1 |
NEXT_PUBLIC_TIME_ZONE |
否 | 应用时间时区,留空默认上海 | Asia/Shanghai |
REDIS_URL |
按需 | Redis 地址(仅接入 Redis 限流存储时用) | redis://127.0.0.1:6379 |
TRUSTED_CLIENT_IP_HEADER |
按需 | 指定可信反向代理覆盖写入的客户端 IP 头 | x-real-ip |
DEFAULT_EMAIL_DOMAIN |
否 | 手机号注册时生成临时邮箱所使用的域名 | example.com |
IS_INTRANET |
否 | 是否使用内网短信通道 | 0 |
QJP_SMS_URL |
按需 | 内网短信服务地址 | http://sms.example.com |
ALIYUN_ACCESS_KEY_ID |
按需 | 阿里云短信 AccessKey ID | your_access_key_id |
ALIYUN_ACCESS_KEY_SECRET |
按需 | 阿里云短信 AccessKey Secret | your_access_key_secret |
COOKIE_PREFIX="geshu"
BETTER_AUTH_SECRET="your_better_auth_secret"
# Better Auth URL(按需)
BETTER_AUTH_URL=""
# 客户端可选(未配置时使用当前域名)
NEXT_PUBLIC_BETTER_AUTH_URL=""
# 格数账号平台登录
GESHU_OAUTH_LOGIN_ENABLED="0"
GESHU_OAUTH_ISSUER="https://auth.example.com"
GESHU_OAUTH_CLIENT_ID="your_client_id"
GESHU_OAUTH_CLIENT_SECRET="your_client_secret"
GESHU_OAUTH_ALLOW_CREATE_USER="0"
# geshu-agent 登录与显式绑定
GESHU_AGENT_OAUTH_LOGIN_ENABLED="0"
GESHU_AGENT_OAUTH_ISSUER="https://agent.example.com/api/auth"
GESHU_AGENT_OAUTH_CLIENT_ID="your_agent_client_id"
GESHU_AGENT_OAUTH_CLIENT_SECRET="your_agent_client_secret"
# 构建与运行
NEXT_OUTPUT="standalone"
NEXT_TELEMETRY_DISABLED="1"
NEXT_PUBLIC_TIME_ZONE=""
# 可选:仅在你启用 Redis 限流存储时使用
REDIS_URL="redis://127.0.0.1:6379"
# 可选:代理必须移除客户端同名请求头后覆盖写入;不配置时不信任转发头
TRUSTED_CLIENT_IP_HEADER=""
# 临时邮箱域名
DEFAULT_EMAIL_DOMAIN="example.com"
# 短信配置
# 全部留空时仅在服务端日志打印验证码,不发送短信。
IS_INTRANET="0"
QJP_SMS_URL=""
ALIYUN_ACCESS_KEY_ID=""
ALIYUN_ACCESS_KEY_SECRET=""服务端 auth 的 baseURL 解析顺序:
- 开发环境固定使用
http://localhost:<PORT>,PORT未设置时为3000 - 生产环境使用
BETTER_AUTH_URL
客户端 authClient 的 baseURL 解析顺序:
- 浏览器当前域名
window.location.origin - 服务端渲染处于开发环境时使用
http://localhost:<PORT>,PORT未设置时为3000 - 服务端渲染处于生产环境时使用
NEXT_PUBLIC_BETTER_AUTH_URL
PowerShell 中可这样指定开发端口:
$env:PORT = "3100"
pnpm dev本项目可以作为 OAuth Client 接入 geshu-oauth 账号平台。账号平台后台新增 OAuth 应用时,推荐配置:
- 应用主页:
BETTER_AUTH_URL - 回调地址:
{BETTER_AUTH_URL}/api/auth/oauth2/callback/geshu-oauth - 授权范围:
openid profile phone - 授权流程:
authorization_code - Token Endpoint 认证方式:
client_secret_basic - PKCE:开启
GESHU_OAUTH_LOGIN_ENABLED 默认关闭。设置为 1,并同时配置 GESHU_OAUTH_ISSUER、GESHU_OAUTH_CLIENT_ID 和 GESHU_OAUTH_CLIENT_SECRET 后,登录页的“格数账号登录”才会显示并可用。这些 OAuth 环境变量在应用启动时读取,修改后需要重启应用。
登录成功后会优先使用账号平台返回的手机号匹配本地已有用户并自动绑定 OAuth 账号。GESHU_OAUTH_ALLOW_CREATE_USER 默认关闭;开启后,手机号未匹配本地用户时会使用账号平台用户信息创建普通用户。
本项目还可以作为独立 OAuth Client 接入 geshu-agent。该 Provider ID 固定为 geshu-agent-oauth,不会替换上面的 geshu-oauth。在 geshu-agent 注册客户端时配置:
- 应用主页:
BETTER_AUTH_URL - 回调地址:
{BETTER_AUTH_URL}/api/auth/oauth2/callback/geshu-agent-oauth - issuer:必须以
/api/auth结尾 - 授权范围:
openid offline_access - 授权流程:
authorization_code - Token Endpoint 认证方式:
client_secret_basic - PKCE:开启
GESHU_AGENT_OAUTH_LOGIN_ENABLED 默认关闭。只有设置为 1,并完整配置 issuer、Client ID 和 Client Secret 后,登录页与个人中心才会显示 geshu-agent 入口。
geshu-agent 使用显式绑定模型:未绑定的账户不能直接登录,也不会按手机号、邮箱或昵称自动匹配或创建本地用户。用户需要先使用本地手机号登录,再完成 geshu-agent 授权绑定。绑定不会覆盖本地昵称、邮箱、手机号或权限;解除绑定只删除本地 OAuth 映射和保存的令牌,不删除任何本地资料。
管理员登录后可以在“系统设置”页面维护运行时配置。配置保存在数据库中,保存后无需重启即可影响后续请求或调度。
系统设置不会读取同名环境变量。首次初始化时,缺失的配置项会写入代码默认值;之后以数据库中的值为准。
当前包含的设置:
- 基础设置:是否允许用户修改昵称、是否允许用户修改手机号
- 短信设置:是否在服务端系统日志中打印验证码,默认开启
- 限流设置:是否启用全局限流
- 自动备份:备份开关、备份频率、保留数量、日志保留时长、S3 / 兼容对象存储配置
- Nginx 设置:是否自动应用 Nginx 配置、执行 Nginx 时使用的命令、动态反向代理使用的 DNS resolver
说明:
- 系统设置中的密钥类字段只在服务端使用,页面不会回显明文
- 密钥输入框留空保存表示保持原值不变,输入新值才会覆盖
- 开发环境下,“启用全局限流”和“自动应用 Nginx 配置”的默认值为关闭
- 关闭“自动应用 Nginx 配置”后,应用会立即尝试停止当前 Nginx 进程
项目支持在后台创建内网反向代理和端口转发规则,并由应用写入 Nginx 配置、生成自签证书、执行 nginx -t 和 reload / quit。
能力范围:
- 支持域名、IPv4、IPv6 作为访问地址和目标地址
- IPv6 可填写
fd00::1或[fd00::1],系统会统一保存为fd00::1 - 支持自定义
Location路径规则、HTTP、HTTPS、自签证书、HTTP跳转HTTPS、WebSocket转发 - 支持动态反向代理路径规则,可通过
url等 query 参数转发到请求指定的HTTP(S)或WebSocket目标地址,并可使用正则限制目标 URL - 动态反向代理会将上游
Location重定向改写为当前反代入口,避免浏览器跳转到真实目标地址 - 反向代理开启
HTTPS时,HTTP端口可填写0表示不监听HTTP - 支持
TCP/UDP端口转发,SSL端口转发仅支持TCP - 不支持
Let’s Encrypt、证书上传、高级 Nginx 配置、访问列表、缓存和限速
- 应用启动时会读取系统设置,对账 Nginx 当前应有的运行状态
- 当“自动应用 Nginx 配置”为开启时:
- 应用会生成主配置与代理配置
- 执行
nginx -t - 成功后启动或重载 Nginx
- 当“自动应用 Nginx 配置”为关闭时:
- 应用会跳过代理服务自动生效
- 并立即尝试停止当前 Nginx 进程
Docker 镜像会继续安装 Nginx,并暴露 80、443、3000 端口。
注意:
- 容器入口脚本只负责准备目录、默认主配置和执行
prisma migrate deploy - 是否真正启动 Nginx,由应用在启动后根据“系统设置 / Nginx 设置”决定
NGINX_COMMAND已迁移到系统设置,不再通过环境变量控制- 动态反向代理依赖 Nginx njs HTTP 模块和 DNS resolver;Docker 镜像已内置 njs,非 Docker 部署需要自行安装并加载 Nginx njs HTTP 模块
IPv6 外部访问还需要宿主机和 Docker 网络启用 IPv6。
项目支持在应用启动时通过 instrumentation.ts 自动启动 SQLite 备份调度器。
适用前提:
- 当前部署为单实例或单主实例
- 应用进程是常驻运行,而不是短生命周期
Serverless - 生产环境的
/app/data已挂载为持久化目录
- 每小时 1 份,保留 48 小时
- 每天 1 份,保留 30 天
- 每周 1 份,保留 12 周
- 每月 1 份,保留 12 个月
OperationLog和ErrorLog默认只保留 1 年内数据
自动备份配置位于“系统设置 / 自动备份”。保存后会立即同步调度器状态:
- 开启时启动自动备份
- 关闭时停止自动备份
- 频率、保留策略和 S3 配置会在后续调度中生效
每个层级都有两个配置:
- 备份周期:每隔多少个周期执行一次
- 保留数量:当前层级最多保留多少份本地备份
周期说明:
- 小时备份周期为
2表示每 2 小时备份一次 - 每日备份周期为
3表示每 3 天备份一次 - 每周备份周期为
2表示每 2 周备份一次 - 每月备份周期为
3表示每 3 个月备份一次
备份周期和保留数量都必须是正整数。
默认值为 365d。
支持格式:
30d52w24h90m
无效时会回退到 365d。
字段说明:
S3 地址:对象存储地址S3 区域:区域S3 存储桶:桶名S3 AccessKey ID:访问密钥 IDS3 AccessKey Secret:访问密钥 SecretS3 对象前缀:可选,对象前缀S3 Path Style:可选,兼容部分 S3 网关
只要任一必填字段缺失或无效,则只做本地备份,不上传对象存储。
自动备份会在 data/backups 下创建目录:
data/backups/
├─ hourly/
├─ daily/
├─ weekly/
├─ monthly/
├─ manifests/
├─ tmp/
└─ state.json
- 应用启动时注册备份调度器
- 调度器每分钟检查一次是否进入新的小时 / 日 / 周 / 月周期
- 命中周期后使用 SQLite 热备份生成一致性快照
- 备份成功后执行完整性校验
- 然后按本地保留策略清理旧备份
- 每天执行一次日志清理
- 如果 S3 配置有效,再将备份压缩后上传到对象存储
- 该方案适合单实例常驻进程
- 如果未来部署为多实例,建议补充分布式锁,避免重复备份
- 如果应用长时间停机,错过的周期不会逐个补跑,只会在恢复后补当前周期
- 恢复时建议优先从本地备份恢复,远端对象存储作为灾备副本
项目内的 server action 限流能力已经内置在 createResponseFn 流程中。只要你的 action 是通过 createResponseFn 创建的,就会自动进入限流中间件。
核心入口:
server/createResponseFn.tsserver/createRateLimit.ts
在 shared 函数上定义 rateLimit 属性即可,推荐使用 createRateLimit 获取完整类型提示:
import { createRateLimit } from "@/server/createRateLimit"
export async function login(params: LoginParams) {
// ...
}
login.rateLimit = createRateLimit({
limit: 5,
windowMs: 60_000,
message: "登录尝试过于频繁,请稍后再试",
})如果 shared 函数没有定义 fn.rateLimit,会使用全局默认配置:
limit:120windowMs:60_000prefix:"server-action"message:"操作过于频繁,请稍后再试"
默认 key 规则:
{prefix}:{action}:{userId 或 ip 或 anonymous}
说明:
- 已登录用户优先按
user.id限流 - 未登录用户按
ip限流 - 获取不到
ip时回退到anonymous - 只有配置
TRUSTED_CLIENT_IP_HEADER时才读取代理传入的 IP;边缘代理必须先移除客户端同名请求头再覆盖写入
日志默认记录动作及参数。参数包含密码、验证码、secret、令牌或大段配置正文时,在 createSharedFn 中设置 logParams: false,保留动作、用户、IP 和 User-Agent 事件但不保存请求参数;只有专用安全审计已替代通用事件时才使用 operationLog: false。
当你需要按账号、手机号等字段精细限流时,可以提供 getKey:
import { type RateLimitContext, createRateLimit } from "@/server/createRateLimit"
function getLoginRateLimitKey(context: RateLimitContext) {
const params = context.args[0] as LoginParams | undefined
const account = params?.account || "unknown-account"
const ip = context.ip || "unknown-ip"
return `login:${ip}:${account}`
}
login.rateLimit = createRateLimit({
limit: 5,
windowMs: 60_000,
message: "登录尝试过于频繁,请稍后再试",
getKey: getLoginRateLimitKey,
})RateLimitContext 包含:
action: 当前 action 名称args: action 参数数组user: 当前登录用户ip: 请求来源 IP
方式一,直接关闭:
someFn.rateLimit = false方式二,使用配置对象关闭:
import { createRateLimit } from "@/server/createRateLimit"
someFn.rateLimit = createRateLimit({
enabled: false,
})通过“系统设置 / 限流设置 / 启用全局限流”控制全局是否启用限流。开发环境默认关闭,其他环境默认开启。
你也可以在服务端代码中动态切换:
import { isGlobalRateLimitEnabled, setGlobalRateLimitEnabled } from "@/server/createRateLimit"
setGlobalRateLimitEnabled(false)
const enabled = await isGlobalRateLimitEnabled()
console.log(enabled)import { setGlobalRateLimitOptions } from "@/server/createRateLimit"
setGlobalRateLimitOptions({
limit: 200,
windowMs: 120_000,
prefix: "my-action",
message: "请求太频繁,请稍后重试",
})限流逻辑与存储已解耦,当前支持:
- 内存存储(默认)
- 自建 Redis 存储(通过适配器接入)
无需额外配置,系统默认使用 createMemoryRateLimitStore()。
适用场景:
- 单实例部署
- 本地开发
import { Redis } from "ioredis"
import { createRedisRateLimitStore, setGlobalRateLimitStore } from "@/server/createRateLimit"
const redis = new Redis(process.env.REDIS_URL!)
setGlobalRateLimitStore(
createRedisRateLimitStore({
async get(key) {
return redis.get(key)
},
async set({ key, value, ttlMs }) {
await redis.set(key, value, "PX", ttlMs)
},
async delete(key) {
await redis.del(key)
},
}),
)