Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e4a15fc
Implement GPG signing for plugin manifests and update README with ver…
sethwv Mar 21, 2026
26737b6
Add missing GPG signature for manifest in publish script
sethwv Mar 21, 2026
41dc09a
Refactor GPG key generation script to ensure keys are exported to the…
sethwv Mar 21, 2026
79b3c5f
Increase passphrase length in GPG key generation script for enhanced …
sethwv Mar 21, 2026
eed1d35
Update GPG key generation script to enhance security and improve key …
sethwv Mar 21, 2026
4804653
Remove retract plugin workflow and associated script
sethwv Mar 21, 2026
5f8df36
Enhance GPG signing process and add public key change detection in va…
sethwv Mar 21, 2026
654c819
Implement manifest writing and signing functions to optimize JSON gen…
sethwv Mar 21, 2026
c95f19e
Fix GPG signing condition and update manifest.json.sig handling in pu…
sethwv Mar 21, 2026
bd26f7c
Refactor manifest handling to embed GPG signatures directly and updat…
sethwv Mar 21, 2026
6ebfc22
Add SHA256 checksum to plugin metadata and update README table format
sethwv Mar 21, 2026
11fa6cb
Refactor publishing scripts to use 'zips/' directory for artifacts an…
sethwv Mar 21, 2026
5ad64fa
Migrate legacy release directories to new zips/ structure in cleanup …
sethwv Mar 21, 2026
92ed1c4
Update publish script to add zips directory instead of releases for c…
sethwv Mar 21, 2026
31ce0e9
Stage deletions of legacy directories in publish script
sethwv Mar 21, 2026
a47b3fb
Remove legacy cleanup code from scripts and update README for consist…
sethwv Mar 21, 2026
bbb165d
Refactor build and readme scripts to improve metadata handling and en…
sethwv Mar 21, 2026
f8b7868
test plugin version bump
sethwv Mar 21, 2026
6063aba
Remove unused metadata fields from plugin manifest generation
sethwv Mar 21, 2026
b3bc926
Remove unused latest_url field from plugin manifest generation
sethwv Mar 21, 2026
a5775ba
Fix slug assignment in root entry generation for plugin manifest
sethwv Mar 21, 2026
72c467c
Skip unlisted plugins in manifest generation and refine included fields
sethwv Mar 21, 2026
f295319
Clarify `unlisted` field description in `plugin.json` and update chec…
sethwv Mar 21, 2026
2b0feef
test another version bump for testing publish script changes
sethwv Mar 21, 2026
fcf1008
Add unlisted field to plugin.json for Dispatcharr Exporter
sethwv Mar 21, 2026
c79355a
Revert version to 2.4.0 and remove unlisted field from plugin.json
sethwv Mar 21, 2026
6b04178
Add versioned_zips to plugin manifest generation
sethwv Mar 21, 2026
aebdae8
Remove test PGP public key file
sethwv Mar 22, 2026
1322854
Enhance force rebuild functionality in publish workflow with confirma…
sethwv Mar 22, 2026
13bc07f
Improve handling of GPG signing failures by clarifying conditions for…
sethwv Mar 22, 2026
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
53 changes: 53 additions & 0 deletions .github/scripts/keys/generate-signing-key.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/bin/bash
set -e

# Generates a GPG Ed25519 signing key for Dispatcharr manifest signing.
# Outputs:
# dispatcharr-plugins.pub - public key to bundle in the Dispatcharr app
# dispatcharr-plugins.key - private key to set as the GPG_PRIVATE_KEY repo secret
# dispatcharr-plugins.pass - passphrase to set as the GPG_PASSPHRASE repo secret

EMAIL="plugins@dispatcharr.tv"
NAME="Dispatcharr Plugin Repo"
PASSPHRASE=$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 128)
KEYS_DIR="$(cd "$(dirname "$0")" && pwd)"

# Use an isolated temporary keyring so the user's main keyring is never touched
GNUPGHOME=$(mktemp -d)
export GNUPGHOME
trap 'rm -rf "$GNUPGHOME"' EXIT
chmod 700 "$GNUPGHOME"

echo "Generating GPG signing key..."
echo " Identity : $NAME <$EMAIL>"
echo " Algorithm: Ed25519"
echo " Passphrase: $PASSPHRASE"
echo ""

