create new geyser branch

This commit is contained in:
2026-09-12 12:11:58 -06:00
parent 3c80aef53b
commit a2deef0f5a
23 changed files with 0 additions and 1648 deletions

103
Makefile
View File

@@ -1,103 +0,0 @@
# =============================================================================
# Minecraft Podman Deployment - Makefile
# =============================================================================
# Usage:
# make install # Install dependencies
# make deploy # Deploy Minecraft server
# make check # Dry-run (check mode)
# make debug # Verbose output
# make status # Check server status
# make backup # Run manual backup
# make restore # Restore from latest backup
# make logs # View server logs
# make stop # Stop Minecraft server
# make start # Start Minecraft server
# make restart # Restart Minecraft server
# make update # Update Minecraft to latest version
# make uninstall # Remove Minecraft deployment (dangerous!)
# =============================================================================
PLAYBOOK := site.yml
LIMIT ?= external-apps
INVENTORY := inventory/hosts
.PHONY: help install deploy check debug status backup restore logs stop start restart update uninstall
help:
@echo "Minecraft Podman Deployment Makefile"
@echo ""
@echo "Usage: make [target]"
@echo ""
@echo "Targets:"
@echo " install Install Ansible collections and dependencies"
@echo " deploy Deploy Minecraft server (default: $(LIMIT))"
@echo " check Dry-run deployment (--check mode)"
@echo " debug Deploy with verbose output"
@echo " status Check server status on remote"
@echo " backup Run manual backup on remote"
@echo " restore Restore from latest backup on remote"
@echo " logs View Minecraft server logs"
@echo " stop Stop Minecraft server"
@echo " start Start Minecraft server"
@echo " restart Restart Minecraft server"
@echo " update Update Minecraft to latest version"
@echo " uninstall Remove Minecraft deployment (DANGEROUS)"
@echo ""
@echo "Override target host: make deploy LIMIT=my-server"
install:
@echo "Installing Ansible collections..."
ansible-galaxy collection install -r requirements.yml
deploy:
@echo "Deploying Minecraft to $(LIMIT)..."
ansible-playbook $(PLAYBOOK) --limit $(LIMIT)
check:
@echo "Dry-run deployment for $(LIMIT)..."
ansible-playbook $(PLAYBOOK) --limit $(LIMIT) --check
debug:
@echo "Deploying with debug output for $(LIMIT)..."
ansible-playbook $(PLAYBOOK) --limit $(LIMIT) -vvv
status:
@echo "Checking server status on $(LIMIT)..."
ansible $(LIMIT) -m shell -a 'systemctl status minecraft --no-pager'
backup:
@echo "Running backup on $(LIMIT)..."
ansible $(LIMIT) -m shell -a '/opt/minecraft/backup.sh'
restore:
@echo "Running restore on $(LIMIT)..."
ansible $(LIMIT) -m shell -a '/opt/minecraft/restore.sh'
logs:
@echo "Viewing logs for $(LIMIT)..."
ansible $(LIMIT) -m shell -a 'journalctl -u minecraft -n 100 --no-pager'
stop:
@echo "Stopping Minecraft server on $(LIMIT)..."
ansible $(LIMIT) -m systemd -a 'name=minecraft state=stopped'
start:
@echo "Starting Minecraft server on $(LIMIT)..."
ansible $(LIMIT) -m systemd -a 'name=minecraft state=started'
restart:
@echo "Restarting Minecraft server on $(LIMIT)..."
ansible $(LIMIT) -m systemd -a 'name=minecraft state=restarted'
update:
@echo "Updating Minecraft on $(LIMIT)..."
ansible-playbook $(PLAYBOOK) --limit $(LIMIT) --tags update
uninstall:
@echo "WARNING: This will remove the Minecraft deployment from $(LIMIT)"
@read -p "Are you sure? Type 'yes' to continue: " CONFIRM; \
if [ "$$CONFIRM" = "yes" ]; then \
ansible-playbook $(PLAYBOOK) --limit $(LIMIT) --tags uninstall; \
else \
echo "Aborted."; \
fi

190
README.md
View File

