#!/usr/bin/env bash set -euo pipefail APP_NAME="neo-pod-desktop" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" BUILD_DIR="$PROJECT_DIR/build/appimage" APP_DIR="$BUILD_DIR/$APP_NAME.AppDir" PYTHON="${PYTHON:-python3}" PIP="${PIP:-pip3}" VENV_DIR="$BUILD_DIR/venv" ARCH="${ARCH:-x86_64}" APPIMAGE_OUTPUT="$PROJECT_DIR/$APP_NAME-${ARCH}.AppImage" APPIMAGETOOL_URL="https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-${ARCH}.AppImage" RED='\033[0;31m' GREEN='\033[0;32m' CYAN='\033[0;36m' NC='\033[0m' info() { echo -e "${CYAN}[INFO]${NC} $*"; } ok() { echo -e "${GREEN}[OK]${NC} $*"; } err() { echo -e "${RED}[ERR]${NC} $*"; } cleanup() { info "Cleaning up build directory..." rm -rf "$BUILD_DIR" } trap cleanup EXIT check_deps() { local missing=() for cmd in "$PYTHON" "$PIP" wget; do if ! command -v "$cmd" &>/dev/null; then missing+=("$cmd") fi done if [ ${#missing[@]} -gt 0 ]; then err "Missing dependencies: ${missing[*]}" exit 1 fi } install_pyinstaller() { if [ -f "$VENV_DIR/bin/python" ] && "$VENV_DIR/bin/python" -c "import PyInstaller" 2>/dev/null; then return 0 fi info "Creating virtual environment..." "$PYTHON" -m venv --copies "$VENV_DIR" info "Installing PyInstaller and dependencies..." "$VENV_DIR/bin/pip" install pyinstaller # Install project dependencies for bundling "$VENV_DIR/bin/pip" install -r "$PROJECT_DIR/requirements.txt" # Install wasmtime for HASHAB signing (iPod Nano 6G/7G) "$VENV_DIR/bin/pip" install wasmtime } download_appimagetool() { local tool="$BUILD_DIR/appimagetool" if [ -x "$tool" ]; then return 0 fi info "Downloading appimagetool..." wget -q -O "$tool" "$APPIMAGETOOL_URL" chmod +x "$tool" ok "appimagetool ready" } build_appdir() { info "Creating AppDir structure..." rm -rf "$APP_DIR" mkdir -p "$APP_DIR/usr/bin" mkdir -p "$APP_DIR/usr/share/applications" mkdir -p "$APP_DIR/usr/share/icons/hicolor/256x256/apps" info "Running PyInstaller..." cd "$PROJECT_DIR" "$VENV_DIR/bin/pyinstaller" \ --onedir \ --name "$APP_NAME" \ --distpath "$APP_DIR/usr/bin" \ --workpath "$BUILD_DIR/pybuild" \ --specpath "$BUILD_DIR" \ --add-data "$PROJECT_DIR/src:$APP_NAME/src" \ --paths "$PROJECT_DIR/src" \ --hidden-import PyQt6.QtMultimedia \ --hidden-import PyQt6.QtMultimediaWidgets \ --collect-all PyQt6 \ --exclude-module PyQt6.QtBluetooth \ --exclude-module PyQt6.QtWebEngineWidgets \ --exclude-module PyQt6.QtWebEngineCore \ --exclude-module PyQt6.QtWebEngineQuick \ --exclude-module PyQt6.QtWebChannel \ --exclude-module PyQt6.QtPositioning \ --exclude-module PyQt6.QtSensors \ --exclude-module PyQt6.QtTest \ --exclude-module PyQt6.QtQuick3D \ --exclude-module PyQt6.QtShaderTools \ --exclude-module PyQt6.QtSpatialAudio \ src/main.py } create_desktop_file() { cat > "$APP_DIR/$APP_NAME.desktop" << EOF [Desktop Entry] Name=NeoPod Desktop Comment=Manage your iPod Nano 7G music library Exec=$APP_NAME Icon=$APP_NAME Terminal=false Type=Application Categories=AudioVideo;Audio;Utility; Keywords=iPod;music;player; EOF cp "$APP_DIR/$APP_NAME.desktop" "$APP_DIR/usr/share/applications/" } create_apprun() { cat > "$APP_DIR/AppRun" << 'APPRUN' #!/bin/bash HERE="$(dirname "$(readlink -f "$0")")" export QT_QPA_PLATFORMTHEME=gtk3 export QT_STYLE_OVERRIDE= exec "$HERE/usr/bin/neo-pod-desktop/neo-pod-desktop" "$@" APPRUN chmod +x "$APP_DIR/AppRun" } create_icon() { local icon="$APP_DIR/$APP_NAME.png" local py="$VENV_DIR/bin/python3" if [ ! -f "$py" ]; then py="$PYTHON" fi if "$py" -c "from PIL import Image" 2>/dev/null; then "$py" -c " from PIL import Image, ImageDraw img = Image.new('RGBA', (256, 256), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) # Simple iPod-style icon draw.rounded_rectangle([20, 40, 236, 216], radius=30, fill=(50, 100, 200)) draw.rounded_rectangle([60, 80, 196, 180], radius=20, fill=(200, 220, 255)) draw.ellipse([100, 120, 156, 176], fill=(50, 100, 200)) img.save('$APP_DIR/$APP_NAME.png') img.save('$APP_DIR/usr/share/icons/hicolor/256x256/apps/$APP_NAME.png') " else # Fallback: copy any existing PNG or create a 1x1 pixel "$PYTHON" -c " import struct, zlib def create_png(path): raw = b'' for y in range(256): raw += b'\\x00' + b'\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A' # broken fallback " 2>/dev/null || true fi ok "Icon created" } build_appimage() { info "Building AppImage..." cd "$BUILD_DIR" local tool="$BUILD_DIR/appimagetool" local appdir="$APP_DIR" local output="$APPIMAGE_OUTPUT" ARCH="$ARCH" "$tool" "$appdir" "$output" chmod +x "$output" ok "AppImage built: $output ($(du -h "$output" | cut -f1))" } main() { echo "" echo -e "${CYAN}========================================${NC}" echo -e "${CYAN} NeoPod Desktop AppImage Builder${NC}" echo -e "${CYAN}========================================${NC}" echo "" install_pyinstaller check_deps mkdir -p "$BUILD_DIR" download_appimagetool build_appdir create_desktop_file create_apprun create_icon build_appimage echo "" echo -e "${GREEN}========================================${NC}" echo -e "${GREEN} Done: $APPIMAGE_OUTPUT${NC}" echo -e "${GREEN}========================================${NC}" } main "$@"