initial commital
This commit is contained in:
19
roles/minecraft-podman/defaults/main.yml
Normal file
19
roles/minecraft-podman/defaults/main.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# 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"
|
||||
25
roles/minecraft-podman/handlers/main.yml
Normal file
25
roles/minecraft-podman/handlers/main.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# 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
|
||||
423
roles/minecraft-podman/tasks/main.yml
Normal file
423
roles/minecraft-podman/tasks/main.yml
Normal file
@@ -0,0 +1,423 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# 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]
|
||||
28
roles/minecraft-podman/templates/Containerfile.j2
Normal file
28
roles/minecraft-podman/templates/Containerfile.j2
Normal file
@@ -0,0 +1,28 @@
|
||||
# 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 %}
|
||||
96
roles/minecraft-podman/templates/backup.sh.j2
Normal file
96
roles/minecraft-podman/templates/backup.sh.j2
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/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
|
||||
66
roles/minecraft-podman/templates/health-check.sh.j2
Normal file
66
roles/minecraft-podman/templates/health-check.sh.j2
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/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
|
||||
4
roles/minecraft-podman/templates/jvm.opts.j2
Normal file
4
roles/minecraft-podman/templates/jvm.opts.j2
Normal file
@@ -0,0 +1,4 @@
|
||||
# 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') }}
|
||||
16
roles/minecraft-podman/templates/minecraft-backup.service.j2
Normal file
16
roles/minecraft-podman/templates/minecraft-backup.service.j2
Normal file
@@ -0,0 +1,16 @@
|
||||
# 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
|
||||
15
roles/minecraft-podman/templates/minecraft-backup.timer.j2
Normal file
15
roles/minecraft-podman/templates/minecraft-backup.timer.j2
Normal file
@@ -0,0 +1,15 @@
|
||||
# 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
|
||||
11
roles/minecraft-podman/templates/minecraft-health.service.j2
Normal file
11
roles/minecraft-podman/templates/minecraft-health.service.j2
Normal file
@@ -0,0 +1,11 @@
|
||||
# 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
|
||||
12
roles/minecraft-podman/templates/minecraft-health.timer.j2
Normal file
12
roles/minecraft-podman/templates/minecraft-health.timer.j2
Normal file
@@ -0,0 +1,12 @@
|
||||
# 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
|
||||
61
roles/minecraft-podman/templates/minecraft.env.j2
Normal file
61
roles/minecraft-podman/templates/minecraft.env.j2
Normal file
@@ -0,0 +1,61 @@
|
||||
# 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
|
||||
42
roles/minecraft-podman/templates/minecraft.service.j2
Normal file
42
roles/minecraft-podman/templates/minecraft.service.j2
Normal file
@@ -0,0 +1,42 @@
|
||||
# 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
|
||||
106
roles/minecraft-podman/templates/restore.sh.j2
Normal file
106
roles/minecraft-podman/templates/restore.sh.j2
Normal file
@@ -0,0 +1,106 @@
|
||||
#!/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"
|
||||
59
roles/minecraft-podman/templates/server.properties.j2
Normal file
59
roles/minecraft-podman/templates/server.properties.j2
Normal file
@@ -0,0 +1,59 @@
|
||||
#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) }}
|
||||
Reference in New Issue
Block a user