1 ‐ LINUX Install Scripts - SimonHeggie/FOSS-Artist-workflow-Guide GitHub Wiki

Introduction

This is my opinion, the cream of the crop when it comes to a selection of the best free Linux apps for VFX work.

I have curated them together with what I believe to be some of the most powerful addons.

All this is tested with Nobara 42. Fedora and RockLinux users MAY require additional support.

Copy and paste the code into your terminal app ('Konsole' if you like KDE). Nice and simple, not too many clicks needed.

'INSTALL' contains your install script which also acts as a re-installer and updater.

'UNINSTALL' contains a script to remove all of that.

'ADDONS' Contains information on included and recommended addons.


Core Art Apps


Blender Logo

Blender

INSTALL
# Prompt for admin password at the beginning
if [ "$EUID" -ne 0 ]; then
  echo "This script requires administrative privileges. Please provide your password."
  sudo -v || { echo "Failed to obtain sudo privileges. Exiting."; exit 1; }
fi

# Define variables
BLENDER_DIR="$HOME/.Applications/Blender/stable/blender-stable-baseline"
BLENDER_EXEC="$BLENDER_DIR/blender"
DESKTOP_SHORTCUT="$BLENDER_DIR/blender.desktop"
VERSION_FILE="$BLENDER_DIR/version.txt"

# Function to fetch the latest Blender version number
get_latest_version() {
  curl -s https://www.blender.org/download/ | grep -oP 'blender-\K[0-9]+\.[0-9]+\.[0-9]+' | head -1
}



# Function to fetch the current installed Blender version
get_current_version() {
  if [ -f "$BLENDER_EXEC" ]; then
    $BLENDER_EXEC --version | head -n 1 | awk '{print $2}'
  else
    echo "0.0.0"
  fi
}

# Function to detect GPU setup
detect_gpu_setup() {
  if command -v nvidia-smi &> /dev/null; then
    echo "proprietary"
  elif lspci | grep -i 'VGA' | grep -i 'NVIDIA' &> /dev/null; then
    echo "nouveau"
  elif lspci | grep -i 'VGA' | grep -i 'AMD' &> /dev/null; then
    echo "amd"
  else
    echo "unknown"
  fi
}

# Function to fetch the latest Gather Resources addon release URL from GitHub
get_latest_gather_resources_url() {
  curl -s https://api.github.com/repos/SimonHeggie/Blender-GatherResources/releases/latest | 
  grep "browser_download_url" | 
  grep ".zip" | 
  cut -d '"' -f 4
}

# Fetch the latest Blender version number
latest_version=$(get_latest_version)
current_version=$(get_current_version)

echo "Latest Blender version: $latest_version"
echo "Current Blender version: $current_version"

