Repository navigation
deployment
Installing mbdeploy serve as a systemd service on a node with boards attached — and where the actual machines are documented.
This page is generic. It tells you how to install and run the daemon on a node; it deliberately names no hosts.
The machine list, SSH access, per-node state, and current deployment status live on the internal Robot Garage wiki:
That wiki is reachable only from the garage LAN. If you are looking for which nodes exist, which of them have boards plugged in, where mbdeploy is installed on each, which daemons are currently running, or how to log in — that is the page, and it is the only place that information belongs. Nothing of the sort is published here, because this site is world-readable.
If you change the fleet — a node added or removed, a board moved, an install path or service changed, a daemon down — update that page in the same piece of work.
The manual procedure below still works and still explains what actually
happens on a node, but the normal way to deploy or update the fleet is now the
Ansible package in ansible/ in the repo:
cd ansible
./build-bundles.sh # after any code or dependency change
ansible-playbook deploy.yml # or --limit <host>,<host>It exists because the fleet nodes have no internet access: every Python
dependency travels as a per-architecture wheel bundle (aarch64 from PyPI,
armv6l from piwheels — where zeroconf has no prebuilt wheel and compiles
from sdist on the node, a few minutes on a Pi Zero W, first install only).
The playbook creates the standard layout, installs into the venv with
pip --no-index, installs the systemd system unit via mbdeploy's own
serve --install-service --system, enables and starts it, and verifies the
daemon is active. It is idempotent; a newer mbdeploy wheel in the bundle
upgrades the node and restarts the service.
The real inventory (hostnames, credentials) is ansible/inventory.local.yml,
which is gitignored — copy inventory.example.yml and fill it from the
internal wiki page above, which remains the record of the actual machines.
One host, one mbdeploy serve process, one systemd unit. The host needs USB
access to the boards; everything else reaches those boards over the LAN with
--remote. Several boards on one host is fine — the watcher picks up a hotplug
arrival and advertises the new board with no restart.
It cannot be a container. A Docker Swarm service cannot reach a DAPLink
probe (no --device, no --privileged), which is why a systemd system unit
is the supported shape and the default.
-
Install
mbdeployfor the account that will run the service — see Overview and installation. On aarch64 every dependency is a prebuilt wheel; nothing compiles, and a fresh venv is about 83 MB. -
Put the service user in two groups.
serveboth flashes (raw USB, via pyOCD) and relays serial, so it needs both:sudo usermod -aG plugdev,dialout <service-user>
plugdevfor pyOCD's raw USB access,dialoutfor the serial port. Re-login or reboot before starting the service — group membership only applies to new sessions. -
Do not add a udev rule. On Raspberry Pi OS none is needed:
/lib/udev/rules.d/70-microbit.rulesalready ships, tagging the micro:bit's vendor id withuaccess, and both/dev/ttyACM*and the underlying USB node come uproot:plugdev 0660.plugdevmembership is what grants headless access. In particular, do not write aMODE="0666"rule — an early draft of this documentation proposed one, and it is unnecessary. -
mDNS needs no special setup. The daemon's own zeroconf stack coexists cleanly with a running
avahi-daemon; this was verified on real hardware, including registration, browsing from a separate process, and UTF-8 TXT round-trips. No config change, and noavahi-publishshim, is required.
serve --print-service renders the systemd unit for the exact serve
invocation you would otherwise have typed, to stdout, touching nothing on disk:
cd /path/to/project # WorkingDirectory is baked in from this directory
mbdeploy serve --print-service --base-port 9000 --target-mcu nrf52833Everything that affects the daemon is baked into ExecStart with its effective
value — including --config resolved to an absolute path, because a service
manager gives the process no useful working directory to resolve a relative one
against. Flags that configure the rendering (--print-service,
--install-service, --system, --user) are naturally left out.
The command in ExecStart is the installed mbdeploy console script if one is
on PATH (resolved to an absolute path), and otherwise
<python> -m mbdeploy.cli for the interpreter mbdeploy is importable from.
The generated unit is a plain Type=simple service with
After=/Wants=network-online.target, Restart=on-failure, RestartSec=2, the
baked WorkingDirectory and ExecStart, and an [Install] section. Regenerate
it rather than hand-editing it when the invocation changes.
serve --install-service writes that same unit to disk and exits without
running the daemon; you run daemon-reload and enable --now yourself:
sudo mbdeploy serve --install-service --base-port 9000 --target-mcu nrf52833
sudo systemctl daemon-reload
sudo systemctl enable --now mbdeploy--install-service with neither --system nor --user writes
/etc/systemd/system/mbdeploy.service with WantedBy=multi-user.target, and
requires root.
A systemd --user unit does not start at boot and does not survive
logout on a host with the default Linger=no. Pass --user only when that
tradeoff is acceptable — a workstation you stay logged into — or pair it with
linger:
mbdeploy serve --install-service --user
loginctl enable-linger <user> # required to start at boot / survive logoutA --user install writes ~/.config/systemd/user/mbdeploy.service with
WantedBy=default.target, needs no root, and prints a reminder about linger to
stderr.
A literal --token SECRET in a unit file would be readable by anyone on the box
via systemctl cat. So:
-
--install-service --token SECRETwrites the secret to a fresh mode-0600file —/etc/mbdeploy/tokenfor a system unit,~/.config/mbdeploy/tokenfor--user— and bakes--token-file <that path>intoExecStartinstead of the secret. -
--token-file PATH, naming a file you created yourself, is resolved to an absolute path and passed straight through. -
--print-service --token SECRETis refused outright.--print-servicetouches no filesystem, so there is nowhere to put the secret and it must not be emitted literally. Use--token-filewith--print-service, or switch to--install-service.
sudo mbdeploy serve --install-service --token-file /etc/mbdeploy/token
# or let the install create the file for you:
sudo mbdeploy serve --install-service --token 'a-shared-secret'Before you enable a token, read the client-side gap: no --remote client can
authenticate today, so a token-protected daemon is unreachable by
connect --remote and deploy --remote. See
The fleet daemon. If what
you want is to stop remote flashing, --no-flash works today and the token does
not.
On the node:
systemctl is-active mbdeploy
systemctl is-enabled mbdeploy
journalctl -u mbdeploy -bA healthy start is two lines and nothing else — systemd's own Started …, then
mbdeploy serve: running (poll every 2s; Ctrl-C or SIGTERM to stop)
During a flash the journal additionally carries pyOCD's own progress output and, if a locked part was recovered, the one mass-erase notice. Tracebacks are not normal.
From another machine on the same LAN:
mbdeploy list --remoteEvery board on every node running the daemon should appear, one row each. A node whose daemon is not installed or not running simply contributes no rows — which is why "a board is missing" and "a node is down" look identical from here, and why the node inventory on the internal wiki matters.
systemctl stop mbdeploy makes a node's boards vanish from list --remote
within a poll interval, and start brings them back; that is also the closest
you can get to testing unplug/replug without physical access.
The Ansible playbook above is the normal way to upgrade — rebuild the bundles and rerun it; it restarts the unit only when the installed wheel actually changed. By hand: update the source on the node, reinstall into its environment, then restart the unit:
systemctl restart mbdeployA restart drops any live sessions and any flash in flight — the client sees its connection close — so restart when nothing is mid-deploy. The advertisements come back once the watcher's next poll re-enumerates the boards, typically within a few seconds of the service becoming active.
Record what you changed, on which node, on the internal wiki page above.