Files
CASAN/install.ps1
T

132 lines
7.0 KiB
PowerShell

#requires -Version 5.1
<#
CASAN global installer (Plan-21 hybrid model) — Windows PowerShell.
irm https://<your-gitea>/admin/casan5/raw/branch/main/install.ps1 | iex
# or, from a local CASAN source checkout:
pwsh .\install.ps1
Installs the harness ONCE under $env:CASAN_HOME (default %LOCALAPPDATA%\casan),
writes a `casan` launcher, and records the gate-code integrity hash. Projects
then run `casan init` to adopt CASAN with only per-project config.
Requires: python3 on PATH, and bash (Git for Windows / Git Bash) to RUN the
harness — see docs/casan/CASAN_AGENTIC_CLIENTS_WINDOWS.md.
#>
[CmdletBinding()]
param(
[string]$Source,
[switch]$NoPathLink
)
$ErrorActionPreference = 'Stop'
function Log($m) { Write-Host "[casan-install] $m" }
function Die($m) { Write-Error "[casan-install] ERROR: $m"; exit 1 }
$py = (Get-Command python3 -ErrorAction SilentlyContinue) ?? (Get-Command python -ErrorAction SilentlyContinue)
if (-not $py) { Die 'python3 is required on PATH.' }
$CasanHome = if ($env:CASAN_HOME) { $env:CASAN_HOME } else { Join-Path $env:LOCALAPPDATA 'casan' }
# ── 1) Locate source (param, CASAN_SRC, local checkout, or CASAN_DIST_URL) ────
$cleanup = $null
if (-not $Source) { $Source = $env:CASAN_SRC }
if ((-not $Source) -and (Test-Path './packages/casan-harness')) { $Source = (Get-Location).Path }
if ((-not $Source) -and $env:CASAN_DIST_URL) {
$tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("casan-" + [guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Force -Path $tmp | Out-Null
$cleanup = $tmp
Log "downloading CASAN from $($env:CASAN_DIST_URL)"
$tgz = Join-Path $tmp 'casan.tar.gz'
Invoke-WebRequest -UseBasicParsing -Uri $env:CASAN_DIST_URL -OutFile $tgz
tar -xzf $tgz -C $tmp
if (Test-Path (Join-Path $tmp 'packages/casan-harness')) { $Source = $tmp }
else {
$found = Get-ChildItem -Path $tmp -Recurse -Directory -Filter 'casan-harness' -Depth 2 | Select-Object -First 1
if ($found) { $Source = (Split-Path -Parent (Split-Path -Parent $found.FullName)) }
}
}
if ((-not $Source) -or (-not (Test-Path (Join-Path $Source 'packages/casan-harness')))) {
Die 'no CASAN source found. Run from a checkout, pass -Source <dir>, or set CASAN_DIST_URL.'
}
$version = if (Test-Path (Join-Path $Source 'VERSION')) { (Get-Content (Join-Path $Source 'VERSION') -Raw).Trim() } else { '0.0.0' }
Log "source : $Source"
Log "version : $version"
Log "install : $CasanHome"
# ── 2) Copy the production runtime allowlist into a versioned dir ─────────────
$dest = Join-Path $CasanHome "versions\$version"
if (Test-Path $dest) { Remove-Item -Recurse -Force $dest }
New-Item -ItemType Directory -Force -Path (Join-Path $dest 'packages'), (Join-Path $dest 'bin') | Out-Null
$runtimeCopier = Join-Path $Source 'scripts\copy-runtime.py'
if (-not (Test-Path $runtimeCopier)) { Die 'missing production runtime copier: scripts/copy-runtime.py' }
& $py.Source $runtimeCopier --source-root $Source --destination-root $dest --component harness --component devkit --clean
if ($LASTEXITCODE -ne 0) { Die 'failed to assemble the production runtime.' }
Copy-Item -Force (Join-Path $Source 'bin\casan') (Join-Path $dest 'bin\casan')
Set-Content -Path (Join-Path $dest 'VERSION') -Value $version -Encoding ASCII
Set-Content -Path (Join-Path $dest '.casan-level') -Value 'devkit' -Encoding ASCII
New-Item -ItemType Directory -Force -Path (Join-Path $dest 'packaging') | Out-Null
Copy-Item -Force (Join-Path $Source 'packaging\runtime-layout.json') (Join-Path $dest 'packaging\runtime-layout.json')
Get-ChildItem -Path $dest -Recurse -Directory -Filter '__pycache__' -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
Get-ChildItem -Path $dest -Recurse -File -Filter '*.pyc' -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue
# ── 3) Record gate-code integrity hash ───────────────────────────────────────
$hasher = Join-Path $dest 'packages\casan-harness\scripts\python\harness_hash.py'
$hhash = (& $py.Source $hasher compute (Join-Path $dest 'packages\casan-harness')).Trim()
if (-not $hhash) { Die 'failed to compute harness hash.' }
Set-Content -Path (Join-Path $dest '.harness-hash') -Value $hhash -Encoding ASCII
Log "integrity: $hhash"
# ── 4) Point current -> this version ─────────────────────────────────────────
$cur = Join-Path $CasanHome 'current'
if (Test-Path $cur) { (Get-Item $cur).Delete() }
New-Item -ItemType Junction -Path $cur -Target $dest | Out-Null
# ── 5) Launcher: casan.cmd -> casan.ps1 (sets env, invokes the bash CLI) ─────
$binDir = Join-Path $CasanHome 'bin'
New-Item -ItemType Directory -Force -Path $binDir | Out-Null
$launcherPs1 = @'
$ErrorActionPreference = "Stop"
# Self-locate THIS install from the launcher's own path (<home>\bin\casan.ps1) so a
# specific launcher always uses ITS install regardless of any ambient CASAN_HOME.
$selfHome = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
if ((Test-Path (Join-Path $selfHome "current")) -or (Test-Path (Join-Path $selfHome "versions"))) {
$CasanHome = $selfHome
} elseif ($env:CASAN_HOME) { $CasanHome = $env:CASAN_HOME } else { $CasanHome = Join-Path $env:LOCALAPPDATA "casan" }
$cur = Join-Path $CasanHome "current"
if (-not (Test-Path $cur)) { Write-Error "casan: no install at $cur (run install.ps1)"; exit 1 }
$env:CASAN_HARNESS_ROOT = Join-Path $cur "packages\casan-harness"
$env:CASAN_DEVKIT_ROOT = Join-Path $cur "packages\casan-devkit"
$env:CASAN_INSTALL_ROOT = $cur
if (-not $env:CASAN_APP_ROOT) {
$d = (Get-Location).Path
while ($d -and (Split-Path $d -Parent)) {
if ((Test-Path (Join-Path $d ".casan")) -or (Test-Path (Join-Path $d ".specify"))) { $env:CASAN_APP_ROOT = $d; break }
$d = Split-Path $d -Parent
}
}
$bash = (Get-Command bash -ErrorAction SilentlyContinue)
if (-not $bash) { Write-Error "casan: bash not found. Install Git for Windows (Git Bash)."; exit 1 }
& $bash.Source (Join-Path $cur "bin/casan") @args
exit $LASTEXITCODE
'@
Set-Content -Path (Join-Path $binDir 'casan.ps1') -Value $launcherPs1 -Encoding UTF8
Set-Content -Path (Join-Path $binDir 'casan.cmd') -Value "@echo off`r`npowershell -NoProfile -ExecutionPolicy Bypass -File `"%~dp0casan.ps1`" %*" -Encoding ASCII
# ── 6) Put launcher on PATH (user scope) ─────────────────────────────────────
if (-not $NoPathLink -and $env:CASAN_NO_PATH_LINK -ne '1') {
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
if ($userPath -notlike "*$binDir*") {
[Environment]::SetEnvironmentVariable('Path', "$binDir;$userPath", 'User')
Log "added $binDir to user PATH (restart shell to pick it up)"
}
}
Log "installed CASAN $version"
Log "launcher : $binDir\casan.cmd"
Log "next: cd <your-project>; casan init"
if ($cleanup) { Remove-Item -Recurse -Force $cleanup -ErrorAction SilentlyContinue }