A wipe was quietly turning a Sophie world into an Apocalypse one. 139 sandbox
values had reverted -- loot rates from 0.35 back to 0.9, ranged weapons and ammo
to 2.0, CharacterFreePoints from 0 to 60 -- and ten of Sophie's mod-added
options had vanished entirely.
The reset script was not deleting the settings. PZ reads
Server/<name>_SandboxVars.lua only when it creates a world, and then writes the
running world's settings back over that same file. The file is an output, not an
input. So once any world came up with defaults, the server stamped those defaults
into the file, and every wipe afterwards regenerated from them -- inheriting the
corruption rather than causing it, and with no way back out on its own. The same
shape as the admin-password deadlock.
The intended settings now live in config-template/, which the server has no
reason to touch, and a wipe restores Server/<name>_{SandboxVars,spawnregions,
spawnpoints}.lua from there before restarting. That is also the file to hand-edit
when changing the world: edit the template, wipe, and the new world has the edits.
Verified by wiping: 139 differences before, 0 after. The server still rewrites
the live file on boot -- 996 keys become 1061 as it adds newer options -- but
every Sophie value survives that now.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
724 lines
26 KiB
YAML
724 lines
26 KiB
YAML
---
|
|
- name: load vendored sophie modlist
|
|
ansible.builtin.include_vars:
|
|
file: zomboid_sophie_mods.yml
|
|
tags: zomboid-conf
|
|
|
|
- name: create zomboid host directory volumes
|
|
become: true
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
state: directory
|
|
owner: "{{ podman_subuid.stdout }}"
|
|
group: "{{ podman_user }}"
|
|
mode: 0755
|
|
notify: restorecon podman
|
|
loop:
|
|
- "{{ zomboid_path }}/server"
|
|
- "{{ zomboid_path }}/data"
|
|
- "{{ zomboid_path }}/scripts"
|
|
|
|
# Not a container volume -- nothing mounts it. It holds output from host-side
|
|
# helpers that run as {{ podman_user }}, so it must be owned by that user rather
|
|
# than the container's subuid. Getting this wrong silently broke world resets:
|
|
# the script's first log line failed with EACCES and set -e killed it before it
|
|
# stopped the server, so `@bot` resets did nothing at all.
|
|
- name: create zomboid canonical config directory
|
|
become: true
|
|
ansible.builtin.file:
|
|
path: "{{ zomboid_path }}/config-template"
|
|
state: directory
|
|
owner: "{{ podman_user }}"
|
|
group: "{{ podman_user }}"
|
|
mode: 0755
|
|
|
|
- name: create zomboid host-side log directory
|
|
become: true
|
|
ansible.builtin.file:
|
|
path: "{{ zomboid_path }}/logs"
|
|
state: directory
|
|
owner: "{{ podman_user }}"
|
|
group: "{{ podman_user }}"
|
|
mode: 0755
|
|
|
|
- name: create podman bin directory
|
|
become: true
|
|
ansible.builtin.file:
|
|
path: "{{ podman_home }}/bin"
|
|
state: directory
|
|
owner: "{{ podman_user }}"
|
|
group: "{{ podman_user }}"
|
|
mode: '0755'
|
|
|
|
- name: deploy zomboid world reset script
|
|
become: true
|
|
ansible.builtin.template:
|
|
src: zomboid/world-reset.sh.j2
|
|
dest: "{{ podman_home }}/bin/zomboid-world-reset.sh"
|
|
owner: "{{ podman_user }}"
|
|
group: "{{ podman_user }}"
|
|
mode: '0755'
|
|
|
|
- name: deploy zomboid world reset path unit
|
|
become: true
|
|
ansible.builtin.template:
|
|
src: zomboid/zomboid-world-reset.path.j2
|
|
dest: "{{ podman_home }}/.config/systemd/user/zomboid-world-reset.path"
|
|
owner: "{{ podman_user }}"
|
|
group: "{{ podman_user }}"
|
|
mode: '0644'
|
|
notify: reload zomboid systemd
|
|
|
|
- name: deploy zomboid world reset service unit
|
|
become: true
|
|
ansible.builtin.template:
|
|
src: zomboid/zomboid-world-reset.service.j2
|
|
dest: "{{ podman_home }}/.config/systemd/user/zomboid-world-reset.service"
|
|
owner: "{{ podman_user }}"
|
|
group: "{{ podman_user }}"
|
|
mode: '0644'
|
|
notify: reload zomboid systemd
|
|
|
|
- name: deploy zomboid stats script
|
|
become: true
|
|
ansible.builtin.template:
|
|
src: zomboid/zomboid-stats.sh.j2
|
|
dest: "{{ podman_home }}/bin/zomboid-stats.sh"
|
|
owner: "{{ podman_user }}"
|
|
group: "{{ podman_user }}"
|
|
mode: '0755'
|
|
|
|
- name: create zomboid stats file with correct permissions
|
|
become: true
|
|
ansible.builtin.file:
|
|
path: "{{ podman_volumes }}/zomboid-stats.json"
|
|
state: touch
|
|
owner: "{{ podman_user }}"
|
|
group: "{{ podman_user }}"
|
|
mode: '0644'
|
|
modification_time: preserve
|
|
access_time: preserve
|
|
|
|
- name: deploy zomboid stats service unit
|
|
become: true
|
|
ansible.builtin.template:
|
|
src: zomboid/zomboid-stats.service.j2
|
|
dest: "{{ podman_home }}/.config/systemd/user/zomboid-stats.service"
|
|
owner: "{{ podman_user }}"
|
|
group: "{{ podman_user }}"
|
|
mode: '0644'
|
|
notify: reload zomboid systemd
|
|
|
|
- name: deploy zomboid stats timer unit
|
|
become: true
|
|
ansible.builtin.template:
|
|
src: zomboid/zomboid-stats.timer.j2
|
|
dest: "{{ podman_home }}/.config/systemd/user/zomboid-stats.timer"
|
|
owner: "{{ podman_user }}"
|
|
group: "{{ podman_user }}"
|
|
mode: '0644'
|
|
notify: reload zomboid systemd
|
|
|
|
- name: enable zomboid stats timer
|
|
become: true
|
|
become_user: "{{ podman_user }}"
|
|
ansible.builtin.systemd:
|
|
name: zomboid-stats.timer
|
|
scope: user
|
|
enabled: true
|
|
state: started
|
|
daemon_reload: true
|
|
|
|
- name: copy zomboid entrypoint script
|
|
become: true
|
|
ansible.builtin.template:
|
|
src: zomboid/entrypoint.sh.j2
|
|
dest: "{{ zomboid_path }}/scripts/entrypoint.sh"
|
|
owner: "{{ podman_subuid.stdout }}"
|
|
group: "{{ podman_user }}"
|
|
mode: 0755
|
|
notify: restorecon podman
|
|
|
|
- name: copy zomboid steamcmd install script
|
|
become: true
|
|
ansible.builtin.template:
|
|
src: zomboid/install.scmd.j2
|
|
dest: "{{ zomboid_path }}/scripts/install.scmd"
|
|
owner: "{{ podman_subuid.stdout }}"
|
|
group: "{{ podman_user }}"
|
|
mode: 0644
|
|
notify: restorecon podman
|
|
|
|
# Server config is seeded from the vendored Sophie 42 preset *before* first
|
|
# boot, so the server never generates a vanilla INI we then have to patch.
|
|
#
|
|
# force is off by design: these files are a starting point, not a managed
|
|
# state. Stop the server, hand-edit them, regenerate the world -- Ansible will
|
|
# not clobber the edits on the next deploy. Re-push deliberately with
|
|
# -e zomboid_config_force=true.
|
|
- name: create zomboid server config directory
|
|
become: true
|
|
ansible.builtin.file:
|
|
path: "{{ zomboid_path }}/data/Server"
|
|
state: directory
|
|
owner: "{{ podman_subuid.stdout }}"
|
|
group: "{{ podman_user }}"
|
|
mode: 0755
|
|
notify: restorecon podman
|
|
tags: zomboid-conf
|
|
|
|
- name: seed zomboid server ini from sophie preset
|
|
become: true
|
|
ansible.builtin.template:
|
|
src: zomboid/server.ini.j2
|
|
dest: "{{ zomboid_path }}/data/Server/{{ zomboid_server_name }}.ini"
|
|
force: "{{ zomboid_config_force | bool }}"
|
|
owner: "{{ podman_subuid.stdout }}"
|
|
group: "{{ podman_user }}"
|
|
mode: 0644
|
|
notify: restorecon podman
|
|
tags: zomboid-conf
|
|
|
|
# Copied, not templated: these are Lua and must not go through Jinja.
|
|
- name: seed zomboid sandbox and spawn config from sophie preset
|
|
become: true
|
|
ansible.builtin.copy:
|
|
src: "zomboid/sophie/{{ item }}.lua"
|
|
dest: "{{ zomboid_path }}/data/Server/{{ zomboid_server_name }}_{{ item }}.lua"
|
|
force: "{{ zomboid_config_force | bool }}"
|
|
owner: "{{ podman_subuid.stdout }}"
|
|
group: "{{ podman_user }}"
|
|
mode: 0644
|
|
loop:
|
|
- SandboxVars
|
|
- spawnregions
|
|
- spawnpoints
|
|
notify: restorecon podman
|
|
tags: zomboid-conf
|
|
|
|
- name: deploy zomboid log prune script
|
|
become: true
|
|
ansible.builtin.template:
|
|
src: zomboid/zomboid-log-prune.sh.j2
|
|
dest: "{{ podman_home }}/bin/zomboid-log-prune.sh"
|
|
owner: "{{ podman_user }}"
|
|
group: "{{ podman_user }}"
|
|
mode: '0755'
|
|
|
|
- name: deploy zomboid log prune units
|
|
become: true
|
|
ansible.builtin.template:
|
|
src: "zomboid/zomboid-log-prune.{{ item }}.j2"
|
|
dest: "{{ podman_home }}/.config/systemd/user/zomboid-log-prune.{{ item }}"
|
|
owner: "{{ podman_user }}"
|
|
group: "{{ podman_user }}"
|
|
mode: '0644'
|
|
loop:
|
|
- service
|
|
- timer
|
|
notify: reload zomboid systemd
|
|
|
|
- name: enable zomboid log prune timer
|
|
become: true
|
|
become_user: "{{ podman_user }}"
|
|
ansible.builtin.systemd:
|
|
name: zomboid-log-prune.timer
|
|
scope: user
|
|
enabled: true
|
|
state: started
|
|
daemon_reload: true
|
|
|
|
# The intended world settings, kept somewhere the server cannot reach.
|
|
#
|
|
# PZ reads Server/<name>_SandboxVars.lua only when it creates a world, and then
|
|
# writes the *running world's* settings back over that same file. So the file in
|
|
# Server/ is an output, not an input: once any world is created with defaults,
|
|
# the server stamps those defaults into it and every later wipe regenerates from
|
|
# them. That is how a Sophie world quietly became an Apocalypse one -- 139 values
|
|
# reverted, loot rates from 0.35 back to 0.9, CharacterFreePoints from 0 to 60.
|
|
#
|
|
# This copy is what a wipe restores from, so settings survive it. Seeded once and
|
|
# then left alone, so it is the file to hand-edit when changing the world:
|
|
# edit here, then wipe, and the new world comes up with the edits.
|
|
- name: seed canonical zomboid world settings
|
|
become: true
|
|
ansible.builtin.copy:
|
|
src: "zomboid/sophie/{{ item }}.lua"
|
|
dest: "{{ zomboid_path }}/config-template/{{ item }}.lua"
|
|
force: "{{ zomboid_config_force | bool }}"
|
|
owner: "{{ podman_user }}"
|
|
group: "{{ podman_user }}"
|
|
mode: 0644
|
|
loop:
|
|
- SandboxVars
|
|
- spawnregions
|
|
- spawnpoints
|
|
tags: zomboid-conf
|
|
|
|
# The server's own logs, not the container's. PZ holds these fds open for the
|
|
# life of the process, so copytruncate is mandatory -- a rename would leave it
|
|
# writing into an unlinked inode and the file would appear to stop growing.
|
|
- name: deploy zomboid logrotate config
|
|
become: true
|
|
ansible.builtin.template:
|
|
src: zomboid/logrotate.j2
|
|
dest: /etc/logrotate.d/zomboid
|
|
owner: root
|
|
group: root
|
|
mode: 0644
|
|
|
|
# Set volume permissions for steam user (UID 1000) inside container
|
|
# This uses podman unshare to set ownership correctly for rootless podman
|
|
- name: set zomboid volume permissions for steam user
|
|
become: true
|
|
become_user: "{{ podman_user }}"
|
|
ansible.builtin.shell: |
|
|
podman unshare chown -R 1000:1000 {{ zomboid_path }}/server
|
|
podman unshare chown -R 1000:1000 {{ zomboid_path }}/data
|
|
changed_when: false
|
|
|
|
- name: flush handlers
|
|
ansible.builtin.meta: flush_handlers
|
|
|
|
- import_tasks: podman/podman-check.yml
|
|
vars:
|
|
container_name: zomboid
|
|
container_image: "{{ image }}"
|
|
|
|
- name: create zomboid container
|
|
become: true
|
|
become_user: "{{ podman_user }}"
|
|
containers.podman.podman_container:
|
|
name: zomboid
|
|
image: "{{ image }}"
|
|
restart_policy: on-failure:3
|
|
log_driver: k8s-file
|
|
log_opt:
|
|
max_size: 50m
|
|
env:
|
|
SERVER_NAME: "{{ zomboid_server_name }}"
|
|
MIN_RAM: "{{ zomboid_min_ram }}"
|
|
MAX_RAM: "{{ zomboid_max_ram }}"
|
|
AUTO_UPDATE: "true"
|
|
ADMIN_PASSWORD: "{{ zomboid_admin_password }}"
|
|
SERVER_PASSWORD: "{{ zomboid_password }}"
|
|
PUID: "1000"
|
|
PGID: "1000"
|
|
volumes:
|
|
- "{{ zomboid_path }}/server:/project-zomboid"
|
|
- "{{ zomboid_path }}/data:/project-zomboid-config"
|
|
- "{{ zomboid_path }}/scripts/entrypoint.sh:/entrypoint.sh:ro"
|
|
- "{{ zomboid_path }}/scripts/install.scmd:/home/steam/install.scmd:ro"
|
|
ports:
|
|
- "16261:16261/udp"
|
|
- "16262:16262/udp"
|
|
- "{{ zomboid_rcon_port }}:{{ zomboid_rcon_port }}/tcp"
|
|
command: /bin/bash /entrypoint.sh
|
|
|
|
- name: create systemd startup job for zomboid
|
|
include_tasks: podman/systemd-generate.yml
|
|
vars:
|
|
container_name: zomboid
|
|
|
|
# Make systemd actually supervise the container, so it restarts on any exit --
|
|
# including the clean one that `@bot restart` produces via RCON quit.
|
|
#
|
|
# `podman generate systemd` emits Type=forking with ExecStart=podman start and
|
|
# a PIDFile pointing at conmon. podman start returns immediately, so the process
|
|
# systemd is told to watch was never its child; it says so itself in the journal
|
|
# ("Supervising process N which is not our child. We'll most likely not notice
|
|
# when it exits") and it does not notice. The unit sat at active/running with
|
|
# NRestarts=0 while the container was exited(0) and the server was down. The
|
|
# PIDFile is worse than useless here: it embeds the container ID, so it goes
|
|
# stale every time a deploy recreates the container.
|
|
#
|
|
# Type=simple with `podman start -a` keeps podman in the foreground as systemd's
|
|
# own child, so the exit is seen and Restart=always fires.
|
|
#
|
|
# stdout/stderr are discarded on purpose. Attaching re-emits the container's
|
|
# output on podman's stdout, which systemd would capture straight back into the
|
|
# journal -- exactly the flood the k8s-file log driver exists to avoid. Nothing
|
|
# is lost: `podman logs` still serves it from the container's own log.
|
|
- name: configure zomboid systemd supervision
|
|
become: true
|
|
become_user: "{{ podman_user }}"
|
|
ansible.builtin.lineinfile:
|
|
path: "{{ podman_home }}/.config/systemd/user/zomboid.service"
|
|
regexp: "{{ item.regexp }}"
|
|
line: "{{ item.line }}"
|
|
state: "{{ item.state | default('present') }}"
|
|
insertafter: '^\[Service\]'
|
|
loop:
|
|
- { regexp: '^Restart=', line: 'Restart=always' }
|
|
- { regexp: '^Type=', line: 'Type=simple' }
|
|
- { regexp: '^ExecStart=', line: 'ExecStart=/usr/bin/podman start -a zomboid' }
|
|
- { regexp: '^StandardOutput=', line: 'StandardOutput=null' }
|
|
- { regexp: '^StandardError=', line: 'StandardError=null' }
|
|
- { regexp: '^PIDFile=', line: '', state: absent }
|
|
notify: reload zomboid systemd
|
|
|
|
# Firewall logging for player IP correlation
|
|
# Logs new UDP connections to Zomboid port for IP address tracking
|
|
- name: add firewall rule to log zomboid connections
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0
|
|
-p udp --dport 16261 -m conntrack --ctstate NEW
|
|
-j LOG --log-prefix "ZOMBOID_CONN: " --log-level 4
|
|
register: firewall_result
|
|
changed_when: "'already' not in firewall_result.stderr"
|
|
failed_when: false
|
|
notify: restart firewalld
|
|
tags: firewall
|
|
|
|
- name: add firewall rule to log zomboid connections (runtime)
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --direct --add-rule ipv4 filter INPUT 0
|
|
-p udp --dport 16261 -m conntrack --ctstate NEW
|
|
-j LOG --log-prefix "ZOMBOID_CONN: " --log-level 4
|
|
changed_when: false
|
|
failed_when: false
|
|
tags: firewall
|
|
|
|
# =============================================================================
|
|
# Add logging for port 16262 (mirrors existing 16261 logging)
|
|
# =============================================================================
|
|
- name: add firewall rule to log zomboid connections on 16262
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0
|
|
-p udp --dport 16262 -m conntrack --ctstate NEW
|
|
-j LOG --log-prefix "ZOMBOID_CONN: " --log-level 4
|
|
register: firewall_result_16262
|
|
changed_when: "'already' not in firewall_result_16262.stderr"
|
|
failed_when: false
|
|
notify: restart firewalld
|
|
tags: firewall
|
|
|
|
- name: add firewall rule to log zomboid connections on 16262 (runtime)
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --direct --add-rule ipv4 filter INPUT 0
|
|
-p udp --dport 16262 -m conntrack --ctstate NEW
|
|
-j LOG --log-prefix "ZOMBOID_CONN: " --log-level 4
|
|
changed_when: false
|
|
failed_when: false
|
|
tags: firewall
|
|
|
|
# =============================================================================
|
|
# Zomboid Rate Limiting and Query Flood Protection
|
|
# =============================================================================
|
|
# These rules mitigate Steam server query floods while allowing legitimate play.
|
|
# Query packets are typically 53 bytes; game traffic is larger and sustained.
|
|
#
|
|
# Rule priority: 0=logging (existing), 1=allow established, 2=rate limit queries
|
|
|
|
# Allow established/related connections without rate limiting
|
|
# This ensures active players aren't affected by query rate limits
|
|
- name: allow established zomboid connections on 16261
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1
|
|
-p udp --dport 16261 -m conntrack --ctstate ESTABLISHED,RELATED
|
|
-j ACCEPT
|
|
register: established_result
|
|
changed_when: "'already' not in established_result.stderr"
|
|
failed_when: false
|
|
notify: restart firewalld
|
|
tags: firewall
|
|
|
|
- name: allow established zomboid connections on 16262
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1
|
|
-p udp --dport 16262 -m conntrack --ctstate ESTABLISHED,RELATED
|
|
-j ACCEPT
|
|
register: established_result_16262
|
|
changed_when: "'already' not in established_result_16262.stderr"
|
|
failed_when: false
|
|
notify: restart firewalld
|
|
tags: firewall
|
|
|
|
# =============================================================================
|
|
# Smart Zomboid Traffic Filtering (Packet-Size Based)
|
|
# =============================================================================
|
|
# Distinguishes legitimate players from scanner bots:
|
|
# - Players send varied packet sizes (53, 37, 1472 bytes)
|
|
# - Scanners only send 53-byte query packets
|
|
#
|
|
# Rule priority:
|
|
# 0 = LOG all (existing above)
|
|
# 1 = ACCEPT established (existing above)
|
|
# 2 = Mark + ACCEPT non-query packets (verifies player)
|
|
# 3 = ACCEPT queries from verified IPs
|
|
# 4 = LOG rate-limited queries from unverified IPs
|
|
# 5 = DROP rate-limited queries from unverified IPs
|
|
|
|
# Priority 2: Mark IPs sending non-query packets as verified (1 hour TTL)
|
|
# Any packet NOT 53 bytes proves actual connection attempt
|
|
- name: mark verified players on 16261 (non-query packets)
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 2
|
|
-p udp --dport 16261 -m conntrack --ctstate NEW
|
|
-m length ! --length 53
|
|
-m recent --name zomboid_verified --set
|
|
-j ACCEPT
|
|
register: verify_result
|
|
changed_when: "'already' not in verify_result.stderr"
|
|
failed_when: false
|
|
notify: restart firewalld
|
|
tags: firewall
|
|
|
|
- name: mark verified players on 16262 (non-query packets)
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 2
|
|
-p udp --dport 16262 -m conntrack --ctstate NEW
|
|
-m length ! --length 53
|
|
-m recent --name zomboid_verified --set
|
|
-j ACCEPT
|
|
register: verify_result_16262
|
|
changed_when: "'already' not in verify_result_16262.stderr"
|
|
failed_when: false
|
|
notify: restart firewalld
|
|
tags: firewall
|
|
|
|
# Priority 3: Allow queries from verified players (within 1 hour)
|
|
- name: allow queries from verified players on 16261
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 3
|
|
-p udp --dport 16261 -m conntrack --ctstate NEW
|
|
-m length --length 53
|
|
-m recent --name zomboid_verified --rcheck --seconds 3600
|
|
-j ACCEPT
|
|
register: verified_query_result
|
|
changed_when: "'already' not in verified_query_result.stderr"
|
|
failed_when: false
|
|
notify: restart firewalld
|
|
tags: firewall
|
|
|
|
- name: allow queries from verified players on 16262
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 3
|
|
-p udp --dport 16262 -m conntrack --ctstate NEW
|
|
-m length --length 53
|
|
-m recent --name zomboid_verified --rcheck --seconds 3600
|
|
-j ACCEPT
|
|
register: verified_query_result_16262
|
|
changed_when: "'already' not in verified_query_result_16262.stderr"
|
|
failed_when: false
|
|
notify: restart firewalld
|
|
tags: firewall
|
|
|
|
# Priority 4/5: query flood limiting.
|
|
#
|
|
# The original thresholds were 1/hour burst 2 per source IP, on the theory that
|
|
# only scanners send 53-byte query packets and that real players would first be
|
|
# marked verified by the priority-2 rule when they sent something else. That
|
|
# premise is inverted: a client's *first* contact is a 53-byte query, so nobody
|
|
# can be verified before they query, and nobody can query more than twice an
|
|
# hour without being dropped. The counters were unambiguous -- 5 packets ever
|
|
# matched the verified-accept rule against 30,563 drops -- and fail2ban then
|
|
# banned the dropped players for a week each, several an hour, all residential
|
|
# IPs. The server was unreachable for everyone.
|
|
#
|
|
# Remove the old rules so hosts carrying them converge, then re-add the same
|
|
# shape at a threshold no real client reaches. Opening the server browser sends
|
|
# a handful of queries; a flood sends thousands.
|
|
|
|
- name: remove broken log query rate-limit rule on 16261 (permanent)
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 4
|
|
-p udp --dport 16261 -m conntrack --ctstate NEW
|
|
-m length --length 53
|
|
-m hashlimit --hashlimit-above 1/hour --hashlimit-burst 2
|
|
--hashlimit-mode srcip --hashlimit-name zomboid_query_16261
|
|
--hashlimit-htable-expire 3600000
|
|
-j LOG --log-prefix "ZOMBOID_RATELIMIT: " --log-level 4
|
|
changed_when: false
|
|
failed_when: false
|
|
tags: firewall
|
|
|
|
- name: remove broken log query rate-limit rule on 16261 (runtime)
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --direct --remove-rule ipv4 filter INPUT 4
|
|
-p udp --dport 16261 -m conntrack --ctstate NEW
|
|
-m length --length 53
|
|
-m hashlimit --hashlimit-above 1/hour --hashlimit-burst 2
|
|
--hashlimit-mode srcip --hashlimit-name zomboid_query_16261
|
|
--hashlimit-htable-expire 3600000
|
|
-j LOG --log-prefix "ZOMBOID_RATELIMIT: " --log-level 4
|
|
changed_when: false
|
|
failed_when: false
|
|
tags: firewall
|
|
|
|
- name: remove broken drop query rate-limit rule on 16261 (permanent)
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 5
|
|
-p udp --dport 16261 -m conntrack --ctstate NEW
|
|
-m length --length 53
|
|
-m hashlimit --hashlimit-above 1/hour --hashlimit-burst 2
|
|
--hashlimit-mode srcip --hashlimit-name zomboid_query_16261
|
|
--hashlimit-htable-expire 3600000
|
|
-j DROP
|
|
changed_when: false
|
|
failed_when: false
|
|
tags: firewall
|
|
|
|
- name: remove broken drop query rate-limit rule on 16261 (runtime)
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --direct --remove-rule ipv4 filter INPUT 5
|
|
-p udp --dport 16261 -m conntrack --ctstate NEW
|
|
-m length --length 53
|
|
-m hashlimit --hashlimit-above 1/hour --hashlimit-burst 2
|
|
--hashlimit-mode srcip --hashlimit-name zomboid_query_16261
|
|
--hashlimit-htable-expire 3600000
|
|
-j DROP
|
|
changed_when: false
|
|
failed_when: false
|
|
tags: firewall
|
|
|
|
- name: remove broken log query rate-limit rule on 16262 (permanent)
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 4
|
|
-p udp --dport 16262 -m conntrack --ctstate NEW
|
|
-m length --length 53
|
|
-m hashlimit --hashlimit-above 1/hour --hashlimit-burst 2
|
|
--hashlimit-mode srcip --hashlimit-name zomboid_query_16262
|
|
--hashlimit-htable-expire 3600000
|
|
-j LOG --log-prefix "ZOMBOID_RATELIMIT: " --log-level 4
|
|
changed_when: false
|
|
failed_when: false
|
|
tags: firewall
|
|
|
|
- name: remove broken log query rate-limit rule on 16262 (runtime)
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --direct --remove-rule ipv4 filter INPUT 4
|
|
-p udp --dport 16262 -m conntrack --ctstate NEW
|
|
-m length --length 53
|
|
-m hashlimit --hashlimit-above 1/hour --hashlimit-burst 2
|
|
--hashlimit-mode srcip --hashlimit-name zomboid_query_16262
|
|
--hashlimit-htable-expire 3600000
|
|
-j LOG --log-prefix "ZOMBOID_RATELIMIT: " --log-level 4
|
|
changed_when: false
|
|
failed_when: false
|
|
tags: firewall
|
|
|
|
- name: remove broken drop query rate-limit rule on 16262 (permanent)
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 5
|
|
-p udp --dport 16262 -m conntrack --ctstate NEW
|
|
-m length --length 53
|
|
-m hashlimit --hashlimit-above 1/hour --hashlimit-burst 2
|
|
--hashlimit-mode srcip --hashlimit-name zomboid_query_16262
|
|
--hashlimit-htable-expire 3600000
|
|
-j DROP
|
|
changed_when: false
|
|
failed_when: false
|
|
tags: firewall
|
|
|
|
- name: remove broken drop query rate-limit rule on 16262 (runtime)
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --direct --remove-rule ipv4 filter INPUT 5
|
|
-p udp --dport 16262 -m conntrack --ctstate NEW
|
|
-m length --length 53
|
|
-m hashlimit --hashlimit-above 1/hour --hashlimit-burst 2
|
|
--hashlimit-mode srcip --hashlimit-name zomboid_query_16262
|
|
--hashlimit-htable-expire 3600000
|
|
-j DROP
|
|
changed_when: false
|
|
failed_when: false
|
|
tags: firewall
|
|
|
|
- name: log query floods on 16261
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 4
|
|
-p udp --dport 16261 -m conntrack --ctstate NEW
|
|
-m length --length 53
|
|
-m hashlimit --hashlimit-above {{ zomboid_query_rate_limit }}
|
|
--hashlimit-burst {{ zomboid_query_burst }}
|
|
--hashlimit-mode srcip --hashlimit-name zomboid_query_16261
|
|
--hashlimit-htable-expire 3600000
|
|
-j LOG --log-prefix "ZOMBOID_RATELIMIT: " --log-level 4
|
|
register: qflood_log_16261
|
|
changed_when: "'already' not in qflood_log_16261.stderr"
|
|
failed_when: false
|
|
notify: restart firewalld
|
|
tags: firewall
|
|
|
|
- name: drop query floods on 16261
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 5
|
|
-p udp --dport 16261 -m conntrack --ctstate NEW
|
|
-m length --length 53
|
|
-m hashlimit --hashlimit-above {{ zomboid_query_rate_limit }}
|
|
--hashlimit-burst {{ zomboid_query_burst }}
|
|
--hashlimit-mode srcip --hashlimit-name zomboid_query_16261
|
|
--hashlimit-htable-expire 3600000
|
|
-j DROP
|
|
register: qflood_drop_16261
|
|
changed_when: "'already' not in qflood_drop_16261.stderr"
|
|
failed_when: false
|
|
notify: restart firewalld
|
|
tags: firewall
|
|
|
|
- name: log query floods on 16262
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 4
|
|
-p udp --dport 16262 -m conntrack --ctstate NEW
|
|
-m length --length 53
|
|
-m hashlimit --hashlimit-above {{ zomboid_query_rate_limit }}
|
|
--hashlimit-burst {{ zomboid_query_burst }}
|
|
--hashlimit-mode srcip --hashlimit-name zomboid_query_16262
|
|
--hashlimit-htable-expire 3600000
|
|
-j LOG --log-prefix "ZOMBOID_RATELIMIT: " --log-level 4
|
|
register: qflood_log_16262
|
|
changed_when: "'already' not in qflood_log_16262.stderr"
|
|
failed_when: false
|
|
notify: restart firewalld
|
|
tags: firewall
|
|
|
|
- name: drop query floods on 16262
|
|
become: true
|
|
ansible.builtin.command: >
|
|
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 5
|
|
-p udp --dport 16262 -m conntrack --ctstate NEW
|
|
-m length --length 53
|
|
-m hashlimit --hashlimit-above {{ zomboid_query_rate_limit }}
|
|
--hashlimit-burst {{ zomboid_query_burst }}
|
|
--hashlimit-mode srcip --hashlimit-name zomboid_query_16262
|
|
--hashlimit-htable-expire 3600000
|
|
-j DROP
|
|
register: qflood_drop_16262
|
|
changed_when: "'already' not in qflood_drop_16262.stderr"
|
|
failed_when: false
|
|
notify: restart firewalld
|
|
tags: firewall
|
|
|
|
# World reset is now triggered via Discord bot -> systemd path unit
|
|
# See zomboid-world-reset.path and zomboid-world-reset.service
|
|
- name: enable zomboid world reset path unit
|
|
become: true
|
|
become_user: "{{ podman_user }}"
|
|
ansible.builtin.systemd:
|
|
name: zomboid-world-reset.path
|
|
scope: user
|
|
enabled: true
|
|
state: started
|
|
daemon_reload: true
|