@@ -1,190 +0,0 @@
# Minecraft Podman Deployment (Ansible)
Automated deployment of a Minecraft server running in a **Podman** container on a remote **Fedora** server using **Ansible**.
## Architecture
```
┌─────────────────────────────────────────────────┐
│ Ansible Control Node │
│ (your machine) │
│ ansible.cfg + site.yml + roles/ │
└──────────────────────┬──────────────────────────┘
│ SSH
┌──────────────────────▼──────────────────────────┐
│ Fedora Server │
│ "external-apps" │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ Podman Container │ │
│ │ itzg/minecraft-server:latest (Paper) │ │
│ │ ┌────────────────────────────────────┐ │ │
│ │ │ Minecraft Server (Java) │ │ │
│ │ └────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────┘ │
│ │
│ systemd: minecraft.service │
│ systemd: minecraft-backup.timer │
│ data: /opt/minecraft/data │
│ backups: /opt/minecraft/backups │
└─────────────────────────────────────────────────┘
```
## Directory Structure
```
external-minecraft/
├── ansible.cfg # Ansible configuration
├── site.yml # Main deployment playbook
├── uninstall.yml # Cleanup playbook
├── requirements.yml # Galaxy collections
├── Makefile # Convenient wrapper
├── inventory/
│ └── hosts # Server inventory
├── group_vars/
│ └── all.yml # All configuration variables
├── host_vars/ # (optional) Per-host overrides
└── roles/
└── minecraft-podman/
├── tasks/main.yml # All deployment tasks
├── handlers/main.yml # Service handlers
├── templates/ # Jinja2 templates
│ ├── minecraft.service.j2
│ ├── minecraft.env.j2
│ ├── server.properties.j2
│ ├── Containerfile.j2
│ ├── backup.sh.j2
│ ├── restore.sh.j2
│ ├── health-check.sh.j2
│ └── jvm.opts.j2
└── defaults/main.yml # Role defaults
```
## Prerequisites
### On the control node (your machine)
```bash
# Fedora/RHEL/CentOS
sudo dnf install python3-pip
pip3 install --user ansible
# or via pipx
pipx install ansible
# Debian/Ubuntu
sudo apt install ansible
```
### Install Ansible collections
```bash
make install
# or
ansible-galaxy collection install -r requirements.yml
```
## Configuration
Edit `group_vars/all.yml` to configure your server:
| Variable | Description | Default |
|----------|-------------|---------|
| `minecraft.server_name` | Service/container name | `minecraft` |
| `minecraft.version` | Server version | `latest` |
| `minecraft.server_type` | Vanilla / Paper / etc. | `paper` |
| `minecraft.java_memory` | Java heap size | `4G` |
| `minecraft.max_players` | Max players | `20` |
| `minecraft.port` | Minecraft port | `25565` |
| `minecraft.rcon_password` | RCON password | `CHANGE_ME_NOW` |
| `minecraft.motd` | Server message of day | Welcome message |
| `paths.base_dir` | Install directory | `/opt/minecraft` |
| `backup.enabled` | Enable automatic backups | `true` |
**IMPORTANT**: Change `minecraft.rcon_password` before deploying.
## Deployment
```bash
# Basic deployment
make deploy
# With explicit host
make deploy LIMIT=external-apps
# Or directly
ansible-playbook site.yml --limit external-apps
# Dry-run
make check
# Debug mode
make debug
```
## Management
```bash
make status # Check server status
make logs # View recent logs
make restart # Restart the server
make stop # Stop the server
make start # Start the server
make backup # Run backup immediately
```
## Connecting to the Server
In Minecraft, use:
```
<server-ip>:<port>
```
For the default setup: `external-apps:25565`
## Backups
Backups run automatically every 6 hours (configurable in `group_vars/all.yml` under `backup.schedule`).
- **Location**: `/opt/minecraft/backups/`
- **Format**: `minecraft_YYYYMMDD_HHMMSS.tar.gz`
- **Retention**: 10 backups (configurable)
Restore with:
```bash
make restore
```
## Uninstall
```bash
make uninstall # Interactive confirmation
```
Or manually:
```bash
ansible-playbook uninstall.yml --limit external-apps
```
## Advanced Configuration
### Custom Server Type
Set `minecraft.server_type` to:
- `vanilla` - Official Mojang server
- `paper` - High-performance fork (recommended)
- `fabric` - Modding platform
- `forge` - Classic modding
- `purpur` - Paper fork with more features
### Custom Container Image
To use your own `Containerfile` instead of pulling a public image:
1. Set `minecraft.use_custom_container: true` in `group_vars`
2. The role will build and deploy a custom image
### Extras
- **RCON**: Enabled by default on port 25575. Allows remote console admin.
- **Whitelist**: Set `minecraft.whitelist_enabled: true` to require whitelist
- **Command Blocks**: Set `minecraft.enable_command_block: true` to enable

View File

@@ -1,18 +0,0 @@
[defaults]
inventory = inventory/hosts
roles_path = roles
remote_user = boxswapper
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
timeout = 30
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ServerAliveInterval=30

View File

@@ -1,162 +0,0 @@
---
# =============================================================================
# Minecraft Podman Deployment - Global Variables
# =============================================================================
# All variables defined here can be overridden in host_vars/<hostname>.yml
# or passed at runtime with -e "variable=value"
# -----------------------------------------------------------------------------
# Server Configuration
# -----------------------------------------------------------------------------
minecraft:
# Server name - used for systemd service naming and container name
server_name: "minecraft"
# Minecraft version (latest, 1.21.4, 1.20.6, etc.)
version: "latest"
# Server type: "vanilla", "paper", "fabric", "forge", "purpur"
server_type: "paper"
# Memory allocation for Java (e.g., "4G", "8G", "2G")
java_memory: "4G"
# Java JVM arguments (additional flags beyond memory)
java_args: "-XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200"
# Maximum players allowed
max_players: 20
# Server port (default Minecraft port: 25565)
port: 25565
# RCON port (for remote console access)
rcon_port: 25575
# Enable RCON (useful for management)
rcon_enabled: true
# RCON password (CHANGE THIS!)
rcon_password: "CHANGE_ME_NOW"
# Server MOTD (Message of the Day)
motd: "Welcome to the Minecraft Server!"
# Game mode: survival, creative, adventure, spectator
game_mode: "survival"
# Difficulty: peaceful, easy, normal, hard
difficulty: "normal"
# Enable whitelist
whitelist_enabled: false
# Allow command blocks
enable_command_block: false
# View distance (chunks, max 32)
view_distance: 10
# Simulation distance (chunks)
simulation_distance: 6
# Online mode (true = requires Mojang account verification)
online_mode: true
# Max world size (blocks, default 29999984)
max_world_size: 29999984
# Spawn protection (blocks around spawn that can't be modified)
spawn_protection: 16
# -----------------------------------------------------------------------------
# Podman Configuration
# -----------------------------------------------------------------------------
podman:
# Container image (itzg/minecraft-server is highly configurable)
image: "itzg/minecraft-server"
# Image tag (usually the server type)
image_tag: "latest"
# Container restart policy: always, unless-stopped, on-failure, no
restart_policy: "unless-stopped"
# Container memory limit (leave empty for unlimited)
memory_limit: ""
# CPU shares (relative weight)
cpu_shares: 1024
# Container log driver
log_driver: "journald"
# Container log max size
log_max_size: "50m"
# Container log max file count
log_max_file: "3"
# Extra volumes to mount (format: "host_path:container_path:options")
extra_volumes: []
# Container labels
labels:
managed-by: "ansible"
app: "minecraft"
# Network mode (host, bridge, or slirp4netns)
network_mode: "host"
# -----------------------------------------------------------------------------
# Path Configuration
# -----------------------------------------------------------------------------
paths:
# Base installation directory
base_dir: "/opt/minecraft"
# Data directory (where world, configs, etc. are stored)
data_dir: "/opt/minecraft/data"
# Backup directory
backup_dir: "/opt/minecraft/backups"
# Systemd service directory
systemd_dir: "/etc/systemd/system"
# -----------------------------------------------------------------------------
# Backup Configuration
# -----------------------------------------------------------------------------
backup:
# Enable automatic backups
enabled: true
# Backup schedule (cron format)
schedule: "0 */6 * * *" # Every 6 hours
# Number of backups to retain
retention_count: 10
# Compress backups with gzip
compress: true
# -----------------------------------------------------------------------------
# Firewall Configuration
# -----------------------------------------------------------------------------
firewall:
# Open Minecraft ports in firewalld
enabled: true
# Additional ports to open (for mods, voice chat, etc.)
additional_ports:
- "19132/udp" # Bedrock (if needed)
# -----------------------------------------------------------------------------
# Monitoring (Optional)
# -----------------------------------------------------------------------------
monitoring:
# Enable basic health checks
enabled: false
# Health check interval in seconds
interval: 300

View File

@@ -1,9 +0,0 @@
# Ansible Inventory - External Minecraft Server
# Managed by Sisyphus for Minecraft Podman Deployment
[minecraft]
external-apps ansible_host=external-apps ansible_user=boxswapper
[minecraft:vars]
ansible_python_interpreter=/usr/bin/python3
ansible_ssh_common_args='-o StrictHostKeyChecking=no'

View File

@@ -1,9 +0,0 @@
---
# Ansible Galaxy collections required by this deployment
# Install with: ansible-galaxy collection install -r requirements.yml
collections:
- name: ansible.posix
version: ">=1.5.0"
- name: containers.podman
version: ">=1.10.0"

View File

@@ -1,19 +0,0 @@
---
# =============================================================================
# Minecraft Podman Deployment - Role Defaults
# =============================================================================
# These are the default values for this role. They can be overridden by
# group_vars/all.yml, host_vars/<host>.yml, or -e at runtime.
# =============================================================================
# User to run the Minecraft container as
minecraft_user: "minecraft"
# Podman container name
podman_container_name: "minecraft"
# Whether to use a custom Containerfile (true) or pull pre-built image (false)
minecraft_use_custom_container: "{{ minecraft.use_custom_container | default(false) }}"
# Custom image name (only used if use_custom_container is true)
minecraft_custom_image_name: "minecraft-custom"

View File

@@ -1,25 +0,0 @@
---
# =============================================================================
# Minecraft Podman Deployment - Handlers
# =============================================================================
- name: restart minecraft
ansible.builtin.systemd:
name: "{{ minecraft.server_name }}.service"
state: restarted
daemon_reload: true
- name: reload systemd
ansible.builtin.systemd:
daemon_reload: true
- name: restart firewalld
ansible.builtin.service:
name: firewalld
state: restarted
- name: reload backup timer
ansible.builtin.systemd:
name: "{{ minecraft.server_name }}-backup.timer"
state: restarted
daemon_reload: true

View File

@@ -1,423 +0,0 @@
---
# =============================================================================
# Minecraft Podman Deployment - Main Tasks
# =============================================================================
# This role deploys a Minecraft server using Podman on Fedora
# with systemd integration for service management
# -----------------------------------------------------------------------------
# System Preparation
# -----------------------------------------------------------------------------
- name: System Preparation
block:
- name: Update DNF cache
ansible.builtin.dnf:
update_cache: true
state: present
tags: [install, packages]
- name: Install required packages
ansible.builtin.dnf:
name:
- podman
- podman-plugins # Podman plugins (Netavark, etc.)
- fuse-overlayfs # Required for rootless storage
- slirp4netns # Required for rootless networking
- systemd-container # Systemd integration
- firewalld
- python3-firewall # For ansible firewalld module
- python3-podman # For ansible podman modules
- tar # For backups
- gzip # For compressed backups
- curl # For health checks
state: present
tags: [install, packages]
- name: Ensure Podman is installed
ansible.builtin.command: podman --version
register: podman_version
changed_when: false
tags: [install, verify]
- name: Display Podman version
ansible.builtin.debug:
msg: "Podman {{ podman_version.stdout }} installed"
tags: [install, verify]
tags: [install, packages]
# -----------------------------------------------------------------------------
# User and Permissions
# -----------------------------------------------------------------------------
- name: User Configuration
block:
- name: Create minecraft system user
ansible.builtin.user:
name: "{{ minecraft_user }}"
system: true
shell: /bin/bash
home: "{{ paths.base_dir }}"
create_home: false
comment: "Minecraft Server User"
when: minecraft_user != "root"
tags: [install, user]
- name: Configure subuid for rootless podman
ansible.builtin.lineinfile:
path: /etc/subuid
line: "{{ minecraft_user }}:100000:65536"
regexp: "^{{ minecraft_user }}:"
state: present
create: true
mode: "0644"
when: minecraft_user != "root"
tags: [install, user]
- name: Configure subgid for rootless podman
ansible.builtin.lineinfile:
path: /etc/subgid
line: "{{ minecraft_user }}:100000:65536"
regexp: "^{{ minecraft_user }}:"
state: present
create: true
mode: "0644"
when: minecraft_user != "root"
tags: [install, user]
- name: Create user config directory for podman
ansible.builtin.file:
path: "{{ paths.base_dir }}/.config/containers"
state: directory
owner: "{{ minecraft_user }}"
group: "{{ minecraft_user }}"
mode: "0755"
when: minecraft_user != "root"
tags: [install, user]
- name: Create user local share for podman storage
ansible.builtin.file:
path: "{{ paths.base_dir }}/.local/share/containers"
state: directory
owner: "{{ minecraft_user }}"
group: "{{ minecraft_user }}"
mode: "0755"
when: minecraft_user != "root"
tags: [install, user]
- name: Ensure systemd lingering is enabled for user
ansible.builtin.command: "loginctl enable-linger {{ minecraft_user }}"
changed_when: false
when: minecraft_user != "root"
tags: [install, user]
tags: [install]
# -----------------------------------------------------------------------------
# Directory Structure
# -----------------------------------------------------------------------------
- name: Directory Setup
block:
- name: Create base directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ minecraft_user }}"
group: "{{ minecraft_user }}"
mode: "0755"
loop:
- "{{ paths.base_dir }}"
- "{{ paths.data_dir }}"
- "{{ paths.backup_dir }}"
- "{{ paths.data_dir }}/logs"
- "{{ paths.data_dir }}/world"
tags: [install, directories]
- name: Create Containerfile directory
ansible.builtin.file:
path: "{{ paths.base_dir }}/container"
state: directory
owner: "{{ minecraft_user }}"
group: "{{ minecraft_user }}"
mode: "0755"
tags: [install, container]
tags: [install]
# -----------------------------------------------------------------------------
# Container Build (if using custom Containerfile)
# -----------------------------------------------------------------------------
- name: Container Build
block:
- name: Copy Containerfile
ansible.builtin.template:
src: Containerfile.j2
dest: "{{ paths.base_dir }}/container/Containerfile"
owner: "{{ minecraft_user }}"
group: "{{ minecraft_user }}"
mode: "0644"
when: minecraft.use_custom_container | default(false)
tags: [install, container]
- name: Build custom Minecraft container image
containers.podman.podman_image:
name: "{{ minecraft.custom_image_name | default('minecraft-custom') }}"
build:
path: "{{ paths.base_dir }}/container/"
file: Containerfile
tag: "{{ minecraft.version }}"
state: present
when: minecraft.use_custom_container | default(false)
tags: [install, container]
tags: [install]
# -----------------------------------------------------------------------------
# Server Configuration
# -----------------------------------------------------------------------------
- name: Server Configuration
block:
- name: Deploy server.properties
ansible.builtin.template:
src: server.properties.j2
dest: "{{ paths.data_dir }}/server.properties"
owner: "{{ minecraft_user }}"
group: "{{ minecraft_user }}"
mode: "0644"
notify: restart minecraft
tags: [config]
- name: Deploy eula.txt
ansible.builtin.copy:
content: "eula=true\n"
dest: "{{ paths.data_dir }}/eula.txt"
owner: "{{ minecraft_user }}"
group: "{{ minecraft_user }}"
mode: "0644"
tags: [config]
- name: Deploy environment variables
ansible.builtin.template:
src: minecraft.env.j2
dest: "{{ paths.base_dir }}/minecraft.env"
owner: "{{ minecraft_user }}"
group: "{{ minecraft_user }}"
mode: "0600"
notify: restart minecraft
tags: [config]
- name: Deploy JVM options file
ansible.builtin.template:
src: jvm.opts.j2
dest: "{{ paths.data_dir }}/jvm.opts"
owner: "{{ minecraft_user }}"
group: "{{ minecraft_user }}"
mode: "0644"
when: minecraft.jvm_opts_file | default(false)
notify: restart minecraft
tags: [config]
tags: [config]
# -----------------------------------------------------------------------------
# Firewall Configuration
# -----------------------------------------------------------------------------
- name: Firewall Configuration
block:
- name: Ensure firewalld is running
ansible.builtin.systemd:
name: firewalld
state: started
enabled: true
tags: [firewall]
- name: Open Minecraft port
ansible.posix.firewalld:
port: "{{ minecraft.port }}/tcp"
permanent: true
immediate: true
state: enabled
tags: [firewall]
- name: Open RCON port
ansible.posix.firewalld:
port: "{{ minecraft.rcon_port }}/tcp"
permanent: true
immediate: true
state: enabled
when: minecraft.rcon_enabled
tags: [firewall]
- name: Open additional firewall ports
ansible.posix.firewalld:
port: "{{ item }}"
permanent: true
immediate: true
state: enabled
loop: "{{ firewall.additional_ports | default([]) }}"
tags: [firewall]
tags: [firewall]
# -----------------------------------------------------------------------------
# Systemd Service
# -----------------------------------------------------------------------------
- name: Systemd Service Configuration
block:
- name: Deploy systemd service unit
ansible.builtin.template:
src: minecraft.service.j2
dest: "{{ paths.systemd_dir }}/{{ minecraft.server_name }}.service"
owner: root
group: root
mode: "0644"
notify:
- reload systemd
- restart minecraft
tags: [service]
- name: Reload systemd daemon
ansible.builtin.systemd:
daemon_reload: true
tags: [service]
- name: Enable and start Minecraft service
ansible.builtin.systemd:
name: "{{ minecraft.server_name }}.service"
state: started
enabled: true
tags: [service]
tags: [service]
# -----------------------------------------------------------------------------
# Backup Configuration
# -----------------------------------------------------------------------------
- name: Backup Configuration
block:
- name: Deploy backup script
ansible.builtin.template:
src: backup.sh.j2
dest: "{{ paths.base_dir }}/backup.sh"
owner: "{{ minecraft_user }}"
group: "{{ minecraft_user }}"
mode: "0755"
when: backup.enabled
tags: [backup]
- name: Deploy restore script
ansible.builtin.template:
src: restore.sh.j2
dest: "{{ paths.base_dir }}/restore.sh"
owner: "{{ minecraft_user }}"
group: "{{ minecraft_user }}"
mode: "0755"
when: backup.enabled
tags: [backup]
- name: Create systemd timer for backups
ansible.builtin.template:
src: minecraft-backup.timer.j2
dest: "{{ paths.systemd_dir }}/{{ minecraft.server_name }}-backup.timer"
owner: root
group: root
mode: "0644"
when: backup.enabled
tags: [backup]
- name: Create systemd service for backups
ansible.builtin.template:
src: minecraft-backup.service.j2
dest: "{{ paths.systemd_dir }}/{{ minecraft.server_name }}-backup.service"
owner: root
group: root
mode: "0644"
when: backup.enabled
tags: [backup]
- name: Enable backup timer
ansible.builtin.systemd:
name: "{{ minecraft.server_name }}-backup.timer"
state: started
enabled: true
when: backup.enabled
tags: [backup]
tags: [backup]
# -----------------------------------------------------------------------------
# Health Check
# -----------------------------------------------------------------------------
- name: Health Check Configuration
block:
- name: Deploy health check script
ansible.builtin.template:
src: health-check.sh.j2
dest: "{{ paths.base_dir }}/health-check.sh"
owner: "{{ minecraft_user }}"
group: "{{ minecraft_user }}"
mode: "0755"
when: monitoring.enabled
tags: [monitoring]
- name: Create systemd timer for health checks
ansible.builtin.template:
src: minecraft-health.timer.j2
dest: "{{ paths.systemd_dir }}/{{ minecraft.server_name }}-health.timer"
owner: root
group: root
mode: "0644"
when: monitoring.enabled
tags: [monitoring]
- name: Create systemd service for health checks
ansible.builtin.template:
src: minecraft-health.service.j2
dest: "{{ paths.systemd_dir }}/{{ minecraft.server_name }}-health.service"
owner: root
group: root
mode: "0644"
when: monitoring.enabled
tags: [monitoring]
- name: Enable health check timer
ansible.builtin.systemd:
name: "{{ minecraft.server_name }}-health.timer"
state: started
enabled: true
when: monitoring.enabled
tags: [monitoring]
tags: [monitoring]
# -----------------------------------------------------------------------------
# Final Verification
# -----------------------------------------------------------------------------
- name: Final Verification
block:
- name: Wait for Minecraft server to start
ansible.builtin.uri:
url: "http://localhost:{{ minecraft.port }}"
return_content: false
status_code: [401, 200] # Minecraft responds to HTTP with 401
register: server_check
retries: 30
delay: 5
until: server_check.status in [401, 200]
ignore_errors: true
tags: [verify]
- name: Check service status
ansible.builtin.systemd:
name: "{{ minecraft.server_name }}.service"
register: service_status
tags: [verify]
- name: Display verification results
ansible.builtin.debug:
msg: |
Server Status: {{ 'Running' if service_status.status.ActiveState == 'active' else 'Stopped' }}
Service: {{ service_status.status.SubState }}
Port {{ minecraft.port }}: {{ 'Open' if server_check is succeeded else 'Closed' }}
tags: [verify]
tags: [verify]

