# SPDX-License-Identifier: LSL-1.0 # Copyright (c) 2026 Markus Maiwald # Stewardship: Self Sovereign Society Foundation # # This file is part of the Nexus Sovereign Core. # See legal/LICENSE_SOVEREIGN.md for license terms. # nimpak/install.nim # Package installation orchestrator with atomic operations import std/[tables, sequtils, strformat] import ./types, dependency, transactions, filesystem, cas type InstallStep* = object package*: PackageId fragment*: Fragment stepNumber*: int totalSteps*: int InstallPlan* = object steps*: seq[InstallStep] transaction*: Transaction rollbackData*: seq[RollbackInfo] InstallProgress* = object currentStep*: int totalSteps*: int currentPackage*: PackageId status*: InstallStatus InstallStatus* = enum Planning, Installing, Completed, Failed, RolledBack InstallError* = object of NimPakError failedPackage*: PackageId failedStep*: int rollbackSuccess*: bool # Public API proc installPackages*(packages: seq[PackageId], fragments: Table[PackageId, Fragment], fsManager: FilesystemManager, casManager: CasManager): Result[void, InstallError] = ## Main installation orchestrator (6.2.1, 6.2.2, 6.2.3, 6.2.4) # Create installation plan let planResult = createInstallPlan(packages, fragments) if planResult.isErr: return err(InstallError( code: DependencyConflict, msg: "Failed to create installation plan: " & planResult.error.msg )) var plan = planResult.get() # Begin atomic transaction plan.transaction = beginTransaction() # Execute installation steps let executeResult = executeInstallPlan(plan, fsManager, casManager) if executeResult.isErr: # Rollback on failure let rollbackResult = rollbackTransaction(plan.transaction) return err(InstallError( code: executeResult.error.code, msg: executeResult.error.msg, failedPackage: executeResult.error.failedPackage, failedStep: executeResult.error.failedStep, rollbackSuccess: rollbackResult.isOk )) # Commit transaction let commitResult = commitTransaction(plan.transaction) if commitResult.isErr: return err(InstallError( code: TransactionFailed, msg: "Failed to commit installation: " & commitResult.error )) ok() proc createInstallPlan(packages: seq[PackageId], fragments: Table[PackageId, Fragment]): Result[InstallPlan, DependencyError] = ## Create ordered installation plan from dependency resolution (6.2.1) var allPackages: seq[PackageId] = @[] var processedPackages = initHashSet[PackageId]() # Resolve dependencies for each requested package for pkg in packages: let resolveResult = resolveDependencies(pkg, fragments) if resolveResult.isErr: return err(resolveResult.error) let installOrder = resolveResult.get() for orderedPkg in installOrder.packages: if orderedPkg notin processedPackages: allPackages.add(orderedPkg) processedPackages.incl(orderedPkg) # Create installation steps var steps: seq[InstallStep] = @[] for i, pkg in allPackages.pairs: if pkg in fragments: steps.add(InstallStep( package: pkg, fragment: fragments[pkg], stepNumber: i + 1, totalSteps: allPackages.len )) ok(InstallPlan( steps: steps, transaction: Transaction(), # Will be initialized later rollbackData: @[] )) proc executeInstallPlan(plan: var InstallPlan, fsManager: FilesystemManager, casManager: CasManager): Result[void, InstallError] = ## Execute installation plan with progress tracking (6.2.3, 6.2.5) for step in plan.steps: echo fmt"Installing {step.package.name} ({step.stepNumber}/{step.totalSteps})" # Install individual package let installResult = installSinglePackage(step, fsManager, casManager, plan.transaction) if installResult.isErr: return err(InstallError( code: installResult.error.code, msg: fmt"Failed to install {step.package.name}: {installResult.error.msg}", failedPackage: step.package, failedStep: step.stepNumber )) ok() proc installSinglePackage(step: InstallStep, fsManager: FilesystemManager, casManager: CasManager, transaction: var Transaction): Result[void, NimPakError] = ## Install a single package with atomic operations let pkg = step.package let fragment = step.fragment # Create package directory structure let programDir = fmt"/Programs/{pkg.name}/{pkg.version}" let createDirOp = Operation( kind: CreateDir, target: programDir, data: %*{"permissions": "755"} ) transaction.addOperation(createDirOp) # Install package files from CAS or source let installResult = installPackageFiles(fragment, programDir, casManager) if installResult.isErr: return installResult # Create symlinks in /System/Index let symlinkResult = createPackageSymlinks(fragment, programDir, fsManager, transaction) if symlinkResult.isErr: return symlinkResult ok() proc installPackageFiles(fragment: Fragment, targetDir: string, casManager: CasManager): Result[void, NimPakError] = ## Install package files from CAS or extract from source # TODO: Implement file extraction from CAS or NPK package # For now, create placeholder implementation echo fmt"Installing files for {fragment.id.name} to {targetDir}" ok() proc createPackageSymlinks(fragment: Fragment, programDir: string, fsManager: FilesystemManager, transaction: var Transaction): Result[void, NimPakError] = ## Create symlinks in /System/Index for package binaries and libraries # TODO: Implement symlink creation based on package manifest # For now, create placeholder implementation echo fmt"Creating symlinks for {fragment.id.name}" ok() # Progress tracking utilities (6.2.5) proc getInstallProgress*(plan: InstallPlan, currentStep: int): InstallProgress = ## Get current installation progress let current = if currentStep <= plan.steps.len: currentStep else: plan.steps.len let currentPkg = if current > 0 and current <= plan.steps.len: plan.steps[current - 1].package else: PackageId(name: "", version: "", stream: Stable) InstallProgress( currentStep: current, totalSteps: plan.steps.len, currentPackage: currentPkg, status: if current == plan.steps.len: Completed else: Installing ) proc formatInstallProgress*(progress: InstallProgress): string = ## Format installation progress for display let percentage = if progress.totalSteps > 0: (progress.currentStep * 100) div progress.totalSteps else: 0 fmt"[{percentage:3}%] Installing {progress.currentPackage.name} ({progress.currentStep}/{progress.totalSteps})" # Parallel installation support (6.2.6 - future enhancement) proc installPackagesParallel*(packages: seq[PackageId], fragments: Table[PackageId, Fragment], fsManager: FilesystemManager, casManager: CasManager): Result[void, InstallError] = ## Parallel installation of independent package subtrees (future enhancement) # TODO: Implement parallel installation using spawn for independent subtrees # For now, fall back to sequential installation installPackages(packages, fragments, fsManager, casManager) # Utility functions proc validateInstallPlan*(plan: InstallPlan): Result[void, InstallError] = ## Validate installation plan before execution if plan.steps.len == 0: return err(InstallError( code: InvalidOperation, msg: "Installation plan is empty" )) # Check for duplicate packages var seen = initHashSet[PackageId]() for step in plan.steps: if step.package in seen: return err(InstallError( code: DependencyConflict, msg: fmt"Duplicate package in plan: {step.package.name}" )) seen.incl(step.package) ok() proc getInstallSummary*(plan: InstallPlan): string = ## Generate installation summary result = fmt"Installation Plan Summary:\n" result.add(fmt"Total packages: {plan.steps.len}\n") result.add("Installation order:\n") for step in plan.steps: result.add(fmt" {step.stepNumber}. {step.package.name} {step.package.version}\n")