From dac0b6564d5135c6615e0501d44c0abcfccfae81 Mon Sep 17 00:00:00 2001 From: salmane Date: Sun, 15 Feb 2026 22:27:15 +0000 Subject: [PATCH 1/6] Add java tests to build path to put the test in the appropriate directory, id have to java files to grade build configs, because it currently only checks for kotlin tests. --- app/build.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4f91e6d98..741461bb4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -41,6 +41,9 @@ sourceSets{ } } test{ + java { + srcDirs("test") + } kotlin{ srcDirs("test") } From 1b08eec808c45e17ef4640b5d26d6d3a8dad00a8 Mon Sep 17 00:00:00 2001 From: salmane Date: Sun, 15 Feb 2026 22:40:20 +0000 Subject: [PATCH 2/6] Unit test for rsrc leakage in unzip create a temp zip file > create a destination that is a file not a directory (guaranteed exception) -> unzip throws ioexception because it expects a directory not a file -> catch it -> check if the zip file is still open -> if true == leak. --- app/test/processing/app/UtilTest.java | 81 +++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 app/test/processing/app/UtilTest.java diff --git a/app/test/processing/app/UtilTest.java b/app/test/processing/app/UtilTest.java new file mode 100644 index 000000000..c394d1cdf --- /dev/null +++ b/app/test/processing/app/UtilTest.java @@ -0,0 +1,81 @@ +package processing.app; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assumptions; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class UtilTest { + + @Test + public void unzipLeaksFileDescriptorsOnException() throws IOException { + // thi only runs on Linux where /proc/self/fd exists otherwise skip + Assumptions.assumeTrue(new File("/proc/self/fd").exists(), + "Skipping test: /proc/self/fd not available (not Linux)"); + // create a temporary zip file here with one entry + File zipFile = File.createTempFile("leak-test", ".zip"); + zipFile.deleteOnExit(); + File destDir = File.createTempFile("dest", ""); + destDir.delete(); // turn into a directory + destDir.mkdirs(); + destDir.deleteOnExit(); + // build a simple zip file + try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { + ZipEntry entry = new ZipEntry("test.txt"); + zos.putNextEntry(entry); + zos.write("hello".getBytes()); + zos.closeEntry(); + } + // make the destination directory read‑only – this will cause extraction to fail + destDir.setReadOnly(); + boolean exceptionThrown = false; + try { + Util.unzip(zipFile, destDir); + } catch (IOException e) { + exceptionThrown = true; + } + assertTrue(exceptionThrown, "Expected an exception because destDir is read‑only"); + + // check if the file is open by examining /proc/self/fd symlinks + boolean fileStillOpen = isFileOpen(zipFile); + assertFalse(fileStillOpen, "File " + zipFile + " is still open after exception – leak detected"); + + destDir.setWritable(true); + destDir.delete(); + zipFile.delete(); + } + + /** + * Checks whether the given file is currently open by the current process. + * Works on Linux by reading the symlinks in /proc/self/fd. + */ + private boolean isFileOpen(File file) throws IOException { + Path fdDir = Paths.get("/proc/self/fd"); + String targetPath = file.getCanonicalPath(); + + try (Stream fdPaths = Files.list(fdDir)) { + return fdPaths + .map(Path::toFile) + .map(File::toPath) + .map(path -> { + try { + return Files.readSymbolicLink(path); + } catch (IOException e) { + return null; // not a symlink or inaccessible + } + }) + .filter(resolved -> resolved != null) + .anyMatch(resolved -> resolved.toString().equals(targetPath)); + } + } +} \ No newline at end of file From 5a79e32d5e453c6181626099c63ab5d051066c65 Mon Sep 17 00:00:00 2001 From: salmane Date: Sun, 15 Feb 2026 22:43:41 +0000 Subject: [PATCH 3/6] Add try() to manage opened files/rsrcs --- app/src/processing/app/Util.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/processing/app/Util.java b/app/src/processing/app/Util.java index 4c94af5fe..9c48138c7 100644 --- a/app/src/processing/app/Util.java +++ b/app/src/processing/app/Util.java @@ -688,9 +688,7 @@ static private void packageListFromFolder(File dir, String sofar, * Ignores (does not extract) any __MACOSX files from macOS archives. */ static public void unzip(File zipFile, File dest) throws IOException { - FileInputStream fis = new FileInputStream(zipFile); - CheckedInputStream checksum = new CheckedInputStream(fis, new Adler32()); - ZipInputStream zis = new ZipInputStream(new BufferedInputStream(checksum)); + try (ZipInputStream zis = new ZipInputStream( new BufferedInputStream( new CheckedInputStream( new FileInputStream(zipFile), new Adler32())))) { ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { final String name = entry.getName(); @@ -710,6 +708,7 @@ static public void unzip(File zipFile, File dest) throws IOException { } } } + } static protected void unzipEntry(ZipInputStream zin, File f) throws IOException { From 27bcf6aa77f1aeb3aaed61d5a552587e5468eeb5 Mon Sep 17 00:00:00 2001 From: salmane Date: Tue, 17 Feb 2026 02:59:23 +0000 Subject: [PATCH 4/6] Applying try() to more rsrcs Ive also removed the test since its OS specific, and new code is supposed to be in kotlin. --- app/build.gradle.kts | 3 - app/src/processing/app/Util.java | 83 +++++++++++++-------------- app/test/processing/app/UtilTest.java | 81 -------------------------- 3 files changed, 39 insertions(+), 128 deletions(-) delete mode 100644 app/test/processing/app/UtilTest.java diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 741461bb4..4f91e6d98 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -41,9 +41,6 @@ sourceSets{ } } test{ - java { - srcDirs("test") - } kotlin{ srcDirs("test") } diff --git a/app/src/processing/app/Util.java b/app/src/processing/app/Util.java index 9c48138c7..f87a6fdce 100644 --- a/app/src/processing/app/Util.java +++ b/app/src/processing/app/Util.java @@ -60,16 +60,17 @@ static public int countLines(String what) { */ static public byte[] loadBytesRaw(File file) throws IOException { int size = (int) file.length(); - FileInputStream input = new FileInputStream(file); - byte[] buffer = new byte[size]; - int offset = 0; - int bytesRead; - while ((bytesRead = input.read(buffer, offset, size-offset)) != -1) { - offset += bytesRead; - if (bytesRead == 0) break; - } - input.close(); // weren't properly being closed - return buffer; + byte[] buffer; + try (FileInputStream input = new FileInputStream(file)) { + buffer = new byte[size]; + int offset = 0; + int bytesRead; + while ((bytesRead = input.read(buffer, offset, size - offset)) != -1) { + offset += bytesRead; + if (bytesRead == 0) break; + } + } + return buffer; } @@ -143,7 +144,7 @@ static public StringDict readSettings(String filename, String[] lines, boolean a line = line.substring(0, line.indexOf('#')).trim(); } - if (line.length() != 0 && line.charAt(0) != '#') { + if (!line.isEmpty() && line.charAt(0) != '#') { int equals = line.indexOf('='); if (equals == -1) { if (filename != null) { @@ -161,26 +162,20 @@ static public StringDict readSettings(String filename, String[] lines, boolean a } - static public void copyFile(File sourceFile, - File targetFile) throws IOException { - BufferedInputStream from = - new BufferedInputStream(new FileInputStream(sourceFile)); - BufferedOutputStream to = - new BufferedOutputStream(new FileOutputStream(targetFile)); + static public void copyFile(File sourceFile, File targetFile) throws IOException { + try ( + BufferedInputStream from = new BufferedInputStream(new FileInputStream(sourceFile)); + BufferedOutputStream to = new BufferedOutputStream(new FileOutputStream(targetFile))) { byte[] buffer = new byte[16 * 1024]; int bytesRead; while ((bytesRead = from.read(buffer)) != -1) { to.write(buffer, 0, bytesRead); } - from.close(); - - to.flush(); - to.close(); - //noinspection ResultOfMethodCallIgnored targetFile.setLastModified(sourceFile.lastModified()); //noinspection ResultOfMethodCallIgnored targetFile.setExecutable(sourceFile.canExecute()); + } } @@ -218,13 +213,15 @@ static public void saveFile(String text, File file) throws IOException { file.getAbsolutePath()); } // Could use saveStrings(), but we wouldn't be able to checkError() - PrintWriter writer = PApplet.createWriter(temp); - for (String line : lines) { - writer.println(line); - } - boolean error = writer.checkError(); // calls flush() - writer.close(); // attempt to close regardless - if (error) { + boolean error; + try (PrintWriter writer = PApplet.createWriter(temp)) { + for (String line : lines) { + writer.println(line); + } + // calls flush() + error = writer.checkError(); + } + if (error) { throw new IOException("Error while trying to save " + file); } @@ -589,7 +586,7 @@ static public StringList packageListFromClassPath(String path) { for (String piece : pieces) { //System.out.println("checking piece '" + pieces[i] + "'"); - if (piece.length() != 0) { + if (!piece.isEmpty()) { if (piece.toLowerCase().endsWith(".jar") || piece.toLowerCase().endsWith(".zip")) { //System.out.println("checking " + pieces[i]); @@ -623,8 +620,7 @@ static public StringList packageListFromClassPath(String path) { static private void packageListFromZip(String filename, StringList list) { - try { - ZipFile file = new ZipFile(filename); + try (ZipFile file = new ZipFile(filename);) { Enumeration entries = file.entries(); while (entries.hasMoreElements()) { ZipEntry entry = (ZipEntry) entries.nextElement(); @@ -643,7 +639,6 @@ static private void packageListFromZip(String filename, StringList list) { } } } - file.close(); } catch (IOException e) { System.err.println("Ignoring " + filename + " (" + e.getMessage() + ")"); //e.printStackTrace(); @@ -712,22 +707,22 @@ static public void unzip(File zipFile, File dest) throws IOException { static protected void unzipEntry(ZipInputStream zin, File f) throws IOException { - FileOutputStream out = new FileOutputStream(f); - byte[] b = new byte[512]; - int len; - while ((len = zin.read(b)) != -1) { - out.write(b, 0, len); - } - out.flush(); - out.close(); + try (FileOutputStream out = new FileOutputStream(f)) { + byte[] b = new byte[512]; + int len; + while ((len = zin.read(b)) != -1) { + out.write(b, 0, len); + } + out.flush(); + } } static public byte[] gzipEncode(byte[] what) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - GZIPOutputStream output = new GZIPOutputStream(baos); - PApplet.saveStream(output, new ByteArrayInputStream(what)); - output.close(); + try (GZIPOutputStream output = new GZIPOutputStream(baos);) { + PApplet.saveStream(output, new ByteArrayInputStream(what)); + } return baos.toByteArray(); } diff --git a/app/test/processing/app/UtilTest.java b/app/test/processing/app/UtilTest.java deleted file mode 100644 index c394d1cdf..000000000 --- a/app/test/processing/app/UtilTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package processing.app; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Assumptions; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.stream.Stream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class UtilTest { - - @Test - public void unzipLeaksFileDescriptorsOnException() throws IOException { - // thi only runs on Linux where /proc/self/fd exists otherwise skip - Assumptions.assumeTrue(new File("/proc/self/fd").exists(), - "Skipping test: /proc/self/fd not available (not Linux)"); - // create a temporary zip file here with one entry - File zipFile = File.createTempFile("leak-test", ".zip"); - zipFile.deleteOnExit(); - File destDir = File.createTempFile("dest", ""); - destDir.delete(); // turn into a directory - destDir.mkdirs(); - destDir.deleteOnExit(); - // build a simple zip file - try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { - ZipEntry entry = new ZipEntry("test.txt"); - zos.putNextEntry(entry); - zos.write("hello".getBytes()); - zos.closeEntry(); - } - // make the destination directory read‑only – this will cause extraction to fail - destDir.setReadOnly(); - boolean exceptionThrown = false; - try { - Util.unzip(zipFile, destDir); - } catch (IOException e) { - exceptionThrown = true; - } - assertTrue(exceptionThrown, "Expected an exception because destDir is read‑only"); - - // check if the file is open by examining /proc/self/fd symlinks - boolean fileStillOpen = isFileOpen(zipFile); - assertFalse(fileStillOpen, "File " + zipFile + " is still open after exception – leak detected"); - - destDir.setWritable(true); - destDir.delete(); - zipFile.delete(); - } - - /** - * Checks whether the given file is currently open by the current process. - * Works on Linux by reading the symlinks in /proc/self/fd. - */ - private boolean isFileOpen(File file) throws IOException { - Path fdDir = Paths.get("/proc/self/fd"); - String targetPath = file.getCanonicalPath(); - - try (Stream fdPaths = Files.list(fdDir)) { - return fdPaths - .map(Path::toFile) - .map(File::toPath) - .map(path -> { - try { - return Files.readSymbolicLink(path); - } catch (IOException e) { - return null; // not a symlink or inaccessible - } - }) - .filter(resolved -> resolved != null) - .anyMatch(resolved -> resolved.toString().equals(targetPath)); - } - } -} \ No newline at end of file From 75e73e2b364bc9445673ba860e6d71534a8655f9 Mon Sep 17 00:00:00 2001 From: salmane Date: Fri, 20 Feb 2026 18:11:48 +0000 Subject: [PATCH 5/6] Install script for picking the corrent installation method relevent to the distro --- install.sh | 228 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100755 install.sh diff --git a/install.sh b/install.sh new file mode 100755 index 000000000..3df07017b --- /dev/null +++ b/install.sh @@ -0,0 +1,228 @@ +#!/bin/bash + + +set -euo pipefail +trap 'cleanup' EXIT + +# ---------------------------------------------------------------------- +# Global variables +METHOD="" # user‑requested method (snap|flatpak|aur) +VERSION="latest" # version to install (default latest) +DRY_RUN=false +TEMP_DIR="" +SCRIPT_NAME="$(basename "$0")" + +# ---------------------------------------------------------------------- +# Helper functions +usage() { + cat <&2 + exit 1 +} + +confirm() { + # Prompt user for yes/no, default yes + local prompt="$1" + local response + read -r -p "$prompt [Y/n] " response + case "$response" in + [nN][oO]|[nN]) return 1 ;; + *) return 0 ;; + esac +} + +run() { + if [ "$DRY_RUN" = true ]; then + echo "[DRY RUN] $*" + else + "$@" + fi +} + +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +cleanup() { + if [ -n "$TEMP_DIR" ] && [ -d "$TEMP_DIR" ]; then + rm -rf "$TEMP_DIR" + fi +} + +# ---------------------------------------------------------------------- +# Installation method functions + +install_snap() { + echo "Installing Processing via Snap..." + if confirm "This will run 'sudo snap install processing --classic'. Continue?"; then + run sudo snap install processing --classic + echo "Snap installation complete." + else + echo "Aborted." + fi +} + +install_flatpak() { + echo "Installing Processing via Flatpak..." + if confirm "This will run 'flatpak install flathub org.processing.processingide'. Continue?"; then + run flatpak install flathub org.processing.processingide + echo "Flatpak installation complete." + else + echo "Aborted." + fi +} + +install_aur() { + echo "Installing Processing from AUR..." + + local helper="" + if command_exists "yay"; then + helper="yay" + elif command_exists "paru"; then + helper="paru" + fi + + if [ -n "$helper" ]; then + if confirm "This will run '$helper -S processing'. Continue?"; then + run "$helper" -S processing + echo "AUR installation complete." + else + echo "Aborted." + fi + else + # This case should not be reached because we only call install_aur after detection + echo "No AUR helper found. Install manually from AUR:" + echo " git clone https://aur.archlinux.org/processing.git" + echo " cd processing && makepkg -si" + fi +} + +install_tarball() { + echo "Installing Processing via direct download..." + # STUB – for now just inform user. + echo "This feature is coming soon! For now, you can manually download from:" + echo " https://processing.org/download/" +} + +# ---------------------------------------------------------------------- +# Distribution detection helpers + +is_arch_based() { + # Check for typical Arch indicators + [ -f /etc/arch-release ] && return 0 + if [ -f /etc/os-release ]; then + . /etc/os-release + case "$ID" in + arch|manjaro|endeavouros) return 0 ;; + esac + fi + return 1 +} + +has_flathub() { + # Check if flatpak is installed and flathub remote is present + command_exists flatpak || return 1 + flatpak remote-list 2>/dev/null | grep -q flathub +} + +# ---------------------------------------------------------------------- +# Main + +# Create temporary directory +TEMP_DIR="$(mktemp -d)" + +# Parse command‑line arguments +while [ $# -gt 0 ]; do + case "$1" in + --method) + if [ -z "$2" ]; then + error "--method requires an argument" + fi + METHOD="$2" + shift 2 + ;; + --version) + if [ -z "$2" ]; then + error "--version requires an argument" + fi + VERSION="$2" + shift 2 + ;; + --dry-run) + DRY_RUN=true + shift + ;; + --help) + usage + ;; + *) + error "Unknown option: $1" + ;; + esac +done + +# If override method is provided, use it directly +if [ -n "$METHOD" ]; then + case "$METHOD" in + snap) install_snap ;; + flatpak) install_flatpak ;; + aur) install_aur ;; + tarball) install_tarball ;; + *) error "Invalid method: $METHOD. Use snap, flatpak, aur, or tarball." ;; + esac + exit 0 +fi + +# Auto‑detection +echo "Detecting best installation method for your system..." + + +if command_exists snap; then + echo "Snap is available (official package)." + if confirm "Install Processing via Snap?"; then + install_snap + exit 0 + fi +fi + + +if has_flathub; then + echo "Flatpak (Flathub) is available." + if confirm "Install Processing via Flatpak?"; then + install_flatpak + exit 0 + fi +fi + + +if is_arch_based; then + echo "Arch‑based distribution detected." + if command_exists yay || command_exists paru; then + if confirm "Install Processing from AUR?"; then + install_aur + exit 0 + fi + else + echo "No AUR helper found. You can install manually from AUR:" + echo " git clone https://aur.archlinux.org/processing.git" + echo " cd processing && makepkg -si" + exit 0 + fi +fi + +# Fallback: tarball +echo "No suitable package manager found. Falling back to direct download." +install_tarball +exit 0 From 9a95ebf8f0859767b32496e4ac8e88a17852f482 Mon Sep 17 00:00:00 2001 From: salmane Date: Wed, 25 Feb 2026 07:20:48 +0000 Subject: [PATCH 6/6] installer script This script provides an automated way to install processing on linux systems. It supports multiple installation methods (snap, flatpak, AUR, direct .deb, Nix, and tarball) and can auto-detect the best method based on the distribution and available package managers. --- install.sh | 215 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 164 insertions(+), 51 deletions(-) diff --git a/install.sh b/install.sh index 3df07017b..0115a6d1a 100755 --- a/install.sh +++ b/install.sh @@ -1,42 +1,58 @@ #!/bin/bash - set -euo pipefail trap 'cleanup' EXIT -# ---------------------------------------------------------------------- + # Global variables -METHOD="" # user‑requested method (snap|flatpak|aur) -VERSION="latest" # version to install (default latest) +METHOD="" # user-requested method (snap|flatpak|aur|deb|nix|tarball) DRY_RUN=false +AUTO_YES=false TEMP_DIR="" SCRIPT_NAME="$(basename "$0")" -# ---------------------------------------------------------------------- +# Default .deb URL +DEB_URL="https://processing.org/download/processing-latest-amd64.deb" # placeholder + + # Helper functions usage() { cat <&2 exit 1 } confirm() { - # Prompt user for yes/no, default yes + if [ "$AUTO_YES" = true ]; then + return 0 + fi local prompt="$1" local response - read -r -p "$prompt [Y/n] " response + read -r -p "$prompt [Y/n] " response || true case "$response" in [nN][oO]|[nN]) return 1 ;; *) return 0 ;; @@ -55,16 +71,40 @@ command_exists() { command -v "$1" >/dev/null 2>&1 } +sudo_run() { + if [ "$DRY_RUN" = true ]; then + echo "[DRY RUN] sudo $*" + return 0 + fi + if command_exists sudo; then + sudo "$@" + else + error "sudo is required but not available." + fi +} + cleanup() { if [ -n "$TEMP_DIR" ] && [ -d "$TEMP_DIR" ]; then rm -rf "$TEMP_DIR" fi } -# ---------------------------------------------------------------------- +# Check if Processing is already installed +check_existing() { + if command_exists processing || command_exists Processing; then + echo "Processing appears to already be installed." + if ! confirm "Continue with installation anyway?"; then + echo "Installation aborted." + exit 0 + fi + fi +} + + # Installation method functions install_snap() { + check_existing echo "Installing Processing via Snap..." if confirm "This will run 'sudo snap install processing --classic'. Continue?"; then run sudo snap install processing --classic @@ -75,6 +115,7 @@ install_snap() { } install_flatpak() { + check_existing echo "Installing Processing via Flatpak..." if confirm "This will run 'flatpak install flathub org.processing.processingide'. Continue?"; then run flatpak install flathub org.processing.processingide @@ -85,12 +126,13 @@ install_flatpak() { } install_aur() { + check_existing echo "Installing Processing from AUR..." local helper="" - if command_exists "yay"; then + if command_exists yay; then helper="yay" - elif command_exists "paru"; then + elif command_exists paru; then helper="paru" fi @@ -102,68 +144,117 @@ install_aur() { echo "Aborted." fi else - # This case should not be reached because we only call install_aur after detection echo "No AUR helper found. Install manually from AUR:" echo " git clone https://aur.archlinux.org/processing.git" echo " cd processing && makepkg -si" fi } +install_deb_direct() { + check_existing + echo "Installing Processing via direct .deb download..." + local deb_file="$TEMP_DIR/processing.deb" + if confirm "This will download and install the latest .deb package. Continue?"; then + run curl -L -o "$deb_file" "$DEB_URL" + run sudo dpkg -i "$deb_file" + run sudo apt-get install -f -y + echo "Debian package installation complete." + else + echo "Aborted." + fi +} + +install_nix() { + check_existing + echo "Installing Processing via Nix..." + if ! command_exists nix; then + echo "Nix package manager not found. Please install Nix first: https://nixos.org/download/" + return 1 + fi + if confirm "This will run 'nix profile install nixpkgs#processing'. Continue?"; then + run nix profile install nixpkgs#processing + echo "Nix installation complete. The binary is available as 'Processing' (or 'processing' via symlink)." + else + echo "Aborted." + fi +} + install_tarball() { + check_existing echo "Installing Processing via direct download..." - # STUB – for now just inform user. echo "This feature is coming soon! For now, you can manually download from:" echo " https://processing.org/download/" } -# ---------------------------------------------------------------------- + # Distribution detection helpers +is_debian_based() { + if [ -f /etc/debian_version ]; then + return 0 + fi + if [ -f /etc/os-release ]; then + . /etc/os-release + case "${ID:-}" in + debian|ubuntu|linuxmint|pop) return 0 ;; + esac + fi + return 1 +} + is_arch_based() { - # Check for typical Arch indicators - [ -f /etc/arch-release ] && return 0 + if [ -f /etc/arch-release ]; then + return 0 + fi if [ -f /etc/os-release ]; then . /etc/os-release - case "$ID" in + case "${ID:-}" in arch|manjaro|endeavouros) return 0 ;; esac fi return 1 } +is_nixos() { + if [ -f /etc/NIXOS ] || command_exists nixos-version; then + return 0 + fi + return 1 +} + has_flathub() { - # Check if flatpak is installed and flathub remote is present command_exists flatpak || return 1 flatpak remote-list 2>/dev/null | grep -q flathub } -# ---------------------------------------------------------------------- +has_snap() { + command_exists snap +} + + # Main -# Create temporary directory TEMP_DIR="$(mktemp -d)" -# Parse command‑line arguments +# Parse command-line arguments while [ $# -gt 0 ]; do case "$1" in --method) - if [ -z "$2" ]; then - error "--method requires an argument" - fi + [ $# -ge 2 ] || error "--method requires an argument" METHOD="$2" shift 2 ;; - --version) - if [ -z "$2" ]; then - error "--version requires an argument" - fi - VERSION="$2" - shift 2 - ;; --dry-run) DRY_RUN=true shift ;; + --yes|-y) + AUTO_YES=true + shift + ;; + --list-methods) + list_methods + ;; --help) usage ;; @@ -179,38 +270,42 @@ if [ -n "$METHOD" ]; then snap) install_snap ;; flatpak) install_flatpak ;; aur) install_aur ;; + deb) install_deb_direct ;; + nix) install_nix ;; tarball) install_tarball ;; - *) error "Invalid method: $METHOD. Use snap, flatpak, aur, or tarball." ;; + *) error "Invalid method: $METHOD. Use snap, flatpak, aur, deb, nix, or tarball." ;; esac exit 0 fi -# Auto‑detection -echo "Detecting best installation method for your system..." +# Auto-detection – try most native first -if command_exists snap; then - echo "Snap is available (official package)." - if confirm "Install Processing via Snap?"; then - install_snap +echo "Detecting best installation method for your system..." + +# NixOS +if is_nixos; then + echo "NixOS detected." + if confirm "Install Processing via Nix (official package)?"; then + install_nix exit 0 fi fi - -if has_flathub; then - echo "Flatpak (Flathub) is available." - if confirm "Install Processing via Flatpak?"; then - install_flatpak +# Debian/Ubuntu – prefer .deb over snap/flatpak +if is_debian_based; then + echo "Debian/Ubuntu-based system detected." + if confirm "Install Processing via .deb package (native)?"; then + install_deb_direct exit 0 fi fi - +# Arch Linux – AUR if is_arch_based; then - echo "Arch‑based distribution detected." + echo "Arch-based distribution detected." if command_exists yay || command_exists paru; then - if confirm "Install Processing from AUR?"; then + if confirm "Install Processing from AUR (community package)?"; then install_aur exit 0 fi @@ -222,7 +317,25 @@ if is_arch_based; then fi fi -# Fallback: tarball +# Snap (universal, auto-updating) +if has_snap; then + echo "Snap is available." + if confirm "Install Processing via Snap (auto-updating)?"; then + install_snap + exit 0 + fi +fi + +# Flatpak (universal, auto-updating) +if has_flathub; then + echo "Flatpak (Flathub) is available." + if confirm "Install Processing via Flatpak (auto-updating)?"; then + install_flatpak + exit 0 + fi +fi + +# Fallback echo "No suitable package manager found. Falling back to direct download." install_tarball -exit 0 +exit 0 \ No newline at end of file