View File

@@ -1,28 +0,0 @@
# Custom Minecraft Server Containerfile
# Deployed by Ansible - only used if minecraft.use_custom_container is true
# Base image
FROM itzg/minecraft-server:java{{ minecraft.java_version | default('21') }}
# Custom server type
ENV TYPE={{ minecraft.server_type | upper }}
ENV VERSION={{ minecraft.version }}
# Custom plugins/mods directory (uncomment if using mods)
# VOLUME /data/mods
# VOLUME /data/plugins
# VOLUME /data/config
# Custom entrypoint overrides
COPY server.properties /tmp/server.properties
RUN mkdir -p /data && cp /tmp/server.properties /data/server.properties
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD curl -f http://localhost:{{ minecraft.port }} || exit 1
# Expose ports
EXPOSE {{ minecraft.port }}/tcp
{% if minecraft.rcon_enabled %}
EXPOSE {{ minecraft.rcon_port }}/tcp
{% endif %}

View File

@@ -1,96 +0,0 @@
#!/bin/bash
#
# Minecraft Server Backup Script
# Deployed by Ansible - {{ ansible_date_time.iso8601 if ansible_date_time is defined else 'managed' }}
#
# Creates rolling backups of the Minecraft world and data directories.
# Prunes old backups based on retention count.
#
set -euo pipefail
# Configuration
DATA_DIR="{{ paths.data_dir }}"
BACKUP_DIR="{{ paths.backup_dir }}"
RETENTION_COUNT="{{ backup.retention_count | default(10) }}"
COMPRESS="{{ backup.compress | default('true') }}"
SERVER_NAME="{{ minecraft.server_name }}"
# Timestamp for this backup
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
# Log function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
# Safety checks
if [ ! -d "$DATA_DIR" ]; then
log "ERROR: Data directory $DATA_DIR does not exist"
exit 1
fi
if [ ! -d "$BACKUP_DIR" ]; then
log "Creating backup directory: $BACKUP_DIR"
mkdir -p "$BACKUP_DIR"
fi
# Create backup using tar
BACKUP_FILE="${BACKUP_DIR}/${SERVER_NAME}_${TIMESTAMP}"
log "Starting backup of $DATA_DIR..."
# Use atomic save if RCON is enabled
USE_RCON="{{ minecraft.rcon_enabled | default('false') }}"
if [ "$USE_RCON" = "true" ]; then
log "Using RCON for world save..."
# Send save-all command via rcon-cli or telnet
# This is a best-effort operation - fall back to tar if it fails
if command -v rcon-cli >/dev/null 2>&1; then
rcon-cli -a localhost -p {{ minecraft.rcon_port }} -P {{ minecraft.rcon_password }} save-all || true
sleep 5
elif command -v mcrcon >/dev/null 2>&1; then
mcrcon -H localhost -P {{ minecraft.rcon_port }} -p {{ minecraft.rcon_password }} "save-all" || true
sleep 5
else
log "RCON client not found, skipping save-all command (backup may be inconsistent)"
fi
fi
# Deterministic backup using tar
if [ "$COMPRESS" = "true" ]; then
BACKUP_FILE="${BACKUP_FILE}.tar.gz"
log "Creating compressed backup: $BACKUP_FILE"
tar --warning=no-file-changed -czf "$BACKUP_FILE" \
--exclude="$DATA_DIR/logs/*.gz" \
-C "$(dirname $DATA_DIR)" "$(basename $DATA_DIR)"
else
BACKUP_FILE="${BACKUP_FILE}.tar"
log "Creating uncompressed backup: $BACKUP_FILE"
tar --warning=no-file-changed -cf "$BACKUP_FILE" \
--exclude="$DATA_DIR/logs/*.gz" \
-C "$(dirname $DATA_DIR)" "$(basename $DATA_DIR)"
fi
log "Backup created successfully: $BACKUP_FILE"
# Prune old backups, retaining the most recent N
log "Pruning old backups (keeping $RETENTION_COUNT)..."
# Count backups (whether .tar or .tar.gz)
BACKUP_COUNT=$(ls "${BACKUP_DIR}/"${SERVER_NAME}_*.tar* 2>/dev/null | wc -l)
OVERFLOW=$((BACKUP_COUNT - RETENTION_COUNT))
if [ "$OVERFLOW" -gt 0 ]; then
# Remove oldest backups
ls -t "${BACKUP_DIR}/"${SERVER_NAME}_*.tar* 2>/dev/null | tail -n "$OVERFLOW" | while read -r old_backup; do
log "Removing old backup: $old_backup"
rm -f "$old_backup"
done
else
log "Backup count ($BACKUP_COUNT) is within retention limit ($RETENTION_COUNT)"
fi
log "Backup completed at $(date '+%Y-%m-%d %H:%M:%S')"
log "Current backups:"
ls -lh "${BACKUP_DIR}/" 2>/dev/null || true

View File

