diff --git a/contrib/package/nix/copyparty/default.nix b/contrib/package/nix/copyparty/default.nix index 28d733ab..22ae5bcd 100644 --- a/contrib/package/nix/copyparty/default.nix +++ b/contrib/package/nix/copyparty/default.nix @@ -1,10 +1,10 @@ { lib, - stdenv, - makeWrapper, + buildPythonApplication, fetchurl, util-linux, python, + setuptools, jinja2, impacket, pyopenssl, @@ -15,6 +15,10 @@ pyzmq, ffmpeg, mutagen, + pyftpdlib, + magic, + partftpy, + fusepy, # for partyfuse # use argon2id-hashed passwords in config files (sha2 is always available) withHashedPasswords ? true, @@ -40,12 +44,21 @@ # send ZeroMQ messages from event-hooks withZeroMQ ? true, + # enable FTP server + withFTP ? true, + # enable FTPS support in the FTP server withFTPS ? false, + # enable TFTP server + withTFTP ? false, + # samba/cifs server; dangerous and buggy, enable if you really need it withSMB ? false, + # enables filetype detection for nameless uploads + withMagic ? false, + # extra packages to add to the PATH extraPackages ? [ ], @@ -58,14 +71,23 @@ let pinData = lib.importJSON ./pin.json; - pyEnv = python.withPackages ( - ps: - with ps; + runtimeDeps = ([ util-linux ] ++ extraPackages ++ lib.optional withMediaProcessing ffmpeg); +in +buildPythonApplication { + pname = "copyparty"; + inherit (pinData) version; + src = fetchurl { + inherit (pinData) url hash; + }; + dependencies = [ jinja2 + fusepy ] ++ lib.optional withSMB impacket + ++ lib.optional withFTP pyftpdlib ++ lib.optional withFTPS pyopenssl + ++ lib.optional withTFTP partftpy ++ lib.optional withCertgen cfssl ++ lib.optional withThumbnails pillow ++ lib.optional withFastThumbnails pyvips @@ -73,25 +95,14 @@ let ++ lib.optional withBasicAudioMetadata mutagen ++ lib.optional withHashedPasswords argon2-cffi ++ lib.optional withZeroMQ pyzmq - ++ (extraPythonPackages ps) - ); + ++ lib.optional withMagic magic + ++ (extraPythonPackages python.pkgs); + makeWrapperArgs = [ "--prefix PATH : ${lib.makeBinPath runtimeDeps}" ]; - runtimeDeps = ([ util-linux ] ++ extraPackages ++ lib.optional withMediaProcessing ffmpeg); -in -stdenv.mkDerivation { - pname = "copyparty"; - inherit (pinData) version; - src = fetchurl { - inherit (pinData) url hash; - }; - nativeBuildInputs = [ makeWrapper ]; - dontUnpack = true; - installPhase = '' - install -Dm755 $src $out/share/copyparty-sfx.py - makeWrapper ${pyEnv.interpreter} $out/bin/copyparty \ - --prefix PATH : ${lib.makeBinPath runtimeDeps} \ - --add-flag $out/share/copyparty-sfx.py - ''; + pyproject = true; + build-system = [ + setuptools + ]; meta = { description = "Turn almost any device into a file server"; longDescription = '' @@ -101,8 +112,7 @@ stdenv.mkDerivation { homepage = "https://github.com/9001/copyparty"; changelog = "https://github.com/9001/copyparty/releases/tag/v${pinData.version}"; license = lib.licenses.mit; - inherit (python.meta) platforms; mainProgram = "copyparty"; - sourceProvenance = [ lib.sourceTypes.binaryBytecode ]; + sourceProvenance = [ lib.sourceTypes.fromSource ]; }; } diff --git a/contrib/package/nix/copyparty/pin.json b/contrib/package/nix/copyparty/pin.json index 55f1c2e1..2651d12e 100644 --- a/contrib/package/nix/copyparty/pin.json +++ b/contrib/package/nix/copyparty/pin.json @@ -1,5 +1,5 @@ { - "url": "https://github.com/9001/copyparty/releases/download/v1.19.1/copyparty-sfx.py", + "url": "https://github.com/9001/copyparty/releases/download/v1.19.1/copyparty-1.19.1.tar.gz", "version": "1.19.1", - "hash": "sha256-Orn0N//DD5/5rIWK9yYRcvyOnt/bKCE9CeoxkfNO76s=" + "hash": "sha256-u8JQ2yPrgLyWwnsu+kVs4ef0nH36q625GlcfcAZLb5E=" } \ No newline at end of file diff --git a/contrib/package/nix/copyparty/update.py b/contrib/package/nix/copyparty/update.py index 363773c0..90542d2e 100755 --- a/contrib/package/nix/copyparty/update.py +++ b/contrib/package/nix/copyparty/update.py @@ -11,14 +11,14 @@ import base64 import json import hashlib import sys -import re +import tarfile from pathlib import Path OUTPUT_FILE = Path("pin.json") -TARGET_ASSET = "copyparty-sfx.py" +TARGET_ASSET = lambda version: f"copyparty-{version}.tar.gz" HASH_TYPE = "sha256" LATEST_RELEASE_URL = "https://api.github.com/repos/9001/copyparty/releases/latest" -DOWNLOAD_URL = lambda version: f"https://github.com/9001/copyparty/releases/download/v{version}/{TARGET_ASSET}" +DOWNLOAD_URL = lambda version: f"https://github.com/9001/copyparty/releases/download/v{version}/{TARGET_ASSET(version)}" def get_formatted_hash(binary): @@ -29,11 +29,13 @@ def get_formatted_hash(binary): return f"{HASH_TYPE}-{encoded_hash}" -def version_from_sfx(binary): - result = re.search(b'^VER = "(.*)"$', binary, re.MULTILINE) - if result: - return result.groups(1)[0].decode("ascii") +def version_from_tar_gz(path): + with tarfile.open(path) as tarball: + release_name = tarball.getmembers()[0].name + prefix = "copyparty-" + if release_name.startswith(prefix): + return release_name.replace(prefix, "") raise ValueError("version not found in provided file") @@ -42,7 +44,7 @@ def remote_release_pin(): response = requests.get(LATEST_RELEASE_URL).json() version = response["tag_name"].lstrip("v") - asset_info = [a for a in response["assets"] if a["name"] == TARGET_ASSET][0] + asset_info = [a for a in response["assets"] if a["name"] == TARGET_ASSET(version)][0] download_url = asset_info["browser_download_url"] asset = requests.get(download_url) formatted_hash = get_formatted_hash(asset.content) @@ -52,10 +54,9 @@ def remote_release_pin(): def local_release_pin(path): - asset = path.read_bytes() - version = version_from_sfx(asset) + version = version_from_tar_gz(path) download_url = DOWNLOAD_URL(version) - formatted_hash = get_formatted_hash(asset) + formatted_hash = get_formatted_hash(path.read_bytes()) result = {"url": download_url, "version": version, "hash": formatted_hash} return result diff --git a/contrib/package/nix/partftpy/default.nix b/contrib/package/nix/partftpy/default.nix new file mode 100644 index 00000000..8293b44a --- /dev/null +++ b/contrib/package/nix/partftpy/default.nix @@ -0,0 +1,30 @@ +{ + lib, + buildPythonPackage, + fetchurl, + setuptools, +}: +let + pinData = lib.importJSON ./pin.json; +in + +buildPythonPackage rec { + pname = "partftpy"; + inherit (pinData) version; + pyproject = true; + + src = fetchurl { + inherit (pinData) url hash; + }; + + build-system = [ setuptools ]; + + pythonImportsCheck = [ "partftpy.TftpServer" ]; + + meta = { + description = "Pure Python TFTP library (copyparty edition)"; + homepage = "https://github.com/9001/partftpy"; + changelog = "https://github.com/9001/partftpy/releases/tag/${version}"; + license = lib.licenses.mit; + }; +} diff --git a/contrib/package/nix/partftpy/pin.json b/contrib/package/nix/partftpy/pin.json new file mode 100644 index 00000000..99882f05 --- /dev/null +++ b/contrib/package/nix/partftpy/pin.json @@ -0,0 +1,5 @@ +{ + "url": "https://github.com/9001/partftpy/releases/download/v0.4.0/partftpy-0.4.0.tar.gz", + "version": "0.4.0", + "hash": "sha256-5Q2zyuJ892PGZmb+YXg0ZPW/DK8RDL1uE0j5HPd4We0=" +} \ No newline at end of file diff --git a/contrib/package/nix/partftpy/update.py b/contrib/package/nix/partftpy/update.py new file mode 100755 index 00000000..01f046b5 --- /dev/null +++ b/contrib/package/nix/partftpy/update.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +# Update the Nix package pin +# +# Usage: ./update.sh + +import base64 +import json +import hashlib +import sys +from pathlib import Path + +OUTPUT_FILE = Path("pin.json") +TARGET_ASSET = lambda version: f"partftpy-{version}.tar.gz" +HASH_TYPE = "sha256" +LATEST_RELEASE_URL = "https://api.github.com/repos/9001/partftpy/releases/latest" + + +def get_formatted_hash(binary): + hasher = hashlib.new("sha256") + hasher.update(binary) + asset_hash = hasher.digest() + encoded_hash = base64.b64encode(asset_hash).decode("ascii") + return f"{HASH_TYPE}-{encoded_hash}" + + +def remote_release_pin(): + import requests + + response = requests.get(LATEST_RELEASE_URL).json() + version = response["tag_name"].lstrip("v") + asset_info = [a for a in response["assets"] if a["name"] == TARGET_ASSET(version)][0] + download_url = asset_info["browser_download_url"] + asset = requests.get(download_url) + formatted_hash = get_formatted_hash(asset.content) + + result = {"url": download_url, "version": version, "hash": formatted_hash} + return result + + +def main(): + result = remote_release_pin() + + print(result) + json_result = json.dumps(result, indent=4) + OUTPUT_FILE.write_text(json_result) + + +if __name__ == "__main__": + main() diff --git a/contrib/package/nix/partyfuse/default.nix b/contrib/package/nix/partyfuse/default.nix deleted file mode 100644 index 59faa914..00000000 --- a/contrib/package/nix/partyfuse/default.nix +++ /dev/null @@ -1,26 +0,0 @@ -{ - stdenvNoCC, - copyparty, - python3, - makeBinaryWrapper, -}: -let - python = python3.withPackages (p: [ p.fusepy ]); -in -stdenvNoCC.mkDerivation { - pname = "partyfuse"; - inherit (copyparty) version meta; - src = ../../../..; - - nativeBuildInputs = [ makeBinaryWrapper ]; - - installPhase = '' - runHook preInstall - - install -Dm444 bin/partyfuse.py -t $out/share/copyparty - makeWrapper ${python.interpreter} $out/bin/partyfuse \ - --add-flag $out/share/copyparty/partyfuse.py - - runHook postInstall - ''; -} diff --git a/contrib/package/nix/u2c/default.nix b/contrib/package/nix/u2c/default.nix deleted file mode 100644 index dc1e4c56..00000000 --- a/contrib/package/nix/u2c/default.nix +++ /dev/null @@ -1,24 +0,0 @@ -{ - stdenvNoCC, - copyparty, - python312, - makeBinaryWrapper, -}: -stdenvNoCC.mkDerivation { - pname = "u2c"; - inherit (copyparty) version meta; - src = ../../../..; - - nativeBuildInputs = [ makeBinaryWrapper ]; - - installPhase = '' - runHook preInstall - - install -Dm444 bin/u2c.py -t $out/share/copyparty - mkdir $out/bin - makeWrapper ${python312.interpreter} $out/bin/u2c \ - --add-flag $out/share/copyparty/u2c.py - - runHook postInstall - ''; -} diff --git a/flake.nix b/flake.nix index 5f39d605..c5181bfe 100644 --- a/flake.nix +++ b/flake.nix @@ -12,17 +12,14 @@ }: { nixosModules.default = ./contrib/nixos/modules/copyparty.nix; - overlays.default = final: prev: rec { + overlays.default = final: prev: { copyparty = final.python3.pkgs.callPackage ./contrib/package/nix/copyparty { ffmpeg = final.ffmpeg-full; }; - - partyfuse = prev.callPackage ./contrib/package/nix/partyfuse { - inherit copyparty; - }; - - u2c = prev.callPackage ./contrib/package/nix/u2c { - inherit copyparty; + python3 = prev.python3.override { + packageOverrides = pyFinal: pyPrev: { + partftpy = pyFinal.callPackage ./contrib/package/nix/partftpy { }; + }; }; }; } @@ -54,8 +51,6 @@ packages = { inherit (pkgs) copyparty - partyfuse - u2c ; default = self.packages.${system}.copyparty; };