Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 133 additions & 16 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,19 @@ jobs:
org.opencontainers.image.description=Self-hosted cloud storage system built with Rust
org.opencontainers.image.vendor=AsterCommunity

# Build full first so the slim target reuses the exact same feature/architecture binary.
- name: Build and push full Docker image
id: build-push-full
# Push slim first and full last so registry UIs do not treat the most
# recently pushed slim artifact as the default image.
- name: Build and push slim Docker image
id: build-push-slim
uses: docker/build-push-action@v7
with:
context: .
target: runtime-full
target: runtime-slim
platforms: ${{ matrix.platform }}
push: true
tags: |
${{ env.REGISTRY_IMAGE_GHCR }}:${{ env.TEMP_IMAGE_TAG }}-${{ matrix.variant }}-${{ matrix.arch }}
${{ env.REGISTRY_IMAGE_DOCKERHUB }}:${{ env.TEMP_IMAGE_TAG }}-${{ matrix.variant }}-${{ matrix.arch }}
${{ env.REGISTRY_IMAGE_GHCR }}:${{ env.TEMP_IMAGE_TAG }}-${{ matrix.variant }}-slim-${{ matrix.arch }}
${{ env.REGISTRY_IMAGE_DOCKERHUB }}:${{ env.TEMP_IMAGE_TAG }}-${{ matrix.variant }}-slim-${{ matrix.arch }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: |
type=gha,scope=docker-image-${{ matrix.variant }}-${{ matrix.arch }}
Expand All @@ -103,17 +104,17 @@ jobs:
provenance: true
sbom: true

- name: Build and push slim Docker image
id: build-push-slim
- name: Build and push full Docker image
id: build-push-full
uses: docker/build-push-action@v7
with:
context: .
target: runtime-slim
target: runtime-full
platforms: ${{ matrix.platform }}
push: true
tags: |
${{ env.REGISTRY_IMAGE_GHCR }}:${{ env.TEMP_IMAGE_TAG }}-${{ matrix.variant }}-slim-${{ matrix.arch }}
${{ env.REGISTRY_IMAGE_DOCKERHUB }}:${{ env.TEMP_IMAGE_TAG }}-${{ matrix.variant }}-slim-${{ matrix.arch }}
${{ env.REGISTRY_IMAGE_GHCR }}:${{ env.TEMP_IMAGE_TAG }}-${{ matrix.variant }}-${{ matrix.arch }}
${{ env.REGISTRY_IMAGE_DOCKERHUB }}:${{ env.TEMP_IMAGE_TAG }}-${{ matrix.variant }}-${{ matrix.arch }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: |
type=gha,scope=docker-image-${{ matrix.variant }}-${{ matrix.arch }}
Expand All @@ -124,7 +125,7 @@ jobs:
provenance: true
sbom: true

publish-manifest:
publish-manifest-slim:
name: Publish ${{ matrix.variant }} multi-arch manifest
runs-on: ubuntu-latest
needs: build
Expand All @@ -133,12 +134,8 @@ jobs:
fail-fast: false
matrix:
include:
- variant: default
suffix: ""
- variant: default-slim
suffix: "-slim"
- variant: metrics
suffix: "-metrics"
- variant: metrics-slim
suffix: "-metrics-slim"

Expand Down Expand Up @@ -247,3 +244,123 @@ jobs:
run: |
cosign sign --yes "${REGISTRY_IMAGE_GHCR}@${GHCR_DIGEST}"
cosign sign --yes "${REGISTRY_IMAGE_DOCKERHUB}@${DOCKERHUB_DIGEST}"

publish-manifest-full:
name: Publish ${{ matrix.variant }} multi-arch manifest
runs-on: ubuntu-latest
needs: publish-manifest-slim
if: github.event_name != 'pull_request'
strategy:
fail-fast: false
matrix:
include:
- variant: default
suffix: ""
- variant: metrics
suffix: "-metrics"

steps:
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Login to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Install cosign
uses: sigstore/cosign-installer@v3

- name: Extract GHCR metadata
id: meta-ghcr
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY_IMAGE_GHCR }}
tags: |
type=ref,event=tag,suffix=${{ matrix.suffix }}
type=raw,value=latest${{ matrix.suffix }},enable=${{ !contains(github.ref_name, 'alpha') && !contains(github.ref_name, 'beta') && !contains(github.ref_name, 'rc') }}
type=raw,value=stable${{ matrix.suffix }},enable=${{ !contains(github.ref_name, 'alpha') && !contains(github.ref_name, 'beta') && !contains(github.ref_name, 'rc') }}
type=raw,value=edge${{ matrix.suffix }},enable=${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }}

