fix(windows): improve PowerShell installer and add clone script
- Fix return value leakage in install-core.ps1 (suppressed with $null) - Fix ACL -bor compatibility across PowerShell versions - Handle unsigned/lightweight git tags in GPG verification - Skip GPG verification for branch builds (main) - Add explicit GOOS=windows for Go build - Detect Windows syscall build errors with helpful message - Add clone-repos.ps1 as Windows alternative to `core setup` - Update CLAUDE.md with Windows-specific setup instructions Closes #56 workaround documented Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
621438a876
commit
9a52fd937a
3 changed files with 141 additions and 23 deletions
40
CLAUDE.md
40
CLAUDE.md
|
|
@ -53,12 +53,38 @@ See `.core/docs/core-folder-spec.md` for the full specification that each packag
|
||||||
```bash
|
```bash
|
||||||
# macOS/Linux
|
# macOS/Linux
|
||||||
git clone git@github.com:host-uk/core-devops.git && cd core-devops && make setup
|
git clone git@github.com:host-uk/core-devops.git && cd core-devops && make setup
|
||||||
|
|
||||||
# Windows (PowerShell as Admin)
|
|
||||||
.\scripts\install-deps.ps1 && .\scripts\install-core.ps1 && core setup
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Environment variables for `install-core.sh`:
|
### Windows Setup
|
||||||
|
|
||||||
|
The `core` CLI is not yet available on Windows. Use these steps instead:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 1. Install dependencies (PowerShell as Admin)
|
||||||
|
.\scripts\install-deps.ps1
|
||||||
|
|
||||||
|
# 2. Authenticate GitHub CLI
|
||||||
|
gh auth login -h github.com -p https -s workflow,repo,read:org,read:project,project
|
||||||
|
|
||||||
|
# 3. Clone all repos
|
||||||
|
.\scripts\clone-repos.ps1
|
||||||
|
|
||||||
|
# 4. Enable PHP extensions (edit C:\tools\php84\php.ini, uncomment these):
|
||||||
|
# extension=curl
|
||||||
|
# extension=fileinfo
|
||||||
|
# extension=mbstring
|
||||||
|
# extension=openssl
|
||||||
|
# extension=pdo_sqlite
|
||||||
|
|
||||||
|
# 5. Install and test a package
|
||||||
|
cd packages/core-php
|
||||||
|
composer install
|
||||||
|
composer test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables (macOS/Linux)
|
||||||
|
|
||||||
|
For `install-core.sh`:
|
||||||
- `INSTALL_DIR` - Binary location (default: `~/.local/bin`)
|
- `INSTALL_DIR` - Binary location (default: `~/.local/bin`)
|
||||||
- `BUILD_FROM_SOURCE` - `true`, `false`, or `auto` (default: `auto`, tries binary then builds)
|
- `BUILD_FROM_SOURCE` - `true`, `false`, or `auto` (default: `auto`, tries binary then builds)
|
||||||
|
|
||||||
|
|
@ -140,6 +166,12 @@ Defined in `repos.yaml`:
|
||||||
- **"refusing to allow..." or "missing required scopes"** → `gh auth refresh -h github.com -s workflow,read:project,project`
|
- **"refusing to allow..." or "missing required scopes"** → `gh auth refresh -h github.com -s workflow,read:project,project`
|
||||||
- **Clone failures** → `ssh -T git@github.com` to verify SSH keys
|
- **Clone failures** → `ssh -T git@github.com` to verify SSH keys
|
||||||
|
|
||||||
|
### Windows-Specific
|
||||||
|
|
||||||
|
- **"openssl extension is required"** → Enable extensions in `C:\tools\php84\php.ini` (see setup above)
|
||||||
|
- **"composer: command not found"** → Use PowerShell, not Git Bash: `powershell -Command "composer install"`
|
||||||
|
- **core CLI not available** → Use `.\scripts\clone-repos.ps1` and work directly with composer
|
||||||
|
|
||||||
## This Repo's Scope
|
## This Repo's Scope
|
||||||
|
|
||||||
Don't add application code here. This repo only contains:
|
Don't add application code here. This repo only contains:
|
||||||
|
|
|
||||||
48
scripts/clone-repos.ps1
Normal file
48
scripts/clone-repos.ps1
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Clone all Host UK repos (Windows alternative to `core setup`)
|
||||||
|
# Run: .\scripts\clone-repos.ps1
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$repos = @(
|
||||||
|
"core-php",
|
||||||
|
"core-tenant",
|
||||||
|
"core-admin",
|
||||||
|
"core-api",
|
||||||
|
"core-mcp",
|
||||||
|
"core-agentic",
|
||||||
|
"core-bio",
|
||||||
|
"core-social",
|
||||||
|
"core-analytics",
|
||||||
|
"core-notify",
|
||||||
|
"core-trust",
|
||||||
|
"core-support",
|
||||||
|
"core-commerce",
|
||||||
|
"core-content",
|
||||||
|
"core-tools",
|
||||||
|
"core-uptelligence",
|
||||||
|
"core-developer",
|
||||||
|
"core-template"
|
||||||
|
)
|
||||||
|
|
||||||
|
$packagesDir = Join-Path $PSScriptRoot "..\packages"
|
||||||
|
|
||||||
|
Write-Host "[INFO] Cloning repos to $packagesDir" -ForegroundColor Green
|
||||||
|
|
||||||
|
foreach ($repo in $repos) {
|
||||||
|
$repoPath = Join-Path $packagesDir $repo
|
||||||
|
|
||||||
|
if (Test-Path $repoPath) {
|
||||||
|
Write-Host "[SKIP] $repo already exists" -ForegroundColor Yellow
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "[CLONE] $repo..." -ForegroundColor Cyan
|
||||||
|
gh repo clone "host-uk/$repo" $repoPath
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Host "[WARN] Failed to clone $repo" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "[DONE] Repos cloned. Run 'composer install' in each package." -ForegroundColor Green
|
||||||
|
|
@ -27,7 +27,7 @@ if ($PSVersionTable.PSVersion.Major -lt 4) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$Repo = "host-uk/core"
|
$Repo = "host-uk/core"
|
||||||
$Version = "v0.1.0" # Pinned version - update when releasing new versions
|
$Version = "main" # Build from main until stable Windows releases are available
|
||||||
$MinDiskSpaceMB = 100 # Minimum required disk space in MB
|
$MinDiskSpaceMB = 100 # Minimum required disk space in MB
|
||||||
|
|
||||||
function Write-Info { Write-Host "[INFO] $args" -ForegroundColor Green }
|
function Write-Info { Write-Host "[INFO] $args" -ForegroundColor Green }
|
||||||
|
|
@ -144,7 +144,7 @@ function New-SecureDirectory {
|
||||||
# Check parent directory for symlinks first
|
# Check parent directory for symlinks first
|
||||||
$parent = Split-Path $Path -Parent
|
$parent = Split-Path $Path -Parent
|
||||||
if ($parent -and (Test-Path $parent)) {
|
if ($parent -and (Test-Path $parent)) {
|
||||||
Test-SecureDirectory -Path $parent
|
$null = Test-SecureDirectory -Path $parent
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create directory
|
# Create directory
|
||||||
|
|
@ -153,7 +153,7 @@ function New-SecureDirectory {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Immediately verify it's not a symlink (minimize TOCTOU window)
|
# Immediately verify it's not a symlink (minimize TOCTOU window)
|
||||||
Test-SecureDirectory -Path $Path
|
$null = Test-SecureDirectory -Path $Path
|
||||||
|
|
||||||
return $Path
|
return $Path
|
||||||
}
|
}
|
||||||
|
|
@ -181,10 +181,12 @@ function Set-SecureDirectoryAcl {
|
||||||
|
|
||||||
# Add full control for current user only
|
# Add full control for current user only
|
||||||
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
||||||
|
# Pre-calculate flags to avoid -bor compatibility issues across PowerShell versions
|
||||||
|
$inheritFlags = [System.Security.AccessControl.InheritanceFlags]([int][System.Security.AccessControl.InheritanceFlags]::ContainerInherit + [int][System.Security.AccessControl.InheritanceFlags]::ObjectInherit)
|
||||||
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
||||||
$currentUser,
|
$currentUser,
|
||||||
[System.Security.AccessControl.FileSystemRights]::FullControl,
|
[System.Security.AccessControl.FileSystemRights]::FullControl,
|
||||||
[System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit,
|
$inheritFlags,
|
||||||
[System.Security.AccessControl.PropagationFlags]::None,
|
[System.Security.AccessControl.PropagationFlags]::None,
|
||||||
[System.Security.AccessControl.AccessControlType]::Allow
|
[System.Security.AccessControl.AccessControlType]::Allow
|
||||||
)
|
)
|
||||||
|
|
@ -221,8 +223,8 @@ function Download-Binary {
|
||||||
$tempExe = $null
|
$tempExe = $null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
# Create and verify install directory
|
# Create and verify install directory (suppress output to avoid polluting return value)
|
||||||
New-SecureDirectory -Path $InstallDir
|
$null = New-SecureDirectory -Path $InstallDir
|
||||||
|
|
||||||
# Use a temp file in the same directory (same filesystem for atomic move)
|
# Use a temp file in the same directory (same filesystem for atomic move)
|
||||||
$tempExe = Join-Path $InstallDir "core.exe.tmp.$([System.Guid]::NewGuid().ToString('N').Substring(0,8))"
|
$tempExe = Join-Path $InstallDir "core.exe.tmp.$([System.Guid]::NewGuid().ToString('N').Substring(0,8))"
|
||||||
|
|
@ -252,7 +254,7 @@ function Download-Binary {
|
||||||
Test-FileHash -FilePath $tempExe -ExpectedHash $expectedHash
|
Test-FileHash -FilePath $tempExe -ExpectedHash $expectedHash
|
||||||
|
|
||||||
# Re-verify directory hasn't been replaced with symlink (reduce TOCTOU window)
|
# Re-verify directory hasn't been replaced with symlink (reduce TOCTOU window)
|
||||||
Test-SecureDirectory -Path $InstallDir
|
$null = Test-SecureDirectory -Path $InstallDir
|
||||||
|
|
||||||
# Atomic move to final location (same filesystem)
|
# Atomic move to final location (same filesystem)
|
||||||
$finalPath = Join-Path $InstallDir "core.exe"
|
$finalPath = Join-Path $InstallDir "core.exe"
|
||||||
|
|
@ -297,13 +299,19 @@ function Test-GitTagSignature {
|
||||||
Push-Location $RepoPath
|
Push-Location $RepoPath
|
||||||
try {
|
try {
|
||||||
# Attempt to verify the tag signature
|
# Attempt to verify the tag signature
|
||||||
$result = git tag -v $Tag 2>&1
|
# Use $ErrorActionPreference temporarily to prevent stderr from throwing
|
||||||
if ($LASTEXITCODE -eq 0) {
|
$oldErrorAction = $ErrorActionPreference
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
$result = git tag -v $Tag 2>&1 | Out-String
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
$ErrorActionPreference = $oldErrorAction
|
||||||
|
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
Write-Info "GPG signature verified for tag $Tag"
|
Write-Info "GPG signature verified for tag $Tag"
|
||||||
return $true
|
return $true
|
||||||
} else {
|
} else {
|
||||||
# Check if tag is unsigned vs signature invalid
|
# Check if tag is unsigned vs signature invalid
|
||||||
if ($result -match "error: no signature found") {
|
if ($result -match "no signature found" -or $result -match "cannot verify a non-tag") {
|
||||||
Write-Warn "Tag $Tag is not signed - continuing without signature verification"
|
Write-Warn "Tag $Tag is not signed - continuing without signature verification"
|
||||||
return $true
|
return $true
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -326,10 +334,10 @@ function Build-FromSource {
|
||||||
|
|
||||||
# Create secure temp directory with restrictive ACL
|
# Create secure temp directory with restrictive ACL
|
||||||
$tmpdir = Join-Path ([System.IO.Path]::GetTempPath()) "core-build-$([System.Guid]::NewGuid().ToString('N'))"
|
$tmpdir = Join-Path ([System.IO.Path]::GetTempPath()) "core-build-$([System.Guid]::NewGuid().ToString('N'))"
|
||||||
New-SecureDirectory -Path $tmpdir
|
$null = New-SecureDirectory -Path $tmpdir
|
||||||
|
|
||||||
# ACL is REQUIRED for temp build directories (security critical)
|
# ACL is REQUIRED for temp build directories (security critical)
|
||||||
Set-SecureDirectoryAcl -Path $tmpdir -Required
|
$null = Set-SecureDirectoryAcl -Path $tmpdir -Required
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Write-Info "Cloning $Repo (version $Version)..."
|
Write-Info "Cloning $Repo (version $Version)..."
|
||||||
|
|
@ -341,22 +349,52 @@ function Build-FromSource {
|
||||||
Write-Err "Failed to clone repository at version $Version"
|
Write-Err "Failed to clone repository at version $Version"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Verify GPG signature on tag (if available)
|
# Verify GPG signature on tag (if available, skip for branches)
|
||||||
Test-GitTagSignature -RepoPath $cloneDir -Tag $Version
|
if ($Version -match "^v\d") {
|
||||||
|
$null = Test-GitTagSignature -RepoPath $cloneDir -Tag $Version
|
||||||
|
} else {
|
||||||
|
Write-Warn "Building from branch '$Version' - GPG verification skipped (only applies to tags)"
|
||||||
|
}
|
||||||
|
|
||||||
Write-Info "Building core CLI..."
|
Write-Info "Building core CLI..."
|
||||||
Push-Location $cloneDir
|
Push-Location $cloneDir
|
||||||
try {
|
try {
|
||||||
go build -o core.exe .
|
# Explicitly set GOOS/GOARCH to ensure Windows build
|
||||||
if ($LASTEXITCODE -ne 0) {
|
$env:GOOS = "windows"
|
||||||
Write-Err "Go build failed"
|
$env:GOARCH = "amd64"
|
||||||
|
|
||||||
|
$oldErrorAction = $ErrorActionPreference
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
$buildOutput = go build -o core.exe . 2>&1 | Out-String
|
||||||
|
$buildExitCode = $LASTEXITCODE
|
||||||
|
$ErrorActionPreference = $oldErrorAction
|
||||||
|
|
||||||
|
if ($buildExitCode -ne 0) {
|
||||||
|
# Check for Windows-specific build issues
|
||||||
|
if ($buildOutput -match "Setpgid|Getpgid|syscall\.Kill|undefined.*syscall") {
|
||||||
|
Write-Warn "Build failed: core CLI uses Unix-specific syscalls not available on Windows."
|
||||||
|
Write-Warn ""
|
||||||
|
Write-Warn "Options:"
|
||||||
|
Write-Warn " 1. Wait for pre-built Windows binaries in GitHub releases"
|
||||||
|
Write-Warn " 2. Use WSL (Windows Subsystem for Linux) for development"
|
||||||
|
Write-Warn " 3. Work directly with composer in packages/ (core CLI is optional)"
|
||||||
|
Write-Warn ""
|
||||||
|
Write-Warn "For PHP development, you can use composer directly:"
|
||||||
|
Write-Warn " cd packages/core-php && composer test"
|
||||||
|
Write-Host ""
|
||||||
|
# Don't use Write-Err here - exit gracefully
|
||||||
|
exit 0
|
||||||
|
} else {
|
||||||
|
Write-Host $buildOutput
|
||||||
|
Write-Err "Go build failed"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
Pop-Location
|
Pop-Location
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create and verify install directory
|
# Create and verify install directory
|
||||||
New-SecureDirectory -Path $InstallDir
|
$null = New-SecureDirectory -Path $InstallDir
|
||||||
|
|
||||||
# Move built binary to install location
|
# Move built binary to install location
|
||||||
Move-Item (Join-Path $cloneDir "core.exe") (Join-Path $InstallDir "core.exe") -Force
|
Move-Item (Join-Path $cloneDir "core.exe") (Join-Path $InstallDir "core.exe") -Force
|
||||||
|
|
@ -427,7 +465,7 @@ function Main {
|
||||||
Write-Info "Installing Core CLI (version $Version)..."
|
Write-Info "Installing Core CLI (version $Version)..."
|
||||||
|
|
||||||
# Check disk space before starting
|
# Check disk space before starting
|
||||||
Test-DiskSpace -Path $InstallDir
|
$null = Test-DiskSpace -Path $InstallDir
|
||||||
|
|
||||||
# Try download first, fallback to build
|
# Try download first, fallback to build
|
||||||
if (-not (Download-Binary)) {
|
if (-not (Download-Binary)) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue