name: Release distribution preflight
on:
push:
branches:
- ship/core3-release-readiness-2026-06-24
workflow_dispatch:
inputs:
channel:
description: Fleet/app-store channel value to validate
required: true
default: both
type: choice
options:
- canary
- stable
- both
- none
prerelease:
description: Treat this as a prerelease credential check
required: true
default: 'false'
type: choice
options:
- 'false'
- 'true'
permissions:
contents: read
concurrency:
group: release-distribution-preflight
cancel-in-progress: false
jobs:
preflight:
runs-on: ubuntu-latest
env:
UMBREL_STORE_TOKEN: ${{ secrets.UMBREL_STORE_TOKEN }}
UMBREL_OFFICIAL_PR_TOKEN: ${{ secrets.UMBREL_OFFICIAL_PR_TOKEN }}
UMBREL_OFFICIAL_FORK: ${{ secrets.UMBREL_OFFICIAL_FORK }}
ECOSYSTEM_CONSUMER_TOKEN: ${{ secrets.ECOSYSTEM_CONSUMER_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
STARTOS_DEVELOPER_KEY_PEM: ${{ secrets.STARTOS_DEVELOPER_KEY_PEM }}
STARTOS_REGISTRY_URL: ${{ secrets.STARTOS_REGISTRY_URL }}
FLEET_SSH_PRIVATE_KEY: ${{ secrets.FLEET_SSH_PRIVATE_KEY }}
FLEET_ROLLOUT_TIMEOUT_MS: ${{ vars.FLEET_ROLLOUT_TIMEOUT_MS }}
steps:
- name: Checkout HiveRelay
uses: actions/checkout@v7
- name: Resolve preflight inputs
id: rel
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
DISPATCH_CHANNEL: ${{ inputs.channel }}
DISPATCH_PRERELEASE: ${{ inputs.prerelease }}
run: |
set -euo pipefail
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
channel="$DISPATCH_CHANNEL"
prerelease="$DISPATCH_PRERELEASE"
else
channel=none
prerelease=true
fi
echo "channel=$channel" >> "$GITHUB_OUTPUT"
echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT"
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 22
- name: Validate masked release distribution values
run: |
set -euo pipefail
node scripts/check-release-distribution-env.mjs \
--channel "${{ steps.rel.outputs.channel }}" \
--prerelease "${{ steps.rel.outputs.prerelease }}" \
--github-env "$GITHUB_ENV"
- name: Summarize preflight
if: ${{ always() }}
run: |
{
echo "### Release distribution preflight"
echo
echo "- Channel: \`${{ steps.rel.outputs.channel }}\`"
echo "- Prerelease: \`${{ steps.rel.outputs.prerelease }}\`"
echo "- Effective channel: \`${HIVERELAY_RELEASE_EFFECTIVE_CHANNEL:-unknown}\`"
echo "- Distribution preflight: \`${HIVERELAY_RELEASE_DISTRIBUTION_PREFLIGHT_STATUS:-unknown}\`"
echo "- Release surfaces: \`${HIVERELAY_RELEASE_SURFACES_STATUS:-not-started}\`"
echo "- npm publish: \`${HIVERELAY_NPM_PUBLISH_STATUS:-not-checked}\`"
echo "- Fleet rollout: \`${HIVERELAY_FLEET_ROLLOUT_STATUS:-not-checked}\`"
echo "- Umbrel community store: \`${HIVERELAY_UMBREL_COMMUNITY_STORE_STATUS:-not-checked}\`"
echo "- Official Umbrel PR: \`${HIVERELAY_UMBREL_OFFICIAL_PR_STATUS:-not-checked}\`"
echo "- Ecosystem app consumers: \`${HIVERELAY_ECOSYSTEM_CONSUMER_STATUS:-not-checked}\`"
echo "- StartOS registry: \`${HIVERELAY_STARTOS_REGISTRY_STATUS:-not-checked}\`"
if [ "${HIVERELAY_RELEASE_DISTRIBUTION_PREFLIGHT_STATUS:-unknown}" = "failed" ]; then
repair_channel="${HIVERELAY_RELEASE_EFFECTIVE_CHANNEL:-${{ steps.rel.outputs.channel }}}"
case "$repair_channel" in
canary|stable|both) ;;
*) repair_channel=both ;;
esac
echo
echo "#### Repair path"
echo
echo "1. Generate a local candidate file outside the repo with \`npm run release:write-secret-template -- --out /private/tmp/hiverelay-release-secrets.env\`."
echo "2. Replace every \`REPLACE_*\` placeholder, then run \`npm run release:check-distribution-env -- --env-file /private/tmp/hiverelay-release-secrets.env --channel ${repair_channel} --prerelease false\`."
echo "3. Dry-run rotation with \`npm run release:apply-github-secrets -- --repo ${GITHUB_REPOSITORY} --env-file /private/tmp/hiverelay-release-secrets.env --dry-run\`."
echo "4. Apply rotation with \`npm run release:apply-github-secrets -- --repo ${GITHUB_REPOSITORY} --env-file /private/tmp/hiverelay-release-secrets.env\`, then rerun this workflow."
echo
echo "See \`docs/RELEASE_AUTOMATION.md#repository-secret-setup\`."
fi
} >> "$GITHUB_STEP_SUMMARY"