Skip to content

Instantly share code, notes, and snippets.

@mharsch
Last active April 26, 2026 19:53
Show Gist options
  • Select an option

  • Save mharsch/46d826b5b1ba5cd9f3397df1f1080853 to your computer and use it in GitHub Desktop.

Select an option

Save mharsch/46d826b5b1ba5cd9f3397df1f1080853 to your computer and use it in GitHub Desktop.
Preconfigure Pi for pi-plates, CM4NA modem, tailscale, and NodeRED

Recent versions of the Raspberry Pi Imager Tool allow customization using cloud-init config and netplan files that are seeded on the boot partition of the sd card as part of writing the image. These files can be edited before booting the pi for the first time, with customizations that will run automatically at first boot.

To automatically pre-configure the pi SD card image for use with the ConnectedIO CM4NA modem, and tailscale do the following:

Prerequisites:

  • Generate an auth key from the Tailscale Admin Console (Settings -> Keys -> Generate auth key) copy and save for later
  • Plug in the CM4NA Modem (both data and power) to the Pi. Mode should be AT#USBCFG=0 (default from factory)

Use the latest version of Raspberry Pi Imager tool (ver 2.03 as of this writing)

  • Choose the Pi Model (e.g. Raspberry Pi 4)
  • Choose the Pi OS (recommend Raspberry Pi OS (other) -> Raspberry Pi OS Lite (64-bit)
  • Continue as normal up to the Write Image confirmation page
  • Click APP OPTIONS and de-select Eject media when finished (we are going to edit 2 files before ejecting)
  • Save options and write to flash device as usual
  • after write image completion, open the newly written drive (appears as 'bootfs' in the drive list)
  • edit the file network-config, add the following section at the end of the file - change apn to your carrier apn string (e.g. 'vzwinternet' or 'cbc135')
  modems:
    ttyACM0:
      renderer: NetworkManager
      dhcp4: true
      apn: "CHANGEME"
  • save the file and open the file user-data and add the following lines
  • Summary of changes (see complete file below)
  • add the following under packages: modemmanager, minicom, python3-pip
  • check timezone: value
  • under the rpi: -> interfaces: section add spi:true and i2c:true
  • add the remaining sections below, fill in the auth key with value from before

complete template for user-data file

#cloud-config
manage_resolv_conf: false
hostname: CHANGEME
manage_etc_hosts: true
packages:
- avahi-daemon
- modemmanager
- minicom
- python3-pip

apt:
  preserve_sources_list: true
  conf: |
    Acquire {
      Check-Date "false";
    };
timezone: America/Denver # Central is America/Chicago and Eastern is America/New_York
keyboard:
  model: pc105
  layout: "us"
users:
- name: pi
  groups: users,adm,dialout,audio,netdev,video,plugdev,cdrom,games,input,gpio,spi,i2c,render,sudo
  shell: /bin/bash
  lock_passwd: false
  plain_text_passwd: CHANGEME
enable_ssh: true
ssh_pwauth: true
rpi:
  interfaces:
    serial: true
    spi: true
    i2c: true

runcmd:
  # One-command install, from https://tailscale.com/download/
  - ['sh', '-c', 'curl -fsSL https://tailscale.com/install.sh | sh']
  # Generate an auth key from your Admin console
  # https://login.tailscale.com/admin/settings/keys
  # and replace the placeholder below
  - ['tailscale', 'up', '--auth-key=CHANGEME']

bootcmd:
  - [ systemctl, restart, systemd-timesyncd ]
  - [ /bin/sleep, "5" ]

ntp:
  enabled: true
  servers:
    - pool.ntp.org

power_state:
  mode: reboot
  delay: now
  timeout: 300   # wait up to 5min for all modules to complete then reboot
  condition: true
  • save and close file, eject sd card and boot pi + modem. Automated process will take 2-3min.
  • After final reboot, ssh into the pi and confirm modem is connected
  • ip a
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute
       valid_lft forever preferred_lft forever
2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN group default qlen 1000
    link/ether 88:a2:9e:44:10:26 brd ff:ff:ff:ff:ff:ff
3: wwan0: <BROADCAST,MULTICAST,NOARP> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 00:00:11:12:13:14 brd ff:ff:ff:ff:ff:ff
4: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 88:a2:9e:44:10:27 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.234/24 brd 192.168.1.255 scope global dynamic noprefixroute wlan0
       valid_lft 84603sec preferred_lft 84603sec
    inet6 fe80::8aa2:9eff:fe44:1027/64 scope link proto kernel_ll
       valid_lft forever preferred_lft forever
5: tailscale0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1280 qdisc fq_codel state UNKNOWN group default qlen 500
    link/none
    inet 100.79.146.45/32 scope global tailscale0
       valid_lft forever preferred_lft forever
    inet6 fd7a:115c:a1e0::2038:922d/128 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::6f14:4721:1704:6dc9/64 scope link stable-privacy proto kernel_ll
       valid_lft forever preferred_lft forever
6: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 3
    link/ppp
    inet 100.64.102.200/32 scope global ppp0
       valid_lft forever preferred_lft forever
  • mmcli
pi@platetestr:~ $ mmcli -L
    /org/freedesktop/ModemManager1/Modem/0 [Telit] LE910-NA V2
pi@platetestr:~ $ mmcli -m 0
  ----------------------------------
  General  |                   path: /org/freedesktop/ModemManager1/Modem/0
           |              device id: ec923033531d78c859d31c1960c860f34f310205
  ----------------------------------
  Hardware |           manufacturer: Telit
           |                  model: LE910-NA V2
           |      firmware revision: 20.00.505
           |              supported: gsm-umts, lte
           |                current: gsm-umts, lte
           |           equipment id: 358148064849321
  ----------------------------------
  System   |                 device: /sys/devices/platform/axi/1000120000.pcie/1f00300000.usb/xhci-hcd.1/usb3/3-2
           |                physdev: /sys/devices/platform/axi/1000120000.pcie/1f00300000.usb/xhci-hcd.1/usb3/3-2
           |                drivers: cdc_acm, cdc_ncm
           |                 plugin: telit
           |           primary port: ttyACM0
           |                  ports: ttyACM0 (at), ttyACM1 (ignored), ttyACM2 (ignored),
           |                         ttyACM3 (at), ttyACM4 (ignored), ttyACM5 (ignored), wwan0 (net)
  ----------------------------------
  Status   |         unlock retries: sim-pin (3), sim-puk (10), sim-pin2 (3), sim-puk2 (10)
           |                  state: connected
           |            power state: on
           |            access tech: lte
           |         signal quality: 42% (recent)
  ----------------------------------
  Modes    |              supported: allowed: 3g; preferred: none
           |                         allowed: 4g; preferred: none
           |                         allowed: 3g, 4g; preferred: none
           |                current: allowed: 3g, 4g; preferred: none
  ----------------------------------
  Bands    |              supported: utran-5, utran-2, eutran-2, eutran-4, eutran-5, eutran-12,
           |                         eutran-13, eutran-17
           |                current: utran-2, eutran-2
  ----------------------------------
  IP       |              supported: ipv4, ipv6, ipv4v6
  ----------------------------------
  3GPP     |                   imei: 358148064849321
           |            operator id: 310410
           |          operator name: FloLive
           |           registration: roaming
           |   packet service state: attached
  ----------------------------------
  3GPP EPS |   ue mode of operation: csps-1
           |    initial bearer path: /org/freedesktop/ModemManager1/Bearer/0
           | initial bearer ip type: ipv4v6
  ----------------------------------
  SIM      |       primary sim path: /org/freedesktop/ModemManager1/SIM/0
  ----------------------------------
  Bearer   |                  paths: /org/freedesktop/ModemManager1/Bearer/1
  • confirm tailscale is connected from admin console and running tailscale status
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment