Nuvaka › Developer docs › Program packaging

Program packaging: platform keys, files and services

Your program ships as one package per platform. This page lists the 10 platform keys, the file name rules, the server's file-type check and how Nuvaka General App installs each package. It also shows how to install a system service without asking the user to run terminal commands. For the full publishing flow see Programs.

Platform keys

KeyFileFile-type check (first bytes of the file)How the app installs it
windows-msi.msiOLE: D0 CF 11 E0 A1 B1 1A E1msiexec /i; the Windows Installer window opens
windows-exe.exe (x86_64)MZThe setup program starts
windows-exe-arm64.exe (arm64)MZThe setup program starts
windows-msix.msixZIP: PK 03 04App Installer opens
macos-dmg.dmgkoly in the last 512 bytesThe disk image opens; the user drags the app to Applications
linux-appimage.AppImageELF: 7F 45 4C 46Copied to ~/Applications, made executable and started; no admin rights
linux-deb.deb!<arch>\napt-get install after a permission prompt (polkit)
linux-rpm.rpmED AB EE DBdnf install (or zypper install without dnf) after a permission prompt
linux-pacman.pkg.tar.zstzstd: 28 B5 2F FDpacman -U after a permission prompt
android-apk.apkZIP: PK 03 04Android package install
  • The file-type check is light: it only reads the start of the file (the end for dmg). A mismatching file is rejected with 400 bad_package. The review team checks the package contents.
  • The store list and the update check accept either the full key (linux-deb) or the OS (linux) as a filter.
  • One version can have several keys, e.g. windows-msi + windows-exe-arm64 + linux-deb + linux-rpm + linux-appimage. The app picks the best fit for the device (matching).

File names

  • A file name starts with a letter or digit. It contains only letters, digits, . _ + - and spaces, and is at most 151 characters long.
  • It must end with the key's extension (case-insensitive), otherwise you get 400 bad_file_name. For pacman the extension is .pkg.tar.zst; .tar.zst is not accepted.
  • Suggested names, following each ecosystem's habits:
    • App-1.2.0-x64.msi, App-1.2.0-Setup.exe, App-1.2.0-arm64-Setup.exe, App-1.2.0.msix
    • App-1.2.0.dmg, App-1.2.0-x86_64.AppImage
    • app_1.2.0_amd64.deb, app-1.2.0-1.x86_64.rpm, app-1.2.0-1-x86_64.pkg.tar.zst
    • app-1.2.0.apk
  • The version inside the package should match the version in Nuvaka. The update check uses the Nuvaka version; if the package's own version differs, the OS may treat the update as a downgrade and refuse it.

Platform notes

Windows

  • MSI (WiX, Advanced Installer…): use the same UpgradeCode in every version and define a MajorUpgrade so updates work. A per-machine install (ALLUSERS=1, InstallScope="perMachine") brings up the UAC prompt by itself.
  • EXE (NSIS, Inno Setup…): windows-exe is for x86_64 and windows-exe-arm64 for arm64. If one installer covers both architectures, upload it as windows-exe; Windows 11 on ARM runs it under emulation.
  • MSIX: an unsigned MSIX cannot be installed. The signing certificate must be trusted on the user's device.
  • Authenticode signing is recommended for installers. Unsigned installers trigger SmartScreen warnings.

macOS

  • Put a signed and notarized .app in the DMG together with a shortcut to the Applications folder. A single universal build (lipo) for Apple Silicon and Intel is simplest; declare "arch":"universal" in compat.

Linux

  • deb, rpm and pacman are installed with the distribution's package manager. Declare system libraries in the package's own dependency field (Depends:, Requires:, depends=()); the package manager installs them from the repositories. The Nuvaka dependencies list is for things the package manager cannot install: drivers, hardware, other Nuvaka programs.
  • rpm packages do not need a GPG signature. The app re-checks the file's verified hash on the root side and disables the GPG check only for that local file.
  • AppImage needs no root and runs on any distribution, provided its glibc is at least the version you declare in compat. An AppImage cannot install a system service. If you need one, publish a deb, rpm or pacman package.

Android

  • A universal APK (all ABIs) is recommended; declare "arch":"universal" in compat. Updates must use the same package name and signing key.

Installing system services

Some programs need a background service, e.g. a hardware daemon or a network agent. The ground rule:

The user only ever sees the operating system's admin prompt

An install never asks the user to open a terminal, type a sudo command, copy files or start a service by hand. The package itself installs, enables and starts the service, and stops and removes it on uninstall. The app installs Linux packages through polkit (pkexec), so the user sees a single admin password prompt. Package scripts must not ask questions and must run non-interactively.

deb: postinst + systemd

Ship the unit as /lib/systemd/system/app.service. If you use debhelper, dh_installsystemd generates the scripts below for you. By hand:

# debian/postinst
#!/bin/sh
set -e
if [ "$1" = configure ]; then
  systemctl daemon-reload
  systemctl enable app.service
  systemctl restart app.service
fi

# debian/prerm
#!/bin/sh
set -e
if [ "$1" = remove ]; then
  systemctl disable --now app.service || true
fi

# debian/postrm
#!/bin/sh
set -e
[ "$1" = purge ] && rm -rf /var/lib/app
systemctl daemon-reload || true

rpm: %post and the systemd macros

# app.spec
%{?systemd_requires}
BuildRequires: systemd-rpm-macros

%files
%{_unitdir}/app.service

%post
%systemd_post app.service
# Fedora presets may keep the service disabled; if your program does not work without it:
systemctl enable --now app.service >/dev/null 2>&1 || :

%preun
%systemd_preun app.service

%postun
%systemd_postun_with_restart app.service

pacman: the .install file

# PKGBUILD
install=app.install
package() {
  install -Dm644 app.service "$pkgdir/usr/lib/systemd/system/app.service"
}

# app.install
post_install() {
  systemctl daemon-reload
  systemctl enable --now app.service
}
post_upgrade() {
  systemctl daemon-reload
  systemctl try-restart app.service
}
pre_remove() {
  systemctl disable --now app.service
}

Windows: an admin install with NSIS

If the installer requests admin rights, Windows shows the UAC prompt itself. The installer registers the service and the uninstaller removes it:

; app.nsi
RequestExecutionLevel admin
InstallDir "$PROGRAMFILES64\App"

Section "Install"
  SetOutPath "$INSTDIR"
  File "app-service.exe"
  nsExec::ExecToLog 'sc.exe create AppSvc binPath= "\"$INSTDIR\app-service.exe\"" start= auto DisplayName= "App"'
  nsExec::ExecToLog 'sc.exe start AppSvc'
  WriteUninstaller "$INSTDIR\uninstall.exe"
SectionEnd

Section "Uninstall"
  nsExec::ExecToLog 'sc.exe stop AppSvc'
  nsExec::ExecToLog 'sc.exe delete AppSvc'
  RMDir /r "$INSTDIR"
SectionEnd
  • In Inno Setup, use PrivilegesRequired=admin with the [Run] and [UninstallRun] sections. In WiX/MSI, use the ServiceInstall and ServiceControl elements.
  • Install programs that need no service per user (without admin rights). Then no UAC prompt appears at all.

macOS

  • Register background work from inside the app with SMAppService (macOS 13+): SMAppService.agent for a per-user agent, SMAppService.daemon for a system daemon. The user approves a system daemon in System Settings.

Nuvaka Apps API v1 · last updated 2026-10-17