@@ -1,66 +0,0 @@
#!/bin/bash
#
# Minecraft Server Health Check Script
# Deployed by Ansible - managed file
#
# Checks that the Minecraft server is running and responding.
#
set -euo pipefail
SERVER_NAME="{{ minecraft.server_name }}"
PORT="{{ minecraft.port }}"
CONTAINER_NAME="{{ podman_container_name }}"
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
log() {
echo -e "$(date '+%Y-%m-%d %H:%M:%S') $*"
}
# Check 1: Is the systemd service active?
SERVICE_STATUS=$(systemctl is-active "${SERVER_NAME}" 2>/dev/null || echo "inactive")
if [ "$SERVICE_STATUS" != "active" ]; then
log "${RED}[FAIL]${NC} Systemd service ${SERVER_NAME} is not active (status: ${SERVICE_STATUS})"
exit 1
fi
log "${GREEN}[OK]${NC} Systemd service ${SERVER_NAME} is active"
# Check 2: Is the container running?
if ! podman ps --format '{% raw %}{{.Names}} {{.Status}}{% endraw %}' | grep -q "${CONTAINER_NAME}"; then
log "${RED}[FAIL]${NC} Container ${CONTAINER_NAME} is not running"
exit 1
fi
log "${GREEN}[OK]${NC} Container ${CONTAINER_NAME} is running"
# Check 3: Is the Minecraft port responding?
# Minecraft responds to HTTP with 401 "It's a teapot" or similar
if curl -s -o /dev/null -m 5 http://localhost:${PORT} 2>/dev/null; then
log "${GREEN}[OK]${NC} Port ${PORT} is responding"
elif curl -s -o /dev/null -m 5 -w "%{http_code}" http://localhost:${PORT} 2>/dev/null | grep -q "401"; then
log "${GREEN}[OK]${NC} Port ${PORT} is responding (401 is expected for Minecraft)"
else
# Check if port is at least listening
if ss -tln | grep -q ":${PORT}"; then
log "${YELLOW}[WARN]${NC} Port ${PORT} is listening but not responding to HTTP (may be normal)"
else
log "${RED}[FAIL]${NC} Port ${PORT} is not listening"
exit 1
fi
fi
# Check 4: Check recent logs for errors
LOG_TAIL=$(journalctl -u "${SERVER_NAME}" --no-pager -n 50 2>/dev/null | grep -i "error\|fatal\|crash" | tail -n 5)
if [ -n "$LOG_TAIL" ]; then
log "${YELLOW}[WARN]${NC} Recent errors found in logs:"
echo "$LOG_TAIL"
else
log "${GREEN}[OK]${NC} No recent errors in logs"
fi
log "${GREEN}Health check completed successfully${NC}"
exit 0

View File

@@ -1,4 +0,0 @@
# JVM Options file for Minecraft server
# Deployed by Ansible - manage via java_args in group_vars/all.yml
{{ minecraft.java_args | default('-XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200') }}

View File

@@ -1,16 +0,0 @@
# Systemd service for Minecraft backup
# Deployed by Ansible
[Unit]
Description=Minecraft Server Backup
Requires={{ minecraft.server_name }}.service
After={{ minecraft.server_name }}.service
[Service]
Type=oneshot
User={{ minecraft_user }}
Group={{ minecraft_user }}
ExecStart={{ paths.base_dir }}/backup.sh
# Timeout for large worlds
TimeoutStartSec=3600

View File

@@ -1,15 +0,0 @@
# Systemd timer for Minecraft backup
# Deployed by Ansible
[Unit]
Description=Minecraft Server Backup Timer
[Timer]
# Backup schedule (default: every 6 hours)
OnCalendar={{ backup.schedule | default('*-*-* */6:00:00') }}
# Run the backup immediately if the machine was powered off during the scheduled time
Persistent=true
[Install]
WantedBy=timers.target

View File

@@ -1,11 +0,0 @@
# Systemd service for Minecraft health checks
# Deployed by Ansible
[Unit]
Description=Minecraft Server Health Check
[Service]
Type=oneshot
User={{ minecraft_user }}
Group={{ minecraft_user }}
ExecStart={{ paths.base_dir }}/health-check.sh

View File

@@ -1,12 +0,0 @@
# Systemd timer for Minecraft health checks
# Deployed by Ansible
[Unit]
Description=Minecraft Server Health Check Timer
[Timer]
OnBootSec=2min
OnUnitActiveSec={{ monitoring.interval | default(300) }}s
[Install]
WantedBy=timers.target

View File

@@ -1,61 +0,0 @@
# Minecraft Server Environment Variables
# Deployed by Ansible - Managed file, do not edit manually
# Version and Server Type
TYPE={{ minecraft.server_type | upper }}
VERSION={{ minecraft.version }}
# Memory
MEMORY={{ minecraft.java_memory }}
{% if minecraft.java_args is defined %}
JVM_OPTS="{{ minecraft.java_args }}"
{% endif %}
# Network
PORT={{ minecraft.port }}
{% if minecraft.rcon_enabled %}
ENABLE_RCON=true
RCON_PORT={{ minecraft.rcon_port }}
RCON_PASSWORD={{ minecraft.rcon_password }}
{% else %}
ENABLE_RCON=false
{% endif %}
# Server Settings
MOTD={{ minecraft.motd }}
MAX_PLAYERS={{ minecraft.max_players }}
GAMEMODE={{ minecraft.game_mode }}
DIFFICULTY={{ minecraft.difficulty }}
{% if minecraft.online_mode %}
ONLINE_MODE=true
{% else %}
ONLINE_MODE=false
{% endif %}
VIEW_DISTANCE={{ minecraft.view_distance | default(10) }}
SIMULATION_DISTANCE={{ minecraft.simulation_distance | default(6) }}
{% if minecraft.whitelist_enabled %}
ENABLE_WHITELIST=true
{% else %}
ENABLE_WHITELIST=false
{% endif %}
{% if minecraft.enable_command_block %}
ENABLE_COMMAND_BLOCK=true
{% else %}
ENABLE_COMMAND_BLOCK=false
{% endif %}
{% if minecraft.max_world_size %}
MAX_WORLD_SIZE={{ minecraft.max_world_size }}
{% endif %}
{% if minecraft.spawn_protection %}
SPAWN_PROTECTION={{ minecraft.spawn_protection }}
{% endif %}
# Server name broadcast
SERVER_NAME={{ minecraft.server_name }}
# Make the container non-interactive
EULA=TRUE

