oci-sync 设计文档
版本:0.1.0-dev | 更新时间:2026-05-11
1. 项目概述
oci-sync 是一个 Go 语言命令行工具,将本地文件或目录以 OCI artifact 的形式同步到任意兼容 OCI Distribution Spec 的镜像仓库(Docker Hub、GHCR、Harbor、ACR 等),支持可选的 AES-256-GCM 加密,并记录所有操作到本地 activity cache。
使用场景
- 将配置文件、数据集、模型文件等任意内容存入 OCI 仓库进行版本管理
- 跨机器、跨环境分发文件,借助镜像仓库的权限管理做访问控制
- 敏感文件加密存储,密钥不离开本地
- 通过自定义标签对 artifacts 进行分类和筛选管理
- 使用 GUI 图形界面可视化管理快捷仓库和 artifacts
2. 整体架构
┌──────────────────────────────────────────────────────────┐
│ CLI 层 │
│ cmd/root.go cmd/push.go cmd/pull.go cmd/shortcut.go│
│ cmd/delete.go cmd/list.go cmd/label.go cmd/alias.go │
│ cmd/recent.go cmd/gui.go │
└──────────────┬───────────────┬──────────────┬────────────┘
│ │
┌────────▼───┐ ┌─────▼────────┐
│ archive │ │ oci │
│ tar.gz │ │ oras-go v2 │
│ 打包/解包 │ │ push/pull │
└────────────┘ └─────┬────────┘
│
┌─────────────┐ ┌─────▼────────────────┐
│ crypto │ │ Docker Credential │
│ AES-256-GCM │ │ Store (~/.docker/ │
│ + scrypt │ │ config.json) │
└─────────────┘ └──────────────────────┘
│
┌────────▼────────────────┐
│ cache │
│ ~/.cache/oci-sync/ │
│ activity.json │
└───────────────────────┘数据流(push)
本地路径 → [archive.Pack] → tar.gz bytes
→ [crypto.Encrypt](可选)→ 加密 bytes
→ [oci.Push] → OCI manifest + layer → Registry数据流(pull)
Registry → [oci.IsEncrypted] → 检查加密状态(manifest 只读)
→ 校验 --passphrase 参数(若缺失则快速失败,无需下载)
→ [oci.Pull] → layer bytes + annotations
→ [crypto.Decrypt](若加密)→ tar.gz bytes
→ [archive.Unpack] → 本地路径数据流(shortcut commands)
CLI 参数传入(--tag + shortcuts.<name>.repo 配置)
→ 拼装完整 remote ref
→ 复用标准 [push]/[pull]/[delete] 数据流
CLI 参数传入(shortcuts.<name>.repo 配置)
→ 解析 repository
→ 复用标准 [list] 数据流数据流(delete)
CLI 参数传入 → [oci.Delete] 解析 descriptor → Registry 删除 API数据流(list)
CLI 参数传入 → [oci.List] (支持 Registry/Repo 自动解析)
→ [reg.Repositories] (若为 Registry) → 遍历 repos
→ [repo.Tags] → 遍历 tags → [repo.Fetch] 获取 Manifest
→ 过滤 io.oci-sync.version 标记 → 返回 ArtifactInfo 列表
→ 支持 --label 筛选 → 格式化表格输出 (显示 REPO, TAG, DIGEST, LABELS 等)数据流(label set/unset)
CLI 参数传入 → [oci.UpdateAnnotations] 获取 manifest
→ 修改 annotations (set 添加/更新, unset 删除指定 key)
→ Push 新 manifest → 更新 tag 指向新 digest数据流(alias add/remove)
CLI 参数传入 → 读写配置文件 ~/.config/oci-sync/oci-sync.yaml
→ 修改 shortcuts 字段 → 保存数据流(recent)
CLI 参数传入 → 读取 ~/.cache/oci-sync/activity.json
→ 支持 --limit 限制数量、--format 指定格式 (table/json/yaml)
→ 支持 --clear 清空历史记录数据流(gui)
GUI 启动 → 加载所有配置的 shortcuts(左栏)
→ 点击 shortcut → [oci.List] 加载该仓库 artifacts(右栏)
→ Upload(本地路径 + Tag + 可选口令)→ [archive.Pack] → [crypto.Encrypt](可选)→ [oci.Push]
→ Download(目标路径 + 口令)→ [oci.IsEncrypted] → [oci.Pull] → [crypto.Decrypt](可选)→ [archive.Unpack]
→ Delete → [oci.Delete](确认弹窗)
→ 底部详情面板展示选中 artifact 的 Tag/Size/Version/加密状态数据流(activity recording)
CLI push/pull/delete/label 操作成功
→ 写入 ~/.cache/oci-sync/activity.json
→ 记录类型、时间戳、远程引用、本地路径、标签、操作结果3. 项目结构
oci-sync/
├── main.go # 程序入口
├── go.mod / go.sum
├── FEATURE.md # 产品需求文档
├── README.md # 使用文档
├── docs/
│ └── design.md # 本设计文档
├── cmd/
│ ├── root.go # 根命令 & 全局配置(--quiet / -q)
│ ├── push.go # push 子命令
│ ├── pull.go # pull 子命令
│ ├── shortcut.go # 动态 shortcut 子命令组
│ ├── delete.go # delete 子命令
│ ├── list.go # list 子命令
│ ├── label.go # label 子命令 (set/unset)
│ ├── alias.go # alias 子命令 (list/add/remove)
│ ├── recent.go # recent 子命令(查看活动历史)
│ ├── gui.go # gui 子命令(图形界面管理)
│ └── utils.go # 工具函数(formatBytes)
└── internal/
├── config/
│ └── config.go # 配置文件支持
├── archive/
│ ├── archive.go # tar.gz 打包/解包
│ └── archive_test.go # 单元测试
├── crypto/
│ ├── crypto.go # AES-256-GCM 加密/解密
│ └── crypto_test.go # 单元测试
├── oci/
│ └── oci.go # OCI push/pull(oras-go v2)
├── cache/
│ └── cache.go # Activity cache 持久化
├── xdg/
│ └── xdg.go # XDG 目录规范支持
├── gui/
│ ├── gui.go # gogpu/ui 图形界面
│ └── operations.go # GUI 上传/下载/删除操作
└── version/
│ └── version.go # 版本信息4. 模块设计
4.1 internal/archive — 打包/解包
| 函数 | 签名 | 说明 |
|---|---|---|
Pack | (srcPath string) ([]byte, error) | 将文件或目录打包为 tar.gz,返回字节 |
Unpack | (data []byte, destPath string) error | 将 tar.gz 字节解包到指定目录 |
实现细节
- 使用标准库
archive/tar+compress/gzip,无额外依赖 - 目录打包保留完整的子目录结构,以
filepath.Walk遍历 - 解包时进行路径穿越检查(Path Traversal Security)
4.2 internal/crypto — 加密/解密
| 函数 | 签名 | 说明 |
|---|---|---|
Encrypt | (data []byte, passphrase string) ([]byte, error) | 加密数据 |
Decrypt | (data []byte, passphrase string) ([]byte, error) | 解密数据 |
加密算法
- KDF:scrypt(N=2^15=32768, r=8, p=1)→ 32 字节密钥 (OWASP 推荐最低)
- 加密:AES-256-GCM(认证加密,同时提供保密性和完整性)
- 存储格式:
[salt(32B) | nonce(12B) | ciphertext+GCM-tag] - 每次加密生成新的随机 salt 和 nonce,保证相同明文加密结果不同
passphrase ──┐
├─► scrypt(N=32768) ──► 32B key ──► AES-256-GCM ──► ciphertext
random salt ─┘ │
random nonce ────────────────────────────────────────►│
▼
[salt(32B)][nonce(12B)][ciphertext+tag]4.3 internal/oci — OCI 操作
| 函数 | 签名 | 说明 |
|---|---|---|
Push | (ctx, data []byte, ref string, encrypted bool, labels map[string]string) error | 推送 artifact(支持 labels) |
IsEncrypted | (ctx, ref string) (bool, error) | 检查加密状态(仅拉取 manifest) |
Pull | (ctx, ref string) (*PullResult, error) | 拉取 artifact;返回 PullResult{Data, Encrypted} |
Delete | (ctx, ref string) error | 删除远程 artifact |
List | (ctx, ref string) ([]ArtifactInfo, error) | 列出远程仓库镜像记录(支持 Registry/Repo) |
UpdateAnnotations | (ctx, ref string, updates map[string]string, removeKeys []string) error | 更新 manifest annotations(set/unset labels) |
OCI Artifact 结构
- 使用标准 OCI Image Manifest 格式,
schemaVersion: 2 - Config mediaType:
application/vnd.oci.image.config.v1+json(空 JSON{}) - Layer mediaType:
application/octet-stream(不定义自定义类型) - Manifest Annotations 携带元信息:
- 系统保留 annotation:
io.oci-sync.encrypted、io.oci-sync.version - 用户自定义 labels:存储于 annotations,可通过
--label设置
- 系统保留 annotation:
| Annotation Key | 值 | 说明 |
|---|---|---|
io.oci-sync.encrypted | "true" / "false" | 是否加密 |
io.oci-sync.version | "0.1.0" | 工具版本 |
| 用户自定义 | 任意字符串 | 通过 --label key=value 设置 |
数据结构 ArtifactInfo
FullName: 镜像全名,格式为<registry>/<repo>:<tag>(用于 JSON/YAML 输出)Repo: 仓库名称Tag: 镜像标签Digest: 内容摘要Encrypted: 是否加密(布尔值)Version: 上传时的工具版本Labels: 用户自定义标签映射(map[string]string)
认证
- 支持两种认证方式(按顺序查找,找到即用):
- 配置文件 (
auths.<registry>.username/password):支持为每个仓库配置独立凭据 - Docker credential store:通过
oras-go v2内置的credentials.NewStoreFromDocker()加载
- 配置文件 (
- 自动支持
credsStore/credHelpers(macOS keychain、Windows Credential Manager、Linux secret service) - 用户只需提前执行
docker login <registry>即可
4.4 internal/cache — Activity Cache
| 函数 | 签名 | 说明 |
|---|---|---|
InitCache | () error | 初始化 cache 目录(~/.cache/oci-sync/) |
AddActivity | (Activity) error | 添加活动记录 |
GetRecentActivities | (limit int) ([]Activity, error) | 获取最近活动 |
ClearActivities | () error | 清空所有活动记录 |
Activity 数据结构
type Activity struct {
Type ActivityType // push/pull/delete/label
Timestamp time.Time // 操作时间
RemoteRef string // 远程引用
LocalPath string // 本地路径(push/pull)
Labels []string // 标签(label 操作)
Success bool // 是否成功
Error string // 错误信息
}存储位置:遵循 XDG Base Directory Spec
- 默认:
~/.cache/oci-sync/activity.json - 支持
XDG_CACHE_HOME环境变量覆盖
4.5 internal/xdg — XDG 目录规范
| 函数 | 签名 | 说明 |
|---|---|---|
ConfigDir | () string | 配置目录($XDG_CONFIG_HOME 或 ~/.config) |
CacheDir | () string | 缓存目录($XDG_CACHE_HOME 或 ~/.cache) |
DataDir | () string | 数据目录($XDG_DATA_HOME 或 ~/.local/share) |
5. 依赖列表
| 包 | 版本 | 用途 |
|---|---|---|
github.com/spf13/cobra | v1.10.2 | CLI 框架 |
gopkg.in/yaml.v3 | v3.x | 配置文件解析(替代 viper) |
oras.land/oras-go/v2 | v2.6.0 | OCI push/pull |
github.com/opencontainers/image-spec | v1.1.1 | OCI 数据结构 |
github.com/opencontainers/go-digest | v1.0.0 | 内容摘要计算 |
charm.land/log/v2 | v2 | 彩色日志输出 |
github.com/pterm/pterm | v0.12.x | 表格/输出格式化(list、recent) |
golang.org/x/crypto | v0.49.0 | scrypt KDF |
github.com/gogpu/ui | v0.1.18 | 图形界面 (GUI) |
github.com/gogpu/gogpu | v0.32.0 | GUI 窗口渲染后端(参考实现) |
github.com/gogpu/gg | v0.45.1 | GPU 绘制入口(GUI 渲染回退) |
6. 安全考量
| 风险 | 缓解措施 |
|---|---|
| 密码暴力破解 | scrypt 高内存消耗(N=32768)增加破解成本 |
| 重放/篡改攻击 | AES-GCM 提供认证标签,解密失败立即报错 |
| Nonce 重用 | 每次加密随机生成 nonce,与 salt 一起存储 |
| Path traversal | Unpack 时检查解包路径必须在目标目录内 |
| 凭据泄露 | 凭据通过系统 credential store 管理,不写入磁盘 |
7. CLI 接口
push
oci-sync push --local <local_path> --remote <remote_path> [--passphrase <passphrase>] [--label <key=value>]
# 或使用简写
oci-sync push -l <local_path> -r <remote_path> [--passphrase <passphrase>] [--label <key=value>]| 参数 | 必选 | 说明 |
|---|---|---|
--local, -l | ✓ | 本地文件或目录路径 |
--remote, -r | ✓ | 目标仓库引用,格式:<registry>/<repo>:<tag> |
--passphrase | 否 | 加密口令,不提供则不加密 |
--label | 否 | 设置标签,可重复使用(格式:key=value,value 可为空) |
--quiet, -q | 否 | 静默模式,全局生效 |
pull
oci-sync pull --remote <remote_path> --local <local_path> [--passphrase <passphrase>]
# 或使用简写
oci-sync pull -r <remote_path> -l <local_path> [--passphrase <passphrase>]| 参数 | 必选 | 说明 |
|---|---|---|
--remote, -r | ✓ | 源仓库引用,格式:<registry>/<repo>:<tag> |
--local, -l | ✓ | 本地目标目录 |
--passphrase | 否 | 解密口令(内容加密时必须提供) |
<name> push
oci-sync <name> push --local <local_path> --tag <tag> [--passphrase <passphrase>] [--label <key=value>]| 参数 | 必选 | 说明 |
|---|---|---|
--local, -l | ✓ | 本地文件或目录路径 |
--tag | ✓ | 目标标签 |
--passphrase | 否 | 加密口令,不提供则不加密 |
--label | 否 | 设置标签,可重复使用(格式:key=value,value 可为空) |
<name> pull
oci-sync <name> pull --tag <tag> --local <local_path> [--passphrase <passphrase>]| 参数 | 必选 | 说明 |
|---|---|---|
--tag | ✓ | 源标签 |
--local, -l | ✓ | 本地目标目录 |
--passphrase | 否 | 解密口令(内容加密时必须提供) |
<name> list
oci-sync <name> list [--format table|json|yaml] [--label <key>=<value>]| 参数 | 必选 | 说明 |
|---|---|---|
--format, -f | 否 | 输出格式:table(默认)、json、yaml |
--label | 否 | 筛选标签(key=value 精确匹配,key 仅检查 key 存在),可重复 |
<name> delete
oci-sync <name> delete --tag <tag>| 参数 | 必选 | 说明 |
|---|---|---|
--tag | ✓ | 要删除的目标标签 |
配置文件
配置文件使用 YAML 格式,搜索路径顺序:
- 当前工作目录
./oci-sync.yaml - 用户配置目录
~/.config/oci-sync/oci-sync.yaml
配置文件格式:
shortcuts:
<name>:
repo: <registry>/<repository>
x:
repo: <registry>/<repository>
auths:
<registry1.example.com>:
username: <username>
password: <password>
<registry2.example.com>:
username: <username>
password: <password>配置说明:
| 配置项 | 默认值 | 说明 |
|---|---|---|
shortcuts.<name>.repo | - | 动态命令的默认仓库地址 |
auths.<registry>.username | - | 该仓库的认证用户名 |
auths.<registry>.password | - | 该仓库的认证密码或令牌 |
认证优先级: 配置文件 auths > Docker credential store
delete
oci-sync delete --remote <remote_path>
# 或使用简写
oci-sync delete -r <remote_path>| 参数 | 必选 | 说明 |
|---|---|---|
--remote, -r | ✓ | 目标仓库引用,格式:<registry>/<repo>:<tag> |
list
# 列出特定仓库的 tags
oci-sync list --remote <registry>/<repository> [--format table|json|yaml] [-q] [--label <key=value>]
# 列出整个注册表中的所有 oci-sync 镜像
oci-sync list -r <registry> [--format table|json|yaml] [-q] [--label <key=value>]| 参数 | 必选 | 说明 |
|---|---|---|
--remote, -r | ✓ | 目标仓库源或注册表,格式:<registry>/<repo> 或 <registry> |
--format, -f | 否 | 输出格式:table(默认)、json、yaml |
--label | 否 | 筛选标签(key=value 精确匹配,key 仅检查 key 存在),可重复 |
--quiet, -q | 否 | 静默模式,全局生效 |
label
管理 OCI artifact 上的标签(存储于 manifest annotations)。
# 设置标签
oci-sync label set --remote <remote_path> <key1=value1> [<key2=value2>...]
# 删除标签
oci-sync label unset --remote <remote_path> <key1> [<key2>...]| 参数 | 必选 | 说明 |
|---|---|---|
--remote, -r | ✓ | 目标仓库引用,格式:<registry>/<repo>:<tag> |
key=value | ✓(set) | 设置标签,value 可为空字符串 |
key | ✓(unset) | 删除指定标签 |
alias
管理配置文件中的 shortcuts。
# 列出所有 shortcuts
oci-sync alias list
# 添加 shortcut
oci-sync alias add <name> --repo <registry>/<repository>
# 删除 shortcut
oci-sync alias remove <name>| 参数 | 必选 | 说明 |
|---|---|---|
--repo | ✓(add) | shortcut 对应的仓库地址 |
注意:若配置文件不可写,会输出警告但不会报错。
recent
查看本地 activity cache 中记录的操作历史。
# 查看最近 20 条活动(默认)
oci-sync recent
# 指定显示数量
oci-sync recent --limit 10
# 指定输出格式
oci-sync recent --format json
oci-sync recent --format yaml
# 清空所有活动记录
oci-sync recent --clear| 参数 | 必选 | 说明 |
|---|---|---|
--limit, -n | 否 | 最大显示条数,默认 20 |
--format, -f | 否 | 输出格式:table(默认)、json、yaml |
--clear | 否 | 清空所有活动记录 |
存储位置:~/.cache/oci-sync/activity.json(支持 XDG_CACHE_HOME 环境变量)
gui
启动基于 gogpu/ui 的桌面图形界面(GUI),分栏管理 shortcuts 的 artifacts。
# 启动 GUI 界面
oci-sync gui界面分区:
- Shortcuts(左栏):按名称排序的快捷键列表,展示 name 与 repo,点击加载对应仓库。
- Artifacts(右栏):当前 shortcut 仓库的 artifacts 列表,每行显示加密状态
[E]/[ ]、Tag、Size 与 Version;点击选中。 - 工具栏(右栏底部):
Upload/Download/Delete/Refresh四个按钮(Download/Delete 在未选中 artifact 时禁用)。 - Details(底部详情面板):展示选中 artifact 的 Tag、Size、Version、加密状态等完整信息。
操作流程:
- Upload:弹窗输入本地路径、Tag、可选口令(加密),后端打包 → 加密 → 推送。
- Download:弹窗输入目标路径与口令(加密内容必填),后端检查加密状态 → 拉取 → 解密 → 解包。
- Delete:删除所选 artifact。
- Refresh:重新加载当前仓库的 artifacts 列表。
界面使用 Material 3 主题(基于 gogpu/ui),窗口默认尺寸 1000×700,支持鼠标点击选择与操作。
8. 后续扩展方向
--insecure:支持 HTTP(非 TLS)仓库--platform:多架构 manifest list 支持- 进度条:大文件上传/下载显示进度
- 增量同步:对比 digest 跳过未变更内容
- 多文件 layer:多个 layer 对应多个文件,支持细粒度更新