diff --git a/CONTENT_SYNC.md b/CONTENT_SYNC.md index 56bb9c2..f7814f6 100644 --- a/CONTENT_SYNC.md +++ b/CONTENT_SYNC.md @@ -1,5 +1,17 @@ # Content synchronization baseline +## ZBoard documentation migration — 2026-09-10 + +ZBoard documentation is now authored in `docs/projects/zboard/`. Product introductions, installation, basic management, and plugin usage lead the navigation; contracts, design notes and historical acceptance records are separate reference sections. `zboard-document-migration.json` records each source, destination and source digest. + +This migration uses ZBoard develop `37d1716c466ce16a1198399ac91c9de3e2c0c4f8` plus local documentation revisions. It is not a claim that every feature is in the published v0.0.1 artifact. Plugin runtime and marketplace documentation explicitly identifies the post-v0.0.1 development scope; shared-pool RAW editor additions are marked pending publication. Payments and other business extensions belong to plugins; the current payment extension interface is not implemented. Core and client evidence remains on the earlier baseline below. + +Public ZBoard documentation must be changed here, not copied back into the ignored product `docs/` directory. Product release packaging keeps its own release-note artifact under `.github/release-notes/`. + +Verification: source and production-output checks passed for 97 Markdown pages, including every sidebar entry, internal link and anchor, JSON examples and rendered HTML. The migration also removes the product release workflow dependency on its local docs directory. + +## Previous main audit — 2026-09-09 + Public guides were checked on 2026-09-09 against freshly fetched GitHub `main` revisions. None of the three product repositories has a `master` branch. Source was read from immutable Git archives, excluding develop, feature branches and uncommitted changes. In particular, the client checkout contains ongoing uncommitted work and the panel checkout is on a feature branch; neither is the documentation baseline. | Repository | Main revision | diff --git a/README.md b/README.md index 5c68ce5..0a04a54 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The production site is published from `main`, while the development preview is p | --- | --- | --- | | Zero Core | Documentation for the ZeroDeNet network runtime, protocols, control interfaces, and deployment. | [zerodenet/core](https://github.com/zerodenet/core) | | ZNet Sink | Usage, configuration, and platform compatibility documentation for the ZeroDeNet desktop proxy client. | [zerodenet/znet-sink](https://github.com/zerodenet/znet-sink) | -| Zboard | Deployment, initialization, node management, and usage documentation for the service operations platform. | [zerodenet/zboard](https://github.com/zerodenet/zboard) | +| Zboard | Installation, basic panel management, and plugin extension documentation. | [zerodenet/zboard](https://github.com/zerodenet/zboard) | Each project has its own navigation, page hierarchy, and documentation boundaries so that versions, configuration semantics, and usage guidance remain project-specific. @@ -193,3 +193,7 @@ After running the scaffold: ## License Documentation in this repository is published under the license declared by this repository. Source code licenses are defined by the corresponding project repositories. + +## ZBoard documentation ownership + +ZBoard documentation is maintained in `docs/projects/zboard/`, including installation, plugin usage and technical references. The product repository retains only entry-point READMEs and build-owned release notes. Historical materials are labeled and grouped separately. See `zboard-document-migration.json` for migration provenance. diff --git a/README.zh-CN.md b/README.zh-CN.md index 3670657..d5d6176 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -189,3 +189,7 @@ pnpm create:project -- \ ## License 本仓库中的文档内容按照仓库所声明的许可协议发布。各项目代码的许可协议以对应项目仓库为准。 + +## ZBoard 文档维护 + +ZBoard 的安装、插件使用与技术参考统一维护在 `docs/projects/zboard/`。产品仓库保留入口说明和构建使用的发布文案,历史资料在文档站单独归档。迁移来源记录在 `zboard-document-migration.json`。 diff --git a/docs/.vitepress/navigation.ts b/docs/.vitepress/navigation.ts index d6ded2c..b1c5444 100644 --- a/docs/.vitepress/navigation.ts +++ b/docs/.vitepress/navigation.ts @@ -138,25 +138,59 @@ const sinkSidebar: DefaultTheme.SidebarItem[] = [ ] const zboardSidebar: DefaultTheme.SidebarItem[] = [ - page('Zboard 文档', '/projects/zboard/'), + page('ZBoard 基础面板', '/projects/zboard/'), group('开始使用', [ - page('用户指南入口', '/projects/zboard/guides/'), + page('用户指南', '/projects/zboard/guides/'), page('安装与部署', '/projects/zboard/guides/installation'), page('首次初始化', '/projects/zboard/guides/first-setup'), - page('后台导航与日常运营', '/projects/zboard/guides/daily-operations'), + page('日常管理', '/projects/zboard/guides/daily-operations'), + page('数据存储与备份', '/projects/zboard/guides/storage-and-backups'), ], false), - group('功能说明', [ - page('节点与协议服务管理', '/projects/zboard/guides/node-management'), - page('协议服务配置', '/projects/zboard/guides/protocol-services'), - page('套餐、订单与用户交付', '/projects/zboard/guides/plans-and-orders'), - page('订阅交付与流量展示', '/projects/zboard/guides/subscriptions-and-traffic'), - page('公告、注册验证与邮件', '/projects/zboard/guides/announcements-and-email'), - page('系统维护与数据库迁移', '/projects/zboard/guides/maintenance'), - page('DNS 与证书管理', '/projects/zboard/guides/dns-and-certificates'), + group('基础功能', [ + page('节点管理', '/projects/zboard/guides/node-management'), + page('协议服务', '/projects/zboard/guides/protocol-services'), + page('网络前置与共享代理池', '/projects/zboard/guides/network-fronting'), + page('套餐与订单', '/projects/zboard/guides/plans-and-orders'), + page('订阅与流量', '/projects/zboard/guides/subscriptions-and-traffic'), + page('订阅筛选', '/projects/zboard/guides/subscription-filtering'), + page('公告与邮件', '/projects/zboard/guides/announcements-and-email'), + page('DNS 与证书', '/projects/zboard/guides/dns-and-certificates'), + page('系统维护', '/projects/zboard/guides/maintenance'), + page('节点清理', '/projects/zboard/guides/node-cleanup'), page('故障排查', '/projects/zboard/guides/troubleshooting'), ]), - group('参与项目', [ - page('参与 Zboard', '/projects/zboard/contributing/'), + group('插件市场与使用', [ + page('插件能力与边界', '/projects/zboard/plugins/'), + page('配置市场与安装插件', '/projects/zboard/plugins/marketplace'), + page('启用第三方登录', '/projects/zboard/plugins/login'), + page('安装安全与信任', '/projects/zboard/plugins/trust'), + ], false), + group('开发与技术参考', [ + page('参与项目', '/projects/zboard/contributing/'), + page('本地开发', '/projects/zboard/contributing/development'), + page('插件开发', '/projects/zboard/plugins/development'), + page('身份接口', '/projects/zboard/plugins/identity-reference'), + page('插件治理', '/projects/zboard/plugins/governance'), + page('技术参考入口', '/projects/zboard/reference/'), + page('核心边界', '/projects/zboard/reference/core-baseline'), + page('数据模型', '/projects/zboard/reference/data-model'), + page('数据库迁移', '/projects/zboard/reference/database-migrations'), + page('节点配置交付', '/projects/zboard/reference/node-config-delivery'), + page('内核生命周期', '/projects/zboard/reference/node-kernel-lifecycle'), + page('规则兼容性', '/projects/zboard/reference/managed-rule-compatibility'), + page('Mieru 内核契约', '/projects/zboard/reference/mieru-kernel-contract'), + ]), + group('设计与历史资料', [ + page('插件架构设计', '/projects/zboard/reference/plugin-system-design'), + page('插件市场设计', '/projects/zboard/reference/plugin-marketplace-design'), + page('0.0.1 发布记录', '/projects/zboard/reference/history/v0.0.1'), + page('核心 RC 验收记录', '/projects/zboard/reference/history/core-rc'), + page('历史发布清单', '/projects/zboard/reference/history/launch-checklist'), + page('历史路线图', '/projects/zboard/reference/history/roadmap'), + ]), + group('English', [ + page('Installation', '/projects/zboard/guides/installation-en'), + page('Plugin installation trust', '/projects/zboard/plugins/trust-en'), ]), ] @@ -172,6 +206,6 @@ export const sidebar: DefaultTheme.Sidebar = { page('实现与文档进度', '/progress'), group('应用', [page('ZNet Sink', '/projects/znet-sink/')]), group('内核', [page('Zero Core', '/projects/core/')]), - group('运营平台', [page('Zboard', '/projects/zboard/')]), + group('基础面板', [page('ZBoard', '/projects/zboard/')]), ], } diff --git a/docs/.vitepress/projects.json b/docs/.vitepress/projects.json index 069f79c..884a629 100644 --- a/docs/.vitepress/projects.json +++ b/docs/.vitepress/projects.json @@ -30,9 +30,9 @@ }, { "id": "zboard", - "name": "Zboard", - "tagline": "代理服务运营管理", - "description": "基础设施、协议服务、节点组、商品、订单、订阅、配置交付和流量管理。", + "name": "ZBoard", + "tagline": "基础面板与插件扩展", + "description": "管理 Zero 节点、用户、订阅和流量,按需通过插件扩展。", "kind": "application", "status": "active", "repository": "https://github.com/zerodenet/zboard", diff --git a/docs/progress.md b/docs/progress.md index 8acf710..33142ce 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -42,7 +42,11 @@ TUN、DNS/Fake-IP 和多协议能力已提供配置与控制接口;具体协 0.0.1 已发布 Windows x86_64、macOS Intel/Apple Silicon 和 Linux x86_64 安装包。该版本的[发布记录](https://github.com/zerodenet/znet-sink/blob/6d822fb96140be87cdccdd0bea472ba0b089cf04/docs/releases/v0.0.1.md)明确保留四个平台安装运行验收的豁免:DNS 与接管模式组合、升级中断恢复、退出清理及跨资源故障注入等仍待补验。不能把发布成功写成这些场景已经安装验收通过。 -## Zboard +## ZBoard + +::: info 文档迁移与开发版补充(2026-09-10) +以下表格保留 2026-09-09 的 main 核对记录。新迁入的[插件说明](/projects/zboard/plugins/)覆盖 0.0.1 发布后的开发分支能力;不代表原始 0.0.1 安装包已包含插件运行时。ZBoard 定位为基础面板,在线支付等业务扩展通过插件实现,当前尚未开放支付业务接口。 +::: | 已实现能力 | 对运营者的实际作用 | 使用说明与源码依据 | | --- | --- | --- | @@ -52,7 +56,7 @@ TUN、DNS/Fake-IP 和多协议能力已提供配置与控制接口;具体协 | 本地删除与独立远端清理 | 节点或供应商不可达时可清理面板记录;远端停机另行执行 | [节点清理](/projects/zboard/guides/node-management#删除节点与远端清理);[删除实现](https://github.com/zerodenet/zboard/blob/e1b7246cc4ef805bf39b22d634ba209114eb3b14/backend/internal/handler/node_delete_cascade.go) | | 规则集按客户端能力交付 | Clash/sing-box 可保留进程条件,Zero 模板拒绝不支持的规则集 | [规则兼容](/projects/zboard/guides/subscriptions-and-traffic#规则集与客户端兼容性);[兼容检查](https://github.com/zerodenet/zboard/blob/e1b7246cc4ef805bf39b22d634ba209114eb3b14/backend/internal/handler/managed_rule_client_compatibility.go) | -当前提供用户、节点、订阅、基础订单和流量计量闭环。在线支付集成与插件运行时仍是后续方向,不作为 main 已交付能力;详见[当前范围](https://github.com/zerodenet/zboard/blob/e1b7246cc4ef805bf39b22d634ba209114eb3b14/docs/core-baseline.md)。[0.0.1 发布记录](https://github.com/zerodenet/zboard/blob/e1b7246cc4ef805bf39b22d634ba209114eb3b14/docs/release/v0.0.1.md)也未将 24 小时长稳、500 events/s 突发或完整多节点恢复标为验收完成。 +当前提供用户、节点、订阅、基础订单和流量计量闭环。此 main 快照尚未包含后来在开发分支实现的插件运行时。在线支付属于插件扩展范围,支付业务接口仍待实现;历史范围见[当前范围](https://github.com/zerodenet/zboard/blob/e1b7246cc4ef805bf39b22d634ba209114eb3b14/docs/core-baseline.md)。[0.0.1 发布记录](https://github.com/zerodenet/zboard/blob/e1b7246cc4ef805bf39b22d634ba209114eb3b14/docs/release/v0.0.1.md)也未将 24 小时长稳、500 events/s 突发或完整多节点恢复标为验收完成。 ## 本轮核对范围 diff --git a/docs/projects/zboard/contributing/development.md b/docs/projects/zboard/contributing/development.md new file mode 100644 index 0000000..912f6c5 --- /dev/null +++ b/docs/projects/zboard/contributing/development.md @@ -0,0 +1,166 @@ +# Local development + +This guide contains the contributor setup that does not belong in the project +overview. For product architecture and supported capabilities, start with the +repository `README.md`. + +## Toolchain + +Use the versions declared by the repository instead of choosing local defaults: + +- Go and the exact toolchain are declared in `backend/go.mod`; +- Node.js is pinned by the frontend and Docker build; +- pnpm is declared by `frontend/package.json#packageManager`; +- MySQL 8 is required for the current local service. + +The environment checks can resolve the repository Go toolchain automatically: + +```powershell +.\scripts\verify-env.ps1 +``` + +```bash +./scripts/verify-env.sh +``` + +## One-command startup + +The development launcher verifies dependencies, starts MySQL through Docker +Compose when necessary, creates ignored local runtime configuration, +starts the backend and optionally starts the frontend. + +PowerShell: + +```powershell +.\scripts\start-dev.ps1 -WithFrontend +``` + +Bash: + +```bash +./scripts/start-dev.sh --with-frontend +``` + +Use `-SkipDependencies` or `--skip-deps` when MySQL already runs +outside the repository Compose stack. Both launchers expose additional port, +timeout and datasource options in their built-in help or parameter list. + +The backend is available at `http://127.0.0.1:8080`; the frontend development +server defaults to `http://127.0.0.1:5173`. + +## Manual backend startup + +Manual startup requires a datasource, a JWT secret of at least 32 bytes and one +stable 32-byte credential-encryption key: + +```powershell +Set-Location backend +$env:ZBOARD_ENVIRONMENT = "development" +$env:ZBOARD_DATA_SOURCE = "zboard:@tcp(127.0.0.1:3306)/zboard?charset=utf8mb4&parseTime=true&loc=Local" +$env:ZBOARD_JWT_SECRET = "" +$env:ZBOARD_CREDENTIAL_ENCRYPTION_KEY = "<32-random-bytes-as-base64-or-hex>" +go run ./cmd/zboard -f ./etc/zboard.yaml.example +``` + +On an empty database the service enters installation mode. Open `/setup` to +create the first administrator and finish site initialization. + +The embedded SQL baseline is applied during startup. To run migrations without +starting the HTTP service, use `scripts/migrate.ps1` or `scripts/migrate.sh`. +See [database-migrations.md](/projects/zboard/reference/database-migrations) before opening an existing +development database with a newer build. + +## Manual frontend startup + +```powershell +Set-Location frontend +$env:VITE_API_BASE = "http://127.0.0.1:8080/api/v1" +pnpm install --frozen-lockfile +pnpm dev +``` + +On Bash-compatible shells: + +```bash +cd frontend +VITE_API_BASE=http://127.0.0.1:8080/api/v1 pnpm install --frozen-lockfile +VITE_API_BASE=http://127.0.0.1:8080/api/v1 pnpm dev +``` + +## Verification + +Run checks in proportion to the changed area. A complete local verification is: + +```powershell +Set-Location backend +go test ./... +go vet ./... + +Set-Location ..\frontend +pnpm test +pnpm build +``` + +`pnpm build` includes Vue and TypeScript type checking. API changes must also +update `backend/api/openapi.yaml` and its contract tests. + +The repository also provides: + +- `scripts/smoke-test.*` for a running service; +- `scripts/build-all.*` for combined backend and frontend builds; +- `scripts/check-go-version.*` and `scripts/sync-go-baseline.*` for toolchain + maintenance. + +## Performance and stability verification + +Run these commands from the repository root with the pinned Go toolchain +(`GO_BIN` can select its executable): + +```bash +bash scripts/benchmark-accounting.sh local +bash scripts/benchmark-accounting.sh container +bash scripts/acceptance-mixed.sh +``` + +The accounting benchmark defaults to 100 batches per scenario and three runs; +`BENCH_TIME` and `BENCH_COUNT` override these settings. Container checks require +Docker. The mixed workload defaults to 10 nodes, 1,000 subscriptions, 100,000 +historical records, four concurrent readers and 100 events/second for 300 seconds. +`DURATION_SECONDS`, `EVENT_RATE` and `READERS` select other workload profiles. +The application and database share the 1 CPU / 1 GiB budget; the load generator +runs outside it. The [roadmap](/projects/zboard/reference/history/roadmap#资源和性能预算) defines latency and memory +budgets. Also verify exact accounting under replay and reordering, no OOM, +explained failures and a fully drained backlog. + +Real Zero revocation checks use isolated nodes and test credentials: + +```bash +ZERO_ARTIFACT_DIR=/path/to/verified-linux-zero-artifact \ + NODE_SCENARIO=expiry bash scripts/acceptance-node.sh +``` + +The artifact directory must contain `zero` and its matching `verification.json`. +Other scenarios are `exhaustion`, `group_change` and `recovery`. Verify data-plane +access after revocation and publication recovery, including existing connections; +control-plane status or a mock SSH server alone is insufficient. See also +[node publication](/projects/zboard/reference/node-config-delivery#重跑-mysql-验证) for real MySQL checks. + +Keep raw logs, profiles, environment details, source/build hashes and per-run +reports under the ignored `.codex-local-artifacts/acceptance/` directory. A dirty +build needs source hashes as well as Git HEAD. Share selected evidence through +CI/release artifacts when needed, without committing workstation journals. + +A 500-events/second, 60-second burst and a 24-hour soak are separate acceptance +profiles. Record fault injection, restarts, queue recovery and resource trends; +restart the soak clock after changing the build or resetting the environment. +Short runs do not establish long-term stability or production capacity. + +## Restricted networks + +`scripts/ensure-go-env.*` can install the pinned Go toolchain when it is +missing. Set `ZBOARD_GO_DOWNLOAD_BASE` to an approved mirror when direct +downloads from `go.dev` are unavailable. A preinstalled Go directory can be +selected with `ZBOARD_GOROOT_FALLBACK`. + +Do not weaken production secrets or commit generated runtime configuration to +work around a local environment problem. diff --git a/docs/projects/zboard/contributing/index.md b/docs/projects/zboard/contributing/index.md index 42d1fb5..0944bd1 100644 --- a/docs/projects/zboard/contributing/index.md +++ b/docs/projects/zboard/contributing/index.md @@ -1,15 +1,18 @@ -# 参与 Zboard +# 参与 ZBoard -Zboard 的代码、版本规划和实现讨论维护在项目仓库中。 +ZBoard 专注于基础面板能力。扩展业务通过插件实现,贡献前请先确认功能属于面板本身还是插件。 ## 文档贡献 -公开使用文档位于本仓库: +ZBoard 的使用指南、安装教程、插件说明和技术参考统一维护在 [zerodenet/docs](https://github.com/zerodenet/docs) 的 `docs/projects/zboard/`。产品仓库中的本地 `docs/` 不再参与版本管理,请直接向文档仓库提交修改。 -- 修正文档错误; -- 补充部署和运维经验; -- 完善用户使用流程。 +新增页面时同步更新侧边导航,并运行 `pnpm check:build` 验证链接与构建。 -## 项目贡献 +## 代码与插件贡献 -开发、Issue 和技术讨论请访问 [Zboard 仓库](https://github.com/zerodenet/zboard)。 +- [本地开发与检查命令](/projects/zboard/contributing/development) +- [插件开发接口](/projects/zboard/plugins/development) +- [插件治理与权限](/projects/zboard/plugins/governance) +- [技术参考](/projects/zboard/reference/) + +产品代码、缺陷和功能讨论请提交到 [ZBoard 仓库](https://github.com/zerodenet/zboard)。文案、教程和链接问题请提交到文档仓库。 diff --git a/docs/projects/zboard/guides/index.md b/docs/projects/zboard/guides/index.md index 8f66b17..2f99837 100644 --- a/docs/projects/zboard/guides/index.md +++ b/docs/projects/zboard/guides/index.md @@ -1,22 +1,28 @@ -# Zboard 用户指南 +# 使用指南 -从这里开始了解 Zboard 的部署、初始化、节点接入和日常运营。 +ZBoard 的核心是基础面板。先完成安装和一条服务的配置,再按需要添加节点、订阅规则与插件。 -## 推荐阅读顺序 +## 第一次使用 -1. [安装与部署](./installation) -2. [首次初始化](./first-setup) -3. [节点与协议服务管理](./node-management) -4. [协议服务配置](./protocol-services) -5. [订阅交付与流量展示](./subscriptions-and-traffic) -6. [DNS 与证书管理](./dns-and-certificates) -7. [故障排查](./troubleshooting) +1. [安装面板](./installation):选择版本,填写数据库与密钥配置,启动 Docker 服务。 +2. [初始化站点](./first-setup):创建管理员并设置站点。 +3. [接入节点](./node-management):配置 SSH,安装 Zero 并检查节点状态。 +4. [创建协议服务](./protocol-services):设置协议和端口,等待配置发布成功。 +5. [开通订阅](./plans-and-orders):关联节点组、套餐与用户权益。 +6. [连接客户端](./subscriptions-and-traffic):获取对应格式的订阅并查看流量。 -节点资产、协议服务、订阅模板和证书是独立资源。先完成节点接入,再按实际业务启用协议、订阅和基础设施自动化能力。 +English installation instructions are available in [First installation](./installation-en). -## 日常运营专题 +## 按需配置 -- [后台导航与日常运营](./daily-operations) -- [套餐、订单与用户交付](./plans-and-orders) -- [公告、注册验证与邮件](./announcements-and-email) -- [系统维护与数据库迁移](./maintenance) +| 需求 | 指南 | +| --- | --- | +| 使用转发入口或共享代理池 | [网络前置](./network-fronting) | +| 调整订阅中的节点 | [订阅节点过滤](./subscription-filtering) | +| 安装扩展功能 | [插件市场](../plugins/marketplace) | +| 配置第三方登录 | [登录插件](../plugins/login) | +| 保存和恢复面板数据 | [存储与备份](./storage-and-backups) | +| 排查连接和发布错误 | [故障排查](./troubleshooting) | +| 清理节点 | [节点清理](./node-cleanup) | + +日常管理还包括[控制台操作](./daily-operations)、[公告与邮件](./announcements-and-email)、[DNS 与证书](./dns-and-certificates)和[系统维护](./maintenance)。实现契约与开发流程集中在[开发参考](../reference/)中。 diff --git a/docs/projects/zboard/guides/installation-en.md b/docs/projects/zboard/guides/installation-en.md new file mode 100644 index 0000000..b25f73a --- /dev/null +++ b/docs/projects/zboard/guides/installation-en.md @@ -0,0 +1,109 @@ +# First installation + +[English](/projects/zboard/guides/installation-en) | [简体中文](/projects/zboard/guides/installation) + +This guide installs the published Docker image and walks through creating your first service. The image includes the backend and web console; you do not need to build the frontend or install Go and Node.js. + +## Before you start + +Prepare: + +- A Linux amd64 host with Docker Engine, the Docker Compose plugin, Git, and OpenSSL. +- An empty MySQL 8 database and a dedicated application account with permission to create and update its tables. ZBoard refuses the MySQL root account in production. +- An existing Docker network that lets the application reach MySQL. If MySQL runs in Docker, attach its container to that network and use its container name or network alias as the database host. +- A domain with HTTPS, served by a reverse proxy on the Docker host. The example binds ZBoard to `127.0.0.1:8080`. + +The release Compose file starts **only ZBoard**. It does not provision MySQL or Redis. SQLite is an alternative; see [Docker storage](/projects/zboard/guides/storage-and-backups) for its environment settings and Compose override. + +## 1. Get the deployment files + +Choose a version from [Releases](https://github.com/zerodenet/zboard/releases). The following example uses `v0.0.1`: + +```bash +git clone --branch v0.0.1 --depth 1 https://github.com/zerodenet/zboard.git +cd zboard/deploy/docker +cp .env.release.example .env.release +chmod 600 .env.release +``` + +Use the same tag for the checkout and Docker image. For another version, replace `v0.0.1` in both places. + +## 2. Configure the application + +Edit `.env.release`. Set these values for your environment: + +```dotenv +ZBOARD_IMAGE_TAG=v0.0.1 +ZBOARD_PULL_POLICY=always +ZBOARD_HTTP_BIND=127.0.0.1 +ZBOARD_HTTP_PORT=8080 +ZBOARD_EXTERNAL_NETWORK=your_existing_docker_network +ZBOARD_DATABASE_DRIVER=mysql +ZBOARD_DATA_SOURCE='zboard:YOUR_DATABASE_PASSWORD@tcp(mysql:3306)/zboard?charset=utf8mb4&parseTime=true&loc=UTC' +ZBOARD_JWT_SECRET=YOUR_RANDOM_JWT_SECRET +ZBOARD_CREDENTIAL_ENCRYPTION_KEY=YOUR_RANDOM_ENCRYPTION_KEY +``` + +Replace the network name and all database details, including `mysql`, with your actual connection settings. `127.0.0.1` inside the container refers to the ZBoard container itself, not the host or a separate MySQL container. + +Run the following command **twice** to generate two independent values. Use one for `ZBOARD_JWT_SECRET` and the other for `ZBOARD_CREDENTIAL_ENCRYPTION_KEY`: + +```bash +openssl rand -hex 32 +``` + +Keep both values stable across restarts. The encryption key is needed to read saved node credentials; keep a backup separately from the database. + +Leave `ZBOARD_BOOTSTRAP_ADMIN_EMAIL` and `ZBOARD_BOOTSTRAP_ADMIN_PASSWORD` empty to create the administrator in the setup page. There is no default administrator password. + +## 3. Prepare storage and start + +These commands use the default host directory paths in `.env.release`. If you change them, follow the [custom directory instructions](/projects/zboard/guides/storage-and-backups#required-host-directories) before starting. + +```bash +sh ./prepare-host-dirs.sh +docker compose -f docker-compose.release.yml --env-file .env.release config --quiet +docker compose -f docker-compose.release.yml --env-file .env.release pull +docker compose -f docker-compose.release.yml --env-file .env.release up -d +docker compose -f docker-compose.release.yml --env-file .env.release ps +``` + +Check the backend from the Docker host: + +```bash +curl --fail http://127.0.0.1:8080/readyz +curl --fail http://127.0.0.1:8080/api/v1/version +``` + +`/readyz` checks the application database connection. It does not indicate whether any Zero nodes have been installed or published successfully. + +If startup fails, inspect the service logs: + +```bash +docker compose -f docker-compose.release.yml --env-file .env.release logs --tail 100 zboard +``` + +Common causes are an unset environment value, a missing external network, incorrect database credentials, or MySQL being unreachable from the container. + +## 4. Create your administrator + +Configure the host reverse proxy to send requests for your HTTPS domain to `http://127.0.0.1:8080`, then open `https://YOUR_DOMAIN/setup`. + +Complete the site settings and create the first administrator. After initialization, use `/login`. The setup page cannot create another administrator after the site is installed. + +## 5. Configure your first service + +1. **Add a node.** Enter its address and SSH connection details in the administrator console. +2. **Install Zero.** Use the node's kernel management controls and wait for installation and health checks to finish. +3. **Create a protocol service.** Choose the node and protocol, configure its listening address and port, and wait for publication to succeed. Make the service port reachable through the node's firewall. +4. **Create a node group and plan.** Add the service to the group, then select that group in the plan. +5. **Activate a user subscription.** Use the basic order workflow to create an order for the plan and confirm it as an administrator. +6. **Connect a client.** In the user's account, choose the subscription format for the client and import its link. Make a connection, then check traffic usage in the console. + +For a forwarding node or a shared upstream pool, continue with [network fronting](/projects/zboard/guides/network-fronting). Node installation and recovery are covered in the [node lifecycle guide](/projects/zboard/reference/node-kernel-lifecycle). + +## Keep your data + +Persist the database, `.env.release`, credential-encryption key, managed rules, and Zero event spool. Builds with plugin support also need their plugin directory. The [storage and backup guide](/projects/zboard/guides/storage-and-backups) explains the mounts and which data must be restored together. + +For local source development, use the [development guide](/projects/zboard/contributing/development). diff --git a/docs/projects/zboard/guides/installation.md b/docs/projects/zboard/guides/installation.md index c879a23..d4c9587 100644 --- a/docs/projects/zboard/guides/installation.md +++ b/docs/projects/zboard/guides/installation.md @@ -1,75 +1,129 @@ -# 安装与部署 +# 首次安装 -Zboard 支持 MySQL 和 SQLite。使用仓库中的 Docker Compose 发布配置部署,并固定镜像标签;当前产品版本为 0.0.1,部署时仍需核对制品构建和所需能力。 +[English](/projects/zboard/guides/installation-en) | [简体中文](/projects/zboard/guides/installation) -当前已提供 [0.0.1 正式发布](https://github.com/zerodenet/zboard/releases/tag/v0.0.1),包含 Linux amd64 二进制、Docker 镜像离线包与 SHA256SUMS;镜像为 `ghcr.io/zerodenet/zboard:v0.0.1`。同名标签重建后需重新拉取镜像并重建容器,仅重启已有容器不会替换镜像。升级前备份数据库、配置与持久目录。 +本指南使用正式发布的 Docker 镜像安装面板,并说明如何配置第一条服务。镜像已经包含后端和 Web 控制台,无需自行构建前端,也无需安装 Go 或 Node.js。 -## 准备运行环境 +## 安装前准备 -需要 Docker Engine、Compose v2,以及发布配置使用的外部 Docker 网络。MySQL 方案准备可访问的 MySQL 8 数据库;SQLite 方案准备可写的持久目录。当前发布 Compose 不要求部署 Redis。 +需要准备: -取得与所选发布匹配的部署文件,进入 `deploy/docker`,创建自己的 `.env.release`。此文件包含凭证,不提交到公开仓库。 +- 一台 Linux amd64 主机,已安装 Docker Engine、Docker Compose 插件、Git 和 OpenSSL。 +- 一个空的 MySQL 8 数据库,以及拥有该库建表和读写权限的应用账号。生产环境不接受 MySQL root 账号。 +- 一个让面板可以访问 MySQL 的现有 Docker 网络。如果 MySQL 运行在容器中,应将其连接到这个网络,并使用容器名或网络别名作为数据库地址。 +- 一个配置了 HTTPS 的域名,由 Docker 所在主机上的反向代理转发。示例将面板绑定到 `127.0.0.1:8080`。 -## 填写部署参数 +正式版 Compose 文件**只启动 ZBoard**,不会创建 MySQL 或 Redis。也可以选择 SQLite,对应配置及 Compose 覆盖文件见 [Docker 存储说明](/projects/zboard/guides/storage-and-backups)。 -| 变量 | 默认 / 是否必填 | 如何填写 | -| --- | --- | --- | -| `ZBOARD_IMAGE_TAG` | 必填 | 发布页上的固定标签 | -| `ZBOARD_IMAGE_REPOSITORY` | `ghcr.io/zerodenet/zboard` | 镜像仓库 | -| `ZBOARD_EXTERNAL_NETWORK` | 必填 | 已存在的外部 Docker 网络名称 | -| `ZBOARD_DATABASE_DRIVER` | `mysql` | `mysql` 或 `sqlite` | -| `ZBOARD_DATA_SOURCE` | 必填 | MySQL 完整 DSN 或 SQLite 容器内文件路径 | -| `ZBOARD_JWT_SECRET` | 必填 | 足够强的登录令牌密钥 | -| `ZBOARD_CREDENTIAL_ENCRYPTION_KEY` | 必填 | 符合应用校验要求的凭证加密密钥,需备份 | -| `ZBOARD_HTTP_BIND` | `127.0.0.1` | 对宿主机开放的监听地址 | -| `ZBOARD_HTTP_PORT` | `8080` | 宿主机端口 | -| `ZBOARD_DATABASE_HOST_DIR` | `./data` | SQLite 宿主机持久目录,需配合 SQLite override | -| `ZBOARD_ZERO_ARTIFACT_HOST_DIR` | `./artifacts` | 受信任 Zero 制品,只读挂载 | -| `ZBOARD_MANAGED_RULE_HOST_DIR` | `./managed-rules` | 托管规则与编译产物,可写挂载 | -| `ZBOARD_ZERO_EVENT_SPOOL_HOST_DIR` | `./zero-events` | 节点事件暂存文件,可写持久目录 | -| `ZBOARD_DATABASE_MAX_OPEN_CONNECTIONS` | `8` | MySQL 连接池上限;SQLite 固定为 `1` | -| `ZBOARD_DATABASE_MAX_IDLE_CONNECTIONS` | `2` | MySQL 空闲连接上限;SQLite 固定为 `1` | -| `ZBOARD_DATABASE_CONNECTION_MAX_LIFETIME_SECONDS` | `3600` | 连接最长寿命,秒 | +## 1. 获取部署文件 -MySQL 的 DSN 形式为 `user:password@tcp(db:3306)/zboard?charset=utf8mb4&parseTime=True&loc=UTC`,替换账号、密码、数据库和地址。SQLite 使用 `/var/lib/zboard/data/zboard.db`,这是容器内路径,不是宿主机目录。 +从 [Releases](https://github.com/zerodenet/zboard/releases) 选择版本,以下以 `v0.0.1` 为例: -## 准备目录并启动 +```bash +git clone --branch v0.0.1 --depth 1 https://github.com/zerodenet/zboard.git +cd zboard/deploy/docker +cp .env.release.example .env.release +chmod 600 .env.release +``` + +部署文件和镜像使用同一个版本。选择其他版本时,需要同时修改检出标签和下面的镜像标签。 + +## 2. 填写配置 + +编辑 `.env.release`,按实际环境填写以下配置: + +```dotenv +ZBOARD_IMAGE_TAG=v0.0.1 +ZBOARD_PULL_POLICY=always +ZBOARD_HTTP_BIND=127.0.0.1 +ZBOARD_HTTP_PORT=8080 +ZBOARD_EXTERNAL_NETWORK=your_existing_docker_network +ZBOARD_DATABASE_DRIVER=mysql +ZBOARD_DATA_SOURCE='zboard:YOUR_DATABASE_PASSWORD@tcp(mysql:3306)/zboard?charset=utf8mb4&parseTime=true&loc=UTC' +ZBOARD_JWT_SECRET=YOUR_RANDOM_JWT_SECRET +ZBOARD_CREDENTIAL_ENCRYPTION_KEY=YOUR_RANDOM_ENCRYPTION_KEY +``` + +替换网络名、数据库账号、密码、地址和库名。示例中的 `mysql` 必须换成实际可访问的地址;容器里的 `127.0.0.1` 指向 ZBoard 容器自身,不是宿主机,也不是另一个 MySQL 容器。 -以下命令从 `deploy/docker` 执行,`.env.release` 需使用可被 shell 读取的赋值语法。先准备外部网络和持久目录: +下面的命令运行**两次**,分别生成 JWT 密钥和凭据加密密钥,填入对应配置项: ```bash -set -a -. ./.env.release -set +a -sh ./prepare-host-dirs.sh +openssl rand -hex 32 ``` -MySQL 使用发布文件: +这两个值在重启后应保持不变。凭据加密密钥用于解密已保存的节点凭据,需要在数据库之外另行备份。 + +将 `ZBOARD_BOOTSTRAP_ADMIN_EMAIL` 和 `ZBOARD_BOOTSTRAP_ADMIN_PASSWORD` 保持为空,稍后通过安装页面创建管理员。面板没有默认管理员密码。 + +## 3. 准备目录并启动 + +下面的命令使用 `.env.release` 中的默认宿主机目录。如果修改了目录位置,请先按[自定义目录说明](/projects/zboard/guides/storage-and-backups#required-host-directories)准备对应路径。 ```bash +sh ./prepare-host-dirs.sh +docker compose -f docker-compose.release.yml --env-file .env.release config --quiet +docker compose -f docker-compose.release.yml --env-file .env.release pull docker compose -f docker-compose.release.yml --env-file .env.release up -d +docker compose -f docker-compose.release.yml --env-file .env.release ps +``` + +在 Docker 所在主机上检查服务: + +```bash +curl --fail http://127.0.0.1:8080/readyz +curl --fail http://127.0.0.1:8080/api/v1/version ``` -SQLite 同时加载数据目录 override: +`/readyz` 检查面板的数据库连接,不能用它判断 Zero 节点是否安装完成、配置是否发布成功。 + +启动失败时查看日志: ```bash -docker compose -f docker-compose.release.yml -f docker-compose.sqlite.yml \ - --env-file .env.release up -d +docker compose -f docker-compose.release.yml --env-file .env.release logs --tail 100 zboard ``` -后续检查、停止和重建时使用相同的 Compose 文件组合及环境文件。通过反向代理提供 HTTPS;默认宿主机 loopback 绑定适合同机反向代理,需要跨容器或跨主机访问时按部署网络调整。 +常见原因包括必填环境变量为空、外部 Docker 网络不存在、数据库凭据错误,以及容器无法连接 MySQL。 + +## 4. 创建管理员 + +将主机上的反向代理配置为:HTTPS 域名请求转发到 `http://127.0.0.1:8080`。然后访问 `https://你的域名/setup`,完成站点设置并创建第一个管理员。 + +初始化后通过 `/login` 登录。站点安装完成后,不能再次通过安装页面创建管理员。 + +## 5. 配置第一条服务 + +1. **添加节点。** 在管理后台填写节点地址和 SSH 连接信息。 +2. **安装 Zero。** 在节点的内核管理中执行安装,等待安装与健康检查完成。 +3. **创建协议服务。** 选择节点和协议,配置监听地址、端口等参数,等待发布成功,并放行节点防火墙中的对应服务端口。 +4. **创建节点组和套餐。** 将协议服务加入节点组,再在套餐中选择该组。 +5. **开通用户订阅。** 使用基础订单流程,创建对应套餐的订单,由管理员确认后开通订阅。 +6. **连接客户端。** 在用户账户中选择对应的订阅格式,将订阅链接导入客户端;连接后回到控制台查看流量用量。 -## 验证安装 +需要转发节点或共享上游代理池时,继续阅读[网络前置指南](/projects/zboard/guides/network-fronting)。节点安装和故障恢复见[节点生命周期说明](/projects/zboard/reference/node-kernel-lifecycle)。 -1. 使用同一 Compose 组合运行 `ps`,检查 zboard 容器健康。 -2. 请求 `http://127.0.0.1:8080/readyz`,确认就绪和数据库连接;修改端口时同步修改地址。 -3. 访问站点,完成[首次初始化](./first-setup)。 -4. 核对管理员登录、站点公开地址和当前驱动,再接入节点。 +## 保存部署数据 -首次启动由后端建立和检查数据库结构,不手工插入迁移记录。已有数据要改数据库驱动时,使用[系统维护与数据库迁移](./maintenance),不要仅更改连接地址。 +数据库、`.env.release`、凭据加密密钥、托管规则和 Zero 事件队列都需要持久保存。使用支持插件的构建时,还需要保存插件目录。具体挂载位置和配套恢复要求见[存储与备份指南](/projects/zboard/guides/storage-and-backups)。 -## 持久化与升级 +如果需要从源码运行开发环境,请使用[本地开发指南](/projects/zboard/contributing/development)。 -数据库、凭证加密密钥、托管规则和事件存储共同组成恢复所需资料。受信任 Zero 制品目录保持只读,托管规则目录单独可写;不要为解决规则写入问题把所有制品改为可写。 +## 使用 SQLite + +SQLite 使用同一发布镜像,将 `.env.release` 中的数据库设置改为: + +```dotenv +ZBOARD_DATABASE_DRIVER=sqlite +ZBOARD_DATA_SOURCE=/var/lib/zboard/data/zboard.db +ZBOARD_DATABASE_HOST_DIR=./data +``` + +仍需填写发布 Compose 要求的外部网络,但不需要 MySQL 服务。使用默认目录时执行: + +```bash +ZBOARD_DATABASE_DRIVER=sqlite sh ./prepare-host-dirs.sh +docker compose -f docker-compose.release.yml -f docker-compose.sqlite.yml --env-file .env.release config --quiet +docker compose -f docker-compose.release.yml -f docker-compose.sqlite.yml --env-file .env.release up -d +``` -SQLite 备份应取得一致快照,或停止应用后备份完整数据目录。升级前保留原镜像和匹配的部署配置,升级后核对 `/readyz`、驱动、业务数据及节点事件恢复。 +后续查看状态、停止和重建均使用相同的两个 Compose 文件及环境文件。已有数据需要切换数据库时,使用[数据库迁移](./maintenance),不要只修改连接地址。 diff --git a/docs/projects/zboard/guides/network-fronting.md b/docs/projects/zboard/guides/network-fronting.md new file mode 100644 index 0000000..5c40367 --- /dev/null +++ b/docs/projects/zboard/guides/network-fronting.md @@ -0,0 +1,93 @@ +# 前置端口转发服务 + +管理员在「协议服务 → 创建协议服务」选择“实际协议监听”或“前置端口转发”。前置转发选择 A 的端口和 B 的现有协议作为父协议;A 只部署原始端口转发,不创建 B 的协议监听,客户端握手和认证始终在 B 完成。独立的“网络前置”菜单已合并,旧地址跳转到协议服务的前置转发分区。保存、编辑、停用和删除都会进入持久化节点发布队列;失败自动重试,列表展示排队状态与失败原因。 + +## 权限与订阅 + +前置线路是入口 A 与落地协议 B 的组合服务,可以独立加入节点组。节点组分别保存 `protocol_endpoint_ids`(直连协议服务)与 `network_entry_ids`(前置线路)。套餐继续选择节点组,用户仅获得该节点组明确分配的服务。 + +入口授权与落地授权必须分别显式给予。例如,节点组只勾选 A→B 时,仅保存入口使用范围,不创建或同步 B 的用户凭据。只有同一订阅的节点组还显式包含 B,才会为该订阅生成 B 的认证凭据,并交付 B 直连与已授权的 A→B。没有 B 凭据时,订阅导出会省略无法认证的协议节点,不生成假凭据或导出 B 的静态共享凭据。 + +B 承载用户鉴权和流量结算。凭据按订阅和已明确授权的落地协议生成;多个已授权入口可以复用同一落地凭据,流量倍率沿用 B。移除 B 授权后,即使 A 仍在组内,订阅投影和新的 B 运行配置也会排除旧凭据,协调任务会将旧凭据标为撤销。远端生效仍依赖配置成功发布,离线节点可能暂时保留旧运行配置。订阅模板中的策略组仅组织已授权节点,不能补齐落地权限。 + +前置线路只替换客户端连接地址和端口,保留 B 的 TLS、Reality、SNI 与传输参数;原本隐式采用 B 域名的证书校验名称会被显式保留。A 的代理路径凭据加密保存,仅下发 A,不回显到管理端,也不下发订阅。 + +A 或 B 配置尚在发布队列时,前置线路暂不下发;入口停用、A/B 离线或分配被移除时也不下发。不自动补发 B 直连。删除入口时在数据库事务内自动清理节点组关联;删除 A、B 或落地协议会清理相关入口,并为仍保留的 A 排队撤除配置。SSH 不可达不阻止本地删除。 + +### 升级现有入口 + +升级会创建独立的节点组入口关联表,不会把旧入口自动授权给所有能使用 B 的用户。现有入口的转发配置保留,管理员需要在前置服务编辑器的“关联节点组”或「节点组 → 前置线路」明确分配后才恢复订阅交付。B 已有的明确组成员保持原样,不根据入口关联新增 B 成员。若有历史版本产生的隐式 B 凭据,显式授权过滤会阻止其订阅交付与运行配置下发,凭据协调会将其撤销。 + +### 分发权限与连接隔离 + +A 是不鉴权的原始 TCP/UDP 转发器,不会单独认证用户。只授予 A 而未授予 B 时,B 拒绝用户属于预期行为。明确授予 B 的用户本来就有 B 的直连资格;同一凭据经过哪个透明入口,不构成新的权限边界。本功能不宣称能够禁止已授权 B 的用户手动换地址。 + +## 运行方式与兼容 + +A 是固定目标的原始 TCP/UDP 转发器,B 执行协议解密、用户鉴权和用户流量结算。A 的网络统计仍会记录传输量,但不产生 B 用户的第二份扣费。B 看到的网络源地址是转发路径的出口地址。 + +生成的 Zero 入站复用已有的 `udp.enabled`;全局和入站开关均默认开启: + +```json +{ + "tag": "entry-1", + "listen": {"address": "0.0.0.0", "port": 10000}, + "udp": {"enabled": true}, + "protocol": { + "type": "direct", + "target": "landing.example.com", "port": 443 + } +} +``` + +选择“TCP 与 UDP”时,入口 A 和面板本地的校验内核必须在 `build-info` 的 `protocol_capabilities` 中声明 `direct.inbound.udp.supported=true`。仅能解析 `udp.enabled` 不代表支持原始 UDP 转发;未声明能力时拒绝发布。选择“仅 TCP”时生成 `udp.enabled=false`,保留旧内核可用的 TCP 配置;Hysteria2 不允许选择仅 TCP。仅 TCP 的 Shadowsocks 前置线路在 Clash、sing-box 和 Zero 订阅中明确关闭 UDP,原始 SS 分享链接不能通用表达这个限制,导入后也只能用于 TCP。B 直连线路不受影响。旧内核的 `direct` 只接收 TCP;新内核默认同时监听 TCP 和 UDP。升级后需要仅 TCP 的现有入口应明确关闭 UDP;UDP 端口冲突会导致激活失败并回滚,不能静默降级为 TCP。B 不因前置功能本身需要升级。 + +目标使用 B 协议的对外地址和对外端口;A 的监听端口与对外端口可以不同,以支持已有端口映射。管理员应确保 A 的 TCP、UDP 端口以及 A 到 B 的路径可达。A 与 B 必须为不同节点,入口端口不能与 A 的协议、用户端口或其他入口重复。历史独立用户端口未迁移完成时拒绝创建,需先重新发布 B 完成统一协议端口迁移。 + +## 可选代理路径 + +前置服务默认“直连落地”,不需要代理池。需要代理时,在「节点资产 → A 的详情 → 共享代理池」创建池,再在服务的“A 到 B 的路径”选择“使用 A 的共享代理池”。池配置是节点内资源,只能由同一 A 上的入口引用。 + +::: info 代理池编辑说明的适用范围 +本页关于“回填配置、完整 RAW 转表单、读取已下发 RAW”的说明来自待发布的编辑器改动,尚不属于当前正式版或已推送的开发版。现有版本支持创建共享池及转发入口引用。 +::: + +新建时默认提供 URLTest 自动测速池。表单支持添加和编辑 Shadowsocks、VMess、VLESS、Trojan、Hysteria2、SOCKS5 或 Mieru 代理,以及测速组、选择组和多跳链路;可以修改成员顺序和最终出口 `target`。编辑已有池会回填原有节点、分组和凭据。点击“合并表单并查看 RAW”生成当前完整 Zero JSON;粘贴完整 JSON 后,可以直接保存,也可以点击“覆盖并转为表单”。覆盖前先检查标识、成员引用和循环,失败保留原表单。未展示的高级协议参数在表单往返时保持原样;仅在主动更换协议或连接安全类型时替换对应配置。这里只接受 Zero JSON,不导入 Clash/Mihomo YAML。 + +多条入口绑定各自的 B 协议端口,共用一份带 `pool-ID/` 前缀的代理图及测速状态,不会按入口复制 URLTest。每条入口独立生成匹配规则;选择直连的入口显式绕过池。代理池本身的内部认证不改变客户端协议在 B 完成握手和用户认证的事实。 + +更新池会排队发布 A,所有引用入口一起生效;删除仍被引用的池会被拒绝。删除节点资产时自动清理其入口、关联及自有代理池,不执行 SSH 或供应商清理。保存池配置经过 Zero 校验,替换配置还会检查所有已引用入口的 TCP/UDP 兼容性,失败回滚。凭据加密保存,不下发给订阅客户端,列表和保存响应也不返回配置。管理员打开编辑器时,通过独立、带审计的接口读取原始配置和凭据;响应禁止缓存。只修改名称时保留原配置,修改代理参数时携带完整配置和修订号保存。旧的入口独立代理路径保留兼容,可切换成共享池或直连。 + +完整路径示例: + +```json +{ + "outbounds": [ + {"tag":"hop1","protocol":{"type":"socks5","server":"proxy1.example.com","port":1080}}, + {"tag":"hop2","protocol":{"type":"shadowsocks","server":"proxy2.example.com","port":1080,"cipher":"aes-128-gcm","password":"replace-me"}} + ], + "outbound_groups": [ + {"tag":"chain","type":"relay","proxies":["hop1","hop2"]}, + {"tag":"auto","type":"url_test","outbounds":["hop1","hop2"],"interval_seconds":300} + ], + "target":"chain" +} +``` + +`url_test` 在多个候选出口中选择,`relay.proxies` 则按顺序组成多跳代理链,两者含义不同。保存时由 Zero 校验结构、协议字段与引用关系;具体出口还需要支持实际使用的 TCP/UDP 路径。在“TCP 与 UDP”模式下,已知不支持 UDP 的 HTTP CONNECT、VLESS Vision 和以 SOCKS5 为最后一跳的 UDP 代理链会被明确拒绝;仅 TCP 模式允许由内核校验通过的 TCP 路径。每个共享池的出口和组标签只添加一次池前缀;旧的独立路径仍按入口隔离。 + +编辑时省略 `proxy_pool_id` 保留原池,提交 `0` 清除引用;直连时同时提交 `path_config: {}`。`parent_protocol_id` 是 B 的实际协议 ID,兼容旧字段 `endpoint_id`。`node_group_membership_changes` 与服务本身原子保存并创建凭据协调任务。代理池 API 是 `/api/v1/admin/node-proxy-pools`,GET 必须传 `node_id`,POST/PUT 配置字段为 `config`,保存响应不含凭据;更新携带 `revision`。管理员 `GET /api/v1/admin/node-proxy-pools/{id}/config` 返回 `pool` 元数据、原始 `config` 和带节点内标识前缀的 `compiled` 编译预览。编译预览不代表已下发。前置服务 API 继续使用管理员身份访问 `/api/v1/admin/network-entries`,更新携带当前 `revision` 防止覆盖其他管理员的修改。 + +前置线路保留 B 的 TLS/SNI/Reality 身份,以及 WebSocket 和 HTTP/2 的 Host;仅连接地址和端口替换为 A,避免长域名、CDN 或证书校验随入口地址发生变化。UDP 为每个客户端建立独立转发会话,避免多个用户向同一 B 协议发送数据时回包混淆。 + +## 回归验证 + +`TestSharedPoolCompilesOnceAndDirectEntryBypassesIt` 检查同池多个端口只生成一份代理图、直连规则绕过池、跨节点引用被拒绝及凭据不回显。`TestNetworkEntryRealTCPUDPForwarding` 在设置 `ZBOARD_ZERO_VALIDATE_BIN` 时运行真实 Zero,覆盖 TCP/UDP 原始转发、多跳路径、共享池,以及 B 的 VLESS 正确/错误凭据在直连和共享池路径上的认证结果;A 配置不得包含 B 的 VLESS 监听或用户 ID。 + +本地发布版 Zero 已通过 VLESS 认证用例。当前本地调试版在默认线程栈下运行该认证用例会栈溢出,设置 `RUST_MIN_STACK=16777216` 后通过;这项环境差异保留记录,本次未修改 Zero 内核。 + +### 代理池编辑与已下发 RAW(待发布) + +编辑器中的“读取节点配置”调用管理员接口 `GET /api/v1/admin/node-proxy-pools/{id}/runtime`,使用已保存的节点 SSH 配置读取 `/etc/zerodenet/current.json`。响应包含来源、读取时间、节点配置 SHA-256,以及此池的代理、分组和关联转发入口/路由;不返回其他代理池和监听协议的凭据。它是节点当前发布文件的快照,不是内核内存状态查询。 + +此读取有 20 秒超时和 8 MiB 文件上限,并记录审计、禁止缓存。节点无法连接或文件无法读取时明确报错,不用编译预览替代真实快照,也不影响当前编辑草稿。没有启用入口引用、或尚未发布时,快照可能不包含此池,此时 `present` 为 `false`。保存后要等节点发布成功,再重新读取快照。 diff --git a/docs/projects/zboard/guides/node-cleanup.md b/docs/projects/zboard/guides/node-cleanup.md new file mode 100644 index 0000000..a02227e --- /dev/null +++ b/docs/projects/zboard/guides/node-cleanup.md @@ -0,0 +1,68 @@ +# 面板删除与节点清理 + +删除面板记录和清理远端机器是两个独立操作。面板删除不连接 SSH、不请求 CA 或供应商 API;过期凭据和网站外部依赖故障不能阻止本地删除。 + +## 删除范围 + +| 删除对象 | 数据库内完成的清理 | 远端行为 | +|---|---|---| +| 节点资产 | 节点、协议、用户协议凭据、前置入口、代理池、节点组关联、证书/DNS 管理记录与运行投影 | 不停机、不删除机器文件、不修改供应商 DNS | +| 协议服务 | 协议及凭据、节点组和证书关联、以它为落地的前置入口 | 为相关存活节点排队撤除配置,失败重试 | +| 前置入口 | 入口及节点组关联 | 为 A 排队撤除端口转发,不移除 B 的显式权限 | +| 托管证书 | 面板证书记录、协议关联、面板自动续期 | 保留证书、私钥、Certbot 续期配置,不向 CA 撤销证书 | +| DNS 记录 | 面板管理记录 | 保留供应商的真实解析 | +| 供应商账户 | 面板凭据及关联 DNS 管理记录;解除证书账户关联、关闭自动续期 | 保留供应商账户、Token 和远端资源 | + +删除节点 B 会同时移除以 B 协议为目标的前置服务,但保留入口机器 A;A 的撤除配置进入队列。删除 A 不删除 B 或 B 的明确授权。历史流量、订单、订阅、行政任务和审计事实保留,运行配置、凭据与运维明细按其所属资源清理。 + +数据库关联清理失败时事务回滚。仍在执行的安装、发布、签发、同步任务需要串行完成,避免它们与删除并发重建资源;这与 SSH 是否可达无关。删除后远端旧配置可能继续工作,尤其离线节点不能即时撤销已加载的用户凭据。 + +## 网站不可用时的停机与卸载 + +脚本源文件是 `backend/internal/nodecleanup/cleanup-zero-node.sh`,不包含站点地址、Token、SSH 密码,也不需要连接面板或 GitHub。新构建的二进制发布包同时携带 `cleanup-zero-node.sh`。面板的「节点资产 → 内核与运维」提供下载;以后通过面板安装或更新 Zero 时也会安装到 `/usr/local/sbin/zboard-zero-cleanup`。既有节点不会因为本地代码修改而自动获得脚本,需要先下载复制或更新安装。 + +在 Linux/systemd 节点上使用 POSIX Shell(`/bin/sh`),需要系统常用工具 `systemctl`、`timeout`、`readlink` 等,不依赖 Python 或 Bash。默认只查看状态: + +```sh +sh cleanup-zero-node.sh status +``` + +关闭 Zero 及现有连接,禁止开机自动启动;保留配置、安装文件和待上报事件: + +```sh +sudo sh cleanup-zero-node.sh stop --yes +# 已安装到节点时: +sudo /usr/local/sbin/zboard-zero-cleanup stop --yes +``` + +普通停机最多等 15 秒,仍未停止时向该 systemd 服务发送 KILL;同时检查使用 `/usr/local/bin/zero` 和 `/etc/zerodenet/current.json` 的手动启动进程。不会通过模糊进程名杀掉其他程序。停止或状态核验失败时,脚本返回非零状态,不继续删除文件。 + +彻底卸载需要明确选择,先停止服务,再清理以下托管路径: + +```sh +sudo sh cleanup-zero-node.sh uninstall --yes +``` + +- `/etc/systemd/system/zero.service` 和同名 drop-in 目录 +- `/usr/local/bin/zero` +- `/etc/zerodenet`:环境凭据、当前配置和配置代际 +- `/var/lib/zerodenet`:备份与事件 outbox,删除会永久丢弃尚未上报的数据 +- `/run/zerodenet`:控制 socket 等运行文件 + +卸载后保留清理工具本身。`--help` 和卸载完成提示会显示自移除说明。确认清理完成、不再需要工具时,手动删除安装的脚本: + +```sh +sudo rm -- /usr/local/sbin/zboard-zero-cleanup +``` + +手动上传的副本按实际上传路径删除,例如 `rm -- ./cleanup-zero-node.sh`。删除脚本本身不会停止或卸载 Zero。 + +证书文件默认保留;明确需要时添加 `--certificates`,另外清理 `/etc/zboard/certificates` 和 Certbot 中名称严格匹配 `zboard-数字` 的 live/archive/renewal 资料。不会删除其他证书、共享 ACME 账户、Webroot、系统 Python、SSH、BBR 配置,也不修改 Cloudflare DNS 或在线撤销 CA 证书。 + +脚本拒绝托管根路径或父目录的符号链接跳转,也拒绝清理配置不属于上述托管实例的 `zero.service`。遇到非标准安装,应先核对真实路径,不要使用全局 `pkill zero` 或删除整个 `/etc/letsencrypt`。 + +## 面板失联与重试 + +本地核对的 Zero 源码中,Webhook 单次请求默认 10 秒超时,重试间隔按 4、8、16、32、64 秒退避,上限 64 秒;内存待投递数量默认 4096,outbox 有磁盘剩余空间保留限制。默认可持续重试,因此网站长期不可用时进程及待上报数据会继续存在,但源码并不是无间隔的无限重试循环。 + +这些是当前本地源码的默认值,不证明远端运行版本、配置或 CPU 状态正常。没有运行现场证据,不能认定连接残留一定会引起 CPU 雪崩。已废弃节点的确定性处理是执行上述停机命令;不应让面板删除依赖能否登录这台机器,也不应把面板暂时断线直接当作卸载全部代理服务的指令。 diff --git a/docs/projects/zboard/guides/plans-and-orders.md b/docs/projects/zboard/guides/plans-and-orders.md index 693780e..d59fea8 100644 --- a/docs/projects/zboard/guides/plans-and-orders.md +++ b/docs/projects/zboard/guides/plans-and-orders.md @@ -23,11 +23,11 @@ ## 管理员分配与确认订单 -当前 main 尚未接入在线支付。管理员可在订单管理中为指定的启用用户选择 SKU,按需指定目标订阅、调整应付金额,并填写分配原因。保存生成待付款订单,不直接发放权益;完成线下核实后,再执行管理员付款确认。 +ZBoard 核心提供基础订单和权益管理,在线支付渠道由插件扩展。管理员可在订单管理中为指定的启用用户选择 SKU,按需指定目标订阅、调整应付金额,并填写分配原因。保存生成待付款订单,不直接发放权益;完成线下核实后,再执行管理员付款确认。 分配操作保存管理员、原价、应付金额与原因,并通过操作标识防止重试重复创建。相同标识不能用于不同分配内容。确认后检查订阅权益及节点发布状态;已支付订单的重复确认不会再次发放权益或重复排队发布。 -在线支付渠道和插件运行时属于后续能力,不能把“待付款 → 管理员确认”的现有流程描述成已完成支付平台集成。 +支付业务接口尚未在插件宿主中开放。基础版可由管理员确认订单;扩展能力和适用版本见[插件说明](../plugins/)。 ## 续费、切换与加购 diff --git a/docs/projects/zboard/guides/storage-and-backups.md b/docs/projects/zboard/guides/storage-and-backups.md new file mode 100644 index 0000000..0c519a9 --- /dev/null +++ b/docs/projects/zboard/guides/storage-and-backups.md @@ -0,0 +1,110 @@ +# Docker storage and backups + +For a first installation, start with the [English guide](/projects/zboard/guides/installation-en) or [首次安装指南](/projects/zboard/guides/installation). This page covers persistent directories, database storage options, and backup/restore. + +## Required host directories + +Zboard uses two different storage trust boundaries under `/var/lib/zboard/artifacts`: + +- `ZBOARD_ZERO_ARTIFACT_HOST_DIR` contains trusted Zero binaries and checksum files. It is mounted read-only. +- `ZBOARD_MANAGED_RULE_HOST_DIR` contains managed Zero Rule IR sources and compiled ZRS artifacts. It is mounted read-write and must persist across container replacement. + +Prepare both directories before the first deployment: + +```bash +cd deploy/docker +sh ./prepare-host-dirs.sh +``` + +Custom locations can be supplied through the same environment variables used by Compose: + +```bash +ZBOARD_ZERO_ARTIFACT_HOST_DIR=/srv/zboard/artifacts \ +ZBOARD_MANAGED_RULE_HOST_DIR=/srv/zboard/managed-rules \ +sh ./prepare-host-dirs.sh +``` + +MySQL deployments use only the base Compose file and do not create or mount a +SQLite data directory. For SQLite, set `ZBOARD_DATABASE_DRIVER=sqlite`, set +`ZBOARD_DATA_SOURCE=/var/lib/zboard/data/zboard.db`, optionally set +`ZBOARD_DATABASE_HOST_DIR`, and include the SQLite override: + +```bash +set -a +. ./.env.release +set +a +sh ./prepare-host-dirs.sh +docker compose \ + -f docker-compose.release.yml \ + -f docker-compose.sqlite.yml \ + --env-file .env.release \ + up -d +``` + +The preparation script creates an empty `rules/` mount point under the read-only artifact directory. Compose then overlays that path with the separate writable managed-rule directory. + +## Mount layout + +```text +/var/lib/zboard/artifacts read-only trusted artifacts +└── rules writable child bind mount + └── + ├── source.json canonical internal Zero Rule IR + └── artifacts// compiled client artifacts +``` + +Do not make the complete artifact directory writable. Managed rules are the only application-generated data below this path. + +Blue and green application instances must mount the same `ZBOARD_MANAGED_RULE_HOST_DIR`. Using separate directories causes database metadata and rule files to diverge after a traffic switch. + +## Backup and restore + +The database stores managed-rule metadata and revisions, while the canonical source and compiled ZRS files live in `ZBOARD_MANAGED_RULE_HOST_DIR`. A recoverable backup therefore requires both: + +1. a consistent Zboard database backup; +2. an archive or snapshot of `ZBOARD_MANAGED_RULE_HOST_DIR` from the same backup window. + +Example filesystem backup: + +```bash +managed_rule_dir=${ZBOARD_MANAGED_RULE_HOST_DIR:-./managed-rules} +tar -C "$(dirname "$managed_rule_dir")" \ + -czf "zboard-managed-rules-$(date -u +%Y%m%dT%H%M%SZ).tar.gz" \ + "$(basename "$managed_rule_dir")" +``` + +Restore the database and managed-rule directory as one recovery unit before starting Zboard. Restoring only the database leaves rule records whose source and ZRS artifacts are missing; restoring only the directory can reintroduce files for revisions that the database no longer references. + +## Deployment verification + +Render the Compose configuration before applying it: + +```bash +docker compose -f docker-compose.release.yml --env-file .env.release config +``` + +For SQLite, render both files together: + +```bash +docker compose \ + -f docker-compose.release.yml \ + -f docker-compose.sqlite.yml \ + --env-file .env.release \ + config +``` + +After startup, verify that the parent directory is protected and the child directory is writable: + +```bash +docker compose -f docker-compose.release.yml --env-file .env.release exec zboard sh -c ' + test ! -w /var/lib/zboard/artifacts || exit 1 + touch /var/lib/zboard/artifacts/rules/.write-test + rm /var/lib/zboard/artifacts/rules/.write-test +' +``` + +## Plugin persistence + +The service mounts `ZBOARD_PLUGIN_HOST_DIR` (default `./plugins`) at `/var/lib/zboard/plugins`. `prepare-host-dirs.sh` creates this private directory. Back it up together with the database and credential encryption key. Add publisher public keys and the optional signed catalog URL to a custom ZBoard YAML file and mount it read-only at `/app/etc/zboard.yaml`; see [plugin setup](/projects/zboard/plugins/development). + +Only one instance per database owns plugin execution. Standby instances keep core APIs available but do not issue plugin page sessions or execute plugin operations. Stop the owner and restart the replacement to transfer plugin execution; an ungraceful exit requires the one-minute lease to expire. This does not implement active-active plugin routing. diff --git a/docs/projects/zboard/guides/subscription-filtering.md b/docs/projects/zboard/guides/subscription-filtering.md new file mode 100644 index 0000000..f062389 --- /dev/null +++ b/docs/projects/zboard/guides/subscription-filtering.md @@ -0,0 +1,67 @@ +# Public subscription filtering + +Every public subscription URL is bound to exactly one subscription. Query parameters can derive read-only client views from that subscription without changing its authorization boundary: + +```text +/api/v1/client/subscription/{subscription-token} + ?template=clash + &protocol=vless,hysteria2 + ®ion=jp,hk + &tag=premium,streaming + &exclude_tag=maintenance + &plan=pro + &sku=pro-annual + &node_group=jp-premium + &q=日本 +``` + +## Authorization boundary + +Filtering is an output projection, not an authorization mechanism: + +1. Zboard resolves the token to one `subscription_id` and verifies that the same user owns both records. +2. Zboard verifies that exact subscription is active, unexpired, and has remaining traffic. +3. `plan`, `sku`, and `node_group` may remove that source, but they cannot select a different subscription owned by the same account. +4. Zboard resolves protocol endpoints and credentials only from the token-bound subscription. +5. Endpoint filters reduce that authorized endpoint set again. +6. The result is ordered by the configured protocol delivery order and sent to the selected renderer. + +A filter can only remove endpoints authorized by the token-bound subscription. It cannot add a node group, endpoint, credential, plan, SKU, or another subscription. + +The `Subscription-Userinfo` response header and manifest quota metadata always describe the token-bound subscription only. Traffic totals and expiry are never accumulated across the account. + +## Account access API + +Authenticated users manage credentials from the target subscription: + +| Method | Path | Meaning | +| --- | --- | --- | +| `GET` | `/api/v1/account/subscriptions/{id}/access` | Read or lazily provision the link for one active subscription | +| `POST` | `/api/v1/account/subscriptions/{id}/access/rotate` | Replace only that subscription's token | +| `DELETE` | `/api/v1/account/subscriptions/{id}/access` | Revoke only that subscription's token | + +The legacy account-level `/api/v1/subscription/access` routes are removed. Existing aggregate tokens without a `subscription_id` are invalidated during schema reconciliation, and one independent token is provisioned for each usable subscription. + +## Parameters + +| Parameter | Meaning | Matching | +| --- | --- | --- | +| `template` | Existing renderer slug or `native` | Exact | +| `plan` | Stable plan slug | OR within the parameter | +| `sku` | Stable plan SKU code | OR within the parameter | +| `node_group` | Stable node-group code | OR within the parameter | +| `protocol` | Supported protocol code | OR within the parameter | +| `region` | Node region | OR within the parameter | +| `tag` | Structured protocol-service tag | Any requested tag | +| `exclude_tag` | Structured protocol-service tag to remove | Any match excludes | +| `q` | Protocol-service name keyword | Case-insensitive substring | + +Different dimensions use AND semantics. Values can be supplied as comma-separated items or repeated query parameters. Values are normalized, deduplicated, and bounded in count and length. + +Malformed stable codes, unsupported protocol values, overlong values, and control characters return HTTP 400. A valid filter that removes the token-bound source or matches no endpoint returns a valid empty subscription while keeping that subscription's quota metadata and `Cache-Control: no-store`. + +## Delivery order and service state + +All native renderers consume the same ordered manifest. The administrator's protocol delivery order remains authoritative within the token-bound subscription. Endpoint identity is only a deterministic tie-breaker. + +`ProtocolEndpoint.is_active` is the protocol-service delivery switch. Disabled services are removed from public subscription output and from node runtime publication; re-enabling the service restores both through the existing runtime publish flow. diff --git a/docs/projects/zboard/index.md b/docs/projects/zboard/index.md index b4bea71..918f9f0 100644 --- a/docs/projects/zboard/index.md +++ b/docs/projects/zboard/index.md @@ -1,79 +1,31 @@ -# Zboard +# ZBoard -::: info 文档对应版本 -本轮使用说明按 2026-09-09 的 [main 提交 e1b7246c](https://github.com/zerodenet/zboard/tree/e1b7246cc4ef805bf39b22d634ba209114eb3b14)核对。已公开 [0.0.1 正式版](https://github.com/zerodenet/zboard/releases/tag/v0.0.1);源码、发布与安装验收范围见[实现与文档进度](/progress)。实际能力以所用制品及运行时响应为准。 -::: +ZBoard 是面向 Zero 代理服务的自托管基础面板,提供节点、协议、用户、订阅、基础订单和流量管理。在线支付及其他超出基础管理范围的能力通过插件按需实现。 -Zboard 是代理服务运营管理平台,用于管理 VPS、协议服务、节点组、商品、订单、订阅、配置交付、流量、DNS 和证书。 +管理员配置节点和访问权限,用户在账户中心获取订阅、查看用量和提交工单。Zero 运行在节点上,负责代理连接与流量处理。 -## 开始使用 +## 从这里开始 -1. 阅读[部署指南](./guides/installation)。 -2. 完成[首次初始化](./guides/first-setup),确认站点、系统时区和历史保留策略。 -3. 接入基础设施并配置[节点与协议服务](./guides/node-management)。 -4. 根据客户端和商业模型配置[订阅交付与流量](./guides/subscriptions-and-traffic)。 -5. 需要自动维护域名和 TLS 时配置[DNS 与证书](./guides/dns-and-certificates)。 +| 你想做什么 | 阅读入口 | +| --- | --- | +| 安装自己的面板 | [安装教程](./guides/installation) | +| 创建管理员和站点 | [首次初始化](./guides/first-setup) | +| 接入节点并提供订阅 | [节点管理](./guides/node-management) → [协议服务](./guides/protocol-services) → [订阅配置](./guides/subscriptions-and-traffic) | +| 安装按需扩展 | [插件市场](./plugins/marketplace)与[登录插件](./plugins/login) | +| 开发或改进 ZBoard | [参与项目](./contributing/) | -## 核心能力 +## 基础能力 -- 管理 VPS 资产、供应商账号、SSH 凭证、Zero 安装升级和节点生命周期; -- 将 Zero 内核健康、Connector 事件在线和业务流量活跃作为不同运行事实分别观测; -- 管理前置 TCP/UDP 转发与节点共享代理池,分别授权入口线路和落地协议; -- 使用持久化发布队列重试配置交付;本地资源删除与远端停机清理分别执行; -- 为指定用户分配待付款订单,调整应付金额并保留原因,管理员确认后开通权益; -- 管理 VLESS、VMess、Shadowsocks、Trojan、Hysteria2、Mieru 等协议服务; -- 为 VLESS/VMess 配置 TCP、WebSocket、gRPC 和受支持的 TLS/REALITY 组合; -- 生成面向 ZNet Sink、Clash/Mihomo、sing-box 的完整配置订阅,以及 Shadowrocket、Quantumult X、v2rayN 节点订阅; -- 管理用户、套餐、订单、订阅、流量额度和计费倍率,并提供后端聚合的流量历史与趋势; -- 通过管理 Dashboard 查看收入/订单、订阅生命周期、活跃连接、当前待处理事件和基础设施健康; -- 管理 Cloudflare DNS 记录以及 HTTP-01、DNS-01 证书签发与续期; -- 使用 IANA 系统时区统一运营时间和业务日历读取,并配置审计/运营历史保留周期; -- 接收节点运行事件并进行运营审计。 +- **节点与协议**:管理服务器、SSH 连接、Zero 安装和配置发布,配置 VLESS、VMess、Shadowsocks、Trojan、Hysteria2 和 Mieru 服务。 +- **访问与订阅**:通过节点组关联套餐和用户权益,生成 Zero、Clash/Mihomo、sing-box 等客户端配置。 +- **网络前置**:使用转发入口访问落地服务,并按需共用节点代理池。 +- **基础订单与流量**:管理订单、订阅有效期和流量额度,查询用量和任务结果。 +- **账户与支持**:提供用户账户、公告、工单和审计记录。 -```text -节点资产 → 协议服务 → 节点组 → 套餐 / SKU → 订单 → 订阅 - ↘ DNS 记录 / 托管证书 ↗ -``` +具体协议是否可用取决于节点安装的 Zero 内核。连接节点、发布配置与客户端实际连接是不同步骤,首次使用可按[用户指南](./guides/)逐项完成。 -协议服务配置、节点实际发布状态和订阅交付状态分别记录。节点完成配置验证、激活和内核健康检查后,可以独立判断 Connector 是否恢复;没有当前用户流量并不等于节点不可用。 +## 选择版本 -## 运营面板 - -管理 Dashboard 以 `today`、`7d`、`30d` 三个时间范围提供后端聚合的运营读模型,主要包括: - -- 已支付净收入、订单和新购/续费构成; -- 新订阅、当前有效订阅以及即将到期/额度耗尽状态; -- 当前有活跃 Principal flow 的订阅和活跃连接总数; -- 所选区间的计费流量和业务趋势; -- 当前未解决的 Connector 离线、最新协议发布失败和待管理员处理工单; -- SSH、Connector、流量凭据、协议服务等基础设施准备度。 - -历史失败任务、已经被后续成功覆盖的发布失败和普通待支付订单不会继续被当成当前待处理事故。多币种区间也不会被强行相加成一个虚假的收入值。 - -Dashboard 的日期边界遵循系统设置中的 IANA `system_timezone`。底层记录仍保存为 UTC 绝对时间。 - -## 系统运营 - -管理员可以在系统设置中维护时区和运营历史保留策略。默认审计日志保留 180 天,已结束的运营历史和任务历史默认保留 90 天;`0` 表示永久保留。 - -“设置 → 关于 ZBoard”提供当前 Zboard 版本、发布通道、后端启动时间/运行时长、首次安装时间、MPL-2.0 开源许可和项目资源入口。版本和运行信息来自管理员专用系统信息接口,不通过公开系统信息接口暴露。 - -## 文档入口 - -- [用户指南](./guides/) -- [部署指南](./guides/installation) -- [首次初始化](./guides/first-setup) -- [节点与协议服务管理](./guides/node-management) -- [协议服务配置](./guides/protocol-services) -- [订阅交付与流量展示](./guides/subscriptions-and-traffic) -- [DNS 与证书管理](./guides/dns-and-certificates) -- [参与 Zboard](./contributing/) - -## 日常操作入口 - -- [后台导航与日常运营](./guides/daily-operations) -- [套餐、订单与用户交付](./guides/plans-and-orders) -- [公告、注册验证与邮件](./guides/announcements-and-email) -- [系统维护与数据库迁移](./guides/maintenance) +[0.0.1](https://github.com/zerodenet/zboard/releases/tag/v0.0.1) 是首个公开版本。下载入口见 [Releases](https://github.com/zerodenet/zboard/releases)。插件运行时和市场在后续开发版本中提供,对应说明会标注适用范围。 diff --git a/docs/projects/zboard/plugins/development.md b/docs/projects/zboard/plugins/development.md new file mode 100644 index 0000000..69db4ce --- /dev/null +++ b/docs/projects/zboard/plugins/development.md @@ -0,0 +1,137 @@ +# 插件开发与运维 + +本期提供插件市场、插件管理、离线导入、三个前后台页面范围,以及可选服务端配置进程和身份提供方登录。核心业务所有权见 [设计](/projects/zboard/reference/plugin-system-design)。 + +## 配置宿主 + +在现有 ZBoard YAML 配置中添加: + +```yaml +plugins: + directory: /var/lib/zboard/plugins + catalog_url: "" + trusted_publishers: + example.publisher: "BASE64_ED25519_PUBLIC_KEY" +``` + +公钥为 32 字节 Ed25519 公钥的标准 Base64 文本。默认没有可信发布者,因此无法安装包;只配置经过运营者确认的公钥。目录默认是当前工作目录的 `data/plugins`,应显式配置为持久卷中的独立目录。支持 `ZBOARD_PLUGIN_DIRECTORY`、`ZBOARD_PLUGIN_CATALOG_URL` 覆盖;可信发布者通过配置文件维护,修改后重启宿主。 + +安装、启停及配置插件无需重启 ZBoard。一个数据库只允许一个活动插件宿主。其他 ZBoard 实例保留核心服务,但不会获得插件执行或页面会话资格;停机切换后重启接管实例,意外退出需先等待最多一分钟租约到期。插件运行目录或初始化失败只使插件接口不可用,不停止核心控制台。备份数据库、插件目录和既有凭证加密密钥。使用容器时把插件目录放在持久卷内,服务插件二进制需要匹配容器平台并具备执行权限。 + +服务插件为受信任原生代码;进程隔离不等于 OS 沙箱。该版本不提供任意第三方二进制的安全执行环境,也不向插件开放核心业务写入能力。 + +## 管理流程 + +能力准入、私有存储、升级迁移与清除策略由宿主保证,见 [插件宿主生命周期](/projects/zboard/plugins/governance)。管理页只展示范围、状态和结果,没有人工授权或手工迁移步骤。 + +后台“扩展中心”包含插件管理和插件市场。插件管理列表用于搜索、筛选和启停;“查看详情”进入独立页面,按“概览 / 版本管理 / 操作记录”查看信息,操作记录每页展示 10 条最近记录。列表筛选保存在 URL 中,浏览器返回时恢复。 + +“配置插件”进入独立配置页,优先显示插件自带的配置界面;“高级 JSON 配置”在弹窗中编辑完整配置,保存使用打开弹窗时读取的配置修订号,避免覆盖并发变更。离开有未保存内容的 JSON 编辑器时会提示确认。 + +离线导入在弹窗中选择签名 `.zbplugin` 文件,再点击“验证并导入”,不会在选择文件时安装。成功后进入插件详情;首次安装保持停用,升级保持原启停状态。失败信息留在弹窗中供修正、重试。文件最大 32 MiB;展开最多 64 MiB、512 个内容文件。签名或结构校验失败不会执行插件;候选运行时校验失败不会提交安装或切换现有实例。 + +导入时由宿主自动检查兼容性、发布者和范围,完成数据初始化或迁移。首次安装保持停用,必要时保存业务配置后启用。纯页面插件不启动服务进程。服务插件测试使用已保存配置;停用状态下测试临时启动诊断进程,测试完成即退出。 + +升级直接导入同 ID、同发布者的新版本,宿主准备候选数据和进程,成功后原子切换并保留原启停状态;失败则保留旧版本、数据和实例。未声明测试当前宿主的升级包要求先停用。详情中可在停用后切换已保留版本,宿主检查数据及已有配置,切换后保持停用。每插件最多 30 个不同包版本,安装记录最多 200 条,包括卸载墓碑;当前没有自动历史清理接口。 + +卸载由宿主停止运行、撤销会话并删除程序与页面,保留配置、私有数据和记录。详情中的“清除保留数据”仅在卸载后可用,统一清理该插件的配置、私有数据及版本状态,不影响核心用户、身份绑定、订单或凭证。再次安装需重新导入包。配置 JSON 完整替换旧配置,秘密值不回显;请保留安全的原始配置来源。 + +## 创建并签名离线包 + +从仓库根目录运行,需要仓库要求的 Go 工具链: + +```sh +mkdir -p /tmp/zboard-plugin-demo +go -C backend run ./tools/pluginpackager -keygen /tmp/zboard-plugin-demo/publisher.key +go -C backend run ./tools/pluginpackager \ + -source ../examples/plugins/welcome \ + -key /tmp/zboard-plugin-demo/publisher.key \ + -key-id example.publisher \ + -out /tmp/zboard-plugin-demo/welcome.zbplugin +``` + +将 `publisher.key.pub` 的内容加入宿主可信发布者配置。私钥留在发布者机器,不上传 ZBoard,不放进插件源目录。示例在 public/account/admin 各贡献一个页面,用于验证动态导航、资源加载及宿主上下文桥。包源目录只允许 `manifest.json`、`ui/` 和 `runtimes/` 中的普通文件;打包器生成文件摘要与 `signature.json`。 + +manifest 示例见 [welcome/manifest.json](https://github.com/zerodenet/zboard/blob/37d1716c466ce16a1198399ac91c9de3e2c0c4f8/examples/plugins/welcome/manifest.json)。各字段: + +| 字段 | 含义 | +| --- | --- | +| schema_version | 固定为 1 | +| id/version | 稳定插件 ID / 严格 SemVer | +| requires.zboard | 可选的宿主发行版本参考范围,仅作提示,不作为安装、启用或升级门槛 | +| requires.tested_zboard_versions | 发布者明确验证过的宿主版本,仅描述测试覆盖 | +| requires.plugin_protocol / ui_bridge | 当前均为 1 | +| capabilities | 接受 zboard.ui.page.v1、zboard.config.v1、zboard.identity.provider.v1、zboard.storage.v1 | +| surfaces | public、account、admin 中的子集;服务端插件可为空 | +| components.ui | 各范围对应的 ui/ 内 HTML 入口 | +| components.server.executables | 平台到 runtimes/ 内二进制的映射,例如 linux-amd64 | +| contributions.pages | 页面 ID、标题、surface,以及 business 或 configuration 用途 | +| files | 所有内容文件的 SHA-256;由打包器生成 | + +配置页面必须声明 admin 和 config 能力。未知能力或字段、路径穿越、软链接、重复路径、未声明文件和摘要不一致均拒绝。签名覆盖包中 manifest 的原始字节,修改空白也需重新签名。包内不接受独立目录条目,使用仓库打包器生成标准 ZIP。 + +## 页面桥 + +iframe 从 URL fragment 读取 `bridge_token`,通过 `parent.postMessage` 发送: + +```js +parent.postMessage({ + source: 'zboard-plugin-ui', + bridge_token, + request_id: 'context-1', + type: 'context.load' +}, '*') +``` + +宿主响应 `source: 'zboard-plugin-host'`、同一 bridge_token/request_id、`ok` 以及 `result` 或通用 `error`。插件也应校验 `event.source === parent` 和桥令牌。可发送 `ui.resize`(height 320–1000)及 `plugin.ready`。并发最多 8 个请求,ID 最多 80 个字母/数字/下划线/连字符,配置最长 64 KiB。 + +`context.load` 仅返回当前 plugin_id、page_id 和 surface;configuration 用途页面额外支持 config.load、config.save(revision/config)、config.test。config.load 只返回 revision/configured。iframe 没有宿主登录令牌,也不能请求网络、提交表单、访问宿主 DOM 或导航顶层窗口。JS/CSS/图片应放在包内并用相对路径引用。 + +每个资源会话有效十分钟。页面关闭时撤销,停用或版本变化后立即拒绝后续请求;已打开容器通过每十五秒的可见性检查移除失效页面。会话过期后使用“重新加载”。 + +## 服务端 SDK + +公开 Go SDK 位于 `backend/pkg/pluginapi/v1`,协议源是 `control.proto`。服务实现 `PluginControlServer` 的 GetInfo、Health、ValidateConfig、ApplyConfig、TestConfig,用 `pluginapi.Serve` 启动。可运行实现见 [测试服务](https://github.com/zerodenet/zboard/blob/37d1716c466ce16a1198399ac91c9de3e2c0c4f8/backend/internal/plugins/testdata/server/main.go)。 + +宿主使用 go-plugin gRPC/mTLS,限制 RPC 报文和启动/配置超时;每次从已验签包复制当前平台二进制到独立工作目录,不继承宿主环境。GetInfo 必须与 manifest 的 ID、版本和能力一致。配置验证应返回规范化 JSON 对象,ApplyConfig 必须以 revision 幂等,禁止在配置应用或测试中执行业务扣款、修改核心凭证等副作用。标准输出和错误不会直接进入管理页面。 + +该 SDK 当前没有宿主业务回调、通用命令、事件订阅或 KV API;需要业务扩展时先在核心设计专用能力,未开放能力不能通过自定义方法绕过。 + +身份提供方插件额外实现 ListIdentityProviders、GetIdentityProvider 和 ExchangeIdentity。完整注册、绑定、回调与核心会话契约见 [插件身份提供方](/projects/zboard/plugins/identity-reference)。认证能力不向 iframe 桥开放。 + +## 提供市场目录 + +将插件包发布到公开 HTTPS 的 443 端口地址。市场地址与下载地址不允许查询参数、重定向、内网地址或环境代理。目录内容示例: + +```json +{ + "schema_version": 1, + "expires_at": "2030-01-02T00:00:00Z", + "entries": [{ + "id": "example.welcome", + "name": "欢迎卡片", + "description": "前后台页面示例", + "version": "1.0.0", + "publisher": "example.publisher", + "package_url": "https://plugins.example.com/welcome.zbplugin", + "sha256": "REPLACE_WITH_PACKAGE_SHA256", + "surfaces": ["public", "account", "admin"] + }] +} +``` + +将有效期改为发布时刻之后、最多 31 天内的 UTC 时间,摘要替换为整个 `.zbplugin` 的 SHA-256。每个 ID 一条目录记录,最多 200 条。目录签名者与包发布者可不同,但各自公钥都必须受宿主信任。 + +```sh +go -C backend run ./tools/pluginpackager \ + -catalog /tmp/zboard-plugin-demo/catalog-payload.json \ + -key /tmp/zboard-plugin-demo/publisher.key \ + -key-id example.publisher \ + -out /tmp/zboard-plugin-demo/catalog.json +``` + +打包器输出 `{payload, signature}`,对紧凑 payload 的精确原始字节签名。发布该文件并设置 `plugins.catalog_url`;不要通过再格式化或转义修改 payload。宿主校验目录有效期与签名,安装时再次校验包,旧页面选择的摘要过期会返回冲突并要求刷新。 + +没有配置目录时市场显示空状态,离线导入仍可用。本仓库不预置公共插件源,也不包含在线开发者上架服务。 + +第三方登录与注册、多提供方快捷配置、自定义 OAuth2 字段映射和配置密钥保留契约见 [插件身份能力](/projects/zboard/plugins/identity-reference)。配置读取可包含插件投影的公开字段;密钥保持隐藏。 diff --git a/docs/projects/zboard/plugins/governance.md b/docs/projects/zboard/plugins/governance.md new file mode 100644 index 0000000..6e10a6f --- /dev/null +++ b/docs/projects/zboard/plugins/governance.md @@ -0,0 +1,100 @@ +# 插件宿主生命周期、范围与数据 + +## 页面位置与业务作用 + +`surfaces` 只描述插件自带页面的位置(public / account / admin),不是插件业务作用或权限。OAuth 插件可只有 admin 配置页,但通过 `zboard.identity.provider.v1` 向核心提供身份验证能力;前台登录和注册入口由核心生成。这些是宿主协议与运行时约束,管理页只展示页面位置、业务作用、能力和生命周期结果。 + +## 宿主准入与核心策略 + +范围和授权由 ZBoard 自己保证。发布者在签名 manifest 中声明能力和页面位置;宿主安装器校验可信签名、完整性、平台兼容性、能力白名单与结构约束,自动生成绑定当前包摘要的准入记录。调用时继续检查具体能力、用户身份、surface、安装状态和 generation,核心业务还检查自身规则。管理员不需要逐项打勾授权,也不能通过接口绕开宿主白名单。 + +准入记录与安装版本、迁移数据在同一事务提交。更换包必须重新经过宿主校验;未知能力、越界配置页和不兼容包直接拒绝。没有手工授权 API,没有独立的“执行迁移”操作。历史 `plugin_authorizations` 表保留为宿主内部准入记录,原来的人工授权语义已取消;只读 API 字段为 `admission`。 + +当前允许的能力: + +| 能力 | 宿主执行范围 | +|---|---| +| `zboard.ui.page.v1` | 声明的隔离页面;仍须通过 surface 身份校验 | +| `zboard.config.v1` | 自身配置、公开字段投影和诊断 | +| `zboard.identity.provider.v1` | 第三方身份断言;核心负责账户、注册和会话 | +| `zboard.storage.v1` | 自身加密 JSON 数据;没有数据库句柄、SQL 或核心表访问 | + +有效能力必须同时满足当前包声明、宿主准入、当前调用身份、安装状态和核心策略。站点关闭注册后,未绑定身份的登录失败;关联账户不存在或停用也失败。已绑定正常账户可登录。同邮箱不代表绑定,不能通过邮箱自动接管账户。注册提交时再次检查站点开关,防止授权期间策略改变后仍创建账户。 + +原生程序仍只支持由部署方配置的可信发布者签名的包。当前不是操作系统沙箱,原生进程拥有部署账号的文件与网络权限;业务 API 的限制不能视为对恶意原生代码的强隔离。这个部署边界不能通过管理员勾选框变成安全保证。 + +宿主恢复现有安装时重新校验包和发布者,自动补齐历史安装的准入/数据准备,并按已提交启停状态恢复。校验失败不会运行插件,核心服务仍可启动;原因记录在安装状态,重新导入会重新执行完整准备流程。 + +## 生命周期事务 + +- 首次安装:验证签名、平台与能力 → 暂存不可变包 → 准备默认数据及迁移 → 原子提交版本、准入、配置、数据和迁移记录。安装成功后保持停用,业务参数由管理员正常配置。新安装失败不留下半成品安装或私有数据。 +- 启用:检查准入、数据版本、历史迁移和当前包,启动并核验进程身份、健康与配置,成功后才发布页面/服务入口。已经正常启用的实例重复启用不会重启进程。 +- 停用:宿主串行化自身调用,关闭活动状态、撤销会话并停止进程;保留数据。 +- 升级:在旧版本仍为当前版本时准备迁移副本和候选进程;已有配置须通过新运行时校验。一个数据库事务切换版本、准入、配置、数据、迁移记录及 generation,随后切换宿主进程指针并关闭旧实例。升级成功保留原启停状态,失败保留旧版本、旧数据、旧实例和有效会话。宿主准备期间私有存储调用返回可重试的 503。尚未声明测试当前宿主的包不能直接替换运行中的插件,须先停用。 +- 卸载:由宿主停止运行、撤销会话、删除程序和页面;默认保留配置和数据。无需管理员先停用来拼接生命周期。 +- 恢复旧版本:目前要求先停用;宿主检查数据范围、迁移校验和和已有配置,成功后保持停用。不会自动向下迁移数据。 + +所有数据库写入受宿主租约约束。失败尝试写操作日志;成功安装的操作结果与版本切换同一事务提交。崩溃恢复只采用已提交状态。尚未提交的不可变包文件不会成为活动版本;它不是有效安装。 + +## 私有数据 + +宿主在 `plugin_data` 中按插件 ID 保存一个加密 JSON 对象。单插件最多 128 个键、256 KiB;单值最大 32 KiB。键允许 ASCII 字母、数字、下划线、点和横线,最长 128 字符。顶层键区分大小写。不存在动态创建的插件业务表,插件不能提供表名或 SQL。 + +每次写入递增整个命名空间的 revision;所有写入需带最近读取的 revision,冲突返回 409。请求中的命名空间来自已认证页面会话或原生进程私有连接,调用方不能传入其他插件 ID。原生进程只在当前实例处于启用状态且权限仍有效时可访问。 + +后台页面桥支持 `storage.get` / `storage.put` / `storage.delete`,请求字段为 `key`、`revision`、`value`(put 使用)。get 返回 `{ revision, found, value? }`;写入返回新 revision。公开页和用户前台不能访问这个管理员范围的私有数据接口。页面仍不能直接读取宿主令牌或发任意网络请求。 + +服务端 Go SDK: + +```go +store, err := pluginv1.HostStorageFromEnvironment() +if err != nil { return err } +defer store.Close() +current, err := store.Get(ctx, "cursor") +if err != nil { return err } +_, err = store.Put(ctx, "cursor", current.Revision, json.RawMessage(`{"offset":42}`)) +``` + +宿主通过专属 Unix socket 和短期进程 token 提供服务,不暴露管理端令牌。SDK 的调用应在普通后台工作中进行,不能在 Health、ValidateConfig、ApplyConfig、TestConfig 或身份验证等由宿主调用的生命周期 RPC 内同步回调存储。宿主事务期间返回 503,插件应退出当前回调后稍后重试;409 必须重新读取数据后决策。升级切换、停用、卸载、进程退出或重启会关闭旧连接。 + +## 声明式迁移 + +需要私有存储的包必须声明 data。只迁移自身配置也可声明 data,而不申请 storage 能力。版本链从 1 连续到当前 version,最多 32 步,总变更最多 128 条。历史步骤必须完整保留,不得修改已经发布的迁移。 + +```json +{ + "data": { + "version": 2, + "min_compatible_version": 2, + "migrations": [ + { "version": 1, "changes": [] }, + { "version": 2, "changes": [ + { "target": "storage", "operation": "rename", "key": "old_cursor", "to": "cursor" }, + { "target": "config", "operation": "set_default", "key": "batch_size", "value": 20 } + ] } + ] + } +} +``` + +支持的 target 为 `config` / `storage`;分别要求对应的宿主准入能力。操作只允许: + +- `set_default`:键缺失时补值,已有值不覆盖。 +- `rename`:源存在才移动;目标已存在则失败,避免覆盖数据。 +- `remove`:删除该命名空间的一个键,属于显式破坏性变更。 + +宿主不接受 SQL、脚本或插件自定义迁移回调。原生插件仍通过既有配置 RPC 验证、规范化并应用候选配置,验证不通过时不提交数据;停用安装的候选进程完成后退出,运行中的升级在提交后接替旧实例。 + +初始化和前向迁移是安装/升级事务内部步骤,管理员只执行安装或升级。迁移失败使整个安装/升级失败,重新导入可重试。数据未准备完成时禁止启用和私有存储访问。版本页可查看数据状态和已提交记录,不能独立触发迁移来制造版本与数据错配。 + +配置、私有数据、版本号、generation 和迁移记录在一个宿主数据库事务内提交;中途失败全部回滚。操作日志记录失败,迁移台账只记录已提交步骤,包含数据周期、版本、包摘要、步骤校验和、操作者和时间。重启后按已提交状态恢复,未完成操作标记中断。 + +恢复旧程序前检查其声明的数据范围和历史迁移校验和。当前数据超过旧包声明版本时拒绝恢复;不自动执行向下迁移,也不声称旧程序包就是数据备份。数据恢复应使用一致的数据库、插件目录和加密密钥备份。 + +## 卸载与清理 + +- 停用:停止运行,保留配置和数据。 +- 卸载:自动停止运行、撤销会话并删除程序和页面,保留自身配置、私有数据、版本和操作记录,以及核心用户、身份绑定和审计记录。 +- 清除配置与私有数据(purge_data):仅在卸载后执行,清空自身配置与私有数据,数据版本重置为 0、数据周期递增。迁移/操作记录和核心数据保留;重新安装时由宿主在新周期自动初始化。 + +宿主自身的 `0004_plugin_governance` 迁移创建准入记录、私有数据和迁移台账;这是核心 schema 迁移,不能由插件安装/卸载执行。MySQL 版本化 SQL、SQLite 初始化和跨数据库迁移清单均包含这些表。清除插件数据不会删表。 diff --git a/docs/projects/zboard/plugins/identity-reference.md b/docs/projects/zboard/plugins/identity-reference.md new file mode 100644 index 0000000..d513020 --- /dev/null +++ b/docs/projects/zboard/plugins/identity-reference.md @@ -0,0 +1,50 @@ +# 插件第三方登录与注册 + +`zboard.identity.provider.v1` 让 ZBoard 作为第三方 OAuth2 / OIDC 客户端。一个插件可配置多个提供方;参考 OAuth 插件提供 GitHub、Google 快捷配置,以及自定义 OAuth2 / OIDC 配置。提供方之间独立启停,核心登录和注册页分别显示名称。 + +## 用户路径与核心职责 + +1. 运营者设置站点的公开 HTTPS 根地址,提供方精确登记 `<站点地址>/api/v1/auth/oidc/callback`,导入可信插件并配置各客户端。 +2. 用户点击 GitHub、Google 或自定义入口授权。已有绑定直接登录;新身份由核心处理注册。 +3. 新注册遵守核心 `allow_registration` 开关。提供方断言包含有效、已验证邮箱时自动创建普通账户;否则在核心完成页补充邮箱验证码。验证码必需,即使普通密码注册关闭了邮箱验证;邮件服务必须可用。 +4. 同邮箱已有账户(包括软删除账户)不会自动合并。用户先通过原方式登录,再确认当前密码显式绑定。一个外部身份只属于一个用户,一个用户可分别绑定多个提供方。 +5. 第三方注册账户默认没有本地密码,密码列使用不可登录的哨兵值。用户可在刚完成第三方登录后的五分钟内,于“账户安全”设置初始本地密码。核心同时核验登录用户、一次性授权证明、提供方活动状态和绑定。已有密码不能通过此接口覆盖。 + +插件没有用户表、角色、权限、订阅、凭证修改或节点命令接口。它只与配置的第三方通信并返回身份断言。核心负责注册策略、邮箱验证、账户唯一性、普通权限、绑定、审计和会话签发。配置保存与检测没有注册副作用。 + +停用插件或某个提供方、修改配置、升级或卸载会阻止未完成的登录与注册。已提交绑定、已签发核心会话和业务数据不会被删除。解绑即使插件卸载仍可由核心执行,但必须有当前本地密码,防止用户移除唯一登录方式。 + +## 专用契约 + +manifest 声明 `zboard.identity.provider.v1`、`zboard.config.v1` 及服务进程。 + +- `ListIdentityProviders`:返回最多 16 个启用提供方的稳定 ID 和名称,不执行外部网络请求。旧单提供方插件未实现时,宿主回退到插件 ID / 名称。 +- `GetIdentityProvider(IdentityProviderRequest)`:按 `provider_id` 返回协议(`oauth2` / `oidc`)、HTTPS Issuer、授权地址、Client ID 和 scopes。空协议兼容旧 OIDC;空 provider_id 兼容旧单提供方绑定。 +- `ExchangeIdentity`:接收核心保存的 provider_id、code、精确 redirect_uri、nonce、PKCE verifier 和 expected issuer。只返回 Issuer、Subject、邮箱和严格布尔类型的邮箱验证标志,不返回第三方令牌。 +- OIDC 由插件验证签名、issuer、audience、时效、nonce、subject、azp 和存在时的 at_hash。纯 OAuth2 通过令牌调用配置的用户资料接口,按字段映射取得稳定 ID;GitHub 使用数值 ID 和 `/user/emails` 的 primary+verified 邮箱。 +- 配置能力新增可选 `DescribeConfig`,返回插件明确投影的公开字段和密钥存在标志。宿主仅在管理员配置通道调用;原始配置仍加密保存。`ValidateConfig` 的 `previous_config_json` 由宿主提供,插件据此实现保留密钥,输出完整标准化配置。配置 revision 的 CAS 和失败回滚仍归宿主。 + +公开选择键为 `~`;旧单提供方保留 ``。绑定表的 `plugin_id` 保存这个选择键,保持 `(user_id, plugin_id)` 唯一约束。绑定主键是发布者、选择键、Issuer、Subject 的结构化字节 SHA-256,避免数据库排序规则合并大小写身份,也隔离不同发布者。此扩展沿用 `0003_external_identities` 表,不增加迁移。 + +协议 1 增量字段保留 wire 编号;空请求保持旧单提供方行为。新多提供方插件需要本次 SDK / 宿主。旧版本提供页面和配置能力,不代表具备新注册与多提供方能力。 + +## 状态、事务与端点 + +核心生成 256 位 state、nonce、PKCE verifier 和浏览器绑定,始终使用 S256。state 五分钟到期且一次消费。HTTPS cookie 使用 `__Host-`、Secure、HttpOnly、SameSite=Lax,固定回调地址,拒绝跨站 POST,令牌不会出现在完成页 URL。 + +普通完成票据一分钟到期;待注册票据和初始密码证明五分钟到期。换票不延长原始过期时间。邮箱验证码有发送冷却、IP 限额、最多五次验证预算与单次消费;错误尝试独立提交计数,不能通过注册事务回滚重置。验证码失败需要重新授权。 + +核心完成注册、绑定、审计和签发会话时,插件租约和安装状态检查与身份写入共用数据库事务;再次核验发布者、generation、config_revision。新建用户、绑定和审计任一步失败都会回滚。只在提交成功后尝试加入既有注册欢迎邮件任务。 + +- `GET /api/v1/auth/oidc/providers`:公开提供方目录。 +- `POST /api/v1/auth/oidc/:id/start`:发起授权。 +- `GET /api/v1/auth/oidc/callback`:消费 state 并验证身份,跳转固定完成页;迁移维护期禁止此写操作。 +- `POST /api/v1/auth/oidc/finish`:登录 / 注册 / 绑定完成;缺少验证邮箱时返回 `registration_required`,随后提交 `email` 和 `verification_code`。 +- `POST /api/v1/auth/oidc/registration-code`:持有待注册浏览器票据后发送核心邮箱验证码。 +- `POST /api/v1/auth/oidc/password`:登录用户使用刚完成授权的一次性证明设置初始密码。 +- `GET /api/v1/account/identities/security`:查询当前账户是否已有本地密码。 +- `GET /api/v1/account/identities`:当前用户绑定列表,不返回 Subject 或第三方令牌。 +- `POST /api/v1/account/identities/:id/bind`:`:id` 为提供方选择键,需当前用户和密码。 +- `POST /api/v1/account/identities/:id/unlink`:`:id` 为绑定记录 ID,需当前用户和密码。 + +状态在活动插件宿主内存中,每类最多 1024 条;宿主重启需重新授权,多实例入口需路由到活动宿主。真实第三方授权须使用运营者登记的客户端验收;协议 fixture、签名令牌测试或元数据检测不能代替实际账号授权。 diff --git a/docs/projects/zboard/plugins/index.md b/docs/projects/zboard/plugins/index.md new file mode 100644 index 0000000..0328d88 --- /dev/null +++ b/docs/projects/zboard/plugins/index.md @@ -0,0 +1,28 @@ +# 插件与市场 + +ZBoard 提供基础面板,在线支付及其他超出基础管理范围的能力通过插件扩展。管理员根据自己的需求选择插件,并管理其安装、配置、启用和版本。 + +::: info 适用版本 +插件市场和插件运行时在 0.0.1 之后的开发版本中提供,0.0.1 正式版不包含这些入口。使用前先在面板“关于”中核对版本,选择包含插件支持的发布构建。 +::: + +## 从哪里开始 + +- 想安装插件:阅读[插件市场与离线安装](./marketplace)。 +- 想增加第三方登录:阅读[登录插件](./login)。 +- 想了解发布者和安装校验:阅读[安装信任说明](./trust)。 +- 想自己开发插件:阅读[插件开发指南](./development)。 + +## 核心和插件分别负责什么 + +核心管理用户、节点、订阅、基础订单、权益和流量等数据。插件通过宿主开放的接口提供扩展功能和外部服务集成。 + +例如,支付渠道集成应由支付插件负责,订单确认与权益发放仍遵循核心的权限、幂等和审计规则。第三方登录插件负责对接身份提供方,账户绑定与面板会话由核心管理。 + +## 已开放的扩展范围 + +当前开发版本支持扩展页面、插件自身配置、身份提供方和插件私有存储。安装时由宿主检查插件声明的能力和兼容性。 + +支付插件的业务接口尚未开放,也未提供内置支付渠道;“通过插件扩展”描述的是产品边界,不表示任意插件能力已经可用。可用范围以[插件契约](../reference/plugin-system-design)和所用构建为准。 + +插件市场是插件的查找和分发入口,需要由部署者配置市场目录与可信发布者。它不表示官方已经预置了一个包含所有扩展的公共市场。 diff --git a/docs/projects/zboard/plugins/login.md b/docs/projects/zboard/plugins/login.md new file mode 100644 index 0000000..c4f9a71 --- /dev/null +++ b/docs/projects/zboard/plugins/login.md @@ -0,0 +1,25 @@ +# 使用登录插件 + +第三方账号登录由身份提供方插件扩展。ZBoard 核心负责本地账户、绑定关系、登录会话和站点注册策略。 + +## 配置流程 + +1. 按[安装指南](./marketplace)安装支持身份提供方的插件。 +2. 在身份平台创建应用,获取客户端标识和密钥。 +3. 打开插件配置页,填写提供方参数,并将页面给出的回调地址登记到身份平台。 +4. 检查站点的公开 HTTPS 地址与实际访问域名一致,保存配置并执行测试。 +5. 启用插件,在退出登录的浏览器中检查新增登录入口。 + +不同插件支持的平台和配置字段可能不同,使用插件自身说明和身份平台的应用设置,不要套用其他平台的回调地址。 + +## 已有用户与新用户 + +已有用户可在账户安全页面绑定第三方账号。首次使用第三方登录的新用户仍受站点注册开关和验证流程约束,不能通过插件绕过核心注册策略。 + +第三方登录不自动代表已经获得订阅或节点权限。服务访问仍由本地用户的订阅与授权决定。 + +## 排查方向 + +核对回调地址、站点公开地址、客户端标识、密钥和提供方授权设置;通过插件测试和登录页面反馈定位问题。分享错误信息时移除密钥、授权码和令牌。 + +开发者实现身份提供方时,阅读[身份接口契约](./identity-reference)。 diff --git a/docs/projects/zboard/plugins/marketplace.md b/docs/projects/zboard/plugins/marketplace.md new file mode 100644 index 0000000..6b3efdb --- /dev/null +++ b/docs/projects/zboard/plugins/marketplace.md @@ -0,0 +1,59 @@ +# 插件市场与离线安装 + +插件市场用于查找和安装扩展;插件管理用于配置、启停和维护已安装的插件。先阅读[适用版本与功能范围](./)。 + +## 1. 配置市场来源 + +部署者在面板的 ZBoard YAML 配置中填写插件目录、市场地址和可信发布者公钥: + +```yaml +plugins: + directory: /var/lib/zboard/plugins + catalog_url: "https://YOUR_CATALOG_HOST/catalog.json" + trusted_publishers: + your.publisher: "BASE64_ED25519_PUBLIC_KEY" +``` + +将示例域名、发布者 ID 和公钥替换为实际值。市场地址来自你选择的分发方;`trusted_publishers` 的公钥应通过发布者的可信渠道获取,不能把插件附带的陌生公钥直接当作已受信任的来源。 + +市场目录不是 GitHub 仓库首页或任意下载页面。它必须符合 ZBoard 的签名目录格式;当前下载要求公开 HTTPS、443 端口,不接受重定向或内网地址。提供目录的方法见[开发指南](./development#提供市场目录)。 + +使用 Docker 时,将自定义 YAML 挂载到 `/app/etc/zboard.yaml`,并将插件目录持久化到 `/var/lib/zboard/plugins`。应在已有应用配置中增加这些字段,保留其他配置。修改部署配置和可信公钥后重启面板;正常安装、配置和启停插件无需重启。挂载方法见[存储与备份](../guides/storage-and-backups#plugin-persistence)。 + +只使用离线安装时可以让 `catalog_url` 为空,但仍需配置可信发布者。 + +## 2. 从市场安装 + +1. 打开后台的“扩展中心 → 插件市场”。 +2. 查看插件名称、发布者、版本、说明及能力要求。 +3. 打开详情并执行安装,等待面板下载和校验完成。 +4. 到“插件管理”查看安装结果,按插件说明完成配置后启用。 + +首次安装默认停用。安装成功表示包已通过宿主校验并保存,不代表插件已经启用或外部服务连接正常。 + +## 3. 离线安装 + +1. 从可信发布者获取 `.zbplugin` 文件。 +2. 在“扩展中心 → 插件管理”使用离线导入入口上传文件。 +3. 检查安装结果,完成配置并启用。 + +离线安装与市场安装执行相同的签名、内容摘要和能力检查,不会跳过发布者校验。 + +## 4. 配置、启停和更新 + +在插件详情中查看概览、版本和操作结果。需要配置的插件由插件配置页收集参数,保存后使用配置测试入口检查结果。 + +安装新版本前阅读发布者说明;更新失败时面板保留原安装及数据。停用会停止插件运行并撤销其页面会话,已完成的核心订单或订阅操作不会因插件停用而撤销。卸载与清除插件数据是不同操作,具体保留策略见[插件生命周期](./governance)。 + +## 常见问题 + +| 现象 | 检查方向 | +| --- | --- | +| 没有插件市场入口 | 所用版本是否包含插件支持,以及当前账号是否为管理员。 | +| 市场提示未配置 | 部署 YAML 中是否提供了目录地址和可信发布者。 | +| 签名或发布者校验失败 | 包的发布者 ID、公钥和来源是否匹配,文件是否完整。 | +| 不支持某项能力 | 插件需要的宿主接口是否在当前构建中开放;不能通过修改包声明绕过。 | +| 安装成功但看不到扩展页面 | 插件是否已经配置并启用,页面属于公开端、用户端还是管理端。 | +| 插件服务启动失败 | 插件是否包含与容器平台匹配的服务程序,查看插件详情中的操作结果。 | + +更多说明见[安装信任](./trust)。开发者需要包格式、签名与发布流程时,阅读[插件开发](./development)。 diff --git a/docs/projects/zboard/plugins/trust-en.md b/docs/projects/zboard/plugins/trust-en.md new file mode 100644 index 0000000..069a8bb --- /dev/null +++ b/docs/projects/zboard/plugins/trust-en.md @@ -0,0 +1,35 @@ +# Plugin installation trust + +**English** · [简体中文](/projects/zboard/plugins/trust) + +ZBoard verifies package contents before accepting a signing identity. A package may include an Ed25519 public key in `signature.json.public_key`; this proves that its manifest and file hashes match the signature, but does not identify the publisher by itself. + +## Offline import + +The administrator selects a package, reviews its name, publisher, capabilities, UI surfaces and compatibility, and confirms installation. An unknown source additionally requires explicit confirmation of the signing-key fingerprint. The confirmation request binds the SHA-256 of the entire archive and the fingerprint of the verified public key. Inspection performs no installation, runtime execution or trust write. + +The host stores the public key and per-plugin trust together with the installation transaction. A failed installation grants no trust. Later versions must retain both plugin ID and publisher key; trusting one plugin does not trust other plugins from that publisher. Uninstall retains the identity pin along with the installation history, preventing another key from claiming the same plugin ID. Key rotation is deliberately rejected until a separate reviewed rotation workflow is available. + +Legacy archives without an embedded key can supply their public key through the dialog's compatibility option. Previously configured publisher keys remain supported; removing a configured key still revokes installations whose trust originated from that configuration. Explicit per-plugin trust is stored in the database and retained across restarts. + +## Market installation + +Without `plugins.catalog_url`, the market reads the [ZeroDeNet ZBoard registry](https://github.com/zerodenet/plugins/blob/main/catalogs/zboard.json). Each plugin has a detail page with platform downloads, online installation and a link to its installed management page. The detail page resolves `releases/download/v/marketplace-entry.json` from the registered GitHub repository. The release must match the registry's plugin ID, repository, publisher and version, and list platform-specific `.zbplugin` assets with SHA-256, size and the publisher's public key. No arbitrary download URL is accepted from the browser. + +Online installation selects the server's platform (or a platform-independent `any` artifact). Inspection downloads the package, checks its size, digest, identity, signature and compatibility, and displays the same confirmation used by offline import. A public listing or publisher metadata never grants trust: an unknown key requires explicit fingerprint confirmation scoped to that plugin. Confirmation re-fetches and re-validates the package against the inspected archive digest; changed packages require inspection again. Existing signing-key pins, capability admission, migrations and activation policy continue to apply. A missing release or unsupported server platform remains visible without presenting an installable package. + +The host first validates the configured catalog signature and expiry. Each signed entry may supply `public_key`, attesting that publisher key only for the entry's plugin. Before recording trust, the host checks the package digest, signature, plugin ID, publisher and version. Entry keys do not enter the global publisher configuration and cannot sign future catalogs. + +`plugins.catalog_url` must reference a signed host catalog, not the source registry JSON. The catalog signer remains a host-configured trust root. This implementation does not publish a production catalog or invent an official market key. + +Downloads allow at most four redirects. Every destination must use HTTPS on port 443, and every DNS resolution is checked against private addresses before dialing. Redirect targets may contain expiring signed queries used by release hosting services. Authorization and cookie headers are removed on redirects; package signatures and digests remain mandatory. + +## Packaging and compatibility + +`pluginpackager -source -out ` automatically creates and reuses a private local signing key beneath the user's configuration directory. Explicit `-key` and `-key-id` remain available and are required for catalog signing. OAuth's build wrapper also creates and reuses its ignored development key by default. + +The updated packager adds the optional public-key field to package signatures. Earlier hosts with strict signature decoding must be upgraded to read these new packages. Existing released archives are not rewritten; the compatibility input supports them without changing their checksums. + +Installation remains disabled initially. Core capability admission, registration policy, account ownership, configuration validation and data migrations apply independently of publisher trust. + +Plugin admission depends on the host's plugin protocol, UI bridge, supported capabilities and runtime platform, together with host authorization and data lifecycle checks. ZBoard product release numbers, including dev and RC suffixes, do not block installation, activation or active upgrades. `requires.zboard` is optional advisory metadata; `tested_zboard_versions` records publisher test coverage. An out-of-range or unrecognized host version produces a notice, not a refusal or extra activation consent. Existing signed packages need no rebuilding to adopt this host policy. Unsupported APIs, capabilities and runtime platforms remain rejected. diff --git a/docs/projects/zboard/plugins/trust.md b/docs/projects/zboard/plugins/trust.md new file mode 100644 index 0000000..29180dc --- /dev/null +++ b/docs/projects/zboard/plugins/trust.md @@ -0,0 +1,35 @@ +# 插件安装信任 + +[English](/projects/zboard/plugins/trust-en) · **简体中文** + +ZBoard 在接受签名身份前校验安装包。包可通过 `signature.json.public_key` 附带 Ed25519 公钥;这能证明签名、清单和文件摘要匹配,本身不能证明发布者身份。 + +## 离线导入 + +管理员选包后先查看名称、发布者、所需能力、页面范围和兼容性,再确认安装。未知来源还需明确确认签名密钥指纹;确认请求同时绑定整个安装包的 SHA-256 与已验证公钥的指纹。预览不安装文件、不启动进程、不写入信任。 + +公钥及插件独立信任与安装结果在同一事务中保存,安装失败不授予信任。后续版本必须保持插件 ID、发布者及密钥一致,信任不会扩散到其他插件。卸载后随安装历史保留身份约束,防止其他密钥接管同一插件 ID。换钥在独立审核流程具备前保持拒绝。 + +旧包未附带公钥时,可在弹窗兼容选项中提供公钥。原有配置文件的可信发布者继续有效;删除配置公钥仍会撤销基于该配置安装的插件信任。明确确认的插件独立信任保存在数据库中,重启后仍有效。 + +## 市场安装 + +未设置 `plugins.catalog_url` 时,市场读取 [ZeroDeNet ZBoard 公开目录](https://github.com/zerodenet/plugins/blob/main/catalogs/zboard.json)。插件详情页提供各平台安装包下载、在线安装和已安装插件的管理入口。发行信息来自已登记 GitHub 仓库的 `releases/download/v<登记版本>/marketplace-entry.json`;插件 ID、仓库、发布者和版本必须匹配目录,元数据必须包含安装包的平台、SHA-256、大小和发布者公钥。浏览器不能指定任意安装地址。 + +在线安装选择服务器对应平台(或通用 `any` 包),先下载并验证大小、摘要、身份、签名及兼容性,再使用与离线导入相同的确认弹窗。公开收录及发布者元数据不授予信任;未知密钥仍需明确确认指纹,信任仅限该插件。确认时重新下载、校验并绑定预览的安装包摘要,包发生变化必须重新预览。已有密钥约束、能力准入、迁移及启停规则继续由宿主保证。尚无发行包或不支持当前服务器平台时会明确说明。 + +宿主先验证配置目录的签名与有效期。已签名条目可通过 `public_key` 认证该插件的发布者密钥。保存信任前,宿主验证包摘要、签名、插件 ID、发布者及版本。条目公钥不会加入全局发布者配置,也不能用于签署新的市场目录。 + +`plugins.catalog_url` 必须指向宿主签名目录,不能直接使用源码市场的 JSON。目录签名者仍需作为宿主信任根配置。本次实现不发布生产目录,也不预设未经确认的官方市场密钥。 + +下载最多跟随四次重定向。每跳必须使用 HTTPS 443,并在连接前检查每次 DNS 解析结果,拒绝私网目的地址。重定向目标允许发行托管服务使用的临时签名查询参数,移除 Authorization 与 Cookie 请求头,最终仍强制验签及摘要核验。 + +## 打包及兼容性 + +`pluginpackager -source <目录> -out <安装包.zbplugin>` 自动在用户配置目录下生成并复用本地私钥。仍支持显式 `-key` 和 `-key-id`,签署市场目录时必须指定它们。OAuth 构建脚本也默认生成并复用被 Git 忽略的开发密钥。 + +新版打包工具在签名信息中增加可选公钥字段,旧宿主严格解析签名时需要先升级才能读取新包。已有 Release 不覆盖、不改摘要,通过兼容输入支持旧包。 + +首次安装保持停用。核心能力准入、注册策略、账户归属、配置校验和数据迁移独立于发布者信任,继续由宿主执行。 + +插件准入依据宿主支持的插件协议、UI 桥、能力和运行平台,并受宿主授权及数据生命周期检查约束。ZBoard 的产品发行版本号(包括 dev、RC 后缀)不阻止安装、启用或运行中的升级。`requires.zboard` 为可选的参考声明,`tested_zboard_versions` 记录发布者测试覆盖。超出声明范围或无法识别宿主版本时只作提示,不拒绝操作,也不额外要求启用确认。既有签名插件包无需为此重新构建;不支持的接口、能力和运行平台仍会被拒绝。 diff --git a/docs/projects/zboard/reference/core-baseline.md b/docs/projects/zboard/reference/core-baseline.md new file mode 100644 index 0000000..54cc383 --- /dev/null +++ b/docs/projects/zboard/reference/core-baseline.md @@ -0,0 +1,29 @@ +# ZBoard 核心边界与复用基线 + +本文维护长期产品与工程约束。逐轮修复过程、跑测结果和环境记录保存在本地验收产物中。 + +## 已确定的方向 + +- 保留现有自用/小规模面板闭环,通过小步修复提高性能、稳定性与交互质量。 +- 核心包含用户、登录注册、节点/协议、基础订单、权益、订阅、流量、内核基础限制、文档公告及必要维护。 +- 核心定位是基础面板,在线支付及其他超出基础管理范围的能力统一通过插件扩展,不作为核心待补齐的内置功能。当前插件支持页面、自身配置、身份提供方和私有存储,并提供签名市场、管理和离线导入;各能力的实际开放范围见[插件边界](/projects/zboard/reference/plugin-system-design)。核心业务数据的写入仍由明确的专用服务负责。 +- [XBoard](https://github.com/cedar2025/Xboard) 对照业务流程;[X-Panel](https://github.com/xeefei/X-Panel) 参考基础/增值能力分离;[Typecho 插件实现](https://github.com/typecho/typecho/blob/master/var/Typecho/Plugin.php) 参考精简核心和扩展点。三者都不直接决定 ZBoard 的功能清单或性能结论。 +- 现有套餐/SKU、历史订单、权益和节点配置保持兼容,外围能力按真实依赖逐项隔离。未来 ZBoard 插件不能向 Zero Connector 引入支付、套餐或第三方业务语义。 + +## 已存在且应复用的实现 + +| 能力 | 当前证据 | 结论 | +| --- | --- | --- | +| 请求取消、旧响应隔离、卸载保护 | [useRemoteResource.ts](https://github.com/zerodenet/zboard/blob/37d1716c466ce16a1198399ac91c9de3e2c0c4f8/frontend/src/composables/useRemoteResource.ts)、[测试](https://github.com/zerodenet/zboard/blob/37d1716c466ce16a1198399ac91c9de3e2c0c4f8/frontend/src/composables/useRemoteResource.test.ts) | 已实现;检查页面是否正确接入,不另造框架 | +| 分页/游标表格与生命周期处理 | [useRemoteTable.ts](https://github.com/zerodenet/zboard/blob/37d1716c466ce16a1198399ac91c9de3e2c0c4f8/frontend/src/composables/useRemoteTable.ts)、[useCursorTable.ts](https://github.com/zerodenet/zboard/blob/37d1716c466ce16a1198399ac91c9de3e2c0c4f8/frontend/src/composables/useCursorTable.ts)、[生命周期测试](https://github.com/zerodenet/zboard/blob/37d1716c466ce16a1198399ac91c9de3e2c0c4f8/frontend/src/composables/remoteTableLifecycle.test.ts) | 已实现;逐页验证使用方式 | +| 路由懒加载 | [router/index.ts](https://github.com/zerodenet/zboard/blob/37d1716c466ce16a1198399ac91c9de3e2c0c4f8/frontend/src/router/index.ts)、[路由测试](https://github.com/zerodenet/zboard/blob/37d1716c466ce16a1198399ac91c9de3e2c0c4f8/frontend/src/router/routeLoadingPolicy.test.ts) | 已实现;不再列为待建设 | +| 流量分页与区间汇总分离 | [useTrafficUsageTable.ts](https://github.com/zerodenet/zboard/blob/37d1716c466ce16a1198399ac91c9de3e2c0c4f8/frontend/src/composables/useTrafficUsageTable.ts)、[后端测试](https://github.com/zerodenet/zboard/blob/37d1716c466ce16a1198399ac91c9de3e2c0c4f8/backend/internal/handler/traffic_usage_statistics_test.go) | 测试已验证无需总计的分页只执行一次 bucket 查询、summary 执行两次查询;不等于已测得生产耗时 | +| 图表独立加载、错误重试、筛选隔离 | [Traffic.test.ts](https://github.com/zerodenet/zboard/blob/37d1716c466ce16a1198399ac91c9de3e2c0c4f8/frontend/src/views/Traffic.test.ts) | 管理端和用户端复用既有加载与重试行为 | +| 订单状态转移与已支付幂等入口 | [handlers.go](https://github.com/zerodenet/zboard/blob/37d1716c466ce16a1198399ac91c9de3e2c0c4f8/backend/internal/handler/handlers.go)、[状态测试](https://github.com/zerodenet/zboard/blob/37d1716c466ce16a1198399ac91c9de3e2c0c4f8/backend/internal/handler/handlers_test.go) | 已有实现;并发和真实权益发放仍需按具体场景验证,不能称为完全缺失 | +| Fair Use 观测 | [FairUse.vue](https://github.com/zerodenet/zboard/blob/37d1716c466ce16a1198399ac91c9de3e2c0c4f8/frontend/src/views/FairUse.vue) | 已有观测功能;不能误称当前已经执行动态惩罚 | + +## 扩展与验证边界 + +核心独占订单、权益、计量和授权状态转移;外部能力通过明确的命令与查询调用。关键事务不等待可选外部动作,可靠通知根据真实需求设计恢复与幂等语义。 + +性能问题必须通过测量确认,不能仅凭同步调用或模块依赖判断瓶颈。验收预算见[路线图](/projects/zboard/reference/history/roadmap),可复跑方法见[开发指南](/projects/zboard/contributing/development#performance-and-stability-verification)。测试通过不等于生产容量、真实节点撤权或长稳已经达标。 diff --git a/docs/projects/zboard/reference/data-model.md b/docs/projects/zboard/reference/data-model.md new file mode 100644 index 0000000..898d872 --- /dev/null +++ b/docs/projects/zboard/reference/data-model.md @@ -0,0 +1,201 @@ +# zboard data model + +Scope and future extension ownership follow the [core and hardening baseline](/projects/zboard/reference/core-baseline). +The models below describe existing storage and compatibility constraints, not a +requirement to expand core commerce. Preserve historical orders and entitlements +during incremental hardening; future payment integrations belong to plugins. + +zboard is a modular monolith that combines subscription commerce with node operations. Product, entitlement, infrastructure and accounting data are deliberately separated so that editing a node or catalog entry cannot rewrite historical purchases. + +## Bounded self-service read projections + +Catalog pages may select an explicit purchase/renew/change/addon operation and +include or exclude a plan before counting and paging. The same operation scopes +the active SKU count and lowest-price primary SKU, including when the viewer is +an administrator. Cards consume this projection without per-card SKU requests. +The SKU detail collection is separately paginated; an optional anchor identifies +the page containing a deep-linked SKU within the same plan/operation/search +scope. A missing anchor is not replaced with another purchasable SKU. + +Subscription candidates use a server-side management filter and independent +pagination. An explicit subscription ID is resolved inside the authenticated +user scope, not searched only in a preview page. These are read projections: +they do not grant purchase eligibility, alter subscription ownership, change +historical entitlement snapshots or replace order-creation authorization. + +## Identity and installation + +- `users.email` is the normalized unique login identifier; `account_name` is a display name. +- Every login identity is a row in `users`. `is_admin` grants additional management capabilities to that user; it does not create a separate administrator identity or exclude the user from subscriptions, orders, traffic and tickets. +- Passwords are bcrypt hashes. Reusable API credentials live in `user_api_tokens`, which stores only a SHA-256 digest, a non-secret prefix and lifecycle timestamps. +- Optional registration email verification uses `registration_email_challenges` as a short-lived identity proof, not as a user or delivery record. Only a keyed digest of the code and requesting IP is persisted; successful user creation consumes the challenge and records `users.email_verified_at` in the same transaction. Disabling the public switch preserves the original direct-registration flow. +- `installations` is the one-time installation marker. Editable site and operational settings are mirrored into typed `system_configs`. Values are validated by their declared type, revisions provide optimistic concurrency, and rows marked `is_secret` are encrypted and redacted from API responses. + +## Commerce + +- `plans` owns entitlement policy: one required node group, traffic quota, speed/device/family limits, subscriber capacity, renewal policy, reset policy and traffic direction. Plans neither bind protocol endpoints directly nor define a second traffic multiplier. One plan revision protects those shared fields so an older editor cannot silently overwrite a newer save or publish-state change. +- `plan_skus` is the purchasable specification. Billing cadence (`periodic` or `one_time`), entitlement fulfillment (`plan` or `traffic_addon`), allowed operations (`purchase`, `renew`, `change`, `addon`) and renewal fulfillment are independent fields. A timed renewal explicitly chooses either `extend_only` or `extend_and_add_quota`; it never gains quota merely because its service term was extended. A one-time plan SKU may allow new purchase, renewal or plan change while inheriting the plan entitlement. Its day/month/year unit creates a fixed service term, while the `once` unit creates a permanent quota subscription whose traffic never resets and remains usable until exhausted; its only renewal effect is `add_quota_only`. Only an explicit traffic-addon SKU owns an additional traffic grant and is restricted to addon fulfillment. The legacy SKU type remains a compatibility projection rather than the source of these rules. +- `orders` references a SKU and stores a complete commercial snapshot, including the renewal effect used during fulfillment. Payment confirmation is admin/provider controlled; normal users cannot mark their own orders paid. +- `payment_events` reserves an idempotent provider-event boundary for signed payment integrations. +- `subscriptions` stores the granted commercial entitlement and selected node-group ID. Later plan or SKU edits do not rewrite its quota snapshot; changing membership of the referenced node group intentionally changes the endpoints delivered to every plan and subscription using that group. +- `subscription_members` represents actual family members; a numeric family limit alone does not grant membership. +- `subscription_tokens` stores a lookup digest plus encrypted token ciphertext so the owner can view and copy the complete URL again. Explicit rotation or revocation immediately invalidates previous URLs. The public URL is user-level and can aggregate multiple active subscriptions. +- `subscription_templates` stores operator-managed names, link slugs, availability, a constrained `renderer` identifier and versioned declarative customization for optional client-specific exports. Version 3 customization adds an explicit `rule` / `global` / `direct` initial mode, a local mixed HTTP/SOCKS inbound switch, sing-box system HTTP-proxy switching, typed DNS servers/cache/address-family/Fake-IP settings, and TUN settings to the existing native policy-group definitions, designated main group, final routing target and ordered mixture of reusable rule-set references and template-only remote shortcuts. Zero, Clash/Mihomo and sing-box full-configuration renderers project those fields into their native schemas. Every policy group receives all available protocol endpoints by default; optional RE2 include/exclude expressions match only the protocol configuration name, while explicit group references allow native nested selectors. Exported endpoints retain their operator-defined names without panel ID suffixes. Manual groups retain only renderer-valid special targets: Zero and Clash can expose `DIRECT` and `REJECT`, while current sing-box uses route rejection actions and does not emit the deprecated block outbound. The built-in probe default is `http://www.gstatic.com/generate_204`; the former exact HTTPS default is normalized to HTTP on startup without rewriting custom probe URLs. Rule-set bindings target a policy group, direct routing or rejection. Renderer implementation, response Content-Type, endpoint conversion and credential injection remain backend-owned. Advanced YAML (Clash) or JSON (Zero and sing-box) is parsed as data and deep-merged, never executed as Go template code. Dynamic root arrays can be replaced only when the documented generated-content marker is retained, and `$zboard:all-nodes` expands node names in member arrays. The administrator can also edit the complete credential-free versioned model as Raw JSON; valid Raw changes immediately reverse-project into the visual editor. Save, preview and live delivery all validate the final merged client structure, group members and route references. Version 1 and 2 records remain read-compatible and normalize to version 3. Selecting a template changes only response representation; the canonical manifest, endpoint authorization, per-subscription credentials and accounting inputs remain unchanged. The v0.0.1 baseline creates the non-executable renderer/customization boundary directly; a clean install does not create or archive legacy executable template bodies. +- `subscription_rule_sets` owns reusable renderer-specific remote source metadata: administrator name, output tag, URL, client-native format/behavior, update interval, availability and revision. A template reference owns only the action and list position, so editing the source updates every referencing template. `subscription_template_rule_set_bindings` is the foreign-key-backed reference index used for usage counts and deletion integrity; ordered customization remains the API composition boundary so reusable references and quick remote entries can be interleaved. Disabling a library record removes it from new selections without breaking existing subscriptions; deletion is rejected while any template references it. The v0.0.1 baseline creates the reusable rule-set tables and bindings in their final form without rewriting inline data. +- `protocol_credentials` binds one active subscription to one sellable endpoint. VLESS/VMess receive an independent UUID; Trojan/Hysteria2 receive an independent password; Shadowsocks receives an independent PSK and dedicated UDP/TCP listen/public port. `credential_id` remains the panel-side stable identifier, while `principal_key` is compiled into Zero's native managed-user entry for runtime attribution. Secrets are encrypted at rest. + +- Version 3 subscription-template customization owns an independently switchable local loopback mixed HTTP/SOCKS inbound plus optional DNS and TUN runtime settings; the mixed inbound and TUN cannot both be disabled. Zero receives native `runtime.dns`, `runtime.tun` and `mode`, and its visual DNS choices intentionally exclude unsupported plain TCP DNS. Clash/Mihomo receives native `mode`, `dns` and `tun` sections. sing-box receives stable typed DNS servers, TUN, route-mode projection and optional system-proxy activation. Standard Shadowrocket/v2rayN share-link feeds and Quantumult X `server_remote` resources remain node-resource protocols: they cannot carry client-global DNS, TUN, mode or policy settings, so the UI declares that boundary instead of accepting settings that their wire formats would discard. Every generated full-configuration document remains directly runnable without GUI-side repair. +- Public ZNet Sink template delivery and the canonical native manifest base64-encode their already validated JSON documents while administrative preview remains readable JSON. This is representation-level concealment only: subscription authorization still depends on the 256-bit token, TLS, revocation and rotation. Clash and sing-box remain in their client-native representations. Invalid or revoked public subscription tokens return a no-store redirect to the private `subscription_camouflage_url` system setting, falling back to the installation's public site URL; other API 404 responses keep their machine-readable contract. + +## Operations and node groups + +- `provider_accounts` is the reusable external-supplier boundary. A provider + key declares adapter capabilities such as `dns.records`, + `certificate.public` or `payment.checkout`; each configured account owns an + encrypted, redacted credential and verification lifecycle. Domain resources + reference the account, but DNS, certificates and future payment channels + retain separate typed tables and invariants rather than sharing an opaque + provider-resource JSON table. +- `managed_dns_records` stores one handwritten FQDN, explicit A/AAAA target, + selected node and Cloudflare desired/observed state. The target remains an + operator-owned value rather than a live pointer to the node. An on-demand, + admin-only address-candidate projection can read literal node fields, resolve + their hostnames and inspect global interface addresses through an already + verified SSH channel. It returns only publicly routable IPv4/IPv6 candidates, + never mutates a record and never enrolls SSH trust. The create UI may fill an + empty field from the first candidate, but operator edits and existing record + values are never replaced by refreshes; choosing another candidate is an + explicit action. Zboard discovers the longest matching Zone, refuses to + overwrite an unowned remote record unless takeover was explicit, records + provider operations, and distinguishes API synchronization from public-DNS + observation. The create API may accept one A and one AAAA value together, but + persists them as two independently owned records. A background observer + retries public resolvers until each synced record is visible; it never repeats + the Cloudflare write merely because propagation is pending. Mutable target, + address, TTL and proxy policy use optimistic concurrency and resynchronize + after editing. Deletion removes local desired state without calling the provider; old remote records must be managed explicitly in the provider console. Identity changes use explicit delete-and-recreate. A node remains + an infrastructure asset and does not acquire a single canonical domain. +- `managed_certificates` explicitly owns either a verified Cloudflare provider + account for DNS-01 or a canonical node webroot for HTTP-01. DNS-01 does not + require public TCP port 80. HTTP-01 Webroot does not bind a second listener, + and verifies resolvable targets on TCP port 80 before issuance because that + is part of the ACME HTTP-01 contract. A missing IPv6 route on the control + plane is treated as an unobservable family rather than proof that the remote + node is down; concrete refusal and timeout errors still block issuance. + Certificate display name, ACME contact, Webroot and renewal policy are + revision-protected mutable fields; node, domains, environment and challenge + identity require a new certificate asset. DNS-01 first uses the operating + system plugin package and falls back to an isolated Certbot Python virtual + environment when the distribution does not publish that package. Legacy + standalone certificates remain renewal-compatible but cannot be newly + created. +- Deletion of infrastructure resources is local and transactional. SSH, CA or + provider API availability is not a prerequisite. Node deletion removes owned + protocols, credentials, entries, pools, certificate/DNS management records and + membership links. Surviving entry nodes receive queued configuration withdrawal. + Historical traffic, administrative task and audit facts are retained. +- Certificate deletion unlinks protocols and stops panel renewal without + revoking CA certificates or deleting node files. DNS deletion leaves the + provider record intact. Provider deletion removes local DNS records and + detaches certificates with automatic renewal disabled. +- Protocol and entry deletion clears memberships automatically. Runtime removal + is queued and may remain pending while the node is offline. Actual running + operations are still serialized or rejected to avoid concurrent recreation. +- The offline node cleanup utility separates stop/disable from explicit uninstall. + It is downloadable, included in binary archives and installed alongside new + managed Zero installations. See `node-cleanup.md` for exact paths and scope. + +- `nodes` is an independent VPS asset. It can exist without a protocol and owns lifecycle state, encrypted management/report credentials, communication mode, runtime status, enablement, version and synchronization timestamps. +- SSH client authentication selects password or private key. Server identity verification is automatic: an empty fingerprint is enrolled after the first successful SSH handshake, a recorded fingerprint is always enforced, and an administrator must explicitly reset trust after confirming a legitimate VPS reinstall or host-key change. +- SSH login identity and system privilege are separate node settings. `ssh_privilege_mode=none` requires a root login for managed system changes; `sudo` supports passwordless or password-based sudo; `su` requires a separately encrypted root password. Privilege passwords are sent only on the SSH session stdin and are never embedded in remote commands, operation output or audit details. +- Browser SSH terminals remain a node-operations capability. The browser receives only a short-lived, single-use terminal ticket; the backend keeps the encrypted SSH credential, enforces same-origin WebSocket upgrades, proxies a bounded PTY session, and audits session metadata without recording terminal contents. +- SSH reachability, Zero installation, applied runtime configuration, Zero process health, authenticated Connector activity and trusted traffic reporting are separate operational states. A successful SSH test, protocol-config upload or Connector delivery must not imply the other states are healthy. +- Current host resources are an on-demand administrative projection owned by the node asset. CPU cores/load averages, memory, root-filesystem capacity and host uptime are read through the already verified SSH channel and are not persisted as Zero session statistics or exposed without administrator authentication. This projection is deliberately named host resources rather than protocol load. +- `protocol_endpoints` is the sellable runtime network resource and must reference exactly one node when saved. Its configuration is template-like in the administration workflow: an operator may copy it into an independent inactive draft or switch its carrier node, while the saved runtime instance retains one unambiguous node for credentials, publishing and accounting. A node switch updates credential placement, reallocates dedicated Shadowsocks ports when necessary, and publishes only the active runtime sides of the move. The endpoint separates listen and public ports, encrypted server configuration, deliverable client configuration and the sole traffic multiplier. Optional protocol configuration and tags are JSON. Endpoint mutations are classified before server configuration encryption: management, billing and delivery changes remain control-plane updates, while active runtime and credential-placement changes publish only the nodes whose running configuration changes. +- VLESS and VMess transport selection is endpoint template configuration, not a separate protocol resource. Raw TCP is represented by the absence of an additional carrier; managed WebSocket and gRPC choices write matching server and subscriber-client fields, and every renderer converts those canonical values to its native representation. VLESS Reality is an endpoint transport capability, not a separate sellable protocol resource, and Zero `0.0.15` restricts it to raw TCP. Zboard generates a matching X25519 key pair and short ID, encrypts the private key only in the endpoint server configuration, and publishes only the public key, selected short ID, server name and client fingerprint. Maintained one-click scenario presets choose the SNI and client fingerprint together with fresh key material; operators may still adjust the result before saving. The generated Zero, Clash and sing-box representations preserve their renderer-native Reality fields; managed subscription UUID replacement does not rewrite the transport configuration. +- `parent_protocol_id` is a same-node, acyclic self-reference for a single protocol stack parent. +- `node_groups` groups sellable endpoints. `node_group_endpoints` is the many-to-many relationship; endpoint group membership is never stored as comma-separated IDs. One node-group revision protects descriptive fields, status and membership together so concurrent administrators cannot silently replace each other's delivery boundary. `protocol_endpoints.sort_order` is the global client-delivery order. `network_entries.delivery_sort_order` gives forwarding entries an independent position in the same global order; an unset position inherits its landing endpoint position until explicitly ordered. `GET/PUT /api/v1/admin/subscription-delivery-order` uses typed `protocol:` and `entry:` keys, requires the complete list and its current version, and never publishes Zero. Custom forwarding names replace the landing display name while preserving TLS identity, credentials and billing ownership. +- A node-group editor may resolve one bounded, ID-only protocol-endpoint filter snapshot for bulk selection. The snapshot is transient operator input, not a second persisted group or dynamic membership rule; saving still writes explicit `node_group_endpoints` rows. Large saves validate and upsert membership in bounded batches and delete only removed links instead of deleting and recreating the complete relationship set. +- The protocol-endpoint editor uses a separate endpoint-centric membership command. It submits only added or removed node groups together with each group's loaded revision. The transaction locks those groups, rejects stale revisions, appends a new endpoint after the existing group order, removes only the requested relationship, and increments only groups whose relationship actually changed. Copying a protocol endpoint does not inherit memberships implicitly because that would widen a delivery authorization boundary. +- A plan and the resulting subscription reference one node group. Subscription delivery joins through that group and only returns active endpoints on enabled, online nodes. +- Subscription delivery normalizes endpoint order before rendering. Active subscriptions are aggregated by expiry then ID; credential-backed endpoints keep their owning subscription's group position, and legacy endpoints use the earliest applicable subscription group. ZNet Sink, Clash / Mihomo and sing-box renderers all consume that same ordered endpoint slice, including policy-group members, so one saved business order produces the same relative client order across formats. +- Saving a protocol endpoint does not inherently publish a node. Name, client address or port, global delivery order, tags and traffic multiplier are applied without a Zero restart. Runtime fields publish the current node only while the endpoint was or becomes active; carrier-node moves publish the previous and target sides only when each side has an active runtime to remove or add. The publisher still compiles every active endpoint and active subscription credential on an affected node into one complete Zero runtime configuration, records the desired SHA-256, validates it with the installed binary, atomically switches the generation, restarts Zero, checks the local control socket and waits for a fresh authenticated Connector event. Runtime compilation is ordered by stable endpoint identity rather than client-delivery order, so later unrelated publications do not change runtime hashes after a business reorder. +- `protocol_deployments` records desired and applied configuration hashes. A deployment is successful only after the running node passes local health and Connector-event verification; validation or verification failure restores the previous generation and environment. The explicit deploy action is a retry of the same full publish path, not a single-file staging action. +- The runtime-log API merges `protocol_deployments`, `node_operations` and `tasks` into one newest-first operational view while retaining each source record and its error/output. `audit_logs` remains a separate security and business-change trail. + +## Traffic and quota accounting + +- `flow_usages` is the idempotent cursor for Connector-delivered `flow.updated` and `flow.completed` events. Live events charge only the new cumulative delta; the completed event settles any missing final delta. +- Protocol business load is derived from active `flow_usages` seen within the last two minutes. `active_flows` counts current flow IDs and `active_users` counts distinct subscription owners, so allocated credentials are never presented as currently connected people. Administrators see the aggregate on every protocol endpoint; subscribers see only aggregate load for endpoints reachable through their own currently usable node groups. No other user's identity, credential, host detail or traffic record crosses that boundary. +- `protocol_endpoints.managed_principal_ready` gates subscription-specific Trojan and Hysteria2 credentials on the successfully published kernel generation. Older kernels continue to receive and advertise the endpoint fallback credential; a failed publication never exposes credentials that the running node has not accepted. +- Zero uses a generic Webhook event sink with a disk-backed outbox and an opaque authorization header. Every event is authenticated with the node Connector credential. Lifecycle events update Connector activity; flow events are mapped through the native `principal_key` and its protocol credential. Request-provided user IDs are never trusted for billing. The legacy signed node-report endpoint and the old heartbeat/command routes remain compatibility-only. +- Native speed and device policies are projected only when a subscription has one active protocol credential. Copying a subscription-wide limit to multiple independent Zero processes would multiply its allowance. Cross-node speed/device aggregation, directional or weighted traffic calculation and quota balance therefore remain panel-owned until an acknowledged distributed policy protocol exists; `quota_remaining_bytes` is not emitted by zboard. +- Zero `0.0.15-rc.4` adds attributable `principal_key` support to + `MieruUserConfig`. Zboard gates Mieru by the selected or actually installed + node version: older nodes reject creation, re-enabling and publication while + retaining records for disable/delete recovery; rc.4 and newer generate one + encrypted password and principal per subscription. A successful + fallback-bearing migration publication is followed under the same node lock + by a fallback-free publication. Only after both validate, activate, pass + health checks and receive Connector confirmation does + `mieru_principal_ready` switch subscription delivery. `credential_id` + remains panel-side metadata and is never emitted into Zero configuration. + The contract and rollout behavior are documented in + `docs/mieru-kernel-contract.md`. +- The native access contract is an explicit staged boundary: `ZBOARD_ZERO_KERNEL_CONTRACT=legacy` keeps the latest stable GitHub tag only as the unattended batch default, while an operator may explicitly select any published stable or prerelease tag. The selected release uses its immutable `zero-linux-x86_64.tar.gz` GNU artifact or musl artifact plus exact same-name `.sha256`; musl resolution accepts both the current `zero-linux-x86_64-musl.tar.gz` contract and the historical release-owned `zero-v-linux-x86_64-musl.tar.gz` contract. The backend re-resolves every selected version rather than accepting a client URL; an explicit older target also requires a separate downgrade confirmation. Connector serialization follows the selected or actually installed Zero version independently of that access switch: releases through `0.0.15-rc.1` receive the historical `api_key_env` plus `push` contract, while `0.0.15-rc.2` and later receive the controller-neutral Webhook contract with opaque `headers`, the complete `/api/zero/events` URL and a durable outbox. Configuration-only publication probes the node first so it cannot reuse a stale desired-version contract. A trusted-directory historical versioned musl file remains a bounded fallback when the corresponding older GitHub Release has no usable musl pair. `native-local` enables managed users only with an explicit `ZBOARD_ZERO_LOCAL_VERSION`; it still resolves the exact `zero-v-linux-x86_64-musl.tar.gz` plus `.sha256` from the trusted artifact directory and never substitutes a GitHub release. Synchronizing zboard alone does not publish or upgrade the local kernel. +- `traffic_calc_mode` selects upload plus download (`0`), upload only (`1`) or download only (`2`). +- Billed traffic is calculated with integer thousandths: `selected_bytes * protocol_multiplier_milli / 1_000`, rounded up. +- `traffic_records` stores the direction policy and protocol multiplier snapshot, so later endpoint changes never alter historical accounting. +- Human-facing traffic history is a read-only projection over `traffic_records`: details can group by minute, hour or UTC day while retaining user, subscription, node and multiplier dimensions. The node-series projection applies the same authorization and optional filters, then intentionally groups away user/subscription identity to answer node-capacity questions and produce a descending node-total ranking; it never creates a second accounting fact. +- Account usage pages carry bounded `facets.nodes` and `facets.subscriptions` reference maps for their own rows. A top-node chart ranking is not a complete directory. Subscription labels are independently restricted to the authenticated owner; deleted or incorrectly associated foreign subscriptions remain explicit missing references. Node references expose only ID, name, region and lifecycle status, never host or credential fields. Page rows and reference maps are replaced atomically by the client. +- Reconciliation scopes raw traffic totals by the selected subscription IDs before aggregation, not by the raw row's user ID: a misattributed record remains visible as an accounting fact for its subscription. Issue-page counts reuse summary counts and page rows reuse their joined totals. Cursor seeks on usage buckets retain the complete cursor bucket before aggregation; the raw ID must not be used to cut a bucket before its `MIN(id)` and sums are computed. Request cancellation propagates through these read queries, and no accounting writes or persisted aggregate tables are introduced. +- Grouped usage supports an independent `view=usage_summary` projection with `total`, `aggregates`, `bucket` and `as_of`. Its sum/count reads run in one database transaction. Current clients request live pages with `include_totals=false`, receiving explicit null totals/aggregates; they do not fabricate zero or reconstruct whole-range totals from visible rows. The separate statistics snapshot is keyed by authorization/filter/range/bucket, not cursor or page size, and is refreshed on scope changes or an explicit refresh. The UI displays its timestamp and keeps paging usable while statistics are pending or fail; live rows and an older labelled statistics snapshot are not a single cross-request snapshot. +- The server coalesces concurrent identical statistics/trend reads and reuses completed display snapshots for at most two seconds from query start, with 32 entries per projection and per handler instance. Errors are not cached. All account authorization and trend subscription references are read live. Grouped pages with totals expose `statistics_as_of` (null when totals are omitted); summary/trends retain `as_of`. Repeated refresh within this interval may return the same timestamp. These snapshots never participate in settlement, balance, revocation or ledger verification. +- Account subscription filter options use the paged self-service subscription reader, with `q` matching plan/SKU names or an exact subscription ID before count/offset/limit, always inside the authenticated owner. Historical subscriptions are selectable without an active-status restriction. A URL-selected ID is resolved independently, not replaced by a preview row. Trend responses no longer load a subscription directory by default; `include_subscriptions=true` is an explicit deprecated compatibility opt-in only, never used by current UI filters. +- `(node_id, report_id)` provides retry idempotency and `(node_id, nonce)` prevents replay. +- `quota_events` is the auditable allocation/usage ledger. Subscription counters remain the fast balance projection and can be reconciled against traffic and quota events. + +## Background work + +- `tasks` contains common locking, retry and progress fields. Creation uses an idempotency key and resolves the JSON scope into concrete targets before commit. +- `task_items` stores independently retryable recipients or quota targets. Batch progress is therefore derived from concrete items rather than an opaque JSON list. +- Node-group membership mutations persist a `node_group_reconcile` task in the same database transaction. Its first item revokes active subscription credentials outside the current group membership and ensures credentials for current endpoints. Node items are included only for changed endpoints whose protocol stores per-subscription credentials and whose group currently has usable active subscriptions; an empty node scope still records the credential reconciliation without publishing unrelated runtime configuration. This keeps `Subscription -> NodeGroup -> ProtocolEndpoint` as the authorization boundary while making failed credential or node work visible and retryable. +- Quota tasks lock each subscription, reject reductions below already-used traffic, write an idempotent `quota_events` adjustment and queue affected node configurations for reconciliation before completing the item. +- Email tasks are disabled by default. They require encrypted SMTP credentials and either STARTTLS or implicit TLS. Completed recipients are skipped on retry; SMTP delivery remains at-least-once if the process stops after a remote server accepts a message but before the item status commits. +- `email_templates` owns revisioned plain-text presentation content, not delivery state. The fixed `user.registered` template can be enabled or disabled but not deleted; operational templates are reusable drafts. Creating a task snapshots the subject, body, template provenance and current site identity into `tasks.content`, then recipient variables are resolved from the target `users` row at execution time. Template edits therefore cannot rewrite queued or historical messages. +- SMTP verification has two explicit side-effect levels: connection mode completes DNS/TCP, TLS, optional authentication and `NOOP` without sending mail; delivery mode sends one test message to the administrator-specified recipient. Neither endpoint returns SMTP credentials. +- The admin task summary is a read model derived from `tasks` and `task_items`: it exposes task-state counts plus active target progress without creating a second queue or duplicating task ownership. +- Execution is initiated through the admin API or the management page. The lock expiry permits recovery of a task left running by a terminated process. + +## Relationship summary + +```text +nodes -> protocol_endpoints +provider_accounts -> managed_dns_records -> nodes +protocol_endpoints <-> node_group_endpoints <-> node_groups +plans -> node_groups +plans -> plan_skus +users -> orders -> plan_skus +orders -> subscriptions -> node_groups +subscriptions -> protocol_credentials -> protocol_endpoints +subscriptions -> subscription_members +users -> subscription_tokens; subscription_templates -> export representation +protocol_credentials -> flow_usages -> traffic_records +subscriptions -> quota_events +tasks -> task_items +email_templates -> tasks (content snapshot only) +``` + +## Migration ownership + +The embedded SQL under `backend/migrations` is the production schema source of truth. Before the first public release, `0001_init.up.sql` directly expresses the complete v0.0.1 resource model: plans reference one node group, node groups own explicit protocol-endpoint membership, subscriptions retain their granted plan/SKU/node-group snapshot, and no legacy plan-to-endpoint or access-group tables are created. Startup records applied files in `schema_migrations`; GORM `AutoMigrate` is not used in production startup. + +Existing databases from the former v0.0.1 development chain are accepted only after they reached its terminal migration. Startup verifies the final schema signature, removes only the empty legacy template archive and renames the stale access-group index. Previously applied migration rows remain as rollback compatibility metadata even though their SQL files are no longer shipped; a fresh database records only `0001_init.up.sql`. Partial and unversioned non-empty schemas are rejected. After v0.1.0 is released, the baseline becomes immutable and all schema changes use append-only migrations. See [database-migrations.md](/projects/zboard/reference/database-migrations). + +### 管理员分配订单 + +管理员通过 `POST /api/v1/admin/orders` 为指定用户创建待付款订单。选择商品规格后,可以覆盖 `payable_amount`(整数分);未指定时使用规格价格。`amount_cents` 保留规格原价,低于原价的差额记录为 `discount_amount`,高于原价时优惠为零。用户订单列表展示最终应付金额。 + +`orders.assigned_by`、`assignment_note` 保存分配人和原因,创建订单与 `order.assign` 审计日志在同一事务中提交。详情只向管理员公开分配记录。`assignment_fingerprint` 不对外返回;管理员 ID 与请求 UUID 派生唯一交易号,用来防止重复请求创建多笔订单。重复请求返回原订单当前状态,不重新开通。 + +分配只生成 `pending` 订单,即使应付金额为零也不自动开通。收到付款并确认后,复用现有履约事务,为订单所属用户开通或续费订阅、分发其显式授权范围内的凭证,实际收入按该订单最终实收金额统计。 diff --git a/docs/projects/zboard/reference/database-migrations.md b/docs/projects/zboard/reference/database-migrations.md new file mode 100644 index 0000000..94c1b25 --- /dev/null +++ b/docs/projects/zboard/reference/database-migrations.md @@ -0,0 +1,128 @@ +# Database migrations + +> Historical baseline note: v0.0.1 has been publicly released. The pre-public v0.0.1/v0.1.0 numbering and baseline-squashing policy below document earlier development work, not permission to rewrite a published schema. For an existing installation, verify the migration behavior of the exact source and target builds. First-time installation is covered in the [installation guide](/projects/zboard/guides/installation-en). + +Zboard embeds its SQL schema in the backend binary and records applied files in +`schema_migrations`. Production startup does not use GORM `AutoMigrate`. + +## Historical v0.0.1 baseline policy + +`v0.0.1` is the development baseline before the first public release. The +repository therefore contains one migration pair: + +- `backend/migrations/0001_init.up.sql` +- `backend/migrations/0001_init.down.sql` + +The up migration directly creates the complete current business schema and +seeds the 13 system configuration entries and three built-in subscription +templates. It does not retain development-only `ALTER TABLE`, temporary +rewrite tables, data backfills, legacy executable-template archives or +environment-specific auto-increment counters. + +Until `v0.1.0` is released, new schema work must update this baseline and its +clean-database verification. Do not add `0002` merely to preserve an +unreleased development step. + +## Fresh database + +Point the backend at an empty MySQL 8 database and start it normally, or run: + +```bash +cd backend +../scripts/migrate.sh +``` + +```powershell +cd backend +../scripts/migrate.ps1 +``` + +The runner creates `schema_migrations`, applies `0001_init.up.sql`, validates +the final table/column/index signature and records one applied version: +`0001_init.up.sql`. + +Do not manually create application tables or insert a migration record. + +## Existing pre-squash v0.0.1 database + +An existing development database can be retained only when the previous build +already completed the former chain through +`0032_subscription_policy_group_targets.up.sql`. + +On first startup with the squashed build, the runner: + +1. confirms the original `0001_init.up.sql` record exists; +2. refuses a multi-entry history that never reached the terminal development + migration; +3. validates all final business tables, selected critical column types and + cursor/concurrency indexes; +4. removes the empty legacy subscription-template archive table and renames + the old node-group index to its final resource name; +5. adds the nullable managed-certificate provider ownership, Webroot path, + provider index and provider foreign key when an earlier v0.0.1 development + database does not yet contain them; +6. adds the non-null `protocol_endpoints.mieru_principal_ready` publication + gate when it is absent; +7. leaves the already-applied development rows in `schema_migrations` so the + immediately previous development binary remains usable for rollback. + +This path does not rerun the baseline and does not rewrite normal business +rows. An archive table containing rows blocks finalization so an operator can +export or deliberately remove that data first. + +The retained rows are compatibility metadata only. Their SQL files are not +shipped in the squashed build and a fresh database records only +`0001_init.up.sql`. A database created from the squashed baseline cannot be +opened with an older pre-squash binary. + +Before replacing a development build, back up: + +- the MySQL database; +- the credential-encryption key; +- the currently deployed source or image needed for rollback. + +If the database has a partial migration history, start the previous +development build and finish its migrations before using the squashed build. +If a non-empty schema has no migration history, recreate it or perform an +explicitly reviewed data migration; the runner will not guess its origin. + +## Verification + +For a fresh database, verify: + +```sql +SELECT version FROM schema_migrations ORDER BY version; +SELECT COUNT(*) FROM information_schema.tables + WHERE table_schema = DATABASE() AND table_name <> 'schema_migrations'; +SELECT COUNT(*) FROM system_configs; +SELECT COUNT(*) FROM subscription_templates; +``` + +Expected baseline results are one migration record, 43 business tables, +13 system configuration rows and three built-in templates. + +For a retained development database, also verify that application row counts +and the previous applied-history rows remain unchanged across the upgrade, and +that the obsolete +`subscription_template_legacy_archives` table and +`uk_access_groups_code` index are absent. + +## Durable node publication queue + +The baseline includes `node_config_publishes`. For an existing development +schema, startup creates this table additively from the same embedded DDL. +SQLite upgrades include it in the model inventory. Existing business rows are +preserved; pending publication rows are included in cross-database migration. +The due-work index is `idx_node_publish_due`. + +## Policy after v0.1.0 + +Once `v0.1.0` is released: + +- `0001_init.up.sql` becomes immutable; +- every released schema change receives a new ordered up/down migration; +- previously published migration content and filenames are never rewritten; +- destructive changes require an explicit backup, compatibility and rollback + plan; +- clean-install schema and sequential-upgrade schema must be verified as + equivalent before release. diff --git a/docs/projects/zboard/reference/history/core-rc.md b/docs/projects/zboard/reference/history/core-rc.md new file mode 100644 index 0000000..299d214 --- /dev/null +++ b/docs/projects/zboard/reference/history/core-rc.md @@ -0,0 +1,70 @@ +# Core hardening RC freeze — 2026-09-06 + +::: info 历史资料 +此页保留迁移前的发布或工程记录,供维护者追溯;不作为当前安装教程、产品定位或功能可用性说明。使用入口见[项目介绍](/projects/zboard/)。 +::: + +This release candidate freezes the current self-use panel scope: authentication, +users, orders, subscriptions, nodes, traffic accounting, announcements and public +documents. Online payment channels and a plugin runtime remain deferred. + +## Included fixes + +- Persist node configuration publication with transactional enqueue, retries and + process recovery. Revoke expired/exhausted subscriptions and reconcile group changes. +- Preserve order fulfillment idempotency and accounting consistency under concurrent + requests and replayed traffic reports. +- Reduce SQLite accounting and reporting contention, bound display statistics caches, + and accelerate raw event-spool reads without changing accounting authority. +- Fix administration detail lifecycle, subscription-rule forms, order interactions + and theme consistency. + +## Verification and release boundary + +The standard 300-second mixed workload passed on 1 CPU / 1 GiB with 1,000 +subscriptions, 100,000 historical records, 100 unique events per second and +concurrent administration reads. Traffic-page p95 was 231 ms, trends p95 was +686 ms and sampled peak working set was 163.2 MiB. Final expected, ledger and +subscription totals matched; the event spool drained completely. + +Real isolated Zero tests passed expiry, quota exhaustion, single-node group +switching, and SSH publication failure followed by publisher-process recovery. +Backend tests/vet and targeted race checks passed. These performance and real-node +measurements predate the final dependency security updates below. Per-run evidence is retained outside version control. Reproducible methods are +documented in the [development guide](/projects/zboard/contributing/development#performance-and-stability-verification); +the remaining acceptance limits are listed below. + +This is a candidate for self-use and small deployments. The 24-hour soak, +500-events-per-second burst and broader multi-node/full-process recovery matrix +are not complete. These remain follow-up acceptance work; this RC does not claim +the full stable-release checklist is complete. + +## Release security corrections + +The final push surfaced dependency advisories that the earlier functional acceptance +had not assessed. Source-level `govulncheck` identified calls to vulnerable SSH, JWT, +HTTP/TLS and telemetry dependencies. The RC updates the Go toolchain to 1.26.8 within +the existing 1.26 series and upgrades affected backend dependencies to patched versions. +Docker and development-script toolchain baselines are kept in sync. + +After these updates, full backend tests, vet and the pure-Go SQLite checks passed. +Source-level `govulncheck ./cmd/zboard` reported zero reachable vulnerabilities and +zero additional imported-package vulnerabilities. One advisory remains at module +level for unused OpenPGP code; the panel does not import that package. + +Frontend audit findings concern development/test tools and compiler dependencies. +A production Rollup module inventory confirmed that happy-dom, Vitest, nanoid, +PostCSS and the Vue SFC compiler are absent from emitted browser chunks. The runtime +Docker stage contains static browser assets and Go/Rust binaries, not Node.js tools. +Development dependency advisories remain follow-up maintenance, not a claim of a +clean dependency manifest. Two unquoted OpenAPI descriptions were also corrected +after release CI identified YAML flow-mapping schema errors. + +## Upgrade and rollback + +Back up the database and credential encryption key together before upgrading. +Startup applies the persistent publication queue and SQLite reporting/access +schema reconciliation. Preserve the event-spool directory across restarts. +For rollback, restore the previous image with its matching pre-upgrade database +backup; retain the original spool and backup until reconciliation is complete. +Do not run two panel versions against the same writable database during rollback. diff --git a/docs/projects/zboard/reference/history/launch-checklist.md b/docs/projects/zboard/reference/history/launch-checklist.md new file mode 100644 index 0000000..78a6e76 --- /dev/null +++ b/docs/projects/zboard/reference/history/launch-checklist.md @@ -0,0 +1,151 @@ +# zboard v0.1.0 release checklist + +::: info 历史资料 +此页保留迁移前的发布或工程记录,供维护者追溯;不作为当前安装教程、产品定位或功能可用性说明。使用入口见[项目介绍](/projects/zboard/)。 +::: + +> Status: planned first public release. The current development baseline is `v0.0.1`. + +Scope follows the [core and hardening baseline](/projects/zboard/reference/core-baseline). +The core must work without plugins. Existing flows require regression evidence; +online payment channels and a plugin runtime are deferred extensions, not release gates. + +## 0. Version validation + +- `VERSION` is `v0.1.0` +- `backend/internal/version/version.go` has `Version = "v0.1.0"` +- Go baseline validation: + - `backend/go.mod` contains reviewed `go` and `toolchain` directives + - CI and the Docker builder use the same toolchain family + - `go mod tidy`, tests, vet, and build complete without changing tracked files +- Before release, run and record: + - `./scripts/verify-env.ps1` or `./scripts/verify-env.sh` + - Confirm output includes: + - `Go mod baseline` + - `Go toolchain baseline` + - the checked-in Go toolchain baseline + +## 1. Environment preparation + +- Set `ZBOARD_GOROOT_FALLBACK` only when CI or the operator uses a preinstalled + SDK outside `PATH`; never record a workstation-specific absolute path here. +- Ensure scripts: + - `scripts/ensure-go-env.ps1` + - `scripts/ensure-go-env.sh` +- Env check: + - `./scripts/verify-env.ps1` + - `./scripts/verify-env.sh` +- Datastore: MySQL 8+ +- Production configuration contains independent random values for: + - `ZBOARD_MYSQL_ROOT_PASSWORD` + - `ZBOARD_MYSQL_PASSWORD` + - `ZBOARD_JWT_SECRET` + - `ZBOARD_CREDENTIAL_ENCRYPTION_KEY` +- No `generate-` or `choose-` placeholder remains in the deployment environment. +- For a new database, `/setup` completed with the intended site name, public URL, + registration policy, and a unique first-administrator password. +- A second `POST /api/v1/setup/install` is rejected with HTTP 409. +- Nodes can be reached via SSH (`ssh` host/port + authentication) +- Each node has an independently verified OpenSSH SHA256 host-key fingerprint. +- Database backup and secret backup together can decrypt node credentials in a restore rehearsal. + +## 2. Local start and smoke tests + +### Start + +```powershell +cd scripts +./start-dev.ps1 -WithFrontend -StopWhenDone +``` + +```bash +cd scripts +./start-dev.sh --with-frontend --stop-when-done +``` + +### Health checks + +- `GET /healthz` => service response +- `GET /readyz` => readiness response +- `GET /api/v1/version` => `v0.1.0` +- `GET /api/v1/system/info` => system info +- `GET /api/v1/auth/me` => auth check + +## 3. Business feature validation + +1. User / Auth + - `POST /api/v1/auth/register` + - `POST /api/v1/auth/login` + - `GET /api/v1/auth/me` +2. Nodes, protocol endpoints and node groups + - `POST /api/v1/nodes` + - `PUT /api/v1/nodes/:id` + - `POST /api/v1/nodes/ssh/test` + - `POST /api/v1/nodes/:id/report-credential` + - Verify invalid signature, expired timestamp, reused nonce, and duplicate report ID behavior. + - `POST /api/v1/admin/protocol-endpoints` + - `PUT /api/v1/admin/protocol-endpoints/:id` + - `POST /api/v1/admin/protocol-endpoints/:id/deploy` + - `POST /api/v1/admin/node-groups` + - Verify password/private-key authentication, automatic first-connection host-key enrollment, mismatch rejection and explicit trust reset. + - Verify plans select a node group and cannot bind protocol endpoints directly. +3. Orders and subscription + - `GET /api/v1/plans` + - `POST /api/v1/orders` + - `POST /api/v1/admin/orders/:id/pay` (administrator confirmation) + - Verify duplicate confirmation grants entitlements only once, cancellation + races preserve a valid terminal state, and ordinary users cannot confirm orders. + - Existing internal pay-callback routes retain administrator authentication; + they are not public payment-provider callbacks. + - `GET /api/v1/subscriptions` + - `POST /api/v1/traffic/report` + - `GET /api/v1/traffic/summary` + - Verify traffic replay does not double-count usage; restart recovery does not + lose committed accounting; expired/exhausted subscriptions and revoked + credentials stop authorizing access, including on the node. + - Verify failed node publication cannot be presented as applied configuration. +4. Admin + - `GET /api/v1/admin/users` + - `POST /api/v1/admin/users` + - `PUT /api/v1/admin/users/:id` + - `GET /api/v1/admin/dashboard` + +Core hardening evidence: + +- Record browser checks for login/register, orders, subscription access, usage, + announcements and public documents, including failures and retries. +- Record backend tests/vet, frontend tests/build and OpenAPI validation. +- Meet the [resource and performance gates](/projects/zboard/reference/history/roadmap) + with a reproducible workload and a 24-hour stability report; attach measurements, + not only pass/fail statements. +- Record behavior and resource use with optional capabilities disabled. Core + accounting and node authorization must remain correct. + +## 4. Docker deployment validation + +- Build the release image from a clean checkout with the locked frontend dependencies. +- Start MySQL and zboard through the supported Compose deployment. +- Confirm startup fails closed when required production secrets are missing or placeholders are used. +- Back up the database before replacing the application image. +- Verify `/healthz`, `/readyz`, `/api/v1/version`, login, orders, and subscriptions. +- Restore the previous image and database backup in a rehearsal environment and record the result. +- Kubernetes deployment and Kubernetes-driven rollout APIs are outside the v0.1.0 support scope. + +## 5. Release packaging + +- `git tag -a v0.1.0 -m "release v0.1.0"` +- `git push origin v0.1.0` +- GitHub Actions release job succeeds: + - Docker image published + - Release metadata includes: + - `version` + - `commit` + - `build time` + +## 6. Emergency notes + +- If you hit local network issues while fetching Go and need to continue, temporarily set: + - `ZBOARD_ALLOW_STALE_GO_VERSION=1` +- Before continuing the next release run: + - confirm the configured Go executable works + - confirm `scripts/verify-env.ps1` or `verify-env.sh` passes diff --git a/docs/projects/zboard/reference/history/roadmap.md b/docs/projects/zboard/reference/history/roadmap.md new file mode 100644 index 0000000..5fcefda --- /dev/null +++ b/docs/projects/zboard/reference/history/roadmap.md @@ -0,0 +1,88 @@ +# zboard implementation roadmap + +::: info 历史资料 +此页保留迁移前的发布或工程记录,供维护者追溯;不作为当前安装教程、产品定位或功能可用性说明。使用入口见[项目介绍](/projects/zboard/)。 +::: + +> 历史规划说明:本文保留首个公开版本发布前的阶段划分、编号方案和验收目标。`v0.0.1` 已公开发布;下文将 `v0.1.0` 视为“首个公开版本”的表述,以及阶段完成状态,不代表当前发行状态。可用版本以 [Releases](https://github.com/zerodenet/zboard/releases) 为准,当前使用入口见[文档导航](/projects/zboard/guides/)。性能预算是验收目标,不是已测容量。 + +> 当前开发版本固定为 `v0.0.1`。在所有公开发布条件满足前,不因为内部阶段变化而提升版本号;首个对外发布版本仍为 `v0.1.0`。 + +> 2026-09-05 方向对齐:以 [核心能力与加固基线](/projects/zboard/reference/core-baseline) 为产品和工程约束。已有基础闭环采用小步修复与性能加固;内部服务为未来插件保留边界。在线支付和插件运行时不属于当前基础版发布前置条件。下表和历史完成项不代替当前提交的实测验收。 + +> 按[开发指南中的性能与稳定性验收方法](/projects/zboard/contributing/development#performance-and-stability-verification),依次验证计量基准、1C1G 混合负载、真实节点撤权和 24 小时长稳;每次运行的原始证据保存在版本库外。 + +## 版本原则 + +- 从当前状态到公开发布前的全部研发、加固和验收工作都属于 `v0.0.1`。 +- 工程基线、安全边界、业务闭环、可运营性和发布验收是工作阶段,不是版本号。 +- 各阶段允许交叉、回退和重复验证,不通过版本升级制造虚假进度。 +- 候选构建使用 Git commit、构建时间和流水线编号标识,不创建中间 `v0.0.x` 阶段版本。 +- 只有全部发布门禁通过后,才将版本从 `v0.0.1` 切换为 `v0.1.0` 并发布。 +- Kubernetes 清单和集群级滚动升级不在当前支持范围内;通过已验证 SSH 管理单台节点的 Zero 安装、配置、升级和回滚属于节点运维能力。 + +## v0.0.1 工作阶段 + +| 阶段 | 状态 | 目标 | 主要交付 | 退出条件 | +| --- | --- | --- | --- | --- | +| A. 工程基线 | 已完成 | 建立可重复验证的仓库基线 | 统一版本;锁定 Go/Node/pnpm;前端锁文件;后端测试;OpenAPI 校验;CI;Docker-only 范围收口 | 后端 test/vet/build、前端 typecheck/build、OpenAPI lint 和 Docker Compose 配置校验通过 | +| B. 安全与信任边界 | 进行中 | 消除公开部署前的高风险默认行为 | 禁止默认生产管理员;强制安全 JWT 密钥;日志脱敏;SSH 凭据安全存储与主机密钥校验;节点上报鉴权、签名和幂等 | 弱默认配置不能进入生产;敏感信息不落日志;伪造或重复上报被拒绝 | +| C. 可信业务闭环 | 已有实现,待加固验收 | 加固订单、订阅和流量的现有闭环 | 管理员确认订单幂等;订阅生效/过期/耗尽;节点客户端配置交付;流量对账 | 下单、管理员确认、获取订阅、使用流量和额度扣减的正常/异常端到端测试通过 | +| D. 可运营性 | 待完成 | 建立定位、恢复和回退能力 | 结构化日志、指标和审计;备份恢复;Docker 蓝绿切换与回退演练;故障诊断;端到端冒烟测试 | 可观测、可备份、可恢复、可回退,并保留演练证据 | +| E. 低资源部署 | 进行中 | 单机 1 vCPU / 1 GiB 可持续运行 | 有界查询和响应;低并发连接池与后台任务;移除无消费者的运行依赖;MySQL/SQLite 方言隔离;SQLite 部署档案 | 1C1G 压测和长稳验收达标,MySQL/SQLite 语义测试一致 | +| F. 公开发布验收 | 待完成 | 冻结并验证 `v0.1.0` 公开契约 | API/配置兼容性冻结;安全审计;安装与升级文档;候选镜像;真实环境验收 | 发布清单全部通过,无阻断级缺陷 | + +这些阶段描述责任边界和验收顺序,不代表自动推进。安全与可信数据链路是业务闭环验收的前置条件,但修复期间项目版本仍保持 `v0.0.1`。 + +## 当前进度 + +### 已完成 + +- [x] 将仓库版本统一为 `v0.0.1`,明确 `v0.1.0` 为首个公开版本。 +- [x] 固定 Go 1.26.8、Node.js 22.23.0、pnpm 11.9.0。 +- [x] 增加 pnpm 锁文件、Vue 类型检查和可复现安装。 +- [x] 增加后端关键辅助逻辑与版本元数据测试。 +- [x] 建立后端、前端和 OpenAPI CI 门禁。 +- [x] 移除 Kubernetes 资源、升级 API、前端入口和脚本。 +- [x] 保留 Docker Compose、Docker 镜像和单二进制部署路径。 +- [x] 移除固定管理员密码和 JWT 回退密钥,生产环境强制显式安全配置。 +- [x] 对日志中的数据库 DSN 脱敏,并拒绝生产 root 账号和已知弱数据库口令。 +- [x] 使用 AES-GCM 加密节点 SSH 密码或私钥,首次连接自动登记 SHA256 主机密钥,后续固定校验并要求显式重置信任。 +- [x] 为节点签发独立流量上报凭据,增加 HMAC 签名、五分钟时间窗、nonce 防重放和 report ID 幂等约束。 +- [x] 对订单结算和同用户续期加锁,阻止重复续期与已支付订单状态回退,并统一收口过期/耗尽订阅状态。 +- [x] 协议端点发布同时校验服务端与客户端 JSON,避免订阅继续交付旧客户端配置。 +- [x] 为用户、订单、协议配置和凭据生命周期补齐审计写入,并提供管理员分页查询界面。 +- [x] 将每笔可信流量扣减归属到实际订阅,并提供用户/管理员订阅级对账结果。 + +### v0.0.1 剩余工作 + +1. 补齐订单、订阅、配置交付和流量扣减的代码级闭环测试与异常路径。 +2. 加固管理员订单确认、取消与权益发放的一致性,逐步提取统一内部服务;保持当前内部回调的管理员认证。支付渠道签名接入和外部回调来源认证由未来支付插件实现,不作为基础版缺口。 +3. 补齐可观测性、备份恢复、Docker 回退演练和公开发布证据。 +4. 按 [Zero 节点生命周期](/projects/zboard/reference/node-kernel-lifecycle) 实现探测、安装、整机配置编译、校验、启动、健康确认与可回滚升级;不得把“SSH 可连接”或“配置文件已上传”显示成内核健康。 +5. 完成下述 1C1G 与 SQLite 迁移门禁;在双数据库语义测试通过前,SQLite 不作为受支持的生产数据库。 +6. 按核心基线逐项闭环前后端缺陷,记录复现、根因、回归与性能对比;按实际依赖隔离 Fair Use 等非核心能力,保持基础计量、限制和既有配置有效。 +7. 随业务修复建立窄服务与类型化错误,避免 handler 相互调用和扩展直接写核心数据;现阶段不建设插件加载器、市场或通用事件总线。 + +## 1C1G 与 SQLite 迁移门禁 + +### 资源和性能预算 + +- 单机档案以 1 vCPU、1 GiB RAM 为硬上限;应用与 SQLite 的稳定工作集目标不超过 700 MiB,并至少给操作系统和突发请求保留 300 MiB。 +- 普通分页和详情接口在代表性数据集上的服务端 p95 不超过 250 ms;受控的流量趋势/对账接口 p95 不超过 1 s,单次数据库查询不得依赖反向代理超时结束。 +- 列表、图表和导出都必须有服务端分页、游标或明确结果上限。节点流量序列默认最多 8 个节点、约 12,000 个点;选择单节点后才允许扩大时间窗口。 +- 低资源默认数据库连接池为 8 个打开连接、2 个空闲连接;固定并发后台任务必须改成共享预算或可配置并发,不能让多个四路 worker 在 1 核上相互争抢。 +- 前端首屏不得同时触发所有重聚合接口;独立视图按优先级渐进加载,前端只负责有界结果的展示转换,不承担全量数据聚合。 + +### SQLite 推进顺序 + +1. 引入数据库驱动和方言能力边界,先保持 MySQL 行为不变;时间分桶、错误分类、锁、upsert、索引检查和表元数据查询不得继续散落在 handler/datastore 中。 +2. 将迁移拆成 MySQL 与 SQLite 两套可审计路径,为 SQLite 明确启用 WAL、`busy_timeout`、外键约束,并限制写并发。 +3. 对 CGO 与纯 Go SQLite 驱动做独立构建、迁移、备份恢复和基准验证,再固定生产驱动;驱动选择不得只以“本机能编译”为验收依据。 +4. 在 CI 中对 MySQL 与 SQLite 执行同一套订单、订阅、流量扣减、幂等和分页语义测试;任何方言分支都必须有回归覆盖。 +5. 增加 1C1G 容器档案、代表性数据生成器和至少 24 小时长稳测试,记录 RSS、CPU、连接数、查询 p95/p99、错误率及数据库文件增长。 + +## v0.1.0 发布门禁 + +`v0.1.0` 不是按日期或阶段自动产生的版本。只有上述剩余工作和 +[v0.1.0 release checklist](/projects/zboard/reference/history/launch-checklist) 全部完成,才允许修改版本号、创建 tag 和推送公开镜像。 diff --git a/docs/projects/zboard/reference/history/v0.0.1.md b/docs/projects/zboard/reference/history/v0.0.1.md new file mode 100644 index 0000000..048bde2 --- /dev/null +++ b/docs/projects/zboard/reference/history/v0.0.1.md @@ -0,0 +1,19 @@ +# ZBoard v0.0.1 + +::: info 历史资料 +此页保留迁移前的发布或工程记录,供维护者追溯;不作为当前安装教程、产品定位或功能可用性说明。使用入口见[项目介绍](/projects/zboard/)。 +::: + +首个统一编号正式版,与 Zero Core 和 ZNet Sink v0.0.1 配套。旧 Release 与 tag 已按项目所有者要求清理。 + +- 提供用户、订阅、节点、订单、流量与规则管理,以及内核配置发布和事件计量。 +- 支持新 Zero v0.0.1 的通用 Connector、托管用户与 Mieru 用户归属;移除版本编号重置后失效的协议门槛。 +- 前置入口集成到协议服务,支持可选的节点共享代理池;落地协议授权必须显式分配。 +- 管理员可为指定用户创建待付款订单并调整应付金额,付款确认后开通权益,保留分配审计与防重复提交记录。 +- 资源删除不再依赖 SSH 或外部服务连通性,提供独立 Shell 工具清理节点残留。 +- 商品列表和详情展示商品描述,修复复制协议时的节点组选择。 +- 发布 Linux amd64 二进制包、Docker 镜像及镜像离线包,附 SHA256SUMS。 + +Docker 镜像:`ghcr.io/zerodenet/zboard:v0.0.1`,正式版同时更新 `latest`。同名版本重建后,部署端需要重新拉取镜像并重建容器。 + +升级前请备份数据库、配置与持久化目录。旧编号内核版本高于新 v0.0.1,切换内核需明确选择目标并允许降级。现有自动化测试与构建验证不等同于 24 小时长稳、500 events/s 突发或完整多节点故障恢复验收。 diff --git a/docs/projects/zboard/reference/index.md b/docs/projects/zboard/reference/index.md new file mode 100644 index 0000000..e1a144c --- /dev/null +++ b/docs/projects/zboard/reference/index.md @@ -0,0 +1,21 @@ +# 技术参考 + +这里面向维护者和集成开发者,说明面板的数据、配置交付与内核接口。首次部署请从[安装教程](/projects/zboard/guides/installation)开始。 + +- [核心能力与边界](/projects/zboard/reference/core-baseline) +- [数据模型](/projects/zboard/reference/data-model)与[数据库迁移](/projects/zboard/reference/database-migrations) +- [节点配置交付](/projects/zboard/reference/node-config-delivery)与[内核生命周期](/projects/zboard/reference/node-kernel-lifecycle) +- [规则兼容性](/projects/zboard/reference/managed-rule-compatibility) +- [Mieru 内核契约](/projects/zboard/reference/mieru-kernel-contract) +- [插件开发](/projects/zboard/plugins/development)与[身份接口](/projects/zboard/plugins/identity-reference) + +## 设计与历史资料 + +以下资料保留设计依据和当时的验收范围。计划项不代表当前版本已经实现,历史版本编号也不作为新安装的版本要求。 + +- [插件架构设计](/projects/zboard/reference/plugin-system-design) +- [插件市场设计](/projects/zboard/reference/plugin-marketplace-design) +- [0.0.1 发布记录](/projects/zboard/reference/history/v0.0.1) +- [核心 RC 验收记录](/projects/zboard/reference/history/core-rc) +- [历史发布检查清单](/projects/zboard/reference/history/launch-checklist) +- [历史路线图](/projects/zboard/reference/history/roadmap) diff --git a/docs/projects/zboard/reference/managed-rule-compatibility.md b/docs/projects/zboard/reference/managed-rule-compatibility.md new file mode 100644 index 0000000..f138ad9 --- /dev/null +++ b/docs/projects/zboard/reference/managed-rule-compatibility.md @@ -0,0 +1,43 @@ +# Managed rule import and client compatibility + +ZBoard imports independent rule sets, stores their matching conditions, and publishes stable client-specific URLs. Routing actions and ordering belong to subscription templates. + +## Importing dler-io/Rules + +Use files under `Clash/Provider/` with source format `auto` (recommended) or `clash_classical`. Auto detection accepts Provider YAML, line-oriented Classical rules, domain lists, CIDR lists and canonical JSON. An explicitly selected format is never silently changed. + +Supported Classical matchers are `DOMAIN`, `DOMAIN-SUFFIX`, `DOMAIN-KEYWORD`, `IP-CIDR`, `IP-CIDR6`, `PROCESS-NAME` and `PROCESS-PATH`. Process names and paths retain case and spaces. Unknown matchers fail with their position rather than being dropped. + +`sing-box/1.12/Head.conf` and `Rule.conf` are configuration fragments, not independent rule sets. They must be adapted in subscription templates; they are not importable as Provider files. Files containing only comments, such as the current `Media/MOO.yaml`, are rejected with an empty-source explanation. A rejected synchronization retains the previous source. + +## Storage and publishing + +Existing network-only documents retain the Zero Rule IR v1 shape and continue to compile to ZRS. ZBoard stores client-specific matchers in an optional `client_rules` extension, outside the Zero IR `rules` array: + +```json +{ + "version": 1, + "rules": [{ "type": "domain_suffix", "value": "example.com" }], + "client_rules": [{ "type": "process_name", "value": "Example.exe" }] +} +``` + +This extended document is internal ZBoard storage, not a new Zero kernel contract. Client-only sources may have `rules: []`, but the combined document must contain at least one matcher. Their database format is `managed_client_rules`. + +Clash YAML/text and sing-box source exports preserve both arrays. sing-box uses separate rule objects for different condition types, preserving the imported set's OR semantics. The sing-box artifact cache has a new format revision so previously generated files cannot bypass the corrected encoder. + +Zero currently cannot evaluate process rules. A source containing any client rules does not produce ZRS, is excluded from the Zero template picker, and is rejected if explicitly bound to a Zero template. Updates adding client rules to a source already bound by Zero templates are rejected before changing its content. There is no partial ZRS export that silently drops process conditions. + +The copied public URL for client-only sources selects sing-box source format. Clash templates automatically select the Classical YAML endpoint. Actual process detection remains a client/platform capability; forwarding a remote device's traffic does not provide its process identity. + +## Verification + +Normal backend tests cover parsing, canonical round trips, client exports, public downloads, Zero compatibility guards and rejected updates. To validate a checked-out external Provider corpus with a real sing-box binary: + +```sh +ZBOARD_RULE_PROVIDER_TEST_DIR=/path/to/Rules/Clash/Provider \ +ZBOARD_SING_BOX_VALIDATE_BIN=/path/to/sing-box \ +go test ./internal/handler -run TestManagedRuleProviderRepositoryCompatibility -v +``` + +Run from `backend/`. The optional corpus test performs no network downloads. It imports each nonempty YAML source, round-trips through Clash YAML, and compiles the sing-box output to SRS. diff --git a/docs/projects/zboard/reference/mieru-kernel-contract.md b/docs/projects/zboard/reference/mieru-kernel-contract.md new file mode 100644 index 0000000..383e5d5 --- /dev/null +++ b/docs/projects/zboard/reference/mieru-kernel-contract.md @@ -0,0 +1,77 @@ +# Mieru native principal attribution prerequisite + +Zboard does not modify the Zero kernel as part of Mieru endpoint-credential +support. Zero `0.0.15-rc.4` implements the required matched-user +`principal_key` propagation. Older releases accept Mieru users by password but +cannot attribute their traffic to the correct subscription, so Zboard keeps +Mieru disabled on those nodes. + +## Required Zero contract + +The rc.4-or-newer contract used by Zboard must: + +1. add a stable `principal_key` field to each `MieruUserConfig`; +2. preserve the matched Mieru user identity after password authentication; +3. attach that user's `principal_key` to the authenticated session and every + emitted flow event, using the same semantics as the existing native managed + protocols; +4. reject duplicate or empty principal keys and configurations that contain a + password without an attributable principal; +5. keep username/password client compatibility explicit. If a client requires + username, Zero or the renderer may use `username=password`; username must + not become the accounting identity; +6. add kernel tests covering two Mieru users on one listener, successful + authentication, isolation, reconnects, and correct principal propagation to + completed flow events. + +Managed speed/device policy fields should be added only if Zero can enforce +their semantics consistently with other native managed users. They are not a +precondition for identity attribution. + +## Zboard activation gate + +For a target node running Zero rc.4 or newer, Zboard: + +- include Mieru in `protocol_credentials`; +- generate one encrypted password and `principal_key` per active subscription; +- compile those users into the endpoint's Mieru server configuration; +- render only the requesting subscription's password; +- migrate active subscriptions, validate with the installed Zero binary, and + republish affected nodes. + +The public protocol capability contract advertises `0.0.15-rc.4` as the +minimum Zero version. The backend checks the selected or actually installed +version rather than a panel-wide flag. On older nodes it rejects new Mieru +endpoints, re-enabling and publication; subscription generation excludes +retained Mieru records. Existing records remain visible and can be disabled or +deleted safely. + +## Zboard rollout gate + +`native-local-mieru` remains a backwards-compatible contract name. A reviewed, +locally pinned rc.4-or-newer artifact under the normal `native-local` contract +enables the same Mieru behavior automatically. GitHub-managed nodes are gated +by the selected or probed installed version. + +Under that contract Zboard compiles Mieru users with +`username=password`, `password`, and `principal_key`. Template save and preview +execute `zero validate` from the checksum-pinned artifact. Node publication +then executes the installed Zero validator, atomically activates the +generation, checks process/control health and waits for a Connector event. +Only after that complete publication succeeds is +`protocol_endpoints.mieru_principal_ready` set and subscription delivery +switched from the endpoint credential to the requesting subscription's +credential. Validation or activation failure retains the previous generation +and endpoint credential. The first successful migration generation retains the +fallback user under a bounded `migration:endpoint:` principal so existing +clients are not cut off before the database readiness switch. Zboard +acknowledges but never bills those temporary flows. While holding the same +node publication lock, Zboard then performs a second full publication without +the fallback user. Readiness and subscription delivery switch only after this +fallback-free generation also passes validation, activation, health and +Connector confirmation. If cleanup fails, Zero rolls back to the compatibility +generation and the endpoint remains unready, so the shared credential cannot +remain accepted behind a falsely ready state. + +`credential_id` is deliberately absent from every emitted Zero user object. It +is a stable panel/database identifier, not part of the Zero runtime schema. diff --git a/docs/projects/zboard/reference/node-config-delivery.md b/docs/projects/zboard/reference/node-config-delivery.md new file mode 100644 index 0000000..6d9f1a0 --- /dev/null +++ b/docs/projects/zboard/reference/node-config-delivery.md @@ -0,0 +1,73 @@ +# 节点配置发布与恢复 + +自动发布使用数据库表 `node_config_publishes`,不依赖外部消息队列。 +每个节点仅保留一条待发布状态;执行时根据当前已提交数据生成完整配置。 + +## 提交与执行 + +订单确认及跨节点组切换、订阅凭据到期(包括读取触发的过期处理)、 +流量耗尽、配额任务调整、协议端点直接变更 +以及内核操作完成后的 Mieru 就绪发布, +会在业务事务内写入发布请求。请求写入失败时业务事务回滚,调用方可以重试。 +启动时的凭据兼容处理也持久化发布请求,失败会阻止启动完成。 +这些路径不在业务事务内等待 SSH。 + +服务启动主动检查待办;最多四个线程发布,空闲时由一个 5 秒定时器轮询。 +数据库以节点为主键合并请求;新版本到达时,正在执行的版本完成后还会再发布。 +已支付订单的重复确认不会生成新请求。 + +## 失败与重启 + +- 失败保留待办、错误和次数,按 5、10、20 秒等间隔退避,间隔上限 5 分钟。 + 未成功的任务继续重试,不以达到次数上限为由丢弃。 +- 工作线程领取任务时设置唯一标记和 3 分钟租约,执行期间定期续租。 + 进程异常退出后,其他线程只能在租约到期后接手;正常退出会取消执行并保留失败待办。 +- 单次自动发布限时 2 分钟。SSH 的 TCP 连接和完整握手共用最多 12 秒的期限; + 任务取消或超时会关闭 SSH 连接,使会话建立、上传及远端命令退出等待。 + 关闭 SSH 不能证明远端进程已经停止,恢复仍按至少一次交付处理。 +- 发布成功后,只有拥有相同领取标记和版本的线程才能删除待办。 + 新版本、过期线程的迟到结果和确认写入失败都不会导致待办被误删。 +- 节点删除会级联清理待办。原触发端点删除或移动后,会重新选取当前节点的端点; + 暂无端点时保留任务重试。 + +这是至少一次交付:节点已经应用配置、数据库确认却未完成时,恢复后可能再次应用配置。 +现有发布过程会生成当前配置并使用原有校验、原子切换及失败回滚,不能把队列恢复 +解释为 SSH 操作恰好执行一次。手工发布仍走原有同步流程。 + +## 查看待办 + +已有发布尝试的结果仍记录在协议发布历史中。待领取或重试状态可用只读查询检查: + +```sql +SELECT node_id, generation, attempts, next_attempt_at, lease_until, last_error +FROM node_config_publishes +ORDER BY next_attempt_at, node_id; +``` + +表中时间使用 UTC。队列表清空表示当前没有自动发布待办;不替代节点在线状态检查。 + +## 验证边界 + +SQLite 文件回归覆盖关闭后重开、租约过期、过期确认、执行期间新请求、 +业务事务回滚、执行失败及数据库确认失败;队列状态测试替换外部执行函数。 + +可选 MySQL 测试覆盖基线建表、旧库补表、独立连接池并发入队与领取、代次合并、 +租约恢复、迟到确认、退避、回滚和节点删除。订单测试覆盖重复确认、发布写入失败 +回滚、读取触发撤权及失败回滚、不同用户并发确认时的套餐容量。运行方法见下节; +未运行真实 MySQL 测试时,不能用 SQLite 结果代替。 + +真实本地 TCP/SSH 故障服务器覆盖握手、会话建立、命令卡住及期限到达,验证生产发布执行器 +退出等待并保存重试。它不运行 Zero 或 systemd,不替代实际节点应用配置、撤权时延或长稳验证。 + +## 重跑 MySQL 验证 + +使用可创建和删除数据库的独立测试账户。测试只迁移和删除自己创建的随机 +`zboard_test_*` 数据库;忽略 DSN 中的库名,不访问已有业务库。 +未设置环境变量时,这组测试明确跳过。 + +```sh +cd backend +export ZBOARD_TEST_MYSQL_DSN='test_user:test_password@tcp(127.0.0.1:3306)/' +go test ./internal/handler -run '^TestMySQL' -count=1 +go test -race ./internal/handler -run 'Test(MySQL|PublishCancellation)' -count=1 +``` diff --git a/docs/projects/zboard/reference/node-kernel-lifecycle.md b/docs/projects/zboard/reference/node-kernel-lifecycle.md new file mode 100644 index 0000000..2959d0c --- /dev/null +++ b/docs/projects/zboard/reference/node-kernel-lifecycle.md @@ -0,0 +1,66 @@ +# Zero 节点生命周期规划 + +## 结论 + +浏览器 SSH 终端解决的是人工运维入口,协议配置保存解决的是业务期望状态;两者都不能替代 Zero 内核生命周期管理。zboard 需要把节点自动化明确拆成探测、安装、配置、启动、健康确认和回滚,并把每个阶段的真实结果反馈给管理员。 + +协议保存会触发节点级完整配置发布,不再把单个端点 JSON 写成“暂存成功”。只有 Zero 自身的 `validate`、generation 原子切换、本地 control socket 和新鲜的已认证 Connector 事件全部通过,协议页面才显示配置已生效。 + +## 状态边界 + +节点页面分别展示以下状态,不合并成一个“在线”: + +1. SSH:未配置、待验证、已验证、主机身份变化。 +2. 内核安装:未探测、未安装、安装中、已安装、升级中、失败。 +3. 配置:未生成、待应用、校验中、已应用、回滚、失败。 +4. 进程:未知、启动中、运行、停止、异常。 +5. Zero Connector:按最近一次已认证事件独立判断近期活跃或离线。 +6. 计量:上报凭证状态和最近可信上报时间。 + +## 数据与任务模型 + +新增一对一的 `node_kernel_states`,保存期望版本、已安装版本、二进制 SHA-256、期望/已应用配置修订、当前阶段、服务管理器、最后健康时间和脱敏错误摘要。 + +新增只追加的 `node_operations`,记录 `detect`、`install`、`configure`、`repair`、`upgrade` 操作。每次操作保存请求人、状态、阶段、锁定版本/制品、开始/结束时间和结果摘要。每个节点只允许一个内核操作运行;通用批量任务可以调度多个节点操作,但不直接执行 SSH 脚本。可恢复的客户端幂等键仍属于后续能力。 + +凭证明文不进入操作记录。zboard 使用站点凭证密钥加密保存 Connector 凭证,并只向管理界面暴露前缀;生成的 Zero 配置权限为 `0600`,只把凭证放入 Webhook sink 的 opaque authorization header,不写入操作记录、审计详情或状态输出。 + +## 安装与升级流程 + +1. **预检**:通过已验证 SSH 探测操作系统、架构、libc、systemd、系统提权能力、现有 Zero 版本和服务状态。节点可以直接以 root 登录,也可以为普通登录用户显式配置免密/密码 `sudo` 或带独立 root 密码的 `su`;Linux x86_64 + systemd 节点按 libc 选择制品,禁止为了安装内核而升级系统 libc。 +2. **锁定制品**:`legacy` 仅在无人值守批量任务中默认选择 `zerodenet/zero` 的最新稳定 Release;单节点操作允许管理员显式选择任意已发布的稳定版或预发布版。glibc ≥ 2.34 使用该标签的 `zero-linux-x86_64.tar.gz`;旧 glibc 优先使用同一 Release 的 `zero-linux-x86_64-musl.tar.gz`,并兼容历史 Release 已发布的 `zero-v-linux-x86_64-musl.tar.gz`。每个压缩包都必须存在引用其精确文件名的同名 `.sha256`。前端只提交版本,后端重新按标签解析发布和固定下载地址,不接受任意 URL。GitHub Release 没有可用 musl 制品时,才回退到 `ZBOARD_ZERO_ARTIFACT_DIR` 中同标签的历史版本化制品。`native-local` 则要求显式 `ZBOARD_ZERO_LOCAL_VERSION`,只读取受信任目录内精确匹配的 `zero-v-linux-x86_64-musl.tar.gz` 和 `.sha256`,不访问或替换为 GitHub Release。所有制品都锁定大小和 SHA-256,不接受跨版本替代或未锁定下载。 +3. **暂存与校验**:下载到节点临时目录,核对大小和 SHA-256,执行 `zero version` / `zero build_info`,不覆盖当前版本。 +4. **生成完整配置**:把通用 Webhook Connector、磁盘 outbox、控制 socket和全部启用协议端点及原生 managed users 编译为一个规范化 Zero 配置。配置写入版本化 generation 目录并以 `0600` 权限安装。 +5. **离线校验**:先运行 `zero validate `。校验失败时不修改二进制、配置软链接或服务。 +6. **原子激活**:备份当前二进制和 generation,原子替换二进制及 `current` 配置软链接,安装或更新 `zero.service`,随后 `daemon-reload`、enable、restart。 +7. **分层验收**:内核操作依次确认 systemd active、本地 control socket 的 `zero status --json`、配置摘要和激活开始后的新鲜 `stats.sampled` 等已认证 Connector 事件。Connector 活跃仍作为独立状态展示,不与 SSH 或本地进程状态混合,但首次安装/切换只有通过三段验收才算成功。 +8. **自动回滚**:任一验收失败,恢复上一二进制和配置 generation,重启并再次健康检查;回滚结果也必须落入操作记录和审计日志。 + +## 配置发布 + +协议保存先提交数据库期望状态,随后立即排队发布。zboard 对该节点的全部启用端点和有效订阅凭证生成新的完整配置修订,而不是逐个文件覆盖运行态。新配置先离线校验,再切换 generation 并受控重启;本地健康或 Connector 事件确认失败会恢复上一 generation。协议页的手动操作仅用于重试同一发布链。 + +安装、配置应用和升级共用同一节点操作锁。这样可以避免在升级二进制时同时发布协议配置,也能让重复请求通过幂等键安全返回同一操作。 + +## 分阶段交付 + +- 第一阶段:节点探测、内核状态模型、操作记录和只读 UI,不改变服务器。 +- 第二阶段:固定版本首次安装、完整配置生成、systemd 启动、健康验证和自动回滚。 +- 第三阶段:配置 generation + validate + 受控重启,把协议 SSH 暂存替换为真实生效流程。(已完成) +- 第四阶段:可控升级/降级、批量调度、灰度和失败节点隔离。 + +协议页面必须以 desired/applied 哈希、部署结果和节点健康事实展示状态,不得把文件上传显示成“协议已生效”。 + +## 当前实现(2026-07-26) + +已落地 `node_kernel_states`、`node_operations`、节点检测、当前线上稳定版解析、按 libc 选择 official GNU/面板托管 musl 制品、发布包与二进制双重 SHA-256 校验、节点级完整配置生成、`zero validate`、systemd 原子切换、本地 control socket 与 Connector 事件验收和失败回滚。节点页面可以直接检测并执行“安装 / 升级 / 修复 / 配置同步”,同时展示最近操作的真实阶段与错误。 + +升级判定使用已安装 build ID、实际二进制 SHA-256、期望配置 SHA-256 和本地健康状态:未安装执行安装;版本较旧执行升级;同版本摘要不同执行修复;配置摘要不同执行配置同步;服务或 control socket 异常执行修复。管理员可从稳定发布列表选择精确版本;目标低于已安装版本时,界面必须显示降级语义并二次确认,后端还要求 `allow_downgrade` 与明确版本同时出现。无论选择最新还是历史稳定版,后端都必须重新解析为明确标签、不可变制品 URL 和 SHA-256,随后才允许执行。 + +当前 musl 制品契约为 `zero-linux-x86_64-musl.tar.gz`,同一 Release 必须包含 `zero-linux-x86_64-musl.tar.gz.sha256`,校验文件内部也必须引用这个精确文件名。为确保已发布版本仍可由管理员指定安装,解析器也接受同一 Release 中历史命名的 `zero-v-linux-x86_64-musl.tar.gz` 及其同名校验文件。旧 glibc 节点不会升级系统 libc;新发布不再依赖历史命名。 + +节点 SSH 设置已经把登录认证与系统提权拆开:登录仍支持密码/私钥和固定主机指纹;系统命令根据节点配置使用直接 root、`sudo` 或 `su`。提权密码使用站点凭证密钥独立加密,只通过 SSH stdin 提供,不进入远程命令、任务输出和审计详情;普通交互终端仍保持登录用户身份,由管理员自行决定是否在终端内提权。 + +协议页面保存后的自动 generation 已完成。Zero `0.0.15-rc.3` 起的原生 managed-user 配置为 VLESS、VMess、Shadowsocks、Trojan 和 Hysteria2 携带稳定 `principal_key`;`0.0.15-rc.4` 又为 Mieru 增加 matched-user `principal_key` 归属。面板按目标或节点实际安装版本判断 Mieru:旧节点拒绝创建、重新启用和发布,但保留停用/删除恢复路径;rc.4 及以后版本自动生成每订阅凭据,并在两阶段 validate、激活、健康检查和 Connector 确认后设置 `mieru_principal_ready`。`native-local-mieru` 仅作为兼容名称保留,普通 `native-local` 的 rc.4+ 制品会自动启用同一能力。Connector wire contract 同样跟随目标或节点版本:`0.0.15-rc.1` 及更早版本使用历史 `api_key_env` 与 `push`,`0.0.15-rc.2` 及以后版本使用完整 URL、opaque authorization header 和磁盘 outbox。内核协调使用已解析的目标版本;普通配置发布先探测节点实际版本,避免选择 prerelease 后仍生成旧字段。发布仍使用 `zero validate`、原子软链接、受控重启、control socket、Connector 事件确认和失败回滚。订阅开通、续费、额度调整、额度耗尽与 Connector 活动发现的到期变更都会触发同一发布链。 + +仍未完成的是可恢复的远程幂等键、批量/灰度调度和通过 `config.apply` 热更新替代受控重启。Zero 的策略计数按单进程 principal 执行,因此 zboard 只在订阅恰好有一个活跃且可归属凭证时下发本地限速和设备数;多节点/多凭证全局限速、设备数和剩余额度仍由面板统一计算,不能把完整额度复制给每个内核。zboard 订阅 `stats.sampled` 作为 Connector 活性信号并更新节点摘要,但它不替代 control socket 的进程健康判断。Shadowsocks 继续使用每凭证独立端口以保持现有订阅地址兼容,但运行态归因已经使用原生 `principal_key`。Mieru 的 rc.4 契约目前提供身份归属;未由内核声明的 managed-policy 字段不会由面板臆造。面板稳定 `credential_id` 只保存在数据库/清单中,不进入 Zero 运行配置。 diff --git a/docs/projects/zboard/reference/plugin-marketplace-design.md b/docs/projects/zboard/reference/plugin-marketplace-design.md new file mode 100644 index 0000000..b82191a --- /dev/null +++ b/docs/projects/zboard/reference/plugin-marketplace-design.md @@ -0,0 +1,7 @@ +# ZeroDeNet 共享插件市场设计 + +共享设计已迁入 [zerodenet/plugins 的市场草案](https://github.com/zerodenet/plugins/blob/main/docs/marketplace-design.md),后续统一在共享仓库维护。 + +OAuth 插件源码独立维护于 [higanbana986/zboard-oauth](https://github.com/higanbana986/zboard-oauth)。共享市场主分支分别维护 `catalogs/zboard.json` 与 `catalogs/znet-sink.json`,收录各插件仓库发布的发行元数据。开发、使用和发布规范见各仓库 README。 + +共享仓库与独立插件仓库已建立,市场自动化通过审核 PR 同步发行元数据;正式签名安装目录与客户端插件运行时尚未发布。当前 ZBoard 继续使用本仓库描述的 v1 目录及宿主生命周期实现,不因源码迁移改变安装协议。 diff --git a/docs/projects/zboard/reference/plugin-system-design.md b/docs/projects/zboard/reference/plugin-system-design.md new file mode 100644 index 0000000..5c4dd8b --- /dev/null +++ b/docs/projects/zboard/reference/plugin-system-design.md @@ -0,0 +1,95 @@ +# ZBoard 动态插件系统 + +状态:`feature/plugin` 实现 A 段——签名包、插件市场、管理、离线导入、前后台页面、配置运行时及专用身份提供方登录。当前还提供宿主管理的包准入、加密 JSON 私有存储和声明式配置/数据迁移,见 [宿主生命周期与数据](/projects/zboard/plugins/governance)。支付、凭证轮换、事件订阅、任务与关系表扩展属于后续能力,当前安装器拒绝声明这些能力的包。使用方法和准确的包格式见 [插件开发与运维](/projects/zboard/plugins/development)。 + +## 核心所有权 + +ZBoard 的核心定位是基础面板。在线支付及其他超出基础管理范围的能力通过插件实现;扩展需求通过明确的宿主接口接入。 + +核心独占用户、凭证、订单、权益、流量和节点配置发布。插件负责扩展交互及外围集成,通过宿主明确开放、版本化的能力提出请求。插件 SDK 不提供数据库连接、GORM、任意 SQL、任意核心命令、管理员令牌、节点控制地址或节点凭证。 + +`surface` 表示页面所在位置,不授予权限;声明后台页面不意味着插件能访问后台业务。页面位置不授予权限;有效能力取当前包声明、宿主准入、调用身份和核心策略的交集。插件生命周期与核心事务相互独立:停用或卸载插件不撤销已经提交的核心工作,不改变用户、订单、订阅或节点状态。 + +当前 RPC 包含插件身份、健康检查、配置验证、配置应用、已保存配置测试,以及专用身份提供方元数据/身份验证。登录状态、绑定与会话由核心实现,详见 [身份提供方契约](/projects/zboard/plugins/identity-reference)。浏览器桥只有最小页面上下文及管理员配置能力。新增扩展点必须先在核心服务内定义命令、鉴权、资源范围、幂等和结果语义,再增加独立能力协议与验证用例。 + +## 参考 Sub2API 的取舍 + +参考提交 `270eac6973049fe1b50eb75560a74a029e82884c` 的签名包、go-plugin/gRPC、版本要求、配置页面桥及配置应用机制;ZBoard 使用自己的 `.zbplugin`、协议和贡献声明,不兼容 `.s2plugin`。 + +- 使用独立服务进程、gRPC、握手及运行身份核验;纯页面插件无需进程。 +- 包以 Ed25519 签名完整原始 manifest,manifest 用 SHA-256 绑定文件内容。 +- 宿主兼容范围、发布者测试版本、协议版本分别校验;未声明测试的兼容版本启用时需管理员确认。 +- 升级由宿主准备候选数据和进程,在同一事务内切换版本、配置、数据和准入;成功保留原启停状态,失败保留旧实例与数据。保留最多 30 个版本的记录;恢复旧版本需校验数据和已有配置,不自动向下迁移。 +- 配置先验证和应用,再加密提交;持久化失败恢复旧配置,恢复失败终止进程并标记异常。重启以数据库已提交配置为准。 +- 首版一个活动宿主,由数据库租约和 epoch 限制,不宣称多实例协调已经完成。 + +独立进程用于故障隔离,并不是不可信原生代码的 OS 沙箱。签名只证明来源与完整性,不证明代码安全。服务组件必须来自运营者信任并审查的发布者;启动不继承宿主环境、使用独立工作目录和加密 RPC,但当前没有 UID、文件系统、网络或 CPU/RSS 的强制沙箱。运行任意第三方不可信二进制不在本期能力内。 + +## 交付和动态生命周期 + +同一包可包含 `public`(公开前台)、`account`(用户前台)、`admin`(管理后台)入口及可选多平台服务二进制。管理系统展示来源、范围、运行组件、兼容性、当前版本、错误和操作记录。 + +安装路径统一为:市场下载或离线上传 → 包大小/路径检查 → 发布者签名 → manifest 与能力白名单 → 文件摘要 → 私有目录原子保存 → 候选配置/数据迁移与运行时准备 → 安装、准入、数据和迁移记录原子提交。首次安装默认停用;已有配置或数据迁移需要候选原生进程校验时,宿主会启动它,失败则保留旧安装。不兼容包拒绝安装。 + +启用验证完整包及当前平台,启动可选进程、核验身份、应用配置后提交活动状态,随后页面目录可见。停用先关闭活动状态并递增 generation,撤销页面会话,再终止进程;管理器串行化正在执行的配置操作。当前没有业务 RPC,因此没有在途付款或任务可排空。未来业务能力需独立实现 drain 和超时,不能把配置操作串行化当作业务排空。 + +卸载由宿主停止运行并撤销会话,删除所有保留版本的程序/页面,保留安装墓碑、加密配置、私有数据与操作记录。单独删除配置不清理核心事实。重新导入同 ID 必须仍为相同签名发布者。 + +## 市场与离线导入 + +市场按 ZeroDeNet 多宿主共享方向演进,覆盖 ZBoard 与 ZNet Sink;共享分发协议,各宿主分别负责能力与生命周期。仓库命名、目录兼容和分阶段落地见 [共享插件市场草案](/projects/zboard/reference/plugin-marketplace-design)。以下描述为当前已实现的 ZBoard v1 目录能力。 + +市场来自部署配置指定的签名目录,不允许从浏览器提交任意下载地址。目录具备有效期,发布者和目录签名均使用运营者配置的可信公钥;安装重新获取目录并比对用户选择的摘要,然后校验下载包的摘要、签名、ID、版本及发布者。 + +下载仅允许公开 HTTPS 的 443 端口,禁用重定向和环境代理,DNS 解析后检查全部地址并固定连接已检查 IP。未配置市场显示配置提示;离线导入无需网络,使用完全相同的包校验链。首版没有预置或虚构公共市场,也不提供自动更新、评分和开发者上架后台。 + +## UI 边界 + +路由在宿主编译时固定,插件只贡献页面描述;导航从活动页面目录刷新。各 surface 由宿主鉴权。插件 HTML 在 `sandbox="allow-scripts"` 的 iframe 内运行,不授予同源、弹窗、表单、顶层导航能力。 + +宿主签发十分钟资源会话,绑定插件、页面、surface、用途、用户和 generation。资源 token 只读取该插件签名包内 UI 文件,不暴露二进制、manifest、源码映射或其他包。CSP 禁止网络请求、表单和子 frame;不向 iframe 传递宿主登录令牌。HTTP 会话撤销、停用、版本变化、宿主重启均使旧资源失效。 + +postMessage 校验发送窗口、桥令牌、请求 ID、操作白名单和消息大小。管理员配置页面才可调用 config.load/save/test;普通业务页面只能取得 plugin_id/page_id/surface。宿主卸载容器时撤销会话并取消请求,旧 generation 响应不进入新页面。导航和 iframe 会话每 15 秒可见时刷新,停用后的后端访问立即拒绝,已经绘制的页面在下一次检查时移除。 + +## 配置和故障恢复 + +配置为最长 64 KiB 的 JSON 对象,整份使用既有 CredentialCipher 加密;管理 API 返回是否配置、revision 以及可选的插件公开配置投影,不回显秘密值。当前使用 JSON 编辑器或插件自带配置页,不声称已实现 JSON Schema 表单生成。服务插件不能在 ApplyConfig 或 TestConfig 内执行扣款等业务副作用。 + +配置持久化使用 revision CAS,生命周期使用 generation;宿主租约检查以事务中的 epoch 为准。配置应用成功后 DB 提交失败会重新应用原配置;宿主崩溃后所有进程重新从已提交配置恢复。运行中操作记录在重启时标记 interrupted。进程异常只标记对应插件 failed 并撤销其会话,不把未确认业务结果标成成功。 + +插件目录、数据库和凭证加密密钥必须一起备份;密钥变更需复用核心密钥迁移流程。SQL 迁移通过新增 `0002_plugins` 建表,MySQL/SQLite 均有明确脚本,不修改已发布的核心 baseline。卸载某个插件不回滚全局 schema。 + +## 凭证轮换能力的后续接入路径 + +以后需要“插件动态修改用户凭证并通知节点”时,路径固定如下,当前版本尚未开放此能力: + +```mermaid +sequenceDiagram + participant P as 插件 + participant H as 宿主能力网关 + participant C as 核心凭证服务 + participant D as 核心数据库 + participant W as 核心节点发布器 + participant N as Zero节点 + P->>H: RotateCredential(请求ID, 目标资源, 预期版本) + H->>H: 检查能力、调用身份、资源范围和generation + H->>C: 经授权的专用命令 + C->>D: 同事务更新凭证并写node_config_publishes + D-->>C: 提交成功 + C-->>P: operation_id + 已提交 + W->>D: 领取持久化发布工作 + W->>N: 通过现有控制API应用配置 + N-->>W: 应用结果 + W->>D: 保存逐节点结果与重试状态 + P->>H: 查询operation_id状态 +``` + +“数据库已提交”“节点已应用”“旧会话已终止”必须分开表达。节点失败由核心发布器重试,部分节点成功需显示部分完成。轮换是否终止已有连接由核心命令明确决定,不能从配置更新推断。插件停用后核心发布继续执行;插件不能拼装节点配置、直接发节点命令或自行改写发布记录。 + +## 后续独立能力 + +身份提供方已经通过专用契约落地。后续支付能力仍需独立设计:插件负责提供方通信及回调验签,核心拥有订单金额/币种、支付 attempt、幂等确认和权益发放。回调成功 ACK 必须在宿主持久化确认后产生;重复、乱序、结果未知和停用中回调均需要独立验收。 + +事件、定时任务和插件数据 API 后续分别定义。核心事件用事务 Outbox、至少一次投递及幂等,不允许可选插件执行拖住核心事务;任务使用持久化触发标识和租约;插件自有存储由宿主按 plugin_id 隔离,不开放 SQL。灰度、业务 drain、多活动宿主、硬资源限制和不可信代码沙箱都不得由当前健康检查推导为已实现。 + +第三方登录与注册、多提供方快捷配置、自定义 OAuth2 字段映射和配置密钥保留契约见 [插件身份能力](/projects/zboard/plugins/identity-reference)。配置读取可包含插件投影的公开字段;密钥保持隐藏。 diff --git a/zboard-document-migration.json b/zboard-document-migration.json new file mode 100644 index 0000000..be47463 --- /dev/null +++ b/zboard-document-migration.json @@ -0,0 +1,137 @@ +{ + "source_repository": "zerodenet/zboard", + "source_head": "37d1716c466ce16a1198399ac91c9de3e2c0c4f8", + "includes_local_document_edits": true, + "files": [ + { + "source": "docs/README.md", + "destination": "docs/projects/zboard/guides/index.md", + "source_sha256": "92a97ca0e0a609b9893d787163bc4b9aeb736f7906cc6e71fa172713e6767288" + }, + { + "source": "docs/getting-started.zh-CN.md", + "destination": "docs/projects/zboard/guides/installation.md", + "source_sha256": "3b95936d16d746e4e46ffbdd7657b57f66d37ecc8c7307fba6df5a4540492a1f" + }, + { + "source": "docs/getting-started.md", + "destination": "docs/projects/zboard/guides/installation-en.md", + "source_sha256": "cce43ed1c3d9ad71f642248bbbe0f84a5771870290a47e75c63051e2ecb2fd8b" + }, + { + "source": "docs/core-baseline.md", + "destination": "docs/projects/zboard/reference/core-baseline.md", + "source_sha256": "1290d8a6dc16d2f7c49320c46230027d4b54730c944265b5b4f2d097de2707b6" + }, + { + "source": "docs/data-model.md", + "destination": "docs/projects/zboard/reference/data-model.md", + "source_sha256": "0534d6ee4df981908f73950e006aeea4504617bcd6fc214546b092f3b1b4fda9" + }, + { + "source": "docs/database-migrations.md", + "destination": "docs/projects/zboard/reference/database-migrations.md", + "source_sha256": "4037a83bb4152454c494f983b7df4456b48b8e66c72f97b977162834daa5b82c" + }, + { + "source": "docs/development.md", + "destination": "docs/projects/zboard/contributing/development.md", + "source_sha256": "d671964dfa51d87d7b383c705d814d89c336357323f3250daadd84b18009acfc" + }, + { + "source": "docs/managed-rule-compatibility.md", + "destination": "docs/projects/zboard/reference/managed-rule-compatibility.md", + "source_sha256": "16d32d883111c1e05eeafb1149552efeb1e2022ef8644e83377973e8625901df" + }, + { + "source": "docs/mieru-kernel-contract.md", + "destination": "docs/projects/zboard/reference/mieru-kernel-contract.md", + "source_sha256": "24583bf35106e05b6b070f2d5205f827cc272ceab74a79cfca3944774b7e9a85" + }, + { + "source": "docs/network-fronting.md", + "destination": "docs/projects/zboard/guides/network-fronting.md", + "source_sha256": "b5d15a14318ec076f4c437a338bb613b8cd5c3695644c9d097029a5f92d80691" + }, + { + "source": "docs/node-cleanup.md", + "destination": "docs/projects/zboard/guides/node-cleanup.md", + "source_sha256": "e2599ecb3b01f2f2bcaf5ca451ee1167daa9c43ad5c1c52ddf78ba9d1555af0f" + }, + { + "source": "docs/node-config-delivery.md", + "destination": "docs/projects/zboard/reference/node-config-delivery.md", + "source_sha256": "ab17515b729b7dce00bc9d3c7eb94797d9489d701117301411225315f01b93ec" + }, + { + "source": "docs/node-kernel-lifecycle.md", + "destination": "docs/projects/zboard/reference/node-kernel-lifecycle.md", + "source_sha256": "060878515c8552182917d644f522f39673ebdb25ef3657c5b9b795c4be99af20" + }, + { + "source": "docs/plugin-governance.md", + "destination": "docs/projects/zboard/plugins/governance.md", + "source_sha256": "1bf5ac826f530767c43e74cfdf95d0d8242b1f48358c18f0c6e027e9ca42e7e5" + }, + { + "source": "docs/plugin-identity.md", + "destination": "docs/projects/zboard/plugins/identity-reference.md", + "source_sha256": "a08c051771cff5328167c462099b49f6fe1f4c15043046930f1b96b75bd81e58" + }, + { + "source": "docs/plugin-installation-trust.md", + "destination": "docs/projects/zboard/plugins/trust-en.md", + "source_sha256": "9f3f42f7291ce69c15fbed439f7f6a72ffa3f27b1f7e795fa8d460062198e9bf" + }, + { + "source": "docs/plugin-installation-trust.zh-CN.md", + "destination": "docs/projects/zboard/plugins/trust.md", + "source_sha256": "63716eaff9ff3e1cc447b66da5cca526cf2eef48792f61f6804f8b8431305382" + }, + { + "source": "docs/plugin-marketplace-design.md", + "destination": "docs/projects/zboard/reference/plugin-marketplace-design.md", + "source_sha256": "de953f3347a362412ce345dc5a534fbeafb2d62c8c59b6f54c6c130277f9d3a0" + }, + { + "source": "docs/plugin-system-design.md", + "destination": "docs/projects/zboard/reference/plugin-system-design.md", + "source_sha256": "cbeb5624d82280bc09b2e1aa5c153ffa90d6c95671840b85de24058f64dd6969" + }, + { + "source": "docs/plugins.md", + "destination": "docs/projects/zboard/plugins/development.md", + "source_sha256": "50c5ee0b0d9413a9a8fcfed2f84474ad5ec20cb4099478d01835628af7cdd5a3" + }, + { + "source": "docs/public-subscription-filtering.md", + "destination": "docs/projects/zboard/guides/subscription-filtering.md", + "source_sha256": "49ac8e1c5683b402f5955ad8fe49061393e6e6664ed5b3ce5101085a579017d1" + }, + { + "source": "docs/release/2026-09-06-core-rc.md", + "destination": "docs/projects/zboard/reference/history/core-rc.md", + "source_sha256": "7e9166cf98846bb98c1047c547e9886e54e70b99b73577f20ed023659d1d1975" + }, + { + "source": "docs/release/v0.0.1.md", + "destination": "docs/projects/zboard/reference/history/v0.0.1.md", + "source_sha256": "ff65e5c0db7f5f0d61129b3cf2fb23200d46fa6139feb7e2ff45e9aa4946fe6c" + }, + { + "source": "docs/release/v0.1.0-launch-checklist.md", + "destination": "docs/projects/zboard/reference/history/launch-checklist.md", + "source_sha256": "642ba473b2da6855b87ca5d6862f312e590d8abd4ed764d3065d5b011bdd1e69" + }, + { + "source": "docs/roadmap.md", + "destination": "docs/projects/zboard/reference/history/roadmap.md", + "source_sha256": "8def6fc6c8c53489de86b755e284adf2cbdfbf9dbf0c19d76a6a60ba3c805221" + }, + { + "source": "deploy/docker/README.md", + "destination": "docs/projects/zboard/guides/storage-and-backups.md", + "source_sha256": "11e7b29c7852c0e0bba912b53beecacfa4921344e7d3eb772933650a3145b75c" + } + ] +}