Skip to content

Commit 0f01ee5

Browse files
authored
Merge pull request #3 from weiyong1024/dev/polish
feat: unified version management — lock CLI and Docker image versions
2 parents bb1916d + 5aa3128 commit 0f01ee5

14 files changed

Lines changed: 256 additions & 54 deletions

File tree

.github/workflows/release.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ on:
77

88
permissions:
99
contents: write
10+
packages: write
1011

1112
jobs:
1213
release:
@@ -26,3 +27,30 @@ jobs:
2627
args: release --clean
2728
env:
2829
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30+
31+
docker:
32+
runs-on: ubuntu-latest
33+
steps:
34+
- uses: actions/checkout@v4
35+
36+
- name: Extract version from tag
37+
id: meta
38+
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
39+
40+
- uses: docker/login-action@v3
41+
with:
42+
registry: ghcr.io
43+
username: ${{ github.actor }}
44+
password: ${{ secrets.GITHUB_TOKEN }}
45+
46+
- uses: docker/setup-buildx-action@v3
47+
48+
- uses: docker/build-push-action@v6
49+
with:
50+
context: internal/assets/docker
51+
push: true
52+
tags: |
53+
ghcr.io/weiyong1024/clawsandbox:${{ steps.meta.outputs.version }}
54+
ghcr.io/weiyong1024/clawsandbox:latest
55+
cache-from: type=gha
56+
cache-to: type=gha,mode=max

.goreleaser.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ builds:
1414
- CGO_ENABLED=0
1515
ldflags:
1616
- -s -w
17-
- -X github.com/weiyong1024/clawsandbox/internal/cli.Version={{.Version}}
18-
- -X github.com/weiyong1024/clawsandbox/internal/cli.GitCommit={{.ShortCommit}}
19-
- -X github.com/weiyong1024/clawsandbox/internal/cli.BuildDate={{.Date}}
17+
- -X github.com/weiyong1024/clawsandbox/internal/version.Version={{.Version}}
18+
- -X github.com/weiyong1024/clawsandbox/internal/version.GitCommit={{.ShortCommit}}
19+
- -X github.com/weiyong1024/clawsandbox/internal/version.BuildDate={{.Date}}
2020
goos:
2121
- darwin
2222
- linux

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ GIT_COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
88
BUILD_DATE ?= $(shell date -u '+%Y-%m-%dT%H:%M:%SZ')
99

1010
LDFLAGS = -s -w \
11-
-X '$(MODULE)/internal/cli.Version=$(VERSION)' \
12-
-X '$(MODULE)/internal/cli.GitCommit=$(GIT_COMMIT)' \
13-
-X '$(MODULE)/internal/cli.BuildDate=$(BUILD_DATE)'
11+
-X '$(MODULE)/internal/version.Version=$(VERSION)' \
12+
-X '$(MODULE)/internal/version.GitCommit=$(GIT_COMMIT)' \
13+
-X '$(MODULE)/internal/version.BuildDate=$(BUILD_DATE)'
1414

1515
.PHONY: build build-all docker-build install clean tidy
1616

README.md

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,7 @@ make build
5151
sudo make install
5252
```
5353

54-
### 2. Build the Docker image
55-
56-
Required once. Downloads ~1.4 GB and takes a few minutes.
57-
58-
```bash
59-
clawsandbox build
60-
```
61-
62-
### 3. Deploy your fleet
54+
### 2. Deploy your fleet
6355

6456
**Option A: Web Dashboard (recommended)**
6557

@@ -89,7 +81,7 @@ clawsandbox create 3
8981
clawsandbox list
9082
```
9183

92-
### 4. Set up each claw
84+
### 3. Set up each claw
9385

9486
Each claw needs a one-time configuration via its desktop. Open it from the Dashboard (click **"Desktop"** on an instance card) or via CLI:
9587

@@ -121,8 +113,7 @@ clawsandbox dashboard --help # Show dashboard subcommands
121113
Quick reference:
122114