- name: Extract Docker Hub metadata
id: meta-dockerhub
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY_IMAGE_DOCKERHUB }}
tags: |
type=ref,event=tag,suffix=${{ matrix.suffix }}
type=raw,value=latest${{ matrix.suffix }},enable=${{ !contains(github.ref_name, 'alpha') && !contains(github.ref_name, 'beta') && !contains(github.ref_name, 'rc') }}
type=raw,value=stable${{ matrix.suffix }},enable=${{ !contains(github.ref_name, 'alpha') && !contains(github.ref_name, 'beta') && !contains(github.ref_name, 'rc') }}
type=raw,value=edge${{ matrix.suffix }},enable=${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }}

- name: Create multi-arch manifests
id: manifests
env:
GHCR_TAGS: ${{ steps.meta-ghcr.outputs.tags }}
DOCKERHUB_TAGS: ${{ steps.meta-dockerhub.outputs.tags }}
MATRIX_VARIANT: ${{ matrix.variant }}
run: |
create_manifest() {
local tags="$1"
local image="$2"
local metadata_file="$3"
local first_tag=""
local tag
local -a args=()

while IFS= read -r tag; do
if [ -n "$tag" ]; then
args+=("-t" "$tag")
if [ -z "$first_tag" ]; then
first_tag="$tag"
fi
fi
done <<< "$tags"

if [ "${#args[@]}" -eq 0 ]; then
echo "No manifest tags generated for ${image}" >&2
exit 1
fi

docker buildx imagetools create "${args[@]}" \
--metadata-file "$metadata_file" \
"${image}:${TEMP_IMAGE_TAG}-${MATRIX_VARIANT}-amd64" \
"${image}:${TEMP_IMAGE_TAG}-${MATRIX_VARIANT}-arm64" >&2

printf '%s\n' "$first_tag"
}

create_manifest "$GHCR_TAGS" "$REGISTRY_IMAGE_GHCR" /tmp/ghcr-manifest-metadata.json
create_manifest "$DOCKERHUB_TAGS" "$REGISTRY_IMAGE_DOCKERHUB" /tmp/dockerhub-manifest-metadata.json

GHCR_DIGEST="$(jq -r '."containerimage.descriptor".digest' /tmp/ghcr-manifest-metadata.json)"
DOCKERHUB_DIGEST="$(jq -r '."containerimage.descriptor".digest' /tmp/dockerhub-manifest-metadata.json)"

if [ -z "$GHCR_DIGEST" ] || [ "$GHCR_DIGEST" = "null" ]; then
echo "Failed to read GHCR manifest digest" >&2
exit 1
fi

if [ -z "$DOCKERHUB_DIGEST" ] || [ "$DOCKERHUB_DIGEST" = "null" ]; then
echo "Failed to read Docker Hub manifest digest" >&2
exit 1
fi

echo "ghcr_digest=${GHCR_DIGEST}" >> "$GITHUB_OUTPUT"
echo "dockerhub_digest=${DOCKERHUB_DIGEST}" >> "$GITHUB_OUTPUT"

