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
37 changes: 37 additions & 0 deletions .github/DESKTOP_NIGHTLY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->

# Desktop Nightly

Desktop Nightly is an ephemeral developer snapshot, not an Apache release. It builds the current `main` commit every day so contributors can try recent Desktop changes and report problems without waiting for an ASF source-release vote.

The workflow gives each snapshot an immutable version such as `0.2.0-dev.20260829.42`. The download page records its exact source commit. A packaged Nightly accepts updates only from `https://nightlies.apache.org/maka/desktop/`, accepts only newer `dev` versions, and verifies that downloaded bytes were attested by `.github/workflows/desktop-nightly.yml` on `main`. A formal Desktop build continues to use the GitHub Release feed and the formal product-release attestation identity.

Nightly currently uses the same application identity as the formal Desktop. Installing it replaces the existing Maka installation rather than creating a second side-by-side app. Its user data remains in the same location. Testers who need the formal build should reinstall that build before returning to the formal channel.

## One-time setup

1. Ask Apache Infra to allow `apache/maka` to publish GitHub Actions output to `nightlies.apache.org`, provide the SSH `known_hosts` entry through an authenticated channel, and confirm whether retention is service-managed or requires a separate project cleanup job. Do not enable scheduled publication until that retention owner is explicit.
2. Create a GitHub Environment named `nightly` that permits only `main`. Store `NIGHTLIES_RSYNC_PATH`, `NIGHTLIES_RSYNC_HOST`, `NIGHTLIES_RSYNC_PORT`, `NIGHTLIES_RSYNC_USER`, `NIGHTLIES_RSYNC_KEY`, and the Infra-verified `NIGHTLIES_RSYNC_KNOWN_HOSTS` value as Environment secrets. Configure its macOS signing and notarization secrets: `CSC_LINK`, `CSC_KEY_PASSWORD`, `APPLE_API_KEY`, `APPLE_API_KEY_ID`, and `APPLE_API_ISSUER`. Do not expose these secrets to repository-wide or pull-request workflows.
3. Merge the Nightly workflow while it is disabled. After Infra publishing and the Environment secrets are ready, set the repository variable `DESKTOP_NIGHTLY_ENABLED` to `true` and run `Desktop Nightly` manually once.
4. Verify the download page, `latest-mac.yml`, and `latest.yml` under `https://nightlies.apache.org/maka/desktop/`, install both platform artifacts on clean machines, and confirm one automatic update before sharing the channel with testers.

The scheduled run starts at 18:17 UTC. It audits the shipped dependency closure, builds and verifies macOS arm64 and Windows x64 artifacts, issues and locally verifies Sigstore provenance, appends a new immutable version directory, and advances the mutable update metadata last. A failure before publication leaves both existing platform feeds untouched. Each platform feed file is replaced independently after its complete payload exists, so an interrupted feed transfer may temporarily leave macOS and Windows on different valid Nightly versions. Do not rerun a failed workflow attempt in place; start a fresh manual run so it receives a new version. Historical payload cleanup is separate from publication, targets the Nightlies retention policy, and must never rewrite a published version or delete one referenced by a feed. Apache Nightlies storage is temporary; it must not be used as a formal release archive.

Remote Runtime Host setup still follows the package identity embedded in the repository manifests. A Nightly does not publish a matching npm package, so clean remote setup is outside this channel until that dependency has its own reviewed snapshot distribution contract.
308 changes: 308 additions & 0 deletions .github/workflows/desktop-nightly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

name: Desktop Nightly

on:
schedule:
- cron: '17 18 * * *'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: desktop-nightly
cancel-in-progress: false

jobs:
identity:
if: vars.DESKTOP_NIGHTLY_ENABLED == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 10
outputs:
source_commit: ${{ steps.identity.outputs.source_commit }}
version: ${{ steps.identity.outputs.version }}
steps:
- name: Reject in-place workflow reruns
if: github.run_attempt != 1
run: |
echo "Desktop Nightly retries require a fresh workflow dispatch" >&2
exit 1

- name: Check out the scheduled main commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.sha }}
persist-credentials: false

- name: Require the Apache main branch
run: |
test "$GITHUB_REPOSITORY" = apache/maka
test "$GITHUB_REF" = refs/heads/main

- name: Resolve the immutable Nightly identity
id: identity
env:
GITHUB_SHA: ${{ github.sha }}
GITHUB_RUN_NUMBER: ${{ github.run_number }}
run: node scripts/desktop-nightly.mjs identity

