From a5f0f07eb4b67848b75f992006f4b4ba61d25772 Mon Sep 17 00:00:00 2001 From: Seth Van Niekerk Date: Sat, 28 Mar 2026 14:36:34 -0400 Subject: [PATCH 1/4] Add key for testing --- .github/scripts/keys/dispatcharr-plugins.pub | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/scripts/keys/dispatcharr-plugins.pub diff --git a/.github/scripts/keys/dispatcharr-plugins.pub b/.github/scripts/keys/dispatcharr-plugins.pub new file mode 100644 index 0000000..e4173db --- /dev/null +++ b/.github/scripts/keys/dispatcharr-plugins.pub @@ -0,0 +1,11 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEacgfABYJKwYBBAHaRw8BAQdAh1MuVNBxk+CExQPjOVDvAGvIk6BdGS2ce9/h +zB7lYtW0TERpc3BhdGNoYXJyIFBsdWdpbiBSZXBvIChkaXNwYXRjaGFyci1hdXRv +Z2VuZXJhdGVkKSA8cGx1Z2luc0BkaXNwYXRjaGFyci50dj6IrwQTFgoAVxYhBEap +MFaOD7nKg0zX+H7AOmtMIjTOBQJpyB8AGxSAAAAAAAQADm1hbnUyLDIuNSsxLjEy +LDAsMwIbAwULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRB+wDprTCI0zvNZ +AP9r3TpMpiI8BCNo9B5M9lJ+QLRo9ihPWIcqBzJ9eFCoSQEAgguiZsNy6aJzKjIb +yDvGuoZi3I2/GNM/f2qVzFtgPQk= +=Zf/y +-----END PGP PUBLIC KEY BLOCK----- From c9462f62515043b6cb9d184cc2459aecc4a041ae Mon Sep 17 00:00:00 2001 From: Seth Van Niekerk Date: Sat, 28 Mar 2026 15:18:52 -0400 Subject: [PATCH 2/4] Refactor manifest generation and update README for new structure --- .github/scripts/publish/generate-manifest.sh | 20 +++++----------- .github/workflows/publish-plugins.yml | 2 +- README.md | 25 +++++++++++++++++--- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/.github/scripts/publish/generate-manifest.sh b/.github/scripts/publish/generate-manifest.sh index db06dc5..c639fe1 100644 --- a/.github/scripts/publish/generate-manifest.sh +++ b/.github/scripts/publish/generate-manifest.sh @@ -12,6 +12,7 @@ set -e generated_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" repo_url="https://github.com/${GITHUB_REPOSITORY}" repo_name="${GITHUB_REPOSITORY}" +root_url="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/${RELEASES_BRANCH}" # GPG signing setup - optional; set GPG_PRIVATE_KEY (armored) and optionally GPG_PASSPHRASE gpg_key_id="" @@ -114,7 +115,7 @@ for plugin_dir in plugins/*/; do echo " $plugin_name" - latest_url="https://github.com/${GITHUB_REPOSITORY}/raw/$RELEASES_BRANCH/zips/${plugin_name}/${plugin_name}-latest.zip" + latest_url="zips/${plugin_name}/${plugin_name}-latest.zip" versioned_zips="[]" latest_metadata="{}" @@ -125,7 +126,7 @@ for plugin_dir in plugins/*/; do while IFS= read -r zipfile; do zip_basename=$(basename "$zipfile") zip_version=$(echo "$zip_basename" | sed "s/${plugin_name}-\(.*\)\.zip/\1/") - zip_url="https://github.com/${GITHUB_REPOSITORY}/raw/$RELEASES_BRANCH/zips/${plugin_name}/${zip_basename}" + zip_url="zips/${plugin_name}/${zip_basename}" # Fresh metadata from this run takes priority; fall back to existing manifest fresh_meta_file="${BUILD_META_DIR:-}/$plugin_name/${plugin_name}-${zip_version}.json" @@ -151,16 +152,9 @@ for plugin_dir in plugins/*/; do done < <(ls -1 "zips/$plugin_name/${plugin_name}"-*.zip 2>/dev/null \ | grep -v latest | sort -t- -k2 -V -r) - # Compute icon_url before building plugin_entry so it can be included in both manifests - icon_url="" - if [[ -f "plugins/$plugin_name/logo.png" ]]; then - icon_url="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/${SOURCE_BRANCH}/plugins/${plugin_name}/logo.png" - fi - plugin_entry=$(jq \ --arg plugin_name "$plugin_name" \ --arg latest_url "$latest_url" \ - --arg icon_url "$icon_url" \ --argjson versioned_zips "$versioned_zips" \ --argjson latest_metadata "$latest_metadata" \ 'with_entries(select(.key | IN( @@ -169,8 +163,7 @@ for plugin_dir in plugins/*/; do ))) + { slug: $plugin_name, versions: $versioned_zips - } + (if $icon_url != "" then {icon_url: $icon_url} else {} end) - + ( + } + ( if ($latest_metadata | length > 0) then { last_updated: $latest_metadata.last_updated, latest: ($latest_metadata + { @@ -196,7 +189,7 @@ for plugin_dir in plugins/*/; do desc_trimmed="$desc_raw" fi - plugin_manifest_url="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/${RELEASES_BRANCH}/zips/${plugin_name}/manifest.json" + plugin_manifest_url="zips/${plugin_name}/manifest.json" root_entry=$(jq -n \ --argjson latest_metadata "$latest_metadata" \ @@ -204,7 +197,6 @@ for plugin_dir in plugins/*/; do --arg slug "$plugin_name" \ --arg name "$(jq -r '.name // ""' "$plugin_file")" \ --arg description "$desc_trimmed" \ - --arg icon_url "$icon_url" \ --arg manifest_url "$plugin_manifest_url" \ --arg author "$(jq -r '.author // ""' "$plugin_file")" \ --arg license "$(jq -r '.license // ""' "$plugin_file")" \ @@ -212,7 +204,6 @@ for plugin_dir in plugins/*/; do slug: $slug, name: $name, description: $description, - icon_url: (if $icon_url != "" then $icon_url else null end), manifest_url: $manifest_url, author: $author, license: (if $license != "" then $license else null end), @@ -229,6 +220,7 @@ done inner_root=$( { echo '{' + echo ' "root_url": '"$(jq -n --arg u "$root_url" '$u')"',' echo ' "plugins": [' first=true for entry in "${root_entries[@]}"; do diff --git a/.github/workflows/publish-plugins.yml b/.github/workflows/publish-plugins.yml index 2669cbb..1162dde 100644 --- a/.github/workflows/publish-plugins.yml +++ b/.github/workflows/publish-plugins.yml @@ -106,7 +106,7 @@ jobs: if [ -d /tmp/tmp.*/repo ]; then REPO_DIR=$(find /tmp -maxdepth 1 -name "tmp.*" -type d 2>/dev/null | head -1)/repo if [ -f "$REPO_DIR/manifest.json" ]; then - cat "$REPO_DIR/manifest.json" | jq -r '.plugins[] | "- **\(.name)** v\(.version)"' >> $GITHUB_STEP_SUMMARY + cat "$REPO_DIR/manifest.json" | jq -r '.manifest.plugins[] | "- **\(.name)** v\(.latest_version)"' >> $GITHUB_STEP_SUMMARY fi fi else diff --git a/README.md b/README.md index 87d164a..61e8c5c 100644 --- a/README.md +++ b/README.md @@ -52,9 +52,9 @@ Visit the [releases branch](https://github.com/Dispatcharr/Plugins/tree/releases curl https://raw.githubusercontent.com/Dispatcharr/Plugins/releases/manifest.json ``` -## Verifying Manifest Signatures +## Manifest Structure -Each manifest file embeds its GPG signature directly. The `signature` field covers the compact (`jq -c '.manifest'`) form of the `manifest` payload: +The root `manifest.json` uses a `root_url` plus relative paths to save space. All URL fields (`manifest_url`, `latest_url`, versioned zip `url`) are relative to `root_url`: ```json { @@ -62,10 +62,29 @@ Each manifest file embeds its GPG signature directly. The `signature` field cove "repo_url": "...", "repo_name": "owner/repo", "signature": "-----BEGIN PGP SIGNATURE-----\n...", - "manifest": { ... } + "manifest": { + "root_url": "https://raw.githubusercontent.com/Dispatcharr/Plugins/releases", + "plugins": [ + { + "slug": "my-plugin", + "name": "My Plugin", + "manifest_url": "zips/my-plugin/manifest.json", + "latest_url": "zips/my-plugin/my-plugin-1.0.0.zip", + ... + } + ] + } } ``` +To resolve a full download URL: `root_url + "/" + latest_url`. + +The `slug` matches the plugin folder name and can be used to construct other paths (e.g. icon: `plugins//logo.png` on the source branch). + +## Verifying Manifest Signatures + +Each manifest file embeds its GPG signature directly. The `signature` field covers the compact (`jq -c '.manifest'`) form of the `manifest` payload. + The public key is bundled with Dispatcharr. To verify manually, export it from the application or obtain `.github/scripts/keys/dispatcharr-plugins.pub` from the default branch. ### Steps From 0ec6aede08c6a5032621ec4c441738d1c897df57 Mon Sep 17 00:00:00 2001 From: Seth Van Niekerk Date: Sat, 28 Mar 2026 15:26:09 -0400 Subject: [PATCH 3/4] Update manifest generation to include repo_url and repo_name in signed area of output --- .github/scripts/publish/generate-manifest.sh | 6 +++--- README.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/scripts/publish/generate-manifest.sh b/.github/scripts/publish/generate-manifest.sh index c639fe1..8f8f750 100644 --- a/.github/scripts/publish/generate-manifest.sh +++ b/.github/scripts/publish/generate-manifest.sh @@ -49,10 +49,8 @@ write_manifest_if_changed() { fi jq -n \ --arg generated_at "$generated_at" \ - --arg repo_url "$repo_url" \ - --arg repo_name "$repo_name" \ --argjson manifest "$new_compact" \ - '{generated_at: $generated_at, repo_url: $repo_url, repo_name: $repo_name, manifest: $manifest}' \ + '{generated_at: $generated_at, manifest: $manifest}' \ > "$dest" return 0 } @@ -220,6 +218,8 @@ done inner_root=$( { echo '{' + echo ' "repo_url": '"$(jq -n --arg u "$repo_url" '$u')"',' + echo ' "repo_name": '"$(jq -n --arg u "$repo_name" '$u')"',' echo ' "root_url": '"$(jq -n --arg u "$root_url" '$u')"',' echo ' "plugins": [' first=true diff --git a/README.md b/README.md index 61e8c5c..919bdfa 100644 --- a/README.md +++ b/README.md @@ -59,10 +59,10 @@ The root `manifest.json` uses a `root_url` plus relative paths to save space. Al ```json { "generated_at": "...", - "repo_url": "...", - "repo_name": "owner/repo", "signature": "-----BEGIN PGP SIGNATURE-----\n...", "manifest": { + "repo_url": "https://github.com/Dispatcharr/Plugins", + "repo_name": "Dispatcharr/Plugins", "root_url": "https://raw.githubusercontent.com/Dispatcharr/Plugins/releases", "plugins": [ { From 66460b6e4edb59075d3ab9c3136d1b6c3ec8060e Mon Sep 17 00:00:00 2001 From: Seth Van Niekerk Date: Sat, 28 Mar 2026 15:28:24 -0400 Subject: [PATCH 4/4] Update manifest generation to use registry_url and registry_name instead of repo_url and repo_name --- .github/scripts/publish/generate-manifest.sh | 14 +++++++++----- README.md | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/scripts/publish/generate-manifest.sh b/.github/scripts/publish/generate-manifest.sh index 8f8f750..06526a8 100644 --- a/.github/scripts/publish/generate-manifest.sh +++ b/.github/scripts/publish/generate-manifest.sh @@ -10,8 +10,8 @@ set -e : "${SOURCE_BRANCH:?}" "${RELEASES_BRANCH:?}" "${GITHUB_REPOSITORY:?}" generated_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" -repo_url="https://github.com/${GITHUB_REPOSITORY}" -repo_name="${GITHUB_REPOSITORY}" +registry_url="https://github.com/${GITHUB_REPOSITORY}" +registry_name="${GITHUB_REPOSITORY}" root_url="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/${RELEASES_BRANCH}" # GPG signing setup - optional; set GPG_PRIVATE_KEY (armored) and optionally GPG_PASSPHRASE @@ -33,7 +33,7 @@ fi # Writes a manifest wrapper to $1 with $2 as the signed payload (.manifest), # only when .manifest content differs from what is on disk. -# Wrapper structure: {generated_at, repo_url, repo_name, manifest: } +# Wrapper structure: {generated_at, manifest: } # The .signature field is added separately by sign_manifest. # Returns 0 if written, 1 if skipped (content unchanged). write_manifest_if_changed() { @@ -153,6 +153,8 @@ for plugin_dir in plugins/*/; do plugin_entry=$(jq \ --arg plugin_name "$plugin_name" \ --arg latest_url "$latest_url" \ + --arg registry_url "$registry_url" \ + --arg registry_name "$registry_name" \ --argjson versioned_zips "$versioned_zips" \ --argjson latest_metadata "$latest_metadata" \ 'with_entries(select(.key | IN( @@ -160,6 +162,8 @@ for plugin_dir in plugins/*/; do "deprecated","repo_url","discord_thread","license" ))) + { slug: $plugin_name, + registry_url: $registry_url, + registry_name: $registry_name, versions: $versioned_zips } + ( if ($latest_metadata | length > 0) then { @@ -218,8 +222,8 @@ done inner_root=$( { echo '{' - echo ' "repo_url": '"$(jq -n --arg u "$repo_url" '$u')"',' - echo ' "repo_name": '"$(jq -n --arg u "$repo_name" '$u')"',' + echo ' "registry_url": '"$(jq -n --arg u "$registry_url" '$u')"',' + echo ' "registry_name": '"$(jq -n --arg u "$registry_name" '$u')"',' echo ' "root_url": '"$(jq -n --arg u "$root_url" '$u')"',' echo ' "plugins": [' first=true diff --git a/README.md b/README.md index 919bdfa..0a3f76d 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ The root `manifest.json` uses a `root_url` plus relative paths to save space. Al "generated_at": "...", "signature": "-----BEGIN PGP SIGNATURE-----\n...", "manifest": { - "repo_url": "https://github.com/Dispatcharr/Plugins", - "repo_name": "Dispatcharr/Plugins", + "registry_url": "https://github.com/Dispatcharr/Plugins", + "registry_name": "Dispatcharr/Plugins", "root_url": "https://raw.githubusercontent.com/Dispatcharr/Plugins/releases", "plugins": [ {