Skip to content

Releases: deevus/pixels

v0.6.2

30 Apr 08:24

Choose a tag to compare

fix(incus): cloning fails with mac address conflict

Remove volatile keys from incus config upon clone

v0.6.1

29 Apr 08:51

Choose a tag to compare

The headline is the MCP server. Plus base pixels, SSH host-key verification, and a batch of TrueNAS-backend reliability fixes.

Highlights

pixels mcp — the MCP code-sandbox server. A streamable-HTTP MCP server that exposes container lifecycle, exec, and file CRUD as MCP tools. Run it once on your machine, point any number of MCP clients at it, and your agent gets disposable Linux sandboxes on demand.

Base pixels. Containers that sandboxes clone from. Three ship out of the box (dev, python, node) and you can declare your own with from inheritance. With a built base, a new sandbox is up in ~5s.

SSH host-key verification (TOFU). The TrueNAS backend now pins the host key on first connect and verifies on every reconnect. On by default.

Alpha caveat for MCP. Sandboxes spawned via MCP don't get per-call egress policies yet, and base clones inherit whatever was baked in at build time. Treat an MCP-spawned sandbox like pixels create --egress unrestricted. Egress and the rest of the pixels create hardening are coming. The server has no auth either, so keep it on loopback or behind a reverse proxy. See SECURITY.md for the full posture.

What's New

MCP server

  • pixels mcp runs a streamable-HTTP daemon. Default bind http://127.0.0.1:8765/mcp. PID file at ~/.cache/pixels/mcp.pid so you can't accidentally start two.
  • Tools: create_sandbox, start_sandbox, stop_sandbox, destroy_sandbox, list_sandboxes, list_bases, exec, read_file, write_file, edit_file, delete_file, list_files.
  • create_sandbox is async. Returns immediately with status: "provisioning". Poll list_sandboxes until status flips to running or failed. Failed sandboxes carry an error field.
  • list_sandboxes reconciles in-memory state with the backend on a 15s cache, so a sandbox destroyed out-of-band stops appearing once the cache rolls.
  • Two TTLs keep things tidy. idle_stop_after (default 1h) stops idle sandboxes. hard_destroy_after (default 24h) destroys anything older than that.
  • File ops own files as the pixel user so subsequent exec calls can read and modify them. read_file and edit_file cap in-memory reads (edit_file refuses files that would truncate trailing content).
  • [mcp] config section with sane defaults for prefix, listen address, endpoint path, lifetimes, and exec timeout ceiling.

Base pixels

  • New CLI verbs: pixels base list, pixels base build <name>. base build cascade-builds missing deps with a spinner.
  • Three default bases shipped embedded in the binary:
    • dev — Ubuntu 24.04 + git, curl, wget, jq, vim, build-essential
    • pythondev + python3, pip, pipx, venv
    • nodedev + Node 22 LTS, npm
  • from inheritance lets you layer bases. Cycles, missing deps, and parent_image + from set together are rejected at config load.
  • Customise a base by mutating its container, then pixels checkpoint create px-base-<name>. The next clone picks up your changes. Existing sandboxes are independent and unaffected.
  • Base build scripts hold a flock on /var/lib/apt/daily_lock for the duration of the build so apt-daily can't fire mid-build, and retry on lists-lock contention with a single semantic message per attempt.
  • NodeSource GPG key fingerprint pinned in node.sh.

A couple of gotchas worth knowing:

  • Checkpoint-advances-clone-source. Future sandboxes clone from the most recent checkpoint by creation time, not by label. Always re-checkpoint after mutating.
  • Mutation propagation isn't automatic. Changes to dev do not flow into python or node. Destroy and rebuild dependents to propagate. Same as Docker layered images.
  • Setup scripts run as root with pixels create hardening absent. Treat them like Docker RUN lines: only build a base from a script you wrote or reviewed.

mise.toml for sandbox devtools

Sandboxes get a declarative mise.toml written to /home/pixel/.config/mise/config.toml instead of a hand-rolled install script. Devtool versions are explicit and easier to override.

SSH host-key verification

TOFU: first connection trusts and pins, reconnects verify, key changes are detected and re-pinned. On by default; opt out with strict_host_keys = false.

TrueNAS backend reliability

The pixels create flow on TrueNAS gets meaningfully more reliable in this release. Each of these was a real bug that bit me running the tool day-to-day.

  • Ready polls for IP within the timeout instead of returning single-shot. Cloned containers boot DHCP slowly — 15 to 60 seconds in practice. Previously Ready returned before the IP appeared, so the next step ran against a container with no network and failed.
  • Stop timeout bumped from 30s to 120s for post-apt systemd shutdown. Any container that recently ran apt install couldn't stop cleanly because systemd took longer than 30s to settle. pixels stop and pixels destroy were unreliable on freshly-provisioned containers. Fixed.
  • Stop and CloneFrom are now idempotent via a new StopInstanceIfRunning helper. Retries and orchestrated flows no longer error out on already-stopped containers.
  • zmx install uses $(uname -m) so arm64 containers actually get a working binary. Without this, devtools provisioning and console persistence were silently broken on arm64 hosts.
  • console shell-escapes the zmx session name so metacharacters in -s <session> can't be interpreted by the remote shell.

