Cross-Platform Crucible (#2)
Crucible groundwork for cross-platform compatibility. Reviewed-on: #2 Reviewed-by: xtul <mpiasecki720@protonmail.com> Co-authored-by: vickydotbat <vickydotbat@tutamail.com> Co-committed-by: vickydotbat <vickydotbat@tutamail.com>
This commit was merged in pull request #2.
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env pwsh
|
||||
# crucible - bootstrap wrapper (PowerShell). Prefers a crucible already on PATH;
|
||||
# otherwise downloads the latest released binary from Gitea into a per-version
|
||||
# cache and execs it.
|
||||
#
|
||||
# CANONICAL SOURCE: sow-tools/wrappers/crucible.ps1. Do not hand-edit copies in
|
||||
# consumer repos - kept in sync by sow-tools CI (see drift-check).
|
||||
#
|
||||
# Env: CRUCIBLE_HOME, CRUCIBLE_TOKEN, CRUCIBLE_GITEA, CRUCIBLE_REPO.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# 0. Prefer an existing crucible.
|
||||
$existing = Get-Command crucible -ErrorAction SilentlyContinue
|
||||
if ($existing) { & $existing.Source @args; exit $LASTEXITCODE }
|
||||
|
||||
$gitea = if ($env:CRUCIBLE_GITEA) { $env:CRUCIBLE_GITEA } else { 'https://git.westgate.pw' }
|
||||
$repo = if ($env:CRUCIBLE_REPO) { $env:CRUCIBLE_REPO } else { 'ShadowsOverWestgate/sow-tools' }
|
||||
|
||||
# 1. OS/arch -> asset name (.exe on Windows).
|
||||
$os = if ($IsWindows) { 'windows' } elseif ($IsMacOS) { 'darwin' } else { 'linux' }
|
||||
$arch = switch ($env:PROCESSOR_ARCHITECTURE) {
|
||||
'AMD64' { 'amd64' } 'ARM64' { 'arm64' } default { 'amd64' }
|
||||
}
|
||||
$ext = if ($os -eq 'windows') { '.exe' } else { '' }
|
||||
$asset = "crucible-$os-$arch$ext"
|
||||
|
||||
# 2. Auth header (anon first, token fallback).
|
||||
$token = $env:CRUCIBLE_TOKEN
|
||||
$tokenFile = Join-Path $HOME '.config/crucible/token'
|
||||
if (-not $token -and (Test-Path $tokenFile)) { $token = (Get-Content $tokenFile -Raw).Trim() }
|
||||
function Invoke-Fetch($url, $outFile) {
|
||||
try { Invoke-WebRequest -Uri $url -OutFile $outFile -UseBasicParsing; return $true } catch {}
|
||||
if ($token) {
|
||||
Invoke-WebRequest -Uri $url -OutFile $outFile -UseBasicParsing -Headers @{ Authorization = "token $token" }
|
||||
return $true
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
# 3. Resolve latest tag.
|
||||
$tag = $null
|
||||
try {
|
||||
$hdr = if ($token) { @{ Authorization = "token $token" } } else { @{} }
|
||||
$rel = Invoke-RestMethod -Uri "$gitea/api/v1/repos/$repo/releases/latest" -Headers $hdr
|
||||
$tag = $rel.tag_name
|
||||
} catch {}
|
||||
|
||||
# 4. Cache dir.
|
||||
$cacheRoot = if ($env:CRUCIBLE_HOME) { $env:CRUCIBLE_HOME }
|
||||
elseif ($env:LOCALAPPDATA) { Join-Path $env:LOCALAPPDATA 'crucible' }
|
||||
else { Join-Path $HOME '.cache/crucible' }
|
||||
|
||||
# 5. Offline fallback.
|
||||
if (-not $tag) {
|
||||
if (Test-Path $cacheRoot) {
|
||||
$cached = Get-ChildItem $cacheRoot -Directory | Sort-Object Name | Select-Object -Last 1
|
||||
$cachedBin = if ($cached) { Join-Path $cached.FullName "crucible$ext" } else { $null }
|
||||
if ($cachedBin -and (Test-Path $cachedBin)) {
|
||||
Write-Error "crucible: offline - using cached $($cached.Name)" -ErrorAction Continue
|
||||
& $cachedBin @args; exit $LASTEXITCODE
|
||||
}
|
||||
}
|
||||
Write-Error "crucible: cannot reach $gitea and no cached binary found (set CRUCIBLE_TOKEN if private)."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$destDir = Join-Path $cacheRoot $tag
|
||||
$bin = Join-Path $destDir "crucible$ext"
|
||||
|
||||
# 6. Download + verify on cache miss.
|
||||
if (-not (Test-Path $bin)) {
|
||||
New-Item -ItemType Directory -Force -Path $destDir | Out-Null
|
||||
$base = "$gitea/$repo/releases/download/$tag"
|
||||
$tmp = New-Item -ItemType Directory -Path (Join-Path ([System.IO.Path]::GetTempPath()) ([guid]::NewGuid()))
|
||||
$tmpBin = Join-Path $tmp "crucible$ext"
|
||||
Write-Host "crucible: fetching $asset $tag..."
|
||||
if (-not (Invoke-Fetch "$base/$asset" $tmpBin)) { Write-Error "crucible: download failed for $asset"; exit 1 }
|
||||
$sums = Join-Path $tmp 'SHA256SUMS'
|
||||
if (Invoke-Fetch "$base/SHA256SUMS" $sums) {
|
||||
$want = (Select-String -Path $sums -Pattern ([regex]::Escape($asset) + '$') | Select-Object -First 1).Line -split '\s+' | Select-Object -First 1
|
||||
$got = (Get-FileHash $tmpBin -Algorithm SHA256).Hash.ToLower()
|
||||
if ($want -and ($want.ToLower() -ne $got)) { Write-Error "crucible: checksum mismatch for $asset"; exit 1 }
|
||||
}
|
||||
Move-Item -Force $tmpBin $bin
|
||||
Remove-Item -Recurse -Force $tmp
|
||||
}
|
||||
|
||||
& $bin @args
|
||||
exit $LASTEXITCODE
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
# crucible — bootstrap wrapper (POSIX/bash). Prefers a crucible already on PATH
|
||||
# (Nix devshell / installed); otherwise downloads the latest released binary
|
||||
# from Gitea into a per-version cache and execs it.
|
||||
#
|
||||
# CANONICAL SOURCE: sow-tools/wrappers/crucible.sh. Do not hand-edit copies in
|
||||
# consumer repos — they are kept in sync by sow-tools CI (see drift-check).
|
||||
#
|
||||
# Requires: bash, curl, and sha256sum (Linux) or shasum (macOS).
|
||||
# Env: CRUCIBLE_HOME (cache dir), CRUCIBLE_TOKEN (private releases),
|
||||
# CRUCIBLE_GITEA (default https://git.westgate.pw),
|
||||
# CRUCIBLE_REPO (default ShadowsOverWestgate/sow-tools).
|
||||
set -euo pipefail
|
||||
|
||||
# 0. Prefer an existing crucible (Nix users, or already installed).
|
||||
if command -v crucible >/dev/null 2>&1; then
|
||||
exec crucible "$@"
|
||||
fi
|
||||
|
||||
gitea="${CRUCIBLE_GITEA:-https://git.westgate.pw}"
|
||||
repo="${CRUCIBLE_REPO:-ShadowsOverWestgate/sow-tools}"
|
||||
|
||||
# 1. --repo-local materializes into <repo>/.crucible instead of the user cache.
|
||||
repo_local=0
|
||||
if [ "${1:-}" = "--repo-local" ]; then repo_local=1; shift; fi
|
||||
|
||||
# 2. OS/arch -> asset name.
|
||||
os="$(uname -s)"; arch="$(uname -m)"
|
||||
case "$os" in
|
||||
Linux) os=linux ;;
|
||||
Darwin) os=darwin ;;
|
||||
*) echo "crucible: unsupported OS '$os' — install ffmpeg-free crucible manually" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$arch" in
|
||||
x86_64|amd64) arch=amd64 ;;
|
||||
aarch64|arm64) arch=arm64 ;;
|
||||
*) echo "crucible: unsupported arch '$arch'" >&2; exit 1 ;;
|
||||
esac
|
||||
asset="crucible-${os}-${arch}"
|
||||
|
||||
# 3. Auth: anonymous first, token fallback.
|
||||
token="${CRUCIBLE_TOKEN:-}"
|
||||
if [ -z "$token" ] && [ -f "$HOME/.config/crucible/token" ]; then
|
||||
token="$(cat "$HOME/.config/crucible/token")"
|
||||
fi
|
||||
fetch() { # fetch URL DEST ; tries anon then token
|
||||
if curl -fsSL "$1" -o "$2" 2>/dev/null; then return 0; fi
|
||||
if [ -n "$token" ]; then curl -fsSL -H "Authorization: token $token" "$1" -o "$2"; return $?; fi
|
||||
return 1
|
||||
}
|
||||
fetch_stdout() { # prints body; anon then token
|
||||
if curl -fsSL "$1" 2>/dev/null; then return 0; fi
|
||||
if [ -n "$token" ]; then curl -fsSL -H "Authorization: token $token" "$1"; return $?; fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# 4. Resolve the latest release tag (no jq dependency).
|
||||
api="${gitea}/api/v1/repos/${repo}/releases/latest"
|
||||
tag="$(fetch_stdout "$api" 2>/dev/null | grep -o '"tag_name":"[^"]*"' | head -1 | cut -d'"' -f4 || true)"
|
||||
|
||||
# 5. Cache dir.
|
||||
if [ "$repo_local" = 1 ]; then
|
||||
cache_root="$(cd "$(dirname "$0")" && pwd)/.crucible"
|
||||
else
|
||||
cache_root="${CRUCIBLE_HOME:-${XDG_CACHE_HOME:-$HOME/.cache}/crucible}"
|
||||
fi
|
||||
|
||||
# 6. Offline fallback: if tag lookup failed, use the newest cached version.
|
||||
if [ -z "$tag" ]; then
|
||||
if [ -d "$cache_root" ]; then
|
||||
cached="$(ls -1 "$cache_root" 2>/dev/null | sort -V | tail -1 || true)"
|
||||
if [ -n "$cached" ] && [ -x "$cache_root/$cached/crucible" ]; then
|
||||
echo "crucible: offline — using cached $cached" >&2
|
||||
exec "$cache_root/$cached/crucible" "$@"
|
||||
fi
|
||||
fi
|
||||
echo "crucible: cannot reach $gitea and no cached binary found." >&2
|
||||
echo " set CRUCIBLE_TOKEN if releases are private, or check the network." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dest_dir="$cache_root/$tag"
|
||||
bin="$dest_dir/crucible"
|
||||
|
||||
# 7. Download + verify on cache miss.
|
||||
if [ ! -x "$bin" ]; then
|
||||
mkdir -p "$dest_dir"
|
||||
base="${gitea}/${repo}/releases/download/${tag}"
|
||||
tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT
|
||||
echo "crucible: fetching ${asset} ${tag}..." >&2
|
||||
fetch "${base}/${asset}" "$tmp/crucible" || { echo "crucible: download failed for ${asset}" >&2; exit 1; }
|
||||
if fetch "${base}/SHA256SUMS" "$tmp/SHA256SUMS"; then
|
||||
want="$(grep " ${asset}\$" "$tmp/SHA256SUMS" | awk '{print $1}')"
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
got="$(sha256sum "$tmp/crucible" | awk '{print $1}')"
|
||||
else
|
||||
got="$(shasum -a 256 "$tmp/crucible" | awk '{print $1}')"
|
||||
fi
|
||||
if [ -n "$want" ] && [ "$want" != "$got" ]; then
|
||||
echo "crucible: checksum mismatch for ${asset} (want $want got $got)" >&2; exit 1
|
||||
fi
|
||||
fi
|
||||
chmod +x "$tmp/crucible"
|
||||
mv "$tmp/crucible" "$bin"
|
||||
fi
|
||||
|
||||
exec "$bin" "$@"
|
||||
Reference in New Issue
Block a user