123115
```bash
124-
clawsandbox build # Build the OpenClaw sandbox image
125-
clawsandbox create <N> # Create N claw instances
116+
clawsandbox create <N> # Create N claw instances (auto-pulls image)
126117
clawsandbox list # List all instances and their status
127118
clawsandbox desktop <name> # Open an instance's desktop in the browser
128119
clawsandbox start <name|all> # Start a stopped instance
@@ -132,9 +123,12 @@ clawsandbox logs <name> [-f] # View instance logs
132123
clawsandbox destroy <name|all> # Destroy instance (data kept by default)
133124
clawsandbox destroy --purge <name|all> # Destroy instance and delete its data
134125
clawsandbox dashboard serve # Start the Web Dashboard
126+
clawsandbox dashboard stop # Stop the Web Dashboard
127+
clawsandbox dashboard restart # Restart the Web Dashboard
135128
clawsandbox dashboard open # Open the Dashboard in your browser
136-
clawsandbox config # Show current configuration
137-
clawsandbox version # Print version info
129+
clawsandbox build # Build image locally (offline/custom use)
130+
clawsandbox config # Show current configuration
131+
clawsandbox version # Print version info
138132
```
139133

140134
## Resource Usage

README.zh-CN.md

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,7 @@ make build
5151
sudo make install
5252
```
5353

54-
### 2. 构建 Docker 镜像
55-
56-
首次使用需要执行一次,下载约 1.4 GB,需要几分钟:
57-
58-
```bash
59-
clawsandbox build
60-
```
61-
62-
### 3. 部署龙虾军团
54+
### 2. 部署龙虾军团
6355

6456
**方式 A:Web 仪表盘(推荐)**
6557

@@ -89,7 +81,7 @@ clawsandbox create 3
8981
clawsandbox list
9082
```
9183

92-
### 4. 配置每只龙虾
84+
### 3. 配置每只龙虾
9385

9486
每只龙虾需要通过其桌面完成一次初始化。可以在仪表盘点击 **「桌面」** 打开,也可以用 CLI:
9587

@@ -121,8 +113,7 @@ clawsandbox dashboard --help # 查看 dashboard 子命令组
121113
常用命令速查:
122114

123115
```bash
124-
clawsandbox build # 构建 OpenClaw 龙虾沙箱镜像
125-
clawsandbox create <N> # 创建 N 个龙虾实例
116+
clawsandbox create <N> # 创建 N 个龙虾实例(自动拉取镜像)
126117
clawsandbox list # 列出所有实例及状态
127118
clawsandbox desktop <name> # 在浏览器中打开实例桌面
128119
clawsandbox start <name|all> # 启动已停止的实例
@@ -132,9 +123,12 @@ clawsandbox logs <name> [-f] # 查看实例日志
132123
clawsandbox destroy <name|all> # 销毁实例(默认保留数据)
133124
clawsandbox destroy --purge <name|all> # 销毁实例并删除数据
134125
clawsandbox dashboard serve # 启动 Web 仪表盘
126+
clawsandbox dashboard stop # 停止 Web 仪表盘
127+
clawsandbox dashboard restart # 重启 Web 仪表盘
135128
clawsandbox dashboard open # 在浏览器中打开仪表盘
136-
clawsandbox config # 显示当前配置
137-
clawsandbox version # 查看版本信息
129+
clawsandbox build # 本地构建镜像(离线或自定义场景)
130+
clawsandbox config # 显示当前配置
131+
clawsandbox version # 查看版本信息
138132
```
139133

140134
## 资源占用参考

