feat(updates): built-in Joomla/Dolibarr update server with auto-transfer and manifest sync #239

Open
opened 2026-05-30 16:55:59 +00:00 by jmiller · 3 comments
Owner

Summary

Add a built-in update server feature to MokoGitea that serves Joomla and Dolibarr compatible update XML endpoints directly from repository releases. This eliminates the need to manually maintain updates.xml files and external update servers.

Goals

  1. Built-in update server endpoints — MokoGitea serves Joomla-compatible and Dolibarr-compatible update XML feeds directly from repository release data
  2. Auto-transfer updates.xml — When a repo has an updates.xml in root, automatically parse it, register the update channel, and serve it at a stable URL
  3. Auto-update server addresses — When a Joomla extension repo creates a release, automatically update the server address in the extension manifest XML to point to the MokoGitea update endpoint

How it would work

Update server endpoints

  • GET /{owner}/{repo}/updates.xml — serves a Joomla-compatible update XML feed built from releases
  • GET /{owner}/{repo}/updates/dolibarr.json — serves a Dolibarr-compatible update feed
  • These endpoints read release tags, assets, and changelogs to generate the update feed dynamically

Auto-transfer updates.xml

  • When a repo contains an updates.xml file in root, MokoGitea ingests the channel configuration (stable, rc, dev, security)
  • Releases automatically populate the appropriate channel based on tag naming convention (e.g. -rc. suffix goes to RC channel)
  • The static updates.xml is replaced by the dynamic endpoint — no manual file maintenance needed

Auto-update Joomla manifest

  • When a Joomla extension repo creates a release, MokoGitea can automatically:
    • Parse the extension manifest XML (e.g. mod_example.xml, plg_example.xml, com_example.xml)
    • Update the <updateservers> section to point to https://git.mokoconsulting.tech/{owner}/{repo}/updates.xml
    • Commit the change back (using the actions bot user)
  • This ensures the packaged extension always references the correct update server

Joomla update XML format reference

<updates>
  <update>
    <name>Extension Name</name>
    <version>1.0.0</version>
    <tag>stable</tag>
    <infourl>https://git.mokoconsulting.tech/owner/repo/releases/tag/v1.0.0</infourl>
    <downloadurl>https://git.mokoconsulting.tech/owner/repo/releases/download/v1.0.0/package.zip</downloadurl>
    <targetplatform name="joomla" version="(4|5)\.*"/>
  </update>
</updates>

Dolibarr update format reference

Dolibarr uses a simpler approach — typically a conf/conf.class.php with version info and a URL to download the latest module archive.

Implementation areas

  • New route handler for update XML endpoints
  • Update feed generator (reads releases, builds XML/JSON)
  • Repo settings: enable/disable update server, configure channel mapping
  • Webhook or post-release hook to auto-update Joomla manifests
  • Migration for storing update server config per repo (or use repo topics/settings)

References


Created by @MokoBot - Claude Opus 4.6

