Compare commits
14 Commits
8c225bbe06
...
84b0470b49
| Author | SHA1 | Date | |
|---|---|---|---|
| 84b0470b49 | |||
| 6423c134be | |||
| f2f50e98e1 | |||
| cb0ece0478 | |||
| e3722d2648 | |||
| a6d4180b2b | |||
| f6a169afaa | |||
| a0546f5fa6 | |||
| c3375bea75 | |||
| 6b9b50ef2e | |||
| 874a81f664 | |||
| f434429dd1 | |||
| ed7b14dcbb | |||
| 2fb290b146 |
@@ -5,6 +5,11 @@ mediapi_has_dhtsensor: false
|
|||||||
mediapi_disable_onboard_bluetooth: false
|
mediapi_disable_onboard_bluetooth: false
|
||||||
mediapi_install_bluetooth_monitor: false
|
mediapi_install_bluetooth_monitor: false
|
||||||
mediapi_install_kidsmusic: false
|
mediapi_install_kidsmusic: false
|
||||||
|
|
||||||
|
# The native Slint front-end, built on the device and installed beside the web UI.
|
||||||
|
# Needs mediapi_install_kidsmusic, which owns the checkout it builds from. Off by
|
||||||
|
# default: the build is ~40 minutes of the device's own CPU the first time.
|
||||||
|
mediapi_install_slint_frontend: false
|
||||||
mediapi_has_monitor: false
|
mediapi_has_monitor: false
|
||||||
|
|
||||||
# No media pi runs its own Logitech Media Server. Every squeezelite in the fleet is
|
# No media pi runs its own Logitech Media Server. Every squeezelite in the fleet is
|
||||||
|
|||||||
@@ -4,16 +4,29 @@ pi_shairport_name: _Oben_Wohnzimmer
|
|||||||
alsa_card_name: sndrpihifiberry
|
alsa_card_name: sndrpihifiberry
|
||||||
sensor_room_name_ascii: wohnzimmeroben
|
sensor_room_name_ascii: wohnzimmeroben
|
||||||
sensor_room_name: WohnzimmerOben
|
sensor_room_name: WohnzimmerOben
|
||||||
hifiberry_overlay: hifiberry-dacplus
|
# -std: the Pi drives the I2S clocks. The plain dacplus overlay makes the Pi the clock
|
||||||
|
# consumer, which the Pi 5's I2S controller rejects (snd_soc_dai_set_fmt -22), so the
|
||||||
|
# card never probes and every softvol_* device is dead.
|
||||||
|
hifiberry_overlay: hifiberry-dacplus-std
|
||||||
main_user: root
|
main_user: root
|
||||||
|
|
||||||
mediapi_has_hifiberry_amp: true
|
mediapi_has_hifiberry_amp: true
|
||||||
mediapi_ir_control: irserver
|
mediapi_ir_control: irserver
|
||||||
mediapi_has_dhtsensor: true
|
# Off for the Pi 5: the legacy Adafruit_DHT library bit-bangs the BCM283x registers,
|
||||||
|
# which the Pi 5 no longer has (GPIO sits behind the RP1 chip), so the poller cannot
|
||||||
|
# work there. Needs a libgpiod-based replacement before this goes back to true.
|
||||||
|
mediapi_has_dhtsensor: false
|
||||||
|
|
||||||
|
# Raspberry Pi 5 (detected from the device tree; the Pi 5 kernel is installed as needed).
|
||||||
|
# The Pi is fed 5V through the HiFiBerry board's GPIO header, not USB-C, so it cannot
|
||||||
|
# negotiate a 5A supply and would cap USB current at 600mA.
|
||||||
|
hifiberry_powers_pi: true
|
||||||
|
|
||||||
# Display-only MusicMouse instance: no physical mouse attached, shown on the monitor
|
# Display-only MusicMouse instance: no physical mouse attached, shown on the monitor
|
||||||
# via pi_kiosk instead. See roles/pi_musicmouse/files/config-musicdolphin.yml.
|
# via pi_kiosk instead. See roles/pi_musicmouse/files/config-musicdolphin.yml.
|
||||||
mediapi_install_kidsmusic: true
|
mediapi_install_kidsmusic: true
|
||||||
|
# The native Slint front-end is not deployed here; the browser kiosk is the front-end.
|
||||||
|
mediapi_install_slint_frontend: false
|
||||||
mediapi_has_monitor: true
|
mediapi_has_monitor: true
|
||||||
# Temporarily a debuggable desktop rather than a locked-down kiosk: Openbox root menu,
|
# Temporarily a debuggable desktop rather than a locked-down kiosk: Openbox root menu,
|
||||||
# visible pointer, Firefox in a normal window. Set back to "kiosk" when done.
|
# visible pointer, Firefox in a normal window. Set back to "kiosk" when done.
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
when: mediapi_has_hifiberry_amp
|
when: mediapi_has_hifiberry_amp
|
||||||
- role: pi_musicmouse
|
- role: pi_musicmouse
|
||||||
when: mediapi_install_kidsmusic
|
when: mediapi_install_kidsmusic
|
||||||
|
- role: pi_slint_frontend
|
||||||
|
when: mediapi_install_slint_frontend
|
||||||
- role: pi_kiosk
|
- role: pi_kiosk
|
||||||
when: mediapi_has_monitor
|
when: mediapi_has_monitor
|
||||||
- pi_squeezelite_custom
|
- pi_squeezelite_custom
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
---
|
---
|
||||||
- name: Reboot
|
# Handler names are play-global: if several roles define the same name, only the
|
||||||
|
# last one defined runs, under *that* role's `when:`. A shared "Reboot" therefore
|
||||||
|
# never fired while pi_disable_onboard_bluetooth (usually switched off) defined one
|
||||||
|
# too. Keep this name unique to the role.
|
||||||
|
- name: Reboot after disabling onboard Bluetooth
|
||||||
ansible.builtin.reboot:
|
ansible.builtin.reboot:
|
||||||
|
|||||||
@@ -4,4 +4,4 @@
|
|||||||
path: /boot/firmware/config.txt
|
path: /boot/firmware/config.txt
|
||||||
regexp: "^#?dtoverlay=disable-bt"
|
regexp: "^#?dtoverlay=disable-bt"
|
||||||
line: "dtoverlay=disable-bt"
|
line: "dtoverlay=disable-bt"
|
||||||
notify: Reboot
|
notify: Reboot after disabling onboard Bluetooth
|
||||||
|
|||||||
@@ -3,4 +3,7 @@
|
|||||||
Deactivates the Pi's onboard analog audio and activates a HiFiBerry
|
Deactivates the Pi's onboard analog audio and activates a HiFiBerry
|
||||||
amp/DAC overlay instead.
|
amp/DAC overlay instead.
|
||||||
|
|
||||||
**Key vars:** `hifiberry_overlay`
|
**Key vars:** `hifiberry_overlay`, `hifiberry_powers_pi`
|
||||||
|
|
||||||
|
`hifiberry_powers_pi` (default `false`) is for a Pi fed through the HiFiBerry board's
|
||||||
|
GPIO header. On a Pi 5 that sets `usb_max_current_enable=1`, or USB is capped at 600mA.
|
||||||
|
|||||||
2
roles/pi_hifiberry_amp/defaults/main.yml
Normal file
2
roles/pi_hifiberry_amp/defaults/main.yml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
---
|
||||||
|
hifiberry_powers_pi: false
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
---
|
---
|
||||||
- name: Reboot
|
# Handler names are play-global: if several roles define the same name, only the
|
||||||
|
# last one defined runs, under *that* role's `when:`. A shared "Reboot" therefore
|
||||||
|
# never fired while pi_disable_onboard_bluetooth (usually switched off) defined one
|
||||||
|
# too. Keep this name unique to the role.
|
||||||
|
- name: Reboot after HiFiBerry boot config change
|
||||||
ansible.builtin.reboot:
|
ansible.builtin.reboot:
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
---
|
---
|
||||||
- name: Deactivate normal audio
|
- name: Deactivate normal audio
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
path: /boot/config.txt
|
path: /boot/firmware/config.txt
|
||||||
regexp: "^#?dtparam=audio=on"
|
regexp: "^#?dtparam=audio=on"
|
||||||
line: "#dtparam=audio=on"
|
line: "#dtparam=audio=on"
|
||||||
notify: Reboot
|
notify: Reboot after HiFiBerry boot config change
|
||||||
- name: Activate Hifiberry
|
- name: Activate Hifiberry
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
path: /boot/firmware/config.txt
|
path: /boot/firmware/config.txt
|
||||||
regexp: "^#?dtoverlay=hifiberry-amp"
|
# Any hifiberry overlay, not just the one this used to set: otherwise changing
|
||||||
|
# hifiberry_overlay appends a second dtoverlay line and leaves the old one active.
|
||||||
|
regexp: "^#?dtoverlay=hifiberry-"
|
||||||
line: "dtoverlay={{ hifiberry_overlay }}"
|
line: "dtoverlay={{ hifiberry_overlay }}"
|
||||||
notify: Reboot
|
notify: Reboot after HiFiBerry boot config change
|
||||||
|
- name: Lift the Pi 5's USB current cap when the HiFiBerry board powers it
|
||||||
|
# A Pi 5 fed over the GPIO header has no USB-C power negotiation, so it assumes a
|
||||||
|
# weak supply and limits USB ports to 600mA. Unknown to a Pi 4, which ignores it.
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /boot/firmware/config.txt
|
||||||
|
regexp: "^#?usb_max_current_enable="
|
||||||
|
line: "usb_max_current_enable=1"
|
||||||
|
insertafter: EOF
|
||||||
|
when: hifiberry_powers_pi
|
||||||
|
notify: Reboot after HiFiBerry boot config change
|
||||||
|
|||||||
@@ -3,3 +3,9 @@
|
|||||||
Installs and configures `irserver` (with a Hauppauge remote config) as an
|
Installs and configures `irserver` (with a Hauppauge remote config) as an
|
||||||
autostarted systemd service for IR remote control, and registers it with
|
autostarted systemd service for IR remote control, and registers it with
|
||||||
`pi_sysdweb`.
|
`pi_sysdweb`.
|
||||||
|
|
||||||
|
The shipped `files/irserver` is a 32-bit ARM binary needing `libzip.so.4`, so it is used
|
||||||
|
only on an armhf userland with Debian 12 or older. On a 64-bit userland or trixie it is
|
||||||
|
compiled on the device from `files/irserver-src.tar.gz` instead (the 2019 sources need
|
||||||
|
GCC's newer hard errors downgraded; see `tasks/build.yml`). Automatic; override with
|
||||||
|
`pi_irserver_build_from_source: true|false`.
|
||||||
|
|||||||
6
roles/pi_irserver/defaults/main.yml
Normal file
6
roles/pi_irserver/defaults/main.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
# The shipped files/irserver is a 32-bit ARM binary linked against libzip.so.4 (Debian
|
||||||
|
# bookworm's libzip4 on armhf). It cannot run on a 64-bit userland, and trixie no longer
|
||||||
|
# ships libzip.so.4 at all. "auto" uses it only on armhf + Debian <= 12 and compiles
|
||||||
|
# files/irserver-src.tar.gz on the device everywhere else; set true/false to force.
|
||||||
|
pi_irserver_build_from_source: auto
|
||||||
@@ -1,10 +1,17 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=IR server for IR remotes
|
Description=IR server for IR remotes
|
||||||
After=network.target
|
After=network.target
|
||||||
|
# Keep retrying rather than giving up: the IRTrans receiver is a USB serial device, and
|
||||||
|
# when it is missing at boot (unplugged, or enumerated late) irserver exits at once with
|
||||||
|
# "Error opening COM/USB Port". Without a restart it then stays failed until someone
|
||||||
|
# notices the remote does nothing.
|
||||||
|
StartLimitIntervalSec=0
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
ExecStart=/usr/bin/irserver /dev/ttyUSB0
|
ExecStart=/usr/bin/irserver /dev/ttyUSB0
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
44
roles/pi_irserver/tasks/build.yml
Normal file
44
roles/pi_irserver/tasks/build.yml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
- name: Install irserver build dependencies
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name:
|
||||||
|
- build-essential
|
||||||
|
- libzip-dev
|
||||||
|
cache_valid_time: 7200
|
||||||
|
|
||||||
|
- name: Create irserver source directory
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /opt/irserver-src
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Unpack irserver source
|
||||||
|
ansible.builtin.unarchive:
|
||||||
|
src: irserver-src.tar.gz
|
||||||
|
dest: /opt/irserver-src
|
||||||
|
creates: /opt/irserver-src/makefile
|
||||||
|
|
||||||
|
- name: Build irserver
|
||||||
|
# The 2019 sources predate modern C: GCC 14 rejects implicit function declarations,
|
||||||
|
# implicit int and a few pointer/int mix-ups as hard errors. They are old but working
|
||||||
|
# code, so the errors are downgraded rather than the source patched. libzip comes from
|
||||||
|
# libzip-dev (`irserver_arm_noccf` links -lzip); the bundled 32-bit static libzip*.a
|
||||||
|
# are not used. `arm/` is where the makefile puts its objects.
|
||||||
|
ansible.builtin.shell:
|
||||||
|
cmd: |
|
||||||
|
set -e
|
||||||
|
mkdir -p arm
|
||||||
|
make irserver_arm_noccf LDFLAGS="-lzip" \
|
||||||
|
CFLAGS="-w -Wno-error=implicit-function-declaration -Wno-error=int-conversion \
|
||||||
|
-Wno-error=incompatible-pointer-types -Wno-error=return-mismatch \
|
||||||
|
-Wno-error=implicit-int"
|
||||||
|
chdir: /opt/irserver-src
|
||||||
|
creates: /opt/irserver-src/irserver
|
||||||
|
|
||||||
|
- name: Install built irserver
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: /opt/irserver-src/irserver
|
||||||
|
dest: /usr/bin/irserver
|
||||||
|
mode: u+rx
|
||||||
|
remote_src: true
|
||||||
|
notify: Restart irserver
|
||||||
@@ -1,10 +1,27 @@
|
|||||||
---
|
---
|
||||||
- name: Copy irserver
|
- name: Get the userland architecture
|
||||||
|
# See pi_squeezelite_custom: the kernel's architecture is not the userland's.
|
||||||
|
ansible.builtin.command: dpkg --print-architecture
|
||||||
|
register: pi_irserver_dpkg_arch
|
||||||
|
changed_when: false
|
||||||
|
check_mode: false # read-only
|
||||||
|
- name: Decide between the prebuilt binary and a build on the device
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
pi_irserver_build: >-
|
||||||
|
{{ (pi_irserver_build_from_source | string | lower == 'true')
|
||||||
|
or (pi_irserver_build_from_source | string | lower == 'auto'
|
||||||
|
and (pi_irserver_dpkg_arch.stdout | trim != 'armhf'
|
||||||
|
or ansible_facts.distribution_major_version | int >= 13)) }}
|
||||||
|
- name: Copy prebuilt irserver (32-bit ARM binary)
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
src: irserver
|
src: irserver
|
||||||
dest: /usr/bin/irserver
|
dest: /usr/bin/irserver
|
||||||
mode: u+rx
|
mode: u+rx
|
||||||
notify: Restart irserver
|
notify: Restart irserver
|
||||||
|
when: not pi_irserver_build
|
||||||
|
- name: Build irserver from source (needed on a 64-bit userland, see README)
|
||||||
|
ansible.builtin.include_tasks: build.yml
|
||||||
|
when: pi_irserver_build
|
||||||
- name: Make config dir for remotes
|
- name: Make config dir for remotes
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: /usr/bin/remotes
|
path: /usr/bin/remotes
|
||||||
|
|||||||
@@ -10,7 +10,37 @@
|
|||||||
# size in the corner of the screen. Openbox answers it, and nothing else.
|
# size in the corner of the screen. Openbox answers it, and nothing else.
|
||||||
- openbox
|
- openbox
|
||||||
- unclutter
|
- unclutter
|
||||||
|
# The MusicMouse front-end's icons are emoji characters in the markup, not an icon
|
||||||
|
# font. Lite images ship no emoji font at all, so without this every icon renders
|
||||||
|
# as an empty box.
|
||||||
|
- fonts-noto-color-emoji
|
||||||
cache_valid_time: 7200
|
cache_valid_time: 7200
|
||||||
|
notify: Reload systemd and restart getty
|
||||||
|
|
||||||
|
- name: Ensure the Xorg config directory exists
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /etc/X11/xorg.conf.d
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Point X at the display controller
|
||||||
|
# The Pi 4 and 5 have two DRM devices: v3d (the 3D GPU, no outputs) and vc4 (the
|
||||||
|
# HDMI display controller). Left to itself Xorg cannot tell which to drive and dies
|
||||||
|
# with "Cannot run in framebuffer mode. Please specify busIDs for all framebuffer
|
||||||
|
# devices", so the kiosk loops on a blank screen. Display goes through vc4; rendering
|
||||||
|
# still lands on v3d, because glamor's GL context is routed there by Mesa (kmsro).
|
||||||
|
# Raspberry Pi's desktop image ships this same file; the Lite image does not.
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /etc/X11/xorg.conf.d/99-vc4.conf
|
||||||
|
content: |
|
||||||
|
Section "OutputClass"
|
||||||
|
Identifier "vc4"
|
||||||
|
MatchDriver "vc4"
|
||||||
|
Driver "modesetting"
|
||||||
|
Option "PrimaryGPU" "true"
|
||||||
|
EndSection
|
||||||
|
mode: "0644"
|
||||||
|
notify: Reload systemd and restart getty
|
||||||
|
|
||||||
- name: Browser
|
- name: Browser
|
||||||
# Chromium comes from archive.raspberrypi.com, not Debian: that build carries the
|
# Chromium comes from archive.raspberrypi.com, not Debian: that build carries the
|
||||||
|
|||||||
@@ -17,14 +17,24 @@
|
|||||||
dest: /usr/local/bin/bat
|
dest: /usr/local/bin/bat
|
||||||
state: link
|
state: link
|
||||||
|
|
||||||
|
- name: Check whether oh-my-posh is already installed
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /usr/local/bin/oh-my-posh
|
||||||
|
register: pi_modern_shell_env_ohmyposh
|
||||||
|
|
||||||
- name: Download oh-my-posh binary
|
- name: Download oh-my-posh binary
|
||||||
|
# Only when missing. With the file present get_url still sends a conditional request
|
||||||
|
# every run, so a CDN that is slow or unreachable from the device (TLS handshake
|
||||||
|
# timeouts happen) failed the whole play for a prompt that was already installed.
|
||||||
|
# Delete the binary to pick up a newer release.
|
||||||
|
when: not pi_modern_shell_env_ohmyposh.stat.exists
|
||||||
ansible.builtin.get_url:
|
ansible.builtin.get_url:
|
||||||
# The official install script's own curl-based downloads are blocked by
|
# The official install script's own curl-based downloads are blocked by
|
||||||
# oh-my-posh's CDN on some devices (observed on Raspberry Pi OS's curl
|
# oh-my-posh's CDN on some devices (observed on Raspberry Pi OS's curl
|
||||||
# build: TLS handshake fails with SSL_ERROR_SYSCALL), while Ansible's
|
# build: TLS handshake fails with SSL_ERROR_SYSCALL), while Ansible's
|
||||||
# get_url (Python urllib) succeeds against the same CDN. Download the
|
# get_url (Python urllib) succeeds against the same CDN. Download the
|
||||||
# binary directly instead of shelling out to the install script.
|
# binary directly instead of shelling out to the install script.
|
||||||
url: "https://cdn.ohmyposh.dev/releases/latest/posh-linux-{{ pi_modern_shell_env_ohmyposh_arch_map[ansible_architecture] }}"
|
url: "https://cdn.ohmyposh.dev/releases/latest/posh-linux-{{ pi_modern_shell_env_ohmyposh_arch_map[ansible_facts['architecture']] }}"
|
||||||
dest: /usr/local/bin/oh-my-posh
|
dest: /usr/local/bin/oh-my-posh
|
||||||
mode: "0755"
|
mode: "0755"
|
||||||
|
|
||||||
|
|||||||
@@ -68,10 +68,15 @@ general:
|
|||||||
# buttons, touch areas and tag scanner, none of which exist on a simulate-only
|
# buttons, touch areas and tag scanner, none of which exist on a simulate-only
|
||||||
# instance - it would publish a device of dead entities.
|
# instance - it would publish a device of dead entities.
|
||||||
#
|
#
|
||||||
# No lirc: section either, and consequently no top-level `remote:` mapping. This host
|
# IR remote. This host uses mediapi_ir_control: irserver (the IRTrans receiver on
|
||||||
# uses mediapi_ir_control: irserver, a custom serial daemon on /dev/ttyUSB0; the
|
# /dev/ttyUSB0) rather than lircd, but irserver also serves lircd's network protocol
|
||||||
# backend's LircConfig speaks lircd's TCP protocol (pi_lirc, port 2222) and cannot
|
# on its own port 8765 - which is all the backend's LircConfig needs. The remote name
|
||||||
# talk to it.
|
# is irserver's remote file, /usr/bin/remotes/hauppauge.rem, in lowercase. Number keys
|
||||||
|
# play whatever the top-level `remote:` mapping assigns them (none yet).
|
||||||
|
lirc:
|
||||||
|
host: "127.0.0.1"
|
||||||
|
port: 8765
|
||||||
|
remote_name: "hauppauge"
|
||||||
|
|
||||||
# Backlight of the prev/next buttons while a figure is playing, 0..1. Inert here
|
# Backlight of the prev/next buttons while a figure is playing, 0..1. Inert here
|
||||||
# (no mouse), kept so the file reads the same as the other hosts'.
|
# (no mouse), kept so the file reads the same as the other hosts'.
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
- git
|
- git
|
||||||
- libvlc5 # runtime lib python-vlc binds against
|
- libvlc5 # runtime lib python-vlc binds against
|
||||||
- vlc-plugin-base
|
- vlc-plugin-base
|
||||||
|
- ffmpeg # podcast feeds that publish video episodes get their audio extracted with it
|
||||||
|
- chromium
|
||||||
- alsa-utils
|
- alsa-utils
|
||||||
- samba
|
- samba
|
||||||
- python3
|
- python3
|
||||||
@@ -237,6 +239,15 @@
|
|||||||
name: smbd
|
name: smbd
|
||||||
state: started
|
state: started
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
|
- name: Build, sync and restart now rather than at the end of the play
|
||||||
|
# Handlers only run once the whole play is through, and a failure in any later role
|
||||||
|
# (squeezelite, shairport, ...) throws the queue away. The .built-commit guard above
|
||||||
|
# re-queues the build on the next run, but a play that keeps failing elsewhere then
|
||||||
|
# never delivers a front-end at all: the backend serves the API only and the kiosk
|
||||||
|
# shows nothing. Flushing here ties the front-end to this role succeeding, not to
|
||||||
|
# every role after it.
|
||||||
|
ansible.builtin.meta: flush_handlers
|
||||||
# manual steps:
|
# manual steps:
|
||||||
# - set samba passwords with smbpasswd
|
# - set samba passwords with smbpasswd
|
||||||
# - copy music into /media/musicmouse: scripts/sync-library.sh in the app repo, which
|
# - copy music into /media/musicmouse: scripts/sync-library.sh in the app repo, which
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
---
|
---
|
||||||
pi_shairport_sync_version: "3.3.5"
|
pi_shairport_sync_version: "4.3.7"
|
||||||
pi_shairport_name: Unnamed Raspberry with shairport
|
pi_shairport_name: Unnamed Raspberry with shairport
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
ARG1=$1
|
VERSION=${1:?usage: build-shairport-sync.sh <git tag>}
|
||||||
VERSION=${ARG1:="3.3.6"}
|
|
||||||
|
|
||||||
# Make a new temporary directory and go there
|
# Make a new temporary directory and go there
|
||||||
tmp_dir=$(mktemp -d)
|
tmp_dir=$(mktemp -d)
|
||||||
cd $tmp_dir
|
cd "$tmp_dir"
|
||||||
|
|
||||||
# Clone, build and install
|
# Clone the requested tag, build and install
|
||||||
git clone https://github.com/mikebrady/shairport-sync.git
|
git clone --depth 1 --branch "$VERSION" https://github.com/mikebrady/shairport-sync.git
|
||||||
git reset --hard origin/$VERSION
|
|
||||||
cd shairport-sync
|
cd shairport-sync
|
||||||
autoreconf -fi
|
autoreconf -fi
|
||||||
./configure --sysconfdir=/etc --with-alsa --with-soxr --with-avahi --with-ssl=openssl --with-systemd
|
./configure --sysconfdir=/etc --with-alsa --with-soxr --with-avahi --with-ssl=openssl --with-systemd
|
||||||
@@ -18,4 +17,4 @@ make install
|
|||||||
|
|
||||||
# Clean up
|
# Clean up
|
||||||
cd ~
|
cd ~
|
||||||
rm -rf $tmp_dir
|
rm -rf "$tmp_dir"
|
||||||
|
|||||||
@@ -3,24 +3,20 @@
|
|||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
cache_valid_time: 7200
|
cache_valid_time: 7200
|
||||||
state: present
|
state: present
|
||||||
name:
|
# systemd-dev: --with-systemd takes the unit directory from systemd.pc, which trixie
|
||||||
- build-essential
|
# moved out of the systemd package. Without it `make install` fails right after
|
||||||
- git
|
# installing the binary, with an empty `install -d`.
|
||||||
- xmltoman
|
name: >-
|
||||||
- autoconf
|
{{ ['build-essential', 'git', 'xmltoman', 'autoconf', 'automake', 'libtool',
|
||||||
- automake
|
'libpopt-dev', 'libconfig-dev', 'libasound2-dev', 'avahi-daemon',
|
||||||
- libtool
|
'libavahi-client-dev', 'libssl-dev', 'libsoxr-dev']
|
||||||
- libpopt-dev
|
+ (['systemd-dev'] if ansible_facts.distribution_major_version | int >= 13 else []) }}
|
||||||
- libconfig-dev
|
|
||||||
- libasound2-dev
|
|
||||||
- avahi-daemon
|
|
||||||
- libavahi-client-dev
|
|
||||||
- libssl-dev
|
|
||||||
- libsoxr-dev
|
|
||||||
- name: Build and Install Shairport sync (may take a while)
|
- name: Build and Install Shairport sync (may take a while)
|
||||||
ansible.builtin.script: "build-shairport-sync.sh ${shairport_sync_version}"
|
ansible.builtin.script: "build-shairport-sync.sh {{ pi_shairport_sync_version }}"
|
||||||
args:
|
args:
|
||||||
creates: /usr/local/bin/shairport-sync
|
# The unit file rather than the binary: make install puts the binary in place
|
||||||
|
# first, so a build that failed halfway would otherwise never be retried.
|
||||||
|
creates: /lib/systemd/system/shairport-sync.service
|
||||||
notify: Restart shairport-sync
|
notify: Restart shairport-sync
|
||||||
- name: Copy config
|
- name: Copy config
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
|
|||||||
44
roles/pi_slint_frontend/README.md
Normal file
44
roles/pi_slint_frontend/README.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# pi_slint_frontend
|
||||||
|
|
||||||
|
Builds and installs `slint-frontend/` from the app repo - the native front-end that
|
||||||
|
exists because the React UI is sluggish on the kiosk. Installed *alongside* the web UI,
|
||||||
|
never instead of it, and the systemd unit is installed but not enabled: the device still
|
||||||
|
boots into the kiosk browser.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
systemctl start musikdelfin # takes over the screen
|
||||||
|
systemctl stop musikdelfin # back to the browser underneath
|
||||||
|
```
|
||||||
|
|
||||||
|
or over SSH, `DISPLAY=:0 /usr/local/bin/musikdelfin`.
|
||||||
|
|
||||||
|
## Why this builds on the device
|
||||||
|
|
||||||
|
`pi_musicmouse` builds the web front-end on the control machine and rsyncs `dist/`,
|
||||||
|
because npm output is portable and npm is not installed on the Pis. A Rust binary is
|
||||||
|
not portable, so this role has a harder choice.
|
||||||
|
|
||||||
|
The Pis run **32-bit armhf Raspbian on a 64-bit kernel** - `uname -m` says `aarch64`
|
||||||
|
and `dpkg --print-architecture` says `armhf`, and the second one is the one that
|
||||||
|
matters. The target is `armv7-unknown-linux-gnueabihf`. Cross-compiling to it from the
|
||||||
|
control machine needs a sysroot, and the devices carry no `-dev` packages to build one
|
||||||
|
out of, so it would have to be reconstructed from the Debian archive - and then kept in
|
||||||
|
step with the devices' glibc forever. Spending the device's own CPU once is the smaller
|
||||||
|
liability. A rebuild only happens when the commit changes.
|
||||||
|
|
||||||
|
The same fact rules out the Skia renderer: rust-skia publishes no prebuilt binaries for
|
||||||
|
armhf, so `--features kiosk` would mean compiling Skia from source on a Pi. This role
|
||||||
|
builds `--features desktop` - winit, FemtoVG and the software renderer - and runs as an
|
||||||
|
X client in the existing session rather than taking over KMS.
|
||||||
|
|
||||||
|
`SLINT_BACKEND=winit-software musikdelfin` switches renderer without a rebuild, which
|
||||||
|
is the thing to try first if the GPU path misbehaves. The FemtoVG bug that makes
|
||||||
|
`kiosk` use Skia upstream (slint-ui/slint#11785) was reported on a Pi 5 with a 64-bit
|
||||||
|
userland, so it may well not apply here - but it is worth checking that album covers
|
||||||
|
actually appear before drawing conclusions about frame rate.
|
||||||
|
|
||||||
|
## First run
|
||||||
|
|
||||||
|
Roughly 40 minutes: 570 crates on two cores. Later runs are incremental and usually
|
||||||
|
seconds. The build is `nice`d and limited to two jobs, which bounds peak memory on
|
||||||
|
musicdolphin's 1.8 GB.
|
||||||
42
roles/pi_slint_frontend/defaults/main.yml
Normal file
42
roles/pi_slint_frontend/defaults/main.yml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
# The native Slint front-end (slint-frontend/ in the app repo). Installed alongside the
|
||||||
|
# web UI, not instead of it: both talk to the same backend, so the two can be compared
|
||||||
|
# on the same screen.
|
||||||
|
|
||||||
|
# Built on the device rather than on the control machine, which is the opposite of what
|
||||||
|
# pi_musicmouse does for the web front-end - and for a reason. The Pis run 32-bit armhf
|
||||||
|
# Raspbian on a 64-bit kernel, so the target is armv7-unknown-linux-gnueabihf. Cross
|
||||||
|
# compiling to it needs a sysroot, and the device has no -dev packages to build one
|
||||||
|
# from, so the choice is between reconstructing one from the Debian archive (and
|
||||||
|
# owning a glibc-version skew risk forever) or spending the device's own CPU once.
|
||||||
|
# A rebuild only happens when the commit changes - see the stamp below.
|
||||||
|
pi_slint_frontend_toolchain: stable
|
||||||
|
|
||||||
|
# Bounds peak memory during the build. musicdolphin has 4 cores but only 1.8 GB.
|
||||||
|
pi_slint_frontend_jobs: 2
|
||||||
|
|
||||||
|
# winit + FemtoVG + the software renderer. Deliberately not Skia: rust-skia ships no
|
||||||
|
# prebuilt binaries for armhf, so `kiosk` would mean compiling Skia from source on a
|
||||||
|
# Pi, which will not finish in any reasonable time and would not fit on the disk.
|
||||||
|
pi_slint_frontend_features: desktop
|
||||||
|
|
||||||
|
# Which backend the app is served by. Same machine as the kiosk browser uses.
|
||||||
|
pi_slint_frontend_server: "http://localhost:8080"
|
||||||
|
|
||||||
|
# Where the checkout lives. Shared with pi_musicmouse, which owns the checkout itself.
|
||||||
|
pi_slint_frontend_root: /opt/musicmouse/slint-frontend
|
||||||
|
|
||||||
|
# The user the kiosk's X session belongs to - the app has to run as them to reach the
|
||||||
|
# display. pi_kiosk owns this name; repeated here as a default so the role still works
|
||||||
|
# on a host where pi_kiosk has not run.
|
||||||
|
pi_slint_frontend_x_user: "{{ pi_kiosk_user | default('kiosk') }}"
|
||||||
|
|
||||||
|
# The device is a kiosk, so the window takes the whole screen. Set false to get a
|
||||||
|
# normal window - useful while pi_kiosk_mode is "debug" and you want the browser and
|
||||||
|
# this side by side on the same display.
|
||||||
|
pi_slint_frontend_fullscreen: true
|
||||||
|
|
||||||
|
# Slint's own frame-timing readout, for measuring rather than guessing. Empty to leave
|
||||||
|
# it off; "overlay,refresh_lazy" draws the numbers on screen and only counts frames
|
||||||
|
# that were actually rendered, which is the honest measure for a UI that idles.
|
||||||
|
pi_slint_frontend_debug_performance: ""
|
||||||
4
roles/pi_slint_frontend/handlers/main.yml
Normal file
4
roles/pi_slint_frontend/handlers/main.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
- name: Reload systemd
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
daemon_reload: true
|
||||||
143
roles/pi_slint_frontend/tasks/main.yml
Normal file
143
roles/pi_slint_frontend/tasks/main.yml
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
---
|
||||||
|
- name: Packages needed to build the front-end
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name:
|
||||||
|
- build-essential
|
||||||
|
- pkg-config
|
||||||
|
# The only C libraries the binary links: everything else - X11, Wayland, EGL,
|
||||||
|
# GL - is dlopen'd at runtime by winit and glutin, so it is not needed to build.
|
||||||
|
- libfontconfig1-dev
|
||||||
|
- libfreetype-dev
|
||||||
|
- curl
|
||||||
|
# Runtime, not build: winit and glutin dlopen all of these, so they never show
|
||||||
|
# up in `ldd` and the binary links happily without them - it then aborts on
|
||||||
|
# first run with "Library libxkbcommon-x11.so could not be loaded".
|
||||||
|
- libxkbcommon0
|
||||||
|
- libxkbcommon-x11-0
|
||||||
|
- libx11-xcb1
|
||||||
|
- libxcb-xkb1
|
||||||
|
- libxcursor1
|
||||||
|
- libxi6
|
||||||
|
- libxrandr2
|
||||||
|
- libgl1
|
||||||
|
- libegl1
|
||||||
|
- libgles2
|
||||||
|
# The design uses emoji as iconography - the shelf badges, the figurine marker,
|
||||||
|
# the scene sparkle. Raspbian ships no font that has them, so without these they
|
||||||
|
# render as empty boxes. Symbola is the monochrome fallback for the glyphs Noto
|
||||||
|
# does not cover.
|
||||||
|
- fonts-noto-color-emoji
|
||||||
|
- fonts-symbola
|
||||||
|
cache_valid_time: 7200
|
||||||
|
|
||||||
|
- name: Check for a rust toolchain
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /root/.cargo/bin/cargo
|
||||||
|
register: pi_slint_frontend_cargo
|
||||||
|
|
||||||
|
- name: Fetch the rustup installer
|
||||||
|
ansible.builtin.get_url:
|
||||||
|
url: https://sh.rustup.rs
|
||||||
|
dest: /tmp/rustup-init.sh
|
||||||
|
mode: "0755"
|
||||||
|
when: not pi_slint_frontend_cargo.stat.exists
|
||||||
|
|
||||||
|
- name: Install the rust toolchain
|
||||||
|
# Debian 12 ships rustc 1.63, which is far too old for Slint, so this comes from
|
||||||
|
# rustup rather than apt.
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: "/tmp/rustup-init.sh -y --profile minimal --default-toolchain {{ pi_slint_frontend_toolchain }}"
|
||||||
|
creates: /root/.cargo/bin/cargo
|
||||||
|
when: not pi_slint_frontend_cargo.stat.exists
|
||||||
|
|
||||||
|
- name: Check the checkout has a slint-frontend
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ pi_slint_frontend_root }}/Cargo.toml"
|
||||||
|
register: pi_slint_frontend_manifest
|
||||||
|
when: not ansible_check_mode
|
||||||
|
|
||||||
|
- name: Fail on a checkout without the front-end
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: >-
|
||||||
|
{{ musicmouse_version }} has no slint-frontend/, so it predates the native
|
||||||
|
front-end. Point musicmouse_version at a branch that has it, or set
|
||||||
|
mediapi_install_slint_frontend to false for this host.
|
||||||
|
when: not ansible_check_mode and not pi_slint_frontend_manifest.stat.exists
|
||||||
|
|
||||||
|
- name: Fail when the checkout's commit is unknown
|
||||||
|
# This role builds what pi_musicmouse checked out, and reuses its record of which
|
||||||
|
# commit that was. With mediapi_install_kidsmusic false that role never ran, so
|
||||||
|
# there is no checkout to build and no commit to stamp - and the stamp comparison
|
||||||
|
# below would compare against an empty string and rebuild on every run.
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: >-
|
||||||
|
pi_slint_frontend needs pi_musicmouse to have run first - it builds the
|
||||||
|
slint-frontend/ inside the checkout that role owns. Set
|
||||||
|
mediapi_install_kidsmusic to true for this host, or
|
||||||
|
mediapi_install_slint_frontend to false.
|
||||||
|
when: not ansible_check_mode and pi_musicmouse_commit | length == 0
|
||||||
|
|
||||||
|
- name: Read which commit the binary was built from
|
||||||
|
# The same guard pi_musicmouse uses for the venv and the web dist, for the same
|
||||||
|
# reason: hanging a 40-minute build off "the checkout changed" strands the device
|
||||||
|
# when a run fails after the checkout, because the next run finds it current and
|
||||||
|
# rebuilds nothing.
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: "cat {{ pi_slint_frontend_root }}/.built-commit"
|
||||||
|
register: pi_slint_frontend_stamp
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Build the front-end
|
||||||
|
# First build takes roughly 40 minutes on a Pi 4 - 570 crates, two jobs. Later ones
|
||||||
|
# are incremental. `async` keeps the SSH connection from idling out underneath it.
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: >-
|
||||||
|
nice -n 10 /root/.cargo/bin/cargo build --release
|
||||||
|
--no-default-features --features {{ pi_slint_frontend_features }}
|
||||||
|
-j {{ pi_slint_frontend_jobs }}
|
||||||
|
chdir: "{{ pi_slint_frontend_root }}"
|
||||||
|
environment:
|
||||||
|
# The committed release profile is tuned for a workstation. Thin LTO with
|
||||||
|
# codegen-units=1 is a memory risk on 1.8 GB and buys nothing that matters here.
|
||||||
|
CARGO_PROFILE_RELEASE_LTO: "false"
|
||||||
|
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "16"
|
||||||
|
PATH: "/root/.cargo/bin:{{ ansible_facts['env']['PATH'] }}"
|
||||||
|
async: 7200
|
||||||
|
poll: 30
|
||||||
|
changed_when: true
|
||||||
|
when: >-
|
||||||
|
not ansible_check_mode
|
||||||
|
and pi_slint_frontend_stamp.stdout | trim != pi_musicmouse_commit
|
||||||
|
|
||||||
|
- name: Record which commit the binary was built from
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: "{{ pi_musicmouse_commit }}\n"
|
||||||
|
dest: "{{ pi_slint_frontend_root }}/.built-commit"
|
||||||
|
mode: "0644"
|
||||||
|
when: not ansible_check_mode
|
||||||
|
|
||||||
|
- name: Find the X session user's home directory
|
||||||
|
ansible.builtin.getent:
|
||||||
|
database: passwd
|
||||||
|
key: "{{ pi_slint_frontend_x_user }}"
|
||||||
|
|
||||||
|
- name: Note where the X cookie lives
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
pi_slint_frontend_x_home: "{{ ansible_facts.getent_passwd[pi_slint_frontend_x_user][4] }}"
|
||||||
|
|
||||||
|
- name: Install the launcher
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: musikdelfin.sh.j2
|
||||||
|
dest: /usr/local/bin/musikdelfin
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Install the systemd unit
|
||||||
|
# Installed but deliberately not enabled: this is the thing under test, and the
|
||||||
|
# kiosk browser is still what the device shows on boot. Start it by hand -
|
||||||
|
# `systemctl start musikdelfin` - or run /usr/local/bin/musikdelfin over SSH.
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: musikdelfin.service.j2
|
||||||
|
dest: /etc/systemd/system/musikdelfin.service
|
||||||
|
mode: "0644"
|
||||||
|
notify: Reload systemd
|
||||||
25
roles/pi_slint_frontend/templates/musikdelfin.service.j2
Normal file
25
roles/pi_slint_frontend/templates/musikdelfin.service.j2
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# {{ ansible_managed }}
|
||||||
|
#
|
||||||
|
# Installed, not enabled. The kiosk browser is still what this device shows on boot;
|
||||||
|
# this unit is here so the native front-end can be started and stopped the same way
|
||||||
|
# while it is being evaluated:
|
||||||
|
#
|
||||||
|
# systemctl start musikdelfin # takes over the screen
|
||||||
|
# systemctl stop musikdelfin # back to the browser underneath
|
||||||
|
[Unit]
|
||||||
|
Description=Musik Delfin (native Slint front-end)
|
||||||
|
After=musicmouse.service graphical.target
|
||||||
|
Wants=musicmouse.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
# Runs as the X session's own user, not root: the display belongs to them, and the
|
||||||
|
# cover cache should land in their home rather than root's.
|
||||||
|
User={{ pi_slint_frontend_x_user }}
|
||||||
|
ExecStart=/usr/local/bin/musikdelfin
|
||||||
|
Restart=no
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=graphical.target
|
||||||
40
roles/pi_slint_frontend/templates/musikdelfin.sh.j2
Normal file
40
roles/pi_slint_frontend/templates/musikdelfin.sh.j2
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# {{ ansible_managed }}
|
||||||
|
#
|
||||||
|
# Run the native front-end on the kiosk's X display. The web UI stays where it is;
|
||||||
|
# both talk to the same backend, so they can be compared on the same screen.
|
||||||
|
#
|
||||||
|
# musikdelfin the display, as the kiosk uses it
|
||||||
|
# SLINT_BACKEND=winit-software musikdelfin no GPU, for comparison
|
||||||
|
# SLINT_FULLSCREEN=0 musikdelfin a normal window, beside the browser
|
||||||
|
# SLINT_DEBUG_PERFORMANCE=overlay,refresh_lazy musikdelfin frame timings on screen
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# The kiosk's X session belongs to {{ pi_slint_frontend_x_user }}, and its cookie is
|
||||||
|
# the only way onto that display - without it the connection is refused before winit
|
||||||
|
# gets far enough to report anything more useful than "could not initialize backend".
|
||||||
|
export DISPLAY="${DISPLAY:-:0}"
|
||||||
|
export XAUTHORITY="${XAUTHORITY:-{{ pi_slint_frontend_x_home }}/.Xauthority}"
|
||||||
|
|
||||||
|
# Nunito is the face the design is drawn in; without it Slint falls back to whatever
|
||||||
|
# fontconfig hands back, which changes how this looks but not whether it runs.
|
||||||
|
for candidate in \
|
||||||
|
/usr/share/fonts/truetype/nunito/Nunito[wght].ttf \
|
||||||
|
/usr/share/fonts/truetype/nunito/Nunito-Regular.ttf \
|
||||||
|
{{ pi_slint_frontend_root }}/fonts/Nunito.ttf
|
||||||
|
do
|
||||||
|
if [ -f "$candidate" ]; then
|
||||||
|
export SLINT_DEFAULT_FONT="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
{% if pi_slint_frontend_fullscreen %}
|
||||||
|
export SLINT_FULLSCREEN="${SLINT_FULLSCREEN:-1}"
|
||||||
|
{% endif %}
|
||||||
|
{% if pi_slint_frontend_debug_performance %}
|
||||||
|
export SLINT_DEBUG_PERFORMANCE="${SLINT_DEBUG_PERFORMANCE:-{{ pi_slint_frontend_debug_performance }}}"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
exec {{ pi_slint_frontend_root }}/target/release/musicmouse-slint \
|
||||||
|
--server {{ pi_slint_frontend_server }} "$@"
|
||||||
@@ -3,3 +3,9 @@
|
|||||||
Like `pi_squeezelite`, but uninstalls the distro package and deploys a
|
Like `pi_squeezelite`, but uninstalls the distro package and deploys a
|
||||||
custom-compiled `squeezelite` build instead as a systemd service. See
|
custom-compiled `squeezelite` build instead as a systemd service. See
|
||||||
`custom build/readme_build.md` for how that build is produced.
|
`custom build/readme_build.md` for how that build is produced.
|
||||||
|
|
||||||
|
The shipped 32-bit binary only runs on an armhf userland with Debian 12 (bookworm) or
|
||||||
|
older. On a 64-bit userland or Debian 13 (trixie) the role builds the same upstream commit
|
||||||
|
on the device and installs it to `/opt/squeezelite` instead. That is picked
|
||||||
|
automatically from `dpkg --print-architecture` and the distribution version; override with
|
||||||
|
`pi_squeezelite_custom_build_from_source: true|false`.
|
||||||
|
|||||||
34
roles/pi_squeezelite_custom/defaults/main.yml
Normal file
34
roles/pi_squeezelite_custom/defaults/main.yml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
# The shipped files/squeezelite is a 32-bit ARM binary linked against libFLAC.so.12, which
|
||||||
|
# is what Debian bookworm on armhf provides. It cannot run on a 64-bit userland or on
|
||||||
|
# trixie (libflac14), so by default ("auto") the role uses it only on armhf + Debian <= 12
|
||||||
|
# and compiles pi_squeezelite_custom_version on the device everywhere else. Set true/false
|
||||||
|
# to force one or the other.
|
||||||
|
pi_squeezelite_custom_build_from_source: auto
|
||||||
|
|
||||||
|
pi_squeezelite_custom_repo: https://github.com/ralph-irving/squeezelite.git
|
||||||
|
# Upstream master when this was written (squeezelite 2.0, revision 1595). The commit the
|
||||||
|
# prebuilt binary came from (c89faf3 in the old build notes) does not exist upstream.
|
||||||
|
pi_squeezelite_custom_version: c7c4248ddd70e47dbfeba0bf4a8a7ec08d8a995c
|
||||||
|
|
||||||
|
# Runtime libraries the prebuilt binary links (bookworm package names).
|
||||||
|
pi_squeezelite_custom_runtime_packages:
|
||||||
|
- libmad0
|
||||||
|
- libmpg123-0
|
||||||
|
- libflac12
|
||||||
|
- libvorbisfile3
|
||||||
|
- libfaad2
|
||||||
|
|
||||||
|
# Headers (and, through them, the runtime libraries) for building. Installing the -dev
|
||||||
|
# packages is what makes this independent of soname bumps between Debian releases.
|
||||||
|
pi_squeezelite_custom_build_packages:
|
||||||
|
- build-essential
|
||||||
|
- git
|
||||||
|
- libasound2-dev
|
||||||
|
- libssl-dev
|
||||||
|
- libsoxr-dev
|
||||||
|
- libflac-dev
|
||||||
|
- libvorbis-dev
|
||||||
|
- libmad0-dev
|
||||||
|
- libfaad-dev
|
||||||
|
- libmpg123-dev
|
||||||
41
roles/pi_squeezelite_custom/tasks/build.yml
Normal file
41
roles/pi_squeezelite_custom/tasks/build.yml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
- name: Check out squeezelite
|
||||||
|
ansible.builtin.git:
|
||||||
|
repo: "{{ pi_squeezelite_custom_repo }}"
|
||||||
|
dest: /opt/squeezelite-src
|
||||||
|
version: "{{ pi_squeezelite_custom_version }}"
|
||||||
|
force: true
|
||||||
|
register: pi_squeezelite_custom_checkout
|
||||||
|
|
||||||
|
- name: Check whether a built binary already exists
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /opt/squeezelite
|
||||||
|
register: pi_squeezelite_custom_binary
|
||||||
|
|
||||||
|
- name: Write the makefile (upstream Makefile plus this build's options)
|
||||||
|
# Same options as the ARM build in "custom build/readme_build.md", minus the ARMv6
|
||||||
|
# -march/-mfpu/-mfloat-abi flags that make no sense for aarch64. No RPI (GPIO switching)
|
||||||
|
# option. -lasound is added explicitly, as before.
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /opt/squeezelite-src/mymakefile
|
||||||
|
mode: "0644"
|
||||||
|
content: |
|
||||||
|
OPTS = -DLINUX -DRESAMPLE -DDSD -DUSE_SSL -DLINKALL -I./include -I./include/opus -I./include/alac -I/usr/local/include
|
||||||
|
LDFLAGS = -L./lib -L/usr/local/lib -s -lasound
|
||||||
|
|
||||||
|
include Makefile
|
||||||
|
|
||||||
|
- name: Build squeezelite
|
||||||
|
ansible.builtin.shell:
|
||||||
|
cmd: make -f mymakefile clean && make -f mymakefile
|
||||||
|
chdir: /opt/squeezelite-src
|
||||||
|
when: pi_squeezelite_custom_checkout.changed or not pi_squeezelite_custom_binary.stat.exists
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Install built squeezelite
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: /opt/squeezelite-src/squeezelite
|
||||||
|
dest: /opt/squeezelite
|
||||||
|
mode: "700"
|
||||||
|
remote_src: true
|
||||||
|
notify: Restart squeezelite
|
||||||
@@ -3,15 +3,23 @@
|
|||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
name: squeezelite
|
name: squeezelite
|
||||||
state: absent
|
state: absent
|
||||||
|
- name: Get the userland architecture
|
||||||
|
# `ansible_architecture` is the *kernel's*: a Pi on a 64-bit kernel with an armhf
|
||||||
|
# userland says aarch64. What decides whether a 32-bit binary runs is dpkg's.
|
||||||
|
ansible.builtin.command: dpkg --print-architecture
|
||||||
|
register: pi_squeezelite_custom_dpkg_arch
|
||||||
|
changed_when: false
|
||||||
|
check_mode: false # read-only
|
||||||
|
- name: Decide between the prebuilt binary and a build on the device
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
pi_squeezelite_custom_build: >-
|
||||||
|
{{ (pi_squeezelite_custom_build_from_source | string | lower == 'true')
|
||||||
|
or (pi_squeezelite_custom_build_from_source | string | lower == 'auto'
|
||||||
|
and (pi_squeezelite_custom_dpkg_arch.stdout | trim != 'armhf'
|
||||||
|
or ansible_facts.distribution_major_version | int >= 13)) }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
name:
|
name: "{{ pi_squeezelite_custom_build_packages if pi_squeezelite_custom_build else pi_squeezelite_custom_runtime_packages }}"
|
||||||
- libmad0
|
|
||||||
- libmpg123-0
|
|
||||||
- libflac12
|
|
||||||
- libvorbisfile3
|
|
||||||
- libfaad2
|
|
||||||
# for building libssl-dev, libasound2-dev, libflac-dev, libvorbis-dev, libsoxr-dev, libfaad-dev, libmad0-dev, libmpg123-dev
|
|
||||||
state: present
|
state: present
|
||||||
cache_valid_time: 7200
|
cache_valid_time: 7200
|
||||||
- name: Remove old config file if present
|
- name: Remove old config file if present
|
||||||
@@ -24,6 +32,10 @@
|
|||||||
dest: /opt/squeezelite
|
dest: /opt/squeezelite
|
||||||
mode: "700"
|
mode: "700"
|
||||||
notify: Restart squeezelite
|
notify: Restart squeezelite
|
||||||
|
when: not pi_squeezelite_custom_build
|
||||||
|
- name: Build squeezelite from source (needed on a 64-bit userland, see README)
|
||||||
|
ansible.builtin.include_tasks: build.yml
|
||||||
|
when: pi_squeezelite_custom_build
|
||||||
- name: Install systemd service file
|
- name: Install systemd service file
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: squeezelite.service
|
src: squeezelite.service
|
||||||
|
|||||||
@@ -36,7 +36,17 @@
|
|||||||
state: absent
|
state: absent
|
||||||
notify: Restart sysdweb after dropping the LMS entry
|
notify: Restart sysdweb after dropping the LMS entry
|
||||||
|
|
||||||
|
- name: Check whether iptables is installed
|
||||||
|
# Trixie images ship nftables only. A host without iptables cannot have the redirect
|
||||||
|
# below (present.yml installs iptables-persistent), so there is nothing to undo.
|
||||||
|
ansible.builtin.command: which iptables
|
||||||
|
register: pi_squeezeserver_iptables
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
check_mode: false # read-only
|
||||||
|
|
||||||
- name: Undo the port 80 -> 9000 redirect the install added
|
- name: Undo the port 80 -> 9000 redirect the install added
|
||||||
|
when: pi_squeezeserver_iptables.rc == 0
|
||||||
ansible.builtin.iptables:
|
ansible.builtin.iptables:
|
||||||
table: nat
|
table: nat
|
||||||
chain: PREROUTING
|
chain: PREROUTING
|
||||||
|
|||||||
@@ -9,3 +9,9 @@ pi_standard_setup_wifi_pass_url: "bauer_wifi" # has to be in keepass with url "w
|
|||||||
ansible_ssh_pass: "raspberry"
|
ansible_ssh_pass: "raspberry"
|
||||||
ansible_become_password: "raspberry"
|
ansible_become_password: "raspberry"
|
||||||
ansible_become: true
|
ansible_become: true
|
||||||
|
|
||||||
|
# Install the Pi 5 (BCM2712) kernel, kernel_2712.img (an arm64 package, hence the :arm64 on
|
||||||
|
# an armhf userland). A card imaged on a Pi 4 does not have it, and a Pi 5 will not boot
|
||||||
|
# without. "auto" installs it when the detected model is a Pi 5 (a no-op on current
|
||||||
|
# images, which ship it); set true/false to force.
|
||||||
|
pi_standard_setup_install_pi5_kernel: auto
|
||||||
|
|||||||
@@ -3,7 +3,11 @@
|
|||||||
ansible.builtin.service:
|
ansible.builtin.service:
|
||||||
name: sshd
|
name: sshd
|
||||||
state: restarted
|
state: restarted
|
||||||
- name: Reboot
|
# Handler names are play-global: if several roles define the same name, only the
|
||||||
|
# last one defined runs, under *that* role's `when:`. A shared "Reboot" therefore
|
||||||
|
# never fired while pi_disable_onboard_bluetooth (usually switched off) defined one
|
||||||
|
# too. Keep this name unique to the role.
|
||||||
|
- name: Reboot after standard setup
|
||||||
ansible.builtin.reboot:
|
ansible.builtin.reboot:
|
||||||
- name: Restart raspi-leds-off
|
- name: Restart raspi-leds-off
|
||||||
ansible.builtin.service:
|
ansible.builtin.service:
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
when: pi_standard_setup_new_hostname | length > 0 and pi_standard_setup_pi_hostname.stdout != pi_standard_setup_new_hostname
|
when: pi_standard_setup_new_hostname | length > 0 and pi_standard_setup_pi_hostname.stdout != pi_standard_setup_new_hostname
|
||||||
register: pi_standard_setup_set_hostname
|
register: pi_standard_setup_set_hostname
|
||||||
changed_when: true
|
changed_when: true
|
||||||
notify: Reboot
|
notify: Reboot after standard setup
|
||||||
- name: Get hostname
|
- name: Get hostname
|
||||||
ansible.builtin.command: "raspi-config nonint get_hostname"
|
ansible.builtin.command: "raspi-config nonint get_hostname"
|
||||||
register: pi_standard_setup_pi_hostname
|
register: pi_standard_setup_pi_hostname
|
||||||
@@ -115,6 +115,19 @@
|
|||||||
- telnet
|
- telnet
|
||||||
cache_valid_time: 7200
|
cache_valid_time: 7200
|
||||||
state: present
|
state: present
|
||||||
|
- name: Install the Pi 5 kernel
|
||||||
|
# Only where it can matter: a Pi 5 running an image that was built for an older Pi
|
||||||
|
# (a card moved over from a Pi 4). A current arm64 image already ships it, and then
|
||||||
|
# this is a no-op; an armhf userland needs the arm64 kernel package cross-installed.
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: "linux-image-rpi-2712:arm64"
|
||||||
|
cache_valid_time: 7200
|
||||||
|
state: present
|
||||||
|
when: >-
|
||||||
|
(pi_standard_setup_install_pi5_kernel | string | lower == 'true')
|
||||||
|
or (pi_standard_setup_install_pi5_kernel | string | lower == 'auto'
|
||||||
|
and 'Raspberry Pi 5' in (pi_standard_setup_raspberry_model.content | b64decode))
|
||||||
|
notify: Reboot after standard setup
|
||||||
- name: Copy vim config
|
- name: Copy vim config
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
src: vimrc
|
src: vimrc
|
||||||
|
|||||||
@@ -4,6 +4,18 @@
|
|||||||
name: python3-pip
|
name: python3-pip
|
||||||
cache_valid_time: 7200
|
cache_valid_time: 7200
|
||||||
state: present
|
state: present
|
||||||
|
- name: Apt install sysdweb's dbus and systemd dependencies
|
||||||
|
# sysdweb needs dbus-python ~= 1.4, which PyPI only ships as source (meson build needing
|
||||||
|
# pkg-config and the dbus/glib headers), and systemd-python, which needs libsystemd-dev.
|
||||||
|
# Trixie packages both (python3-dbus is exactly 1.4, python3-systemd 235), and pip counts
|
||||||
|
# the apt versions as satisfied. Bookworm has dbus 1.3, so there pip has to build 1.4.
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: >-
|
||||||
|
{{ ['python3-dbus', 'python3-systemd']
|
||||||
|
+ (['build-essential', 'pkg-config', 'python3-dev', 'libdbus-1-dev', 'libglib2.0-dev', 'libsystemd-dev']
|
||||||
|
if ansible_facts.distribution_major_version | int < 13 else []) }}
|
||||||
|
cache_valid_time: 7200
|
||||||
|
state: present
|
||||||
- name: Install sysdweb
|
- name: Install sysdweb
|
||||||
ansible.builtin.pip:
|
ansible.builtin.pip:
|
||||||
name: sysdweb
|
name: sysdweb
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
set -e # exit on error
|
set -e # exit on error
|
||||||
|
|
||||||
TARGET_FOLDER="./rpi_image"
|
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
|
||||||
IMG_FILE_BASENAME="2023-12-11-raspios-bookworm-armhf-lite"
|
TARGET_FOLDER="$SCRIPT_DIR/rpi_image"
|
||||||
#DOWNLOAD_URL="https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2021-11-08/${IMG_FILE_BASENAME}.zip"
|
#IMG_FILE_BASENAME="2023-12-11-raspios-bookworm-armhf-lite"
|
||||||
DOWNLOAD_URL="https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2023-12-11/${IMG_FILE_BASENAME}.img.xz"
|
#DOWNLOAD_URL="https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2023-12-11/${IMG_FILE_BASENAME}.img.xz"
|
||||||
|
IMG_FILE_BASENAME="2026-09-15-raspios-trixie-arm64-lite"
|
||||||
|
DOWNLOAD_URL="https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2026-09-15/${IMG_FILE_BASENAME}.img.xz"
|
||||||
|
|
||||||
echo "This script downloads raspian lite, and modifies the image to enable SSH and set hostname"
|
echo "This script downloads raspian lite, and modifies the image to enable SSH and set hostname"
|
||||||
|
|
||||||
@@ -14,15 +16,20 @@ if [[ $EUID -ne 0 ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -n Hostname of new pi
|
|
||||||
read -p "Hostname of new pi [newrpi]: " RPI_HOSTNAME
|
read -p "Hostname of new pi [newrpi]: " RPI_HOSTNAME
|
||||||
|
read -p "WIFI SSID (only used if a password is given) [WLAN]: " WIFI_SSID
|
||||||
read -p "WIFI password (leave empty for non-wifi setup) [] " WIFI_PASSWORD
|
read -p "WIFI password (leave empty for non-wifi setup) [] " WIFI_PASSWORD
|
||||||
RPI_HOSTNAME=${RPI_HOSTNAME:-newrpi}
|
RPI_HOSTNAME=${RPI_HOSTNAME:-newrpi}
|
||||||
|
WIFI_SSID=${WIFI_SSID:-WLAN}
|
||||||
|
|
||||||
SCRIPT_DIR=`pwd`
|
mkdir -p "$TARGET_FOLDER"
|
||||||
mkdir -p $TARGET_FOLDER
|
cd "$TARGET_FOLDER"
|
||||||
cd $TARGET_FOLDER
|
|
||||||
|
|
||||||
|
if [ -e "${IMG_FILE_BASENAME}.img" ]; then
|
||||||
|
echo "${TARGET_FOLDER}/${IMG_FILE_BASENAME}.img already exists (already customised?)."
|
||||||
|
echo "Remove it first if you want to start over."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Downloading image"
|
echo "Downloading image"
|
||||||
wget ${DOWNLOAD_URL}
|
wget ${DOWNLOAD_URL}
|
||||||
@@ -36,36 +43,74 @@ unxz ${IMG_FILE_BASENAME}.img.xz
|
|||||||
|
|
||||||
|
|
||||||
echo "Mounting image"
|
echo "Mounting image"
|
||||||
mkdir mounted_image
|
MOUNT_DIR=$(mktemp -d "$TARGET_FOLDER/mounted_image.XXXXXX")
|
||||||
mkdir mounted_image/boot
|
LOOP_DEV=""
|
||||||
mkdir mounted_image/system
|
cleanup() {
|
||||||
losetup -P /dev/loop42 ${IMG_FILE_BASENAME}.img
|
set +e
|
||||||
mount /dev/loop42p1 mounted_image/boot
|
umount "$MOUNT_DIR/boot" "$MOUNT_DIR/system" 2>/dev/null
|
||||||
mount /dev/loop42p2 mounted_image/system
|
[ -n "$LOOP_DEV" ] && losetup -d "$LOOP_DEV"
|
||||||
|
rmdir "$MOUNT_DIR/boot" "$MOUNT_DIR/system" "$MOUNT_DIR" 2>/dev/null
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
mkdir "$MOUNT_DIR/boot" "$MOUNT_DIR/system"
|
||||||
|
LOOP_DEV=$(losetup -f --show -P ${IMG_FILE_BASENAME}.img)
|
||||||
|
mount ${LOOP_DEV}p1 "$MOUNT_DIR/boot" # firmware partition, /boot/firmware on the Pi
|
||||||
|
mount ${LOOP_DEV}p2 "$MOUNT_DIR/system"
|
||||||
|
|
||||||
|
|
||||||
echo "Enabling SSH and writing hostname"
|
echo "Enabling SSH and writing hostname"
|
||||||
echo $RPI_HOSTNAME > mounted_image/system/etc/hostname
|
echo $RPI_HOSTNAME > "$MOUNT_DIR/system/etc/hostname"
|
||||||
touch mounted_image/boot/ssh # startup ssh
|
touch "$MOUNT_DIR/boot/ssh" # startup ssh
|
||||||
sed -i "/^#PermitRootLogin/ cPermitRootLogin prohibit-password" mounted_image/system/etc/ssh/sshd_config
|
sed -i "/^#PermitRootLogin/ cPermitRootLogin prohibit-password" "$MOUNT_DIR/system/etc/ssh/sshd_config"
|
||||||
mkdir -p mounted_image/system/root/.ssh
|
mkdir -p "$MOUNT_DIR/system/root/.ssh"
|
||||||
cat $SCRIPT_DIR/*.pub > mounted_image/system/root/.ssh/authorized_keys
|
cat $SCRIPT_DIR/*.pub > "$MOUNT_DIR/system/root/.ssh/authorized_keys"
|
||||||
chmod 700 mounted_image/system/root/.ssh
|
chmod 700 "$MOUNT_DIR/system/root/.ssh"
|
||||||
chmod 600 mounted_image/system/root/.ssh/authorized_keys
|
chmod 600 "$MOUNT_DIR/system/root/.ssh/authorized_keys"
|
||||||
|
|
||||||
|
|
||||||
if [ -z "$WIFI_PASSWORD" ]
|
if [ -z "$WIFI_PASSWORD" ]
|
||||||
then
|
then
|
||||||
echo "Skipping WIFI setup"
|
echo "Skipping WIFI setup"
|
||||||
|
elif [ -d "$MOUNT_DIR/system/etc/NetworkManager" ] && [ -e "$MOUNT_DIR/boot/user-data" ]
|
||||||
|
then
|
||||||
|
# Trixie (and bookworm images from 2023-10 on, when they use NetworkManager) ignore
|
||||||
|
# wpa_supplicant.conf on the boot partition; give NetworkManager a connection profile.
|
||||||
|
echo "Setting up initial wifi config (NetworkManager)"
|
||||||
|
NM_DIR="$MOUNT_DIR/system/etc/NetworkManager/system-connections"
|
||||||
|
mkdir -p "$NM_DIR"
|
||||||
|
/bin/cat <<EOM >"$NM_DIR/wifi.nmconnection"
|
||||||
|
[connection]
|
||||||
|
id=wifi
|
||||||
|
type=wifi
|
||||||
|
autoconnect=true
|
||||||
|
|
||||||
|
[wifi]
|
||||||
|
mode=infrastructure
|
||||||
|
ssid=${WIFI_SSID}
|
||||||
|
|
||||||
|
[wifi-security]
|
||||||
|
key-mgmt=wpa-psk
|
||||||
|
psk=${WIFI_PASSWORD}
|
||||||
|
|
||||||
|
[ipv4]
|
||||||
|
method=auto
|
||||||
|
|
||||||
|
[ipv6]
|
||||||
|
method=auto
|
||||||
|
EOM
|
||||||
|
chmod 600 "$NM_DIR/wifi.nmconnection" # NetworkManager ignores profiles readable by others
|
||||||
|
# regulatory domain, so the 5GHz channels and the right power limits are usable at boot
|
||||||
|
sed -i '1 s/$/ cfg80211.ieee80211_regdom=DE/' "$MOUNT_DIR/boot/cmdline.txt"
|
||||||
else
|
else
|
||||||
echo "Setting up initial wifi config"
|
echo "Setting up initial wifi config (wpa_supplicant)"
|
||||||
/bin/cat <<EOM >mounted_image/boot/wpa_supplicant.conf
|
/bin/cat <<EOM >"$MOUNT_DIR/boot/wpa_supplicant.conf"
|
||||||
country=DE
|
country=DE
|
||||||
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
||||||
update_config=1
|
update_config=1
|
||||||
|
|
||||||
network={
|
network={
|
||||||
ssid="WLAN"
|
ssid="${WIFI_SSID}"
|
||||||
psk="${WIFI_PASSWORD}"
|
psk="${WIFI_PASSWORD}"
|
||||||
}
|
}
|
||||||
EOM
|
EOM
|
||||||
@@ -75,12 +120,8 @@ fi
|
|||||||
|
|
||||||
echo "Unmounting image and other cleanup"
|
echo "Unmounting image and other cleanup"
|
||||||
sleep 3 # to prevent "device is busy"
|
sleep 3 # to prevent "device is busy"
|
||||||
umount /dev/loop42p1
|
cleanup
|
||||||
umount /dev/loop42p2
|
trap - EXIT
|
||||||
losetup -d /dev/loop42
|
|
||||||
rmdir mounted_image/boot
|
|
||||||
rmdir mounted_image/system
|
|
||||||
rmdir mounted_image
|
|
||||||
rm ${IMG_FILE_BASENAME}.img.xz.sha256
|
rm ${IMG_FILE_BASENAME}.img.xz.sha256
|
||||||
chmod a+rw ${IMG_FILE_BASENAME}.img .
|
chmod a+rw ${IMG_FILE_BASENAME}.img .
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user