#!/usr/bin/env nu # Platform distribution preparation tool - prepares platform services for distribution # # Prepares: # - Rust platform binaries (orchestrator, control-center, etc.) # - Container images and deployment configurations # - Service definitions and systemd units # - Platform-specific installation packages # - Cross-compilation for multiple architectures use std log def main [ --source-root: string = "" # Source root directory (auto-detected if empty) --output-dir: string = "dist/platform" # Output directory for platform distribution --target-platforms: string = "linux-amd64,macos-amd64,windows-amd64" # Target platforms --build-mode: string = "release" # Build mode: debug, release, optimized --strip-symbols: bool = true # Strip debug symbols from release builds --upx-compress: bool = false # Compress binaries with UPX --create-containers: bool = false # Create container images --generate-services: bool = true # Generate service definitions --sign-binaries: bool = false # Sign binaries (requires signing keys) --parallel-builds: bool = true # Build platforms in parallel --verbose: bool = false # Enable verbose logging ] -> record { let repo_root = if $source_root == "" { ($env.PWD | path dirname | path dirname | path dirname) } else { ($source_root | path expand) } let target_platforms_list = ($target_platforms | split row "," | each { str trim }) let platform_config = { source_root: $repo_root output_dir: ($output_dir | path expand) target_platforms: $target_platforms_list build_mode: $build_mode strip_symbols: $strip_symbols upx_compress: $upx_compress create_containers: $create_containers generate_services: $generate_services sign_binaries: $sign_binaries parallel_builds: $parallel_builds verbose: $verbose } log info $"Starting platform distribution preparation with config: ($platform_config)" # Ensure output directory exists mkdir ($platform_config.output_dir) let preparation_results = [] try { # Phase 1: Discover platform components let discovery_result = discover_platform_components $platform_config let preparation_results = ($preparation_results | append { phase: "discovery", result: $discovery_result }) if $discovery_result.status != "success" { log error $"Platform component discovery failed: ($discovery_result.reason)" exit 1 } # Phase 2: Build platform binaries let build_result = build_platform_binaries $platform_config $discovery_result let preparation_results = ($preparation_results | append { phase: "build", result: $build_result }) if $build_result.status != "success" and $build_result.status != "partial" { log error $"Platform binary build failed: ($build_result.reason)" exit 1 } # Phase 3: Post-process binaries let processing_result = post_process_binaries $platform_config $build_result let preparation_results = ($preparation_results | append { phase: "processing", result: $processing_result }) # Phase 4: Generate service definitions let services_result = if $platform_config.generate_services { generate_service_definitions $platform_config $build_result } else { { status: "skipped", reason: "service generation disabled" } } let preparation_results = ($preparation_results | append { phase: "services", result: $services_result }) # Phase 5: Create container images let containers_result = if $platform_config.create_containers { create_container_images $platform_config $build_result } else { { status: "skipped", reason: "container creation disabled" } } let preparation_results = ($preparation_results | append { phase: "containers", result: $containers_result }) # Phase 6: Generate platform metadata let metadata_result = generate_platform_metadata $platform_config $preparation_results let preparation_results = ($preparation_results | append { phase: "metadata", result: $metadata_result }) let summary = { source_root: $platform_config.source_root output_directory: $platform_config.output_dir target_platforms: ($platform_config.target_platforms | length) successful_phases: ($preparation_results | where {|r| $r.result.status == "success"} | length) total_phases: ($preparation_results | length) binaries_built: ($build_result.successful_builds) total_size: (get_directory_size $platform_config.output_dir) platform_config: $platform_config phases: $preparation_results } log info $"Platform distribution preparation completed - ($summary.binaries_built) binaries built for ($summary.target_platforms) platforms" return $summary } catch {|err| log error $"Platform distribution preparation failed: ($err.msg)" exit 1 } } # Discover platform components in the source tree def discover_platform_components [platform_config: record] -> record { log info "Discovering platform components..." let start_time = (date now) try { # Define platform project locations let rust_projects = [ { name: "orchestrator" path: ($platform_config.source_root | path join "orchestrator") binary: "provisioning-orchestrator" description: "Main orchestration service" required: true }, { name: "control-center" path: ($platform_config.source_root | path join "control-center") binary: "control-center" description: "Control center API service" required: true }, { name: "control-center-ui" path: ($platform_config.source_root | path join "control-center-ui") binary: "control-center-ui" description: "Control center web UI" required: false }, { name: "mcp-server" path: ($platform_config.source_root | path join "provisioning" "mcp-server-rust") binary: "mcp-server-rust" description: "MCP integration server" required: false } ] # Validate project existence and Cargo.toml files let mut validated_projects = [] let mut missing_projects = [] for project in $rust_projects { let cargo_file = ($project.path | path join "Cargo.toml") if ($project.path | path exists) and ($cargo_file | path exists) { # Parse Cargo.toml to get project information let cargo_data = (open $cargo_file) let project_info = ($project | insert version $cargo_data.package.version | insert cargo_data $cargo_data) $validated_projects = ($validated_projects | append $project_info) } else { if $project.required { $missing_projects = ($missing_projects | append $project.name) } } } if ($missing_projects | length) > 0 { return { status: "failed" reason: $"Missing required projects: ($missing_projects | str join ', ')" duration: ((date now) - $start_time) } } # Check build environment let build_env = validate_build_environment $platform_config { status: "success" rust_projects: $validated_projects missing_projects: $missing_projects total_projects: ($validated_projects | length) build_environment: $build_env duration: ((date now) - $start_time) } } catch {|err| { status: "failed" reason: $err.msg duration: ((date now) - $start_time) } } } # Validate build environment def validate_build_environment [platform_config: record] -> record { let mut build_tools = {} # Check Rust toolchain let rust_version = try { rustc --version | str trim } catch { "not available" } let cargo_version = try { cargo --version | str trim } catch { "not available" } $build_tools = ($build_tools | insert rust $rust_version | insert cargo $cargo_version) # Check for target platforms let mut target_availability = {} for platform in $platform_config.target_platforms { let target_triple = get_rust_target_triple $platform let target_installed = try { rustup target list --installed | lines | any {|line| $line | str contains $target_triple} } catch { false } $target_availability = ($target_availability | insert $platform $target_installed) } # Check optional tools let upx_available = try { upx --version | complete | get exit_code | $in == 0 } catch { false } let strip_available = try { strip --version | complete | get exit_code | $in == 0 } catch { false } let docker_available = try { docker --version | complete | get exit_code | $in == 0 } catch { false } { build_tools: $build_tools target_availability: $target_availability optional_tools: { upx: $upx_available strip: $strip_available docker: $docker_available } rust_ready: ($rust_version != "not available" and $cargo_version != "not available") } } # Build platform binaries for all target platforms def build_platform_binaries [ platform_config: record discovery_result: record ] -> record { log info "Building platform binaries..." let start_time = (date now) try { # Build binaries for each platform let build_results = if $platform_config.parallel_builds { build_platforms_parallel $platform_config $discovery_result } else { build_platforms_sequential $platform_config $discovery_result } let successful_builds = ($build_results | where status == "success" | length) let total_builds = ($build_results | length) let failed_builds = ($build_results | where status == "failed") let status = if $successful_builds == 0 { "failed" } else if $successful_builds < $total_builds { "partial" } else { "success" } { status: $status successful_builds: $successful_builds total_builds: $total_builds failed_builds: ($failed_builds | length) build_results: $build_results duration: ((date now) - $start_time) } } catch {|err| { status: "failed" reason: $err.msg duration: ((date now) - $start_time) } } } # Build platforms in parallel (simplified sequential for now) def build_platforms_parallel [ platform_config: record discovery_result: record ] -> list { build_platforms_sequential $platform_config $discovery_result } # Build platforms sequentially def build_platforms_sequential [ platform_config: record discovery_result: record ] -> list { let mut all_build_results = [] for platform in $platform_config.target_platforms { for project in $discovery_result.rust_projects { let build_result = build_single_binary $platform $project $platform_config $all_build_results = ($all_build_results | append $build_result) } } return $all_build_results } # Build a single binary for a specific platform def build_single_binary [ platform: string project: record platform_config: record ] -> record { log info $"Building ($project.name) for ($platform)..." let start_time = (date now) let target_triple = get_rust_target_triple $platform try { cd $project.path # Build cargo command let mut cargo_cmd = ["cargo", "build"] # Add build mode if $platform_config.build_mode == "release" or $platform_config.build_mode == "optimized" { $cargo_cmd = ($cargo_cmd | append "--release") } # Add target platform $cargo_cmd = ($cargo_cmd | append ["--target", $target_triple]) # Add optimization flags for optimized builds if $platform_config.build_mode == "optimized" { $env.RUSTFLAGS = "-C target-cpu=native -C opt-level=3 -C lto=fat" } # Execute build if $platform_config.verbose { log info $"Running: ($cargo_cmd | str join ' ')" } let build_result = (run-external --redirect-combine $cargo_cmd.0 ...$cargo_cmd.1.. | complete) if $build_result.exit_code == 0 { # Determine binary path let profile = if $platform_config.build_mode == "debug" { "debug" } else { "release" } let binary_path = ($project.path | path join "target" $target_triple $profile $project.binary) # Add .exe extension for Windows let final_binary_path = if ($platform | str contains "windows") { $binary_path + ".exe" } else { $binary_path } if ($final_binary_path | path exists) { # Copy to output directory with platform suffix let output_name = $"($project.binary)-($platform)" let output_path = ($platform_config.output_dir | path join $output_name) cp $final_binary_path $output_path let binary_size = (ls $output_path | get 0.size) { project: $project.name platform: $platform target: $target_triple status: "success" binary_path: $output_path binary_size: $binary_size duration: ((date now) - $start_time) } } else { { project: $project.name platform: $platform target: $target_triple status: "failed" reason: $"binary not found at ($final_binary_path)" duration: ((date now) - $start_time) } } } else { { project: $project.name platform: $platform target: $target_triple status: "failed" reason: $build_result.stderr duration: ((date now) - $start_time) } } } catch {|err| { project: $project.name platform: $platform target: $target_triple status: "failed" reason: $err.msg duration: ((date now) - $start_time) } } } # Get Rust target triple for platform def get_rust_target_triple [platform: string] -> string { match $platform { "linux-amd64" => "x86_64-unknown-linux-gnu" "linux-arm64" => "aarch64-unknown-linux-gnu" "macos-amd64" => "x86_64-apple-darwin" "macos-arm64" => "aarch64-apple-darwin" "windows-amd64" => "x86_64-pc-windows-gnu" "windows-arm64" => "aarch64-pc-windows-msvc" _ => $platform # Assume it's already a target triple } } # Post-process binaries (strip, compress, sign) def post_process_binaries [ platform_config: record build_result: record ] -> record { log info "Post-processing binaries..." let start_time = (date now) try { let successful_builds = ($build_result.build_results | where status == "success") let mut processing_results = [] for build in $successful_builds { let processing_result = process_single_binary $build $platform_config $processing_results = ($processing_results | append $processing_result) } let successful_processing = ($processing_results | where status == "success" | length) let total_processing = ($processing_results | length) { status: (if $successful_processing == $total_processing { "success" } else { "partial" }) processed_binaries: $successful_processing total_binaries: $total_processing processing_results: $processing_results duration: ((date now) - $start_time) } } catch {|err| { status: "failed" reason: $err.msg duration: ((date now) - $start_time) } } } # Process a single binary def process_single_binary [ build: record platform_config: record ] -> record { let mut processing_steps = [] let binary_path = $build.binary_path try { let original_size = $build.binary_size # Strip debug symbols if requested if $platform_config.strip_symbols and not ($build.platform | str contains "windows") { let strip_result = strip_binary_symbols $binary_path $platform_config $processing_steps = ($processing_steps | append { step: "strip", result: $strip_result }) } # UPX compress if requested let mut final_size = $original_size if $platform_config.upx_compress { let upx_result = upx_compress_binary $binary_path $platform_config $processing_steps = ($processing_steps | append { step: "upx", result: $upx_result }) if $upx_result.status == "success" { $final_size = $upx_result.compressed_size } } # Sign binary if requested if $platform_config.sign_binaries { let sign_result = sign_binary $binary_path $platform_config $processing_steps = ($processing_steps | append { step: "sign", result: $sign_result }) } # Update final size let current_size = (ls $binary_path | get 0.size) let compression_ratio = (($current_size | into float) / ($original_size | into float) * 100) { project: $build.project platform: $build.platform binary_path: $binary_path status: "success" original_size: $original_size final_size: $current_size compression_ratio: $compression_ratio processing_steps: $processing_steps } } catch {|err| { project: $build.project platform: $build.platform binary_path: $binary_path status: "failed" reason: $err.msg processing_steps: $processing_steps } } } # Strip debug symbols from binary def strip_binary_symbols [binary_path: string, platform_config: record] -> record { try { if $platform_config.verbose { log info $"Stripping symbols from: ($binary_path)" } let strip_result = (strip $binary_path | complete) if $strip_result.exit_code == 0 { { status: "success", stripped: true } } else { { status: "failed", reason: $strip_result.stderr } } } catch {|err| { status: "failed", reason: $err.msg } } } # Compress binary with UPX def upx_compress_binary [binary_path: string, platform_config: record] -> record { try { if $platform_config.verbose { log info $"UPX compressing: ($binary_path)" } let original_size = (ls $binary_path | get 0.size) let upx_result = (upx --best $binary_path | complete) if $upx_result.exit_code == 0 { let compressed_size = (ls $binary_path | get 0.size) let ratio = (($compressed_size | into float) / ($original_size | into float) * 100) { status: "success" compressed: true original_size: $original_size compressed_size: $compressed_size compression_ratio: $ratio } } else { { status: "failed", reason: $upx_result.stderr } } } catch {|err| { status: "failed", reason: $err.msg } } } # Sign binary (placeholder - would need actual signing implementation) def sign_binary [binary_path: string, platform_config: record] -> record { log warning "Binary signing not implemented - skipping" { status: "skipped", reason: "signing not implemented" } } # Generate service definitions def generate_service_definitions [ platform_config: record build_result: record ] -> record { log info "Generating service definitions..." let start_time = (date now) try { let services_dir = ($platform_config.output_dir | path join "services") mkdir $services_dir let successful_builds = ($build_result.build_results | where status == "success") let mut generated_services = [] # Generate systemd service files for build in $successful_builds { if $build.platform | str starts-with "linux" { let systemd_service = generate_systemd_service $build $platform_config let service_file = ($services_dir | path join $"($build.project).service") $systemd_service | save $service_file $generated_services = ($generated_services | append { project: $build.project platform: $build.platform service_type: "systemd" service_file: $service_file }) } } # Generate Docker Compose definitions let docker_compose = generate_docker_compose $successful_builds $platform_config let compose_file = ($services_dir | path join "docker-compose.yml") $docker_compose | save $compose_file { status: "success" services_generated: ($generated_services | length) services_directory: $services_dir generated_services: $generated_services docker_compose: $compose_file duration: ((date now) - $start_time) } } catch {|err| { status: "failed" reason: $err.msg duration: ((date now) - $start_time) } } } # Generate systemd service file def generate_systemd_service [build: record, platform_config: record] -> string { $"[Unit] Description=Provisioning ($build.project | str title-case) Service After=network.target Wants=network.target [Service] Type=simple User=provisioning Group=provisioning ExecStart=/usr/local/bin/($build.project | str replace "_" "-") Restart=always RestartSec=5 StandardOutput=journal StandardError=journal SyslogIdentifier=provisioning-($build.project) # Security settings NoNewPrivileges=true PrivateTmp=true ProtectSystem=strict ProtectHome=true ReadWritePaths=/var/lib/provisioning /var/log/provisioning [Install] WantedBy=multi-user.target " } # Generate Docker Compose file def generate_docker_compose [builds: list, platform_config: record] -> string { let mut services = [] for build in $builds { let service_def = $" ($build.project | str replace "_" "-"): image: provisioning/($build.project):latest restart: unless-stopped ports: - \"8080:8080\" # Adjust port as needed volumes: - provisioning-data:/data - ./config:/etc/provisioning:ro environment: - PROVISIONING_ENV=production healthcheck: test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:8080/health\"] interval: 30s timeout: 10s retries: 3" $services = ($services | append $service_def) } let compose_content = $"version: '3.8' services: ($services | str join "\n\n") volumes: provisioning-data: driver: local networks: default: name: provisioning " return $compose_content } # Create container images def create_container_images [ platform_config: record build_result: record ] -> record { log info "Creating container images..." let start_time = (date now) # Container creation would use the build-containers.nu tool let containers_result = try { nu ($platform_config.source_root | path join "src" "tools" "package" "build-containers.nu") --dist-dir ($platform_config.output_dir | path dirname) --tag-prefix "provisioning" --platforms "linux/amd64" --verbose:$platform_config.verbose } catch {|err| { status: "failed" reason: $err.msg } } { status: $containers_result.status containers_created: (if "successful_builds" in ($containers_result | columns) { $containers_result.successful_builds } else { 0 }) container_results: $containers_result duration: ((date now) - $start_time) } } # Generate platform metadata def generate_platform_metadata [ platform_config: record preparation_results: list ] -> record { log info "Generating platform metadata..." let start_time = (date now) try { let metadata = { name: "provisioning-platform" version: (detect_version $platform_config.source_root) type: "platform-distribution" created_at: (date now) created_by: "platform-distribution-tool" build_configuration: $platform_config target_platforms: $platform_config.target_platforms preparation_phases: ($preparation_results | each {|r| { phase: $r.phase status: $r.result.status duration: (if "duration" in ($r.result | columns) { $r.result.duration } else { 0 }) } }) build_statistics: { total_phases: ($preparation_results | length) successful_phases: ($preparation_results | where {|r| $r.result.status == "success"} | length) distribution_size: (get_directory_size $platform_config.output_dir) } } let metadata_file = ($platform_config.output_dir | path join "platform-metadata.json") $metadata | to json | save $metadata_file { status: "success" metadata_file: $metadata_file metadata: $metadata duration: ((date now) - $start_time) } } catch {|err| { status: "failed" reason: $err.msg duration: ((date now) - $start_time) } } } # Detect version from git or other sources def detect_version [repo_root: string] -> string { cd $repo_root try { let git_version = (git describe --tags --always --dirty 2>/dev/null | str trim) if $git_version != "" { return ($git_version | str replace "^v" "") } return $"dev-(date now | format date '%Y%m%d')" } catch { return "unknown" } } # Get directory size helper def get_directory_size [dir: string] -> int { if not ($dir | path exists) { return 0 } try { find $dir -type f | each {|file| ls $file | get 0.size } | math sum | if $in == null { 0 } else { $in } } catch { 0 } } # Show platform distribution status def "main status" [] { let repo_root = ($env.PWD | path dirname | path dirname | path dirname) let version = (detect_version $repo_root) # Check for platform components let orchestrator_exists = (($repo_root | path join "orchestrator" "Cargo.toml") | path exists) let control_center_exists = (($repo_root | path join "control-center" "Cargo.toml") | path exists) let build_env = validate_build_environment { target_platforms: ["linux-amd64", "macos-amd64", "windows-amd64"] } { repository: $repo_root version: $version platform_components: { orchestrator: $orchestrator_exists control_center: $control_center_exists } build_environment: $build_env ready_for_build: ($build_env.rust_ready and $orchestrator_exists and $control_center_exists) supported_platforms: ["linux-amd64", "linux-arm64", "macos-amd64", "macos-arm64", "windows-amd64"] } } # Quick platform build for single target def "main quick" [ --platform: string = "linux-amd64" # Single platform to build --output-dir: string = "dist/platform" # Output directory ] { main --target-platforms $platform --output-dir $output_dir --parallel-builds:false }