Skip to content

feat: add PyInstaller standalone binary builds #8

feat: add PyInstaller standalone binary builds

feat: add PyInstaller standalone binary builds #8

Workflow file for this run

name: Build and Release Binaries
on:
release:
types: [published]
pull_request:
branches: [master]
paths:
- 'src/**'
- 'ha-mcp.spec'
- 'mcpb/**'
- '.github/workflows/build-binary.yml'
workflow_dispatch:
inputs:
version:
description: 'Version to build (e.g., 4.7.4)'
required: true
default: '0.0.0-dev'
# Restrict permissions by default (security best practice)
permissions:
contents: read
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
artifact_name: ha-mcp-linux
binary_ext: ''
platform: linux
create_mcpb: false
- os: windows-latest
artifact_name: ha-mcp-windows
binary_ext: '.exe'
platform: win32
create_mcpb: true
- os: macos-latest
artifact_name: ha-mcp-macos-arm64
binary_ext: ''
platform: darwin
create_mcpb: true
runs-on: ${{ matrix.os }}
name: Build on ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"
- name: Create virtual environment and install dependencies
run: |
uv venv .venv
uv pip install -e . pyinstaller
shell: bash
- name: Activate venv (Unix)
if: runner.os != 'Windows'
run: echo "$PWD/.venv/bin" >> $GITHUB_PATH
- name: Activate venv (Windows)
if: runner.os == 'Windows'
run: echo "$PWD/.venv/Scripts" >> $env:GITHUB_PATH
shell: pwsh
- name: Build binary
run: pyinstaller ha-mcp.spec
- name: Test binary starts correctly (Unix)
if: runner.os != 'Windows'
run: |
chmod +x dist/ha-mcp
# Test that the binary starts and shows the FastMCP banner
# Set dummy env vars so it doesn't fail on missing config
export HOMEASSISTANT_URL=http://test:8123
export HOMEASSISTANT_TOKEN=test
# Run binary in background, wait, then kill it
./dist/ha-mcp > output.txt 2>&1 &
PID=$!
sleep 5
kill $PID 2>/dev/null || true
# Verify FastMCP banner appears (indicates successful startup)
cat output.txt
grep -q "FastMCP" output.txt
echo "Binary started successfully!"
- name: Test binary starts correctly (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$env:HOMEASSISTANT_URL = "http://test:8123"
$env:HOMEASSISTANT_TOKEN = "test"
# Start process and capture output
$process = Start-Process -FilePath "dist\ha-mcp.exe" -NoNewWindow -PassThru -RedirectStandardOutput "output.txt" -RedirectStandardError "error.txt"
Start-Sleep -Seconds 10
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 1
# Check output files
Write-Host "=== STDOUT ==="
if (Test-Path "output.txt") { Get-Content -Path "output.txt" -Raw } else { Write-Host "(no output.txt)" }
Write-Host "=== STDERR ==="
if (Test-Path "error.txt") { Get-Content -Path "error.txt" -Raw } else { Write-Host "(no error.txt)" }
Write-Host "=== END ==="
# Check for success
$stdout = if (Test-Path "output.txt") { Get-Content -Path "output.txt" -Raw } else { "" }
$stderr = if (Test-Path "error.txt") { Get-Content -Path "error.txt" -Raw } else { "" }
if ($stdout -match "FastMCP" -or $stderr -match "FastMCP") {
Write-Host "Binary started successfully!"
} else {
Write-Host "Binary failed to start properly"
exit 1
}
- name: Set version
id: version
shell: bash
run: |
if [ "${{ github.event_name }}" = "release" ]; then
VERSION="${{ github.event.release.tag_name }}"
# Remove 'v' prefix if present
VERSION="${VERSION#v}"
else
VERSION="${{ github.event.inputs.version }}"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Rename binary for artifact
shell: bash
run: |
mv dist/ha-mcp${{ matrix.binary_ext }} dist/${{ matrix.artifact_name }}${{ matrix.binary_ext }}
- name: Create mcpb bundle (Windows/macOS only)
if: matrix.create_mcpb
shell: bash
run: |
VERSION="${{ steps.version.outputs.version }}"
PLATFORM="${{ matrix.platform }}"
BINARY_EXT="${{ matrix.binary_ext }}"
# Create mcpb directory structure
mkdir -p mcpb-bundle
# Copy binary to bundle
cp dist/${{ matrix.artifact_name }}${{ matrix.binary_ext }} mcpb-bundle/ha-mcp${{ matrix.binary_ext }}
# Generate manifest.json from template
sed -e "s/\${VERSION}/$VERSION/g" \
-e "s/\${PLATFORM}/$PLATFORM/g" \
-e "s/\${BINARY_EXT}/$BINARY_EXT/g" \
-e 's/\${__dirname}/\${__dirname}/g' \
mcpb/manifest.template.json > mcpb-bundle/manifest.json
# Create .mcpb file (zip archive)
cd mcpb-bundle
if [ "${{ runner.os }}" = "Windows" ]; then
7z a -tzip "../${{ matrix.artifact_name }}.mcpb" *
else
zip -r "../${{ matrix.artifact_name }}.mcpb" *
fi
cd ..
- name: Upload binary artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}
path: dist/${{ matrix.artifact_name }}${{ matrix.binary_ext }}
if-no-files-found: error
- name: Upload mcpb artifact
if: matrix.create_mcpb
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}-mcpb
path: ${{ matrix.artifact_name }}.mcpb
if-no-files-found: error
release:
needs: build
runs-on: ubuntu-latest
if: github.event_name == 'release'
permissions:
contents: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: List artifacts
run: find artifacts -type f
- name: Upload binaries to release
uses: softprops/action-gh-release@v2
with:
files: |
artifacts/ha-mcp-linux/ha-mcp-linux
artifacts/ha-mcp-windows/ha-mcp-windows.exe
artifacts/ha-mcp-macos-arm64/ha-mcp-macos-arm64
artifacts/ha-mcp-windows-mcpb/ha-mcp-windows.mcpb
artifacts/ha-mcp-macos-arm64-mcpb/ha-mcp-macos-arm64.mcpb