Skip to main content

Building + Shipping Code

Do the build on your Mac / dev machine — the device only runs the artifacts. The Go binary cross-compiles to linux/arm64; the HAL is plain Python, no build step needed.

Disable the OTA bootstrap first

The bootstrap service polls the Intern team's OTA channel and, if it finds a newer official build, will reinstall os-server / HAL / web on top of whatever you just pushed — silently wiping your custom binary at the next tick. Before you push a single custom build to a device, turn bootstrap off so your changes stick:

sudo systemctl disable --now bootstrap

See Controlling the OTA Bootstrap for the full enable / disable / re-enable flow. Only turn bootstrap back on when you want to go back to tracking the official fleet builds — at that point the next tick will overwrite your custom changes with the current OTA version.

Go — os-server + bootstrap

# From the repo root
make os-build # → os/services/os-server
make os-build-bootstrap # → os/services/bootstrap

Push the binary and restart:

sshpass -p 'orangepi' scp os/services/os-server orangepi@<ip>:/tmp/os-server-new
sshpass -p 'orangepi' ssh orangepi@<ip> "echo orangepi | sudo -S bash -c '
cp /usr/local/bin/os-server /usr/local/bin/os-server.bak.\$(date +%s)
mv /tmp/os-server-new /usr/local/bin/os-server
chmod 755 /usr/local/bin/os-server
systemctl restart os-server
'"

Python — HAL

Sync individual files, or the whole directory:

# One file
sshpass -p 'orangepi' scp os/hal/drivers/mic_button.py orangepi@<ip>:/tmp/
sshpass -p 'orangepi' ssh orangepi@<ip> "echo orangepi | sudo -S bash -c '
mv /tmp/mic_button.py /opt/hal/drivers/mic_button.py
chown root:root /opt/hal/drivers/mic_button.py
systemctl restart hal
'"

# Whole HAL tree (destructive — replaces /opt/hal contents)
sshpass -p 'orangepi' rsync -avz --delete os/hal/ orangepi@<ip>:/tmp/hal/
sshpass -p 'orangepi' ssh orangepi@<ip> "echo orangepi | sudo -S bash -c '
rsync -avz --delete /tmp/hal/ /opt/hal/
systemctl restart hal
'"

Web (Setup + Admin UI)

The React SPA lives in os/services/web/. Nginx serves the built assets from /usr/share/nginx/html/setup/.

# Build
cd os/services/web && npm install && npm run build # → dist/

# Push (from repo root)
cd os/services/web/dist && zip -qr /tmp/setup-web.zip .
sshpass -p 'orangepi' scp /tmp/setup-web.zip orangepi@<ip>:/tmp/
sshpass -p 'orangepi' ssh orangepi@<ip> "echo orangepi | sudo -S bash -c '
find /usr/share/nginx/html/setup -mindepth 1 -not -name VERSION -exec rm -rf {} + 2>/dev/null || true
unzip -o -q /tmp/setup-web.zip -d /usr/share/nginx/html/setup
chown -R root:root /usr/share/nginx/html/setup
'"

Nginx serves the SPA — no restart needed after replacing files.