8.1 KiB
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 |
| 可申请扩展 | vector(pgvector) |
不要连接 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 权限。 - 应用角色拥有自己的数据库和
publicschema,可正常运行 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
排查顺序:
- 确认
nxsir.postgresql已安装且状态为运行中。 - 确认应用使用
127.0.0.1,而不是 NAS 局域网 IP。 - 检查接入 API 端口
15433与数据库端口15432是否混用。 - 检查凭据文件权限是否为
0600,字段是否完整。 password authentication failed通常表示密码已轮换;用相同appId重新注册并原子更新凭据。permission denied for database通常表示连接了其他应用的数据库;必须使用响应中的 database。- 共享服务升级或重启不会要求客户端重新注册,应用应使用已有凭据自动重连。
10. 接入验收清单
- manifest 已声明
install_dep_apps=nxsir.postgresql。 - 接入 API 只从回环地址调用。
appId固定且不会随版本变化。- 令牌和数据库密码从不写日志。
- 凭据以
0600原子落盘,成功后删除令牌种子。 - 普通启动不重复注册、不意外轮换密码。
- 应用 migrations 能在自己的数据库执行。
- 已验证无法连接另一个测试应用的数据库。
- 共享服务重启后应用能用原凭据恢复。
- 已准备凭据丢失、密码轮换和迁移失败的明确恢复流程。