Skip to content

Repository files navigation

Zero Install GitHub Actions

Reusable GitHub Actions for publishing software with Zero Install, wrapping the 0template, 0publish and 0repo tools.

All actions run on Linux, macOS and Windows runners and download the tools they need on-demand.

Action Purpose
setup Make the 0install command available to subsequent steps
0template Generate a feed (and archive) for a specific version from a template
0publish Edit a feed in place, e.g. add a new version to it or sign it
0repo-setup Check out a 0repo repository into the directory structure 0repo expects
0repo Merge incoming feeds into the repository, sign them and regenerate the catalog
0repo-submit Trigger the workflow of a 0repo repository to pick up a new release

Usage

setup

Makes the 0install command available to subsequent steps, downloading it if it is not already on the PATH. The other actions do this for themselves, so you only need this if you want to run 0install commands yourself.

- uses: 0install/github-actions/setup@v1
- run: 0install run https://apps.0install.net/0install/0test.xml myapp.xml

0template

Runs 0template on a *.xml.template file, producing a feed for a single version. If the template uses local-path with a relative href, the archive is generated too.

- uses: 0install/github-actions/0template@v1
  id: feed
  with:
    template: myapp.xml.template
    version: '1.2.3'
Input Description
template The path of the *.xml.template file. Required.
version The version number of the release. Required. Write in quotes to avoid accidental interpretation as a floating point number (e.g., '1.0' instead of 1.0).
args Additional whitespace-separated key=value parameters for the template, e.g. released=2026-08-02.
archive-url The URL the generated archive will be published at. Relative hrefs in the generated feed are replaced with this URL. A value ending in / is treated as a directory and the archive's file name is appended.
github-release Set to true if the generated archive will be attached to the GitHub Release for the current tag. Shorthand for archive-url: https://github.com/OWNER/REPO/releases/download/TAG/.
upload-artifact Set to false to keep the generated feed and archives from being uploaded as a GitHub Actions artifact. Defaults to true.
artifact-name The name of that artifact. Defaults to feed. Artifact names must be unique within a workflow run, so override this if you run the action more than once (e.g. in a matrix job).
Output Description
feed The path of the generated feed, e.g. myapp-1.2.3.xml.
archive The paths of the generated archives, one per line. Empty if the feed references external archives.

0template writes its output next to the template and resolves local-path relative to it, so a template in a subdirectory produces its feed in that subdirectory. The outputs are relative to the workspace either way, so they can be passed straight to other actions:

- uses: softprops/action-gh-release@v3
  with:
    files: |
      ${{steps.feed.outputs.feed}}
      ${{steps.feed.outputs.archive}}

The files are also uploaded as a GitHub Actions artifact, so they can be inspected or downloaded by a later job even on builds that do not publish anything.

Feeds usually need to point at an absolute URL, while 0template needs a relative href to generate the archive. Use archive-url to bridge the two, or github-release if the archive is going to be an asset of the GitHub Release for the current tag:

- uses: 0install/github-actions/0template@v1
  with:
    template: myapp.xml.template
    version: ${{github.ref_name}}
    github-release: true

is equivalent to:

    archive-url: https://github.com/${{github.repository}}/releases/download/${{github.ref_name}}/

0publish

Runs 0publish on a feed, editing it in place. Typically used to merge a newly generated per-version feed into a master feed that lists all versions.

- uses: 0install/github-actions/0publish@v1
  with:
    feed: gh-pages/myapp.xml
    add-from: ${{steps.feed.outputs.feed}}
    gpg-key: ${{secrets.GPG_KEY}}
Input Description
feed The path of the feed to edit. Required.
add-from The path or URL of a feed whose implementations should be added to the feed.
set-released The release date to set in the format YYYY-MM-DD (or today).
set-stability The stability rating to set (stable, testing, developer, ...).
key The ID of the GnuPG key to sign with. Defaults to the key the feed was previously signed with.
xmlsign Set to true to add a signature to a previously unsigned feed and export the public key next to it.
gpg-key An ASCII-armored GnuPG secret key to import before running 0publish.
args Additional whitespace-separated arguments to pass to 0publish.

