diff --git a/roles/craftbukkit/defaults/main.yaml b/roles/craftbukkit/defaults/main.yaml new file mode 100644 index 0000000..aa60551 --- /dev/null +++ b/roles/craftbukkit/defaults/main.yaml @@ -0,0 +1,22 @@ +--- +craftbukkit_java_package_name: openjdk-8-jre-headless +craftbukkit_java_package_state: present + +craftbukkit_version: 1.15.2 +craftbukkit_jar: "craftbukkit-{{ craftbukkit_version }}.jar" + +craftbukkit_service_name: craftbukkit.service +craftbukkit_service_state: started +craftbukkit_service_enabled: yes + +craftbukkit_port: 25565 + +craftbukkit_user: craftbukkit +craftbukkit_group: craftbukkit + +craftbukkit_opt_path: /opt/craftbukkit +craftbukkit_var_path: /var/opt/craftbukkit + +craftbukkit_syslog_facility: local5 + +craftbukkit_notifier_state: present diff --git a/roles/craftbukkit/files/discord.py b/roles/craftbukkit/files/discord.py new file mode 100644 index 0000000..1f9e574 --- /dev/null +++ b/roles/craftbukkit/files/discord.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python + +from __future__ import print_function + +import sys +import requests +import re +import argparse + +from urlparse import urljoin + +PATTERN = re.compile(r"(\S+) (joined|left) the game") + +PATTERNS = ( + (re.compile(r": (\S+)\[.+logged in"), "{0} joined the game"), + (re.compile(r"(\S+) (joined|left) the game"), "{0} {1} the game"), + (re.compile(r"\[(\S+): Gave (\d+) \[(.+)\] to (\S+)\]"), "{0} gave {1} \"{2}\" to {3}"), + (re.compile(r"(\S+) was (\S+) by (\S+)"), ":skull: {0} was {1} by {2}"), +) + + +def print_err(s): + print(s, file=sys.stderr) + sys.stderr.flush() + + +def ok(): + print("OK") + sys.stdout.flush() + + +def cli_parse(args): + parser = argparse.ArgumentParser() + opt = parser.add_argument + opt("--config", "-c", dest="config", type=parse_kv_file) + opt("--confirm", action="store_const", dest="confirm", const=True, default=True) + opt("--no-confirm", action="store_const", dest="confirm", const=False) + opt("--verbose", "-v", action="store_true") + opt("--debug", "-d", action="store_true") + cli_args = parser.parse_args(args[1:]) + return cli_args, parser + + +def parse_kv_file(f, mode="r"): + if isinstance(f, str): + f = open(f, mode) + + kv = {} + with f: + for line in f: + k, v = line.partition("=")[::2] + kv[k.strip().lower()] = v.strip() + + return kv + + +class DiscordHook: + def __init__(self, hook_id, hook_token): + url_path = "/".join([hook_id, hook_token]) + url = urljoin("https://discordapp.com/api/webhooks/", url_path) + self.url = url + + def send(self, content): + data = {"content": content} + r = requests.post(self.url, data=data) + r.raise_for_status() + return r + + +def loop(handler, confirm=True): + if confirm: + ok() + + while 1: + try: + line = sys.stdin.readline() + except KeyboardInterrupt: + print_err("\nreceived sigint, exiting") + break + + if not line: + break + + for pattern, fmt in PATTERNS: + match = pattern.search(line.strip()) + + if match: + message = fmt.format(*match.groups()) + + try: + handler.send(message) + except Exception as e: + print_err(e) + continue + + if confirm: + ok() + + +def main(argv): + args, _ = cli_parse(argv) + + if args.debug: + print("started with args {0}".format(vars(args))) + + webhook_id = args.config.get("webhook_id") + webhook_token = args.config.get("webhook_token") + + if webhook_id is None: + raise SystemExit("webhook_id is unset") + if webhook_token is None: + raise SystemExit("webhook_token is unset") + + handler = DiscordHook(webhook_id, webhook_token) + + return loop(handler, confirm=args.confirm) + + +raise SystemExit(main(sys.argv)) diff --git a/roles/craftbukkit/handlers/main.yaml b/roles/craftbukkit/handlers/main.yaml new file mode 100644 index 0000000..05c1614 --- /dev/null +++ b/roles/craftbukkit/handlers/main.yaml @@ -0,0 +1,11 @@ +--- +- name: craftbukkit daemon-reload + systemd: + name: "{{ craftbukkit_service_name }}" + daemon_reload: yes + state: restarted + +- name: restart craftbukkit + service: + name: "{{ craftbukkit_service_name }}" + state: restarted diff --git a/roles/craftbukkit/tasks/main.yaml b/roles/craftbukkit/tasks/main.yaml new file mode 100644 index 0000000..6c7f1ca --- /dev/null +++ b/roles/craftbukkit/tasks/main.yaml @@ -0,0 +1,119 @@ +--- +- name: create craftbukkit group + group: + name: "{{ craftbukkit_group }}" + gid: "{{ craftbukkit_group_gid | default(omit) }}" + state: "{{ craftbukkit_group_state | default('present') }}" + system: yes + +- name: create craftbukkit user + user: + name: "{{ craftbukkit_user }}" + uid: "{{ craftbukkit_user_uid | default(omit) }}" + group: "{{ craftbukkit_group }}" + home: "{{ craftbukkit_var_path }}" + create_home: no + shell: "{{ craftbukkit_shell | default('/usr/sbin/nologin') }}" + state: "{{ craftbukkit_user_state | default('present') }}" + system: yes + +- name: install java + package: + name: "{{ craftbukkit_java_package_name }}" + state: "{{ craftbukkit_java_package_state }}" + +- name: create craftbukkit installation directory + file: + path: "{{ item }}" + state: directory + owner: root + group: root + mode: "0755" + with_items: + - "{{ craftbukkit_opt_path }}" + - "{{ craftbukkit_opt_path }}/bin" + - "{{ craftbukkit_opt_path }}/etc" + +- name: create craftbukkit var directory + file: + path: "{{ craftbukkit_var_path }}" + state: directory + owner: "{{ craftbukkit_user }}" + group: "{{ craftbukkit_group }}" + mode: "0755" + +- name: "upload {{ craftbukkit_jar }}" + copy: + src: "files/craftbukkit/{{ craftbukkit_jar }}" + dest: "{{ craftbukkit_opt_path }}/bin/{{ craftbukkit_jar }}" + owner: "{{ craftbukkit_user }}" + group: "{{ craftbukkit_group }}" + mode: "0644" + +- name: agree to the eula + copy: + content: "eula=true" + dest: "{{ craftbukkit_var_path }}/eula.txt" + owner: "{{ craftbukkit_user }}" + group: "{{ craftbukkit_group }}" + mode: "0644" + +- name: configure server.properties + template: + src: server.properties.j2 + dest: "{{ craftbukkit_var_path }}/server.properties" + owner: root + group: root + mode: 0644 + +- name: configure systemd unit + template: + src: craftbukkit.service.j2 + dest: /etc/systemd/system/craftbukkit.service + owner: root + group: root + mode: 0644 + notify: craftbukkit daemon-reload + +- name: manage craftbukkit service + service: + name: "{{ craftbukkit_service_name }}" + state: "{{ craftbukkit_service_state }}" + enabled: "{{ craftbukkit_service_enabled }}" + +- name: install discord notifier + copy: + src: discord.py + dest: "{{ craftbukkit_opt_path }}/bin/craftbukkit-discord" + owner: root + group: root + mode: 0755 + notify: restart rsyslog + +- name: configure discord notifier + copy: + dest: "{{ craftbukkit_opt_path }}/etc/discord.cfg" + owner: syslog + group: syslog + mode: 0600 + content: "{% for k, v in craftbukkit_discord_config.items() %}{{ k }}={{ v }}{{ \"\n\" }}{% endfor %}" + notify: restart rsyslog + +- name: configure rsyslog program + template: + src: rsyslog/craftbukkit.conf.j2 + dest: /etc/rsyslog.d/05-craftbukkit.conf + owner: root + group: root + mode: 0644 + notify: restart rsyslog + +- name: manage rsyslog configuration + file: + path: "{{ item }}" + state: "{{ (craftbukkit_notifier_state == 'present') | ternary('file', 'absent') }}" + loop: + - /etc/rsyslog.d/05-craftbukkit.conf + - "{{ craftbukkit_opt_path }}/etc/discord.cfg" + - "{{ craftbukkit_opt_path }}/bin/craftbukkit-discord" + notify: restart rsyslog diff --git a/roles/craftbukkit/templates/craftbukkit.service.j2 b/roles/craftbukkit/templates/craftbukkit.service.j2 new file mode 100644 index 0000000..6e9d2a0 --- /dev/null +++ b/roles/craftbukkit/templates/craftbukkit.service.j2 @@ -0,0 +1,19 @@ +# {{ ansible_managed }} + +[Unit] +Description=Craftbukkit server %i +After=network.target + +[Service] +ExecStart=/usr/bin/java -Xmx{{ craftbukkit_java_xmx | default('1024M') }} -Xms{{ craftbukkit_java_xms | default('1024M') }} -jar {{ craftbukkit_opt_path }}/bin/{{ craftbukkit_jar }} nogui +SuccessExitStatus=143 +Type=simple +User={{ craftbukkit_user }} +Group={{ craftbukkit_group }} +WorkingDirectory={{ craftbukkit_var_path }}/%i +Restart=on-failure +SyslogIdentifier=craftbukkit +SyslogFacility={{ craftbukkit_syslog_facility }} + +[Install] +WantedBy=multi-user.target diff --git a/roles/craftbukkit/templates/rsyslog/craftbukkit.conf.j2 b/roles/craftbukkit/templates/rsyslog/craftbukkit.conf.j2 new file mode 100644 index 0000000..9cde2ac --- /dev/null +++ b/roles/craftbukkit/templates/rsyslog/craftbukkit.conf.j2 @@ -0,0 +1,11 @@ +# {{ ansible_managed }} + +module(load="omprog") + +if ( $programname == "craftbukkit" ) then { + action( + type="omprog" + binary="{{ craftbukkit_opt_path }}/bin/craftbukkit-discord --config {{ craftbukkit_opt_path }}/etc/discord.cfg" + confirmmessages="on" + ) +} diff --git a/roles/craftbukkit/templates/server.properties.j2 b/roles/craftbukkit/templates/server.properties.j2 new file mode 100644 index 0000000..a9ccac0 --- /dev/null +++ b/roles/craftbukkit/templates/server.properties.j2 @@ -0,0 +1,47 @@ +# {{ ansible_managed }} + +spawn-protection=16 +max-tick-time=60000 +query.port: {{ craftbukkit_port | default(25565) }} +generator-settings= +force-gamemode=false +allow-nether=true +enforce-whitelist: {{ (craftbukkit_config.enfoce_whitelist | default(true)) | ternary('true', 'false') }} +gamemode=survival +broadcast-console-to-ops=true +enable-query=false +player-idle-timeout=0 +difficulty=easy +spawn-monsters=true +broadcast-rcon-to-ops=true +op-permission-level=4 +pvp=true +snooper-enabled=true +level-type=default +hardcore=false +enable-command-block=false +max-players=20 +network-compression-threshold=256 +resource-pack-sha1= +max-world-size=29999984 +function-permission-level=2 +rcon.port=25575 +server-port: {{ craftbukkit_port | default(25565) }} +debug=false +server-ip= +spawn-npcs=true +allow-flight=false +level-name=world +view-distance=10 +resource-pack= +spawn-animals=true +white-list: {{ (craftbukkit_config.whitelist | default(true)) | ternary('true', 'false') }} +rcon.password= +generate-structures=true +max-build-height=256 +online-mode=true +level-seed= +use-native-transport=true +prevent-proxy-connections=false +enable-rcon=false +motd=A Minecraft Server