Build nix packages from source (#253)

* nix: get source tarball with update.py

* nix: build from source

nix: remove u2c and partyfuse packages
The main copyparty package has u2c and partyfuse, so these packages are
redundant now

nix: add fusepy dependency

fix: nix:  use replace pyfuse with fusepy

* nix: fix extra python packages

* nix: add optional dependencies

* nix: add partftpy package

* nix: add tftp parameter to package

* nix: enable pyproject for partftpy package

* nix: replace partftpy overlay with real package

* nix: add updater for partftpy

* nix: bring back local release pin to update.py

nix: update local release pin function in update.py

---------

Signed-off-by: Toast <39011842+toast003@users.noreply.github.com>
This commit is contained in:
Toast 2025-08-16 13:29:03 +09:00 committed by GitHub
parent 43a19779c1
commit 187cae25bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 139 additions and 98 deletions

View file

@ -1,10 +1,10 @@
{ {
lib, lib,
stdenv, buildPythonApplication,
makeWrapper,
fetchurl, fetchurl,
util-linux, util-linux,
python, python,
setuptools,
jinja2, jinja2,
impacket, impacket,
pyopenssl, pyopenssl,
@ -15,6 +15,10 @@
pyzmq, pyzmq,
ffmpeg, ffmpeg,
mutagen, mutagen,
pyftpdlib,
magic,
partftpy,
fusepy, # for partyfuse
# use argon2id-hashed passwords in config files (sha2 is always available) # use argon2id-hashed passwords in config files (sha2 is always available)
withHashedPasswords ? true, withHashedPasswords ? true,
@ -40,12 +44,21 @@
# send ZeroMQ messages from event-hooks # send ZeroMQ messages from event-hooks
withZeroMQ ? true, withZeroMQ ? true,
# enable FTP server
withFTP ? true,
# enable FTPS support in the FTP server # enable FTPS support in the FTP server
withFTPS ? false, withFTPS ? false,
# enable TFTP server
withTFTP ? false,
# samba/cifs server; dangerous and buggy, enable if you really need it # samba/cifs server; dangerous and buggy, enable if you really need it
withSMB ? false, withSMB ? false,
# enables filetype detection for nameless uploads
withMagic ? false,
# extra packages to add to the PATH # extra packages to add to the PATH
extraPackages ? [ ], extraPackages ? [ ],
@ -58,14 +71,23 @@
let let
pinData = lib.importJSON ./pin.json; pinData = lib.importJSON ./pin.json;
pyEnv = python.withPackages ( runtimeDeps = ([ util-linux ] ++ extraPackages ++ lib.optional withMediaProcessing ffmpeg);
ps: in
with ps; buildPythonApplication {
pname = "copyparty";
inherit (pinData) version;
src = fetchurl {
inherit (pinData) url hash;
};
dependencies =
[ [
jinja2 jinja2
fusepy
] ]
++ lib.optional withSMB impacket ++ lib.optional withSMB impacket
++ lib.optional withFTP pyftpdlib
++ lib.optional withFTPS pyopenssl ++ lib.optional withFTPS pyopenssl
++ lib.optional withTFTP partftpy
++ lib.optional withCertgen cfssl ++ lib.optional withCertgen cfssl
++ lib.optional withThumbnails pillow ++ lib.optional withThumbnails pillow
++ lib.optional withFastThumbnails pyvips ++ lib.optional withFastThumbnails pyvips
@ -73,25 +95,14 @@ let
++ lib.optional withBasicAudioMetadata mutagen ++ lib.optional withBasicAudioMetadata mutagen
++ lib.optional withHashedPasswords argon2-cffi ++ lib.optional withHashedPasswords argon2-cffi
++ lib.optional withZeroMQ pyzmq ++ 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); pyproject = true;
in build-system = [
stdenv.mkDerivation { setuptools
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
'';
meta = { meta = {
description = "Turn almost any device into a file server"; description = "Turn almost any device into a file server";
longDescription = '' longDescription = ''
@ -101,8 +112,7 @@ stdenv.mkDerivation {
homepage = "https://github.com/9001/copyparty"; homepage = "https://github.com/9001/copyparty";
changelog = "https://github.com/9001/copyparty/releases/tag/v${pinData.version}"; changelog = "https://github.com/9001/copyparty/releases/tag/v${pinData.version}";
license = lib.licenses.mit; license = lib.licenses.mit;
inherit (python.meta) platforms;
mainProgram = "copyparty"; mainProgram = "copyparty";
sourceProvenance = [ lib.sourceTypes.binaryBytecode ]; sourceProvenance = [ lib.sourceTypes.fromSource ];
}; };
} }

View file

@ -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", "version": "1.19.1",
"hash": "sha256-Orn0N//DD5/5rIWK9yYRcvyOnt/bKCE9CeoxkfNO76s=" "hash": "sha256-u8JQ2yPrgLyWwnsu+kVs4ef0nH36q625GlcfcAZLb5E="
} }

View file

@ -11,14 +11,14 @@ import base64
import json import json
import hashlib import hashlib
import sys import sys
import re import tarfile
from pathlib import Path from pathlib import Path
OUTPUT_FILE = Path("pin.json") OUTPUT_FILE = Path("pin.json")
TARGET_ASSET = "copyparty-sfx.py" TARGET_ASSET = lambda version: f"copyparty-{version}.tar.gz"
HASH_TYPE = "sha256" HASH_TYPE = "sha256"
LATEST_RELEASE_URL = "https://api.github.com/repos/9001/copyparty/releases/latest" 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): def get_formatted_hash(binary):
@ -29,11 +29,13 @@ def get_formatted_hash(binary):
return f"{HASH_TYPE}-{encoded_hash}" return f"{HASH_TYPE}-{encoded_hash}"
def version_from_sfx(binary): def version_from_tar_gz(path):
result = re.search(b'^VER = "(.*)"$', binary, re.MULTILINE) with tarfile.open(path) as tarball:
if result: release_name = tarball.getmembers()[0].name
return result.groups(1)[0].decode("ascii") prefix = "copyparty-"
if release_name.startswith(prefix):
return release_name.replace(prefix, "")
raise ValueError("version not found in provided file") raise ValueError("version not found in provided file")
@ -42,7 +44,7 @@ def remote_release_pin():
response = requests.get(LATEST_RELEASE_URL).json() response = requests.get(LATEST_RELEASE_URL).json()
version = response["tag_name"].lstrip("v") 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"] download_url = asset_info["browser_download_url"]
asset = requests.get(download_url) asset = requests.get(download_url)
formatted_hash = get_formatted_hash(asset.content) formatted_hash = get_formatted_hash(asset.content)
@ -52,10 +54,9 @@ def remote_release_pin():
def local_release_pin(path): def local_release_pin(path):
asset = path.read_bytes() version = version_from_tar_gz(path)
version = version_from_sfx(asset)
download_url = DOWNLOAD_URL(version) 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} result = {"url": download_url, "version": version, "hash": formatted_hash}
return result return result

View file

@ -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;
};
}

View file

@ -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="
}

View file

@ -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()

View file

@ -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
'';
}

View file

@ -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
'';
}

View file

@ -12,17 +12,14 @@
}: }:
{ {
nixosModules.default = ./contrib/nixos/modules/copyparty.nix; 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 { copyparty = final.python3.pkgs.callPackage ./contrib/package/nix/copyparty {
ffmpeg = final.ffmpeg-full; ffmpeg = final.ffmpeg-full;
}; };
python3 = prev.python3.override {
partyfuse = prev.callPackage ./contrib/package/nix/partyfuse { packageOverrides = pyFinal: pyPrev: {
inherit copyparty; partftpy = pyFinal.callPackage ./contrib/package/nix/partftpy { };
}; };
u2c = prev.callPackage ./contrib/package/nix/u2c {
inherit copyparty;
}; };
}; };
} }
@ -54,8 +51,6 @@
packages = { packages = {
inherit (pkgs) inherit (pkgs)
copyparty copyparty
partyfuse
u2c
; ;
default = self.packages.${system}.copyparty; default = self.packages.${system}.copyparty;
}; };