From a457c0932b697fd32febe79c473c7e06dccd355a Mon Sep 17 00:00:00 2001 From: Luke Hoersten Date: Sat, 1 Aug 2026 09:53:44 -0500 Subject: rpi-base: role-owned netplan replaces wpa_supplicant; auto-upgrades gain -updates and 04:00 reboot Networking: one 40-net.yaml (eth0 always, wifi via rpi_base_wifi_*) with cloud-init's 50-cloud-init.yaml deleted after first boot. cloud-init still renders it on an instance_id bump, and it sorts later, so editing the card's network-config and bumping meta-data is the headless rescue path. ssh_deletekeys off so a rescue bump keeps the host keys. Auto-updates: periodic switches and local policy merged into one 52unattended-upgrades-local; 50unattended-upgrades stays a distro conffile and debconf's 20auto-upgrades can no longer disable anything. --- rpi-base/defaults/main.yaml | 4 ++++ rpi-base/files/10-rpi-base.cfg | 4 ++++ rpi-base/files/20auto-upgrades | 4 ---- rpi-base/files/52unattended-upgrades-local | 20 ++++++++++++++++++++ rpi-base/handlers/main.yaml | 4 ++++ rpi-base/tasks/main.yaml | 17 +++++++++++------ rpi-base/tasks/netplan.yaml | 25 +++++++++++++++++++++++++ rpi-base/templates/40-net.yaml.j2 | 18 ++++++++++++++++++ rpi-base/templates/wpa_supplicant.conf.j2 | 14 -------------- 9 files changed, 86 insertions(+), 24 deletions(-) create mode 100644 rpi-base/files/10-rpi-base.cfg delete mode 100644 rpi-base/files/20auto-upgrades create mode 100644 rpi-base/files/52unattended-upgrades-local create mode 100644 rpi-base/tasks/netplan.yaml create mode 100644 rpi-base/templates/40-net.yaml.j2 delete mode 100644 rpi-base/templates/wpa_supplicant.conf.j2 (limited to 'rpi-base') diff --git a/rpi-base/defaults/main.yaml b/rpi-base/defaults/main.yaml index 34f4072..696a179 100644 --- a/rpi-base/defaults/main.yaml +++ b/rpi-base/defaults/main.yaml @@ -1,6 +1,10 @@ --- rpi_base_enable_wifi: True +# Public key file on the controller, authorized for the admin and ansible +# users on every host. EXCLUSIVE: every other key gets removed from +# authorized_keys, so this must be the key used to reach the hosts. +rpi_base_ssh_public_key_file: "~/.ssh/id_ed25519.pub" rpi_base_timezone: "America/Chicago" rpi_base_log_size: "100M" rpi_base_apt_packages: diff --git a/rpi-base/files/10-rpi-base.cfg b/rpi-base/files/10-rpi-base.cfg new file mode 100644 index 0000000..7ce3927 --- /dev/null +++ b/rpi-base/files/10-rpi-base.cfg @@ -0,0 +1,4 @@ +# Ansible managed (rpi-base). A rescue instance_id bump re-runs the +# cloud-init ssh module, which by default deletes and regenerates the +# ssh host keys on a new instance; keep them. +ssh_deletekeys: false diff --git a/rpi-base/files/20auto-upgrades b/rpi-base/files/20auto-upgrades deleted file mode 100644 index 5d37e9f..0000000 --- a/rpi-base/files/20auto-upgrades +++ /dev/null @@ -1,4 +0,0 @@ -APT::Periodic::Update-Package-Lists "1"; -APT::Periodic::Download-Upgradeable-Packages "1"; -APT::Periodic::AutocleanInterval "7"; -APT::Periodic::Unattended-Upgrade "1"; diff --git a/rpi-base/files/52unattended-upgrades-local b/rpi-base/files/52unattended-upgrades-local new file mode 100644 index 0000000..e567293 --- /dev/null +++ b/rpi-base/files/52unattended-upgrades-local @@ -0,0 +1,20 @@ +// Ansible managed (rpi-base). The whole auto-update config: the periodic +// switches conventionally kept in the debconf-owned 20auto-upgrades, plus +// local policy over the stock 50unattended-upgrades (left a pristine +// distro conffile). This file sorts after both; apt gives later files the +// last word on scalars and appends list entries, so 20auto-upgrades can +// never disable these switches and Allowed-Origins keeps its security +// entries while gaining -updates. +APT::Periodic::Update-Package-Lists "1"; +APT::Periodic::Download-Upgradeable-Packages "1"; +APT::Periodic::AutocleanInterval "7"; +APT::Periodic::Unattended-Upgrade "1"; + +// security (from 50) plus regular bugfix updates +Unattended-Upgrade::Allowed-Origins { + "${distro_id}:${distro_codename}-updates"; +}; + +// reboot at night when an installed update requires it +Unattended-Upgrade::Automatic-Reboot "true"; +Unattended-Upgrade::Automatic-Reboot-Time "04:00"; diff --git a/rpi-base/handlers/main.yaml b/rpi-base/handlers/main.yaml index 5335035..e474112 100644 --- a/rpi-base/handlers/main.yaml +++ b/rpi-base/handlers/main.yaml @@ -1,5 +1,9 @@ --- +- name: apply netplan + command: "netplan apply" + become: yes + - name: restart log2ram service systemd: name="log2ram" state="restarted" daemon_reload="yes" become: yes diff --git a/rpi-base/tasks/main.yaml b/rpi-base/tasks/main.yaml index 2a94df5..b7168e3 100644 --- a/rpi-base/tasks/main.yaml +++ b/rpi-base/tasks/main.yaml @@ -13,9 +13,8 @@ become: yes timezone: name="{{rpi_base_timezone}}" -- name: setup wifi - become: yes - template: src="wpa_supplicant.conf.j2" dest="/etc/wpa_supplicant/wpa_supplicant.conf" mode="0600" +- include_tasks: netplan.yaml + when: ansible_distribution == "Ubuntu" - name: update apt package cache become: yes @@ -25,9 +24,12 @@ become: yes apt: name="{{rpi_base_apt_packages}}" state="latest" +# Periodic switches plus local policy in one file; the stock +# 50unattended-upgrades (security-only, no reboot) stays pristine and the +# debconf-owned 20auto-upgrades is left alone (52 sorts later and wins). - name: configure auto upgrades become: yes - copy: src="20auto-upgrades" dest="/etc/apt/apt.conf.d/20auto-upgrades" + copy: src="52unattended-upgrades-local" dest="/etc/apt/apt.conf.d/52unattended-upgrades-local" - name: configure logrotate become: yes @@ -69,9 +71,12 @@ append: yes no_log: true -- name: authorize ssh keys +- name: authorize ssh key become: yes - authorized_key: user="{{item}}" key="https://github.com/{{github_user}}.keys" + authorized_key: + user: "{{item}}" + key: "{{ lookup('file', rpi_base_ssh_public_key_file) }}" + exclusive: yes loop: - "{{admin_user_name}}" - "{{ansible_user}}" diff --git a/rpi-base/tasks/netplan.yaml b/rpi-base/tasks/netplan.yaml new file mode 100644 index 0000000..eda0e69 --- /dev/null +++ b/rpi-base/tasks/netplan.yaml @@ -0,0 +1,25 @@ +--- + +# One role-owned netplan file replaces cloud-init's render: eth0 always, +# wifi when enabled. cloud-init only re-renders on a new instance_id, so +# the deletion below sticks across reboots. +# +# RESCUE a dark pi: on the card's FAT partition, edit network-config (a +# phone hotspot entry works) and bump instance_id in meta-data, then +# boot. The re-rendered 50-cloud-init.yaml sorts after 40-net.yaml and +# overrides it; the next playbook run deletes it again. +- name: install netplan config + become: yes + template: src="40-net.yaml.j2" dest="/etc/netplan/40-net.yaml" mode="0600" + notify: apply netplan + +- name: remove the cloud-init netplan config + become: yes + file: path="/etc/netplan/50-cloud-init.yaml" state="absent" + notify: apply netplan + +# A rescue instance_id bump re-runs cloud-init's ssh module, which by +# default deletes and regenerates the host keys; keep them. +- name: keep ssh host keys on cloud-init re-runs + become: yes + copy: src="10-rpi-base.cfg" dest="/etc/cloud/cloud.cfg.d/10-rpi-base.cfg" diff --git a/rpi-base/templates/40-net.yaml.j2 b/rpi-base/templates/40-net.yaml.j2 new file mode 100644 index 0000000..f473a2b --- /dev/null +++ b/rpi-base/templates/40-net.yaml.j2 @@ -0,0 +1,18 @@ +# {{ansible_managed}} + +network: + version: 2 + ethernets: + eth0: + dhcp4: true + optional: true +{% if rpi_base_enable_wifi %} + wifis: + wlan0: + regulatory-domain: "US" + dhcp4: true + optional: true + access-points: + "{{rpi_base_wifi_ssid}}": + password: "{{rpi_base_wifi_password}}" +{% endif %} diff --git a/rpi-base/templates/wpa_supplicant.conf.j2 b/rpi-base/templates/wpa_supplicant.conf.j2 deleted file mode 100644 index c1312d0..0000000 --- a/rpi-base/templates/wpa_supplicant.conf.j2 +++ /dev/null @@ -1,14 +0,0 @@ -# {{ansible_managed}} - -country=US -ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev -update_config=1 -{% for network in wpa_networks %} - -network={ - ssid="{{network.ssid}}" - psk="{{network.psk}}" - disabled={% if rpi_base_enable_wifi %}0{% else %}1{% endif %} - -} -{% endfor %} -- cgit v1.2.3