If the feed was already signed, 0publish resigns it with the same key, so gpg-key needs to hold that key. Export it with gpg --export-secret-keys --armor YOURKEY and store it as a repository secret. The key must not be protected by a passphrase.

0repo-setup

Checks out a 0repo repository into the directory structure 0repo expects.

- uses: 0install/github-actions/0repo-setup@v1

This replaces the actions/checkout step that usually sits at the top of a workflow. 0repo works on two branches of the same repository at once, and needs them next to each other rather than at the workspace root, so this action performs both checkouts itself and creates the scaffolding around them:

feeds/           # default branch: 0repo-config.py, the unsigned feeds and templates
public/          # gh-pages branch: the published (signed) feeds and archives.db
incoming/        # empty, holds feeds waiting to be merged
0repo-config.py  # symlink to feeds/0repo-config.py
archives.db      # symlink to public/archives.db

Later steps in the job therefore work inside these directories rather than at the workspace root.

Input Description
repository The repository holding the 0repo configuration and the unsigned feeds. Defaults to the current repository.
ref The branch, tag or SHA to check out the feeds from. Defaults to the default branch.
public-ref The branch holding the published (signed) feeds. Defaults to gh-pages.
token The token to use for checking out and (later) pushing the repository. Defaults to github.token.
git-user-name, git-user-email The identity to use for Git commits.

0repo

Runs 0repo, which validates the feeds in incoming/, merges them into the master feeds, signs everything and regenerates the catalog.

- uses: 0install/github-actions/0repo@v1
  with:
    gpg-key: ${{secrets.GPG_KEY}}
Input Description
incoming-feed-url The URL of a feed to download into incoming/ before running 0repo.
incoming-archive-url The URL of an archive to register in archives.db.
gpg-key An ASCII-armored GnuPG secret key to sign the feeds with.
push-feeds Set to true to push the merged feeds in feeds/ back to the repository.
push-public Set to true to push the published feeds in public/ back to the repository.
working-directory The directory containing the 0repo directory structure. Defaults to the workspace root.
args Additional whitespace-separated arguments to pass to 0repo.

0repo commits its results to both checkouts made by 0repo-setup: the published feeds to public/ and the merged feeds to feeds/.

By default it leaves those commits sitting in the workspace, which is what you want when validating a pull request, or when the changes are going into a pull request of their own. You can configure the action to push them for you, so the workflow needs no git push step of its own:

- uses: 0install/github-actions/0repo@v1
  with:
    gpg-key: ${{secrets.GPG_KEY}}
    push-feeds: true
    push-public: true

If gpg-key is not set, 0repo runs with the environment variable NO_SIGN set. Have your 0repo-config.py honor it, so that pull requests can be validated without exposing the production key:

GPG_SIGNING_KEY = None if os.getenv('NO_SIGN') else "0xYOURKEYFINGERPRINT"

Such a run produces unsigned feeds, which must never end up on the published branch. public/ is therefore only pushed when gpg-key is set, even if push-public is true.

incoming-feed-url fetches a feed published elsewhere into incoming/, so a single step covers the whole "a new release arrived" case:

- uses: 0install/github-actions/0repo@v1
  with:
    incoming-feed-url: ${{inputs.feed_url}}
    incoming-archive-url: ${{inputs.archive_url}}
    gpg-key: ${{secrets.GPG_KEY}}
    push-feeds: true
    push-public: true

