Windows: official port -- UCRT static build, UTF-8 throughout, Low IL sandbox, ncurses TUI, installer; v0.6.0
Build
* cross build switches to pkgsCross.ucrt64 (shell-win.nix); curl (Schannel),
winpthreads and ncurses 6.6 are linked statically, so the release is
cst-agent.exe + busybox.exe (busybox-w32 Unicode) with no extra DLLs
* windres embeds a manifest with activeCodePage=UTF-8 and a version resource
* ncurses 6.6 Windows console driver patches (mouse button state, A_DIM,
wide-char declarations): scripts/patches/
* scripts/bootstrap_msys2.sh builds the same release natively in MSYS2
UCRT64; PR #1's MINGW64 notes now point there
* vendor/tre-regex (musl's TRE) provides <regex.h> on MinGW
UTF-8
* paths cross the system call boundary as UTF-16 (*W APIs); console code
pages set to 65001 and restored on exit; files are read and written in
binary mode
* child output that is not valid UTF-8 is decoded from the console, OEM or
ANSI system code page
* read_file hides CR; edit_file, apply_patch and write_file keep CRLF line
endings and a UTF-8 BOM
OS layer and processes
* os_dir_* and cst_fnmatch replace the POSIX-only directory walks in
subagent, plugins, skills, undo and fs_tools; realpath / path_under and
symlink rejection work on Windows
* every child goes through CreateProcessW with its own environment block
inside a Job Object, so timeouts and Esc end the whole process tree
Sandbox (auto mode, "auto · lowil")
* run_shell runs with a restricted Low integrity token; the workspace gets
an inheritable Low label once, the sandbox TEMP is labeled Low, and the
credentials file gets a no-read-up label
* drive roots, the profile and system folders are never labeled
* network commands still ask, since Low IL does not block the network;
Access denied and MSYS2's 0xc0000142 offer a rerun outside the sandbox
* /sandbox lists labeled folders, /sandbox reset-labels restores them
TUI
* raster console fonts are switched to Consolas / NSimSun while running
* the home logo falls back to text when the console font lacks U+2580
* mouse drag copy goes to the Windows clipboard
Distribution
* scripts/install_windows.ps1 installs a Gitea release into
%LOCALAPPDATA%\Programs\cst-agent and adds it to the user PATH;
cst-agent-update.cmd reruns it; scripts/release_windows.sh uploads the
zip with the token taken from the environment
* scripts/run_windows.bat is gone; docs/README-windows.md is rewritten
Tests
* new test_lowil, test_win_utf8 and test_os_dir; tests made portable
(cst_test_util.h)
* scripts/win_test.sh and scripts/winvm/ run the suite on English and
Chinese Windows Server VMs as Administrator, a standard user and a
Chinese user name: 29/29 on all six
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
# Install or update CST-Agent on Windows from a Gitea release.
|
||||
#
|
||||
# irm https://git.whcself.xyz/WHC/CST-agent/raw/branch/main/scripts/install_windows.ps1 | iex
|
||||
# powershell -ExecutionPolicy Bypass -File install_windows.ps1 [-Version 0.6.0] [-Prefix DIR] [-Zip FILE] [-NoPath]
|
||||
#
|
||||
# Installs into %LOCALAPPDATA%\Programs\cst-agent (no admin rights needed),
|
||||
# adds it to the user PATH and leaves cst-agent-update.cmd beside the exe,
|
||||
# which reruns this script for that folder. Environment overrides:
|
||||
# CST_VERSION, CST_PREFIX, CST_GITEA (server URL), CST_REPO (owner/name).
|
||||
# Needs Windows 10 1903+ / Server 2022+ (UTF-8 process code page).
|
||||
# Keep this file ASCII: Windows PowerShell 5.1 reads BOM-less scripts in the
|
||||
# ANSI code page.
|
||||
param(
|
||||
[string]$Version = $env:CST_VERSION,
|
||||
[string]$Prefix = $env:CST_PREFIX,
|
||||
[string]$Zip = "",
|
||||
[switch]$NoPath
|
||||
)
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
|
||||
$Server = if ($env:CST_GITEA) { $env:CST_GITEA.TrimEnd("/") } else { "https://git.whcself.xyz" }
|
||||
$Repo = if ($env:CST_REPO) { $env:CST_REPO } else { "WHC/CST-agent" }
|
||||
if (-not $Prefix) { $Prefix = Join-Path $env:LOCALAPPDATA "Programs\cst-agent" }
|
||||
$Prefix = [IO.Path]::GetFullPath($Prefix)
|
||||
|
||||
$build = [Environment]::OSVersion.Version.Build
|
||||
if ($build -lt 18362) {
|
||||
throw "CST-Agent needs Windows 10 1903 (build 18362) or newer; this is build $build."
|
||||
}
|
||||
|
||||
$work = Join-Path ([IO.Path]::GetTempPath()) ("cst-agent-install-" + [Guid]::NewGuid().ToString("N"))
|
||||
New-Item -ItemType Directory -Force $work | Out-Null
|
||||
try {
|
||||
if (-not $Zip) {
|
||||
$api = "$Server/api/v1/repos/$Repo/releases"
|
||||
$url = if (-not $Version -or $Version -eq "latest") { "$api/latest" } else { "$api/tags/v$($Version.TrimStart('v'))" }
|
||||
Write-Host "==> $url"
|
||||
try {
|
||||
$rel = Invoke-RestMethod -Uri $url -UseBasicParsing
|
||||
} catch {
|
||||
throw "no release found at $url ($($_.Exception.Message))"
|
||||
}
|
||||
$asset = $rel.assets | Where-Object { $_.name -like "*-windows-x86_64.zip" } | Select-Object -First 1
|
||||
if (-not $asset) { throw "release $($rel.tag_name) has no *-windows-x86_64.zip" }
|
||||
$Zip = Join-Path $work $asset.name
|
||||
Write-Host "==> download $($asset.name) ($($rel.tag_name))"
|
||||
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $Zip -UseBasicParsing
|
||||
$sum = $rel.assets | Where-Object { $_.name -eq "$($asset.name).sha256" } | Select-Object -First 1
|
||||
if ($sum) {
|
||||
$sumFile = Join-Path $work $sum.name
|
||||
Invoke-WebRequest -Uri $sum.browser_download_url -OutFile $sumFile -UseBasicParsing
|
||||
$want = ((Get-Content $sumFile -Raw).Trim() -split "\s+")[0].ToLower()
|
||||
$got = (Get-FileHash -Algorithm SHA256 $Zip).Hash.ToLower()
|
||||
if ($want -ne $got) { throw "sha256 mismatch for $($asset.name): expected $want, got $got" }
|
||||
Write-Host "==> sha256 ok"
|
||||
}
|
||||
}
|
||||
|
||||
Expand-Archive -Path $Zip -DestinationPath $work -Force
|
||||
$src = Join-Path $work "windows-x86_64"
|
||||
if (-not (Test-Path (Join-Path $src "cst-agent.exe"))) { throw "$Zip has no windows-x86_64\cst-agent.exe" }
|
||||
|
||||
New-Item -ItemType Directory -Force $Prefix | Out-Null
|
||||
# A running exe cannot be overwritten but can be renamed out of the way.
|
||||
foreach ($name in @("cst-agent.exe", "busybox.exe")) {
|
||||
$dst = Join-Path $Prefix $name
|
||||
$old = "$dst.old"
|
||||
if (Test-Path $old) { Remove-Item -Force $old -ErrorAction SilentlyContinue }
|
||||
if (Test-Path $dst) {
|
||||
try { Remove-Item -Force $dst } catch { Rename-Item -Force $dst "$name.old" }
|
||||
}
|
||||
}
|
||||
Copy-Item -Force -Recurse (Join-Path $src "*") $Prefix
|
||||
|
||||
$update = Join-Path $Prefix "cst-agent-update.cmd"
|
||||
if (-not (Test-Path $update)) {
|
||||
Set-Content -Encoding Ascii -Path $update -Value @(
|
||||
"@echo off",
|
||||
"rem Update CST-Agent in this folder from the latest release.",
|
||||
"powershell -NoProfile -ExecutionPolicy Bypass -File `"%~dp0install_windows.ps1`" -Prefix `"%~dp0.`" %*"
|
||||
)
|
||||
}
|
||||
if (-not (Test-Path (Join-Path $Prefix "install_windows.ps1")) -and $PSCommandPath) {
|
||||
Copy-Item -Force $PSCommandPath (Join-Path $Prefix "install_windows.ps1")
|
||||
}
|
||||
|
||||
if (-not $NoPath) {
|
||||
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
|
||||
$parts = @($userPath -split ";" | Where-Object { $_ })
|
||||
if (-not ($parts | Where-Object { $_.TrimEnd("\") -ieq $Prefix.TrimEnd("\") })) {
|
||||
[Environment]::SetEnvironmentVariable("Path", (($parts + $Prefix) -join ";"), "User")
|
||||
Write-Host "==> added $Prefix to the user PATH (open a new terminal to use it)"
|
||||
}
|
||||
if (-not (($env:Path -split ";") | Where-Object { $_.TrimEnd("\") -ieq $Prefix.TrimEnd("\") })) {
|
||||
$env:Path = "$env:Path;$Prefix"
|
||||
}
|
||||
}
|
||||
|
||||
$ver = (& (Join-Path $Prefix "cst-agent.exe") --version) 2>&1 | Select-Object -First 1
|
||||
Write-Host "==> installed $ver in $Prefix"
|
||||
Write-Host "Run 'cst-agent' inside a project folder; update later with 'cst-agent-update'."
|
||||
} finally {
|
||||
Remove-Item -Recurse -Force $work -ErrorAction SilentlyContinue
|
||||
}
|
||||
Reference in New Issue
Block a user