# G14 Platform PostgreSQL

本文是 G14 节点原生 PostgreSQL 平台库的长期参考。它覆盖 host-native PostgreSQL 的定位、连接边界、HWLAB v0.3 接入方式、备份恢复和旧自有 DB 清理入口。Redis 方案暂不纳入当前标准路径。

## 定位

G14 platform DB 是 G14 host OS 上的原生 PostgreSQL，不是 k3s workload，也不属于 `devops-infra` namespace。它位于 HWLAB runtime lane、AgentRun 和未来 G14 平台控制能力之下，作为节点级基础设施承载 durable runtime、OpenFGA datastore、平台控制元数据、租约、审计和状态观测。

数据库可以保存一部分 k3s 管理所需的状态，例如节点 inventory、runtime lane registry、operation lease、k3s state observation 和 audit event；它不直接执行 `kubectl`、Argo、Tekton 或 GitHub 写操作。k3s 写操作仍必须通过 UniDesk 受控 CLI 或对应控制器执行，数据库只作为状态与协调底座。

不要把平台 DB 部署成每个业务 namespace 内的自有 StatefulSet。业务 namespace 只保留指向 host-native PostgreSQL 的 ClusterIP Service/Endpoint 桥接对象和自身 SecretRef。

## Host 边界

G14 平台库固定由 systemd 管理：

```bash
trans G14 sh -- 'systemctl status postgresql'
trans G14 sh -- '/usr/local/sbin/g14-platform-db-health'
```

PostgreSQL 只监听 G14 host loopback 与 k3s pod 可达的 node gateway 地址：

- `127.0.0.1:5432`
- `10.42.0.1:5432`

不得开放 `0.0.0.0:5432`、NodePort、FRP 公网端口或直接公网 DNS。`pg_hba.conf` 只允许 localhost、k3s pod CIDR 和 service CIDR 通过 SCRAM 认证连接。凭据文件固定在 G14 host：

```text
/root/.config/g14-platform-db/credentials.env
```

该文件必须保持 root-only；任何 CLI、issue、日志和长期文档只允许输出 secret 是否存在、路径和 redacted 摘要，不得打印完整密码、完整 URI 或可复用凭据。

## 数据库与角色

平台库当前按用途拆分 database/role：

- `g14_platform_control` / `g14_platform_app`：G14 节点平台控制元数据、租约、观测和审计。
- `hwlab_v03` / `hwlab_v03_app`：HWLAB v0.3 cloud-api durable runtime。
- `openfga_v03` / `openfga_v03_app`：HWLAB v0.3 OpenFGA datastore。

`postgres` 只作为管理数据库保留。新增 runtime lane 时按同一模式分配独立 database/role，不复用业务应用账号访问 `g14_platform_control`。

HWPOD v0.3 不再有当前 platform DB 目标；`hwpod-v03-db`、`hwpod_v03` 和 `hwpod_v03_app` 出现时只能视为废弃残留，不能作为 SecretRef 完成态或平台库目标保留。

## k3s 桥接

业务 Pod 不直接访问 host IP 字面量，而是访问所在 namespace 内的 `g14-platform-postgres` Service。HWLAB v0.3 的桥接对象在 `hwlab-v03` namespace 中：

- `Service/g14-platform-postgres`
- `EndpointSlice/g14-platform-postgres-host`

目标地址固定为 `10.42.0.1:5432`。bridge 最低验证标准是 namespace-local `Service/g14-platform-postgres` 存在、固定 `EndpointSlice/g14-platform-postgres-host` 存在且指向 host PostgreSQL，并且旧 `Endpoints/g14-platform-postgres` 不存在。不要保留 `Endpoints` 兼容路径或由它派生的随机 EndpointSlice；最终仍以 runtime `/health/live` 和 `hwlab nodes control-plane status` 的真实连接结果为准。

业务 SecretRef 固定使用现有应用 Secret 名称，不迁移为共享明文配置：

- `hwlab-cloud-api-v03-db/database-url` 指向 `hwlab_v03`。
- `hwlab-v03-openfga/datastore-uri` 指向 `openfga_v03`。

