-
Notifications
You must be signed in to change notification settings - Fork 273
Add self-contained macOS installer package #2036
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Reviewer's GuideThis PR adds a complete self-contained macOS installer workflow by bundling Python and all dependencies into a .pkg, automating its build and release, and updating documentation to surface the new installer as the primary macOS installation method. Sequence diagram for automated macOS installer build and releasesequenceDiagram
participant Dev as Developer
participant GH as GitHub Actions
participant Runner as macos-latest runner
participant PyInstaller
participant Script as build_macos_pkg.sh
participant Release as GitHub Release
Dev->>GH: Push release or trigger workflow
GH->>Runner: Start job
Runner->>PyInstaller: Build standalone executable
Runner->>Script: Run build_macos_pkg.sh
Script->>Runner: Create .pkg installer
Runner->>Release: Upload installer and checksum
Dev->>Release: Download installer
Class diagram for PyInstaller spec and packaging structureclassDiagram
class Analysis {
+pathex
+binaries
+datas
+hiddenimports
+excludes
}
class PYZ {
+pure
+zipped_data
}
class EXE {
+name
+debug
+console
+upx
}
class BUNDLE {
+name
+icon
+bundle_identifier
+version
+info_plist
}
Analysis --> PYZ
PYZ --> EXE
EXE --> BUNDLE
BUNDLE : creates macOS app bundle
class Package_Structure {
+/usr/local/bin/ramalama
+/usr/local/share/ramalama
+/usr/local/share/man/man1
+/usr/local/share/man/man5
+/usr/local/share/man/man7
+/usr/local/share/bash-completion/completions
+/usr/local/share/fish/vendor_completions.d
+/usr/local/share/zsh/site-functions
}
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Summary of ChangesHello @rhatdan, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the user experience for macOS users by introducing a self-contained installer package for RamaLama. This new .pkg installer simplifies the installation process by bundling Python and all its dependencies, eliminating the need for users to manually set up their Python environment. The changes include new build scripts, PyInstaller configuration, and comprehensive documentation, making RamaLama easier to install and manage on macOS. Highlights
Ignored Files
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes - here's some feedback:
Blocking issues:
- An action sourced from a third-party repository on GitHub is not pinned to a full length commit SHA. Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload. (link)
General comments:
- The hardcoded version in ramalama.spec (and BUNDLE info_plist) can drift out of sync—consider pulling the version dynamically from your version module instead of repeating it.
- Modifying users’ ~/.zshrc and ~/.bash_profile in postinstall may feel intrusive—using /etc/paths.d or pkgutil to register /usr/local/bin is more in line with macOS conventions and avoids touching user files.
- Right now build_macos_pkg.sh installs PyInstaller directly into the global Python—consider isolating build dependencies via a virtualenv, Homebrew, or GitHub Actions cache to avoid polluting the system environment.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The hardcoded version in ramalama.spec (and BUNDLE info_plist) can drift out of sync—consider pulling the version dynamically from your version module instead of repeating it.
- Modifying users’ ~/.zshrc and ~/.bash_profile in postinstall may feel intrusive—using /etc/paths.d or pkgutil to register /usr/local/bin is more in line with macOS conventions and avoids touching user files.
- Right now build_macos_pkg.sh installs PyInstaller directly into the global Python—consider isolating build dependencies via a virtualenv, Homebrew, or GitHub Actions cache to avoid polluting the system environment.
## Individual Comments
### Comment 1
<location> `scripts/build_macos_pkg.sh:35-37` </location>
<code_context>
+mkdir -p "$BUILD_DIR"
+
+# Install PyInstaller if not present
+if ! command -v pyinstaller &> /dev/null; then
+ echo "Installing PyInstaller..."
+ pip3 install pyinstaller
+fi
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** PyInstaller installation does not check for pip availability.
Check for pip3 before running the installation, and provide a clear error message if it's not available.
</issue_to_address>
### Comment 2
<location> `scripts/build_macos_pkg.sh:45-49` </location>
<code_context>
+pyinstaller ramalama.spec --clean --noconfirm
+
+# Verify build
+if [ ! -f "$PROJECT_ROOT/dist/ramalama" ]; then
+ echo "Error: PyInstaller build failed - executable not found"
+ exit 1
</code_context>
<issue_to_address>
**suggestion:** Executable existence check assumes a specific output path.
PyInstaller's output path can vary based on the spec file, sometimes producing a nested directory. Update the existence check to account for both flat files and directories.
```suggestion
# Verify build
if [ ! -e "$PROJECT_ROOT/dist/ramalama" ] && [ ! -d "$PROJECT_ROOT/dist/ramalama" ]; then
echo "Error: PyInstaller build failed - executable or directory not found in dist/"
exit 1
fi
```
</issue_to_address>
### Comment 3
<location> `scripts/build_macos_pkg.sh:97-105` </location>
<code_context>
+# Post-installation script for RamaLama
+
+# Ensure /usr/local/bin is in PATH
+if ! grep -q '/usr/local/bin' ~/.zshrc 2>/dev/null; then
+ echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.zshrc
+fi
+
</code_context>
<issue_to_address>
**suggestion:** Appending PATH to shell rc files may duplicate entries.
To avoid duplicate PATH entries, check if the exact export line exists before appending.
```suggestion
cat > "$SCRIPTS_DIR/postinstall" << 'EOF'
#!/bin/bash
# Post-installation script for RamaLama
# Ensure /usr/local/bin is in PATH
EXPORT_LINE='export PATH="/usr/local/bin:$PATH"'
if ! grep -Fxq "$EXPORT_LINE" ~/.zshrc 2>/dev/null; then
echo "$EXPORT_LINE" >> ~/.zshrc
fi
```
</issue_to_address>
### Comment 4
<location> `ramalama.spec:20` </location>
<code_context>
+project_root = Path.cwd()
+
+# Collect all ramalama package files
+a = Analysis(
+ ['bin/ramalama'],
+ pathex=[str(project_root)],
</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring the spec file to use globbing, auto-discovery, and default arguments for improved readability and maintainability.
Here are a few simple refactors that will preserve every bit of functionality while cutting down on boilerplate:
1. Use glob to collect files instead of hard-coding every tuple
2. Drop all Analysis/EXE/BUNDLE arguments that PyInstaller already defaults to
3. Collapse your long hidden-imports list by walking the package
```python
from pathlib import Path
import pkgutil
import ramalama
def collect_datas(pattern: str, dest: str):
return [(str(p), dest) for p in Path().glob(pattern)]
# 1. replace your datas=[… huge list …] with:
datas = (
collect_datas("shortnames/shortnames.conf", "share/ramalama") +
collect_datas("docs/ramalama.conf", "share/ramalama") +
collect_datas("inference-spec/**/*.json", "share/ramalama/inference") +
collect_datas("completions/**/*", "") +
collect_datas("docs/*.[157]", "share/man")
)
# 3. auto-discover all submodules of ramalama
hiddenimports = [
mod.name
for _, mod, _ in pkgutil.walk_packages(ramalama.__path__, prefix="ramalama.")
] + ["argcomplete", "yaml", "jsonschema", "jinja2"]
a = Analysis(
["bin/ramalama"],
pathex=[str(Path.cwd())],
datas=datas,
hiddenimports=hiddenimports,
# 2. everything else is default, so we can drop binaries, hookspath, excludes if empty, etc.
)
# likewise trim EXE down to only non-defaults:
exe = EXE(pyz, a.scripts, name="ramalama", upx=True, console=True)
# and BUNDLE can lose icon=None, entitlements_file=None, etc.
app = BUNDLE(exe, name="ramalama.app", bundle_identifier="com.github.containers.ramalama")
```
That single-page spec is functionally identical but far easier to read and maintain.
</issue_to_address>
### Comment 5
<location> `.github/workflows/build-macos-installer.yml:75` </location>
<code_context>
uses: softprops/action-gh-release@v1
</code_context>
<issue_to_address>
**security (yaml.github-actions.security.third-party-action-not-pinned-to-commit-sha):** An action sourced from a third-party repository on GitHub is not pinned to a full length commit SHA. Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload.
*Source: opengrep*
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces a self-contained macOS installer package for RamaLama, bundling Python and all dependencies. It includes a PyInstaller spec file, a build script, a GitHub Actions workflow, and documentation. The changes aim to simplify the installation process for macOS users. I have provided comments addressing potential issues related to error handling and security.
scripts/build_macos_pkg.sh
Outdated
| if [ ! -f "$PROJECT_ROOT/dist/ramalama" ]; then | ||
| echo "Error: PyInstaller build failed - executable not found" | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check only verifies the existence of the executable but doesn't validate the integrity of the build process. It's critical to ensure that the PyInstaller build process completed successfully and didn't encounter any errors during the build. Without a more robust error check, a partially failed build could still produce an executable, leading to unexpected behavior.
Consider adding a check for the PyInstaller return code to ensure it is 0, indicating success.
| if [ ! -f "$PROJECT_ROOT/dist/ramalama" ]; then | |
| echo "Error: PyInstaller build failed - executable not found" | |
| exit 1 | |
| fi | |
| if [ ! -f "$PROJECT_ROOT/dist/ramalama" ] || [ $? -ne 0 ]; then | |
| echo "Error: PyInstaller build failed - executable not found or build process failed" | |
| exit 1 | |
| fi |
| echo " 1. Sign the package (recommended for public distribution)" | ||
| echo " 2. Upload to GitHub Releases" | ||
| echo " 3. Optionally notarize with Apple" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The script provides instructions to sign the package for public distribution, which is a high security concern. However, it does not enforce or automate this step. An unsigned package will trigger Gatekeeper warnings, potentially deterring users from installing the application. It is important to ensure that the signing process is either automated or explicitly required before distribution.
Consider adding a check to verify that the package is signed before it is uploaded to GitHub Releases, or automate the signing process within the script.
2fd7a59 to
2f99cc6
Compare
ashley-cui
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on a quick read of the code, the script:
- Builds ramalama using ramalama.spec with
pyinstaller ramalama.spec - Installs the built ramalama binary into
/usr/local/bin/ramalama - Installs manpages into
/usr/local/share/man - Installs conf files to
/usr/local/share/ramalama/ - Installs
inference-spec/schema/*.json tousr/local/share/ramalama/inference/and Installsinference-spec/engines/*to/usr/local/share/ramalama/inference/` - Installs shell completions
If this is the correct way to build Python binaries, and the locations look right, then this is probably on the right track. I'm not fluent enough in the XML files to pick through all the options set there, but a quick glance at them looks correct. I don't think Ramalama needs entitlements, so we're probably good there, but if you run into any errors, that's the first place I'd check. The other thing is are there any other dependencies or other binaries we want to ship with ramalama?
Last thing, is that this pkginstaller is not signed. This means that the MacOS gatekeeper will flag the ramalama binary and refuse to run it, unless the user goes into settings and manually approves it. I think you'd definitely want to have a signed version in the future.
scripts/build_macos_pkg.sh
Outdated
| if ! command -v pyinstaller &> /dev/null; then | ||
| echo "Installing PyInstaller..." | ||
| pip3 install pyinstaller | ||
| fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
personal preference but I wouldn't install build dependencies in the script, I would expect those to be installed beforehand.
| cat > "$BUILD_DIR/distribution.xml" << EOF | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <installer-gui-script minSpecVersion="1"> | ||
| <title>RamaLama</title> | ||
| <organization>com.github.containers</organization> | ||
| <domains enable_localSystem="true"/> | ||
| <options customize="never" require-scripts="false"/> | ||
| <welcome file="welcome.html"/> | ||
| <readme file="readme.html"/> | ||
| <license file="LICENSE"/> | ||
| <conclusion file="conclusion.html"/> | ||
| <choices-outline> | ||
| <line choice="default"> | ||
| <line choice="com.github.containers.ramalama"/> | ||
| </line> | ||
| </choices-outline> | ||
| <choice id="default"/> | ||
| <choice id="com.github.containers.ramalama" visible="false"> | ||
| <pkg-ref id="com.github.containers.ramalama"/> | ||
| </choice> | ||
| <pkg-ref id="com.github.containers.ramalama" version="$VERSION" onConclusion="none">$PKG_NAME</pkg-ref> | ||
| </installer-gui-script> | ||
| EOF | ||
|
|
||
| # Create welcome message | ||
| cat > "$BUILD_DIR/welcome.html" << 'EOF' | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head><title>Welcome to RamaLama</title></head> | ||
| <body> | ||
| <h1>Welcome to RamaLama Installer</h1> | ||
| <p>This installer will install RamaLama, a command-line tool for working with AI LLM models.</p> | ||
| <p>RamaLama makes working with AI models simple and straightforward.</p> | ||
| </body> | ||
| </html> | ||
| EOF | ||
|
|
||
| # Create readme | ||
| cat > "$BUILD_DIR/readme.html" << 'EOF' | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head><title>RamaLama Information</title></head> | ||
| <body> | ||
| <h1>About RamaLama</h1> | ||
| <p>RamaLama is a command-line tool that facilitates local management and serving of AI Models.</p> | ||
| <h2>Requirements</h2> | ||
| <ul> | ||
| <li>macOS 10.15 or later</li> | ||
| <li>Podman or Docker (recommended)</li> | ||
| </ul> | ||
| <h2>After Installation</h2> | ||
| <p>Run <code>ramalama --help</code> to get started.</p> | ||
| <p>For more information, visit: https://github.com/containers/ramalama</p> | ||
| </body> | ||
| </html> | ||
| EOF | ||
|
|
||
| # Create conclusion | ||
| cat > "$BUILD_DIR/conclusion.html" << 'EOF' | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head><title>Installation Complete</title></head> | ||
| <body> | ||
| <h1>Installation Complete!</h1> | ||
| <p>RamaLama has been successfully installed.</p> | ||
| <h2>Next Steps:</h2> | ||
| <ol> | ||
| <li>Restart your terminal or run: <code>source ~/.zshrc</code></li> | ||
| <li>Verify installation: <code>ramalama --version</code></li> | ||
| <li>Get help: <code>ramalama --help</code></li> | ||
| <li>Pull a model: <code>ramalama pull tinyllama</code></li> | ||
| <li>Run a chatbot: <code>ramalama run tinyllama</code></li> | ||
| </ol> | ||
| <p>Documentation: https://github.com/containers/ramalama</p> | ||
| </body> | ||
| </html> | ||
| EOF |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also personal preference but I feel like these XML files can live as their own files somewhere else, and just be pulled into the script, since having them here makes the script harder to read.
docs/MACOS_INSTALL.md
Outdated
| Or disable Gatekeeper temporarily: | ||
|
|
||
| ```bash | ||
| sudo spctl --master-disable | ||
| # Install the package | ||
| sudo spctl --master-enable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bad thing to recommend.
docs/MACOS_INSTALL.md
Outdated
|
|
||
| ### "Cannot verify developer" warning | ||
|
|
||
| macOS may show a security warning for unsigned packages. To bypass: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd add a sentence on saying we're working on getting keys to sign it or something, we should not only ship unsigned pkgs.
| release: | ||
| types: [published] | ||
| workflow_dispatch: | ||
| inputs: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would also need a inpt or something for manual dispatch, or else the manual run wouldn't know which release to upload the installer to, or what to checkout to build. Needs a followup later on the the script in checkout and upload too if you want to keep the manual dispatch.
|
|
||
| ## Method 1: Self-Contained Installer Package (Recommended) | ||
|
|
||
| The easiest way to install RamaLama on macOS is using our self-contained `.pkg` installer. This method includes Python and all dependencies, so you don't need to install anything else. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'd still need to install podman. We can either add podman to the pkginstaller, or document the dependency here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Podman and podman-machine are not fully required, only recommended, You can run most of RamaLama without using containers or even use Docker.
|
@mikebonnet PTAL |
|
@benoitf PTAL |
2f99cc6 to
e2f0bc5
Compare
Fixes containers#812 This commit adds a mechanism to create self-contained macOS installer packages (.pkg) that bundle Python and all dependencies, eliminating the need for users to install Python separately. **New Features:** 1. **PyInstaller Spec File** (ramalama.spec) - Configures PyInstaller to create standalone executable - Bundles all ramalama modules and dependencies - Includes configuration files, man pages, and shell completions - Creates macOS app bundle structure 2. **Build Script** (scripts/build_macos_pkg.sh) - Automated build process for macOS .pkg installer - Uses PyInstaller to create standalone binary - Packages everything into macOS installer format - Includes post-install script for PATH configuration - Generates installer with welcome/readme/conclusion screens 3. **GitHub Actions Workflow** (.github/workflows/build-macos-installer.yml) - Automatically builds macOS installer on release - Runs on macOS runners with proper dependencies - Uploads installer as release asset - Generates SHA256 checksums for verification - Can be triggered manually for testing 4. **Documentation** (docs/MACOS_INSTALL.md) - Comprehensive installation guide for macOS users - Multiple installation methods documented - Troubleshooting section - Prerequisites and system requirements - Uninstallation instructions 5. **README Update** - Added macOS installer as primary installation method - Links to detailed installation guide **Benefits:** - No Python installation required for end users - Single-click installation experience - Includes all dependencies in one package - Follows macOS packaging best practices - Automatic PATH configuration - Professional installer UI with instructions **Installation:** Users can now download RamaLama-VERSION-macOS-Installer.pkg from GitHub Releases and install with a simple double-click or: sudo installer -pkg RamaLama-*-macOS-Installer.pkg -target / The installer places files in /usr/local/ following macOS conventions. Signed-off-by: Daniel J Walsh <[email protected]>
e2f0bc5 to
f16458d
Compare
|
@engelmi @mikebonnet @ieaves @olliewalsh @benoitf PTAL I need help in getting this properly signed, but for now we can probably move forward with an unsigned version. Bottom line on this, is this is all AI built, and I have never really done something like this on a MAC. I am a Linux guy. |
|
@rhatdan I believe we will need an apple developer account for the project in order to generate a signing key. Do we have one for the project yet? If not I can set it up and add the key as a secret in ci. |
|
I have no idea, @ashley-cui @mikebonnet Thoughts? |
|
We don't have one yet, but I'll set one up. |
|
Thanks. |
Fixes #812
This commit adds a mechanism to create self-contained macOS installer packages (.pkg) that bundle Python and all dependencies, eliminating the need for users to install Python separately.
New Features:
PyInstaller Spec File (ramalama.spec)
Build Script (scripts/build_macos_pkg.sh)
GitHub Actions Workflow (.github/workflows/build-macos-installer.yml)
Documentation (docs/MACOS_INSTALL.md)
README Update
Benefits:
Installation:
Users can now download RamaLama-VERSION-macOS-Installer.pkg from GitHub Releases and install with a simple double-click or:
sudo installer -pkg RamaLama-*-macOS-Installer.pkg -target /
The installer places files in /usr/local/ following macOS conventions.
Summary by Sourcery
Provide a self-contained macOS installer for RamaLama that bundles Python and all dependencies, complete with automated build script, CI workflow, and user documentation
New Features:
CI:
Documentation: