feat!: installation only from a release, into an empty folder; upstream merge/verify and private-instance.md removed, dist adopt, shell-neutral instructions (#153)
CI / verify (push) Successful in 5m19s
CI / pwsh (push) Successful in 1m55s
Release / release (push) Successful in 36s

Files changed:
- .gitea/workflows/ci.yml
- .gitea/workflows/release.yml
- AGENTS.md
- CHANGES.md
- DEVELOPMENT.md
- EVALS.md
- INSTALL.md
- README.md
- VERSION
- docs/ownership-and-templates.md
- instructions/CONTRACT.md
- instructions/bootstrap.md
- instructions/dev/dev-setup.md
- instructions/dev/stack-dev/SKILL.md
- instructions/gates.md
- instructions/ingest-large-tree.md
- instructions/kb-profiles.md
- instructions/mcp-read-server.md
- instructions/migrations/3.0.0-authoring-conventions.md
- instructions/preflight.md
- instructions/private-instance.md
- instructions/session-setup.md
- instructions/setup-instance.md
- instructions/upgrade-instance.md
- tools/CONTRACT.md
- tools/README.md
- tools/chemenu/cli.py
- tools/chemenu/cli_contract.py
- tools/chemenu/commands/dist_cmd.py
- tools/chemenu/commands/docs_verify.py
- tools/chemenu/commands/doctor.py
- tools/chemenu/commands/git_publish.py
- tools/chemenu/commands/upstream_cmd.py
- tools/chemenu/commands/work_cmd.py
- tools/chemenu/config.py
- tools/chemenu/ownership.py
- tools/chemenu/tests/test_cli.py
- tools/chemenu/tests/test_dist_cmd.py
- tools/chemenu/tests/test_instructions_shell.py
- tools/chemenu/tests/test_preflight.py
- tools/chemenu/tests/test_preflight_pwsh.py
- tools/chemenu/tests/test_run_budget.py
- tools/chemenu/tests/test_upstream_cmd.py
- tools/chemenu/toc.py
- tools/preflight.ps1
- tools/preflight.sh
This commit is contained in:
torben committed 2026-10-01 22:12:09 +02:00
1 parent d0f08d1fba
commit a6d07f97c4
46 files changed
+1314 -1936

No files matched your search

+53 -14
View File
@@ -8,8 +8,10 @@
# As the release asset `preflight.ps1` - a copy with no tools/prerequisites.txt next
# to it - the script is the first install step instead: it downloads the stack
# release named in $ReleaseArchiveUrl / $ReleaseChecksumUrl, checks the sha256,
# unpacks it into <script folder>\chemenu (or --into <path>), and runs the copy of
# this script inside the unpacked tree, which does everything above.
# unpacks it into the folder it lies in (or --into <path>), removes itself there, and
# runs the copy of this script inside the unpacked tree, which does everything above.
# That folder has to be empty apart from this script and a .git (an empty clone of the
# instance's own repository).
#
# pwsh -NoProfile -ExecutionPolicy Bypass -File preflight.ps1 [--into <path>] [--archive <tarball>]
#
@@ -108,8 +110,8 @@ while ($index -lt $args.Count) {
Write-Line 'means the user has to act - the output says how.'
Write-Line ''
Write-Line 'The second form is the release asset: it downloads the release (or takes'
Write-Line '--archive), checks its sha256, unpacks it into <script folder>\chemenu or --into,'
Write-Line 'and runs the preflight inside it.'
Write-Line '--archive), checks its sha256, unpacks it into its own folder or --into (empty,'
Write-Line 'or holding only .git), and runs the preflight inside it.'
exit 0
} else {
Write-ErrorLine "preflight: unknown argument: $arg"
@@ -235,10 +237,33 @@ function Resolve-AssetPath {
return [IO.Path]::GetFullPath($Path).TrimEnd('\', '/')
}
# Whether the install folder holds anything besides this script and a .git - the two
# things an empty folder may already carry: the script was downloaded into it, and the
# user may have cloned the instance's own, still empty repository there.
function Test-TargetOccupied {
param([string]$Target)
$selfName = Split-Path -Leaf $PSCommandPath
$isOwnFolder = [IO.Path]::GetFullPath($Target).TrimEnd('\', '/') -eq [IO.Path]::GetFullPath($Dir).TrimEnd('\', '/')
foreach ($entry in @(Get-ChildItem -LiteralPath $Target -Force)) {
if ($entry.Name -eq '.git') {
continue
}
if ($isOwnFolder -and $entry.Name -eq $selfName) {
continue
}
return $true
}
return $false
}
function Invoke-AssetMode {
$target = if ($Into) { Resolve-AssetPath $Into } else { Join-Path $Dir 'chemenu' }
if ($null -ne (Get-Item -LiteralPath $target -Force -ErrorAction SilentlyContinue)) {
Exit-Asset "$target already exists - nothing was unpacked. Choose another folder with --into <path>, or move the existing one away."
$target = if ($Into) { Resolve-AssetPath $Into } else { $Dir.TrimEnd('\', '/') }
$existing = Get-Item -LiteralPath $target -Force -ErrorAction SilentlyContinue
if ($null -ne $existing -and -not $existing.PSIsContainer) {
Exit-Asset "$target exists and is not a folder - nothing was unpacked."
}
if ($null -ne $existing -and (Test-TargetOccupied $target)) {
Exit-Asset "$target is not empty - nothing was unpacked. The wiki installs into an empty folder (an empty git clone is fine): put this script into one and run it there, or pass --into <path>."
}
if (-not $Archive -and (-not $ReleaseArchiveUrl -or -not $ReleaseChecksumUrl)) {
@@ -327,20 +352,28 @@ function Invoke-AssetMode {
if ($length -gt $limit) {
Add-Problem "The folder this wiki would be installed in is too long ($length characters, at most $limit): $target" `
"Windows on this computer only allows paths of up to 259 characters, and the wiki's own files need the rest" `
"Run this again with a shorter folder, for example: --into C:\Chemenu`nAlternatively, someone with administrator rights can turn on long paths in Windows."
"Use a shorter folder such as C:\Chemenu - put this script there and run it again,`nor pass --into C:\Chemenu.`nAlternatively, someone with administrator rights can turn on long paths in Windows."
Exit-WithGuide
}
}
$parent = Split-Path -Parent $target
$null = New-Item -ItemType Directory -Path $parent -Force
$unpack = Join-Path $parent ".chemenu-unpack.$PID"
# Unpacked inside the target rather than beside it: the folder above may be a drive
# root nobody can write to. Only then are the entries moved up, one by one.
$null = New-Item -ItemType Directory -Path $target -Force
$unpack = Join-Path $target ".chemenu-unpack.$PID"
$null = New-Item -ItemType Directory -Path $unpack
$null = Invoke-NativeVerbose $tar.Source @('-xzf', $archivePath, '-C', $unpack)
if (-not (Test-Path -LiteralPath (Join-Path $unpack $top) -PathType Container)) {
Exit-Asset "unpacking failed - the target was not created."
$unpacked = Join-Path $unpack $top
if (-not (Test-Path -LiteralPath $unpacked -PathType Container)) {
Exit-Asset 'unpacking failed - nothing was installed.'
}
foreach ($entry in @(Get-ChildItem -LiteralPath $unpacked -Force)) {
try {
Move-Item -LiteralPath $entry.FullName -Destination $target
} catch {
Exit-Asset "could not move $($entry.Name) into $target - the folder holds part of the stack now; empty it (keep .git) and run this again."
}
}
Move-Item -LiteralPath (Join-Path $unpack $top) -Destination $target
} finally {
foreach ($leftover in @($work, $unpack)) {
if ($leftover -and (Test-Path -LiteralPath $leftover)) {
@@ -353,6 +386,12 @@ function Invoke-AssetMode {
if (-not (Test-Path -LiteralPath $treeScript -PathType Leaf)) {
Exit-Asset 'the unpacked stack has no tools/preflight.ps1.'
}
# The release asset is not part of the stack; left in the folder, it would end up in
# the instance's first commit beside the tree's own tools/preflight.ps1. PowerShell has
# read the whole script before running it, so removing the file is safe here.
if ($target -eq $Dir.TrimEnd('\', '/')) {
Remove-Item -LiteralPath $PSCommandPath -Force
}
Write-Line "Unpacked into $target."
Write-Line 'If a later step stops, run tools/preflight.ps1 from inside that folder.'
Write-Line ''