## Summary Add a built-in update server feature to MokoGitea that serves Joomla and Dolibarr compatible update XML endpoints directly from repository releases. This eliminates the need to manually maintain updates.xml files and external update servers. ### Goals 1. **Built-in update server endpoints** — MokoGitea serves Joomla-compatible and Dolibarr-compatible update XML feeds directly from repository release data 2. **Auto-transfer updates.xml** — When a repo has an updates.xml in root, automatically parse it, register the update channel, and serve it at a stable URL 3. **Auto-update server addresses** — When a Joomla extension repo creates a release, automatically update the server address in the extension manifest XML to point to the MokoGitea update endpoint ### How it would work #### Update server endpoints - `GET /{owner}/{repo}/updates.xml` — serves a Joomla-compatible update XML feed built from releases - `GET /{owner}/{repo}/updates/dolibarr.json` — serves a Dolibarr-compatible update feed - These endpoints read release tags, assets, and changelogs to generate the update feed dynamically #### Auto-transfer updates.xml - When a repo contains an `updates.xml` file in root, MokoGitea ingests the channel configuration (stable, rc, dev, security) - Releases automatically populate the appropriate channel based on tag naming convention (e.g. `-rc.` suffix goes to RC channel) - The static updates.xml is replaced by the dynamic endpoint — no manual file maintenance needed #### Auto-update Joomla manifest - When a Joomla extension repo creates a release, MokoGitea can automatically: - Parse the extension manifest XML (e.g. `mod_example.xml`, `plg_example.xml`, `com_example.xml`) - Update the `<updateservers>` section to point to `https://git.mokoconsulting.tech/{owner}/{repo}/updates.xml` - Commit the change back (using the actions bot user) - This ensures the packaged extension always references the correct update server ### Joomla update XML format reference ```xml <updates> <update> <name>Extension Name</name> <version>1.0.0</version> <tag>stable</tag> <infourl>https://git.mokoconsulting.tech/owner/repo/releases/tag/v1.0.0</infourl> <downloadurl>https://git.mokoconsulting.tech/owner/repo/releases/download/v1.0.0/package.zip</downloadurl> <targetplatform name="joomla" version="(4|5)\.*"/> </update> </updates> ``` ### Dolibarr update format reference Dolibarr uses a simpler approach — typically a `conf/conf.class.php` with version info and a URL to download the latest module archive. ### Implementation areas - New route handler for update XML endpoints - Update feed generator (reads releases, builds XML/JSON) - Repo settings: enable/disable update server, configure channel mapping - Webhook or post-release hook to auto-update Joomla manifests - Migration for storing update server config per repo (or use repo topics/settings) ### References - Existing updates.xml in MokoGitea repo root (manually maintained) - moko-platform has `cli/updates_xml_build.php` and `cli/updates_xml_sync.php` - Joomla update server spec: https://manual.joomla.org/docs/building-extensions/update-server/ - MokoGitea already has an update checker that reads updates.xml --- *Created by @MokoBot - Claude Opus 4.6*
Author
Owner

Additional Requirement: License Key System

The update server should support license key validation so that:

  1. License-gated downloads -- repos can require a valid license key to download release assets and receive update notifications
  2. Key management -- admin UI to generate, revoke, and manage license keys per customer/org
  3. Key validation on update check -- the Joomla/Dolibarr update endpoint validates the license key passed as a query param or header before returning download URLs
  4. Expiration and tiers -- license keys can have expiration dates and tier levels (e.g. basic = minor updates only, pro = all updates)
  5. Usage tracking -- track which keys are checking for updates, from which IPs/domains, to detect sharing

How it works in Joomla

Joomla extensions pass an extra_query parameter in the update server definition:

<updateservers>
  <server type="extension" name="My Extension">
    https://git.mokoconsulting.tech/owner/repo/updates.xml?key={license_key}
  </server>
</updateservers>

The update server validates the key and either returns the full update XML or an empty response.

Proposed data model

  • license_key table: id, key_hash, owner_id, repo_id (or org-wide), tier, expires_at, max_domains, is_active, created_at
  • license_key_usage table: id, key_id, domain, ip, checked_at (for tracking)

@MokoBot - Claude Opus 4.6

## Additional Requirement: License Key System The update server should support license key validation so that: 1. **License-gated downloads** -- repos can require a valid license key to download release assets and receive update notifications 2. **Key management** -- admin UI to generate, revoke, and manage license keys per customer/org 3. **Key validation on update check** -- the Joomla/Dolibarr update endpoint validates the license key passed as a query param or header before returning download URLs 4. **Expiration and tiers** -- license keys can have expiration dates and tier levels (e.g. basic = minor updates only, pro = all updates) 5. **Usage tracking** -- track which keys are checking for updates, from which IPs/domains, to detect sharing ### How it works in Joomla Joomla extensions pass an `extra_query` parameter in the update server definition: ```xml <updateservers> <server type="extension" name="My Extension"> https://git.mokoconsulting.tech/owner/repo/updates.xml?key={license_key} </server> </updateservers> ``` The update server validates the key and either returns the full update XML or an empty response. ### Proposed data model - `license_key` table: id, key_hash, owner_id, repo_id (or org-wide), tier, expires_at, max_domains, is_active, created_at - `license_key_usage` table: id, key_id, domain, ip, checked_at (for tracking) --- *@MokoBot - Claude Opus 4.6*
Author
Owner

