Skip to content
Syno Package Land
Language: English

Package an existing native product

Use the native layout when you already have a product that builds its own binaries, start script and DSM window, and you only need an SPK around it. synopkgland runs your build hooks, checks the result and writes one SPK per architecture. It does not generate application code, a backend or a UI.

If you want a new app with generated code, start from Build a native app instead.

Write dsm-package.yaml by hand. The native layout is not an init template.

schema: 2
backend: {kind: native}
package: ExampleNative
version: 1.2.3
build: 8
displayname: Example Native
description: Existing native product
maintainer: Example
os_min_ver: 7.0-40000
arches: [x86_64, armv8, armv7]
layout:
payload: target/native-layout/{arch}/payload
metadata: target/spk-metadata
binaries:
- {path: bin/app, linkage: static, arches: [x86_64, armv8]}
- {path: bin/app, linkage: dynamic, arches: [armv7]}
native:
prepare: {cwd: ., command: [python3, package/prepare.py]}
hook: {cwd: ., command: [python3, package/compile.py]}
toolkits:
x86_64:
- {url: 'https://example.com/toolkit.txz', sha256: '<64 lowercase hex digits>'}

Replace the toolkit URL and digest with your own, or remove toolkits if your compile hook needs none.

Then run:

Terminal window
synopkgland check
synopkgland build --arch x86_64,armv8,armv7
synopkgland inspect dist/ExampleNative-1.2.3-0008-x86_64.spk

build writes dist/<package>-<version>-<build padded to 4 digits>-<arch>.spk. Without --arch it builds every architecture in arches.

To install it on a NAS, run synopkgland deploy --host <nas>. It builds only the NAS architecture, using armv8 on an aarch64 NAS when you declare that name, and installs the SPK. Increase build before every deploy. synopkgland status --host <nas> then reports the installed state.

FieldRule
Allowed keysschema, backend, package, version, build, displayname, description, maintainer, metadata, icon, os_min_ver, privilege, arches, layout, native
schema2
backend{kind: native}
package^[A-Za-z][A-Za-z0-9]{1,63}$
version^\d+(\.\d+){0,3}$
buildInteger from 1 to 2147483647
os_min_ver7.x-<build>, for example 7.0-40000
privilegenone, or omit it
archesNon-empty, no duplicates: x86_64, aarch64, armv8, armv7
layout.payloadDirectory with the files that go into the package. May contain {arch}.
layout.metadataDirectory with the SPK metadata files (see below)
layout.binariesOne or more {path, linkage, arches?}. linkage is static or dynamic. arches must be a subset of the manifest arches. Every architecture needs at least one binary.
iconOptional. Square PNG of at least 256×256 in the project, or an icon_<size>.png directory with icon_256.png. build writes PACKAGE_ICON.PNG and PACKAGE_ICON_256.PNG from it; the metadata folder must not also contain them. Window icons stay in your payload.
layout.anchorsOptional. <key>_sha256: <payload path>. Writes the file’s SHA-256 into INFO for every architecture. See Product root helpers and upgrades.
native.hookRequired. Compiles or collects the payload for one architecture.
native.prepareOptional. Runs once before the per-architecture hooks.
native.toolkitsOptional. <arch>: [{url, sha256}]. url is HTTPS. sha256 is 64 lowercase hex digits.
reproducibleOptional. {inputs?: [file...], clean?: [path...]} for build --verify-reproducible. List build outputs such as target/{arch} under clean so the second build compiles from scratch. See reproducible builds.

Paths in the manifest are relative to the project, never absolute, and never contain .. or {/} other than {arch}. A compose.yaml in the same project is rejected.

A hook is {command: [argv...], cwd?: '.', env?: {...}}. The command runs with arguments and without a shell, inside the project. A hook must exit with status 0. env keys match ^[A-Z_][A-Z0-9_]*$ and must not start with SYNOPKGLAND_.

VariableMeaning
SYNOPKGLAND_PACKAGEPackage name
SYNOPKGLAND_VERSION, SYNOPKGLAND_BUILDManifest version and build number
SYNOPKGLAND_SPK_VERSIONVersion plus the build padded to 4 digits
SYNOPKGLAND_PROJECT_DIRAbsolute project root
SYNOPKGLAND_ARCHCurrent architecture. Compile hook only.
SYNOPKGLAND_TOOLKIT_DIRFreshly extracted toolkit tree. Compile hook only.
SYNOPKGLAND_WORK_DIREmpty scratch folder for this architecture, removed after the build. Compile hook only.

synopkgland build --jobs <n> runs the compile hook for up to n architectures at the same time. Use it only when your hook keeps architectures apart: layout.payload must contain {arch} (the CLI refuses --jobs above 1 otherwise), and the hook must not write to a shared folder such as a single build/ or out/ directory. Put scratch files in SYNOPKGLAND_WORK_DIR. Cargo is safe when each architecture uses its own --target, because it locks the shared target/ folder, but the builds then wait for each other there. native.prepare still runs once, before any architecture starts.

Toolkit archives are downloaded over HTTPS into a cache and checked against sha256 on every build. Set SYNOPKGLAND_CACHE_DIR to choose the cache folder. The default is a per-user cache shared by every project and worktree: $XDG_CACHE_HOME/synopkgland or ~/.cache/synopkgland on Linux, and ~/Library/Caches/synopkgland on macOS. Several builds can use the cache at the same time: each download goes to a private temporary file that is checked and then renamed into place, and each build extracts its own toolkit copy. The cache folder must not be a symlink or be writable by other users.

