Skip to main content

Publishing to the official store

The official add-on store lives at github.com/apexinfosysindia/addons. The Supervisor ships with this repository built in (repository slug core); every device syncs it, so a merged add-on is visible to the whole fleet on the next store refresh.

Repository layout

Each add-on is a top-level directory named after its slug, containing the full add-on anatomy described in getting started:

addons/
├── .github/workflows/publish-addons.yml
├── samba/
│ ├── config.yaml
│ ├── build.yaml
│ ├── Dockerfile
│ ├── rootfs/
│ ├── DOCS.md
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── icon.png
│ ├── logo.png
│ └── translations/en.yaml
├── ssh/
├── zigbee2mqtt/
└── ... (18 add-ons)

PR flow

  1. Fork/branch, add or modify your add-on directory.
  2. Bump version: in config.yaml — the version is the image tag, so a content change without a version bump will not reach devices.
  3. Update CHANGELOG.md for the new version.
  4. Open a pull request against main. The publish-addons workflow runs on every PR and builds every add-on for every supported architecture — a PR must prove the builds, but nothing is pushed from a PR.
  5. After merge to main, the same workflow rebuilds and pushes the prebuilt images to the registry.

Two writing rules for PRs in this repository:

  • Wording in configs, docs, commits, and code must be ApexOS-native — no upstream project names (the platform validates only native keys anyway; see the legacy-alias note).
  • User-facing text follows the store conventions in presentation.

The publish workflow

.github/workflows/publish-addons.yml builds prebuilt images for all locally-buildable add-ons and pushes them to the ApexOS registry as:

registry.apexinfosys.in/apexos/{arch}-addon-<slug>:<version>

Triggers and gating

  • Runs on push to main, on pull_request targeting main, and manually (workflow_dispatch).
  • Concurrency is grouped per ref with cancel-in-progress, so a superseded run is stopped.
  • All jobs skip for dependabot[bot].
  • The push step runs only on main (github.event_name != 'pull_request') and fails hard if the REGISTRY_PASSWORD secret is missing.

Job 1: mirror-bases

Third-party base images that add-ons build FROM are mirrored into the org registry first, so CI and devices never depend on external registries at build or ship time. Currently that is the matter_server base: ghcr.io/matter-js/matterjs-server:1.2.6 is copied (with skopeo copy --all, preserving the multi-arch manifest) to registry.apexinfosys.in/apexos/matterjs-server:1.2.6. The copy is idempotent.

Job 2: build (the matrix)

A full cross-product of:

  • Architectures: amd64 on ubuntu-latest, aarch64 on ubuntu-24.04-arm — native-arch runners, no emulation.
  • Add-ons: all 18 store add-ons (aircast, apex-cloud-link, autossh, cec_scan, configurator, matter_server, mosquitto, openthread_border_router, openwakeword, piper, plex, samba, speech_to_phrase, spotify, ssh, vscode, whisper, zigbee2mqtt).

Each leg:

  1. Reads metadata from the add-on's config.yaml (or config.json) and its build.yaml — falling back to a repo-root build.yaml if the add-on has none. It extracts slug, version, the per-arch build_from, and args.

  2. Auto-skips when the add-on does not list the architecture in arch: or has no build_from for it — an unsupported leg is a clean skip, not a failure.

  3. Builds with the same argument contract the Supervisor uses for local builds:

    docker buildx build --load "$ADDON" \
    --build-arg "BUILD_FROM=$BUILD_FROM" \
    --build-arg "BUILD_ARCH=$ARCH" \
    --build-arg "BUILD_VERSION=$VERSION" \
    ${extra_args_from_build_yaml} \
    --label "io.apexos.arch=$ARCH" \
    -t "registry.apexinfosys.in/apexos/$ARCH-addon-$SLUG:$VERSION"

    Because CI and the on-device builder pass identical BUILD_FROM/BUILD_ARCH/BUILD_VERSION args, a Dockerfile that builds locally also builds in the pipeline.

  4. Pushes the tag on main after docker login with the REGISTRY_USER/REGISTRY_PASSWORD org secrets (a robot credential).

A final addons-published job asserts the whole matrix succeeded, giving one required status to gate on.

One deliberate difference from the product pipelines

The add-on pipeline runs no denylist image-gate: add-on payloads are third-party upstream software (mosquitto, media servers, ...), not pipeline output, so the gate's zero-violation contract applies only to ApexOS base and product images.

The image: key convention

Prebuilt add-ons point the Supervisor at the published image via config.yaml:

image: registry.apexinfosys.in/apexos/{arch}-addon-samba
  • {arch} is substituted with the device architecture at install time.
  • The pulled tag is always the version from config.yaml — which is why the workflow tags images with the config version, and why every content change needs a version bump.
  • Presence of image: means "pull"; absence means "build on device". Devices then pull prebuilds from the ApexOS registry instead of building locally. Keep the image: slug identical to the add-on slug so it matches what the workflow pushes: {arch}-addon-<slug>.

Store metadata checklist

Before opening the PR, make sure the directory carries the presentation assets the store renders (details in presentation):

  • icon.png — square icon, fixed filename.
  • logo.png — wide logo, fixed filename.
  • README.md — becomes the add-on's long description in the store.
  • DOCS.md — rendered as the Documentation tab; written for end users (install steps, options, examples).
  • CHANGELOG.md — rendered when an update is offered; add a ## <version> section per release.
  • translations/en.yaml — names and descriptions for every option in schema:, so the configuration UI shows labels instead of bare keys.