Architecture Plan — Built-in Update Server + License Key System

Full plan posted by @MokoBot


Data Model

license_package table

Defines purchasable subscription tiers (e.g. "Pro Annual", "Lifetime").

  • id, owner_id (org or user), name, description
  • duration_days (0 = unlimited/lifetime)
  • tier (basic, pro, enterprise — controls which repos/channels are accessible)
  • price (for display/reference, not billing)
  • max_sites (0 = unlimited)
  • repo_scope ("all" = org-wide, or JSON array of repo IDs for package-specific)
  • is_active, created_unix, updated_unix

license_key table

Individual keys issued from a package.

  • id, package_id (FK to license_package)
  • owner_id (org or user that issued it)
  • key_hash (SHA-256, never stored raw)
  • key_prefix (first 8 chars for display: "MOKO-12AB...")
  • licensee_name, licensee_email
  • domain_restriction (optional, comma-separated)
  • max_sites (overrides package default if set)
  • starts_unix (custom start date, 0 = creation time)
  • expires_unix (auto-calculated from package duration, or manually set)
  • is_active, created_unix, updated_unix

Key behaviors:

  • Org-level keys: owner_id = org ID, package.repo_scope = "all" — works for every repo in the org
  • Repo-specific keys: package.repo_scope = [repoID1, repoID2]
  • Lifetime keys: package.duration_days = 0, key.expires_unix = 0
  • Annual keys: package.duration_days = 365, key.expires_unix = starts_unix + 365 days
  • Custom date range: key.starts_unix and key.expires_unix manually set

license_key_usage table

Tracks update check activity per key.

  • id, key_id, repo_id, domain, ip_address, user_agent, version_from, created_unix

update_server_config table

Per-repo update server settings.

  • id, repo_id (UNIQUE), enabled, platform (joomla/dolibarr/generic)
  • require_key, element, client_id, folder, asset_pattern
  • stable_branch, channel_map (JSON for custom tag-to-channel mapping)
  • created_unix, updated_unix

Endpoints

Update feeds (web routes)

  • GET /{owner}/{repo}/updates.xml?key=MOKO-XXXX — Joomla XML feed
  • GET /{owner}/{repo}/updates/dolibarr.json?key=MOKO-XXXX — Dolibarr JSON feed
  • Key param is optional unless update_server_config.require_key = true

License key management API

  • GET/POST /api/v1/repos/{owner}/{repo}/license-keys — list/create keys for repo
  • GET/POST /api/v1/orgs/{org}/license-keys — list/create org-wide keys
  • GET/PATCH/DELETE .../license-keys/{id} — manage individual key
  • GET .../license-keys/{id}/usage — usage log

License package management API

  • GET/POST /api/v1/orgs/{org}/license-packages — list/create packages
  • GET/PATCH/DELETE .../license-packages/{id} — manage package
  • POST .../license-packages/{id}/generate-key — generate a key from this package

Validation Flow

  1. Request hits GET /{owner}/{repo}/updates.xml?key=MOKO-XXXX
  2. Load update_server_config for repo
  3. If require_key = true and no key provided -> return empty XML (Joomla-compatible "no updates")
  4. Hash the provided key, lookup in license_key table
  5. Check: is_active, not expired (expires_unix = 0 or > now), starts_unix <= now
  6. Check scope: key.package.repo_scope = "all" (org-wide) OR repo.id in repo_scope list
  7. Check tier: filter available releases by package tier if applicable
  8. Record usage in license_key_usage
  9. Generate and return the update XML with download URLs

Service Layer

  • services/updateserver/joomla.go — generates updates.xml from releases
  • services/updateserver/dolibarr.go — generates dolibarr JSON from releases
  • services/updateserver/license.go — key validation, usage tracking
  • services/updateserver/ingest.go — auto-detect updates.xml in repos, auto-configure
  • models/licenses/ — license_key, license_package, license_key_usage models

Channel Mapping (tag -> channel)

  • vX.Y.Z or no prerelease suffix -> stable
  • -rc suffix or IsPrerelease + rc -> rc
  • -beta -> beta
  • -alpha -> alpha
  • -dev -> development
  • Custom mapping via update_server_config.channel_map JSON