- name: Sign Docker images
env:
GHCR_DIGEST: ${{ steps.manifests.outputs.ghcr_digest }}
DOCKERHUB_DIGEST: ${{ steps.manifests.outputs.dockerhub_digest }}
run: |
cosign sign --yes "${REGISTRY_IMAGE_GHCR}@${GHCR_DIGEST}"
cosign sign --yes "${REGISTRY_IMAGE_DOCKERHUB}@${DOCKERHUB_DIGEST}"
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **内置登录方式控制** — 新增可热更新的密码登录开关,并继续与 Passkey 开关独立组合;关闭密码登录会同时关闭公开注册、激活重发、密码邀请接受、密码重置和外部身份密码绑定,未完成的密码第一因子 MFA flow 会在完成时重新检查策略,外部认证和 Passkey 登录不再被遗留的强制改密标记阻塞。后端仅在存在已启用外部认证 provider 时允许同时关闭密码与 Passkey,并阻止禁用或删除最后一个外部 provider,避免保存后失去全部登录入口。
- **远端节点连接生命周期审计** — reverse tunnel 连接、正常下线、异常断线和心跳超时现在会按 remote node / binding 聚合写入系统 audit;四条 streaming lane 的同时变化只产生一次节点级状态转换,并记录连接次数、中断次数、lane 数量、transport 和稳定 reason code,不包含 access key、secret、signature、URL 凭据或 token。

### Fixed

- **Slim 镜像媒体处理能力与派生缓存** — full 与 slim 镜像切换时保留已有媒体处理配置,管理端分别展示已配置、运行时可用和有效启用状态;公开缩略图能力只声明当前可生成的格式,并与媒体元数据能力独立。已有缩略图和图片预览缓存继续可读,缺少 `vips`、`ffmpeg` 或 `ffprobe` 时仅阻止新的相关派生并返回结构化处理器不可用错误;Docker 发布流程也保证所有 slim 变体先于 full 变体推送。

## [v0.5.0] - 2026-08-20

### Changed
Expand Down
3 changes: 3 additions & 0 deletions developer-docs/en/api/admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,7 @@ Admin team creation can create a team for another user and give that user the in
| `GET` | `/admin/config` | List runtime config entries |
| `GET` | `/admin/config/schema` | Read system config schema |
| `GET` | `/admin/config/template-variables` | Read template variable catalog |
| `GET` | `/admin/config/media-processing-status` | Read configured, runtime-available, and effective media processor status |
| `GET` | `/admin/config/{key}` | Read one runtime config entry |
| `PUT` | `/admin/config/{key}` | Set runtime config entry |
| `DELETE` | `/admin/config/{key}` | Delete custom runtime config entry |
Expand Down Expand Up @@ -610,6 +611,8 @@ The field only applies to `source = "custom"` entries. Built-in system configura

`GET /admin/config` now includes `visibility` in addition to `id`, `key`, `value`, `source`, `namespace`, `updated_at`, and `updated_by`. Sensitive values are still redacted as `***REDACTED***`.

`GET /admin/config/media-processing-status` does not modify persisted configuration. It probes the current instance and returns `configured_enabled`, `runtime_available`, `effective_enabled`, and an optional `unavailable_reason` for each media processor. `command_not_found` never includes the configured local command path, so administrators can explain a full/slim image mismatch without exposing host details.

The frontend custom-configuration read path is documented in [Public API](./public.md) under `GET /public/custom-config`. That endpoint only returns the key/value map visible to the current request identity and does not expose admin-only fields.

