From f82e56ea27d704f522858e7831b6c5674d0d10b6 Mon Sep 17 00:00:00 2001 From: boxswapper Date: Wed, 28 Oct 2026 12:06:51 -0600 Subject: [PATCH] init commit --- ansible-playbook.yml | 39 +++++ inventory.ini | 2 + requirements.yml | 5 + roles/7dtd/files/mods/MoreAnimals.zip | Bin 0 -> 1962 bytes .../mods/MoreAnimals/Config/spawning.xml | 33 +++++ roles/7dtd/files/mods/MoreAnimals/ModInfo.xml | 7 + roles/7dtd/tasks/main.yml | 135 ++++++++++++++++++ roles/7dtd/templates/sdtdserver.cfg.j2 | 14 ++ roles/7dtd/templates/sdtdserver.xml.j2 | 117 +++++++++++++++ 9 files changed, 352 insertions(+) create mode 100755 ansible-playbook.yml create mode 100755 inventory.ini create mode 100755 requirements.yml create mode 100644 roles/7dtd/files/mods/MoreAnimals.zip create mode 100644 roles/7dtd/files/mods/MoreAnimals/Config/spawning.xml create mode 100644 roles/7dtd/files/mods/MoreAnimals/ModInfo.xml create mode 100644 roles/7dtd/tasks/main.yml create mode 100644 roles/7dtd/templates/sdtdserver.cfg.j2 create mode 100644 roles/7dtd/templates/sdtdserver.xml.j2 diff --git a/ansible-playbook.yml b/ansible-playbook.yml new file mode 100755 index 0000000..668326c --- /dev/null +++ b/ansible-playbook.yml @@ -0,0 +1,39 @@ +--- +- hosts: internal-apps + become: true + vars_files: + - ./vars.yml + tasks: + - name: Pull images + containers.podman.podman_image: + name: "{{ item }}" + loop: "{{ images }}" + tags: + - images + + roles: + - role: packages + tags: [packages] + - role: directories + tags: [directories] + - role: sysctl + tags: [sysctl] + - role: network + tags: [network] + - role: wireguard + tags: [wireguard] + - role: npm + tags: [npm] + - role: gitea + tags: [gitea] + - role: jellyfin + tags: [jellyfin] + - role: wazuh + tags: [wazuh] + # - role: minecraft + # tags: [minecraft] + # - role: 7dtd + # tags: [7dtd] + # - role: zomboid + # tags: [zomboid] + diff --git a/inventory.ini b/inventory.ini new file mode 100755 index 0000000..11c0d53 --- /dev/null +++ b/inventory.ini @@ -0,0 +1,2 @@ +[internal-apps] +7dtd ansible_user=fedora ansible_become_password="VjocLlmw6faSdM" diff --git a/requirements.yml b/requirements.yml new file mode 100755 index 0000000..54b1ffa --- /dev/null +++ b/requirements.yml @@ -0,0 +1,5 @@ +collections: + - name: containers.podman + - name: ansible.posix + + \ No newline at end of file diff --git a/roles/7dtd/files/mods/MoreAnimals.zip b/roles/7dtd/files/mods/MoreAnimals.zip new file mode 100644 index 0000000000000000000000000000000000000000..538ace69e143475289fb326e0f74004be51d0f30 GIT binary patch literal 1962 zcmWIWW@h1H0D*)R=CNP~l;B~IVeri_N_EW3%uUQG)(;KgWMFEAYKgBaIEnlx9HwSFs`6*knKp4%y z*a?o_haCi3-~a1!_viBUdLPJfEn4f5;~u}s7x{9^rqA@9{HNO7l=Y+8hkcbb##gWG zUT(dhYU#W}Me`4jpGi!~*0;61{bt=uM;)tK|D7W=UBa%M+VV(#joQKc7ixFz|GHtT zmf>H{`<^|#i+8O15%=q4dB-W)r3*HAEOtMBPvz{6_nBFTA6z~>HRxaOR3T@ZABU_S zt1Nsjl<)lQw~g5e*A|bJwu>A1ElhfIr!Oo$WcSq7`^gOnw z@Qj=#GxTF0Wo@^4#|{c1?BOho7DCSXd1;yHL|iI zu{&dPT=st`k$)q74@GeCmmCJ7};s(aQ2;MgOem z(@z%i6#iPyD=__aN;sE6t$*$no|we#_<)IR9*1*XwZgw?8?LQdSJlbRy5rIu zPpxlfucvYK$6Uw`+N3SQrdg_ab9wR3{ss=le-f9w=Y`z;T$;$7u>H}brK_X7t3$SL zNqM_^LH2~|U8={7997<`euy-l;+SdkCumcV{H}`*?u_3mo;EY*Pn#I|s&MgqEis#Fb+|pT6VU6qVi9d$4!H z^=;R567oBqxkNnz04qDwg+sHpjn^r(MRz?iW`wZC_vV zjk~s+QUASJP|EJ|wupOm&Q7VGNr$x-?Ol6Kqo(Jtv(%zAH73=W<-Lwp+FP6Tc{z#_ zdi)Uj_-`ZT9p<^MavrEa=~YfOkQ{O_>+h*KQrxqv>$Pq#IsRGe#E{-)R* zI$lun;ie4FulNPCBn4fR!`Hs%np!x|{q2SMUcXj)KVUZ0DVXPEUupeG?Hs?~(d?~h zzsvVD-!VPwD?L#zcS4VX4}+D!${%kpnVPI^y54j*+I0PM{^`L#zP;r0a@`m`gVXC# zVC$p|W3Jmw>=&E2Un&*z)hclaou+;LAS-7aPoJTt!6l_7@!+dP`tb4{O9(5b4F?VdvW*we|%c>P`1qH!fW>EGopLqdz<9u zUur5|F0nDg_i&W%JU{Q^rC)wpRzA-E+HaoCArZ33EncYWQSr=9smVqcV&6)4CAK7l zM&HoN<^IvfzR(+(cnayMtw(Nbac{olPqcpu%ATqbgV!{dxEGbOfu ze$;qT!DOCg^2PfLOIr$mx3@c0%--|aHTl&t->a{g{yHQ`%zqmY>d1P2=5G0YdQxv6 zRg?+c)3Fcws?aHY`0}w9d)JpDeU|K-w9OfG1c{Nj4BfAh-6rN?wEr2S%)ek}i=WFYrw zv;9mKrnP0}ry|UA*TzFOtRI^Ll5xihk-l>XSisW0B?>f@Cai{5;2o$~i- z!}3>sh9$rKE_9xcRsUqY{G)lco-z0RfYh=TSpKRwc22X zR2yg|Jj6s?l?KE_VEw_cq|pt{M3gE7XgHSY1D|t+QJk9rv;*0>@Y0vCt1-)7WLIy+ dZZK*|473DP5@Q4nD;p>nS%5GXXpB0T2LMT!5Uc + + + + 4 + 0.25,0.438,0.338,0.25,0.163,0.088 + + + 3 + 0.6,1.05,0.81,0.6,0.39,0.21 + \ No newline at end of file diff --git a/roles/7dtd/files/mods/MoreAnimals/ModInfo.xml b/roles/7dtd/files/mods/MoreAnimals/ModInfo.xml new file mode 100644 index 0000000..fe020df --- /dev/null +++ b/roles/7dtd/files/mods/MoreAnimals/ModInfo.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/roles/7dtd/tasks/main.yml b/roles/7dtd/tasks/main.yml new file mode 100644 index 0000000..6fa45da --- /dev/null +++ b/roles/7dtd/tasks/main.yml @@ -0,0 +1,135 @@ +--- +# 7 Days to Die dedicated server (vinanrra/7dtd-server container, LinuxGSM-based, SteamCMD-installed). +# PUBLIC server: players connect to 7dtd.boxswapper.io:26900 (TCP+UDP) and enter the join password +# (sdtd_server_password). Server config is rendered from roles/7dtd/templates/sdtdserver.xml.j2 on +# every deploy - vars.yml is the source of truth for the server config. +# External prerequisites (NOT managed by this repo): +# - Cloudflare A record 7dtd.boxswapper.io -> public IP (grey-cloud, like mc.boxswapper.io) +# - Router port forwards: TCP+UDP 26900, UDP 26901-26902 + TCP 8081 (telnet) -> 10.10.0.47 +# - CHANGED sdtd_server_password / sdtd_telnet_password in vars.yml before first deploy +- name: Create 7 Days to Die data directory + ansible.builtin.file: + path: /opt/apps/7dtd/data + state: directory + mode: '0755' + +# The container (linuxserver-style base) runs as uid/gid 1000 (PUID/PGID) and chowns its volumes on +# start; pre-seeding with that ownership lets the init script and server write alongside our xml. +# NOTE: NO /etc/timezone or /etc/localtime bind mounts here (unlike minecraft/zomboid) - the image's +# user.sh writes BOTH paths itself to apply the TimeZone env var, so ro binds crash-loop the container. +- name: Create 7 Days to Die saves dir (container user) + ansible.builtin.file: + path: /opt/apps/7dtd/data/7DaysToDie + state: directory + owner: '1000' + group: '1000' + mode: '0755' + +- name: Create 7 Days to Die serverfiles directory (container user) + ansible.builtin.file: + path: /opt/apps/7dtd/serverfiles + state: directory + owner: '1000' + group: '1000' + mode: '0755' + +# Mods live in the bind-mounted serverfiles; the container loads them on its next start +# (recreate on every deploy re-reads them). zips come from roles/7dtd/files/mods (sdtd_mods +# in vars.yml), extracted one level deep (each zip carries its own mod subfolder; prefix +# folders with z- if they must sort LAST for load order). +# Deployed now: MoreAnimals — a PURE XML data patch (no DLL/Harmony) raising wild-game/predator +# spawn density (v3.x schema: maxcount per 80x80m chunk + 6-value respawndelay tuples). Works +# with EAC ON, needs NO client install (spawning is server-authoritative; the server pushes its +# XML state to clients on connect). GATE: XML values apply only while the sandbox Density options +# are Default — once sdtd_sandbox_code sets any density != Default, the sandbox value wins and +# this patch is inert; use SandboxCode Day/Night Animal Density + Respawn instead. +# unarchive only ADDS — it never removes, so a mod dropped from sdtd_mods would keep loading: +# with an empty list we wipe the whole dir and the create-task below rebuilds it empty. +# Partial mod swaps are not auto-cleaned (the role only knows zip names, not the folders zips +# unpack into) — remove those retired folders from the box by hand. +- name: Remove 7 Days to Die Mods directory when no mods are deployed + ansible.builtin.file: + path: /opt/apps/7dtd/serverfiles/Mods + state: absent + when: sdtd_mods | length == 0 + +- name: Create 7 Days to Die Mods directory (container user) + ansible.builtin.file: + path: /opt/apps/7dtd/serverfiles/Mods + state: directory + owner: '1000' + group: '1000' + mode: '0755' + +- name: Deploy 7 Days to Die mods from vendored zips + ansible.builtin.unarchive: + src: "mods/{{ item }}" + dest: /opt/apps/7dtd/serverfiles/Mods + owner: '1000' + group: '1000' + mode: '0755' + loop: "{{ sdtd_mods }}" + +- name: Template 7 Days to Die serverconfig from vars.yml + ansible.builtin.template: + src: sdtdserver.xml.j2 + dest: /opt/apps/7dtd/serverfiles/sdtdserver.xml + owner: '1000' + group: '1000' + mode: '0644' + +# LinuxGSM reads config-lgsm/sdtdserver/sdtdserver.cfg on EVERY invocation (start/monitor/stop). +# Bind-mounting it makes the role the owner of that config (see vars.yml sdtd_lgsm_querymode); +# a previously unmanaged podman volume left defaults in place, causing the monitor crash-loop. +- name: Create 7 Days to Die LGSM config directory (container user) + ansible.builtin.file: + path: /opt/apps/7dtd/lgsm + state: directory + owner: '1000' + group: '1000' + mode: '0755' + +- name: Template 7 Days to Die LGSM config from vars.yml + ansible.builtin.template: + src: sdtdserver.cfg.j2 + dest: /opt/apps/7dtd/lgsm/sdtdserver.cfg + owner: '1000' + group: '1000' + mode: '0644' + +- name: Deploy 7 Days to Die Server + containers.podman.podman_container: + image: "{{ sdtd_image }}" + name: 7dtd + env: "{{ sdtd_env_base }}" + restart_policy: always + recreate: true + state: started + volumes: + - /opt/apps/7dtd/data:/home/sdtdserver/.local/share/7DaysToDie:z + - /opt/apps/7dtd/serverfiles:/home/sdtdserver/serverfiles:z + - /opt/apps/7dtd/lgsm:/home/sdtdserver/lgsm/config-lgsm/sdtdserver:z + ports: + # Required: game port (TCP+UDP), Steam query/beacon ports (UDP) + - "26900:26900/tcp" + - "26900:26900/udp" + - "26901:26901/udp" + - "26902:26902/udp" + # Optional: telnet admin console (gated by sdtd_telnet_password) + - "8081:8081/tcp" + network: + - npm_internal + ip: '{{ sdtd_ip }}' + vars: + # sdtd_env_base: START_MODE=1 starts the server; PUID/PGID match the pre-seeded dir ownership; + # MONITOR=YES restarts the game process if it crashes (LinuxGSM cron). + # IMPORTANT: restart_policy: always is only valid with START_MODE=1 (never 0/2/4 per image docs). + sdtd_env_base: + START_MODE: "{{ sdtd_start_mode }}" + VERSION: "{{ sdtd_version }}" + PUID: "{{ sdtd_puid }}" + PGID: "{{ sdtd_pgid }}" + TimeZone: "{{ sdtd_timezone }}" + BACKUP: "{{ sdtd_backup }}" + MONITOR: "{{ sdtd_monitor }}" + TEST_ALERT: "NO" \ No newline at end of file diff --git a/roles/7dtd/templates/sdtdserver.cfg.j2 b/roles/7dtd/templates/sdtdserver.cfg.j2 new file mode 100644 index 0000000..c990db7 --- /dev/null +++ b/roles/7dtd/templates/sdtdserver.cfg.j2 @@ -0,0 +1,14 @@ +################################## +####### Instance Settings ######## +################################## +# Managed by Ansible (roles/7dtd/templates/sdtdserver.cfg.j2) - local edits are overwritten on re-run. +# Source of truth: sdtd_* variables in vars.yml. + +## 7dtd answers NO A2S query on the game port (ServerVisibility=0 + SteamNetworking disabled), +## so the default gamedig-based monitor (querymode=2) always fails and force-restarts the healthy +## server every ~5 min. querymode=1 checks the tmux session instead. +querymode="{{ sdtd_lgsm_querymode }}" + +## LGSM graceful stop reads ${telnetpassword} from this cfg (the game XML TelnetPassword is parsed +## into ${telnetpass}, a different var). Without it, stop falls back to a hard tmux kill. +telnetpassword="{{ sdtd_telnet_password }}" \ No newline at end of file diff --git a/roles/7dtd/templates/sdtdserver.xml.j2 b/roles/7dtd/templates/sdtdserver.xml.j2 new file mode 100644 index 0000000..6aa16e1 --- /dev/null +++ b/roles/7dtd/templates/sdtdserver.xml.j2 @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{% if sdtd_sandbox_code %} + +{% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file