METAINFO
Software centres β Bazaar, GNOME Software, KDE Discover β render an app page from AppStream metadata. When that metadata is missing the page falls back to the raw application ID, an "Unknown" licence and an empty screenshot frame.
For an OCI remote like ours the metadata travels as image labels. This document covers how it gets there and what to put in it.
How the metadata reaches the remoteβ
data/<app-id>.metainfo.xml in the app repo
β meson/make installs it to
βΌ
/app/share/metainfo/<app-id>.metainfo.xml
β flatpak-builder runs appstreamcli compose at the end of the build
βΌ
/app/share/app-info/xmls/<app-id>.xml.gz catalogue XML
/app/share/app-info/icons/flatpak/{64x64,128x128}/<app-id>.png
β flatpak build-bundle --oci reads those and writes image labels
βΌ
org.freedesktop.appstream.appdata catalogue XML
org.freedesktop.appstream.icon-64 data:image/png;base64,...
org.freedesktop.appstream.icon-128 data:image/png;base64,...
β scripts/update-index.py copies the labels into
βΌ
index/static published at tunaos.org/flatpak/
β flatpak builds the remote's AppStream catalogue from the labels
βΌ
Bazaar / GNOME Software / Discover
Every link in that chain must hold. If any one drops the metadata, the app page is blank β and the failure is silent at every step.
Check an app end to end with:
./scripts/enrich-index.py index/static --check
It reports which published images carry no AppStream metadata.
What each field controlsβ
| Bazaar shows | Comes from |
|---|---|
| App title | <name> |
| Icon | org.freedesktop.appstream.icon-64 / -128 labels |
| One-line description | <summary> |
| Licence badge | <project_license> β must be a valid SPDX identifier |
| Age Rating | <content_rating type="oars-1.1"> |
| Desktop Only / Mobile | <requires><display_length> and <supports><control> |
| Screenshots | <screenshots> β needs publicly reachable image URLs; generate them in CI, see SCREENSHOTS.md |
| Release notes | <releases> |
| Accent colour | <branding><color> |
| Support / donate link | <url type="donation"> |
| Download size | org.flatpak.download-size (automatic) |
| Risk level | the app's finish-args permissions (automatic) |
| Downloads/Month | Flathub's statistics API only. It stays blank on a custom remote β there is nothing to add on our side. |
Writing the fileβ
Start from templates/org.tunaos.example.metainfo.xml.
It follows Flathub's quality guidelines,
which is the same bar Bazaar renders against, and every field is annotated
inline.
The constraints worth repeating:
<name>β 15 characters or fewer reads best, 20 maximum. No version, no tagline.<summary>β 10β25 characters ideal, 35 maximum. Sentence case, no trailing period, no leading article, does not repeat the name.<description>β 3β10 lines. Must not restate the summary.<screenshots>β at least one, 3β6 for a typical app. Window only, no desktop background, default theme, 1000Γ700 maximum (2000Γ1400 HiDPI). Do not take these by hand: generate them in CI from the real app and commit them, so they cannot go stale as the UI changes. See SCREENSHOTS.md for the shared capture action and the wiring.<releases>β real notes per release, not "bug fixes and improvements".<content_rating type="oars-1.1" />β an empty element is correct for most apps and is what turns the "?" age-rating tile into a real value.
Making sure the icon is picked upβ
appstreamcli compose builds the icon-64 / icon-128 labels by scaling the
app's hicolor icon. The build must install one at:
/app/share/icons/hicolor/scalable/apps/<app-id>.svg
or PNGs at 128x128 and 64x64. Without it, the labels are absent and the app
shows a generic placeholder.
Validating locallyβ
# 1. The source file
appstreamcli validate data/org.tunaos.<app>.metainfo.xml
# 2. What the build actually produced
flatpak build-bundle --oci --arch=x86_64 repo <app>.oci org.tunaos.<app>
python3 -c "
import json,pathlib
d=pathlib.Path('<app>.oci')
m=json.loads((d/'index.json').read_text())['manifests'][0]['digest'].split(':')[1]
man=json.loads((d/'blobs/sha256'/m).read_text())
cfg=json.loads((d/'blobs/sha256'/man['config']['digest'].split(':')[1]).read_text())
labels=cfg['config']['Labels']
for k in sorted(labels):
if 'appstream' in k: print(k, len(labels[k]))
"
If that prints nothing, the metainfo file was not installed into the build β
check that the build system actually installs it to /app/share/metainfo/.
Publishingβ
App repositories vendor scripts/update-index.py
at .github/scripts/update-index.py. Pass --require-appstream to make the
publish job fail rather than silently ship an app with no metadata:
python3 .github/scripts/update-index.py \
--oci-dir <app>.oci \
--index-file index-repo/static/flatpak/index/static \
--repo-name tuna-os/<app> \
--require-appstream \
--tags latest