1
0
mirror of https://github.com/containers/ramalama.git synced 2026-02-05 06:46:39 +01:00
Files
ramalama/scripts/build_macos_pkg.sh
Mike Bonnet c72493f24a macOS installer: build and install an app bundle
Switch from building a onefile executable to a onedir executable and app bundle.
The onefile executable is less efficient because it needs to extract resources
into a temp directory on every run, and it will no longer be supported as part
of an app bundle in future versions of pyinstaller.

Skip installing config and inference files under /usr, they will be referenced
from within the app bundle.

Use a plist when calling pkgbuild to avoid the macOS installer "relocating"
the app bundle to arbitrary locations, which breaks the "ramalama" symlink.

Signed-off-by: Mike Bonnet <mikeb@redhat.com>
2026-01-13 11:52:31 -08:00

219 lines
6.7 KiB
Bash
Executable File

#!/bin/bash
#
# Build macOS installer package for RamaLama
#
# This script creates a self-contained .pkg installer that includes:
# - Standalone ramalama executable (built with PyInstaller)
# - All configuration files and man pages
# - Automatic PATH configuration
#
# Requirements:
# - macOS with Xcode Command Line Tools
# - Python 3.10+
# - PyInstaller (pip install pyinstaller)
#
# Usage: ./scripts/build_macos_pkg.sh
set -e
# Ensure we're on macOS
if [[ "$(uname)" != "Darwin" ]]; then
echo "Error: This script must be run on macOS"
exit 1
fi
# Check for required commands
for cmd in python3 pyinstaller pkgbuild productbuild; do
if ! command -v "$cmd" &> /dev/null; then
echo "Error: Required command '$cmd' not found"
echo "Please install required dependencies:"
echo " pip3 install pyinstaller"
exit 1
fi
done
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
BUILD_DIR="$PROJECT_ROOT/build/macos-pkg"
# Get version with error handling
if ! VERSION=$(cd "$PROJECT_ROOT/ramalama" && python3 -c "import version; print(version.version())" 2>/dev/null); then
echo "Error: Failed to determine version"
exit 1
fi
if [ -z "$VERSION" ]; then
echo "Error: Version string is empty"
exit 1
fi
echo "Building RamaLama v${VERSION} macOS Package"
echo "============================================="
# Clean previous builds
echo "Cleaning previous builds..."
rm -rf "$BUILD_DIR"
rm -rf "$PROJECT_ROOT/dist"
rm -rf "$PROJECT_ROOT/build"
mkdir -p "$BUILD_DIR"
# Build app bundle with PyInstaller
echo "Building app bundle..."
cd "$PROJECT_ROOT"
pyinstaller ramalama.spec --clean --noconfirm
# Verify build - PyInstaller creates a .app directory
BUNDLE_DIR="$PROJECT_ROOT/dist/ramalama.app"
BUILT_EXECUTABLE="$BUNDLE_DIR/Contents/MacOS/ramalama"
if [ -d "$BUNDLE_DIR" ]; then
echo "Found app bundle: $BUNDLE_DIR"
else
echo "Error: PyInstaller build failed - no bundle found in dist/"
echo "Contents of dist/:"
ls -la "$PROJECT_ROOT/dist/" || true
exit 1
fi
# Verify the executable is actually executable
if [ ! -x "$BUILT_EXECUTABLE" ]; then
echo "Error: Built file is not executable: $BUILT_EXECUTABLE"
exit 1
fi
echo "App bundle built successfully: $BUNDLE_DIR"
# Create package structure
echo "Creating package structure..."
PKG_ROOT="$BUILD_DIR/package-root"
mkdir -p "$PKG_ROOT/usr/local/bin"
mkdir -p "$PKG_ROOT/usr/local/share/man/man1"
mkdir -p "$PKG_ROOT/usr/local/share/man/man5"
mkdir -p "$PKG_ROOT/usr/local/share/man/man7"
mkdir -p "$PKG_ROOT/usr/local/share/bash-completion/completions"
mkdir -p "$PKG_ROOT/usr/local/share/fish/vendor_completions.d"
mkdir -p "$PKG_ROOT/usr/local/share/zsh/site-functions"
mkdir -p "$PKG_ROOT/Applications"
# Copy bundle
echo "Copying bundle..."
cp -a "$BUNDLE_DIR" "$PKG_ROOT/Applications"
ln -s ../../../Applications/ramalama.app/Contents/MacOS/ramalama "$PKG_ROOT/usr/local/bin/ramalama"
# Copy man pages
echo "Copying documentation..."
cp "$PROJECT_ROOT"/docs/*.1 "$PKG_ROOT/usr/local/share/man/man1/" || true
cp "$PROJECT_ROOT"/docs/*.5 "$PKG_ROOT/usr/local/share/man/man5/" || true
cp "$PROJECT_ROOT"/docs/*.7 "$PKG_ROOT/usr/local/share/man/man7/" || true
# Copy shell completions
echo "Copying shell completions..."
cp "$PROJECT_ROOT"/completions/bash-completion/completions/* "$PKG_ROOT/usr/local/share/bash-completion/completions/" || true
cp "$PROJECT_ROOT"/completions/fish/vendor_completions.d/* "$PKG_ROOT/usr/local/share/fish/vendor_completions.d/" || true
cp "$PROJECT_ROOT"/completions/zsh/site-functions/* "$PKG_ROOT/usr/local/share/zsh/site-functions/" || true
# Create package metadata
echo "Creating package metadata..."
SCRIPTS_DIR="$BUILD_DIR/scripts"
mkdir -p "$SCRIPTS_DIR"
cat > "$SCRIPTS_DIR/postinstall" << 'EOF'
#!/bin/bash
# Post-installation script for RamaLama
set -e
# Function to add PATH to shell config if not already present
add_to_shell_config() {
local config_file="$1"
local export_line='export PATH="/usr/local/bin:$PATH"'
# Only modify if file exists or can be created
if [ -f "$config_file" ] || touch "$config_file" 2>/dev/null; then
# Check if PATH already includes /usr/local/bin
if ! grep -q '/usr/local/bin' "$config_file" 2>/dev/null; then
echo "" >> "$config_file"
echo "# Added by RamaLama installer" >> "$config_file"
echo "$export_line" >> "$config_file"
fi
fi
}
# Get the actual user (not root if using sudo)
ACTUAL_USER="${USER:-${SUDO_USER:-$(whoami)}}"
USER_HOME=$(eval echo "~$ACTUAL_USER")
# Add to shell configurations if they exist
for shell_config in "$USER_HOME/.zshrc" "$USER_HOME/.bash_profile" "$USER_HOME/.bashrc"; do
if [ -f "$shell_config" ]; then
add_to_shell_config "$shell_config"
fi
done
echo "=========================================="
echo "RamaLama installed successfully!"
echo "=========================================="
echo ""
echo "Location: /usr/local/bin/ramalama"
echo ""
echo "Next steps:"
echo " 1. Restart your terminal or run: source ~/.zshrc"
echo " 2. Verify installation: ramalama --version"
echo " 3. Get help: ramalama --help"
echo ""
exit 0
EOF
chmod +x "$SCRIPTS_DIR/postinstall"
# Build the package
echo "Building .pkg installer..."
PKG_NAME="RamaLama-${VERSION}-macOS.pkg"
PKG_OUTPUT="$BUILD_DIR/$PKG_NAME"
pkgbuild \
--root "$PKG_ROOT" \
--identifier "com.github.containers.ramalama" \
--version "$VERSION" \
--scripts "$SCRIPTS_DIR" \
--install-location "/" \
--component-plist "$SCRIPT_DIR/macos-installer/ramalama-pkg.plist" \
"$PKG_OUTPUT"
# Copy installer resource files from templates
echo "Preparing installer resources..."
TEMPLATE_DIR="$SCRIPT_DIR/macos-installer"
# Process distribution XML template - replace placeholders
sed "s/{{VERSION}}/$VERSION/g; s/{{PKG_NAME}}/$PKG_NAME/g" \
"$TEMPLATE_DIR/distribution.xml.template" > "$BUILD_DIR/distribution.xml"
# Copy HTML files
cp "$TEMPLATE_DIR/welcome.html" "$BUILD_DIR/welcome.html"
cp "$TEMPLATE_DIR/readme.html" "$BUILD_DIR/readme.html"
cp "$TEMPLATE_DIR/conclusion.html" "$BUILD_DIR/conclusion.html"
# Copy LICENSE
cp "$PROJECT_ROOT/LICENSE" "$BUILD_DIR/"
# Build final product
PRODUCT_PKG="$BUILD_DIR/RamaLama-${VERSION}-macOS-Installer.pkg"
productbuild \
--distribution "$BUILD_DIR/distribution.xml" \
--resources "$BUILD_DIR" \
--package-path "$BUILD_DIR" \
"$PRODUCT_PKG"
echo ""
echo "✓ Build complete!"
echo " Package: $PRODUCT_PKG"
echo " Size: $(du -h "$PRODUCT_PKG" | cut -f1)"
echo ""
echo "To test installation:"
echo " sudo installer -pkg '$PRODUCT_PKG' -target /"
echo ""
echo "To distribute:"
echo " 1. Sign the package (recommended for public distribution)"
echo " 2. Upload to GitHub Releases"
echo " 3. Optionally notarize with Apple"