Packaging and releases¶
Everything is built by GitHub Actions from a version tag. This page explains the pieces and how to build them locally.
Version¶
The version lives in one place: __version__ in backend/fbadmin/__init__.py
(pyproject.toml reads it dynamically). Keep frontend/package.json and the Helm chart's
appVersion in step when releasing.
Release checklist¶
- Bump
__version__,deploy/helm/fbadmin/Chart.yaml(appVersion, andversionif the chart changed) and the<release>entry inpackaging/flatpak/io.github.mariuz.fbadmin.metainfo.xml. - Run
scripts/gen-api-types.shif the API changed; make sure CI is green. - Tag and push:
git tag -a v1.0.1 -m "fbadmin 1.0.1" && git push origin v1.0.1.
The tag starts three workflows:
| Workflow | Output |
|---|---|
release.yml |
sdist + wheel (with the UI) → PyPI (trusted publishing) and a GitHub Release |
docker.yml |
ghcr.io/mariuz/fbadmin:1.0.0, :1.0, :latest for amd64 and arm64 |
desktop.yml |
Windows zip + MSI, macOS dmg (arm64, x86_64), Linux AppImage/deb/rpm, attached to the release |
release.yml refuses to publish when the tag does not match __version__.
One-time setup¶
- PyPI trusted publishing: the distribution is
firebird-admin, because the namefbadminon PyPI belongs to an unrelated older project. The import name and thefbadmin/fbadmin-desktopcommands are unchanged. On pypi.org, add a GitHub publisher for project firebird-admin: ownermariuz, repositoryfbadmin, workflowrelease.yml, environmentpypi. Before the first upload, add it as a "pending publisher" under Your account → Publishing. No API token is stored in GitHub. - GitHub environment
pypi(Settings → Environments), optionally with required reviewers so a person approves each upload. - GHCR: the workflow pushes with
GITHUB_TOKEN; make the package public once in the package settings. - Pages: Settings → Pages → Source: GitHub Actions (for
docs.yml).
Python package (PyPI)¶
scripts/build-dist.sh # npm ci + build UI, python -m build, scripts/check_dist.py
SMOKE=1 scripts/build-dist.sh # also install the wheel in a fresh venv and fetch the UI
scripts/smoke-wheel.sh dist/*.whl 5106
scripts/check_dist.py fails when the wheel lacks fbadmin/static/index.html, when an
asset referenced by index.html is missing, or when a console script is missing. The
wheel job in CI runs the same steps on every push.
The wheel (firebird_admin-X.Y.Z-py3-none-any.whl, installed with pip install firebird-admin)
is pure Python and installs two commands: fbadmin (server,
opens the browser) and fbadmin-desktop (native window). Extras: desktop (pywebview),
native (firebird-driver), dev (tests).
Docker image¶
Dockerfile builds the UI in a node stage (on the build platform, since the output is
architecture-independent) and installs the backend into python:3.12-slim. It runs as
uid 1000, exposes 5050, keeps data in /data and has a HEALTHCHECK on /api/health.
docker.yml builds linux/amd64,linux/arm64 with QEMU + Buildx, pushes edge from main
and semver tags from releases, with SBOM and provenance attestations. Pull requests that
touch the Dockerfile build both platforms without pushing.
Desktop app¶
fbadmin-desktop (backend/fbadmin/desktop.py) starts uvicorn on a free 127.0.0.1 port
in a background thread and opens a pywebview window
(WebView2 / WKWebView / WebKitGTK). Without pywebview or a GUI backend it falls back to the
default browser and keeps serving until Ctrl+C.
The frozen app is built with PyInstaller:
pip install "./backend[desktop]" pyinstaller pillow
(cd frontend && npm ci && npm run build)
pyinstaller packaging/pyinstaller/fbadmin.spec --noconfirm # -> dist/fbadmin/ or dist/fbadmin.app
packaging/linux/build-packages.sh 0.2.0 appimage deb rpm # Linux: needs fpm (and rpmbuild)
wix build packaging/windows/fbadmin.wxs -arch x64 -wx -d SourceDir=%CD%\dist\fbadmin -d Version=0.2.0 -o fbadmin.msi
| Platform | Format | How |
|---|---|---|
| Windows | .zip (portable), .msi |
Compress-Archive; WiX v5 (packaging/windows/fbadmin.wxs) |
| macOS | .dmg with fbadmin.app |
PyInstaller BUNDLE + hdiutil (arm64 on macos-14, x86_64 on macos-15-intel) |
| Linux | AppImage, .deb, .rpm |
packaging/linux/build-packages.sh (appimagetool, fpm) on Ubuntu 22.04 |
| Linux | Flatpak | packaging/flatpak/io.github.mariuz.fbadmin.yml (GNOME runtime). The flatpak job in desktop.yml builds a .flatpak bundle, and a failure there doesn't fail the workflow. |
Notes and limitations:
- Signing:
desktop.ymlsigns the builds once these repository secrets exist (Settings → Secrets and variables → Actions). Without them the packages are unsigned, the workflow notes that, and macOS Gatekeeper and Windows SmartScreen warn on first start.
| Secret | Content |
|---|---|
MACOS_CERT_P12 |
base64 of the Developer ID Application certificate and key (.p12) |
MACOS_CERT_PASSWORD |
password of that .p12 |
MACOS_SIGN_IDENTITY |
Developer ID Application: Your Name (TEAMID) |
APPLE_ID, APPLE_TEAM_ID, APPLE_APP_PASSWORD |
notarization: Apple ID, team ID, app-specific password |
WINDOWS_CERT_PFX |
base64 of the Authenticode code-signing certificate (.pfx) |
WINDOWS_CERT_PASSWORD |
password of that .pfx |
- macOS: the app is signed with the hardened runtime and
packaging/macos/entitlements.plist, then the dmg is signed, notarized, stapled and checked withspctl. - Windows:
fbadmin.exeand the MSI are signed withsigntooland an RFC 3161 timestamp. The zip contains the signed exe.
Create base64 values with base64 -i cert.p12 | pbcopy (macOS) or
[Convert]::ToBase64String([IO.File]::ReadAllBytes("cert.pfx")) (PowerShell).
- Linux window: the native window uses WebKitGTK through PyGObject, which is bundled at
build time; the .deb/.rpm depend on the system WebKitGTK 4.1 libraries. The
AppImage expects them on the host as well and otherwise falls back to the browser.
- Flatpak: the manifest installs the wheel with pip during the build (needs
--share=network at build time). For Flathub, generate a vendored module with
flatpak-pip-generator instead.
- MSI: built with WiX v5's Files harvesting; the step is allowed to fail without
failing the job, the zip is always produced.
Updates¶
The desktop app never updates itself silently. Users opt in to Check GitHub for new
releases in the About dialog. When a newer release exists, the top bar shows "Update X.Y.Z"
and the About dialog offers Download and install (backend/fbadmin/updater.py):
- It picks the package for this platform from the latest release:
fbadmin-X.Y.Z-x64.msi,fbadmin-X.Y.Z-macos-<arch>.dmgorfbadmin-X.Y.Z-<arch>.AppImage. - It downloads the package into
<data dir>/updatesand checks it against the release'sSHA256SUMS, which the desktop workflow publishes. A package with no checksum, or a wrong one, is refused. - It installs the package:
| Install | What happens |
|---|---|
| Windows (MSI) | Runs msiexec /i … /passive, then fbadmin quits so the installer can replace it. |
| Linux (AppImage) | Atomically replaces the running AppImage ($APPIMAGE), then restarts. |
| macOS (dmg) | Opens the disk image; you drag fbadmin to Applications. An unsigned app cannot replace itself. |
deb, rpm, Flatpak, pip, Docker and Helm installs don't update themselves. Their package
manager does that, and the UI only links to the release. Self-update needs the desktop
launcher (FBADMIN_DESKTOP=1), so a server install never offers it, and
FBADMIN_UPDATE_CHECK=off turns it off together with the check. See Privacy.
Helm chart and Kubernetes¶
See Deployment. CI runs helm lint and helm template with server
mode and ingress enabled.
Documentation site¶
pip install -r requirements-docs.txt
mkdocs serve # http://127.0.0.1:8000
mkdocs build --strict
docs.yml deploys main to GitHub Pages. To add a page, put it in docs/ and list it
in nav in mkdocs.yml.