gpg --batch --pinentry-mode loopback --passphrase "$PASSPHRASE" --gen-key <<EOF
Key-Type: EdDSA
Key-Curve: ed25519
Key-Usage: sign
Name-Real: ${NAME}
Name-Comment: dispatcharr-autogenerated
Name-Email: ${EMAIL}
Expire-Date: 0
%commit
EOF

FPR=$(gpg --list-secret-keys --with-colons 2>/dev/null | awk -F: '/^fpr/{print $10}' | head -1)

rm -f "$KEYS_DIR/dispatcharr-plugins.key" "$KEYS_DIR/dispatcharr-plugins.pub" "$KEYS_DIR/dispatcharr-plugins.pass"
gpg --batch --pinentry-mode loopback --passphrase "$PASSPHRASE" --armor --export-secret-keys "$FPR" > "$KEYS_DIR/dispatcharr-plugins.key"
gpg --armor --export "$FPR" > "$KEYS_DIR/dispatcharr-plugins.pub"
echo "$PASSPHRASE" > "$KEYS_DIR/dispatcharr-plugins.pass"

echo ""
gpg --list-secret-keys --keyid-format LONG

echo ""
echo "Files written:"
echo " dispatcharr-plugins.pub → bundle into Dispatcharr app (included in this repo)"
echo " dispatcharr-plugins.key → set as GPG_PRIVATE_KEY repo secret (ignored by .gitignore)"
echo " dispatcharr-plugins.pass → set as GPG_PASSPHRASE repo secret (ignored by .gitignore)"
echo ""
31 changes: 20 additions & 11 deletions .github/scripts/publish/build-zips.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
set -e

# publish-build-zips.sh
# Builds versioned ZIPs and per-version metadata files for all plugins.
# Skips plugins whose current version already has a ZIP + metadata file.
# Builds versioned ZIPs and per-version metadata for all plugins.
# Per-version metadata is written to a temporary working directory (BUILD_META_DIR)
# so generate-manifest.sh can consume it within this CI run without persisting
# per-version JSON files to the releases branch.
# Skips plugins whose current version already has a ZIP and an entry in the
# existing per-plugin manifest.
# Writes changed_plugins.txt to cwd (one "name@version" per line).
#
# Called from the releases branch checkout directory by publish-plugins.sh.
# Required env: SOURCE_BRANCH, RELEASES_BRANCH, GITHUB_REPOSITORY

: "${SOURCE_BRANCH:?}" "${RELEASES_BRANCH:?}" "${GITHUB_REPOSITORY:?}"
: "${SOURCE_BRANCH:?}" "${RELEASES_BRANCH:?}" "${GITHUB_REPOSITORY:?}" "${BUILD_META_DIR:?}"

> changed_plugins.txt

Expand All @@ -18,14 +22,18 @@ for plugin_dir in plugins/*/; do
plugin_name=$(basename "$plugin_dir")
version=$(jq -r '.version' "$plugin_dir/plugin.json")

mkdir -p "releases/$plugin_name" "metadata/$plugin_name"
mkdir -p "zips/$plugin_name"

zip_path="releases/$plugin_name/${plugin_name}-${version}.zip"
metadata_path="metadata/$plugin_name/${plugin_name}-${version}.json"
zip_path="zips/$plugin_name/${plugin_name}-${version}.zip"
existing_manifest="zips/$plugin_name/manifest.json"

if [[ -f "$zip_path" ]] && [[ -f "$metadata_path" ]]; then
echo " $plugin_name v$version - skipping (already exists)"
continue
# Skip if ZIP exists and the version is already in the existing manifest
if [[ -f "$zip_path" ]]; then
if [[ -f "$existing_manifest" ]] && \
jq -e --arg v "$version" '.manifest.versions[]? | select(.version == $v)' "$existing_manifest" >/dev/null 2>&1; then
echo " $plugin_name v$version - skipping (already exists)"
continue
fi
fi

echo " $plugin_name v$version - building"
Expand All @@ -45,6 +53,7 @@ for plugin_dir in plugins/*/; do
min_da_version=$(jq -r '.min_dispatcharr_version // ""' "$plugin_dir/plugin.json")
max_da_version=$(jq -r '.max_dispatcharr_version // ""' "$plugin_dir/plugin.json")