docs/SYSTEM_DESIGN.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,3 +457,69 @@ clawsandbox list
457457
docker stats claw-1 claw-2
458458
# → Confirm memory stays within memory_limit
459459
```
460+
461+
## 11. Version Management
462+
463+
A single `git tag` locks both the CLI binary and Docker image to the same version, with automated publishing to GitHub Release and GHCR.
464+
465+
### Architecture
466+
467+
```
468+
git tag v0.1.0 && git push origin v0.1.0
469+
470+
471+
GitHub Actions
472+
┌──────────────────┬──────────────────────┐
473+
│ GoReleaser │ Docker Build+Push │
474+
│ CLI binaries x4 │ ghcr.io image │
475+
│ (darwin/linux │ :0.1.0 + :latest │
476+
│ x amd64/arm64) │ │
477+
└────────┬─────────┴──────────┬─────────────┘
478+
▼ ▼
479+
GitHub Release ghcr.io/weiyong1024/clawsandbox
480+
```
481+
482+
### Shared version package (`internal/version/`)
483+
484+
Version metadata (`Version`, `GitCommit`, `BuildDate`) is injected at build time via ldflags into `internal/version`. Both `internal/cli/version.go` and `internal/config/config.go` import from this shared package.
485+
486+
The `ImageTag()` helper derives the Docker image tag from the CLI version:
487+
- Release build (e.g. `v0.1.0`): returns `0.1.0`
488+
- Dev build (no tag): returns `latest`
489+
490+
### Image naming and tagging
491+
492+
- **Registry**: `ghcr.io/weiyong1024/clawsandbox`
493+
- **Default tag**: determined at runtime via `version.ImageTag()`
494+
- `clawsandbox build` tags the image with both `:<version>` and `:latest`
495+
- `clawsandbox create` uses `:<version>` to ensure CLI-image version consistency
496+
497+
### Auto-pull on create
498+
499+
When `clawsandbox create` (or the Dashboard's create action) finds the image missing locally, it automatically attempts `docker pull` from GHCR before failing. Users with internet access never need to run `clawsandbox build`.
500+
501+
```
502+
image not found locally
503+
→ docker pull ghcr.io/weiyong1024/clawsandbox:0.1.0
504+
→ success? proceed with create
505+
→ fail? suggest `clawsandbox build`
506+
```
507+
508+
### CI pipeline (`.github/workflows/release.yml`)
509+
510+
Triggered on `v*` tag push. Two parallel jobs:
511+
512+
| Job | Tool | Output |
513+
|-----|------|--------|
514+
| `release` | GoReleaser | 4 CLI binaries → GitHub Release |
515+
| `docker` | docker/build-push-action | Image → `ghcr.io/weiyong1024/clawsandbox:<version>` + `:latest` |
516+
517+
### Release workflow (for maintainers)
518+
519+
```bash
520+
git checkout main
521+
git pull origin main
522+
git tag v0.1.0
523+
git push origin v0.1.0
524+
# CI handles the rest
525+
```

docs/SYSTEM_DESIGN.zh-CN.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,3 +469,69 @@ clawsandbox list
469469
docker stats lobster-1 lobster-2
470470
# → 确认内存在 memory_limit 之内
471471
```
472+
473+
## 11. 版本管理
474+
475+
一次 `git tag` 同时锁定 CLI 和 Docker 镜像的版本,自动发布到 GitHub Release 和 GHCR。
476+
477+
### 架构
478+
479+
```
480+
git tag v0.1.0 && git push origin v0.1.0
481+
482+
483+
GitHub Actions
484+
┌──────────────────┬──────────────────────┐
485+
│ GoReleaser │ Docker Build+Push │
486+
│ CLI 二进制 x4 │ ghcr.io 镜像 │
487+
│ (darwin/linux │ :0.1.0 + :latest │
488+
│ x amd64/arm64) │ │
489+
└────────┬─────────┴──────────┬─────────────┘
490+
▼ ▼
491+
GitHub Release ghcr.io/weiyong1024/clawsandbox
492+
```
493+
494+
### 共享版本包 (`internal/version/`)
495+
496+
版本元数据(`Version``GitCommit``BuildDate`)在构建时通过 ldflags 注入到 `internal/version``internal/cli/version.go``internal/config/config.go` 均从此包导入。
497+
498+
`ImageTag()` 函数根据 CLI 版本推导 Docker 镜像标签:
499+
- release 版本(如 `v0.1.0`):返回 `0.1.0`
500+
- dev 版本(无标签):返回 `latest`
501+
502+
### 镜像命名与标签
503+
504+
- **仓库地址**`ghcr.io/weiyong1024/clawsandbox`
505+
- **默认标签**:运行时通过 `version.ImageTag()` 动态确定
506+
- `clawsandbox build` 同时打 `:<version>``:latest` 两个标签
507+
- `clawsandbox create` 使用 `:<version>` 确保 CLI 与镜像版本一致
508+
509+
### 自动拉取镜像
510+
511+
`clawsandbox create`(或仪表盘的创建接口)发现本地没有镜像时,自动尝试从 GHCR 拉取。联网用户无需执行 `clawsandbox build`
512+
513+
```
514+
本地未找到镜像
515+
→ docker pull ghcr.io/weiyong1024/clawsandbox:0.1.0
516+
→ 成功?继续创建
517+
→ 失败?提示用户执行 clawsandbox build
518+
```
519+
520+
### CI 流水线 (`.github/workflows/release.yml`)
521+
522+
触发条件:`v*` 标签推送。两个并行任务:
523+
524+
| 任务 | 工具 | 产出 |
525+
|------|------|------|
526+
| `release` | GoReleaser | 4 个平台的 CLI 二进制 → GitHub Release |
527+
| `docker` | docker/build-push-action | 镜像 → `ghcr.io/weiyong1024/clawsandbox:<version>` + `:latest` |
528+
529+
### 发版流程(维护者操作)
530+
531+
```bash
532+
git checkout main
533+
git pull origin main
534+
git tag v0.1.0
535+
git push origin v0.1.0
536+
# CI 自动完成剩余工作
537+
```

