Files
deploy_home/ansible/roles/podman/tasks/containers/home/zomboid.yml
T
Bastian de BylandClaude Opus 5 ba9c4f2bfe fix(zomboid): re-sync the world settings from the live server
The vendored Sophie preset and the running debbzoid world had drifted, and the
repo only held the preset. That is not a restore point: force-pushing it would
have reverted the admins' in-game tuning rather than recovering it, which is
exactly how a Sophie world quietly became an Apocalypse one once already -- 139
values reverted, loot from 0.35 back to 0.9, CharacterFreePoints 0 to 60.

files/zomboid/sophie/SandboxVars.lua is now a snapshot of the live world taken
2026-08-31, not the preset as shipped. The modlist is untouched and still
upstream, which is why zomboid_preset_version now names the two halves and their
separate dates. server.ini.j2 carries the eight keys that had drifted:

  PlayerSafehouse              false -> true
  SafehouseAllowNonResidential false -> true   (the diner/gas-station case)
  SafehouseAllowRespawn        false -> true
  SafehouseAllowLoot           true  -> false
  SafehouseAllowFire           true  -> false
  TrashDeleteAll               false -> true
  MapRemotePlayerVisibility    1     -> 4
  ResetID                      6953472 -> 826046

ResetID is in that list on purpose, and matters most. It is the world's
soft-reset token: a file value that differs from the one the live world was
created with tells every connected client to roll a new character. Carrying the
live value makes a deliberate force-push a no-op instead of a server-wide wipe
prompt.

Spawn config gets its own switch, zomboid_spawn_force. spawnregions.lua and
spawnpoints.lua are the only config a running world re-reads -- at every server
start, where SandboxVars is read once, when the world is created -- so a spawn
edit is deployable on the live world without a wipe. Sharing zomboid_config_force
between them would have meant force-pushing the whole preset to land a one-line
spawn edit, rewriting the INI (hence the ResetID hazard above) and the world's
SandboxVars along with it.

config-template/ now tracks the repo unconditionally. Nothing on the host writes
that directory and the server cannot see it, so it has no hand edits to protect;
if it does not track the repo it is not a restore point, just an older world's
settings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QdWYhwUtwM2NQGukiRh12
2026-09-05 09:11:31 -04:00

764 lines
27 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 host-side config directories
become: true
ansible.builtin.file:
path: "{{ zomboid_path }}/{{ item }}"
state: directory
owner: "{{ podman_user }}"
group: "{{ podman_user }}"
mode: 0755
loop:
- config-template
- config-backup
- 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 *before* first boot, so the server never generates a
# vanilla INI we then have to patch.
#
# The INI and SandboxVars started as the vendored Sophie 42 preset and were
# re-synced from the running debbzoid world on 2026-08-31 -- see the header of
# templates/zomboid/server.ini.j2 for the INI keys that moved and why.
#
# 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
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 settings
become: true
ansible.builtin.copy:
src: zomboid/sophie/SandboxVars.lua
dest: "{{ zomboid_path }}/data/Server/{{ zomboid_server_name }}_SandboxVars.lua"
force: "{{ zomboid_config_force | bool }}"
owner: "{{ podman_subuid.stdout }}"
group: "{{ podman_user }}"
mode: 0644
notify: restorecon podman
tags: zomboid-conf
# Spawn config gets its own force switch, and it is not pedantry: these two
# files are the only part of the server config that a *running* world will pick
# up. PZ reads <name>_spawnregions.lua (and any serverfile it names) at every
# server start; SandboxVars is read once, when the world is created. So a spawn
# change is deployable on the live world -- push, restart, done -- while a
# SandboxVars change is not, and needs a wipe to mean anything.
#
# Sharing zomboid_config_force between them would mean force-pushing the whole
# preset to land a one-line spawnregions edit, which also rewrites the INI (a
# ResetID mismatch tells every client to reroll) and the world's SandboxVars.
#
# make deploy TAGS=zomboid-conf -e zomboid_spawn_force=true
#
# then restart the server -- with players offline -- for it to take effect.
- name: seed zomboid spawn config
become: true
ansible.builtin.copy:
src: "zomboid/sophie/{{ item }}.lua"
dest: "{{ zomboid_path }}/data/Server/{{ zomboid_server_name }}_{{ item }}.lua"
force: "{{ zomboid_spawn_force | bool }}"
owner: "{{ podman_subuid.stdout }}"
group: "{{ podman_user }}"
mode: 0644
loop:
- 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 settings this repo would deploy, kept where the server cannot overwrite them.
#
# Reference only -- nothing applies this automatically. A wipe deliberately leaves
# the live settings alone so hand tuning survives it.
#
# It exists because the live files cannot be trusted as a record of intent: PZ
# reads Server/<name>_SandboxVars.lua only when it creates a world, then writes
# the running world's settings back over it. The file in Server/ is an output as
# much as an input, and that is how a Sophie world quietly became an Apocalypse
# one -- 139 values reverted, loot from 0.35 back to 0.9, CharacterFreePoints
# from 0 to 60. When that happens again, this is what to copy back from.
#
# What it holds changed on 2026-08-31. It was the pristine Sophie preset; it is
# now a snapshot of the live debbzoid world, because the admins' tuning had by
# then diverged from the preset in the same 139 values and re-seeding from the
# preset would have thrown that tuning away rather than restored it.
#
# force is on here, and only here. Nothing on the host writes this directory --
# the server cannot see it -- so it has no hand edits to protect, and if it does
# not track the repo it is not a restore point, just an older world's settings.
- name: seed canonical zomboid world settings
become: true
ansible.builtin.copy:
src: "zomboid/sophie/{{ item }}.lua"
dest: "{{ zomboid_path }}/config-template/{{ item }}.lua"
force: true
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