Skip to content

feat: add BoxLite provider for local container-based ships - #9

Closed
smithclay wants to merge 16 commits into
mainfrom
boxlite
Closed

feat: add BoxLite provider for local container-based ships#9
smithclay wants to merge 16 commits into
mainfrom
boxlite

Conversation

@smithclay

@smithclay smithclay commented Jan 31, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds BoxLite provider as an alternative to sprites.dev for local development
  • Ships run as hardware-isolated micro-VMs using boxlite (sub-second boot times)
  • Includes Tailscale bootstrap for mesh networking integration
  • Implements full provider interface: create(), destroy(), list(), get(), wait_ready()
  • Adds boxlite check to the doctor command
  • Updates documentation to reflect multi-provider architecture

Changes

  • src/ocaptain/providers/boxlite.py — New BoxLite provider implementation
  • src/ocaptain/providers/__init__.py — Register BoxLite in provider registry
  • src/ocaptain/cli.py — Add boxlite availability check to doctor
  • tests/test_boxlite.py — Comprehensive test coverage
  • tests/test_config.py — Config defaults test
  • pyproject.toml / uv.lock — Add boxlite as optional dependency
  • README.md / CLAUDE.md — Multi-provider documentation

Test plan

  • Unit tests for BoxLite provider methods
  • Config defaults verification
  • Manual testing with local micro-VMs

Enables local micro-VM development without cloud costs.
Install with: pip install ocaptain[boxlite]
Registers boxlite provider with NotImplementedError stubs for create,
destroy, and wait_ready. Full implementation follows in subsequent commits.
Verifies prefix filtering and lookup behavior.
Polls SSH via Tailscale IP until accessible or timeout.
Ships boot ubuntu:22.04, install Tailscale, join mesh network.
Uses async boxlite SDK wrapped in sync Provider interface.
Verifies cleanup of box and VM tracking state.
Shows whether boxlite is available for local development.
- Make boxlite import conditional to avoid ImportError when not installed
- Fix shell injection vulnerability in SSH key injection using shlex.quote
- Replace contextlib.suppress with proper error logging in destroy
- Add logging throughout the provider
- Add Claude Code installation to bootstrap
- Re-raise KeyboardInterrupt in wait_ready
- Add cleanup on _create failure
- Validate Tailscale IP result is not empty
- Add __del__ for event loop cleanup
- Rename box_exec to run for brevity and clarity
- Remove stale comment about getattr usage
- Eliminate intermediate pub_key variable
- Streamline list() method to avoid extra list conversion
- Reflect that ships can sail on sprites.dev, exe.dev, or boxlite
- Add BoxLite setup instructions for local development
- Update architecture diagram and components table
- Add OCAPTAIN_PROVIDER environment variable documentation
The voyage flow was hanging during ship bootstrap because ship.py
ran apt-get install for tmux/expect while the BoxLite provider's
apt lock was still held from the bootstrap script.

Changes:
- Install tmux and expect in BoxLite bootstrap script alongside
  other packages to avoid apt lock contention
- Skip tmux/expect installation in ship.py for BoxLite provider
- Add skip_install param to _bootstrap_tailscale for BoxLite
  (Tailscale already configured in provider bootstrap)
- Fix get_connection() to use port 2222 and explicit SSH key
  for BoxLite VMs
- Consolidate BoxLite bootstrap into single script (BoxLite
  exec doesn't persist state between invocations)
- Use subshell + stdio redirect for tailscaled daemonization
  (BoxLite waits for all file descriptors to close)

Tested with full voyage flow - ships now bootstrap successfully.
- Add get_ssh_private_key_path() to config.py for centralized key path
- Simplify boxlite.py wait_ready() by removing redundant local imports
- Inline box.exec call in _destroy() instead of unused variable
- Use helper function in provider.py get_connection()
Consolidate duplicated code across exedev and sprites providers:
- run_cli_command(): unified CLI execution with error handling
- setup_ssh_keys(): SSH key injection on VMs
- install_claude_code(): Claude Code installation
- poll_until_ready(): generic retry/polling logic

Net reduction of ~78 lines across providers while improving
maintainability with single points of change.
@smithclay smithclay closed this Feb 2, 2026
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