Skip to content

Add the boxes provider: hosted microVMs, onctl login, ssh through the provider - #1168

Merged
cdalar merged 1 commit into
mainfrom
feat/boxes-provider
Oct 4, 2026
Merged

cdalar merged 1 commit into
mainfrom
feat/boxes-provider

Conversation

@cdalar

@cdalar cdalar commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Rollout step 1 of docs/plans/boxes-provider.md: the hosted boxes service (boxctl.io) becomes an onctl provider.

onctl login                                  # once; reads boxctl's saved token if you have one
onctl create -p boxes --type small -a setup.sh
onctl ls -p boxes
onctl ssh -p boxes my-box -- uname -a
onctl pause -p boxes my-box                  # then ssh resumes it automatically
onctl images -p boxes
onctl destroy -p boxes my-box

What's added

  • internal/providerboxes: the API client, ported from boxctl's internal/client. DialPort is the port tunnel as a net.Conn over WebSocket.
  • Token lookup: $ONCTL_BOXES_TOKEN, then ~/.onctl/boxes.json (0600), then boxctl's ~/.boxctl/config.json.
  • pkg/cloud.ProviderBoxes: implements CloudProviderInterface and ImageLister. The service takes no ssh key at create, so Deploy authorizes it through the exec endpoint. SSHInto does too, so boxes made in the dashboard work. A key containing a quote or newline is refused before it reaches a shell.
  • cloud.Dialer and tools.Remote.Dial: a box has no address of its own, so create, ssh and deploy run ssh over the provider's tunnel. A paused box is resumed first.
  • onctl ssh-proxy: relays stdio to a VM port through the provider. onctl ssh uses it as the system ssh's ProxyCommand, and it also works from ~/.ssh/config (Host *.box) for plain ssh, scp, rsync, git and VS Code.
  • onctl login / onctl logout.

Behaviour for -p boxes

  • --image works.
  • --type applies only when given explicitly, because its default is Hetzner's cpx21.
  • --domain is refused, since a box has no public IP.
  • The cloud-init wait is skipped.
  • No onctl.yaml is needed.

Change for every provider: pause's "snapshot saved, server deleted" message now appears for Hetzner only.

The plan's open questions 1, 2 and 5 are settled as proposed: provider ID boxes, onctl login, gorilla/websocket.

Tests

  • The client against httptest: auth header, the 401 → onctl login message, the server's error text, WaitReady, and the tunnel as a byte stream.
  • Credentials lookup order and file permissions.
  • The provider against a fake boxes API: deploy authorizes the key; malformed keys are refused; the full lifecycle; dialing resumes a paused box; ProxyCommand quoting.
  • A real ssh handshake and command over Remote.Dial, against an in-process ssh server.
  • The stdio relay.
  • go test -race -shuffle=on ./... with an empty HOME, go vet and golangci-lint are all clean.

End-to-end against boxctl.io (throwaway boxes, destroyed afterwards):

  • create with an apply script over the tunnel took about 8s;
  • ssh -- cmd works;
  • pause -f, then ssh resumed the box in about 2s;
  • plain ssh -o ProxyCommand='onctl ssh-proxy -p boxes %h' works;
  • destroy works;
  • ls/images list the account's real boxes and catalog.

Next steps (2–5 in the plan): port-forward/sizes/--idle-ttl, onctl claude, the plugin, then retiring boxctl.

🤖 Generated with Claude Code

https://claude.ai/code/session_01U8g3mHkS6JmtYVYc4AjcV4

… provider

Rollout step 1 of docs/plans/boxes-provider.md: the hosted boxes service
(boxctl.io) becomes an onctl provider, -p boxes.

- internal/providerboxes: the API client, ported from boxctl's
  internal/client (list/create/destroy/pause/resume/exec/images, WaitReady,
  and DialPort -- the port tunnel as a net.Conn over WebSocket), plus the
  saved token: $ONCTL_BOXES_TOKEN, then ~/.onctl/boxes.json, then
  boxctl's own ~/.boxctl/config.json so boxctl users needn't log in again.
- pkg/cloud.ProviderBoxes: CloudProviderInterface plus ImageLister. The
  service takes no ssh key at create, so Deploy (and SSHInto, for boxes
  made elsewhere) authorizes it over the exec endpoint.
- cloud.Dialer and tools.Remote.Dial: a box has no address of its own,
  so create/ssh/deploy run their ssh over the provider's tunnel when it
  implements Dialer. A paused box is resumed first.
- onctl ssh-proxy: relays stdio to a VM port through the provider, used
  as the system ssh's ProxyCommand by onctl ssh -- and usable from
  ~/.ssh/config for plain ssh, scp, rsync, git, VS Code.
- onctl login/logout.
- create: --image works for boxes; --type is taken only when given (its
  default is Hetzner's cpx21); --domain is refused (no public IP); the
  cloud-init wait is skipped. boxes needs no onctl.yaml. images lists
  boxes images. pause's 'snapshot saved, server deleted' is now
  Hetzner-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U8g3mHkS6JmtYVYc4AjcV4
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedgolang/​github.com/​gorilla/​websocket@​v1.5.386100100100100

View full report

@cdalar
cdalar enabled auto-merge (squash) October 4, 2026 19:22
@cdalar
cdalar merged commit 1d1f094 into main Oct 4, 2026
12 checks passed
@cdalar
cdalar deleted the feat/boxes-provider branch October 4, 2026 19:28
@codecov

codecov Bot commented Oct 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 55.52941% with 189 lines in your changes missing coverage. Please review.
✅ Project coverage is 38.81%. Comparing base (7055d64) to head (a9101b0).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
pkg/cloud/boxes.go 68.25% 40 Missing ⚠️
cmd/login.go 8.57% 32 Missing ⚠️
internal/providerboxes/client.go 67.03% 30 Missing ⚠️
cmd/root.go 0.00% 17 Missing ⚠️
cmd/sshproxy.go 21.05% 15 Missing ⚠️
internal/providerboxes/wsconn.go 60.52% 15 Missing ⚠️
internal/providerboxes/credentials.go 80.00% 11 Missing ⚠️
cmd/create.go 10.00% 9 Missing ⚠️
cmd/common.go 0.00% 5 Missing ⚠️
internal/tools/remote-run.go 69.23% 4 Missing ⚠️
... and 5 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1168      +/-   ##
==========================================
+ Coverage   36.93%   38.81%   +1.87%     
==========================================
  Files          51       57       +6     
  Lines        5656     6073     +417     
==========================================
+ Hits         2089     2357     +268     
- Misses       3567     3716     +149     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

⌂ 换网址