mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-01 16:08:30 +00:00
Dev infra: harden Windows bootstrap after two adversarial debug passes
Pass 1 (correctness sweep): - All native invocations (mysql, mysqld init, winget, curl downloads, cargo run, seed curl) now go through Invoke-Native, so a caller capturing the scripts' output can never turn ordinary stderr (warnings, progress) into a terminating NativeCommandError under Stop preference - Provisioning DDL feeds through a temp file + `source` and prints the mysql output when it fails (with the MYSQL_ROOT_PASSWORD hint) - Get-MySqlCount distinguishes "table empty" (0) from "query failed" (-1); bootstrap and doctor no longer misread a failed COUNT as unseeded - diesel_cli that is present but broken (moved libmysql.dll) is detected via an exit-code probe and reinstalled with --force - Shared Add-BuildToolsToSession maps winget install dirs + LIBCLANG_PATH in bootstrap, doctor, and run_backend_dev (cargo run may recompile); the tools check maps PATH before probing so re-runs skip winget entirely - my.ini values quoted (clone paths with spaces); service-start timeouts point at the .devstack log files Pass 2 (live retest matrix, all green on this machine): - Full re-run with output captured in-PowerShell: exit 0, no NativeCommandError - Wiped-state rebuild (mysql-data/my.ini/redis/secrets deleted): re-init, re-provision, 281 migrations replayed, seeds + secrets regenerated, backend booted, demo user recreated via /v1/create_account, doctor 0 fail / 0 warn - Failure paths: seed without backend and doctor without services both die cleanly with actionable messages and exit 1; stop_dev_services frees both ports gracefully
This commit is contained in:
1 parent
345cbe766a
commit
f23a6df80b
6 files changed
+98
-48
No files matched your search
@@ -99,6 +99,9 @@ function Ensure-CoreTools {
|
||||
}
|
||||
|
||||
function Ensure-BuildTools {
|
||||
# Tools installed by a previous run land in dirs this shell may not have on
|
||||
# PATH yet - map them in before probing, so we don't re-invoke winget.
|
||||
Add-BuildToolsToSession
|
||||
$missing = @($BuildTools | Where-Object { -not (Get-Command $_.Exe -ErrorAction SilentlyContinue) })
|
||||
if ($missing.Count -eq 0) {
|
||||
Write-Log "All native build tools present (cmake, perl, nasm, clang)."
|
||||
@@ -116,7 +119,7 @@ function Ensure-BuildTools {
|
||||
|
||||
foreach ($tool in $missing) {
|
||||
Write-Log "winget install $($tool.WingetId)..."
|
||||
winget install --id $tool.WingetId -e --accept-source-agreements --accept-package-agreements --disable-interactivity
|
||||
Invoke-Native "winget install --id $($tool.WingetId) -e --accept-source-agreements --accept-package-agreements --disable-interactivity"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warn2 "winget exited with $LASTEXITCODE for $($tool.Name); checking whether the tool landed anyway..."
|
||||
}
|
||||
@@ -156,13 +159,14 @@ function Install-PortableMySql {
|
||||
|
||||
if (-not (Test-Path $MySqlIniPath)) {
|
||||
# Minimal config; utf8mb4 collation comes from each migration's DDL.
|
||||
# MySQL's ini parser escape-processes backslashes - use forward slashes.
|
||||
# MySQL's ini parser escape-processes backslashes - use forward slashes,
|
||||
# and quote the values in case the clone path contains spaces.
|
||||
$baseForward = $MySqlBaseDir -replace '\\', '/'
|
||||
$dataForward = $MySqlDataDir -replace '\\', '/'
|
||||
$ini = @"
|
||||
[mysqld]
|
||||
basedir=$baseForward
|
||||
datadir=$dataForward
|
||||
basedir="$baseForward"
|
||||
datadir="$dataForward"
|
||||
port=3306
|
||||
bind-address=127.0.0.1
|
||||
"@
|
||||
@@ -185,13 +189,15 @@ function Provision-MySqlDatabase {
|
||||
}
|
||||
|
||||
$mysql = Get-MySqlTool "mysql"
|
||||
if (-not $mysql) { Die "mysql client not found (portable install missing and none on PATH)." }
|
||||
# Portable install: root has no password. External server: honor MYSQL_ROOT_PASSWORD.
|
||||
$prev = $env:MYSQL_PWD
|
||||
if ($env:MYSQL_ROOT_PASSWORD) { $env:MYSQL_PWD = $env:MYSQL_ROOT_PASSWORD } else { $env:MYSQL_PWD = "" }
|
||||
try {
|
||||
# Same DDL as _docs/dev_setup_server.md, made idempotent. The extra
|
||||
# '127.0.0.1' account covers hosts where reverse-DNS of 127.0.0.1 doesn't
|
||||
# resolve to 'localhost'.
|
||||
# resolve to 'localhost'. Fed through a temp file + `source` so mysql's
|
||||
# stderr can never become a terminating PowerShell error mid-provision.
|
||||
$ddl = @"
|
||||
CREATE DATABASE IF NOT EXISTS $DevMySqlDb;
|
||||
CREATE USER IF NOT EXISTS '$DevMySqlUser'@'localhost' IDENTIFIED BY '$DevMySqlPassword';
|
||||
@@ -200,8 +206,13 @@ GRANT ALL PRIVILEGES ON $DevMySqlDb.* TO '$DevMySqlUser'@'localhost';
|
||||
GRANT ALL PRIVILEGES ON $DevMySqlDb.* TO '$DevMySqlUser'@'127.0.0.1';
|
||||
FLUSH PRIVILEGES;
|
||||
"@
|
||||
$ddl | & $mysql -u root -h 127.0.0.1
|
||||
$ddlFile = Join-Path $env:TEMP "artcraft_provision_ddl.sql"
|
||||
Set-Content -Path $ddlFile -Value $ddl -Encoding ascii
|
||||
$ddlForward = $ddlFile -replace '\\', '/'
|
||||
$out = Invoke-Native "`"$mysql`" -u root -h 127.0.0.1 -e `"source $ddlForward`""
|
||||
Remove-Item $ddlFile -Force -Confirm:$false -ErrorAction SilentlyContinue
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
if ($out) { Write-Host ($out -join "`n") }
|
||||
Die "Provisioning failed. For an external MySQL server, set MYSQL_ROOT_PASSWORD and re-run."
|
||||
}
|
||||
} finally {
|
||||
@@ -236,10 +247,22 @@ function Install-PortableRedis {
|
||||
|
||||
function Ensure-DieselCli {
|
||||
Add-MySqlClientToSession
|
||||
$needInstall = $true
|
||||
$forceFlag = ""
|
||||
if (Get-Command diesel -ErrorAction SilentlyContinue) {
|
||||
Write-Log "Found $(diesel --version)."
|
||||
return
|
||||
# A diesel.exe built against a client lib that has since moved fails at
|
||||
# load time - probe it rather than trusting its presence.
|
||||
$version = Invoke-Native "diesel --version"
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Log "Found $version."
|
||||
$needInstall = $false
|
||||
} else {
|
||||
Write-Warn2 "diesel is on PATH but broken (missing libmysql.dll?) - reinstalling."
|
||||
$forceFlag = " --force"
|
||||
}
|
||||
}
|
||||
if (-not $needInstall) { return }
|
||||
|
||||
# diesel_cli's mysql feature links against libmysqlclient. The portable MySQL
|
||||
# ships its own client lib - point mysqlclient-sys at it instead of requiring
|
||||
# vcpkg. sqlite is bundled so no system sqlite is needed.
|
||||
@@ -249,9 +272,9 @@ function Ensure-DieselCli {
|
||||
Write-Log "Installing diesel_cli (compiles from source; a few minutes)..."
|
||||
$env:MYSQLCLIENT_LIB_DIR = Join-Path $MySqlBaseDir "lib"
|
||||
$env:MYSQLCLIENT_VERSION = $MySqlVersion
|
||||
Invoke-Native "cargo install diesel_cli --no-default-features --features mysql,sqlite-bundled"
|
||||
Invoke-Native "cargo install diesel_cli --no-default-features --features mysql,sqlite-bundled$forceFlag"
|
||||
if ($LASTEXITCODE -ne 0) { Die "cargo install diesel_cli failed (exit $LASTEXITCODE)." }
|
||||
Write-Log "Installed $(diesel --version)."
|
||||
Write-Log "Installed $(Invoke-Native 'diesel --version')."
|
||||
}
|
||||
|
||||
function Run-Migrations {
|
||||
@@ -276,7 +299,8 @@ function Seed-RolesAndBadges {
|
||||
# Same data as _database/sql/seed/bootstrap_inserts_roles_etc.sh, but guarded
|
||||
# so re-runs don't hit duplicate-key errors. The 'user' role is mandatory:
|
||||
# account creation hardcodes user_role_slug='user'.
|
||||
$roleCount = [int](Invoke-MySqlApp -Sql "SELECT COUNT(*) FROM user_roles" | Select-Object -First 1)
|
||||
$roleCount = Get-MySqlCount "SELECT COUNT(*) FROM user_roles"
|
||||
if ($roleCount -lt 0) { Die "Could not query user_roles - are migrations applied? (re-run bootstrap)" }
|
||||
if ($roleCount -gt 0) {
|
||||
Write-Log "user_roles already seeded ($roleCount rows)."
|
||||
} else {
|
||||
@@ -285,7 +309,8 @@ function Seed-RolesAndBadges {
|
||||
Write-Log "Inserted system roles (user, mod, admin)."
|
||||
}
|
||||
|
||||
$badgeCount = [int](Invoke-MySqlApp -Sql "SELECT COUNT(*) FROM badges" | Select-Object -First 1)
|
||||
$badgeCount = Get-MySqlCount "SELECT COUNT(*) FROM badges"
|
||||
if ($badgeCount -lt 0) { Die "Could not query badges - are migrations applied? (re-run bootstrap)" }
|
||||
if ($badgeCount -gt 0) {
|
||||
Write-Log "badges already seeded ($badgeCount rows)."
|
||||
} else {
|
||||
@@ -361,13 +386,7 @@ function Build-Backend {
|
||||
Write-Log "Skipped (-SkipRustBuild)."
|
||||
return
|
||||
}
|
||||
# Freshly-installed tools live in dirs this shell may not have on PATH yet;
|
||||
# bindgen (boring2/aws-lc-sys) additionally wants LIBCLANG_PATH.
|
||||
foreach ($tool in $BuildTools) { Add-SessionPath $tool.PathDirs }
|
||||
if (-not $env:LIBCLANG_PATH) {
|
||||
$clang = Get-Command clang -ErrorAction SilentlyContinue
|
||||
if ($clang) { $env:LIBCLANG_PATH = Split-Path $clang.Source }
|
||||
}
|
||||
Add-BuildToolsToSession
|
||||
Push-Location $RootDir
|
||||
try {
|
||||
# SQLX_OFFLINE only affects compile-time query verification (checked-in
|
||||
@@ -413,8 +432,11 @@ function Setup-Frontend {
|
||||
function Download-File([string]$Url, [string]$Destination) {
|
||||
# curl.exe ships with Windows 10+ and handles redirects/resume better than
|
||||
# Invoke-WebRequest (which is also painfully slow on large files in PS 5.1).
|
||||
curl.exe -fL --retry 3 -o "$Destination.partial" $Url
|
||||
# -sS: no progress meter (it goes to stderr, which pollutes captured logs),
|
||||
# but errors still print.
|
||||
$out = Invoke-Native "curl.exe -fsSL --retry 3 -o `"$Destination.partial`" `"$Url`""
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
if ($out) { Write-Host ($out -join "`n") }
|
||||
Remove-Item "$Destination.partial" -Force -Confirm:$false -ErrorAction SilentlyContinue
|
||||
Die "Download failed: $Url"
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user