Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,33 @@ jobs:
- name: Analyze
shell: pwsh
run: fvm flutter analyze --no-fatal-infos

- name: Validate PowerShell Scripts
shell: pwsh
run: |
$parseErrors = @(
Get-ChildItem -Path scripts, installer -Filter "*.ps1" -File | ForEach-Object {
$tokens = $null
$parseFileErrors = $null
[void][System.Management.Automation.Language.Parser]::ParseFile(
$_.FullName,
[ref]$tokens,
[ref]$parseFileErrors
)
$parseFileErrors
}
)

if ($parseErrors.Count -gt 0) {
throw "PowerShell parse errors: $($parseErrors.Message -join ', ')"
}

- name: Check Bundled Wall Heights
shell: pwsh
run: fvm dart run tool/check_bundled_wall_heights.dart
- name: Test Release Signing Gates
shell: powershell
run: ./scripts/test_release_signing.ps1
- name: Run Tests
shell: pwsh
run: fvm flutter test
85 changes: 81 additions & 4 deletions .github/workflows/release-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ on:

permissions:
contents: write
id-token: write

jobs:
build:
Expand All @@ -54,6 +55,11 @@ jobs:
with:
fetch-depth: 0

- name: Require Main Branch
if: ${{ github.ref != 'refs/heads/main' }}
shell: pwsh
run: throw "Signed desktop releases must be dispatched from main."

- uses: dart-lang/setup-dart@v1

- name: Add Pub Cache To PATH
Expand All @@ -69,18 +75,89 @@ jobs:
shell: pwsh
run: fvm install

- name: Run Desktop Release Script
- name: Build Windows Release Binaries
shell: pwsh
env:
POSTHOG_PROJECT_TOKEN: ${{ secrets.POSTHOG_PROJECT_TOKEN }}
run: |
powershell -ExecutionPolicy Bypass -File scripts/release_desktop.ps1 -Phase build -VersionBump "${{ inputs.version_bump }}" -Channel "${{ inputs.channel }}"

- name: Sign In To Azure With OIDC
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

- name: Sign Windows Release Binaries
uses: azure/artifact-signing-action@v2
with:
endpoint: ${{ vars.AZURE_ARTIFACT_SIGNING_ENDPOINT }}
signing-account-name: ${{ vars.AZURE_ARTIFACT_SIGNING_ACCOUNT }}
certificate-profile-name: ${{ vars.AZURE_ARTIFACT_SIGNING_PROFILE }}
files-folder: ${{ github.workspace }}\build\windows\x64\runner\Release
files-folder-filter: exe,dll
files-folder-recurse: true
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
exclude-environment-credential: true
exclude-workload-identity-credential: true
exclude-managed-identity-credential: true
exclude-shared-token-cache-credential: true
exclude-visual-studio-credential: true
exclude-visual-studio-code-credential: true
exclude-azure-cli-credential: false
exclude-azure-powershell-credential: true
exclude-azure-developer-cli-credential: true
exclude-interactive-browser-credential: true

- name: Build Signed Updater Archive And Installer
shell: pwsh
run: |
powershell -ExecutionPolicy Bypass -File scripts/release_desktop.ps1 -Phase package -Channel "${{ inputs.channel }}"

- name: Sign Windows Installer
uses: azure/artifact-signing-action@v2
with:
endpoint: ${{ vars.AZURE_ARTIFACT_SIGNING_ENDPOINT }}
signing-account-name: ${{ vars.AZURE_ARTIFACT_SIGNING_ACCOUNT }}
certificate-profile-name: ${{ vars.AZURE_ARTIFACT_SIGNING_PROFILE }}
files-folder: ${{ github.workspace }}\build\installer
files-folder-filter: exe
files-folder-recurse: false
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
exclude-environment-credential: true
exclude-workload-identity-credential: true
exclude-managed-identity-credential: true
exclude-shared-token-cache-credential: true
exclude-visual-studio-credential: true
exclude-visual-studio-code-credential: true
exclude-azure-cli-credential: false
exclude-azure-powershell-credential: true
exclude-azure-developer-cli-credential: true
exclude-interactive-browser-credential: true

- name: Verify Authenticode Signatures
shell: pwsh
run: |
. ./scripts/common_release.ps1
Assert-AuthenticodeSignatures -Path "build/windows/x64/runner/Release"
Assert-AuthenticodeSignatures -Path "build/installer"

- name: Stage And Publish Desktop Release
shell: pwsh
env:
CHANGE_MESSAGE: ${{ inputs.change_message }}
RELEASE_TITLE: ${{ inputs.release_title }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
POSTHOG_PROJECT_TOKEN: ${{ secrets.POSTHOG_PROJECT_TOKEN }}
run: |
$args = @(
"-ExecutionPolicy", "Bypass",
"-File", "scripts/release_desktop.ps1",
"-VersionBump", "${{ inputs.version_bump }}",
"-Phase", "stage",
"-Channel", "${{ inputs.channel }}",
"-ChangeMessage", $env:CHANGE_MESSAGE
)
Expand Down Expand Up @@ -128,7 +205,7 @@ jobs:
Write-Host "https://sunkenintime.github.io/icarus/updates/windows/$channel/app-archive.json"

- name: Commit Version And Metadata Changes
if: ${{ github.ref_type == 'branch' }}
if: ${{ inputs.publish_pages && github.ref_type == 'branch' }}
shell: pwsh
run: |
$changes = git status --porcelain -- pubspec.yaml lib/const/settings.dart release/metadata
Expand Down
40 changes: 33 additions & 7 deletions docs/release_process.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Use this when you want to publish the direct installer channel.

1. Go to `Actions` in GitHub.
2. Open `Release Desktop`.
3. Click `Run workflow`.
3. Click `Run workflow` and select `main`.
4. Choose:
- `version_bump`: `none` if the version is already correct, otherwise `patch`, `minor`, or `major`
- `channel`: `stable`
Expand All @@ -51,10 +51,11 @@ Use this when you want to publish the direct installer channel.

## Desktop Prerelease Checklist

Use this when you want to validate updater behavior before shipping to `main`.
Use this to validate updater behavior before publishing to the stable channel.
Signed releases run from `main`, matching the Azure federated credential.

1. Checkout branch `update/prerelease`.
2. Push the updater changes you want to validate.
1. Merge the reviewed changes into `main`.
2. Select `main` as the workflow branch.
3. Go to `Actions` in GitHub.
4. Open `Release Desktop`.
5. Click `Run workflow`.
Expand All @@ -73,7 +74,7 @@ Use this when you want to validate updater behavior before shipping to `main`.
- app exits for restart
- relaunched app is the new version
- second cold launch still shows the new version
10. After validation, merge/fix as needed and publish stable from `main`.
10. After validation, publish stable from `main`.

## Store Release Checklist

Expand All @@ -96,7 +97,7 @@ Use this when you want to publish the Microsoft Store channel.
- Desktop-only update:
- Run `Release Desktop` only.
- Desktop prerelease validation:
- Use branch `update/prerelease`.
- Use branch `main`.
- Run `Release Desktop` with `channel=prerelease`.
- After validation, rerun desktop release on `main` with `channel=stable`.
- Store-only update:
Expand All @@ -107,9 +108,34 @@ Use this when you want to publish the Microsoft Store channel.
## Notes

- Local prerelease publish:
- `scripts/publish_prerelease_local.ps1` pushes the staged site content to `gh-pages`.
- `scripts/publish_prerelease_local.ps1` cannot publish an unsigned build. Use `Release Desktop` on `main` with `channel=prerelease` for signing and publication.
- The shared scripts verify EXE and DLL signatures before packaging, staging, and pushing Pages content. Manual phased releases require signing between build and package, then signing the installer before stage.
- GitHub Pages should be configured to serve `gh-pages` from `/ (root)`.
- No extra Pages deploy workflow is needed for prerelease testing.
- Direct desktop installs now use a per-user install path and per-user registry registration.
- Store installs should continue to use the Microsoft Store update path only.
- The metadata file should not be a generic `template.json` in the live metadata folder, because the manifest generator treats every JSON file there as a real release entry.

## Azure signing setup and first verification

GitHub repository secrets: `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, and
`AZURE_SUBSCRIPTION_ID`. Repository variables:
`AZURE_ARTIFACT_SIGNING_ENDPOINT`, `AZURE_ARTIFACT_SIGNING_ACCOUNT`, and
`AZURE_ARTIFACT_SIGNING_PROFILE`.

The Azure application needs a federated credential with issuer
`https://token.actions.githubusercontent.com`, audience
`api://AzureADTokenExchange`, and subject
`repo:SunkenInTime/icarus:ref:refs/heads/main`. Assign its service principal the
Artifact Signing Certificate Profile Signer role on the signing profile.
The public trust identity validation and certificate profile must be active.

After merging the signing workflow, first run it on `main` with
`version_bump=none`, `channel=prerelease`, and `publish_pages=false`.
This signs and verifies artifacts without publishing Pages or committing
version/metadata changes. Download the installer artifact, check its expected
publisher in Windows, and test installation and launch. Then publish a
prerelease and test updating an older prerelease installation before stable.

A green PR check validates code and the signature rejection gates. It does not
prove Azure login, signing permissions, or an end-to-end signed release works.
Loading
Loading