Files
postgresqlfpk/docs/fnos-postgresql-client-integration.md
T

8.1 KiB
Raw Blame History

fnOS PostgreSQL 共享服务接入指南

本文面向需要接入 nxsir.postgresql 的 fnOS 原生应用。共享服务不依赖 Docker,多个应用共用一个 PostgreSQL 15 进程,但每个应用获得独立数据库、独立登录角色和独立随机密码。

1. 服务约定

项目 默认值
fnOS 应用名 nxsir.postgresql
PostgreSQL 地址 127.0.0.1:15432
管理/API 地址 http://127.0.0.1:15433
传输加密 不启用 TLS,仅允许本机回环连接
密码认证 PostgreSQL SCRAM-SHA-256
可申请扩展 vectorpgvector

不要连接 Unix Socket、不要使用服务管理员角色,也不要假设数据库名或角色名。客户端必须通过接入 API 获取完整凭据。

2. 声明 fnOS 依赖

在应用 FPK 的 manifest 中声明:

install_dep_apps=nxsir.postgresql

用户应先安装并启动 PostgreSQL 共享服务,再安装你的应用。你的安装/升级向导需要提供一个密码字段,让用户填写安装共享服务时设置的“应用接入令牌”。令牌长度为 20~256 个字符,不允许换行。

接入令牌与 PostgreSQL 管理员密码是两套独立凭据:

  • 管理员密码只登录 PostgreSQL 管理面板。
  • 接入令牌只用于本机应用首次签发或重新签发数据库凭据。

3. 注册客户端

注册接口只接受来自回环地址的请求:

POST http://127.0.0.1:15433/internal/v1/enroll
Authorization: Bearer <应用接入令牌>
Content-Type: application/json

普通 PostgreSQL 客户端:

{
  "appId": "myapp",
  "displayName": "My fnOS App",
  "requestedExtensions": []
}

需要 pgvector 的应用:

{
  "appId": "imagefind",
  "displayName": "ImageFind",
  "requestedExtensions": ["vector"]
}

字段限制:

  • appId:稳定且全局唯一,3~64 个字符;以小写字母开头,只允许小写字母、数字、点、下划线和连字符。发布后不要更改。
  • displayName:1~100 个字符,用于管理面板展示。
  • requestedExtensions:目前只能是空数组或包含 vector

curl 示例:

curl --fail --silent --show-error \
  -H "Authorization: Bearer $APP_ENROLLMENT_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"appId":"myapp","displayName":"My fnOS App","requestedExtensions":[]}' \
  http://127.0.0.1:15433/internal/v1/enroll

成功响应:

{
  "host": "127.0.0.1",
  "port": 15432,
  "database": "appdb_myapp_...",
  "username": "app_myapp_...",
  "password": "一次性返回的随机密码",
  "sslMode": "Disable",
  "serviceVersion": "15"
}

每次对同一 appId 重新注册都会复用其数据库和角色,但会立即轮换密码,使旧密码失效。因此正常启动时应先读取本地凭据,只有首次安装、凭据丢失或明确执行密码轮换时才重新注册。

常见 HTTP 状态:

状态 含义
200 注册成功;响应中包含新密码
400 appId、显示名或扩展参数无效
401 接入令牌错误或已被管理员轮换
403 请求不是从本机回环地址发起
500 共享服务内部错误;查看共享服务日志

4. fnOS 生命周期脚本建议

安装回调只负责以 0600 保存令牌种子,不要在向导校验阶段依赖网络。应用启动时执行注册,并采用临时文件加原子重命名保存响应。

建议的持久化文件:

${TRIM_PKGVAR}/postgres-enrollment-token.seed  # 首次注册前,0600
${TRIM_PKGVAR}/postgres-client.conf            # 注册成功后,0600

推荐配置格式:

host=127.0.0.1
port=15432
database=接口返回值
username=接口返回值
password=接口返回值

注册成功并安全落盘后应删除令牌种子,避免长期保存高权限接入令牌。不要把密码写入日志、命令行参数、进程标题或 Web 前端。应用卸载时是否保留数据库由用户决定;不要自行执行 DROP DATABASE

