Skip to content

serversideup/github-action-selfhostpro-release

Repository files navigation

Self-Host Pro — Publish Release · GitHub Action

Ship desktop app releases to your customers — powered by Self-Host Pro.

GitHub Marketplace License: MIT

Upload your desktop app builds (Tauri, Electron, native) to Self-Host Pro straight from CI, then publish and promote a release channel — in one step of your pipeline.

The action is a thin, auditable wrapper (bash + curl + jq) over the Self-Host Pro CI release API:

Command What it does Use it for
github-release Mirrors an on: release event — classifies assets, uploads, finalizes The one-step path. Start here.
upload (default) Uploads one artifact (file + os + arch) one platform per matrix job
finalize Publishes the release + promotes a channel run once, after a matrix

Quick start

📁 Runnable, copy-paste versions of every workflow below live in examples/ (github-release.yml, matrix-build.yml, parallel-builds.yml).

Option A — mirror a GitHub Release (simplest) ✨

Cut a release in the GitHub UI (or with gh release create) — attach your binaries, write notes, mark it Latest or Pre-release. This workflow mirrors all of it to Self-Host Pro automatically:

name: Publish to Self-Host Pro
on:
  release:
    types: [published]

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: serversideup/github-action-selfhostpro-release@v1
        with:
          command: github-release
          team: ${{ vars.SHP_TEAM }}
          product: ${{ vars.SHP_PRODUCT }}
          email: ${{ secrets.SHP_EMAIL }}
          token: ${{ secrets.SHP_TOKEN }}