View File

@@ -1,42 +0,0 @@
# Systemd unit for {{ minecraft.server_name }} Minecraft server running on Podman
# Deployed by Ansible - managed file
[Unit]
Description=Minecraft Server ({{ minecraft.server_name }}) - Podman Container
Documentation=https://hub.docker.com/r/itzg/minecraft-server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User={{ minecraft_user }}
Group={{ minecraft_user }}
WorkingDirectory={{ paths.data_dir }}
# Pull the image if not present (idempotent)
ExecStartPre=/usr/bin/podman pull {{ podman.image }}:{{ podman.image_tag }}
# Stop existing container before starting
ExecStartPre=-/usr/bin/podman rm -f {{ podman_container_name }}
# Run the Minecraft server in a Podman container
ExecStart=/usr/bin/podman run --name {{ podman_container_name }} --replace --env-file {{ paths.base_dir }}/minecraft.env --volume {{ paths.data_dir }}:/data:Z {% if podman.extra_volumes is defined and podman.extra_volumes %}{% for vol in podman.extra_volumes %}--volume {{ vol }} {% endfor %}{% endif %}{% if podman.memory_limit is defined and podman.memory_limit %}--memory {{ podman.memory_limit }} {% endif %}{% if podman.cpu_shares is defined and podman.cpu_shares %}--cpu-shares {{ podman.cpu_shares }} {% endif %}{% if podman.network_mode == 'host' %}--network host {% else %}--publish {{ minecraft.port }}:{{ minecraft.port }}/tcp {% if minecraft.rcon_enabled %}--publish {{ minecraft.rcon_port }}:{{ minecraft.rcon_port }}/tcp {% endif %}{% endif %}{% for key, value in podman.labels.items() %}--label "{{ key }}={{ value }}" {% endfor %}--log-driver {{ podman.log_driver | default('journald') }} {{ podman.image }}:{{ podman.image_tag }}
# Restart if it crashes (systemd handles restart policy)
Restart=on-failure
RestartSec=15
# Basic process limits
LimitNOFILE=65536
LimitNPROC=4096
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier={{ minecraft.server_name }}
# Allow container to manage its own namespaces/limits
NoNewPrivileges=false
ProtectSystem=false
[Install]
WantedBy=multi-user.target

View File

@@ -1,106 +0,0 @@
#!/bin/bash
#
# Minecraft Server Restore Script
# Deployed by Ansible - managed file
#
# Usage: ./restore.sh [path_to_backup_file]
# If no argument given, restores the most recent backup.
#
# WARNING: This will OVERWRITE the current world data!
#
set -euo pipefail
# Configuration
DATA_DIR="{{ paths.data_dir }}"
BACKUP_DIR="{{ paths.backup_dir }}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log() {
echo -e "$(date '+%Y-%m-%d %H:%M:%S') $*"
}
error() {
log "${RED}[ERROR]${NC} $*"
exit 1
}
warn() {
log "${YELLOW}[WARN]${NC} $*"
}
ok() {
log "${GREEN}[OK]${NC} $*"
}
# Determine which backup to restore
if [ $# -eq 1 ]; then
# Explicit file provided
RESTORE_FILE="$1"
if [ ! -f "$RESTORE_FILE" ]; then
# Try to find it in the backup dir
RESTORE_FILE="${BACKUP_DIR}/$1"
if [ ! -f "$RESTORE_FILE" ]; then
error "Backup file not found: $1 (looked in $BACKUP_DIR)"
fi
fi
else
# Find most recent backup
RESTORE_FILE=$(ls -t "${BACKUP_DIR}/"{{ minecraft.server_name }}_*.tar* 2>/dev/null | head -n1)
if [ -z "$RESTORE_FILE" ]; then
error "No backups found in $BACKUP_DIR"
fi
fi
log "Selected backup: $RESTORE_FILE"
# Double-check safety
warn "This will overwrite the current Minecraft world data in $DATA_DIR"
read -p "Are you sure you want to continue? (yes/no): " CONFIRM
if [ "$CONFIRM" != "yes" ]; then
log "Aborting restore."
exit 1
fi
# Stop the Minecraft server if running
if podman ps --format '{% raw %}{{.Names}}{% endraw %}' | grep -q '{{ podman_container_name }}'; then
warn "Stopping Minecraft server container..."
systemctl stop {{ minecraft.server_name }}
sleep 3
warn "Server stopped."
fi
# Create a temporary backup of current state (safety net)
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
SAFETY_BACKUP="${BACKUP_DIR}/pre_restore_${TIMESTAMP}.tar.gz"
log "Creating safety backup of current state: $SAFETY_BACKUP"
tar -czf "$SAFETY_BACKUP" -C "$(dirname $DATA_DIR)" "$(basename $DATA_DIR)"
# Extract the restore file
log "Restoring from: $RESTORE_FILE"
case "$RESTORE_FILE" in
*.tar.gz|*.tgz)
tar -xzf "$RESTORE_FILE" -C "$(dirname $DATA_DIR)"
;;
*.tar)
tar -xf "$RESTORE_FILE" -C "$(dirname $DATA_DIR)"
;;
*)
error "Unsupported archive format: $RESTORE_FILE"
;;
esac
ok "Restore completed successfully!"
# Restart the server
log "Restarting Minecraft server..."
systemctl start {{ minecraft.server_name }}
sleep 5
ok "Server restarted. Verify it's running: systemctl status {{ minecraft.server_name }}"
log "A safety backup of the pre-restore state was saved to: $SAFETY_BACKUP"

View File

@@ -1,59 +0,0 @@
#Minecraft server properties
#Deployed by Ansible - Manage via group_vars/all.yml
#Minecraft properties
enable-jmx-monitoring=false
rcon.port={{ minecraft.rcon_port | default(25575) }}
level-seed=
gamemode={{ minecraft.game_mode }}
enable-command-block={{ minecraft.enable_command_block | default('false') }}
enable-query=false
generator-settings={}
enforce-secure-profile={{ minecraft.enforce_secure_profile | default('true') }}
level-name=world
motd={{ minecraft.motd | default('A Minecraft Server') }}
query.port={{ minecraft.port | default(25565) }}
pvp=true
generate-structures=true
max-chained-neighbor-updates=1000000
difficulty={{ minecraft.difficulty }}
network-compression-threshold=256
max-tick-time=60000
require-resource-pack=false
use-native-transport=true
max-players={{ minecraft.max_players | default(20) }}
online-mode={{ minecraft.online_mode | default('true') }}
enable-status=true
allow-flight=false
initial-disabled-packs=
broadcast-rcon-to-ops=true
view-distance={{ minecraft.view_distance | default(10) }}
server-ip=
resource-pack-prompt=
allow-nether=true
server-port={{ minecraft.port | default(25565) }}
enable-rcon={{ minecraft.rcon_enabled | default('false') }}
sync-chunk-writes=true
op-permission-level=4
prevent-proxy-connections=false
hide-online-players=false
resource-pack=
entity-broadcast-range-percentage=100
simulation-distance={{ minecraft.simulation_distance | default(6) }}
rcon.password={{ minecraft.rcon_password | default('') }}
player-idle-timeout=0
force-gamemode=false
rate-limit=0
hardcore=false
white-list={{ minecraft.whitelist_enabled | default('false') }}
broadcast-console-to-ops=true
spawn-npcs=true
spawn-animals=true
log-ips=true
function-permission-level=2
level-type=minecraft\:normal
text-filtering-config=
spawn-monsters=true
enforce-whitelist=false
spawn-protection={{ minecraft.spawn_protection | default(16) }}
max-world-size={{ minecraft.max_world_size | default(29999984) }}

View File

@@ -1,62 +0,0 @@
---
# =============================================================================
# Minecraft Podman Deployment - Main Playbook
# =============================================================================
# Usage:
# ansible-playbook site.yml # Deploy to all minecraft servers
# ansible-playbook site.yml --limit external-apps # Deploy to specific host
# ansible-playbook site.yml --tags install # Only run install tasks
# ansible-playbook site.yml --tags config # Only run config tasks
# ansible-playbook site.yml --tags backup # Only run backup tasks
# ansible-playbook site.yml --tags update # Only run update tasks
- name: Deploy Minecraft Server on Podman
hosts: minecraft
become: true
gather_facts: true
pre_tasks:
- name: Display deployment info
ansible.builtin.debug:
msg: |
========================================
Minecraft Podman Deployment
========================================
Target: {{ inventory_hostname }}
Server: {{ minecraft.server_name }}
Type: {{ minecraft.server_type }}
Image: {{ podman.image }}
Port: {{ minecraft.port }}
Memory: {{ minecraft.java_memory }}
========================================
- name: Verify target is Fedora
ansible.builtin.assert:
that:
- ansible_distribution == "Fedora"
fail_msg: "This playbook is designed for Fedora. Detected: {{ ansible_distribution }}"
success_msg: "Fedora {{ ansible_distribution_version }} confirmed"
roles:
- role: minecraft-podman
tags:
- install
- config
- backup
- update
- always
post_tasks:
- name: Display completion message
ansible.builtin.debug:
msg: |
========================================
Deployment Complete!
========================================
Server IP: {{ ansible_default_ipv4.address | default(ansible_host) }}
Port: {{ minecraft.port }}
RCON: {{ 'Enabled' if minecraft.rcon_enabled else 'Disabled' }}
Status: {{ minecraft.server_name }}.service
========================================
Connect: {{ ansible_default_ipv4.address | default(ansible_host) }}:{{ minecraft.port }}
========================================

View File

@@ -1,112 +0,0 @@
---
# =============================================================================
# Minecraft Podman Deployment - Uninstall Playbook
# =============================================================================
# Usage:
# ansible-playbook uninstall.yml --limit external-apps
#
# WARNING: This will remove ALL Minecraft data, containers, and configuration
# from the target server. Run only if you're sure you want to clean up.
# =============================================================================
- name: Uninstall Minecraft Server from Podman
hosts: minecraft
become: true
gather_facts: true
tasks:
- name: Confirm uninstall
ansible.builtin.pause:
prompt: "WARNING: This will remove all Minecraft data from {{ inventory_hostname }}. Type 'yes' to continue"
register: confirm
when: not ansible_check_mode
- name: Abort if not confirmed
ansible.builtin.fail:
msg: "Uninstall aborted"
when:
- not ansible_check_mode
- confirm.user_input | lower != "yes"
- name: Stop Minecraft service
ansible.builtin.systemd:
name: "minecraft.service"
state: stopped
enabled: false
ignore_errors: true
- name: Stop backup timer
ansible.builtin.systemd:
name: "minecraft-backup.timer"
state: stopped
enabled: false
ignore_errors: true
- name: Stop health timer
ansible.builtin.systemd:
name: "minecraft-health.timer"
state: stopped
enabled: false
ignore_errors: true
- name: Remove systemd unit files
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- "/etc/systemd/system/minecraft.service"
- "/etc/systemd/system/minecraft-backup.timer"
- "/etc/systemd/system/minecraft-backup.service"
- "/etc/systemd/system/minecraft-health.timer"
- "/etc/systemd/system/minecraft-health.service"
ignore_errors: true
- name: Reload systemd daemon
ansible.builtin.systemd:
daemon_reload: true
- name: Stop and remove container
containers.podman.podman_container:
name: "minecraft"
state: absent
ignore_errors: true
register: container_remove
when: not ansible_check_mode
- name: Remove container (fallback with command)
ansible.builtin.command: podman rm -f minecraft
ignore_errors: true
when:
- not ansible_check_mode
- container_remove is failed
- name: Remove base directory
ansible.builtin.file:
path: "/opt/minecraft"
state: absent
ignore_errors: true
when: not ansible_check_mode
- name: Remove Minecraft system user
ansible.builtin.user:
name: "minecraft"
state: absent
remove: true
ignore_errors: true
when: not ansible_check_mode
- name: Close firewall ports
ansible.posix.firewalld:
port: "{{ item }}"
permanent: true
immediate: true
state: disabled
loop:
- "25565/tcp"
- "25575/tcp"
ignore_errors: true
tags: [firewall]
- name: Uninstall complete message
ansible.builtin.debug:
msg: "Minecraft server removed from {{ inventory_hostname }}"