# Compare versions and download if necessary
if [ "$latest_version" != "$current_version" ]; then
  echo "Updating Blender to version $latest_version..."

  # Construct the download URL
  download_url="https://mirror.clarkson.edu/blender/release/Blender${latest_version%.*}/blender-$latest_version-linux-x64.tar.xz"

  # Download the latest Blender tarball
  wget -O blender-latest.tar.xz $download_url

  # Extract the downloaded tarball
  mkdir -p $BLENDER_DIR
  tar -xf blender-latest.tar.xz -C $HOME/.Applications/Blender/stable
  mv $HOME/.Applications/Blender/stable/blender-$latest_version-linux-x64/* $BLENDER_DIR

  # Clean up the tarball and temporary directory
  rm -rf blender-latest.tar.xz
  rm -rf $HOME/.Applications/Blender/stable/blender-$latest_version-linux-x64

  # Save the current version to the version file
  echo "$latest_version" > "$VERSION_FILE"
else
  echo "Blender is already up-to-date. Refreshing desktop shortcut..."
fi

# Create a symbolic link to make Blender accessible from anywhere
sudo ln -sf $BLENDER_EXEC /usr/local/bin/blender

# Detect GPU setup and determine the correct Exec command
GPU_SETUP=$(detect_gpu_setup)
echo "GPU Setup detected: $GPU_SETUP"

case "$GPU_SETUP" in
  "proprietary")
    EXEC_COMMAND="__NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia $BLENDER_EXEC"
    ;;
  "nouveau"|"amd")
    EXEC_COMMAND="DRI_PRIME=1 $BLENDER_EXEC"
    ;;
  *)
    EXEC_COMMAND="$BLENDER_EXEC"
    ;;
esac

echo "Using Exec command: $EXEC_COMMAND"

# Update the Exec line in the existing desktop shortcut
if [ -f "$DESKTOP_SHORTCUT" ]; then
  sudo sed -i "s|^Exec=.*|Exec=$EXEC_COMMAND|" "$DESKTOP_SHORTCUT"
  echo "Updated the Exec line in the desktop shortcut: $DESKTOP_SHORTCUT"

  # Copy the .desktop file to ~/.local/share/applications for taskbar/menu use
  cp "$DESKTOP_SHORTCUT" ~/.local/share/applications/
  echo "Copied updated desktop shortcut to ~/.local/share/applications/"

  # Refresh the desktop database to ensure changes take effect
  sudo update-desktop-database ~/.local/share/applications
  sudo update-desktop-database /usr/share/applications
  echo "Desktop database refreshed."
else
  echo "Desktop shortcut not found at $DESKTOP_SHORTCUT. Skipping update."
fi

echo "Verifying the desktop shortcut content:"
echo "Blender $latest_version installation and desktop shortcut refresh complete."

# ---- Dynamic Addon Directory Handling ----

# Scan for Blender version folders
blender_versions=($(ls -d $BLENDER_DIR/[0-9]* 2>/dev/null | awk -F'/' '{print $NF}' | sort -V))

# Remove all but the highest version folder
if [ ${#blender_versions[@]} -gt 1 ]; then
  echo "Multiple Blender version directories detected: ${blender_versions[@]}"
  
  for ((i = 0; i < ${#blender_versions[@]} - 1; i++)); do
    echo "Removing old Blender version: ${blender_versions[i]}"
    rm -rf "$BLENDER_DIR/${blender_versions[i]}"
  done
fi

# Get the latest version directory
LATEST_BLENDER_VERSION="${blender_versions[-1]}"
echo "Using Blender version: $LATEST_BLENDER_VERSION"

# Construct the dynamic addons directory path
ADDONS_DIR="$BLENDER_DIR/$LATEST_BLENDER_VERSION/scripts/addons_core/"
mkdir -p "$ADDONS_DIR"
echo "Blender Addons Directory: $ADDONS_DIR"

# ---- Install Gather Resources Addon ----

# Get the latest release URL for Gather Resources
echo "Fetching latest Gather Resources addon..."
gather_resources_url=$(get_latest_gather_resources_url)

if [ -z "$gather_resources_url" ]; then
  echo "Failed to fetch the Gather Resources addon URL. Skipping installation."
else
  echo "Downloading Gather Resources from: $gather_resources_url"
  wget -O GatherResources.zip "$gather_resources_url"

  # Ensure the addons directory exists
  mkdir -p "$ADDONS_DIR"

  # Extract and install the addon
  echo "Installing Gather Resources addon..."
  unzip -o GatherResources.zip -d "$ADDONS_DIR"
  rm GatherResources.zip

  echo "Gather Resources addon installed successfully."
fi

echo "Installation complete! Blender is up to date, and Gather Resources addon is installed."
UNINSTALL
BLENDER_DIR="$HOME/.Applications/Blender/stable/blender-stable-baseline"
BLENDER_EXEC="$BLENDER_DIR/blender"
DESKTOP_ENTRY="$HOME/.local/share/applications/blender.desktop"
SYMLINK="/usr/local/bin/blender"

# Check if Blender directory exists
if [ -d "$BLENDER_DIR" ]; then
  echo "Removing Blender directory: $BLENDER_DIR"
  rm -rf "$BLENDER_DIR"
else
  echo "Blender directory does not exist: $BLENDER_DIR"
fi

# Check if symbolic link exists
if [ -L "$SYMLINK" ]; then
  echo "Removing symbolic link: $SYMLINK"
  sudo rm "$SYMLINK"
else
  echo "Symbolic link does not exist: $SYMLINK"
fi

# Check if desktop entry exists
if [ -f "$DESKTOP_ENTRY" ]; then
  echo "Removing desktop entry: $DESKTOP_ENTRY"
  rm "$DESKTOP_ENTRY"
else
  echo "Desktop entry does not exist: $DESKTOP_ENTRY"
fi

echo "Blender uninstallation complete."

ADDONS

Included Addons:

📦 Gather Resources: https://github.com/SimonHeggie/Blender-GatherResources

  • Similar to 'Collect Resources' in After Effects. I made this.

COMING SOON:

🔧 https://github.com/Aodaruma/coa_tools2

  • Opensource Alternative to Spine.

Which would I purchase?

Blender is already quite capable, but these tools fill in gaps, allowing the artist to ignore more of the "must-have" paid options.

💵 Animation Layers – $30
https://blendermarket.com/products/animation-layers

  • Blender’s animation layering is improving, but this is a pretty complete solution. Maya animators will appreciate it.

💵 Bake Wrangler – $25
https://blendermarket.com/products/bake-wrangler

  • Fixes Blender’s baking tools and makes them even handier than other baking software. With this, you won’t really need Substance or Marmoset for baking.

💵 Better FBX Exporter – Starts at $28 for single indie users, up to $840 for unlimited studio seats. https://blendermarket.com/products/better-fbx-importer--exporter?search_id=36427533 -Essential for working with studios that don't support Gltf and USD, particularly if they are Autodesk users.

💵 Quad Remesher by Exoside – $25
https://exoside.com/

  • The best auto re-topology tool for Blender that money can buy. Perfect for sculptors and fixing 3D scan data.
  • This is the very same remesher used in Zbrush. One less reason to require it.

Blender Logo

Krita

INSTALL
#!/usr/bin/env bash

# ─── Where to store install files (if run from $HOME) ────────────────
if [ "$PWD" = "$HOME" ]; then
  cd "$HOME/Downloads" || exit 1
fi

bash -c '
set -euo pipefail
IFS=$'\''\n\t'\''

BOOT_DIR="$(pwd)"
INSTALL_DIR="$BOOT_DIR/krita_install"
INSTALL_SCRIPT="$INSTALL_DIR/install.sh"
UNINSTALL_SCRIPT="$INSTALL_DIR/uninstall.sh"
VERSION_FILE="$HOME/.Applications/Krita/version"

GITHUB_RELEASE_URL="https://github.com/SimonHeggie/FOSS-Artist-workflow-Guide/releases/download/v0.0.2/foss-artist-install.tar.gz"
ARCHIVE_TEMP="foss-artist-install.tar.gz"

# ─── Step 1: Ensure krita_install folder exists ─────────────────────
echo "[BOOT] Checking krita_install folder..."
if [ -d "$INSTALL_DIR" ]; then
    echo "[INFO] Found existing ./krita_install — skipping download."
else
    echo "[INFO] krita_install/ not found. Downloading v0.0.2 release..."
    curl -L "$GITHUB_RELEASE_URL" -o "$ARCHIVE_TEMP"
    echo "[INFO] Extracting installer..."
    tar -xf "$ARCHIVE_TEMP" || {
        echo "[ERROR] Failed to extract archive $ARCHIVE_TEMP" >&2
        exit 1
    }
    rm -f "$ARCHIVE_TEMP"

    if [ ! -d "$INSTALL_DIR" ]; then
        echo "[ERROR] Expected folder '\''krita_install/'\'' not found after extraction." >&2
        exit 1
    fi
fi

# ─── Step 2: Validate install.sh exists ──────────────────────────────
if [ ! -f "$INSTALL_SCRIPT" ]; then
    echo "[ERROR] Missing install.sh inside krita_install/ — aborting." >&2
    ls -l "$INSTALL_DIR"
    exit 1
fi

# ─── Step 3: Present menu ────────────────────────────────────────────
if [ ! -f "$VERSION_FILE" ]; then
    echo "[INFO] Krita not installed."
    echo "[I]   Install Krita only"
    echo "[AI]  Install Krita + AI Diffusion"
    echo "[AIC] Install Krita + AI + ComfyUI shortcut"
    echo "[Q]   Quit"
    printf "Select an option: "
    read -r option
    case "$option" in
        [Ii])         bash "$INSTALL_SCRIPT" --with-default ;;
        [Aa][Ii])     bash "$INSTALL_SCRIPT" --with-ai ;;
        [Aa][Ii][Cc]) bash "$INSTALL_SCRIPT" --with-ai --with-comfy ;;
        [Qq])         echo "Goodbye!" && exit 0 ;;
        *)            echo "Invalid input." && exit 1 ;;
    esac
else
    echo "[INFO] Krita install detected."
    echo "[INFO] Installed versions:"
    grep -E '^(FOSS_INSTALL|KRITA|AIDIFFUSION|SEGMENTATIONTOOLS)=' "$VERSION_FILE"

    echo ""
    echo "[I]   Install latest (overwrite, update version)"
    echo "[AI]  Add AI Diffusion"
    echo "[AIC] Add AI Diffusion + ComfyUI shortcut"
    echo "[D]   Download archives only"
    echo "[R]   Repair (re-extract AppImage + reinstall addons)"
    echo "[U]   Uninstall (with ComfyUI backup)"
    echo "[C]   Complete uninstall (no backup)"
    echo "[Q]   Quit"

    printf "Select an option: "
    read -r option
    case "$option" in
        [Aa][Ii])     bash "$INSTALL_SCRIPT" --with-ai ;;
        [Aa][Ii][Cc]) bash "$INSTALL_SCRIPT" --with-ai --with-comfy ;;
        [Ii])         bash "$INSTALL_SCRIPT" --with-default ;;
        [Dd])         bash "$INSTALL_SCRIPT" --download-only ;;
        [Rr])         bash "$INSTALL_SCRIPT" --repair ;;
        [Uu])         bash "$UNINSTALL_SCRIPT" --backup ;;
        [Cc])         bash "$UNINSTALL_SCRIPT" --complete ;;
        [Qq])         echo "Goodbye!" && exit 0 ;;
        *)            echo "Invalid input." && exit 1 ;;
    esac
fi
'
AI GEN
If you want more than just segmentation (object) select with Krita...

AI generation requires some additional steps because of large download sizes, and varying set up requirements.
I still saved you from having the download the addon and activate the plugins within Krita yourself, the rest is manual.
It's not that tough to set up. But it keeps people happy who are not interested in dealing with AI.

---

1: In Krita, open a new document. Then go to: Settings/Dockers/AI Image Generation...
 -That will open the docker, click 'configure' to then access the...

2: 'Connection' section.
 -Powerful computer? (IE: RTX 4090)?, Don't trust your IP security on an online server?: 
    GO with: 'Local Managed Server'.

 -Already got COMFYUI set up? Want to connect straight into your existing setup?: 
    Go with: 'Custom Server (local or remote)'.
 -Don't have any good hardware for AI? Got some cash to spend on the cloud? 
    Go with : 'Online Service'

3: Server Path - If you don't know or care where you store this, leave this setting as default.
(home/USERNAME/.Applications/ComfyUI/server)

NOTE: within this path, /models will be stored, by far the biggest; so make sure you back this up.
Another folder is /venv which stores almost 10gb worth of isolated python. Backup, but be prepared to wipe if there's an issue.

The rest of this depends on how many features and control measures you'd like unlock.

You will need the core components, I recommend grabbing all workloads, then grab the models and the control-net models that you'd like.
If in doubt, if you have a decent internet connection; I recommend you tick it all and try out all these different features.

4: Hit install. If you get red text describing issues downloading something, try re-downloading, preferably with the best internet connection you can find. In totally there is almost 100gb worth of data you can download if you're serious about AI generation. Curated by ACLY.

DONE.
ADDONS

Included Addons:

📦 krita-ai-tools/Krita Segmentation Tools: https://github.com/Acly/krita-ai-tools

  • It's like Object select. AI tools for auto masking in Krita.

📦 [See above section] krita-ai-diffusion: https://github.com/Acly/krita-ai-diffusion

Blender Logo AI diffusion uses ComfyUI which in this script will include a shortcut so you can run it seperatly.

COMING SOON!

🔧 svg-merge-save:https://github.com/SimonHeggie/KritaSvgMerge/tree/main

  • This just let's you export multiple layers of SVG into one flat SVG, so you don't have to flatten your work while working to export SVGs.
  • I also made this :)

Other useful free Addons:

🔎 https://github.com/chartinger/krita-unofficial-spine-export

-Watch this space, a lot more to come.



Blender Logo

Inkscape

INSTALL
# Check for Inkscape updates
sudo dnf check-update inkscape

# Optional: If you want to upgrade Inkscape specifically, uncomment the next line
# sudo dnf upgrade inkscape -y

# Installing Inkscape
echo "Installing Inkscape..."
sudo dnf install inkscape -y

# Defining Inkscape's extension directory
EXT_DIR="$HOME/.config/inkscape/extensions"

# Creating the directory if it doesn't exist
mkdir -p "$EXT_DIR"

# Downloading the InkSync addon
echo "Downloading InkSync addon..."
wget -O inksync.zip https://github.com/SimonHeggie/InkSync/archive/refs/heads/main.zip || { echo "Failed to download InkSync addon"; exit 1; }

# Unzipping and moving to Inkscape's extensions directory
echo "Installing InkSync addon..."
unzip inksync.zip -d inksync
cp -r inksync/InkSync-main/* "$EXT_DIR"

# Clean up downloaded files
rm -rf inksync
rm inksync.zip

# Creating a desktop shortcut for Inkscape with InkSync addon
echo "Creating desktop shortcut..."
if [ -d "$HOME/Desktop" ]; then
  echo "[Desktop Entry]
  Version=1.0
  Type=Application
  Name=Inkscape with InkSync
  Icon=inkscape
  Exec=inkscape %F
  Comment=Create and edit Scalable Vector Graphics images with InkSync addon
  Categories=Graphics;VectorGraphics;GTK;
  Terminal=false
  MimeType=image/svg+xml;image/x-eps;image/wmf;image/svg+xml-compressed;image/emf;application/pdf;
  StartupNotify=true
  Keywords=image;editor;vector;drawing;
  TryExec=inkscape
  " > $HOME/Desktop/inkscape.desktop
  chmod +x $HOME/Desktop/inkscape.desktop
else
  echo "Desktop directory not found. Skipping shortcut creation."
fi

echo "Installation complete! Inkscape with InkSync addon is ready."
UNINSTALL
# Uninstalling Inkscape
sudo dnf remove inkscape -y

# Removing Inkscape extensions
EXT_DIR="$HOME/.config/inkscape/extensions"
rm -rf "$EXT_DIR"

# Removing the desktop shortcut
rm -f $HOME/Desktop/inkscape.desktop

echo "Uninstallation complete! Inkscape and its extensions have been removed."

ADDONS

Included Addons:

📦 InkSync: https://github.com/SimonHeggie/InkSync

  • An exporter that focusing on making it easier to sync file embedded layers in other software (Like Krita)
  • Useful for Layout of Inkscape content in Krita or similar for printing (for CMYK).

Worth looking at

📎 Inkscape ConfyUI Extension: https://inkscape.org/~nielowait/%E2%98%85inkscape-comfyui-extension

  • Could be useful. (I have not checked it out)

(FontForge Logo)

FontForge

INSTALL
INSTALL_DIR="$HOME/.Applications/FontForge"
ICON_DIR="$HOME/.local/share/icons/hicolor/scalable/apps"
DESKTOP_DIR="$HOME/.local/share/applications"
VERSION_FILE="$INSTALL_DIR/usr/share/metainfo/org.fontforge.FontForge.appdata.xml"
LOG_FILE="$INSTALL_DIR/fontforge_install.log"

# Ensure necessary directories exist
mkdir -p "$INSTALL_DIR" "$ICON_DIR" "$DESKTOP_DIR"

# Redirect stdout and stderr to the log file
exec > >(tee -a "$LOG_FILE") 2>&1

# Get the URL of the latest FontForge AppImage
FONTFORGE_URL=$(curl -s https://api.github.com/repos/fontforge/fontforge/releases/latest | jq -r '.assets[] | select(.name | test("AppImage")) | .browser_download_url')

# Get the latest version from the GitHub API
LATEST_VERSION=$(curl -s https://api.github.com/repos/fontforge/fontforge/releases/latest | jq -r '.tag_name')

# Function to get the installed version from the XML file using xmllint
get_installed_version() {
    if [[ -f "$VERSION_FILE" ]]; then
        xmllint --xpath 'string(//component/releases/release[1]/@version)' "$VERSION_FILE"
    else
        echo "none"
    fi
}

# Check the installed version
INSTALLED_VERSION=$(get_installed_version)

# Download and install if the version is different
if [[ "$LATEST_VERSION" != "$INSTALLED_VERSION" ]]; then
    echo "Downloading FontForge AppImage..."
    curl -L "$FONTFORGE_URL" -o "$INSTALL_DIR/fontforge.AppImage"
    chmod +x "$INSTALL_DIR/fontforge.AppImage"

    # Extract AppImage
    echo "Extracting AppImage..."
    "$INSTALL_DIR/fontforge.AppImage" --appimage-extract
    mv squashfs-root/* "$INSTALL_DIR/"
    rm -rf squashfs-root "$INSTALL_DIR/fontforge.AppImage"

    # Copy the icon to the appropriate directory
    echo "Copying FontForge icon..."
    ICON_SRC_PATH="$INSTALL_DIR/usr/share/icons/hicolor/scalable/apps/org.fontforge.FontForge.svg"
    if [[ -f "$ICON_SRC_PATH" ]]; then
        cp "$ICON_SRC_PATH" "$ICON_DIR/"
    else
        echo "FontForge icon not found at $ICON_SRC_PATH"
    fi

    # Create desktop entry
    echo "Creating desktop entry..."
    cat > "$DESKTOP_DIR/fontforge.desktop" <<EOL
[Desktop Entry]
Name=FontForge
Exec=$INSTALL_DIR/AppRun
Icon=$ICON_DIR/org.fontforge.FontForge.svg
Type=Application
Categories=Graphics;Font
EOL

    echo "FontForge $LATEST_VERSION installed successfully."
else
    echo "FontForge is already up to date (version $INSTALLED_VERSION)."
fi

# Update the icon cache if the index.theme file exists
if [[ -f "$HOME/.local/share/icons/hicolor/index.theme" ]]; then
    echo "Updating icon cache..."
    gtk-update-icon-cache "$HOME/.local/share/icons/hicolor"
else
    echo "No theme index file found, skipping icon cache update."
fi

echo "Font Forge is installed and up to date."


UNINSTALL
# Set the installation directory to $HOME/.Applications/fontforge
INSTALL_DIR="$HOME/.Applications/FontForge"
ICON_DIR="$HOME/.local/share/icons/hicolor/scalable/apps"
DESKTOP_DIR="$HOME/.local/share/applications"
LOG_FILE="$INSTALL_DIR/fontforge_uninstall.log"

# Redirect stdout and stderr to the log file
exec > >(tee -a "$LOG_FILE") 2>&1

# Remove the FontForge installation directory
if [[ -d "$INSTALL_DIR" ]]; then
    echo "Removing FontForge installation directory..."
    rm -rf "$INSTALL_DIR"
else
    echo "FontForge installation directory not found."
fi

# Remove the FontForge icon
ICON_PATH="$ICON_DIR/org.fontforge.FontForge.svg"
if [[ -f "$ICON_PATH" ]]; then
    echo "Removing FontForge icon..."
    rm "$ICON_PATH"
else
    echo "FontForge icon not found."
fi

# Remove the desktop entry
DESKTOP_ENTRY="$DESKTOP_DIR/fontforge.desktop"
if [[ -f "$DESKTOP_ENTRY" ]]; then
    echo "Removing FontForge desktop entry..."
    rm "$DESKTOP_ENTRY"
else
    echo "FontForge desktop entry not found."
fi

# Update the icon cache if the index.theme file exists
if [[ -f "$HOME/.local/share/icons/hicolor/index.theme" ]]; then
    echo "Updating icon cache..."
    gtk-update-icon-cache "$HOME/.local/share/icons/hicolor"
else
    echo "No theme index file found, skipping icon cache update."
fi

echo "FontForge uninstallation completed."

Secondary Art Apps


(FontForge Logo)

MeshRoom

INSTALL
MESHROOM_INSTALL_DIR="$HOME/.Applications/MeshRoom"
MESHROOM_VERSION_FILE="$MESHROOM_INSTALL_DIR/version.txt"
LOG_FILE="$MESHROOM_INSTALL_DIR/install_log.txt"

# Start logging
echo "Starting installation script..." > "$LOG_FILE"
echo "Log file: $LOG_FILE" >> "$LOG_FILE"
date >> "$LOG_FILE"
echo "==============================" >> "$LOG_FILE"

# Function to log and display messages
log() {
    echo "$1" | tee -a "$LOG_FILE"
}

# Install ExifTool if not already installed
log "Checking for ExifTool installation..."
if ! command -v exiftool &> /dev/null; then
    log "ExifTool not found. Installing ExifTool..."
    sudo dnf install -y exiftool 2>> "$LOG_FILE"
    if [ $? -ne 0 ]; then
        log "Error: ExifTool installation failed."
        exit 1
    fi
    log "ExifTool successfully installed."
else
    log "ExifTool is already installed."
fi

# Function to fetch latest Meshroom release version from GitHub API
get_latest_meshroom_version() {
    curl -s https://api.github.com/repos/alicevision/meshroom/releases/latest | grep -oP '"tag_name": "\K(.*)(?=")' 2>> "$LOG_FILE"
}

# Function to get the Meshroom version from the tarball filename
get_meshroom_version_from_tar() {
    ls "$MESHROOM_INSTALL_DIR" | grep -oP 'Meshroom-\K[0-9]+\.[0-9]+\.[0-9]+(?=-linux.tar.gz)' | head -1 2>> "$LOG_FILE"
}

# Function to fetch current installed Meshroom version from version file
get_current_installed_version() {
    if [ -f "$MESHROOM_VERSION_FILE" ]; then
        cat "$MESHROOM_VERSION_FILE"
    else
        echo "0.0.0"
    fi
}

# Fetch the latest Meshroom version number from GitHub
latest_meshroom_version=$(get_latest_meshroom_version | sed 's/^v//')  # Remove 'v' prefix for filename matching
current_installed_version=$(get_current_installed_version)

log "Latest Meshroom version: $latest_meshroom_version"
log "Current installed Meshroom version: $current_installed_version"

# Log the list of tar files in the directory
log "Checking for existing tar files in $MESHROOM_INSTALL_DIR:"
ls -lah "$MESHROOM_INSTALL_DIR" | tee -a "$LOG_FILE"

# Define the tarball path
tarball_path="$HOME/Installs/Meshroom/Meshroom-$latest_meshroom_version-linux.tar.gz"
extract_dir="$MESHROOM_INSTALL_DIR/Meshroom-$latest_meshroom_version"

log "Tarball path: $tarball_path"
log "Extract directory: $extract_dir"

# Check if the tarball exists and if the extraction directory can be created
if [ "$latest_meshroom_version" != "$current_installed_version" ]; then
    log "Updating Meshroom to version $latest_meshroom_version..."

    # Skip download if tarball exists, otherwise download it
    if [ -f "$tarball_path" ]; then
        log "Tarball already exists at $tarball_path. Skipping download."
    else
        log "Downloading Meshroom..."
        download_url="https://github.com/alicevision/meshroom/releases/download/v$latest_meshroom_version/Meshroom-$latest_meshroom_version-linux.tar.gz"
        log "Download URL: $download_url"
        wget -O "$tarball_path" "$download_url" 2>> "$LOG_FILE"
        if [ $? -ne 0 ]; then
            log "Error: Meshroom download failed."
            exit 1
        fi
    fi

    # Log before creating the extraction directory
    log "Attempting to create extraction directory: $extract_dir"
    mkdir -p "$extract_dir"
    if [ $? -ne 0 ]; then
        log "Error: Failed to create extraction directory."
        exit 1
    fi
    log "Extraction directory created: $extract_dir"

    # Log before extracting
    log "Extracting Meshroom to $MESHROOM_INSTALL_DIR..."
    tar -xzf "$tarball_path" -C "$MESHROOM_INSTALL_DIR" --strip-components=1 2>> "$LOG_FILE"
    if [ $? -ne 0 ]; then
        log "Error: Failed to extract Meshroom tarball."
        exit 1
    fi
    log "Extraction completed."

    # Save the current version to the version file
    log "Saving current version to version file."
    echo "$latest_meshroom_version" > "$MESHROOM_VERSION_FILE"
else
    log "Meshroom is already up-to-date."
fi

# Create a symbolic link to make Meshroom accessible from anywhere
log "Creating symbolic link for Meshroom..."
sudo ln -sf "$MESHROOM_INSTALL_DIR/Meshroom" /usr/local/bin/meshroom 2>> "$LOG_FILE"
if [ $? -ne 0 ]; then
    log "Error: Failed to create symbolic link."
    exit 1
fi

# Locate the icon files and create the desktop entry
meshroom_icon_svg="$MESHROOM_INSTALL_DIR/lib/meshroom/ui/img/meshroom.svg"
meshroom_icon_ico="$MESHROOM_INSTALL_DIR/lib/meshroom/ui/img/meshroom.ico"

log "Checking for icon files..."
if [ -f "$meshroom_icon_svg" ] || [ -f "$meshroom_icon_ico" ]; then
    log "Creating KDE desktop entry for Meshroom..."

    # Prioritize SVG but use ICO if SVG is not found
    icon_path="$meshroom_icon_svg"
    [ -f "$meshroom_icon_ico" ] && icon_path="$meshroom_icon_ico"
    
    DESKTOP_ENTRY="[Desktop Entry]
Version=1.0
Type=Application
Name=Meshroom
Exec=$MESHROOM_INSTALL_DIR/Meshroom
Icon=$icon_path
Terminal=false
Categories=Graphics;3DGraphics;
"
    echo "$DESKTOP_ENTRY" > "$HOME/.local/share/applications/meshroom.desktop"
    chmod +x "$HOME/.local/share/applications/meshroom.desktop"
    log "Meshroom $latest_meshroom_version desktop entry created with icon $icon_path."
else
    log "Meshroom icon not found. Skipping desktop entry creation."
fi

log "Script complete."
UNINSTALL
MESHROOM_INSTALL_DIR="$HOME/.Applications/MeshRoom"
MESHROOM_VERSION_FILE="$MESHROOM_INSTALL_DIR/version.txt"
LOG_FILE="$MESHROOM_INSTALL_DIR/install_log.txt"
MESHROOM_DESKTOP_ENTRY="$HOME/.local/share/applications/meshroom.desktop"
SYMLINK_PATH="/usr/local/bin/meshroom"

# Function to log and display messages
log() {
    echo "$1"
}

log "Starting Meshroom uninstallation..."

# Check if Meshroom is installed
if [ -d "$MESHROOM_INSTALL_DIR" ]; then
    log "Removing Meshroom installation directory: $MESHROOM_INSTALL_DIR"
    rm -rf "$MESHROOM_INSTALL_DIR"
    log "Meshroom directory removed."
else
    log "Meshroom installation directory not found. Skipping removal."
fi

# Remove the symbolic link if it exists
if [ -L "$SYMLINK_PATH" ]; then
    log "Removing Meshroom symbolic link: $SYMLINK_PATH"
    sudo rm -f "$SYMLINK_PATH"
    log "Symbolic link removed."
else
    log "Meshroom symbolic link not found. Skipping removal."
fi

# Remove desktop entry if it exists
if [ -f "$MESHROOM_DESKTOP_ENTRY" ]; then
    log "Removing Meshroom desktop entry: $MESHROOM_DESKTOP_ENTRY"
    rm -f "$MESHROOM_DESKTOP_ENTRY"
    log "Desktop entry removed."
else
    log "Meshroom desktop entry not found. Skipping removal."
fi

# Remove the log file if it exists
if [ -f "$LOG_FILE" ]; then
    log "Removing Meshroom log file: $LOG_FILE"
    rm -f "$LOG_FILE"
    log "Log file removed."
else
    log "Meshroom log file not found. Skipping removal."
fi

log "Meshroom uninstallation complete."


Blender Logo

Blender Launcher

INSTALL
LOGFILE="$HOME/.Applications/BlenderLauncher/install_log.txt"
INSTALL_DIR="$HOME/.Applications/BlenderLauncher"
LATEST_RELEASE_URL="https://api.github.com/repos/Victor-IX/Blender-Launcher-V2/releases/latest"
ICON_URL="https://github.com/Victor-IX/Blender-Launcher-V2/raw/main/docs/mkdocs/icons/favicon.ico"
DESKTOP_ENTRY="$HOME/.local/share/applications/blender-launcher.desktop"

# Function to log messages
log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOGFILE"
}

log "Installing Blender Launcher to $INSTALL_DIR (hidden in your home directory)"
mkdir -p "$INSTALL_DIR" || { log "Failed to create install directory"; exit 1; }
cd "$INSTALL_DIR" || { log "Failed to change directory to install directory"; exit 1; }

log "Fetching latest Blender Launcher release information"
LATEST_RELEASE=$(curl -s $LATEST_RELEASE_URL)
LATEST_VERSION=$(echo $LATEST_RELEASE | grep -oP '"tag_name": "\K(.*)(?=")')
LATEST_DOWNLOAD_URL=$(echo $LATEST_RELEASE | grep -oP '"browser_download_url": "\K(.*Linux_x64.zip)(?=")')

if [ -z "$LATEST_VERSION" ] || [ -z "$LATEST_DOWNLOAD_URL" ]; then
    log "Failed to fetch the latest version or download URL from $LATEST_RELEASE_URL"
    exit 1
fi

log "Latest Blender Launcher version: $LATEST_VERSION"
log "Latest Blender Launcher download URL: $LATEST_DOWNLOAD_URL"

ZIP_FILE="Blender_Launcher_$LATEST_VERSION_Linux_x64.zip"
if [ -f "$ZIP_FILE" ]; then
    log "The latest version ($LATEST_VERSION) is already downloaded."
else
    log "Downloading Blender Launcher $LATEST_VERSION"
    wget -O "$ZIP_FILE" "$LATEST_DOWNLOAD_URL" || { log "Failed to download Blender Launcher"; exit 1; }
fi

log "Extracting Blender Launcher"
unzip -o "$ZIP_FILE" -d "$INSTALL_DIR" || { log "Failed to extract Blender Launcher"; exit 1; }

EXECUTABLE_PATH="$INSTALL_DIR/Blender Launcher"
if [ ! -f "$EXECUTABLE_PATH" ]; then
    log "Could not find the Blender Launcher executable at $EXECUTABLE_PATH"
    exit 1
fi
log "Blender Launcher executable path: $EXECUTABLE_PATH"

log "Downloading icon"
ICON_PATH="$INSTALL_DIR/favicon.ico"
wget -O "$ICON_PATH" "$ICON_URL" || { log "Failed to download icon"; exit 1; }

log "Creating desktop entry"
cat > "$DESKTOP_ENTRY" <<EOL
[Desktop Entry]
Name=Blender Launcher
Exec="$EXECUTABLE_PATH"
Icon=$ICON_PATH
Type=Application
Categories=Graphics;
EOL

log "DONE! Installed Blender Launcher."
UNINSTALL
INSTALL_DIR="$HOME/.Applications/BlenderLauncher"
BLENDER_DIR="$HOME/.Applications/Blender"
DESKTOP_ENTRY="$HOME/.local/share/applications/blender-launcher.desktop"

# Function to log messages
log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOGFILE"
}

log "Starting uninstallation of Blender Launcher"

# Prompt the user for whether to remove the Blender directory
read -p "Do you want to remove the entire Blender directory and all its versions? (Y/N): " remove_blender

if [ "$remove_blender" = "Y" ] || [ "$remove_blender" = "y" ]; then
    log "Removing entire Blender directory: $BLENDER_DIR"
    rm -rf "$BLENDER_DIR" || { log "Failed to remove Blender directory"; exit 1; }
else
    log "Keeping Blender directory: $BLENDER_DIR"
fi

# Remove Blender Launcher installation directory
log "Removing Blender Launcher directory: $INSTALL_DIR"
rm -rf "$INSTALL_DIR" || { log "Failed to remove Blender Launcher directory"; exit 1; }

# Remove desktop entry
log "Removing desktop entry: $DESKTOP_ENTRY"
rm -f "$DESKTOP_ENTRY" || { log "Failed to remove desktop entry"; exit 1; }

log "DONE! Uninstalled Blender Launcher."

echo "Uninstallation complete. Please check the log file for details: $LOGFILE"

Sound Apps

(Tenacity Logo)

Tenacity

Note: This install script is not yet complete, will complete at some point.

INSTALL
# === PATHS ===
APPDIR="$HOME/.Applications/Tenacity"
EXTRACT_DIR="$APPDIR/squashfs-root"
APPIMAGE="$APPDIR/tenacity-linux-v1.3.4.AppImage"
DESKTOP_ENTRY="$HOME/.local/share/applications/tenacity.desktop"
ICON_SOURCE="$EXTRACT_DIR/share/pixmaps/tenacity32.xpm"
ICON_NAME="tenacity32"
LOGFILE="$APPDIR/install_log.txt"

# === LOGGING ===
mkdir -p "$APPDIR"
touch "$LOGFILE"
exec > >(tee -a "$LOGFILE") 2>&1

echo "[INFO] Installing Tenacity..."

# === CHECK APPIMAGE ===
if [ ! -f "$APPIMAGE" ]; then
    echo "[ERROR] AppImage not found: $APPIMAGE"
    exit 1
fi

# === MAKE EXECUTABLE ===
chmod +x "$APPIMAGE"

# === EXTRACT IF NEEDED ===
if [ ! -d "$EXTRACT_DIR" ]; then
    echo "[INFO] Extracting AppImage..."
    "$APPIMAGE" --appimage-extract
    if [ ! -d squashfs-root ]; then
        echo "[ERROR] AppImage extraction failed."
        exit 1
    fi
    mv squashfs-root "$EXTRACT_DIR"
else
    echo "[INFO] Using existing extracted AppImage directory."
fi

# === VERIFY AppRun ===
EXEC_PATH="$EXTRACT_DIR/AppRun"
if [ ! -f "$EXEC_PATH" ]; then
    echo "[ERROR] AppRun not found at expected path."
    exit 1
fi

# === ICON HANDLING ===
if [ -f "$ICON_SOURCE" ]; then
    echo "[INFO] Found icon at: $ICON_SOURCE"
    ICON_PATH="$ICON_SOURCE"
else
    echo "[WARNING] Icon not found at expected location. Using fallback."
    ICON_NAME="audio-x-generic"
fi

# === CREATE DESKTOP ENTRY ===
echo "[INFO] Creating desktop entry..."
mkdir -p "$(dirname "$DESKTOP_ENTRY")"

cat <<EOF > "$DESKTOP_ENTRY"
[Desktop Entry]
Name=Tenacity
Exec=$EXEC_PATH
Icon=${ICON_PATH:-$ICON_NAME}
Type=Application
Categories=AudioVideo;Audio;
Terminal=false
StartupNotify=true
EOF

chmod +x "$DESKTOP_ENTRY"

# === REFRESH MENU DATABASE ===
update-desktop-database ~/.local/share/applications/ > /dev/null 2>&1

echo "[INFO] Installation complete. Tenacity should now appear in your application menu."

Utility Apps


(KVM Logo)

KVM

INSTALL
pause_exit() {
  echo "Press Enter to exit..."
  read -r
  exit "$1"
}

# Prompt for admin password at the beginning
if [ "$EUID" -ne 0 ]; then
  echo "This script requires administrative privileges. Please provide your password."
  sudo -v || { echo "Failed to obtain sudo privileges."; pause_exit 1; }
fi

# Function to detect the Linux distribution
detect_distro() {
  if [ -f /etc/os-release ]; then
    . /etc/os-release
    echo "$ID"
  else
    echo "unknown"
  fi
}

# Function to check hardware virtualization support
check_virtualization_support() {
  VT_X=$(lscpu | grep -i "Virtualization" | awk '{print $2}')
  VT_D=$(sudo dmesg | grep -i "IOMMU" | wc -l)

  if [[ "$VT_X" == "VT-x" || "$VT_X" == "AMD-V" ]]; then
    echo "Hardware virtualization ($VT_X) is enabled."
  else
    echo "No hardware virtualization detected. Enable VT-x/AMD-V in BIOS!"
    echo "Restart your laptop, enter BIOS (F2 or Fn + F2 on Lenovo Legion), and enable:"
    echo "   - Intel Virtualization Technology (VT-x)"
    echo "   - Intel VT-d (IOMMU) for PCI passthrough (optional)"
    pause_exit 1
  fi

  if [[ "$VT_D" -gt 0 ]]; then
    echo "Intel VT-d (IOMMU) is enabled."
  else
    echo "Intel VT-d (IOMMU) not detected. Enable it in BIOS for PCI passthrough support."
  fi
}

# Function to install KVM based on distribution
install_kvm() {
  DISTRO=$(detect_distro)
  echo "Detected Linux distribution: $DISTRO"

  case "$DISTRO" in
    "fedora"|"rocky"|"nobara")
      sudo dnf install -y qemu-kvm libvirt virt-install virt-manager virt-viewer \
                          edk2-ovmf swtpm qemu-img guestfs-tools libosinfo tuned
      ;;
    "ubuntu"|"debian")
      sudo apt install -y qemu-system-x86 libvirt-daemon-system virtinst \
                          virt-manager virt-viewer ovmf swtpm qemu-utils guestfs-tools \
                          libosinfo-bin tuned
      ;;
    "arch")
      sudo pacman -S --noconfirm qemu-full libvirt virt-install virt-manager virt-viewer \
                                edk2-ovmf swtpm qemu-img guestfs-tools libosinfo
      yay -S --noconfirm tuned
      ;;
    *)
      echo "Unsupported distribution. Install KVM manually."
      pause_exit 1
      ;;
  esac
}

# Function to enable and start libvirt service
enable_libvirt_services() {
  echo "Enabling and starting Libvirt services..."
  systemctl enable --now libvirtd
  systemctl start libvirtd
}

# Function to configure TuneD for virtualization performance
optimize_with_tuned() {
  echo "Configuring TuneD for virtualization..."
  systemctl enable --now tuned
  tuned-adm profile virtual-host
}

# Function to configure user permissions for libvirt
configure_user_permissions() {
  echo "Granting user permissions for Libvirt..."
  sudo usermod -aG libvirt $USER
  echo "export LIBVIRT_DEFAULT_URI='qemu:///system'" >> ~/.bashrc
  source ~/.bashrc
}

# Function to set proper ACL permissions for VM storage
set_acl_permissions() {
  echo "Configuring ACL for VM storage directory..."
  sudo setfacl -R -b /var/lib/libvirt/images
  sudo setfacl -R -m u:$USER:rwX /var/lib/libvirt/images
  sudo setfacl -m d:u:$USER:rwx /var/lib/libvirt/images
}

# Function to verify KVM installation
verify_kvm_installation() {
  echo "Verifying KVM installation..."
  sudo virt-host-validate qemu
}

# Main script execution
check_virtualization_support
install_kvm
enable_libvirt_services
optimize_with_tuned
configure_user_permissions
set_acl_permissions
verify_kvm_installation

echo "KVM installation and configuration complete. Reboot your system for changes to take effect."
pause_exit 0

# ---- Guide for New Users ----
cat <<EOF

======================================================
           POST-INSTALLATION INSTRUCTIONS
======================================================

If your virtual machines are not working, ensure the 
**libvirtd** service is running and enabled on boot.

To manually start the virtualization service, run:
    sudo systemctl start libvirtd.service

To check if the service is running, use:
    sudo systemctl status libvirtd.service

To enable it on boot so it starts automatically:
    sudo systemctl enable libvirtd.service

If you encounter issues, try rebooting your system 
after running the above commands.

EOF
UNINSTALL
pause_exit() {
  echo "Press Enter to exit..."
  read -r
  exit "$1"
}

# Prompt for admin password at the beginning
if [ "$EUID" -ne 0 ]; then
  echo "This script requires administrative privileges. Please provide your password."
  sudo -v || { echo "Failed to obtain sudo privileges."; pause_exit 1; }
fi

# Function to detect the Linux distribution
detect_distro() {
  if [ -f /etc/os-release ]; then
    . /etc/os-release
    echo "$ID"
  else
    echo "unknown"
  fi
}

# Function to stop and disable libvirtd service
stop_libvirt_services() {
  echo "Stopping and disabling libvirt services..."
  sudo systemctl stop libvirtd
  sudo systemctl disable libvirtd
}

# Function to remove KVM and related packages
uninstall_kvm() {
  DISTRO=$(detect_distro)
  echo "Detected Linux distribution: $DISTRO"

  case "$DISTRO" in
    "fedora"|"rocky"|"nobara")
      sudo dnf remove -y qemu-kvm libvirt virt-install virt-manager virt-viewer \
                          edk2-ovmf swtpm qemu-img guestfs-tools libosinfo tuned
      ;;
    "ubuntu"|"debian")
      sudo apt remove -y qemu-system-x86 libvirt-daemon-system virtinst \
                          virt-manager virt-viewer ovmf swtpm qemu-utils guestfs-tools \
                          libosinfo-bin tuned
      sudo apt autoremove -y
      ;;
    "arch")
      sudo pacman -Rns --noconfirm qemu-full libvirt virt-install virt-manager virt-viewer \
                                    edk2-ovmf swtpm qemu-img guestfs-tools libosinfo tuned
      ;;
    *)
      echo "Unsupported distribution. Uninstall manually."
      pause_exit 1
      ;;
  esac
}

# Function to remove user from libvirt group
remove_libvirt_user() {
  echo "Removing user from libvirt group..."
  sudo gpasswd -d "$USER" libvirt
}

# Function to delete VM storage directories
remove_vm_storage() {
  echo "Deleting VM storage directories..."
  sudo rm -rf /var/lib/libvirt/images
  sudo rm -rf ~/.config/libvirt
}

# Function to clean up configuration files
remove_config_files() {
  echo "Removing libvirt and QEMU configuration files..."
  sudo rm -rf /etc/libvirt
  sudo rm -rf /etc/qemu
}

# Main script execution
stop_libvirt_services
uninstall_kvm
remove_libvirt_user
remove_vm_storage
remove_config_files

echo "KVM, QEMU, and all virtualization services have been uninstalled."
pause_exit 0
⚠️ **GitHub.com Fallback** ⚠️