Application distribution packer built with MoonBit
| Asset | Platform |
|---|---|
| lunapack-windows-x86_64.exe | Windows |
| lunapack-linux-x86_64 | Linux |
| lunapack-macos-aarch64 | macOS (Apple Silicon) |
| lunapack.wasm | WebAssembly, for moonx |
| lunapack.core | the compiled package the WebAssembly module needs |
moonx riantr/LunaPack/cmd/main@latest \
--input-exe=[input-exe-path] \
--output-exe=[output-exe-path] \
--Files=[Pack-Folder]| Flag | Meaning |
|---|---|
| --input-exe | the application executable to distribute |
| --output-exe | the produced single-file bundle |
| --Files | folder of files to bundle with it (repeatable) |
The bundle is self-extracting when the launcher is a native LunaPack binary. The one that runs on this machine is used by default, so a native LunaPack always produces a self-extracting file. A bundle produced through moonx runs on WebAssembly and has no native binary of its own, so it needs one named with --launcher - see below.
myapp.exe = [ LunaPack launcher ] + [ archive: myapp.exe, res/app.cfg, ... ]$ ./myapp.exe
LunaPack: starting D:/apps/.lunapack/9c445884d23653fb/myapp.exe# A bundle for Windows, made with the launcher of the release.
lunapack bundle -i ./myapp.exe -o ./myapp.exe \
--Files=./build --launcher ./lunapack-windows-x86_64.exe$ ./myapp.exe --version
myapp 2.6.0
$ echo $?
0$ lunapack --input-exe=./BRLabeler.exe --output-exe=./BRLabeler.exe --Files=./build
md5 cb77181515d4680a960433f65ba05bef BRLabeler.exe
...
scanned 392 files
bundled 392 files -> ./BRLabeler.exe (icon copied)
output md5 f5f06ca638d59a31e773c0a28358804d BRLabeler.exelunapack verify myapp.exe
lunapack info myapp.exe
lunapack unpack -i myapp.exe -o ./outmoon build --release
# -> _build/native/release/build/cmd/main/main.exe (every platform)lunapack pack -i <input_dir> -o <output.appkg> [options]
lunapack bundle -i <exe> -o <output.exe> [--Files <dir>]... [options]
lunapack unpack -i <package> -o <dest_dir> [--skip-scripts]
lunapack verify <package> [--extracted <dir>]
lunapack info <package>| Option | Meaning |
|---|---|
| --name <name> | package name (default: input dir / exe name) |
| --version <ver> | package version (default: 0.1.0) |
| --os <os> | target OS tag (default: detected) |
| --arch <arch> | target arch tag (default: detected) |
| --pre-install <file> | script executed before extraction |
| --post-install <file> | script executed after extraction |
| --compression-method <m> | none, gzip, lz4 or auto (default: auto) |
| --launcher <file> | the launcher of the single file (default: this one) |
| --encryption-method <m> | x25519 or passphrase (default: none) |
| --public-key <file> | the public key of the recipient |
| --secret-key <file> | the secret key of the recipient |
| --passphrase-file <file> | the file that holds the passphrase |
| --skip-scripts | unpack without running the install scripts |
| --extracted <dir> | with verify: also check the files already in <dir> |
| Method | What it does |
|---|---|
| none | store the files as they are |
| gzip | gzip every entry |
| lz4 | LZ4 every entry |
| auto | gzip every entry, but keep the raw bytes when gzip would make the entry bigger (the default) |
| gzip | LZ4 | |
|---|---|---|
| packing | 39 s | 16 s |
| unpacking | 21 s | 19 s |
| size of the package | 139 MB | 175 MB |
| Method | What it does |
|---|---|
| x25519 | the package is sealed to the owner of a public key; only the owner of the secret key can open it |
| passphrase | the package is sealed with a passphrase |
# Make a pair of keys. The public key goes to the machine that seals the
# packages, the secret key to the machine that opens them.
lunapack keygen --public-key recipient.pub --secret-key recipient.key
# Seal a package to the owner of the public key.
lunapack pack -i ./app -o app.appkg \
--encryption-method x25519 --public-key recipient.pub
# Open it again with the secret key.
lunapack unpack -i app.appkg -o ./out --secret-key recipient.key
# Or with a passphrase.
lunapack pack -i ./app -o app.appkg \
--encryption-method passphrase --passphrase-file passphrase.txt
lunapack unpack -i app.appkg -o ./out --passphrase-file passphrase.txtlunapack keygen --public-key recipient.pub --secret-key recipient.key \
--passphrase-file passphrase.txt# Bundle an application and its resources into one self-extracting file.
lunapack --input-exe=./build/myapp \
--output-exe=./dist/myapp \
--Files=./build
# Or produce a plain archive instead.
lunapack pack -i ./myapp -o myapp.appkg \
--name myapp --version 1.4.0 \
--pre-install setup.sh --post-install setup.sh
lunapack verify myapp.appkg
lunapack unpack -i myapp.appkg -o /opt/myapp md5 6f5902ac237024bdd0c176cb93063dc4 a.txt
md5 3db2050fcf84bb631dcae417d3db518c sub/b.txt
scanned 2 files
packed 2 files -> myapp.appkg
output md5 63da650c906fe3ab1446b121a4edd4ca myapp.appkgunpacked 2 files -> /opt/myapp
md5 6f5902ac237024bdd0c176cb93063dc4 a.txt
md5 3db2050fcf84bb631dcae417d3db518c sub/b.txt
verified 2 files, 0 mismatcheslunapack verify BRLabeler.exe --extracted .lunapack\60844df795cb7f31verify: OK (392 files)
verify: OK (392 files in .lunapack/60844df795cb7f31)
VERIFY OKverify: OK (392 files)
verify: md5 mismatch for appsettings.json: expected 86ff00ef... got 3ddcb685...
verify: FAILED (392 files checked)
VERIFY FAILEDlunapack ^
--input-exe="D:/src/App/bin/Release/net10.0-windows/App.exe" ^
--output-exe="D:/dist/App.exe" ^
--Files="D:/src/App/bin/Release/net10.0-windows"D://src//WorkBuddy//Moonbit//LunaPack//_build//native//release//build//cmd//main//main.exe ^
--input-exe="D://zkhongtuo//src//MOlabeler_V2.6//bin//x64//Release//net10.0-windows10.0.22000.0//BRLabeler.exe" ^
--output-exe="D://zkhongtuo//src//MOLabeler_V2.6//bin//BRLabeler.exe" ^
--Files="D://zkhongtuo//src//MOlabeler_V2.6//bin//x64//Release//net10.0-windows10.0.22000.0"moon login # once
moon publish --dry-run # validate
moon publish # publish riantr/LunaPack
moonx riantr/LunaPack/cmd/main@latest --input-exe=... --output-exe=... --Files=...| Extension | Target platform |
|---|---|
| .cmd, .bat, .ps1 | windows |
| .sh | posix (Linux + macOS) |
| anything else | any |
+--------------------+ offset 0
| magic "APPKGD02" | 8 bytes
+--------------------+ offset 8
| entry payloads | stored back to back, raw, gzip, lz4 or sealed
+--------------------+
| manifest (UTF-8 |
| JSON) |
+--------------------+
| magic "APPKGD02" | 8 bytes
| manifest offset | 8 bytes, little-endian int64
| manifest length | 8 bytes, little-endian int64
+--------------------+ end of file{
"format_version": 4,
"name": "myapp",
"version": "1.4.0",
"os": "linux",
"arch": "x86_64",
"checksum_algo": "sha256",
"compression": "auto",
"created_at": "2026-11-04T04:10:20Z",
"main_program": "myapp.exe",
"pre_install": { "platform": "posix", "body": "#!/bin/sh\n..." },
"post_install": null,
"entries": [
{
"path": "res/app.cfg",
"mode": 420,
"size": 22500,
"stored_size": 913,
"codec": "gzip",
"checksum": "0a77c24c45d0c61f906862c4706f5dd739166985e8fc5c6344dadd9220507b28",
"md5": "1520e796ba25554696b3cef6e913cc90",
"offset": 8
}
]
}moon test # unit + integration tests
bash scripts/test_linux.sh # end-to-end suite (Linux / WSL)pub struct Encryption {
method_name : String
recipient : String
ephemeral : String
salt : String
iterations : Int
key_check : String
}pub struct EncryptionOptions {
method_name : String
public_key : String
passphrase : String
iterations : Int
}pub struct Entry {
path : String
mode : Int
size : Int64
stored_size : Int64
codec : String
checksum : String
md5 : String
offset : Int64
}pub struct Manifest {
format_version : Int
name : String
version : String
os : String
arch : String
checksum_algo : String
compression : String
created_at : String
main_program : String
pre_install : Script?
post_install : Script?
encryption : Encryption?
entries : Array[Entry]
}pub struct PackOptions {
name : String
version : String
os : String
arch : String
pre_install : Script?
post_install : Script?
compression : String
main_program : String
encryption : EncryptionOptions?
}pub struct PackageKey {
master : Bytes
salt : Bytes
}pub struct PeFile {
data : Bytes
sections : Array[(Int64, Int64, Int64, Int64)]
resource_rva : Int64
resource_size : Int64
}pub struct Poly1305 {
r0 : UInt64
r1 : UInt64
r2 : UInt64
r3 : UInt64
r4 : UInt64
s0 : UInt64
s1 : UInt64
s2 : UInt64
s3 : UInt64
h0 : UInt64
h1 : UInt64
h2 : UInt64
h3 : UInt64
h4 : UInt64
buffer : Array[Byte]
}pub struct Script {
platform : String
body : String
}pub struct SealedPackage {
shared : Bytes
ephemeral : Bytes
recipient : Bytes
salt : Bytes
}let FORMAT_VERSION : Intlet GZIP_LEVEL : Intlet KEY_HEADER_BYTES : Intlet KEY_SALT_BYTES : Intlet MAGIC : Stringlet METHOD_PASSPHRASE : Stringlet METHOD_X25519 : Stringlet PASSPHRASE_ITERATIONS : Intlet PE_SUBSYSTEM_OFFSET : Int64async fn bundle(input_exe : String, output_exe : String, file_dirs : Array[String], opts : PackOptions, launcher : String) -> Unitfn bytes_concat(a : Bytes, b : Bytes) -> Bytesfn chacha20_xor(key : Bytes, nonce : Bytes, counter : UInt, data : BytesView) -> Bytesfn chacha20poly1305_open(key : Bytes, nonce : Bytes, sealed_data : BytesView, aad : BytesView) -> Bytes?fn chacha20poly1305_seal(key : Bytes, nonce : Bytes, plaintext : BytesView, aad : BytesView) -> Bytesfn compress_with(data : Bytes, codec_name : String) -> (Bytes, String)fn compute_key_check(shared : Bytes, recipient : Bytes, ephemeral : Bytes, salt : Bytes) -> Bytesasync fn copy_icon(source_exe : String, target_exe : String) -> Boolfn current_exe_path() -> String?fn default_arch() -> Stringfn dirname(p : String) -> Stringasync fn ensure_parent_dir(path : String) -> Unitasync fn file_md5_hex(path : String) -> Stringfn gzip_decompress(data : Bytes) -> Bytesfn hex_to_bytes(text : String) -> Bytes?fn hkdf_expand_sha512(prk : Bytes, info : Bytes, length : Int) -> Bytesfn hkdf_extract_sha512(salt : Bytes, ikm : Bytes) -> Bytesfn hkdf_sha512(ikm : Bytes, salt : Bytes, info : Bytes, length : Int) -> Bytesfn hmac_sha512(key : Bytes, data : Bytes) -> Bytesfn host_platform() -> Stringfn i64_to_le8(v : Int64) -> Bytesfn is_all_zero(data : Bytes) -> Boolfn is_native_target() -> Boolasync fn is_protected_key_file(path : String) -> Boolfn iso8601_utc(epoch_millis : UInt64) -> Stringfn le8_to_i64(b : Bytes, start : Int) -> Int64async fn load_secret_key(path : String, passphrase : String) -> Bytesfn lz4_compress_frame(data : Bytes) -> Bytesfn lz4_decompress_frame(src : Bytes, size : Int) -> Bytesfn magic_bytes() -> Bytesfn md5_hex_of_string(s : String) -> Stringfn new_encryption(method_name : String, recipient : String, ephemeral : String, salt : String, iterations : Int, key_check : String) -> Encryptionfn new_encryption_options(method_name : String, public_key : String, passphrase : String, iterations : Int) -> EncryptionOptionsfn new_pack_options(name : String, version : String, os : String, arch : String, pre_install : Script?, post_install : Script?, compression : String) -> PackOptionsfn new_sealed_package(shared : Bytes, ephemeral : Bytes, recipient : Bytes, salt : Bytes) -> SealedPackagefn norm_sep(p : String) -> Stringfn package_tag_of(text : String) -> Stringfn passphrase_secret(passphrase : String, salt : Bytes, iterations : Int) -> Bytesfn pbkdf2_sha512(password : Bytes, salt : Bytes, iterations : Int, length : Int) -> Bytesfn pe_subsystem(data : Bytes) -> Intasync fn pe_subsystem_of(path : String) -> Intfn poly1305(key : Bytes, data : BytesView) -> Bytesasync fn recover_encryption(info : Encryption, secret_key : String, passphrase : String) -> PackageKeyfn report_md5(path : String, digest : String) -> Unitasync fn report_output_md5(path : String) -> Unitasync fn run_install_script(platform : String, kind : String, script : Script, dest : String) -> Intasync fn save_public_key(path : String, public_key : Bytes) -> Unitasync fn save_secret_key(path : String, secret_key : Bytes, passphrase : String) -> Unitasync fn save_secret_key_raw(path : String, secret_key : Bytes) -> Unitfn script_applies(script_platform : String, host : String) -> Boolfn script_platform_of(path : String) -> Stringasync fn set_pe_subsystem(path : String, subsystem : Int) -> Boolasync fn set_permissions(platform : String, path : String, mode : Int) -> Unitfn sha256_hex(data : Bytes) -> Stringfn sha256_hex_of_string(s : String) -> Stringfn sha512_hex(data : Bytes) -> Stringfn shared_with_secret_key(secret : Bytes, ephemeral : Bytes) -> Bytesfn string_to_utf8_bytes(s : String) -> Bytesfn truncate_bytes(data : Bytes, count : Int) -> Bytesasync fn try_launch_self_extracting() -> Boolfn utf8_bytes_to_string(bs : Bytes) -> Stringasync fn verify_extracted_dir(pkg : String, dir : String) -> Boolfn with_defaults(opts : PackOptions, name : String, version : String, os : String, arch : String) -> PackOptionsfn x25519(scalar : Bytes, point : Bytes) -> Bytesfn x25519_public_key(scalar : Bytes) -> Bytesfn xxhash32(data : Bytes, start : Int, end : Int, seed : UInt) -> UInt