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 only when it is produced by a native LunaPack binary, because the launcher stub is the binary itself. A bundle produced through moonx runs on WebAssembly and therefore carries the payload without a native launcher; use the native binary to get a self-extracting file.
myapp.exe = [ LunaPack launcher ] + [ archive: myapp.exe, res/app.cfg, ... ]$ ./myapp.exe
LunaPack: starting D:/apps/.lunapack/9c445884d23653fb/myapp.exe$ 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 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 <mode> | none, gzip or auto (default: auto) |
| --skip-scripts | unpack without running the install scripts |
| --extracted <dir> | with verify: also check the files already in <dir> |
# 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 or gzip
+--------------------+
| 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": 3,
"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 Entry {
path : String
mode : Int
size : Int64
stored_size : Int64
codec : String
checksum : String
md5 : String
offset : Int64
}pub struct PeFile {
data : Bytes
sections : Array[(Int64, Int64, Int64, Int64)]
resource_rva : Int64
resource_size : Int64
}pub struct Script {
platform : String
body : String
}let FORMAT_VERSION : Intlet MAGIC : Stringasync fn bundle(input_exe : String, output_exe : String, file_dirs : Array[String], opts : PackOptions) -> Unitasync fn compress_if_smaller(data : Bytes, always : Bool) -> (Bytes, String)async 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 extract_to(pkg : String, dest : String) -> Unitasync fn file_md5_hex(path : String) -> Stringasync fn gzip_decompress(data : Bytes) -> Bytesfn host_platform() -> Stringfn i64_to_le8(v : Int64) -> Bytesfn is_native_target() -> Boolfn iso8601_utc(epoch_millis : UInt64) -> Stringfn le8_to_i64(b : Bytes, start : Int) -> Int64fn magic_bytes() -> Bytesfn md5_hex_of_string(s : String) -> Stringfn new_pack_options(name : String, version : String, os : String, arch : String, pre_install : Script?, post_install : Script?, compression : String) -> PackOptionsfn norm_sep(p : String) -> Stringfn package_tag_of(text : String) -> Stringasync fn report_output_md5(path : String) -> Unitasync fn run_install_script(platform : String, kind : String, script : Script, dest : String) -> Intfn script_applies(script_platform : String, host : String) -> Boolfn script_platform_of(path : String) -> Stringasync fn set_permissions(platform : String, path : String, mode : Int) -> Unitfn sha256_hex(data : Bytes) -> Stringfn sha256_hex_of_string(s : String) -> Stringfn string_to_utf8_bytes(s : String) -> Bytesasync fn try_launch_self_extracting() -> Boolasync fn unpack(pkg : String, dest : String, run_scripts : Bool) -> Unitfn utf8_bytes_to_string(bs : Bytes) -> Stringasync fn verify(pkg : String) -> Boolasync fn verify_extracted_dir(pkg : String, dir : String) -> Boolfn with_defaults(opts : PackOptions, name : String, version : String, os : String, arch : String) -> PackOptions