Phased Roadmap

Phase Scope Deliverable
1 Migration + models + Joomla XML generation /updates.xml returns dynamic feed from releases
2 Dolibarr + repo settings UI Dolibarr JSON endpoint, config page
3 License packages + keys Full CRUD, org-wide keys, expiration
4 Key validation middleware Gated downloads, usage tracking
5 Auto-detect + workflow migration Ingest existing updates.xml, stop manual commits
6 Polish Caching, rate limiting, expiry notifications, bulk key ops

@MokoBot - Claude Opus 4.6

## Architecture Plan — Built-in Update Server + License Key System ### Full plan posted by @MokoBot --- ### Data Model #### license_package table Defines purchasable subscription tiers (e.g. "Pro Annual", "Lifetime"). - id, owner_id (org or user), name, description - duration_days (0 = unlimited/lifetime) - tier (basic, pro, enterprise — controls which repos/channels are accessible) - price (for display/reference, not billing) - max_sites (0 = unlimited) - repo_scope ("all" = org-wide, or JSON array of repo IDs for package-specific) - is_active, created_unix, updated_unix #### license_key table Individual keys issued from a package. - id, package_id (FK to license_package) - owner_id (org or user that issued it) - key_hash (SHA-256, never stored raw) - key_prefix (first 8 chars for display: "MOKO-12AB...") - licensee_name, licensee_email - domain_restriction (optional, comma-separated) - max_sites (overrides package default if set) - starts_unix (custom start date, 0 = creation time) - expires_unix (auto-calculated from package duration, or manually set) - is_active, created_unix, updated_unix **Key behaviors:** - Org-level keys: owner_id = org ID, package.repo_scope = "all" — works for every repo in the org - Repo-specific keys: package.repo_scope = [repoID1, repoID2] - Lifetime keys: package.duration_days = 0, key.expires_unix = 0 - Annual keys: package.duration_days = 365, key.expires_unix = starts_unix + 365 days - Custom date range: key.starts_unix and key.expires_unix manually set #### license_key_usage table Tracks update check activity per key. - id, key_id, repo_id, domain, ip_address, user_agent, version_from, created_unix #### update_server_config table Per-repo update server settings. - id, repo_id (UNIQUE), enabled, platform (joomla/dolibarr/generic) - require_key, element, client_id, folder, asset_pattern - stable_branch, channel_map (JSON for custom tag-to-channel mapping) - created_unix, updated_unix --- ### Endpoints #### Update feeds (web routes) - `GET /{owner}/{repo}/updates.xml?key=MOKO-XXXX` — Joomla XML feed - `GET /{owner}/{repo}/updates/dolibarr.json?key=MOKO-XXXX` — Dolibarr JSON feed - Key param is optional unless update_server_config.require_key = true #### License key management API - `GET/POST /api/v1/repos/{owner}/{repo}/license-keys` — list/create keys for repo - `GET/POST /api/v1/orgs/{org}/license-keys` — list/create org-wide keys - `GET/PATCH/DELETE .../license-keys/{id}` — manage individual key - `GET .../license-keys/{id}/usage` — usage log #### License package management API - `GET/POST /api/v1/orgs/{org}/license-packages` — list/create packages - `GET/PATCH/DELETE .../license-packages/{id}` — manage package - `POST .../license-packages/{id}/generate-key` — generate a key from this package --- ### Validation Flow 1. Request hits `GET /{owner}/{repo}/updates.xml?key=MOKO-XXXX` 2. Load update_server_config for repo 3. If require_key = true and no key provided -> return empty XML (Joomla-compatible "no updates") 4. Hash the provided key, lookup in license_key table 5. Check: is_active, not expired (expires_unix = 0 or > now), starts_unix <= now 6. Check scope: key.package.repo_scope = "all" (org-wide) OR repo.id in repo_scope list 7. Check tier: filter available releases by package tier if applicable 8. Record usage in license_key_usage 9. Generate and return the update XML with download URLs --- ### Service Layer - `services/updateserver/joomla.go` — generates updates.xml from releases - `services/updateserver/dolibarr.go` — generates dolibarr JSON from releases - `services/updateserver/license.go` — key validation, usage tracking - `services/updateserver/ingest.go` — auto-detect updates.xml in repos, auto-configure - `models/licenses/` — license_key, license_package, license_key_usage models ### Channel Mapping (tag -> channel) - `vX.Y.Z` or no prerelease suffix -> stable - `-rc` suffix or IsPrerelease + rc -> rc - `-beta` -> beta - `-alpha` -> alpha - `-dev` -> development - Custom mapping via update_server_config.channel_map JSON ### Phased Roadmap | Phase | Scope | Deliverable | |-------|-------|-------------| | **1** | Migration + models + Joomla XML generation | `/updates.xml` returns dynamic feed from releases | | **2** | Dolibarr + repo settings UI | Dolibarr JSON endpoint, config page | | **3** | License packages + keys | Full CRUD, org-wide keys, expiration | | **4** | Key validation middleware | Gated downloads, usage tracking | | **5** | Auto-detect + workflow migration | Ingest existing updates.xml, stop manual commits | | **6** | Polish | Caching, rate limiting, expiry notifications, bulk key ops | --- *@MokoBot - Claude Opus 4.6*
Author
Owner

