diff --git a/.github/workflows/release-hooks.yml b/.github/workflows/release-hooks.yml new file mode 100644 index 00000000..1bb163e3 --- /dev/null +++ b/.github/workflows/release-hooks.yml @@ -0,0 +1,77 @@ +name: Release hooks + +# After a Release succeeds, tells the repositories that follow Querya's +# releases: +# +# QueryaHub/extensions re-tests every registered extension against the +# new version and refreshes compat.json +# QueryaHub/extension-template opens a pull request that raises the test +# database image tag and adds the version to the +# CI matrix +# +# The querya-testdb-* images and querya-headless are published by their own +# workflows on the same Release. The dev container Feature does not follow +# releases: it downloads the newest tester when it is installed. +# +# `workflow_run`, not `release`: the release is created with GITHUB_TOKEN, and +# events made by that token do not start other workflows. A repository_dispatch +# into another repository needs a token that can write there, so this needs the +# secret QUERYA_DISPATCH_TOKEN (see docs/extension-registry.md); without it the +# job only says so. +on: + workflow_run: + workflows: [Release] + types: [completed] + workflow_dispatch: + inputs: + version: + description: Querya Desktop release to announce (e.g. 0.5.0) + required: true + +permissions: + contents: read + +jobs: + dispatch: + name: Tell the registry and the template + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' + steps: + - name: Resolve the version + id: v + shell: bash + run: | + set -euo pipefail + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + VERSION="${{ inputs.version }}" + else + # A tag push has the tag as head_branch. + VERSION="${{ github.event.workflow_run.head_branch }}" + fi + VERSION="${VERSION#v}" + if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::notice::'$VERSION' is not a release tag, nothing to announce" + echo "announce=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "announce=true" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Dispatch + if: steps.v.outputs.announce == 'true' + shell: bash + env: + GH_TOKEN: ${{ secrets.QUERYA_DISPATCH_TOKEN }} + VERSION: ${{ steps.v.outputs.version }} + run: | + set -euo pipefail + if [ -z "$GH_TOKEN" ]; then + echo "::warning::QUERYA_DISPATCH_TOKEN is not set, so the registry and the template were not told about $VERSION" + exit 0 + fi + for repo in QueryaHub/extensions QueryaHub/extension-template; do + gh api "repos/${repo}/dispatches" -X POST \ + -f event_type=querya-release \ + -f "client_payload[version]=${VERSION}" + echo "told ${repo} about ${VERSION}" + done diff --git a/CHANGELOG.md b/CHANGELOG.md index 80546183..990fec34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **`querya-ext-tester --strict`** — warnings fail the run (exit 1), for example a recommended method that errors or an OS sandbox that could not be applied; with `--sandbox` in CI a green run now means the plugin really ran isolated. The `test-extension` action has a matching `strict` input. +- **Extension registry** — `StaticRegistryRepository` reads a static `index.json` published by [QueryaHub/extensions](https://github.com/QueryaHub/extensions) (GitHub Pages): yanked entries, non-GitHub-release URLs and extensions the running version is too old for are hidden, the index is cached, installs keep the SHA-256 / archive / sandbox checks. Not wired into the Marketplace page yet. Also new for extension authors: the `ghcr.io/queryahub/features/querya-ext-tester` Dev Container Feature, `querya-testdb-*` test database images, and the [extension template](https://github.com/QueryaHub/extension-template). See [docs/extension-registry.md](docs/extension-registry.md). +- **Release hooks** — after a Release the registry and the template are told about the new version (`repository_dispatch`); needs the `QUERYA_DISPATCH_TOKEN` secret. + ### Fixed - **In-app update of a package install (AUR, `.deb`, `.rpm`)** — the updater no longer downloads a zip and quits when the app lives in `/opt/querya-desktop` (owned by root): the update dialog says how to update it (`yay -Syu querya-desktop`, `apt`, `dnf`) with a *Copy update command* button, or offers the release page for any other folder the user cannot write. If an in-place replace fails anyway, the app is started again and the reason is logged to `~/.local/state/querya-update.log`, instead of staying closed. diff --git a/docs/README.md b/docs/README.md index 30f164df..808f2568 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,6 +9,7 @@ Index of Querya Desktop documentation, grouped by audience. - [Security / local data](security.md) — where metadata and secrets are stored. - [MCP server](mcp-server.md) — let Claude Desktop, Cursor, VS Code and other AI clients read shared connections (read-only). - [Testing extensions in CI](extension-testing.md) — `querya-ext-tester`, release binaries and the GitHub Action. +- [Extension registry](extension-registry.md) — from an author's machine to the Marketplace: the template, test databases, the registry's CI and index, and how it follows Querya releases. ## For contributors diff --git a/docs/dev-containers.md b/docs/dev-containers.md index 56bbd796..dc6a2bde 100644 --- a/docs/dev-containers.md +++ b/docs/dev-containers.md @@ -295,13 +295,13 @@ You do not need this repository to write a database-driver extension. Start from Built from `docker//Dockerfile.testdb`; a new `Dockerfile.testdb` adds a database to the build by itself. +The whole path from the template to the Marketplace is in +[extension-registry.md](extension-registry.md). + ## Known gaps -- **The extension registry** (registry CI and `StaticRegistryRepository`) and the - release hooks that publish the Feature and the images are not done yet; they - are tracked in [#1487](https://github.com/QueryaHub/Querya-Desktop/issues/1487). - Until then the images are published by hand with the *Test database images* - workflow. +- **The Marketplace page still uses the mock repository.** The registry and + `StaticRegistryRepository` exist; wiring the page to them is the 0.5.0 work. ## Troubleshooting diff --git a/docs/extension-registry.md b/docs/extension-registry.md new file mode 100644 index 00000000..648e28be --- /dev/null +++ b/docs/extension-registry.md @@ -0,0 +1,114 @@ +# Extension registry + +How a database-driver extension gets from an author's machine into Querya +Desktop's Marketplace, and what keeps it safe. There is no Marketplace server: +the catalog is a Git repository and one static file. + +``` +author ──► extension-template ──► release zip on GitHub ──► record PR to QueryaHub/extensions + │ CI: download, SHA-256, archive, + │ manifest, querya-ext-tester --sandbox + ▼ +Querya Desktop ◄── index.json (GitHub Pages) ◄── publish (on merge, nightly, on a Querya release) +``` + +| Piece | Where | What it does | +|-------|-------|--------------| +| `extension-template` | [QueryaHub/extension-template](https://github.com/QueryaHub/extension-template) | A working Rust driver with a dev container, a test database and CI. Click *Use this template*. | +| `querya-ext-tester` | Querya Desktop releases, the Dev Container Feature, the `test-extension` action | Runs the extension like the app does and checks its JSON-RPC lifecycle ([extension-testing.md](extension-testing.md)). | +| `querya-testdb-*` | `ghcr.io/queryahub/querya-testdb-{postgres,mysql,mongo,clickhouse,redis}:` | The test databases, seeds inside. Built from `docker//Dockerfile.testdb`. | +| Registry | [QueryaHub/extensions](https://github.com/QueryaHub/extensions) | Records, CI, and `index.json` / `compat.json` on GitHub Pages. | +| `StaticRegistryRepository` | `lib/core/market/static_registry_repository.dart` | The app's reader of `index.json`. | + +## Publishing an extension + +1. Start from the template, or any project that produces a **zip** with + `manifest.json` at its root (and, for a driver, the executable `main` points at). +2. Test it: `scripts/test.sh` in the template's dev container, or + `querya-ext-tester ./dist --connect '{...}' --sandbox` against a + `querya-testdb-*` container. +3. Publish a GitHub release with the zip as an asset. +4. Add `extensions/.json` to the registry in a pull request (the format and + what CI checks are in the registry's README). Everything the Marketplace + shows is read from `manifest.json` **inside the archive**; the record only + holds `id`, `version`, `downloadUrl`, `sha256` and an optional `testEnv`. +5. A new version is a change to `version`, `downloadUrl` and `sha256` in the same + file. `"yanked": true` hides an extension without deleting its record. + +The extension's binary runs in the registry's CI, inside the OS sandbox, in a job +with no secrets and a read-only token. Only a Linux x86-64 build is exercised. + +## What the app trusts + +`StaticRegistryRepository` reads `https://queryahub.github.io/extensions/index.json`: + +```json +{ + "schema": 1, + "generatedAt": "2026-10-16T18:59:53Z", + "extensions": [ + { + "id": "acme.redis-driver", "name": "Redis", "version": "1.0.0", "type": "database_driver", + "engines": { "querya_desktop": ">=0.4.19" }, "main": "bin/driver", + "downloadUrl": "https://github.com/acme/redis-driver/releases/download/v1.0.0/redis-driver.zip", + "sha256": "…", "size": 232353 + } + ] +} +``` + +- An index with another `schema` is refused, not half read. +- Hidden from the list: `yanked` entries, entries without a checksum, a + `downloadUrl` that is not `https://github.com///releases/download//`, + and extensions whose `engines.querya_desktop` range excludes the running + version. One malformed entry does not hide the others. +- The index is cached for ten minutes; if a refresh fails the last good copy is + served. +- Installing is the same pipeline as any marketplace install: the archive's + SHA-256 must equal the index's, the archive is unpacked with path-traversal and + zip-bomb limits, and the sandbox policy must accept the manifest. + +It is **not wired into the Marketplace page yet** (that still uses the mock +repository): the live Marketplace is the 0.5.0 work. + +`compat.json` lists, per extension and per Querya version in the registry's +`hosts.json`, `pass`, `fail` or `untested`. The app does not read it yet. + +## Following Querya's releases + +The [Release hooks](../.github/workflows/release-hooks.yml) workflow runs after a +**Release** succeeds and sends `repository_dispatch` `querya-release` with the +version to: + +- **the registry**, which tests every record against that version too and adds a + column to `compat.json` (raise `hosts.json` in a pull request to keep the + column); +- **the template**, which pushes a branch `querya-` that moves its test + image tag and CI matrix, starts its Test workflow on it, and opens a pull + request (or an issue pointing at the branch when the organization does not + allow Actions to open pull requests). + +The `querya-testdb-*` images and `querya-headless` have their own workflows on +the same Release. The Dev Container Feature does not follow releases: it +downloads the newest tester when it is installed. + +### One-time setup + +A dispatch into another repository needs a token that can write there. Create a +fine-grained personal access token (or a GitHub App token) with **Contents: +Read and write** on `QueryaHub/extensions` and `QueryaHub/extension-template` +and store it as the secret **`QUERYA_DISPATCH_TOKEN`** in this repository. +Without it the Release hooks only log a warning, and nothing breaks. + +To let the template open its pull request itself, enable *Settings → Actions → +General → Allow GitHub Actions to create and approve pull requests* for the +organization. Without that the template opens an issue instead. + +## The nightly check + +The registry rebuilds the index every night from the records. A record whose +asset disappeared, changed its hash, or whose extension fails its tests on every +Querya version is **left out of the index** and gets an issue +`Registry check failed: ` (label `registry-check`) with the reason; the +issue closes itself when the record passes again. The rest of the index is still +published. diff --git a/docs/extension-testing.md b/docs/extension-testing.md index 899ead4b..e1c3789d 100644 --- a/docs/extension-testing.md +++ b/docs/extension-testing.md @@ -178,6 +178,10 @@ the container with `--security-opt seccomp=unconfined` (and, on hosts that restrict namespaces with AppArmor, `--security-opt apparmor=unconfined`), or run the tester directly on the CI runner with the action below. +Starting a new extension? The [extension template](https://github.com/QueryaHub/extension-template) +has all of this wired up, and [extension-registry.md](extension-registry.md) +shows how a tested extension gets into the Marketplace. + ## Dev Container Feature In a dev container the tester can be installed as a Feature. It downloads the diff --git a/docs/release-checklist.md b/docs/release-checklist.md index cd7ef0ce..0cdadc7f 100644 --- a/docs/release-checklist.md +++ b/docs/release-checklist.md @@ -89,6 +89,7 @@ Verify the 0.4.4 motion tokens, smooth animations, and high refresh rate support - [ ] After merge to `main`, confirm **Auto Version Bump** yields a **0.4.20+…** placeholder. - [ ] **Tag** `0.4.19` is placed on the **main merge commit that includes `0.4.19+2`** (not the auto-bump commit). - [ ] Run the **Release** workflow via that tag (see [tags-and-releases.md](tags-and-releases.md)). +- [ ] After the Release workflow: the **Release hooks** run told the registry and the template (`QUERYA_DISPATCH_TOKEN` must be set, see [extension-registry.md](extension-registry.md#one-time-setup)), the `querya-testdb-*` and `querya-headless` images are published for the new version, and the template's *Move to Querya …* pull request or issue exists. - [ ] Verify **portable** zips (`*-linux.zip`, `*-windows.zip`, `*-macos.zip`), **installable** artifacts (`*.AppImage`, `*.deb`, `*.rpm`, `*.flatpak`, `*-windows-setup.exe`), and `SHA256SUMS.txt` on the GitHub Release. - [ ] After the release, set the release zip's URL and `sha256` in `packaging/linux/flatpak/com.queryahub.querya_desktop.flathub.yml` (and the `metainfo.xml` release entry is already `0.4.19`); the Flathub submission (flathub/flathub#10606) is separate and has open lint errors (HTTPS on `queryahub.com` or another app id, no `--filesystem=home`, a supported GNOME runtime).