Releases and packaging¶
This page explains how Dinatos builds and publishes a release, and how to
build the same artifacts on your computer. A release has the Docker image, the
Linux packages and the Android APK. It is for contributors who change the
packaging, the Dockerfile or release.yml. For what runs on every pull
request, see CI and checks.
Cut a release¶
A release is a Git tag with the name v<version>, for example v1.2.3. When
you push the tag, .github/workflows/release.yml starts. You do not run a
build step by hand. The jobs of the workflow run in parallel.
just release dry # print the next patch tag (v1.2.3 -> v1.2.4) without creating it
just release # create and push that tag at HEAD
Artifact |
Built by |
Notes |
|---|---|---|
Docker image (backend plus built web app) |
root |
The workflow scans it with Trivy, then pushes it to |
Linux |
|
Built from the same |
Linux |
|
Built from the same bundle, so the two packages cannot differ. |
Android APK |
|
Signed with the release key. See Android APK signing. |
The version job removes the leading v from the tag one time. Because of
this, every artifact and the GitHub Release use one version string. dpkg-deb
rejects a leading v. The last job, release, puts all artifacts into one
GitHub Release.
Before you push a tag, read these cautions:
First push to GHCR. The package is private by default, even if the repository is public. To let people pull the image without a login, make the package public once, in its settings on GitHub.
Scan failure. If Trivy finds a fixable CRITICAL vulnerability, the
dockerjob stops before the push. The other jobs continue. See Security scanning for how to fix or accept the finding.
Warning
No rollback. No just recipe undoes a release. If a job fails after
other jobs finished, the release is partial. For example, the image is on
GHCR and then the .deb step fails. The workflow cannot roll back by itself.
Before you tag, test changes to packaging/linux/ or the Dockerfile on
your computer.
Nightly releases¶
nightly.yml runs every day at 02:17 UTC, or when you start it manually. The
force input makes it release even if there are no new commits.
If main has no commits since the last nightly release, the workflow does
nothing. Otherwise it calls release.yml to publish a GitHub prerelease.
The prerelease has the same image, packages and APK. It does not need a v*
tag.
Item |
Value |
|---|---|
Tag |
|
Version |
The newest |
Python package version |
|
Docker tags |
The version and a moving |
Retention |
The 14 newest nightly releases and their tags. The workflow does not remove old images from GHCR. |
A v* tag does not fit a nightly release for two reasons. It starts
release.yml as a stable release. It also sorts among the real versions.
A tag that a workflow pushes with GITHUB_TOKEN cannot start another
workflow. For this reason, the nightly workflow calls release.yml directly
(workflow_call).
The Docker build writes the version into pyproject.toml of the backend. The
API, its OpenAPI document and the backups report this version.
Build the artifacts locally¶
Run the commands from the root of the repository. You need the Flutter SDK, the Android SDK (for the APK) and the Linux build dependencies in Linux desktop target. For the tools, see Environment setup.
just packaging build-apk <version> [build-number]
just packaging build-deb <version>
just packaging build-appimage <version>
just packaging build-linux-artifacts <version> # both Linux formats, one shared build
just docker build # the Docker image
The release workflow calls these same recipes.
The APK is in
frontend/build/app/outputs/flutter-apk/.The Linux packages go to
dist/. You can give a different directory as the second argument.
Docker-based Linux builds¶
The recipes build-deb-docker, build-appimage-docker and
build-linux-artifacts-docker take the same arguments. They build inside
ubuntu:22.04 (packaging/linux/Dockerfile.build). They need only Docker.
They build from a copy of the checkout, so they never reuse old
frontend/build/ state.
Use these recipes in a Nix or devbox shell. A binary that you build in such a
shell contains the /nix/store paths of the libraries and the interpreter. On
other machines, it fails, or it cannot find the GL drivers of the host. For
this reason, the recipes that do not use Docker refuse to run in a devbox
shell. To override this for a test on your machine only, set
DINATOS_ALLOW_DEVBOX_BUILD=1.
The Docker image builds its own frontend¶
The root Dockerfile has three stages. The build context is the root of the
repository.
frontend-builder: downloads the current stable Flutter SDK and runsflutter build web --release.builder: installs the backend withuv.runtime: copies the.venv/of the backend and the web build (build/web) toweb/.Settings.web_direxpects the web build there. See_mount_web_uiinbackend/src/dinatos_backend/main.pyand Container image.
The backend serves the web app. Both use one origin, so the image does not need a CORS setup.
Android APK signing¶
frontend/android/app/build.gradle.kts reads the signing key from
frontend/android/key.properties. Android does not install an APK over an
existing APK if the signing key is different. Each machine generates its own
debug keystore. For this reason, a debug-signed build can never update an
earlier install.
In CI: the workflow writes the keystore from the repository secrets
ANDROID_KEYSTORE_BASE64,ANDROID_KEYSTORE_PASSWORD,ANDROID_KEY_ALIASand, as an option,ANDROID_KEY_PASSWORD. The workflow fails if the required secrets are missing.In local builds: if
key.propertiesdoes not exist, the build uses the debug key.To create the key: do this one time. Never lose the key and never rotate it.
keytool -genkeypair -v -keystore release.jks -alias dinatos \ -keyalg RSA -keysize 2048 -validity 10000
The release manifest (android/app/src/main/AndroidManifest.xml) must declare
android.permission.INTERNET. Flutter adds this permission only to the debug
and profile manifests. A release APK without it installs correctly. Then every
request fails with “server not reachable”. The command
just android check-apk checks the permission on a built APK.
Linux desktop target¶
Build dependencies¶
flutter build linux fails at the CMake configure step if these packages are
missing:
libgtk-3-dev: the windowing toolkit.libsecret-1-dev: used byflutter_secure_storage.clang cmake ninja-build pkg-config: the rest of the toolchain.
flutter analyze and flutter test do not need these packages. The
linux-packages job installs them with apt-get.
Build on the oldest supported Ubuntu¶
The linux-packages job runs on ubuntu-22.04. It does not use
ubuntu-latest, on purpose. The bundle links against the glibc of the build
machine. A newer build image makes a package that fails on older systems with
GLIBC_2.38 not found. Raise the build image only together with the minimum
supported Ubuntu.
Smoke test¶
The linux-smoke job must pass before the release. It installs the .deb in
clean ubuntu:22.04 and ubuntu:24.04 containers. It starts the app under
Xvfb (packaging/linux/smoke-test-deb.sh). You can also run this script
locally with Docker. The job also runs ldd on each bundled library alone,
with no exemptions. Every library must resolve on a clean machine. The job
found three problems:
Plugin rpath. The packaging installs the plugin libraries (
flutter_secure_storage,url_launcher) as plain files. CMake does not rewrite their rpath. Without a change, the rpath points to thelibflutter_linux_gtk.soin the build tree. This works on the machine that built the app, and showsnot foundon other machines.frontend/linux/CMakeLists.txtsets$ORIGINon these libraries, as it does for the rest of the bundle.libdartjni.so. This is the native part of thejnipackage, whichpath_provider_androidpulls in. It linkslibjvm.so, and only a JDK has that library. Nothing on desktop uses JNI, soCMakeLists.txtleaves the library out of the bundle.GL libraries. The engine reaches
libgles2andlibegl1through thedlopenof libepoxy. Link-time checks cannot see this. The.debdeclares both as dependencies (packaging/linux/build-deb.sh). Without them, the app stops withCouldn't open libGLESv2.so.2.
flutter create is not additive¶
The command flutter create --platforms=linux . changes an existing project.
It rewrites the migration list in frontend/.metadata so that it has only
linux. It removes the android and web entries. It also changes
analysis_options.yaml. After you run it for any platform, compare both files
with the old versions. Restore what the command removed.