#!/usr/bin/env nu # Backup KOGRAL graphs to archive # # Usage: nu kogral-backup.nu [--output ] [--format ] [--compress] def main [ --output: string = "kogral-backup" # Output filename (without extension) --format: string = "tar" # Archive format: tar or zip --compress # Compress the archive --kogral-dir: string = ".kogral" # KOGRAL directory --include-metadata # Include .git and other metadata ] { print $"(ansi green_bold)KOGRAL Backup(ansi reset)" print $"KOGRAL Directory: ($kogral-dir)" print $"Format: ($format)" print $"Compress: ($compress)" # Check if .kogral directory exists if not ($kogral-dir | path exists) { print $"(ansi red)Error: KOGRAL directory not found: ($kogral-dir)(ansi reset)" exit 1 } # Generate timestamp let timestamp = date now | format date "%Y%m%d_%H%M%S" let backup_name = $"($output)_($timestamp)" # Determine file extension let extension = if $compress { if $format == "tar" { ".tar.gz" } else { ".zip" } } else { if $format == "tar" { ".tar" } else { ".zip" } } let backup_file = $"($backup_name)($extension)" print $"\n(ansi cyan_bold)Preparing backup...(ansi reset)" print $"Output file: ($backup_file)" # Count files let stats = get_kogral_stats $kogral_dir print $"\n(ansi cyan_bold)Files to backup:(ansi reset)" print $" Notes: ($stats.notes)" print $" Decisions: ($stats.decisions)" print $" Guidelines: ($stats.guidelines)" print $" Patterns: ($stats.patterns)" print $" Journal: ($stats.journal)" print $" Config: ($stats.config)" print $" Total: ($stats.total)" # Create backup print $"\n(ansi cyan_bold)Creating backup...(ansi reset)" match $format { "tar" => { create_tar_backup $kogral_dir $backup_file $compress $include_metadata }, "zip" => { create_zip_backup $kogral_dir $backup_file $include_metadata }, _ => { print $"(ansi red)Error: Invalid format. Use: tar or zip(ansi reset)" exit 1 } } # Verify backup if ($backup_file | path exists) { let size = ls $backup_file | get size | first print $"\n(ansi green_bold)✓ Backup created successfully(ansi reset)" print $"File: ($backup_file)" print $"Size: ($size)" } else { print $"\n(ansi red)✗ Backup creation failed(ansi reset)" exit 1 } # Generate manifest print $"\n(ansi cyan_bold)Generating manifest...(ansi reset)" let manifest = generate_manifest $kogral_dir $backup_file $stats $timestamp let manifest_file = $"($backup_name).manifest.json" $manifest | to json | save $manifest_file print $"Manifest saved: ($manifest_file)" print $"\n(ansi green_bold)✓ Backup completed(ansi reset)" } def get_kogral_stats [kogral_dir: string] -> record { let notes = (glob $"($kogral_dir)/notes/**/*.md" | length) let decisions = (glob $"($kogral_dir)/decisions/**/*.md" | length) let guidelines = (glob $"($kogral_dir)/guidelines/**/*.md" | length) let patterns = (glob $"($kogral_dir)/patterns/**/*.md" | length) let journal = (glob $"($kogral_dir)/journal/**/*.md" | length) let config = if ($"($kogral_dir)/config.toml" | path exists) { 1 } else { 0 } { notes: $notes, decisions: $decisions, guidelines: $guidelines, patterns: $patterns, journal: $journal, config: $config, total: ($notes + $decisions + $guidelines + $patterns + $journal + $config) } } def create_tar_backup [kogral_dir: string, output: string, compress: bool, include_metadata: bool] { let compress_flag = if $compress { "z" } else { "" } let exclude_flags = if not $include_metadata { ["--exclude=.git", "--exclude=.DS_Store"] } else { [] } print $" Creating tar archive..." # Use tar command let tar_cmd = if $compress { $"tar -c($compress_flag)f ($output) ($exclude_flags | str join ' ') ($kogral_dir)" } else { $"tar -cf ($output) ($exclude_flags | str join ' ') ($kogral_dir)" } try { bash -c $tar_cmd print $" (ansi green)✓ Tar archive created(ansi reset)" } catch { print $" (ansi red)✗ Tar archive creation failed(ansi reset)" } } def create_zip_backup [kogral_dir: string, output: string, include_metadata: bool] { print $" Creating zip archive..." let exclude_pattern = if not $include_metadata { "*.git* */.DS_Store" } else { "" } try { bash -c $"zip -r ($output) ($kogral_dir) -x ($exclude_pattern)" print $" (ansi green)✓ Zip archive created(ansi reset)" } catch { print $" (ansi red)✗ Zip archive creation failed(ansi reset)" } } def generate_manifest [kogral_dir: string, backup_file: string, stats: record, timestamp: string] -> record { let config_path = $"($kogral_dir)/config.toml" let config = if ($config_path | path exists) { open $config_path | from toml } else { { graph: { name: "unknown", version: "unknown" } } } { backup_timestamp: $timestamp, backup_file: $backup_file, kogral_directory: $kogral_dir, graph_name: $config.graph.name, graph_version: $config.graph.version, statistics: $stats, created_by: "kogral-backup.nu", version: "1.0.0" } }