YouDuck.ai

Setting up a dev board

Last updated

A dev board trusts the team dev key, so it will install anything anyone on the team builds. A customer robot is set up differently and deliberately refuses those builds.

Everything here assumes a dev board, never a customer robot.

Nothing is relaxed

This is worth being clear about, because it is easy to assume otherwise. A dev build gets the same signature and hash verification, the same health gate, the same auto-rollback. The only difference is which key signed it.

That is also what keeps these builds off customer robots, which refuse a dev key twice over: allow_dev_keys = false, and a trusted key only counts as a dev key if its filename ends .dev.pub. Both halves of the setup below exist to flip exactly that.

Flash the board

Use the Armbian imager: pick Radxa Zero 3, then Armbian 26.2.1 Minimal.

Fill in the imager’s profile before writing — wifi network and password, and the username and password you want. Doing it there saves a serial console later: the board joins your network on first boot and is reachable over ssh straight away.

Then send your ssh key, so provisioning can reconnect after it reboots the board:

ssh-copy-id [email protected]

What you need

  • The board’s IP address. mDNS on this image is unreliable. duckctl ip asks the robot over Bluetooth, which needs no network of your own; your router’s lease table is the fallback.
  • ssh key access, from the step above. Provisioning reboots the board and reconnects by itself, and a password prompt cannot survive that.
  • A GitHub token while the repository is private — its release assets are unreachable without one.
  • A clone of the repo. The dev key it needs is committed at deploy/dev-key/team.dev.pub, so there is nothing to ask anyone for.

Install

export DUCK_TOKEN=github_pat_replace_with_your_token
./scripts/provision-board.sh --pause-btd-on-pair --name <MY_ROBOT> [email protected]

That sends your dev key, starts provisioning, waits out the reboot, streams the log, and ends on robotctl health.

The log output is a viewer, not the thing doing the work. Provisioning installs a systemd unit that resumes at boot, so the board finishes whether or not you are still watching. Ctrl-C costs you nothing:

ssh -t [email protected] 'sudo tail -f /var/lib/robot/provision.log'

Why --pause-btd-on-pair is in that command

On the aic8800 radio a pad cannot form a new bond while btd is advertising. The flag leaves a marker so robotctl pad pair stops btd and power-cycles the adapter for the pairing window, then starts it again. An existing bond is unaffected.

It is the default here for a practical reason: a board that needed it and was provisioned without it presents as a gamepad that will not pair, and every plausible cause you chase first is somewhere else.

The three configurations, and how to tell which you have

There are two independent faults, so there are two flags. Pair a pad and read the failure, then pick:

What you see What the board wants
The pad bonds and drives Nothing — provision with no flag
The pad will not bond; the last SMP step never completes --pause-btd-on-pair
The pad will not bond even with btd paused --weird-ble (implies the pause, adds Privacy = device)
The pad bonds, then flapsPIN or Key Missing (0x06), no input device --weird-ble is wrong here: drop it, keep the pause

That last row is the one to watch for. Privacy = device on a board that only needed the pause produces a bond that immediately stops working, which is harder to diagnose than a pad that plainly will not pair — measured on one board as 46 flaps in 45 seconds where off plus the pause held. --weird-ble is therefore not the default.

Moving a board between the two

# from --weird-ble back to the pause alone
sudo sed -i 's/^Privacy = device/Privacy = off/' /etc/bluetooth/main.conf && sudo reboot

# the other way, with the copy provisioning leaves on the board
sudo DUCK_WEIRD_BLE=1 /usr/local/sbin/robot-setup-board && sudo reboot

# check the board needs neither
sudo rm /var/lib/robot/weird-ble
sudo sed -i '/^Privacy = /d' /etc/bluetooth/main.conf && sudo reboot

Re-pair after any change to Privacy. It changes the address the stored keys were derived against, so existing bonds stop matching and flap with PIN or Key Missing until they are re-made.

Provisioning from a branch

./scripts/provision-board.sh --ref BRANCH [email protected]

One design decision is worth understanding: golden stays the stable release — it is the boot recovery net’s fallback, and a branch build as golden would give a broken branch a broken fallback — while current is the branch.

Provisioning fails if that build cannot be installed, or if it is installed and then rolled back by the health gate. This is deliberate:

A dev board quietly running the stable release when a branch was asked for is the worst failure to debug: everything looks installed and the code under test is not there.

Give CI its minute or two, and check with gh run list --branch BRANCH if it stalls.

Other useful flags

  • --name Ducky names the robot instead of leaving it the duck-7f3a it derives from its serial
  • --local sends this clone’s provision.sh instead of fetching it — how to test a change to the provisioning scripts without merging first
  • --no-dev-key makes a board that only takes releases

Check it worked

robotctl health
grep -c 'DEV BOARD' /var/lib/robot/provision.log

The board only counts as a dev board if the key really installed, and that is checkable rather than something to remember. 1 means yes. 0 means the key did not land, and --ref will be refused later with an error that reads like a corrupt release. That is the failure this check exists to catch early.