服务可能在 NAS 启动时稍晚就绪。建议:

  • 请求超时 10 秒左右。
  • 每 2 秒重试一次,最多等待 1~2 分钟。
  • 先探测 GET /health/ready,或直接重试注册。
  • 已有本地凭据时不要因为管理 API 暂时不可用而重新注册;直接尝试 PostgreSQL 连接。

5. 连接字符串

.NET / Npgsql

Host=127.0.0.1;Port=15432;Database=<database>;Username=<username>;Password=<password>;SSL Mode=Disable;Timeout=15;Command Timeout=120;Keepalive=30

Python / psycopg

import psycopg

connection = psycopg.connect(
    host="127.0.0.1",
    port=15432,
    dbname=database,
    user=username,
    password=password,
    sslmode="disable",
    connect_timeout=15,
)

JDBC

jdbc:postgresql://127.0.0.1:15432/<database>?sslmode=disable&connectTimeout=15

应用角色是目标数据库及 public schema 的所有者,可以执行自身 migrations、建表和创建索引,但不能创建数据库、创建角色、成为超级用户或连接其他托管应用的数据库。

6. pgvector

注册时申请 "requestedExtensions":["vector"] 后,共享服务会在应用数据库内执行:

CREATE EXTENSION IF NOT EXISTS vector;

应用可直接在 migration 中使用:

CREATE TABLE embeddings (
    id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    embedding vector(768) NOT NULL
);

CREATE INDEX embeddings_cosine_idx
    ON embeddings USING hnsw (embedding vector_cosine_ops);

不要由应用尝试安装系统扩展文件。未在允许列表中的扩展会在注册阶段被拒绝。

7. schema 与迁移

  • 新数据库默认撤销 PUBLIC 的数据库连接权和 schema 权限。
  • 应用角色拥有自己的数据库和 public schema,可正常运行 Flyway、EF Core、Alembic、Liquibase 等迁移。
  • 用 custom-format pg_dump 迁移旧库时建议使用 --no-owner --no-acl
  • 恢复前确认目标库为空;不要覆盖其他 appId 的数据库。
  • 切换前至少校验关键表行数,保留旧数据和带 SHA-256 的转储,确认稳定后再人工清理。

8. 密码轮换、吊销与恢复

管理员可以在 PostgreSQL 管理面板中:

  • 为某个客户端轮换密码:应用必须立即更新本地凭据。
  • 吊销客户端:对应角色变为 NOLOGIN,数据库不会被删除。
  • 轮换全局接入令牌:旧令牌立即失效,已经签发的数据库密码不受影响。

应用本地凭据丢失时,让用户在升级/修复向导重新输入当前接入令牌,再用相同 appId 注册。数据库内容会保留,但旧数据库密码会失效。

不要静默回退到陈旧数据库副本。若应用已经完成共享数据库迁移,但凭据不可用,应停止启动并提示修复凭据,避免产生两套分叉数据。

9. 健康检查与排错

curl -fsS http://127.0.0.1:15433/health
curl -fsS http://127.0.0.1:15433/health/ready

排查顺序:

  1. 确认 nxsir.postgresql 已安装且状态为运行中。
  2. 确认应用使用 127.0.0.1,而不是 NAS 局域网 IP。
  3. 检查接入 API 端口 15433 与数据库端口 15432 是否混用。
  4. 检查凭据文件权限是否为 0600,字段是否完整。
  5. password authentication failed 通常表示密码已轮换;用相同 appId 重新注册并原子更新凭据。
  6. permission denied for database 通常表示连接了其他应用的数据库;必须使用响应中的 database。
  7. 共享服务升级或重启不会要求客户端重新注册,应用应使用已有凭据自动重连。

10. 接入验收清单

  • manifest 已声明 install_dep_apps=nxsir.postgresql
  • 接入 API 只从回环地址调用。
  • appId 固定且不会随版本变化。
  • 令牌和数据库密码从不写日志。
  • 凭据以 0600 原子落盘,成功后删除令牌种子。
  • 普通启动不重复注册、不意外轮换密码。
  • 应用 migrations 能在自己的数据库执行。
  • 已验证无法连接另一个测试应用的数据库。
  • 共享服务重启后应用能用原凭据恢复。
  • 已准备凭据丢失、密码轮换和迁移失败的明确恢复流程。