desktop:
needs: identity
strategy:
fail-fast: false
matrix:
include:
- platform: macos
runner: macos-15
- platform: windows
runner: windows-2025
runs-on: ${{ matrix.runner }}
environment: nightly
timeout-minutes: 75
defaults:
run:
shell: bash
env:
MAKA_DESKTOP_NIGHTLY_VERSION: ${{ needs.identity.outputs.version }}
steps:
- name: Reject in-place workflow reruns
if: github.run_attempt != 1
run: |
echo "Desktop Nightly retries require a fresh workflow dispatch" >&2
exit 1

- name: Check out the exact Nightly source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.identity.outputs.source_commit }}
persist-credentials: false

- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'
cache: npm

- name: Install dependencies
run: npm ci

- name: Audit the shipped Desktop dependency closure
run: |
npm audit --omit=dev --audit-level=moderate
node scripts/audit-shipped-dependencies.mjs

- name: Update stable Rust for native Desktop artifacts
run: rustup update stable --no-self-update

- name: Write the App Store Connect API key
if: matrix.platform == 'macos'
env:
APPLE_API_KEY_CONTENT: ${{ secrets.APPLE_API_KEY }}
APPLE_API_KEY_PATH: ${{ runner.temp }}/AuthKey_Maka.p8
run: |
umask 077
printf '%s' "$APPLE_API_KEY_CONTENT" > "$APPLE_API_KEY_PATH"

- name: Package the signed and notarized macOS Nightly
if: matrix.platform == 'macos'
env:
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
APPLE_API_KEY: ${{ runner.temp }}/AuthKey_Maka.p8
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
run: npm run package:macos-arm64

- name: Notarize and verify the macOS Nightly
if: matrix.platform == 'macos'
env:
APPLE_API_KEY: ${{ runner.temp }}/AuthKey_Maka.p8
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
DMG_PATH: apps/desktop/release/Maka-${{ needs.identity.outputs.version }}-mac-arm64.dmg
run: |
xcrun notarytool submit "$DMG_PATH" \
--key "$APPLE_API_KEY" \
--key-id "$APPLE_API_KEY_ID" \
--issuer "$APPLE_API_ISSUER" \
--wait
xcrun stapler staple "$DMG_PATH"
npm run verify:macos-arm64 -- "$DMG_PATH"

- name: Package the Windows Nightly
if: matrix.platform == 'windows'
run: npm run package:windows-x64

- name: Verify the Windows Nightly
if: matrix.platform == 'windows'
run: >-
npm run verify:windows-x64 --
"apps/desktop/release/Maka-${{ needs.identity.outputs.version }}-win-x64.exe"

- name: Stage the exact Nightly artifacts
env:
NIGHTLY_VERSION: ${{ needs.identity.outputs.version }}
STAGE_DIRECTORY: ${{ runner.temp }}/desktop-nightly
run: |
mkdir -p "$STAGE_DIRECTORY"
if [[ "${{ matrix.platform }}" = macos ]]; then
cp -- \
"apps/desktop/release/Maka-$NIGHTLY_VERSION-mac-arm64.dmg" \
"apps/desktop/release/Maka-$NIGHTLY_VERSION-mac-arm64.zip" \
"apps/desktop/release/Maka-$NIGHTLY_VERSION-mac-arm64.zip.blockmap" \
apps/desktop/release/latest-mac.yml \
"$STAGE_DIRECTORY/"
else
cp -- \
"apps/desktop/release/Maka-$NIGHTLY_VERSION-win-x64.exe" \
"apps/desktop/release/Maka-$NIGHTLY_VERSION-win-x64.exe.blockmap" \
"apps/desktop/release/Maka-$NIGHTLY_VERSION-win-x64.zip" \
apps/desktop/release/latest.yml \
"$STAGE_DIRECTORY/"
fi

- name: Preserve the verified Nightly artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: desktop-nightly-${{ matrix.platform }}
path: ${{ runner.temp }}/desktop-nightly
if-no-files-found: error
compression-level: 0
retention-days: 30

publish:
needs: [identity, desktop]
runs-on: ubuntu-24.04
environment: nightly
timeout-minutes: 20
permissions:
artifact-metadata: write
attestations: write
contents: read
id-token: write
steps:
- name: Reject in-place workflow reruns
if: github.run_attempt != 1
run: |
echo "Desktop Nightly retries require a fresh workflow dispatch" >&2
exit 1

- name: Check out the Nightly publisher
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.identity.outputs.source_commit }}
persist-credentials: false

- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'
cache: npm

- name: Install publisher dependencies
run: npm ci --ignore-scripts

- name: Download both verified Desktop builds
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: desktop-nightly-*
path: ${{ github.workspace }}/.nightly-input
merge-multiple: true

- name: Stage the versioned Nightly site
env:
NIGHTLY_VERSION: ${{ needs.identity.outputs.version }}
SOURCE_COMMIT: ${{ needs.identity.outputs.source_commit }}
run: |
node scripts/desktop-nightly.mjs stage \
"$GITHUB_WORKSPACE/.nightly-input" \
"$GITHUB_WORKSPACE/.nightly-publish" \
"$NIGHTLY_VERSION" \
"$SOURCE_COMMIT"

