From c018701512e6e2437f24fa405a6e876cdd6a1e69 Mon Sep 17 00:00:00 2001 From: kassam Date: Fri, 17 Jul 2026 11:55:50 +0400 Subject: [PATCH] Update 2026-07-17 11:55:48 --- README.md | 79 +++++++++++++++++++++++++++++++++++++++++++++--- fleet_install.sh | 31 ++++++++++++++++++- 2 files changed, 104 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9b71e75..faf6ffb 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,8 @@ Project/fleet/ └── agents/ ├── g1/ ← CANONICAL source (edit here) │ ├── sanad_api_g1.py - │ ├── Dockerfile (lean: python + requests) + │ ├── Dockerfile (DDS: CycloneDDS + unitree_sdk2py) + │ ├── vendor/ (unitree_sdk2py wheel + crc libs) │ ├── requirements.txt · docker-compose.yml (local use only) · .env.example ├── r1/ ← generated · unitree_hg · R1 FSM ids · eth10 │ ├── sanad_api_r1.py · Dockerfile (DDS: CycloneDDS + unitree_sdk2py) · vendor/ · .env.example @@ -187,11 +188,16 @@ cd Project/fleet # Interactive — asks robot type, IP, and (if new) name + token + server: ./fleet_install.sh -# …or scripted against the real fleet server: +# …or scripted against the real fleet server (FIRST install — needs the token): ./fleet_install.sh install r1 10.255.254.82 \ --sn E39N4000Q6D7E70F --name r1_82 \ --token --server-url https://eco.yslootahrobotics.com +# UPDATE an already-installed robot (reuses the token already on it): +./fleet_install.sh install r1 10.255.254.82 \ + --sn E39N4000Q6D7E70F --name r1_82 \ + --server-url https://eco.yslootahrobotics.com --keep-token + # Inspect / manage: ./fleet_install.sh data r1 10.255.254.82 # what it's currently sending ./fleet_install.sh logs r1 10.255.254.82 # live docker logs @@ -248,8 +254,8 @@ a NAT bridge) and so the agent can reach the Sanad dashboard on `127.0.0.1`. | command | action | |---|---| -| `install` | rsync → build → create → install + enable the systemd service | -| `uninstall` | disable/remove service, container, image, and `~/sanad_api_` | +| `install` | rsync → build → create → install + enable the systemd service (also the **update** path — see below) | +| `uninstall` | disable/remove service, container, image, and `~/sanad_api_` — ⚠ **destroys the token**, see below | | `status` | systemd service state + container state | | `data` | recent telemetry/map log lines (what it's sending) | | `logs` | `docker logs -f` (live tail) | @@ -266,7 +272,8 @@ a NAT bridge) and so the agent can reach the Sanad dashboard on `127.0.0.1`. |---|---|---| | `--sn SERIAL` | **required** | robot's REAL serial — keys it on the server (e.g. `E39N4000Q6D7E70F`) | | `--name NAME` | `_` | friendly display name (`r1_82`, `g1_58`) | -| `--token TOK` | `test-token` | device bearer token | +| `--token TOK` | `test-token` | device bearer token — ⚠ omitting it on an update **overwrites the real token with `test-token`**; use `--keep-token` | +| `--keep-token` | off | **updating an installed robot**: reuse the `DEVICE_TOKEN` already in the robot's `.env` (never leaves the robot) | | `--server-url URL` | — | full HTTPS fleet server → `VERIFY_TLS=1` | | `--post PATH` | agent default | override the telemetry ingest path | | `--server-ip IP` | auto (route to robot) | use the local test server instead → `VERIFY_TLS=0` | @@ -274,6 +281,49 @@ a NAT bridge) and so the agent can reach the Sanad dashboard on `127.0.0.1`. | `--user USER` | `unitree` | SSH user on the robot | | `--keep-server` | off | (test) leave the workstation test server running | +### Updating a robot that's already installed + +`install` **is** the update path — it rsyncs the new code, rebuilds (Docker layer +cache makes it fast: only the `.py` layer changes), recreates the container and +restarts the service. Use `--keep-token` so you don't need the token again: + +```bash +./fleet_install.sh install g1 10.255.254.58 \ + --sn E21D6000PB89GF88 --name g1_58 \ + --server-url https://eco.yslootahrobotics.com \ + --keep-token +``` + +> **Always pass `--keep-token` when updating.** `--token` defaults to +> `test-token`, so an update *without* either flag silently overwrites the +> robot's real token and the fleet feed dies with `401`. + +`--keep-token` copies the robot's current `.env`, writes the fresh one with a +placeholder, then splices the old `DEVICE_TOKEN` line back **on the robot** by +line surgery (no shell interpolation) — so any token characters are safe and the +token is never transferred or printed. Downtime is a few seconds. + +### ⚠ `uninstall` destroys the device token + +`uninstall` does `rm -rf ~/sanad_api_`, which includes `.env` — **the only +copy of `DEVICE_TOKEN` on the robot**. After uninstalling you cannot reinstall +without a token from the fleet admin. If you intend to reinstall, back it up +outside the install dir first: + +```bash +# BEFORE uninstall — stash the token somewhere rm -rf can't reach +ssh unitree@ 'cp ~/sanad_api_/.env ~/.sanad_api_.env.bak' + +./fleet_install.sh uninstall + +# BEFORE reinstall — restore it, then --keep-token picks it up +# (rsync uses --exclude '.env', so a pre-placed .env survives the install) +ssh unitree@ 'mkdir -p ~/sanad_api_ && cp ~/.sanad_api_.env.bak ~/sanad_api_/.env' +./fleet_install.sh install --sn --server-url --keep-token + +ssh unitree@ 'rm -f ~/.sanad_api_.env.bak' # tidy up the extra secret copy +``` + --- ## 9. The auto-start service (systemd) @@ -303,6 +353,22 @@ systemctl --user restart sanad-api-r1 journalctl --user -u sanad-api-r1 -f # or: docker logs -f sanad-api-r1 ``` +**What `uninstall` removes** (verified end-to-end on the G1): + +``` +systemctl --user disable --now sanad-api- # stop + un-enable +rm -f ~/.config/systemd/user/sanad-api-.service +systemctl --user daemon-reload +systemctl --user reset-failed sanad-api- # ← clears the stale "failed" entry +docker rm -f sanad-api- ; docker rmi sanad-api-:latest +rm -rf ~/sanad_api_ # ← includes .env (the token!) +``` + +`reset-failed` matters: stopping the unit leaves `docker start -a` exiting +non-zero, so without it systemd keeps a `not-found failed` entry in its runtime +state — a clean uninstall would still look like a broken service in +`systemctl --user --failed` forever. + --- ## 10. Configuration reference @@ -438,6 +504,9 @@ Production fleet server: **`https://eco.yslootahrobotics.com`**. | build very slow (r1/go2) | first build compiles CycloneDDS (minutes). Run detached: `setsid bash -c 'cd ~/sanad_api_r1 && docker build -t sanad-api-r1:latest . >build.log 2>&1' \| grep Linger` → `Linger=yes` (the installer sets it). | | r1/go2 out of sync with g1 | you edited g1 without regenerating — run `python3 tools/gen_agents.py`. | +| after an update the server returns **401** | you ran `install` without `--token` or `--keep-token`, so `.env` got the default `test-token`. Re-run with `--keep-token` (if the real token is still on the robot) or `--token `. | +| uninstalled and now can't reinstall (no token) | `uninstall` deletes `~/sanad_api_/.env` — the only token copy. Get a fresh token from the fleet admin, or restore a `.env` backup. Back it up **before** uninstalling next time (see [§8](#-uninstall-destroys-the-device-token)). | +| `systemctl --user --failed` shows a dead `sanad-api-*` after uninstall | fixed — `uninstall` now runs `reset-failed`. On an old install, clear it once: `systemctl --user reset-failed sanad-api-.service`. | --- diff --git a/fleet_install.sh b/fleet_install.sh index 8acd222..da68bdc 100755 --- a/fleet_install.sh +++ b/fleet_install.sh @@ -10,6 +10,11 @@ # ./fleet_install.sh install --sn --token \ # --server-url https://fleet.example.com [--post /api/v1/fleet/ingest/telemetry] \ # [--name NAME] [--user U] +# +# UPDATE an already-installed robot without re-entering the token — reuses the +# DEVICE_TOKEN already in the robot's .env (it never leaves the robot): +# ./fleet_install.sh install --sn --keep-token \ +# --server-url https://fleet.example.com # ./fleet_install.sh uninstall [--user U] # ./fleet_install.sh status # ./fleet_install.sh data # show the data it is sending @@ -109,6 +114,14 @@ push_env(){ for c in "/home/$USER_/SanadR1/data" "/home/$USER_/sanad_deploy/Sanad_Package_4/data"; do if rmt "test -d $c" 2>/dev/null; then dpath="/host$c"; break; fi done + # --keep-token: updating an already-installed robot without re-entering the + # device token. Snapshot the CURRENT .env on the robot; the old DEVICE_TOKEN + # line is spliced back below. The token never leaves the robot. + if [ "$KEEP_TOKEN" = 1 ]; then + rmt "test -f ~/$rdir/.env && grep -q '^DEVICE_TOKEN=' ~/$rdir/.env" \ + || die "--keep-token: no existing ~/$rdir/.env with a DEVICE_TOKEN on $IP (pass --token instead)" + rmt "cp ~/$rdir/.env ~/$rdir/.env.prev" + fi # common telemetry env (all agents) rmt_in "cat > ~/$rdir/.env" <_) --post) POST_ENDPOINT="$2"; shift 2;; # ingest POST path (telemetry or map endpoint)