#!/usr/bin/env python3 from argparse import ArgumentParser, BooleanOptionalAction from datetime import datetime from re import search from typing import Iterable import docker from ftbtypes import * from jsongetcache import * import jsongetcache default_base_url = "https://api.modpacks.ch" cache_path = Path("cache") def get_entity(route: str, base_url: str = default_base_url): slashes = '/\\' cachefile = cache_path / f"{route.strip(slashes)}.json" return jsongetcache.load_cached(base_url + route, cachefile) def get_latest_release(versions: Iterable[ModPackVersion], version_types: list[str] = ["release"]): return max((version for version in versions if version["type"].lower() in version_types), key=lambda version: version["updated"], default=None) def get_version_by_id(versions: Iterable[ModPackVersion], version_id: int): return next((version for version in versions if version["id"] == version_id), None) def get_version_by_name(versions: Iterable[ModPackVersion], version_name: str): return next((version for version in versions if version["name"] == version_name), None) def get_modpack_route(modpack: ModPackManifest | int): return f"/public/modpack/{modpack if isinstance(modpack, int) else modpack['id']}" def get_modpack_manifest(modpack: ModPackManifest | int) -> ModPackManifest: return get_entity(get_modpack_route(modpack)) def get_version_route(modpack: ModPackManifest | int, version: ModPackVersion | int): return f"{get_modpack_route(modpack)}/{version if isinstance(version, int) else version['id']}" def get_version_manifest(modpack: ModPackManifest | int, version: ModPackVersion | int) -> ModPackVersionManifest: return get_entity(get_version_route(modpack, version)) def version_without_build(version: str): print(version) match = search(r"^(\d+(.\d+)*)\+(\d+)?$", version) if match is None: raise Exception(f"Invalid version string: {version}") else: return match.group(1) def get_modpack_slug(modpack: ModPackManifest): return '-'.join(filter(None, modpack["name"].lower().split(' '))) def get_installer(modpack: ModPackManifest | int, version: ModPackVersion | int, architecture: docker.Platforms): match architecture: case "linux/arm64": return f"{get_version_route(modpack, version)}/server/arm/linux", "83b9ef3f8b0f525da83c10fd8692c12a6a200c5ce79eba9da97ac29a414232fd" case "linux/amd64": return f"{get_version_route(modpack, version)}/server/linux", "9c5eed5e160e329bb6c393db549db356b9cc6a9711a5461aba35607b4124485a" case _: raise Exception(f"Invalid or unsupported architecture {architecture}!") def parse_arguments(): parser = ArgumentParser("build.py", description="FTB Docker image build helper.") parser.add_argument("modpack", type=int, help="Modpack ID") parser.add_argument("--version", "-v", type=str, help="Specific Modpack Version ID, otherwise uses the latest release") parser.add_argument("--amd64", action=BooleanOptionalAction, default=True, help="Build releases for AMD64") parser.add_argument("--arm64", action=BooleanOptionalAction, default=True, help="Build releases for ARM64") return parser.parse_args() def get_version_by_description(modpack: ModPackManifest, version_name: str | None) -> ModPackVersion | None: if version_name is None: return get_version_by_description(modpack, "release") if version_name.lower() in ["alpha", "beta", "release"]: return get_latest_release(modpack["versions"], version_types=[version_name.lower()]) match get_version_by_name(modpack["versions"], version_name): case None: pass case idd_version: return idd_version try: match get_version_by_id(modpack["versions"], int(version_name)): case None: pass case idd_version: return idd_version except ValueError: pass if __name__ == "__main__": args = parse_arguments() modpack = get_modpack_manifest(args.modpack) slug = get_modpack_slug(modpack) print("Slug", slug) selected_version = get_version_by_description(modpack, args.version) if selected_version is None: print(f"Available versions for: {modpack['name']}") for version in modpack["versions"]: print(f"- {version['type']} {version['name']} [{version['id']}]") raise Exception(f"No version was found matching \"{args.version}\"") version = get_version_manifest(modpack, selected_version) print(f"{modpack['name']} version {version['name']}: updated {datetime.fromtimestamp(version['updated'])}") java_target = next((target for target in version["targets"] if target["type"].lower() == "runtime" and target["name"].lower() == "java"), None) if java_target is None: raise Exception(f"{modpack['name']} version {version['name']} has no java target: {' ,'.join(str(x) for x in version['targets'])}") # java_version = version_without_build(java_target["version"]) print(f"Required java version is version {java_target['version']}") java_version = java_target["version"].replace('+', '_') # base_image = f"azul/zulu-openjdk:{java_version}-jre" base_image = f"eclipse-temurin:{java_version}-jre" print(f"Using docker base image: {base_image}") repo = f"hub.cnml.de/{slug}" semver_version_tags = docker.semver_tags(version["name"]) platforms: list[docker.Platforms] = list(filter(None, [ "linux/arm64" if args.arm64 else None, "linux/amd64" if args.amd64 else None ])) for platform in platforms: installer, checksum = get_installer(modpack, version, platform) tags = list(f"{ver}-{platform[(platform.rfind('/')+1):]}" for ver in semver_version_tags) print(tags) print(installer, checksum) docker.buildx(repo, tags, [platform], build_args={ "INSTALLER_URL": default_base_url + installer, "INSTALLER_CHECKSUM": checksum, "MODPACK_ID": str(modpack["id"]), "MODPACK_VERSION": str(version["id"]), "BASE_IMAGE": base_image }, write_command=True) for version_tag in semver_version_tags: tags = list(f"{version_tag}-{platform[(platform.rfind('/')+1):]}" for platform in platforms) docker.create_manifest(repo, version_tag, tags, write_command=True)