devbox dev-env installs or removes a whole language environment in one call,
through mise. No curl | sh, and no pacman except for PHP, the browser,
media, network and one library for Swift (see below). The GPU drivers have their own command,
devbox gpu (see gpu.md). Whatever mise installs is declared in
~/.config/mise/config.toml, survives a rebuild, and is upgraded by
devbox update tools like the rest.
devbox dev-env --list # what is on offer, and what is installed
devbox dev-env --info ruby # what one installs, and what a removal leaves
devbox dev-env node go # install these two
devbox dev-env --remove node go # remove them
devbox dev-env # menu: install or remove, then several at a timeWithout arguments it first asks whether to install or remove, then opens a gum menu with multiple selection, the environments on the left and their description on the right. The remove menu only offers what is installed. Running it again on an environment already installed, or already removed, changes nothing.
The same environments can be asked for from .env: DEV_ENVS="node go python"
and every start makes sure they are there, through
devbox dev-env --if-missing, in the background after the mise tools. The
setup script asks for this list. What is installed already is
skipped, so a start only spends time on a fresh home or a name added since. An
unknown name refuses the whole list, nothing is installed. The output goes to
~/.cache/dev-box/dev-envs.log; while it runs, and when it failed, the login
message and devbox status say so on one line, and the line disappears once
everything is there.
DEV_ENVS never removes anything: take a name out of the list and the
environment stays until devbox dev-env --remove. The other way round, an
environment listed in DEV_ENVS cannot be removed, since the next start would
install it again: devbox dev-env --remove refuses it and the remove menu
leaves it out. Take it out of .env first, restart the container, then remove it.
A removal takes the tools out of ~/.config/mise/config.toml with
mise unuse -g, which also prunes the versions no other config needs. It only
removes what the environment itself brought: laravel drops the installer but
keeps PHP and Node, phoenix drops the phx_new archive but keeps Elixir,
scala keeps Java, db-clients and ansible keep Python, and the message
says how to remove the base. Project data
is never touched: ~/go, ~/.cargo, ~/.mix, ~/.m2, ~/.config/composer
and the like stay where they are, so a later devbox dev-env <name> finds
everything back.
A few of them do more than pull a runtime. python also installs uv. ruby
writes ~/.gemrc, turns off ruby.compile so mise takes a precompiled build
instead of spending minutes on a compiler, and installs Rails. elixir runs
mix local.hex, and phoenix adds rebar and the phx_new generator. rust
is the mise toolchain, not rustup, so there is a single place where versions
are declared.
db-clients adds the database shells that go with devbox dbs (see
databases.md), through mise: mongosh, usql (one shell for
PostgreSQL, MySQL, SQLite, SQL Server and more), and mycli and litecli
from PyPI, which is why it installs python first. psql and mariadb (or
mysql) are not in it: they are in the image. pgcli is not in it either: it
is in devbox tui.
ansible installs python first, then ansible-core and ansible-lint from
PyPI through mise. ansible-core only has the builtin modules:
ansible-galaxy collection install community.general adds the usual ones, in
~/.ansible, which a removal leaves in place. The box is the control node,
the targets are other machines over SSH (VMs, the lab, the tailnet), or the
box's own containers once podman is on.
android is the platform-tools only, adb and fastboot, taken from the zip
Google publishes, through mise's http backend: no SDK manager, no platform, no
Java. That URL carries no version, so devbox update tools cannot refresh it;
run devbox dev-env android again to take the latest build. The zip also
carries an old sqlite3, which the command removes so that the one of the
image stays first on the PATH. Google publishes no arm64 build, so on an arm64
box the command points to devbox pkg add android-tools instead.
android-sdk is the full SDK, to build apps: the cmdline-tools from the mise
registry, pinned to the version of the day so that devbox update tools never
moves ANDROID_HOME, then sdkmanager lays the platform-tools, the newest
stable platform and build-tools inside it, the licenses accepted on your
behalf. It needs a JDK 17 or 21: java@temurin-21 when no java is declared,
the declared one otherwise. There is no emulator (no KVM, no display): deploy
to a phone over USB or adb connect. flutter sits on it, from the official
stable archive through mise, with the Android and web engine artifacts fetched
at install and Linux desktop turned off; the web target runs with
flutter run -d web-server --web-hostname 0.0.0.0. Both are x86_64 only,
Google publishes no arm64 build-tools.
swift is the toolchain of swift.org, which publishes no build for Arch: mise
takes the Red Hat UBI 9 one, and the environment gives it the two things Arch
no longer has. libxml2.so.2 comes from the libxml2-legacy package, through
devbox pkg. The ncurses libraries without wide characters are links to the
wide ones of the image, laid inside the toolchain itself, where its binaries
look before the system: nothing is added to /usr, and no LD_LIBRARY_PATH
is set. A version devbox update tools installed would come without those
links, so the version is pinned to the one of the day; remove the environment
and install it again to move to a newer Swift. lldb does not start: it wants
the Python 3.9 of UBI 9.
ios builds iPhone and iPad apps without a Mac. It sits on swift and
python and takes its tools from
omarchy-apple-dev, the
same tools on Arch with what was found along the way. The box clones it at
the release it pins, into ~/.local/share/omarchy-apple-dev, and runs its
install-toolchain.sh --user-only on the Swift of mise: it builds
xtool from source, with fixes not
released upstream yet (a few minutes, the first time), and installs
pymobiledevice3 (in ~/pymobile3-venv), rcodesign and ipsw. Once the
SDK is in, it adds what the SDK of xtool lacks: the SwiftData and
#Predicate macros, and Linux stand-ins for actool, ibtool,
xcstringstool and momc. Its scripts are on the PATH, run under mise x so
they find the Swift of mise: install-toolchain.sh (always --user-only),
device-run.sh (build, install, launch, logs, --lldb, --console,
--symbolicate) and ship.sh (an App Store .ipa, --upload to
TestFlight). Flutter apps and the experimental mode without an Xcode download
are in the clone. libimobiledevice, which the xtool build links, comes
through devbox pkg. One step is left to you, since it takes your Apple account:
XCODE_XIP=~/Xcode.xip install-toolchain.sh builds the iOS SDK from an
Xcode.xip downloaded from developer.apple.com, and xtool auth signs in;
devbox ios chains both, the .xip picked in a list.
There is no simulator: an app runs on a real device, plugged into the host
and given to the box by the USB block of compose.override.example.yaml, with
devbox pkg add usbmuxd and sudo usbmuxd in the box (below). The install ends on
those steps, one by one (the Xcode download and the SDK, the device,
xtool new and xtool dev or device-run.sh for a first app, what to answer
on the device, debugging, TestFlight, moving to a newer Swift), and
devbox help ios shows them again. A box that had ios before
omarchy-apple-dev keeps its xtool AppImage until devbox dev-env ios runs
again, which moves it over; the same command moves the clone to the release a
newer image pins.
Docker gives the box no USB device by default. For a device plugged into the
host, add this to compose.override.yaml, next to compose.yaml on the host
(cp compose.override.example.yaml compose.override.yaml the first time):
services:
dev-box:
volumes:
- /dev/bus/usb:/dev/bus/usb # the whole USB bus, devices plugged in later included
device_cgroup_rules:
- 'c 189:* rmw' # lets the box open USB devices (major 189)The bus goes in as a volume, not as a devices entry, which would freeze the
list at start: an iPhone plugged in afterwards, or one that reconnects when it
restarts, would never show up. The cgroup rule is what lets the box open
those device files. Both keys add to the volumes of compose.yaml; when
compose.override.yaml already has a volumes or a device_cgroup_rules
key (another block of the example), add the lines to it, a second key of the
same name is refused. Every USB device of the host is then within reach of the
box. The device has one owner, so the usbmuxd of the host has to let go of it
first: on a host with systemd, udev starts it again at each plug, so mask it
for as long as the box keeps the device (sudo systemctl unmask usbmuxd gives
it back to the host). Then the box starts again with the new settings, and
usbmuxd runs inside it:
# on the host, in the dev-box directory
sudo systemctl mask --now usbmuxd
docker compose up -d
# in the box, the device plugged in and unlocked
devbox pkg add usbmuxd
sudo usbmuxd # again after each restart of the box
xtool devices # answer Trust on the device, then run it againdevbox ios pair, for Wi-Fi afterwards, needs this once too.
A Kotlin Multiplatform app needs one more piece: Gradle builds its klibs on
Linux but skips the iOS framework, since Kotlin/Native turns Apple targets
down when the host is not a Mac. devbox ios kmp [dir] links it anyway,
with java installed (devbox dev-env java), through
kmp/build-framework.sh of omarchy-apple-dev (its kmp/README.md has the
details). The script reads the framework the project declares
(binaries.framework: base name, exported modules, options, Compose
resources) through a Gradle init script and builds its klibs. The compiler
then runs as on a Mac, with its Apple parts swapped:
- the iOS runtime of its macOS distribution, from Maven Central once, kept
in
~/.cache/omarchy-apple-dev/kmp; - LLVM for Linux;
- the
ld64.lldof the SDK's toolset; - the SDK built by
devbox ios sdk.
The output is <module>/build/omarchy-kmp/<target> (ios_arm64, or
ios_simulator_arm64 with --target), with <Name>.xcframework next to it
with --xcframework. It is optimized and has no debug info: the Linux LLVM
asserts on the debug info of Apple targets. --dynamic links a dynamic
framework even when the project declares a static one. swift build in the
box loads every member of a static archive (-all_load, from the toolset),
and the static framework of a Compose Multiplatform app carries some Skia
objects twice, which then fail as duplicate symbols. A dynamic one, copied
into the Frameworks/ of the .app, links fine. The command only checks
for the JDK first and handles the root case below. dev-box releases up to
1.54 linked the framework themselves, with their cache in
~/.cache/devbox-ios-kmp, which can be deleted.
With rootless Docker, a project mounted from the host belongs to the user of
the host, which is root in the box: only root can write its build/. Run the
command as root from the host then; it takes the mise, java, swift, the
ios environment and the SDK of the box's user, and keeps Gradle,
Kotlin/Native and its own cache in root's home, writing nothing in the user's.
A project that also has Android targets needs the Android SDK even for its
iOS framework, since Gradle configures every target: mount the host's
read-only at the path sdk.dir of the project's local.properties names,
in compose.override.yaml.
services:
dev-box:
volumes:
- /home/<user>/Android/Sdk:/home/<user>/Android/Sdk:ro # sdk.dir of local.properties# from the host, the project mounted at ~/projets/<app>
docker exec -u 0:0 -w /home/dev/projets/<app> dev-box \
devbox ios kmp . --module :shared --out build/ios --dynamic --xcframeworkChecked with a framework that exports a second module, on both targets, and
a Swift program linked against it, and, run as root from the host, on a
Compose Multiplatform app with Android targets: its static framework, and the
dynamic one its Swift app and .ipa were then built against with
swift build. The app around it builds with devbox ios xcode --kmp, below,
which links the framework through this command first.
An app that lives in an Xcode project, not in an xtool package, builds with
devbox ios xcode [dir] [step]: run (the default) builds it, signs it for
the device plugged in (or reached over Wi-Fi with --wifi, below), installs
and launches it; project, build (the unsigned .app, under
build/devbox-ios/xtool/xtool), install, launch and logs take one step. The project is the
.xcodeproj of the dir, or its project.yml: XcodeGen (built once from its
sources into the cache, IOS_XCODEGEN_VERSION) generates the .xcodeproj
under build/devbox-ios/xcodegen, and the files it writes next to the
sources (a generated Info.plist) are removed once read. It runs
xcodeproj2xtool.py of the clone, which turns the app target into a SwiftPM
package, in the configuration given by --configuration (Debug by default:
identifier, name, version, the values of the Info.plist; a Release
configuration also builds with -c release), under
build/devbox-ios/xtool with one warning: line for each thing it cannot
carry over (run-script phases among them: SwiftGen, SwiftLint, the dSYM upload
of Crashlytics, so code they generate has to be committed), then makes up for
what that package lacks next to Xcode. Each part was found on a real app
(SwiftUI, Firebase, RxSwift, a private package), built, installed and run on an
iPhone 11 Pro under iOS 27. Most of them were taken up by omarchy-apple-dev
since, and the command now calls its tools:
-
Root. Run as root from the host (the rootless Docker case above),
swiftandxtoollook for the SDK and the Apple sign-in in/rootand ignoreHOME: links there point at those of the box's user, inside the container only. -
Manifests read on Linux. SwiftPM evaluates
Package.swifton the build machine. Some test it: RxSwift 6.9 and later leave RxCocoa out when that machine is not a Mac (#if canImport(Darwin)), though it builds for iOS.tools/host-manifests.pyof omarchy-apple-dev copies the direct dependencies whose manifest testscanImport(Darwin),os(Linux)oros(macOS)underbuild/devbox-ios/xtool/.host-manifests, answers those tests as on a Mac, and declares the copies by path in their place.--no-darwin-manifestskeeps the manifests as they are. -
Asset symbols. The
actoolof the box writesColorResourceandImageResource, marked iOS 17, without the fallback types Xcode writes for an older deployment target, so the package targets iOS 17 at least (--min-ios). The SwiftUI and UIKit shortcuts (Color.text,UIColor(.text),Image.logo) that Xcode writes whenASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONSis on come from the generator, which follows that setting. It stops on an asset name it cannot map the way actool does (a leading combining mark, a format character): rename the asset. dev-box releases up to 1.54 wrote the shortcuts themselves,--no-asset-extensionsturned that off; the option is gone. -
The build and the resources.
xtool dev buildstops short of what Xcode does next. The app gets no icon, so the.apphas none on the home screen. The resources of the target are left in<Product>_<Product>.bundle, where code that readsBundle.mainfinds nothing (the fonts ofUIAppFonts,GoogleService-Info.plist). The command builds throughship.sh --deviceof omarchy-apple-dev, which does both. It also adds the keys of the app'sInfo.plistto the one of that bundle: code built as a package (SWIFT_PACKAGE, the files SwiftGen generates) readsBundle.module, soPlistFilesreads thatInfo.plist, and itsfatalErrorstops the app when the keys are missing. -
Signing. The profile
xtool installcreates holds the first 20 devices of the team only (one page of the API): a device past them is refused at install time (0xe8008015, no valid provisioning profile). Two apps with the same bundle identifier also share the name of that profile (XTL profile <id>), and creating it again then fails with409 Multiple profiles found. The command signs throughtools/provision-dev.py(a development profile for the device plugged in alone, with the API key ofxtool auth),tools/sign-dev.sh(rcodesign) andpymobiledevice3 apps install, underXTL-<team key>.<identifier>like xtool, so it never replaces the App Store or TestFlight one (--signed-idpicks another,--signed-suffix .kmpsets two apps of one identifier side by side).installandlaunchstop the running app first, since one installed or launched over the open app stays stuck until it is closed by hand; the developer disk image is mounted when needed. -
A newer compiler. The Swift of the box can be newer than the one the project was last built with: Swift 6.4 refuses
self.flag = valueon an@Statebefore the other properties of aninitare set;_flag = State(initialValue: value)means the same and builds everywhere. That one is a change to the project. -
A Kotlin Multiplatform app.
--kmp :modulelinks the framework of that Gradle project first (devbox ios kmp, from the nearest dir above with agradlew, dynamic, with its.xcframework), since the generator dropsOTHER_LDFLAGSandFRAMEWORK_SEARCH_PATHSand the run-script phase that calls Gradle. The.xcframeworkis copied into the package and declared as a binary target of the app, the framework is embedded into the.app, and its Compose resources are put incompose-resources/there. It needsjavaand, for a build with Android targets, the Android SDK mounted wheresdk.dirpoints (above).
The parts both commands share live in ios-common.sh (running as root,
xtool, the build through ship.sh --device, signing, install, launch, the
device over USB or Wi-Fi). ios-kmp.sh checks for the JDK and calls kmp/build-framework.sh,
and ios-xcode.sh handles the project.
Both commands need the omarchy-apple-dev the image pins (OAD_REF of
dev-envs/ios.sh: a release tag, or the full SHA of a commit of its main
while what is needed is in no release). The clone lives in the home and is
kept across images, so after an image update they stop and ask for devbox dev-env ios, which moves the clone and runs install-toolchain.sh again.
devbox ios status shows the clone's tag or commit.
--package name=path[@ref] takes a dependency from a local git copy, at the
commit of ref, in place of the one the project names: a private repository
the box has no key for, a branch not pushed yet. Anything else a project
needs: run the steps one by one and change the package between project and
build, or wrap the command in a script of the project; devbox ios xcode --help lists the options. omarchy-apple-dev 0.4.0 also wrote PNGs whose width
is not a multiple of 4 pixels sheared into Assets.car (a stride declared
padded, rows written unpadded); 0.4.4, which the box pins, writes
them right. Moving to it rebuilds the SDK from the .xip once: the SDK of
the older xtool is refused.
# from the host, the project mounted at ~/projets/<app>
docker exec -u 0:0 -w /home/dev/projets/<app> dev-box devbox ios xcode . \
--configuration Debug --package mylib=/home/dev/projets/mylib@main
# a Kotlin Multiplatform app: the iOS app in iosApp/, the framework in :shared
docker exec -u 0:0 -w /home/dev/projets/<app>/iosApp dev-box devbox ios xcode . \
--kmp :shared --signed-suffix .kmpWhen the box runs on a remote machine and the device is on your desk, the USB block is of no use: the usbmuxd of your own machine does the USB part, and the box reaches it through SSH. Nothing goes into the compose file and no usbmuxd runs in the box.
ssh -R 27015:/var/run/usbmuxd <user>@<box> # from your machine, device plugged in
export USBMUXD_SOCKET_ADDRESS=127.0.0.1:27015 # in the box, in that session
xtool devicesdevbox help ios-usb has the whole of it, checked as far as xtool opening
that address, not yet with a device at the other end: what your machine needs, what to do
when the SSH connection refuses the forward, and the other ways.
Without the cable, an iPhone or iPad under iOS 27 can be reached over Wi-Fi,
by devbox ios xcode only. xtool has no Wi-Fi on Linux: its --network waits
for a device that the usbmuxd of Linux never sees over the network, and iOS 26
and older accept no new computer over Wi-Fi at all (FINDINGS.md 17 of
omarchy-apple-dev). iOS 27 opens a RemotePairing port that answers any routed
address once the device holds a pairing record for the computer, and
tools/tailnet-tunnel.py of omarchy-apple-dev opens its tunnel. The box
uses it as is: devbox ios pair, once with the device on USB, writes the
record into ~/.local/share/pymobiledevice3 (in the home, kept across
rebuilds; it holds a private key), then --wifi <address> (or IOS_WIFI)
opens the tunnel for the length of the command and sends install, launch and
logs through it (--rsd) instead of usbmuxd. The tunnel needs a TUN device and
root: the box has /dev/net/tun and NET_ADMIN for tailscaled already, and
its user has sudo. A box started with docker run rather than compose.yaml
needs them on its command line: --device /dev/net/tun --cap-add NET_ADMIN --cap-add NET_RAW (measured with both; whether NET_RAW is needed was not
tried). devbox ios pair starts the usbmuxd of the box itself, as the USB
commands do. mDNS does not reach the box, so the device is given by its
address: its tailnet one when the Tailscale app runs on it, which works
wherever the box and the device are, or a local address the box routes to.
With Tailscale off in the box (TS_DISABLE=true), the Tailscale of the host
routes the tailnet address as well: measured from rootless Docker on its
default bridge, with an iPhone 14 under iOS 27.2.
devbox ios status lists the devices paired for Wi-Fi, IOS_UDID picks one
when there are several. An xtool package goes through
device-run.sh --over-tailnet <address> <signed .ipa>. lldb over Wi-Fi is
measured upstream but wrapped by no command yet (device-run.sh --lldb needs
the cable). devbox help ios-wifi has the steps; the route and its measures
are in OVER-TAILNET.md.
devbox ios pair # once, device on USB
devbox ios xcode --wifi 100.x.y.z # then without the cableios also makes the
lldb of the toolchain work: it takes the Python 3.9 lldb was built against
as a mise tool of its own (http:lldb-python, pinned with its sha256, no
command on the PATH, the default python untouched) and starts lldb and
lldb-dap through a launcher that sets PYTHONHOME for them alone. Both are
x86_64 only.
desktop gives the box a graphical session, for a browser or any window,
shown on your machine by gliff through the
ssh connection you already have. It is off by design, installed or not:
nothing runs until a gliff client connects, and the session starts with it, on
a private screen sized to the client's window. It then stays, with what is
open in it, until devbox desktop stop: closing gliff or losing the link
leaves it as it is, and nothing stops it on its own, so nothing open is lost.
It holds about 290 MB of memory while it is on, before any app, so the box keeps it in sight:
the status line of tmux shows desktop on and the login message warns about it
until it is stopped.
devbox dev-env desktop # in the box, once; needs the GPU (devbox help gpu)
devbox desktop # its menu, in the list of devbox once it is installed
devbox desktop install code # VS Code in it; chromium for a browser
gliff --headless dev@dev-box # from your machine: the session starts
devbox desktop stop # stop it, with every window in itThe apps are not part of the environment. devbox desktop apps lists the ones
on offer, devbox desktop install <app> and remove <app> go through
devbox pkg, so an app comes back after a rebuild: code (VS Code as Arch
packages it, Code - OSS, whose extensions come from Open VSX) and chromium
(with noto-fonts and noto-fonts-emoji, the same browser as
devbox dev-env browser). Both start
with --no-sandbox, since a container refuses the namespaces their sandbox
wants. In the session SUPER + C and SUPER + B open them, and from a shell
devbox desktop open <app>.
A gliff client runs gliff-server over ssh. In the box that name is a
launcher of the image, which starts the session when it is not there, then
hands over to the real gliff-server: a mise tool (http:gliff, the package
of the Omarchy repository at the latest release of gliff, nothing on the
PATH), brought up to date by devbox update tools like the other tools. The
session is a Hyprland nested in a labwc that has no screen, since Hyprland
does not start without one; both come through devbox pkg, with foot,
swaybg, noto-fonts-emoji (no other font of the box has an emoji) and
libva. Hyprland needs the GPU of the host in the box
(/dev/dri) and does not start without it. The packages weigh about 350 MB
once installed, in the box and not in the image, which only carries the
config and the wallpaper (230 KB). It reads
/etc/devbox/desktop/hyprland.lua, bare on purpose and in the Lua format
Hyprland is moving to, or your own ~/.config/dev-box/desktop/hyprland.lua.
The background is /usr/share/devbox/desktop/wallpaper.jpg, or your own
~/.config/dev-box/desktop/wallpaper.jpg (or .png).
The session opens no app on its own. An application launcher lists the apps
of the box, the terminal ones included, filtered as you type: Enter opens one,
Escape closes it. It shows itself on a screen with no window, when you connect
to an empty session and when the last window closes, and SUPER + Space asks
for it at any time. It is fuzzel, a small
package of the environment that runs only while it is on screen, with
/etc/devbox/desktop/fuzzel.ini or your own
~/.config/dev-box/desktop/fuzzel.ini. It starts every app through
devbox desktop run, which gives the apps of devbox desktop install the
options a container needs. x86_64 only, the one it was tried on, with the test client of gliff
through a real ssh connection; Tailscale SSH with the key of a client is not
tried yet. devbox help desktop has the steps and the keys.
PHP is the one exception. mise can only build PHP from source, which takes
minutes and needs a pile of development headers, so php, composer,
php-sqlite, php-gd, php-sodium and xdebug are pacman packages baked
into the image, with the usual extensions and xdebug already enabled at build
time. devbox dev-env php only checks and shows what is there. laravel adds
Node and the Laravel installer through composer global, kept in
~/.config/composer, which is in the PATH and in the persistent home.
symfony adds symfony-cli through mise's github backend.
browser is the other exception: a headless Chromium, with noto-fonts and
noto-fonts-emoji so that non Latin scripts and emojis do not render as
squares, so that a coding
agent can look at a dev server and check the result. The builds Playwright and
Puppeteer download target Debian and Ubuntu and still need a pile of pacman
libraries, so the environment installs the distribution package through
devbox pkg add chromium noto-fonts noto-fonts-emoji instead: it works as it is on amd64 and on
Arch Linux ARM, and devbox pkg reinstalls it after a rebuild. Chromium is not
baked into the image because it weighs about half a gigabyte and most boxes
never need it.
It also adds agent-browser
through mise, a single binary made for coding agents: agent-browser open,
snapshot (the accessibility tree, with a ref per element), click, fill,
screenshot, eval, one command per step, with no script to write and no
npm install. ~/.agent-browser/config.json points it at the system Chromium
(created when missing, completed when it has no executablePath, left alone
when it names another browser), so it never downloads one. How an agent uses
all of it, and Playwright or Puppeteer on the system Chromium when a project
needs them, is in the
browser.md guide of
the agent skill. A box that installed browser before gets agent-browser
with devbox dev-env browser again, or at the next start when DEV_ENVS has
browser.
media gathers the tools to fetch, convert and inspect audio, video and
pictures. yt-dlp and oxipng come through mise, so devbox update tools
keeps yt-dlp current: it breaks whenever a site changes. The rest goes
through devbox pkg, like the browser: ffmpeg (the mise registry only builds
it from source), pngquant, jpegoptim, cwebp (libwebp-utils),
exiftool, mediainfo and aria2. ffmpeg stays out of the image for its
size; once there, yazi shows video thumbnails. YouTube wants a JavaScript
runtime for yt-dlp: devbox dev-env deno. A removal takes everything out,
ffmpeg included.
network gathers the tools of a SISR network course: nmap, tcpdump,
iperf3, mtr, socat, ethtool and ipcalc through devbox pkg (the
mise registry has none of them), and doggo, a DNS client with a readable
output, through mise. The small basics are in the image already, with no
environment: dig, nslookup and host, nc, whois, traceroute,
ping, ip and ss. devbox tui has trippy, gping and termshark on
top. The box runs with NET_RAW and NET_ADMIN (Tailscale needs them) and
sudo asks no password, so sudo nmap -sS and sudo tcpdump -i any work;
without sudo, nmap falls back to a connect scan. The box is still a
container: tcpdump only sees its own interfaces, not the traffic of the host
or the LAN, and the LAN is reached through the NAT of Docker, so an ARP scan
never shows the real MAC addresses. A removal takes out what the environment
brought and leaves the image's tools.
OCaml is not offered: upstream it goes through the opam installer, which would be wiped by the next image rebuild.
Each environment is a short script with three functions, details, install
and uninstall, plus is_installed when the mise config cannot tell and
is_supported when it does not run everywhere, and is_hidden for an alias
kept out of the menu and the list but still installed by name and by
DEV_ENVS (gpu, now devbox gpu install). What this machine cannot take
(android, android-sdk, flutter, swift, ios and desktop on arm64, desktop in a box without a GPU) is left out of the menu and
the list, and skipped by DEV_ENVS, so one .env serves both architectures.
The image ships them in /usr/share/devbox/dev-envs/, and devbox dev-env
finds every file there on its own.
A script of the same shape in ~/.config/dev-box/dev-envs/<name>.sh adds an
environment to your box, or replaces the image's one of the same name, and
survives rebuilds since it lives in the home; --list marks it. The format is
described at the top of /usr/share/devbox/lib/dev-env.sh, and copying one of
the image's scripts is the quickest start. To ship one in the image for every
box, see
extending.md.

