mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-16 19:29:26 +02:00
* fix: randomize self-host postgres password Co-authored-by: multica-agent <github@multica.ai> * docs: sync self-host password guidance Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
572 lines
20 KiB
PowerShell
572 lines
20 KiB
PowerShell
# Multica installer for Windows — one command to get started.
|
|
#
|
|
# Install CLI (default): connects to multica.ai
|
|
# irm https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.ps1 | iex
|
|
#
|
|
# Self-host: starts a local Multica server + installs CLI + configures
|
|
# $env:MULTICA_MODE="local"; irm https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.ps1 | iex
|
|
#
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Configuration
|
|
# ---------------------------------------------------------------------------
|
|
$RepoUrl = "https://github.com/multica-ai/multica.git"
|
|
$RepoWebUrl = "https://github.com/multica-ai/multica"
|
|
$DefaultInstallDir = Join-Path $env:USERPROFILE ".multica\server"
|
|
$InstallDir = if ($env:MULTICA_INSTALL_DIR) { $env:MULTICA_INSTALL_DIR } else { $DefaultInstallDir }
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
function Write-Info { param([string]$Msg) Write-Host "==> $Msg" -ForegroundColor Cyan }
|
|
function Write-Ok { param([string]$Msg) Write-Host "[OK] $Msg" -ForegroundColor Green }
|
|
function Write-Warn { param([string]$Msg) Write-Warning $Msg }
|
|
function Write-Fail { param([string]$Msg) Write-Host "[ERROR] $Msg" -ForegroundColor Red; exit 1 }
|
|
|
|
function Test-CommandExists {
|
|
param([string]$Name)
|
|
$null -ne (Get-Command $Name -ErrorAction SilentlyContinue)
|
|
}
|
|
|
|
function New-RandomHex {
|
|
param([int]$ByteCount)
|
|
|
|
$bytes = New-Object byte[] $ByteCount
|
|
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
|
|
try {
|
|
$rng.GetBytes($bytes)
|
|
} finally {
|
|
$rng.Dispose()
|
|
}
|
|
return -join ($bytes | ForEach-Object { "{0:x2}" -f $_ })
|
|
}
|
|
|
|
function Get-EnvFileValue {
|
|
param(
|
|
[string]$Path,
|
|
[string]$Name,
|
|
[string]$Default
|
|
)
|
|
|
|
if (-not (Test-Path $Path)) {
|
|
return $Default
|
|
}
|
|
|
|
$prefix = "$Name="
|
|
$line = Get-Content $Path |
|
|
Where-Object { $_.StartsWith($prefix) } |
|
|
Select-Object -Last 1
|
|
if (-not $line) {
|
|
return $Default
|
|
}
|
|
|
|
$value = $line.Substring($prefix.Length).Trim().Trim('"').Trim("'")
|
|
if ([string]::IsNullOrWhiteSpace($value)) {
|
|
return $Default
|
|
}
|
|
return $value
|
|
}
|
|
|
|
function Get-SelfHostBackendPort {
|
|
foreach ($name in @("BACKEND_PORT", "API_PORT", "SERVER_PORT", "PORT")) {
|
|
$value = Get-EnvFileValue -Path (Join-Path $InstallDir ".env") -Name $name -Default ""
|
|
if (-not [string]::IsNullOrWhiteSpace($value)) {
|
|
return $value
|
|
}
|
|
}
|
|
return "8080"
|
|
}
|
|
|
|
function Get-SelfHostFrontendPort {
|
|
return Get-EnvFileValue -Path (Join-Path $InstallDir ".env") -Name "FRONTEND_PORT" -Default "3000"
|
|
}
|
|
|
|
function Get-LatestVersion {
|
|
try {
|
|
$release = Invoke-RestMethod -Uri "https://api.github.com/repos/multica-ai/multica/releases/latest" -ErrorAction Stop
|
|
return $release.tag_name
|
|
} catch {
|
|
return $null
|
|
}
|
|
}
|
|
|
|
function Get-SelfHostRef {
|
|
if ($env:MULTICA_SELFHOST_REF) {
|
|
return $env:MULTICA_SELFHOST_REF
|
|
}
|
|
|
|
$latest = Get-LatestVersion
|
|
if ($latest) {
|
|
return $latest
|
|
}
|
|
|
|
return "main"
|
|
}
|
|
|
|
function Checkout-ServerRef {
|
|
param([string]$Ref)
|
|
|
|
if ($Ref -eq "main") {
|
|
git fetch origin main --depth 1 2>$null
|
|
git checkout --force main 2>$null
|
|
git reset --hard origin/main 2>$null
|
|
return
|
|
}
|
|
|
|
git fetch origin --tags --force 2>$null
|
|
$tagRef = "refs/tags/$Ref"
|
|
git show-ref --verify --quiet $tagRef 2>$null
|
|
if ($LASTEXITCODE -eq 0) {
|
|
git checkout --force $Ref 2>$null
|
|
return
|
|
}
|
|
|
|
git fetch origin $Ref --depth 1 2>$null
|
|
git checkout --force $Ref 2>$null
|
|
}
|
|
|
|
function Pull-OfficialSelfHostImages {
|
|
docker compose -f docker-compose.selfhost.yml pull
|
|
if ($LASTEXITCODE -eq 0) {
|
|
return
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Warn "Official images for the selected self-host channel are not published yet."
|
|
Write-Host "This can happen before the first GHCR release is available."
|
|
Write-Host "From $InstallDir, build from source instead:"
|
|
Write-Host " docker compose -f docker-compose.selfhost.yml -f docker-compose.selfhost.build.yml up -d --build"
|
|
exit 1
|
|
}
|
|
|
|
function Convert-ToCliArch {
|
|
param([object]$Value)
|
|
|
|
if ($null -eq $Value) {
|
|
return $null
|
|
}
|
|
|
|
$normalized = "$Value".Trim().ToUpperInvariant()
|
|
switch ($normalized) {
|
|
"9" { return "amd64" }
|
|
"AMD64" { return "amd64" }
|
|
"X64" { return "amd64" }
|
|
"X86_64" { return "amd64" }
|
|
"12" { return "arm64" }
|
|
"ARM64" { return "arm64" }
|
|
"AARCH64" { return "arm64" }
|
|
default { return $null }
|
|
}
|
|
}
|
|
|
|
function Get-WindowsCliArch {
|
|
$signals = @()
|
|
$nativeArchSignalFound = $false
|
|
|
|
# Prefer the native processor architecture over the current PowerShell
|
|
# process architecture. This keeps Windows on ARM from being misdetected
|
|
# when PowerShell is running through x64/x86 emulation.
|
|
try {
|
|
if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {
|
|
$processorArch = Get-CimInstance -ClassName Win32_Processor -ErrorAction Stop |
|
|
Select-Object -First 1 -ExpandProperty Architecture
|
|
$signals += [pscustomobject]@{ Source = "Win32_Processor.Architecture"; Value = $processorArch }
|
|
$nativeArchSignalFound = $true
|
|
}
|
|
} catch {}
|
|
|
|
try {
|
|
if (-not $nativeArchSignalFound -and (Get-Command Get-WmiObject -ErrorAction SilentlyContinue)) {
|
|
$processorArch = Get-WmiObject -Class Win32_Processor -ErrorAction Stop |
|
|
Select-Object -First 1 -ExpandProperty Architecture
|
|
$signals += [pscustomobject]@{ Source = "Win32_Processor.Architecture"; Value = $processorArch }
|
|
$nativeArchSignalFound = $true
|
|
}
|
|
} catch {}
|
|
|
|
try {
|
|
$signals += [pscustomobject]@{
|
|
Source = "RuntimeInformation.OSArchitecture"
|
|
Value = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
|
|
}
|
|
} catch {}
|
|
|
|
$signals += [pscustomobject]@{ Source = "PROCESSOR_ARCHITEW6432"; Value = $env:PROCESSOR_ARCHITEW6432 }
|
|
$signals += [pscustomobject]@{ Source = "PROCESSOR_ARCHITECTURE"; Value = $env:PROCESSOR_ARCHITECTURE }
|
|
|
|
foreach ($signal in $signals) {
|
|
$arch = Convert-ToCliArch $signal.Value
|
|
if ($arch) {
|
|
return $arch
|
|
}
|
|
}
|
|
|
|
$details = ($signals |
|
|
Where-Object { $null -ne $_.Value -and "$($_.Value)".Trim() -ne "" } |
|
|
ForEach-Object { "$($_.Source)=$($_.Value)" }) -join ", "
|
|
if (-not $details) {
|
|
$details = "no architecture signals available"
|
|
}
|
|
|
|
Write-Fail "Unsupported Windows architecture ($details). Only x64 and ARM64 are supported."
|
|
}
|
|
|
|
function Get-InstalledCliVersion {
|
|
try {
|
|
$firstLine = multica version 2>$null | Select-Object -First 1
|
|
if ("$firstLine" -match '\b(v?\d+(?:\.\d+)+)\b') {
|
|
$version = $Matches[1]
|
|
if ($version -notlike 'v*') {
|
|
$version = "v$version"
|
|
}
|
|
return $version
|
|
}
|
|
} catch {}
|
|
|
|
return $null
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CLI Installation
|
|
# ---------------------------------------------------------------------------
|
|
function Install-CliBinary {
|
|
Write-Info "Installing Multica CLI from GitHub Releases..."
|
|
|
|
if (-not [Environment]::Is64BitOperatingSystem) {
|
|
Write-Fail "Multica requires a 64-bit Windows installation."
|
|
}
|
|
|
|
$arch = Get-WindowsCliArch
|
|
|
|
$latest = Get-LatestVersion
|
|
if (-not $latest) {
|
|
Write-Fail "Could not determine latest release. Check your network connection."
|
|
}
|
|
|
|
$version = $latest.TrimStart('v')
|
|
$url = "https://github.com/multica-ai/multica/releases/download/$latest/multica-cli-$version-windows-$arch.zip"
|
|
$tmpDir = Join-Path ([System.IO.Path]::GetTempPath()) "multica-install"
|
|
|
|
if (Test-Path $tmpDir) { Remove-Item $tmpDir -Recurse -Force }
|
|
New-Item -ItemType Directory -Path $tmpDir | Out-Null
|
|
|
|
Write-Info "Downloading $url ..."
|
|
try {
|
|
Invoke-WebRequest -Uri $url -OutFile (Join-Path $tmpDir "multica.zip") -UseBasicParsing
|
|
} catch {
|
|
Remove-Item $tmpDir -Recurse -Force
|
|
Write-Fail "Failed to download CLI binary: $_"
|
|
}
|
|
|
|
# Verify SHA256 checksum
|
|
$checksumUrl = "https://github.com/multica-ai/multica/releases/download/$latest/checksums.txt"
|
|
try {
|
|
$checksums = Invoke-WebRequest -Uri $checksumUrl -UseBasicParsing -ErrorAction Stop
|
|
$checksumContent = if ($checksums.Content -is [byte[]]) {
|
|
[System.Text.Encoding]::UTF8.GetString($checksums.Content)
|
|
} else {
|
|
[string]$checksums.Content
|
|
}
|
|
$zipFile = Join-Path $tmpDir "multica.zip"
|
|
$actualHash = (Get-FileHash -Path $zipFile -Algorithm SHA256).Hash.ToLower()
|
|
$releaseAsset = "multica-cli-$version-windows-$arch.zip"
|
|
$legacyAsset = "multica_windows_$arch.zip"
|
|
$expectedLine = ($checksumContent -split "`r?`n") |
|
|
Where-Object {
|
|
$_ -match [regex]::Escape($releaseAsset) -or
|
|
$_ -match [regex]::Escape($legacyAsset)
|
|
} |
|
|
Select-Object -First 1
|
|
if ($expectedLine) {
|
|
$expectedHash = ($expectedLine -split "\s+")[0].ToLower()
|
|
if ($actualHash -ne $expectedHash) {
|
|
Remove-Item $tmpDir -Recurse -Force
|
|
Write-Fail "Checksum verification failed. Expected: $expectedHash, Got: $actualHash"
|
|
}
|
|
Write-Ok "Checksum verified"
|
|
} else {
|
|
Write-Warn "Could not find checksum entry for $releaseAsset — skipping verification."
|
|
}
|
|
} catch {
|
|
Write-Warn "Could not download checksums.txt — skipping verification."
|
|
}
|
|
|
|
Expand-Archive -Path (Join-Path $tmpDir "multica.zip") -DestinationPath $tmpDir -Force
|
|
|
|
$binDir = Join-Path $env:USERPROFILE ".multica\bin"
|
|
if (-not (Test-Path $binDir)) {
|
|
New-Item -ItemType Directory -Path $binDir -Force | Out-Null
|
|
}
|
|
|
|
$exeSrc = Join-Path $tmpDir "multica.exe"
|
|
if (-not (Test-Path $exeSrc)) {
|
|
$exeSrc = Get-ChildItem -Path $tmpDir -Filter "multica.exe" -Recurse | Select-Object -First 1 -ExpandProperty FullName
|
|
}
|
|
if (-not $exeSrc -or -not (Test-Path $exeSrc)) {
|
|
Remove-Item $tmpDir -Recurse -Force
|
|
Write-Fail "multica.exe not found in downloaded archive."
|
|
}
|
|
|
|
Copy-Item $exeSrc (Join-Path $binDir "multica.exe") -Force
|
|
Remove-Item $tmpDir -Recurse -Force
|
|
|
|
Add-ToUserPath $binDir
|
|
Write-Ok "Multica CLI installed to $binDir\multica.exe"
|
|
}
|
|
|
|
function Add-ToUserPath {
|
|
param([string]$Dir)
|
|
$currentPath = [Environment]::GetEnvironmentVariable("Path", "User")
|
|
if ($currentPath -and $currentPath.Split(";") -contains $Dir) {
|
|
return
|
|
}
|
|
$newPath = if ($currentPath) { "$currentPath;$Dir" } else { $Dir }
|
|
[Environment]::SetEnvironmentVariable("Path", $newPath, "User")
|
|
# Also update current session
|
|
if ($env:Path -notlike "*$Dir*") {
|
|
$env:Path = "$Dir;$env:Path"
|
|
}
|
|
Write-Info "Added $Dir to user PATH (restart your terminal for other sessions to pick it up)."
|
|
}
|
|
|
|
function Install-Cli {
|
|
if (Test-CommandExists "multica") {
|
|
$currentVer = Get-InstalledCliVersion
|
|
$latestVer = Get-LatestVersion
|
|
|
|
$currentCmp = if ($currentVer) { $currentVer -replace '^v','' } else { $null }
|
|
$latestCmp = if ($latestVer) { $latestVer -replace '^v','' } else { $null }
|
|
|
|
$isUpToDate = $currentCmp -and -not $latestCmp
|
|
if (-not $isUpToDate) {
|
|
try {
|
|
$isUpToDate = $currentCmp -and $latestCmp -and ([System.Version]$currentCmp -ge [System.Version]$latestCmp)
|
|
} catch {
|
|
$isUpToDate = $currentCmp -and $latestCmp -and ($currentCmp -eq $latestCmp)
|
|
}
|
|
}
|
|
|
|
if ($isUpToDate) {
|
|
Write-Ok "Multica CLI is up to date ($currentVer)"
|
|
return
|
|
}
|
|
|
|
Write-Info "Multica CLI $currentVer installed, latest is $latestVer - upgrading..."
|
|
Install-CliBinary
|
|
|
|
$newVer = Get-InstalledCliVersion
|
|
Write-Ok "Multica CLI upgraded ($currentVer -> $newVer)"
|
|
return
|
|
}
|
|
|
|
Install-CliBinary
|
|
|
|
if (-not (Test-CommandExists "multica")) {
|
|
Write-Fail "CLI installed but 'multica' not found on PATH. Restart your terminal and try again."
|
|
}
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Docker check
|
|
# ---------------------------------------------------------------------------
|
|
function Test-Docker {
|
|
if (-not (Test-CommandExists "docker")) {
|
|
Write-Fail @"
|
|
Docker is not installed. Multica self-hosting requires Docker and Docker Compose.
|
|
|
|
Install Docker Desktop for Windows:
|
|
https://docs.docker.com/desktop/install/windows-install/
|
|
|
|
After installing Docker, re-run this script with `$env:MULTICA_MODE="local"`.
|
|
"@
|
|
}
|
|
|
|
try {
|
|
docker info 2>$null | Out-Null
|
|
} catch {
|
|
Write-Fail "Docker is installed but not running. Please start Docker Desktop and re-run this script."
|
|
}
|
|
|
|
Write-Ok "Docker is available"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Server setup (self-host / local)
|
|
# ---------------------------------------------------------------------------
|
|
function Install-Server {
|
|
Write-Info "Setting up Multica server..."
|
|
$serverRef = Get-SelfHostRef
|
|
Write-Info "Using self-host assets from $serverRef..."
|
|
|
|
if (Test-Path (Join-Path $InstallDir ".git")) {
|
|
Write-Info "Updating existing installation at $InstallDir..."
|
|
Write-Warn "Any local changes in $InstallDir will be overwritten."
|
|
} else {
|
|
Write-Info "Cloning Multica repository..."
|
|
if (-not (Test-CommandExists "git")) {
|
|
Write-Fail "Git is not installed. Please install git and re-run."
|
|
}
|
|
if (Test-Path $InstallDir) {
|
|
Write-Warn "Removing incomplete installation at $InstallDir..."
|
|
Remove-Item $InstallDir -Recurse -Force
|
|
}
|
|
$parentDir = Split-Path $InstallDir -Parent
|
|
if (-not (Test-Path $parentDir)) {
|
|
New-Item -ItemType Directory -Path $parentDir -Force | Out-Null
|
|
}
|
|
git clone --depth 1 $RepoUrl $InstallDir
|
|
}
|
|
|
|
Push-Location $InstallDir
|
|
Checkout-ServerRef $serverRef
|
|
Write-Ok "Repository ready at $InstallDir ($serverRef)"
|
|
|
|
if (-not (Test-Path ".env")) {
|
|
Write-Info "Creating .env with random secrets..."
|
|
Copy-Item ".env.example" ".env"
|
|
$jwt = New-RandomHex 32
|
|
$pgpass = New-RandomHex 24
|
|
$content = Get-Content ".env"
|
|
$content = $content -replace '^JWT_SECRET=.*', "JWT_SECRET=$jwt"
|
|
$content = $content -replace '^POSTGRES_PASSWORD=.*', "POSTGRES_PASSWORD=$pgpass"
|
|
$content = $content -replace '^(DATABASE_URL=postgres://[^:]+:)[^@]*(@.*)', "`${1}$pgpass`${2}"
|
|
$content | Set-Content ".env"
|
|
Write-Ok "Generated .env with random JWT_SECRET and POSTGRES_PASSWORD"
|
|
} else {
|
|
Write-Ok "Using existing .env"
|
|
}
|
|
|
|
Write-Info "Pulling official Multica images..."
|
|
Pull-OfficialSelfHostImages
|
|
Write-Info "Starting Multica services (this may take a few minutes on first run)..."
|
|
docker compose -f docker-compose.selfhost.yml up -d
|
|
|
|
Write-Info "Waiting for backend to be ready..."
|
|
$backendPort = Get-SelfHostBackendPort
|
|
$ready = $false
|
|
for ($i = 1; $i -le 45; $i++) {
|
|
try {
|
|
$null = Invoke-WebRequest -Uri "http://localhost:$backendPort/health" -UseBasicParsing -TimeoutSec 2
|
|
$ready = $true
|
|
break
|
|
} catch {
|
|
Start-Sleep -Seconds 2
|
|
}
|
|
}
|
|
|
|
if ($ready) {
|
|
Write-Ok "Multica server is running"
|
|
} else {
|
|
Write-Warn "Server is still starting. Check logs with:"
|
|
Write-Host " cd $InstallDir; docker compose -f docker-compose.selfhost.yml logs"
|
|
}
|
|
|
|
Pop-Location
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Main: Default mode (cloud)
|
|
# ---------------------------------------------------------------------------
|
|
function Start-DefaultInstall {
|
|
Write-Host ""
|
|
Write-Host " Multica - Installer" -ForegroundColor White
|
|
Write-Host ""
|
|
|
|
Install-Cli
|
|
|
|
Write-Host ""
|
|
Write-Host " ============================================" -ForegroundColor Green
|
|
Write-Host " [OK] Multica CLI is ready!" -ForegroundColor Green
|
|
Write-Host " ============================================" -ForegroundColor Green
|
|
Write-Host ""
|
|
Write-Host " Next: configure your environment"
|
|
Write-Host ""
|
|
Write-Host " multica setup " -NoNewline; Write-Host "# Connect to Multica Cloud (multica.ai)" -ForegroundColor DarkGray
|
|
Write-Host " multica setup self-host " -NoNewline; Write-Host "# Connect to a self-hosted server" -ForegroundColor DarkGray
|
|
Write-Host ""
|
|
Write-Host " Self-hosting? Install the server first:"
|
|
Write-Host ' $env:MULTICA_MODE="with-server"; irm https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.ps1 | iex'
|
|
Write-Host ""
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Main: Local mode (self-host)
|
|
# ---------------------------------------------------------------------------
|
|
function Start-LocalInstall {
|
|
Write-Host ""
|
|
Write-Host " Multica - Self-Host Installer" -ForegroundColor White
|
|
Write-Host " Provisioning server infrastructure + installing CLI"
|
|
Write-Host ""
|
|
|
|
Test-Docker
|
|
Install-Server
|
|
Install-Cli
|
|
|
|
Write-Host ""
|
|
Write-Host " ============================================" -ForegroundColor Green
|
|
Write-Host " [OK] Multica server is running and CLI is ready!" -ForegroundColor Green
|
|
Write-Host " ============================================" -ForegroundColor Green
|
|
Write-Host ""
|
|
$frontendPort = Get-SelfHostFrontendPort
|
|
$backendPort = Get-SelfHostBackendPort
|
|
Write-Host " Frontend: http://localhost:$frontendPort"
|
|
Write-Host " Backend: http://localhost:$backendPort"
|
|
Write-Host " Server at: $InstallDir"
|
|
Write-Host ""
|
|
Write-Host " Next: configure your CLI to connect"
|
|
Write-Host ""
|
|
Write-Host " multica setup self-host " -NoNewline; Write-Host "# Configure + authenticate + start daemon" -ForegroundColor DarkGray
|
|
Write-Host ""
|
|
Write-Host " Login: configure RESEND_API_KEY in .env for email codes,"
|
|
Write-Host " or read the generated code from backend logs when Resend is unset."
|
|
Write-Host ""
|
|
Write-Host " To stop all services:"
|
|
Write-Host ' $env:MULTICA_MODE="stop"; irm https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.ps1 | iex'
|
|
Write-Host ""
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Stop: shut down a self-hosted installation
|
|
# ---------------------------------------------------------------------------
|
|
function Start-Stop {
|
|
Write-Host ""
|
|
Write-Info "Stopping Multica services..."
|
|
|
|
if (Test-Path $InstallDir) {
|
|
Push-Location $InstallDir
|
|
if (Test-Path "docker-compose.selfhost.yml") {
|
|
docker compose -f docker-compose.selfhost.yml down
|
|
Write-Ok "Docker services stopped"
|
|
} else {
|
|
Write-Warn "No docker-compose.selfhost.yml found at $InstallDir"
|
|
}
|
|
Pop-Location
|
|
} else {
|
|
Write-Warn "No Multica installation found at $InstallDir"
|
|
}
|
|
|
|
if (Test-CommandExists "multica") {
|
|
try {
|
|
multica daemon stop 2>$null
|
|
Write-Ok "Daemon stopped"
|
|
} catch {}
|
|
}
|
|
|
|
Write-Host ""
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Entry point
|
|
# ---------------------------------------------------------------------------
|
|
$mode = if ($env:MULTICA_MODE) { $env:MULTICA_MODE.ToLower() } else { "default" }
|
|
|
|
switch ($mode) {
|
|
"with-server" { Start-LocalInstall }
|
|
"local" { Start-LocalInstall } # backwards compat alias
|
|
"stop" { Start-Stop }
|
|
default { Start-DefaultInstall }
|
|
}
|