mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: release QA hardening across processing, media, security, and CI gates (#649)
A release-readiness QA pass over the whole product. The commits split into defects a user would hit and gates that were reporting green while measuring nothing. ## Fixes that change behaviour Rate limiting was bypassable on every install: TRUST_PROXY defaulted to true, so request.ip came from a client-set header and a forged X-Forwarded-For got past the login limiter. The default is now a private-network trust list. A transient Postgres outage stranded in-flight jobs, leaving finished output on disk with no row pointing at it. A reconciler now resolves those rows and adopts the bytes rather than dropping the work. A Redis connection that moved to a new address wedged every read-blocked consumer, so completions stopped signalling while health still answered 200. Socket timeouts plus subscriber pings recover it. Installing more than one AI bundle left the shared venv multi-versioned and silently broke three tools. The installer now reconciles distributions to one version each. Converting an image to JXL at quality 1 through 4 returned a 500, because libjxl 0.7 rejects the distance those values compute. The quality is floored at what the encoder honours. A missing ffmpeg was also reported to the user as a corrupt upload; it now says the engine is unavailable. RAW uploads reached an unpatched LibRaw on arm64, so it is built from source at 0.22.2, and the release scan was split so it can fail on an unfixed critical instead of hiding it behind ignore-unfixed. ## Gates that could not fail Two mutation lanes ran zero mutants because Stryker crawled the gitignored docs build; coverage discarded its whole report on any failing test; the lint gate skipped root tests, scripts, and two workspaces; and several generated matrices counted a host missing ffmpeg as a passing tool. Each now measures what it claims. Full evidence and the outstanding release items are tracked locally and are not part of this branch.
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
---
|
||||
description: "SnapOtter 的 monorepo 结构、应用与包架构、请求生命周期以及资源占用。"
|
||||
i18n_output_hash: bc9e6a754251
|
||||
i18n_source_hash: a53946e760b0
|
||||
i18n_source_hash: 50e076925c4b
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: 02e1261334e5
|
||||
i18n_hash_version: 2
|
||||
---
|
||||
|
||||
# 架构 {#architecture}
|
||||
@@ -52,7 +53,7 @@ Python 脚本位于 `packages/ai/python/` 中。大型可选模型包根据需
|
||||
|
||||
### API(`apps/api`) {#api-apps-api}
|
||||
|
||||
一个 Fastify v5 服务器,暴露跨五种模态(image、video、audio、PDF、file)的 241 个工具路由,负责处理:
|
||||
一个 Fastify v5 服务器,暴露跨五种模态(image、video、audio、PDF、file)的 243 个工具路由,负责处理:
|
||||
- 文件上传、临时工作区管理以及持久化文件存储
|
||||
- 用户文件库(`user_files` 表):默认情况下,保存的编辑会存储为一个独立的新文件;而当你覆盖原文件时,则存储为一个与父文件链接的版本。它记录所应用的工具(`toolChain`),并为 Files 页面自动生成缩略图
|
||||
- 工具执行(将每个工具请求路由到图像引擎或 AI 桥接层)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
description: "所有 SnapOtter 环境变量及其默认值。配置认证、存储、AI 模型、分析等。"
|
||||
i18n_source_hash: 8e9e9ca2840c
|
||||
i18n_source_hash: 25970c776f7c
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: 2d14d29daa92
|
||||
i18n_output_hash: a0f2d2b10467
|
||||
i18n_hash_version: 2
|
||||
---
|
||||
|
||||
# 配置 {#configuration}
|
||||
@@ -19,28 +20,51 @@ i18n_output_hash: 2d14d29daa92
|
||||
| `RATE_LIMIT_PER_MIN` | `1000` | 每个 IP 每分钟的最大请求数。设为 0 可禁用限流。 |
|
||||
| `CORS_ORIGIN` | (空) | 用于 CORS 的逗号分隔的允许来源,留空则仅限同源。 |
|
||||
| `LOG_LEVEL` | `info` | 日志详细程度。取值之一:`fatal`、`error`、`warn`、`info`、`debug`、`trace`。 |
|
||||
| `TRUST_PROXY` | `true` | 信任来自反向代理的 `X-Forwarded-For` 头。如果不在代理之后,设为 `false`。 |
|
||||
| `TRUST_PROXY` | `loopback,linklocal,uniquelocal` | 允许哪些对端通过 `X-Forwarded-For` 设置客户端 IP。默认值只相信来自私有网络的对端,因此 Docker 网络或局域网中的反向代理会被信任,而公网客户端伪造的头不会。只有当你自己控制的代理以公网地址位于前端时,才设为 `true`。 |
|
||||
|
||||
### 认证 {#authentication}
|
||||
|
||||
下面两个布尔值只接受 `true` 和 `false`。其他任何取值,比如 `1`、`yes` 或 `on`,都会校验失败,服务器会在开始监听之前退出。
|
||||
|
||||
| 变量 | 默认值 | 描述 |
|
||||
|---|---|---|
|
||||
| `AUTH_ENABLED` | `false` | 设为 `true` 以要求登录。Docker 镜像默认为 `true`。 |
|
||||
| `AUTH_ENABLED` | `true` | 要求登录。设为 `false` 可在完全没有账户的情况下运行,此时每个请求都拥有管理员权限,所以只应在可信网络中这样做。 |
|
||||
| `DEFAULT_USERNAME` | `admin` | 初始管理员账户的用户名。仅在首次运行时使用。 |
|
||||
| `DEFAULT_PASSWORD` | `admin` | 初始管理员账户的密码。首次登录后请更改。 |
|
||||
| `MAX_USERS` | `0`(无限制) | 已注册用户账户的最大数量。设为 0 表示无限制。 |
|
||||
| `SESSION_DURATION_HOURS` | `168` | 登录会话的存续时长(小时)(默认为 7 天)。 |
|
||||
| `SKIP_MUST_CHANGE_PASSWORD` | - | 设为任意非空值可跳过首次登录时的强制改密提示。 |
|
||||
| `SKIP_MUST_CHANGE_PASSWORD` | `false` | 设为 `true` 可跳过首次登录时的强制改密提示。 |
|
||||
|
||||
### 存储 {#storage}
|
||||
|
||||
| 变量 | 默认值 | 描述 |
|
||||
|---|---|---|
|
||||
| `STORAGE_MODE` | `local` | `local` 或 `s3`。S3/MinIO 需要带有 s3_storage 功能的许可证。 |
|
||||
| `DATABASE_URL` | `postgres://snapotter:snapotter@postgres:5432/snapotter` | PostgreSQL 连接字符串。 |
|
||||
| `REDIS_URL` | `redis://redis:6379` | Redis 连接字符串(用于 BullMQ 作业队列)。 |
|
||||
| `WORKSPACE_PATH` | `./tmp/workspace` | 处理期间临时文件的目录。会自动清理。 |
|
||||
| `FILES_STORAGE_PATH` | `./data/files` | 持久化用户文件(上传的图像、已保存的结果)的目录。 |
|
||||
| `STORAGE_MODE` | `local` | `local` 或 `s3`。S3 和 MinIO 需要带有 s3_storage 功能的许可证,以及下面的 `S3_*` 变量。 |
|
||||
| `DATABASE_URL` | `postgres://snapotter:snapotter@localhost:5432/snapotter` | PostgreSQL 连接字符串。Compose 栈会把它指向自己的 `postgres` 服务;把它(连同 `REDIS_URL`)留作未设置即可进入内嵌模式。 |
|
||||
| `REDIS_URL` | `redis://localhost:6379` | Redis 连接字符串(用于 BullMQ 作业队列)。Compose 会把它指向自己的 `redis` 服务。 |
|
||||
| `WORKSPACE_PATH` | `./tmp/workspace` | 处理期间临时文件的目录。会自动清理。镜像将其设为 `/tmp/workspace`。 |
|
||||
| `FILES_STORAGE_PATH` | `./data/files` | 持久化用户文件(上传的图像、已保存的结果)的目录。镜像将其设为 `/data/files`。 |
|
||||
|
||||
### S3 对象存储 {#s3-object-storage}
|
||||
|
||||
仅在 `STORAGE_MODE=s3` 时才会读取。三个必填项中缺少任何一个,启动都会失败,并指出你漏掉的变量名。
|
||||
|
||||
| 变量 | 默认值 | 描述 |
|
||||
|---|---|---|
|
||||
| `S3_BUCKET` | (空) | 存放上传文件和输出的存储桶。必填。 |
|
||||
| `S3_ACCESS_KEY_ID` | (空) | 访问密钥。必填。在容器中也可以改用 `S3_ACCESS_KEY_ID_FILE` 挂载它。 |
|
||||
| `S3_SECRET_ACCESS_KEY` | (空) | 私有密钥。必填。同样的文件约定:`S3_SECRET_ACCESS_KEY_FILE`。 |
|
||||
| `S3_REGION` | `us-east-1` | 存储桶所在区域。 |
|
||||
| `S3_ENDPOINT` | (空) | 供 MinIO、R2、Backblaze 及其他 S3 兼容存储使用的自定义端点。留空表示 AWS。 |
|
||||
| `S3_FORCE_PATH_STYLE` | `false` | 对 MinIO 以及其他需要 `endpoint/bucket/key` 而非虚拟主机寻址的存储,设为 `true`。 |
|
||||
| `S3_PREFIX` | (空) | 键前缀,可让一个存储桶容纳多个实例。 |
|
||||
|
||||
### 静态数据加密 {#encryption-at-rest}
|
||||
|
||||
| 变量 | 默认值 | 描述 |
|
||||
|---|---|---|
|
||||
| `DATA_ENCRYPTION_KEY` | (空) | 64 个十六进制字符(32 字节)。用于加密数据库中存储的敏感设置。不是 64 个十六进制字符的值会在启动时被拒绝。 |
|
||||
| `DATA_ENCRYPTION_KEY_PREVIOUS` | (空) | 你正在轮换掉的旧密钥,格式相同。轮换期间同时设置两者,让已有数据行仍能解密,之后再移除这一个。 |
|
||||
|
||||
### 内嵌模式 {#embedded-mode}
|
||||
|
||||
@@ -59,16 +83,15 @@ i18n_output_hash: 2d14d29daa92
|
||||
|
||||
| 变量 | 默认值 | 描述 |
|
||||
|---|---|---|
|
||||
| `MAX_UPLOAD_SIZE_MB` | `100` | 每次上传的最大文件大小(MB)。设为 0 表示无限制。 |
|
||||
| `MAX_BATCH_SIZE` | `100` | 单个批处理请求中的最大文件数。设为 0 表示无限制。 |
|
||||
| `MAX_UPLOAD_SIZE_MB` | `0`(无限制) | 每次上传的最大文件大小(MB)。设为 0 表示无限制。已发布镜像出厂即为 `0`;源码构建的起点是 100。 |
|
||||
| `MAX_BATCH_SIZE` | `0`(无限制) | 单个批处理请求中的最大文件数。设为 0 表示无限制。已发布镜像出厂即为 `0`;源码构建的起点是 100。 |
|
||||
| `CONCURRENT_JOBS` | `0`(自动) | 并行运行的批处理作业数量。设为 0 可根据可用 CPU 核心自动检测。 |
|
||||
| `MAX_MEGAPIXELS` | `0`(无限制) | 允许的最大图像分辨率(百万像素)。设为 0 表示无限制。 |
|
||||
| `MAX_WORKER_THREADS` | `0`(自动) | 图像处理的最大工作线程数。设为 0 可根据可用 CPU 核心自动检测。 |
|
||||
| `PROCESSING_TIMEOUT_S` | `0`(无限制) | 每个请求的最大处理时间(秒)。设为 0 表示无超时。 |
|
||||
| `MAX_PIPELINE_STEPS` | `20` | 一个流水线中的最大步骤数。设为 0 表示无限制。 |
|
||||
| `MAX_CANVAS_PIXELS` | `0`(无限制) | 输出图像的最大画布尺寸(像素)。设为 0 表示无限制。 |
|
||||
| `MAX_SVG_SIZE_MB` | `0`(无限制) | 最大 SVG 文件大小(MB)。设为 0 表示无限制。 |
|
||||
| `MAX_SPLIT_GRID` | `100` | 图像分割工具的最大网格维度。 |
|
||||
| `MAX_SVG_SIZE_MB` | `50` | 净化处理之前所接受的最大 SVG 大小(MB)。这里的 `0` 与相邻各行的含义不同:它不是把上限调高,而是彻底移除解析前的大小限制,所以这一项请保持设置。 |
|
||||
| `MAX_PDF_PAGES` | `0`(无限制) | PDF-to-image 转换的最大 PDF 页数。设为 0 表示无限制。 |
|
||||
|
||||
### 清理 {#cleanup}
|
||||
@@ -82,7 +105,7 @@ i18n_output_hash: 2d14d29daa92
|
||||
|
||||
| 变量 | 默认值 | 描述 |
|
||||
|---|---|---|
|
||||
| `DEFAULT_THEME` | `light` | 新会话的默认主题。`light` 或 `dark`。 |
|
||||
| `DEFAULT_THEME` | `light` | 新会话的默认主题。`light`、`dark` 或 `system`。 |
|
||||
| `DEFAULT_LOCALE` | `en` | 默认界面语言。 |
|
||||
| `DEFAULT_TOOL_VIEW` | `sidebar` | 默认工具布局。`sidebar` 或 `fullscreen`。 |
|
||||
|
||||
@@ -124,13 +147,13 @@ services:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_USER: snapotter
|
||||
POSTGRES_PASSWORD: snapotter
|
||||
POSTGRES_PASSWORD: snapotter # 针对非本地部署更改此设置
|
||||
POSTGRES_DB: snapotter
|
||||
volumes:
|
||||
- SnapOtter-pgdata:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U snapotter"]
|
||||
test: ["CMD-SHELL", "pg_isready -U snapotter -d snapotter"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
description: "如何为 SnapOtter 做贡献。缺陷报告、功能请求、拉取请求以及 CLA 要求。"
|
||||
i18n_source_hash: 528802503035
|
||||
i18n_source_hash: 6c920a5f83e0
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: ae428c516557
|
||||
i18n_output_hash: 048997196c13
|
||||
i18n_hash_version: 2
|
||||
---
|
||||
|
||||
# 贡献指南 {#contributing}
|
||||
@@ -53,7 +54,7 @@ i18n_output_hash: ae428c516557
|
||||
|
||||
### 前置条件 {#prerequisites}
|
||||
|
||||
- Node.js 22+
|
||||
- Node.js 22.22+
|
||||
- pnpm 9+
|
||||
- Python 3.11+(仅 AI 工具需要)
|
||||
- Docker(可选,用于完整的集成测试)
|
||||
@@ -71,7 +72,7 @@ docker compose -f docker-compose.dev.yml up -d
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Start dev servers (web on :1349, API on :13490)
|
||||
# Start dev servers (web on :1351, API on :13490)
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
description: "SnapOtter 的 PostgreSQL 数据库架构、表、迁移和备份流程。"
|
||||
i18n_source_hash: 50d5d4f220cf
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: 3289d2d06514
|
||||
i18n_source_hash: a68264552836
|
||||
i18n_provenance: machine
|
||||
i18n_output_hash: 127adc94ef4c
|
||||
i18n_hash_version: 2
|
||||
---
|
||||
|
||||
# 数据库 {#database}
|
||||
@@ -145,6 +146,17 @@ SnapOtter 使用 PostgreSQL 17 配合 [Drizzle ORM](https://orm.drizzle.team/)
|
||||
| `details` | jsonb | 特定于操作的数据 |
|
||||
| `createdAt` | timestamp | 操作时间 |
|
||||
|
||||
### user_preferences {#user-preferences}
|
||||
|
||||
按偏好名称存储的每用户界面状态。首页的已固定工具通过 `PUT /api/v1/preferences` 写入这里。
|
||||
|
||||
| 列 | 类型 | 备注 |
|
||||
|---|---|---|
|
||||
| `userId` | text | 指向 users 的外键,级联删除。与 `key` 共同构成主键 |
|
||||
| `key` | text | 偏好名称。与 `userId` 共同构成主键 |
|
||||
| `value` | jsonb | 偏好内容 |
|
||||
| `updatedAt` | timestamp | 最后写入时间 |
|
||||
|
||||
## 迁移 {#migrations}
|
||||
|
||||
Drizzle 负责处理架构迁移。迁移文件位于 `apps/api/drizzle/`。开发期间:
|
||||
@@ -157,29 +169,37 @@ npx drizzle-kit migrate # apply pending migrations
|
||||
|
||||
在生产环境中,待处理的迁移会在启动时自动应用。
|
||||
|
||||
## 备份与恢复 {#backup-and-restore}
|
||||
## 备份和恢复{#backup-and-restore}
|
||||
|
||||
关系数据库位于 Postgres 容器的 `SnapOtter-pgdata` 卷中,而不是应用的 `/data` 卷中。
|
||||
关系数据库位于 Postgres 容器的 `SnapOtter-pgdata` 卷中,而不是应用程序的 `/data` 卷中。
|
||||
|
||||
**方案 1:pg_dump(推荐)**
|
||||
**带验证的逻辑备份(推荐)**
|
||||
|
||||
```bash
|
||||
# Dump the database while the stack is running
|
||||
docker exec SnapOtter-postgres pg_dump -U snapotter snapotter > backup.sql
|
||||
# Dump into PostgreSQL's portable custom archive format
|
||||
docker exec SnapOtter-postgres \
|
||||
pg_dump --format=custom --no-owner -U snapotter snapotter > snapotter.dump
|
||||
test -s snapotter.dump
|
||||
docker exec -i SnapOtter-postgres pg_restore --list < snapotter.dump >/dev/null
|
||||
|
||||
# Restore into a fresh database
|
||||
cat backup.sql | docker exec -i SnapOtter-postgres psql -U snapotter snapotter
|
||||
# Restore into a fresh/disposable target first and fail on the first SQL error
|
||||
docker exec -i SnapOtter-postgres \
|
||||
pg_restore --exit-on-error --clean --if-exists --no-owner \
|
||||
-U snapotter -d snapotter < snapotter.dump
|
||||
```
|
||||
|
||||
**方案 2:卷快照**
|
||||
此数据库转储不包含以 `/data/files` 保存的库对象或 Redis 中的持久 BullMQ 状态。使用[安全与强化](/zh-CN/guide/security#backup-and-recovery) 中的协调程序备份和恢复这些内容。
|
||||
|
||||
**冷卷快照**
|
||||
|
||||
```bash
|
||||
# Stop the stack, then snapshot the pgdata volume
|
||||
docker compose down
|
||||
docker run --rm -v SnapOtter-pgdata:/data -v $(pwd)/backup:/backup \
|
||||
alpine tar czf /backup/snapotter-pgdata.tar.gz -C /data .
|
||||
# Stop every service first, then use your storage platform to snapshot the
|
||||
# PostgreSQL, app-data, and Redis volumes as one crash-consistent set.
|
||||
docker compose -f docker/docker-compose.yml stop
|
||||
```
|
||||
|
||||
不要使用 `tar` 复制实时 PostgreSQL 数据目录。按项目编写卷名称前缀,因此从 `docker inspect` 或您的存储平台解析已安装的卷 ID,而不是假设文字标签 `SnapOtter-pgdata`。
|
||||
|
||||
### 从 1.x(SQLite)迁移 {#migrating-from-1-x-sqlite}
|
||||
|
||||
从 SnapOtter 1.x 升级有专门的指南:参见 [从 1.x 升级到 2.0](./upgrading)。简而言之,复用你现有的 `/data` 卷,2.0 会在首次启动时自动检测并导入 `/data/snapotter.db`(或设置 `SQLITE_MIGRATE_PATH` 明确指向它)。请先备份整个 `/data` 卷,而不仅仅是 `snapotter.db`:1.x 使用 SQLite WAL 模式,因此一个已停止的容器往往会把大部分数据留在 `snapotter.db-wal` 中,旁边则是一个几乎为空的 `snapotter.db`。
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
description: "使用 Docker 将 SnapOtter 部署到生产环境。涵盖硬件要求、GPU 配置,以及 Nginx、Traefik 和 Cloudflare 的反向代理配置。"
|
||||
i18n_output_hash: 63267650bd5f
|
||||
i18n_source_hash: 98172965118b
|
||||
i18n_source_hash: 2a722f86da75
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: 94e82ef1fffe
|
||||
i18n_hash_version: 2
|
||||
---
|
||||
|
||||
# 部署 {#deployment}
|
||||
@@ -47,7 +48,7 @@ services:
|
||||
# - MAX_USERS=0 # Max user accounts
|
||||
|
||||
# --- Networking ---
|
||||
# - TRUST_PROXY=true # Trust X-Forwarded-For headers (set false if not behind a proxy)
|
||||
# - TRUST_PROXY=loopback,linklocal,uniquelocal # Which peers may set the client IP via X-Forwarded-For (default shown)
|
||||
|
||||
# --- Bind mount permissions ---
|
||||
# - PUID=1000 # Match your host user's UID (run: id -u)
|
||||
@@ -82,7 +83,7 @@ services:
|
||||
- SnapOtter-pgdata:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U snapotter"]
|
||||
test: ["CMD-SHELL", "pg_isready -U snapotter -d snapotter"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
@@ -170,13 +171,13 @@ services:
|
||||
container_name: SnapOtter-postgres
|
||||
environment:
|
||||
POSTGRES_USER: snapotter
|
||||
POSTGRES_PASSWORD: snapotter
|
||||
POSTGRES_PASSWORD: snapotter # 针对非本地部署更改此设置
|
||||
POSTGRES_DB: snapotter
|
||||
volumes:
|
||||
- SnapOtter-pgdata:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U snapotter"]
|
||||
test: ["CMD-SHELL", "pg_isready -U snapotter -d snapotter"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
@@ -207,13 +208,17 @@ volumes:
|
||||
docker compose -f docker-compose-gpu.yml up -d
|
||||
```
|
||||
|
||||
在日志中检查 CUDA 是否被检测到:
|
||||
### 验证 GPU 加速 {#verify-gpu-acceleration}
|
||||
|
||||
检查日志中的 CUDA 检测:
|
||||
|
||||
```bash
|
||||
docker logs SnapOtter 2>&1 | head -20
|
||||
# Look for: [gpu] CUDA available via torch
|
||||
```
|
||||
|
||||
如果 AI 工具在 CPU 上运行,即使 `--gpus all` 和 NVIDIA Container Toolkit 设置正确,请从 **设置 → AI 功能** 重新安装受影响的捆绑包(例如背景删除)。安装程序会恢复 ONNX 运行时的 GPU 版本,而由另一个捆绑包(例如转录)引入的仅 CPU 版本可能会在共享 AI 环境中隐藏。如果从 UI 重新安装无法恢复旧映像上的 GPU,请参阅 [问题 #490](https://github.com/snapotter-hq/SnapOtter/issues/490) 中的手动修复。
|
||||
|
||||
## 硬件要求 {#hardware-requirements}
|
||||
|
||||
这些数字来自一系列系统上的基准测试,从配备 NVIDIA RTX 4070 的现代 amd64 工作站到 Raspberry Pi,在每台设备上运行整个工具目录,并扫描 Docker 资源限制以找出真实的下限。
|
||||
@@ -436,11 +441,11 @@ securityContext:
|
||||
| `AUTH_ENABLED` | `true` | 启用/禁用登录要求 |
|
||||
| `DEFAULT_USERNAME` | `admin` | 初始管理员用户名 |
|
||||
| `DEFAULT_PASSWORD` | `admin` | 初始管理员密码(首次登录时强制更改) |
|
||||
| `MAX_UPLOAD_SIZE_MB` | `100` | 单文件上传限制 |
|
||||
| `MAX_BATCH_SIZE` | `100` | 每个批量请求的最大文件数 |
|
||||
| `MAX_UPLOAD_SIZE_MB` | `0`(无限制) | 单文件上传限制(MB)。镜像出厂即为 `0`;从源码构建则从 100 起 |
|
||||
| `MAX_BATCH_SIZE` | `0`(无限制) | 每个批量请求的最大文件数。镜像出厂即为 `0`;从源码构建则从 100 起 |
|
||||
| `RATE_LIMIT_PER_MIN` | `1000` | 每 IP 每分钟的 API 请求数(设为 0 可禁用) |
|
||||
| `MAX_USERS` | `0`(无限制) | 最大用户账户数 |
|
||||
| `TRUST_PROXY` | `true` | 信任来自反向代理的 X-Forwarded-For 头 |
|
||||
| `TRUST_PROXY` | `loopback,linklocal,uniquelocal` | 允许哪些对端通过 `X-Forwarded-For` 设置客户端 IP。默认仅限私有网络 |
|
||||
| `PUID` | `999` | 以此 UID 运行(用于绑定挂载权限) |
|
||||
| `PGID` | `999` | 以此 GID 运行(用于绑定挂载权限) |
|
||||
| `LOG_LEVEL` | `info` | 日志详细程度:fatal、error、warn、info、debug、trace |
|
||||
@@ -483,7 +488,13 @@ curl http://localhost:1349/api/v1/health
|
||||
|
||||
## 反向代理 {#reverse-proxy}
|
||||
|
||||
SnapOtter 默认设置 `TRUST_PROXY=true`,因此速率限制和日志记录会使用来自 `X-Forwarded-For` 头的真实客户端 IP。
|
||||
`TRUST_PROXY` 默认为 `loopback,linklocal,uniquelocal`,因此 SnapOtter 只相信来自私有网络对端的 `X-Forwarded-For`。同一主机、Docker 网络或局域网中的反向代理开箱即受信任,这意味着速率限制、登录暴力破解限制、审计日志以及 enterprise 版的 IP 允许列表,无需任何配置就能看到真实的客户端 IP。
|
||||
|
||||
只有当前置代理从**公网**地址访问 SnapOtter 时才设为 `TRUST_PROXY=true`,例如位于另一网络的云负载均衡器。在直接暴露的实例上,该值会让 `request.ip` 落入攻击者手中,因为不断更换该头的调用方每次请求都能拿到一个全新的速率限制计数。
|
||||
|
||||
在动手测量客户端 IP 之前,有两件事要知道。macOS 和 Windows 上的 Docker Desktop 通过用户态代理提供已发布端口,会把所有源地址改写为虚拟机网关 `192.168.65.1`,因此在那里无论 `TRUST_PROXY` 取何值都拿不回真实客户端;面向互联网的部署请放在 Linux 上。而且在任何平台上,经 `localhost` 访问已发布端口都会被视为网桥网关而非你的客户端,所以 localhost 测试完全说明不了真实客户端是如何归属的。`TRUST_PROXY` 取值的完整表格和 Docker Desktop 注意事项见 [SECURITY.md](https://github.com/snapotter-hq/SnapOtter/blob/main/SECURITY.md#client-ip-resolution-trust_proxy)。
|
||||
|
||||
对于下面的每个代理来说,有两件事很重要:允许大型请求正文(上传),并且不缓冲响应。响应缓冲代理会破坏 SSE 进度,更明显的是,使大文件下载“开始但永远不会完成”,因为代理在传递之前保存整个文件。 SnapOtter 在下载时发送 `X-Accel-Buffering: no`,因此即使在其他地方保留缓冲,nginx 也会对它们进行流式传输,但除 nginx 之外的代理需要显式禁用响应缓冲(如下面的每个配置所示)。如果下载中途停止,首先要检查前面的缓冲代理。
|
||||
|
||||
### Nginx {#nginx}
|
||||
|
||||
@@ -505,7 +516,7 @@ server {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# SSE support (batch progress, feature install progress)
|
||||
# 流响应而不是缓冲:SSE 进度(批量、AI、功能安装)和大文件下载需要。
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
@@ -549,7 +560,7 @@ images.example.com {
|
||||
}
|
||||
```
|
||||
|
||||
`flush_interval -1` 会禁用响应缓冲,这对于 SSE 进度事件(批量处理、AI 工具、功能安装)是必需的。延长的超时时间允许大文件上传在 Caddy 不提前关闭连接的情况下完成。
|
||||
`flush_interval -1` 禁用响应缓冲,这是 SSE 进度事件(批处理、AI 工具、功能安装)以及大文件下载流式传输(而不是停滞)所必需的。延长的超时允许完成大文件上传,而无需 Caddy 提前关闭连接。
|
||||
|
||||
### Cloudflare Tunnels {#cloudflare-tunnels}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
description: "本地开发环境搭建、命令、代码约定,以及如何为 SnapOtter 添加新工具。"
|
||||
i18n_source_hash: cb03724d2829
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: c9c7c992d6d7
|
||||
i18n_source_hash: 56acc1bf9a9b
|
||||
i18n_provenance: machine
|
||||
i18n_output_hash: 064e6879f3bb
|
||||
i18n_hash_version: 2
|
||||
---
|
||||
|
||||
# 开发者指南 {#developer-guide}
|
||||
@@ -11,12 +12,12 @@ i18n_output_hash: c9c7c992d6d7
|
||||
|
||||
## 前置条件 {#prerequisites}
|
||||
|
||||
- [Node.js](https://nodejs.org/) 22+
|
||||
- [Node.js](https://nodejs.org/) 22.22+
|
||||
- [pnpm](https://pnpm.io/) 9+(`corepack enable && corepack prepare pnpm@latest --activate`)
|
||||
- [Docker](https://www.docker.com/)(本地 Postgres + Redis、容器构建和 AI 功能所必需)
|
||||
- Git
|
||||
|
||||
仅当你在开发 AI/ML sidecar(背景移除、放大、OCR)时才需要 Python 3.10+。
|
||||
仅当你在开发 AI/ML sidecar(背景移除、放大、OCR)时才需要 Python 3.11+。
|
||||
|
||||
## 搭建环境 {#setup}
|
||||
|
||||
@@ -32,10 +33,10 @@ pnpm dev
|
||||
|
||||
| 服务 | URL | 备注 |
|
||||
|----------|--------------------------|------------------------------------|
|
||||
| 前端 | http://localhost:1349 | Vite 开发服务器,代理 /api |
|
||||
| 前端 | http://localhost:1351 | Vite 开发服务器,代理 /api |
|
||||
| 后端 | http://localhost:13490 | Fastify API(通过代理访问) |
|
||||
|
||||
在浏览器中打开 http://localhost:1349。使用 `admin` / `admin` 登录。首次登录时会提示你修改密码。
|
||||
在浏览器中打开 http://localhost:1351。使用 `admin` / `admin` 登录。首次登录时会提示你修改密码。
|
||||
|
||||
## 项目结构 {#project-structure}
|
||||
|
||||
@@ -220,6 +221,17 @@ docker build -f docker/Dockerfile -t snapotter:latest .
|
||||
DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile -t snapotter:latest .
|
||||
```
|
||||
|
||||
## 发布版本域 {#release-version-domains}
|
||||
|
||||
SnapOtter 有意具有三个版本域。发布期间请勿将一个域复制到另一个域:
|
||||
|
||||
- 应用程序发布版本涵盖根清单、所有私有工作区包和 `APP_VERSION`。 Semantic-release 提供此值,`pnpm version:sync <version>` 在应用程序发布之前更新每个工作区。
|
||||
- OpenAPI `info.version` 是稳定的公开 API-主力合约。所有本地化规范都保留在 `<major>.0.0` 上以实现兼容的应用程序版本,并且仅当 API 合约转移到新的主要版本时才会更改。
|
||||
- `docker/feature-manifest.json` 保留 `imageVersion: 2.0.0` 作为不可变的旧功能包存储时代。这些 v2 存档路径不是应用程序包版本。准确的 OCR 使用运行时格式 v3 并单独记录其应用程序发布来源。
|
||||
|
||||
`tests/unit/infra/release-version-policy.test.ts` 强制执行这些边界。新版本的域或迁移必须一起更新该合同和相关的工件迁移设计。
|
||||
独立的 API 和旧包值位于 `config/release-version-policy.json` 中;应用程序版本同步绝不能隐式重写该策略文件。
|
||||
|
||||
## 环境变量 {#environment-variables}
|
||||
|
||||
完整列表请参见 [配置指南](/zh-CN/guide/configuration)。开发中的关键项:
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
description: "SnapOtter Docker 镜像标签、GPU 基准测试、版本锁定,以及对 AMD64 和 ARM64 的多平台支持。"
|
||||
i18n_output_hash: 444ed01d924d
|
||||
i18n_source_hash: fda322e78b4b
|
||||
i18n_source_hash: 566e20ca07fc
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: e1269a251ab6
|
||||
i18n_hash_version: 2
|
||||
---
|
||||
|
||||
# Docker 镜像 {#docker-image}
|
||||
@@ -93,13 +94,13 @@ services:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_USER: snapotter
|
||||
POSTGRES_PASSWORD: snapotter
|
||||
POSTGRES_PASSWORD: snapotter # 针对非本地部署更改此设置
|
||||
POSTGRES_DB: snapotter
|
||||
volumes:
|
||||
- SnapOtter-pgdata:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U snapotter"]
|
||||
test: ["CMD-SHELL", "pg_isready -U snapotter -d snapotter"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
@@ -140,9 +141,9 @@ volumes:
|
||||
| 标签 | 说明 |
|
||||
|-----|------------|
|
||||
| `latest` | 最新发布版 |
|
||||
| `1.11.0` | 精确版本 |
|
||||
| `1.11` | 1.11.x 中的最新补丁版 |
|
||||
| `1` | 1.x 中的最新次版本 |
|
||||
| `2.1.0` | 精确版本 |
|
||||
| `2.1` | 2.1.x 中的最新补丁版 |
|
||||
| `2` | 2.x 中的最新次版本 |
|
||||
|
||||
## 平台 {#platforms}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
description: "用一条 Docker 命令安装 SnapOtter。包含 Docker Compose 配置、从源码构建,以及完整的功能概览。"
|
||||
i18n_output_hash: 3da9d8045239
|
||||
i18n_source_hash: 68bf7f60b68d
|
||||
i18n_provenance: human
|
||||
i18n_source_hash: 8040133a6982
|
||||
i18n_provenance: machine
|
||||
i18n_output_hash: 00a743d88802
|
||||
i18n_hash_version: 2
|
||||
---
|
||||
|
||||
# 快速上手 {#getting-started}
|
||||
@@ -17,7 +18,7 @@ i18n_provenance: human
|
||||
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest
|
||||
```
|
||||
|
||||
这个单容器运行它所需的一切:在未设置 `DATABASE_URL` 时,它会在回环接口上启动自己的 PostgreSQL 和 Redis(嵌入式模式),并将所有数据保存在 `SnapOtter-data` 卷中。这是试用 SnapOtter 或在家庭实验室中自托管的最快方式。生产环境请运行下面的 [Docker Compose](#docker-compose) 栈,它会将 PostgreSQL 和 Redis 分别放在各自的容器中。嵌入式模式以 root 运行(默认),一旦你设置了 `DATABASE_URL` 便会自动关闭。
|
||||
这个单一容器运行它所需的一切:在没有设置 `DATABASE_URL` 的情况下,它在环回接口(嵌入模式)上启动自己的 PostgreSQL 和 Redis,并将所有数据保存在 `SnapOtter-data` 卷中。这是在家庭实验室上尝试 SnapOtter 或自托管的最快方法。对于生产,请使用[规范的 Docker Compose 堆栈](#docker-compose),它将 PostgreSQL 和 Redis 保留在自己的容器中。嵌入模式以 root 身份运行(默认),并在您设置 `DATABASE_URL` 后自动关闭。
|
||||
|
||||
要安装在 Raspberry Pi、旧笔记本电脑或小型 VPS 上?参阅[低资源环境部署](/zh-CN/guide/low-resource),那里有调优后的分步指南,以及对受限硬件该有的预期。
|
||||
|
||||
@@ -40,7 +41,7 @@ SnapOtter 默认包含匿名产品分析。要关闭它,请打开 **Settings
|
||||
docker run -d --name SnapOtter -p 1349:1349 --gpus all -v SnapOtter-data:/data snapotter/snapotter:latest
|
||||
```
|
||||
|
||||
需要 [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html)。当 CUDA 不可用时会自动回退到 CPU。目前不支持通过 VA-API、Quick Sync 或 OpenCL 使用 Intel/AMD 核显加速 AI 推理。基准测试参见 [Docker 标签](/zh-CN/guide/docker-tags)。
|
||||
需要 [NVIDIA 容器工具包](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html)。当 CUDA 不可用时自动回退到 CPU。目前,AI 推理不支持通过 VA-API、Quick Sync 或 OpenCL 进行 Intel/AMD iGPU 加速。请参阅 [Docker 标签](/zh-CN/guide/docker-tags) 了解基准。如果 AI 工具在 CPU 上运行(尽管 `--gpus all`),请参阅[验证 GPU 加速](/zh-CN/guide/deployment#verify-gpu-acceleration)。
|
||||
:::
|
||||
|
||||
::: details 也可在 GHCR 获取
|
||||
@@ -51,67 +52,33 @@ docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data ghcr.io/snap
|
||||
两个镜像仓库在每次发布时都会发布相同的镜像。
|
||||
:::
|
||||
|
||||
## Docker Compose {#docker-compose}
|
||||
## Docker 编写 {#docker-compose}
|
||||
|
||||
```yaml
|
||||
services:
|
||||
SnapOtter:
|
||||
image: snapotter/snapotter:latest # or ghcr.io/snapotter-hq/snapotter:latest
|
||||
ports:
|
||||
- "1349:1349"
|
||||
volumes:
|
||||
- SnapOtter-data:/data
|
||||
environment:
|
||||
- AUTH_ENABLED=true
|
||||
- DEFAULT_USERNAME=admin
|
||||
- DEFAULT_PASSWORD=admin
|
||||
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
|
||||
- REDIS_URL=redis://redis:6379
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
使用每个版本维护和测试的生产文件,而不是从此页面复制缩写的 Compose 示例:
|
||||
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_USER: snapotter
|
||||
POSTGRES_PASSWORD: snapotter
|
||||
POSTGRES_DB: snapotter
|
||||
volumes:
|
||||
- SnapOtter-pgdata:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U snapotter"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
```bash
|
||||
install -d -m 700 snapotter && cd snapotter
|
||||
curl --proto '=https' --tlsv1.2 -fsSLo docker-compose.yml \
|
||||
https://raw.githubusercontent.com/snapotter-hq/SnapOtter/v2.1.0/docker/docker-compose.yml
|
||||
|
||||
redis:
|
||||
image: redis:8-alpine
|
||||
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
|
||||
volumes:
|
||||
- SnapOtter-redisdata:/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
# Keep generated service credentials out of shell history and world-readable files.
|
||||
umask 077
|
||||
POSTGRES_PASSWORD="$(openssl rand -hex 32)"
|
||||
REDIS_PASSWORD="$(openssl rand -hex 32)"
|
||||
printf 'POSTGRES_PASSWORD=%s\nREDIS_PASSWORD=%s\n' \
|
||||
"$POSTGRES_PASSWORD" "$REDIS_PASSWORD" > .env
|
||||
|
||||
volumes:
|
||||
SnapOtter-data:
|
||||
SnapOtter-pgdata:
|
||||
SnapOtter-redisdata:
|
||||
docker compose -f docker-compose.yml pull
|
||||
docker compose -f docker-compose.yml up -d --no-build
|
||||
```
|
||||
|
||||
所有环境变量请参阅 [配置](/zh-CN/guide/configuration)。
|
||||
规范的 [`docker/docker-compose.yml`](https://github.com/snapotter-hq/SnapOtter/blob/v2.1.0/docker/docker-compose.yml) 包括所有四个运行时卷、运行状况检查、资源限制、持久 Redis 配置、固定数据库/缓存映像以及当前容器强化。首次登录后立即更改默认管理员密码。对于可重现的部署,请将 SnapOtter 应用程序映像固定到您验证的发布标签或摘要,而不是遵循 `latest`。
|
||||
|
||||
有关所有环境变量,请参阅[配置](/zh-CN/guide/configuration);有关机密、网络策略和备份指南,请参阅[安全和强化](/zh-CN/guide/security)。
|
||||
|
||||
## 从源码构建 {#build-from-source}
|
||||
|
||||
**前置条件:** Node.js 22+、pnpm 9+、Docker(用于 Postgres + Redis)、Python 3.10+(用于 AI 功能)、Git。
|
||||
**前置条件:** Node.js 22.22+、pnpm 9+、Docker(用于 Postgres + Redis)、Python 3.11+(用于 AI 功能)、Git。
|
||||
|
||||
```bash
|
||||
git clone https://github.com/snapotter-hq/SnapOtter.git
|
||||
@@ -121,7 +88,7 @@ pnpm install
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
- 前端:[http://localhost:1349](http://localhost:1349)
|
||||
- 前端:[http://localhost:1351](http://localhost:1351)
|
||||
- 后端:[http://localhost:13490](http://localhost:13490)
|
||||
|
||||
## 你能做什么 {#what-you-can-do}
|
||||
@@ -130,11 +97,11 @@ pnpm dev
|
||||
|
||||
| 模态 | 数量 | 示例工具 |
|
||||
|----------|-------|---------------|
|
||||
| **图像** | 105 | 缩放、裁剪、压缩、转换、背景移除、放大、OCR、水印、拼贴、上色、GIF 工具、格式预设 |
|
||||
| **图像** | 107 | 缩放、裁剪、压缩、转换、背景移除、放大、OCR、水印、拼贴、上色、GIF 工具、格式预设 |
|
||||
| **视频** | 57 | 剪辑、裁剪、压缩、转换、合并、提取音频、自动字幕、视频转 GIF、缩放、防抖、格式预设 |
|
||||
| **音频** | 27 | 剪辑、合并、转换、归一化、降噪、转录、变调、淡入淡出、铃声制作、格式预设 |
|
||||
| **PDF / 文档** | 42 | 合并、拆分、压缩、OCR、水印、涂黑、Word 转 PDF、Excel 转 PDF、旋转、加密、修复 |
|
||||
| **文件** | 10 | CSV 转 JSON、JSON 转 XML、合并 CSV、拆分 CSV、创建 ZIP、解压 ZIP、图表制作、YAML/JSON |
|
||||
| **PDF / 文档** | 29 | 合并、拆分、压缩、OCR、水印、涂黑、Word 转 PDF、Excel 转 PDF、旋转、加密、修复 |
|
||||
| **文件** | 23 | CSV 转 JSON、JSON 转 XML、合并 CSV、拆分 CSV、创建 ZIP、解压 ZIP、图表制作、YAML/JSON |
|
||||
|
||||
### 流水线 {#pipelines}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
i18n_source_hash: f5de74aee1b9
|
||||
i18n_source_hash: 521c03a6416c
|
||||
i18n_provenance: machine
|
||||
i18n_output_hash: 6c82cfe39a38
|
||||
i18n_output_hash: 339f642bc99f
|
||||
i18n_hash_version: 2
|
||||
---
|
||||
# 低资源环境部署 {#low-resource-setups}
|
||||
|
||||
@@ -59,7 +60,7 @@ services:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
- POSTGRES_USER=snapotter
|
||||
- POSTGRES_PASSWORD=snapotter
|
||||
- POSTGRES_PASSWORD=snapotter # 针对非本地部署更改此设置
|
||||
- POSTGRES_DB=snapotter
|
||||
volumes:
|
||||
- ./postgres-data:/var/lib/postgresql/data
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
description: "设置 SCIM 2.0 预配以将用户和组从你的身份提供商同步到 SnapOtter。涵盖 Okta、Azure AD / Entra ID 以及自定义集成。"
|
||||
i18n_source_hash: bbd50119ec12
|
||||
i18n_source_hash: 06ee702b386e
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: 70121241a9af
|
||||
i18n_output_hash: d318d3799726
|
||||
i18n_hash_version: 2
|
||||
---
|
||||
|
||||
# SCIM 预配 {#scim-provisioning}
|
||||
@@ -17,7 +18,7 @@ SCIM 预配需要带有 `scim` 功能的 **enterprise** 许可证。它在 team
|
||||
|
||||
- 一个可通过公网 URL 访问的正在运行的 SnapOtter 实例
|
||||
- 一个带有 `scim` 功能的 enterprise 许可证密钥
|
||||
- SnapOtter 的管理员访问权限(生成或吊销 SCIM 令牌需要 `users:manage` 权限)
|
||||
- 内置 SnapOtter `admin` 帐户及其完整的有效权限集。委托的自定义角色或缺少任何管理权限的管理 API 密钥无法生成或撤销全局 SCIM 令牌。
|
||||
- 你的身份提供商的预配设置的管理员访问权限
|
||||
|
||||
## 快速开始 {#quick-start}
|
||||
@@ -34,7 +35,7 @@ curl -X POST https://photos.example.com/api/v1/enterprise/scim/token \
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "a1b2c3d4e5f6...",
|
||||
"token": "so_scim_v2_a1b2c3d4e5f6...",
|
||||
"message": "Save this token - it cannot be retrieved again"
|
||||
}
|
||||
```
|
||||
@@ -49,15 +50,19 @@ SCIM 端点使用专用的 Bearer 令牌,与用户会话和 API 密钥分开
|
||||
|
||||
### 生成令牌 {#generating-a-token}
|
||||
|
||||
`POST /api/v1/enterprise/scim/token` 会生成一个新的 SCIM 令牌。此端点需要一个具有 `users:manage` 权限的有效会话。
|
||||
`POST /api/v1/enterprise/scim/token` 生成新的 SCIM 代币。由于令牌可以跨实例配置和更改用户,因此此端点需要具有完整有效管理权限集的内置 `admin` 角色。将 `users:manage` 保留在自定义角色中是不够的。
|
||||
|
||||
令牌以明文形式仅返回一次。SnapOtter 只存储 scrypt 哈希值。如果你丢失了令牌,请吊销它并生成一个新的。
|
||||
|
||||
同一时间只有一个 SCIM 令牌处于活动状态。生成新令牌会替换先前的令牌。
|
||||
|
||||
::: warning 升级后重新发行令牌
|
||||
旧版未版本控制的 SCIM 令牌将被拒绝。升级到颁发 `so_scim_v2_...` 令牌的版本后,请生成新令牌并更新您的身份提供商,然后再恢复配置。
|
||||
:::
|
||||
|
||||
### 吊销令牌 {#revoking-a-token}
|
||||
|
||||
`DELETE /api/v1/enterprise/scim/token` 会吊销当前的 SCIM 令牌。此端点同样需要 `users:manage`。
|
||||
`DELETE /api/v1/enterprise/scim/token` 撤销当前的 SCIM 令牌。它具有与令牌生成相同的完整内置管理要求。
|
||||
|
||||
### 速率限制 {#rate-limiting}
|
||||
|
||||
@@ -279,7 +284,7 @@ SCIM 请求未包含 `Authorization: Bearer <token>` 标头。请检查你的 Id
|
||||
|
||||
### 401 "Invalid token" {#_401-invalid-token}
|
||||
|
||||
令牌与存储的哈希值不匹配。如果令牌被吊销并重新生成,就会发生这种情况。请在你的 IdP 的预配设置中更新令牌。
|
||||
令牌格式错误、使用已停用的未版本化格式或与存储的哈希不匹配。生成当前的 `so_scim_v2_...` 令牌并在 IdP 的配置设置中更新该令牌。
|
||||
|
||||
### 401 "SCIM not configured" {#_401-scim-not-configured}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
description: "SnapOtter 的安全加固指南。涵盖容器安全、网络隔离、Docker 密钥、Kubernetes 部署和合规产物。"
|
||||
i18n_source_hash: 986f7658430c
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: 05d4a7e4d409
|
||||
i18n_source_hash: 9ff337fa0417
|
||||
i18n_provenance: machine
|
||||
i18n_output_hash: 5cdae11497f9
|
||||
i18n_hash_version: 2
|
||||
---
|
||||
|
||||
# 安全与加固 {#security-hardening}
|
||||
@@ -11,133 +12,42 @@ SnapOtter 完全在你自己的基础设施上处理文件。它默认发送匿
|
||||
|
||||
容器以专用的非 root 用户(`snapotter`)运行,除最低必需集之外的所有 Linux 权能都被丢弃。完整的漏洞披露政策和安全架构,请参阅 GitHub 上的 [SECURITY.md](https://github.com/snapotter-hq/SnapOtter/blob/main/SECURITY.md)。
|
||||
|
||||
## 容器加固 {#container-hardening}
|
||||
## 容器硬化 {#container-hardening}
|
||||
|
||||
[默认 docker-compose.yml](https://github.com/snapotter-hq/SnapOtter/blob/main/docker/docker-compose.yml) 包含生产环境的安全加固。以下是对每个选项及其重要性的逐项说明:
|
||||
规范的 [CPU](https://github.com/snapotter-hq/SnapOtter/blob/main/docker/docker-compose.yml) 和 [GPU](https://github.com/snapotter-hq/SnapOtter/blob/main/docker/docker-compose-gpu.yml) Compose 文件是事实来源。不要将缩写示例复制到生产中;从您验证的发布标签部署文件。
|
||||
|
||||
```yaml
|
||||
services:
|
||||
SnapOtter:
|
||||
image: snapotter/snapotter:latest
|
||||
ports:
|
||||
# Bind to localhost only for internet-facing deployments:
|
||||
- "127.0.0.1:1349:1349"
|
||||
volumes:
|
||||
- SnapOtter-data:/data
|
||||
- SnapOtter-workspace:/tmp/workspace
|
||||
environment:
|
||||
- AUTH_ENABLED=true
|
||||
- DEFAULT_PASSWORD=change-me-immediately
|
||||
- RATE_LIMIT_PER_MIN=1000
|
||||
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
|
||||
- REDIS_URL=redis://redis:6379
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
两个堆栈都应用以下控制:
|
||||
|
||||
# --- Resource limits ---
|
||||
mem_limit: 6g # Prevents runaway memory from crashing the host
|
||||
memswap_limit: 6g # No swap - fail fast instead of degrading the host
|
||||
cpus: 4 # Cap CPU usage to 4 cores
|
||||
pids_limit: 512 # Prevents fork bombs
|
||||
- 内存、交换、CPU 和 PID 限制包含失控的本机处理。
|
||||
- 每个服务都会放弃所有 Linux 功能。该应用程序仅添加回 `CHOWN, SETUID, SETGID, DAC_OVERRIDE, FOWNER, KILL` 来实现卷所有权、单向 `gosu` 身份删除以及优雅的信号转发。 PostgreSQL 和 Redis 仅接收其官方入口点所需的子集。
|
||||
- `security_opt: [no-new-privileges:true]` 防止应用程序、PostgreSQL 和 Redis 容器中的进程获得额外权限。这仍然与 `gosu` 兼容:入口点以 root 身份开始,准备卷,并且仅下降到专用的 `snapotter` 用户。
|
||||
- PostgreSQL 和 Redis 图像输入由摘要固定。应用程序同样应该固定到经过验证的发布标签或摘要,而不是 `latest`。
|
||||
- 健康检查、有界 JSON 日志轮换、持久的 Redis AOF 和重启策略在规范文件中集中定义。
|
||||
|
||||
# --- Capability restrictions ---
|
||||
cap_drop:
|
||||
- ALL # Drop ALL Linux capabilities first
|
||||
cap_add:
|
||||
- CHOWN # Needed for volume permission setup
|
||||
- SETUID # Needed for gosu privilege drop (root -> snapotter)
|
||||
- SETGID # Needed for gosu privilege drop
|
||||
- DAC_OVERRIDE # Needed for volume permission setup
|
||||
- FOWNER # Needed for volume permission setup
|
||||
对于面向互联网的部署,将端口 1349 绑定到环回并在维护的反向代理处终止 TLS。生成唯一的 PostgreSQL 和 Redis 凭据,将机密存储在受保护的文件或机密管理器中,并立即更改初始管理员密码。
|
||||
|
||||
# --- Logging ---
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "50m" # Rotate logs at 50 MB
|
||||
max-file: "5" # Keep 5 rotated log files
|
||||
### 为什么 `read_only` 没有设置 {#why-read-only-is-not-set}
|
||||
|
||||
# --- Health check ---
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "--max-time", "5", "http://localhost:1349/api/v1/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
start_period: 60s
|
||||
retries: 3
|
||||
未设置 `read_only: true`,因为 PUID/PGID 重新映射在启动时写入 `/etc/passwd` 和 `/etc/group`。如果您使用 Docker 的 `--user` 标志或 Kubernetes `runAsUser` 而不是 PUID/PGID,则可以安全地启用只读根文件系统。
|
||||
|
||||
shm_size: "2gb" # Required for Python ML shared memory
|
||||
restart: unless-stopped
|
||||
## 网络隔离{#network-isolation}
|
||||
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_USER: snapotter
|
||||
POSTGRES_PASSWORD: snapotter
|
||||
POSTGRES_DB: snapotter
|
||||
volumes:
|
||||
- SnapOtter-pgdata:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U snapotter"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
start_period: 15s
|
||||
文件处理是本地的,但默认安装**不是无出口系统**。启用遥测功能时,匿名产品分析使用 PostHog,崩溃报告使用 Sentry。设置 `SNAPOTTER_TELEMETRY=0`(或在“设置”>“系统”>“隐私”下禁用分析)以关闭两者。 SnapOtter 绝不会在这些事件中包含上传的文件、文件名、OCR 输出、文档文本或其他文件内容。
|
||||
|
||||
redis:
|
||||
image: redis:8-alpine
|
||||
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
|
||||
volumes:
|
||||
- SnapOtter-redisdata:/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
start_period: 10s
|
||||
|
||||
volumes:
|
||||
SnapOtter-data:
|
||||
SnapOtter-workspace:
|
||||
SnapOtter-pgdata:
|
||||
SnapOtter-redisdata:
|
||||
```
|
||||
|
||||
### 为何不设置 `no-new-privileges` {#why-no-new-privileges-is-not-set}
|
||||
|
||||
`security_opt: [no-new-privileges:true]` 被有意省略。入口点以 root 启动以修复卷的所有权,然后通过 [gosu](https://github.com/tianon/gosu) 降权到 `snapotter` 用户,而这需要 setuid。一旦降权完成,进程就以 `snapotter` 运行,除上面列出的五项外的所有权能都被移除。
|
||||
|
||||
如果你使用 Kubernetes 或 Docker 的 `--user` 标志直接以非 root 运行(绕过 gosu),那么启用 `no-new-privileges` 是安全的。
|
||||
|
||||
### 为何不设置 `read_only` {#why-read-only-is-not-set}
|
||||
|
||||
没有设置 `read_only: true`,因为 PUID/PGID 重映射会在启动时写入 `/etc/passwd` 和 `/etc/group`。如果你使用 Docker 的 `--user` 标志或 Kubernetes 的 `runAsUser` 而非 PUID/PGID,你可以安全地启用只读根文件系统。
|
||||
|
||||
## 网络隔离 {#network-isolation}
|
||||
|
||||
在正常运行期间,容器**不发起任何出站网络连接**。所有文件处理都使用捆绑的库在本地进行。
|
||||
|
||||
```
|
||||
Browser --> Reverse Proxy (TLS) --> SnapOtter container --> (nothing)
|
||||
```
|
||||
|
||||
唯一的例外是 **AI 模型下载**:当用户通过 UI 安装 AI 功能包时,容器会从 Hugging Face 下载预构建的包归档,外加来自 GitHub Releases、Google Storage 和 PyPI 的少量单独模型文件。这些下载每个包只发生一次,并存储在 `/data` 卷中。
|
||||
其他出站流量是功能驱动的:AI 捆绑包/模型安装下载签名发布输入; URL导入获取用户请求的公共URL;并显式配置的 OIDC、SAML、OpenTelemetry、webhooks、S3 兼容存储或类似集成会联系管理员选择的目标。运行时模型下载默认处于禁用状态。仅在明确选择启用自动回退下载时设置 `SNAPOTTER_ALLOW_MODEL_DOWNLOAD=1`。[离线捆绑导入](/zh-CN/guide/deployment) 可以在没有运行时模型出口的情况下提供 AI 功能。
|
||||
|
||||
**防火墙建议:**
|
||||
|
||||
| 场景 | 出站规则 |
|
||||
|设想|出站规则|
|
||||
|---|---|
|
||||
| 隔离网络(无 AI) | 阻止容器的所有出站流量 |
|
||||
| 需要 AI 包 | 安装期间允许对 `huggingface.co`、`*.xethub.hf.co`、`cdn-lfs.huggingface.co`、`github.com`、`objects.githubusercontent.com`、`storage.googleapis.com`、`pypi.org`、`files.pythonhosted.org` 的 HTTPS,之后阻止 |
|
||||
| AI 安装完成后 | 阻止所有出站流量 - 模型已在本地缓存 |
|
||||
|气隙|设置`SNAPOTTER_TELEMETRY=0`和`SNAPOTTER_ALLOW_MODEL_DOWNLOAD=0`,使用离线AI捆绑导入,禁用URL导入和外部集成,然后阻止出口|
|
||||
|默认遥测|允许浏览器/网络日志列出的 PostHog 和 Sentry 端点;如果策略不允许,则禁用遥测|
|
||||
|需要 AI 捆绑包|安装过程中,允许HTTPS到`huggingface.co, *.xethub.hf.co, cdn-lfs.huggingface.co, github.com, objects.githubusercontent.com, storage.googleapis.com, pypi.org, files.pythonhosted.org`;然后阻止这些主机|
|
||||
|外部集成|仅允许管理员准确配置的 OIDC/SAML/OTLP/webhook/对象存储目标|
|
||||
|
||||
包归档由 Hugging Face 的 Xet 存储提供,它通过 `*.xethub.hf.co` 端点并行传输,正是它让数 GB 的包下载变得快速。如果你的防火墙允许 `huggingface.co` 但阻止 `*.xethub.hf.co`,安装仍会成功,但会回退到较慢的单流下载,所以请将 Xet 主机加入允许列表以保持在快速路径上。完全离线的安装可以跳过这一切,改用 [离线包导入](/zh-CN/guide/deployment)。
|
||||
捆绑包档案由 Hugging Face 的 Xet 存储提供,该存储通过 `*.xethub.hf.co` 端点并行传输,这使得多 GB 捆绑包下载速度更快。如果您的防火墙允许 `huggingface.co` 但阻止 `*.xethub.hf.co`,安装仍然会成功,但会回退到较慢的单流下载,因此将 Xet 主机列入白名单以保持快速路径。完全离线安装可以跳过所有这些并使用[离线捆绑导入](/zh-CN/guide/deployment)。
|
||||
|
||||
关于反向代理配置(Nginx、Traefik、Caddy、Cloudflare Tunnels),请参阅 [部署指南](/zh-CN/guide/deployment#reverse-proxy)。
|
||||
有关反向代理配置(Nginx、Traefik、Caddy、Cloudflare Tunnels),请参阅[部署指南](/zh-CN/guide/deployment#reverse-proxy)。
|
||||
|
||||
## Docker 密钥 {#docker-secrets}
|
||||
|
||||
@@ -257,83 +167,101 @@ spec:
|
||||
|
||||
## 备份与恢复 {#backup-and-recovery}
|
||||
|
||||
持久化状态分布在两个卷中:
|
||||
生产 Compose 堆栈定义了四个卷。在进行协调备份之前停止入口并让活动作业完成,以便 PostgreSQL、Redis 和文件状态描述相同的时间点。
|
||||
|
||||
| 卷 | 内容 | 是否关键? |
|
||||
|体积|内容|康复治疗|
|
||||
|---|---|---|
|
||||
| `SnapOtter-pgdata` | PostgreSQL 数据库(用户、设置、流水线、任务、审计日志) | 是 |
|
||||
| `/data`(应用卷) | 用户上传的文件、AI 模型、Python venv | 部分(见下文) |
|
||||
|`SnapOtter-pgdata`|PostgreSQL 用户、设置、管道、作业、文件元数据和审核日志|批判的;使用快速故障逻辑转储进行可移植恢复|
|
||||
|`SnapOtter-data`|保存的库对象、日志和 AI 状态 (`/data/files, /data/logs, /data/ai, /data/ai/venv`)|备份整个卷;为了节省空间,故意省略所有 AI 状态并重新安装其捆绑包|
|
||||
|`SnapOtter-redisdata`|Redis AOF 用于持久的 BullMQ 队列状态|暂停应用程序并强制`SAVE`后备份;需要准确地恢复排队的工作|
|
||||
|`SnapOtter-workspace`|临时对象存储键 (`/tmp/workspace/uploads, /tmp/workspace/outputs`)|所有作业清空或取消后不要进行备份;当工作处于活动状态时切勿丢弃它|
|
||||
|
||||
在 `/data` 卷内:
|
||||
Compose 通常在卷名称前加上项目名称作为前缀。从已安装的容器中解析真实的源卷,而不是假设显示名称(例如 `SnapOtter-data`)是 Docker 卷名称。
|
||||
|
||||
| 路径 | 内容 | 是否关键? |
|
||||
|---|---|---|
|
||||
| `/data/uploads/`、`/data/outputs/` | 用户文件和处理结果 | 是 |
|
||||
| `/data/ai/` | 下载的 AI 模型文件 | 否(可重新下载) |
|
||||
| `/data/venv/` | Python 虚拟环境 | 否(启动时重建) |
|
||||
### 数据库备份{#database-backup}
|
||||
|
||||
### 数据库备份 {#database-backup}
|
||||
|
||||
在栈运行期间使用 `pg_dump` 备份数据库:
|
||||
使用 PostgreSQL 的自定义存档格式并在将备份视为完整之前验证存档:
|
||||
|
||||
```bash
|
||||
# Dump the database
|
||||
docker exec SnapOtter-postgres pg_dump -U snapotter snapotter > backup.sql
|
||||
docker exec SnapOtter-postgres \
|
||||
pg_dump --format=custom --no-owner -U snapotter snapotter > snapotter.dump
|
||||
test -s snapotter.dump
|
||||
docker exec -i SnapOtter-postgres pg_restore --list < snapotter.dump >/dev/null
|
||||
|
||||
# Restore into a fresh database
|
||||
cat backup.sql | docker exec -i SnapOtter-postgres psql -U snapotter snapotter
|
||||
# Restore only into a fresh/disposable target first; any SQL error fails the command.
|
||||
docker exec -i SnapOtter-postgres \
|
||||
pg_restore --exit-on-error --clean --if-exists --no-owner \
|
||||
-U snapotter -d snapotter < snapotter.dump
|
||||
```
|
||||
|
||||
或者,停止栈并对 `SnapOtter-pgdata` 卷做快照:
|
||||
通过将每个备份恢复到隔离堆栈、检查数据库记录和文件校验和以及启动应用程序来测试每个备份。存储库的 `tests/qa/backup-restore-drill.sh` 会针对显式 `QA_IMAGE` 自动执行该发布门。
|
||||
|
||||
如果您的平台采用崩溃一致的卷快照,请首先停止整个堆栈,并将所有关键卷快照为一组。来自正在运行的容器的原始 PostgreSQL 数据目录副本不是受支持的逻辑备份。
|
||||
|
||||
### 文件和队列备份 {#file-and-queue-backup}
|
||||
|
||||
在捕获文件和队列卷之前暂停应用程序。使用 `docker inspect` 解析实际卷名称,强制 Redis 保留其当前状态,并在保留所有权和权限的情况下进行归档:
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
docker run --rm -v SnapOtter-pgdata:/data -v $(pwd)/backup:/backup \
|
||||
alpine tar czf /backup/snapotter-pgdata.tar.gz -C /data .
|
||||
docker stop SnapOtter
|
||||
docker exec SnapOtter-redis redis-cli -a "$REDIS_PASSWORD" --no-auth-warning SAVE
|
||||
docker stop SnapOtter-redis
|
||||
|
||||
DATA_VOLUME="$(docker inspect SnapOtter --format '{{range .Mounts}}{{if eq .Destination "/data"}}{{.Name}}{{end}}{{end}}')"
|
||||
REDIS_VOLUME="$(docker inspect SnapOtter-redis --format '{{range .Mounts}}{{if eq .Destination "/data"}}{{.Name}}{{end}}{{end}}')"
|
||||
|
||||
install -d -m 700 backup
|
||||
docker run --rm -v "$DATA_VOLUME:/source:ro" -v "$PWD/backup:/backup" \
|
||||
alpine:3.22@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce tar czf /backup/snapotter-data.tar.gz -C /source .
|
||||
docker run --rm -v "$REDIS_VOLUME:/source:ro" -v "$PWD/backup:/backup" \
|
||||
alpine:3.22@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce tar czf /backup/snapotter-redis.tar.gz -C /source .
|
||||
sha256sum backup/snapotter-*.tar.gz > backup/SHA256SUMS
|
||||
```
|
||||
|
||||
### 用户文件备份 {#user-files-backup}
|
||||
应用前重启Redis。如果您有意排除 `/data/ai`,请删除整个 AI 子树,而不是保留不带模型或虚拟环境的 `installed.json` 记录。保持备份文件加密、访问受控,并与运行 SnapOtter 的主机分开。
|
||||
|
||||
```bash
|
||||
# Snapshot the app data volume (excluding re-downloadable AI models)
|
||||
docker run --rm -v SnapOtter-data:/data -v $(pwd)/backup:/backup \
|
||||
alpine tar czf /backup/snapotter-files.tar.gz \
|
||||
--exclude='ai' --exclude='venv' -C /data .
|
||||
```
|
||||
## 合规工件 {#compliance-artifacts}
|
||||
|
||||
AI 模型跨所有包总计约 24 GB。由于它们可重新下载,请从备份中排除 `/data/ai/` 和 `/data/venv/` 以节省空间。只有数据库和用户文件是关键的。
|
||||
每个 SnapOtter 版本都包含以下安全工件:
|
||||
|
||||
## 合规产物 {#compliance-artifacts}
|
||||
|
||||
每个 SnapOtter 发布都包含以下安全产物:
|
||||
|
||||
| 产物 | 格式 | 获取位置 |
|
||||
| 人工制品 | 格式 | 在哪里可以找到它 |
|
||||
|---|---|---|
|
||||
| SBOM(CycloneDX) | JSON | [GitHub Release](https://github.com/snapotter-hq/SnapOtter/releases) 资产:`snapotter-v{version}-sbom.cdx.json` |
|
||||
| SBOM(SPDX) | JSON | [GitHub Release](https://github.com/snapotter-hq/SnapOtter/releases) 资产:`snapotter-v{version}-sbom.spdx.json` |
|
||||
| 漏洞扫描 | Trivy JSON | [GitHub Release](https://github.com/snapotter-hq/SnapOtter/releases) 资产:`snapotter-v{version}-trivy.json` |
|
||||
| 漏洞扫描 | SARIF | [GitHub Security](https://github.com/snapotter-hq/SnapOtter/security) 选项卡 |
|
||||
| 静态分析 | CodeQL(JS/TS + Python) | [GitHub Security](https://github.com/snapotter-hq/SnapOtter/security) 选项卡,每周 + 每 PR 运行 |
|
||||
| 依赖审查 | GitHub 原生 | 每 PR 检查,在新增高危项时失败 |
|
||||
| Python 依赖审计 | pip-audit | 每次推送的 CI 运行日志 |
|
||||
| 安全政策 | Markdown | 仓库中的 [SECURITY.md](https://github.com/snapotter-hq/SnapOtter/blob/main/SECURITY.md) |
|
||||
| 依赖更新 | Dependabot | 针对 npm、pip、Docker、Actions 的自动化每周 PR |
|
||||
| 释放主体绑定 | 规范 JSON + GitHub 证明 | [GitHub发布](https://github.com/snapotter-hq/SnapOtter/releases)资产:`snapotter-v{version}-release-subjects.json` |
|
||||
| 归档 SBOM | CycloneDX 和 SPDX JSON | 释放资产:`snapotter-v{version}-archive-linux-{arch}-sbom.{cdx,spdx}.json` |
|
||||
| 图片 SBOM | CycloneDX 和 SPDX JSON | 释放资产:`snapotter-v{version}-image-linux-{arch}-sbom.{cdx,spdx}.json` |
|
||||
| 漏洞扫描 | Trivy JSON | 发布具有匹配 `archive-linux-{arch}` 或 `image-linux-{arch}` 前缀的资产 |
|
||||
| 漏洞扫描 | SARIF | [GitHub 安全](https://github.com/snapotter-hq/SnapOtter/security) 选项卡 |
|
||||
| 静态分析 | CodeQL (JS/TS + Python) | [GitHub 安全](https://github.com/snapotter-hq/SnapOtter/security) 选项卡,每周运行 + 每个 PR |
|
||||
| 依赖性审查 | GitHub 本机 | 按 PR 检查,高严重性添加失败 |
|
||||
| Python依赖审计 | pip-audit | CI 在每次推送时运行日志 |
|
||||
| 安全政策 | Markdown | 存储库中的 [SECURITY.md](https://github.com/snapotter-hq/SnapOtter/blob/main/SECURITY.md) |
|
||||
| 依赖项更新 | Dependabot | npm、pip、Docker、Actions 的自动每周 PR |
|
||||
|
||||
**运行你自己的扫描:**
|
||||
**运行您自己的扫描:**
|
||||
|
||||
从发布中下载 SBOM,并用你偏好的工具扫描它:
|
||||
下载发布主题清单并验证它是否已由发布工作流程证明:
|
||||
|
||||
```bash
|
||||
gh attestation verify snapotter-v2.1.0-release-subjects.json \
|
||||
--repo snapotter-hq/SnapOtter \
|
||||
--signer-workflow snapotter-hq/SnapOtter/.github/workflows/release.yml
|
||||
```
|
||||
|
||||
清单中分别记录了 `releaseTag`、`releaseCommit` 和 `workflowTriggerCommit`。验证 `releaseCommit` 是否是从不可变标记中剥离的提交,然后验证存档、映像、SBOM 的 SHA-256 摘要,或根据 `subjects` 中的条目进行扫描。这种区别是有意为之的:签出新创建的发布提交不会更改工作流的 OIDC 凭证中的提交标识。
|
||||
|
||||
您还可以直接扫描下载的 SBOM 或图像:
|
||||
|
||||
```bash
|
||||
# Scan with Grype using the CycloneDX SBOM
|
||||
grype sbom:snapotter-v1.17.2-sbom.cdx.json
|
||||
grype sbom:snapotter-v2.1.0-image-linux-amd64-sbom.cdx.json
|
||||
|
||||
# Scan with Trivy using the SPDX SBOM
|
||||
trivy sbom snapotter-v1.17.2-sbom.spdx.json
|
||||
trivy sbom snapotter-v2.1.0-image-linux-amd64-sbom.spdx.json
|
||||
|
||||
# Scan the Docker image directly
|
||||
trivy image snapotter/snapotter:1.17.2
|
||||
trivy image snapotter/snapotter:2.1.0
|
||||
```
|
||||
|
||||
::: info
|
||||
SBOM 和漏洞扫描反映的是该次发布所发布的确切镜像。部署后安装的 AI 模型包不包含在 SBOM 中,因为它们是在运行时下载的。
|
||||
::: info
|
||||
图像 SBOMs 和扫描反映了针对该版本发布的具体架构特定图像。存档 SBOMs 和扫描分别描述预建存档。部署后安装的 AI 模型包不包含在这些 SBOMs 中,因为它们是在运行时下载的。
|
||||
:::
|
||||
|
||||
@@ -11,7 +11,7 @@ SnapOtter 跨五种模态处理文件:图像、视频、音频、PDF 和文件
|
||||
|
||||
## 图像格式 {#image-formats}
|
||||
|
||||
SnapOtter 支持 55+ 种图像输入格式和 13 种输出格式。
|
||||
SnapOtter 支持 55+ 种图像输入格式和 17 种输出格式。
|
||||
|
||||
## 输入格式 {#input-formats}
|
||||
|
||||
@@ -121,6 +121,10 @@ SnapOtter 支持 55+ 种图像输入格式和 13 种输出格式。
|
||||
| ICO | ImageMagick CLI | 无损 | 转换工具 |
|
||||
| JP2 | opj_compress CLI | 压缩比 | 转换工具 |
|
||||
| QOI | 内联编解码器 | 无损 | 转换工具 |
|
||||
| PSD | ImageMagick CLI | 无损 | 转换工具 |
|
||||
| PPM | ImageMagick CLI | 无损 | 转换工具 |
|
||||
| EPS | ImageMagick CLI | 无损 | 转换工具 |
|
||||
| TGA | ImageMagick CLI | 无损 | 转换工具 |
|
||||
|
||||
## 视频格式 {#video-formats}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
description: "在 SnapOtter 中管理用户、内置角色与自定义角色、权限、API 密钥、团队、会话以及审计日志。"
|
||||
i18n_source_hash: 5e28af686c96
|
||||
i18n_source_hash: bea8955f3aff
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: db9aecb6141c
|
||||
i18n_output_hash: 586307e9d95e
|
||||
i18n_hash_version: 2
|
||||
---
|
||||
|
||||
# 用户、角色与权限 {#users-roles-permissions}
|
||||
@@ -82,12 +83,12 @@ SnapOtter 包含三个内置角色。它们无法被修改或删除。
|
||||
| `pipelines:all` | 查看和管理所有用户的流水线 |
|
||||
| `settings:read` | 查看实例设置 |
|
||||
| `settings:write` | 修改实例设置 |
|
||||
| `users:manage` | 创建、更新和删除用户账户 |
|
||||
| `users:manage` | 在参与者的权限范围内创建和管理用户帐户 |
|
||||
| `teams:manage` | 创建、更新和删除团队 |
|
||||
| `features:manage` | 安装和管理 AI 功能包 |
|
||||
| `system:health` | 访问健康与就绪端点 |
|
||||
| `audit:read` | 查看审计日志并列出角色 |
|
||||
| `compliance:manage` | 管理 GDPR 生命周期和合规功能 |
|
||||
| `compliance:manage` | 管理 GDPR 生命周期和合规性功能;破坏性用户操作仍受权限限制 |
|
||||
| `webhooks:manage` | 配置出站 webhook |
|
||||
| `security:manage` | 管理安全设置(IP 允许列表、SSO 强制) |
|
||||
|
||||
@@ -110,15 +111,17 @@ curl -X POST http://localhost:1349/api/v1/roles \
|
||||
|
||||
角色名称必须为 2 到 30 个字符,小写字母数字,可包含连字符和下划线。
|
||||
|
||||
### 管理员保留权限 {#admin-reserved-permissions}
|
||||
### 委派管理边界 {#delegated-administration-boundaries}
|
||||
|
||||
有三项权限保留给内置角色,无法分配给自定义角色:
|
||||
所有 17 个权限都可以通过自定义角色委派,但管理权限并不会使该角色等同于内置 `admin` 角色。 `users:manage`授权的用户突变、`compliance:manage`授权的破坏性操作以及`security:manage`授权的自定义角色管理均受参与者当前权限的约束:
|
||||
|
||||
- `compliance:manage`
|
||||
- `webhooks:manage`
|
||||
- `security:manage`
|
||||
- 内置角色遵循`admin` > `editor` > `user`;自定义角色位于内置角色下方。
|
||||
- 目标的权限必须包含在参与者的**有效**权限中。因此,限定范围的 API 密钥无法行使其范围中省略的权限。
|
||||
- 目标角色的工具访问权限必须包含在参与者自己的工具访问权限中。
|
||||
- 当角色记录为 `disabled:<original-role>` 时,已禁用的帐户将根据其原始角色进行检查。
|
||||
- 删除自定义角色还需要分配内置`user`后备的权限;禁用的成员仍然禁用为 `disabled:user`。
|
||||
|
||||
角色 API 会拒绝任何包含这些权限的请求。只有内置的 `admin` 角色才能访问它们。
|
||||
全局凭证和配置更加严格:颁发或撤销 SCIM 令牌以及导入实例配置需要具有完整有效管理权限的内置 `admin` 角色。
|
||||
|
||||
### 工具级权限 {#tool-level-permissions}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user