# CI/CD Integration Guide for particle-os particle-os is designed to be a first-class CI/CD tool for OS image building. This guide shows you how to integrate particle-os into any CI/CD system. ## 🚀 Why particle-os is CI/CD Friendly ### **Built-in CI/CD Features:** - ✅ **Non-interactive mode** - No prompts, pure automation - ✅ **JSON output** - Machine-readable build results - ✅ **Quiet mode** - Suppressed non-essential output - ✅ **Proper exit codes** - Standard status codes for CI systems - ✅ **Structured output** - Predictable, parseable results - ✅ **Artifact management** - Organized output files ### **CI/CD Usage Pattern:** ```bash # Basic CI/CD build particle-os build --json --quiet recipes/debian-server.yml # With custom output and cleanup particle-os build --json --quiet --output /artifacts/server.img --clean recipes/debian-server.yml ``` ## 🔧 GitHub Actions Integration ### **Basic OS Image Build Workflow:** ```yaml # .github/workflows/build-os.yml name: Build OS Images on: push: branches: [main] paths: ['recipes/**', 'src/**'] pull_request: branches: [main] jobs: build-images: runs-on: ubuntu-latest strategy: matrix: recipe: [debian-server, debian-desktop, debian-gaming] steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Go uses: actions/setup-go@v4 with: go-version: '1.21' - name: Build particle-os run: | cd bib go build -o particle-os cmd/particle_os/main.go - name: Build OS Image run: | sudo ./bib/particle-os build --json --quiet --clean recipes/${{ matrix.recipe }}.yml - name: Parse build results id: build-results run: | BUILD_OUTPUT=$(sudo ./bib/particle-os build --json --quiet --clean recipes/${{ matrix.recipe }}.yml) echo "build_output<> $GITHUB_OUTPUT echo "$BUILD_OUTPUT" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT # Extract success status SUCCESS=$(echo "$BUILD_OUTPUT" | jq -r '.success') echo "success=$SUCCESS" >> $GITHUB_OUTPUT # Extract image path IMAGE_PATH=$(echo "$BUILD_OUTPUT" | jq -r '.image_path') echo "image_path=$IMAGE_PATH" >> $GITHUB_OUTPUT - name: Upload artifacts if: steps.build-results.outputs.success == 'true' uses: actions/upload-artifact@v3 with: name: ${{ matrix.recipe }}-images path: | ${{ steps.build-results.outputs.image_path }} /tmp/particle-os-build/output/*.raw /tmp/particle-os-build/output/*.qcow2 /tmp/particle-os-build/output/*.vmdk retention-days: 30 - name: Build summary run: | echo "## Build Results for ${{ matrix.recipe }}" >> $GITHUB_STEP_SUMMARY echo "- **Status**: ${{ steps.build-results.outputs.success }}" >> $GITHUB_STEP_SUMMARY echo "- **Image**: ${{ steps.build-results.outputs.image_path }}" >> $GITHUB_STEP_SUMMARY echo "- **Recipe**: ${{ matrix.recipe }}" >> $GITHUB_STEP_SUMMARY ``` ### **Advanced Multi-Stage Workflow:** ```yaml # .github/workflows/advanced-os-build.yml name: Advanced OS Image Pipeline on: push: tags: ['v*'] jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Validate recipes run: | cd bib && go build -o particle-os cmd/particle_os/main.go cd .. for recipe in recipes/*.yml; do echo "Validating $recipe..." ./bib/particle-os validate "$recipe" done build-base: needs: validate runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build base images run: | cd bib && go build -o particle-os cmd/particle_os/main.go cd .. sudo ./bib/particle-os build --json --quiet --clean recipes/debian-minimal.yml build-variants: needs: [validate, build-base] runs-on: ubuntu-latest strategy: matrix: variant: [server, desktop, gaming] steps: - uses: actions/checkout@v4 - name: Build variant run: | cd bib && go build -o particle-os cmd/particle_os/main.go cd .. sudo ./bib/particle-os build --json --quiet --clean recipes/debian-${{ matrix.variant }}.yml test-images: needs: build-variants runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Download artifacts uses: actions/download-artifact@v3 with: pattern: debian-*-images - name: Test image boot run: | # Basic image validation for img in *.qcow2; do echo "Testing $img..." qemu-img info "$img" # Add more validation as needed done release: needs: test-images runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/') steps: - uses: actions/checkout@v4 - name: Download artifacts uses: actions/download-artifact@v3 with: pattern: debian-*-images - name: Create release uses: softprops/action-gh-release@v1 with: files: | debian-*-images/*.qcow2 debian-*-images/*.raw debian-*-images/*.vmdk body: | ## OS Images Built with particle-os This release contains Debian-based OS images built using particle-os. ### Available Images: - **debian-server**: Server-optimized Debian - **debian-desktop**: Desktop Debian with GNOME - **debian-gaming**: Gaming-optimized Debian ### Build Details: - Built with particle-os v${{ github.ref_name }} - Base: Debian Trixie - Formats: raw, qcow2, vmdk ``` ## 🔧 GitLab CI Integration ### **Basic GitLab CI Pipeline:** ```yaml # .gitlab-ci.yml stages: - validate - build - test - deploy variables: WORK_DIR: "/tmp/particle-os-build" validate-recipes: stage: validate script: - cd bib && go build -o particle-os cmd/particle_os/main.go - cd .. - for recipe in recipes/*.yml; do echo "Validating $recipe..." ./bib/particle-os validate "$recipe" done artifacts: paths: - bib/particle-os expire_in: 1 hour build-server: stage: build script: - sudo ./bib/particle-os build --json --quiet --clean recipes/debian-server.yml artifacts: paths: - /tmp/particle-os-build/output/ expire_in: 1 week dependencies: - validate-recipes build-desktop: stage: build script: - sudo ./bib/particle-os build --json --quiet --clean recipes/debian-desktop.yml artifacts: paths: - /tmp/particle-os-build/output/ expire_in: 1 week dependencies: - validate-recipes test-images: stage: test script: - echo "Testing built images..." - for img in /tmp/particle-os-build/output/*.qcow2; do echo "Validating $img..." qemu-img info "$img" done dependencies: - build-server - build-desktop deploy-images: stage: deploy script: - echo "Deploying images to artifact storage..." - # Add your deployment logic here dependencies: - test-images only: - main ``` ## 🔧 Jenkins Integration ### **Jenkins Pipeline Script:** ```groovy // Jenkinsfile pipeline { agent any environment { WORKSPACE_DIR = '/tmp/particle-os-build' RECIPES_DIR = 'recipes' } stages { stage('Setup') { steps { sh 'cd bib && go build -o particle-os cmd/particle_os/main.go' sh 'cd ..' } } stage('Validate Recipes') { steps { script { def recipes = sh( script: "find ${RECIPES_DIR} -name '*.yml' -type f", returnStdout: true ).trim().split('\n') for (recipe in recipes) { echo "Validating ${recipe}..." sh "./bib/particle-os validate ${recipe}" } } } } stage('Build Images') { parallel { stage('Build Server') { steps { sh ''' sudo ./bib/particle-os build --json --quiet --clean recipes/debian-server.yml # Parse JSON output for Jenkins BUILD_OUTPUT=$(sudo ./bib/particle-os build --json --quiet --clean recipes/debian-server.yml) echo "BUILD_OUTPUT=${BUILD_OUTPUT}" > build.env ''' archiveArtifacts artifacts: 'build.env' } } stage('Build Desktop') { steps { sh ''' sudo ./bib/particle-os build --json --quiet --clean recipes/debian-desktop.yml BUILD_OUTPUT=$(sudo ./bib/particle-os build --json --quiet --clean recipes/debian-desktop.yml) echo "BUILD_OUTPUT=${BUILD_OUTPUT}" > build.env ''' archiveArtifacts artifacts: 'build.env' } } } } stage('Test Images') { steps { sh ''' echo "Testing built images..." for img in /tmp/particle-os-build/output/*.qcow2; do echo "Validating $img..." qemu-img info "$img" done ''' } } stage('Archive Artifacts') { steps { archiveArtifacts artifacts: '/tmp/particle-os-build/output/**/*' } } } post { always { cleanWs() } success { echo "All OS images built successfully!" } failure { echo "OS image build failed!" } } } ``` ## 🔧 Generic CI/CD Integration ### **Shell Script for Any CI/CD System:** ```bash #!/bin/bash # build-os-images.sh - Generic CI/CD script for particle-os set -euo pipefail # Configuration RECIPE_DIR="recipes" OUTPUT_DIR="/artifacts" BUILD_LOG="/tmp/build.log" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color log() { echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}" } warn() { echo -e "${YELLOW}[$(date +'%Y-%m %H:%M:%S')] WARNING: $1${NC}" } error() { echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}" exit 1 } # Build particle-os binary build_particle_os() { log "Building particle-os binary..." cd bib if ! go build -o particle-os cmd/particle_os/main.go; then error "Failed to build particle-os binary" fi cd .. log "particle-os binary built successfully" } # Validate all recipes validate_recipes() { log "Validating all recipes..." local failed_recipes=() for recipe in "$RECIPE_DIR"/*.yml; do if [[ -f "$recipe" ]]; then log "Validating $recipe..." if ! ./bib/particle-os validate "$recipe"; then failed_recipes+=("$recipe") fi fi done if [[ ${#failed_recipes[@]} -gt 0 ]]; then error "Recipe validation failed for: ${failed_recipes[*]}" fi log "All recipes validated successfully" } # Build OS image from recipe build_image() { local recipe="$1" local recipe_name=$(basename "$recipe" .yml) log "Building image from recipe: $recipe_name" # Build with CI/CD flags local build_output if ! build_output=$(sudo ./bib/particle-os build --json --quiet --clean "$recipe" 2>&1); then error "Build failed for $recipe_name" fi # Parse JSON output local success local image_path local exit_code success=$(echo "$build_output" | jq -r '.success // false') image_path=$(echo "$build_output" | jq -r '.image_path // ""') exit_code=$(echo "$build_output" | jq -r '.exit_code // 1') if [[ "$success" != "true" ]] || [[ "$exit_code" != "0" ]]; then error "Build failed for $recipe_name (exit code: $exit_code)" fi if [[ -z "$image_path" ]]; then error "No image path returned for $recipe_name" fi log "Build successful for $recipe_name: $image_path" # Copy to artifacts directory if [[ -n "$OUTPUT_DIR" ]]; then mkdir -p "$OUTPUT_DIR/$recipe_name" cp "$image_path" "$OUTPUT_DIR/$recipe_name/" # Copy all output formats local work_dir work_dir=$(echo "$build_output" | jq -r '.work_directory // ""') if [[ -n "$work_dir" ]] && [[ -d "$work_dir/output" ]]; then cp "$work_dir/output"/* "$OUTPUT_DIR/$recipe_name/" 2>/dev/null || true fi log "Artifacts copied to $OUTPUT_DIR/$recipe_name" fi # Output build info for CI/CD system echo "BUILD_SUCCESS_$recipe_name=true" >> "$GITHUB_ENV" 2>/dev/null || true echo "IMAGE_PATH_$recipe_name=$image_path" >> "$GITHUB_ENV" 2>/dev/null || true } # Main build process main() { log "Starting OS image build process..." # Check prerequisites if ! command -v go &> /dev/null; then error "Go is not installed" fi if ! command -v jq &> /dev/null; then error "jq is not installed" fi # Build particle-os build_particle_os # Validate recipes validate_recipes # Build all images local build_count=0 local success_count=0 for recipe in "$RECIPE_DIR"/*.yml; do if [[ -f "$recipe" ]]; then build_count=$((build_count + 1)) if build_image "$recipe"; then success_count=$((success_count + 1)) fi fi done # Summary log "Build process completed: $success_count/$build_count images built successfully" if [[ $success_count -eq $build_count ]]; then log "All images built successfully!" exit 0 else error "Some images failed to build" fi } # Run main function main "$@" ``` ## 🔧 Docker Integration ### **Dockerfile for CI/CD:** ```dockerfile # Dockerfile.ci FROM debian:bookworm-slim # Install dependencies RUN apt-get update && apt-get install -y \ golang-go \ jq \ qemu-utils \ podman \ && rm -rf /var/lib/apt/lists/* # Set working directory WORKDIR /app # Copy source code COPY . . # Build particle-os RUN cd bib && go build -o particle-os cmd/particle_os/main.go # Create recipes directory RUN mkdir -p /recipes # Set entrypoint ENTRYPOINT ["./bib/particle-os"] # Default command CMD ["--help"] ``` ### **Docker Compose for CI/CD:** ```yaml # docker-compose.ci.yml version: '3.8' services: particle-os-builder: build: context: . dockerfile: Dockerfile.ci volumes: - ./recipes:/recipes - ./artifacts:/artifacts - /var/lib/containers:/var/lib/containers environment: - RECIPES_DIR=/recipes - OUTPUT_DIR=/artifacts command: ["build", "--json", "--quiet", "--clean", "/recipes/debian-server.yml"] privileged: true # Required for container operations ``` ## 🔧 Best Practices for CI/CD ### **1. Always Use CI/CD Flags:** ```bash # ✅ Good - CI/CD friendly particle-os build --json --quiet --clean recipes/debian-server.yml # ❌ Bad - Human-friendly only particle-os build recipes/debian-server.yml ``` ### **2. Parse JSON Output:** ```bash # Extract build information BUILD_OUTPUT=$(particle-os build --json --quiet recipes/debian-server.yml) SUCCESS=$(echo "$BUILD_OUTPUT" | jq -r '.success') IMAGE_PATH=$(echo "$BUILD_OUTPUT" | jq -r '.image_path') EXIT_CODE=$(echo "$BUILD_OUTPUT" | jq -r '.exit_code') if [[ "$SUCCESS" == "true" ]] && [[ "$EXIT_CODE" == "0" ]]; then echo "Build successful: $IMAGE_PATH" else echo "Build failed" exit 1 fi ``` ### **3. Handle Artifacts Properly:** ```bash # Create organized artifact structure mkdir -p artifacts/$RECIPE_NAME cp "$IMAGE_PATH" artifacts/$RECIPE_NAME/ cp /tmp/particle-os-build/output/* artifacts/$RECIPE_NAME/ ``` ### **4. Clean Up After Builds:** ```bash # Always use --clean flag in CI/CD particle-os build --json --quiet --clean recipes/debian-server.yml # Or manually clean up sudo rm -rf /tmp/particle-os-build ``` ### **5. Set Proper Exit Codes:** ```bash # Check exit code from particle-os if particle-os build --json --quiet recipes/debian-server.yml; then echo "Build successful" exit 0 else echo "Build failed" exit 1 fi ``` ## 🚀 Next Steps Now that you have CI/CD integration examples, you can: 1. **Choose your CI/CD system** and implement the appropriate example 2. **Customize the workflows** for your specific needs 3. **Add more validation** and testing steps 4. **Implement artifact management** for your deployment pipeline 5. **Add monitoring** and alerting for build failures particle-os is designed to work seamlessly in any CI/CD environment, providing reliable, reproducible OS image builds with machine-readable output perfect for automation.