internal/cli/build.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ import (
88

99
"github.com/weiyong1024/clawsandbox/internal/config"
1010
"github.com/weiyong1024/clawsandbox/internal/container"
11+
"github.com/weiyong1024/clawsandbox/internal/version"
1112
)
1213

1314
var buildCmd = &cobra.Command{
1415
Use: "build",
1516
Short: "Build the OpenClaw sandbox Docker image",
16-
Long: `Build the clawsandbox/openclaw Docker image locally.
17-
This is required once before running 'clawsandbox create'.
18-
The build downloads ~1.4 GB and may take several minutes.`,
17+
Long: `Build the OpenClaw sandbox Docker image locally.
18+
This is only needed for offline use or customization.
19+
When connected to the internet, 'clawsandbox create' auto-pulls the
20+
pre-built image from GHCR.`,
1921
Example: " clawsandbox build",
2022
RunE: runBuild,
2123
}
@@ -38,6 +40,16 @@ func runBuild(cmd *cobra.Command, args []string) error {
3840
return fmt.Errorf("build failed: %w", err)
3941
}
4042

43+
// Also tag as :latest when building a versioned image
44+
if version.ImageTag() != "latest" {
45+
latestRef := fmt.Sprintf("%s:latest", cfg.Image.Name)
46+
if err := container.TagImage(cli, imageRef, cfg.Image.Name, "latest"); err != nil {
47+
fmt.Fprintf(os.Stderr, "Warning: failed to tag as %s: %v\n", latestRef, err)
48+
} else {
49+
fmt.Printf("Also tagged as %s\n", latestRef)
50+
}
51+
}
52+
4153
fmt.Printf("\nImage %s built successfully.\n", imageRef)
4254
fmt.Println("Run 'clawsandbox create <N>' to deploy your fleet.")
4355
return nil

internal/cli/create.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,17 @@ func runCreate(cmd *cobra.Command, args []string) error {
3939
return err
4040
}
4141

42-
// Check image exists
42+
// Check image exists; attempt auto-pull from GHCR if missing
4343
exists, err := container.ImageExists(cli, cfg.ImageRef())
4444
if err != nil {
4545
return err
4646
}
4747
if !exists {
48-
return fmt.Errorf("image %s not found\nRun 'clawsandbox build' first", cfg.ImageRef())
48+
fmt.Printf("Image %s not found locally, pulling from registry...\n", cfg.ImageRef())
49+
if pullErr := container.PullImage(cli, cfg.Image.Name, cfg.Image.Tag, os.Stdout); pullErr != nil {
50+
return fmt.Errorf("image %s not found locally and pull failed: %v\nRun 'clawsandbox build' to build it manually", cfg.ImageRef(), pullErr)
51+
}
52+
fmt.Println("Image pulled successfully.")
4953
}
5054

5155
// Ensure network

internal/cli/version.go

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,8 @@ import (
55
"runtime"
66

77
"github.com/spf13/cobra"
8-
)
98

10-
var (
11-
// Injected at build time via ldflags. See Makefile / .goreleaser.yml.
12-
Version = "dev"
13-
GitCommit = "unknown"
14-
BuildDate = "unknown"
9+
"github.com/weiyong1024/clawsandbox/internal/version"
1510
)
1611

1712
var versionShort bool
@@ -21,12 +16,12 @@ var versionCmd = &cobra.Command{
2116
Short: "Print the version of ClawSandbox",
2217
Run: func(cmd *cobra.Command, args []string) {
2318
if versionShort {
24-
fmt.Println(Version)
19+
fmt.Println(version.Version)
2520
return
2621
}
27-
fmt.Printf("clawsandbox %s\n", Version)
28-
fmt.Printf(" commit: %s\n", GitCommit)
29-
fmt.Printf(" built: %s\n", BuildDate)
22+
fmt.Printf("clawsandbox %s\n", version.Version)
23+
fmt.Printf(" commit: %s\n", version.GitCommit)
24+
fmt.Printf(" built: %s\n", version.BuildDate)
3025
fmt.Printf(" go: %s\n", runtime.Version())
3126
fmt.Printf(" platform: %s/%s\n", runtime.GOOS, runtime.GOARCH)
3227
},

0 commit comments

Comments
 (0)