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

238 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 中声明:
```ini
install_dep_apps=nxsir.postgresql
```
用户应先安装并启动 PostgreSQL 共享服务,再安装你的应用。你的安装/升级向导需要提供一个密码字段,让用户填写安装共享服务时设置的“应用接入令牌”。令牌长度为 20~256 个字符,不允许换行。
接入令牌与 PostgreSQL 管理员密码是两套独立凭据:
- 管理员密码只登录 PostgreSQL 管理面板。
- 接入令牌只用于本机应用首次签发或重新签发数据库凭据。
## 3. 注册客户端
注册接口只接受来自回环地址的请求:
```http
POST http://127.0.0.1:15433/internal/v1/enroll
Authorization: Bearer <应用接入令牌>
Content-Type: application/json
```
普通 PostgreSQL 客户端:
```json
{
"appId": "myapp",
"displayName": "My fnOS App",
"requestedExtensions": []
}
```
需要 pgvector 的应用:
```json
{
"appId": "imagefind",
"displayName": "ImageFind",
"requestedExtensions": ["vector"]
}
```
字段限制:
- `appId`:稳定且全局唯一,3~64 个字符;以小写字母开头,只允许小写字母、数字、点、下划线和连字符。发布后不要更改。
- `displayName`:1~100 个字符,用于管理面板展示。
- `requestedExtensions`:目前只能是空数组或包含 `vector`
curl 示例:
```bash
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
```
成功响应:
```json
{
"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` 保存令牌种子,不要在向导校验阶段依赖网络。应用启动时执行注册,并采用临时文件加原子重命名保存响应。
建议的持久化文件:
```text
${TRIM_PKGVAR}/postgres-enrollment-token.seed # 首次注册前,0600
${TRIM_PKGVAR}/postgres-client.conf # 注册成功后,0600
```
推荐配置格式:
```ini
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
```text
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
```python
import psycopg
connection = psycopg.connect(
host="127.0.0.1",
port=15432,
dbname=database,
user=username,
password=password,
sslmode="disable",
connect_timeout=15,
)
```
JDBC
```text
jdbc:postgresql://127.0.0.1:15432/<database>?sslmode=disable&connectTimeout=15
```
应用角色是目标数据库及 `public` schema 的所有者,可以执行自身 migrations、建表和创建索引,但不能创建数据库、创建角色、成为超级用户或连接其他托管应用的数据库。
## 6. pgvector
注册时申请 `"requestedExtensions":["vector"]` 后,共享服务会在应用数据库内执行:
```sql
CREATE EXTENSION IF NOT EXISTS vector;
```
应用可直接在 migration 中使用:
```sql
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. 健康检查与排错
```bash
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 能在自己的数据库执行。
- 已验证无法连接另一个测试应用的数据库。
- 共享服务重启后应用能用原凭据恢复。
- 已准备凭据丢失、密码轮换和迁移失败的明确恢复流程。