Compare commits
No commits in common. "master" and "fix/sentinel-backup-stats" have entirely different histories.
master
...
fix/sentin
12 changed files with 12 additions and 248 deletions
|
|
@ -15,7 +15,6 @@ sysctl_params:
|
|||
- { key: net.ipv4.tcp_window_scaling, value: "1" }
|
||||
- { key: net.core.default_qdisc, value: "fq" }
|
||||
- { key: net.ipv4.tcp_congestion_control, value: "bbr" }
|
||||
- { key: net.ipv4.tcp_no_metrics_save, value: "0" }
|
||||
- { key: net.ipv4.tcp_slow_start_after_idle, value: "0" }
|
||||
- { key: net.ipv4.tcp_fastopen, value: "3" }
|
||||
- { key: net.core.netdev_max_backlog, value: "16384" }
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
- name: NFS and mergerfs stability
|
||||
hosts: emby-sascha:emby-chris:arrapps
|
||||
become: yes
|
||||
roles:
|
||||
- nfs_stability
|
||||
|
|
@ -40,7 +40,7 @@ n8n ansible_host=10.4.1.113
|
|||
hermes ansible_host=10.4.1.100
|
||||
monitoring ansible_host=10.1.1.111
|
||||
automation1 ansible_host=10.5.85.5
|
||||
|
||||
outline ansible_host=10.1.1.100
|
||||
satisfactory ansible_host=10.3.1.120
|
||||
|
||||
[communication]
|
||||
|
|
@ -52,7 +52,6 @@ butler ansible_host=10.5.85.2
|
|||
|
||||
[hetzner]
|
||||
pfannkuchen ansible_host=46.225.230.72 ansible_user=root
|
||||
guck-vps ansible_host=141.94.237.199 ansible_user=debian
|
||||
|
||||
[nvidia]
|
||||
tdarr
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ usage() {
|
|||
echo -e " ${C}wstunnel${N} [host] wstunnel + WireGuard deployen"
|
||||
echo -e " ${C}sshfs${N} [host] SSHFS Mounts einrichten"
|
||||
echo -e " ${C}tune${N} <host> Sysctl Netzwerk-Tuning"
|
||||
echo -e " ${C}nfs${N} <host> NFS-/mergerfs-Stabilisierung ausrollen"
|
||||
echo -e " ${C}pvetune${N} [host] Proxmox Host Tuning (sysctl, resolv, hosts)"
|
||||
echo -e " ${C}tc${N} [host] tc per-flow Rate-Limit (50 Mbit/s pro Connection)"
|
||||
echo -e " ${C}watchdog${N} Network Watchdog deployen (Whitelist aus Ansible)"
|
||||
|
|
@ -104,10 +103,6 @@ case "$CMD" in
|
|||
[ -z "$HOST" ] && echo -e "${R}Fehler: Host angeben${N}" && exit 1
|
||||
run sysctl.yaml -l "$HOST"
|
||||
;;
|
||||
nfs)
|
||||
[ -z "$HOST" ] && echo -e "${R}Fehler: Host angeben${N}" && exit 1
|
||||
run nfs-stability.yml -l "$HOST"
|
||||
;;
|
||||
pvetune)
|
||||
if [ -n "$HOST" ]; then
|
||||
run sysctl-proxmox.yaml -l "$HOST"
|
||||
|
|
|
|||
|
|
@ -1,134 +0,0 @@
|
|||
---
|
||||
- name: Ensure this role only targets the three mergerfs media hosts
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- inventory_hostname in ['emby-sascha', 'emby-chris', 'arrapps']
|
||||
fail_msg: "nfs_stability must not run on {{ inventory_hostname }}"
|
||||
|
||||
- name: Stop and disable the destructive legacy retry timer first
|
||||
ansible.builtin.systemd:
|
||||
name: remount-nfs.timer
|
||||
state: stopped
|
||||
enabled: false
|
||||
failed_when: false
|
||||
|
||||
- name: Stop a currently running destructive legacy retry service
|
||||
ansible.builtin.systemd:
|
||||
name: remount-nfs.service
|
||||
state: stopped
|
||||
failed_when: false
|
||||
|
||||
- name: Mark mergerfs as a network-dependent mount without remounting it live
|
||||
ansible.builtin.replace:
|
||||
path: /etc/systemd/system/mnt-media.mount
|
||||
regexp: '^Options=(?![^\n]*_netdev)(.*)$'
|
||||
replace: 'Options=_netdev,\1'
|
||||
backup: true
|
||||
register: mergerfs_unit
|
||||
|
||||
- name: Install non-destructive one-shot NFS recovery helper
|
||||
ansible.builtin.copy:
|
||||
dest: /usr/local/sbin/remount-nfs-safe
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0755'
|
||||
content: |
|
||||
#!/bin/bash
|
||||
set -u
|
||||
is_nfs() {
|
||||
findmnt -rn --target "$1" -t nfs,nfs4 -o TARGET | grep -Fxq "$1"
|
||||
}
|
||||
is_media() {
|
||||
findmnt -rn --target /mnt/media -t fuse.mergerfs -o TARGET | grep -Fxq /mnt/media
|
||||
}
|
||||
for attempt in 1 2 3; do
|
||||
missing=0
|
||||
for entry in 'mnt-nas.mount:/mnt/nas' 'mnt-qnap1.mount:/mnt/qnap1' 'mnt-qnap2.mount:/mnt/qnap2'; do
|
||||
unit="${entry%%:*}"
|
||||
target="${entry#*:}"
|
||||
if ! is_nfs "$target"; then
|
||||
systemctl start "$unit" || true
|
||||
fi
|
||||
is_nfs "$target" || missing=1
|
||||
done
|
||||
if [ "$missing" -eq 0 ]; then
|
||||
if ! is_media; then
|
||||
systemctl start mnt-media.mount || true
|
||||
fi
|
||||
is_media && exit 0
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
echo 'NFS recovery failed without restarting any active mount' >&2
|
||||
exit 1
|
||||
|
||||
- name: Install safe one-shot NFS recovery service
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/remount-nfs.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
backup: true
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Safely start missing NFS mounts once after boot
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/remount-nfs-safe
|
||||
|
||||
- name: Install once-per-boot NFS recovery timer
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/remount-nfs.timer
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
backup: true
|
||||
content: |
|
||||
[Unit]
|
||||
Description=One-shot NFS mount validation after boot
|
||||
|
||||
[Timer]
|
||||
OnBootSec=45
|
||||
AccuracySec=5
|
||||
Persistent=false
|
||||
Unit=remount-nfs.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
|
||||
- name: Reload systemd and enable the one-shot boot timer
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
name: remount-nfs.timer
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Run the safe helper once to recover mounts left missing by the legacy loop
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/local/sbin/remount-nfs-safe
|
||||
changed_when: false
|
||||
|
||||
- name: Verify systemd unit dependency graph
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- systemd-analyze
|
||||
- verify
|
||||
- /etc/systemd/system/mnt-media.mount
|
||||
- /etc/systemd/system/remount-nfs.service
|
||||
- /etc/systemd/system/remount-nfs.timer
|
||||
changed_when: false
|
||||
|
||||
- name: Verify all active storage mounts without restarting them
|
||||
ansible.builtin.shell: |
|
||||
set -e
|
||||
for target in /mnt/nas /mnt/qnap1 /mnt/qnap2; do
|
||||
findmnt -rn --target "$target" -t nfs,nfs4 -o TARGET | grep -Fxq "$target"
|
||||
done
|
||||
findmnt -rn --target /mnt/media -t fuse.mergerfs -o TARGET | grep -Fxq /mnt/media
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: false
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
# Default-Sysctl-Werte fuer Streaming-VMs und den WireGuard-Medienpfad.
|
||||
# Default-Sysctl-Werte fuer Streaming-VMs.
|
||||
# Pro Host/Gruppe ueberschreibbar via group_vars/host_vars (z.B. group_vars/vps).
|
||||
sysctl_params:
|
||||
- { key: net.core.rmem_default, value: "262144" }
|
||||
|
|
@ -9,16 +9,13 @@ sysctl_params:
|
|||
- { key: net.ipv4.tcp_rmem, value: "4096 87380 67108864" }
|
||||
- { key: net.ipv4.tcp_wmem, value: "4096 65536 67108864" }
|
||||
- { key: net.ipv4.tcp_window_scaling, value: "1" }
|
||||
- { key: net.core.default_qdisc, value: "fq" }
|
||||
- { key: net.ipv4.tcp_congestion_control, value: "bbr" }
|
||||
- { key: net.ipv4.tcp_no_metrics_save, value: "0" }
|
||||
- { key: net.ipv4.tcp_slow_start_after_idle, value: "0" }
|
||||
- { key: net.ipv4.tcp_fastopen, value: "3" }
|
||||
- { key: net.core.netdev_max_backlog, value: "16384" }
|
||||
- { key: net.core.somaxconn, value: "4096" }
|
||||
- { key: net.core.optmem_max, value: "2097152" }
|
||||
- { key: net.ipv4.tcp_notsent_lowat, value: "16384" }
|
||||
- { key: net.ipv4.tcp_fin_timeout, value: "30" }
|
||||
- { key: net.ipv4.tcp_fin_timeout, value: "15" }
|
||||
- { key: net.ipv4.tcp_tw_reuse, value: "1" }
|
||||
- { key: vm.swappiness, value: "1" }
|
||||
- { key: vm.dirty_ratio, value: "15" }
|
||||
|
|
|
|||
|
|
@ -10,27 +10,7 @@
|
|||
dest: /etc/modules-load.d/bbr.conf
|
||||
mode: "0644"
|
||||
|
||||
- name: Legacy-Sysctl-Dateien pruefen
|
||||
ansible.builtin.stat:
|
||||
path: "{{ item }}"
|
||||
loop:
|
||||
- /etc/sysctl.conf
|
||||
- /etc/sysctl.d/99-streaming.conf
|
||||
- /etc/sysctl.d/99-proxmox-tuning.conf
|
||||
- /etc/sysctl.d/99-zzz-cloudflare-warp-connector.conf
|
||||
register: legacy_sysctl_files
|
||||
|
||||
- name: Verwaltete Werte aus konkurrierenden Sysctl-Quellen entfernen
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ item.0.item }}"
|
||||
regexp: "^[ \\t]*{{ item.1.key | regex_escape }}[ \\t]*="
|
||||
state: absent
|
||||
loop: "{{ legacy_sysctl_files.results | product(sysctl_params) | list }}"
|
||||
loop_control:
|
||||
label: "{{ item.0.item }}: {{ item.1.key }}"
|
||||
when: item.0.stat.exists
|
||||
|
||||
- name: Sysctl Parameter kanonisch setzen
|
||||
- name: Sysctl Parameter setzen
|
||||
ansible.posix.sysctl:
|
||||
name: "{{ item.key }}"
|
||||
value: "{{ item.value }}"
|
||||
|
|
|
|||
|
|
@ -38,10 +38,18 @@
|
|||
state: present
|
||||
loop:
|
||||
- { key: vm.overcommit_memory, value: "1" }
|
||||
- { key: vm.swappiness, value: "1" }
|
||||
- { key: fs.file-max, value: "9999999" }
|
||||
- { key: fs.inotify.max_user_watches, value: "524288" }
|
||||
- { key: fs.inotify.max_user_instances, value: "512" }
|
||||
- { key: net.ipv4.ip_forward, value: "1" }
|
||||
- { key: net.ipv6.conf.all.forwarding, value: "1" }
|
||||
- { key: net.bridge.bridge-nf-call-iptables, value: "0" }
|
||||
- { key: net.bridge.bridge-nf-call-ip6tables, value: "0" }
|
||||
- { key: vm.dirty_expire_centisecs, value: "3000" }
|
||||
- { key: vm.dirty_writeback_centisecs, value: "500" }
|
||||
- { key: net.ipv4.tcp_mtu_probing, value: "1" }
|
||||
- { key: net.core.rmem_max, value: "67108864" }
|
||||
- { key: net.core.wmem_max, value: "67108864" }
|
||||
- { key: net.ipv4.tcp_rmem, value: "4096 87380 67108864" }
|
||||
- { key: net.ipv4.tcp_wmem, value: "4096 65536 67108864" }
|
||||
|
|
|
|||
7
site.yml
7
site.yml
|
|
@ -1,11 +1,4 @@
|
|||
---
|
||||
# Stabilize NFS/mergerfs only on hosts that use the shared media pool.
|
||||
- name: NFS and mergerfs stability
|
||||
hosts: emby-sascha:emby-chris:arrapps
|
||||
become: yes
|
||||
roles:
|
||||
- nfs_stability
|
||||
|
||||
# Neue VM komplett einrichten
|
||||
- name: VM Setup
|
||||
hosts: all
|
||||
|
|
|
|||
|
|
@ -3,5 +3,4 @@
|
|||
hosts: proxmox
|
||||
become: yes
|
||||
roles:
|
||||
- sysctl
|
||||
- sysctl_proxmox
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
from pathlib import Path
|
||||
|
||||
ROLE = Path("roles/nfs_stability/tasks/main.yml").read_text()
|
||||
SITE = Path("site.yml").read_text()
|
||||
|
||||
|
||||
def test_recovery_is_non_destructive():
|
||||
assert "systemctl restart" not in ROLE
|
||||
assert "OnUnitActiveSec" not in ROLE
|
||||
assert "systemctl start" in ROLE
|
||||
|
||||
|
||||
def test_recovery_validates_real_nfs_and_mergerfs_mounts():
|
||||
assert "-t nfs,nfs4" in ROLE
|
||||
assert "-t fuse.mergerfs" in ROLE
|
||||
assert all(path in ROLE for path in ("/mnt/nas", "/mnt/qnap1", "/mnt/qnap2", "/mnt/media"))
|
||||
|
||||
|
||||
def test_legacy_loop_is_stopped_before_replacement():
|
||||
stop_at = ROLE.index("Stop and disable the destructive legacy retry timer first")
|
||||
install_at = ROLE.index("Install once-per-boot NFS recovery timer")
|
||||
assert stop_at < install_at
|
||||
|
||||
|
||||
def test_role_is_scoped_to_media_mount_hosts():
|
||||
assert "hosts: emby-sascha:emby-chris:arrapps" in SITE
|
||||
assert "- nfs_stability" in SITE
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
from pathlib import Path
|
||||
import re
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).parents[1]
|
||||
|
||||
|
||||
class SysctlRoleContract(unittest.TestCase):
|
||||
def test_canonical_streaming_profile_keeps_64mib_and_bbr_fq(self):
|
||||
text = (ROOT / "roles/sysctl/defaults/main.yml").read_text()
|
||||
self.assertIn("net.core.rmem_max", text)
|
||||
self.assertIn('value: "67108864"', text)
|
||||
self.assertIn("net.core.default_qdisc", text)
|
||||
self.assertIn("net.ipv4.tcp_congestion_control", text)
|
||||
self.assertIn("net.ipv4.tcp_no_metrics_save", text)
|
||||
|
||||
def test_role_removes_managed_keys_from_legacy_sources(self):
|
||||
text = (ROOT / "roles/sysctl/tasks/main.yml").read_text()
|
||||
self.assertIn("/etc/sysctl.conf", text)
|
||||
self.assertIn("/etc/sysctl.d/99-streaming.conf", text)
|
||||
self.assertIn("/etc/sysctl.d/99-proxmox-tuning.conf", text)
|
||||
self.assertIn("/etc/sysctl.d/99-zzz-cloudflare-warp-connector.conf", text)
|
||||
self.assertIn("state: absent", text)
|
||||
self.assertIn("regex_escape", text)
|
||||
|
||||
def test_proxmox_tuning_runs_canonical_network_role_first(self):
|
||||
play = (ROOT / "sysctl-proxmox.yaml").read_text()
|
||||
self.assertLess(play.index("- sysctl\n"), play.index("- sysctl_proxmox"))
|
||||
|
||||
def test_proxmox_role_does_not_duplicate_canonical_network_keys(self):
|
||||
canonical = (ROOT / "roles/sysctl/defaults/main.yml").read_text()
|
||||
proxmox = (ROOT / "roles/sysctl_proxmox/tasks/main.yml").read_text()
|
||||
canonical_keys = set(re.findall(r"key:\s*([a-z0-9_.-]+)", canonical))
|
||||
proxmox_keys = set(re.findall(r"key:\s*([a-z0-9_.-]+)", proxmox))
|
||||
self.assertEqual(canonical_keys & proxmox_keys, set())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue