diff --git a/bot/Dockerfile b/bot/Dockerfile index c21f683..5877c8f 100644 --- a/bot/Dockerfile +++ b/bot/Dockerfile @@ -5,4 +5,4 @@ RUN pip install --upgrade pip RUN pip install -r requirements.txt ADD . . -CMD ["python", "main.py"] +CMD ["python", "bot.py"] diff --git a/bot/bot.py b/bot/bot.py index c929153..e374aaf 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -3,6 +3,7 @@ from discord.ext import commands from cogs.minecraft import Minecraft from cogs.user_management import UserManager +from cogs.spawner import Spawner # Setup Environment import os @@ -12,11 +13,6 @@ load_dotenv() # Discord Stuff TOKEN = os.environ['TOKEN'] -# Server Stuff -RCON_SERVER = os.environ['RCON_SERVER'] -RCON_PASS = os.environ['RCON_PASS'] -RCON_PORT = int(os.environ['RCON_PORT']) - # Setup Basic Permission intents = discord.Intents.default() intents.message_content = True @@ -25,8 +21,9 @@ intents.message_content = True bot = commands.Bot(command_prefix='-', intents=intents) async def setup(): - await bot.add_cog(Minecraft(bot, RCON_SERVER, RCON_PASS, RCON_PORT)) + await bot.add_cog(Minecraft(bot)) await bot.add_cog(UserManager(bot)) + await bot.add_cog(Spawner(bot)) @bot.event async def on_ready(): diff --git a/bot/cogs/minecraft.py b/bot/cogs/minecraft.py index db10c72..c0de23f 100644 --- a/bot/cogs/minecraft.py +++ b/bot/cogs/minecraft.py @@ -2,79 +2,14 @@ import discord from discord.ext import commands from mcrcon import MCRcon import enum -from statemachine import StateMachine, State -from statemachine.states import States +from transitions import Machine +from cogs.spawner import containers -class BorderWarsSession(StateMachine): - "A workflow maschine for managing InGame States" - Nothing = State(initial=True) - Initialization = State() - Safe = State() - Fight = State() - SuddenDeath = State() - End = State() - - init_game = Nothing.to(Initialization) - start_game = Initialization.to(Safe) - start_fight = Safe.to(Fight) - start_last_round = Fight.to(SuddenDeath) - end_game = Fight.to(End) | SuddenDeath.to(End) - - abort = Initialization.to(Nothing) | Safe.to(Nothing) | Fight.to(Nothing) | SuddenDeath.to(Nothing) | End.to(Nothing) | Nothing.to(Nothing) - reset = End.to(Initialization) - - @Initialization.enter - def initialization(self) -> list: - - - @Safe.enter - def safe(self) -> list: - # Chat countdown - return [ - '''/title @a subtitle ["",{"text":"bei ","color":"blue"},{"text":"BORDER WARS!","bold":true,"color":"red"}]''', - '''/title @a title {"text":"Viel Glück!","bold":true,"color":"blue"}''', - "playsound minecraft:entity.wither.spawn ambient @a 0 64 080", - "/worldborder set 1000", - "/gamerule keepInventory true" - ] - - @Fight.enter - def fight(self) -> list: - # Timer Starten - return [ - '''/title @a subtitle {"text":"ÜBERLEBEN!","bold":true,"color":"red"}''', - '''/title @a title {"text":"Möge der beste","color":"blue"}''', - "/playsound minecraft:item.totem.use ambient @a 0 64 0 80", - "/worldborder set 75 3600", - "/gamerule keepInventory false" - ] - - @SuddenDeath.enter - def death(self) -> list: - # Timer Starten - return [ - '''/title @a title ["",{"text":"Sudden ","color":"dark_blue"},{"text":"DEATH!","bold":true,"color":"red"}]''', - "/playsound minecraft:entity.ender_dragon.growl ambient @a 0 64 0 80", - "/worldborder set 5 600" - ] - - @End.enter - def end(self, playername: str) -> list: - return [ - "/worldborder center 0 0", - "/worldborder set 75", - "/gamerule keepInventory true", - '''/title @a subtitle ["",{"text":"''' + playername + '''","bold":true,"color":"red"},{"text":" gewinnt","color":"dark_blue"}]''', - '''/title @a title {"text":"ENDE!","color":"dark_blue"}''', - "/playsound minecraft:entity.ender_dragon_death ambient @a 0 64 0 80", - ] - - -class Whitelist(StateMachine): +class Whitelist: "A workflow machine for managing Whitelist states" - On = State(initial=True) - Off = State() - toggle = On.to(Off) | Off.to(On) + On = None + Off = None + toggle = None class RCON(MCRcon): def __init__(self, ip: str, secret: str, port: int = 31066): @@ -86,50 +21,43 @@ class RCON(MCRcon): cmds = "/whitelist {}".format(self.whitelist.current_state.id) print(cmds) - def _sendcmd(self, cmds: str | list) -> None: + def sendcmd(self, cmds) -> None: if isinstance(cmds, str): - return self.server.command(str) + return self.command(str) if isinstance(cmds, list): - return [self.server.commands(cmd) for cmd in cmds] + return [self.command(cmd) for cmd in cmds] def __del__(self): self.disconnect() +class States(enum.Enum): + NOTHING = 0 + INIT = 1 + SAFE = 2 + FIGHT = 3 + SUDDENDEATH = 4 + END = 5 + class Minecraft(commands.Cog): - def __init__(self, bot: commands.Bot, ip: str, secret: str, port: int = 31066): + def __init__(self, bot: commands.Bot): self.bot = bot - self.server = RCON(ip, secret, port) - self.session = BorderWarsSession() - self.whitelist = Whitelist() + self.servers = dict() + + transitions = [ + ['init_game', States.NOTHING, States.INIT], + ['start_game', States.INIT, States.SAFE], + ['start_fight', States.SAFE, States.FIGHT], + ['start_last_round', States.FIGHT, States.SUDDENDEATH], + ['end_game', States.FIGHT, States.END], + ['end_game', States.SUDDENDEATH, States.END], + ['reset', States.END, States.INIT], + ['abort', '*', States.NOTHING] + ] - @commands.hybrid_command(name='whitelist') - async def whitelist(self, ctx: commands.Context): - """ - Toggles Servers Whitelist - - Parameters - ---------- - ctx: commands.Context - The context of the command invocation - """ - await self.whitelist.activate_initial_state() - await ctx.send("Whitelist") - - @commands.hybrid_command(name='start') - async def start(self, ctx: commands.Context): - """ - Starts a Border Wars Session - - Parameters - ---------- - ctx: commands.Context - The context of the command invocation - """ - cmds = - await ctx.send("Start") + self.machine = Machine(states=States, transitions=transitions, initial=States.NOTHING) @commands.hybrid_command(name='init') - async def init(self, ctx: commands.Context): + async def init(self, ctx: commands.Context, server_name: str): """ Initialize a Border Wars session @@ -137,18 +65,165 @@ class Minecraft(commands.Cog): ---------- ctx: commands.Context The context of the command invocation + server_name: str + Server on which the Session should be initialized """ + server_name = server_name.title() + + c = None + for container in containers: + if server_name == container.name: + c = container + break + + if not c: + await ctx.send("---The server doesn't run---") + return + + conn = RCON(str(c.ip), c.rcon_pass, c.rcon_port) + self.servers[server_name] = conn + cmds = [ + "/effect give @a minecraft:resistance infinite 255 true", + "/effect give @a minecraft:saturation infinite 4 true", + "/tp @a 0 200 0", + "/gamemode adventure @a", "/worldborder center 0 0", "/worldborder set 5", "/whitelist off" ] - await self.session.activate_initial_state() - await self.session.init_game() - await ctx.send(self.session.current_state) + + conn.sendcmd(cmds) + await ctx.send("init Border Wars Game") + + @commands.hybrid_command(name='safe') + async def safe(self, ctx: commands.Context, server_name: str): + """ + Switches to Safe Phase on a Border Wars session + + Parameters + ---------- + ctx: commands.Context + The context of the command invocation + server_name: str + Server on which the Session should be initialized + """ + server_name = server_name.title() + + c = None + for container in containers: + if server_name == container.name: + c = container + break + + if not c: + await ctx.send("---The server doesn't run---") + return + + conn = self.servers.get(server_name) + + if not conn: + await ctx.send("---Border Wars Session not Initialized---") + return + + + cmds = [ + '''/title @a subtitle ["",{"text":"bei ","color":"blue"},{"text":"BORDER WARS!","bold":true,"color":"red"}]''', + '''/title @a title {"text":"Viel Glück!","bold":true,"color":"blue"}''', + "playsound minecraft:entity.wither.spawn ambient @a 0 64 080", + "/worldborder set 1000", + "/gamerule keepInventory true", + "/gamemode survival @a", + "/effect clear @a" + ] + + conn.sendcmd(cmds) + await ctx.send("Switched to Safe Phase") + + @commands.hybrid_command(name='fight') + async def fight(self, ctx: commands.Context, server_name: str): + """ + Switches to Fight Phase on a Border Wars session + + Parameters + ---------- + ctx: commands.Context + The context of the command invocation + server_name: str + Server on which the Session should be initialized + """ + server_name = server_name.title() + + c = None + for container in containers: + if server_name == container.name: + c = container + break + + if not c: + await ctx.send("---The server doesn't run---") + return + + conn = self.servers.get(server_name) + + if not conn: + await ctx.send("---Border Wars Session not Initialized---") + return + + + cmds = [ + '''/title @a subtitle {"text":"ÜBERLEBEN!","bold":true,"color":"red"}''', + '''/title @a title {"text":"Möge der beste","color":"blue"}''', + "/playsound minecraft:item.totem.use ambient @a 0 64 0 80", + "/worldborder set 75 3600", + "/gamerule keepInventory false" + ] + + conn.sendcmd(cmds) + await ctx.send("Switched to Fight Phase") + + @commands.hybrid_command(name='death') + async def death(self, ctx: commands.Context, server_name: str): + """ + Switches to Sudden Death Phase on a Border Wars session + + Parameters + ---------- + ctx: commands.Context + The context of the command invocation + server_name: str + Server on which the Session should be initialized + """ + server_name = server_name.title() + + c = None + for container in containers: + if server_name == container.name: + c = container + break + + if not c: + await ctx.send("---The server doesn't run---") + return + + conn = self.servers.get(server_name) + + if not conn: + await ctx.send("---Border Wars Session not Initialized---") + return + + + cmds = [ + '''/title @a title ["",{"text":"Sudden ","color":"dark_blue"},{"text":"DEATH!","bold":true,"color":"red"}]''', + "/playsound minecraft:entity.ender_dragon.growl ambient @a 0 64 0 80", + "/worldborder set 5 600" + ] + + conn.sendcmd(cmds) + await ctx.send("Switched to Sudden Death Phase") @commands.hybrid_command(name='end') - async def end(self, ctx: commands.Context): + async def end(self, ctx: commands.Context, server_name: str, playername: str): """ Ends a Border Wars session @@ -156,30 +231,41 @@ class Minecraft(commands.Cog): ---------- ctx: commands.Context The context of the command invocation + server_name: str + Server on which the Session should be initialized + playername: str + Player which is announced as the Winner """ - await ctx.send("End") + server_name = server_name.title() - @commands.hybrid_command(name='rules') - async def rules(self, ctx: commands.Context): - """ - Displays the Border Wars rules + c = None + for container in containers: + if server_name == container.name: + c = container + break + + if not c: + await ctx.send("---The server doesn't run---") + return + + conn = self.servers.get(server_name) + + if not conn: + await ctx.send("---Border Wars Session not Initialized---") + return - Parameters - ---------- - ctx: commands.Context - The context of the command invocation - """ - await ctx.send("Rules") - @commands.hybrid_command(name='custom') - async def custom(self, ctx: commands.Context): - """ - Register a custom command + cmds = [ + "/worldborder center 0 0", + "/worldborder set 75", + "/gamerule keepInventory true", + '''/title @a subtitle ["",{"text":"''' + playername + '''","bold":true,"color":"red"},{"text":" gewinnt","color":"dark_blue"}]''', + '''/title @a title {"text":"ENDE!","color":"dark_blue"}''', + "/playsound minecraft:entity.ender_dragon_death ambient @a 0 64 0 80", + ] - Parameters - ---------- - ctx: commands.Context - The context of the command invocation - """ - await ctx.send("Custom") + conn.sendcmd(cmds) + await ctx.send("Ended Border Wars Session") + + diff --git a/bot/cogs/spawner.py b/bot/cogs/spawner.py new file mode 100644 index 0000000..603fe56 --- /dev/null +++ b/bot/cogs/spawner.py @@ -0,0 +1,242 @@ +import discord +from discord.ext import commands +import docker +import random +import socket +from contextlib import closing +from datetime import datetime +import pytz +from dataclasses import dataclass +from ipaddress import IPv4Address +import secrets +import asyncio + +@dataclass +class Server: + container: None + name: str + ip: IPv4Address + port: int + players: int + rcon_pass: str + rcon_port: int + +# Global List of all running Containers +containers = list() + +def seed_generator(): + seed = random.randrange(1_000_000_000, 100_000_000_000_000) + if random.randrange(0,2) == 0: + seed *= -1 + return str(seed) + +def find_free_port(): + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: + s.bind(('', 0)) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + return s.getsockname()[1] + +class Spawner(commands.Cog): + def __init__(self, bot: commands.Bot): + self.bot = bot + self.client = docker.from_env() + self.client.images.pull('itzg/minecraft-server:latest') + + @commands.hybrid_command(name='spawn') + async def spawn(self, + ctx: commands.Context, + server_name: str, + world_url: str = None, + seed: str = None, + enable_command_blocks: bool = False, + max_players: int = 10, + enable_hardcore: bool = False, + ): + ''' + Spawns a standard defined Minecraft Server + Either from a World Download Link or a Seed + + Parameters + ---------- + ctx: commands.Context + The context of the command invocation + server_name: str + Name of the Server + world_url: str + Download link of a minecraft world (Should be a downloadable ZIPP Archive + seed: str + Seed to generate a World from + enable_command_blocks: bool + Enable or disable command Block + max_players: int + Maximum Number of Players who can join the Server + enable_hardcore: bool + Enables Hardcore Minecraft + ''' + embed = discord.Embed( + title="Starting Server", + description=f''' + Setting up {server_name} + + This could take up to **5 minutes** + ''', + color=discord.Color.random(), + timestamp=datetime.now(pytz.timezone('Europe/Berlin')) + ) + + start = await ctx.send(embed=embed) + + port = find_free_port() + server_name = server_name.title() + passwd = secrets.token_hex(32) + rcon_port = find_free_port() + + env = { + "EULA": "true", + "TYPE": "FABRIC", + "VERSION": "1.21.1", + "SERVER_NAME": server_name, + "LEVEL": server_name, + "ONLINE_MODE": "true", + "TZ": "Europe/Berlin", + "MOTD": "\u00a7d\u00a7khhh\u00a76Powered by\u00a7b Garde Studios\u00a76!\u00a7d\u00a7khhh", + + "OVERRIDE_SERVER_PROPERTIES": "true", + "ENABLE_COMMAND_BLOCK": enable_command_blocks, + "GAMEMODE": "survival", + "FORCE_GAMEMODE": "true", + + "RCON_PASSWORD": passwd, + "RCON_PORT": rcon_port, + "BROADCAST_CONSOLE_TO_OPS": "false", + "BROADCAST_RCON_TO_OPS": "false", + "SERVER_PORT": port, + + "FORCE_REDOWNLOAD": "true", + "INIT_MEMORY": "500M", + "MAX_MEMORY": "2G", + "USE_AIKAR_FLAGS": "true", + + #"MODS_FILE": "/extras/mods.txt", + "OPS_FILE": "https://git.cyperpunk.de/Garde-Studios/Uno-MC/raw/branch/main/ops.json", + "SYNC_SKIP_NEWER_IN_DESTINATION": "false", + "MAX_PLAYERS": max_players, + + "ANNOUNCE_PLAYER_ACHIEVMENTS": "true", + "HARDCORE": enable_hardcore, + + "SNOOPER_ENABLED": "false", + "SPAWN_PROTECTION": 0, + "VIEW_DISTANCE": 12, + "ALLOW_FLIGHT": "false", + + # "RESOURCE_PACK": "", + # "RESOURCE_PACK_SHA1": "", + } + + + if not seed and not world_url: + seed = seed_generator() + if seed: + env["SEED"] = seed + if world_url: + env["WORLD"] = world_url + + container = self.client.containers.run( + image='itzg/minecraft-server:latest', + environment=env, + detach=True, + hostname=server_name, + name=server_name, + network_mode='bridge', + ports={port:port, rcon_port:rcon_port}, + restart_policy={"Name": "always"}, + volumes={'mods.txt': {'bind': '/extras/mods.txt', 'mode': 'ro'}} + ) + + net = self.client.networks.get('bot_rcon') + net.connect(container) + + ip = self.client.containers.get(server_name).attrs['NetworkSettings']['Networks']['bot_rcon']['IPAddress'] + server = Server(container, server_name, IPv4Address(ip), port, max_players, passwd, rcon_port) + + containers.append(server) + + embed = discord.Embed( + title="Success", + description=f''' + **{server_name}** has started! + + **Connection URL**: + garde-studios.de:{port} + ''', + color=discord.Color.random(), + timestamp=datetime.now(pytz.timezone('Europe/Berlin')) + ) + await start.delete() + await ctx.send(embed=embed) + + @commands.hybrid_command(name='servers') + async def servers(self, ctx: commands.Context): + ''' + List all currently Running Servers + + Parameters + ---------- + ctx: commands.Context + The context of the command invocation + ''' + + embed = discord.Embed( + title="Currently Running Servers", + description="List of all currently running Minecraft Servers", + color=discord.Color.random(), + timestamp=datetime.now(pytz.timezone('Europe/Berlin')) + ) + + for container in containers: + desc = f''' + *Status*: {container.container.status} + *URL*: garde-studios.de:{container.port} + ''' + embed.add_field(name=f'{container.name} 0/{container.players}', value=desc) + await ctx.send(embed=embed) + + @commands.hybrid_command(name='kill') + async def kill(self, ctx: commands.Context, server_name: str): + ''' + Kill & remove currently Running Servers + + Parameters + ---------- + ctx: commands.Context + The context of the command invocation + server_name: str + Name of the server that should be removed + ''' + + server_name = server_name.title() + + # Check if Server exist + rm = str() + conn = None + for container in containers: + if container.name == server_name: + rm = server_name + conn = container + break + + if not rm: + await ctx.send("---Server not found---") + return + + conn.container.remove(force=True) + #self.client.volumes.get(server_name).remove() + containers.remove(conn) + + await ctx.send(f"Server {server_name} killed successfully") + +if __name__ == '__main__': + for _ in range(10): + print("|", seed_generator(), "|") + print("Port:", find_free_port()) diff --git a/bot/compose.yml b/bot/compose.yml index b9b088e..96be4da 100644 --- a/bot/compose.yml +++ b/bot/compose.yml @@ -1,16 +1,15 @@ services: bot: + container_name: bot build: context: . dockerfile: Dockerfile - # volumes: - # - bot_data:/home + volumes: + - ./bot_data:/home + - /var/run/docker.sock:/var/run/docker.sock + networks: + - rcon - -volumes: - bot_data: - driver: local - driver_opts: - type: none - device: ./data - o: bind + +networks: + rcon: {} diff --git a/bot/mods.txt b/bot/mods.txt new file mode 100644 index 0000000..ab29fc9 --- /dev/null +++ b/bot/mods.txt @@ -0,0 +1,58 @@ +# Fabric API +https://cdn.modrinth.com/data/P7dR8mSH/versions/bK6OgzFj/fabric-api-0.102.1%2B1.21.1.jar + +# Cloth Config API +https://cdn.modrinth.com/data/9s6osm5g/versions/7jtvrmVP/cloth-config-15.0.130-fabric.jar + +# Moonlight Lib +#https://cdn.modrinth.com/data/twkfQtEc/versions/tP7HsFBI/moonlight-1.21-2.14.12-fabric.jar + +# Yungs API +https://cdn.modrinth.com/data/Ua7DFN59/versions/Nx7XHO30/YungsApi-1.21-Fabric-5.0.0.jar + +# Performance +https://cdn.modrinth.com/data/gvQqBUqZ/versions/5szYtenV/lithium-fabric-mc1.21.1-0.13.0.jar +https://cdn.modrinth.com/data/fALzjamp/versions/dPliWter/Chunky-1.4.16.jar +https://cdn.modrinth.com/data/s86X568j/versions/uT1cdd3k/ChunkyBorder-1.2.18.jar +https://cdn.modrinth.com/data/LFJf0Klb/versions/7e8Rxgsk/ce-2.1.1.jar + +# Proxy +#https://cdn.modrinth.com/data/8dI2tmqs/versions/AQhF7kvw/FabricProxy-Lite-2.9.0.jar + +# Monitoring +#https://cdn.modrinth.com/data/dbVXHSlv/versions/YcE9H1C5/fabricexporter-1.0.11.jar +#https://cdn.modrinth.com/data/l6YH9Als/versions/qTSaozEL/spark-1.10.97-fabric.jar + +# World Edit +https://cdn.modrinth.com/data/1u6JkXh5/versions/vBzkrSYP/worldedit-mod-7.3.6.jar + +# Dynmap +# https://cdn.modrinth.com/data/fRQREgAc/versions/ipBhc6VW/Dynmap-3.7-beta-6-fabric-1.21.jar + +# World Guard +https://cdn.modrinth.com/data/py6EMmAJ/versions/xpvSS4oW/yawp-0.0.2.10-alpha2.jar +https://cdn.modrinth.com/data/ohNO6lps/versions/gtorYSGm/ForgeConfigAPIPort-v21.1.0-1.21.1-Fabric.jar + +# Permission Management +#https://cdn.modrinth.com/data/Vebnzrzj/versions/oLykW1F8/LuckPerms-Fabric-5.4.139.jar + +# Custom +#https://cdn.modrinth.com/data/HjmxVlSr/versions/2Z4xpeH5/YungsBetterMineshafts-1.20.4-Fabric-4.4.0.jar +#https://cdn.modrinth.com/data/o1C1Dkj5/versions/4RpKnxDR/YungsBetterDungeons-1.20.4-Fabric-4.4.0.jar +#https://cdn.modrinth.com/data/3dT9sgt4/versions/V46v23Uz/YungsBetterOceanMonuments-1.20.4-Fabric-3.4.0.jar +#https://cdn.modrinth.com/data/kidLKymU/versions/Y05JQWx3/YungsBetterStrongholds-1.20.4-Fabric-4.4.0.jar +#https://cdn.modrinth.com/data/Z2mXHnxP/versions/QplnGAIz/YungsBetterNetherFortresses-1.20.4-Fabric-2.4.0.jar +#https://cdn.modrinth.com/data/t5FRdP87/versions/3CEVoaSN/YungsBetterWitchHuts-1.20.4-Fabric-3.4.0.jar +#https://cdn.modrinth.com/data/XNlO7sBv/versions/Rnvv7pHS/YungsBetterDesertTemples-1.20.4-Fabric-3.4.0.jar +#https://cdn.modrinth.com/data/Ht4BfYp6/versions/tx2e5Fjp/YungsBridges-1.20.4-Fabric-4.4.0.jar +#https://cdn.modrinth.com/data/ZYgyPyfq/versions/F1adMKW8/YungsExtras-1.20.4-Fabric-4.4.0.jar +#https://cdn.modrinth.com/data/z9Ve58Ih/versions/DIG3Vtjv/YungsBetterJungleTemples-1.20.4-Fabric-2.4.0.jar +#https://cdn.modrinth.com/data/2BwBOmBQ/versions/mRCm0pL5/YungsBetterEndIsland-1.20.4-Fabric-2.4.0.jar +https://cdn.modrinth.com/data/klXONLDA/versions/FQrJz3KA/villagesandpillages-fabric-mc1.21.1-1.0.1.jar +https://cdn.modrinth.com/data/tpehi7ww/versions/QmeQn0Mp/dungeons-and-taverns-v4.3.jar +#https://cdn.modrinth.com/data/fgmhI8kH/versions/5D3oDlLM/%5BFabric%5DCTOV-3.5.1.jar + +#https://cdn.modrinth.com/data/7tKn1fLd/versions/65K7sgmM/MobCaptains-v3.2.1.jar + +https://cdn.modrinth.com/data/cnIatHrN/versions/BfXSBkjs/universal_shops-1.7.1%2B1.21.jar + diff --git a/bot/requirements.txt b/bot/requirements.txt index c7b829a..a6dbfe8 100644 --- a/bot/requirements.txt +++ b/bot/requirements.txt @@ -1,14 +1,24 @@ -aiohttp==3.9.5 +aiohappyeyeballs==2.4.0 +aiohttp==3.10.5 aiosignal==1.3.1 -attrs==23.2.0 +async-timeout==4.0.3 +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.3.2 +discord==2.3.2 discord.py==2.4.0 +docker==7.1.0 frozenlist==1.4.1 -greenlet==3.0.3 -idna==3.7 +greenlet==3.1.1 +idna==3.10 mcrcon==0.7.0 -multidict==6.0.5 +multidict==6.1.0 python-dotenv==1.0.1 -python-statemachine==2.3.1 -SQLAlchemy==2.0.31 +pytz==2024.2 +requests==2.32.3 +six==1.16.0 +SQLAlchemy==2.0.35 +transitions==0.9.2 typing_extensions==4.12.2 -yarl==1.9.4 +urllib3==2.2.3 +yarl==1.12.1 diff --git a/bot/spawner.py b/bot/spawner.py new file mode 100644 index 0000000..b975b4c --- /dev/null +++ b/bot/spawner.py @@ -0,0 +1,7 @@ +import docker + +client = docker.from_env() + +print(client.containers.run("itzg/minecraft-server", detach=True)) + + diff --git a/postgres/compose.yml b/postgres/compose.yml index 0819cb9..94651ef 100644 --- a/postgres/compose.yml +++ b/postgres/compose.yml @@ -7,12 +7,5 @@ services: ports: - "5432:5432" volumes: - - db_data:/var/lib/postgresql/data + - ./db_data:/var/lib/postgresql/data -volumes: - db_data: - driver: local - driver_opts: - type: none - device: ./data - o: bind