Docs

  • pixels mcp documented end-to-end in the README: config options, naming rules, base pixels, async provisioning, lifetimes.
  • SECURITY.md got the MCP server's security posture and known gaps.

Upgrading

mise use -g github:deevus/pixels@v0.6.1

Or go install github.com/deevus/pixels@v0.6.1.

If you're on the TrueNAS backend, this upgrade fixes a pile of reliability bugs around cloned containers, post-apt stops, and arm64 hosts. If you want to try the MCP server, start the daemon and point Claude Code at it. That's it.

Full Changelog

v0.5.0...v0.6.1

v0.5.0

02 Mar 12:41

Choose a tag to compare

Highlights

Native Incus backend (now the default). Connect directly to a local or remote Incus daemon — no TrueNAS required. Console and exec go through the Incus WebSocket API, eliminating the SSH dependency for the default workflow.

Extensible sandbox architecture. pixels now uses a pluggable backend interface. Container lifecycle, exec, snapshots, and network policy are all defined by the sandbox.Sandbox interface, making it straightforward to add new backends.

TrueNAS remains fully supported as an alternative backend, selected via backend = "truenas" in config or PIXELS_BACKEND=truenas.

What's New

  • Sandbox interface — new sandbox.Sandbox composite interface (Backend, Exec, NetworkPolicy) with a registry for self-registering backends (sandbox.Register, sandbox.Open).
  • Incus backend — manages containers via the native Incus Go client. Supports local unix socket and remote HTTPS connections with TLS client certs. Exec/console uses Incus WebSocket API with terminal resize handling. Snapshots use native Incus snapshot API (storage-backend agnostic). Clone uses CopyInstance.
  • Backend selectionbackend config key (default "incus"), PIXELS_BACKEND env var, and new [incus] config section with socket, remote, client_cert, client_key, server_cert, and project keys.
  • Typed Status enumsandbox.Status replaces raw strings for container state.

Breaking Changes

  • Removed CLI flags --host, --api-key, and -u/--username. These were TrueNAS-specific and are now set via config file or environment variables (PIXELS_TRUENAS_HOST, PIXELS_TRUENAS_API_KEY, PIXELS_TRUENAS_USERNAME).
  • Removed disk cache (~/.cache/pixels/). The sandbox backends query live state directly. The internal/cache package has been deleted.

Bug Fixes

  • Console zmx command, provisioning executor, and start IP polling.
  • TrueNAS exec respects Root flag and re-authorizes SSH key on auth failure.
  • Incus exec runs as configured user with proper shell wrapping and WebSocket IO.
  • Network egress setup no longer fails on dpkg conffile prompts.
  • Exec runs commands in a login shell with mise activation.

Full Changelog

v0.4.0...v0.5.0

v0.4.0

01 Mar 11:59

Choose a tag to compare

Highlights

Persistent sessions with zmx. pixels console now uses zmx for session persistence. Disconnect and reconnect to pick up where you left off — works cleanly with TUIs. Detach with Ctrl+\ or detach.

What's New

  • zmx session persistencepixels console creates a named zmx session (default "console"). Reconnecting re-attaches to the same session. Use -s <name> for named sessions or --no-persist for a plain shell.
  • pixels sessions command — lists active zmx sessions in a container.
  • Detach alias — type detach inside a session as a shortcut for Ctrl+\.
  • Detach hint — sourced from profile.d inside zmx sessions so users know how to detach.
  • Login shell for zmx — zmx sessions use a login shell so profile.d scripts are sourced.

Improvements

  • Extracted Executor interface from provision.Runner for testability.
  • Extracted shared helpers from root.go into resolve.go.
  • Embedded pixels-profile.sh instead of inlining it.
  • Added WriteContainerFile and ReplaceContainerRootfs tests.
  • Added lookupRunningIP tests.
  • Used resolveRunningIP consistently in the sessions command.

Full Changelog

v0.3.0...v0.4.0

v0.3.0

01 Mar 05:03

Choose a tag to compare

Merge pull request 'feat: self-contained zmx provisioning' (#7) from …

v0.2.0

28 Feb 05:03

Choose a tag to compare

refactor: replace manual env overrides with caarlos0/env

v0.1.0

27 Feb 12:21

Choose a tag to compare

ci: add release workflow with GoReleaser and git-cliff changelog gene…