Command Line Install Headless Raspberry Pi OS (bookworm)

Command Line Install Headless Raspberry Pi OS (bookworm)

Here's how we do our Raspberry Pi builds on the geekfarm. Most of the instructions I see for building raspi images require using the Raspberry Pi Imager or some other image utility. These instructions are for folks like me who just want to edit the files and run the commands themselves. This stuff is pretty simple, but doesn't seem to be well documented. Having this information makes it really easy to incorporate these steps in your own automation.

Step 1. Download the OS

Check for the latest release here:

Raspberry Pi OS downloads – Raspberry Pi
Raspberry Pi OS (previously called Raspbian) is our official, supported operating system.

Currently the latest release is trixie, but the initialization stuff has changed in trixie, and I haven't figured out how to fully automate it yet. So, let's stick with bookworm for now.


# where my raspi images live
cd ~/projects/rpi/images

# download the image
# use arm64 for raspi 4, 5, and pi zero 2w
curl -O https://downloads.raspberrypi.com/raspios_oldstable_lite_arm64/images/raspios_oldstable_lite_arm64-2025-10-02/2025-10-01-raspios-bookworm-arm64-lite.img.xz

# verify the digest from the website
sha256sum 2025-10-01-raspios-bookworm-arm64-lite.img.xz
#
# expected output
#
#   a65ebc80a792f0d04b720ec829dbf7b66b36f56310dbea34d462b19ba17b3687  2025-10-01-raspios-bookworm-arm64-lite.img.xz
#

# extract the image
xz -d 2025-10-01-raspios-bookworm-arm64-lite.img.xz

Step 2. Burn the image

Insert the SD Card and determine the device.


# check for sd card immediately after inserting
dmesg | tail
#    [1148783.447015] scsi host0: usb-storage 1-2:1.0
#    [1148784.450887] scsi 0:0:0:0: Direct-Access     Kingston UHS-II SD Reader 0004 PQ: 0 ANSI: 6
#    [1148784.747769] sd 0:0:0:0: [sda] 62333952 512-byte logical blocks: (31.9 GB/29.7 GiB)
#    [1148784.748569] sd 0:0:0:0: [sda] Write Protect is off
#    [1148784.748572] sd 0:0:0:0: [sda] Mode Sense: 21 00 00 00
#    [1148784.749313] sd 0:0:0:0: [sda] Write cache: disabled, read cache: enabled, doesn't support DPO or FUA
#    [1148784.754798]  sda: sda1 sda2
#    [1148784.755260] sd 0:0:0:0: [sda] Attached SCSI removable disk
#    [1148784.759276] sd 0:0:0:0: Attached scsi generic sg0 type 0
#    [1148785.178103] EXT4-fs (sda2): mounted filesystem 4d48c72f-b919-4823-96cc-04d8e6dcc211 r/w with ordered data mode. Quota mode: none.

# alternately check with fdisk
sudo /usr/sbin/fdisk -l
#
# example output
#
# ...snip...
#
#    Disk /dev/sda: 29.72 GiB, 31914983424 bytes, 62333952 sectors
#    Disk model: UHS-II SD Reader
#    Units: sectors of 1 * 512 = 512 bytes
#    Sector size (logical/physical): 512 bytes / 512 bytes
#    I/O size (minimum/optimal): 512 bytes / 512 bytes
#    Disklabel type: dos
#    Disk identifier: 0xa6973e7c
#
# ...snip...
#

IMPORTANT: Make sure the disk is not mounted. If it is mounted, you won't see errors during the 'dd' operation, but then the filesystem will have issues. Mine auto-mounted, but it took a minute or so after I inserted it, so patience is recommended Note the lines in the output that match your device name (the one in the example is /dev/sda).


# check to see if the disk is mounted
df -h
#
# example output - note the lines matching the device (mine are /dev/sda)
#
#    Filesystem      Size  Used Avail Use% Mounted on
#    udev            3.9G     0  3.9G   0% /dev
#    tmpfs           807M  6.6M  800M   1% /run
#    /dev/mmcblk0p2  117G   51G   61G  46% /
#    tmpfs           4.0G  528K  4.0G   1% /dev/shm
#    tmpfs           5.0M   64K  5.0M   2% /run/lock
#    /dev/mmcblk0p1  510M   77M  434M  16% /boot/firmware
#    tmpfs           806M  192K  806M   1% /run/user/1000
#    /dev/sda1       510M   95M  416M  19% /mnt/bootfs
#    /dev/sda2        29G  1.9G   26G   7% /mnt/rootfs


# unmount any filesystems that are mounted
umount /mnt/bootfs
umount /mnt/rootfs

Now use the device path to burn the image:

