Bridge: fetch stable URL from MokoOnyx updates.xml, fix double-exec
Some checks failed
Repo Health / Access control (push) Successful in 1s
Auto-Update SHA Hash / Update SHA-256 Hash in updates.xml (release) Failing after 5s
Repo Health / Release configuration (push) Failing after 3s
Repo Health / Scripts governance (push) Successful in 3s
Repo Health / Repository health (push) Failing after 3s

- Remove hardcoded RELEASE_URL, discover from updates.xml stable channel
- Add discoverStableUrl() to parse MokoOnyx updates.xml at runtime
- Extract httpGet() helper for reuse across download + XML fetch
- Remove bridge call from update() — postflight() handles it
- Always targets stable channel for production-safe installs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan Miller
2026-04-21 12:01:51 -05:00
parent 532ead999d
commit 4d20eee830
2 changed files with 89 additions and 27 deletions

View File

@@ -28,8 +28,11 @@ class MokoBridgeMigration
private const OLD_DISPLAY = 'MokoCassiopeia'; private const OLD_DISPLAY = 'MokoCassiopeia';
private const NEW_DISPLAY = 'MokoOnyx'; private const NEW_DISPLAY = 'MokoOnyx';
/** URL to the latest MokoOnyx stable release ZIP */ /** Raw URL for MokoOnyx updates.xml on main — used to discover the stable download URL */
private const RELEASE_URL = 'https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/v01/mokoonyx-01.00.00.zip'; private const UPDATES_XML_URL = 'https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/raw/branch/main/updates.xml';
/** Fallback URL if updates.xml cannot be parsed */
private const FALLBACK_URL = 'https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/v01/mokoonyx-01.00.00.zip';
/** /**
* Run the full migration. * Run the full migration.
@@ -85,15 +88,94 @@ class MokoBridgeMigration
/** /**
* Download the MokoOnyx ZIP to Joomla's tmp directory. * Download the MokoOnyx ZIP to Joomla's tmp directory.
*
* Reads MokoOnyx's updates.xml on main to discover the current stable
* download URL, falling back to a hardcoded URL if parsing fails.
*/ */
private static function downloadRelease(): ?string private static function downloadRelease(): ?string
{ {
$tmpDir = Factory::getApplication()->get('tmp_path', JPATH_ROOT . '/tmp'); $tmpDir = Factory::getApplication()->get('tmp_path', JPATH_ROOT . '/tmp');
$zipPath = $tmpDir . '/mokoonyx-install.zip'; $zipPath = $tmpDir . '/mokoonyx-install.zip';
// 1. Discover the stable download URL from MokoOnyx's updates.xml
$releaseUrl = self::discoverStableUrl();
if (!$releaseUrl) {
self::log('Bridge: could not discover release URL from updates.xml, using fallback');
$releaseUrl = self::FALLBACK_URL;
}
self::log('Bridge: downloading MokoOnyx from ' . $releaseUrl);
// 2. Download the ZIP
$content = self::httpGet($releaseUrl);
if ($content === false || strlen($content) < 1000) {
self::log('Bridge: failed to download MokoOnyx ZIP from ' . $releaseUrl, 'error');
return null;
}
if (file_put_contents($zipPath, $content) === false) {
self::log('Bridge: failed to write ZIP to ' . $zipPath, 'error');
return null;
}
self::log('Bridge: downloaded MokoOnyx ZIP (' . strlen($content) . ' bytes)');
return $zipPath;
}
/**
* Fetch MokoOnyx's updates.xml and extract the stable channel ZIP URL.
*
* Always targets the stable channel — the bridge should only install
* production-ready builds of MokoOnyx.
*/
private static function discoverStableUrl(): ?string
{
$xml = self::httpGet(self::UPDATES_XML_URL);
if ($xml === false || strlen($xml) < 100) {
self::log('Bridge: failed to fetch MokoOnyx updates.xml', 'warning');
return null;
}
libxml_use_internal_errors(true);
$doc = simplexml_load_string($xml);
libxml_clear_errors();
if (!$doc) {
self::log('Bridge: failed to parse MokoOnyx updates.xml', 'warning');
return null;
}
// Find the stable <update> block
foreach ($doc->update as $update) {
$tags = $update->tags->tag ?? [];
foreach ($tags as $tag) {
if ((string) $tag === 'stable') {
foreach ($update->downloads->downloadurl as $dl) {
$format = (string) ($dl['format'] ?? '');
$url = trim((string) $dl);
if ($format === 'zip' && !empty($url)) {
self::log('Bridge: discovered stable URL: ' . $url);
return $url;
}
}
}
}
}
self::log('Bridge: no stable ZIP URL found in MokoOnyx updates.xml', 'warning');
return null;
}
/**
* HTTP GET helper — tries file_get_contents then cURL.
*
* @return string|false Response body or false on failure.
*/
private static function httpGet(string $url)
{
$content = false; $content = false;
// Method 1: file_get_contents
if (ini_get('allow_url_fopen')) { if (ini_get('allow_url_fopen')) {
$ctx = stream_context_create([ $ctx = stream_context_create([
'http' => [ 'http' => [
@@ -106,12 +188,11 @@ class MokoBridgeMigration
'verify_peer_name' => true, 'verify_peer_name' => true,
], ],
]); ]);
$content = @file_get_contents(self::RELEASE_URL, false, $ctx); $content = @file_get_contents($url, false, $ctx);
} }
// Method 2: cURL
if ($content === false && function_exists('curl_init')) { if ($content === false && function_exists('curl_init')) {
$ch = curl_init(self::RELEASE_URL); $ch = curl_init($url);
curl_setopt_array($ch, [ curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true, CURLOPT_FOLLOWLOCATION => true,
@@ -128,18 +209,7 @@ class MokoBridgeMigration
} }
} }
if ($content === false || strlen($content) < 1000) { return $content;
self::log('Bridge: failed to download MokoOnyx ZIP from ' . self::RELEASE_URL, 'error');
return null;
}
if (file_put_contents($zipPath, $content) === false) {
self::log('Bridge: failed to write ZIP to ' . $zipPath, 'error');
return null;
}
self::log('Bridge: downloaded MokoOnyx ZIP (' . strlen($content) . ' bytes)');
return $zipPath;
} }
/** /**

View File

@@ -113,15 +113,7 @@ class Tpl_MokocassiopeiaInstallerScript
); );
} }
// Bridge migration: MokoCassiopeia → MokoOnyx // Bridge migration runs in postflight() — not here — to avoid double execution
$bridgeScript = __DIR__ . '/helper/bridge.php';
if (is_file($bridgeScript)) {
require_once $bridgeScript;
if (class_exists('MokoBridgeMigration')) {
MokoBridgeMigration::run();
}
}
return true; return true;
} }