Files
moko-platform/docs/workflows/update-server.md
T

419 lines
20 KiB
Markdown

<!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
DEFGROUP: MokoStandards.Documentation
INGROUP: MokoStandards.Workflows
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
PATH: /docs/workflows/update-server.md
VERSION: 04.06.00
BRIEF: How update files (update.txt and updates.xml) are generated per platform
-->
# Update Server Files
MokoStandards automatically generates platform-specific update server files on every release and dev/alpha/beta/rc deployment.
## Overview
| Platform | File | Format | Reference |
|----------|------|--------|-----------|
| Dolibarr (`crm-module`) | `update.txt` | Plain text version string (< 30 chars) | Dolibarr `url_last_version` check |
| Joomla (`waas-component`) | `updates.xml` | Multi-entry XML following Joomla update server spec | [Joomla Update Server Docs](https://docs.joomla.org/Deploying_an_Update_Server) |
## Stability Tags
Joomla's `updates.xml` contains **multiple `<update>` entries simultaneously** — one per stability level. Joomla filters which entries the admin sees based on the site's **Minimum Stability** setting (Extensions > Update > Options).
| Stability | Joomla `<tag>` | Who sees it | Source workflow |
|-----------|---------------|-------------|----------------|
| Stable | `<tag>stable</tag>` | All sites (default) | `auto-release.yml` on `main` |
| Release Candidate | `<tag>rc</tag>` | Sites set to RC or lower | `update-server.yml` on `rc/**` push |
| Beta | `<tag>beta</tag>` | Sites set to Beta or lower | `update-server.yml` on `beta/**` push |
| Alpha | `<tag>alpha</tag>` | Sites set to Alpha or lower | `update-server.yml` on `alpha/**` push |
| Development | `<tag>development</tag>` | Sites set to Development | `update-server.yml` on `dev` or `dev/**` push |
**Note**: Alpha and beta are optional stages. Not every release cycle will have alpha/beta entries.
### `update-server.yml` Trigger Behavior
The `update-server.yml` workflow triggers on **both direct pushes and PR merges** to the following branches:
- `dev` (bare branch — no sub-path required)
- `dev/**` (versioned dev branches like `dev/02.01`)
- `alpha/**`
- `beta/**`
- `rc/**`
Previously, the workflow only triggered on PR merges. The addition of push triggers ensures that direct commits to these branches (e.g., CI-generated version bumps, automated fixes) also update the `updates.xml` entries.
### Cascade Release Channels
Each stability level writes its own channel **and all lower channels** to `updates.xml`. This ensures Joomla sites on any "Minimum Stability" setting always see the latest available release:
| Release Stream | Channels written to updates.xml |
|---------------|-------------------------------|
| development | `development` |
| alpha | `development`, `alpha` |
| beta | `development`, `alpha`, `beta` |
| rc | `development`, `alpha`, `beta`, `rc` |
| stable | `development`, `alpha`, `beta`, `rc`, `stable` |
Without cascade, a site set to "Development" minimum stability would only see `<tag>development</tag>` entries and would miss stable releases entirely. The cascade ensures stable releases are visible to all sites regardless of their minimum stability setting.
For full cascade documentation, see [Cascade Release Channels](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/src/branch/main/docs/release-management/cascade-channels.md).
### Sync to Main
Since Joomla sites read `updates.xml` from the `main` branch, the `update-server.yml` workflow **syncs `updates.xml` to `main` via the Gitea API** after building on non-main branches. This ensures pre-release channel entries (dev, alpha, beta, rc) are visible to sites checking for updates, without requiring a PR merge to main.
Previously, `update-server.yml` only committed `updates.xml` to the current branch, so Joomla sites never saw dev/alpha/beta/rc releases until they were merged to main.
### How Joomla Filters Updates
Joomla's update system reads all `<update>` entries from the XML file but only presents entries whose `<tag>` matches the site's minimum stability threshold:
- **Minimum Stability = Stable** (default): Only sees `<tag>stable</tag>` entries
- **Minimum Stability = RC**: Sees `stable` + `rc` entries
- **Minimum Stability = Beta**: Sees `stable` + `rc` + `beta` entries
- **Minimum Stability = Alpha**: Sees `stable` + `rc` + `beta` + `alpha` entries
- **Minimum Stability = Development**: Sees all entries (`stable` + `rc` + `beta` + `alpha` + `development`)
The admin always gets the **highest version** among visible entries.
### Dolibarr Stability
| Workflow | Branch | `update.txt` content |
|----------|--------|---------------------|
| `auto-release.yml` | `main` | `XX.YY.ZZ` (real version) |
| `deploy-dev.yml` | `rc/**` | `XX.YY.ZZ-rc` |
| `deploy-dev.yml` | `beta/**` | `XX.YY.ZZ-beta` |
| `deploy-dev.yml` | `alpha/**` | `XX.YY.ZZ-alpha` |
| `deploy-dev.yml` | `dev/**` | `development` |
### Branch Lifecycle
```
dev → [alpha] → [beta] → rc → version/XX → main → dev
optional optional (integration) (production) (feedback)
```
- **`dev` or `dev/**`**: Active development. Update files tagged as `development`.
- **`alpha/**`**: *(Optional)* Early internal testing. Update files tagged as `alpha`. Can be skipped.
- **`beta/**`**: *(Optional)* Broader external testing. Update files tagged as `beta`. Can be skipped.
- **`rc/**`**: Release candidate. Update files tagged as `rc`. RC branches deploy to dev server for final testing.
- **`version/XX`**: Major version integration branch (major only). All minors and patches flow into the same major branch.
- **`main`**: Stable release. `auto-release.yml` creates GitHub Release and `vXX` tag. Update files tagged as `stable`.
- **Main merges back to `dev`** to start the next cycle.
## Dolibarr: `update.txt`
Dolibarr modules check for updates by fetching a plain-text file from the URL in `$this->url_last_version`. The file must contain only the version string (e.g., `01.02.03`) — no JSON, no XML, no newlines.
**On release (main):**
```
01.02.03
```
**On RC deploy (rc/**):**
```
01.02.03-rc
```
**On dev deploy (dev/**):**
```
development
```
The module descriptor's `url_last_version` should point to:
```
https://git.mokoconsulting.tech/MokoConsulting/{repo}/raw/branch/main/update.txt
```
## Joomla: `updates.xml` (Multi-Entry)
The `updates.xml` file contains **up to five stability entries at once** (one per stability level). Joomla reads the entire file and filters by the site's minimum stability setting.
### Platform Distribution
| Release Type | Gitea Release | GitHub Release | Download URLs |
|-------------|---------------|----------------|---------------|
| **Stable** | Yes | Yes (via mirror) | Dual (Gitea + GitHub) |
| **RC** | Yes | No | Single (Gitea only) |
| **Beta** | Yes | No | Single (Gitea only) |
| **Alpha** | Yes | No | Single (Gitea only) |
| **Development** | Yes | No | Single (Gitea only) |
Pre-release builds stay on Gitea for internal testing. Only stable releases are mirrored to GitHub.
### Complete Multi-Entry Example
```xml
<?xml version="1.0" encoding="utf-8"?>
<updates>
<!-- Stable: dual download (Gitea + GitHub), visible to all sites -->
<update>
<name>My Extension</name>
<description>My Extension stable release</description>
<element>com_myextension</element>
<type>component</type>
<version>01.02.03</version>
<tags>
<tag>stable</tag>
</tags>
<infourl title="My Extension">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/releases</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/releases/download/v01/com_myextension-01.02.03.zip</downloadurl>
<downloadurl type="full" format="zip">https://github.com/mokoconsulting-tech/MyExtension/releases/download/v01/com_myextension-01.02.03.zip</downloadurl>
</downloads>
<targetplatform name="joomla" version="((4\.[3-9])|(5\.[0-9]))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
<!-- RC: Gitea only, visible to sites with minimum stability = RC or lower -->
<update>
<name>My Extension</name>
<description>My Extension release candidate</description>
<element>com_myextension</element>
<type>component</type>
<version>01.03.01-rc</version>
<tags>
<tag>rc</tag>
</tags>
<infourl title="My Extension">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/src/branch/rc</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/releases/download/rc/com_myextension-01.03.01-rc.zip</downloadurl>
</downloads>
<targetplatform name="joomla" version="((4\.[3-9])|(5\.[0-9]))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
<!-- Beta: Gitea only, visible to sites with minimum stability = Beta or lower -->
<update>
<name>My Extension</name>
<description>My Extension beta build</description>
<element>com_myextension</element>
<type>component</type>
<version>01.03.01-beta</version>
<tags>
<tag>beta</tag>
</tags>
<infourl title="My Extension">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/src/branch/beta</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/releases/download/beta/com_myextension-01.03.01-beta.zip</downloadurl>
</downloads>
<targetplatform name="joomla" version="((4\.[3-9])|(5\.[0-9]))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
<!-- Alpha: Gitea only, visible to sites with minimum stability = Alpha or lower -->
<update>
<name>My Extension</name>
<description>My Extension alpha build</description>
<element>com_myextension</element>
<type>component</type>
<version>01.03.01-alpha</version>
<tags>
<tag>alpha</tag>
</tags>
<infourl title="My Extension">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/src/branch/alpha</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/releases/download/alpha/com_myextension-01.03.01-alpha.zip</downloadurl>
</downloads>
<targetplatform name="joomla" version="((4\.[3-9])|(5\.[0-9]))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
<!-- Development entry: visible to sites with minimum stability = Development -->
<update>
<name>My Extension</name>
<description>My Extension development build</description>
<element>com_myextension</element>
<type>component</type>
<version>01.04.00-dev</version>
<tags>
<tag>development</tag>
</tags>
<infourl title="My Extension">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/src/branch/dev</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MyExtension/releases/download/development/com_myextension-01.04.00-dev.zip</downloadurl>
</downloads>
<targetplatform name="joomla" version="((4\.[3-9])|(5\.[0-9]))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
</updates>
```
### Which Workflow Writes Which Entry
| Workflow | Trigger | Entry written |
|----------|---------|---------------|
| `auto-release.yml` | Push to `main` | `<tag>stable</tag>` — writes the stable entry with SHA-256 hash of the ZIP |
| `update-server.yml` | Push to `rc/**` | `<tag>rc</tag>` — adds/updates the RC entry (+ cascaded lower channels) |
| `update-server.yml` | Push to `beta/**` | `<tag>beta</tag>` — adds/updates the beta entry (+ cascaded lower channels) |
| `update-server.yml` | Push to `alpha/**` | `<tag>alpha</tag>` — adds/updates the alpha entry (+ cascaded lower channels) |
| `update-server.yml` | Push to `dev` or `dev/**` | `<tag>development</tag>` — adds/updates the development entry |
The `auto-release.yml` workflow writes the stable entry and preserves any existing pre-release entries. The `update-server.yml` workflow writes its specific entry (and cascaded lower channels) and preserves the others.
**Important**: All `update-server.yml` runs also sync the updated `updates.xml` to `main` via the Gitea API, since Joomla sites read the update server XML from the `main` branch.
### XML Elements
All metadata is auto-extracted from the extension's XML manifest at build time:
| Element | Source | Notes |
|---------|--------|-------|
| `<name>` | `<name>` in manifest | Extension display name |
| `<element>` | `<element>` in manifest, or manifest filename | Must match installed extension |
| `<type>` | `type` attribute on `<extension>` | `component`, `module`, `plugin`, `library`, `package`, `template` |
| `<client>` | `client` attribute on `<extension>` | `site` or `administrator`**required for plugins and modules** |
| `<folder>` | `group` attribute on `<extension>` | Plugin group (e.g., `system`, `content`) — **required for plugins** |
| `<version>` | README.md VERSION field | Real version on release, `development` on dev |
| `<tags><tag>` | Workflow determines | `stable` on release, `development` on dev |
| `<targetplatform>` | `<targetplatform>` in manifest | Falls back to Joomla 5.x / 6.x |
| `<php_minimum>` | `<php_minimum>` in manifest | Optional, included if present |
| `<infourl>` | GitHub release/branch URL | Links to release page or branch |
| `<downloads><downloadurl>` | GitHub release asset or branch archive | `type="full" format="zip"` |
### Extension Manifest Setup
For the updates.xml generation to work correctly, your Joomla extension manifest must include:
```xml
<extension type="component" client="site" method="upgrade">
<name>My Extension</name>
<element>com_myextension</element>
<!-- ... -->
<updateservers>
<server type="extension" priority="1" name="My Extension Update Server (Gitea)">
https://git.mokoconsulting.tech/mokoconsulting-tech/{repo}/raw/branch/main/updates.xml
</server>
<server type="extension" priority="2" name="My Extension Update Server (GitHub)">
https://raw.githubusercontent.com/mokoconsulting-tech/{repo}/main/updates.xml
</server>
</updateservers>
</extension>
```
The `<updateservers>` tag tells Joomla where to check for updates. Both servers are declared for redundancy — Gitea (primary, priority 1) and GitHub mirror (fallback, priority 2).
## How It Works
### Joomla (updates.xml)
1. **On release** (`auto-release.yml` → main branch):
- Builds ZIP from `src/` directory
- Uploads ZIP to the `vXX` major release on GitHub
- Computes SHA-256 hash of the ZIP
- Writes/updates the `<tag>stable</tag>` entry in `updates.xml` with version, download URL, and SHA-256
- Cascades to all 5 stability channels
- Preserves any existing rc/dev entries in the file
- Commits updated `updates.xml` to main
2. **On RC push** (`update-server.yml` → rc/** branches):
- Writes/updates the `<tag>rc</tag>` entry in `updates.xml`
- Cascades to `rc`, `beta`, `alpha`, and `development` channels
- Download URL points to the Gitea release ZIP
- Preserves all other stability entries
- Commits updated `updates.xml` to the rc branch
- **Syncs `updates.xml` to `main` via Gitea API**
3. **On beta push** (`update-server.yml` → beta/** branches):
- Writes/updates the `<tag>beta</tag>` entry in `updates.xml`
- Cascades to `beta`, `alpha`, and `development` channels
- Download URL points to the Gitea release ZIP
- Preserves all other stability entries
- Commits updated `updates.xml` to the beta branch
- **Syncs `updates.xml` to `main` via Gitea API**
4. **On alpha push** (`update-server.yml` → alpha/** branches):
- Writes/updates the `<tag>alpha</tag>` entry in `updates.xml`
- Cascades to `alpha` and `development` channels
- Download URL points to the Gitea release ZIP
- Preserves all other stability entries
- Commits updated `updates.xml` to the alpha branch
- **Syncs `updates.xml` to `main` via Gitea API**
5. **On dev push** (`update-server.yml``dev` or `dev/**` branches):
- Writes/updates the `<tag>development</tag>` entry in `updates.xml`
- Download URL points to the Gitea release ZIP
- Preserves all other stability entries
- Commits updated `updates.xml` to the dev branch
- **Syncs `updates.xml` to `main` via Gitea API**
### Dolibarr (update.txt)
1. **On release** (`auto-release.yml` → main): writes real version to `update.txt`
2. **On RC deploy** (`deploy-dev.yml` → rc/**): writes `XX.YY.ZZ-rc` to `update.txt`
3. **On beta deploy** (`deploy-dev.yml` → beta/**): writes `XX.YY.ZZ-beta` to `update.txt`
4. **On alpha deploy** (`deploy-dev.yml` → alpha/**): writes `XX.YY.ZZ-alpha` to `update.txt`
5. **On dev deploy** (`deploy-dev.yml` → dev/**): writes `development` to `update.txt`
### RC --> Main Flow
When a release candidate is ready:
1. `rc/XX.YY.ZZ` branch is tested with `<tag>rc</tag>` update entries
2. RC branch is merged to `main` via PR
3. Push to main triggers `auto-release.yml` → GitHub Release + `vXX` tag
4. `updates.xml` on main gets a new/updated `<tag>stable</tag>` entry
5. `version/XX` archive branch is auto-created
## Health Checks
The platform-specific `repo_health.yml` workflows verify:
- **Dolibarr**: `update.txt` exists in root, module descriptor valid, url_last_version correct
- **Joomla**: `updates.xml` exists in root, XML manifest valid, language files present
## Rulesets
Branch protection rulesets (applied via `sync_rulesets.php`):
- **MAIN**: prevents deletion, non-fast-forward, requires PRs
- **VERSION**: immutable — prevents updates, deletion, force-push
- **DEV**: prevents deletion, non-fast-forward
- **ALPHA**: prevents deletion, non-fast-forward
- **BETA**: prevents deletion, non-fast-forward
- **RC**: prevents deletion, non-fast-forward
## Update Server Priority
Joomla manifest `<updateservers>` entries MUST follow this priority order:
| Priority | Server | URL pattern |
|----------|--------|-------------|
| **1 (primary)** | Gitea | `https://git.mokoconsulting.tech/MokoConsulting/{REPO}/raw/branch/main/updates.xml` |
| **2 (fallback)** | GitHub | `https://raw.githubusercontent.com/mokoconsulting-tech/{REPO}/main/updates.xml` |
### Why Gitea first
1. **Gitea is the source of truth** — all CI/CD runs on Gitea, releases are created here first
2. **GitHub is a push mirror** — it may lag behind by minutes or hours
3. **Self-hosted control** — Gitea is under our infrastructure, GitHub is third-party
4. **Availability** — if GitHub has an outage, Joomla sites still get updates from Gitea
### Manifest example
```xml
<updateservers>
<server type="extension" priority="1" name="ExtensionName Update Server (Gitea)">
https://git.mokoconsulting.tech/MokoConsulting/RepoName/raw/branch/main/updates.xml
</server>
<server type="extension" priority="2" name="ExtensionName Update Server (GitHub)">
https://raw.githubusercontent.com/mokoconsulting-tech/RepoName/main/updates.xml
</server>
</updateservers>
```
### Enforcement
The `enforce_tags.sh` script and `repo_health.yml` workflow validate this ordering.
Repos with GitHub as priority 1 will be flagged as non-compliant.