Admin task APIs can see system tasks and blob-level cache tasks that ordinary users normally cannot see.
Expand Down
7 changes: 3 additions & 4 deletions developer-docs/en/api/public.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,7 @@ Returns the server's public thumbnail-generation support:
},
"video_thumbnail": {
"enabled": false
},
"extensions": ["bmp", "gif", "jpe", "jpeg", "jpg", "png", "tif", "tiff", "webp"]
}
}
}
```
Expand All @@ -160,10 +159,10 @@ Notes:

- extensions are normalized to lowercase without leading dots
- `image_preview`, `image_thumbnail`, `audio_thumbnail`, and `video_thumbnail` are the current per-use capability fields
- top-level `extensions` is kept as a compatibility union for older clients
- the built-in image processor exposes common image formats when enabled
- the built-in `lofty` processor can expose audio suffixes for embedded cover thumbnails
- `vips_cli` / `ffmpeg_cli` expose configured extensions only when the commands are available and the processors are enabled
- this endpoint describes the instance's **effective generation capability**, not the configured database capability; administrators can use `GET /api/v1/admin/config/media-processing-status` to inspect `configured_enabled`, `runtime_available`, and `effective_enabled` for each processor
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- the capability mainly comes from `media_processing_registry_json`
- storage-native thumbnails and image previews can also contribute extensions when a storage policy opts in and the driver exposes that capability; built-in `tencent_cos` policies can expose it through COS CI, while built-in Local, S3-compatible, Azure Blob, OneDrive, and Remote policies do not

Expand Down Expand Up @@ -200,7 +199,7 @@ Returns media metadata parsing support:
}
```

The top-level `enabled` maps to `media_metadata_enabled`. The per-kind entries are derived from the active media-processing registry and bounded by `media_metadata_max_source_bytes`. Storage-native media metadata extensions can also be merged into audio/video support when a policy opts in and the driver exposes that capability; built-in `tencent_cos` policies can expose it through COS CI.
The top-level `enabled` maps to `media_metadata_enabled`. The per-kind entries are derived from the active media-processing registry and bounded by `media_metadata_max_source_bytes`. Image metadata support is independent from thumbnail generation support: the built-in Rust image parser may advertise `heic` metadata while the thumbnail endpoint filters `heic` when `vips` is missing. Storage-native media metadata extensions can also be merged into audio/video support when a policy opts in and the driver exposes that capability; built-in `tencent_cos` policies can expose it through COS CI.

## `POST /public/remote-enrollment/redeem`

Expand Down
3 changes: 3 additions & 0 deletions developer-docs/zh-CN/api/admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,7 @@ POST /api/v1/admin/policies/action
| `GET` | `/admin/config` | 列出全部运行时配置 |
| `GET` | `/admin/config/schema` | 读取系统配置 schema |
| `GET` | `/admin/config/template-variables` | 读取模板变量清单 |
| `GET` | `/admin/config/media-processing-status` | 读取媒体处理器的已配置、运行时可用和有效启用状态 |
| `GET` | `/admin/config/{key}` | 获取单个配置项 |
| `PUT` | `/admin/config/{key}` | 设置配置项 |
| `DELETE` | `/admin/config/{key}` | 删除配置项 |
Expand Down Expand Up @@ -897,6 +898,8 @@ POST /api/v1/admin/policies/action

`GET /admin/config` 返回的是实际配置项分页,字段还会包含 `id`、`key`、`value`、`source`、`visibility`、`namespace`、`updated_at` 和 `updated_by`。敏感配置项的 `value` 会被脱敏成 `***REDACTED***`。

`GET /admin/config/media-processing-status` 不修改数据库配置,只探测当前实例的运行时命令并返回每个处理器的 `configured_enabled`、`runtime_available`、`effective_enabled` 和可选 `unavailable_reason`。`command_not_found` 不会包含配置中的本地命令路径;full/slim 镜像切换后用这条接口解释“数据库仍启用、当前实例不可生成”的状态。

前端管理后台就是靠它动态渲染设置页,而不是写死每个配置项。

