From 6b54363ba5bb2adb7e3f92d1daaaa99bbf9f2c23 Mon Sep 17 00:00:00 2001 From: CMGS Date: Sat, 7 Nov 2026 22:41:30 +0800 Subject: [PATCH 1/2] docs, silkd, images: match the docs and comments to git on every lane, the fence admission refusal and silkd as the claim readiness signal Push is no longer the only way in on the no-network lane, net_verb has no egress guard, the BRIDGE smoke step no longer checks lane detection, Admission refusals lists the fence cause cluster.md links to, has_egress has only an internal caller, and cocoon-agent is cocoon's VM readiness signal, not the sandbox's. --- .github/workflows/images.yml | 9 ++------- docs/deploy.md | 6 +++--- docs/sandboxd-api.md | 2 ++ docs/sdk-python.md | 6 +++--- docs/sdk.md | 5 +++-- os-image/android/silkd.rc | 2 +- os-image/base/install-agent.sh | 4 ++-- scripts/sandboxd-e2e.sh | 10 ++++------ silkd/src/git.rs | 2 +- silkd/src/net.rs | 20 ++++++++++---------- 10 files changed, 31 insertions(+), 35 deletions(-) diff --git a/.github/workflows/images.yml b/.github/workflows/images.yml index 658255746..9528add2b 100644 --- a/.github/workflows/images.yml +++ b/.github/workflows/images.yml @@ -1,10 +1,5 @@ -# The image chain's single entry point: one push runs boot and silkd (only -# the changed ones) and then os-images, in order, so the build is -# deterministic. Previously build-os-images was triggered independently by -# each carrier's workflow_run and GitHub's concurrency queue kept only the -# newest pending run — which carrier actually built was a race, and a run -# could bake a stale carrier. Here os-images runs once, after both carriers, -# and bases FROM their sha-pinned refs. +# The image chain's single entry point: one push builds the changed carriers +# (boot, silkd), then os-images once, FROM their sha-pinned refs. name: images on: diff --git a/docs/deploy.md b/docs/deploy.md index 9a856723c..a32b91684 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -1181,9 +1181,9 @@ reap → restart reconcile): TEMPLATE=rt:24.04 scripts/sandboxd-e2e.sh ``` -Set `BRIDGE=` to include the egress lane: it adds an egress pool and the -lane-detect check. A plain `ip link add br0 type bridge` with no uplink is -enough, since the check needs a NIC, not a network. On a node without a Go +Set `BRIDGE=` to include the egress lane: it adds an egress pool and a +smoke step that claims from it. A plain `ip link add br0 type bridge` with no +uplink is enough, since the step needs a NIC, not a network. On a node without a Go toolchain, point `SANDBOXD_BIN`, `DEMO_BIN`, and `SMOKE_BIN` at prebuilt binaries. diff --git a/docs/sandboxd-api.md b/docs/sandboxd-api.md index 810ce5c35..c1c1025a1 100644 --- a/docs/sandboxd-api.md +++ b/docs/sandboxd-api.md @@ -54,6 +54,8 @@ capacity, with the pool state unharmed. The causes: ([`POST /v1/cordon`](#post-v1cordon)) - the disk is under `disk_reserve` - memory is over `memory_limit` +- the node is [fenced](cluster.md#running-claims-across-node-loss) and the + claim's policy pool has `replicate_seconds` On a cluster, a claim without `volumes`, `no_redirect` or `require_promoted` that meets one of these redirects to a peer with warm capacity or, failing diff --git a/docs/sdk-python.md b/docs/sdk-python.md index 7854a6692..476a36fb3 100644 --- a/docs/sdk-python.md +++ b/docs/sdk-python.md @@ -52,7 +52,7 @@ def main() -> None: with client.new("node:24.04", size="medium", ttl_seconds=600) as sb: print(f"claimed {sb.id} on {sb.owner}") - sb.push("/work", project_tar()) # only ingestion path on the no-network lane + sb.push("/work", project_tar()) # needs no egress policy try: print(sb.exec("sh", "-c", "cd /work && make build 2>&1"), end="") except ExitError as e: @@ -477,8 +477,8 @@ sb.push("/work", tar_bytes) # extract a tar stream under /work (atomic) tar = sb.pull("/work") # /work back as tar bytes ``` -`push` is atomic against a truncated stream and the only project-ingestion -path on the no-network lane. +`push` is atomic against a truncated stream and needs no egress policy, so it +brings a project into a no-network sandbox whose pool allows no git host. ## Search diff --git a/docs/sdk.md b/docs/sdk.md index ce0e30892..f54f7e1cd 100644 --- a/docs/sdk.md +++ b/docs/sdk.md @@ -50,7 +50,7 @@ func main() { defer sb.Close() fmt.Printf("claimed %s on %s\n", sb.ID, sb.Owner()) - // Push is the only ingestion path on the no-network lane. + // Push needs no egress policy, so it works on the no-network lane. if err := sb.Push(ctx, "/work", projectTar()); err != nil { log.Fatal(err) } @@ -607,7 +607,8 @@ err = sb.Push(ctx, "/work", tarReader) // extract a tar stream under /work err = sb.Pull(ctx, "/work", tarWriter) // stream /work back as a tar ``` -`Push` is the only project-ingestion path on the no-network lane. +`Push` needs no egress policy, so it brings a project into a no-network +sandbox whose pool allows no git host. ## Search diff --git a/os-image/android/silkd.rc b/os-image/android/silkd.rc index ece0a61ac..c37239e29 100644 --- a/os-image/android/silkd.rc +++ b/os-image/android/silkd.rc @@ -11,7 +11,7 @@ service silkd /system/bin/silkd group root setenv SILKD_PORT 2048 setenv SHELL /system/bin/sh - # Same relay URLs as the base:24.04 unit; forwarded into execs only on the no-NIC lane. + # Same relay URLs as the base:24.04 unit; forwarded into execs only where nothing routes directly. setenv http_proxy http://127.0.0.1:3128 setenv https_proxy http://127.0.0.1:3128 setenv no_proxy localhost,127.0.0.1,::1 diff --git a/os-image/base/install-agent.sh b/os-image/base/install-agent.sh index 11d2fe7b4..6fc34e407 100755 --- a/os-image/base/install-agent.sh +++ b/os-image/base/install-agent.sh @@ -2,7 +2,7 @@ # Bake cocoon-agent (vsock exec daemon) into a sandbox image. # Mirrors cocoon/os-image/ubuntu/install-agent.sh with two deltas: # - the unit starts at sysinit (DefaultDependencies=no): the agent listening -# on vsock IS the sandbox readiness signal, so it must not wait for +# on vsock is cocoon's VM readiness signal, so it must not wait for # network or multi-user; the sandbox kernel builds vsock in (no modprobe). # - sshd stays socket-activated (Dockerfile enables ssh.socket), so there is # no `systemctl enable ssh` here. @@ -35,7 +35,7 @@ cat > /etc/systemd/system/cocoon-agent.service <<'EOF' [Unit] Description=Cocoon agent (vsock command exec) Documentation=https://github.com/cocoonstack/cocoon-agent -# Readiness signal for the whole sandbox: start as early as systemd allows. +# cocoon's VM readiness signal: start as early as systemd allows. # No After= at all: sandbox-init hands over a fully-assembled rootfs before # PID 1 runs and /dev/vsock is devtmpfs — measured, waiting for # local-fs.target cost ~140ms of pure ordering delay on the critical chain. diff --git a/scripts/sandboxd-e2e.sh b/scripts/sandboxd-e2e.sh index 00b0f5e0a..cd58fe046 100755 --- a/scripts/sandboxd-e2e.sh +++ b/scripts/sandboxd-e2e.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Bare-metal e2e for sandboxd v0: warm pool, claim tiers, reap, reconcile. +# Bare-metal e2e for sandboxd: warm pool, claim tiers, reap, reconcile. # Needs a silkd-baked template (os-image/) and a cocoon root no other sandboxd serves; cleanup removes every sbx-* VM. set -euo pipefail @@ -109,10 +109,8 @@ else fi fi -# BRIDGE (optional) attaches the node to an egress bridge, adds a small -# egress pool, and turns on the smoke's egress-lane check; lane detection -# needs a NIC, not connectivity, so a plain `ip link add ... type bridge` -# with no uplink is enough. +# BRIDGE (optional) adds a small egress pool and the smoke's egress-lane step; +# a bridge with no uplink is enough. BRIDGE_LINE="" EGRESS_POOL="" if [[ -n ${BRIDGE:-} ]]; then @@ -179,7 +177,7 @@ else "$DATA/demo" -addr "$ADDR" -token "$TOKEN" -template "$TEMPLATE" -n 3 fi -echo "== v2 smoke: files/session/find/replace/watch/git/pty through the relay" +echo "== smoke: files/session/find/replace/watch/git/pty through the relay" # LSP_TEMPLATE (optional, a python-flavor ref) adds the LSP broker step. "$DATA/smoke" -addr "$ADDR" -token "$TOKEN" -template "$TEMPLATE" ${BRIDGE:+-egress} ${LSP_TEMPLATE:+-lsp-template "$LSP_TEMPLATE"} diff --git a/silkd/src/git.rs b/silkd/src/git.rs index ca846fa25..9779dbfd5 100644 --- a/silkd/src/git.rs +++ b/silkd/src/git.rs @@ -85,7 +85,7 @@ pub async fn commit( proto::write_frame(w, &Response::GitCommitResult { hash }).await } -/// Runs `push` or `pull` on the current branch behind the egress guard. +/// Runs `push` or `pull` on the current branch. pub async fn net_verb( w: &mut W, path: &str, diff --git a/silkd/src/net.rs b/silkd/src/net.rs index e05ce0735..f211a1cdc 100644 --- a/silkd/src/net.rs +++ b/silkd/src/net.rs @@ -24,16 +24,6 @@ static SILKD_NET: LazyLock = LazyLock::new(|| match std::env::var("SILKD_NET _ => -1, }); -/// Reports whether the guest can reach a network; only a `device`-backed interface counts, since the kernel auto-creates virtual tunnels. -pub fn has_egress() -> bool { - match *SILKD_NET { - 0 => return false, - 1 => return true, - _ => {} - } - *DEVICE_BACKED -} - /// Reports whether execs route directly: a NIC the host did not mark as relayed. pub fn routes_directly() -> bool { if !has_egress() { @@ -49,6 +39,16 @@ pub fn routes_directly() -> bool { !relay } +/// Reports whether the guest can reach a network; only a `device`-backed interface counts, since the kernel auto-creates virtual tunnels. +fn has_egress() -> bool { + match *SILKD_NET { + 0 => return false, + 1 => return true, + _ => {} + } + *DEVICE_BACKED +} + /// Reads the lane file: `Some(true)` for relay, `Some(false)` for direct, `None` while the host has not written it. fn lane_verdict(path: &str) -> Option { let mut buf = [0u8; 8]; From ff1941c4dfe505a4cdfb90596e5cf8f2593c1718 Mon Sep 17 00:00:00 2001 From: CMGS Date: Sat, 7 Nov 2026 22:43:57 +0800 Subject: [PATCH 2/2] review: errors.New for fixed messages, one tunnet flag block, config.Field, tighter comments and go.mod layout, a set in tun Confirm, no dead proxy check, test fixes Fixed error texts take errors.New; the TCP flag constants join packet.go; config.ConfigField drops its stutter; the go.mod pin note and the silkd Dockerfile header shrink; e2e/go.mod lists direct requires first with one replace block; Confirm looks confirmed claims up in a set instead of a slice scan; DropTunnels drops a nil proxy check ArmProxy makes impossible; a duplicate refill test goes, TestValidateSizes covers 2xlarge, testCAOnly is a helper. --- .gitignore | 6 +++--- boot/init/src/boot.rs | 2 +- e2e/go.mod | 15 +++++++-------- mcp/server.go | 2 +- sandboxd/ca.go | 4 ++-- sandboxd/config/config.go | 6 +++--- sandboxd/egress/ca_test.go | 1 + sandboxd/outbound/outbound.go | 2 +- sandboxd/pool/archive_test.go | 4 ++-- sandboxd/pool/pool_test.go | 14 -------------- sandboxd/tunnet/gro.go | 6 +----- sandboxd/tunnet/packet.go | 3 +++ sandboxd/tunnet/pg/pg.go | 9 ++++++++- sandboxd/tunnet/segment.go | 3 --- sandboxd/types/types_test.go | 1 + sdk/go/endpoint.go | 7 ++++--- sdk/go/go.mod | 4 +--- sdk/go/lsp.go | 4 ++-- sdk/go/proc.go | 6 +++--- sdk/go/sandbox.go | 2 +- sdk/go/upgrade.go | 3 ++- silkd/Dockerfile | 12 +++--------- silkd/src/server.rs | 4 ++-- 23 files changed, 52 insertions(+), 68 deletions(-) diff --git a/.gitignore b/.gitignore index 5444b30a7..79112ac1c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,13 @@ bin/ dist/ +dist-extra/ boot/init/target/ silkd/target/ e2e/demo e2e/guestserver e2e/portsmoke +e2e/pullbench +e2e/rpcbench e2e/smoke *.log .DS_Store @@ -16,6 +19,3 @@ __pycache__/ *.pyc *.egg-info/ .pytest_cache/ -e2e/pullbench -e2e/rpcbench -dist-extra/ diff --git a/boot/init/src/boot.rs b/boot/init/src/boot.rs index 2ea72e700..8a79da2e6 100644 --- a/boot/init/src/boot.rs +++ b/boot/init/src/boot.rs @@ -238,7 +238,7 @@ fn scan_serials(ids: &[&str], found: &mut [Option]) { } } -// cocoon's Firecracker lane names the device node itself; there is no virtio serial to match +/// cocoon's Firecracker lane names the device node itself; there is no virtio serial to match. fn record_devices(ids: &[&str], found: &mut [Option]) { for (slot, id) in found.iter_mut().zip(ids) { if slot.is_none() && id.starts_with("/dev/") && Path::new(id).exists() { diff --git a/e2e/go.mod b/e2e/go.mod index 12eba6a5f..8745abf26 100644 --- a/e2e/go.mod +++ b/e2e/go.mod @@ -2,6 +2,12 @@ module github.com/cocoonstack/sandbox/e2e go 1.27.1 +require ( + github.com/cocoonstack/sandbox/protocol/wire v0.1.24 + github.com/cocoonstack/sandbox/sandboxd v0.0.0 + github.com/cocoonstack/sandbox/sdk/go v0.0.0 +) + require ( github.com/cockroachdb/errors v1.14.0 // indirect github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect @@ -34,14 +40,7 @@ require ( ) replace ( + github.com/cocoonstack/sandbox/protocol/wire => ../protocol/wire github.com/cocoonstack/sandbox/sandboxd => ../sandboxd github.com/cocoonstack/sandbox/sdk/go => ../sdk/go ) - -require ( - github.com/cocoonstack/sandbox/protocol/wire v0.1.24 - github.com/cocoonstack/sandbox/sandboxd v0.0.0 - github.com/cocoonstack/sandbox/sdk/go v0.0.0 -) - -replace github.com/cocoonstack/sandbox/protocol/wire => ../protocol/wire diff --git a/mcp/server.go b/mcp/server.go index 2d8423b71..aa939751d 100644 --- a/mcp/server.go +++ b/mcp/server.go @@ -229,7 +229,7 @@ func (s *server) checkpointArg(raw jsontext.Value) (*sandbox.Checkpoint, error) return ckpt, nil } if args.CheckpointID == "" { - return nil, fmt.Errorf("checkpoint_id is required") + return nil, errors.New("checkpoint_id is required") } return s.client.Checkpoint(args.CheckpointID), nil } diff --git a/sandboxd/ca.go b/sandboxd/ca.go index 3d388bcf1..0371042ef 100644 --- a/sandboxd/ca.go +++ b/sandboxd/ca.go @@ -12,7 +12,7 @@ import ( func runCA(args []string) error { if len(args) == 0 { - return fmt.Errorf("usage: sandboxd ca {init|issue-intermediate}") + return errors.New("usage: sandboxd ca {init|issue-intermediate}") } switch args[0] { case "init": @@ -54,7 +54,7 @@ func caIssueIntermediate(args []string) error { return err } if *rootCert == "" || *rootKey == "" || *node == "" { - return fmt.Errorf("-root-cert, -root-key, and -node are required") + return errors.New("-root-cert, -root-key, and -node are required") } rc, err := os.ReadFile(*rootCert) if err != nil { diff --git a/sandboxd/config/config.go b/sandboxd/config/config.go index af5ae49ef..ffd8e94a1 100644 --- a/sandboxd/config/config.go +++ b/sandboxd/config/config.go @@ -128,14 +128,14 @@ func (s *PoolSpec) ValidateLimits() error { return validateArchiveWindow(s.IdleHibernateSeconds, s.ArchiveAfterSeconds, s.ArchiveDeleteAfterSeconds) } -type ConfigField struct { +type Field struct { Name string Value reflect.Value } // ConfigOwned lists s's config-only fields; PUT /v1/pools refuses them, the seed hash drops them, and reload diffs them. -func (s *PoolSpec) ConfigOwned() []ConfigField { - return []ConfigField{ +func (s *PoolSpec) ConfigOwned() []Field { + return []Field{ {"egress", reflect.ValueOf(&s.Egress).Elem()}, {"warmup", reflect.ValueOf(&s.Warmup).Elem()}, {"capture_trim", reflect.ValueOf(&s.CaptureTrim).Elem()}, diff --git a/sandboxd/egress/ca_test.go b/sandboxd/egress/ca_test.go index 107c8ac73..687f9c1ee 100644 --- a/sandboxd/egress/ca_test.go +++ b/sandboxd/egress/ca_test.go @@ -184,6 +184,7 @@ func testCA(tb testing.TB) (*CA, []byte) { } func testCAOnly(tb testing.TB) *CA { + tb.Helper() ca, _ := testCA(tb) return ca } diff --git a/sandboxd/outbound/outbound.go b/sandboxd/outbound/outbound.go index da971f03a..21b659792 100644 --- a/sandboxd/outbound/outbound.go +++ b/sandboxd/outbound/outbound.go @@ -234,7 +234,7 @@ func (h *Host) DropTunnels(ids []string) { var proxies []*egress.Proxy h.mu.Lock() for _, id := range ids { - if d := h.doors[id]; d != nil && d.proxy != nil { + if d := h.doors[id]; d != nil { proxies = append(proxies, d.proxy) } } diff --git a/sandboxd/pool/archive_test.go b/sandboxd/pool/archive_test.go index 8a0a73268..5fae0fba0 100644 --- a/sandboxd/pool/archive_test.go +++ b/sandboxd/pool/archive_test.go @@ -698,7 +698,7 @@ func TestIdleOnceSkipsArchived(t *testing.T) { sweepIdle(t, m) if got := eng.hibernateCount(); got != hibBefore { - t.Errorf("idle sweep hibernated an archived claim: %d→%d", hibBefore, got) + t.Errorf("idle sweep hibernated an archived claim: %d->%d", hibBefore, got) } if archivedCount(m) != 1 { t.Error("idle sweep disturbed the archived claim") @@ -839,7 +839,7 @@ func TestArchiveOnceSkipsInFlight(t *testing.T) { m.archiveOnce(t.Context()) waitFor(t, func() bool { return !m.sweeps.archive.Load() }) if got := eng.exportCount(); got != exportsBefore { - t.Errorf("archiveOnce double-exported an in-flight sandbox: %d→%d", exportsBefore, got) + t.Errorf("archiveOnce double-exported an in-flight sandbox: %d->%d", exportsBefore, got) } if archivedCount(m) != 0 { t.Error("archiveOnce archived a sandbox already being archived elsewhere") diff --git a/sandboxd/pool/pool_test.go b/sandboxd/pool/pool_test.go index 5655af078..b7159b529 100644 --- a/sandboxd/pool/pool_test.go +++ b/sandboxd/pool/pool_test.go @@ -379,20 +379,6 @@ func TestReconcileAdoptsLiveClaimsAndDestroysTheRest(t *testing.T) { } } -func TestRefillTopsUpToTarget(t *testing.T) { - synctest.Test(t, func(t *testing.T) { - eng := newFakeEngine() - m := newTestManager(t, eng, config.PoolSpec{PoolKey: testKey, Warm: 2}) - m.pools[testKey].goldenDir = "/goldens/x" - - m.refillOnce(t.Context()) - waitWarm(t, m, 2) - if n := eng.cloneCount(); n != 2 { - t.Errorf("clones=%d, want 2", n) - } - }) -} - func TestSetPoolsGrowShrinkAndDrain(t *testing.T) { synctest.Test(t, func(t *testing.T) { eng := newFakeEngine() diff --git a/sandboxd/tunnet/gro.go b/sandboxd/tunnet/gro.go index 8b9ce6687..b6a5d9a26 100644 --- a/sandboxd/tunnet/gro.go +++ b/sandboxd/tunnet/gro.go @@ -5,11 +5,7 @@ import ( "encoding/binary" ) -const ( - tcpAck = 0x10 - - ipTTL = 8 -) +const ipTTL = 8 type tcpSegment struct { seq uint32 diff --git a/sandboxd/tunnet/packet.go b/sandboxd/tunnet/packet.go index 8bd8c7755..edb71fbc6 100644 --- a/sandboxd/tunnet/packet.go +++ b/sandboxd/tunnet/packet.go @@ -37,6 +37,9 @@ const ( tcpFin = 0x01 tcpSyn = 0x02 tcpRst = 0x04 + tcpPsh = 0x08 + tcpAck = 0x10 + tcpCwr = 0x80 ) var ( diff --git a/sandboxd/tunnet/pg/pg.go b/sandboxd/tunnet/pg/pg.go index 32002421f..de2f877a5 100644 --- a/sandboxd/tunnet/pg/pg.go +++ b/sandboxd/tunnet/pg/pg.go @@ -109,7 +109,14 @@ func (s *Store) Confirm(ctx context.Context, held map[string]netip.Addr) ([]stri if err != nil { return nil, down(err) } - lost := slices.DeleteFunc(claims, func(c string) bool { return slices.Contains(confirmed, c) }) + done := make(map[string]struct{}, len(confirmed)) + for _, c := range confirmed { + done[c] = struct{}{} + } + lost := slices.DeleteFunc(claims, func(c string) bool { + _, ok := done[c] + return ok + }) return lost, nil } diff --git a/sandboxd/tunnet/segment.go b/sandboxd/tunnet/segment.go index 64b09cd1f..7baf0323b 100644 --- a/sandboxd/tunnet/segment.go +++ b/sandboxd/tunnet/segment.go @@ -7,9 +7,6 @@ const ( gsoTCPv4 = 1 gsoUDPL4 = 5 gsoECN = 0x80 - - tcpPsh = 0x08 - tcpCwr = 0x80 ) // appendSegments appends frame's packets to out behind hdr, splitting a GSO frame and finishing a partial checksum; it returns out, the length of every datagram but the last, and the count, zero for a dropped frame. diff --git a/sandboxd/types/types_test.go b/sandboxd/types/types_test.go index 442b2513a..a8d3357d2 100644 --- a/sandboxd/types/types_test.go +++ b/sandboxd/types/types_test.go @@ -34,6 +34,7 @@ func TestValidateSizes(t *testing.T) { {SizeMedium4G, true}, {SizeLarge, true}, {SizeXLarge, true}, + {Size2XLarge, true}, {Size("huge"), false}, {Size(""), false}, } { diff --git a/sdk/go/endpoint.go b/sdk/go/endpoint.go index d40bf36f2..2fb0a138a 100644 --- a/sdk/go/endpoint.go +++ b/sdk/go/endpoint.go @@ -3,6 +3,7 @@ package sandbox import ( "cmp" "crypto/tls" + "errors" "fmt" "net/http" "net/url" @@ -16,7 +17,7 @@ func (c *Client) configureTLS() error { tr, ok := cmp.Or(c.hc.Transport, http.DefaultTransport).(*http.Transport) switch { case !ok && c.tlsConfig != nil: - return fmt.Errorf("tls configuration requires an http.Transport") + return errors.New("tls configuration requires an http.Transport") case !ok: c.tlsConfig = &tls.Config{MinVersion: tls.VersionTLS12} case c.tlsConfig == nil && c.hc.Transport != nil: @@ -53,12 +54,12 @@ func endpointURL(addr, scheme string) (*url.URL, error) { } if (u.Scheme != "http" && u.Scheme != httpsScheme) || u.Hostname() == "" || u.User != nil || (u.Path != "" && u.Path != "/") || strings.ContainsAny(addr, "?#") { - return nil, fmt.Errorf("sandboxd endpoint must be an http or https origin") + return nil, errors.New("sandboxd endpoint must be an http or https origin") } if port := u.Port(); port != "" { n, err := strconv.Atoi(port) if err != nil || n < 1 || n > 65535 { - return nil, fmt.Errorf("sandboxd endpoint port must be between 1 and 65535") + return nil, errors.New("sandboxd endpoint port must be between 1 and 65535") } } u.Path = "" diff --git a/sdk/go/go.mod b/sdk/go/go.mod index 78b5cd34d..a3be836f1 100644 --- a/sdk/go/go.mod +++ b/sdk/go/go.mod @@ -4,7 +4,5 @@ go 1.27.1 replace github.com/cocoonstack/sandbox/protocol/wire => ../../protocol/wire -// Downstream consumers ignore the local replace, so this must name a real, -// resolvable version: cut a protocol/wire tag alongside every sdk/go release -// and keep this pinned to it. +// Consumers ignore the replace: pin the protocol/wire tag cut with each sdk/go release. require github.com/cocoonstack/sandbox/protocol/wire v0.1.24 diff --git a/sdk/go/lsp.go b/sdk/go/lsp.go index a4e3d29f5..9d3f84385 100644 --- a/sdk/go/lsp.go +++ b/sdk/go/lsp.go @@ -2,7 +2,7 @@ package sandbox import ( "context" - "fmt" + "errors" "github.com/cocoonstack/sandbox/protocol/wire" ) @@ -31,7 +31,7 @@ func (s *Sandbox) StartLsp(ctx context.Context, language, root string) (*Lsp, er return nil, err } if started.ServerID == "" { - return nil, fmt.Errorf("lsp start: empty server id") + return nil, errors.New("lsp start: empty server id") } return &Lsp{ServerID: started.ServerID, s: s}, nil } diff --git a/sdk/go/proc.go b/sdk/go/proc.go index 7675eed00..911f2e112 100644 --- a/sdk/go/proc.go +++ b/sdk/go/proc.go @@ -2,7 +2,7 @@ package sandbox import ( "context" - "fmt" + "errors" "io" "github.com/cocoonstack/sandbox/protocol/wire" @@ -11,10 +11,10 @@ import ( // Spawn starts cmd detached and returns its guest pid; stdio is ignored and Session must be empty. func (s *Sandbox) Spawn(ctx context.Context, cmd Cmd) (uint32, error) { if len(cmd.Argv) == 0 { - return 0, fmt.Errorf("empty argv") + return 0, errors.New("empty argv") } if cmd.Session != "" { - return 0, fmt.Errorf("spawn does not support session") + return 0, errors.New("spawn does not support session") } req := &wire.Exec{Argv: cmd.Argv, Cwd: cmd.Cwd, Env: cmd.Env, User: cmd.User, Detach: true} started, err := s.oneShotRPC[wire.Started](ctx, req) diff --git a/sdk/go/sandbox.go b/sdk/go/sandbox.go index 42ab1de38..8a3388d3a 100644 --- a/sdk/go/sandbox.go +++ b/sdk/go/sandbox.go @@ -149,7 +149,7 @@ func (s *Sandbox) Exec(ctx context.Context, argv ...string) (string, error) { // Run executes cmd in the sandbox, streaming stdio over one relayed silkd connection, and returns the exit code; a ctx that ends first kills the command. func (s *Sandbox) Run(ctx context.Context, cmd Cmd) (int, error) { if len(cmd.Argv) == 0 { - return 0, fmt.Errorf("empty argv") + return 0, errors.New("empty argv") } code, err := s.run(ctx, cmd) if s.rebind(ctx, err) && cmd.Stdin == nil { diff --git a/sdk/go/upgrade.go b/sdk/go/upgrade.go index 854121281..69467d5d1 100644 --- a/sdk/go/upgrade.go +++ b/sdk/go/upgrade.go @@ -5,6 +5,7 @@ import ( "cmp" "context" "crypto/tls" + "errors" "fmt" "net" "net/http" @@ -60,7 +61,7 @@ func (u *upgradedConn) handshake() error { func (c *Client) dialAgent(ctx context.Context, addr, id, token string) (*upgradedConn, error) { if strings.ContainsAny(id, "\r\n\x00") || strings.ContainsAny(token, "\r\n\x00") { - return nil, fmt.Errorf("agent upgrade: id or token contains a control character") + return nil, errors.New("agent upgrade: id or token contains a control character") } scheme, authority, target := agentEndpoint(addr, c.scheme) d := net.Dialer{Timeout: agentDialTimeout} diff --git a/silkd/Dockerfile b/silkd/Dockerfile index 26ee35505..0ec35cc0f 100644 --- a/silkd/Dockerfile +++ b/silkd/Dockerfile @@ -1,12 +1,6 @@ -# Builds silkd and ships it as a binaries-only carrier image, the same shape -# as the boot artifact: base images COPY --from this image rather than -# rebuilding silkd, so a silkd change rebuilds only this artifact and the -# images gating workflow refolds it into the os-images. -# Two binaries: /silkd is glibc-dynamic (Debian's glibc is older than Ubuntu -# 24.04's, so the binary is forward-compatible into the base image); -# /silkd-static is musl-static for guests without glibc, such as the android -# flavor's non-systemd userspace. Built per-platform (amd64/arm64) on native -# runners; TARGETARCH picks the musl target triple. +# Binaries-only carrier the os-images COPY --from: /silkd is glibc-dynamic +# (Debian's glibc is older than Ubuntu 24.04's), /silkd-static is musl-static +# for guests without glibc, such as android. TARGETARCH picks the musl triple. FROM rust:1.99.1-slim-bookworm@sha256:24707e70a4f1e23d9a7b25202ec43126d28a52294b96aad5c670df1fd1f1d400 AS build ARG TARGETARCH WORKDIR /silkd diff --git a/silkd/src/server.rs b/silkd/src/server.rs index 56771b903..a6fcec86c 100644 --- a/silkd/src/server.rs +++ b/silkd/src/server.rs @@ -8,7 +8,7 @@ use tokio::sync::mpsc; use tokio::task::JoinHandle; use crate::proc::{Chunk, Table, write_chunk}; -use crate::proto::{self, ErrorKind, ExecReq, ProcInfo, Request, Response}; +use crate::proto::{self, ErrorKind, ExecReq, Request, Response}; use crate::{exec, find, forward, fs, git, lsp, pty, session, sysutil, tree, tun, watch}; type Parsed = serde_json::Result; @@ -261,7 +261,7 @@ impl State { } async fn ps(&self, w: &mut W) -> std::io::Result<()> { - let mut procs: Vec = self.table.list(); + let mut procs = self.table.list(); procs.sort_by_key(|p| p.pid); proto::write_frame(w, &Response::Procs { procs }).await }