The archives are extracted with the tar on PATH, which must be GNU tar or bsdtar (the macOS default). A later archive can write through a folder symlink an earlier archive created. With bsdtar the CLI first refuses an archive that has an absolute or .. member.

check and build verify the payload and the metadata folder.

AreaRule
FilesNo symlinks and no special files. No setuid or setgid bits.
SizeEach file at most 256 MiB, all files together at most 1 GiB.
BinariesEach listed binary is an executable ELF file for the architecture, and its linkage matches linkage.
metadata top levelOnly conf, scripts, WIZARD_UIFILES, PACKAGE_ICON.PNG and PACKAGE_ICON_256.PNG (not both with manifest icon)
scripts/start-stop-statusRequired
scripts/*Must not contain sudo, chown root or chmod +s
Lifecycle scriptsExecutable (chmod +x): preinst, postinst, preuninst, postuninst, preupgrade, postupgrade, start-stop-status, prereplace, postreplace
conf/privilegeJSON with defaults.run-as set to package. username and groupname are optional and must equal the package name.
conf/resourceOptional. Only an empty systemd-unit object is accepted.
conf/systemdOnly top-level *.service files. Each has exactly one User= equal to the package name. Group= equals the package name if present.
Service unitsNo line continuations. User, Group and Exec* lines only in [Service]. No AmbientCapabilities, SupplementaryGroups, PermissionsStartOnly, RootDirectory or RootImage. No + or ! prefix on an Exec* command.

The package always runs as its own package account. The native layout has no root option.

If your product ships its own root helper with an administrator grant, bind the grant to what the administrator approved: the capabilities, the package account and the pinned task command. Do not bind it to a build, manifest or file-tree digest. To let a new SPK change root code without a new approval, declare each root payload in layout.anchors:

layout:
anchors:
example_helper_sha256: bin/helper
example_bootstrap_sha256: bin/bootstrap

For every architecture, build writes the SHA-256 of that architecture’s payload file into INFO, for example example_helper_sha256="<64 lowercase hex>". Package Center writes /var/packages/<package>/INFO as root, so the package account cannot change it.

RuleValue
Key^[a-z][a-z0-9_]{0,48}_sha256$, not starting with synopkgland_, at most 16 keys
PathA regular file in the payload, relative to the payload folder, without {arch}
buildFails when the file is missing or a key collides with another INFO field
inspectRefuses an SPK whose INFO anchor is changed, duplicated, missing or undeclared

Your root helper reads exactly one valid line for each key from the root-owned INFO, copies the payload into its protected folder, checks the copy against the value and only then runs it. Keep the pinned task command free of build-specific digests. A missing, duplicated or different value keeps the grant inactive until an administrator approves. Revoke the grant on upgrade only when the capabilities change, and answer authorization.status with reason: upgrade_reapproval then. The SDK executor follows the same rule with synopkgland_executor_sha256.

arches valueELF checkNotes
x86_6464-bit, machine 62
aarch6464-bit, machine 183INFO arch lists the aarch64 platforms (armada37xx rtd1296 rtd1619 rtd1619b), as native-go and binary packages do.
armv864-bit, machine 183Same binary format as aarch64. The SPK keeps the armv8 name.
armv732-bit, machine 40

dynamic linkage means your binary depends on libraries of the target DSM. Pin the matching DSM toolkit in native.toolkits.

LimitValue
Commands that workcheck, build, inspect, deploy, status
Commands that reject itlogs
build range1 to 2147483647
Privilegenone (package account)
Architecturesx86_64, aarch64, armv8, armv7
File size256 MiB each, 1 GiB total

inspect checks the archive without installing or running anything. It does not prove the product works on a device.

Code or messageCauseFix
NATIVE_LAYOUT_INVALID: schema 2 requiredschema is not 2, or backend is missing.Set schema: 2 and backend: {kind: native}.
NATIVE_LAYOUT_INVALID: unknown fieldA key is not in the allowed list.Remove it.
NATIVE_LAYOUT_INVALID: path must stay inside projectA path is absolute, has .., or has { other than {arch}.Use a relative path inside the project.
NATIVE_LAYOUT_INVALID: ELF architecture mismatchThe binary is not for the architecture being built.Build the right binary in the hook.
NATIVE_LAYOUT_INVALID: ELF linkage mismatchlinkage does not match the binary.Fix linkage or the build.
NATIVE_LAYOUT_INVALID: native hook failedA hook exited with a non-zero status.Run the hook command by hand.
NATIVE_LAYOUT_INVALID: toolkit checksum mismatchThe download does not match sha256.Fix the URL or the digest.
NATIVE_LAYOUT_INVALID: native layout requires package-only lifecycle privilegeconf/privilege asks for more than the package account.Set defaults.run-as to package.
NATIVE_LAYOUT_INVALID: start-stop-status missingscripts/start-stop-status is not in metadata.Add it.
NATIVE_TOOLKIT_TAR_UNSUPPORTEDThe tar on PATH is neither GNU tar nor bsdtar.Install one and put it first on PATH.
NATIVE_LAYOUT_SCRIPT_NOT_EXECUTABLEA lifecycle script in metadata/scripts is not executable.Run chmod +x on it.

See Errors for every code.