### 配置分区
Expand Down
6 changes: 3 additions & 3 deletions developer-docs/zh-CN/api/public.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,7 @@
},
"video_thumbnail": {
"enabled": false
},
"extensions": ["bmp", "gif", "jpe", "jpeg", "jpg", "png", "tif", "tiff", "webp"]
}
}
}
```
Expand All @@ -162,10 +161,10 @@

- `extensions` 已经做过规范化,统一是不带点的小写扩展名
- `image_preview`、`image_thumbnail`、`audio_thumbnail`、`video_thumbnail` 是当前按用途拆分的能力字段
- 顶层 `extensions` 是给旧客户端保留的兼容并集字段
- 内置图片处理器启用时会暴露常见图片格式
- 内置 `lofty` 处理器启用 `thumbnail:audio` 时会暴露音频后缀,前端可通过同一条 thumbnail 接口请求音频内嵌封面
- `vips_cli` / `ffmpeg_cli` 只有在对应命令可用且处理器启用时,才会把配置里的扩展名暴露出去;因此它可能包含图片以外的文档或视频扩展名
- 这条接口表达的是当前实例的 **effective generation capability**,不是数据库里的 configured capability;管理员可通过 `GET /api/v1/admin/config/media-processing-status` 查看每个处理器的 `configured_enabled`、`runtime_available` 和 `effective_enabled`
- 这份能力主要来自运行时配置 `media_processing_registry_json`
- 如果某条存储策略设置了 `storage_native_thumbnail_enabled = true`,且实际驱动暴露存储原生缩略图 / 图片预览能力,策略里的 `storage_native_thumbnail_extensions` 也会合并进公开能力列表;设置为 `false` 只停用原生候选,不影响全局缩略图处理器。内置 `tencent_cos` 策略可通过 COS CI 暴露这项能力,内置 Local、S3-compatible、Azure Blob、OneDrive 和 Remote 策略不暴露

Expand Down Expand Up @@ -207,6 +206,7 @@
- `enabled` 是媒体元数据总开关,对应运行时配置 `media_metadata_enabled`
- `max_source_bytes` 会按服务端配置值返回,但会裁剪到 JavaScript 安全整数范围内
- `kinds.image` 来自内置 `images` 处理器的 `metadata:image` 用途
- 图片元数据支持与缩略图生成支持是独立契约;例如内置 Rust 图片解析链可以继续暴露 `heic` 元数据,而缺少 `vips` 时缩略图接口仍会过滤 `heic`
- `kinds.audio` 来自内置 `lofty` 处理器的 `metadata:audio` 用途
- `kinds.video` 来自 `ffprobe_cli` 处理器的 `metadata:video` 用途;命令不可用或处理器未启用时会返回 `enabled = false`
- `match = "extensions"` 表示前端应按扩展名匹配;`match = "any"` 当前只会出现在启用 `ffprobe_cli` 且没有配置扩展名过滤时,表示视频元数据可尝试所有视频候选文件
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/deploy/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ NAS、单机、小团队,或者已经在用容器编排的单实例部署。10
| Slim | `vX.Y.Z-slim` / `latest-slim` / `stable-slim` | `vX.Y.Z-metrics-slim` / `latest-metrics-slim` / `stable-metrics-slim` |
| 预发布 | `edge` / `edge-slim` | `edge-metrics` / `edge-metrics-slim` |

新建 slim 实例时,`vips_cli`、`ffmpeg_cli` 和 `ffprobe_cli` 默认关闭。从完整镜像切换已有实例之前,先到 `管理 -> 系统设置 -> 文件处理 -> 媒体处理` 检查这三个处理器;已有数据库会保留原配置,但 slim 容器会将缺失命令报告为不可用,也不会在公开能力接口中继续声明对应格式。如果实例需要其中任一能力,继续使用完整镜像。
新建 slim 实例时,`vips_cli`、`ffmpeg_cli` 和 `ffprobe_cli` 默认关闭。从完整镜像切换已有实例之前,先到 `管理 -> 系统设置 -> 文件处理 -> 媒体处理` 检查这三个处理器;已有数据库会保留原配置,管理端会分别显示“已配置”“运行时可用”和“有效启用”状态。Slim 容器会将缺失命令报告为不可用,也不会在公开缩略图能力接口中继续声明对应格式。已经生成的缩略图或图片预览缓存仍可读取,只有新派生内容生成会受影响;如果实例需要其中任一能力,继续使用完整镜像。

如果你把宿主机目录直接 bind mount 到 `/data`,**一定要先把目录创建好并把属主改成 `10001:10001`**,否则容器启动时生成 `config.toml`、SQLite 文件或临时目录都会直接报权限错误:

Expand Down
Loading
Loading