Prepare auto updates
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Release signing for WP-LIT-MCP (called by bin/release.sh, usable by hand).
|
||||
*
|
||||
* php bin/release-sign.php keygen <key-file> new key pair; prints the public key for PackageSignature
|
||||
* php bin/release-sign.php sign <zip> <key-file> prints the signature (the .sig asset)
|
||||
* php bin/release-sign.php verify <zip> <sig-file> checks against the key built into the plugin
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// The class guards against direct web access; this is the command line.
|
||||
define('ABSPATH', __DIR__ . '/');
|
||||
require_once __DIR__ . '/../src/Updater/PackageSignature.php';
|
||||
|
||||
use LIT\MCP\Updater\PackageSignature;
|
||||
|
||||
function fail(string $message): never
|
||||
{
|
||||
fwrite(STDERR, $message . "\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
[, $command, $first, $second] = $argv + [null, '', '', ''];
|
||||
|
||||
switch ($command) {
|
||||
case 'keygen':
|
||||
if ($first === '' || file_exists($first)) {
|
||||
fail('usage: keygen <key-file> (an existing file is never overwritten)');
|
||||
}
|
||||
$pair = sodium_crypto_sign_keypair();
|
||||
if (! is_dir(dirname($first)) && ! mkdir(dirname($first), 0700, true)) {
|
||||
fail('cannot create ' . dirname($first));
|
||||
}
|
||||
// 0600 before the secret is written, so it is never readable by others.
|
||||
touch($first);
|
||||
chmod($first, 0600);
|
||||
file_put_contents($first, base64_encode(sodium_crypto_sign_secretkey($pair)));
|
||||
echo base64_encode(sodium_crypto_sign_publickey($pair)), "\n";
|
||||
break;
|
||||
|
||||
case 'sign':
|
||||
$secret = is_readable($second) ? base64_decode(trim((string) file_get_contents($second)), true) : false;
|
||||
if (! is_readable($first) || $secret === false || strlen($secret) !== SODIUM_CRYPTO_SIGN_SECRETKEYBYTES) {
|
||||
fail('usage: sign <zip> <key-file> (readable ZIP, key file from keygen)');
|
||||
}
|
||||
$message = PackageSignature::message(basename($first), $first);
|
||||
echo base64_encode(sodium_crypto_sign_detached($message, $secret)), "\n";
|
||||
break;
|
||||
|
||||
case 'verify':
|
||||
if (! is_readable($first) || ! is_readable($second)) {
|
||||
fail('usage: verify <zip> <sig-file>');
|
||||
}
|
||||
if (! (new PackageSignature())->verifies(basename($first), $first, (string) file_get_contents($second))) {
|
||||
fail('signature does not verify against the key built into the plugin');
|
||||
}
|
||||
echo "ok\n";
|
||||
break;
|
||||
|
||||
default:
|
||||
fail('usage: keygen <key-file> | sign <zip> <key-file> | verify <zip> <sig-file>');
|
||||
}
|
||||
Executable
+115
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds and signs a WP-LIT-MCP release from the committed state; with --publish it also tags and publishes it.
|
||||
#
|
||||
# bin/release.sh dry run: checks, tests, build, signature, ZIP checks – publishes nothing
|
||||
# bin/release.sh --publish additionally: git tag + push, Gitea release with ZIP and .sig, check of the result
|
||||
#
|
||||
# Needs: bsdtar, composer, curl, jq, php (sodium). For --publish a Gitea token with write access to the repository in
|
||||
# $GITEA_TOKEN. The signing key (php bin/release-sign.php keygen) lives outside the repository.
|
||||
#
|
||||
# Environment: LIT_MCP_RELEASE_KEY (default ~/.config/wp-lit-mcp/release.key), LIT_MCP_DIVI_CHECK (command that must
|
||||
# succeed before a release, default: ddev wp --user=janniclas lit-mcp divi-check).
|
||||
set -euo pipefail
|
||||
|
||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$root"
|
||||
|
||||
repo_url="https://git.loosen-it.de/janniclas.loosen/WP-LIT-MCP"
|
||||
api="https://git.loosen-it.de/api/v1/repos/janniclas.loosen/WP-LIT-MCP"
|
||||
key="${LIT_MCP_RELEASE_KEY:-$HOME/.config/wp-lit-mcp/release.key}"
|
||||
divi_check="${LIT_MCP_DIVI_CHECK:-ddev wp --user=janniclas lit-mcp divi-check}"
|
||||
publish=0
|
||||
[[ "${1:-}" == "--publish" ]] && publish=1
|
||||
|
||||
fail() { echo "ABBRUCH: $*" >&2; exit 1; }
|
||||
step() { echo "==> $*"; }
|
||||
|
||||
# --- 1. The three version places agree, the tree is committed, nothing is released twice ----------------------------
|
||||
step "Version und Zustand prüfen"
|
||||
version="$(sed -n "s/.*const VERSION = '\(.*\)';/\1/p" src/Plugin.php)"
|
||||
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || fail "Plugin::VERSION ist keine Version x.y.z: '$version'"
|
||||
[[ "$(sed -n 's/^ \* Version: //p' wp-lit-mcp.php)" == "$version" ]] || fail "Header-Version in wp-lit-mcp.php weicht ab"
|
||||
[[ "$(sed -n 's/^Stable tag: //p' readme.txt)" == "$version" ]] || fail "Stable tag in readme.txt weicht ab"
|
||||
grep -q "^= $version =\$" readme.txt || fail "readme.txt hat keinen Changelog-Eintrag für $version"
|
||||
[[ -z "$(git status --porcelain)" ]] || fail "Arbeitsbaum nicht sauber: erst committen, das ZIP entsteht aus HEAD"
|
||||
! git rev-parse -q --verify "refs/tags/v$version" >/dev/null || fail "Tag v$version existiert schon"
|
||||
[[ -r "$key" ]] || fail "Signierschlüssel fehlt: $key (php bin/release-sign.php keygen <Datei>)"
|
||||
if [[ $publish -eq 1 ]]; then
|
||||
[[ -n "${GITEA_TOKEN:-}" ]] || fail "GITEA_TOKEN ist nicht gesetzt"
|
||||
fi
|
||||
|
||||
# --- 2. Tests and the Divi check must be green ------------------------------------------------------------------------
|
||||
step "Tests"
|
||||
composer test
|
||||
step "Divi-Prüfung ($divi_check)"
|
||||
eval "$divi_check" || fail "Divi-Prüfung nicht grün: kein Release"
|
||||
|
||||
# --- 3. Build from the commit: git archive honours export-ignore, then production dependencies ----------------------
|
||||
step "ZIP bauen"
|
||||
work="$(mktemp -d)"
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
mkdir "$work/wp-lit-mcp"
|
||||
git archive HEAD | tar -x -C "$work/wp-lit-mcp"
|
||||
(cd "$work/wp-lit-mcp" && composer install --no-dev --optimize-autoloader --no-interaction --quiet)
|
||||
name="wp-lit-mcp-$version.zip"
|
||||
mkdir -p dist
|
||||
zip="$root/dist/$name"
|
||||
rm -f "$zip" "$zip.sig"
|
||||
(cd "$work" && bsdtar --format zip -cf "$zip" wp-lit-mcp)
|
||||
|
||||
# --- 4. Look into the ZIP like WordPress will: one folder, the plugin, its autoloader, the right version --------------
|
||||
step "ZIP prüfen"
|
||||
entries="$(bsdtar -tf "$zip")"
|
||||
if grep -qv '^wp-lit-mcp/' <<<"$entries"; then fail "ZIP enthält Einträge außerhalb von wp-lit-mcp/"; fi
|
||||
for need in wp-lit-mcp/wp-lit-mcp.php wp-lit-mcp/vendor/autoload_packages.php; do
|
||||
grep -qx "$need" <<<"$entries" || fail "Im ZIP fehlt $need"
|
||||
done
|
||||
if grep -qE '^wp-lit-mcp/(tests|bin|docs/superpowers|\.git)(/|$)' <<<"$entries"; then
|
||||
fail "Entwicklungsdateien im ZIP"
|
||||
fi
|
||||
[[ "$(bsdtar -xOf "$zip" wp-lit-mcp/wp-lit-mcp.php | sed -n 's/^ \* Version: //p')" == "$version" ]] \
|
||||
|| fail "Version im ZIP stimmt nicht mit $version überein"
|
||||
|
||||
# --- 5. Sign, and prove the signature against the key built into the plugin -------------------------------------------
|
||||
step "Signieren"
|
||||
php bin/release-sign.php sign "$zip" "$key" > "$zip.sig"
|
||||
php bin/release-sign.php verify "$zip" "$zip.sig" >/dev/null || fail "Signatur passt nicht zum Schlüssel im Plugin (PackageSignature::RELEASE_KEY)"
|
||||
|
||||
if [[ $publish -eq 0 ]]; then
|
||||
echo "Probelauf in Ordnung: dist/$name und dist/$name.sig"
|
||||
echo "Veröffentlichen: GITEA_TOKEN=… bin/release.sh --publish"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- 6. Publish ---------------------------------------------------------------------------------------------------------
|
||||
step "Tag v$version setzen und pushen"
|
||||
git tag -a "v$version" -m "WP-LIT-MCP $version"
|
||||
git push origin "v$version"
|
||||
|
||||
step "Release auf Gitea anlegen"
|
||||
auth=(-H "Authorization: token $GITEA_TOKEN")
|
||||
notes="$(awk -v header="= $version =" '$0 == header { on = 1; next } /^= / { on = 0 } on' readme.txt)"
|
||||
release_id="$(curl -fsS "${auth[@]}" -H 'Content-Type: application/json' \
|
||||
-d "$(jq -n --arg tag "v$version" --arg body "$notes" '{tag_name: $tag, name: $tag, body: $body}')" \
|
||||
"$api/releases" | jq -r .id)"
|
||||
for file in "$zip" "$zip.sig"; do
|
||||
curl -fsS "${auth[@]}" -F "attachment=@$file" "$api/releases/$release_id/assets?name=$(basename "$file")" >/dev/null
|
||||
done
|
||||
|
||||
# --- 7. Check what a customer site will see: latest release, public asset URLs, a signature that verifies ------------
|
||||
step "Ergebnis prüfen (ohne Token, wie eine Kundenseite)"
|
||||
latest="$(curl -fsS "$api/releases/latest")"
|
||||
[[ "$(jq -r .tag_name <<<"$latest")" == "v$version" ]] || fail "releases/latest liefert nicht v$version"
|
||||
asset_url() { jq -r --arg n "$1" '.assets[] | select(.name == $n) | .browser_download_url' <<<"$latest"; }
|
||||
zip_url="$(asset_url "$name")"
|
||||
sig_url="$(asset_url "$name.sig")"
|
||||
for url in "$zip_url" "$sig_url"; do
|
||||
[[ "$url" == "$repo_url/releases/download/"* ]] || fail "Asset-URL nicht wie erwartet: '$url'"
|
||||
done
|
||||
mkdir "$work/check"
|
||||
curl -fsSL "$zip_url" -o "$work/check/$name"
|
||||
curl -fsSL "$sig_url" -o "$work/check/$name.sig"
|
||||
php bin/release-sign.php verify "$work/check/$name" "$work/check/$name.sig" >/dev/null \
|
||||
|| fail "Heruntergeladenes Release besteht die Signaturprüfung nicht"
|
||||
|
||||
echo "Release v$version veröffentlicht: $repo_url/releases/tag/v$version"
|
||||
Reference in New Issue
Block a user