diff --git a/pkgs/games/papermc/default.nix b/pkgs/games/papermc/default.nix index aa16e51a7ac8..29d5c6b85203 100644 --- a/pkgs/games/papermc/default.nix +++ b/pkgs/games/papermc/default.nix @@ -1,50 +1,15 @@ -{ - lib, - stdenvNoCC, - fetchurl, - jre, - makeBinaryWrapper, -}: -stdenvNoCC.mkDerivation (finalAttrs: { - pname = "papermc"; - version = "1.20.4.435"; - - src = - let - mcVersion = lib.versions.pad 3 finalAttrs.version; - buildNum = builtins.elemAt (lib.splitVersion finalAttrs.version) 3; - in - fetchurl { - url = "https://papermc.io/api/v2/projects/paper/versions/${mcVersion}/builds/${buildNum}/downloads/paper-${mcVersion}-${buildNum}.jar"; - hash = "sha256-NrIsYLoAAWORw/S26NDFjYBVwpNITJxuWGZow3696wM="; - }; - - installPhase = '' - runHook preInstall - - install -D $src $out/share/papermc/papermc.jar - - makeWrapper ${lib.getExe jre} "$out/bin/minecraft-server" \ - --append-flags "-jar $out/share/papermc/papermc.jar nogui" - - runHook postInstall - ''; - - nativeBuildInputs = [ - makeBinaryWrapper - ]; - - dontUnpack = true; - preferLocalBuild = true; - allowSubstitutes = false; - - meta = { - description = "High-performance Minecraft Server"; - homepage = "https://papermc.io/"; - sourceProvenance = with lib.sourceTypes; [ binaryBytecode ]; - license = lib.licenses.gpl3Only; - platforms = lib.platforms.unix; - maintainers = with lib.maintainers; [ aaronjanse neonfuz ]; - mainProgram = "minecraft-server"; - }; +{ callPackage, lib, ... }: +let + versions = lib.importJSON ./versions.json; + latestVersion = lib.last (builtins.sort lib.versionOlder (builtins.attrNames versions)); + escapeVersion = builtins.replaceStrings [ "." ] [ "_" ]; + packages = lib.mapAttrs' + (version: value: { + name = "papermc-${escapeVersion version}"; + value = callPackage ./derivation.nix { inherit (value) version hash; }; + }) + versions; +in +lib.recurseIntoAttrs (packages // { + papermc = builtins.getAttr "papermc-${escapeVersion latestVersion}" packages; }) diff --git a/pkgs/games/papermc/derivation.nix b/pkgs/games/papermc/derivation.nix new file mode 100644 index 000000000000..f244031f0590 --- /dev/null +++ b/pkgs/games/papermc/derivation.nix @@ -0,0 +1,50 @@ +{ lib, stdenvNoCC, fetchurl, makeBinaryWrapper, jre, version, hash }: + +stdenvNoCC.mkDerivation { + pname = "papermc"; + inherit version; + + src = + let + version-split = lib.strings.splitString "-" version; + mcVersion = builtins.elemAt version-split 0; + buildNum = builtins.elemAt version-split 1; + in + fetchurl { + url = "https://papermc.io/api/v2/projects/paper/versions/${mcVersion}/builds/${buildNum}/downloads/paper-${version}.jar"; + inherit hash; + }; + + installPhase = '' + runHook preInstall + + install -D $src $out/share/papermc/papermc.jar + + makeWrapper ${lib.getExe jre} "$out/bin/minecraft-server" \ + --append-flags "-jar $out/share/papermc/papermc.jar nogui" + + runHook postInstall + ''; + + nativeBuildInputs = [ + makeBinaryWrapper + ]; + + dontUnpack = true; + preferLocalBuild = true; + allowSubstitutes = false; + + passthru = { + updateScript = ./update.py; + }; + + meta = { + description = "High-performance Minecraft Server"; + homepage = "https://papermc.io/"; + sourceProvenance = with lib.sourceTypes; [ binaryBytecode ]; + license = lib.licenses.gpl3Only; + platforms = lib.platforms.unix; + maintainers = with lib.maintainers; [ aaronjanse neonfuz MayNiklas ]; + mainProgram = "minecraft-server"; + }; +} diff --git a/pkgs/games/papermc/update.py b/pkgs/games/papermc/update.py new file mode 100755 index 000000000000..6e2d0ec9274e --- /dev/null +++ b/pkgs/games/papermc/update.py @@ -0,0 +1,145 @@ +#!/usr/bin/env nix-shell +#! nix-shell -i python -p "python3.withPackages (ps: with ps; [ps.requests ])" + +import hashlib +import base64 +import json + +import requests + + +class Version: + def __init__(self, name: str): + self.name: str = name + self.hash: str | None = None + self.build_number: int | None = None + + @property + def full_name(self): + v_name = f"{self.name}-{self.build_number}" + + # this will probably never happen because the download of a build with NoneType in URL would fail + if not self.name or not self.build_number: + print(f"Warning: version '{v_name}' contains NoneType!") + + return v_name + + +class VersionManager: + def __init__(self, base_url: str = "https://api.papermc.io/v2/projects/paper"): + self.versions: list[Version] = [] + self.base_url: str = base_url + + def fetch_versions(self, not_before_minor_version: int = 18): + """ + Fetch all versions after given minor release + """ + + response = requests.get(self.base_url) + + try: + response.raise_for_status() + + except requests.exceptions.HTTPError as e: + print(e) + return + + # we only want versions that are no pre-releases + release_versions = filter( + lambda v_name: 'pre' not in v_name, response.json()["versions"]) + + for version_name in release_versions: + + # split version string, convert to list ot int + version_split = version_name.split(".") + version_split = list(map(int, version_split)) + + # check if version is higher than 1. + if (version_split[0] > 1) or (version_split[0] == 1 and version_split[1] >= not_before_minor_version): + self.versions.append(Version(version_name)) + + def fetch_latest_version_builds(self): + """ + Set latest build number to each version + """ + + for version in self.versions: + url = f"{self.base_url}/versions/{version.name}" + response = requests.get(url) + + # check that we've got a good response + try: + response.raise_for_status() + + except requests.exceptions.HTTPError as e: + print(e) + return + + # the highest build in response.json()['builds']: + latest_build = response.json()['builds'][-1] + version.build_number = latest_build + + def generate_version_hashes(self): + """ + Generate and set the hashes for all registered versions (versions will are downloaded to memory) + """ + + for version in self.versions: + url = f"{self.base_url}/versions/{version.name}/builds/{version.build_number}/downloads/paper-{version.full_name}.jar" + version.hash = self.download_and_generate_sha256_hash(url) + + def versions_to_json(self): + return json.dumps( + {version.name: {'hash': version.hash, 'version': version.full_name} + for version in self.versions}, + indent=4 + ) + + def write_versions(self, file_name: str): + """ write all processed versions to json """ + # save json to versions.json + with open(file_name, 'w') as f: + f.write(self.versions_to_json() + "\n") + + @staticmethod + def download_and_generate_sha256_hash(url: str) -> str | None: + """ + Fetch the tarball from the given URL. + Then generate a sha256 hash of the tarball. + """ + + try: + # Download the file from the URL + response = requests.get(url) + response.raise_for_status() + + except requests.exceptions.RequestException as e: + print(f"Error: {e}") + return None + + # Create a new SHA-256 hash object + sha256_hash = hashlib.sha256() + + # Update the hash object with chunks of the downloaded content + for byte_block in response.iter_content(4096): + sha256_hash.update(byte_block) + + # Get the hexadecimal representation of the hash + hash_value = sha256_hash.digest() + + # Encode the hash value in base64 + base64_hash = base64.b64encode(hash_value).decode('utf-8') + + # Format it as "sha256-{base64_hash}" + sri_representation = f"sha256-{base64_hash}" + + return sri_representation + + +if __name__ == '__main__': + version_manager = VersionManager() + + version_manager.fetch_versions() + version_manager.fetch_latest_version_builds() + version_manager.generate_version_hashes() + version_manager.write_versions(file_name="versions.json") diff --git a/pkgs/games/papermc/versions.json b/pkgs/games/papermc/versions.json new file mode 100644 index 000000000000..0fa4bad8ba04 --- /dev/null +++ b/pkgs/games/papermc/versions.json @@ -0,0 +1,50 @@ +{ + "1.18": { + "hash": "sha256-PJlfINrk5OIdVVT6yVegqKXIW9W/NJFfrEtPFuDvEBs=", + "version": "1.18-66" + }, + "1.18.1": { + "hash": "sha256-qUkXpEcsLLyZB6FcZmu7eE+V7Ne1PHe8CP5xED5Uh/U=", + "version": "1.18.1-216" + }, + "1.18.2": { + "hash": "sha256-BXjxj01jK0lLRo7FaztBS1tW/qCH7n05z23N9MnQHwU=", + "version": "1.18.2-388" + }, + "1.19": { + "hash": "sha256-DTnKzFGneysHHhzoYvy/C0pL1mjMfosxNZjYT6Cfq6w=", + "version": "1.19-81" + }, + "1.19.1": { + "hash": "sha256-Wv4jofreksVHEk+odLx9kI+mdvSfCYefqHYiS2Lp1Rs=", + "version": "1.19.1-111" + }, + "1.19.2": { + "hash": "sha256-LrXHRZ7JS83Fl+1xHVSaOrSw/aE+QSoHkqGgabWQOGQ=", + "version": "1.19.2-307" + }, + "1.19.3": { + "hash": "sha256-MAfyxjjV8E7TK2raozBT/jY0zPp0NFyD0+pJgtONtdw=", + "version": "1.19.3-448" + }, + "1.19.4": { + "hash": "sha256-5YfXjLo+me+MS8JM8gzDvbvonjOwtXIHBEavTra+XM8=", + "version": "1.19.4-550" + }, + "1.20": { + "hash": "sha256-HkzPwFmfSR7m/uRFXTciMyrF14WE/M1Vy7O1HhFQRQU=", + "version": "1.20-17" + }, + "1.20.1": { + "hash": "sha256-I0qbMgmBAMb8EWZk1k42zNtYtbZJrw+AvMywiwJV6uo=", + "version": "1.20.1-196" + }, + "1.20.2": { + "hash": "sha256-ujQKg1rEC4Vjqn7aHNZHmhGnYjQJyJosNc2ddJDtF6c=", + "version": "1.20.2-318" + }, + "1.20.4": { + "hash": "sha256-NrIsYLoAAWORw/S26NDFjYBVwpNITJxuWGZow3696wM=", + "version": "1.20.4-435" + } +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index c1677d7ddb24..5e03fbae6b72 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -37702,7 +37702,9 @@ with pkgs; pacvim = callPackage ../games/pacvim { }; - papermc = callPackage ../games/papermc { }; + papermcServers = callPackages ../games/papermc { }; + + papermc = papermcServers.papermc; path-of-building = qt6Packages.callPackage ../games/path-of-building {};