Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

463 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

项目介绍

geshu-nginx-management 是一个基于 Next.js 16Better AuthPrisma + SQLiteAnt Design 的 Nginx 管理后台。

当前项目支持:

  • 用户登录、初始化管理员、用户管理
  • 操作日志、异常日志
  • 反向代理与 TCP / UDP 端口转发
  • 自签证书生成、Nginx 配置测试与重载
  • 运行时系统设置
  • SQLite 自动备份与可选的 S3 / 兼容对象存储上传

环境变量

项目将启动、构建、认证核心配置、格数账号平台与 geshu-agent OAuth Client 配置,以及首次登录前就必须可用的默认邮箱域名和短信通道配置放在 .env 或部署平台环境变量中。验证码日志打印、限流、用户资料开关、自动备份和 Nginx 运行控制等配置请登录后台后在“系统设置”页面维护。

说明:

  • NEXT_PUBLIC_ 开头的变量会暴露给浏览器,仅放置浏览器也需要读取的非敏感配置
  • NODE_ENV 由运行命令和框架控制,一般不需要手动设置
  • 开发服务读取进程环境变量 PORT,未设置时使用 Next.js 默认端口 3000
  • 开发环境的 BETTER_AUTH_URLNEXT_PUBLIC_BETTER_AUTH_URL 自动使用 http://localhost:<PORT>,无需手动同步端口
  • 开发环境未设置 GESHU_OAUTH_ISSUER 时,默认使用 http://localhost:<PORT>/api/auth
  • BETTER_AUTH_SECRET 在生产环境是强制项,未配置会导致服务启动失败;开发环境会使用仅本地可用的兜底值
  • 格数账号平台与 geshu-agent 登录由环境变量配置,因为 Better Auth 的 genericOAuth Provider 配置在当前版本中是启动时静态配置
  • 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

推荐的本地 .env 示例

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=""

Better Auth URL 解析规则

服务端 authbaseURL 解析顺序:

  1. 开发环境固定使用 http://localhost:<PORT>PORT 未设置时为 3000
  2. 生产环境使用 BETTER_AUTH_URL

客户端 authClientbaseURL 解析顺序:

  1. 浏览器当前域名 window.location.origin
  2. 服务端渲染处于开发环境时使用 http://localhost:<PORT>PORT 未设置时为 3000
  3. 服务端渲染处于生产环境时使用 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_ISSUERGESHU_OAUTH_CLIENT_IDGESHU_OAUTH_CLIENT_SECRET 后,登录页的“格数账号登录”才会显示并可用。这些 OAuth 环境变量在应用启动时读取,修改后需要重启应用。

登录成功后会优先使用账号平台返回的手机号匹配本地已有用户并自动绑定 OAuth 账号。GESHU_OAUTH_ALLOW_CREATE_USER 默认关闭;开启后,手机号未匹配本地用户时会使用账号平台用户信息创建普通用户。

geshu-agent 登录

本项目还可以作为独立 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 路径规则、HTTPHTTPS、自签证书、HTTP 跳转 HTTPSWebSocket 转发
  • 支持动态反向代理路径规则,可通过 url 等 query 参数转发到请求指定的 HTTP(S)WebSocket 目标地址,并可使用正则限制目标 URL
  • 动态反向代理会将上游 Location 重定向改写为当前反代入口,避免浏览器跳转到真实目标地址
  • 反向代理开启 HTTPS 时,HTTP 端口可填写 0 表示不监听 HTTP
  • 支持 TCP / UDP 端口转发,SSL 端口转发仅支持 TCP
  • 不支持 Let’s Encrypt、证书上传、高级 Nginx 配置、访问列表、缓存和限速

Nginx 运行语义

  • 应用启动时会读取系统设置,对账 Nginx 当前应有的运行状态
  • 当“自动应用 Nginx 配置”为开启时:
    • 应用会生成主配置与代理配置
    • 执行 nginx -t
    • 成功后启动或重载 Nginx
  • 当“自动应用 Nginx 配置”为关闭时:
    • 应用会跳过代理服务自动生效
    • 并立即尝试停止当前 Nginx 进程

Docker 说明

Docker 镜像会继续安装 Nginx,并暴露 804433000 端口。

注意:

  • 容器入口脚本只负责准备目录、默认主配置和执行 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 个月
  • OperationLogErrorLog 默认只保留 1 年内数据

系统设置

自动备份配置位于“系统设置 / 自动备份”。保存后会立即同步调度器状态:

  • 开启时启动自动备份
  • 关闭时停止自动备份
  • 频率、保留策略和 S3 配置会在后续调度中生效

备份频率与保留数量

每个层级都有两个配置:

  • 备份周期:每隔多少个周期执行一次
  • 保留数量:当前层级最多保留多少份本地备份

周期说明:

  • 小时备份周期为 2 表示每 2 小时备份一次
  • 每日备份周期为 3 表示每 3 天备份一次
  • 每周备份周期为 2 表示每 2 周备份一次
  • 每月备份周期为 3 表示每 3 个月备份一次

备份周期和保留数量都必须是正整数。

日志保留时长

默认值为 365d

支持格式:

  • 30d
  • 52w
  • 24h
  • 90m

无效时会回退到 365d

S3 / 兼容对象存储

字段说明:

  • S3 地址:对象存储地址
  • S3 区域:区域
  • S3 存储桶:桶名
  • S3 AccessKey ID:访问密钥 ID
  • S3 AccessKey Secret:访问密钥 Secret
  • S3 对象前缀:可选,对象前缀
  • S3 Path Style:可选,兼容部分 S3 网关

只要任一必填字段缺失或无效,则只做本地备份,不上传对象存储。

目录结构

自动备份会在 data/backups 下创建目录:

data/backups/
├─ hourly/
├─ daily/
├─ weekly/
├─ monthly/
├─ manifests/
├─ tmp/
└─ state.json

工作方式

  1. 应用启动时注册备份调度器
  2. 调度器每分钟检查一次是否进入新的小时 / 日 / 周 / 月周期
  3. 命中周期后使用 SQLite 热备份生成一致性快照
  4. 备份成功后执行完整性校验
  5. 然后按本地保留策略清理旧备份
  6. 每天执行一次日志清理
  7. 如果 S3 配置有效,再将备份压缩后上传到对象存储

注意事项

  • 该方案适合单实例常驻进程
  • 如果未来部署为多实例,建议补充分布式锁,避免重复备份
  • 如果应用长时间停机,错过的周期不会逐个补跑,只会在恢复后补当前周期
  • 恢复时建议优先从本地备份恢复,远端对象存储作为灾备副本

Server Action 限流

项目内的 server action 限流能力已经内置在 createResponseFn 流程中。只要你的 action 是通过 createResponseFn 创建的,就会自动进入限流中间件。

核心入口:

  • server/createResponseFn.ts
  • server/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: 120
  • windowMs: 60_000
  • prefix: "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

3.2 关闭某个函数的限流

方式一,直接关闭:

someFn.rateLimit = false

方式二,使用配置对象关闭:

import { createRateLimit } from "@/server/createRateLimit"

someFn.rateLimit = createRateLimit({
    enabled: false,
})

4. 全局开关

4.1 系统设置开关

通过“系统设置 / 限流设置 / 启用全局限流”控制全局是否启用限流。开发环境默认关闭,其他环境默认开启。

4.2 运行时开关

你也可以在服务端代码中动态切换:

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()

适用场景:

  • 单实例部署
  • 本地开发

使用 Redis 存储

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)
        },
    }),
)

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages