2 Commits
Author SHA1 Message Date
orrisroot a036d3ef46 fix(scripts): report Gitea API errors in release uploads
The macOS and Linux release scripts called the Gitea API with
`curl -sf`, which discards the response on failure. A rejected token
surfaced only as a JSON decode error from the release id lookup.

- Route API calls through gitea_api, which keeps the HTTP status and
  the response body
- On failure print the status and Gitea's message, with a hint about
  the token scope on 401/403, and exit
- Look up the release first and create it only on 404 instead of
  ignoring every creation error
- Check the status of each asset upload
2026-11-04 20:15:15 +09:00
orrisroot e1081aeff4 fix(scripts): keep .env values out of the PowerShell session
A .ps1 runs inside the caller's PowerShell process, so the values the
Windows release script exported from .env outlived the script. A
later run in the same session kept the old GITEA_TOKEN even after .env
was edited, because existing environment variables take precedence.

- Read .env into a script-local table and resolve settings through
  Get-Setting, with environment variables still taking precedence
- Stop changing the caller's current location; build and archive
  with paths anchored at the repository root
- Skip .env lines without "=" instead of failing under StrictMode
2026-11-04 20:15:14 +09:00
3 changed files with 107 additions and 46 deletions
+42 -15
View File
@@ -80,26 +80,53 @@ fi
: "${GITEA_SERVER_URL:?GITEA_SERVER_URL is required for upload}" : "${GITEA_SERVER_URL:?GITEA_SERVER_URL is required for upload}"
: "${GITEA_REPOSITORY:?GITEA_REPOSITORY is required for upload}" : "${GITEA_REPOSITORY:?GITEA_REPOSITORY is required for upload}"
echo "" API_BODY="$(mktemp)"
echo "==> Creating Gitea release ${TAG} ..." trap 'rm -f "${API_BODY}"' EXIT
curl -sf -X POST \
-H "Authorization: Bearer ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
"${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases" \
-d "{\"tag_name\": \"${TAG}\", \"name\": \"${TAG}\"}" > /dev/null || true
RELEASE_ID="$(curl -sf \ # Call the Gitea repository API. The HTTP status is left in API_STATUS and
-H "Authorization: Bearer ${GITEA_TOKEN}" \ # the response body in ${API_BODY}; curl itself failing (DNS, TLS, ...)
"${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases/tags/${TAG}" \ # aborts the script with curl's own message.
| python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])')" gitea_api() {
local method="$1" path="$2"
shift 2
API_STATUS="$(curl -sS -o "${API_BODY}" -w '%{http_code}' -X "${method}" \
-H "Authorization: Bearer ${GITEA_TOKEN}" "$@" \
"${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}${path}")"
}
# Abort with the HTTP status and the error message Gitea returned
api_fail() {
local message
message="$(python3 -c 'import sys,json; print(json.load(sys.stdin)["message"])' \
< "${API_BODY}" 2>/dev/null || head -c 500 "${API_BODY}")"
echo "Error: $1 failed (HTTP ${API_STATUS}): ${message}" >&2
if [[ "${API_STATUS}" == 401 || "${API_STATUS}" == 403 ]]; then
echo "Check GITEA_TOKEN: it must be valid and have the write:repository scope." >&2
fi
exit 1
}
echo ""
gitea_api GET "/releases/tags/${TAG}"
if [[ "${API_STATUS}" == 200 ]]; then
echo "==> Using existing Gitea release ${TAG}"
elif [[ "${API_STATUS}" == 404 ]]; then
echo "==> Creating Gitea release ${TAG} ..."
gitea_api POST "/releases" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"${TAG}\", \"name\": \"${TAG}\"}"
[[ "${API_STATUS}" == 201 ]] || api_fail "Creating release ${TAG}"
else
api_fail "Looking up release ${TAG}"
fi
RELEASE_ID="$(python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])' < "${API_BODY}")"
echo "==> Uploading assets (release id: ${RELEASE_ID}) ..." echo "==> Uploading assets (release id: ${RELEASE_ID}) ..."
for ARCHIVE in "${ARCHIVES[@]}"; do for ARCHIVE in "${ARCHIVES[@]}"; do
echo " Uploading ${ARCHIVE} ..." echo " Uploading ${ARCHIVE} ..."
curl -sf -X POST \ gitea_api POST "/releases/${RELEASE_ID}/assets" -F "attachment=@${ARCHIVE}"
-H "Authorization: Bearer ${GITEA_TOKEN}" \ [[ "${API_STATUS}" == 201 ]] || api_fail "Uploading ${ARCHIVE}"
-F "attachment=@${ARCHIVE}" \
"${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases/${RELEASE_ID}/assets" > /dev/null
echo " Done." echo " Done."
done done
+42 -15
View File
@@ -68,26 +68,53 @@ fi
: "${GITEA_SERVER_URL:?GITEA_SERVER_URL is required for upload}" : "${GITEA_SERVER_URL:?GITEA_SERVER_URL is required for upload}"
: "${GITEA_REPOSITORY:?GITEA_REPOSITORY is required for upload}" : "${GITEA_REPOSITORY:?GITEA_REPOSITORY is required for upload}"
echo "" API_BODY="$(mktemp)"
echo "==> Creating Gitea release ${TAG} ..." trap 'rm -f "${API_BODY}"' EXIT
curl -sf -X POST \
-H "Authorization: Bearer ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
"${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases" \
-d "{\"tag_name\": \"${TAG}\", \"name\": \"${TAG}\"}" > /dev/null || true
RELEASE_ID="$(curl -sf \ # Call the Gitea repository API. The HTTP status is left in API_STATUS and
-H "Authorization: Bearer ${GITEA_TOKEN}" \ # the response body in ${API_BODY}; curl itself failing (DNS, TLS, ...)
"${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases/tags/${TAG}" \ # aborts the script with curl's own message.
| python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])')" gitea_api() {
local method="$1" path="$2"
shift 2
API_STATUS="$(curl -sS -o "${API_BODY}" -w '%{http_code}' -X "${method}" \
-H "Authorization: Bearer ${GITEA_TOKEN}" "$@" \
"${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}${path}")"
}
# Abort with the HTTP status and the error message Gitea returned
api_fail() {
local message
message="$(python3 -c 'import sys,json; print(json.load(sys.stdin)["message"])' \
< "${API_BODY}" 2>/dev/null || head -c 500 "${API_BODY}")"
echo "Error: $1 failed (HTTP ${API_STATUS}): ${message}" >&2
if [[ "${API_STATUS}" == 401 || "${API_STATUS}" == 403 ]]; then
echo "Check GITEA_TOKEN: it must be valid and have the write:repository scope." >&2
fi
exit 1
}
echo ""
gitea_api GET "/releases/tags/${TAG}"
if [[ "${API_STATUS}" == 200 ]]; then
echo "==> Using existing Gitea release ${TAG}"
elif [[ "${API_STATUS}" == 404 ]]; then
echo "==> Creating Gitea release ${TAG} ..."
gitea_api POST "/releases" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"${TAG}\", \"name\": \"${TAG}\"}"
[[ "${API_STATUS}" == 201 ]] || api_fail "Creating release ${TAG}"
else
api_fail "Looking up release ${TAG}"
fi
RELEASE_ID="$(python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])' < "${API_BODY}")"
echo "==> Uploading assets (release id: ${RELEASE_ID}) ..." echo "==> Uploading assets (release id: ${RELEASE_ID}) ..."
for ARCHIVE in "${ARCHIVES[@]}"; do for ARCHIVE in "${ARCHIVES[@]}"; do
echo " Uploading ${ARCHIVE} ..." echo " Uploading ${ARCHIVE} ..."
curl -sf -X POST \ gitea_api POST "/releases/${RELEASE_ID}/assets" -F "attachment=@${ARCHIVE}"
-H "Authorization: Bearer ${GITEA_TOKEN}" \ [[ "${API_STATUS}" == 201 ]] || api_fail "Uploading ${ARCHIVE}"
-F "attachment=@${ARCHIVE}" \
"${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases/${RELEASE_ID}/assets" > /dev/null
echo " Done." echo " Done."
done done
+23 -16
View File
@@ -18,20 +18,28 @@ $ErrorActionPreference = "Stop"
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$RepoRoot = Split-Path -Parent $ScriptDir $RepoRoot = Split-Path -Parent $ScriptDir
# Load .env if present (existing env vars take precedence) # Load .env if present. The values are kept in a script-local table rather
# than written to the process environment: a .ps1 runs inside the caller's
# PowerShell process, so anything set there would outlive the script and
# shadow later edits to .env in the same session.
$DotEnv = @{}
$EnvFile = Join-Path $ScriptDir ".env" $EnvFile = Join-Path $ScriptDir ".env"
if (Test-Path $EnvFile) { if (Test-Path $EnvFile) {
Get-Content $EnvFile | ForEach-Object { Get-Content $EnvFile | ForEach-Object {
if ($_ -match '^\s*#' -or $_ -match '^\s*$') { return } if ($_ -match '^\s*#' -or $_ -notmatch '=') { return }
$parts = $_ -split '=', 2 $parts = $_ -split '=', 2
$key = $parts[0].Trim() $DotEnv[$parts[0].Trim()] = $parts[1].Trim()
$value = $parts[1].Trim()
if (-not [System.Environment]::GetEnvironmentVariable($key)) {
[System.Environment]::SetEnvironmentVariable($key, $value, "Process")
}
} }
} }
# Resolve a setting: an environment variable takes precedence over .env
function Get-Setting([string]$Name) {
$value = [System.Environment]::GetEnvironmentVariable($Name)
if ($value) { return $value }
if ($DotEnv.ContainsKey($Name)) { return $DotEnv[$Name] }
return $null
}
# Determine version # Determine version
if (-not $Version) { if (-not $Version) {
$cargoToml = Get-Content (Join-Path $RepoRoot "Cargo.toml") -Raw $cargoToml = Get-Content (Join-Path $RepoRoot "Cargo.toml") -Raw
@@ -46,23 +54,22 @@ if (-not $Version) {
$Tag = "v$Version" $Tag = "v$Version"
$Target = "x86_64-pc-windows-msvc" $Target = "x86_64-pc-windows-msvc"
Set-Location $RepoRoot
Write-Host "==> Building mdrs $Version for Windows ($Target)" Write-Host "==> Building mdrs $Version for Windows ($Target)"
rustup target add $Target rustup target add $Target
cargo build --release --target $Target cargo build --release --target $Target --manifest-path (Join-Path $RepoRoot "Cargo.toml")
$Archive = "mdrs-$Version-$Target.zip" $Archive = "mdrs-$Version-$Target.zip"
Compress-Archive -Force -Path "target\$Target\release\mdrs.exe" -DestinationPath $Archive $ArchivePath = Join-Path $RepoRoot $Archive
Compress-Archive -Force -Path (Join-Path $RepoRoot "target\$Target\release\mdrs.exe") -DestinationPath $ArchivePath
# Written alongside the archive so `mdrs selfupdate` can check what it fetched. # Written alongside the archive so `mdrs selfupdate` can check what it fetched.
$Checksum = "$Archive.sha256" $Checksum = "$Archive.sha256"
$Hash = (Get-FileHash -Algorithm SHA256 -Path $Archive).Hash.ToLower() $Hash = (Get-FileHash -Algorithm SHA256 -Path $ArchivePath).Hash.ToLower()
"$Hash $Archive" | Set-Content -NoNewline -Encoding ascii $Checksum "$Hash $Archive" | Set-Content -NoNewline -Encoding ascii (Join-Path $RepoRoot $Checksum)
Write-Host " Created: $Archive" Write-Host " Created: $Archive"
# Upload to Gitea if token is provided # Upload to Gitea if token is provided
$GiteaToken = [System.Environment]::GetEnvironmentVariable("GITEA_TOKEN") $GiteaToken = Get-Setting "GITEA_TOKEN"
if (-not $GiteaToken) { if (-not $GiteaToken) {
Write-Host "" Write-Host ""
Write-Host "GITEA_TOKEN not set -- skipping upload." Write-Host "GITEA_TOKEN not set -- skipping upload."
@@ -70,8 +77,8 @@ if (-not $GiteaToken) {
exit 0 exit 0
} }
$ServerUrl = [System.Environment]::GetEnvironmentVariable("GITEA_SERVER_URL") $ServerUrl = Get-Setting "GITEA_SERVER_URL"
$Repository = [System.Environment]::GetEnvironmentVariable("GITEA_REPOSITORY") $Repository = Get-Setting "GITEA_REPOSITORY"
if (-not $ServerUrl) { Write-Error "GITEA_SERVER_URL is required for upload"; exit 1 } if (-not $ServerUrl) { Write-Error "GITEA_SERVER_URL is required for upload"; exit 1 }
if (-not $Repository) { Write-Error "GITEA_REPOSITORY is required for upload"; exit 1 } if (-not $Repository) { Write-Error "GITEA_REPOSITORY is required for upload"; exit 1 }