# wipe existing filesystem, this is probably not required
sudo dd if=/dev/zero of=/dev/sda bs=512 count=1
#
# should run really quickly, expected output is something like this:
#
#    1+0 records in
#    1+0 records out
#    512 bytes copied, 0.0323709 s, 15.8 kB/s
#

# use the device path to burn the image with dd, show progress
sudo dd if=2025-10-01-raspios-bookworm-arm64-lite.img of=/dev/sda status=progress bs=4M

#
# there was a pause for a minute or so at the end 
#               after it stopped showing progress
#
# here was the output I saw:
#
#    2726297600 bytes (2.7 GB, 2.5 GiB) copied, 332 s, 8.2 MB/s
#    650+0 records in
#    650+0 records out
#    2726297600 bytes (2.7 GB, 2.5 GiB) copied, 475.029 s, 5.7 MB/s#
#

Step 3. Configure

After the image finishes writing, the filesystem should be automatically mounted. Now it's time for configuration.


# run the commands below as root
sudo su -

# set variables
export RPI_HOSTNAME=myhostname
export RPI_USER=myuser
export RPI_PASS=mypass

# you may need to unmount the filesystem as before

# remount the filesystems
mkdir -p /mnt/bootfs /mnt/rootfs
mount /dev/sda1 /mnt/bootfs
mount /dev/sda2 /mnt/rootfs

# enable ssh
touch /mnt/bootfs/ssh

# set the hostname
echo "${RPI_HOSTNAME}" > /mnt/rootfs/etc/hostname

# replace the default hostname in /etc/hosts
perl -pi -e"s|raspberrypi|${RPI_HOSTNAME}|g" /mnt/rootfs/etc/hosts

# set up timezone
rm -f /mnt/rootfs/etc/localtime
echo "America/Los_Angeles" >/mnt/rootfs/etc/timezone

# set up default user - make sure to copy all the quotes
echo "${RPI_PASS}" | openssl passwd -6 -stdin | awk "{print \"${RPI_USER}:\"\$1}" > /mnt/bootfs/userconf.txt

# use the uuid command to generate a new uuid for the network config
uuid
#
# here's my output that is used in the example below
#
#    dd8ecdf6-ac94-11f0-91f3-fb01c27eb002

# create your wifi config file
vi /mnt/rootfs/etc/NetworkManager/system-connections/SSID.nmconnection

To generate the content for the file, replace a few things below:

  • SSID-NAME-HERE in two places with your wifi access point name
  • PASSWORD-HERE with your wifi password
  • value of 'uuid' with the one you generated above

[connection]

id=SSID-NAME-HERE
uuid=dd8ecdf6-ac94-11f0-91f3-fb01c27eb002
type=wifi
autoconnect=true

[wifi]
mode=infrastructure
ssid=SSID-NAME-HERE

[wifi-security]
auth-alg=open
key-mgmt=wpa-psk
psk=PASSWORD-HERE

[ipv4]
method=auto

[ipv6]
method=auto

Now just fix the perms and unmount the filesystems:


# now fix the perms on the file
chmod -R 600 /mnt/rootfs/etc/NetworkManager/system-connections/SSID.nmconnection
chown -R root:root /mnt/rootfs/etc/NetworkManager/system-connections/SSID.nmconnection

# finally, unmount the filesystems
umount /mnt/bootfs
umount /mnt/rootfs

Step 4. Boot up and log in

Pop the sd card into the raspi, plug in the power, and wait for it to boot it up.

Give it a few minutes for the first boot since it may be doing things like generating SSH keys and rebooting after resizing the filesystem.

Ideally this process would be fully automated, but if you are relying on wifi, there are unfortunately a few things you need to do interactively So, if you only have a wifi connection, you'll need a keyboard and monitor for this next set of instructions.


# unblock wifi using rfkill
rfkill unblock wifi

# turn on wifi
nmcli radio wifi on

# set the wifi country
wpa_cli -i wlan0 set country US

# now verify you have an ip address
ip a

If you are using a wired connection, find the IP address and verify it's up.


# if you have DHCP / DNS set up, try pinging using the hostname
ping mhostname

# alternately try pinging using mDNS
ping myhostname.local

# you might find the ip address using your router or wifi access point

# if all else fails, hook up a monitor and watch for the ip address


Now you can log in over ssh. If you previously logged into this raspi over ssh from other hosts, you'll need to clear the old ssh keys from the known hosts file on those other hosts.


# remove entries from known hosts file
ssh-keygen -R ${HOSTNAME}

# copy your ssh keys for password-free login
ssh-copy-id ${HOSTNAME}

# now try to login
ssh ${HOSTNAME}

Step 5. Future work

There are still a few things on my list.

  • fully automate wifi setup so it can be done headless
  • default keyboard mapping
  • pre-install my ssh key