Sign in

    moon-binstall

    Checksum-verified MoonBit CLI binary installer using GitHub Releases

    moonbit
    cli
    binary-installer
    binstall
    github-releases
    Download zip
    Author
    Version
    0.1.3
    License
    Apache-2.0
    Last updated
    15 hours ago
    Downloads
    14

    Dependencies

    #moon-binstall

    Install precompiled, SHA-256-verified MoonBit CLI binaries from GitHub Releases or versioned Mooncakes modules. The resolver, CLI, platform matching, archive inspection, filesystem operations and digest verification are implemented in MoonBit (native). curl is used for HTTPS transfers because release downloads involve redirects.

    #Install from Mooncakes

    Requires MoonBit, curl, and Linux or macOS. Install the CLI from Mooncakes:

    moon install f4ah6o/moon-binstall/cmd/main@0.1.3 mv "$HOME/.moon/bin/main" "$HOME/.moon/bin/moon-binstall" export PATH="$HOME/.moon/bin:$PATH" moon binstall --version

    The executable package is f4ah6o/moon-binstall/cmd/main; the module root f4ah6o/moon-binstall is the library. moon install builds the CLI from Mooncakes source and installs it as main in ~/.moon/bin. Rename it to moon-binstall to enable moon binstall. Add the export PATH line to your shell configuration if ~/.moon/bin is not already on PATH. See the Moon install documentation for installation options.

    Then install a tool:

    moon binstall turtles

    #Build

    Requires MoonBit, curl, and Linux or macOS:

    moon update moon install moon test --target native moon build --release --target native moon run cmd/main --target native -- --help

    The generated executable is _build/native/release/build/cmd/main/main.exe (the exact output path can vary with toolchain versions).

    #Use as a moon subcommand

    The official Moon CLI dispatches unknown subcommands to executables named moon-<subcommand> on PATH. Install the moon-binstall binary under that exact filename to enable moon binstall (no Moon CLI patch or shell alias).

    To build and bootstrap from source:

    moon update moon install moon build cmd/main --target native --release mkdir -p "${MOON_HOME:-$HOME/.moon}/bin" binary=$(find _build/native/release/build -type f -name main.exe -print) test -n "$binary" install -m 755 "$binary" "${MOON_HOME:-$HOME/.moon}/bin/moon-binstall" export PATH="${MOON_HOME:-$HOME/.moon}/bin:$PATH" moon binstall --version moon binstall turtles moon binstall hotpath moon binstall dsh moon binstall turtles@v0.4.0

    moon binstall forwards arguments unchanged to moon-binstall. The target repositories must first publish matching verified GitHub Releases; until then the installer exits with an explicit missing-release error. moon --help does not necessarily enumerate externally discovered commands.

    #Usage

    moon run cmd/main --target native -- turtles --dry-run moon run cmd/main --target native -- turtles@v0.4.0 --bin-dir "$HOME/.local/bin" moon run cmd/main --target native -- gpui-mbt/turtles.mbt moon run cmd/main --target native -- hotpath moon run cmd/main --target native -- f4ah6o/dsh.mbt

    Once packaged as a native executable, invoke moon-binstall directly.

    Supported aliases:

    AliasRepositoryInstalled executable
    turtlesgpui-mbt/turtles.mbtturtles
    hotpathgpui-mbt/hotpath.mbthotpath-report (example executable only)
    dshf4ah6o/dsh.mbtdsh

    hotpath.mbt is a library, not a native CLI; to depend on the library in your project, use moon add f4ah6o/hotpath instead. The hotpath-report binary is the repository's terminal-report example.

    #Install Mooncakes packages

    Use owner/module/package[@version] to select a package inside a Mooncakes module. Three or more slash-separated components select Mooncakes metadata; the existing two-component owner/repo[@tag] form and the aliases above keep their GitHub Releases behavior. For a module with several packages, supply the module once with --module and then list package paths:

    moon binstall f4ah6o/moon-binstall/cmd/main@0.1.3 \ --target linux-x86_64 --dry-run \ --pkg-fmt bin \ --pkg-url 'https://github.com/f4ah6o/moon-binstall/releases/download/v{version}/moon-binstall-{target}' moon binstall owner/module/cmd/first@1.2.0 owner/module/cmd/second@2.0.0 \ --bin-dir "$HOME/.local/bin" moon binstall --module owner/module cmd/first cmd/second@2.0.0

    Without an explicit @version, the installer asks Mooncakes for the module's latest release and pins the returned exact version for source and GitHub release resolution. It checks that Mooncakes returned the requested module and version, rejects yanked releases, and verifies the module source ZIP against the registry checksum before reading its optional moon-binstall.json. That source checksum authenticates the source archive only. The selected GitHub executable still needs GitHub's independent sha256:<64 hex digits> release-asset digest.

    A module may put an optional moon-binstall.json at its source root to describe each package's release asset. For example:

    { "repository": "https://github.com/owner/repo", "packages": [ { "package": "cmd/main", "binary": "tool", "pkg-url": "{repo}/releases/download/v{version}/{bin}-{target}{archive-suffix}", "pkg-fmt": "tgz", "bin-path": "{package}/{bin}", "overrides": { "darwin-aarch64": { "pkg-url": "{ repo }/releases/download/v{ version }/{ bin }-{ target }{ archive-suffix }", "pkg-fmt": "zip" } } } ] }

    For Mooncakes installs, the GitHub repository comes from metadata.repository in the registry response. repository in moon-binstall.json is used only with --manifest-path FILE; there it defaults to https://github.com/{owner}/{module} when omitted. Each package entry uses its MoonBit package path. Optional binary sets the installed executable name. pkg-url selects the asset URL template and pkg-fmt selects bin, tar, tgz, tar.gz, tbz2, tar.bz2, txz, tar.xz, tzstd, tar.zst or zip. For an archive without bin-path, the installer searches the archive root first, then exact executable paths under standard module release directories such as module-target-vVERSION/ and module/. It validates the complete listing before reading the first matching member. Set bin-path or pass --bin-path to select an exact member; an explicit path never falls back to discovery. --pkg-url, --pkg-fmt and --bin-path override manifest values for one invocation. A local manifest can be supplied with --manifest-path FILE; this is limited to one package and skips Mooncakes lookup.

    Each package may include an overrides object keyed by one of the four supported targets. Its pkg-url, pkg-fmt and bin-path fields override the corresponding package defaults for that target; omitted fields inherit the package value. Ordered --targets resolution applies each target's own settings. CLI options take precedence over both levels. All declared target names and metadata are validated, including overrides that are not ultimately selected.

    Templates accept both {target} and Cargo-style whitespace such as { target }. They support {repo} (the module GitHub repository URL), {name} or {module} (the module's last path component), {package}, {bin} or {binary}, {version} (without a leading v), {target}, {archive-suffix}, {archive-format}, and Cargo-compatible {target-family}, {target-arch}, {os-name} and {binary-ext}. For supported targets, target-family is linux or darwin, target-arch is x86_64 or aarch64, os-name is linux or macos, and binary-ext is empty. target-libc, target-vendor and Cargo cfg(...) override expressions are unsupported because these target names do not identify those properties. Backslash escapes \{, \} and \\ emit literal braces and a backslash. A template using {archive-suffix} should also set pkg-fmt or pass --pkg-fmt, so the suffix and extraction mode agree. The deprecated {format} alias means {archive-format} in pkg-url and {binary-ext} in bin-path. Resolved asset URLs must remain under the exact GitHub repository and release tag selected by the API.

    By default, the resolver checks common hyphenated asset names first, including <binary>-<target>, <binary>-<target>-<version> and <binary>-<version>-<target>, then Cargo-style underscore variants. It checks the package binary name before the module name and recognizes .tar, .tar.gz, .tgz, .tar.bz2, .tbz2, .tar.xz, .txz, .tar.zst and .zip archive suffixes. Supplying --pkg-fmt restricts matching to that format, so a raw asset cannot shadow a requested archive.

    --bin-dir continues to mean the destination directory for compatibility. Use --bin-path for a member inside an archive. --version applies to one package; use @VERSION on each coordinate in a batch. --targets accepts a comma-separated ordered list from the supported target names and selects the first matching asset. A matching asset with malformed metadata or an invalid digest fails the install instead of falling through to a lower-priority target. --target and --targets cannot be combined. Multiple packages that resolve to executable names that differ only by ASCII case are rejected on all platforms (for example, tool and Tool).

    #Versioned native releases

    Edit the version field in moon.mod to the intended SemVer version (e.g., 0.1.0 to 0.1.1) and merge that change into main. The release workflow compares the previous and new version values, not just the file modification date. On a version increase it validates version consistency, builds three native platforms, then creates the immutable vX.Y.Z tag and GitHub Release for the matching commit. Other changes to moon.mod do not publish. Version downgrades fail.

    An explicitly pushed vX.Y.Z tag remains supported only when the tag matches moon.mod; the workflow never edits source versions or bumps versions on its own. Publishing requires successful binary builds and GitHub Actions permission to create a Release.

    Update the version in installer.mbt as well so moon binstall --version matches.

    #Release contract

    GitHub Releases must include a matching raw executable or a supported .tar, .tgz, .tar.gz, .tbz2, .tar.bz2, .txz, .tar.xz, .tzstd, .tar.zst or .zip archive for the requested target. Every selected asset must include GitHub's sha256:<64 hex digits> digest metadata. Missing assets or digests are errors; installation never falls back to building from source. The built-in target names are linux-x86_64, linux-aarch64, darwin-x86_64 and darwin-aarch64. The resolver accepts all four names; this repository's release workflow currently builds Linux x86_64, Linux aarch64 and macOS aarch64 assets. It does not publish an Intel macOS asset.

    By default the latest non-prerelease GitHub Release is used. Specify @<tag> for a release tag. --dry-run resolves without installing, --force replaces an existing regular file after verification, and --bin-dir or MOON_BINSTALL_DIR overrides $HOME/.local/bin. Install batches are fully downloaded and verified before the first destination is replaced. The destination directory must be added to PATH.

    #Security

    No downloaded asset is executed during installation. Registry, GitHub API and asset transfers use HTTPS and redirects must remain HTTPS. The Mooncakes source ZIP checksum and selected GitHub release asset digest are checked separately. Before extraction, the installer validates every archive entry, including unselected members and entries in the Mooncakes source ZIP. Member paths must use ASCII letters, digits, ., _, -, + and / separators, with no whitespace; it rejects traversal, duplicate names, links, special files and option-like paths. It reads only the chosen regular member, limits archive listings to 16 MiB and 100,000 entries, caps selected binary output at 128 MiB, stages files in private directories on the destination filesystem, and renames only after verification. Symlink and non-regular destinations remain blocked, including with --force.

    Runtime requirements are curl 8.4.0 or newer and Linux or macOS. The minimum version is required because curl only enforces the download size limit during transfers with an unknown content length starting in 8.4.0. Asset downloads are capped at 256 MiB, and captured API/source metadata at 16 MiB. Tar formats require tar and the matching gzip, bzip2, xz or zstd codec; tzstd uses the zstd command. Zip assets require zipinfo and unzip (provided by the common unzip package on Ubuntu). moon test additionally requires Python 3 and the codec commands to create deterministic archive fixtures; production installation does not invoke Python.

    #Scope compared with cargo-binstall

    This project adapts the binary-install workflow to MoonBit: it resolves Mooncakes module metadata, selects versioned GitHub release assets, supports package batches and a source-controlled moon-binstall.json, verifies independent source and executable checksums, and safely installs raw, tar-gzip or zip binaries. It does not implement Cargo- or Rust-specific behavior: it does not read Cargo manifests, query crates.io, invoke cargo or rustup, build Rust source, install Rust dependencies, or support arbitrary registries and external asset hosts. The resolver accepts exactly linux-x86_64, linux-aarch64, darwin-x86_64 and darwin-aarch64; Windows is not supported. This repository publishes release binaries for Linux x86_64, Linux aarch64 and macOS aarch64.

    The repository's own moon-binstall.json maps MoonBit package cmd/main to the moon-binstall executable and the raw release asset naming convention. This lets a published Mooncakes source version use the same release contract without command-line asset overrides.

    #Mooncakes

    This project can publish its MoonBit source module to Mooncakes as f4ah6o/moon-binstall. GitHub Releases remain the distribution channel for prebuilt standalone native executables.

    The Mooncakes registry publication is a separate authenticated step. From an authorized workstation with a Mooncakes f4ah6o account:

    moon login moon update moon install moon package --list moon publish

    After the versioned GitHub Release succeeds, the Publish Mooncakes GitHub Actions workflow publishes the corresponding source module using the MOONCAKES_CREDENTIALS_JSON repository secret. Store the complete contents of ~/.moon/credentials.json from moon login in GitHub's Settings → Secrets and variables → Actions. Do not put credentials in committed files or issue comments. The workflow uses temporary credentials, deletes the file on completion, and supports manual retry.

    Publishing requires this GitHub Secret to be configured before the release workflow is merged. Registry publication is only complete once moon publish reports success and the Mooncakes module/version is visible.

    ArtifactConfig

    pub struct ArtifactConfig {
    package_path : String
    binary : String
    pkg_url : String?
    pkg_fmt : String?
    bin_path : String?
    }

    One package's published binary artifact settings from moon-binstall.json.

    BatchOptions

    pub struct BatchOptions {
    coordinates : Array[InstallCoordinate]
    directory : String?
    dry_run : Bool
    force : Bool
    target : String?
    version : String?
    pkg_url : String?
    pkg_fmt : String?
    bin_path : String?
    module_path : String?
    manifest_path : String?
    }

    Multi-package CLI settings. The existing Options and install entry point remain available to library callers.

    InstallCoordinate

    pub enum InstallCoordinate {
    GitHub(Package)
    Mooncakes(MooncakesPackage)
    }

    A source coordinate accepted by the installer.

    MooncakesPackage

    pub struct MooncakesPackage {
    module_path : String
    package_path : String
    binary : String
    version : String?
    }

    A Mooncakes package coordinate within a versioned module.

    Options

    pub struct Options {
    pkg : Package
    directory : String?
    dry_run : Bool
    force : Bool
    }

    Package

    pub struct Package {
    owner : String
    repo : String
    binary : String
    version : String?
    }

    Only explicit GitHub coordinates and the three owned aliases are accepted. No shell invocation is constructed from these values.

    RegistryRelease

    pub struct RegistryRelease {
    module_path : String
    version : String
    repository : String
    checksum : String
    }

    Exact-version Mooncakes metadata used to resolve the source repository. checksum authenticates the Mooncakes source archive only.

    ReleaseAsset

    pub struct ReleaseAsset {
    url : String
    digest : String
    name : String
    tag : String
    }

    ResolvedInstall

    pub struct ResolvedInstall {
    asset : ReleaseAsset
    binary : String
    format : String
    bin_path : String?
    }

    StagedInstall

    pub struct StagedInstall {
    destination : String
    staging : String
    staging_dir : String
    }

    archive_suffix

    fn archive_suffix(format : String) -> String raise

    artifact_url_asset_name

    fn artifact_url_asset_name(url : String, owner : String, repo : String, tag : String) -> String raise

    default_archive_member_candidates

    fn default_archive_member_candidates(module_name : String, package_path : String, binary : String, version : String, target : String) -> Array[String] raise

    Ordered exact archive member candidates for archives that omit bin-path. The existing root executable path remains first; the remaining paths use the standard release-directory spellings documented by cargo-binstall.

    default_artifact_names

    fn default_artifact_names(binary : String, version : String, target : String) -> Array[String]

    default_artifact_names_for_format

    fn default_artifact_names_for_format(binary : String, version : String, target : String, format : String?) -> Array[String] raise

    When the caller selects an archive format, do not let an earlier raw candidate shadow the matching archive asset in the release.

    execute

    async fn execute(argv : ArrayView[String]) -> Int

    expand_artifact_template

    fn expand_artifact_template(template : String, owner : String, repo : String, module_name : String, package_path : String, binary : String, version : String, target : String, archive_suffix : String) -> String raise

    expected_asset

    fn expected_asset(pkg : Package, target : String) -> String

    infer_archive_format

    fn infer_archive_format(name : String) -> String

    install

    async fn install(config : Options) -> Unit

    install_from_asset_file

    async fn install_from_asset_file(asset_path : String, digest : String, format : String, bin_path : String?, binary : String, bin_dir : String, force : Bool) -> Unit

    install_many

    async fn install_many(options : BatchOptions) -> Unit

    manifest_repository

    fn manifest_repository(payload : String, module_path : String) -> String raise

    Read the source repository from a local test/maintainer manifest. If it is omitted, owner/module is used as the GitHub repository coordinate.

    manifest_version

    fn manifest_version(payload : String) -> String? raise

    mooncakes_manifest_url

    fn mooncakes_manifest_url(module_path : String, version : String?) -> String raise

    mooncakes_source_url

    fn mooncakes_source_url(module_path : String, version : String) -> String raise

    parse_artifact_config

    fn parse_artifact_config(payload : String, package_path : String, default_binary : String) -> ArtifactConfig? raise

    Parse the optional package settings from a module-root moon-binstall.json. The file is part of the Mooncakes source archive, whose independent source checksum is verified before this parser is called.

    parse_batch_options

    fn parse_batch_options(argv : ArrayView[String]) -> BatchOptions raise

    parse_coordinate

    fn parse_coordinate(input : String) -> InstallCoordinate raise

    GitHub owner/repository for backwards compatibility.

    parse_github_repository

    fn parse_github_repository(repository : String) -> (String, String) raise

    parse_module_package

    fn parse_module_package(module_path : String, input : String) -> MooncakesPackage raise

    Parse a package path when the module is supplied separately with --module.

    parse_options

    fn parse_options(argv : ArrayView[String]) -> Options raise

    parse_package

    fn parse_package(input : String) -> Package raise

    parse_target_list

    fn parse_target_list(value : String) -> Array[String] raise

    Keep the public BatchOptions shape source-compatible: --targets is stored in its existing target field as a comma-separated, validated sequence.

    platform_key

    fn platform_key(os : String, cpu : String) -> String raise

    GitHub Releases assets have a stable, archive-free format. Unrecognized systems fail closed instead of downloading a binary for another CPU.

    read_artifact_binary

    async fn read_artifact_binary(archive_path : String, format : String, member_path : String?) -> Bytes

    Read the selected executable's bytes without unpacking archive paths onto disk. Both tar and zip entries are preflighted before their content is read.

    read_artifact_binary_from_candidates

    async fn read_artifact_binary_from_candidates(archive_path : String, format : String, candidates : Array[String]) -> Bytes

    Discover an archive member from ordered, exact paths. Every archive entry is preflighted before the first candidate is accepted.

    read_artifact_binary_from_candidates_with_limit

    async fn read_artifact_binary_from_candidates_with_limit(archive_path : String, format : String, candidates : Array[String], max_binary_bytes : Int) -> Bytes

    read_artifact_binary_with_limit

    async fn read_artifact_binary_with_limit(archive_path : String, format : String, member_path : String?, max_binary_bytes : Int) -> Bytes

    Keep decompression bounded while the helper process produces stdout, before its output is materialized in memory.

    read_zip_member_optional

    async fn read_zip_member_optional(archive_path : String, wanted : String) -> Bytes?

    read_zip_member_optional_with_limit

    async fn read_zip_member_optional_with_limit(archive_path : String, wanted : String, max_member_bytes : Int) -> Bytes?

    release_url

    fn release_url(pkg : Package) -> String

    resolve_registry_release

    fn resolve_registry_release(payload : String, expected_module : String, expected_version : String?) -> RegistryRelease raise

    Parse the response from the Mooncakes exact-release manifest endpoint. The response's repository and source checksum are never treated as binary artifact metadata.

    resolve_release

    fn resolve_release(payload : String, pkg : Package, target : String) -> ReleaseAsset raise

    A release without a GitHub SHA-256 digest is not safe to install. The URL is also pinned to the requested repository's release namespace.

    resolve_release_names

    fn resolve_release_names(payload : String, owner : String, repo : String, expected_names : Array[String], expected_tag : String?) -> ReleaseAsset raise

    Resolve an asset by name and bind its URL to the API's exact release path.

    resolve_release_names_optional

    fn resolve_release_names_optional(payload : String, owner : String, repo : String, expected_names : Array[String], expected_tag : String?) -> ReleaseAsset? raise

    Resolve the first present candidate, returning None only when none of the expected names exists. A matching but malformed entry is always an error.

    safe_binary_name

    fn safe_binary_name(name : String) -> Bool

    usage

    fn usage() -> String

    valid_sha256_hex

    fn valid_sha256_hex(value : String) -> Bool

    validate_archive_format

    fn validate_archive_format(format : String) -> Unit raise

    validate_artifact_template

    fn validate_artifact_template(template : String) -> Unit raise

    Validate placeholders without needing a resolved target. This runs over every declared base and target-specific template before asset fallback.

    validate_member_path

    fn validate_member_path(path : String) -> Unit raise

    Reject absolute paths, traversal, option-like member names, backslashes, and control characters before a member path is passed to an archive tool.

    validate_target

    fn validate_target(target : String) -> Unit raise

    verify_sha256_hex

    async fn verify_sha256_hex(path : String, expected : String) -> Unit