mkdir -p "$BUILD_META_DIR/$plugin_name"
jq -n \
--arg version "$version" \
--arg commit_sha "$commit_sha" \
Expand All @@ -65,9 +74,9 @@ for plugin_dir in plugins/*/; do
checksum_sha256: $checksum_sha256
} + (if $min_da_version != "" then {min_dispatcharr_version: $min_da_version} else {} end)
+ (if $max_da_version != "" then {max_dispatcharr_version: $max_da_version} else {} end)' \
> "$metadata_path"
> "$BUILD_META_DIR/$plugin_name/${plugin_name}-${version}.json"

cp "$zip_path" "releases/$plugin_name/${plugin_name}-latest.zip"
cp "$zip_path" "zips/$plugin_name/${plugin_name}-latest.zip"
done

changed=$(wc -l < changed_plugins.txt | tr -d ' ')
Expand Down
40 changes: 8 additions & 32 deletions .github/scripts/publish/cleanup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set -e

# publish-cleanup.sh
# Removes release artifacts for plugins that no longer exist in source,
# prunes versioned ZIPs beyond MAX_VERSIONED_ZIPS, and removes orphaned files.
# and prunes versioned ZIPs beyond MAX_VERSIONED_ZIPS.
#
# Called from the releases branch checkout directory by publish-plugins.sh.
# Required env: SOURCE_BRANCH
Expand All @@ -13,52 +13,28 @@ set -e
MAX_VERSIONED_ZIPS=${MAX_VERSIONED_ZIPS:-10}

# Remove artifacts for deleted plugins
if [[ -d releases ]]; then
for release_dir in releases/*/; do
if [[ -d zips ]]; then
for release_dir in zips/*/; do
[[ ! -d "$release_dir" ]] && continue
plugin_name=$(basename "$release_dir")
if [[ ! -d "plugins/$plugin_name" ]]; then
echo " Removing deleted plugin: $plugin_name"
rm -rf "$release_dir" "metadata/$plugin_name"
rm -rf "$release_dir"
fi
done
fi

# Prune old versions and orphans per plugin
# Prune old versions per plugin
for plugin_dir in plugins/*/; do
[[ ! -d "$plugin_dir" ]] && continue
plugin_name=$(basename "$plugin_dir")
zip_dir="releases/$plugin_name"
metadata_dir="metadata/$plugin_name"
zip_dir="zips/$plugin_name"

# Remove oldest ZIPs beyond the limit
while IFS= read -r old_zip; do
version=$(basename "$old_zip" | sed "s/${plugin_name}-\(.*\)\.zip/\1/")
rm -f "$old_zip" "$metadata_dir/${plugin_name}-${version}.json"
echo " Removed $plugin_name v$version (over limit)"
echo " Removed $plugin_name $(basename "$old_zip") (over limit)"
rm -f "$old_zip"
done < <(ls -1t "$zip_dir/${plugin_name}-"*.zip 2>/dev/null \
| grep -v "${plugin_name}-latest.zip" \
| awk "NR>$MAX_VERSIONED_ZIPS")

# Remove orphaned ZIPs with no matching metadata
for zipfile in "$zip_dir/${plugin_name}-"*.zip; do
[[ ! -f "$zipfile" ]] && continue
zip_basename=$(basename "$zipfile")
[[ "$zip_basename" == "${plugin_name}-latest.zip" ]] && continue
version=$(echo "$zip_basename" | sed "s/${plugin_name}-\(.*\)\.zip/\1/")
if [[ ! -f "$metadata_dir/${plugin_name}-${version}.json" ]]; then
rm -f "$zipfile"
echo " Removed orphaned ZIP: $zip_basename"
fi
done

# Remove orphaned metadata with no matching ZIP
for metafile in "$metadata_dir/${plugin_name}-"*.json; do
[[ ! -f "$metafile" ]] && continue
version=$(basename "$metafile" | sed "s/${plugin_name}-\(.*\)\.json/\1/")
if [[ ! -f "$zip_dir/${plugin_name}-${version}.zip" ]]; then
rm -f "$metafile"
echo " Removed orphaned metadata: $(basename "$metafile")"
fi
done
done
Loading
Loading