From 71088989f3baf39377683523002fc89daa7a70e5 Mon Sep 17 00:00:00 2001 From: "Aaron D. Lee" Date: Sat, 10 Jan 2026 20:09:52 -0500 Subject: [PATCH] Migrate from jpegio to jpeglib for Python 3.13+ support - Replace jpegio with jpeglib (jpeglib.to_jpegio compatibility layer) - Update Python requirement to >=3.11, add 3.13/3.14 classifiers - AUR: Add install script for user creation and permissions - AUR: Install frontends to site-packages, create Flask instance dir - AUR: Use dynamic ${pyver} for systemd WorkingDirectory Tested: CLI, Web UI (Gunicorn), API (Uvicorn), DCT jpeglib roundtrip Co-Authored-By: Claude Opus 4.5 --- aur/PKGBUILD | 42 +++++++++++++++---------------- aur/stegasoo-git.install | 40 +++++++++++++++++++++++++++++ pyproject.toml | 15 +++++------ src/stegasoo/dct_steganography.py | 21 ++++++++-------- 4 files changed, 80 insertions(+), 38 deletions(-) create mode 100644 aur/stegasoo-git.install diff --git a/aur/PKGBUILD b/aur/PKGBUILD index 89a878a..afb1788 100644 --- a/aur/PKGBUILD +++ b/aur/PKGBUILD @@ -1,31 +1,31 @@ # Maintainer: Aaron D. Lee pkgname=stegasoo-git -pkgver=4.1.7.r0.g1acb5a3 +pkgver=4.2.0.r0.g530e5de pkgrel=1 pkgdesc="Secure steganography with hybrid photo + passphrase + PIN authentication" arch=('x86_64') url="https://github.com/adlee-was-taken/stegasoo" license=('MIT') -# NOTE: Requires Python 3.12 (jpegio not compatible with 3.13 yet) +# Python 3.11-3.14 supported (uses jpeglib for modern Python compatibility) depends=( - 'python312' # AUR package - jpegio requires 3.12 + 'python>=3.11' ) makedepends=( 'git' - 'python312' + 'python' + 'python-build' + 'python-hatchling' ) optdepends=( 'zbar: QR code reading from webcam/images' ) provides=('stegasoo') conflicts=('stegasoo') +install=stegasoo-git.install source=("${pkgname}::git+https://github.com/adlee-was-taken/stegasoo.git#branch=main") sha256sums=('SKIP') -# Python 3.12 from AUR package -_python="/usr/bin/python3.12" - pkgver() { cd "$pkgname" git describe --long --tags 2>/dev/null | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' || \ @@ -34,32 +34,32 @@ pkgver() { build() { cd "$pkgname" - - echo "Using Python: $_python ($($_python --version))" - - # Bootstrap pip for python312 - $_python -m ensurepip --user --upgrade - - # Install build dependencies - $_python -m pip install --user build hatchling - - # Build wheel - $_python -m build --wheel --no-isolation + python -m build --wheel --no-isolation } package() { cd "$pkgname" + # Detect Python version for site-packages path + local pyver=$(python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') + # Install to /opt/stegasoo with dedicated venv install -dm755 "$pkgdir/opt/stegasoo" # Create fresh venv in package - $_python -m venv "$pkgdir/opt/stegasoo/venv" + python -m venv "$pkgdir/opt/stegasoo/venv" # Install the wheel with all extras local wheel=$(ls dist/*.whl | head -1) "$pkgdir/opt/stegasoo/venv/bin/pip" install --no-cache-dir "${wheel}[all]" + # Install frontends (not included in wheel) + local site_packages="$pkgdir/opt/stegasoo/venv/lib/python${pyver}/site-packages" + cp -r frontends "$site_packages/" + + # Create instance directory for Flask (writable by stegasoo user) + install -dm755 "$pkgdir/opt/stegasoo/venv/var/app-instance" + # Fix shebangs - replace build-time paths with installed paths find "$pkgdir/opt/stegasoo/venv/bin" -type f -exec \ sed -i "s|$pkgdir/opt/stegasoo/venv|/opt/stegasoo/venv|g" {} \; @@ -86,7 +86,7 @@ After=network.target [Service] Type=simple User=stegasoo -WorkingDirectory=/opt/stegasoo/venv/lib/python3.12/site-packages/frontends/web +WorkingDirectory=/opt/stegasoo/venv/lib/python${pyver}/site-packages/frontends/web Environment="PATH=/opt/stegasoo/venv/bin" ExecStart=/opt/stegasoo/venv/bin/gunicorn -b 127.0.0.1:5000 app:app Restart=on-failure @@ -104,7 +104,7 @@ After=network.target [Service] Type=simple User=stegasoo -WorkingDirectory=/opt/stegasoo/venv/lib/python3.12/site-packages/frontends/api +WorkingDirectory=/opt/stegasoo/venv/lib/python${pyver}/site-packages/frontends/api Environment="PATH=/opt/stegasoo/venv/bin" ExecStart=/opt/stegasoo/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000 Restart=on-failure diff --git a/aur/stegasoo-git.install b/aur/stegasoo-git.install new file mode 100644 index 0000000..9024b94 --- /dev/null +++ b/aur/stegasoo-git.install @@ -0,0 +1,40 @@ +post_install() { + # Create stegasoo system user if it doesn't exist + if ! getent passwd stegasoo >/dev/null; then + useradd -r -s /usr/bin/nologin -d /opt/stegasoo stegasoo + echo "Created system user 'stegasoo'" + fi + + # Set ownership of instance directory for Flask + chown -R stegasoo:stegasoo /opt/stegasoo/venv/var/app-instance 2>/dev/null || true + + echo "" + echo "Stegasoo installed successfully!" + echo "" + echo "CLI usage:" + echo " stegasoo --help" + echo "" + echo "To start the web UI:" + echo " sudo systemctl start stegasoo-web" + echo "" + echo "To start the REST API:" + echo " sudo systemctl start stegasoo-api" + echo "" +} + +post_upgrade() { + post_install +} + +pre_remove() { + # Stop services if running + systemctl stop stegasoo-web 2>/dev/null || true + systemctl stop stegasoo-api 2>/dev/null || true +} + +post_remove() { + # Optionally remove the stegasoo user + # userdel stegasoo 2>/dev/null || true + echo "Stegasoo removed. User 'stegasoo' was not removed." + echo "To remove: userdel stegasoo" +} diff --git a/pyproject.toml b/pyproject.toml index e106708..793d811 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ version = "4.2.0" description = "Secure steganography with hybrid photo + passphrase + PIN authentication" readme = "README.md" license = "MIT" -requires-python = ">=3.10" +requires-python = ">=3.11" authors = [ { name = "Aaron D. Lee" } ] @@ -29,9 +29,10 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Security :: Cryptography", "Topic :: Multimedia :: Graphics", ] @@ -48,7 +49,7 @@ dependencies = [ dct = [ "numpy>=2.0.0", "scipy>=1.10.0", - "jpegio>=0.2.0", + "jpeglib>=1.0.0", "reedsolo>=1.7.0", ] cli = [ @@ -69,7 +70,7 @@ web = [ # Include DCT support for web UI "numpy>=2.0.0", "scipy>=1.10.0", - "jpegio>=0.2.0", + "jpeglib>=1.0.0", "reedsolo>=1.7.0", ] api = [ @@ -81,7 +82,7 @@ api = [ # Include DCT support for API "numpy>=2.0.0", "scipy>=1.10.0", - "jpegio>=0.2.0", + "jpeglib>=1.0.0", "reedsolo>=1.7.0", ] all = [ @@ -120,7 +121,7 @@ addopts = "-v --cov=stegasoo --cov-report=term-missing" [tool.black] line-length = 100 -target-version = ["py310", "py311", "py312"] +target-version = ["py311", "py312", "py313"] [tool.ruff] line-length = 100 @@ -137,7 +138,7 @@ ignore = ["E501"] "src/stegasoo/__init__.py" = ["E402"] [tool.mypy] -python_version = "3.10" +python_version = "3.11" warn_return_any = true warn_unused_configs = true ignore_missing_imports = true diff --git a/src/stegasoo/dct_steganography.py b/src/stegasoo/dct_steganography.py index 4ecdfbc..0c21ca1 100644 --- a/src/stegasoo/dct_steganography.py +++ b/src/stegasoo/dct_steganography.py @@ -12,7 +12,7 @@ Why is this cool? Two approaches depending on what you want: 1. PNG output: We do our own DCT math via scipy (works on any image) -2. JPEG output: We use jpegio to directly tweak the coefficients (chef's kiss) +2. JPEG output: We use jpeglib to directly tweak the coefficients (chef's kiss) v4.1.0 - The "please stop corrupting my data" release: - Reed-Solomon error correction (can fix up to 16 byte errors per chunk) @@ -24,7 +24,7 @@ v3.2.0-patch2 - The "scipy why are you like this" release: - Process blocks one at a time with fresh arrays - Yes, it's slower. No, I don't care. Correctness > speed. -Requires: scipy (PNG mode), optionally jpegio (JPEG mode), reedsolo (error correction) +Requires: scipy (PNG mode), optionally jpeglib (JPEG mode), reedsolo (error correction) """ import gc @@ -55,14 +55,15 @@ except ImportError: dctn = None idctn = None -# Check for jpegio availability (for proper JPEG mode) +# Check for jpeglib availability (for proper JPEG mode) +# jpeglib replaces jpegio for Python 3.13+ compatibility try: - import jpegio as jio + import jpeglib - HAS_JPEGIO = True + HAS_JPEGIO = True # Keep variable name for compatibility except ImportError: HAS_JPEGIO = False - jio = None + jpeglib = None # Import custom exceptions from .exceptions import InvalidMagicBytesError @@ -742,7 +743,7 @@ def estimate_capacity_comparison(image_data: bytes) -> dict: }, "jpeg_native": { "available": HAS_JPEGIO, - "note": "Uses jpegio for proper JPEG coefficient embedding", + "note": "Uses jpeglib for proper JPEG coefficient embedding", }, } @@ -1082,7 +1083,7 @@ def _embed_jpegio( flags = FLAG_COLOR_MODE if color_mode == "color" else 0 try: - jpeg = jio.read(input_path) + jpeg = jpeglib.to_jpegio(jpeglib.read_dct(input_path)) coef_array = jpeg.coef_arrays[JPEGIO_EMBED_CHANNEL] all_positions = _jpegio_get_usable_positions(coef_array) @@ -1144,7 +1145,7 @@ def _embed_jpegio( if progress_file: _write_progress(progress_file, total_bits, total_bits, "saving") - jio.write(jpeg, output_path) + jpeg.write(output_path) with open(output_path, "rb") as f: stego_bytes = f.read() @@ -1392,7 +1393,7 @@ def _extract_jpegio( temp_path = _jpegio_bytes_to_file(stego_image, suffix=".jpg") try: - jpeg = jio.read(temp_path) + jpeg = jpeglib.to_jpegio(jpeglib.read_dct(temp_path)) coef_array = jpeg.coef_arrays[JPEGIO_EMBED_CHANNEL] all_positions = _jpegio_get_usable_positions(coef_array)