Archives hosted by the repository itself are handled by 0repo. incoming-archive-url is only needed for archives hosted elsewhere (typically as assets of a GitHub Release in the application's own repository): the archive is downloaded, hashed and registered in archives.db so that 0repo can point the published feed at it.

0repo-submit

Triggers a workflow_dispatch workflow in a 0repo repository, telling it to pick up a newly released feed. Use this in the workflow of an application that publishes into a central feed repository.

- uses: 0install/github-actions/0repo-submit@v1
  with:
    repository: 0install/apps
    feed-url: ${{steps.feed.outputs.feed}}
    archive-url: ${{steps.feed.outputs.archive}}
    token: ${{secrets.PERSONAL_TOKEN}}
Input Description
repository The repository running 0repo, e.g. 0install/apps. Required.
feed-url The URL of the feed to merge into the repository. Required.
archive-url The URL of an archive to register in archives.db.
token A token with Actions: write permission for the target repository. Required.
workflow The name of the workflow to trigger. Defaults to Incoming.
ref The branch or tag to run the workflow from.

feed-url and archive-url may be relative instead of absolute, in which case they are resolved against the GitHub Release for the current tag. The example above is therefore equivalent to:

    feed-url: https://github.com/${{github.repository}}/releases/download/${{github.ref_name}}/myapp-1.2.3.xml
    archive-url: https://github.com/${{github.repository}}/releases/download/${{github.ref_name}}/myapp-1.2.3.tar.gz

Since assets of a GitHub Release are flat, only the file name of a relative path is used. That way the paths output by the 0template action can be passed straight through, even for a template in a subdirectory.

The default GITHUB_TOKEN only has permissions for the current repository, so token needs to be a (fine-grained) personal access token stored as a secret.

Examples

Publishing a single feed to GitHub Pages

The master feed lives on the gh-pages branch of the same repository. Each release generates a feed for the new version, merges it into the master feed and pushes it back.

name: Release
on:
  push:
    tags: ['*']

jobs:
  release:
    runs-on: ubuntu-latest
    permissions: {contents: write}
    steps:
      - uses: actions/checkout@v7
      - uses: actions/checkout@v7
        with: {ref: gh-pages, path: gh-pages}

      - name: Build
        run: ./build.sh ${{github.ref_name}}

      - name: Generate feed
        id: feed
        uses: 0install/github-actions/0template@v1
        with:
          template: myapp.xml.template
          version: ${{github.ref_name}}
          github-release: true

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v3
        with:
          files: |
            ${{steps.feed.outputs.feed}}
            ${{steps.feed.outputs.archive}}

      - name: Add to master feed
        uses: 0install/github-actions/0publish@v1
        with:
          feed: gh-pages/myapp.xml
          add-from: ${{steps.feed.outputs.feed}}
          gpg-key: ${{secrets.GPG_KEY}}
      - name: Push gh-pages
        working-directory: gh-pages
        run: |
          git config user.name github-actions[bot]
          git config user.email 41898282+github-actions[bot]@users.noreply.github.com
          git commit -am "Published ${{github.ref_name}}"
          git push

Releasing into a 0repo repository

The application's workflow attaches the feed and the archive to a GitHub Release and hands both URLs to the central feed repository:

      - name: Generate feed
        id: feed
        uses: 0install/github-actions/0template@v1
        with:
          template: myapp.xml.template
          version: ${{github.ref_name}}
          github-release: true

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v3
        with:
          files: |
            ${{steps.feed.outputs.feed}}
            ${{steps.feed.outputs.archive}}

      - name: Submit feed
        uses: 0install/github-actions/0repo-submit@v1
        with:
          repository: myname/feeds
          feed-url: ${{steps.feed.outputs.feed}}
          archive-url: ${{steps.feed.outputs.archive}}
          token: ${{secrets.PERSONAL_TOKEN}}

The matching Incoming workflow in the feed repository:

name: Incoming
on:
  workflow_dispatch:
    inputs:
      feed_url: {required: true, description: URL of feed to merge in}
      archive_url: {required: false, description: URL of archive to add to archives.db}
concurrency: {group: publish}

jobs:
  incoming:
    runs-on: ubuntu-latest
    steps:
      # Checks out the repository; no actions/checkout needed
      - uses: 0install/github-actions/0repo-setup@v1
      - uses: 0install/github-actions/0repo@v1
        with:
          incoming-feed-url: ${{inputs.feed_url}}
          incoming-archive-url: ${{inputs.archive_url}}
          gpg-key: ${{secrets.GPG_KEY}}
          push-feeds: true
          push-public: true

See the packaging tutorials for the bigger picture and 0install/apps for a real-world deployment.

About

Actions for working with 0install feeds in your GitHub CI workflows

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages