] [--profile ]… [service…]` — 像 docker 一样自动发现文件(`compose.yaml`、`compose.yml`、`docker-compose.yml`、`docker-compose.yaml`,并遍历父目录;`COMPOSE_FILE` 优先)。命名服务会限制命令范围:`plan`/`up` 会添加依赖闭包,而 `stop`/`start`/`restart`/`pull`/`ps` 和 `down` 仅应用于指定的服务(docker 的行为)。服务发现:由于平台的 DNS 无法解析 compose 服务名,因此 `up`/`start`/`restart` 会向项目中的每个正在运行的容器写入受管理的 `/etc/hosts` 条目(后缀为 `# davit-compose`),将每个服务和容器名映射到其当前 IP — 每次运行时都会刷新,因此重建后的服务新 IP 能够被一直保持运行的容器访问到。注意事项:在 compose 外部重建的容器会保留过时条目,直到下一次执行 `up` 或 `start`;没有 `/bin/sh` 的镜像无法被修补(会有警告)。
- **镜像仓库** — 登录 Docker Hub / ghcr.io / 任何镜像仓库以拉取私有镜像(设置 → 镜像仓库);凭据会针对镜像仓库进行验证,并存储在登录钥匙串中,与 `container` CLI 共享。同时也支持 **Docker 凭据助手**:如果 `~/.docker/config.json` 包含针对某个镜像仓库的 `credHelpers`/`credsStore` 条目(例如通过 `gcloud` 的 Google Artifact Registry、ECR 等),Davit 会在每次拉取前调用该助手,并为平台暂存短生命周期的 token,因此每小时过期的凭据也能始终正常工作。
- **菜单栏扩展** — 服务状态,随处可用的针对单个容器的快捷操作。
- **自动启动容器** — 将任何容器标记为“Davit 启动时同步启动”(右键菜单);启动时,Davit 会根据需要拉起平台服务并启动它们。配合*登录时打开*,当您坐在电脑前时,容器已经在运行了。
- **登录时打开** — 可选的登录时启动至菜单栏功能(设置 → 通用),通过 `SMAppService` 实现。
- **应用内更新** — 每天检查 GitHub Releases(或从“关于”界面按需检查);一键下载新版本,验证其 Developer ID 签名(开发团队必须匹配),以原子方式替换并支持回滚的 bundle,然后重启应用。支持无界面模式:`Davit update check|install`。
- **设置** — 平台安装根目录覆盖及刷新频率(通用),外加完整的**平台配置编辑器**(平台标签页):默认容器 CPU/内存、镜像仓库、本地 DNS 域名、构建器资源/Rosetta,以及高级参数(内核、init 镜像、机器)。只有与安装默认值不同的值才会被写入 `~/.config/container/config.toml`,会保留 `[plugin.*]` 部分,每次保存都会在提交前通过平台自带的配置加载器进行验证,并且结果会立即发布到应用根目录 — 容器默认值/镜像仓库/DNS 会立即应用于新操作,而 daemon 端的设置则在服务重启后生效(提供有重启按钮)。
## 环境要求
- Apple silicon Mac,macOS 15+(推荐 macOS 26 — 以匹配 `container` 1.1)
- 已安装 [apple/container](https://github.com/apple/container/releases)(或内置打包,见下文)
## 发布
标记 `v*` 会触发 GitHub Actions 工作流,在 macOS
runner 上构建应用,并将 `Davit-.zip` (+ sha256) 附加到 GitHub Release 中。
设置了这些仓库密钥后,发布的版本将带有 Developer ID 签名(hardened
runtime)并经过公证,因此它们可以像任何其他应用一样直接打开:
| Secret | Value |
|---|---|
| `MACOS_CERT_P12` | 导出的 "Developer ID Application" 证书的 base64 编码 (`base64 -i cert.p12`) |
| `MACOS_CERT_PASSWORD` | .p12 导出密码 |
| `APPLE_ID` | 您的 Apple ID 邮箱 |
| `APPLE_TEAM_ID` | 10 字符的团队 ID (developer.apple.com → Membership) |
| `APPLE_APP_SPECIFIC_PASSWORD` | 从 appleid.apple.com 获取的 App 专用密码 |
如果没有这些密钥,构建版本将回退为临时签名 (ad-hoc),并且发布说明中会包含
`xattr -dr com.apple.quarantine` 的变通方法。
## 安装
```
brew install wouterdebie/tap/davit
```
(Homebrew 6 在首次使用时会要求您信任第三方 tap:`brew trust wouterdebie/tap`。)
或者从 [Releases](https://github.com/wouterdebie/davit/releases/latest) / [davit.app](https://davit.app) 下载已签名的 zip 包。
## 快速上手
从全新安装到在浏览器中运行某些内容:
1. **打开 Davit。** 首次启动时,如果未安装 Apple 的 container 平台,Davit 会为您下载并安装它 — 无需管理员密码。
2. **拉取演示镜像** — 镜像 → 拉取镜像 → `nginxdemos/hello`(体积小,并且它会提供一个可见的页面)。
3. **映射端口并运行** — 运行该镜像,将宿主机 `8088` 端口映射到容器 `80` 端口。
4. **打开它** — 容器的“端口”行有*在浏览器中打开*选项,或者直接访问 `localhost:8088`。您会看到一个由容器内部服务提供的页面,显示其自身的主机名和 IP。
5. **探索** — 实时 CPU/内存/磁盘、流式日志、一键终端,以及用于更改端口/环境变量/资源的编辑与重建功能。
## 构建与运行
```
scripts/bundle.sh # builds release binary + assembles dist/Davit.app
open dist/Davit.app
```
原生的 `swift build` / `swift run` 也可用于开发环境(没有 bundle,因此没有图标和菜单栏等额外功能)。
## 仍然必须安装的内容
API 移除了对 CLI 的依赖,但**并未**移除对平台的依赖:`container-apiserver`
及其运行时/网络插件二进制文件属于宿主机 launchd 服务,库仅与它们进行
通信。Davit 按以下顺序解析平台安装根目录:
1. “设置”中的自定义安装根目录
2. Davit 托管的安装位置,位于 `~/Library/Application Support/dev.wouter.davit/platform/`
3. `/usr/local`(官方安装程序的路径)
4. 内置于应用程序内部的 `Davit.app/Contents/Resources/vendor`
**应用内安装:** 当未找到平台时,引导界面会提供一键
安装 — Davit 下载 Apple 签名的安装 pkg 包(带有实时进度条),
验证代码签名,将 payload 解压到受管理的根目录(与官方
安装程序不同,无需管理员权限),并从该处引导启动
各项服务。设置 → 通用还可以安装 `container` shell
命令:在 `/usr/local/bin` 中放置一个 wrapper,在执行真正的 CLI 之前将 `CONTAINER_INSTALL_ROOT` 固定到
受管理的根目录(仅需一次管理员授权提示;简单的符号链接将是
错误的,因为 CLI 是从其未解析的可执行路径推导安装根目录的)。受管理的副本在解析顺序中
排在 `/usr/local` 之前是刻意为之的:它始终与应用链接的客户端
版本相匹配,因此较新的系统 daemon 不会破坏 XPC 兼容性。
同样支持无界面模式:`Davit platform install|remove`。
要发布一个无需系统安装程序即可运行的完全自包含的应用:
```
scripts/vendor.sh 1.0.0 # downloads the official signed .pkg, extracts payload into Vendor/container
scripts/bundle.sh --vendor # bundles it into the app
```
服务启动/停止是在进程内实现的(使用来自
`ContainerPlugin` 的 LaunchPlist + ServiceManager — 即运行 `container system start` 的相同代码),并指向
解析出的任意安装根目录。内核和 init 镜像安装会在
首次启动时以非交互方式处理。应用程序数据位于标准的
`~/Library/Application Support/com.apple.container/` 中,因此 Davit 和 CLI(如果
已安装)看到的是完全相同的容器。
**版本固定:** SPM 对 apple/container 的依赖被精确固定 `exact: "1.0.0"` 以
匹配 daemon;客户端和 apiserver 同步发布,且 XPC 协议并非
稳定的公共 API。当已安装的平台更新时,请提升固定版本并重新构建。
作为记录,内置打包的权衡包括:bundle 体积会增加约 150 MB,您需要自行掌控
工具链的更新节奏,并且 launchd 服务是从
应用 bundle 内部注册的(因此移动/删除应用会使它们变为孤立状态,直到执行 `container system stop` 为止)。
默认设置仍然是“使用系统安装”,因为官方 pkg 会将服务保留在
`/usr/local` 下并独立进行更新。
## 二进制模式
该应用程序二进制文件同时也充当一个小工具:
```
Davit exec [command…] # interactive TTY shell — or a one-off command — in a container (used by "Open Terminal")
Davit selftest # end-to-end test of the XPC service layer against the live daemon
Davit system start|stop # bootstrap / tear down the container launchd services
Davit platform install|remove # download + verify Apple's signed pkg into an app-managed install root
Davit registry login|list|logout # registry credentials (login reads the password from stdin)
Davit compose [-f ] [--env-file ] [--profile ]… [service…]
# subcommands: plan | up [-d|--detach] | down [-v|--volumes] | ps | logs [-f|--follow] [--tail ]
# stop | start | restart | pull | exec
# file autodiscovered like docker; .env + ${VAR} interpolation; naming services scopes the
# command (plan/up add the dependency closure; stop/start/restart/pull/ps/down take exactly
# the named services). In-container service names resolve via managed /etc/hosts entries,
# re-synced on every up/start/restart — recreates outside compose need another up/start.
Davit build -t # build an image from /Dockerfile via the BuildKit shim
Davit machine list|create|boot|stop|delete # container machines (micro VMs)
Davit --snapshot /tmp/shots # render every screen to PNGs via ImageRenderer (no screen-recording permission)
```
## 架构
```
Sources/ContainerStack/
Main.swift entry point + exec/selftest/system binary modes
Backend.swift XPC service layer on ContainerAPIClient: ContainerService facade,
SystemController (launchd bootstrap), LogStreamer (FileHandle tail/follow),
PullProgressModel, TerminalLauncher, platform install resolution
Models.swift view models mapped from ContainerResource types (views stay decoupled
from the unstable library API)
AppState.swift @MainActor store: polling (4s data / 2s stats), CPU% derivation, actions
SnapshotDriver.swift --snapshot harness
App.swift SwiftUI scenes, menu bar extra
Views/ Shell (sidebar), Dashboard, Containers, ContainerDetail, Images,
VolumesNetworks, Sheets (run/pull/create), Settings, Components
```
统计信息说明:daemon 报告的是累计的 `cpuUsageUsec`;Davit 根据轮询之间的增量计算得出 CPU%
,并按实际时间进行标准化(100% = 占满一个完整的核心)。