diff --git a/group_vars/mediapis.yml b/group_vars/mediapis.yml
index 7910589..f2e976b 100644
--- a/group_vars/mediapis.yml
+++ b/group_vars/mediapis.yml
@@ -6,3 +6,10 @@ mediapi_disable_onboard_bluetooth: false
mediapi_install_bluetooth_monitor: false
mediapi_install_kidsmusic: false
mediapi_has_monitor: false
+
+# No media pi runs its own Logitech Media Server. Every squeezelite in the fleet is
+# pointed at pi_squeezelite_squeezeserver (the server, 192.168.178.80) by inventory.yml,
+# so a server on a pi would have no clients. false here does not mean "skip the role" -
+# it means "actively remove it", which is how the stray one on musicdolphin goes away
+# and stays away. See roles/pi_squeezeserver/README.md.
+mediapi_has_squeezeserver: false
diff --git a/mediapis.yml b/mediapis.yml
index 11cd217..8864f5a 100644
--- a/mediapis.yml
+++ b/mediapis.yml
@@ -12,6 +12,9 @@
- role: pi_kiosk
when: mediapi_has_monitor
- pi_squeezelite_custom
+ - role: pi_squeezeserver
+ vars:
+ pi_squeezeserver_state: "{{ 'present' if mediapi_has_squeezeserver else 'absent' }}"
- pi_shairport
- role: pi_irserver
when: mediapi_ir_control == 'irserver'
diff --git a/roles/pi_kiosk/README.md b/roles/pi_kiosk/README.md
index 4688095..532671a 100644
--- a/roles/pi_kiosk/README.md
+++ b/roles/pi_kiosk/README.md
@@ -1,19 +1,41 @@
# pi_kiosk
-Turns a Pi with a monitor attached into a full-screen Firefox display: an autologin
-console user runs `startx`, whose `.xinitrc` starts Openbox and then Firefox pointed at
-`pi_kiosk_url`, relaunching it if it ever exits. If X itself crashes, `.bash_profile`
+Turns a Pi with a monitor attached into a full-screen browser display: an autologin
+console user runs `startx`, whose `.xinitrc` starts Openbox and then the browser pointed
+at `pi_kiosk_url`, relaunching it if it ever exits. If X itself crashes, `.bash_profile`
restarts it.
Vars:
- `pi_kiosk_user` (default `kiosk`) - the account that autologs into tty1.
-- `pi_kiosk_url` (default `http://localhost:8080`) - what Firefox shows.
+- `pi_kiosk_url` (default `http://localhost:8080`) - what the browser shows.
+- `pi_kiosk_browser` (default `chromium`) - `chromium` or `firefox`, see below.
- `pi_kiosk_mode` (default `kiosk`) - `kiosk` or `debug`, see below.
Meant to run alongside `pi_musicmouse` on the same host when it's serving the page
locally (the default `pi_kiosk_url`), gated in `mediapis.yml` by `mediapi_has_monitor`.
+## Why Chromium
+
+Firefox was the original choice and lost on measurement. Same page, same machine, idle:
+**Firefox 24.2% of a core, Chromium 11.4%**. Two reasons, and they compound:
+
+- Chromium here comes from `archive.raspberrypi.com` (versions carry a `+rpt` suffix)
+ and is patched by Raspberry Pi for this board's V3D GPU. Debian also ships a much
+ older `chromium`; pinning the wrong archive would quietly undo the point of choosing
+ it.
+- The whole userland on these images is 32-bit `armhf` on a 64-bit kernel
+ (`dpkg --print-architecture` says `armhf`, `uname -m` says `aarch64`), and Firefox
+ fares worse than Chromium on that target.
+
+The flags in `.xinitrc` are not decoration: `--use-gl=egl`,
+`--enable-gpu-rasterization`, `--ignore-gpu-blocklist` and `--enable-zero-copy` are
+what keep rasterization on the GPU. Chromium's blocklist does not recognise this driver,
+so without `--ignore-gpu-blocklist` it quietly falls back to software.
+
+`pi_kiosk_browser: firefox` still works and is the fallback if a Chromium update ever
+regresses.
+
## Why there is a window manager
This role used to run no WM at all, on the theory that Firefox is the only X client and
@@ -34,8 +56,13 @@ which it is not. Openbox answers the request and otherwise stays out of the way.
- Openbox is the session leader. Right-click the desktop for a menu with a terminal,
both ways of opening the page again, and "End this X session", which drops back to
the autologin loop and starts a fresh one.
-- `xterm`, `x11-utils` and `mesa-utils` get installed, so `xwininfo`, `xprop`,
- `glxinfo` and `glxgears` are on the device. (The menu's Terminal entry runs
+- `xterm`, `x11-utils`, `mesa-utils`, `xdotool` and `scrot` get installed, so
+ `xwininfo`, `xprop`, `glxinfo`, `glxgears`, synthetic input and screenshots are all
+ available over ssh.
+- Chromium gets `--remote-debugging-port=9222`, bound to 127.0.0.1. `curl
+ localhost:9222/json` lists the tabs, and the DevTools protocol will drive and measure
+ the page properly - which beats firing XTEST key events at a window and hoping they
+ land, as they only do about half the time. (The menu's Terminal entry runs
`x-terminal-emulator`, which on the Pi OS image is `zutty`, not the xterm we
install - both work; xterm is the fallback if the alternatives link ever points
somewhere that doesn't.)
diff --git a/roles/pi_kiosk/defaults/main.yml b/roles/pi_kiosk/defaults/main.yml
index 2b84625..a1878d7 100644
--- a/roles/pi_kiosk/defaults/main.yml
+++ b/roles/pi_kiosk/defaults/main.yml
@@ -2,8 +2,15 @@
pi_kiosk_user: "kiosk"
pi_kiosk_url: "http://localhost:8080"
+# "chromium" - the build from archive.raspberrypi.com (versions carry a "+rpt" suffix),
+# which Raspberry Pi patch for this board's V3D GPU. Faster than Firefox
+# here by a wide margin; see the README.
+# "firefox" - firefox-esr from Debian. Kept switchable because it was the original
+# kiosk browser and is the fallback if a Chromium update ever regresses.
+pi_kiosk_browser: "chromium"
+
# "kiosk" - the page full-screen with no browser chrome and no pointer. The normal mode.
-# "debug" - the same page in an ordinary Firefox window, pointer visible, with an
+# "debug" - the same page in an ordinary browser window, pointer visible, with an
# Openbox root menu (right-click the desktop) offering a terminal, so the
# device can be poked at on its own screen. Switch back with
# `just run mediapis.yml musicdolphin -e pi_kiosk_mode=kiosk`, or by editing
diff --git a/roles/pi_kiosk/files/chromium-policies.json b/roles/pi_kiosk/files/chromium-policies.json
new file mode 100644
index 0000000..b80bc2c
--- /dev/null
+++ b/roles/pi_kiosk/files/chromium-policies.json
@@ -0,0 +1,15 @@
+{
+ "DefaultBrowserSettingEnabled": false,
+ "PasswordManagerEnabled": false,
+ "MetricsReportingEnabled": false,
+ "SearchSuggestEnabled": false,
+ "TranslateEnabled": false,
+ "SyncDisabled": true,
+ "BrowserSignin": 0,
+ "BackgroundModeEnabled": false,
+ "AutofillAddressEnabled": false,
+ "AutofillCreditCardEnabled": false,
+ "PromptForDownloadLocation": false,
+ "ShowHomeButton": false,
+ "BookmarkBarEnabled": false
+}
diff --git a/roles/pi_kiosk/tasks/main.yml b/roles/pi_kiosk/tasks/main.yml
index 8c3fb26..43d5aa1 100644
--- a/roles/pi_kiosk/tasks/main.yml
+++ b/roles/pi_kiosk/tasks/main.yml
@@ -5,14 +5,23 @@
- xserver-xorg
- xinit
- x11-xserver-utils
- # Without a window manager, Firefox's request to go full-screen (which is all
+ # Without a window manager, a browser's request to go full-screen (which is all
# --kiosk does) has nobody to answer it, and it settles for its default window
# size in the corner of the screen. Openbox answers it, and nothing else.
- openbox
- - firefox-esr
- unclutter
cache_valid_time: 7200
+- name: Browser
+ # Chromium comes from archive.raspberrypi.com, not Debian: that build carries the
+ # "+rpt" suffix and Raspberry Pi's own patches for the V3D GPU on this board. Debian
+ # also ships a much older `chromium`, so pinning the wrong archive here would quietly
+ # undo the reason for choosing it.
+ ansible.builtin.apt:
+ name: "{{ 'chromium' if pi_kiosk_browser == 'chromium' else 'firefox-esr' }}"
+ cache_valid_time: 7200
+ notify: Reload systemd and restart getty
+
- name: Debugging tools for the attached screen
# Only pulled in for pi_kiosk_mode=debug; apt does not take them away again when the
# host goes back to kiosk mode, which is fine - they are inert and small.
@@ -21,6 +30,9 @@
- xterm # a terminal that is ours, whatever x-terminal-emulator points at
- x11-utils # xwininfo, xprop: is the window actually the size of the screen?
- mesa-utils # glxinfo, glxgears: is anything accelerated, or is this llvmpipe?
+ - xdotool # drive the UI from ssh, so a measurement can be taken on a real
+ # screen rather than an idle one
+ - scrot # and see what that screen actually shows, over ssh
cache_valid_time: 7200
when: pi_kiosk_mode == 'debug'
@@ -81,7 +93,7 @@
mode: "0644"
notify: Reload systemd and restart getty
-- name: Install .xinitrc (starts Openbox, then Firefox)
+- name: Install .xinitrc (starts Openbox, then the browser)
ansible.builtin.template:
src: xinitrc.j2
dest: "/home/{{ pi_kiosk_user }}/.xinitrc"
@@ -90,18 +102,24 @@
mode: "0755"
notify: Reload systemd and restart getty
-- name: Ensure firefox-esr policy directory exists
+- name: Ensure the browser policy directory exists
ansible.builtin.file:
- path: /etc/firefox-esr
+ path: "{{ '/etc/chromium/policies/managed' if pi_kiosk_browser == 'chromium' else '/etc/firefox-esr' }}"
state: directory
mode: "0755"
- # apt normally creates this; a backstop in case the package layout doesn't
+ # The packages normally create these; a backstop in case a layout changes
-- name: Install Firefox enterprise policy (suppress session-restore/update prompts)
+- name: Install the browser's enterprise policy
+ # Everything that is a setting rather than a startup flag: no sign-in, no sync, no
+ # metrics, no password manager, no translate bar. The flags themselves are in
+ # .xinitrc, where they have to be.
ansible.builtin.copy:
- src: policies.json
- dest: /etc/firefox-esr/policies.json
+ src: "{{ 'chromium-policies.json' if pi_kiosk_browser == 'chromium' else 'policies.json' }}"
+ dest: >-
+ {{ '/etc/chromium/policies/managed/kiosk.json'
+ if pi_kiosk_browser == 'chromium' else '/etc/firefox-esr/policies.json' }}
mode: "0644"
+ notify: Reload systemd and restart getty
- name: Ensure the Openbox config directories exist
# Both levels explicitly, and owned by the user: a file: task creates missing parents
diff --git a/roles/pi_kiosk/templates/openbox-menu.xml.j2 b/roles/pi_kiosk/templates/openbox-menu.xml.j2
index 9fc4c71..8443390 100644
--- a/roles/pi_kiosk/templates/openbox-menu.xml.j2
+++ b/roles/pi_kiosk/templates/openbox-menu.xml.j2
@@ -13,12 +13,12 @@
-
- firefox {{ pi_kiosk_url }}
+ {{ pi_kiosk_browser_command }} {{ pi_kiosk_url }}
-
- firefox --kiosk {{ pi_kiosk_url }}
+ {{ pi_kiosk_browser_command }} --kiosk {{ pi_kiosk_url }}
diff --git a/roles/pi_kiosk/templates/xinitrc.j2 b/roles/pi_kiosk/templates/xinitrc.j2
index c9fda5c..5f058f2 100644
--- a/roles/pi_kiosk/templates/xinitrc.j2
+++ b/roles/pi_kiosk/templates/xinitrc.j2
@@ -3,21 +3,52 @@
#
# Openbox is here for one concrete reason: a browser asks to be full-screen over EWMH,
# and only a window manager answers that request. With no WM running, --kiosk was
-# silently ignored and Firefox fell back to its default 1280x972 window on a 1920x1080
-# screen - the page in the top-left corner with black bands down the right edge and
-# along the bottom. Openbox costs a couple of MB and does nothing else here.
+# silently ignored and the browser fell back to its default window size in the
+# top-left corner of a 1920x1080 screen - black bands down the right edge and along
+# the bottom, looking exactly like an overscan problem and not being one.
xset s off
xset -dpms
xset s noblank
+{% if pi_kiosk_browser == 'chromium' %}
+BROWSER=chromium
+
+# The first four flags are the difference between compositing on the GPU and
+# rasterizing every frame on the CPU:
+# --use-gl=egl reach V3D through EGL rather than desktop GLX
+# --enable-gpu-rasterization rasterize layer content on the GPU
+# --ignore-gpu-blocklist Chromium's blocklist does not know this driver, and
+# without this it quietly falls back to software
+# --enable-zero-copy hand tiles to the compositor without a CPU copy
+# The rest are about a machine nobody sits in front of: no update checks, no crash
+# bubbles, no keyring prompt, no first-run wizard, no "restore pages?" after a power
+# cut. Anything policy-shaped rather than flag-shaped is in
+# /etc/chromium/policies/managed/.
+BROWSER_ARGS="--use-gl=egl --enable-gpu-rasterization --ignore-gpu-blocklist --enable-zero-copy"
+BROWSER_ARGS="$BROWSER_ARGS --noerrdialogs --disable-infobars --no-first-run"
+BROWSER_ARGS="$BROWSER_ARGS --no-default-browser-check --disable-session-crashed-bubble"
+BROWSER_ARGS="$BROWSER_ARGS --password-store=basic --check-for-update-interval=31536000"
{% if pi_kiosk_mode == 'debug' %}
-# Debug session. Firefox is an ordinary window with its chrome (so devtools and the URL
-# bar are reachable) and is *not* relaunched when it exits - quitting it should leave a
-# usable desktop, not fight you for the screen. Openbox is the session leader instead:
-# when it exits (root menu -> Exit) the X session ends and .bash_profile starts a fresh
-# one. Right-click the desktop for a terminal.
-firefox "{{ pi_kiosk_url }}" &
+# Debug mode only. Binds to 127.0.0.1, so it is reachable over an ssh tunnel and from
+# nowhere else. Lets the page be driven and measured from a shell - `curl
+# localhost:9222/json` lists the tabs - instead of by poking XTEST key events at a
+# window and hoping they land.
+BROWSER_ARGS="$BROWSER_ARGS --remote-debugging-port=9222"
+{% endif %}
+{% else %}
+BROWSER=firefox
+BROWSER_ARGS=""
+{% endif %}
+
+{% if pi_kiosk_mode == 'debug' %}
+# Debug session. The browser is an ordinary window with its chrome (so devtools and the
+# URL bar are reachable) and is *not* relaunched when it exits - quitting it should
+# leave a usable desktop, not fight you for the screen. Openbox is the session leader
+# instead: when it exits (root menu -> Exit) the X session ends and .bash_profile
+# starts a fresh one. Right-click the desktop for a terminal.
+# shellcheck disable=SC2086 # BROWSER_ARGS is a word list on purpose
+$BROWSER $BROWSER_ARGS "{{ pi_kiosk_url }}" &
exec openbox
{% else %}
@@ -26,9 +57,10 @@ openbox &
unclutter -idle 0.5 -root &
# The loop is what takes the place of a WM's "keep something on screen" job: Openbox
-# manages the window, but nothing else would bring Firefox back if it died.
+# manages the window, but nothing else would bring the browser back if it died.
while true; do
- firefox --kiosk "{{ pi_kiosk_url }}"
+ # shellcheck disable=SC2086
+ $BROWSER --kiosk $BROWSER_ARGS "{{ pi_kiosk_url }}"
sleep 2
done
{% endif %}
diff --git a/roles/pi_kiosk/vars/main.yml b/roles/pi_kiosk/vars/main.yml
new file mode 100644
index 0000000..879ad6b
--- /dev/null
+++ b/roles/pi_kiosk/vars/main.yml
@@ -0,0 +1,4 @@
+---
+# The executable name for each supported browser. In vars/ rather than defaults/
+# because it is derived from pi_kiosk_browser, not something to override.
+pi_kiosk_browser_command: "{{ 'chromium' if pi_kiosk_browser == 'chromium' else 'firefox' }}"
diff --git a/roles/pi_squeezeserver/README.md b/roles/pi_squeezeserver/README.md
index 97c18b2..3b6e23a 100644
--- a/roles/pi_squeezeserver/README.md
+++ b/roles/pi_squeezeserver/README.md
@@ -1,4 +1,22 @@
# pi_squeezeserver
-Installs and enables the Logitech Media Server (`squeezeserver`) package,
-and registers it with `pi_sysdweb`.
+The Logitech Media Server (`logitechmediaserver`, the daemon formerly known as
+squeezeboxserver), plus its `pi_sysdweb` registration and a port 80 -> 9000 redirect.
+
+`pi_squeezeserver_state` picks a direction:
+
+- `present` - install it and run it (`tasks/present.yml`).
+- `absent` - stop it, purge the package, unregister it from sysdweb, drop the iptables
+ redirect and delete `/var/lib/squeezeboxserver` and `/var/log/squeezeboxserver`
+ (`tasks/absent.yml`).
+
+`mediapis.yml` derives the state from `mediapi_has_squeezeserver`, which defaults to
+`false` in `group_vars/mediapis.yml`. That default is deliberate and is a policy, not
+an oversight: **no media pi runs its own LMS.** Every `squeezelite` in the fleet is
+pointed at `pi_squeezelite_squeezeserver` (192.168.178.80, the server) by
+`inventory.yml`, so a server running on a pi has no clients. musicdolphin had one
+anyway - left over from earlier provisioning, since no playbook has installed it - and
+on a 2 GB Pi also driving a kiosk browser it was costing ~30 MB resident and ~47 MB of
+swap for nothing.
+
+Set `mediapi_has_squeezeserver: true` in a host's vars if one ever genuinely needs it.
diff --git a/roles/pi_squeezeserver/defaults/main.yml b/roles/pi_squeezeserver/defaults/main.yml
new file mode 100644
index 0000000..5630657
--- /dev/null
+++ b/roles/pi_squeezeserver/defaults/main.yml
@@ -0,0 +1,6 @@
+---
+# "present" installs and runs the Logitech Media Server; "absent" removes it and
+# everything the present path put in place. Driven from mediapis.yml by
+# `mediapi_has_squeezeserver`, so a host that does not ask for LMS actively has it
+# taken away rather than merely not being given it.
+pi_squeezeserver_state: present
diff --git a/roles/pi_squeezeserver/handlers/main.yml b/roles/pi_squeezeserver/handlers/main.yml
new file mode 100644
index 0000000..bc9f21a
--- /dev/null
+++ b/roles/pi_squeezeserver/handlers/main.yml
@@ -0,0 +1,8 @@
+---
+- name: Restart sysdweb after dropping the LMS entry
+ # This role's own handler rather than pi_sysdweb's: handlers only exist once the role
+ # defining them has been included, and role ordering does not guarantee pi_sysdweb has
+ # been by the time this one runs.
+ ansible.builtin.systemd:
+ name: sysdweb-system
+ state: restarted
diff --git a/roles/pi_squeezeserver/tasks/absent.yml b/roles/pi_squeezeserver/tasks/absent.yml
new file mode 100644
index 0000000..2e8c190
--- /dev/null
+++ b/roles/pi_squeezeserver/tasks/absent.yml
@@ -0,0 +1,60 @@
+---
+# The mirror image of present.yml. Worth having as a real code path rather than a
+# one-off ssh session: musicdolphin had LMS running because some earlier provisioning
+# put it there, no playbook has installed it since, and nothing on the network used
+# it - every squeezelite in the fleet points at `pi_squeezelite_squeezeserver`
+# (192.168.178.80, the server), not at the pi it happens to be running on. On a 2 GB Pi
+# that is also driving a kiosk browser, a stray Perl daemon holding ~30 MB resident and
+# ~47 MB of swap is worth reclaiming.
+
+- name: Check whether the Logitech Media Server unit is there at all
+ ansible.builtin.stat:
+ path: /lib/systemd/system/logitechmediaserver.service
+ register: pi_squeezeserver_unit
+
+- name: Stop and disable the Logitech Media Server
+ # Before the package goes, so systemd is told rather than left holding a unit file
+ # that vanishes underneath it.
+ ansible.builtin.systemd:
+ name: logitechmediaserver
+ state: stopped
+ enabled: false
+ when: pi_squeezeserver_unit.stat.exists
+
+- name: Purge the Logitech Media Server package
+ ansible.builtin.apt:
+ name: logitechmediaserver
+ state: absent
+ purge: true
+ autoremove: true
+
+- name: Drop the Logitech Media Server from sysdweb
+ # Same marker pi_sysdweb writes, so this removes exactly the block that role added.
+ ansible.builtin.blockinfile:
+ path: /etc/sysdweb.conf
+ marker: "# {mark} ansible managed for logitechmediaserver"
+ state: absent
+ notify: Restart sysdweb after dropping the LMS entry
+
+- name: Undo the port 80 -> 9000 redirect the install added
+ ansible.builtin.iptables:
+ table: nat
+ chain: PREROUTING
+ in_interface: eth0
+ protocol: tcp
+ match: tcp
+ destination_port: "80"
+ jump: REDIRECT
+ to_ports: "9000"
+ comment: Redirect web traffic to port 9000
+ state: absent
+
+- name: Remove the server's database and logs
+ # apt purge takes /usr/share/squeezeboxserver (118 MB) with it but leaves these. They
+ # are a scan cache and a log of a server nobody talked to.
+ ansible.builtin.file:
+ path: "{{ item }}"
+ state: absent
+ loop:
+ - /var/lib/squeezeboxserver
+ - /var/log/squeezeboxserver
diff --git a/roles/pi_squeezeserver/tasks/main.yml b/roles/pi_squeezeserver/tasks/main.yml
index 25f9a5b..0d07ae0 100644
--- a/roles/pi_squeezeserver/tasks/main.yml
+++ b/roles/pi_squeezeserver/tasks/main.yml
@@ -1,45 +1,8 @@
---
-- name: Install packages required for squeeze server
- ansible.builtin.apt:
- name:
- - libsox-fmt-all
- - libflac-dev
- - libfaad2
- - libmad0
- - perl-openssl-defaults
- - libnet-ssleay-perl
- - libio-socket-ssl-perl
- - nasm
- - build-essential
- - iptables-persistent
- cache_valid_time: 7200
- state: present
-- name: Copy squeezeserver package
- ansible.builtin.copy:
- src: logitechmediaserver_8.4.0_arm.deb
- dest: /tmp
- mode: "0644"
-- name: Install squeezeserver package
- ansible.builtin.apt:
- deb: /tmp/logitechmediaserver_8.4.0_arm.deb
-- name: Enable sysdweb autostart
- ansible.builtin.systemd:
- name: logitechmediaserver
- state: started
- enabled: "yes"
-- name: Add to sysdweb
- ansible.builtin.include_role:
- name: pi_sysdweb
- vars:
- pi_sysdweb_name: logitechmediaserver
-- name: Forward port 80 to 9000
- ansible.builtin.iptables:
- table: nat
- chain: PREROUTING
- in_interface: eth0
- protocol: tcp
- match: tcp
- destination_port: "80"
- jump: REDIRECT
- to_ports: "9000"
- comment: Redirect web traffic to port 9000
+- name: Install the Logitech Media Server
+ ansible.builtin.include_tasks: present.yml
+ when: pi_squeezeserver_state == 'present'
+
+- name: Remove the Logitech Media Server
+ ansible.builtin.include_tasks: absent.yml
+ when: pi_squeezeserver_state == 'absent'
diff --git a/roles/pi_squeezeserver/tasks/present.yml b/roles/pi_squeezeserver/tasks/present.yml
new file mode 100644
index 0000000..25f9a5b
--- /dev/null
+++ b/roles/pi_squeezeserver/tasks/present.yml
@@ -0,0 +1,45 @@
+---
+- name: Install packages required for squeeze server
+ ansible.builtin.apt:
+ name:
+ - libsox-fmt-all
+ - libflac-dev
+ - libfaad2
+ - libmad0
+ - perl-openssl-defaults
+ - libnet-ssleay-perl
+ - libio-socket-ssl-perl
+ - nasm
+ - build-essential
+ - iptables-persistent
+ cache_valid_time: 7200
+ state: present
+- name: Copy squeezeserver package
+ ansible.builtin.copy:
+ src: logitechmediaserver_8.4.0_arm.deb
+ dest: /tmp
+ mode: "0644"
+- name: Install squeezeserver package
+ ansible.builtin.apt:
+ deb: /tmp/logitechmediaserver_8.4.0_arm.deb
+- name: Enable sysdweb autostart
+ ansible.builtin.systemd:
+ name: logitechmediaserver
+ state: started
+ enabled: "yes"
+- name: Add to sysdweb
+ ansible.builtin.include_role:
+ name: pi_sysdweb
+ vars:
+ pi_sysdweb_name: logitechmediaserver
+- name: Forward port 80 to 9000
+ ansible.builtin.iptables:
+ table: nat
+ chain: PREROUTING
+ in_interface: eth0
+ protocol: tcp
+ match: tcp
+ destination_port: "80"
+ jump: REDIRECT
+ to_ports: "9000"
+ comment: Redirect web traffic to port 9000