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
4 changes: 3 additions & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ jobs:
cache: true

- name: Build
run: make build
run: |
make build
make build-windows

- name: Test
run: make test
25 changes: 21 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ jobs:
goarch: amd64
- goos: darwin
goarch: arm64
- goos: windows
goarch: amd64
- goos: windows
goarch: arm64

steps:
- name: Checkout
Expand All @@ -45,18 +49,31 @@ jobs:
GOARCH: ${{ matrix.goarch }}
run: |
make clean
make build
if [ "$GOOS" = "windows" ]; then
make "build-windows-${GOARCH}"
else
make build
fi

- name: Package
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
run: |
mkdir -p dist
tar -C bin -czf "dist/ucloud-sandbox-cli_${{ matrix.goos }}_${{ matrix.goarch }}.tar.gz" ucloud-sandbox-cli
if [ "$GOOS" = "windows" ]; then
zip -j "dist/ucloud-sandbox-cli_${GOOS}_${GOARCH}.zip" "bin/windows/${GOARCH}/ucloud-sandbox-cli.exe"
else
tar -C bin -czf "dist/ucloud-sandbox-cli_${GOOS}_${GOARCH}.tar.gz" ucloud-sandbox-cli
fi

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ucloud-sandbox-cli-${{ matrix.goos }}-${{ matrix.goarch }}
path: dist/*.tar.gz
path: |
dist/*.tar.gz
dist/*.zip
if-no-files-found: error

release:
Expand All @@ -83,7 +100,7 @@ jobs:
GH_REPO: ${{ github.repository }}
TAG_NAME: ${{ github.ref_name }}
run: |
gh release create "${TAG_NAME}" dist/*.tar.gz \
gh release create "${TAG_NAME}" dist/*.tar.gz dist/*.zip \
--draft \
--verify-tag \
--title "${TAG_NAME}" \
Expand Down
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ build:
@mkdir -p bin
CGO_ENABLED=0 go build -ldflags "-X main.Version=$(VERSION) -X main.Commit=$(COMMIT)" -o bin/ucloud-sandbox-cli .

.PHONY: build-windows build-windows-amd64 build-windows-arm64
build-windows: build-windows-amd64 build-windows-arm64

build-windows-amd64: WINDOWS_ARCH := amd64
build-windows-arm64: WINDOWS_ARCH := arm64
build-windows-amd64 build-windows-arm64:
@mkdir -p bin/windows/$(WINDOWS_ARCH)
CGO_ENABLED=0 GOOS=windows GOARCH=$(WINDOWS_ARCH) go build -ldflags "-X main.Version=$(VERSION) -X main.Commit=$(COMMIT)" -o bin/windows/$(WINDOWS_ARCH)/ucloud-sandbox-cli.exe .

.PHONY: test
test:
CGO_ENABLED=0 go test -v ./...
Expand Down
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,24 @@ npm uninstall -g @ucloud-sdks/ucloud-sandbox-cli

如果您之前从未安装过`ucloud-sandbox-cli`,或者没有使用npm安装过,可以跳过这一步。

### 安装CLI
### 安装 CLI

使用下面的命令安装CLI
Linux 和 macOS 使用

```bash
curl -sS https://raw.githubusercontent.com/ucloud/ucloud-sandbox-cli/main/install.sh | sh
```

安装脚本会要求您确认安装路径(默认为`/usr/local/bin`),直接输入回车可以确认安装,或者您也可以手动输入安装路径。注意请确保安装路径在您的`$PATH`下面以可以直接使用命令行。

Windows 在 PowerShell 中使用:

```powershell
Invoke-RestMethod https://raw.githubusercontent.com/ucloud/ucloud-sandbox-cli/main/install.ps1 | Invoke-Expression
```

Windows 安装脚本会自动选择 amd64 或 arm64 版本,默认安装到`%LOCALAPPDATA%\Programs\ucloud-sandbox-cli`,并将该目录加入用户`PATH`。

## 身份认证与配置

### 环境注入
Expand Down Expand Up @@ -178,4 +186,4 @@ ucloud-sandbox-cli tpl publish --unpublish <template-id>
1. 准备环境:`ucloud-sandbox-cli login`
2. 创建模板:`ucloud-sandbox-cli template init` -> 编写 `Dockerfile`
3. 业务接入:在 SDK 中使用 `Sandbox.create(template='my-agent-env')`
4. 资源回收:`ucloud-sandbox-cli sandbox kill --all`
4. 资源回收:`ucloud-sandbox-cli sandbox kill --all`
17 changes: 3 additions & 14 deletions cmd/sandbox/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import (
"context"
"fmt"
"os"
"os/signal"
"syscall"

"github.com/spf13/cobra"
"github.com/ucloud/ucloud-sandbox-cli/internal/config"
Expand All @@ -18,7 +16,7 @@ func newConnectCmd() *cobra.Command {
Use: "connect <sandbox-id>",
Aliases: []string{"conn"},
Short: "Connect to a running sandbox",
Args: cobra.ExactArgs(1),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := config.Load()
if err != nil {
Expand Down Expand Up @@ -81,17 +79,8 @@ func connectTerminal(ctx context.Context, sbx *sdk.Sandbox) error {
}
}()

// Handle terminal resize.
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGWINCH)
go func() {
for range sigCh {
if c, r, err := term.GetSize(fd); err == nil {
handle.Resize(ctx, sdk.PtySize{Cols: c, Rows: r})
}
}
}()
defer signal.Stop(sigCh)
stopResize := watchTerminalResize(ctx, fd, handle, cols, rows)
defer stopResize()

handle.Wait()
return nil
Expand Down
39 changes: 39 additions & 0 deletions cmd/sandbox/connect_resize_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//go:build darwin || linux

package sandbox

import (
"context"
"os"
"os/signal"
"syscall"

sdk "github.com/ucloud/ucloud-sandbox-sdk-go"
"golang.org/x/term"
)

func watchTerminalResize(ctx context.Context, fd int, handle *sdk.PtyHandle, _, _ int) func() {
sigCh := make(chan os.Signal, 1)
done := make(chan struct{})
signal.Notify(sigCh, syscall.SIGWINCH)

go func() {
for {
select {
case <-ctx.Done():
return
case <-done:
return
case <-sigCh:
if cols, rows, err := term.GetSize(fd); err == nil {
_ = handle.Resize(ctx, sdk.PtySize{Cols: cols, Rows: rows})
}
}
}
}()

return func() {
signal.Stop(sigCh)
close(done)
}
}
38 changes: 38 additions & 0 deletions cmd/sandbox/connect_resize_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//go:build windows

package sandbox

import (
"context"
"time"

sdk "github.com/ucloud/ucloud-sandbox-sdk-go"
"golang.org/x/term"
)

func watchTerminalResize(ctx context.Context, fd int, handle *sdk.PtyHandle, cols, rows int) func() {
done := make(chan struct{})
go func() {
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()

for {
select {
case <-ctx.Done():
return
case <-done:
return
case <-ticker.C:
newCols, newRows, err := term.GetSize(fd)
if err != nil || (newCols == cols && newRows == rows) {
continue
}
if err := handle.Resize(ctx, sdk.PtySize{Cols: newCols, Rows: newRows}); err == nil {
cols, rows = newCols, newRows
}
}
}
}()

return func() { close(done) }
}
165 changes: 165 additions & 0 deletions install.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<#
.SYNOPSIS
Downloads and installs UCloud Sandbox CLI on Windows.

.PARAMETER Version
Release tag to install. Defaults to the latest release.

.PARAMETER InstallDir
Directory where ucloud-sandbox-cli.exe is installed.

.PARAMETER BaseUrl
Base URL of the GitHub releases page.

.EXAMPLE
Invoke-RestMethod https://raw.githubusercontent.com/ucloud/ucloud-sandbox-cli/main/install.ps1 | Invoke-Expression

.EXAMPLE
.\install.ps1 -Version v1.2.3
#>

[CmdletBinding()]
param(
[ValidateNotNullOrEmpty()]
[string]$Version = "latest",

[string]$InstallDir = "",

[ValidateNotNullOrEmpty()]
[string]$BaseUrl = "https://github.com/ucloud/ucloud-sandbox-cli/releases"
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

$BinaryName = "ucloud-sandbox-cli"
$DocsUrl = "https://astraflow.ucloud.cn/docs/agent-sandbox/product/cli"

function Write-Info {
param([string]$Message)
Write-Host "> $Message"
}

function Write-Success {
param([string]$Message)
Write-Host "+ $Message" -ForegroundColor Green
}

function Get-WindowsArchitecture {
$machineArchitecture = if ($env:PROCESSOR_ARCHITEW6432) {
$env:PROCESSOR_ARCHITEW6432
} else {
$env:PROCESSOR_ARCHITECTURE
}

if ([string]::IsNullOrWhiteSpace($machineArchitecture)) {
throw "Unable to detect the Windows architecture."
}

switch ($machineArchitecture.ToUpperInvariant()) {
"AMD64" { return "amd64" }
"ARM64" { return "arm64" }
default { throw "Unsupported Windows architecture: $machineArchitecture. Supported architectures are: amd64, arm64." }
}
}

function Add-InstallDirToPath {
param([string]$Directory)

$normalizedDirectory = [Environment]::ExpandEnvironmentVariables($Directory).TrimEnd("\")
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
$userEntries = @($userPath -split ";" | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
$isInUserPath = $userEntries | Where-Object {
[Environment]::ExpandEnvironmentVariables($_).Trim().TrimEnd("\") -ieq $normalizedDirectory
}

if (-not $isInUserPath) {
$newUserPath = if ([string]::IsNullOrWhiteSpace($userPath)) {
$Directory
} else {
"$Directory;$userPath"
}
[Environment]::SetEnvironmentVariable("Path", $newUserPath, "User")
Write-Info "Added $Directory to the user PATH."
}

$processEntries = @($env:Path -split ";" | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
$isInProcessPath = $processEntries | Where-Object {
[Environment]::ExpandEnvironmentVariables($_).Trim().TrimEnd("\") -ieq $normalizedDirectory
}
if (-not $isInProcessPath) {
$env:Path = "$Directory;$env:Path"
}
}

if ($env:OS -ne "Windows_NT") {
throw "install.ps1 only supports Windows. Use install.sh on Linux or macOS."
}

if ([string]::IsNullOrWhiteSpace($InstallDir)) {
if ([string]::IsNullOrWhiteSpace($env:LOCALAPPDATA)) {
throw "LOCALAPPDATA is not set. Pass -InstallDir with a writable installation directory."
}
$InstallDir = Join-Path $env:LOCALAPPDATA "Programs\ucloud-sandbox-cli"
}
$InstallDir = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($InstallDir)

$architecture = Get-WindowsArchitecture
$assetName = "${BinaryName}_windows_${architecture}.zip"
$releaseBaseUrl = $BaseUrl.TrimEnd("/")
$downloadUrl = if ($Version -eq "latest") {
"$releaseBaseUrl/latest/download/$assetName"
} else {
"$releaseBaseUrl/download/$Version/$assetName"
}
$targetBinary = Join-Path $InstallDir "$BinaryName.exe"
$tempDir = Join-Path ([IO.Path]::GetTempPath()) "$BinaryName-install-$([Guid]::NewGuid().ToString('N'))"
$archivePath = Join-Path $tempDir $assetName
$extractDir = Join-Path $tempDir "extract"

Write-Host ""
Write-Info "Welcome to the $BinaryName installer."
Write-Info "Installer configuration:"
Write-Info " Version: $Version"
Write-Info " OS: windows"
Write-Info " Arch: $architecture"
Write-Info " Install dir: $InstallDir"
Write-Info " Download URL: $downloadUrl"
Write-Host ""

try {
New-Item -ItemType Directory -Force -Path $tempDir, $extractDir, $InstallDir | Out-Null

# Windows PowerShell 5.1 can otherwise negotiate an obsolete TLS version.
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12

Write-Info "Downloading $BinaryName..."
Invoke-WebRequest -Uri $downloadUrl -OutFile $archivePath -UseBasicParsing

Write-Info "Installing $BinaryName to $InstallDir..."
Expand-Archive -LiteralPath $archivePath -DestinationPath $extractDir -Force
$extractedBinary = Join-Path $extractDir "$BinaryName.exe"
if (-not (Test-Path -LiteralPath $extractedBinary -PathType Leaf)) {
throw "Archive did not contain $BinaryName.exe."
}

Copy-Item -LiteralPath $extractedBinary -Destination $targetBinary -Force
Unblock-File -LiteralPath $targetBinary -ErrorAction SilentlyContinue
Add-InstallDirToPath -Directory $InstallDir

Write-Success "$BinaryName installed successfully."
Write-Host ""
Write-Info "Installed binary version:"
& $targetBinary version
if ($LASTEXITCODE -ne 0) {
throw "$BinaryName version exited with code $LASTEXITCODE."
}
} finally {
if (Test-Path -LiteralPath $tempDir) {
Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}

Write-Host ""
Write-Info "Documentation: $DocsUrl"
Write-Info "Run '$BinaryName login' first, then start using the CLI."
Loading
Loading