758 lines
25 KiB
Plaintext
Raw Normal View History

# 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 <public-key> 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 ""
}