Use AutoPkg to package GitHub repositories with no binary assets

Posted on by Matthew Warren

GitHub is a popular channel for software distribution, and AutoPkg simplifies the task of packaging GitHub-released apps. AutoPkg's GitHubReleasesInfoProvider processor is the standard method for identifying the latest version of software released using GitHub's Releases feature, but it has one key requirement - a binary asset must be attached to the release.

However, not all software adheres to this convention. Some repositories might only contain simple shell scripts or other non-binary assets.

To work around this requirement of GitHubReleasesInfoProvider, you can use AutoPkg's URLTextSearcher and URLDownloader processors to download the release in the form of a zip archive.

Every GitHub release automatically provides compressed archives of repository contents in both zip and tar formats. You can access the URLs of these archives via GitHub's REST API at the following endpoint:

https://api.github.com/repos/{OWNER}/{REPOSITORY}/releases/latest

The JSON response contains a key called "zipball_url" which points to the zip archive:

{
  ...
  "zipball_url": "https://api.github.com/repos/{OWNER}/{REPOSITORY}/zipball/v1.0.0"
  ...
}

Extracting this URL is straighforward using the URLTextSearcher processor and the regular expression zipball_url\": \"(.*)\". Once extracted, you can download the archive using the URLDownloader processor.

With an archive of the repository downloaded, you can proceed with additional processors as needed to package the repository contents.

Example

Consider an example repository that contains a shell script named example.sh, and your goal is to create an installer package that places an executable copy of this script in the default path. Below is a complete example recipe process:

Process:
  - Processor: URLTextSearcher
    Arguments:
      url: https://api.github.com/repos/{OWNER}/{REPOSITORY}/releases/latest
      re_pattern: "zipball_url\": \"(.*)\""
      result_output_var_name: "zipball_url"
      request_headers:
        Accept: application/vnd.github+json
        X-GitHub-Api-Version: "2022-11-28"

  - Processor: URLDownloader
    Arguments:
      url: "%zipball_url%"
      filename: "%NAME%.zip"

  - Processor: Unarchiver
    Arguments:
      archive_path: "%RECIPE_CACHE_DIR%/downloads/%NAME%.zip"

  - Processor: PkgRootCreator
    Arguments:
      pkgdirs: {}
      pkgroot: "%RECIPE_CACHE_DIR%/scripts"

  - Processor: PkgRootCreator
    Arguments:
      pkgdirs:
        usr: "0755"
        usr/local: "0755"
        usr/local/bin: "0755"
      pkgroot: "%RECIPE_CACHE_DIR%/pkgroot"

  - Processor: Copier
    Arguments:
      destination_path: "%RECIPE_CACHE_DIR%/pkgroot/usr/local/bin/example"
      overwrite: true
      source_path: "%RECIPE_CACHE_DIR%/%NAME%/*/example.sh"

  - Processor: FileCreator
    Arguments:
      file_path: "%RECIPE_CACHE_DIR%/scripts/postinstall"
      file_mode: "0755"
      file_content: |
        #!/bin/bash
        /bin/chmod +x /usr/local/bin/example

  - Processor: PkgCreator
    Arguments:
      pkg_request:
        pkgname: "%NAME%"
        pkgdir: "%RECIPE_CACHE_DIR%"
        id: "org.macblog.githubexample"
        options: purge_ds_store
        scripts: scripts
        version: "1.0.0"
        chown:
          - path: usr
            user: root
            group: admin

First, the recipe will locate the latest release of the target repository and its associated zipball_url using URLTextSearcher. Next, it will download the archive using URLDownloader.

Following that, the recipe will extract the archive and create a root directory for building an installer package. It will then copy the target shell script example.sh from the extracted repository contents to the package root directory at the required location (e.g., /usr/local/bin/example). Finally, a postinstall script is created to ensure that the file is executable after installation, and everything is packaged into a .pkg.

Authentication for private repos

This recipe pattern is also handy for accessing releases without assets in private repositories. To do this, you'll need to create a personal access token and set the GITHUB_TOKEN environment variable as described here. Then, simply add an Authorization header using Bearer authentication to the URLTextSearcher and URLDownloader processors as follows:

- Processor: URLTextSearcher
  Arguments:
    url: https://api.github.com/repos/{OWNER}/{REPOSITORY}/releases/latest
    re_pattern: "zipball_url\": \"(.*)\""
    result_output_var_name: "zipball_url"
    request_headers:
      Accept: application/vnd.github+json
      X-GitHub-Api-Version: "2022-11-28"
      Authorization: "Bearer %GITHUB_TOKEN%"

- Processor: URLDownloader
  Arguments:
    url: "%zipball_url%"
    filename: "%NAME%.zip"
    request_headers:
      Authorization: "Bearer %GITHUB_TOKEN%"

This approach keeps your options open for packaging a variety of GitHub releases, whether they include binary assets or not.

Questions or comments?

Discuss this post on GitHub Discussions ➫.