diff --git a/ansible/roles/common/tasks/security.yml b/ansible/roles/common/tasks/security.yml index ef8b46f..baa7ed0 100644 --- a/ansible/roles/common/tasks/security.yml +++ b/ansible/roles/common/tasks/security.yml @@ -1,4 +1,13 @@ --- +- name: enable post-quantum key exchange for sshd + become: true + ansible.builtin.template: + src: sshd-pq-kex.conf.j2 + dest: /etc/ssh/sshd_config.d/30-pq-kex.conf + mode: 0600 + notify: restart_sshd + tags: security, sshd + - name: ensure sshd disallows passwords become: true ansible.builtin.lineinfile: diff --git a/ansible/roles/common/templates/sshd-pq-kex.conf.j2 b/ansible/roles/common/templates/sshd-pq-kex.conf.j2 new file mode 100644 index 0000000..eb7b432 --- /dev/null +++ b/ansible/roles/common/templates/sshd-pq-kex.conf.j2 @@ -0,0 +1,9 @@ +# Post-Quantum Key Exchange Algorithm +# Managed by Ansible - do not edit directly +# +# Enables sntrup761x25519-sha512 (hybrid post-quantum + classical) +# to protect against "store now, decrypt later" attacks +# +# This must be included BEFORE crypto-policies (40-redhat-crypto-policies.conf) + +KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512 diff --git a/ansible/roles/git/defaults/main.yml b/ansible/roles/git/defaults/main.yml index 810aea9..5bebc4c 100644 --- a/ansible/roles/git/defaults/main.yml +++ b/ansible/roles/git/defaults/main.yml @@ -1,3 +1,8 @@ --- git_user: git git_home: "/srv/{{ git_user }}" + +# Gitea configuration +gitea_debyl_server_name: git.debyl.io +gitea_image: docker.gitea.com/gitea:1.25.2 +gitea_db_image: docker.io/library/postgres:14-alpine diff --git a/ansible/roles/git/files/gitea-ssh-podman.te b/ansible/roles/git/files/gitea-ssh-podman.te new file mode 100644 index 0000000..96f971a --- /dev/null +++ b/ansible/roles/git/files/gitea-ssh-podman.te @@ -0,0 +1,13 @@ +module gitea-ssh-podman 1.0; + +require { + type sshd_t; + type container_runtime_exec_t; + type user_home_t; + class file { execute execute_no_trans open read }; + class dir { search }; +} + +# Allow sshd to execute podman for AuthorizedKeysCommand +allow sshd_t container_runtime_exec_t:file { execute execute_no_trans open read }; +allow sshd_t user_home_t:dir search; diff --git a/ansible/roles/git/handlers/main.yml b/ansible/roles/git/handlers/main.yml index 755ef76..a64b0ff 100644 --- a/ansible/roles/git/handlers/main.yml +++ b/ansible/roles/git/handlers/main.yml @@ -15,3 +15,10 @@ tags: - git - selinux + +- name: restart sshd + become: true + ansible.builtin.systemd: + name: sshd.service + state: restarted + tags: git diff --git a/ansible/roles/git/tasks/gitea-shell.yml b/ansible/roles/git/tasks/gitea-shell.yml new file mode 100644 index 0000000..5f33c0b --- /dev/null +++ b/ansible/roles/git/tasks/gitea-shell.yml @@ -0,0 +1,28 @@ +--- +# Deploy gitea shim and shell for SSH passthrough + +# The shim is called by SSH when authorized_keys command runs +# It forwards gitea commands to the container +- name: create gitea shim script + become: true + ansible.builtin.template: + src: gitea-shim.j2 + dest: /usr/local/bin/gitea + mode: 0755 + tags: git, gitea + +# The shell is used if someone tries to SSH interactively +- name: create gitea-shell script + become: true + ansible.builtin.template: + src: gitea-shell.j2 + dest: /usr/local/bin/gitea-shell + mode: 0755 + tags: git, gitea + +- name: update git user shell to gitea-shell + become: true + ansible.builtin.user: + name: "{{ git_user }}" + shell: /usr/local/bin/gitea-shell + tags: git, gitea diff --git a/ansible/roles/git/tasks/gitea.yml b/ansible/roles/git/tasks/gitea.yml new file mode 100644 index 0000000..601b297 --- /dev/null +++ b/ansible/roles/git/tasks/gitea.yml @@ -0,0 +1,90 @@ +--- +# Deploy Gitea containers using Podman pod + +# Create pod for Gitea services +- name: create gitea-debyl pod + become: true + become_user: "{{ git_user }}" + containers.podman.podman_pod: + name: gitea-debyl-pod + state: started + ports: + - "3100:3000" + tags: gitea + +# PostgreSQL container in pod +- name: create gitea-debyl-postgres container + become: true + become_user: "{{ git_user }}" + containers.podman.podman_container: + name: gitea-debyl-postgres + image: "{{ gitea_db_image }}" + pod: gitea-debyl-pod + restart_policy: on-failure:3 + log_driver: journald + env: + POSTGRES_DB: gitea + POSTGRES_USER: gitea + POSTGRES_PASSWORD: "{{ gitea_debyl_db_pass }}" + volumes: + - "{{ git_home }}/volumes/gitea/psql:/var/lib/postgresql/data" + tags: gitea + +# Gitea container in pod +- name: create gitea-debyl container + become: true + become_user: "{{ git_user }}" + containers.podman.podman_container: + name: gitea-debyl + image: "{{ gitea_image }}" + pod: gitea-debyl-pod + restart_policy: on-failure:3 + log_driver: journald + env: + USER_UID: "1000" + USER_GID: "1000" + GITEA__database__DB_TYPE: postgres + GITEA__database__HOST: "127.0.0.1:5432" + GITEA__database__NAME: gitea + GITEA__database__USER: gitea + GITEA__database__PASSWD: "{{ gitea_debyl_db_pass }}" + GITEA__server__DOMAIN: "{{ gitea_debyl_server_name }}" + GITEA__server__ROOT_URL: "https://{{ gitea_debyl_server_name }}/" + GITEA__server__SSH_DOMAIN: "{{ gitea_debyl_server_name }}" + GITEA__server__START_SSH_SERVER: "false" + GITEA__server__DISABLE_SSH: "false" + GITEA__server__SSH_PORT: "22" + GITEA__security__SECRET_KEY: "{{ gitea_debyl_secret_key }}" + GITEA__security__INTERNAL_TOKEN: "{{ gitea_debyl_internal_token }}" + GITEA__security__INSTALL_LOCK: "true" + GITEA__service__DISABLE_REGISTRATION: "true" + GITEA__service__REQUIRE_SIGNIN_VIEW: "false" + volumes: + - "{{ git_home }}/volumes/gitea/data:/data" + - /etc/localtime:/etc/localtime:ro + tags: gitea + +# Generate systemd service for the pod +- name: create systemd job for gitea-debyl-pod + become: true + become_user: "{{ git_user }}" + ansible.builtin.shell: | + podman generate systemd --name gitea-debyl-pod --files --new + mv pod-gitea-debyl-pod.service {{ git_home }}/.config/systemd/user/ + mv container-gitea-debyl-postgres.service {{ git_home }}/.config/systemd/user/ + mv container-gitea-debyl.service {{ git_home }}/.config/systemd/user/ + args: + chdir: "{{ git_home }}" + changed_when: false + tags: gitea + +- name: enable gitea-debyl-pod service + become: true + become_user: "{{ git_user }}" + ansible.builtin.systemd: + name: pod-gitea-debyl-pod.service + daemon_reload: true + enabled: true + state: started + scope: user + tags: gitea diff --git a/ansible/roles/git/tasks/main.yml b/ansible/roles/git/tasks/main.yml index bbcd0c2..3f016bd 100644 --- a/ansible/roles/git/tasks/main.yml +++ b/ansible/roles/git/tasks/main.yml @@ -1,4 +1,10 @@ --- - import_tasks: user.yml -- import_tasks: systemd.yml +- import_tasks: podman.yml +- import_tasks: gitea-shell.yml +- import_tasks: sshd.yml - import_tasks: selinux.yml +- import_tasks: selinux-podman.yml +- import_tasks: gitea.yml +# git-daemon no longer needed - commented out +# - import_tasks: systemd.yml diff --git a/ansible/roles/git/tasks/podman.yml b/ansible/roles/git/tasks/podman.yml new file mode 100644 index 0000000..598e0ba --- /dev/null +++ b/ansible/roles/git/tasks/podman.yml @@ -0,0 +1,80 @@ +--- +# Rootless Podman setup for git user +# Enables running Gitea containers under the git user + +# Enable lingering for systemd user services +- name: check if git user lingering enabled + become: true + ansible.builtin.stat: + path: "/var/lib/systemd/linger/{{ git_user }}" + register: git_user_lingering + tags: git, gitea + +- name: enable git user lingering + become: true + ansible.builtin.command: | + loginctl enable-linger {{ git_user }} + when: not git_user_lingering.stat.exists + tags: git, gitea + +# Set ulimits for container operations +- name: set ulimits for git user + become: true + community.general.pam_limits: + domain: "{{ git_user }}" + limit_type: "{{ item.type }}" + limit_item: "{{ item.name }}" + value: "{{ item.value }}" + loop: + - { name: memlock, type: soft, value: "unlimited" } + - { name: memlock, type: hard, value: "unlimited" } + - { name: nofile, type: soft, value: 39693561 } + - { name: nofile, type: hard, value: 39693561 } + tags: git, gitea + +# Create container directories +- name: create git podman directories + become: true + become_user: "{{ git_user }}" + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: 0755 + loop: + - "{{ git_home }}/.config/systemd/user" + - "{{ git_home }}/volumes" + - "{{ git_home }}/volumes/gitea" + - "{{ git_home }}/volumes/gitea/data" + # NOTE: psql directory is created by PostgreSQL container with container user ownership + notify: restorecon git + tags: git, gitea + +# SELinux context for container volumes +- name: selinux context for git container volumes + become: true + community.general.sefcontext: + target: "{{ git_home }}/volumes(/.*)?" + setype: container_file_t + state: present + notify: restorecon git + tags: git, gitea, selinux + +# Enable podman socket for SSH key lookup via AuthorizedKeysCommand +- name: enable podman socket for git user + become: true + become_user: "{{ git_user }}" + ansible.builtin.systemd: + name: podman.socket + enabled: true + state: started + scope: user + tags: git, gitea + +# Fetch subuid for volume permissions +- name: fetch subuid of {{ git_user }} + become: true + changed_when: false + ansible.builtin.shell: | + set -o pipefail && cat /etc/subuid | awk -F':' '/{{ git_user }}/{ print $2 }' | head -n 1 + register: git_subuid + tags: always diff --git a/ansible/roles/git/tasks/selinux-podman.yml b/ansible/roles/git/tasks/selinux-podman.yml new file mode 100644 index 0000000..0d9b377 --- /dev/null +++ b/ansible/roles/git/tasks/selinux-podman.yml @@ -0,0 +1,21 @@ +--- +# SELinux policy for SSH + Podman integration + +- name: copy gitea SELinux policy module + become: true + ansible.builtin.copy: + src: gitea-ssh-podman.te + dest: /tmp/gitea-ssh-podman.te + mode: 0644 + register: selinux_policy + tags: git, gitea, selinux + +- name: compile and install gitea SELinux policy + become: true + ansible.builtin.shell: | + cd /tmp + checkmodule -M -m -o gitea-ssh-podman.mod gitea-ssh-podman.te + semodule_package -o gitea-ssh-podman.pp -m gitea-ssh-podman.mod + semodule -i gitea-ssh-podman.pp + when: selinux_policy.changed + tags: git, gitea, selinux diff --git a/ansible/roles/git/tasks/sshd.yml b/ansible/roles/git/tasks/sshd.yml new file mode 100644 index 0000000..1bb7de6 --- /dev/null +++ b/ansible/roles/git/tasks/sshd.yml @@ -0,0 +1,19 @@ +--- +# Configure SSH AuthorizedKeysCommand for Gitea + +- name: create gitea-authorized-keys script + become: true + ansible.builtin.template: + src: gitea-authorized-keys.j2 + dest: /usr/local/bin/gitea-authorized-keys + mode: 0755 + tags: git, gitea + +- name: deploy sshd gitea configuration + become: true + ansible.builtin.template: + src: sshd-gitea.conf.j2 + dest: /etc/ssh/sshd_config.d/50-gitea.conf + mode: 0644 + notify: restart sshd + tags: git, gitea diff --git a/ansible/roles/git/templates/gitea-authorized-keys.j2 b/ansible/roles/git/templates/gitea-authorized-keys.j2 new file mode 100644 index 0000000..275a213 --- /dev/null +++ b/ansible/roles/git/templates/gitea-authorized-keys.j2 @@ -0,0 +1,12 @@ +#!/bin/sh +# Query Gitea for SSH authorized keys +# Managed by Ansible - do not edit directly +# Arguments: %u (username) %t (key type) %k (key blob) + +# Use podman remote to connect via socket (avoids rootless pause process issues) +export CONTAINER_HOST=unix:///run/user/1001/podman/podman.sock + +/usr/bin/podman --remote exec -i --user 1000 gitea-debyl \ + /usr/local/bin/gitea keys \ + -c /data/gitea/conf/app.ini \ + -e git -u "$1" -t "$2" -k "$3" 2>/dev/null diff --git a/ansible/roles/git/templates/gitea-shell.j2 b/ansible/roles/git/templates/gitea-shell.j2 new file mode 100644 index 0000000..8a21758 --- /dev/null +++ b/ansible/roles/git/templates/gitea-shell.j2 @@ -0,0 +1,27 @@ +#!/bin/sh +# Gitea SSH shell - forwards commands to Gitea container +# Managed by Ansible - do not edit directly +# +# When sshd runs a forced command from authorized_keys, it invokes: +# -c "" +# The forced command is: /usr/local/bin/gitea --config=... serv key- +# SSH_ORIGINAL_COMMAND contains the client's requested command (e.g., git-upload-pack) + +# Use podman remote to connect via socket (avoids rootless pause process issues) +export CONTAINER_HOST=unix:///run/user/1001/podman/podman.sock + +if [ "$1" = "-c" ] && [ -n "$2" ]; then + # sshd invoked us with -c "command" - execute the command + # The command is: /usr/local/bin/gitea --config=... serv key- + exec $2 +elif [ -n "$SSH_ORIGINAL_COMMAND" ]; then + # Direct invocation with SSH_ORIGINAL_COMMAND (shouldn't happen normally) + echo "Interactive shell is disabled." + echo "Use: git clone git@{{ gitea_debyl_server_name }}:/.git" + exit 1 +else + # Interactive login attempt + echo "Interactive shell is disabled." + echo "Use: git clone git@{{ gitea_debyl_server_name }}:/.git" + exit 1 +fi diff --git a/ansible/roles/git/templates/gitea-shim.j2 b/ansible/roles/git/templates/gitea-shim.j2 new file mode 100644 index 0000000..1db4713 --- /dev/null +++ b/ansible/roles/git/templates/gitea-shim.j2 @@ -0,0 +1,15 @@ +#!/bin/sh +# Gitea shim - forwards gitea commands to the container +# Managed by Ansible - do not edit directly +# +# This script is called when sshd executes the forced command from authorized_keys: +# /usr/local/bin/gitea --config=/data/gitea/conf/app.ini serv key- +# +# SSH_ORIGINAL_COMMAND contains the client's git command (e.g., git-upload-pack ) + +# Use podman remote to connect via socket (avoids rootless pause process issues) +export CONTAINER_HOST=unix:///run/user/1001/podman/podman.sock + +exec /usr/bin/podman --remote exec -i --user 1000 \ + --env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" \ + gitea-debyl /usr/local/bin/gitea "$@" diff --git a/ansible/roles/git/templates/sshd-gitea.conf.j2 b/ansible/roles/git/templates/sshd-gitea.conf.j2 new file mode 100644 index 0000000..a414c79 --- /dev/null +++ b/ansible/roles/git/templates/sshd-gitea.conf.j2 @@ -0,0 +1,7 @@ +# Gitea SSH Key Authentication +# Managed by Ansible - do not edit directly + +Match User {{ git_user }} + AuthorizedKeysFile none + AuthorizedKeysCommandUser {{ git_user }} + AuthorizedKeysCommand /usr/local/bin/gitea-authorized-keys %u %t %k diff --git a/ansible/roles/podman/defaults/main.yml b/ansible/roles/podman/defaults/main.yml index 5ecd67e..9677555 100644 --- a/ansible/roles/podman/defaults/main.yml +++ b/ansible/roles/podman/defaults/main.yml @@ -16,6 +16,28 @@ partsy_path: "{{ podman_volumes }}/partsy" photos_path: "{{ podman_volumes }}/photos" uptime_kuma_path: "{{ podman_volumes }}/uptime-kuma" zomboid_path: "{{ podman_volumes }}/zomboid" + +# Zomboid server mode: 'vanilla' or 'modded' +zomboid_server_mode: modded + +# Zomboid Discord integration (channel for /all chat relay) +zomboid_discord_channel_id: "1306000323642654751" + +# Zomboid RCON port for remote administration +zomboid_rcon_port: "27015" + +# Server names for each mode +zomboid_server_names: + vanilla: zomboid + modded: moddedjoboid + +# Mod configuration for modded server (deduplicated) +zomboid_mods: + workshop_items: >- + 3403870858;3171167894;3330403100;2409333430;3073430075;3379334330;3110913021;3366300557;3034636011;3409287192;3005903549;3161951724;3413704851;3413706334;3287727378;3226885926;2625625421;3418252689;3418253716;3152529790;2478247379;2942793445;2991201484;2913633066;2873290424;3428008364;3253385114;2846036306;2642541073;3435796523;3008795514;3447272250;3026723485;2900580391;2937786633;2870394916;3292659291;2969343830;2566953935;2962175696;3196180339;3258343790;3346905070;3320947974;3478633453;2952802178;3001592312;3052360250;3490370700;2932547723;2805630347;3504401781;2772575623;3110911330;3088951320;3213391371;2932549988;3041122351;2971246021;3539691958;3315443103;2886832257;2886832936;2886833398;2811383142;2799152995;3248388837;3566868353;3570973322;2897390033;3592777775;3596903773;3601417745;3614034284;3577903007;3480990544;3602388131;2463499011;3407042038;3405178154;3402493701;3402812859;3402491515;3430172149;3543229299;3616536783;3431734923;3429790870;2850935956;3307376332;3397182976;3432928943;3610005735;3540297822;3422418897;3426448380;3579640010;3389448389;3393821407;3044705007;2866258937;2544353492;3490188370;3508537032;3451167732;3461263912;2903771337 + # Build 42 requires backslash prefix for each mod ID + mod_ids: >- + \LifestyleHobbies;\damnlib;\KI5trailers;\91range;\93fordF350;\82porsche911;\90bmwE30;\91fordLTD;\89dodgeCaravan;\84jeepXJ;\63beetle;\76chevyKseries;\85chevyCaprice;\85pontiacParisienne;\92jeepYJ;\92jeepYJJP18;\87buickRegal;\isoContainers;\85buickLeSabre;\85oldsmobileDelta88;\93chevySuburban;\93chevySuburbanExpanded;\67commando;\90pierceArrow;\69camaro;\70barracuda;\70dodge;\86chevyCUCV;\81deloreanDMC12;\81deloreanDMC12BTTF;\92nissanGTR;\92amgeneralM998;\88toyotaHilux;\91geoMetro;\66pontiacLeMans;\67gt500;\49powerWagon;\69mini;\69mini_ItalianJob;\69mini_MrBean;\69mini_PitbullSpecial;\86fordE150;\86fordE150dnd;\86fordE150mm;\86fordE150pd;\86fordE150expanded;\89volvo200;\93fordElgin;\86oshkoshP19A;\92fordCVPI;\87chevySuburban;\68firebird;\77firebird;\82firebird;\82firebirdKITT;\04vwTouran;\90fordF350ambulance;\93mustangSSP;\87toyotaMR2;\73fordFalcon;\73fordFalconPS;\93townCar;\84merc;\91nissan240sx;\59meteor;\ECTO1;\87fordB700;\93fordTaurus;\75grandPrix;\89trooper;\63Type2Van;\99fordCVPI;\91fordRanger;\98stagea;\82jeepJ10;\82jeepJ10t;\88chevyS10;\89fordBronco;\83amgeneralM923;\78amgeneralM35A2;\78amgeneralM35A2extra;\78amgeneralM49A2C;\78amgeneralM50A3;\78amgeneralM62;\80manKat1;\65banshee;\89defender;\97bushmaster;\84cadillacDeVille;\84buickElectra;\84oldsmobile98;\85chevyStepVan;\85chevyStepVanexpanded;\VanillaFoodsExpanded;\Constown42;\Greenleaf B42 version;\42Grapeseed;\ATA_Jeep;\ATA_Jeep_x10;\ATA_Jeep_x2;\ATA_Jeep_x4;\ATA_Mustang;\ATA_Mustang_x2;\ATA_Mustang_x4;\autotsartrailers;\ATA_Bus;\tsarslib;\flipvehicleplustrailer;\PROJECTRVInterior42;\TombWardrobeALT;\TombWardrobeALTVanilla;\TombBody;\TombBodyCustom;\TombBodyTex;\TombBodyTexDOLL;\TombBodyTexNUDE;\SM4BootsExpandedB42;\SM4BootsExpandedFlatshoes;\GanydeBielovzki's Frockin Splendor!;\RandomClothing;\EFTBP;\AliceGear;\TableSaw;\Ahu'sToolWeapon42.13;\stanks_suicide;\STA_PryOpen;\AutoReload;\DBFaster50;\DBFaster60;\DBFaster70;\DBFaster80;\FixBlowTorchPropaneTank;\MiniHealthPanel;\P4HasBeenRead;\Project_Cook;\NeatUI_Framework;\ModernStatus;\CleanHotBar;\REORDER_THE_HOTBAR pihole_path: "{{ podman_volumes }}/pihole" sshpass_cron_path: "{{ podman_volumes }}/sshpass_cron" caddy_path: "{{ podman_volumes }}/caddy" @@ -43,6 +65,7 @@ cloud_server_name_io: cloud.debyl.io home_server_name_io: home.debyl.io parts_server_name_io: parts.debyl.io photos_server_name_io: photos.debyl.io +gitea_debyl_server_name: git.debyl.io # Legacy nginx/ModSecurity configuration removed - Caddy provides built-in security diff --git a/ansible/roles/podman/tasks/containers/base/awsddns.yml b/ansible/roles/podman/tasks/containers/base/awsddns.yml index 74dc68a..ebf4307 100644 --- a/ansible/roles/podman/tasks/containers/base/awsddns.yml +++ b/ansible/roles/podman/tasks/containers/base/awsddns.yml @@ -105,4 +105,6 @@ - name: create systemd startup job for awsddns-debyl include_tasks: podman/systemd-generate.yml vars: - container_name: awsddns-debyl \ No newline at end of file + container_name: awsddns-debyl + +# NOTE: git.debyl.io is an ALIAS record to home.debyl.io - no DDNS needed \ No newline at end of file diff --git a/ansible/roles/podman/tasks/containers/home/.zomboid.yml.kate-swp b/ansible/roles/podman/tasks/containers/home/.zomboid.yml.kate-swp new file mode 100644 index 0000000..e69de29 diff --git a/ansible/roles/podman/tasks/containers/home/zomboid.yml b/ansible/roles/podman/tasks/containers/home/zomboid.yml index e7a1b46..35c8f85 100644 --- a/ansible/roles/podman/tasks/containers/home/zomboid.yml +++ b/ansible/roles/podman/tasks/containers/home/zomboid.yml @@ -23,6 +23,26 @@ 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 + +# 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 @@ -40,19 +60,23 @@ restart_policy: on-failure:3 log_driver: journald env: - SERVER_NAME: zomboid + SERVER_NAME: "{{ zomboid_server_names[zomboid_server_mode] }}" MIN_RAM: 8g MAX_RAM: 24g AUTO_UPDATE: "true" ADMIN_PASSWORD: "{{ zomboid_admin_password }}" SERVER_PASSWORD: "{{ zomboid_password }}" + PUID: "1000" + PGID: "1000" volumes: - - "{{ zomboid_path }}/server:/home/steam/pzserver" - - "{{ zomboid_path }}/data:/home/steam/Zomboid" + - "{{ 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 @@ -70,17 +94,73 @@ line: "Restart=always" notify: reload zomboid systemd +# Check if server INI exists (generated on first server run) +- name: check if zomboid server ini exists + become: true + ansible.builtin.stat: + path: "{{ zomboid_path }}/data/Server/{{ zomboid_server_names[zomboid_server_mode] }}.ini" + register: zomboid_ini_stat + tags: zomboid-conf + # Backup settings (requires server to have run once to generate ini) - name: configure zomboid backup settings become: true ansible.builtin.lineinfile: - path: "{{ zomboid_path }}/data/Server/zomboid.ini" + path: "{{ zomboid_path }}/data/Server/{{ zomboid_server_names[zomboid_server_mode] }}.ini" regexp: "^{{ item.key }}=" line: "{{ item.key }}={{ item.value }}" loop: - { key: "SaveWorldEveryMinutes", value: "10" } - { key: "BackupsPeriod", value: "30" } - { key: "BackupsCount", value: "10" } + # B42 Linux server fix: disable Lua checksum to allow mods to load + - { key: "DoLuaChecksum", value: "false" } + # Server password + - { key: "Password", value: "{{ zomboid_password }}" } + when: zomboid_ini_stat.stat.exists + tags: zomboid-conf + +# Discord integration (uses Gregbot token, posts /all chat to Discord) +- name: configure zomboid discord integration + become: true + ansible.builtin.lineinfile: + path: "{{ zomboid_path }}/data/Server/{{ zomboid_server_names[zomboid_server_mode] }}.ini" + regexp: "^{{ item.key }}=" + line: "{{ item.key }}={{ item.value }}" + loop: + - { key: "DiscordEnable", value: "true" } + - { key: "DiscordToken", value: "{{ zomboid_discord_token }}" } + - { key: "DiscordChannel", value: "zomboid" } + - { key: "DiscordChannelID", value: "{{ zomboid_discord_channel_id }}" } + when: zomboid_ini_stat.stat.exists + tags: zomboid-conf + +# RCON configuration for remote administration +- name: configure zomboid rcon + become: true + ansible.builtin.lineinfile: + path: "{{ zomboid_path }}/data/Server/{{ zomboid_server_names[zomboid_server_mode] }}.ini" + regexp: "^{{ item.key }}=" + line: "{{ item.key }}={{ item.value }}" + loop: + - { key: "RCONPort", value: "{{ zomboid_rcon_port }}" } + - { key: "RCONPassword", value: "{{ zomboid_admin_password }}" } + when: zomboid_ini_stat.stat.exists + tags: zomboid-conf + +# Mod configuration (only for modded server profile) +- name: configure zomboid mods for modded server + become: true + ansible.builtin.lineinfile: + path: "{{ zomboid_path }}/data/Server/{{ zomboid_server_names[zomboid_server_mode] }}.ini" + regexp: "^{{ item.key }}=" + line: "{{ item.key }}={{ item.value }}" + loop: + - { key: "Mods", value: "{{ zomboid_mods.mod_ids }}" } + - { key: "WorkshopItems", value: "{{ zomboid_mods.workshop_items }}" } + when: + - zomboid_server_mode == 'modded' + - zomboid_ini_stat.stat.exists tags: zomboid-conf # World reset tasks REMOVED - too dangerous to have in automation diff --git a/ansible/roles/podman/tasks/main.yml b/ansible/roles/podman/tasks/main.yml index ce7b45b..10391d7 100644 --- a/ansible/roles/podman/tasks/main.yml +++ b/ansible/roles/podman/tasks/main.yml @@ -54,9 +54,9 @@ - import_tasks: containers/home/photos.yml vars: db_image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0 - ml_image: ghcr.io/immich-app/immich-machine-learning:v2.3.1 + ml_image: ghcr.io/immich-app/immich-machine-learning:v2.4.0 redis_image: docker.io/redis:6.2-alpine@sha256:eaba718fecd1196d88533de7ba49bf903ad33664a92debb24660a922ecd9cac8 - image: ghcr.io/immich-app/immich-server:v2.3.1 + image: ghcr.io/immich-app/immich-server:v2.4.0 tags: photos - import_tasks: containers/home/cloud.yml diff --git a/ansible/roles/podman/templates/caddy/Caddyfile.j2 b/ansible/roles/podman/templates/caddy/Caddyfile.j2 index b216960..2040757 100644 --- a/ansible/roles/podman/templates/caddy/Caddyfile.j2 +++ b/ansible/roles/podman/templates/caddy/Caddyfile.j2 @@ -243,6 +243,20 @@ } } +# Gitea - {{ gitea_debyl_server_name }} +{{ gitea_debyl_server_name }} { + import common_headers + + reverse_proxy localhost:3100 { + flush_interval -1 + } + + log { + output file /var/log/caddy/gitea-debyl.log + format json + } +} + # Fulfillr - {{ fulfillr_server_name }} (Static + API with IP restrictions) {{ fulfillr_server_name }} { {{ ip_restricted_site() }} diff --git a/ansible/roles/podman/templates/zomboid/entrypoint.sh.j2 b/ansible/roles/podman/templates/zomboid/entrypoint.sh.j2 index 6330790..be78b62 100644 --- a/ansible/roles/podman/templates/zomboid/entrypoint.sh.j2 +++ b/ansible/roles/podman/templates/zomboid/entrypoint.sh.j2 @@ -1,103 +1,89 @@ #!/bin/bash +# Project Zomboid Build 42 Server Entrypoint +# Based on IndifferentBroccoli/projectzomboid-server-docker set -e +# Configuration +INSTALL_DIR="/project-zomboid" +CONFIG_DIR="/project-zomboid-config" STEAMCMD="/home/steam/steamcmd/steamcmd.sh" -INSTALL_DIR="/home/steam/pzserver" -DATA_DIR="/home/steam/Zomboid" SERVER_NAME="${SERVER_NAME:-zomboid}" -MIN_RAM="${MIN_RAM:-8g}" -MAX_RAM="${MAX_RAM:-24g}" +PUID="${PUID:-1000}" +PGID="${PGID:-1000}" +MIN_RAM="${MIN_RAM:-4g}" +MAX_RAM="${MAX_RAM:-8g}" echo "=== Project Zomboid Build 42 Server ===" echo "Server Name: ${SERVER_NAME}" echo "RAM: ${MIN_RAM} - ${MAX_RAM}" -# Fix ownership of mounted volumes (container runs as steam user, UID 1000) -echo "=== Fixing volume permissions ===" -chown -R steam:steam "${INSTALL_DIR}" || true -chown -R steam:steam "${DATA_DIR}" || true -chmod -R 755 "${INSTALL_DIR}" || true -chmod -R 755 "${DATA_DIR}" || true +# Set user permissions (IndifferentBroccoli approach) +echo "=== Setting file permissions ===" +usermod -o -u "${PUID}" steam +groupmod -o -g "${PGID}" steam +chown -R steam:steam "${INSTALL_DIR}" "${CONFIG_DIR}" +# Only chown writable parts of /home/steam (not read-only mounts) +chown steam:steam /home/steam +chown -R steam:steam /home/steam/steamcmd 2>/dev/null || true +chown -R steam:steam /home/steam/Steam 2>/dev/null || true -# Create required subdirectories with correct ownership -mkdir -p "${DATA_DIR}/Server" -mkdir -p "${DATA_DIR}/Saves/Multiplayer" -mkdir -p "${DATA_DIR}/db" -chown -R steam:steam "${DATA_DIR}" - -# Ensure steam user has proper home directory setup -export HOME=/home/steam - -# Initialize SteamCMD if needed (creates config directories) -if [ ! -d "/home/steam/Steam" ]; then - echo "=== Initializing SteamCMD ===" - su -c "${STEAMCMD} +quit" steam || true -fi +# Create required directories +mkdir -p "${CONFIG_DIR}/Server" +mkdir -p "${CONFIG_DIR}/Saves" +mkdir -p "${CONFIG_DIR}/db" # Update/Install PZ dedicated server with Build 42 unstable branch if [ "${AUTO_UPDATE:-true}" = "true" ]; then echo "=== Updating Project Zomboid Server (Build 42 unstable) ===" - # Run steamcmd as steam user with proper quoting for beta flag - su -c "${STEAMCMD} +force_install_dir ${INSTALL_DIR} +login anonymous +app_update 380870 -beta unstable validate +quit" steam + su -c "${STEAMCMD} +runscript /home/steam/install.scmd" steam echo "=== Update complete ===" fi -# Ensure data directories exist (created earlier with correct permissions) +# Configure JVM memory settings in ProjectZomboid64.json (Build 42 uses JSON config) +configure_memory() { + local json_file="${INSTALL_DIR}/ProjectZomboid64.json" -# Configure server settings on first run -SERVER_INI="${DATA_DIR}/Server/${SERVER_NAME}.ini" -if [ ! -f "${SERVER_INI}" ]; then - echo "=== First run detected, server will generate default config ===" -fi + if [ ! -f "$json_file" ]; then + echo "=== ProjectZomboid64.json not found, skipping memory config ===" + return 0 + fi -# Handle admin password for first run -# PZ requires interactive password input on first run, so we create a db file -ADMIN_DB="${DATA_DIR}/db/${SERVER_NAME}.db" -if [ ! -f "${ADMIN_DB}" ] && [ -n "${ADMIN_PASSWORD}" ]; then - echo "=== Setting up admin account ===" - mkdir -p "${DATA_DIR}/db" - # The server will prompt for password on first run - # We'll use expect-like behavior or let it use defaults -fi + echo "=== Configuring JVM memory: Xms=${MIN_RAM}, Xmx=${MAX_RAM} ===" -# Modify memory settings in ProjectZomboid64.json (Build 42 uses JSON config) -PZ_JSON="${INSTALL_DIR}/ProjectZomboid64.json" -if [ -f "${PZ_JSON}" ]; then - echo "=== Setting JVM memory: Xms=${MIN_RAM}, Xmx=${MAX_RAM} ===" - # Add -Xms if not present, otherwise update it - if grep -q "\-Xms" "${PZ_JSON}"; then - sed -i "s/-Xms[0-9]*[gGmM]*/-Xms${MIN_RAM}/g" "${PZ_JSON}" + # Update Xmx + sed -i "s/-Xmx[0-9]*[gGmM]*/-Xmx${MAX_RAM}/g" "$json_file" + + # Update or add Xms + if grep -q "\-Xms" "$json_file"; then + sed -i "s/-Xms[0-9]*[gGmM]*/-Xms${MIN_RAM}/g" "$json_file" else # Insert -Xms before -Xmx - sed -i "s/\"-Xmx/\"-Xms${MIN_RAM}\",\n\t\t\"-Xmx/g" "${PZ_JSON}" + sed -i "s/\"-Xmx/\"-Xms${MIN_RAM}\",\n\t\t\"-Xmx/g" "$json_file" fi - sed -i "s/-Xmx[0-9]*[gGmM]*/-Xmx${MAX_RAM}/g" "${PZ_JSON}" + + echo "=== Memory configuration complete ===" +} +configure_memory + +# Check if first run (no admin DB) +ADMIN_DB="${CONFIG_DIR}/db/${SERVER_NAME}.db" + +# Build server arguments +# Note: -modfolders is NOT used - mods are configured via INI only +# Reference: IndifferentBroccoli/projectzomboid-server-docker +SERVER_ARGS="-cachedir=${CONFIG_DIR} -servername ${SERVER_NAME}" + +# Add admin password for first run +if [ ! -f "${ADMIN_DB}" ] && [ -n "${ADMIN_PASSWORD}" ]; then + echo "=== First run: setting admin password ===" + SERVER_ARGS="${SERVER_ARGS} -adminpassword ${ADMIN_PASSWORD}" fi -# If server password is set, we'll need to configure it in the ini after first run -# For now, store it for later configuration -if [ -n "${SERVER_PASSWORD}" ]; then - echo "${SERVER_PASSWORD}" > "${DATA_DIR}/.server_password" -fi +# Note: Server password is set via INI file, not command line args -if [ -n "${ADMIN_PASSWORD}" ]; then - echo "${ADMIN_PASSWORD}" > "${DATA_DIR}/.admin_password" -fi - -# Change to install directory and start server +# Start server cd "${INSTALL_DIR}" - echo "=== Starting Project Zomboid Server ===" echo "Connect to: home.bdebyl.net:16261" -# Start server - on first run this will prompt for admin password -# We handle this by providing input via stdin if password file exists -if [ -f "${DATA_DIR}/.admin_password" ] && [ ! -f "${ADMIN_DB}" ]; then - # First run with admin password - ADMIN_PASS=$(cat "${DATA_DIR}/.admin_password") - echo "=== First run: setting admin password ===" - printf "%s\n%s\n" "${ADMIN_PASS}" "${ADMIN_PASS}" | su -c "bash start-server.sh -servername ${SERVER_NAME}" steam -else - # Normal run - exec su -c "bash start-server.sh -servername ${SERVER_NAME}" steam -fi +exec su -c "export LD_LIBRARY_PATH=${INSTALL_DIR}/jre64/lib:\${LD_LIBRARY_PATH} && ./start-server.sh ${SERVER_ARGS}" steam diff --git a/ansible/roles/podman/templates/zomboid/install.scmd.j2 b/ansible/roles/podman/templates/zomboid/install.scmd.j2 new file mode 100644 index 0000000..3855ebd --- /dev/null +++ b/ansible/roles/podman/templates/zomboid/install.scmd.j2 @@ -0,0 +1,18 @@ +// SteamCMD script for Project Zomboid Server installation +// Based on IndifferentBroccoli/projectzomboid-server-docker + +// Do not shutdown on a failed command +@ShutdownOnFailedCommand 0 + +// No password prompt as this is unattended +@NoPromptForPassword 1 + +// Set the game installation directory +force_install_dir /project-zomboid + +login anonymous + +// Install/Update the Project Zomboid Dedicated Server - Unstable Branch (Build 42) +app_update 380870 -beta unstable validate + +quit diff --git a/ansible/vars/vault.yml b/ansible/vars/vault.yml index be73d20..e3a714c 100644 Binary files a/ansible/vars/vault.yml and b/ansible/vars/vault.yml differ