# System Setup Module # Orchestrates complete provisioning system setup and initialization # Follows Nushell guidelines: explicit types, single purpose, no try-catch use ./mod.nu * use ./detection.nu * use ./validation.nu * use ./wizard.nu * # ============================================================================ # SYSTEM CONFIGURATION CREATION # ============================================================================ # Create system configuration file export def create-system-config-file [ config_base: string config_data: record ] { let system_config_path = $"($config_base)/system.toml" let system_config = { version: "1.0.0" install_path: (config_data.install_path? | default "") os_name: (config_data.os_name? | default (detect-os)) os_version: (config_data.os_version? | default "unknown") config_base_path: $config_base cache_base_path: $"($config_base)/cache" workspaces_dir: $"($config_base)/workspaces" system_architecture: (detect-architecture) cpu_count: (config_data.cpu_count? | default (get-cpu-count)) memory_total_gb: (config_data.memory_gb? | default (get-system-memory-gb)) disk_total_gb: (config_data.disk_gb? | default (get-system-disk-gb)) setup_date: (get-timestamp-iso8601) setup_by_user: (get-current-user) setup_hostname: (get-system-hostname) } save-config-toml $system_config_path $system_config } # Create platform services configuration export def create-platform-config-file [ config_base: string config_data: record ] { let platform_config_path = $"($config_base)/platform/deployment.toml" let platform_config = { version: "1.0.0" deployment: { mode: (config_data.deployment_mode? | default "docker-compose") location_type: "local" } docker_compose: { enabled: true path: (config_data.docker_compose_path? | default ".") } kubernetes: { enabled: false namespace: "provisioning" kubeconfig: "" manifests_path: "./k8s" } remote_ssh: { enabled: false host: "" user: "deploy" ssh_key: "~/.ssh/id_rsa" } systemd: { enabled: false service_prefix: "provisioning-" } services: { orchestrator: { endpoint: "http://localhost:9090/health" timeout_seconds: 30 type: "http" } control_center: { endpoint: "http://localhost:3000/health" timeout_seconds: 30 type: "http" } kms_service: { endpoint: "http://localhost:3001/health" timeout_seconds: 30 type: "http" } } platform: { orchestrator: { endpoint: "http://localhost:9090" api_version: "v1" } control_center: { url: "http://localhost:3000" token: "" } kms: { endpoint: "http://localhost:3001" backend: "rustyvault" rustyvault: { enabled: true master_key: "" } } } } save-config-toml $platform_config_path $platform_config } # Create user preferences configuration export def create-user-preferences-file [ config_base: string config_data: record ] { let user_prefs_path = $"($config_base)/user_preferences.toml" let user_prefs = { output_format: (config_data.output_format? | default "yaml") use_colors: (config_data.use_colors? | default true) file_viewer: (config_data.file_viewer? | default "bat") confirm_delete: (config_data.confirm_delete? | default true) confirm_deploy: (config_data.confirm_deploy? | default true) default_log_level: (config_data.default_log_level? | default "info") default_provider: (config_data.default_provider? | default "local") http_use_curl: (config_data.http_use_curl? | default false) http_timeout_seconds: (config_data.http_timeout_seconds? | default 30) terraform_auto_approve: (config_data.terraform_auto_approve? | default false) editor: (config_data.editor? | default "vim") } save-config-toml $user_prefs_path $user_prefs } # ============================================================================ # PROVIDER CONFIGURATION # ============================================================================ # Create provider configuration file export def create-provider-config-file [ config_base: string provider_name: string credentials_source: string = "" ] { let provider_config_path = $"($config_base)/providers/($provider_name).toml" let provider_config = (match $provider_name { "upcloud" => { { api_url: "https://api.upcloud.com/1.3" interface: "API" credentials_source: ($credentials_source | default "rustyvault://system/providers/upcloud") timeout_seconds: 30 } } "aws" => { { region: "us-east-1" credentials_source: ($credentials_source | default "rustyvault://system/providers/aws") timeout_seconds: 30 } } "hetzner" => { { api_url: "https://api.hetzner.cloud/v1" credentials_source: ($credentials_source | default "rustyvault://system/providers/hetzner") timeout_seconds: 30 } } "local" => { { base_path: "/tmp/provisioning-local" timeout_seconds: 10 } } _ => {} }) save-config-toml $provider_config_path $provider_config } # ============================================================================ # RUSTYVAULT BOOTSTRAP SETUP # ============================================================================ # Create RustyVault bootstrap key placeholder export def create-rustyvault-bootstrap-placeholder [ config_base: string ] { let bootstrap_path = $"($config_base)/rustyvault_bootstrap.age" # Create placeholder file with instructions let bootstrap_content = "# RustyVault Bootstrap Key (SOPS-Encrypted)\n# This file contains the encrypted RustyVault master key\n# Generated: (get-timestamp-iso8601)\n#\n# To generate the actual key:\n# age-keygen -o > ~/.age/keys.txt\n# sops encrypt --age rustyvault_bootstrap.age > .sops.yaml\n#\n# This is the ONLY secret stored with SOPS - all other credentials\n# are stored encrypted in RustyVault with encrypt/decrypt at-rest support.\n\n# Placeholder - run: provisioning setup rustyvault-bootstrap\n" let result = (do { $bootstrap_content | save -f $bootstrap_path } | complete) ($result.exit_code == 0) } # ============================================================================ # WORKSPACE REGISTRY # ============================================================================ # Create workspace registry file export def create-workspace-registry [ config_base: string ] { let registry_path = $"($config_base)/workspaces_registry.yaml" let workspace_registry = { version: "1.0.0" active_workspace: "" workspaces: [] created_at: (get-timestamp-iso8601) metadata: { initialized: true } } save-config-yaml $registry_path $workspace_registry } # ============================================================================ # CEDAR POLICIES SETUP # ============================================================================ # Create default Cedar policies directory and files export def setup-cedar-policies [ config_base: string ] { let policies_dir = $"($config_base)/cedar-policies" # Create directory let mkdir_result = (do { mkdir $policies_dir } | complete) if ($mkdir_result.exit_code != 0) { return false } # Create default policies let default_policies = "// Default Cedar Authorization Policies\n// Version: 1.0.0\n// Auto-generated during system setup\n\n// Allow admin full access\npermit(\n principal in Role::\"Admin\",\n action,\n resource\n);\n\n// Allow users workspace-scoped access\npermit(\n principal in Workspace::\".*\",\n action in [\n Action::\"server.create\",\n Action::\"server.read\",\n Action::\"taskserv.install\"\n ],\n resource\n) when {\n context has workspace_id &&\n resource.workspace_id == principal.id\n};\n\n// Require MFA for production operations\nforbid(\n principal,\n action in [\n Action::\"server.delete\",\n Action::\"cluster.delete\",\n Action::\"database.delete\"\n ],\n resource in Environment::\"production\"\n) unless {\n context.mfa_verified == true\n};\n" let policies_file = $"($policies_dir)/default.cedar" let result = (do { $default_policies | save -f $policies_file } | complete) ($result.exit_code == 0) } # ============================================================================ # NICKEL CONFIGURATION GENERATION # ============================================================================ # Get Nickel schema path for config type def get-nickel-schema-path [config_type: string] { match $config_type { "system" => "provisioning/schemas/platform/schemas/system.ncl" "deployment" => "provisioning/schemas/platform/schemas/deployment.ncl" "user_preferences" => "provisioning/schemas/platform/schemas/user_preferences.ncl" "provider" => "provisioning/schemas/platform/schemas/provider.ncl" _ => "" } } # Generate Nickel system configuration from defaults export def create-system-config-nickel [ config_base: string profile: string = "developer" ] { let system_config_path = $"($config_base)/system.ncl" let os_name = (detect-os) let architecture = (detect-architecture) let cpu_count = (get-cpu-count) let memory_gb = (get-system-memory-gb) let disk_gb = (get-system-disk-gb) let system_nickel = $"# System Configuration (Nickel) # Generated: (get-timestamp-iso8601) # Profile: ($profile) let helpers = import \"../../schemas/platform/common/helpers.ncl\" in let system_schema = import \"../../schemas/platform/schemas/system.ncl\" in let defaults = import \"../../schemas/platform/defaults/system-defaults.ncl\" in # Compose: defaults + platform-specific values helpers.compose_config defaults {} { version = \"1.0.0\", config_base_path = \"($config_base)\", os_name = '$os_name, system_architecture = '$architecture, cpu_count = $cpu_count, memory_total_gb = $memory_gb, disk_total_gb = $disk_gb, setup_date = \"(get-timestamp-iso8601)\", setup_by_user = \"(get-current-user)\", setup_hostname = \"(get-system-hostname)\", } | system_schema.SystemConfig " let result = (do { $system_nickel | save -f $system_config_path } | complete) ($result.exit_code == 0) } # Generate Nickel platform deployment configuration from defaults + profile overlay export def create-platform-config-nickel [ config_base: string deployment_mode: string = "docker-compose" profile: string = "developer" ] { let platform_config_path = $"($config_base)/platform/deployment.ncl" let deployment_mode_tag = match $deployment_mode { "docker-compose" => "'docker_compose" "kubernetes" => "'kubernetes" "remote-ssh" | "ssh" => "'remote_ssh" "systemd" => "'systemd" _ => "'docker_compose" } let platform_nickel = $"# Platform Deployment Configuration (Nickel) # Generated: (get-timestamp-iso8601) # Profile: ($profile) # Deployment Mode: ($deployment_mode) let helpers = import \"../../schemas/platform/common/helpers.ncl\" in let deployment_schema = import \"../../schemas/platform/schemas/deployment.ncl\" in let defaults = import \"../../schemas/platform/defaults/deployment-defaults.ncl\" in # Profile-specific overlay let profile_overlay = import \"../../schemas/platform/defaults/deployment/($profile)-defaults.ncl\" in # Compose: defaults + profile overlay + user customization helpers.compose_config defaults profile_overlay { deployment = { mode = $deployment_mode_tag, location_type = 'local, }, services = { orchestrator = { endpoint = \"http://localhost:9090/health\", timeout_seconds = 30, }, control_center = { endpoint = \"http://localhost:3000/health\", timeout_seconds = 30, }, kms_service = { endpoint = \"http://localhost:3001/health\", timeout_seconds = 30, }, }, } | deployment_schema.DeploymentConfig " let result = (do { $platform_nickel | save -f $platform_config_path } | complete) ($result.exit_code == 0) } # Generate Nickel user preferences configuration from defaults export def create-user-preferences-nickel [ config_base: string profile: string = "developer" ] { let user_prefs_path = $"($config_base)/user_preferences.ncl" let user_prefs_nickel = $"# User Preferences Configuration (Nickel) # Generated: (get-timestamp-iso8601) # Profile: ($profile) let helpers = import \"../../schemas/platform/common/helpers.ncl\" in let prefs_schema = import \"../../schemas/platform/schemas/user_preferences.ncl\" in let defaults = import \"../../schemas/platform/defaults/user_preferences-defaults.ncl\" in # Profile-specific overlay (production has stricter defaults) let profile_overlay = if \"($profile)\" == \"production\" then { confirm_delete = true, confirm_deploy = true } else {} in # Compose: defaults + profile overlay helpers.compose_config defaults profile_overlay { output_format = 'yaml, use_colors = true, confirm_delete = true, confirm_deploy = true, default_log_level = 'info, default_provider = \"local\", http_timeout_seconds = 30, editor = \"vim\", } | prefs_schema.UserPreferencesConfig " let result = (do { $user_prefs_nickel | save -f $user_prefs_path } | complete) ($result.exit_code == 0) } # Generate Nickel provider configuration export def create-provider-config-nickel [ config_base: string provider: string ] { let provider_config_path = $"($config_base)/providers/($provider).ncl" let provider_nickel = (match $provider { "upcloud" => { $"# UpCloud Provider Configuration (Nickel) # Generated: (get-timestamp-iso8601) let provider_schema = import \"../../schemas/platform/schemas/provider.ncl\" in { api_url = \"https://api.upcloud.com/1.3\", interface = \"API\", credentials_source = \"rustyvault://system/providers/upcloud\", timeout_seconds = 30, } | provider_schema.ProviderConfig " } "aws" => { $"# AWS Provider Configuration (Nickel) # Generated: (get-timestamp-iso8601) let provider_schema = import \"../../schemas/platform/schemas/provider.ncl\" in { region = \"us-east-1\", credentials_source = \"rustyvault://system/providers/aws\", timeout_seconds = 30, } | provider_schema.ProviderConfig " } "hetzner" => { $"# Hetzner Provider Configuration (Nickel) # Generated: (get-timestamp-iso8601) let provider_schema = import \"../../schemas/platform/schemas/provider.ncl\" in { api_url = \"https://api.hetzner.cloud/v1\", credentials_source = \"rustyvault://system/providers/hetzner\", timeout_seconds = 30, } | provider_schema.ProviderConfig " } "local" => { $"# Local Provider Configuration (Nickel) # Generated: (get-timestamp-iso8601) let provider_schema = import \"../../schemas/platform/schemas/provider.ncl\" in { base_path = \"/tmp/provisioning-local\", timeout_seconds = 10, } | provider_schema.ProviderConfig " } _ => "" }) if ($provider_nickel | is-empty) { return false } let result = (do { $provider_nickel | save -f $provider_config_path } | complete) ($result.exit_code == 0) } # Compose Nickel config from defaults, overlay, and user customizations export def compose-nickel-from-defaults [ config_type: string profile: string = "developer" ] { let schema_path = (get-nickel-schema-path $config_type) if ($schema_path | is-empty) { print-setup-error $"Unknown config type: ($config_type)" return {} } { schema_path: $schema_path profile: $profile defaults_available: true } } # Validate Nickel configuration using nickel typecheck export def validate-nickel-config [ config_path: path ] { if not ($config_path | path exists) { print-setup-warning $"Config file not found: ($config_path)" return false } # Check if nickel command is available let nickel_check = (do { which nickel } | complete) if ($nickel_check.exit_code != 0) { print-setup-warning "Nickel not installed - skipping typecheck validation" return true } # Run nickel typecheck let validation = (do { nickel typecheck $config_path } | complete) if ($validation.exit_code == 0) { return true } else { print-setup-error $"Nickel validation failed for ($config_path)" print-setup-error ($validation.stderr | default "Unknown error") return false } } # Export Nickel config to TOML (optional, for services that require TOML) export def export-nickel-to-toml [ ncl_path: path toml_path: path ] { if not ($ncl_path | path exists) { print-setup-error $"Nickel config not found: ($ncl_path)" return false } # Check if nickel command is available let nickel_check = (do { which nickel } | complete) if ($nickel_check.exit_code != 0) { print-setup-warning "Nickel not installed - cannot export to TOML" return false } # Run nickel export let export_result = (do { nickel export --format toml $ncl_path | save -f $toml_path } | complete) if ($export_result.exit_code == 0) { return true } else { print-setup-error $"Failed to export ($ncl_path) to TOML" return false } } # ============================================================================ # COMPLETE SYSTEM SETUP # ============================================================================ # Execute complete system setup export def setup-system-complete [ setup_config: record --verbose = false ] { print-setup-header "Complete System Setup" print "" let config_base = (get-config-base-path) # Step 1: Ensure directories exist print-setup-info "Creating configuration directories..." if not (ensure-config-dirs) { print-setup-error "Failed to create configuration directories" return { success: false errors: ["Failed to create configuration directories"] } } print-setup-success "Configuration directories created" # Step 2: Create system configuration print-setup-info "Creating system configuration..." if not (create-system-config-file $config_base $setup_config.system_config) { print-setup-error "Failed to create system configuration" return { success: false errors: ["Failed to create system configuration"] } } print-setup-success "System configuration created" # Step 3: Create platform configuration print-setup-info "Creating platform services configuration..." if not (create-platform-config-file $config_base $setup_config) { print-setup-error "Failed to create platform configuration" return { success: false errors: ["Failed to create platform configuration"] } } print-setup-success "Platform services configuration created" # Step 4: Create user preferences print-setup-info "Creating user preferences..." if not (create-user-preferences-file $config_base {}) { print-setup-error "Failed to create user preferences" return { success: false errors: ["Failed to create user preferences"] } } print-setup-success "User preferences created" # Step 5: Setup provider configurations print-setup-info "Setting up provider configurations..." for provider in ($setup_config.providers? | default ["local"]) { if not (create-provider-config-file $config_base $provider) { print-setup-warning $"Failed to create provider config for ($provider)" } } print-setup-success "Provider configurations created" # Step 6: Create RustyVault bootstrap placeholder print-setup-info "Setting up RustyVault bootstrap key..." if not (create-rustyvault-bootstrap-placeholder $config_base) { print-setup-warning "Failed to create RustyVault bootstrap placeholder" } print-setup-success "RustyVault bootstrap key placeholder created" # Step 7: Create workspace registry print-setup-info "Creating workspace registry..." if not (create-workspace-registry $config_base) { print-setup-error "Failed to create workspace registry" return { success: false errors: ["Failed to create workspace registry"] } } print-setup-success "Workspace registry created" # Step 8: Setup Cedar policies print-setup-info "Setting up Cedar authorization policies..." if not (setup-cedar-policies $config_base) { print-setup-warning "Failed to setup Cedar policies" } print-setup-success "Cedar authorization policies configured" # Step 9: Create setup metadata print-setup-info "Finalizing setup..." let setup_metadata = { version: "1.0.0" completed_at: (get-timestamp-iso8601) setup_by: (get-current-user) setup_on_hostname: (get-system-hostname) deployment_mode: ($setup_config.deployment_mode? | default "docker-compose") providers: ($setup_config.providers? | default ["local"]) security: ($setup_config.security? | default {}) } let metadata_path = $"($config_base)/setup_metadata.yaml" let save_result = (save-config-yaml $metadata_path $setup_metadata) if not $save_result { print-setup-warning "Failed to save setup metadata" } print "" print-setup-success "System setup completed successfully!" print "" { success: true config_base: $config_base timestamp: (get-timestamp-iso8601) setup_config: $setup_config } } # Run interactive setup wizard with all steps export def run-interactive-setup [ --verbose = false ] { let wizard_result = (run-setup-wizard --verbose=$verbose) if not $wizard_result.completed { return { success: false reason: "Setup wizard cancelled by user" } } setup-system-complete $wizard_result --verbose=$verbose } # Run setup with defaults (no interaction) export def run-setup-defaults [ --verbose = false ] { let defaults = (run-setup-with-defaults) setup-system-complete $defaults --verbose=$verbose } # Run minimal setup export def run-setup-minimal [ --verbose = false ] { let minimal = (run-minimal-setup) setup-system-complete $minimal --verbose=$verbose } # ============================================================================ # SETUP INFORMATION # ============================================================================ # Print setup status export def print-setup-status [] { let config_base = (get-config-base-path) print "" print "╔═══════════════════════════════════════════════════════════════╗" print "║ PROVISIONING SYSTEM SETUP STATUS ║" print "╚═══════════════════════════════════════════════════════════════╝" print "" print $"Configuration Base: ($config_base)" print "" if ($"($config_base)/system.toml" | path exists) { print "✅ System configuration found" } else { print "❌ System configuration NOT found" } if ($"($config_base)/platform/deployment.toml" | path exists) { print "✅ Platform configuration found" } else { print "❌ Platform configuration NOT found" } if ($"($config_base)/user_preferences.toml" | path exists) { print "✅ User preferences found" } else { print "❌ User preferences NOT found" } if ($"($config_base)/workspaces_registry.yaml" | path exists) { print "✅ Workspace registry found" } else { print "❌ Workspace registry NOT found" } if ($"($config_base)/cedar-policies" | path exists) { print "✅ Cedar policies directory found" } else { print "❌ Cedar policies directory NOT found" } print "" print "═══════════════════════════════════════════════════════════════" print "" }