import docker import os import json LABEL_MANAGED = "stronghold.managed" LABEL_VALUE = "true" def get_docker_client(): """Get Docker client, automatically detecting Docker or Podman""" try: # Try Docker socket first if os.path.exists('/var/run/docker.sock'): return docker.DockerClient(base_url='unix://var/run/docker.sock') # Try Podman rootful if os.path.exists('/run/podman/podman.sock'): return docker.DockerClient(base_url='unix://run/podman/podman.sock') # Try Podman rootless xdg_runtime = os.environ.get('XDG_RUNTIME_DIR', '') podman_sock = f'{xdg_runtime}/podman/podman.sock' if xdg_runtime and os.path.exists(podman_sock): return docker.DockerClient(base_url=f'unix://{podman_sock}') # Fallback to default return docker.DockerClient(base_url='unix://var/run/docker.sock') except Exception as e: raise Exception(f"Failed to connect to Docker/Podman: {e}") def list_containers(client): """List all Stronghold-managed containers""" filters = {"label": f"{LABEL_MANAGED}={LABEL_VALUE}"} return client.containers.list(all=True, filters=filters) def create_container(client, name, config): """Create a new container with Stronghold label""" # Build environment variables from config environment = { "EULA": "TRUE" if config.get("acceptEULA") else "FALSE", "TYPE": config.get("serverType", "PAPER"), } # Add optional environment variables if config.get("version") and config.get("version") != "LATEST": environment["VERSION"] = config["version"] if config.get("memory"): environment["MEMORY"] = config["memory"] if config.get("difficulty"): environment["DIFFICULTY"] = config["difficulty"] if config.get("gamemode"): environment["MODE"] = config["gamemode"] if config.get("levelType"): environment["LEVEL_TYPE"] = config["levelType"] if config.get("motd"): environment["MOTD"] = config["motd"] if config.get("maxPlayers"): environment["MAX_PLAYERS"] = str(config["maxPlayers"]) if config.get("viewDistance"): environment["VIEW_DISTANCE"] = str(config["viewDistance"]) if not config.get("pvpEnabled", True): environment["PVP"] = "false" if config.get("allowFlight"): environment["ALLOW_FLIGHT"] = "true" if config.get("enableRCON"): environment["ENABLE_RCON"] = "true" if config.get("rconPassword"): environment["RCON_PASSWORD"] = config["rconPassword"] # Port bindings (host_port:container_port) port = config.get("port", "25565") port_bindings = {f"{25565}/tcp": int(port)} if config.get("enableRCON"): rcon_port = config.get("rconPort", "25575") port_bindings[f"{25575}/tcp"] = int(rcon_port) # Create or get volume volume_name = f"{name}_data" try: client.volumes.get(volume_name) except: client.volumes.create(name=volume_name, labels={LABEL_MANAGED: LABEL_VALUE}) # Create container with proper volume mount restart_policy = config.get("restartPolicy", "unless-stopped") container = client.containers.create( image="itzg/minecraft-server", name=name, environment=environment, ports=port_bindings, volumes=[f"{volume_name}:/data"], labels={LABEL_MANAGED: LABEL_VALUE}, restart_policy={"Name": restart_policy} if restart_policy != "no" else None, detach=True ) return container def start_container(client, container_id): """Start a container""" container = client.containers.get(container_id) container.start() return container def stop_container(client, container_id): """Stop a container""" container = client.containers.get(container_id) container.stop() return container def remove_container(client, container_id): """Remove a container""" container = client.containers.get(container_id) container.remove(force=True) return container