Last active
July 20, 2025 12:33
-
-
Save paalbra/f623b9d55007b2f413f072ef5644d5db to your computer and use it in GitHub Desktop.
Initialization script for Raspberry PI (first boot)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| WLAN_COUNTRY="NO" | |
| LOCALE_KEYMAP="no" | |
| LOCALE_TIMEZONE="Europe/Oslo" | |
| if [ "$EUID" -ne 0 ]; then | |
| >&2 echo "Error. Need to be root." | |
| exit 1 | |
| fi | |
| if [ -z $1 ] || [ -z "$2" ]; then | |
| >&2 echo "Usage: $0 DEVICE ISOFILE" | |
| exit 1 | |
| fi | |
| DEVICE="$1" | |
| ISOFILE="$2" | |
| if [ ! -b "$DEVICE" ] || [ ! -f "$ISOFILE" ]; then | |
| >&2 echo "Error. No such device \"$DEVICE\" or file \"$ISOFILE\"." | |
| exit 1 | |
| fi | |
| readarray -t DEVICEPARTS < <(lsblk -ln -o PATH "$DEVICE") | |
| for i in "${!DEVICEPARTS[@]}"; do | |
| if findmnt -rnS "${DEVICEPARTS[$i]}" >/dev/null; then | |
| >&2 echo "Error. Parition is mounted \"${DEVICEPARTS[$i]}." | |
| exit 1 | |
| fi | |
| done | |
| read -p "Password for user \"pi\": " -s PI_PASSWORD && echo | |
| read -p "Wifi SSID: " WIFI_SSID | |
| read -p "Password for wifi: " -s WIFI_PASSWORD && echo | |
| echo "Writing \"$ISOFILE\" to \"$DEVICE\"." | |
| dd if="$ISOFILE" of="$DEVICE" bs=128M status=progress | |
| readarray -t DEVICEPARTS < <(lsblk -ln -o PATH "$DEVICE") | |
| if [ ${#DEVICEPARTS[@]} -ne 3 ]; then | |
| >&2 echo "Error. Device \"$DEVICE\" does not have two partitions: \"${DEVICEPARTS[@]}\"." | |
| exit 1 | |
| fi | |
| BOOTPART="${DEVICEPARTS[1]}" | |
| ROOTPART="${DEVICEPARTS[2]}" | |
| echo "Boot partition: $BOOTPART" | |
| echo "Root partition: $ROOTPART" | |
| BOOTMNT="$(mktemp -d /tmp/tmp.rpi.boot.XXXXXXXXXX)/" | |
| ROOTMNT="$(mktemp -d /tmp/tmp.rpi.root.XXXXXXXXXX)/" | |
| mount -o rw,noatime "$BOOTPART" "$BOOTMNT" | |
| mount -o rw,noatime "$ROOTPART" "$ROOTMNT" | |
| if [ ! -f "${ROOTMNT}etc/rpi-issue" ]; then | |
| >&2 echo "Error. rpi-issue file not found: \"${ROOTMNT}etc/rpi-issue\"." | |
| exit 1 | |
| fi | |
| if ! ls "$BOOTMNT"*rpi* &>/dev/null; then | |
| >&2 echo "Error. Boot mount doesn't look like a Raspberry PI boot mount: \"$BOOTMNT\"." | |
| exit 1 | |
| fi | |
| cat << EOF > "${BOOTMNT}custom.toml" | |
| config_version = 1 | |
| [system] | |
| hostname = "raspy" | |
| [user] | |
| name = "pi" | |
| password = "$(echo "$PI_PASSWORD" | openssl passwd -5 -stdin)" | |
| password_encrypted = true | |
| [ssh] | |
| enabled = true | |
| password_authentication = true | |
| [wlan] | |
| ssid = "$WIFI_SSID" | |
| password = "$WIFI_PASSWORD" | |
| country = "$WLAN_COUNTRY" | |
| [locale] | |
| keymap = "$LOCALE_KEYMAP" | |
| timezone = "$LOCALE_TIMEZONE" | |
| EOF | |
| less "${BOOTMNT}custom.toml" | |
| cat << EOF > "${ROOTMNT}init.sh" | |
| PACKAGES=( | |
| lsof | |
| python3-gpiozero | |
| python3-picamera | |
| python3-venv | |
| vim | |
| ) | |
| function header() { | |
| echo -e "\n=========================" | |
| date --iso-8601=s | |
| echo -e "=========================\n" | |
| } | |
| function perform() { | |
| header | |
| echo "$1" | |
| shift | |
| echo Performing: "\${@}" | |
| "\$@" | |
| } | |
| function set_hostname() { | |
| if [[ "\$HOSTNAME" != "raspy" ]]; then | |
| echo "Custom hostname already set: \$HOSTNAME" | |
| return | |
| fi | |
| # Notes: | |
| # Example content from /proc/device-tree/model: | |
| # * 1b: Raspberry Pi Model B Rev 2 | |
| # * 3b+: Raspberry Pi 3 Model B Plus Rev 1.3 | |
| # * 4b: Raspberry Pi 4 Model B Rev 1.1 | |
| LASTMAC=\$(perl -pe 's/://g' /sys/class/net/wlan0/address | tail -c 5) | |
| SHORTNAME=\$(perl -pe 's/(Raspberry Pi|Model|Rev .*|Plus| )//g' /proc/device-tree/model | tr '[:upper:]' '[:lower:]') | |
| NEW_HOSTNAME="pi-\$SHORTNAME-\$LASTMAC" | |
| echo "HOSTNAME: \\"\$HOSTNAME\\", NEW_HOSTNAME: \\"\$NEW_HOSTNAME\\"" | |
| hostnamectl set-hostname "\$NEW_HOSTNAME" && perl -i -pe "s/raspy/\$NEW_HOSTNAME/g" /etc/hosts | |
| } | |
| function install_and_upgrade() { | |
| apt-get update | |
| apt-get install -y "\$@" | |
| apt-get upgrade -y | |
| } | |
| perform "Setting hostname:" set_hostname | |
| perform "Enabling SSH:" systemctl enable --now ssh.service | |
| perform "Current ENV:" env | |
| perform "Sleeping 30s. Hoping for network..." sleep 30 | |
| perform "IP info:" ip address | |
| header | |
| if ! ping -c 4 -W 1 8.8.8.8; then | |
| echo "No network? Exiting" | |
| exit 1 | |
| fi | |
| perform "Performing apt-get update, install and upgrade:" install_and_upgrade \${PACKAGES[*]} | |
| perform "Done. Rebooting..." /usr/sbin/reboot | |
| EOF | |
| less "${ROOTMNT}init.sh" | |
| echo "@reboot root /bin/bash /init.sh 1> /init.log 2>&1 && rm /etc/cron.d/init" > "${ROOTMNT}/etc/cron.d/init" | |
| less ${ROOTMNT}etc/cron.d/init | |
| umount "$BOOTMNT" | |
| umount "$ROOTMNT" | |
| rmdir "$BOOTMNT" | |
| rmdir "$ROOTMNT" | |
| echo "Done." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment