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/.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/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/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/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/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/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/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/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]; 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 }