# 仓库服务

`cipherfs serve` 持有仓库租约并运行 HTTP 服务库；独立 `cipherfs-web` 可执行程序已删除。本机客户端使用私有 Unix socket 或 Windows 命名管道与文件中的归属凭据，服务不要求挂载驱动。

```sh
# 现有仓库；通过通常的交互提示或 CIPHERFS_PASSWORD 取得密码。
cipherfs serve --vault ~/vault

# HTTP/JSON 监听，初始密封，通过 API 解封。
cipherfs serve --vault ~/vault --listen-address 127.0.0.1:3000

# 非回环监听强制使用 PEM 证书链与私钥文件。
cipherfs serve --vault ~/vault --listen-address 0.0.0.0:3443 \
  --tls-certificate /etc/cipherfs/server.pem --tls-private-key /etc/cipherfs/server-key.pem
```

TCP 使用可撤销 Bearer JWT 会话，无权调用私有本机挂载、导出或凭据操作。只有回环地址允许明文 HTTP；IPv4/IPv6 通配地址及其他地址强制 TLS，握手限时十秒。命令参数与配置只放 TLS 文件路径、解锁来源引用，不放密码或密钥值。

## 配置

`cipherfs serve --config /etc/cipherfs/serve.json` 使用有界 JSON，拒绝未知字段、重复别名/路径与不合法监听策略。路径必须绝对；最多 128 个仓库、16 个监听，端口零表示由系统分配。

```json
{
  "vaults": [
    {"name": "personal", "vault_path": "/srv/cipherfs/personal", "unlock_source_ref": "env:PERSONAL_PASSWORD"},
    {"name": "work", "vault_path": "/srv/cipherfs/work", "unlock_source_ref": "env:WORK_PASSWORD"}
  ],
  "listeners": [{"listen_address": "127.0.0.1:3000"}]
}
```

所有现有仓库先以密封状态取得租约并发布私有端点；没有来源时也可仅用本机 CLI 解封。`env:NAME` 每次读取密码，`secret_sources` 与 `auto_unseal` 支持文件、systemd、原生钥匙串和 HTTPS 来源，见[凭据与密封](credentials-sealing.zh.md)。来源失败保持 sealed 并按 1–30 秒退避重试；仓库无法取得或配置非法才排空已取得归属并报错。TLS 监听增加 `"tls": {"certificate_path": "/etc/cipherfs/server.pem", "private_key_path": "/etc/cipherfs/server-key.pem"}`。

每个仓库独立拥有租约、会话、同步与挂载服务。API 前缀为 `/vaults/personal/api/v1`、`/vaults/work/api/v1`，会话不能跨仓库。单仓库同时开放 `/api/v1`。服务只提供 API；桌面界面由 `cipherfs-app` 提供。

## S3 前门

CLI 构建启用 `--features s3`（协议适配器要求 Rust 1.96 以上），在同一个配置中增加 `s3_gateway`。它使用独立监听器和同一批仓库归属，不需要另开数据库、挂载或对象存放点；HTTP/JSON 和私有本机 API 继续并存。

```json
{
  "vaults": [{"name": "personal", "vault_path": "/srv/cipherfs/personal"}],
  "secret_sources": {"applications": {"kind": "files", "directory_path": "/etc/cipherfs/application-secrets"}},
  "s3_gateway": {
    "allowed_hosts": ["127.0.0.1:9000"],
    "buckets": {"personal-files": {"vault": "personal"}},
    "applications": [{
      "application_id": "backup",
      "access_key_id": "backup-application",
      "credential_ref": {"source": "applications", "name": "backup"},
      "buckets": ["personal-files"],
      "read_only": false
    }]
  }
}
```

应用秘密单独放在仓库之外的私有凭据文件中，16–256 个可见 ASCII 字符，不带行尾换行。每次验签重新读取命名来源，来源失败拒绝访问。此例不自动解封：通过本机 CLI 解封，或另配独立 `auto_unseal` 来源。应用 S3 密钥不能解封仓库或调用 JSON/本机管理 API。

默认监听 `127.0.0.1:9000`，签名 region 为 `us-east-1`；可以设置 `listen_address`、`region` 和 `tls`。非回环地址强制 TLS，证书路径格式与 HTTP 监听相同。`allowed_hosts` 是精确 HTTP authority 白名单，包括客户端实际使用的端口；不接受通配、转发 Host 路由或 absolute-form 请求目标。客户端使用 path-style（`endpoint/bucket/key`）与 SigV4，匿名与 SigV2 均拒绝。应用仅能访问自己配置的桶，`read_only` 禁止变更；每个桶声明仓库，可另设规范路径 `prefix`；键、列举和历史都相对此子树。子树之外的版本号不能绕过键映射。

