diff --git a/.github/workflows/freebsd-release.yml b/.github/workflows/freebsd-release.yml new file mode 100644 index 00000000..7801420a --- /dev/null +++ b/.github/workflows/freebsd-release.yml @@ -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 diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 00000000..3aa453e3 --- /dev/null +++ b/BUILD.md @@ -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 Version string (default: from version.py) + --output 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 diff --git a/scripts/build-freebsd.sh b/scripts/build-freebsd.sh new file mode 100755 index 00000000..12462b52 --- /dev/null +++ b/scripts/build-freebsd.sh @@ -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 Version string (default: from version.py or 0.0.0-dev) +# --output 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 Version string to embed in package + Default: extracted from version.py or "0.0.0-dev" + + --output 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--freebsd.tar.gz + With checksums: dispatcharr--freebsd.tar.gz.sha256 + dispatcharr--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 "$@"