Additional Requirements Captured

1. Org-level license keys

Keys can be scoped to an entire org so all repos under that org share the same key. The license_package model has repo_scope = "all" for this. A customer installs multiple extensions from the same org and only needs one key.

2. License packages with configurable timeframes

  • Lifetime: duration_days = 0, never expires
  • Annual: duration_days = 365
  • Custom: manually set starts_unix and expires_unix per key
  • Packages define the default duration; individual keys can override with custom start/end dates

3. Update stream binding (Joomla convention)

License keys should be tiable to specific Joomla update streams (channels). Following Joomla convention:

  • stable — production-ready releases
  • rc — release candidates
  • beta — beta testing
  • alpha — early access
  • development — bleeding edge

A license package defines which streams the key grants access to:

  • Basic tier: ["stable"] — only stable releases
  • Pro tier: ["stable", "rc", "beta"] — stable + pre-release
  • Enterprise tier: ["stable", "rc", "beta", "alpha", "development"] — everything

The update XML endpoint filters <update> entries by the key holder is allowed streams. If a key only has "stable" access, rc/beta/alpha releases are omitted from the XML response.

Data model addition to license_package:

allowed_channels  TEXT JSON  (e.g. ["stable", "rc", "beta"])

This maps directly to Joomla is <tag> element in updates.xml — the server only returns <update> blocks where <tag> matches one of the key is allowed channels.


@MokoBot - Claude Opus 4.6

## Additional Requirements Captured ### 1. Org-level license keys Keys can be scoped to an entire org so all repos under that org share the same key. The license_package model has `repo_scope = "all"` for this. A customer installs multiple extensions from the same org and only needs one key. ### 2. License packages with configurable timeframes - **Lifetime**: duration_days = 0, never expires - **Annual**: duration_days = 365 - **Custom**: manually set starts_unix and expires_unix per key - Packages define the default duration; individual keys can override with custom start/end dates ### 3. Update stream binding (Joomla convention) License keys should be tiable to specific Joomla update streams (channels). Following Joomla convention: - **stable** — production-ready releases - **rc** — release candidates - **beta** — beta testing - **alpha** — early access - **development** — bleeding edge A license package defines which streams the key grants access to: - Basic tier: `["stable"]` — only stable releases - Pro tier: `["stable", "rc", "beta"]` — stable + pre-release - Enterprise tier: `["stable", "rc", "beta", "alpha", "development"]` — everything The update XML endpoint filters `<update>` entries by the key holder is allowed streams. If a key only has "stable" access, rc/beta/alpha releases are omitted from the XML response. **Data model addition to license_package:** ``` allowed_channels TEXT JSON (e.g. ["stable", "rc", "beta"]) ``` This maps directly to Joomla is `<tag>` element in updates.xml — the server only returns `<update>` blocks where `<tag>` matches one of the key is allowed channels. --- *@MokoBot - Claude Opus 4.6*
Sign in to join this conversation.
No labels
Type Feature
Status
Priority High
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: MokoConsulting/MokoGitea#239