支持桶发现/HEAD/location/versioning、PUT/GET/HEAD/DELETE、ListObjects v1/v2、ListObjectVersions、分片创建/上传/列举/完成/中止、单字节范围与 SigV4 预签名请求。对象列举支持 prefix、delimiter 和分页。共享版本号就是 CipherFS 版本身份，普通历史按原始键读取；并发 head 在文件当前生效的键下列举、读取，也覆盖并发移动。历史仍受仓库保留与退役规则约束。本地专属文件可以读取，写入沿用本地专属规则，不因此获得共享历史；显式仓库目录不作为 S3 对象列出。

默认 `etag_policy: "version"` 使用包含版本身份的强校验标记，分片完成后也一样，**不是明文 MD5**。需要明文 MD5 校验标记的客户端可选择 `etag_policy: "content_md5"`；GET、HEAD 和列举会流式读取并认证正文以计算 ETag，因此元数据操作也可能拉取未缓存正文，分片完成返回整体明文的 MD5。分片 ETag 使用 MD5，完成请求必须匹配。发布前检查 Content-MD5、SHA-1、SHA-256、CRC32、CRC32C、CRC64NVME 校验和及已验签的流式 trailer；正文不完整、取消或校验失败不会替换现有对象。GET/HEAD 支持 ETag 条件；日期条件和条件写入返回 `NotImplemented`。

键精确对应 NFC 仓库文件路径，最多 1,024 UTF-8 字节；空键、首尾斜杠、重复斜杠、点段、反斜杠和控制字符明确拒绝，不归一化成别的对象。不实现桶创建/删除、virtual-host 桶路由、CopyObject、ACL、标签、S3 自定义 metadata、S3 加密选项及永久版本删除；仓库支持的管理和退役能力通过原有 API 使用。共享格式不变。

上传使用有界明文缓冲与加密临时文件；默认最多 128 个分片上传、合计 16 GiB 密文暂存预算，通过 `max_uploads`、`spool_budget_bytes` 调整。单次 PUT/分片最多 5 GiB；完成清单最多 10,000 段，除最后一段外至少 5 MiB。上传 ID 绑定应用、桶、键和归属会话，完成只发布一次仓库写入；中止、24 小时过期、密封和退出释放临时状态，重启不恢复未完成上传。下载背压可被密封取消。上游协议库的 debug 事件可能带解码键或签名材料，因此整个协议调用隔离 tracing；访问审计阶段与 tracing 分开实施。

## 应用服务器控制

认证 JSON 接口另提供准确版本的耐久确认、FileId 读写、单文件固定、有界访问热度预取和可选的毫秒保留期限。路由、回执与取消语义见[应用服务器 API](server-api.zh.md)。

## 状态与退出

`GET /health` 返回进程存活、`sealed`、`vaults_count`、`sealed_count`、`recovery_pending_count`，不含仓库路径、身份或凭据。全部仓库解锁时 `GET /ready` 为 200，否则 503。别名下的 `/health`、`/ready` 仅反映该仓库。状态接口不代替文件访问认证。

启用同步时，配置式服务的健康/就绪响应另含 `directory_delete_attempts_count`：本进程全部 Directory 后端 DELETE 调用次数，含失败与目标不存在的调用。计数在进程启动时清零，不是持久删除历史。`serve` 排空后输出含最终计数的 JSON `serve_stopped` 事件；核对跨重启期间时须同时记录进程生命周期与退出计数。

SIGINT 与 Unix SIGTERM 停止接受 TCP 连接、撤销会话、卸载并等待挂载退出，排空已接纳操作与同步，释放本机端点和租约；TCP 慢连接的退出等待有界。`cipherfs seal` / `POST /api/v1/seal` 撤销会话、停止同步与挂载、排空工作并清钥，但保留租约和私有端点；`cipherfs unseal` 或 `/api/v1/unseal` 重新认证，同一归属的旧句柄与会话不复活。锁定/注销只关闭对应仓库；配置守护进程继续服务其他仓库并允许以后解锁。单仓库纯本机命令在仓库关闭后退出。

下载 worker 失败时会记录固定错误分类、OS 错误编号与已入队字节数，不记录明文路径、凭据或提供方错误正文。CLI 若在响应 body 中途断开，会报告已输出字节数并以非零状态退出，不把已读前缀当作完整文件；可结合归属进程日志区分正文不可用、文件系统错误、取消和完整性失败。

## 归属挂载

```sh
cipherfs mount --vault ~/vault --mount-point ~/mounted
cipherfs mount --vault ~/vault --mount-point ~/mounted --unmount
```

已有可达归属进程时，请它完成挂载/卸载后返回，挂载随归属进程存活；本机 API 只能卸载该归属跟踪的目标。关闭仓库先卸载全部目标，再释放租约。没有归属时 mount 自己成为归属并发布同一本机 API；`--background` 继续使用既有私有密码/就绪握手。归属构建需包含 `fuse`（通常默认含 `fuse-t`）；Windows 明确不支持挂载。

继续使用既有空目标、存储不重叠、只读与后端检查；macOS 默认原生 FUSE-T，Linux 使用内核 FUSE。Partial 正文由归属既有后台运行时经已配置存放点/对端取得。
