Packaging & distribution
day pack turns a Day project into an installable package. It defaults to a release build, invokes
the platform’s signing and packaging tools, and writes the output, checksum, and signing status
to build/day/dist/.
The command runs from the project directory and requires the target’s packaging tools. For an Android APK:
day pack -t android-mdc --formats apk
Configure release credentials before distributing the result. Without them, Day can produce a development-signed or unsigned artifact; check the reported signing tier. If signing fails, start with the signing checks.
Build and install a release package on each target before distributing it. Configure signing credentials and check the destination store’s submission requirements.
Artifacts per target
| Target | Output | Distribution |
|---|---|---|
macos-appkit | .dmg; .pkg with --formats pkg | Disk image for direct download; installer package for the Mac App Store |
ios-uikit | .ipa | Device app archive |
android-mdc | .apk, .aab | Direct installation or Google Play |
linux-gtk, linux-qt | .flatpak, .appimage | Flatpak bundle or executable with bundled libraries |
windows-winui | .msix, -setup.exe | Self-contained MSIX package or per-user installer |
windows-xaml (deprecated) | .msix, -setup.exe | MSIX package or per-user installer |
harmony-arkui | .hap | HarmonyOS application package |
web-dom | Static dist/ directory | Static hosting; use day build |
The development combinations macos-gtk, macos-qt, windows-gtk, and windows-qt do not have a
day pack step. See platform support for their limitations.
macOS and iOS
For macOS, Day assembles the .app, signs its nested components with codesign, and creates a
compressed UDZO disk image with an /Applications link. With release credentials configured,
it signs the disk image, submits it through notarytool, and staples the notarization ticket.
--formats pkg builds the Mac App Store package instead, and
--formats dmg,pkg builds both from one build.
For iOS, with an App Store provisioning profile for the app id installed under
~/Library/MobileDevice/Provisioning Profiles/ and its distribution certificate in the keychain,
Day builds the arm64 device app unsigned and then signs the .ipa with that profile and identity,
exactly what day sign apply does for a package built elsewhere; xcodebuild’s own signing never
runs, so nothing is provisioned or minted. Without a profile, Day runs xcodebuild archive with
automatic signing through the App Store Connect key and exports the archive with a generated
ExportOptions.plist using app-store-connect, which needs a developer’s Xcode account session.
Without signing configuration, it produces <stem>-ios-uikit-unsigned.ipa for subsequent signing
or sideloading.
Android
Day runs Gradle’s assembleRelease and bundleRelease with the configured signing settings.
It verifies the APK with apksigner and checks 16 KB page alignment. Use the APK for direct
installation and the AAB for Google Play submission.
Linux: Flatpak or AppImage
A Flatpak bundle uses a shared runtime: org.gnome.Platform for GTK or org.kde.Platform for Qt.
The runtime is resolved from Flathub during installation. Install a bundle with:
flatpak install ./my-app-1.0-linux-gtk-x86_64.flatpak
The filename includes the toolkit so GTK and Qt bundles can coexist. The webview crate declares a Qt WebEngine BaseApp requirement, which Day includes when the binary links that engine. Other dependencies can declare their own Flatpak base requirements.
An AppImage bundles the toolkit libraries with the executable. Mark it executable before running it:
chmod +x ./my-app-1.0-linux-gtk-x86_64.appimage
./my-app-1.0-linux-gtk-x86_64.appimage
Day prepares an AppDir and invokes linuxdeploy with its GTK or Qt plugin. These plugins collect resources that a library dependency scan can miss, including image loaders, GIO modules, GSettings schemas, and Qt platform plugins.
Without the matching plugin, packaging can still succeed, but the AppImage requires the toolkit to be installed on the user’s machine. Day reports this during packaging.
Windows and HarmonyOS
Windows packaging uses makeappx and signtool for MSIX, and NSIS for the per-user -setup.exe
installer. The NSIS installer does not require elevation, registers with Add/Remove Programs,
and accepts /S for silent installation. A windows-winui package is self-contained: the Windows
App SDK runtime travels inside the .msix and the installer, so users install nothing extra.
HarmonyOS packaging uses hvigor for a release build and hap-sign-tool for signing. Day uses
configured release credentials when available, or the public development certificate otherwise.
Web
There is no web packaging step. Run day build -t web-dom and deploy the generated dist/
directory to a static host. It contains the HTML host page, JavaScript shim, stylesheet,
WebAssembly module, images, and fonts.
Packaging options
Use --formats to select a subset of output formats, as in the Android example above.
--profile defaults to release; another supported profile changes the build configuration,
not whether the resulting signing tier is suitable for distribution.
--no-sign skips signing and names the store package for a signer that runs later
(<stem>-<target>-unsigned.ipa, .aab, .apk or .hap, which day sign apply takes as it
is), and --no-notarize skips macOS notarization. To submit notarization
without waiting for completion, pass --no-wait and check it later with
day sign status <id>.
Artifact filenames follow this pattern:
<stem>[-<version>]-<target>[-<extra>].<ext>
For example: day-showcase-0.1.0-macos-appkit.dmg. Set the stem with [app] artifact in
Day.toml, or override it with --artifact-name <stem>; Day converts the value to a filename slug.
Use --no-version-in-name for a stable filename suitable for a releases/latest/download/<name> URL.
Signing configuration
Signing lives in Day.toml under [signing], with every secret referenced as ${ENV_VAR}; values
resolve from the environment at pack time and are never stored in the manifest or printed by the
tool:
[signing.macos]
identity = "${DAY_SIGN_MACOS_IDENTITY}" # "Developer ID Application: …"
[signing.macos.notarize]
key-id = "${DAY_NOTARY_KEY_ID}" # App Store Connect API key
issuer = "${DAY_NOTARY_ISSUER}"
key-path = "${DAY_NOTARY_KEY}"
[signing.macos.app-store] # the Mac App Store .pkg
identity = "${DAY_MAS_IDENTITY}" # "Apple Distribution: …"
installer-identity = "${DAY_MAS_INSTALLER_IDENTITY}" # "3rd Party Mac Developer Installer: …"
profile = "${DAY_MAS_PROFILE}" # Mac App Store Connect .provisionprofile
[signing.ios]
team = "${DAY_APPLE_TEAM}"
key-id = "${DAY_ASC_KEY_ID}" # ASC key for -allowProvisioningUpdates in CI
issuer = "${DAY_ASC_ISSUER}"
key-path = "${DAY_ASC_KEY}"
[signing.android]
keystore = "${DAY_ANDROID_KEYSTORE}"
key-alias = "${DAY_ANDROID_KEY_ALIAS}"
store-pass = "${DAY_KS_PASS}"
key-pass = "${DAY_KEY_PASS}"
[signing.windows]
provider = "self-signed-dev" # or signtool-cert-store | azure-artifact-signing
[signing.ohos]
keystore = "${DAY_OHOS_KEYSTORE}"
key-alias = "${DAY_OHOS_KEY_ALIAS}"
store-pass = "${DAY_OHOS_KS_PASS}"
key-pass = "${DAY_OHOS_KEY_PASS}"
cert = "${DAY_OHOS_CERT}"
profile = "${DAY_OHOS_PROFILE}"
day sign check reports each platform’s readiness (env vars set, key files present) without
printing any secret value.
Signing an existing package
day pack builds and signs in one step, which is what a project’s own CI wants. A distributor
wants the two apart: build the submitted source with no credentials in reach, then sign the
artifact that came out. Building runs the app’s own code — build scripts, Gradle plugins, Xcode
run scripts — and signing runs none of it, so the credentials only ever meet a finished file.
day sign apply app-release.aab # signs in place
day sign apply app-release.apk --out signed.apk # leaves the input alone
The keys come from [signing.android], resolved strictly: an unset ${VAR} is an error naming
the variable rather than a quiet drop to the dev keystore, because a dev-signed artifact looks
finished and no store will take it. A distributor names its own key instead, and then the
submitted app’s manifest is never read:
DAY_SIGN_STORE_PASS=… DAY_SIGN_KEY_PASS=… \
day sign apply app-release.aab --keystore upload.keystore --key-alias upload
With both flags given there is no need for a project at all: day sign apply runs in a directory
holding the package alone, which is how a submission queue keeps the app it signs away from the
runner that holds the key. A .hap takes the same shape with its three files:
DAY_SIGN_STORE_PASS=… DAY_SIGN_KEY_PASS=… \
day sign apply app-unsigned.hap --keystore release.p12 --cert app.cer \
--profile app.p7b --key-alias release
The passwords stay in the environment, out of the argument list. .aab goes through jarsigner, .apk through zipalign
then apksigner (v4 off, so no .idsig litter), and passwords reach both through the
environment rather than the argument list, which every other process on the machine can read.
The signed copy is written beside the destination and moved into place only after it verifies, so a failure leaves the unsigned artifact exactly as it was. The report names both digests and the signing certificate, which is the field a store matches an upload against:
Signed app-release.aab
Digest 5f080ef8becf6015… → b9cc48e09326d9a1…
Certificate sha256 98860a7b9c357cb68fee7d1cb6944238f8316a02ca75972ffc89b56e8ed68e24
--format json prints the same four fields as artifact, sha256, sha256_unsigned and
certificate_sha256, which is what a pipeline records to tie a published binary to the build it
came from.
Apple packages
An .ipa (or a bare .app, which comes back out as an .ipa) takes the steps Xcode’s export
performs, spelled out because no project is present to drive it: embed the provisioning profile,
sign every nested framework and plug-in before the bundle that contains them, sign the app, then
repack with ditto.
day sign apply app.ipa \
--profile "AppStore.mobileprovision" \
--identity "Apple Distribution: Example Inc (ABCDE12345)"
--profile and --identity default to signing.ios.profile and signing.ios.identity. The
flags matter more here than the Day.toml values do: a distributor signs an artifact built from
someone else’s project, whose Day.toml names the developer’s profile and certificate rather
than the distributor’s.
Entitlements come from the profile unless --entitlements <file> says otherwise. That is what
Xcode does when a project overrides nothing, and it is the safe default in both directions:
entitlements a profile does not grant are rejected at install, and asking for fewer than it grants
silently drops capabilities the app shipped with. The profile’s set is what lands —
application-identifier, aps-environment, beta-reports-active, get-task-allow=false for a
distribution profile.
The signed bundle is verified with codesign --verify --deep --strict before it replaces
anything, so a bad identity or an out-of-order nested signature fails with the artifact untouched.
certificate_sha256 is the signing certificate itself, the same field the Android path reports.
Signing an Apple package needs macOS: codesign, security and ditto ship with Xcode. A
macOS .app is signed for the Mac App Store into a .pkg. Signing an
existing .dmg or .pkg is not implemented yet; day pack -t macos-appkit signs and notarizes
the .dmg during the build.
The Mac App Store
The Mac App Store takes an installer package, not a disk image. The app inside is signed with an
Apple Distribution identity over a Mac App Store provisioning profile, and the package is signed
with a Mac Installer Distribution identity. The notarized .dmg and the store’s .pkg carry
different signatures, so the two are separate packages built from the same app:
day pack -t macos-appkit --formats pkg # build/day/dist/<stem>-macos-appkit.pkg
day pack -t macos-appkit --formats dmg,pkg # the download and the store package together
The material comes from [signing.macos.app-store] in Day.toml
(configuration). Without it, Day packs <stem>-macos-appkit-unsigned.pkg
around the ad-hoc signed app and says so; the store refuses that package, and day sign apply
signs the app inside it later.
To sign an app built elsewhere, as a release pipeline does after its build, give day sign apply
the .app:
day sign apply "Day Showcase.app" \
--profile ShowcaseMacAppStore.provisionprofile \
--identity "Apple Distribution: Example Inc (ABCDE12345)" \
--installer-identity "3rd Party Mac Developer Installer: Example Inc (ABCDE12345)" \
--out day-showcase-macos-appkit.pkg
The flags default to the [signing.macos.app-store] values, and --out defaults to the bundle’s
name with .pkg. The bundle is left as it was. Day signs a copy: it embeds the profile as
Contents/embedded.provisionprofile, signs nested code before the bundle, signs the bundle with
the hardened runtime and the app’s entitlements, verifies the result with
codesign --verify --deep --strict, wraps it with productbuild, and checks the package with
pkgutil --check-signature.
Entitlements come from the app. A Day build signs its sandbox
policy into the bundle, even ad hoc, and that policy is what the store build keeps. Day adds the
two identifiers the profile ties the signature to (com.apple.application-identifier and
com.apple.developer.team-identifier) and drops the debugger grant. --entitlements <file>
replaces the app’s set.
Before signing, Day checks what App Store Connect would otherwise reject after the upload:
- The profile is a Mac App Store profile for this bundle id that has not expired. A development profile, a Developer ID profile, or an iOS profile is named as such.
- The app is sandboxed.
Info.plisthasLSApplicationCategoryTypeand a version.- Every device or personal-data entitlement has its usage description: the camera, the microphone, location, photos, contacts and calendars.
- Every
com.apple.developer.*capability the app asks for is granted by the profile.
Declare permissions in Day.toml [permissions] and Day writes their usage descriptions into
platform/macos/Runner/Info.plist (permissions). Add the category to
the same file:
<key>LSApplicationCategoryType</key>
<string>public.app-category.developer-tools</string>
The store listing and the upload lanes come from store/storefront.toml; see
App Store Submissions.
Signing tiers
Every artifact carries a tier: release, dev-signed, or unsigned. When a ${VAR} is
unset (a laptop without the release keys, a fork PR without repository secrets), day pack warns
naming the variable and drops that platform to the dev tier (ad-hoc codesign on macOS, the fixed dev
keystore embedded in the CLI on Android, a self-signed certificate on Windows, the unsigned device
.ipa on iOS) instead of failing. The Android dev keystore is fixed, so dev builds stay
byte-reproducible and an install from one machine can be upgraded by a build from another. The
result JSON and the console both say so:
Warning signing.macos.identity: ${DAY_SIGN_MACOS_IDENTITY} is not set — degrading to the dev signing tier
Packed …/day-showcase-0.1.0-macos-appkit.dmg (dmg, dev-signed) sha256:ec51fa5f02ab…
Warning day-showcase-0.1.0-macos-appkit.dmg is dev-signed — NOT distributable
A resolved configuration that is broken (a keystore path that doesn’t exist, a rejected notarization) is a hard failure with exit code 6.
Continuous integration
The GitHub Actions guide describes the intended app packaging and release workflow. Its reusable implementation lives outside this checkout and was not inspected in this repository-only review. Verify the workflow revision, artifact names, signing setup, and upload behavior before relying on it for distribution.
See App Store Submissions for iOS, Mac App Store and Android
signing secrets, store records and upload lanes. macOS download signing and notarization use the
shared workflow’s signing-environment input.
macOS App Sandbox
Enable [sandbox.macos-appkit] enabled = true in Day.toml to sandbox AppKit builds and
packaged apps. See macOS sandboxing for configuration, native file
access, persistent bookmarks, and App Store distribution limitations.
Separate configuration checks from distribution evidence
Use day pack --help to verify the installed version’s options and day doctor --toolkit <name>
to diagnose its SDKs. Signing and store acceptance are separate from building the package.
Source: pack dispatch and packaging implementations. Keep credentials out of examples and logs. No signing, publishing, or store submission command was executed during this documentation work.