197 lines
5.5 KiB
Bash
Executable File
197 lines
5.5 KiB
Bash
Executable File
#!/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"
|
|
}
|
|
|
|
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 "$@"
|