diff options
author | Christian Pointner <equinox@spreadspace.org> | 2024-04-28 18:46:49 +0200 |
---|---|---|
committer | Christian Pointner <equinox@spreadspace.org> | 2024-04-28 18:46:49 +0200 |
commit | 2ccdc4546d38ed0e9d6784668168c4d566311f6f (patch) | |
tree | f7893ea03c648e63a1ace6388b2ab0162b1861a6 /roles/installer/raspios | |
parent | update prometheus exporter ssl and chrony (diff) |
move raspios and openwrt folders to installer/
Diffstat (limited to 'roles/installer/raspios')
-rw-r--r-- | roles/installer/raspios/image/defaults/main.yml | 17 | ||||
-rw-r--r-- | roles/installer/raspios/image/filter_plugins/main.py | 51 | ||||
-rw-r--r-- | roles/installer/raspios/image/tasks/fetch.yml | 60 | ||||
-rw-r--r-- | roles/installer/raspios/image/tasks/main.yml | 103 | ||||
-rw-r--r-- | roles/installer/raspios/image/templates/firstrun.sh.j2 | 103 | ||||
-rw-r--r-- | roles/installer/raspios/image/vars/main.yml | 4 |
6 files changed, 338 insertions, 0 deletions
diff --git a/roles/installer/raspios/image/defaults/main.yml b/roles/installer/raspios/image/defaults/main.yml new file mode 100644 index 00000000..3f6ab3a3 --- /dev/null +++ b/roles/installer/raspios/image/defaults/main.yml @@ -0,0 +1,17 @@ +--- +raspios_variant: lite ## (lite|desktop|full) +raspios_codename: "{{ install_codename }}" +# raspios_arch: (arm64|armhf) +raspios_download_dir: "{{ global_cache_dir }}/raspios" + +raspios_output_dir: "{{ global_artifacts_dir }}/{{ inventory_hostname }}/raspios" + +raspios_keep_boot_dir_mounted: no + +# raspios_boot_config: +# - regexp: '^#dtparam=i2c_vc' +# line: 'dtparam=i2c_vc=on' + +raspios_locale: en_US.UTF-8 +raspios_timezone: Europe/Vienna +raspios_keyboard_layout: de diff --git a/roles/installer/raspios/image/filter_plugins/main.py b/roles/installer/raspios/image/filter_plugins/main.py new file mode 100644 index 00000000..5ac2d5ab --- /dev/null +++ b/roles/installer/raspios/image/filter_plugins/main.py @@ -0,0 +1,51 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +from ansible import errors +from urllib.parse import urlparse, urlunparse + + +def _raspios_extract_latest_image_download_url_recursive(items, image_name_suffix): + for item in items: + if not item['name'].startswith('Raspberry Pi OS '): + continue + + if 'url' in item: + result = urlparse(item['url']) + if not os.path.basename(result.path).endswith(image_name_suffix): + continue + + return urlunparse(result) + if 'subitems' in item: + result = _raspios_extract_latest_image_download_url_recursive(item['subitems'], image_name_suffix) + if result: + return result + return None + + +def raspios_extract_latest_image_download_url(os_list, variant, codename, arch): + try: + image_name_suffix = 'raspios-%s-%s' % (codename, arch) + if variant != 'desktop': + image_name_suffix += '-%s' % variant + image_name_suffix += '.img.xz' + + result = _raspios_extract_latest_image_download_url_recursive(os_list, image_name_suffix) + if result: + return result + + except Exception as e: + raise errors.AnsibleFilterError("raspios_extract_latest_image_download_url: %s" % str(e)) + + raise errors.AnsibleFilterError("unable to find latest image url for: %s" % (base_url)) + + +class FilterModule(object): + + filter_map = { + 'raspios_extract_latest_image_download_url': raspios_extract_latest_image_download_url, + } + + def filters(self): + return self.filter_map diff --git a/roles/installer/raspios/image/tasks/fetch.yml b/roles/installer/raspios/image/tasks/fetch.yml new file mode 100644 index 00000000..bbde6bdf --- /dev/null +++ b/roles/installer/raspios/image/tasks/fetch.yml @@ -0,0 +1,60 @@ +--- +- name: Create download directory + file: + dest: "{{ raspios_download_dir }}" + state: directory + +- name: fetch imageutility os list from download server + check_mode: no + uri: + url: "{{ raspios_download_url }}/os_list_imagingutility_v4.json" + body_format: json + register: raspios_os_list_imagingutility + +- set_fact: + raspios_download_url_image: "{{ raspios_os_list_imagingutility.json.os_list | raspios_extract_latest_image_download_url(raspios_variant, raspios_codename, raspios_arch) }}" + +- name: download the raspios image + block: + - name: download sha256sum and signature + loop: + - sha256 + - sig + get_url: + url: "{{ raspios_download_url_image }}.{{ item }}" + dest: "{{ raspios_download_dir }}/{{ raspios_download_image_base_name }}.{{ item }}" + + - name: extract SHA256 hash of the image archive + command: grep '{{ raspios_download_image_base_name }}' "{{ raspios_download_dir }}/{{ raspios_download_image_base_name }}.sha256" + changed_when: False + register: sha256 + + - name: download image + get_url: + url: "{{ raspios_download_url_image }}" + dest: "{{ raspios_download_dir }}/{{ raspios_download_image_base_name }}" + checksum: sha256:{{ sha256.stdout.split(' ') | first }} + + - name: check OpenPGP signature + command: >- + gpgv --keyring "{{ global_files_dir }}/common/keyrings/raspberrypi-downloads.gpg" + "{{ raspios_download_dir }}/{{ raspios_download_image_base_name }}.sig" "{{ raspios_download_dir }}/{{ raspios_download_image_base_name }}" + changed_when: False + register: raspios_image_gpg_result + + - debug: + var: raspios_image_gpg_result.stderr_lines + + rescue: + - name: delete downloaded artifacts + loop: + - "{{ raspios_download_dir }}/{{ raspios_download_image_base_name }}-sha256" + - "{{ raspios_download_dir }}/{{ raspios_download_image_base_name }}.sig" + - "{{ raspios_download_dir }}/{{ raspios_download_image_base_name }}" + file: + path: "{{ item }}" + state: absent + + - name: the download has failed... + fail: + msg: Something borked diff --git a/roles/installer/raspios/image/tasks/main.yml b/roles/installer/raspios/image/tasks/main.yml new file mode 100644 index 00000000..b8296f9a --- /dev/null +++ b/roles/installer/raspios/image/tasks/main.yml @@ -0,0 +1,103 @@ +--- +- name: check if host is member of the raspios group + assert: + msg: "please add the host to the group 'raspios'" + that: + - "'raspios' in group_names" + +- name: fetch base image + run_once: true + import_tasks: fetch.yml + +- name: build the image + block: + - name: create the output directory for built images + file: + path: "{{ raspios_output_dir }}" + state: directory + + - name: extract image + decompress: + src: "{{ raspios_download_dir }}/{{ raspios_download_image_base_name }}" + dest: "{{ raspios_output_dir }}" + force: yes + register: raspios_image_extract + + - set_fact: + raspios_output_image_base_name: "{{ raspios_image_extract.files | first | basename }}" + + - name: read partition layout from image + command: "sfdisk -q -r -J '{{ raspios_output_dir }}/{{ raspios_output_image_base_name }}'" + register: raspios_image_sfdisk + + - set_fact: + raspios_image_partitions: "{{ (raspios_image_sfdisk.stdout | from_json)['partitiontable']['partitions'] }}" + + - name: bind loop device for boot partition + command: "udisksctl loop-setup --no-user-interaction -o {{ raspios_image_partitions[0].start * 512 }} -s {{ raspios_image_partitions[0].size * 512 }} -f '{{ raspios_output_dir }}/{{ raspios_output_image_base_name }}'" + register: raspios_image_loop_setup + + - set_fact: + raspios_image_loop_device: "{{ raspios_image_loop_setup.stdout | regex_search('as (/dev/loop[0-9]+)\\.$', '\\1') | first }}" + + - name: mount boot partition + command: "udisksctl mount --no-user-interaction -b '{{ raspios_image_loop_device }}'" + register: raspios_image_mount + + - set_fact: + raspios_image_mount_point: "{{ raspios_image_mount.stdout | regex_search('at (/media/.+)\\.?$', '\\1') | first }}" + + - name: edit boot/config.txt + when: raspios_boot_config is defined + loop: "{{ raspios_boot_config }}" + loop_control: + label: "{{ item.line }}" + lineinfile: + path: "{{ raspios_image_mount_point }}/config.txt" + regexp: "{{ item.regexp }}" + line: "{{ item.line }}" + + - name: Generate authorized_keys file + authorized_key: + user: root + manage_dir: no + path: "{{ raspios_image_mount_point }}/firstrun.authorized_keys" + key: "{{ ssh_keys_root | join('\n') }}" + + - name: install firstrun.sh script + template: + src: firstrun.sh.j2 + dest: "{{ raspios_image_mount_point }}/firstrun.sh" + mode: 0755 + + - name: edit boot/cmdline.txt + lineinfile: + path: "{{ raspios_image_mount_point }}/cmdline.txt" + regexp: '^(.*)( systemd.run=/boot/firstrun.sh systemd.run_success_action=reboot systemd.run_failure_action=none systemd.unit=kernel-command-line.target)?(.*?)$' + backrefs: yes + line: '\1 systemd.run=/boot/firstrun.sh systemd.run_success_action=reboot systemd.run_failure_action=none systemd.unit=kernel-command-line.target \3' + + always: + - name: unmount image + when: + - not raspios_keep_boot_dir_mounted + - raspios_image_mount_point is defined + - raspios_image_mount_point is mount + command: "udisksctl unmount --no-user-interaction -b '{{ raspios_image_loop_device }}'" + + - name: delete loop_device + when: + - not raspios_keep_boot_dir_mounted + - raspios_image_loop_device is defined + command: "udisksctl loop-delete --no-user-interaction -b '{{ raspios_image_loop_device }}'" + + - name: print temporary build directory information + when: + - raspios_keep_boot_dir_mounted + debug: + msg: "As per request the boot partition of the image is still mounted to: {{ raspios_image_mount_point }}" + +- name: set output image names + set_fact: + output_images: + - "{{ (raspios_output_dir, raspios_output_image_base_name) | path_join | realpath }}" diff --git a/roles/installer/raspios/image/templates/firstrun.sh.j2 b/roles/installer/raspios/image/templates/firstrun.sh.j2 new file mode 100644 index 00000000..bc35b764 --- /dev/null +++ b/roles/installer/raspios/image/templates/firstrun.sh.j2 @@ -0,0 +1,103 @@ +#!/bin/bash +set +e + +if [ -x /usr/lib/raspberrypi-sys-mods/get_fw_loc ]; then + FW_LOC=$(/usr/lib/raspberrypi-sys-mods/get_fw_loc) +else + FW_LOC=/boot +fi + +raspi-config nonint do_hostname "{{ host_name }}" +echo "{{ host_name }}" > /etc/hostname +raspi-config nonint do_change_locale "{{ raspios_locale }}" +raspi-config nonint do_change_timezone "{{ raspios_timezone }}" +raspi-config nonint do_configure_keyboard "{{ raspios_keyboard_layout }}" + +{# 0 -> predictable interface names, 1 -> legacy (eth0...) #} +raspi-config nonint do_net_names 1 + +{% if raspios_codename == 'bullseye' %} +{% if not (install_dhcp | default(false)) %} +cat <<EOF >> /etc/dhcpcd.conf + +# +interface {{ network.primary.name }} +static ip_address={{ network.primary.address }} +static routers={{ network.primary.gateway }} +static domain_name_servers={{ network.nameservers | join(' ') }} +EOF +systemctl restart dhcpcd.service +{% endif %} +systemctl disable hciuart.service +{% if 'wifi' in network.primary %} +raspi-config nonint do_wifi_ssid_passphrase "{{ network.primary.wifi.ssid }}" "{{ network.primary.wifi.key }}" +raspi-config nonint do_wifi_country "AT" +{% else %} +systemctl disable wpa_supplicant.service +{% endif %} +{% else %} +cat <<EOF >> /etc/network/interfaces + +# The loopback network interface +auto lo +iface lo inet loopback + +# The primary network interface +auto {{ network.primary.name }} +{% if (install_dhcp | default(false)) %} +iface {{ network.primary.name }} inet dhcp +{% else %} +iface {{ network.primary.name }} inet static + up echo 0 > /proc/sys/net/ipv6/conf/$IFACE/accept_ra + up echo 0 > /proc/sys/net/ipv6/conf/$IFACE/autoconf + address {{ network.primary.address | ansible.utils.ipaddr('address') }} + netmask {{ network.primary.address | ansible.utils.ipaddr('netmask') }} + gateway {{ network.primary.gateway }} +{% endif %} +{% if 'wifi' in network.primary %} + wpa-ssid {{ network.primary.wifi.ssid }} + wpa-psk {{ network.primary.wifi.key }} +{% endif %} +EOF +{% if not (install_dhcp | default(false)) %} +cat <<EOF > /etc/resolv.conf +# Generated by ansible +search {{ network.domain }} +{% for nameserver in network.nameservers %} +nameserver {{ nameserver }} +{% endfor %} +EOF +{% endif %} +systemctl disable wpa_supplicant.service +rfkill unblock wlan +ifup {{ network.primary.name }} +{% endif %} + +{% if ansible_port != 22 %} +sed -e 's/^\s*#*\s*Port\s\s*[0-9][0-9]*$/Port {{ ansible_port }}/' -i /etc/ssh/sshd_config +{% endif %} +install -m 0700 -d /root/.ssh +install -m 0644 "$FW_LOC/firstrun.authorized_keys" /root/.ssh/authorized_keys +{# 0 -> enable ssh, 1 -> disable ssh #} +raspi-config nonint do_ssh 0 + +export DEBIAN_FRONTEND=noninteractive +export SUDO_FORCE_REMOVE=yes +apt-get purge -q -y userconf-pi avahi-daemon triggerhappy dpkg-dev patch gdb make strace ssh-import-id network-manager udisks2 p7zip p7zip-full sudo dphys-swapfile +apt-get autoremove -q -y +dpkg -l | grep "^rc" | awk "{ print(\$2) }" | xargs -r dpkg -P + +sed 's#systemd.run=/boot/firstrun.sh systemd.run_success_action=reboot systemd.run_failure_action=none systemd.unit=kernel-command-line.target##' -i /boot/cmdline.txt +sed 's#\s*$##' -i /boot/cmdline.txt +rm "$FW_LOC/firstrun.authorized_keys" +rm "$FW_LOC/firstrun.sh" +rm -f /etc/sudoers.d/010_pi-nopasswd +rm -f /etc/apt/sources.list.d/vscode.list +rm -f /etc/apt/trusted.gpg.d/microsoft.gpg + +apt-get update -q +apt-get dist-upgrade -y -q + +{# B1 -> Console, B2 -> console autologin, B3 -> desktop, B4 -> desktop autologin #} +raspi-config nonint do_boot_behaviour B1 +systemctl --quiet enable getty@tty1 diff --git a/roles/installer/raspios/image/vars/main.yml b/roles/installer/raspios/image/vars/main.yml new file mode 100644 index 00000000..7c547ce2 --- /dev/null +++ b/roles/installer/raspios/image/vars/main.yml @@ -0,0 +1,4 @@ +--- +raspios_download_url: "https://downloads.raspberrypi.org" + +raspios_download_image_base_name: "{{ raspios_download_url_image | urlsplit('path') | basename }}" |