- name: Attest the exact Nightly payloads
id: attest
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-path: ${{ github.workspace }}/.nightly-publish/versions/${{ needs.identity.outputs.version }}/*

- name: Verify the issued Nightly provenance
env:
ATTESTATION_BUNDLE: ${{ steps.attest.outputs.bundle-path }}
CERTIFICATE_IDENTITY: https://github.com/${{ github.repository }}/.github/workflows/desktop-nightly.yml@refs/heads/main
GH_TOKEN: ${{ github.token }}
NIGHTLY_VERSION: ${{ needs.identity.outputs.version }}
run: |
verified=0
while IFS= read -r -d '' artifact; do
gh attestation verify "$artifact" \
--bundle "$ATTESTATION_BUNDLE" \
--repo "$GITHUB_REPOSITORY" \
--cert-identity "$CERTIFICATE_IDENTITY" \
--cert-oidc-issuer https://token.actions.githubusercontent.com
verified=$((verified + 1))
done < <(find "$GITHUB_WORKSPACE/.nightly-publish/versions/$NIGHTLY_VERSION" -maxdepth 1 -type f -print0)
if (( verified == 0 )); then
echo "No Desktop Nightly artifacts were verified" >&2
exit 1
fi

- name: Add the offline provenance bundle
env:
ATTESTATION_BUNDLE: ${{ steps.attest.outputs.bundle-path }}
NIGHTLY_VERSION: ${{ needs.identity.outputs.version }}
run: >-
cp -- "$ATTESTATION_BUNDLE"
"$GITHUB_WORKSPACE/.nightly-publish/versions/$NIGHTLY_VERSION/Maka-$NIGHTLY_VERSION-attestation.sigstore.json"

- name: Prepare authenticated Nightlies SSH transport
env:
NIGHTLIES_RSYNC_HOST: ${{ secrets.NIGHTLIES_RSYNC_HOST }}
NIGHTLIES_RSYNC_KEY: ${{ secrets.NIGHTLIES_RSYNC_KEY }}
NIGHTLIES_RSYNC_KNOWN_HOSTS: ${{ secrets.NIGHTLIES_RSYNC_KNOWN_HOSTS }}
NIGHTLIES_RSYNC_PATH: ${{ secrets.NIGHTLIES_RSYNC_PATH }}
NIGHTLIES_RSYNC_PORT: ${{ secrets.NIGHTLIES_RSYNC_PORT }}
NIGHTLIES_RSYNC_USER: ${{ secrets.NIGHTLIES_RSYNC_USER }}
run: |
test -n "$NIGHTLIES_RSYNC_HOST"
test -n "$NIGHTLIES_RSYNC_KEY"
test -n "$NIGHTLIES_RSYNC_KNOWN_HOSTS"
test -n "$NIGHTLIES_RSYNC_PATH"
test -n "$NIGHTLIES_RSYNC_USER"
[[ "$NIGHTLIES_RSYNC_PORT" =~ ^[0-9]{1,5}$ ]]
(( NIGHTLIES_RSYNC_PORT >= 1 && NIGHTLIES_RSYNC_PORT <= 65535 ))
ssh_directory="$RUNNER_TEMP/nightlies-ssh"
install -m 700 -d "$ssh_directory"
umask 077
printf '%s\n' "$NIGHTLIES_RSYNC_KEY" > "$ssh_directory/key"
printf '%s\n' "$NIGHTLIES_RSYNC_KNOWN_HOSTS" > "$ssh_directory/known_hosts"
{
echo "NIGHTLIES_RSYNC_TARGET=$NIGHTLIES_RSYNC_USER@$NIGHTLIES_RSYNC_HOST:${NIGHTLIES_RSYNC_PATH%/}/maka/desktop"
echo "RSYNC_RSH=ssh -i $ssh_directory/key -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$ssh_directory/known_hosts -p $NIGHTLIES_RSYNC_PORT"
} >> "$GITHUB_ENV"

- name: Publish immutable Nightly payloads
run: rsync -rlptDvz --protect-args .nightly-publish/versions/ "$NIGHTLIES_RSYNC_TARGET/versions/"

- name: Advance the Nightly update feed last
run: rsync -rlptDvz --protect-args .nightly-publish/feed/ "$NIGHTLIES_RSYNC_TARGET/"

- name: Remove the temporary Nightlies credentials
if: always()
run: rm -f "$RUNNER_TEMP/nightlies-ssh/key" "$RUNNER_TEMP/nightlies-ssh/known_hosts"
Loading