这些 SecretRef 的源凭据来自 G14 host 的 platform DB credentials file，不再从旧 lane-local Postgres Secret 派生。`hwpod-v03-db/database-url` 是废弃残留，应该缺席；发现它时用受控清理入口删除，不能为它补 status 可见性或轮换路径。迁移完成后，旧的 repo-owned PostgreSQL bootstrap/ensure 路径已经删除，不能用于轮换 `hwlab-cloud-api-v03-db` 或 `hwlab-v03-openfga`。如需重复轮换，必须先补齐平台 DB 专用 UniDesk CLI，让它从 host 凭据文件读取、写入 k3s Secret，并输出 redacted 状态；不得把手写 `kubectl create secret` 或旧 StatefulSet ensure 路径沉淀成长期流程。

当前运行面验证使用 control-plane status 和选中 node/lane 的 YAML public URL。D601 `v0.3` 的正式用户入口是 `https://hwlab.pikapython.com`；裸 IP/FRP 端口只能作为 Caddy/FRP 边缘诊断，不作为当前用户入口验收口径：

```bash
bun scripts/cli.ts hwlab nodes control-plane status --node D601 --lane v03
curl -fsS https://hwlab.pikapython.com/health/live | jq '{status, ready, db: {ready: .db.ready, connectionResult: .db.connectionResult}, runtime: {ready: .runtime.ready, queryResult: .runtime.connection.queryResult}}'
```

任何 CLI 输出都不得泄露 database URL 或 password，只披露 Secret/key 是否存在、字节数、DB 对象是否存在和 rollout/health 结果。

## HWLAB v0.3 GitOps

HWLAB v0.3 的 source truth 在 `G14:/root/hwlab-v03`、branch `v0.3`。`deploy/deploy.yaml` 的 `lanes.v03.externalPostgres` 是 v0.3 使用 G14 platform DB 的声明入口；render 结果必须包含 `external-postgres.yaml`，并且不得再包含旧 `postgres.yaml`。

标准验证：

```bash
trans G14:/root/hwlab-v03 sh -- 'npm run gitops:ts:check'
trans G14:/root/hwlab-v03 sh -- 'npm run gitops:render -- --lane v03 --out /tmp/hwlab-v03-render-check'
bun scripts/cli.ts hwlab nodes control-plane trigger-current --node G14 --lane v03 --confirm
bun scripts/cli.ts hwlab nodes control-plane status --node G14 --lane v03 --pipeline-run <pipeline-run>
```

Argo Application `hwlab-node-v03` 应显示 `Synced/Healthy`，runtime workloads 中应只保留 `service/g14-platform-postgres`，不应再出现 `statefulset.apps/hwlab-v03-postgres`、`service/hwlab-v03-postgres` 或 `configmap/hwlab-v03-postgres-init`。

Argo 必须跟踪 `Service/g14-platform-postgres` 和固定 `EndpointSlice/g14-platform-postgres-host`。如果 `argocd-cm` 仍排除或忽略 `Endpoints` / `EndpointSlice`，或者旧 GitOps revision 留下了 `Endpoints/g14-platform-postgres` 与随机派生 EndpointSlice，使用受控入口收敛，不要手工 `kubectl delete`：

```bash
bun scripts/cli.ts hwlab nodes control-plane allow-endpoint-bridge --node G14 --lane v03 --dry-run
bun scripts/cli.ts hwlab nodes control-plane allow-endpoint-bridge --node G14 --lane v03 --confirm
```

该入口会移除旧 Argo endpoint 资源排除、重启 Argo application controller、删除旧 `Endpoints/g14-platform-postgres` 和它派生的多余 EndpointSlice，并 hard refresh `hwlab-node-v03`。收敛后的状态必须是 `legacyEndpointsExist=false`、`hostEndpointSliceExists=true`、`extraEndpointSlices=[]`。

## 旧自有 DB 清理

迁移到平台 DB 后，v0.3 自有 PostgreSQL StatefulSet、Service、ConfigMap、Secret 和 PVC 都必须清理。GitOps render 不再生成旧 `postgres.yaml`；如果运行面仍残留旧对象，直接用受控 CLI 清理精确命名资源，不保留兼容路径：

```bash
bun scripts/cli.ts hwlab nodes secret cleanup-owned-postgres --node G14 --lane v03 --dry-run
bun scripts/cli.ts hwlab nodes secret cleanup-owned-postgres --node G14 --lane v03 --confirm
```

该入口只允许 v0.3+ lane 使用，默认 dry-run。它必须先确认：

- `g14-platform-postgres` Service 存在。
- 清理目标仅为旧 `hwlab-v03-postgres` StatefulSet、Service、ConfigMap、Secret 和 `data-hwlab-v03-postgres-0` PVC。

