From 522f0b2b84b1ca8c0823914b3cef6a5810c8d94b Mon Sep 17 00:00:00 2001 From: Rasmus Wejlgaard Date: Sun, 29 Mar 2026 15:39:05 +0000 Subject: [PATCH] Capture london-b media stack and systemd services 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