#!/usr/bin/env nu # Tarball creation tool - creates distribution archives for different platforms # # Creates: # - Source distributions (.tar.gz, .zip) # - Binary distributions by platform # - Complete distributions with all components # - Customized distributions based on feature sets use std log def main [ --dist-dir: string = "dist" # Distribution directory to package --output-dir: string = "packages" # Output directory for archives --format: string = "tar.gz" # Archive format: tar.gz, zip, both --platform: string = "all" # Target platforms: linux, macos, windows, all --variant: string = "complete" # Distribution variant: complete, minimal, custom --version: string = "" # Version string (auto-detected if empty) --compression-level: int = 6 # Compression level 1-9 --exclude: string = "" # Comma-separated patterns to exclude --verbose: bool = false # Enable verbose logging --checksum: bool = true # Generate checksums for archives ] -> record { let repo_root = ($env.PWD | path dirname | path dirname | path dirname) let dist_root = ($dist_dir | path expand) let output_root = ($output_dir | path expand) # Detect version if not provided let detected_version = if $version == "" { detect_version $repo_root } else { $version } let package_config = { dist_dir: $dist_root output_dir: $output_root formats: ($format | if $in == "both" { ["tar.gz", "zip"] } else { [$format] }) platforms: (if $platform == "all" { ["linux", "macos", "windows"] } else { [$platform] }) variant: $variant version: $detected_version compression_level: $compression_level exclude_patterns: ($exclude | if $in == "" { [] } else { $in | split row "," | each { str trim } }) verbose: $verbose checksum: $checksum } log info $"Starting tarball creation with config: ($package_config)" # Validate distribution directory if not ($dist_root | path exists) { log error $"Distribution directory does not exist: ($dist_root)" exit 1 } # Ensure output directory exists mkdir $output_root let package_results = [] # Create packages for each platform and format combination for platform in $package_config.platforms { for format in $package_config.formats { let package_result = create_platform_package $platform $format $package_config $repo_root let package_results = ($package_results | append $package_result) } } # Generate checksums if requested let checksum_result = if $package_config.checksum { generate_checksums $package_results $package_config } else { { status: "skipped", checksums: [] } } # Create package manifest create_package_manifest $package_results $checksum_result $package_config $repo_root let summary = { total_packages: ($package_results | length) successful_packages: ($package_results | where status == "success" | length) failed_packages: ($package_results | where status == "failed" | length) total_size: ($package_results | where status == "success" | get archive_size | math sum) checksum_result: $checksum_result package_config: $package_config results: $package_results } if $summary.failed_packages > 0 { log error $"Package creation completed with ($summary.failed_packages) failures" exit 1 } else { log info $"Package creation completed successfully - ($summary.successful_packages) packages created" } return $summary } # Detect version from git or other sources def detect_version [repo_root: string] -> string { try { cd $repo_root # Try git describe first let git_version = (git describe --tags --always --dirty 2>/dev/null | complete) if $git_version.exit_code == 0 and ($git_version.stdout | str trim) != "" { return ($git_version.stdout | str trim) } # Try git rev-parse for short hash let git_hash = (git rev-parse --short HEAD 2>/dev/null | complete) if $git_hash.exit_code == 0 { return $"dev-($git_hash.stdout | str trim)" } # Fallback to date-based version return $"dev-(date now | format date "%Y%m%d")" } catch { return "dev-unknown" } } # Create package for a specific platform and format def create_platform_package [ platform: string format: string package_config: record repo_root: string ] -> record { log info $"Creating ($format) package for ($platform)..." let start_time = (date now) let package_name = $"provisioning-($package_config.version)-($platform)-($package_config.variant)" try { # Prepare package directory let package_dir = prepare_package_directory $platform $package_config $package_name # Create archive let archive_result = match $format { "tar.gz" => { create_tar_archive $package_dir $package_config $package_name } "zip" => { create_zip_archive $package_dir $package_config $package_name } _ => { log error $"Unsupported format: ($format)" { status: "failed", reason: $"unsupported format: ($format)" } } } if $archive_result.status == "success" { log info $"Successfully created ($format) package for ($platform): ($archive_result.archive_path)" { platform: $platform format: $format package_name: $package_name status: "success" archive_path: $archive_result.archive_path archive_size: $archive_result.archive_size original_size: $archive_result.original_size compression_ratio: $archive_result.compression_ratio duration: ((date now) - $start_time) } } else { { platform: $platform format: $format package_name: $package_name status: "failed" reason: $archive_result.reason duration: ((date now) - $start_time) } } } catch {|err| log error $"Failed to create package for ($platform) ($format): ($err.msg)" { platform: $platform format: $format package_name: $package_name status: "failed" reason: $err.msg duration: ((date now) - $start_time) } } } # Prepare package directory structure def prepare_package_directory [ platform: string package_config: record package_name: string ] -> string { let temp_package_dir = ($package_config.output_dir | path join "tmp" $package_name) # Clean and create package directory if ($temp_package_dir | path exists) { rm -rf $temp_package_dir } mkdir $temp_package_dir # Copy distribution files based on variant and platform match $package_config.variant { "complete" => { copy_complete_distribution $package_config.dist_dir $temp_package_dir $platform $package_config } "minimal" => { copy_minimal_distribution $package_config.dist_dir $temp_package_dir $platform $package_config } "custom" => { copy_custom_distribution $package_config.dist_dir $temp_package_dir $platform $package_config } _ => { log error $"Unknown variant: ($package_config.variant)" exit 1 } } return $temp_package_dir } # Copy complete distribution def copy_complete_distribution [ dist_dir: string package_dir: string platform: string package_config: record ] { # Copy all distribution components let components = ["platform", "core", "config", "kcl", "extensions", "templates", "docs"] for component in $components { let source_path = ($dist_dir | path join $component) let target_path = ($package_dir | path join $component) if ($source_path | path exists) { copy_filtered $source_path $target_path $package_config.exclude_patterns } } # Filter platform-specific binaries filter_platform_binaries $package_dir $platform # Add installation scripts add_installation_scripts $package_dir $platform $package_config } # Copy minimal distribution def copy_minimal_distribution [ dist_dir: string package_dir: string platform: string package_config: record ] { # Copy only essential components let essential_components = ["platform", "core", "config"] for component in $essential_components { let source_path = ($dist_dir | path join $component) let target_path = ($package_dir | path join $component) if ($source_path | path exists) { copy_filtered $source_path $target_path $package_config.exclude_patterns } } # Filter platform-specific binaries filter_platform_binaries $package_dir $platform # Add minimal installation scripts add_minimal_installation_scripts $package_dir $platform $package_config } # Copy custom distribution def copy_custom_distribution [ dist_dir: string package_dir: string platform: string package_config: record ] { # Custom distribution would be configurable - for now, same as complete copy_complete_distribution $dist_dir $package_dir $platform $package_config } # Copy with exclusion filtering def copy_filtered [source: string, target: string, exclude_patterns: list] { if ($source | path type) == "file" { if not (should_exclude $source $exclude_patterns) { mkdir ($target | path dirname) cp $source $target } } else { mkdir $target let items = (ls $source | where type == file) for item in $items { if not (should_exclude $item.name $exclude_patterns) { cp $item.name ($target | path join ($item.name | path basename)) } } let dirs = (ls $source | where type == dir) for dir in $dirs { if not (should_exclude $dir.name $exclude_patterns) { copy_filtered $dir.name ($target | path join ($dir.name | path basename)) $exclude_patterns } } } } # Filter platform-specific binaries def filter_platform_binaries [package_dir: string, platform: string] { let platform_dir = ($package_dir | path join "platform") if not ($platform_dir | path exists) { return } # Get all binaries let binaries = (ls $platform_dir | where type == file | get name) # Filter based on platform for binary in $binaries { let binary_name = ($binary | path basename) let should_keep = match $platform { "linux" => { $binary_name =~ "linux" or not ($binary_name =~ "(macos|windows|darwin|win|exe)") } "macos" => { $binary_name =~ "(macos|darwin)" or not ($binary_name =~ "(linux|windows|win|exe)") } "windows" => { $binary_name =~ "(windows|win|exe)" or not ($binary_name =~ "(linux|macos|darwin)") } _ => true } if not $should_keep { rm $binary } } } # Add installation scripts def add_installation_scripts [package_dir: string, platform: string, package_config: record] { let install_script = match $platform { "linux" => { create_linux_install_script $package_config } "macos" => { create_macos_install_script $package_config } "windows" => { create_windows_install_script $package_config } _ => { create_generic_install_script $package_config } } let script_name = match $platform { "windows" => "install.bat" _ => "install.sh" } $install_script | save ($package_dir | path join $script_name) # Make executable on Unix-like systems if $platform in ["linux", "macos"] { chmod +x ($package_dir | path join $script_name) } # Add README create_package_readme $package_config | save ($package_dir | path join "README.md") } # Add minimal installation scripts def add_minimal_installation_scripts [package_dir: string, platform: string, package_config: record] { # Create simple installation instructions let instructions = match $platform { "linux" | "macos" => $"#!/bin/bash # Provisioning System Installation # Version: ($package_config.version) echo \"Installing provisioning system...\" # Copy binaries to /usr/local/bin sudo cp platform/* /usr/local/bin/ sudo chmod +x /usr/local/bin/provisioning-orchestrator # Copy core libraries sudo mkdir -p /usr/local/lib/provisioning sudo cp -r core/* /usr/local/lib/provisioning/ # Copy configuration sudo mkdir -p /etc/provisioning sudo cp config/* /etc/provisioning/ echo \"Installation complete!\" echo \"Run 'provisioning help' to get started.\" " "windows" => $"@echo off REM Provisioning System Installation REM Version: ($package_config.version) echo Installing provisioning system... REM Create directories mkdir \"C:\\Program Files\\Provisioning\" 2>NUL mkdir \"C:\\Program Files\\Provisioning\\bin\" 2>NUL mkdir \"C:\\Program Files\\Provisioning\\lib\" 2>NUL mkdir \"C:\\ProgramData\\Provisioning\" 2>NUL REM Copy files xcopy platform\\* \"C:\\Program Files\\Provisioning\\bin\\\" /Y /Q xcopy core\\* \"C:\\Program Files\\Provisioning\\lib\\\" /Y /Q /S xcopy config\\* \"C:\\ProgramData\\Provisioning\\\" /Y /Q echo Installation complete! echo Add C:\\Program Files\\Provisioning\\bin to your PATH echo Run 'provisioning-orchestrator --help' to get started. " _ => "# Installation instructions not available for this platform" } let script_name = match $platform { "windows" => "install.bat" _ => "install.sh" } $instructions | save ($package_dir | path join $script_name) } # Create tar.gz archive def create_tar_archive [ package_dir: string package_config: record package_name: string ] -> record { let archive_name = $"($package_name).tar.gz" let archive_path = ($package_config.output_dir | path join $archive_name) let package_parent = ($package_dir | path dirname) let package_basename = ($package_dir | path basename) try { cd $package_parent # Create tar with specified compression level let compression_flag = $"--gzip --compression-level=($package_config.compression_level)" tar --create --file $archive_path $compression_flag $package_basename # Clean up temporary directory rm -rf $package_dir # Get size information let archive_size = (ls $archive_path | get 0.size) let original_size = get_directory_size $package_dir let compression_ratio = if $original_size > 0 { (($archive_size | into float) / ($original_size | into float) * 100) } else { 0.0 } { status: "success" archive_path: $archive_path archive_size: $archive_size original_size: $original_size compression_ratio: $compression_ratio } } catch {|err| { status: "failed" reason: $err.msg } } } # Create zip archive def create_zip_archive [ package_dir: string package_config: record package_name: string ] -> record { let archive_name = $"($package_name).zip" let archive_path = ($package_config.output_dir | path join $archive_name) let package_parent = ($package_dir | path dirname) let package_basename = ($package_dir | path basename) try { cd $package_parent # Create zip archive zip -r -$package_config.compression_level $archive_path $package_basename # Clean up temporary directory rm -rf $package_dir # Get size information let archive_size = (ls $archive_path | get 0.size) let original_size = get_directory_size $package_dir let compression_ratio = if $original_size > 0 { (($archive_size | into float) / ($original_size | into float) * 100) } else { 0.0 } { status: "success" archive_path: $archive_path archive_size: $archive_size original_size: $original_size compression_ratio: $compression_ratio } } catch {|err| { status: "failed" reason: $err.msg } } } # Generate checksums for all packages def generate_checksums [ package_results: list package_config: record ] -> record { log info "Generating checksums..." let successful_packages = ($package_results | where status == "success") let mut checksums = [] for package in $successful_packages { try { let sha256_hash = (shasum -a 256 $package.archive_path | awk '{print $1}') let md5_hash = (md5sum $package.archive_path | awk '{print $1}') $checksums = ($checksums | append { file: ($package.archive_path | path basename) sha256: $sha256_hash md5: $md5_hash size: $package.archive_size }) } catch {|err| log warning $"Failed to generate checksum for ($package.archive_path): ($err.msg)" } } # Save checksums to file let checksum_file = ($package_config.output_dir | path join "checksums.txt") let checksum_content = ($checksums | each {|cs| $"($cs.sha256) ($cs.file)\n($cs.md5) ($cs.file)" } | str join "\n") $checksum_content | save $checksum_file log info $"Generated checksums for ($checksums | length) packages: ($checksum_file)" { status: "success" checksum_file: $checksum_file checksums: $checksums } } # Create package manifest def create_package_manifest [ package_results: list checksum_result: record package_config: record repo_root: string ] { let manifest = { version: $package_config.version created_at: (date now | format date "%Y-%m-%d %H:%M:%S") created_by: "provisioning-package-system" source_commit: (try { cd $repo_root; git rev-parse HEAD } catch { "unknown" }) source_branch: (try { cd $repo_root; git branch --show-current } catch { "unknown" }) package_config: $package_config packages: $package_results checksums: $checksum_result.checksums total_packages: ($package_results | where status == "success" | length) total_size: ($package_results | where status == "success" | get archive_size | math sum) } let manifest_file = ($package_config.output_dir | path join "manifest.json") $manifest | to json | save $manifest_file log info $"Created package manifest: ($manifest_file)" } # Utility functions def should_exclude [path: string, patterns: list] -> bool { if ($patterns | length) == 0 { return false } return ($patterns | any {|pattern| $path =~ $pattern }) } 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 } } # Create installation script templates def create_linux_install_script [config: record] -> string { $"#!/bin/bash # Provisioning System Installation Script # Version: ($config.version) # Platform: Linux set -e INSTALL_DIR=\"/usr/local\" CONFIG_DIR=\"/etc/provisioning\" LIB_DIR=\"$INSTALL_DIR/lib/provisioning\" echo \"Installing Provisioning System ($config.version) for Linux...\" # Check for root privileges if [[ $EUID -ne 0 ]]; then echo \"This script must be run as root (use sudo)\" exit 1 fi # Install binaries echo \"Installing binaries...\" mkdir -p \"$INSTALL_DIR/bin\" cp platform/* \"$INSTALL_DIR/bin/\" chmod +x \"$INSTALL_DIR/bin/\"* # Install libraries echo \"Installing libraries...\" mkdir -p \"$LIB_DIR\" cp -r core/* \"$LIB_DIR/\" # Install configuration echo \"Installing configuration...\" mkdir -p \"$CONFIG_DIR\" cp config/* \"$CONFIG_DIR/\" # Install KCL schemas if [ -d \"kcl\" ]; then echo \"Installing KCL schemas...\" mkdir -p \"$LIB_DIR/kcl\" cp -r kcl/* \"$LIB_DIR/kcl/\" fi echo \"Installation complete!\" echo \"Run 'provisioning help' to get started.\" " } def create_macos_install_script [config: record] -> string { $"#!/bin/bash # Provisioning System Installation Script # Version: ($config.version) # Platform: macOS set -e INSTALL_DIR=\"/usr/local\" CONFIG_DIR=\"/usr/local/etc/provisioning\" LIB_DIR=\"$INSTALL_DIR/lib/provisioning\" echo \"Installing Provisioning System ($config.version) for macOS...\" # Install binaries echo \"Installing binaries...\" mkdir -p \"$INSTALL_DIR/bin\" cp platform/* \"$INSTALL_DIR/bin/\" chmod +x \"$INSTALL_DIR/bin/\"* # Install libraries echo \"Installing libraries...\" mkdir -p \"$LIB_DIR\" cp -r core/* \"$LIB_DIR/\" # Install configuration echo \"Installing configuration...\" mkdir -p \"$CONFIG_DIR\" cp config/* \"$CONFIG_DIR/\" # Install KCL schemas if [ -d \"kcl\" ]; then echo \"Installing KCL schemas...\" mkdir -p \"$LIB_DIR/kcl\" cp -r kcl/* \"$LIB_DIR/kcl/\" fi echo \"Installation complete!\" echo \"Run 'provisioning help' to get started.\" echo \"Note: You may need to add /usr/local/bin to your PATH\" " } def create_windows_install_script [config: record] -> string { $"@echo off REM Provisioning System Installation Script REM Version: ($config.version) REM Platform: Windows echo Installing Provisioning System ($config.version) for Windows... REM Create directories mkdir \"C:\\Program Files\\Provisioning\\bin\" 2>NUL mkdir \"C:\\Program Files\\Provisioning\\lib\" 2>NUL mkdir \"C:\\ProgramData\\Provisioning\" 2>NUL REM Install binaries echo Installing binaries... xcopy platform\\* \"C:\\Program Files\\Provisioning\\bin\\\" /Y /Q REM Install libraries echo Installing libraries... xcopy core\\* \"C:\\Program Files\\Provisioning\\lib\\\" /Y /Q /S REM Install configuration echo Installing configuration... xcopy config\\* \"C:\\ProgramData\\Provisioning\\\" /Y /Q REM Install KCL schemas if exist kcl\\ ( echo Installing KCL schemas... mkdir \"C:\\Program Files\\Provisioning\\lib\\kcl\" 2>NUL xcopy kcl\\* \"C:\\Program Files\\Provisioning\\lib\\kcl\\\" /Y /Q /S ) echo Installation complete! echo Add \"C:\\Program Files\\Provisioning\\bin\" to your PATH echo Run 'provisioning-orchestrator --help' to get started. pause " } def create_generic_install_script [config: record] -> string { $"#!/bin/sh # Generic Installation Instructions # Version: ($config.version) echo \"Manual installation required for this platform\" echo \"Please follow the instructions in README.md\" " } def create_package_readme [config: record] -> string { $"# Provisioning System ($config.version) Cloud-native infrastructure provisioning and management system. ## Installation ### Automated Installation Run the platform-specific installation script: - **Linux/macOS**: `sudo ./install.sh` - **Windows**: Run `install.bat` as Administrator ### Manual Installation 1. Copy platform binaries to your system PATH 2. Copy core libraries to a standard location 3. Copy configuration files to appropriate system directory 4. Set up environment variables if needed ## Getting Started After installation, run: ```bash provisioning help ``` For detailed documentation, visit: https://github.com/your-org/provisioning ## Version Information - **Version**: ($config.version) - **Variant**: ($config.variant) - **Build Date**: (date now | format date "%Y-%m-%d") ## Support For support and issues, please visit the project repository. " } # Show package information def "main info" [packages_dir: string = "packages"] { let packages_root = ($packages_dir | path expand) if not ($packages_root | path exists) { return { error: "packages directory not found", directory: $packages_root } } let manifest_file = ($packages_root | path join "manifest.json") if ($manifest_file | path exists) { open $manifest_file } else { let packages = (ls $packages_root | where name =~ "\.(tar\.gz|zip)$") { directory: $packages_root packages: ($packages | length) total_size: ($packages | get size | math sum) files: ($packages | get name | each { path basename }) } } }