That's the whole workflow. The action reads the release, infers os/arch from each asset's filename (matching the dashboard's own detection), attaches any .sig signatures, uploads every binary, and finalizes — carrying over your notes and mapping Latest → stable, Pre-release → beta. See github-release mode for the details and the asset_map escape hatch.

Option B — upload from a matrix build

When you build inside the same pipeline, a matrix uploads one platform per job to a draft release, then a single finalize job publishes it and promotes it to a channel:

name: Release
on:
  push:
    tags: ["v*"]

jobs:
  build:
    strategy:
      matrix:
        include:
          - { runner: macos-14,     os: macos,   arch: arm64, ext: dmg }
          - { runner: macos-13,     os: macos,   arch: x64,   ext: dmg }
          - { runner: windows-2022, os: windows, arch: x64,   ext: msi }
          - { runner: ubuntu-22.04, os: linux,   arch: x64,   ext: AppImage }
    runs-on: ${{ matrix.runner }}
    steps:
      - uses: actions/checkout@v4
      # ... build your app, producing dist/MyApp.<ext> (and dist/MyApp.<ext>.sig for Tauri) ...
      - uses: serversideup/github-action-selfhostpro-release@v1
        with:
          team: ${{ vars.SHP_TEAM }}
          product: ${{ vars.SHP_PRODUCT }}
          email: ${{ secrets.SHP_EMAIL }}
          token: ${{ secrets.SHP_TOKEN }}
          file: dist/MyApp.${{ matrix.ext }}
          os: ${{ matrix.os }}
          arch: ${{ matrix.arch }}
          # format is auto-derived from the file extension; set it only to override

  finalize:
    needs: build
    runs-on: ubuntu-22.04
    steps:
      - uses: serversideup/github-action-selfhostpro-release@v1
        with:
          command: finalize
          team: ${{ vars.SHP_TEAM }}
          product: ${{ vars.SHP_PRODUCT }}
          email: ${{ secrets.SHP_EMAIL }}
          token: ${{ secrets.SHP_TOKEN }}
          publish: true
          channel: stable

Because release creation is race-safe server-side, every matrix job converges on one draft release for the version — no duplicate rows, no ordering requirements.

Many parallel build jobs → one release

A matrix isn't required. If your pipeline already has separate, independently-parallel build jobs (different runners, different toolchains — a big fan-out CI), each one just adds an upload step for the artifact it produces. They all target the same version, so they converge on a single release no matter what order they finish in. A final release job, gated on all of them with needs:, publishes once:

name: Release
on:
  push:
    tags: ["v*"]          # all jobs derive the same version from the tag

jobs:
  build-macos:
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
      # ... build → dist/MyApp-arm64.dmg (+ .sig) ...
      - uses: serversideup/github-action-selfhostpro-release@v1
        with: { team: "${{ vars.SHP_TEAM }}", product: "${{ vars.SHP_PRODUCT }}",
                email: "${{ secrets.SHP_EMAIL }}", token: "${{ secrets.SHP_TOKEN }}",
                file: dist/MyApp-arm64.dmg, os: macos, arch: arm64 }

  build-windows:
    runs-on: windows-2022
    steps:
      - uses: actions/checkout@v4
      # ... build → dist/MyApp-x64.msi ...
      - uses: serversideup/github-action-selfhostpro-release@v1
        with: { team: "${{ vars.SHP_TEAM }}", product: "${{ vars.SHP_PRODUCT }}",
                email: "${{ secrets.SHP_EMAIL }}", token: "${{ secrets.SHP_TOKEN }}",
                file: dist/MyApp-x64.msi, os: windows, arch: x64 }

  build-linux:
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v4
      # ... build → dist/MyApp-x64.AppImage ...
      - uses: serversideup/github-action-selfhostpro-release@v1
        with: { team: "${{ vars.SHP_TEAM }}", product: "${{ vars.SHP_PRODUCT }}",
                email: "${{ secrets.SHP_EMAIL }}", token: "${{ secrets.SHP_TOKEN }}",
                file: dist/MyApp-x64.AppImage, os: linux, arch: x64 }

  release:
    needs: [build-macos, build-windows, build-linux]   # runs only after every upload
    runs-on: ubuntu-22.04
    steps:
      - uses: serversideup/github-action-selfhostpro-release@v1
        with:
          command: finalize
          team: ${{ vars.SHP_TEAM }}
          product: ${{ vars.SHP_PRODUCT }}
          email: ${{ secrets.SHP_EMAIL }}
          token: ${{ secrets.SHP_TOKEN }}
          publish: true
          channel: stable

Why this works — and what to watch for:

  • No coordination between builds. Concurrent uploads to the same version converge on one draft release (UNIQUE(product_id, version) server-side). Order doesn't matter; there's no "create the release first" step.
  • finalize waits for needs:. The release isn't published until every listed build job succeeds. If one platform fails, nothing is published.
  • Every job must agree on the version. Deriving it from the pushed tag (above) guarantees that. If you're not triggering on a tag, set version: explicitly on every job (e.g. from a shared job output) so they all hit the same release.
  • Re-running one failed build re-uploads only that platform's artifact (it replaces the os+arch slot); the others are untouched. Re-run release afterward to publish.
  • Folding into an existing CI (like a big on: push pipeline): add the upload step to the build jobs you already have, and gate the publish so you don't cut a release on every commit — either trigger a separate workflow on: push: tags, or add if: startsWith(github.ref, 'refs/tags/') to the release job.

Prefer the GitHub-UI release flow instead? github-release mode collapses all of this into a single step.


How it works

matrix jobs → upload one artifact each   (command: upload)   → POST …/releases/{version}/artifacts
final job   → publish + promote once     (command: finalize) → POST …/releases/{version}/finalize
  • Versions are stored bare. A pushed v2.1.0 tag becomes version 2.1.0 (the leading v/V is stripped both by the action and server-side).
  • Re-uploading replaces. Pushing the same os+arch again to a draft replaces that artifact.
  • Opinionated, safe flow. The upload command intentionally does not publish or promote, so a parallel matrix can't publish a release mid-build. Publishing/promoting goes through finalize, which runs needs: build.

Inputs

Input Required Default Notes
command upload upload or finalize
base_url https://app.selfhostpro.com Your instance
team Team slug (the URL slug, not the display name)
product Product slug
version derived from tag Falls back to the pushed git tag (${GITHUB_REF_NAME} with a leading v stripped)
email Account email (HTTP Basic username) — use a secret
token Team access token (HTTP Basic password) — use a secret
file upload only Path to the binary
os upload only macos | windows | linux
arch upload only arm64 | x64 | x86 | universal (universal is macOS-only)
format auto Auto-derived from the file extension server-side. Set only to override.
signature Update signature string. If empty, the action reads <file>.sig when present.
notes Release notes (applied on the first upload of a version, or via finalize)
publish true Used by finalize
channel Channel slug to promote to (e.g. stable) — used by finalize
max_retries 3 Attempts on 429 / 5xx / transport errors
dry_run false Print the composed request without sending
github_token ${{ github.token }} Token to download release assets (github-release)
asset_map Per-file filename=os/arch (or filename=skip) overrides (github-release)
latest_channel stable Channel a Latest release is promoted to (github-release)
prerelease_channel beta Channel a Pre-release is promoted to (github-release)

os and arch are case-folded for you (macOSmacos) and validated before the request, so a typo fails fast with a clear message instead of a server 422.

Outputs

Output Description
version Version acted upon (bare, e.g. 2.1.0)
release_status draft | published
artifact_id Uploaded artifact id (upload command)

The action also appends a summary table (version, command, os/arch, status, size, checksum) to the job's Step Summary.


The two commands

upload (default)

Uploads a single artifact to the version's draft release. Required: file, os, arch. The release is created on first upload and reused by every later upload for that version.

  • A <file>.sig next to the binary is attached automatically as the update signature (handy for Tauri). Override with the signature input.
  • format is derived from the file extension (.dmgdmg, .AppImageappimage, …); set it only when your filename doesn't carry the right extension.

finalize

Publishes the release (requires at least one uploaded artifact) and, if channel is given, promotes it. Run it once with needs: build after the matrix. publish defaults to true.


github-release mode

command: github-release turns a published GitHub Release into a Self-Host Pro release in one step. It runs on on: release and:

  1. Reads the tag → version (v3.2.03.2.0), the body → release notes, and the Latest/Pre-release flag → channel.
  2. Classifies every attached asset into os/arch from its filename (see below).
  3. Attaches a matching <asset>.sig / <asset>.asc signature when present.
  4. Downloads and uploads each binary, then finalizes — publishing and promoting to the channel.

No API changes, no per-asset wiring. For private repos, the default github_token is enough to download the assets.

Channel mapping

GitHub release Self-Host Pro channel Override input
Latest (not a pre-release) stable latest_channel
Pre-release beta prerelease_channel

Set an override to "" to publish without promoting to a channel.

Filename → os/arch inference

Inference mirrors the Self-Host Pro dashboard exactly, so an asset classified here lands the same as one uploaded through the UI.

OS comes from the file extension:

Extension OS
.dmg, .pkg macos
.exe, .msi windows
.deb, .rpm, .AppImage linux
anything else defaults to macos (with a warning — use asset_map)

Arch comes from filename tokens (first match wins, case-insensitive):

Token in filename Arch
aarch64, arm64, apple-silicon, -arm arm64
universal universal (macOS only)
x86_64, x64, amd64, intel x64
i386, ia32, win32, x86, -386 x86
none of the above arm64 (macOS) · x64 (windows / linux)

Arch is then clamped to what the OS allows (universal only on macOS; x86 not on macOS), so the result is always a valid pair.

Sidecars are handled for you: .sig / .asc files are attached to their binary, and checksums.txt, *.sha256, latest.json, and similar are skipped.

asset_map — deterministic overrides

When a filename doesn't carry a recognizable platform — or you want to skip an asset — map it explicitly. One entry per line, exact filename match, # for comments:

      with:
        command: github-release
        # ...
        asset_map: |
          MyApp-portable.bin = linux/x64
          MyApp.zip          = macos/universal
          sbom.spdx.json     = skip

Authentication

Both endpoints use HTTP Basic auth with the same credentials you use for docker login:

  • username = your Self-Host Pro account email
  • password = a team access token

Always pass these as secrets (email/token). The action masks the token in logs.

Signing

The action does not sign anything — it just attaches the signature you provide (the signature input, or a <file>.sig it discovers on disk). Generate signatures with your toolchain (e.g. the Tauri updater) and point the action at them. See the Self-Host Pro and Tauri docs for the signing setup.

Upload size

The CI upload streams through your Self-Host Pro instance (PHP). The server's application-level ceiling defaults to 1 GB (configurable via RELEASES_MAX_UPLOAD_KB), but in production it is also bounded by PHP upload_max_filesize / post_max_size and your reverse-proxy body-size limits — raise all three together if you ship large binaries. Typical Tauri builds fit comfortably.


Versioning & pinning

This action publishes a moving v1 major tag plus immutable vX.Y.Z tags.

  • Convenience: serversideup/github-action-selfhostpro-release@v1
  • Maximum supply-chain safety (recommended for production): pin to a commit SHA serversideup/github-action-selfhostpro-release@<sha>

Links

License

MIT © Server Side Up

About

Ship desktop app releases to your customers — powered by https://selfhostpro.com

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages