Exemplo n.º 1
0
def list_instances(filter_str: str = '') -> None:
    """Print a list of all instances.

    Output a table of all instances with their respective Name, Server Version String, Status and persistence.

    Keyword Arguments:
        filter_str (str): Filter the list by instance name. (default: {''})
    """
    base_path = storage.get_instance_path(bare=True)
    server_paths = base_path.iterdir()
    servers = [x.name for x in server_paths]

    name_col_width = str(len(max(servers, key=len)) + 1)
    template = "{:" + name_col_width + "} {:<6} {:20} {:14} {:10} {:10}"
    title = template.format("Name", "Port", "Server Version", "Player Count",
                            "Status", "Persistent")

    print(title)
    for name in servers:
        if filter_str in name:
            cfg = config.get_properties(base_path / name / "server.properties")
            port = int(cfg.get("server-port"))
            server = MinecraftServer('localhost', port)
            status_info = status.get_simple_status(server)

            unit = service.get_unit(name)
            state = get_online_state(unit, status_info.get("proto"))

            player_ratio = f"{status_info.get('online')}/{cfg.get('max-players')}"
            contents = template.format(name, port, status_info.get("version"),
                                       player_ratio, state.capitalize(),
                                       str(service.is_enabled(unit)))
            print(contents)
Exemplo n.º 2
0
def export(instance: str, zip_path: Path = None, compress: bool = False, world_only: bool = False) -> Path:
    """Export a minecraft server instance to a Zip-File.

    Export a minecraft server instance to a Zip-File for archiving or similar.
    Optionally, the File can also be compressed and all config Files can be excluded.

    Arguments:
        instance (str): The name of the Instance to be exported.

    Keyword Arguments:
        zip_path (Path): The path of the Zip-File that is generated. (default: {None})
        compress (bool): True: Compress the Zip-File using ZIP_DEFLATE. False: Use ZIP_STORE (default: {False})
        world_only (bool): Only export the World data without configuration files. (default: {False})

    Returns:
        Path: The Path where the Zip-File was saved to.
    """
    if not zip_path:
        zip_path = Path(
            f"{instance}_{datetime.now().strftime('%y-%m-%d-%H.%M.%S')}.zip")

    server_path = get_instance_path(instance)

    filter_str = ""
    if world_only:
        server_cfg = config.get_properties(server_path / "server.properties")
        filter_str = server_cfg.get("level-name", "world")

    file_list = get_relative_paths(server_path, filter_str)
    total_size = sum((server_path / x).stat().st_size for x in file_list)
    compress_mode = zf.ZIP_DEFLATED if compress else zf.ZIP_STORED
    with perms.run_as(0, 0), zf.ZipFile(zip_path, "w", compression=compress_mode, allowZip64=True) as zip_file:
        written = 0
        for file_path in file_list:
            full_path = server_path / file_path
            written += full_path.stat().st_size
            msg = f"\r[{(written * 100 / total_size):3.0f}%] Writing: {file_path}...\033[K"
            print(msg, end='', flush=True)
            zip_file.write(full_path, file_path)
    print()

    try:
        login_name = os.getlogin()
    except FileNotFoundError:
        login_name = None
        print("WARN: Unable to retrieve Login Name.")
    if login_name:
        with perms.run_as(0, 0):
            chown(zip_path, login_name)

    print(f"Archive saved in '{zip_path}'.")
    return zip_path
Exemplo n.º 3
0
def mc_import(zip_path: Path, instance: str = None, world_only: bool = False) -> None:
    """Import a minecraft server instance from a Zip-File.

    Import a minecraft server instance from a Zip-File.
    Also, the world files can be extracted separately.

    Arguments:
        zip_path (Path): The path of the Zip-File to import from. (default: {None})

    Keyword Arguments:
        instance (str): The name of the Instance to be imported. Auto-generated from archive name if None. (default: {None})
        world_only (bool): Only import the World data without configuration files. (default: {False})
    """
    if instance is None:
        instance = zip_path.stem
        print(f"No Instance specified, importing to instance '{instance}'.")
    instance_path = get_instance_path(instance)
    if instance_path.exists() != world_only:
        if world_only:
            raise FileNotFoundError(
                f"Instance Path not found: {instance_path}.")
        raise FileExistsError("Instance already exists.")

    if not instance_path.exists():
        create_dirs(instance_path)
    filter_str = ""
    with zf.ZipFile(zip_path) as zip_file:
        if world_only:
            with zip_file.open("server.properties") as prop, tmpf.NamedTemporaryFile() as tmp_file:
                shutil.copyfileobj(prop, tmp_file)
                properties = config.get_properties(tmp_file.name)
                filter_str = properties.get("level-name", "world") + "/"
        file_list = (x for x in zip_file.infolist()
                     if x.name.startswith(filter_str))
        t_size = sum(x.file_size for x in file_list)
        written = 0
        for zpath in file_list:
            safe_zpath = zpath.filename.replace("../", "")
            dst_path = instance_path / safe_zpath
            with zip_file.open(zpath) as zfile, open(dst_path, 'wb') as dst_file:
                written += zpath.file_size
                print(f"\r[{(written * 100 / t_size):3.0f}%] Extracting: {safe_zpath}...\033[K",
                      end='', flush=True)
                shutil.copyfileobj(zfile, dst_file)
    print()
    chown(instance_path, user=SERVER_USER)
