Automatically build Raspberry PIs using Ansible

I started to build Raspberry PI images automatically using packer-builder-arm. This combined with Ansible is very reproducible tooling to automatically build images for only slightly different RaspberryPIs.

I need to automatically add a SSH key for the pi user, a wpa_supplicant config and a Tinc setup with the private key for exactly this PI.

But first I want to see if everything is good without flashing the PI every time. This can be done by mounting the image. To find out which sector is the start sector we can use fdisk like this:

$ fdisk -l raspios-arm.img

Disk raspios-arm.img: 4 GiB, 4294967296 bytes, 8388608 sectors
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: 0x8acef004

Device           Boot  Start     End Sectors  Size Id Type
raspios-arm.img1        8192  532479  524288  256M  c W95 FAT32 (LBA)
raspios-arm.img2      532480 8388607 7856128  3.7G 83 Linux

The start sector of the root partition is 532480 multiplied with the sector size of 512 bytes. So to mount the root partition we can run this:

mkdir -p root
sudo mount raspios-arm.img -o loop,offset=$(( 512 * 532480 )) root/

But now to the Ansible setup. I use this to setup Rasperry PIs and add then the actual sensor and scripts manually. The important parts for me are Wifi, SSH and (Tinc) VPN, so that I can login when they are deployed. To store this setup in a public repository, for others to see, I use Ansible Vault a lot.

The ssh playbook adds the ssh key and activates the service. Nobody needs to know the key I use for my PIs so the key is stored encrypted.

ssh.yaml:

---
- name: ssh keys
  ansible.posix.authorized_key:
    user: pi
    exclusive: true
    key: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          39626231646331333861393737303430396164303565373538626338346136303263316639303465
          3161346235626331323961666637323335383536363136300a396537663234643863363531623931
          39633665613232636637393932663063636165343466393262303137643266656532613761346236
          3963393766373632340a363162656365306332623630393665333539336134376534633434363635
          35323331363565663837333635363663656338353836613236343663333736666339356138303665
          35383239666131393635653563643137343461636435356165386164353635393766636432346566
          30313663653263303563643262643165356563386365313261663961313832303866393438326162
          64333036663631303531346239303162303837326466616634333530633438363063363934303762
          66346465323839643766343737323038386364633032663833303565323763316632
- name: Enable ssh service
  ansible.builtin.systemd:
    name: ssh
    enabled: true

Creating the "key" entry could be done like this:

cat ~/.ssh/id_ed25519.pub | ansible-vault encrypt_string --vault-password-file ansible/.vault_pass.txt --stdin-name 'key'

The other setups for WiFi and Tinc are similar. Tinc needs a few files to be generated so I used templates for them with a few variables from Ansible Vault. Differences between the PIs are stored in a variables file for each PI. Additionally a common.yaml with the variables that are shared.

My current setup is in a public repository and I will add more of my PIs in the following weeks.