Skip to content

Fix workflow permissions syntax for GitHub release creation #19

Fix workflow permissions syntax for GitHub release creation

Fix workflow permissions syntax for GitHub release creation #19

Workflow file for this run

name: Build Huntarr Windows Standalone Application
on:
push:
tags:
- 'v*.*.*' # This will trigger on version tags like v1.1.1
workflow_dispatch: # Allow manual triggering
# Define permissions needed for release creation
permissions:
contents: write
jobs:
build-windows:
runs-on: windows-latest
steps:
# Checkout code with full depth
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0
# Set up Python environment
- name: Set up Python 3.9
uses: actions/setup-python@v4
with:
python-version: '3.9'
cache: 'pip'
# Install dependencies
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pyinstaller pywin32
# Get version from tag or set to v1.1.1 if not available
- name: Extract version
id: get_version
run: |
if ("${{ github.ref }}" -like "refs/tags/v*") {
$version = "${{ github.ref }}".Substring(11)
echo "VERSION=$version" >> $env:GITHUB_OUTPUT
} else {
$version = "1.1.1"
echo "VERSION=$version" >> $env:GITHUB_OUTPUT
echo "BUILD_VERSION=$version" >> $env:GITHUB_ENV
}
# Create necessary directories and prepare icon
- name: Create required directories and copy icon
run: |
# Create static directory if it doesn't exist
if (-not (Test-Path "static")) {
New-Item -Path "static" -ItemType Directory -Force
echo "Created static directory"
}
# Create target directory for icon if it doesn't exist
if (-not (Test-Path "frontend/static/img")) {
New-Item -Path "frontend/static/img" -ItemType Directory -Force
echo "Created frontend/static/img directory"
}
# Check if we have the icon and copy it to the expected location
if (Test-Path "frontend/static/img/logo/huntarr.ico") {
Copy-Item -Path "frontend/static/img/logo/huntarr.ico" -Destination "frontend/static/img/logo.ico" -Force
echo "Copied huntarr.ico to the expected location"
} else {
echo "WARNING: Could not find huntarr.ico at expected location. Creating a placeholder."
# Create a minimal ICO file as placeholder
$minimumIconPath = "frontend/static/img/logo.ico"
if (-not (Test-Path $minimumIconPath)) {
# Download a generic icon if we can
try {
Invoke-WebRequest -Uri "https://www.google.com/favicon.ico" -OutFile $minimumIconPath
echo "Downloaded a placeholder icon"
} catch {
echo "Could not download placeholder icon. Build may fail."
}
}
}
# Verify Windows config file exists
- name: Verify Windows config file
run: |
# Verify windows_config.py exists
if (Test-Path src/primary/windows_config.py) {
echo "Windows config file exists."
} else {
echo "ERROR: windows_config.py not found. Creating emergency version..."
$windowsConfigContent = @"
# Windows patch for config paths
import os
import sys
import pathlib
import logging
import shutil
# Setup a logger for this module
logger = logging.getLogger("windows_config")
# Patch function to ensure config path works on Windows
def get_config_dir():
"""
Get the appropriate config directory for Windows.
When running as an executable, this will be next to the exe.
When running as a script, this will be in the project root.
"""
if getattr(sys, 'frozen', False):
# Running as compiled exe
base_dir = os.path.dirname(sys.executable)
logger.info(f"Running as frozen application from {base_dir}")
else:
# Running as script
base_dir = os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
logger.info(f"Running as script from {base_dir}")
config_dir = os.path.join(base_dir, 'config')
os.makedirs(config_dir, exist_ok=True)
logger.info(f"Using config directory: {config_dir}")
return config_dir
# Create directories for config
def ensure_config_dirs():
"""
Ensure all required config directories exist.
Returns the base config directory path.
"""
config_dir = get_config_dir()
data_dir = os.path.join(config_dir, 'data')
logs_dir = os.path.join(config_dir, 'logs')
settings_dir = os.path.join(config_dir, 'settings')
# Create each directory
os.makedirs(data_dir, exist_ok=True)
os.makedirs(logs_dir, exist_ok=True)
os.makedirs(settings_dir, exist_ok=True)
return config_dir
"@
New-Item -Path src/primary -Name windows_config.py -ItemType File -Force
Set-Content -Path src/primary/windows_config.py -Value $windowsConfigContent
echo "Created emergency windows_config.py file."
}
# Patch the settings_manager.py file to handle Windows paths
- name: Patch settings_manager for Windows
run: |
# Create a patch file
$patchContent = @"
# Path patch for settings_manager.py
import re
import os
# Read the file
with open('src/primary/settings_manager.py', 'r') as f:
content = f.read()
# Create the replacement for the SETTINGS_DIR declaration
pathPatch = '''
# Settings directory setup - Dynamically determine based on environment
import os
import sys
# Import Windows-specific config handling for the executable
try:
from primary.windows_config import get_config_dir, ensure_config_dirs
# On Windows executable, use the special config directory
if sys.platform == 'win32' and getattr(sys, 'frozen', False):
config_dir = ensure_config_dirs()
SETTINGS_DIR = pathlib.Path(os.path.join(config_dir, 'settings'))
settings_logger.info(f"Windows executable mode: Using config at {SETTINGS_DIR}")
else:
# Default Docker/Linux behavior
SETTINGS_DIR = pathlib.Path("/config")
except ImportError:
# Fallback to default behavior
SETTINGS_DIR = pathlib.Path("/config")
SETTINGS_DIR.mkdir(parents=True, exist_ok=True)
'''
# Define the pattern to replace
pattern = r'# Settings directory setup.*?SETTINGS_DIR\.mkdir\(parents=True, exist_ok=True\)'
# Replace using regex with DOTALL flag to match across multiple lines
newContent = re.sub(pattern, pathPatch, content, flags=re.DOTALL)
# Write the modified content back
with open('src/primary/settings_manager.py', 'w') as f:
f.write(newContent)
print("Successfully patched settings_manager.py for Windows compatibility")
"@
Set-Content -Path patch_settings_manager.py -Value $patchContent
# Run the patch script
python patch_settings_manager.py
# Create config directory and default settings
- name: Set up default config structure
run: |
# Create config directory structure
mkdir -p config\data
mkdir -p config\logs
mkdir -p config\settings
# Copy default configs to the settings directory
Copy-Item -Path src\primary\default_configs\*.json -Destination config\settings\ -Force
# Create a launcher.bat file for better user experience
$launcherContent = @"
@echo off
echo ======================================
echo Welcome to Huntarr v${{ steps.get_version.outputs.VERSION }}
echo ======================================
echo Starting Huntarr server...
echo.
echo Access the web interface at: http://localhost:9705
echo.
echo New features in this version:
echo * Improved API handling with consistent timeout settings
echo * Modern UI with animated app icons
echo * Enhanced community links via GitHub
echo.
echo Press Ctrl+C in the Huntarr console window to quit
echo.
start http://localhost:9705
start Huntarr.exe
"@
Set-Content -Path launcher.bat -Value $launcherContent
# Create a Windows-specific README
- name: Create Windows README
run: |
$readmeContent = @"
# Huntarr for Windows v${{ steps.get_version.outputs.VERSION }}
## Quick Start
1. Double-click on the `launcher.bat` file to start Huntarr
2. Your default browser will open to http://localhost:9705
3. Configure your Arr applications in the Settings page
## New Features
- **Bazarr Integration**: Manage your subtitle needs directly from Huntarr
- **Improved API Handling**: Consistent API timeouts and User-Agent (Huntarr/1.0) across all apps
- **Enhanced UI**: Modern interface with animated app icons and improved tooltips
- **GitHub Community**: All community links now point to GitHub resources instead of plex.one
## Configuration
- All configuration files are stored in the `config` folder next to the executable
- Logs are stored in the `config/logs` folder
- App settings are in `config/settings`
- Default API timeout is 120 seconds (configurable in general.json)
## Troubleshooting
- If you encounter Error 500, check the logs in the `config/logs` folder
- Ensure you have proper permissions on the `config` directory
- For support, visit our [GitHub Discussions](https://github.com/Admin9705/Huntarr.io/discussions)
- Report bugs at [GitHub Issues](https://github.com/Admin9705/Huntarr.io/issues)
- Join our community on [Discord](https://discord.com/invite/PGJJjR5Cww)
"@
Set-Content -Path Huntarr-Windows-README.md -Value $readmeContent
# Create PyInstaller spec file
- name: Create PyInstaller spec file
run: |
$specContent = @"
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=[
('frontend', 'frontend'),
('config', 'config'),
('src/primary/default_configs', 'src/primary/default_configs'),
('static', 'static'),
('Huntarr-Windows-README.md', '.'),
('launcher.bat', '.'),
],
hiddenimports=[
'win32timezone',
'waitress',
'src.primary.windows_config',
'win32api',
'win32con',
'win32service',
'win32serviceutil',
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='Huntarr',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon='frontend/static/img/logo.ico',
)
"@
Set-Content -Path Huntarr.spec -Value $specContent
# Build executable with version information
- name: Build executable with version information
run: |
# Get version from previous step
$version = "${{ steps.get_version.outputs.VERSION }}"
# Add version info to executable
$versionInfo = @"
VSVersionInfo(
ffi=FixedFileInfo(
filevers=($version.Split('.') + @(0) * 4)[0..3],
prodvers=($version.Split('.') + @(0) * 4)[0..3],
mask=0x3f,
flags=0x0,
OS=0x40004,
fileType=0x1,
subtype=0x0,
date=(0, 0)
),
kids=[
StringFileInfo(
[
StringTable(
'040904B0',
[StringStruct('CompanyName', 'Huntarr.io'),
StringStruct('FileDescription', 'Huntarr - Arr App Manager'),
StringStruct('FileVersion', '$version'),
StringStruct('InternalName', 'Huntarr'),
StringStruct('LegalCopyright', 'Copyright (c) 2025'),
StringStruct('OriginalFilename', 'Huntarr.exe'),
StringStruct('ProductName', 'Huntarr'),
StringStruct('ProductVersion', '$version')]
)
]
),
VarFileInfo([VarStruct('Translation', [1033, 1200])])
]
)
"@
Set-Content -Path file_version_info.txt -Value $versionInfo
# Build with the spec file - can't use --version-file with a spec file
pyinstaller Huntarr.spec
# Create Windows package
- name: Create Windows package
run: |
# Get version from previous step
$version = "${{ steps.get_version.outputs.VERSION }}"
# Copy additional files to dist directory
Copy-Item -Path launcher.bat -Destination dist\
Copy-Item -Path Huntarr-Windows-README.md -Destination dist\
# Create a zip file of the dist directory with version in name
Compress-Archive -Path dist\* -DestinationPath "Huntarr-Windows-v$version.zip" -Force
# Test the executable
- name: Test executable setup
run: |
echo "Verifying build artifacts"
dir dist
# List and upload build artifacts
- name: List build artifacts
run: |
echo "Build completed. Here are the artifacts:"
dir
echo "Windows build available at: ./Huntarr-Windows-v${{ steps.get_version.outputs.VERSION }}.zip"
# Create a release using the github CLI
- name: Create GitHub Release
run: |
# Get version
$version = "${{ steps.get_version.outputs.VERSION }}"
$releaseTag = "v$version"
echo "Creating release for $releaseTag..."
# Copy the standalone executable to a more accessible location
Copy-Item -Path ./dist/Huntarr.exe -Destination ./Huntarr.exe -Force
# Create release notes file
$releaseNotes = @"
# Huntarr Windows $releaseTag
Windows standalone executable for Huntarr
## Improvements
- Improved API handling with consistent timeout settings
- Modern UI with animated app icons
- GitHub community integrations
- Windows path handling to prevent configuration errors
## Files
- **Huntarr.exe**: Standalone executable
- **Huntarr-Windows-v$version.zip**: Complete package with launcher and config
"@
Set-Content -Path release-notes.md -Value $releaseNotes
# Attempt to create release
try {
# Try creating a new release with both files
New-Item -Path release -ItemType Directory -Force | Out-Null
Copy-Item -Path ./Huntarr.exe -Destination ./release/ -Force
Copy-Item -Path ./Huntarr-Windows-v$version.zip -Destination ./release/ -Force
Write-Host "Files are ready for release in ./release/ directory"
Get-ChildItem ./release/
} catch {
Write-Error "Error preparing release files: $_"
}
# Create GitHub Release with proper permissions
- name: Create GitHub Release
uses: ncipollo/release-action@v1
with:
artifacts: "./dist/Huntarr.exe,./Huntarr-Windows-v${{ steps.get_version.outputs.VERSION }}.zip"
artifactContentType: application/zip
body: |
# Huntarr Windows Build v${{ steps.get_version.outputs.VERSION }}
Windows standalone executable for Huntarr.io
## New in this Build
- Improved API handling with consistent timeout settings
- Modern UI with animated app icons
- GitHub community integrations
- Windows path handling to prevent configuration errors
## Files
- **Huntarr.exe**: Standalone executable
- **Huntarr-Windows-v${{ steps.get_version.outputs.VERSION }}.zip**: Complete package with launcher and configuration
draft: false
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
tag: v${{ steps.get_version.outputs.VERSION }}
name: Huntarr Windows v${{ steps.get_version.outputs.VERSION }}
# Provide download information
- name: Build Summary
run: |
echo "========================================"
echo "Windows build completed successfully!"
echo "Version: v${{ steps.get_version.outputs.VERSION }}"
echo "========================================"
echo "Download from GitHub Releases:"
echo "https://github.com/Admin9705/Huntarr.io/releases/tag/v${{ steps.get_version.outputs.VERSION }}"
echo "========================================"
shell: pwsh