Exemplo n.º 4
0
def is_ready(instance: str) -> bool:
    """Check if the Server is ready to serve connections/is fully started.

    Args:
        instance (str): The Instance ID.

    Returns:
        bool: True if the Server is ready to serve connections.
    """
    cfg = config.get_properties(
        storage.get_instance_path(instance) / "server.properties")
    port = int(cfg.get("server-port"))
    try:
        server = MinecraftServer('localhost', port)
        mc_status = server.status()
        proto = mc_status.version.protocol
    except (ConnectionError, sock_error):
        return False

    return proto > -1
Exemplo n.º 5
0
def remove_jar(source: str, force: bool = False) -> None:
    """Remove an .jar-File from disk.

    Arguments:
        type_id (str): The type_id of the .jar-File to be deleted.
    Keyword Arguments:
        force (bool): Delete the Jar File without a prompt. (default: {False})
    """
    del_all = source == "all"
    if not del_all:
        del_path = get_jar_path(source)
        msg = f"Are you absolutely sure you want to remove the Server Jar '{source}'?"
    else:
        del_path = get_jar_path(bare=True)
        msg = "Are you sure you want to remove all unused cached Server Jars?"

    if not del_all:
        if not del_path.exists():
            raise FileNotFoundError(f"Type-ID not found in cache: {del_path}.")
        all_instances = get_instance_path(bare=True).iterdir()
        for instance_path in all_instances:
            env_path = instance_path / CFGVARS.get('system', 'env_file')
            try:
                env = config.get_properties(env_path)
            except OSError:
                env = {}
            server_jar = instance_path / env.get("JARFILE", "server.jar")
            if get_real_abspath(server_jar) == del_path:
                raise f"Type-ID is associated with Instance {instance_path.name}."
    else:
        if not del_path.exists():
            raise FileNotFoundError("Cache already cleared.")
    if force or visuals.bool_selector(msg):
        if not del_all:
            del_path.unlink()
        else:
            remove_all(del_path)
Exemplo n.º 6
0
def collect_server_data(instance: str) -> dict:
    """Collect Data about the server and return it as a dict.

    Args:
        instance (str): The instance of which the data should be collected.

    Returns:
        dict: A dict containing various Information about the server.
    """
    instance_path = storage.get_instance_path(instance)
    if not instance_path.exists():
        raise FileNotFoundError(f"Instance not found: {instance_path}.")

    properties = config.get_properties(instance_path / "server.properties")
    try:
        envinfo = config.get_properties(instance_path /
                                        CFGVARS.get('system', 'env_file'))
    except FileNotFoundError:
        envinfo = None

    port = properties.get("server-port")
    server = MinecraftServer('localhost', int(port))
    status_info = status.get_simple_status(server)

    files = storage.get_child_paths(instance_path)
    total_size = sum(x.stat().st_size for x in files)

    unit = service.get_unit(instance)
    state = get_online_state(unit, status_info.get("proto"))

    cmdvars = {
        k: v
        for k, v in (x.decode().split("=") for x in unit.Service.Environment)
    }
    cmd = " ".join(x.decode() for x in unit.Service.ExecStart[0][1])
    resolved_cmd = cmd.replace("${", "{").format(**cmdvars)

    jar_path = instance_path / cmdvars.get("JARFILE", "server.jar")
    resolved_jar_path = storage.get_real_abspath(jar_path)
    type_id = None
    if resolved_jar_path != jar_path:
        type_id = storage.get_type_id(resolved_jar_path)

    try:
        with open(instance_path / "whitelist.json") as wlist_hnd:
            whitelist = json.load(wlist_hnd)
    except FileNotFoundError:
        whitelist = None

    try:
        plugins = plugin.get_plugins(instance)
    except FileNotFoundError:
        plugins = None

    data = {
        "instance_name": instance,
        "instance_path": str(instance_path),
        "total_file_size": total_size,
        "status": {
            "players_online": status_info.get('online'),
            "players_max": properties.get('max-players', '?'),
            "protocol_name": status_info.get('proto'),
            "protocol_version": status_info.get('version'),
        },
        "service": {
            "description": unit.Unit.Description.decode(),
            "unit_file_state": unit.UnitFileState.decode(),
            "state": state,
            "main_pid": unit.Service.MainPID,
            "start_command": resolved_cmd,
            "memory_usage": unit.Service.MemoryCurrent,
            "env": dict(cmdvars),
        },
        "type_id": type_id,
        "config": {
            "server.properties": properties,
            "whitelist": whitelist,
            "env_file": envinfo
        },
        "plugins": plugins,
    }

    return data