Require git provenance for Android Argus staging
This commit is contained in:
@@ -16,6 +16,9 @@ local.properties
|
|||||||
argus-signing.local.properties
|
argus-signing.local.properties
|
||||||
signing/
|
signing/
|
||||||
|
|
||||||
|
# Local temporary release scratch files
|
||||||
|
.codex-temp/
|
||||||
|
|
||||||
# Mac .DS_Store files
|
# Mac .DS_Store files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
## Release Checklist
|
## Release Checklist
|
||||||
|
|
||||||
1. Build the stable APK with the same release key material through `QSFERA_RELEASE_KEYSTORE`, `QSFERA_RELEASE_KEYSTORE_PASSWORD`, `QSFERA_RELEASE_KEY_ALIAS`, and `QSFERA_RELEASE_KEY_PASSWORD`.
|
1. Build the stable APK with the same release key material through `QSFERA_RELEASE_KEYSTORE`, `QSFERA_RELEASE_KEYSTORE_PASSWORD`, `QSFERA_RELEASE_KEY_ALIAS`, and `QSFERA_RELEASE_KEY_PASSWORD`.
|
||||||
2. Verify the APK certificate before upload with `apksigner verify --print-certs`.
|
2. Prepare Argus publication only from a clean release branch whose `HEAD` matches its upstream; `Android/scripts/prepare-argus-publication.ps1` enforces this by default and records repository root, branch, upstream, commit, and verification time in `argus-publication.json`. When the Android checkout is used from the pushed `QSfera.git` monorepo, run it from `Android/` with `-GitProvenanceRoot ..`.
|
||||||
3. Publish to Argus only after certificate, package name, version code, and SHA-256 checks pass.
|
3. Verify the APK certificate before upload with `apksigner verify --print-certs`.
|
||||||
4. Verify the published manifest and download with `scripts/verify-production-state.ps1 -VerifyAndroidDownload` from the repository root.
|
4. Publish to Argus only after certificate, package name, version code, SHA-256, and git provenance checks pass.
|
||||||
|
5. Verify the published manifest and download with `scripts/verify-production-state.ps1 -VerifyAndroidDownload` from the repository root.
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ param(
|
|||||||
[string]$Notes,
|
[string]$Notes,
|
||||||
[string]$RepositoryUrl = 'https://git.kusoft.xyz/sevenhill/QSfera.git',
|
[string]$RepositoryUrl = 'https://git.kusoft.xyz/sevenhill/QSfera.git',
|
||||||
[string]$HomepageUrl = 'https://qsfera.kusoft.xyz',
|
[string]$HomepageUrl = 'https://qsfera.kusoft.xyz',
|
||||||
|
[string]$GitProvenanceRoot,
|
||||||
[switch]$Unlisted,
|
[switch]$Unlisted,
|
||||||
[switch]$SkipSigningInitialization
|
[switch]$SkipSigningInitialization,
|
||||||
|
[switch]$SkipGitProvenanceCheck
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
@@ -112,8 +114,83 @@ function Find-FirstTool([string]$AndroidHome, [string]$ToolName) {
|
|||||||
return $tool.FullName
|
return $tool.FullName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Invoke-Git([string[]]$Arguments) {
|
||||||
|
$output = & git @Arguments 2>&1
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "git $($Arguments -join ' ') failed with exit code $LASTEXITCODE. Output: $($output | Out-String)"
|
||||||
|
}
|
||||||
|
return @($output)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Assert-GitReleaseProvenance([string]$RepositoryRoot) {
|
||||||
|
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
|
||||||
|
throw 'git was not found. Argus publication requires git provenance verification.'
|
||||||
|
}
|
||||||
|
|
||||||
|
$previousLocation = Get-Location
|
||||||
|
try {
|
||||||
|
Set-Location $RepositoryRoot
|
||||||
|
|
||||||
|
$insideWorkTree = (Invoke-Git @('rev-parse', '--is-inside-work-tree') | Select-Object -First 1).Trim()
|
||||||
|
if ($insideWorkTree -ne 'true') {
|
||||||
|
throw 'Argus publication must be prepared from inside a git work tree.'
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = Invoke-Git @('status', '--porcelain')
|
||||||
|
if ($status.Count -gt 0) {
|
||||||
|
throw "Git work tree has uncommitted changes. Commit or stash all changes before preparing Argus publication."
|
||||||
|
}
|
||||||
|
|
||||||
|
$branch = (Invoke-Git @('rev-parse', '--abbrev-ref', 'HEAD') | Select-Object -First 1).Trim()
|
||||||
|
if ($branch -eq 'HEAD') {
|
||||||
|
throw 'Git HEAD is detached. Check out the release branch before preparing Argus publication.'
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$upstream = (Invoke-Git @('rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}') | Select-Object -First 1).Trim()
|
||||||
|
} catch {
|
||||||
|
throw "Git branch has no upstream. Push $branch and set its upstream before Argus publication."
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoke-Git @('fetch', '--quiet')
|
||||||
|
|
||||||
|
$aheadBehind = (Invoke-Git @('rev-list', '--left-right', '--count', 'HEAD...@{u}') | Select-Object -First 1).Trim() -split '\s+'
|
||||||
|
$ahead = [int]$aheadBehind[0]
|
||||||
|
$behind = [int]$aheadBehind[1]
|
||||||
|
if ($ahead -ne 0 -or $behind -ne 0) {
|
||||||
|
throw "Git HEAD must match upstream before Argus publication. Branch $branch tracks $upstream, ahead=$ahead, behind=$behind."
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ordered]@{
|
||||||
|
repositoryRoot = (Invoke-Git @('rev-parse', '--show-toplevel') | Select-Object -First 1).Trim()
|
||||||
|
branch = $branch
|
||||||
|
upstream = $upstream
|
||||||
|
commit = (Invoke-Git @('rev-parse', 'HEAD') | Select-Object -First 1).Trim()
|
||||||
|
verifiedAt = [DateTimeOffset]::UtcNow.ToString('O')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Set-Location $previousLocation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||||
Set-Location $repoRoot
|
Set-Location $repoRoot
|
||||||
|
$gitProvenanceRootPath = if ($GitProvenanceRoot) {
|
||||||
|
(Resolve-Path -LiteralPath $GitProvenanceRoot).Path
|
||||||
|
} else {
|
||||||
|
$repoRoot
|
||||||
|
}
|
||||||
|
|
||||||
|
$gitProvenance = if ($SkipGitProvenanceCheck) {
|
||||||
|
[ordered]@{
|
||||||
|
skipped = $true
|
||||||
|
repositoryRoot = $gitProvenanceRootPath
|
||||||
|
reason = 'SkipGitProvenanceCheck was set'
|
||||||
|
verifiedAt = [DateTimeOffset]::UtcNow.ToString('O')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Assert-GitReleaseProvenance $gitProvenanceRootPath
|
||||||
|
}
|
||||||
|
|
||||||
$javaHome = Resolve-JavaHome
|
$javaHome = Resolve-JavaHome
|
||||||
$androidHome = Resolve-AndroidHome
|
$androidHome = Resolve-AndroidHome
|
||||||
@@ -251,6 +328,7 @@ $metadata = [ordered]@{
|
|||||||
notes = if ($Notes) { $Notes } else { $null }
|
notes = if ($Notes) { $Notes } else { $null }
|
||||||
repositoryUrl = if ($RepositoryUrl) { $RepositoryUrl } else { $null }
|
repositoryUrl = if ($RepositoryUrl) { $RepositoryUrl } else { $null }
|
||||||
homepageUrl = if ($HomepageUrl) { $HomepageUrl } else { $null }
|
homepageUrl = if ($HomepageUrl) { $HomepageUrl } else { $null }
|
||||||
|
git = $gitProvenance
|
||||||
androidPackageName = $packageName
|
androidPackageName = $packageName
|
||||||
androidVersionCode = [int]$versionCode
|
androidVersionCode = [int]$versionCode
|
||||||
targetSdkVersion = [int]$targetSdk
|
targetSdkVersion = [int]$targetSdk
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ Date: 2026-06-08.
|
|||||||
- Android release builds now fail fast when release signing variables are missing, so unsigned release artifacts cannot be published by accident.
|
- Android release builds now fail fast when release signing variables are missing, so unsigned release artifacts cannot be published by accident.
|
||||||
- Android version was raised to `1.3.12` with `versionCode` `40`.
|
- Android version was raised to `1.3.12` with `versionCode` `40`.
|
||||||
- Android now has QSfera-specific Argus publishing scripts: local upload-keystore generation, signed APK staging, APK metadata verification, debug-certificate rejection, and Argus upload.
|
- Android now has QSfera-specific Argus publishing scripts: local upload-keystore generation, signed APK staging, APK metadata verification, debug-certificate rejection, and Argus upload.
|
||||||
|
- Android Argus staging now enforces release git provenance by default: the work tree must be clean, the branch must have an upstream, `git fetch` must succeed, and `HEAD` must match `@{u}` before a stable APK is prepared for upload. The generated `argus-publication.json` records the verified repository root, branch, upstream, commit, and timestamp; current monorepo publication can verify the pushed root `QSfera.git` branch by running from `Android/` with `-GitProvenanceRoot ..`.
|
||||||
- Android `1.3.9`/`37` was published to Argus as `qsfera-mobile` on 2026-06-10. Argus upload returned release id `53ecc24e-106b-43a5-a39e-ffacd72a43f8`, package size `12226572`, and SHA-256 `cbd2c25328e395ea4da4e0f582feac39dec18d2d47b3e105d344904d8a24f017`.
|
- Android `1.3.9`/`37` was published to Argus as `qsfera-mobile` on 2026-06-10. Argus upload returned release id `53ecc24e-106b-43a5-a39e-ffacd72a43f8`, package size `12226572`, and SHA-256 `cbd2c25328e395ea4da4e0f582feac39dec18d2d47b3e105d344904d8a24f017`.
|
||||||
- Android `1.3.10`/`38` was published to Argus as `qsfera-mobile` on 2026-06-10. Argus upload returned release id `8465ba6d-a008-45f5-9c70-e6cd2c94317a`, package size `12228464`, and SHA-256 `ce33a73ce122de6c9c797e7cb552e5493b628c60ddc98fda6a9e230e3c1ed7fe`.
|
- Android `1.3.10`/`38` was published to Argus as `qsfera-mobile` on 2026-06-10. Argus upload returned release id `8465ba6d-a008-45f5-9c70-e6cd2c94317a`, package size `12228464`, and SHA-256 `ce33a73ce122de6c9c797e7cb552e5493b628c60ddc98fda6a9e230e3c1ed7fe`.
|
||||||
- Android `1.3.11`/`39` was published to Argus as `qsfera-mobile` on 2026-06-10. Argus upload returned release id `3dbac5ad-b19c-46e6-b24f-8f22e1c466dd`, package size `12234884`, and SHA-256 `c9eae41bbaae421e1a69721e891d8431ce599684e0f655b71693a422aa83720a`.
|
- Android `1.3.11`/`39` was published to Argus as `qsfera-mobile` on 2026-06-10. Argus upload returned release id `3dbac5ad-b19c-46e6-b24f-8f22e1c466dd`, package size `12234884`, and SHA-256 `c9eae41bbaae421e1a69721e891d8431ce599684e0f655b71693a422aa83720a`.
|
||||||
|
|||||||
Reference in New Issue
Block a user