The robot's computer · Chapter 06 · Time: 1.5 hours · Level: Beginner · Status: Done on this robot
The new system checked from top to bottom, your laptop's SSH aliases, passwordless sudo, and the base settings the robot runs with - shared memory kept, persistent journal, no Braille daemon, no desktop, linger for the audio user, fixed addresses.
The robot now boots its own clean system, but it is still a stock NVIDIA install. This chapter checks what booted
and then applies the handful of system settings every later chapter assumes. None of them is a robotics feature;
each one is here for a reason recorded on this robot.
The order below is the order that works on a fresh install. On this robot the steps were spread over twelve days:
the checks, SSH alias, brltty and sudo on 2026-09-26, the logind setting on 09-28, the fixed addresses on 09-29,
the journal on 10-02, and the headless boot with linger on 10-06. Each step names its date.
From here on, @robot means the new system, logged in as burgerbarn.
Run the full check the log ran at 18:46:24, right after the first boot. From your laptop it was sent as
ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 burgerbarn@192.168.1.22 'bash -s' <<'EOF',
then the lines below, then EOF.
On the robot:
echo "== identity"; hostname; whoami; id
echo "== root fs"; findmnt -no SOURCE,FSTYPE /; lsblk -no PARTLABEL,PARTUUID $(findmnt -no SOURCE /)
echo "== kernel"; uname -r; cat /proc/cmdline
head -1 /etc/nv_tegra_release; lsb_release -ds
echo "== net"; ip -br addr | grep -v "^lo"
echo "== model"; tr -d "\0" < /proc/device-tree/model; echo
echo "== hiwonder remnants"; systemctl list-unit-files --no-legend 2>/dev/null | grep -Ei "start_app|button_scan|find_device|wifi\.service|nxserver|remote\.service|set_default_device|sync_typerc" || echo none
ls /home
echo "== failed units"; systemctl --failed --no-legend
echo "== sshd"; sudo -n true 2>&1 | head -1
The hiwonder remnants line searches for the factory's own services by name. It must print none: this system
was built without any of them.
Check
The start of the output (log 18:46:24):== identity rosorin burgerbarn uid=1000(burgerbarn) gid=1000(burgerbarn) groups=1000(burgerbarn),4(adm),20(dialout),27(sudo),29(audio),44(video),104(render),996(weston-launch) == root fs /dev/nvme0n1p16 ext4 APP ef01d088-5975-4263-b620-2d0f5137dfe1 == kernel 5.15.148-tegra root=PARTUUID=ef01d088-5975-4263-b620-2d0f5137dfe1 rw rootwait rootfstype=ext4 mminit_loglevel=4 console=ttyTCU0,115200 ...The log cut off there.
docs/status.mdrecords the rest, written after the boot-control fix at the end of
chapter 5: R36.4.3, Ubuntu 22.04.5, wired cardenP8p1s0with DHCP address 192.168.1.22, no Hiwonder units,
0 failed units, and sudo asking for a password. The release, model and/homelines read on 2026-10-07 (the L4T
packages have been held since, chapter 9):# R36 (release), REVISION: 4.3, GCID: 38968081, BOARD: generic, EABI: aarch64, DATE: Wed Jan 8 01:49:37 UTC 2025 Ubuntu 22.04.5 LTS NVIDIA Jetson Orin NX Engineering Reference Developer Kit Super burgerbarnThe last line of your run is
sudo: a password is required(the robot's sudo log has
a password is required ; ... COMMAND=/usr/bin/trueat 18:46:27). That changes below.
If nv-l4t-bootloader-config.service is in the failed list, you skipped the last step of chapter 5, path A.
The etc/nv/nvautoconfig file from chapter 4 made NVIDIA's late-boot script run nvresizefs.sh and nvswap.sh
once. The new partition, created as 128-256 GB, now runs to the end of the disk, and swap is compressed RAM (zram),
not a swap file.
On the robot:
df -h / | tail -1
swapon --show
ls -la /etc/nv/
Check
From the log (18:53:53 and 18:54:55):/dev/nvme0n1p16 352G 6.7G 330G 2% / NAME TYPE SIZE USED PRIO /dev/zram0 partition 978.5M 0B 5 /dev/zram1 partition 978.5M 0B 5 ... /dev/zram7 partition 978.5M 0B 5Eight zram devices of 978.5M each.
docs/status.md: APP (p16) grew to 366,281 MB, zram swap only. Do not plan
around the partition size you created; it is not the final size (docs/lessons.md).
So far you typed burgerbarn@192.168.1.22. Give the robot short names in ~/.ssh/config on your laptop.
The first alias, written at 18:52:33 on 2026-09-26, pointed rosorin at the new system (it had pointed at the
factory's ubuntu@192.168.1.108):
On your laptop:
Host rosorin
HostName 192.168.1.22
User burgerbarn
IdentityFile ~/.ssh/id_ed25519
The aliases as they are now, after the router reservations later in this chapter and the Wi-Fi driver in chapter 7.
Add these to ~/.ssh/config on your laptop:
On your laptop:
Host rosorin-wifi
HostName 192.168.1.108
User burgerbarn
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
HostKeyAlias 192.168.1.22
Host rosorin
HostName 192.168.1.109
HostKeyAlias 192.168.1.22
User burgerbarn
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
ServerAliveInterval 30
ServerAliveCountMax 4
Host server
HostName 192.168.1.122
User matty
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
ServerAliveInterval 30
ServerAliveCountMax 4
Host bigbuddy fedora
HostName 192.168.1.111
User burgerbarn
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes: offer only this key, not every key your SSH agent holds.ServerAliveInterval 30 / ServerAliveCountMax 4: notice a dead connection after about two minutes.HostKeyAlias 192.168.1.22: look up and store the robot's host key under the name 192.168.1.22, whateverWhy HostKeyAlias
The robot's host key was first stored in~/.ssh/known_hostsunder 192.168.1.22, the address it had on first
boot. Its Wi-Fi address 192.168.1.108 is the address the factory install used, andknown_hostsstill held the
factory's keys for it (log 2026-09-27 23:13:02: three192.168.1.108entries plus three192.168.1.22entries).
Connecting to .108 by its plain address would fail with a changed-host-key warning. WithHostKeyAliasboth
addresses check against the one stored key of the new system. If you rebuild, use whatever address your robot
had on its first boot as the alias.The fingerprint you are trusting, read on the robot (log 2026-09-27 23:13:02):
256 SHA256:Kijh1+qj+MvcpH3FFvPX8TCsUwDDY5yPvl8VYrz4Xzc root@17a580ed7a62 (ED25519). The comment is the
container's hostname from chapter 4. A rebuild makes new keys and a new fingerprint.
rosorin-wifi works only after chapter 7. Test both aliases once both links are up (log 2026-09-29 14:39:35):
On your laptop:
ssh -o ConnectTimeout=5 rosorin 'echo "rosorin -> $(hostname) via $(echo $SSH_CONNECTION | awk "{print \$3}")"'; ssh -o ConnectTimeout=5 rosorin-wifi 'echo "rosorin-wifi -> $(hostname) via $(echo $SSH_CONNECTION | awk "{print \$3}")"'
Check
$SSH_CONNECTION's third field is the robot address your session arrived at:rosorin -> rosorin via 192.168.1.109 rosorin-wifi -> rosorin via 192.168.1.108
Why
Every install script in this guide runs with sudo, many of them over non-interactive SSH. The owner decided on
2026-09-26 that typing the password each time was too much intervention. The risk is bounded: SSH accepts only
your key, and logins by password or as root are off (docs/decisions.md2026-09-26). This is for the robot only,
not bigbuddy.
Create the file /tmp/90-burgerbarn on the robot with any editor. It holds exactly one line (34 bytes with the
final newline):
On the robot:
burgerbarn ALL=(ALL) NOPASSWD:ALL
Check its syntax, then install it with the owner, group and mode sudo requires. These are the two commands the owner
ran at 23:29:39 on 2026-09-26 (robot sudo log); sudo asks for your password one last time:
On the robot:
sudo visudo -cf /tmp/90-burgerbarn
sudo install -m 0440 -o root -g root /tmp/90-burgerbarn /etc/sudoers.d/90-burgerbarn
Always run visudo -c first. A file in /etc/sudoers.d with a syntax error can stop sudo from working at all, and
then you have no root access to fix it.
On your laptop:
for i in $(seq 1 30); do ssh -o BatchMode=yes rosorin 'sudo -n true' 2>/dev/null && { echo "passwordless sudo works"; break; }; sleep 5; done; ssh -o BatchMode=yes rosorin 'sudo -n ls -l /etc/sudoers.d/90-burgerbarn; sudo -n cat /etc/sudoers.d/90-burgerbarn'
Check
From the log (23:29:41):passwordless sudo works -r--r----- 1 root root 34 Sep 26 23:29 /etc/sudoers.d/90-burgerbarn burgerbarn ALL=(ALL) NOPASSWD:ALLTo undo:
sudo rm /etc/sudoers.d/90-burgerbarn.
The sample rootfs ships brltty, a daemon for Braille displays. On this robot it filled the journal with
Ignored Byte lines. It was the only package removed in the whole rebuild.
On the robot:
journalctl -b --no-pager -u brltty -u brltty-udev 2>/dev/null | grep -iE "tty|serial|device" | head -5
apt-get -s purge brltty 2>&1 | grep -E "^(Purg|Remv|Inst)|newly installed|to remove"
apt-get -s only simulates.
Check
From the log (22:40:47): the journal noise, then the simulation removing exactly one package:Sep 26 22:35:09 rosorin brltty[476]: Ignored Byte: FA Sep 26 22:35:09 rosorin brltty[476]: Ignored Byte: 1C Sep 26 22:35:09 rosorin brltty[476]: brltty: Ignored Byte: 1C Use 'apt autoremove' to remove them. 0 upgraded, 0 newly installed, 1 to remove and 48 not upgraded. Purg brltty [6.4-4ubuntu3]
Then the real purge, as the owner ran it at 22:48:36 (robot sudo log):
On the robot:
sudo apt-get purge -y brltty
On the robot:
dpkg-query -W -f='${Package} ${db:Status-Abbrev}\n' brltty 2>&1
pgrep -x brltty || echo "no brltty process"
Check
From the log (22:48:46).unmeans not installed:brltty un no brltty process
Never run apt autoremove on the robot
After the purge apt suggestsapt autoremove. On this robot the packages it wants to remove include
initramfs-tools, which L4T uses to build the initrd the robot boots with (docs/lessons.md). Ignore the
suggestion, here and after every later removal.
brltty and the LiDAR
brlttywas suspected of taking the LiDAR's serial port. It was not the cause: the stock kernel has no driver
for the LiDAR's CH340 chip at all (chapter 7).
New idea: how ROS 2 programs on one machine talk
The robot's software is many separate programs (ROS 2 nodes, chapter 9) that exchange messages through a
middleware called DDS; this robot uses Fast DDS. When two nodes run on the same machine, Fast DDS passes the
data through shared-memory files in/dev/shminstead of the network. Those files belong to the user the nodes
run as, hereburgerbarn.
systemd-logind, the service that tracks logins, has a setting RemoveIPC, which is yes by default: when a user's
last login session ends, logind deletes that user's shared-memory objects. The robot's services run as burgerbarn
without a login session, so every time you closed your last SSH session the running nodes lost their shared memory
and went silent. On 2026-09-28 this showed up as nodes that saw no messages for 30 seconds or more, and 0
fastrtps* files in /dev/shm minutes after a restart (docs/lessons.md 2026-09-28). The fix is a drop-in that
turns the setting off.
You have no ROS 2 yet, but set it now, before anything depends on it. The log's command (2026-09-28 05:35:34),
without its second half, which removed a test file that does not exist on a fresh build:
On your laptop:
ssh rosorin 'sudo mkdir -p /etc/systemd/logind.conf.d && printf "[Login]\n# rosorin-base runs as burgerbarn; default RemoveIPC=yes deletes its FastDDS /dev/shm segments\n# when the last burgerbarn login session ends -> base nodes go silent.\nRemoveIPC=no\n" | sudo tee /etc/systemd/logind.conf.d/10-rosorin-keep-ipc.conf >/dev/null && sudo systemctl restart systemd-logind && systemd-analyze cat-config systemd/logind.conf | grep "^RemoveIPC"'
It writes /etc/systemd/logind.conf.d/10-rosorin-keep-ipc.conf:
On the robot:
[Login]
# rosorin-base runs as burgerbarn; default RemoveIPC=yes deletes its FastDDS /dev/shm segments
# when the last burgerbarn login session ends -> base nodes go silent.
RemoveIPC=no
Check
systemd-analyze cat-configmerges the main file and all drop-ins; the lastRemoveIPCline wins:RemoveIPC=noAfter the fix on 2026-09-28: the shared-memory files survived separate SSH sessions, and a new node got its first
message after 0.6-1.7 s instead of none in 30 s.
If it fails
Two other suspects were tested on 2026-09-28 and were not the cause: two network interfaces on one subnet, and
ROS_LOCALHOST_ONLY=1(it did not help and was reverted). If nodes go silent after you log out, check this file
first.
On a stock install the systemd journal lives only in memory. On 2026-10-02 the robot failed to stop after a test,
was rebooted, and the log of the incident was gone (docs/lessons.md).
The state before the fix
ls /var/log/journal; grep -E "^#?Storage" /etc/systemd/journald.confon 2026-10-02 05:25:28:ls: cannot access '/var/log/journal': No such file or directory #Storage=auto
Storage=auto (the default, commented out in /etc/systemd/journald.conf) means: keep the journal on disk if the
folder /var/log/journal exists, otherwise in memory. So the fix is to create the folder correctly.
scripts/persistent_journal.sh is eight lines. The header says why and how to undo it, and refuses to run without
root:
On the robot:
#!/bin/bash
# Keep the systemd journal across reboots (2026-10-02: the stop-failure incident log was lost in the reboot).
# Rollback: sudo rm -rf /var/log/journal && sudo systemctl restart systemd-journald
set -euo pipefail
[ "$(id -u)" = 0 ] || { echo "run with sudo"; exit 1; }
Then it creates the folder with the ownership and permissions systemd's own rules define (systemd-tmpfiles),
moves the in-memory journal to disk (--flush), restarts journald, and prints the space used:
On the robot:
mkdir -p /var/log/journal && systemd-tmpfiles --create --prefix /var/log/journal
journalctl --flush; systemctl restart systemd-journald
journalctl --disk-usage
The whole file, as in the repo:
On the robot:
#!/bin/bash
# Keep the systemd journal across reboots (2026-10-02: the stop-failure incident log was lost in the reboot).
# Rollback: sudo rm -rf /var/log/journal && sudo systemctl restart systemd-journald
set -euo pipefail
[ "$(id -u)" = 0 ] || { echo "run with sudo"; exit 1; }
mkdir -p /var/log/journal && systemd-tmpfiles --create --prefix /var/log/journal
journalctl --flush; systemctl restart systemd-journald
journalctl --disk-usage
Every install script in this guide lives in ~/setup on the robot. Create the folder once, copy the script from the
repo folder on your laptop, and run it (the log's form, 2026-10-02 05:25:41, with rosorin instead of
rosorin-wifi):
On the robot:
mkdir -p ~/setup
On your laptop:
scp -q scripts/persistent_journal.sh rosorin:~/setup/
ssh rosorin 'sudo bash ~/setup/persistent_journal.sh | tail -1'
Check
Read on 2026-10-07 (the folder was created 2026-10-02 05:25):$ ls -ld /var/log/journal drwxr-sr-x+ 3 root systemd-journal 4096 Oct 2 05:25 /var/log/journal $ journalctl --disk-usage Archived and active journals take up 632.1M in the file system.After the next reboot,
journalctl -b -1 -n 2 --no-pagermust print the last lines of the previous boot (on
2026-10-07 it printedsystemd-shutdown[1]: Sending SIGTERM to remaining processes...and
systemd-journald[288]: Journal stopped).
If it fails
Do not judge persistence byjournalctl --list-bootson this robot. On 2026-10-07 it printed a single line
datedThu 1970-01-01 00:00:29 UTC, althoughjournalctl -b -1worked and the journal held 14 boot IDs. The
robot's clock is wrong for the first seconds of each boot (docs/lessons.md); whether that causes this is not
verified.
The stock install boots into a graphical login screen. The robot has no screen. On 2026-10-06 the owner turned the
desktop off: the GNOME login screen used about 340 MB of memory, and its audio server held the control device of the
robot's microphone array (chapter 27) (docs/decisions.md 2026-10-06).
New idea: systemd targets
A target is a named set of services that systemd brings up.graphical.targetincludes the display manager
(gdm3, the login screen);multi-user.targetis everything except that.default.targetis a link to the one
used at boot. Chapter 4 removed NVIDIA's link to its setup wizard; this step creates a new link.
The two commands, as run at 17:21:23 and 17:21:24 on 2026-10-06 (robot sudo log):
On the robot:
sudo systemctl set-default multi-user.target
sudo systemctl stop gdm3
On the robot:
systemctl get-default; echo "desktop processes left: $(pgrep -c "gnome-shell|Xorg|gdm")"
Check
From the log (17:21:22):Created symlink /etc/systemd/system/default.target → /lib/systemd/system/multi-user.target. multi-user.target desktop processes left: 0After a reboot (17:22:40):
target: multi-user.target | desktop procs: 0. To undo:
sudo systemctl set-default graphical.target.
Each user has their own systemd manager, which runs that user's background services, for example the PulseAudio
sound server the robot's microphone and speaker use (chapter 27). Normally it starts at the first login and stops at
the last logout. Linger makes it start at boot and stay, with nobody logged in.
The first headless reboot showed why it is needed: linger: Linger=no (2026-10-06 17:22:40). It was switched on two
minutes later (17:24:47, robot sudo log):
On the robot:
sudo loginctl enable-linger burgerbarn
loginctl show-user burgerbarn -p Linger
Check
From the log (2026-10-06 17:24:43):Linger=yes
The robot gets its addresses by DHCP from the router. NetworkManager manages the wired card automatically as
Wired connection 1 (the stock rootfs has the empty /etc/NetworkManager/conf.d/10-globally-managed-devices.conf
that allows it). Fixed addresses are DHCP reservations in the router, by MAC address. The owner set them on
2026-09-29 (docs/hardware.md "Network addresses"):
| Link | Interface | MAC | Address |
|---|---|---|---|
| Wired | enP8p1s0 |
192.168.1.109 | |
| Wi-Fi (chapter 7) | wlP1p1s0 |
192.168.1.108 |
Read the MACs on your robot:
On the robot:
ip -br link show enP8p1s0; ip -br link show wlP1p1s0
Check
Read on 2026-10-07 (the wired cable was unplugged that day, henceDOWN):enP8p1s0 DOWN <robot-ethernet-MAC> <NO-CARRIER,BROADCAST,MULTICAST,UP> wlP1p1s0 UP <robot-wifi-MAC> <BROADCAST,MULTICAST,UP,LOWER_UP>The MACs are fixed: on 2026-09-29 the current MAC equalled the permanent one on both cards, neither
NetworkManager connection had a cloned MAC, andwifi.scan-rand-mac-address=no.
The robot takes the reservation the next time it renews its lease. On 2026-09-29 each link's lease was renewed over
the other link, so the SSH session never ran over the link being restarted (14:38:59 and 14:39:20). Before Wi-Fi
works (chapter 7) you cannot do this; the record does not show another method.
On your laptop:
ssh -o HostKeyAlias=192.168.1.22 -o ConnectTimeout=5 burgerbarn@192.168.1.108 'sudo -n nmcli connection down "Wired connection 1" >/dev/null; sudo -n nmcli connection up "Wired connection 1" >/dev/null; sleep 3; ip -4 -br addr show enP8p1s0; ip -4 -br addr show wlP1p1s0'
Check
From the log (14:39:20):enP8p1s0 UP 192.168.1.109/24 wlP1p1s0 UP 192.168.1.108/24Then the alias test from the SSH section must show
via 192.168.1.109andvia 192.168.1.108.
If it fails
NO-CARRIERright after plugging in the cable. The link takes several seconds. On the factory system on
2026-09-26 the card readNO-CARRIERat 17:06:54 and had 192.168.1.22 at 17:09:01. Wait and check again
before you suspect the cable (docs/lessons.md).- No
eth0. The wired card isenP8p1s0on this system, because chapter 5 left outnet.ifnames=0.- The address changed by itself. Before the reservations the wired address moved from .22 to .26. That was a
new DHCP lease, not a new MAC.- With both links up the default route is the wired one (NetworkManager gave wired a lower route metric, 100
against 600, on the factory system). On 2026-09-27,ip route get 192.168.1.1in a session that came in over
Wi-Fi still answereddev enP8p1s0.
Reboot once and check that every setting survived. Use the reboot form from chapter 5:
On your laptop:
ssh -o BatchMode=yes -o ServerAliveInterval=3 -o ServerAliveCountMax=2 rosorin 'sync; sudo -n systemctl reboot'
When it answers again (about 50 s to a prompt on this robot, docs/status.md 2026-10-06):
On your laptop:
ssh rosorin 'systemctl get-default; loginctl show-user burgerbarn -p Linger; systemd-analyze cat-config systemd/logind.conf | grep "^RemoveIPC"; ls -ld /var/log/journal; dpkg-query -W -f="\${Package} \${db:Status-Abbrev}\n" brltty; sudo -n true && echo "passwordless sudo works"; systemctl --failed --no-legend | wc -l'
Check
This line combines the individual checks above. Read on 2026-10-07 the same commands gave:multi-user.target Linger=yes RemoveIPC=no drwxr-sr-x+ 3 root systemd-journal 4096 Oct 2 05:25 /var/log/journal brltty un passwordless sudo works 0The last line is the number of failed units.
ssh rosorin hostname prints rosorin, and the root filesystem is /dev/nvme0n1p16.ssh rosorin 'sudo -n true && echo ok' prints ok without asking for a password.multi-user.target, Linger=yes, RemoveIPC=no, and journalctl -b -1 shows the previous boot.brltty is un, and you did not run apt autoremove.systemctl --failed lists nothing.Where this comes from
Command log, robot: 2026-09-26 18:46:24 (first-boot check), 18:52:33 (firstrosorinalias), 18:53:53 /
18:54:35 / 18:54:46 / 18:54:55 (resize, nvautoconfig, zram), 22:40:47 and 22:48:46 (brltty), 23:29:41 (sudo
check); 2026-09-27 23:13:02 / 23:13:18 (known_hosts,rosorin-wifialias), 17:06:54 / 17:09:01 (no carrier);
2026-09-28 05:35:34 (logind drop-in); 2026-09-29 14:38:59 / 14:39:20 / 14:39:35 (lease renewals, alias test);
2026-10-02 05:25:28 / 05:25:41 (journal); 2026-10-06 17:21:22, 17:22:40, 17:24:43 (headless, linger). Robot
/var/log/auth.logsudo lines (apt-get purge -y brltty09-26 22:48:36;visudo -cfandinstall -m 0440
09-26 23:29:39; logind 09-28 05:35:37;persistent_journal.sh10-02 05:25:43;set-default,stop gdm3,
enable-linger10-06). Read-only lookups on the robot 2026-10-07 (settings, journal, MACs, release lines) and
the laptop's~/.ssh/config. Repo:scripts/persistent_journal.sh,docs/decisions.md2026-09-26 (sudo) and
2026-10-06 (no desktop),docs/lessons.md"Rebuild (2026-09-26)" and 2026-09-28 (RemoveIPC),docs/hardware.md
"Network" and "Network addresses",docs/status.md"Done (verified)" and 2026-10-06 milestone,AGENTS.mdrule 7.
← Install the OS on the NVMe · Contents · Add the missing drivers →