From 353c2ad7909cafb6837bbe4b0bf1d852b961be1e Mon Sep 17 00:00:00 2001 From: "Rasmus \"Pez\" Wejlgaard" Date: Sun, 29 Mar 2026 19:13:48 +0100 Subject: [PATCH] Capture london-b media stack and systemd services (#19) Add the full media automation stack (sonarr, radarr, prowlarr, lidarr, readarr, whisparr), media servers (jellyfin, plex), and supporting services (transmission, samba, ollama, promtail, cloudflared, vsftpd) to the repo as a media_stack Ansible role. Includes: - Custom systemd unit files for non-package-managed services - Config files for promtail, samba, transmission, vsftpd - Cron jobs for movie-rename-fix, sonarr/radarr midnight restarts - Updated deploy.yml to wire the role into london-b's stage - Updated london-b docs with full service inventory Backup script (backup.sh) already covered by the existing backup role. Node/systemd exporters already covered by existing monitoring roles. Closes PESO-92 --- ansible/deploy.yml | 5 +- ansible/roles/media_stack/handlers/main.yml | 24 ++++ ansible/roles/media_stack/tasks/main.yml | 128 ++++++++++++++++++ ansible/scripts/movie-rename-fix.fish | 7 + ansible/services/lidarr/lidarr.service | 16 +++ ansible/services/ollama/ollama.service | 14 ++ ansible/services/promtail/config/london-b.yml | 31 +++++ ansible/services/promtail/promtail.service | 14 ++ ansible/services/prowlarr/prowlarr.service | 16 +++ ansible/services/radarr/radarr.service | 16 +++ ansible/services/readarr/readarr.service | 16 +++ ansible/services/samba/config/london-b.conf | 53 ++++++++ .../systemd/london-b/cloudflared.service | 13 ++ .../transmission/config/settings.json | 84 ++++++++++++ ansible/services/vsftpd/config/london-b.conf | 18 +++ ansible/services/whisparr/whisparr.service | 16 +++ docs/hosts/london-b.md | 44 +++++- 17 files changed, 512 insertions(+), 3 deletions(-) create mode 100644 ansible/roles/media_stack/handlers/main.yml create mode 100644 ansible/roles/media_stack/tasks/main.yml create mode 100755 ansible/scripts/movie-rename-fix.fish create mode 100644 ansible/services/lidarr/lidarr.service create mode 100644 ansible/services/ollama/ollama.service create mode 100644 ansible/services/promtail/config/london-b.yml create mode 100644 ansible/services/promtail/promtail.service create mode 100644 ansible/services/prowlarr/prowlarr.service create mode 100644 ansible/services/radarr/radarr.service create mode 100644 ansible/services/readarr/readarr.service create mode 100644 ansible/services/samba/config/london-b.conf create mode 100644 ansible/services/systemd/london-b/cloudflared.service create mode 100644 ansible/services/transmission/config/settings.json create mode 100644 ansible/services/vsftpd/config/london-b.conf create mode 100644 ansible/services/whisparr/whisparr.service diff --git a/ansible/deploy.yml b/ansible/deploy.yml index 91ce3bf..6b9e2b5 100644 --- a/ansible/deploy.yml +++ b/ansible/deploy.yml @@ -54,12 +54,13 @@ - role: caddy - role: status_page -# london-b: Docker services (storage, apps) + backups -- name: "Stage 4b: Docker services (london-b)" +# london-b: Docker services (storage, apps) + media stack + backups +- name: "Stage 4b: Services (london-b)" hosts: london-b tags: [services, london-b] roles: - role: docker_services + - role: media_stack - role: backup # nuremberg-a: Mail (poste.io via Docker) diff --git a/ansible/roles/media_stack/handlers/main.yml b/ansible/roles/media_stack/handlers/main.yml new file mode 100644 index 0000000..94caced --- /dev/null +++ b/ansible/roles/media_stack/handlers/main.yml @@ -0,0 +1,24 @@ +--- +- name: Reload systemd daemon + ansible.builtin.systemd: + daemon_reload: true + +- name: Restart promtail + ansible.builtin.systemd: + name: promtail + state: restarted + +- name: Restart smbd + ansible.builtin.systemd: + name: smbd + state: restarted + +- name: Restart transmission + ansible.builtin.systemd: + name: transmission-daemon + state: restarted + +- name: Restart vsftpd + ansible.builtin.systemd: + name: vsftpd + state: restarted diff --git a/ansible/roles/media_stack/tasks/main.yml b/ansible/roles/media_stack/tasks/main.yml new file mode 100644 index 0000000..3b86352 --- /dev/null +++ b/ansible/roles/media_stack/tasks/main.yml @@ -0,0 +1,128 @@ +--- +# media_stack role — deploys the full media stack on london-b +# Manages: *arr suite, jellyfin, plex, transmission, samba, +# ollama, promtail, cloudflared, vsftpd, and cron jobs. + +# ── Systemd service units (custom, not package-managed) ── + +- name: Deploy custom systemd unit files + ansible.builtin.copy: + src: "{{ playbook_dir }}/services/{{ item }}/{{ item }}.service" + dest: "/etc/systemd/system/{{ item }}.service" + mode: '0644' + loop: + - radarr + - prowlarr + - lidarr + - readarr + - whisparr + - ollama + - promtail + notify: Reload systemd daemon + +- name: Enable and start custom systemd services + ansible.builtin.systemd: + name: "{{ item }}" + state: started + enabled: true + loop: + - radarr + - prowlarr + - lidarr + - readarr + - ollama + - promtail + +# Whisparr is installed but disabled (kept as-is) +- name: Ensure whisparr unit is present but disabled + ansible.builtin.systemd: + name: whisparr + enabled: false + +# ── Package-managed services (ensure enabled) ── + +- name: Ensure package-managed services are enabled + ansible.builtin.systemd: + name: "{{ item }}" + state: started + enabled: true + loop: + - sonarr + - jellyfin + - plexmediaserver + - transmission-daemon + - smbd + - vsftpd + - cloudflared + +# ── Configuration files ── + +- name: Deploy promtail config + ansible.builtin.copy: + src: "{{ playbook_dir }}/services/promtail/config/london-b.yml" + dest: /etc/promtail/config.yml + mode: '0644' + notify: Restart promtail + +- name: Deploy samba config + ansible.builtin.copy: + src: "{{ playbook_dir }}/services/samba/config/london-b.conf" + dest: /etc/samba/smb.conf + mode: '0644' + backup: true + notify: Restart smbd + +- name: Deploy transmission settings + ansible.builtin.copy: + src: "{{ playbook_dir }}/services/transmission/config/settings.json" + dest: /etc/transmission-daemon/settings.json + owner: debian-transmission + group: debian-transmission + mode: '0600' + notify: Restart transmission + +- name: Deploy vsftpd config + ansible.builtin.copy: + src: "{{ playbook_dir }}/services/vsftpd/config/london-b.conf" + dest: /etc/vsftpd.conf + mode: '0644' + notify: Restart vsftpd + +# ── Scripts ── + +- name: Ensure scripts directory exists + ansible.builtin.file: + path: /root/scripts + state: directory + mode: '0755' + +- name: Deploy movie-rename-fix script + ansible.builtin.copy: + src: "{{ playbook_dir }}/scripts/movie-rename-fix.fish" + dest: /root/scripts/movie-rename-fix.fish + mode: '0755' + +# ── Cron jobs ── + +- name: Movie rename fix (hourly) + ansible.builtin.cron: + name: "Movie rename fix" + minute: "0" + job: "/root/scripts/movie-rename-fix.fish" + user: root + +- name: Restart radarr at midnight + ansible.builtin.cron: + name: "Restart radarr" + minute: "0" + hour: "0" + job: "systemctl restart radarr" + user: root + +- name: Restart sonarr at midnight + ansible.builtin.cron: + name: "Restart sonarr" + minute: "0" + hour: "0" + job: "systemctl restart sonarr" + user: root diff --git a/ansible/scripts/movie-rename-fix.fish b/ansible/scripts/movie-rename-fix.fish new file mode 100755 index 0000000..66f4f0e --- /dev/null +++ b/ansible/scripts/movie-rename-fix.fish @@ -0,0 +1,7 @@ +#!/usr/bin/env fish + +cd /hdd/movies + +for i in (find . -name "*www.UIndex.org - *") + mv $i (echo $i | sed 's/www.UIndex.org - //') +end diff --git a/ansible/services/lidarr/lidarr.service b/ansible/services/lidarr/lidarr.service new file mode 100644 index 0000000..3c3fcc1 --- /dev/null +++ b/ansible/services/lidarr/lidarr.service @@ -0,0 +1,16 @@ +[Unit] +Description=Lidarr Daemon +After=syslog.target network.target + +[Service] +User=root +Group=root +UMask=0002 +Type=simple +ExecStart=/opt/Lidarr/Lidarr -nobrowser -data=/var/lib/lidarr/ +TimeoutStopSec=20 +KillMode=process +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/ansible/services/ollama/ollama.service b/ansible/services/ollama/ollama.service new file mode 100644 index 0000000..3a82455 --- /dev/null +++ b/ansible/services/ollama/ollama.service @@ -0,0 +1,14 @@ +[Unit] +Description=Ollama Service +After=network-online.target + +[Service] +ExecStart=/usr/local/bin/ollama serve +User=ollama +Group=ollama +Restart=always +RestartSec=3 +Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin" + +[Install] +WantedBy=default.target diff --git a/ansible/services/promtail/config/london-b.yml b/ansible/services/promtail/config/london-b.yml new file mode 100644 index 0000000..4c979b6 --- /dev/null +++ b/ansible/services/promtail/config/london-b.yml @@ -0,0 +1,31 @@ +server: + http_listen_port: 9080 + grpc_listen_port: 0 + +positions: + filename: /tmp/positions.yaml + +clients: + - url: http://192.168.1.254:3100/loki/api/v1/push + +scrape_configs: +- job_name: london-b + static_configs: + - targets: + - localhost + labels: + job: varlogs + instance: london-b + __path__: /var/log/*log + - targets: + - localhost + labels: + job: plex + instance: london-b + __path__: /var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Logs/*log + - targets: + - localhost + labels: + job: jellyfin + instance: london-b + __path__: /var/log/jellyfin/*log diff --git a/ansible/services/promtail/promtail.service b/ansible/services/promtail/promtail.service new file mode 100644 index 0000000..f07acb8 --- /dev/null +++ b/ansible/services/promtail/promtail.service @@ -0,0 +1,14 @@ +[Unit] +Description=Promtail service +After=network.target + +[Service] +Type=simple +User=root +ExecStart=/usr/bin/promtail -config.file /etc/promtail/config.yml +TimeoutSec=60 +Restart=on-failure +RestartSec=2 + +[Install] +WantedBy=multi-user.target diff --git a/ansible/services/prowlarr/prowlarr.service b/ansible/services/prowlarr/prowlarr.service new file mode 100644 index 0000000..f366c5c --- /dev/null +++ b/ansible/services/prowlarr/prowlarr.service @@ -0,0 +1,16 @@ +[Unit] +Description=Prowlarr Daemon +After=syslog.target network.target + +[Service] +User=root +Group=root +UMask=0002 +Type=simple +ExecStart=/opt/Prowlarr/Prowlarr -nobrowser -data=/var/lib/prowlarr/ +TimeoutStopSec=20 +KillMode=process +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/ansible/services/radarr/radarr.service b/ansible/services/radarr/radarr.service new file mode 100644 index 0000000..36fb51b --- /dev/null +++ b/ansible/services/radarr/radarr.service @@ -0,0 +1,16 @@ +[Unit] +Description=Radarr Daemon +After=syslog.target network.target + +[Service] +User=root +Group=root +UMask=0002 +Type=simple +ExecStart=/opt/Radarr/Radarr -nobrowser -data=/var/lib/radarr/ +TimeoutStopSec=20 +KillMode=process +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/ansible/services/readarr/readarr.service b/ansible/services/readarr/readarr.service new file mode 100644 index 0000000..e703901 --- /dev/null +++ b/ansible/services/readarr/readarr.service @@ -0,0 +1,16 @@ +[Unit] +Description=Readarr Daemon +After=syslog.target network.target + +[Service] +User=root +Group=root +UMask=0002 +Type=simple +ExecStart=/opt/Readarr/Readarr -nobrowser -data=/var/lib/readarr/ +TimeoutStopSec=20 +KillMode=process +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/ansible/services/samba/config/london-b.conf b/ansible/services/samba/config/london-b.conf new file mode 100644 index 0000000..3492080 --- /dev/null +++ b/ansible/services/samba/config/london-b.conf @@ -0,0 +1,53 @@ +[global] + workgroup = WORKGROUP + server string = %h server (Samba, Ubuntu) + log file = /var/log/samba/log.%m + max log size = 1000 + logging = file + panic action = /usr/share/samba/panic-action %d + server role = standalone server + obey pam restrictions = yes + unix password sync = yes + passwd program = /usr/bin/passwd %u + passwd chat = *Enter\snew\s*\spassword:* %n\n *Retype\snew\s*\spassword:* %n\n *password\supdated\ssuccessfully* . + pam password change = yes + map to guest = bad user + + # COCKPIT ZFS MANAGER + # WARNING: DO NOT EDIT, AUTO-GENERATED CONFIGURATION + include = /etc/cockpit/zfs/shares.conf + +[HDD] + comment = HDD + path = /hdd + valid users = pez root + public = no + writable = yes + +[Movies] + comment = Movies + path = /hdd/movies + public = yes + writable = no + +[TV Shows] + comment = TV Shows + path = /hdd/tv + public = yes + writable = no + +[printers] + comment = All Printers + browseable = no + path = /var/tmp + printable = yes + guest ok = no + read only = yes + create mask = 0700 + +[print$] + comment = Printer Drivers + path = /var/lib/samba/printers + browseable = yes + read only = yes + guest ok = no diff --git a/ansible/services/systemd/london-b/cloudflared.service b/ansible/services/systemd/london-b/cloudflared.service new file mode 100644 index 0000000..6e9b1f1 --- /dev/null +++ b/ansible/services/systemd/london-b/cloudflared.service @@ -0,0 +1,13 @@ +[Unit] +Description=cloudflared +After=network.target + +[Service] +TimeoutStartSec=0 +Type=notify +ExecStart=/usr/bin/cloudflared --no-autoupdate tunnel run +Restart=on-failure +RestartSec=5s + +[Install] +WantedBy=multi-user.target diff --git a/ansible/services/transmission/config/settings.json b/ansible/services/transmission/config/settings.json new file mode 100644 index 0000000..1f41296 --- /dev/null +++ b/ansible/services/transmission/config/settings.json @@ -0,0 +1,84 @@ +{ + "alt-speed-down": 0, + "alt-speed-enabled": false, + "alt-speed-time-begin": 540, + "alt-speed-time-day": 127, + "alt-speed-time-enabled": false, + "alt-speed-time-end": 1020, + "alt-speed-up": 0, + "announce-ip": "", + "announce-ip-enabled": false, + "anti-brute-force-enabled": false, + "anti-brute-force-threshold": 100, + "bind-address-ipv4": "0.0.0.0", + "bind-address-ipv6": "::", + "blocklist-enabled": false, + "blocklist-url": "http://www.example.com/blocklist", + "cache-size-mb": 4, + "default-trackers": "", + "dht-enabled": true, + "download-dir": "/hdd/downloads", + "download-limit": 100, + "download-limit-enabled": 0, + "download-queue-enabled": true, + "download-queue-size": 100, + "encryption": 1, + "idle-seeding-limit": 30, + "idle-seeding-limit-enabled": false, + "incomplete-dir": "/var/lib/transmission-daemon/Downloads", + "incomplete-dir-enabled": false, + "lpd-enabled": false, + "max-peers-global": 200, + "message-level": 4, + "peer-congestion-algorithm": "", + "peer-id-ttl-hours": 6, + "peer-limit-global": 200, + "peer-limit-per-torrent": 50, + "peer-port": 6881, + "peer-port-random-high": 65535, + "peer-port-random-low": 49152, + "peer-port-random-on-start": false, + "peer-socket-tos": "le", + "pex-enabled": true, + "port-forwarding-enabled": false, + "preallocation": 1, + "prefetch-enabled": true, + "queue-stalled-enabled": true, + "queue-stalled-minutes": 30, + "ratio-limit": 0, + "ratio-limit-enabled": true, + "rename-partial-files": true, + "rpc-authentication-required": false, + "rpc-bind-address": "0.0.0.0", + "rpc-enabled": true, + "rpc-host-whitelist": "127.0.0.1,localhost,london-b,download.pez.sh,download.pez.solutions", + "rpc-host-whitelist-enabled": false, + "rpc-port": 9091, + "rpc-socket-mode": "0750", + "rpc-url": "/transmission/", + "rpc-username": "transmission", + "rpc-whitelist": "127.0.0.1,localhost,london-b,download.pez.sh,download.pez.solutions", + "rpc-whitelist-enabled": false, + "scrape-paused-torrents-enabled": true, + "script-torrent-added-enabled": false, + "script-torrent-added-filename": "", + "script-torrent-done-enabled": false, + "script-torrent-done-filename": "", + "script-torrent-done-seeding-enabled": false, + "script-torrent-done-seeding-filename": "", + "seed-queue-enabled": false, + "seed-queue-size": 10, + "speed-limit-down": 50, + "speed-limit-down-enabled": false, + "speed-limit-up": 1000, + "speed-limit-up-enabled": true, + "start-added-torrents": true, + "tcp-enabled": true, + "torrent-added-verify-mode": "fast", + "trash-original-torrent-files": false, + "umask": "022", + "upload-limit": 100, + "upload-limit-enabled": 0, + "upload-slots-per-torrent": 14, + "utp-enabled": true +} diff --git a/ansible/services/vsftpd/config/london-b.conf b/ansible/services/vsftpd/config/london-b.conf new file mode 100644 index 0000000..0d17932 --- /dev/null +++ b/ansible/services/vsftpd/config/london-b.conf @@ -0,0 +1,18 @@ +listen=NO +listen_ipv6=YES +anonymous_enable=YES +local_enable=YES +write_enable=NO +anon_upload_enable=NO +dirmessage_enable=YES +use_localtime=YES +xferlog_enable=YES +connect_from_port_20=YES +ftpd_banner=Welcome to the Pez Dispenser +secure_chroot_dir=/var/run/vsftpd/empty +pam_service_name=vsftpd +rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem +rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key +ssl_enable=NO +anon_root=/hdd/ftp +allow_writeable_chroot=YES diff --git a/ansible/services/whisparr/whisparr.service b/ansible/services/whisparr/whisparr.service new file mode 100644 index 0000000..813da93 --- /dev/null +++ b/ansible/services/whisparr/whisparr.service @@ -0,0 +1,16 @@ +[Unit] +Description=Whisparr Daemon +After=syslog.target network.target + +[Service] +User=root +Group=root +UMask=0002 +Type=simple +ExecStart=/opt/Whisparr/Whisparr -nobrowser -data=/var/lib/whisparr/ +TimeoutStopSec=20 +KillMode=process +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/docs/hosts/london-b.md b/docs/hosts/london-b.md index 8c97a68..d216154 100644 --- a/docs/hosts/london-b.md +++ b/docs/hosts/london-b.md @@ -68,7 +68,49 @@ RAIDZ1 tolerates one drive failure per vdev. With this many drives and this much | smartctl_exporter | 9633 | (Prometheus scrape) | | prom-plex-exporter | — | (Prometheus scrape) | -All services run in Docker. Media is served directly from the ZFS pool. +### Systemd Services (non-Docker) + +The media automation suite and several supporting services run as native systemd units, not in Docker: + +| Service | Unit Name | Notes | +|---------|-----------|-------| +| Sonarr | sonarr | Package-managed (mono) | +| Radarr | radarr | /opt/Radarr, custom unit | +| Prowlarr | prowlarr | /opt/Prowlarr, custom unit | +| Lidarr | lidarr | /opt/Lidarr, custom unit | +| Readarr | readarr | /opt/Readarr, custom unit | +| Whisparr | whisparr | /opt/Whisparr, custom unit (disabled) | +| Plex | plexmediaserver | Package-managed | +| Jellyfin | jellyfin | Package-managed | +| Transmission | transmission-daemon | Package-managed | +| Samba | smbd | Package-managed | +| Ollama | ollama | /usr/local/bin, custom unit | +| Promtail | promtail | Custom unit, ships logs to Loki | +| Cloudflared | cloudflared | Tunnel to Cloudflare | +| vsftpd | vsftpd | FTP server for /hdd/ftp | +| systemd_exporter | systemd_exporter | Ansible-managed | +| node_exporter | node_exporter | Ansible-managed | + +Docker services: Nextcloud AIO, Jellyseerr, Navidrome, slskd, Miniflux, smartctl-exporter, plex-exporter. + +### Cron Jobs + +| Schedule | Job | +|----------|-----| +| Every hour | `/root/scripts/movie-rename-fix.fish` | +| Midnight daily | `systemctl restart radarr` | +| Midnight daily | `systemctl restart sonarr` | +| 22:00 daily | `/root/scripts/backup.sh` (rclone to B2) | + +### Samba Shares + +| Share | Path | Access | +|-------|------|--------| +| HDD | /hdd | pez, root (rw) | +| Movies | /hdd/movies | public (ro) | +| TV Shows | /hdd/tv | public (ro) | + +Media is served directly from the ZFS pool. ## Networking