它不得删除平台 database、平台 role、当前 `hwlab-cloud-api-v03-db`、当前 `hwlab-v03-openfga`、OpenFGA/Cloud API Deployment 或当前 platform DB bridge。PVC 删除前必须已经有平台库备份或迁移 dump 可用；迁移 dump 归档在 `/var/backups/g14-platform-db/migration-*`。

清理后用以下只读检查确认旧自有 DB 不再存在；公开 health 仍走选中 node/lane 的 YAML public URL，D601 `v0.3` 使用正式域名：

```bash
trans G14:k3s kubectl -n hwlab-v03 get statefulset,svc,endpoints,endpointslice,configmap,secret,pvc
curl -fsS https://hwlab.pikapython.com/health/live | jq '{status, ready, environment, db: {ready: .db.ready, connectionResult: .db.connectionResult}, runtime: {ready: .runtime.ready, queryResult: .runtime.connection.queryResult}}'
```

其中 `endpoints/g14-platform-postgres` 应为空，`endpointslice/g14-platform-postgres-host` 应存在且是唯一带 `kubernetes.io/service-name=g14-platform-postgres` label 的 EndpointSlice。

HWPOD v0.3 DB 残留不属于旧自有 HWLAB Postgres StatefulSet 清理目标，必须用单独的废弃对象清理入口处理：

```bash
bun scripts/cli.ts hwlab nodes secret cleanup-obsolete --node G14 --lane v03 --name hwpod-v03-db --dry-run
bun scripts/cli.ts hwlab nodes secret cleanup-obsolete --node G14 --lane v03 --name hwpod-v03-db --confirm
```

该入口只允许清理已确认不被 workload 引用的 `hwpod-v03-db` k3s Secret，并同步清理 G14 host PostgreSQL 上同名废弃目标对应的 `hwpod_v03` database 与 `hwpod_v03_app` role；输出只披露对象是否存在、是否删除和退出码，不打印 URI、密码或可复用凭据。清理后 `hwpod-v03-db`、`hwpod_v03`、`hwpod_v03_app` 都应缺席。

## 备份与恢复

备份脚本固定在 G14 host：

```bash
trans G14 sh -- 'systemctl status g14-platform-db-backup.timer'
trans G14 sh -- '/usr/local/sbin/g14-platform-db-backup'
```

备份目录：

```text
/var/backups/g14-platform-db
```

目录权限必须允许 `postgres` 写入备份文件，但不允许普通用户读取。备份文件默认归属 `postgres:postgres`，权限不应宽于 `0640`。恢复操作必须先在 issue/运维记录中说明目标 database、源 dump、停机或切换窗口和 SecretRef 影响面；不得把恢复命令写入业务 Pod 或 GitOps hook 作为默认流程。

最小恢复原则：

- 恢复单个应用 database，不整库覆盖 PostgreSQL cluster。
- 先恢复到临时 database 做 schema/table/count 校验，再切换业务 SecretRef 或执行受控 rename。
- OpenFGA datastore 恢复后必须运行对应版本的 `openfga migrate`，再重启/观察 OpenFGA Deployment。
- 恢复完成后运行 `/usr/local/sbin/g14-platform-db-health`、目标 lane `/health/live` 和 `hwlab nodes control-plane status`。

## 运维判定

平台 DB 可用的最低标准：

- `postgresql` systemd service active。
- `ss -ltnp` 只显示 `127.0.0.1:5432` 和 `10.42.0.1:5432` 监听。
- `/usr/local/sbin/g14-platform-db-health` 能列出预期 database。
- `hwlab-v03` 中 `g14-platform-postgres` Service 可见，`EndpointSlice/g14-platform-postgres-host` 是唯一 bridge endpoint，旧 `Endpoints/g14-platform-postgres` 不存在。
- `hwlab-cloud-api` `/health/live` 返回 `status=ok`、`ready=true`、`db.connectionResult=connected`、`runtime.connection.queryResult=durable_readiness_ready`。
- `hwlab nodes control-plane status --node G14 --lane v03` 显示 Argo `Synced/Healthy`，runtime workload 摘要不包含旧自有 Postgres。

Redis 暂停期间，不为 HWLAB v0.3 新增 Redis 原生部署、SecretRef 或 runtime 依赖。后续如确需 Redis，应先单独形成平台级方案，明确是否 host-native、是否 namespace bridge、是否需要持久化、备份与业务隔离，再更新本文和对应 issue。
