From 9ba897d88ea6a939df8a8de40dc6d0b5fe9236cd Mon Sep 17 00:00:00 2001 From: IlyaZar Date: Thu, 10 Sep 2026 15:09:14 +0200 Subject: [PATCH] ci: guard webui release handoff - prevent publication with invalid app credentials - document repeatable activation and release checks --- .github/workflows/release.yml | 28 ++++++++++++++- RELEASES.md | 64 ++++++++++++++++++++++++++++------- 2 files changed, 79 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1248738..0112927 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,12 +6,19 @@ on: tags: ['v*'] pull_request: workflow_dispatch: + inputs: + verify_handoff: + description: Verify the plugin handoff credentials without releasing + required: false + default: false + type: boolean permissions: contents: read jobs: test: + if: github.event_name != 'workflow_dispatch' || !inputs.verify_handoff runs-on: ubuntu-latest timeout-minutes: 15 steps: @@ -54,8 +61,26 @@ jobs: name: browser-failures path: test-results/ + handoff-credentials: + if: startsWith(github.ref, 'refs/tags/v') || inputs.verify_handoff + runs-on: ubuntu-latest + steps: + - name: Create plugin installation token + uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 + id: app + with: + app-id: ${{ vars.SYNCSHELL_APP_ID }} + private-key: ${{ secrets.SYNCSHELL_APP_PRIVATE_KEY }} + owner: omarchy-QOL + repositories: syncshell + permission-contents: write + - name: Verify plugin repository access + env: + GH_TOKEN: ${{ steps.app.outputs.token }} + run: test "$(gh api repos/omarchy-QOL/syncshell --jq .full_name)" = "omarchy-QOL/syncshell" + publish: - needs: test + needs: [test, handoff-credentials] if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest permissions: @@ -87,6 +112,7 @@ jobs: private-key: ${{ secrets.SYNCSHELL_APP_PRIVATE_KEY }} owner: omarchy-QOL repositories: syncshell + permission-contents: write - name: Notify pinned plugin update env: GH_TOKEN: ${{ steps.app.outputs.token }} diff --git a/RELEASES.md b/RELEASES.md index be3c41b..be205ac 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -8,8 +8,8 @@ clean checkout: ```sh npm ci npm test -npm run release -- 0.1.2 -bash scripts/test-release.sh "$PWD/release/syncshell-webui-v0.1.2.tar.gz" +npm run release -- 0.1.3 +bash scripts/test-release.sh "$PWD/release/syncshell-webui-v0.1.3.tar.gz" ``` The release command rebuilds from committed source. The archive records its @@ -28,18 +28,45 @@ files. Only add a Syncthing version to the manifest after testing it. Expected Web repository: `syncshell/syncshell-webui`. Current plugin repository: `omarchy-QOL/syncshell`. -Create a GitHub App with repository Contents and Pull requests write -permissions. Install it only on the plugin repository. Store its private key -as `SYNCSHELL_APP_PRIVATE_KEY` and its ID as repository variable -`SYNCSHELL_APP_ID` in both repositories. The Web workflow requests a plugin -installation token solely to send the release notification. The plugin uses -its token to push the update branch and open the pull request. +Create a private GitHub App owned by `omarchy-QOL` with repository Contents +and Pull requests write permissions. Disable webhooks and install it only on +`omarchy-QOL/syncshell`. Store its private key as +`SYNCSHELL_APP_PRIVATE_KEY` and its ID as repository variable +`SYNCSHELL_APP_ID` in both repositories. The private key does not expire; only +replace it after a compromise or deliberate rotation. + +Configure both repositories from the machine holding the downloaded PEM: + +```sh +app_id=REPLACE_WITH_APP_ID +app_key_file=/absolute/path/to/syncshell-release.pem +for repo in syncshell/syncshell-webui omarchy-QOL/syncshell; do + gh variable set SYNCSHELL_APP_ID --repo "$repo" --body "$app_id" + gh secret set SYNCSHELL_APP_PRIVATE_KEY --repo "$repo" \ + <"$app_key_file" +done +``` + +Keep the PEM secure until both credential checks pass. GitHub stores only the +public half of an App key, so a lost private key must be replaced rather than +downloaded again. + +The Web workflow requests a one-hour plugin installation token solely to send +the release notification. The plugin requests a separate one-hour token to +push the update branch and open the pull request. Each workflow narrows its +token to the permissions needed by that job. The plugin's `update-webui.yml` must exist on its default branch for `repository_dispatch` delivery, even though updates target `dev`. Landing that workflow is an activation prerequisite. Repository Actions policies must allow the pinned actions and the GitHub App's operations. +Before the first release, manually run the Web repository's `webui` workflow +with `verify_handoff` enabled. Then run the plugin repository's `update webui` +workflow with `verify_only` enabled. These checks validate both copies of the +App ID and private key without publishing a release or changing either +repository. + On Web pull requests and branch pushes, CI builds, packages and tests. A `vX.Y.Z` tag matching `package.json` publishes the tested archive through GitHub Releases. Then it sends the exact version and checksum to the plugin. @@ -51,10 +78,23 @@ The plugin importer is `go -C core run ./cmd/import-webui`, using its existing Go module. It validates the archive and atomically exchanges the bundle directory on Linux. No second importer implementation is supported. -No workflow merges to `main` or publishes a plugin release. A failed handoff -leaves the Web release available. Retry the notification job or run the -plugin update workflow manually with the published version and checksum. -Published tags and archives must remain unchanged; fixes use a new version. +No workflow merges the update pull request or publishes a plugin release. The +pull request remains open against `dev` until a maintainer merges or closes +it. A failed handoff leaves the Web release available. Retry the notification +job or run the plugin update workflow manually with the published version and +checksum. Published tags and archives must remain unchanged; fixes use a new +version. + +## Release procedure + +1. Merge the plugin receiver workflow into its default branch. +2. Configure and verify the GitHub App in both repositories. +3. Merge the tested Web changes into `main` and set the stable package version. +4. Run the local packaging and archive test from a clean checkout. +5. Push the matching annotated tag, for example `v0.1.3`. +6. Confirm the Web release succeeds and the plugin update pull request opens + against `dev`. +7. Review the plugin pull request checks and leave it open until approved. ## Theme contract 1