Add FreeBSD release workflow and build script

New files:

.github/workflows/freebsd-release.yml:
- Triggers on version tags (v*) and manual dispatch
- Builds frontend with npm
- Validates Python syntax
- Runs compatibility tests
- Creates release tarball with checksums
- Tests package in FreeBSD VM (vmactions/freebsd-vm)
- Uploads to GitHub Releases with release notes

scripts/build-freebsd.sh:
- Standalone build script for local builds
- Works on FreeBSD, Linux, or macOS
- Options: --version, --output, --skip-frontend, --skip-tests, --clean
- Generates tarball with SHA256 and MD5 checksums
- Self-contained with dependency checking

BUILD.md:
- Complete documentation for building FreeBSD packages
- Quick start guide for automated and local builds
- Package contents and installation instructions
- Troubleshooting section
- Development workflow guide

The release package includes:
- Pre-built React frontend
- Django application code
- FreeBSD installation script
- Compatibility test suite
- Build metadata
This commit is contained in:
Claude 2026-01-10 05:05:17 +00:00
parent bc0310c0fc
commit 5089b16d36
No known key found for this signature in database
3 changed files with 1134 additions and 0 deletions

343
.github/workflows/freebsd-release.yml vendored Normal file
View file

@ -0,0 +1,343 @@
# FreeBSD Release Build
#
# Builds a FreeBSD release package containing:
# - Pre-built frontend assets
# - Python application code
# - FreeBSD installation scripts
# - RC.d service scripts
# - Configuration templates
#
# The package can be installed on FreeBSD using the included install script.
#
name: FreeBSD Release
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Version to build (e.g., 0.16.0)'
required: false
default: ''
create_release:
description: 'Create GitHub Release'
type: boolean
default: true
env:
PROJECT_NAME: dispatcharr
jobs:
# Job 1: Build the FreeBSD release package
build:
name: Build FreeBSD Package
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
package_name: ${{ steps.package.outputs.name }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine version
id: version
run: |
if [ -n "${{ github.event.inputs.version }}" ]; then
VERSION="${{ github.event.inputs.version }}"
elif [[ "${{ github.ref }}" == refs/tags/v* ]]; then
VERSION="${GITHUB_REF#refs/tags/v}"
else
# Extract from version.py
VERSION=$(grep -oP "(?<=version = ['\"])[^'\"]*" version.py 2>/dev/null || echo "0.0.0")
VERSION="${VERSION}-dev.$(date +%Y%m%d)"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Building version: $VERSION"
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Build frontend
run: |
cd frontend
npm ci --legacy-peer-deps
npm run build
echo "Frontend build complete"
ls -la dist/
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Validate Python syntax
run: |
python -m py_compile manage.py
find apps core dispatcharr -name "*.py" -exec python -m py_compile {} \;
echo "Python syntax validation passed"
- name: Run FreeBSD compatibility tests
run: |
chmod +x scripts/ci_test.sh
./scripts/ci_test.sh
- name: Create release package
id: package
run: |
VERSION="${{ steps.version.outputs.version }}"
PACKAGE_NAME="${{ env.PROJECT_NAME }}-${VERSION}-freebsd"
BUILD_DIR="build/${PACKAGE_NAME}"
echo "Creating package: ${PACKAGE_NAME}"
mkdir -p "${BUILD_DIR}"
# Copy application code
cp -r apps core dispatcharr "${BUILD_DIR}/"
cp manage.py requirements.txt version.py "${BUILD_DIR}/"
# Copy pre-built frontend
mkdir -p "${BUILD_DIR}/frontend"
cp -r frontend/dist "${BUILD_DIR}/frontend/"
cp frontend/package.json "${BUILD_DIR}/frontend/"
# Copy FreeBSD-specific files
cp freebsd_start.sh "${BUILD_DIR}/"
cp -r scripts "${BUILD_DIR}/"
mkdir -p "${BUILD_DIR}/tests/freebsd"
cp -r tests/freebsd/* "${BUILD_DIR}/tests/freebsd/"
# Copy documentation
cp README.md LICENSE "${BUILD_DIR}/" 2>/dev/null || true
[ -f CHANGELOG.md ] && cp CHANGELOG.md "${BUILD_DIR}/"
# Create version info file
cat > "${BUILD_DIR}/BUILD_INFO" << EOF
Package: ${PACKAGE_NAME}
Version: ${VERSION}
Build Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
Git Commit: ${GITHUB_SHA:-unknown}
Git Ref: ${GITHUB_REF:-unknown}
Target: FreeBSD 14.x/15.x (amd64)
EOF
# Create installation wrapper
cat > "${BUILD_DIR}/install.sh" << 'INSTALL_EOF'
#!/bin/sh
# Dispatcharr FreeBSD Installation Wrapper
#
# This script wraps freebsd_start.sh for release package installation.
#
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "=========================================="
echo "Dispatcharr FreeBSD Installation"
echo "=========================================="
cat "${SCRIPT_DIR}/BUILD_INFO"
echo "=========================================="
echo ""
# Check if running as root
if [ "$(id -u)" -ne 0 ]; then
echo "Error: This script must be run as root"
exit 1
fi
# Check if on FreeBSD
if [ "$(uname -s)" != "FreeBSD" ]; then
echo "Error: This package is for FreeBSD only"
exit 1
fi
# Run the main installation script
exec "${SCRIPT_DIR}/freebsd_start.sh" "$@"
INSTALL_EOF
chmod +x "${BUILD_DIR}/install.sh"
# Create the tarball
cd build
tar -czvf "${PACKAGE_NAME}.tar.gz" "${PACKAGE_NAME}"
# Generate checksums
sha256sum "${PACKAGE_NAME}.tar.gz" > "${PACKAGE_NAME}.tar.gz.sha256"
md5sum "${PACKAGE_NAME}.tar.gz" > "${PACKAGE_NAME}.tar.gz.md5"
echo "name=${PACKAGE_NAME}" >> $GITHUB_OUTPUT
echo "Package created: ${PACKAGE_NAME}.tar.gz"
ls -lh "${PACKAGE_NAME}.tar.gz"
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: freebsd-package
path: |
build/${{ steps.package.outputs.name }}.tar.gz
build/${{ steps.package.outputs.name }}.tar.gz.sha256
build/${{ steps.package.outputs.name }}.tar.gz.md5
retention-days: 30
# Job 2: Test in FreeBSD VM (optional but recommended)
test:
name: Test in FreeBSD VM
needs: build
runs-on: ubuntu-latest
# Only run on tag pushes or when explicitly requested
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
steps:
- name: Download package
uses: actions/download-artifact@v4
with:
name: freebsd-package
path: ./package
- name: Test package in FreeBSD VM
uses: vmactions/freebsd-vm@v1
with:
release: "14.0"
usesh: true
prepare: |
pkg update -f
pkg install -y bash git
run: |
set -e
echo "=== FreeBSD Package Test ==="
freebsd-version
# Extract package
cd /root
tar -xzf /home/runner/work/*/*/package/*.tar.gz
# Verify package contents
PACKAGE_DIR=$(ls -d dispatcharr-* | head -1)
echo "Package directory: ${PACKAGE_DIR}"
cd "${PACKAGE_DIR}"
# Verify key files exist
echo "Checking package contents..."
test -f freebsd_start.sh && echo "✓ freebsd_start.sh"
test -f install.sh && echo "✓ install.sh"
test -f manage.py && echo "✓ manage.py"
test -f requirements.txt && echo "✓ requirements.txt"
test -d frontend/dist && echo "✓ frontend/dist/"
test -d apps && echo "✓ apps/"
test -d core && echo "✓ core/"
# Run compatibility tests
echo ""
echo "Running compatibility tests..."
if [ -f tests/freebsd/test_freebsd_compat.sh ]; then
chmod +x tests/freebsd/test_freebsd_compat.sh
bash tests/freebsd/test_freebsd_compat.sh
fi
echo ""
echo "=== Package test completed successfully ==="
# Job 3: Create GitHub Release
release:
name: Create GitHub Release
needs: [build, test]
runs-on: ubuntu-latest
if: |
always() &&
needs.build.result == 'success' &&
(needs.test.result == 'success' || needs.test.result == 'skipped') &&
(startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && github.event.inputs.create_release == 'true'))
permissions:
contents: write
steps:
- name: Download package
uses: actions/download-artifact@v4
with:
name: freebsd-package
path: ./release
- name: Prepare release notes
id: notes
run: |
VERSION="${{ needs.build.outputs.version }}"
PACKAGE="${{ needs.build.outputs.package_name }}"
cat > release_notes.md << EOF
## FreeBSD Release Package
This release includes a pre-built package for FreeBSD 14.x/15.x (amd64).
### Installation
\`\`\`bash
# Download and extract
fetch https://github.com/${{ github.repository }}/releases/download/v${VERSION}/${PACKAGE}.tar.gz
tar -xzf ${PACKAGE}.tar.gz
cd ${PACKAGE}
# Verify checksum (optional)
fetch https://github.com/${{ github.repository }}/releases/download/v${VERSION}/${PACKAGE}.tar.gz.sha256
sha256 -c ${PACKAGE}.tar.gz.sha256
# Install (as root)
sudo ./install.sh
\`\`\`
### Package Contents
- Pre-built React frontend
- Django application code
- FreeBSD installation script
- RC.d service scripts (auto-generated during install)
- Compatibility test suite
### Requirements
- FreeBSD 14.x or 15.x (amd64)
- PostgreSQL 15+
- Redis
- Python 3.11+
- Node.js (only if rebuilding frontend)
### Checksums
See \`.sha256\` and \`.md5\` files for verification.
---
*Built from commit: ${{ github.sha }}*
EOF
- name: Create Release
uses: softprops/action-gh-release@v2
with:
name: "v${{ needs.build.outputs.version }}"
tag_name: "v${{ needs.build.outputs.version }}"
body_path: release_notes.md
draft: ${{ github.event_name == 'workflow_dispatch' }}
prerelease: ${{ contains(needs.build.outputs.version, 'dev') || contains(needs.build.outputs.version, 'alpha') || contains(needs.build.outputs.version, 'beta') }}
files: |
release/${{ needs.build.outputs.package_name }}.tar.gz
release/${{ needs.build.outputs.package_name }}.tar.gz.sha256
release/${{ needs.build.outputs.package_name }}.tar.gz.md5
fail_on_unmatched_files: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Summary
run: |
echo "## FreeBSD Release Published" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Version:** ${{ needs.build.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "**Package:** ${{ needs.build.outputs.package_name }}.tar.gz" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "[View Release](https://github.com/${{ github.repository }}/releases/tag/v${{ needs.build.outputs.version }})" >> $GITHUB_STEP_SUMMARY

260
BUILD.md Normal file
View file

@ -0,0 +1,260 @@
# Building Dispatcharr for FreeBSD
This document describes how to build a FreeBSD release package for Dispatcharr.
## Overview
Dispatcharr is a Python/Django application with a React frontend. The FreeBSD "build" creates a distributable package containing:
- Pre-built React frontend (compiled with Vite)
- Django application code
- FreeBSD installation script (`freebsd_start.sh`)
- RC.d service templates
- Compatibility tests
## Quick Start
### Automated Build (GitHub Actions)
The easiest way to build is using GitHub Actions:
1. **Tag a release:**
```bash
git tag v1.0.0
git push origin v1.0.0
```
This triggers the `freebsd-release.yml` workflow automatically.
2. **Manual trigger:**
- Go to **Actions** → **FreeBSD Release**
- Click **Run workflow**
- Optionally specify a version
- Click **Run workflow**
The workflow will:
- Build the frontend
- Run compatibility tests
- Create a release package
- (Optionally) Test in a FreeBSD VM
- Upload to GitHub Releases
### Local Build
Build locally using the standalone script:
```bash
# Standard build
./scripts/build-freebsd.sh
# Build with specific version
./scripts/build-freebsd.sh --version 1.0.0
# Quick rebuild (skip frontend if unchanged)
./scripts/build-freebsd.sh --skip-frontend
# Clean build to custom location
./scripts/build-freebsd.sh --clean --output /tmp/build
```
## Build Requirements
### For Local Builds
| Tool | Version | Purpose |
|------|---------|---------|
| Node.js | 18+ | Frontend build |
| npm | 9+ | Package management |
| Python | 3.9+ | Syntax validation |
| bash | 4+ | Build script |
| tar | any | Packaging |
| sha256sum | any | Checksums |
### For FreeBSD Installation
| Package | Purpose |
|---------|---------|
| python3 | Runtime |
| py311-pip | Package installation |
| postgresql17-server | Database |
| redis | Cache/queue |
| nginx | Web server |
| node, npm | Frontend (if rebuilding) |
| git | Updates |
## Build Script Options
```
./scripts/build-freebsd.sh [OPTIONS]
OPTIONS:
--version <ver> Version string (default: from version.py)
--output <path> Output directory (default: ./build)
--skip-frontend Skip frontend build, use existing dist/
--skip-tests Skip compatibility tests
--clean Clean build directory first
--help Show help
```
## Package Contents
The generated package (`dispatcharr-VERSION-freebsd.tar.gz`) contains:
```
dispatcharr-VERSION-freebsd/
├── apps/ # Django applications
├── core/ # Core Django app
├── dispatcharr/ # Django settings
├── frontend/
│ └── dist/ # Pre-built React frontend
├── scripts/ # Utility scripts
├── tests/
│ └── freebsd/ # Compatibility tests
├── freebsd_start.sh # Main installation script
├── install.sh # Installation wrapper
├── manage.py # Django management
├── requirements.txt # Python dependencies
├── BUILD_INFO # Build metadata
├── README.md
└── LICENSE
```
## Installation on FreeBSD
```bash
# Download the release
fetch https://github.com/USER/REPO/releases/download/vX.X.X/dispatcharr-X.X.X-freebsd.tar.gz
# Verify checksum (optional but recommended)
fetch https://github.com/USER/REPO/releases/download/vX.X.X/dispatcharr-X.X.X-freebsd.tar.gz.sha256
sha256 -c dispatcharr-X.X.X-freebsd.tar.gz.sha256
# Extract
tar -xzf dispatcharr-X.X.X-freebsd.tar.gz
cd dispatcharr-X.X.X-freebsd
# Install (as root)
sudo ./install.sh
```
The installation script will:
1. Install required packages via `pkg`
2. Create the `dispatcharr` user
3. Set up PostgreSQL database
4. Install Python dependencies
5. Run Django migrations
6. Configure Nginx
7. Create and start RC.d services
## GitHub Actions Workflow
### Triggers
| Event | Condition | Action |
|-------|-----------|--------|
| Push tag | `v*` | Build and release |
| Manual | workflow_dispatch | Build (optional release) |
### Jobs
1. **build**: Creates the package on Ubuntu
2. **test**: Tests package in FreeBSD VM (tags only)
3. **release**: Uploads to GitHub Releases
### Secrets Required
None - uses `GITHUB_TOKEN` automatically provided.
### Customization
Edit `.github/workflows/freebsd-release.yml` to:
- Change FreeBSD version (default: 14.0)
- Add additional build steps
- Modify release notes template
- Adjust artifact retention
## Testing
### Compatibility Tests
Run tests before building:
```bash
# Quick CI tests
./scripts/ci_test.sh
# Detailed tests
./tests/freebsd/test_freebsd_compat.sh --verbose
# Python tests
python3 tests/freebsd/test_freebsd_script.py
```
### Test in FreeBSD Jail
On a FreeBSD host:
```bash
sudo ./scripts/test_in_jail.sh --branch main --verbose
```
## Troubleshooting
### Frontend Build Fails
```bash
# Clear npm cache
cd frontend
rm -rf node_modules package-lock.json
npm cache clean --force
npm install --legacy-peer-deps
```
### Python Syntax Errors
```bash
# Find problematic files
find apps core dispatcharr -name "*.py" -exec python3 -m py_compile {} \;
```
### Package Too Large
The frontend `dist/` includes source maps by default. To reduce size:
```bash
# Edit frontend/vite.config.js
build: {
sourcemap: false,
...
}
```
### Checksum Mismatch
Ensure you're downloading both files from the same release:
```bash
# Re-download and verify
rm -f dispatcharr-*.tar.gz*
fetch URL/dispatcharr-X.X.X-freebsd.tar.gz
fetch URL/dispatcharr-X.X.X-freebsd.tar.gz.sha256
sha256 -c dispatcharr-X.X.X-freebsd.tar.gz.sha256
```
## Development Workflow
1. Make changes to the codebase
2. Run compatibility tests: `./scripts/ci_test.sh`
3. Test locally: `./scripts/build-freebsd.sh --skip-tests`
4. Test in jail: `sudo ./scripts/test_in_jail.sh`
5. Commit and push
6. Tag for release: `git tag vX.X.X && git push origin vX.X.X`
## Related Files
- `.github/workflows/freebsd-release.yml` - GitHub Actions workflow
- `.github/workflows/freebsd-compat.yml` - Compatibility testing
- `scripts/build-freebsd.sh` - Standalone build script
- `scripts/ci_test.sh` - CI test runner
- `scripts/test_in_jail.sh` - Jail-based testing
- `freebsd_start.sh` - Installation script
- `tests/freebsd/` - Compatibility test suite

531
scripts/build-freebsd.sh Executable file
View file

@ -0,0 +1,531 @@
#!/usr/bin/env bash
#
# Dispatcharr FreeBSD Build Script
#
# Creates a release package for FreeBSD containing:
# - Pre-built frontend assets
# - Python application code
# - Installation scripts
# - RC.d service templates
#
# Usage:
# ./build-freebsd.sh [OPTIONS]
#
# Options:
# --version <ver> Version string (default: from version.py or 0.0.0-dev)
# --output <path> Output directory (default: ./build)
# --skip-frontend Skip frontend build (use existing dist/)
# --skip-tests Skip compatibility tests
# --clean Clean build directory before building
# --help Show this help message
#
# Requirements:
# - Node.js and npm (for frontend build)
# - Python 3.x (for syntax validation)
# - tar, gzip (for packaging)
#
# This script can run on FreeBSD, Linux, or macOS.
#
set -e
set -o pipefail
# Script directory and project root
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# Default configuration
VERSION=""
OUTPUT_DIR="${PROJECT_ROOT}/build"
SKIP_FRONTEND=0
SKIP_TESTS=0
CLEAN_BUILD=0
PROJECT_NAME="dispatcharr"
# Colors (disabled if not a terminal)
if [ -t 1 ]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m'
else
RED=''
GREEN=''
YELLOW=''
BLUE=''
BOLD=''
NC=''
fi
# Logging functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[OK]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
}
log_step() {
echo ""
echo -e "${BOLD}━━━ $1 ━━━${NC}"
}
show_help() {
cat << 'EOF'
Dispatcharr FreeBSD Build Script
USAGE:
./build-freebsd.sh [OPTIONS]
OPTIONS:
--version <ver> Version string to embed in package
Default: extracted from version.py or "0.0.0-dev"
--output <path> Output directory for build artifacts
Default: ./build
--skip-frontend Skip frontend build, use existing frontend/dist/
Useful for faster rebuilds when frontend hasn't changed
--skip-tests Skip FreeBSD compatibility tests
Not recommended for release builds
--clean Remove existing build directory before building
--help Show this help message
EXAMPLES:
# Standard build
./build-freebsd.sh
# Build specific version
./build-freebsd.sh --version 1.0.0
# Quick rebuild (skip frontend)
./build-freebsd.sh --skip-frontend
# Clean build to custom directory
./build-freebsd.sh --clean --output /tmp/dispatcharr-build
OUTPUT:
Creates a tarball: dispatcharr-<version>-freebsd.tar.gz
With checksums: dispatcharr-<version>-freebsd.tar.gz.sha256
dispatcharr-<version>-freebsd.tar.gz.md5
REQUIREMENTS:
- Node.js 18+ and npm (for frontend build)
- Python 3.9+ (for syntax validation)
- bash, tar, gzip, sha256sum/shasum
EOF
}
# Parse command line arguments
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
--version)
VERSION="$2"
shift 2
;;
--output)
OUTPUT_DIR="$2"
shift 2
;;
--skip-frontend)
SKIP_FRONTEND=1
shift
;;
--skip-tests)
SKIP_TESTS=1
shift
;;
--clean)
CLEAN_BUILD=1
shift
;;
--help|-h)
show_help
exit 0
;;
*)
log_error "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
}
# Check for required tools
check_dependencies() {
log_step "Checking Dependencies"
local missing=()
# Check Node.js (only if building frontend)
if [ $SKIP_FRONTEND -eq 0 ]; then
if command -v node >/dev/null 2>&1; then
log_success "Node.js $(node --version)"
else
missing+=("node")
fi
if command -v npm >/dev/null 2>&1; then
log_success "npm $(npm --version)"
else
missing+=("npm")
fi
fi
# Check Python
if command -v python3 >/dev/null 2>&1; then
log_success "Python $(python3 --version 2>&1 | cut -d' ' -f2)"
else
missing+=("python3")
fi
# Check tar
if command -v tar >/dev/null 2>&1; then
log_success "tar available"
else
missing+=("tar")
fi
# Check checksum tools
if command -v sha256sum >/dev/null 2>&1; then
SHA256_CMD="sha256sum"
log_success "sha256sum available"
elif command -v shasum >/dev/null 2>&1; then
SHA256_CMD="shasum -a 256"
log_success "shasum available"
else
missing+=("sha256sum or shasum")
fi
if command -v md5sum >/dev/null 2>&1; then
MD5_CMD="md5sum"
elif command -v md5 >/dev/null 2>&1; then
MD5_CMD="md5 -r"
else
MD5_CMD=""
log_warn "md5sum not available, skipping MD5 checksum"
fi
if [ ${#missing[@]} -gt 0 ]; then
log_error "Missing required tools: ${missing[*]}"
echo ""
echo "Please install the missing tools and try again."
exit 1
fi
}
# Determine version
determine_version() {
log_step "Determining Version"
if [ -n "$VERSION" ]; then
log_info "Using specified version: $VERSION"
return
fi
# Try to extract from version.py
if [ -f "${PROJECT_ROOT}/version.py" ]; then
VERSION=$(grep -oP "(?<=version = ['\"])[^'\"]*" "${PROJECT_ROOT}/version.py" 2>/dev/null || true)
if [ -n "$VERSION" ]; then
log_info "Extracted version from version.py: $VERSION"
return
fi
fi
# Try git tag
if command -v git >/dev/null 2>&1 && [ -d "${PROJECT_ROOT}/.git" ]; then
VERSION=$(git -C "${PROJECT_ROOT}" describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || true)
if [ -n "$VERSION" ]; then
log_info "Extracted version from git tag: $VERSION"
return
fi
fi
# Default version
VERSION="0.0.0-dev"
log_warn "Could not determine version, using: $VERSION"
}
# Build frontend
build_frontend() {
log_step "Building Frontend"
if [ $SKIP_FRONTEND -eq 1 ]; then
if [ -d "${PROJECT_ROOT}/frontend/dist" ]; then
log_info "Skipping frontend build (--skip-frontend)"
log_success "Using existing frontend/dist/"
return
else
log_error "frontend/dist/ does not exist. Cannot skip frontend build."
exit 1
fi
fi
cd "${PROJECT_ROOT}/frontend"
log_info "Installing npm dependencies..."
npm ci --legacy-peer-deps 2>&1 | tail -5
log_info "Building frontend..."
npm run build 2>&1 | tail -10
if [ -d "dist" ]; then
log_success "Frontend built successfully"
log_info "Output size: $(du -sh dist | cut -f1)"
else
log_error "Frontend build failed - dist/ not created"
exit 1
fi
cd "${PROJECT_ROOT}"
}
# Validate Python code
validate_python() {
log_step "Validating Python Code"
cd "${PROJECT_ROOT}"
log_info "Checking Python syntax..."
local errors=0
# Check manage.py
if python3 -m py_compile manage.py 2>/dev/null; then
log_success "manage.py"
else
log_error "manage.py has syntax errors"
((errors++))
fi
# Check apps, core, dispatcharr directories
for dir in apps core dispatcharr; do
if [ -d "$dir" ]; then
local count=$(find "$dir" -name "*.py" | wc -l | tr -d ' ')
local failed=0
while IFS= read -r -d '' file; do
if ! python3 -m py_compile "$file" 2>/dev/null; then
log_error "Syntax error: $file"
((failed++))
fi
done < <(find "$dir" -name "*.py" -print0)
if [ $failed -eq 0 ]; then
log_success "${dir}/ (${count} files)"
else
((errors += failed))
fi
fi
done
if [ $errors -gt 0 ]; then
log_error "Python validation failed with $errors errors"
exit 1
fi
log_success "All Python files validated"
}
# Run compatibility tests
run_tests() {
log_step "Running FreeBSD Compatibility Tests"
if [ $SKIP_TESTS -eq 1 ]; then
log_warn "Skipping tests (--skip-tests)"
return
fi
cd "${PROJECT_ROOT}"
if [ -x "scripts/ci_test.sh" ]; then
if ./scripts/ci_test.sh; then
log_success "All compatibility tests passed"
else
log_error "Compatibility tests failed"
exit 1
fi
elif [ -x "tests/freebsd/test_freebsd_compat.sh" ]; then
if bash tests/freebsd/test_freebsd_compat.sh; then
log_success "All compatibility tests passed"
else
log_error "Compatibility tests failed"
exit 1
fi
else
log_warn "No test scripts found, skipping tests"
fi
}
# Create the release package
create_package() {
log_step "Creating Release Package"
PACKAGE_NAME="${PROJECT_NAME}-${VERSION}-freebsd"
BUILD_DIR="${OUTPUT_DIR}/${PACKAGE_NAME}"
# Clean if requested
if [ $CLEAN_BUILD -eq 1 ] && [ -d "${OUTPUT_DIR}" ]; then
log_info "Cleaning build directory..."
rm -rf "${OUTPUT_DIR}"
fi
# Create build directory
mkdir -p "${BUILD_DIR}"
log_info "Copying application code..."
# Copy Python application
cp -r "${PROJECT_ROOT}/apps" "${BUILD_DIR}/"
cp -r "${PROJECT_ROOT}/core" "${BUILD_DIR}/"
cp -r "${PROJECT_ROOT}/dispatcharr" "${BUILD_DIR}/"
cp "${PROJECT_ROOT}/manage.py" "${BUILD_DIR}/"
cp "${PROJECT_ROOT}/requirements.txt" "${BUILD_DIR}/"
[ -f "${PROJECT_ROOT}/version.py" ] && cp "${PROJECT_ROOT}/version.py" "${BUILD_DIR}/"
# Copy pre-built frontend
log_info "Copying frontend assets..."
mkdir -p "${BUILD_DIR}/frontend"
cp -r "${PROJECT_ROOT}/frontend/dist" "${BUILD_DIR}/frontend/"
cp "${PROJECT_ROOT}/frontend/package.json" "${BUILD_DIR}/frontend/"
# Copy FreeBSD-specific files
log_info "Copying FreeBSD files..."
cp "${PROJECT_ROOT}/freebsd_start.sh" "${BUILD_DIR}/"
cp -r "${PROJECT_ROOT}/scripts" "${BUILD_DIR}/"
# Copy tests
mkdir -p "${BUILD_DIR}/tests/freebsd"
cp -r "${PROJECT_ROOT}/tests/freebsd/"* "${BUILD_DIR}/tests/freebsd/" 2>/dev/null || true
# Copy documentation
log_info "Copying documentation..."
[ -f "${PROJECT_ROOT}/README.md" ] && cp "${PROJECT_ROOT}/README.md" "${BUILD_DIR}/"
[ -f "${PROJECT_ROOT}/LICENSE" ] && cp "${PROJECT_ROOT}/LICENSE" "${BUILD_DIR}/"
[ -f "${PROJECT_ROOT}/CHANGELOG.md" ] && cp "${PROJECT_ROOT}/CHANGELOG.md" "${BUILD_DIR}/"
[ -f "${PROJECT_ROOT}/BUILD.md" ] && cp "${PROJECT_ROOT}/BUILD.md" "${BUILD_DIR}/"
# Create BUILD_INFO
log_info "Creating build info..."
cat > "${BUILD_DIR}/BUILD_INFO" << EOF
Package: ${PACKAGE_NAME}
Version: ${VERSION}
Build Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
Build Host: $(hostname)
Build OS: $(uname -s) $(uname -r)
Target: FreeBSD 14.x/15.x (amd64)
EOF
# Add git info if available
if command -v git >/dev/null 2>&1 && [ -d "${PROJECT_ROOT}/.git" ]; then
echo "Git Commit: $(git -C "${PROJECT_ROOT}" rev-parse HEAD 2>/dev/null || echo 'unknown')" >> "${BUILD_DIR}/BUILD_INFO"
echo "Git Branch: $(git -C "${PROJECT_ROOT}" rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'unknown')" >> "${BUILD_DIR}/BUILD_INFO"
fi
# Create installation wrapper
log_info "Creating install wrapper..."
cat > "${BUILD_DIR}/install.sh" << 'EOF'
#!/bin/sh
# Dispatcharr FreeBSD Installation Wrapper
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "=========================================="
echo "Dispatcharr FreeBSD Installation"
echo "=========================================="
cat "${SCRIPT_DIR}/BUILD_INFO"
echo "=========================================="
echo ""
if [ "$(id -u)" -ne 0 ]; then
echo "Error: This script must be run as root"
exit 1
fi
if [ "$(uname -s)" != "FreeBSD" ]; then
echo "Error: This package is for FreeBSD only"
exit 1
fi
exec "${SCRIPT_DIR}/freebsd_start.sh" "$@"
EOF
chmod +x "${BUILD_DIR}/install.sh"
chmod +x "${BUILD_DIR}/freebsd_start.sh"
# Create tarball
log_info "Creating tarball..."
cd "${OUTPUT_DIR}"
tar -czvf "${PACKAGE_NAME}.tar.gz" "${PACKAGE_NAME}" 2>&1 | tail -3
# Generate checksums
log_info "Generating checksums..."
${SHA256_CMD} "${PACKAGE_NAME}.tar.gz" > "${PACKAGE_NAME}.tar.gz.sha256"
if [ -n "$MD5_CMD" ]; then
${MD5_CMD} "${PACKAGE_NAME}.tar.gz" > "${PACKAGE_NAME}.tar.gz.md5"
fi
log_success "Package created successfully"
}
# Print summary
print_summary() {
echo ""
echo -e "${BOLD}══════════════════════════════════════════════════════════════${NC}"
echo -e "${BOLD} BUILD COMPLETE ${NC}"
echo -e "${BOLD}══════════════════════════════════════════════════════════════${NC}"
echo ""
echo " Package: ${PACKAGE_NAME}.tar.gz"
echo " Version: ${VERSION}"
echo " Location: ${OUTPUT_DIR}/"
echo ""
echo " Files created:"
ls -lh "${OUTPUT_DIR}/${PACKAGE_NAME}.tar.gz"*
echo ""
echo " Package size: $(du -sh "${OUTPUT_DIR}/${PACKAGE_NAME}.tar.gz" | cut -f1)"
echo ""
echo -e "${BOLD}══════════════════════════════════════════════════════════════${NC}"
echo ""
echo "To install on FreeBSD:"
echo ""
echo " tar -xzf ${PACKAGE_NAME}.tar.gz"
echo " cd ${PACKAGE_NAME}"
echo " sudo ./install.sh"
echo ""
}
# Main execution
main() {
echo ""
echo -e "${BOLD}Dispatcharr FreeBSD Build Script${NC}"
echo ""
parse_args "$@"
cd "${PROJECT_ROOT}"
check_dependencies
determine_version
build_frontend
validate_python
run_tests
create_package
print_summary
}
main "$@"