Reverts the restore-from-template behaviour. A wipe now resets the world and the
player database only; Server/<name>_{SandboxVars,spawnregions,spawnpoints}.lua
carry over untouched, so hand tuning survives it. Verified by checksum: all three
files byte-identical either side of a wipe.
That gives up the guarantee the restore bought. PZ writes the running world's
settings back over those files, so if a world is ever created with defaults the
file inherits them and later wipes regenerate from them -- which is how a Sophie
world became an Apocalypse one. Nothing corrects that automatically now, so the
wipe takes a snapshot of the settings before it starts, keeping the last ten
under config-backup/. Greg's edits were lost once because the only record of them
was a file the server had since overwritten; that is the hole this fills.
config-template/ still holds the pristine Sophie preset to copy back from.
The snapshot is best-effort throughout. The first version of it created the
directory with plain mkdir, the parent belongs to the container's subuid rather
than the podman user, and set -e turned that into a failed wipe -- the same shape
as the tee that broke this script before. Ansible owns the directory now and
every step of the snapshot tolerates failure, because a backup that cannot be
written is not a reason to refuse to wipe.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
726 lines
26 KiB
YAML
726 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 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 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
|
|
|
|
# A pristine copy of the Sophie preset, kept where the server cannot overwrite it.
|
|
#
|
|
# 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.
|
|
- 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
|