服务器部署准备
构建发布产物,配置 PostgreSQL 与密钥,初始化站点并准备恢复方案。
准备 Linux 服务器、PostgreSQL 17、构建机上的 Bun >=1.3.14,以及运行主机上的 Bun >=1.3.14 或 Node.js 24+。Node 主机复制产物后无需安装 Bun。PostgreSQL 与 Redis 仅开放到私网,通过反向代理提供 HTTPS。每次只构建一个版本,并为前端编译预留足够内存。
构建发布产物
获取 Herta-Network/ppanel-edge 源码后,在仓库根目录依次执行:
git clone https://github.com/Herta-Network/ppanel-edge.git
cd ppanel-edge
bun install --frozen-lockfile
bun run generate
bun run --cwd apps/portal build
bun run --cwd apps/admin build
bun run --cwd apps/web build
bun run --cwd apps/api build
bun run --cwd apps/api build:server将完整的 apps/api/dist 目录复制到 /opt/ppanel-edge 下的相同位置,其中包含 client(官网、门户与后台)、server/node.mjs、server/bun.mjs、server/migrate.mjs 和 server/sql。SQL 文件与迁移产物一起保留。构建机需要源码工作区;运行主机无需 TypeScript 加载器或工作区依赖。
环境与数据库角色
从 apps/api/.env.example 创建受保护的环境文件,替换全部开发值。systemd 使用 /etc/ppanel-edge/api.env,只允许服务账号和管理员读取。设置 NODE_ENV=production、真实域名组成的 SITE_HOSTS、应用数据库的 DATABASE_URL,以及新生成的 MASTER_KEYS:使用 v1: 前缀和随机 32 字节的 base64 值。通过本机密码学随机生成器生成,不能复用示例占位值。主密钥应单独加密备份,与数据库备份分开保存。HOST 默认监听回环地址,默认端口为 3000;普通反向代理部署无需在环境文件设置监听参数。DB_POOL_MAX 默认为 20,需计算全部副本与任务的连接总量。
管理、迁移和 API 使用不同数据库凭据。以数据库管理员身份运行发布产物内的 server/sql/install/roles.sql,通过数据库控制台或交互式 psql 的 \password 设置 migrator 与 app 密码,并让 migrator 拥有空的应用数据库。API 不得使用超级用户;其 DATABASE_URL 使用 app。另建仅管理员可读的 /etc/ppanel-edge/migrate.env,其中 DATABASE_URL 使用直连迁移账号。远程 PostgreSQL 必须使用 TLS 并校验服务端证书。
安装与初始化
Node 主机通过以下命令安装空库和必要初始数据:
node --env-file=/etc/ppanel-edge/migrate.env /opt/ppanel-edge/apps/api/dist/server/migrate.mjs install --seed --dir /opt/ppanel-edge/apps/api/dist/server/sqlBun 主机改用 bun --env-file=/etc/ppanel-edge/migrate.env,其余参数相同。安装会重新授予运行账号表权限。启动所选服务后,访问 https://你的域名/admin/install 完成安装向导并创建初始管理员。数据库安装与浏览器向导是两个独立步骤;完成后检查 /readyz。
共享服务
自托管必须配置 REDIS_URL 或 VALKEY_URL,VOLATILE_STORE=auto 即使用 Redis/Valkey;缺少时服务启动失败,PostgreSQL 只保存持久数据,不作易失状态的后备。全部副本应共享同一 Redis/Valkey 数据集。远程连接使用 rediss:// 并校验证书;REDIS_ALLOW_PLAINTEXT=1 仅用于明确隔离的私网,例如 Compose 内部网络。不支持 Cluster 与 Sentinel。所选实现不可用时按操作类型降级,不会自动切换存储。Redis 状态可丢弃,业务事实保存在 PostgreSQL。
在后台存储设置中,为公开桶和私有桶分别选择 s3 或 local。r2-binding 依赖 Workers 绑定,服务器不能使用。旧配置未提供 bucket_drivers 时,服务器继续使用 S3,Workers 继续使用 R2 绑定。旧字段 driver: r2 与文件行表示逻辑存储位置,切换驱动无需修改业务表。
S3 支持 AWS S3、MinIO、Backblaze B2、Wasabi 与 R2 等兼容 HTTPS 端点。设置 s3.endpoint、s3.region 与 s3.path_style(true 为路径式,false 为桶虚拟主机),在 s3.public 与 s3.private 中配置不同桶名及各自凭据。密钥仍加密保存在设置中。私有桶不得开放匿名访问或公共域名。public_base_url 使用 /f 或仅分发公开桶的 HTTPS CDN 源站。上传保留私有暂存与校验后发布流程;私有文件始终经鉴权下载接口访问。
局域网 MinIO 可在受保护的服务器环境中设置部署变量 OUTBOUND_ALLOW_PRIVATE_CIDRS=10.20.0.0/16,fd42::/48,默认空。必须使用精确、规范的 IPv4/IPv6 网络地址,不得包含主机位;非法条目启动失败。全部 DNS 地址必须为公网或落入明确列出的网段。例外适用于自托管出站传输,也包含提供商请求,故应使用最窄网段。仍要求 HTTPS、证书校验并拒绝重定向。该变量不能通过后台设置,不适用于 Workers。
本地磁盘要求在 local.public_directory 与 local.private_directory 中设置两个绝对、独立且不互相嵌套的目录,仅允许服务账号访问,均放在网站根目录之外。多副本必须共享这些目录。驱动通过临时文件加原子重命名写入完整对象;文件包含内部元数据,禁止直接映射为文件系统静态目录。公开文件通过 /f 提供长期不可变缓存,私有文件仅经鉴权下载接口。本地驱动不能在 Workers 运行。
切换驱动不会自动迁移数据。先暂停新上传,完成已有上传和校验会话,并保留来源与目标的设置修订。为两个桶分别启动 files.migrate_storage,固定来源/目标修订号及版本。任务复制已完成的 ready 与软删除文件,在来源和目标分别校验大小与 SHA-256,仅在校验成功后记录检查点;保留来源数据,拒绝覆盖不一致的目标,支持中断恢复。迁移期间不要编辑固定的草稿,版本变化会使任务停止。两个任务完成且公开/私有下载检查通过后,才发布目标配置;切换完成前持续暂停上传,保留来源备份供回滚。
在通知设置中配置 SMTP 主机、TLS、凭据和发件人,或使用 HTTP 邮件提供商。业务提供商凭据不放在进程环境变量中。上线前测试邮件送达和私有文件授权。
可选运营商识别
设置 ASN_DATABASE_PATH=/var/lib/ppanel-edge/asn.mmdb,即可在 Bun 与 Node 上启用按运营商选择订阅节点。由运营方自行下载 GeoLite2-ASN 或 IPinfo Lite ASN MMDB,遵守提供商的下载、许可与更新要求。文件放在应用仓库和发布目录之外,并允许服务账号读取;应用不会下载数据库。文件上限为 128 MiB。GeoLite 使用 autonomous_system_number 与 autonomous_system_organization 字段;IPinfo Lite 使用 asn(如 AS4134)与 as_name。
服务启动时读取文件,每 30 秒检查文件元数据变更。请求直接使用内存快照,以经过验证的套接字或可信代理客户端 IP 查询,支持 IPv4、IPv6 和 IPv4 映射的 IPv6。更新时将已完整写入的新文件原子重命名到配置路径。更新非法或文件缺失时记录警告并保留最后一个有效快照;没有有效快照则 ASN 未知。未配置文件时 ASN 为 null,启动只记录一次。仍可使用明确允许的用户参数控制运营商规则。此文件仅提供 ASN 与组织名,不改变国家和地区识别;Workers 继续使用 Cloudflare 请求元数据。
可选运行日志文件
设置 LOG_DIR=/var/log/ppanel-edge,即可在 stdout 之外保留私有轮转 JSONL 文件。每个进程使用独立目录,放在公开资源与发布目录之外;每个副本设置不同的 LOG_INSTANCE_ID。目录由服务账号独占,新目录权限为 0700,文件为 0600。拒绝符号链接目录、符号链接文件与硬链接文件。不要对该目录使用外部日志轮转器。
LOG_MAX_BYTES 默认 52428800(50 MiB),最大 104857600(100 MiB);LOG_MAX_FILES 默认 10,最大 100,包含当前文件。文件名保持不变,创建新文件前先删除最旧文件。单行超过 64 KiB 或文件上限时只输出到控制台。文件写入失败后停用可选文件输出,重启后恢复;控制台输出继续工作。
后台运行日志查询需要 ops.logs.read,查询操作写入审计,但不记录搜索文本。每次最多扫描 1 MiB,返回最多 200 行,按写入顺序从新到旧。时间范围包含起点、不含终点;关键词按脱敏记录的普通子串匹配。文件仍在保留范围内时,游标可以跨轮转使用;文件过期后应回到第一页。查询只覆盖当前实例目录,排查多副本时需在代理上选择目标副本。
Workers 不使用文件输出。配置 LOG_WORKERS_ACCOUNT_ID 与 LOG_WORKERS_SCRIPT_NAME 即可生成仪表盘链接并显示请求 ID、时间等筛选上下文。仪表盘 URL 筛选不是稳定公开契约,需核对预填内容,必要时手动粘贴显示的条件。应用不会调用 Cloudflare 遥测 API,也不保存遥测令牌。
运维与恢复
默认队列每 10 秒运行,单次预算 30 秒;重任务每小时运行,单次预算 15 分钟。同队列本地循环不重叠,多个副本通过 PostgreSQL 租约协调。SIGTERM 停止新工作,等待 HTTP 与后台任务结束,再关闭 Redis 和数据库连接池。进程托管应至少等待 17 分钟再强制终止。
stdout 输出结构化 JSON 日志,systemd 将其写入 journal。/healthz 仅检查进程存活,不访问数据库;/readyz 检查数据库、结构兼容性、初始化状态及就绪条件。探测使用允许的 Host 请求头。/metrics 需要配置的 Bearer 令牌保护。
升级前备份 PostgreSQL、两个对象桶和主密钥环,并在隔离数据库验证恢复。保留上一版产物与环境配置。使用与安装相同的环境文件和 SQL 目录参数依次运行 migrate.mjs status、up --dry-run、up,然后切换应用版本。破坏兼容性的迁移须单独审核并显式传入 --contract。优先回退到兼容的上一版应用;数据库 down <version> 可能丢失数据,操作前阅读对应回滚 SQL,必要时从已验证的备份恢复。详见备份恢复与升级回滚。