Rustelo/scripts/install.ps1
Jesús Pérex 095fd89ff7
Some checks failed
CI/CD Pipeline / Test Suite (push) Has been cancelled
CI/CD Pipeline / Security Audit (push) Has been cancelled
CI/CD Pipeline / Build Docker Image (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Performance Benchmarks (push) Has been cancelled
CI/CD Pipeline / Cleanup (push) Has been cancelled
chore: add scripts
2025-07-07 23:53:50 +01:00

735 lines
24 KiB
PowerShell

# Rustelo Unified Installer for Windows
# PowerShell script for installing and setting up Rustelo projects
# Supports development, production, and custom installations
param(
[string]$Mode = "dev",
[string]$ProjectName = "my-rustelo-app",
[string]$Environment = "dev",
[string]$InstallDir = "",
[switch]$EnableTLS,
[switch]$EnableOAuth,
[switch]$DisableAuth,
[switch]$DisableContentDB,
[switch]$SkipDeps,
[switch]$Force,
[switch]$Quiet,
[switch]$Help
)
# Set error action preference
$ErrorActionPreference = "Stop"
# Colors for output
$Colors = @{
Red = "Red"
Green = "Green"
Yellow = "Yellow"
Blue = "Blue"
Purple = "Magenta"
Cyan = "Cyan"
White = "White"
}
# Configuration
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$ProjectRoot = $ScriptDir
$TemplateDir = Join-Path $ProjectRoot "template"
$InstallLog = Join-Path $ProjectRoot "install.log"
# Installation options (can be overridden by environment variables)
$INSTALL_MODE = if ($env:INSTALL_MODE) { $env:INSTALL_MODE } else { $Mode }
$PROJECT_NAME = if ($env:PROJECT_NAME) { $env:PROJECT_NAME } else { $ProjectName }
$ENVIRONMENT = if ($env:ENVIRONMENT) { $env:ENVIRONMENT } else { $Environment }
$ENABLE_AUTH = if ($env:ENABLE_AUTH) { $env:ENABLE_AUTH -eq "true" } else { -not $DisableAuth }
$ENABLE_CONTENT_DB = if ($env:ENABLE_CONTENT_DB) { $env:ENABLE_CONTENT_DB -eq "true" } else { -not $DisableContentDB }
$ENABLE_TLS = if ($env:ENABLE_TLS) { $env:ENABLE_TLS -eq "true" } else { $EnableTLS }
$ENABLE_OAUTH = if ($env:ENABLE_OAUTH) { $env:ENABLE_OAUTH -eq "true" } else { $EnableOAuth }
$SKIP_DEPS = if ($env:SKIP_DEPS) { $env:SKIP_DEPS -eq "true" } else { $SkipDeps }
$FORCE_REINSTALL = if ($env:FORCE_REINSTALL) { $env:FORCE_REINSTALL -eq "true" } else { $Force }
$QUIET = if ($env:QUIET) { $env:QUIET -eq "true" } else { $Quiet }
# Dependency versions
$RUST_MIN_VERSION = "1.75.0"
$NODE_MIN_VERSION = "18.0.0"
# Logging functions
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$timestamp] [$Level] $Message"
switch ($Level) {
"INFO" { Write-Host "[INFO] $Message" -ForegroundColor $Colors.Green }
"WARN" { Write-Host "[WARN] $Message" -ForegroundColor $Colors.Yellow }
"ERROR" { Write-Host "[ERROR] $Message" -ForegroundColor $Colors.Red }
"DEBUG" {
if (-not $QUIET) {
Write-Host "[DEBUG] $Message" -ForegroundColor $Colors.Cyan
}
}
}
Add-Content -Path $InstallLog -Value $logEntry
}
function Write-Header {
param([string]$Message)
Write-Host $Message -ForegroundColor $Colors.Blue
}
function Write-Step {
param([string]$Message)
Write-Host "$Message" -ForegroundColor $Colors.Purple
}
function Write-Success {
param([string]$Message)
Write-Host "$Message" -ForegroundColor $Colors.Green
}
function Write-Banner {
Write-Host ""
Write-Host "╭─────────────────────────────────────────────────────────────╮" -ForegroundColor $Colors.White
Write-Host "│ RUSTELO INSTALLER │" -ForegroundColor $Colors.White
Write-Host "│ │" -ForegroundColor $Colors.White
Write-Host "│ A modern Rust web application framework built with Leptos │" -ForegroundColor $Colors.White
Write-Host "│ │" -ForegroundColor $Colors.White
Write-Host "╰─────────────────────────────────────────────────────────────╯" -ForegroundColor $Colors.White
Write-Host ""
}
# Function to check if a command exists
function Test-Command {
param([string]$Command)
return (Get-Command $Command -ErrorAction SilentlyContinue) -ne $null
}
# Function to compare versions
function Compare-Version {
param([string]$Version1, [string]$Version2)
$v1 = [version]$Version1
$v2 = [version]$Version2
return $v1 -ge $v2
}
# Function to check system requirements
function Test-SystemRequirements {
Write-Step "Checking system requirements..."
$missingTools = @()
if (-not (Test-Command "git")) {
$missingTools += "git"
}
if ($missingTools.Count -gt 0) {
Write-Log "Missing required system tools: $($missingTools -join ', ')" -Level "ERROR"
Write-Host "Please install these tools before continuing."
exit 1
}
Write-Success "System requirements check passed"
}
# Function to install Rust
function Install-Rust {
Write-Step "Checking Rust installation..."
if ((Test-Command "rustc") -and (Test-Command "cargo")) {
$rustVersion = (rustc --version).Split()[1]
Write-Log "Found Rust version: $rustVersion" -Level "DEBUG"
if (Compare-Version $rustVersion $RUST_MIN_VERSION) {
Write-Success "Rust $rustVersion is already installed"
return
} else {
Write-Log "Rust version $rustVersion is too old (minimum: $RUST_MIN_VERSION)" -Level "WARN"
}
}
if ($SKIP_DEPS) {
Write-Log "Skipping Rust installation due to --skip-deps flag" -Level "WARN"
return
}
Write-Log "Installing Rust..."
# Download and install Rust
$rustupUrl = "https://win.rustup.rs/x86_64"
$rustupPath = Join-Path $env:TEMP "rustup-init.exe"
try {
Invoke-WebRequest -Uri $rustupUrl -OutFile $rustupPath
& $rustupPath -y
# Add Cargo to PATH for current session
$env:PATH = "$env:USERPROFILE\.cargo\bin;$env:PATH"
# Verify installation
if ((Test-Command "rustc") -and (Test-Command "cargo")) {
$rustVersion = (rustc --version).Split()[1]
Write-Success "Rust $rustVersion installed successfully"
} else {
Write-Log "Rust installation failed" -Level "ERROR"
exit 1
}
} catch {
Write-Log "Failed to install Rust: $_" -Level "ERROR"
exit 1
} finally {
if (Test-Path $rustupPath) {
Remove-Item $rustupPath -Force
}
}
}
# Function to install Node.js
function Install-NodeJS {
Write-Step "Checking Node.js installation..."
if ((Test-Command "node") -and (Test-Command "npm")) {
$nodeVersion = (node --version).TrimStart('v')
Write-Log "Found Node.js version: $nodeVersion" -Level "DEBUG"
if (Compare-Version $nodeVersion $NODE_MIN_VERSION) {
Write-Success "Node.js $nodeVersion is already installed"
return
} else {
Write-Log "Node.js version $nodeVersion is too old (minimum: $NODE_MIN_VERSION)" -Level "WARN"
}
}
if ($SKIP_DEPS) {
Write-Log "Skipping Node.js installation due to --skip-deps flag" -Level "WARN"
return
}
Write-Log "Node.js installation required"
Write-Host "Please install Node.js manually from https://nodejs.org/"
Write-Host "Then run this script again."
exit 1
}
# Function to install Rust tools
function Install-RustTools {
Write-Step "Installing Rust tools..."
if (Test-Command "cargo-leptos") {
Write-Success "cargo-leptos is already installed"
} else {
Write-Log "Installing cargo-leptos..."
cargo install cargo-leptos
Write-Success "cargo-leptos installed"
}
# Install other useful tools (only in dev mode)
if ($INSTALL_MODE -eq "dev" -or $ENVIRONMENT -eq "dev") {
$tools = @("cargo-watch", "cargo-audit", "cargo-outdated")
foreach ($tool in $tools) {
if (Test-Command $tool) {
Write-Log "$tool is already installed" -Level "DEBUG"
} else {
Write-Log "Installing $tool..."
try {
cargo install $tool
} catch {
Write-Log "Failed to install $tool" -Level "WARN"
}
}
}
}
}
# Function to create project
function New-Project {
Write-Step "Setting up project: $PROJECT_NAME"
# Determine installation directory
if (-not $InstallDir) {
$InstallDir = Join-Path (Get-Location) $PROJECT_NAME
}
# Create project directory
if (Test-Path $InstallDir) {
if ($FORCE_REINSTALL) {
Write-Log "Removing existing project directory: $InstallDir" -Level "WARN"
Remove-Item $InstallDir -Recurse -Force
} else {
Write-Log "Project directory already exists: $InstallDir" -Level "ERROR"
Write-Host "Use --force to overwrite or choose a different name/location"
exit 1
}
}
Write-Log "Creating project directory: $InstallDir"
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
# Copy template files
Write-Log "Copying template files..."
try {
Copy-Item -Path "$TemplateDir\*" -Destination $InstallDir -Recurse -Force
} catch {
Write-Log "Failed to copy template files: $_" -Level "ERROR"
exit 1
}
# Copy additional files
$readmePath = Join-Path $ProjectRoot "README.md"
if (Test-Path $readmePath) {
Copy-Item -Path $readmePath -Destination $InstallDir -Force
}
Write-Success "Project files copied to $InstallDir"
}
# Function to configure project
function Set-ProjectConfiguration {
Write-Step "Configuring project..."
Set-Location $InstallDir
# Create .env file
$envPath = ".env"
if (-not (Test-Path $envPath)) {
Write-Log "Creating .env file..."
$serverHost = if ($ENVIRONMENT -eq "dev") { "127.0.0.1" } else { "0.0.0.0" }
$serverPort = if ($ENVIRONMENT -eq "dev") { "3030" } else { "443" }
$serverProtocol = if ($ENABLE_TLS) { "https" } else { "http" }
$dbUrl = if ($ENVIRONMENT -eq "dev") { "postgresql://dev:dev@localhost:5432/${PROJECT_NAME}_dev" } else { "postgresql://prod:`${DATABASE_PASSWORD}@db.example.com:5432/${PROJECT_NAME}_prod" }
$sessionSecret = if ($ENVIRONMENT -eq "dev") { "dev-secret-not-for-production" } else { -join ((1..32) | ForEach-Object { [char]((65..90) + (97..122) | Get-Random) }) }
$logLevel = if ($ENVIRONMENT -eq "dev") { "debug" } else { "info" }
$envContent = @"
# Environment Configuration
ENVIRONMENT=$ENVIRONMENT
# Server Configuration
SERVER_HOST=$serverHost
SERVER_PORT=$serverPort
SERVER_PROTOCOL=$serverProtocol
# Database Configuration
DATABASE_URL=$dbUrl
# Session Configuration
SESSION_SECRET=$sessionSecret
# Features
ENABLE_AUTH=$ENABLE_AUTH
ENABLE_CONTENT_DB=$ENABLE_CONTENT_DB
ENABLE_TLS=$ENABLE_TLS
ENABLE_OAUTH=$ENABLE_OAUTH
# OAuth Configuration (if enabled)
$(if ($ENABLE_OAUTH) { "GOOGLE_CLIENT_ID=" } else { "# GOOGLE_CLIENT_ID=" })
$(if ($ENABLE_OAUTH) { "GOOGLE_CLIENT_SECRET=" } else { "# GOOGLE_CLIENT_SECRET=" })
$(if ($ENABLE_OAUTH) { "GITHUB_CLIENT_ID=" } else { "# GITHUB_CLIENT_ID=" })
$(if ($ENABLE_OAUTH) { "GITHUB_CLIENT_SECRET=" } else { "# GITHUB_CLIENT_SECRET=" })
# Email Configuration
# SMTP_HOST=
# SMTP_PORT=587
# SMTP_USERNAME=
# SMTP_PASSWORD=
# FROM_EMAIL=
# FROM_NAME=
# Logging
LOG_LEVEL=$logLevel
RUST_LOG=$logLevel
"@
Set-Content -Path $envPath -Value $envContent
Write-Success ".env file created"
} else {
Write-Log ".env file already exists, skipping creation" -Level "WARN"
}
# Update Cargo.toml with project name
$cargoPath = "Cargo.toml"
if (Test-Path $cargoPath) {
$content = Get-Content $cargoPath
$content = $content -replace 'name = "rustelo"', "name = `"$PROJECT_NAME`""
Set-Content -Path $cargoPath -Value $content
Write-Log "Updated project name in Cargo.toml" -Level "DEBUG"
}
# Create necessary directories
$dirs = @("public", "uploads", "logs", "cache", "config", "data")
if ($ENVIRONMENT -eq "prod") {
$dirs += "backups"
}
foreach ($dir in $dirs) {
if (-not (Test-Path $dir)) {
New-Item -ItemType Directory -Path $dir -Force | Out-Null
}
}
if ($ENABLE_TLS) {
if (-not (Test-Path "certs")) {
New-Item -ItemType Directory -Path "certs" -Force | Out-Null
}
Write-Log "Created certs directory for TLS" -Level "DEBUG"
}
Write-Success "Project configured"
}
# Function to install dependencies
function Install-Dependencies {
Write-Step "Installing project dependencies..."
Set-Location $InstallDir
# Install Rust dependencies
Write-Log "Installing Rust dependencies..."
try {
cargo fetch
} catch {
Write-Log "Failed to fetch Rust dependencies: $_" -Level "ERROR"
exit 1
}
# Install Node.js dependencies
if (Test-Path "package.json") {
Write-Log "Installing Node.js dependencies..."
try {
if (Test-Command "pnpm") {
pnpm install
} elseif (Test-Command "npm") {
npm install
} else {
Write-Log "Neither pnpm nor npm found" -Level "ERROR"
exit 1
}
} catch {
Write-Log "Failed to install Node.js dependencies: $_" -Level "ERROR"
exit 1
}
}
Write-Success "Dependencies installed"
}
# Function to build project
function Build-Project {
Write-Step "Building project..."
Set-Location $InstallDir
# Build CSS
Write-Log "Building CSS..."
try {
if (Test-Command "pnpm") {
pnpm run build:css
} elseif (Test-Command "npm") {
npm run build:css
}
} catch {
Write-Log "Failed to build CSS" -Level "WARN"
}
# Build Rust project
Write-Log "Building Rust project..."
try {
if ($ENVIRONMENT -eq "prod") {
cargo build --release
} else {
cargo build
}
} catch {
Write-Log "Failed to build Rust project: $_" -Level "ERROR"
exit 1
}
Write-Success "Project built successfully"
}
# Function to create startup scripts
function New-StartupScripts {
Write-Step "Creating startup scripts..."
Set-Location $InstallDir
# Create development start script
$startScript = @"
@echo off
cd /d "%~dp0"
cargo leptos watch
pause
"@
Set-Content -Path "start.bat" -Value $startScript
# Create production start script
$startProdScript = @"
@echo off
cd /d "%~dp0"
cargo leptos build --release
.\target\release\server.exe
pause
"@
Set-Content -Path "start-prod.bat" -Value $startProdScript
# Create build script
$buildScript = @"
@echo off
cd /d "%~dp0"
cargo leptos build --release
pause
"@
Set-Content -Path "build.bat" -Value $buildScript
# Create PowerShell start script
$startPsScript = @"
# Start development server
Set-Location (Split-Path -Parent `$MyInvocation.MyCommand.Path)
cargo leptos watch
"@
Set-Content -Path "start.ps1" -Value $startPsScript
Write-Success "Startup scripts created"
}
# Function to display final instructions
function Show-Instructions {
Write-Host ""
Write-Header "╭─────────────────────────────────────────────────────────────╮"
Write-Header "│ INSTALLATION COMPLETE │"
Write-Header "╰─────────────────────────────────────────────────────────────╯"
Write-Host ""
Write-Success "Project '$PROJECT_NAME' has been successfully installed!"
Write-Host ""
Write-Host "Installation Details:" -ForegroundColor $Colors.White
Write-Host " Mode: $INSTALL_MODE"
Write-Host " Environment: $ENVIRONMENT"
Write-Host " Location: $InstallDir"
Write-Host " Features:"
Write-Host " - Authentication: $ENABLE_AUTH"
Write-Host " - Content Database: $ENABLE_CONTENT_DB"
Write-Host " - TLS/HTTPS: $ENABLE_TLS"
Write-Host " - OAuth: $ENABLE_OAUTH"
Write-Host ""
Write-Host "Quick Start:" -ForegroundColor $Colors.White
Write-Host "1. cd $InstallDir"
Write-Host "2. .\start.bat (or .\start.ps1)"
Write-Host "3. Open $(if ($ENABLE_TLS) { "https" } else { "http" })://127.0.0.1:3030"
Write-Host ""
Write-Host "Available Commands:" -ForegroundColor $Colors.White
Write-Host " .\start.bat - Start development server"
Write-Host " .\start-prod.bat - Start production server"
Write-Host " .\build.bat - Build for production"
Write-Host " cargo leptos watch - Development with hot reload"
Write-Host " cargo leptos build - Build project"
Write-Host " cargo build - Build Rust code only"
Write-Host " npm run dev - Watch CSS changes"
Write-Host ""
Write-Host "Configuration Files:" -ForegroundColor $Colors.White
Write-Host " .env - Environment variables"
Write-Host " Cargo.toml - Rust dependencies"
Write-Host " package.json - Node.js dependencies"
Write-Host ""
if ($ENABLE_TLS) {
Write-Host "Note: " -ForegroundColor $Colors.Yellow -NoNewline
Write-Host "Self-signed certificates were generated for HTTPS."
Write-Host "Your browser will show a security warning for development."
Write-Host ""
}
if ($ENVIRONMENT -eq "prod") {
Write-Host "Production Checklist:" -ForegroundColor $Colors.Yellow
Write-Host "□ Update SESSION_SECRET in .env"
Write-Host "□ Configure database connection"
Write-Host "□ Set up proper TLS certificates"
Write-Host "□ Review security settings"
Write-Host "□ Configure OAuth providers (if enabled)"
Write-Host ""
}
Write-Success "Happy coding with Rustelo! 🚀"
}
# Function to show usage
function Show-Usage {
Write-Host "Rustelo Unified Installer for Windows"
Write-Host ""
Write-Host "Usage: .\install.ps1 [OPTIONS]"
Write-Host ""
Write-Host "Options:"
Write-Host " -Mode <mode> Installation mode (dev, prod, custom) [default: dev]"
Write-Host " -ProjectName <name> Project name [default: my-rustelo-app]"
Write-Host " -Environment <env> Environment (dev, prod) [default: dev]"
Write-Host " -InstallDir <path> Installation directory [default: .\<project-name>]"
Write-Host " -EnableTLS Enable TLS/HTTPS support"
Write-Host " -EnableOAuth Enable OAuth authentication"
Write-Host " -DisableAuth Disable authentication features"
Write-Host " -DisableContentDB Disable content database features"
Write-Host " -SkipDeps Skip dependency installation"
Write-Host " -Force Force reinstallation (overwrite existing)"
Write-Host " -Quiet Suppress debug output"
Write-Host " -Help Show this help message"
Write-Host ""
Write-Host "Installation Modes:"
Write-Host " dev - Development setup with debugging enabled"
Write-Host " prod - Production setup with optimizations"
Write-Host " custom - Interactive configuration selection"
Write-Host ""
Write-Host "Environment Variables:"
Write-Host " INSTALL_MODE Installation mode (dev/prod/custom)"
Write-Host " PROJECT_NAME Project name"
Write-Host " ENVIRONMENT Environment (dev/prod)"
Write-Host " ENABLE_TLS Enable TLS (true/false)"
Write-Host " ENABLE_AUTH Enable authentication (true/false)"
Write-Host " ENABLE_CONTENT_DB Enable content database (true/false)"
Write-Host " ENABLE_OAUTH Enable OAuth (true/false)"
Write-Host " SKIP_DEPS Skip dependencies (true/false)"
Write-Host " FORCE_REINSTALL Force reinstall (true/false)"
Write-Host " QUIET Quiet mode (true/false)"
Write-Host ""
Write-Host "Examples:"
Write-Host " .\install.ps1 # Quick dev setup"
Write-Host " .\install.ps1 -Mode prod -EnableTLS # Production with HTTPS"
Write-Host " .\install.ps1 -Mode custom # Interactive setup"
Write-Host " `$env:INSTALL_MODE='prod'; .\install.ps1 # Using environment variable"
}
# Function for custom installation
function Invoke-CustomInstall {
Write-Header "Custom Installation Configuration"
Write-Host ""
# Project name
$input = Read-Host "Project name [$PROJECT_NAME]"
if ($input) { $PROJECT_NAME = $input }
# Environment
$input = Read-Host "Environment (dev/prod) [$ENVIRONMENT]"
if ($input) { $ENVIRONMENT = $input }
# Features
$input = Read-Host "Enable authentication? (Y/n)"
$ENABLE_AUTH = -not ($input -match "^[Nn]$")
$input = Read-Host "Enable content database? (Y/n)"
$ENABLE_CONTENT_DB = -not ($input -match "^[Nn]$")
$input = Read-Host "Enable TLS/HTTPS? (y/N)"
$ENABLE_TLS = $input -match "^[Yy]$"
if ($ENABLE_AUTH) {
$input = Read-Host "Enable OAuth authentication? (y/N)"
$ENABLE_OAUTH = $input -match "^[Yy]$"
}
$input = Read-Host "Skip dependency installation? (y/N)"
$SKIP_DEPS = $input -match "^[Yy]$"
Write-Host ""
Write-Host "Configuration Summary:"
Write-Host " Project Name: $PROJECT_NAME"
Write-Host " Environment: $ENVIRONMENT"
Write-Host " Authentication: $ENABLE_AUTH"
Write-Host " Content Database: $ENABLE_CONTENT_DB"
Write-Host " TLS/HTTPS: $ENABLE_TLS"
Write-Host " OAuth: $ENABLE_OAUTH"
Write-Host " Skip Dependencies: $SKIP_DEPS"
Write-Host ""
$input = Read-Host "Proceed with installation? (Y/n)"
if ($input -match "^[Nn]$") {
Write-Host "Installation cancelled."
exit 0
}
}
# Main installation function
function Start-Installation {
Write-Banner
# Initialize log
"Installation started at $(Get-Date)" | Out-File -FilePath $InstallLog -Encoding UTF8
"Mode: $INSTALL_MODE, Environment: $ENVIRONMENT" | Add-Content -Path $InstallLog
# Check if we're in the right directory
if (-not (Test-Path $TemplateDir)) {
Write-Log "Template directory not found: $TemplateDir" -Level "ERROR"
Write-Log "Please run this script from the Rustelo project root" -Level "ERROR"
exit 1
}
# Configure based on mode
switch ($INSTALL_MODE) {
"dev" {
$script:ENVIRONMENT = "dev"
$script:ENABLE_TLS = $ENABLE_TLS
$script:ENABLE_OAUTH = $ENABLE_OAUTH
}
"prod" {
$script:ENVIRONMENT = "prod"
$script:ENABLE_TLS = if ($ENABLE_TLS) { $true } else { $true }
}
"custom" {
Invoke-CustomInstall
}
}
# Run installation steps
Test-SystemRequirements
if (-not $SKIP_DEPS) {
Install-Rust
Install-NodeJS
Install-RustTools
}
New-Project
Set-ProjectConfiguration
Install-Dependencies
Build-Project
New-StartupScripts
# Display final instructions
Show-Instructions
Write-Log "Installation completed successfully at $(Get-Date)"
}
# Main execution
if ($Help) {
Show-Usage
exit 0
}
# Validate parameters
if ($INSTALL_MODE -notin @("dev", "prod", "custom")) {
Write-Log "Invalid installation mode: $INSTALL_MODE" -Level "ERROR"
Write-Host "Valid modes: dev, prod, custom"
exit 1
}
if ($ENVIRONMENT -notin @("dev", "prod")) {
Write-Log "Invalid environment: $ENVIRONMENT" -Level "ERROR"
Write-Host "Valid environments: dev, prod"
exit 1
}
# Run main installation
try {
Start-Installation
} catch {
Write-Log "Installation failed: $_" -Level "ERROR"
exit 1
}