All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[1.6.0] - 2026-07-02
Added
- CLI dispatch via
-eval+start_clean.boot: CLI mode (execution_mode: :cli) now boots the VM withstart_clean.boot(minimal boot — no application supervision tree) instead ofstart.boot(full release boot). CLI args are dispatched via-eval 'Elixir.Module.CLI':main([<<"...">>])followed by-s init stop, so the binary runs the CLI command and exits cleanly without ever starting OTP applications. Daemon and TUI modes continue usingstart.boot(full supervision tree). smoke_tests/test_escript_otp26: regression guard for escript builds targeting OTP 26 (OTP compatibility).smoke_tests/test_release_nif: regression guard that exercisesinclude_erts: falsewith explicit:erlang.system_info/1+Supervisor.start_link/2calls that fail ifkernel/supervisordid not load. Wired intosmoke_tests.sh.derive_cli_module/1: derives the CLI module name from the app name at runtime (:delfos→"Delfos.CLI",:test_cli→"TestCli.CLI"), following Alaja convention.- ROOTDIR heuristic: new logic checks if
<release>/lib/kernel-*exists to determine whether ROOTDIR should point to the release root (standard layout) or the bundled ERTS directory (include_erts: falselayout).
Fixed
- CLI args silently dropped (critical): the
argsvector (containing-eval,-s init stop, and user arguments) was built but never passed to theCommandin CLI mode. TheCommandwas constructed from scratch with individual.arg()calls, duplicating only the base erlexec args. Now uses.args(&args)— the complete vector is passed to the spawned process. - CLI args as charlists instead of binaries:
"version"in Erlang syntax produces a charlist ([118,101,114,...]), which Elixir receives as'version'(charlist) instead of"version"(string). All CLI arg comparisons using== "version"evaluated tofalse, causing'unknown command'errors. Fixed by formatting args as Erlang binary syntax:<<"version">>. - CLI mode used
start.boot(full supervision tree): before this release,exec_mode == :clistill started the full OTP release (start.boot), starting the application supervisor tree, Ecto repos, and all children. The CLI command ran as a side effect after the full tree started, and-s init stopwas ineffective because the supervision tree kept the VM alive. Daemon/TUI remain unaffected. exec_modeoverridden by args presence:cli_mode = !user_args.is_empty()inrun_with_erlexecmeant any binary launched without arguments entered daemon mode even whenexecution_mode: :cliwas configured. An empty-args CLI binary (e.g.delfoswith no subcommand) would boot the full supervision tree and hang forever. Nowexec_modeis the sole determinant of boot strategy.--erl-configvs-configflag: previous releases used the-configflag for erlexec, which is unsupported in newer OTP versions. Changed to--erl-configfor compatibility.- Release mode boot crash (
load_failedon kernel/stdlib): when a release is built withinclude_erts: false(or when batamanta flattens the ERTS intorelease/erts/), the wrapper'sROOTDIRwas set to the release root, but the boot script references$ROOT/lib/kernel-*,$ROOT/lib/stdlib-*, etc. Those modules live in the bundled ERTS, not inrelease/lib/. The VM crashed on boot with{load_failed,[supervisor,kernel,...]}. The wrapper now detects whereerlexecactually lives and pointsROOTDIRto the bundled ERTS directory when it is flattened. Two new Rust unit tests cover theinclude_erts: trueandinclude_erts: falselayouts. - Umbrella release builds were silently broken:
run_umbrella_release/7ranmix releasefrom the umbrella root (nocd:), so only the root's release (typically a no-op) was ever built. The sub-app releases were never assembled. The loop now iterates sub-apps and runsmix releasewithcd: app_pathso each sub-app produces its own release. - Umbrella
get_release_path/1pointed at the wrong directory: the function computed<root>/_build/prod/rel/<app>for every app, but in a sub-app the release lives in<sub_app>/_build/prod/rel/<app>. Now accepts an optionalapp_pathargument; the umbrella caller passes the sub-app path while the standalone path is unchanged. Four new unit tests cover standalone, single-level umbrella, nested umbrella, andapp_pathprecedence. flatten_nested_ertsremoved fromPackager.prepare_erts/1: the flattening step was accidentally removed in a previous refactor, causinginclude_erts: falsereleases to have a malformed ERTS layout (erlexec two levels deep). Restored the call to ensure erlexec and kernel are at the same level under ROOTDIR.test_escriptmissingmain_module: thetest_escript_otp26smoke test project lacked theescript: [main_module: ...]configuration in itsmix.exsand the correspondingcli.exentry point. Added both.
Changed
exec_modeis the sole boot strategy selector: Previously, the presence of CLI arguments could overrideexec_mode, causing daemon-configured apps to accidentally enter CLI mode (and vice versa). Nowexec_modeis evaluated first and always respected.flatten_nested_ertsis now always applied: even for standard releases, the flattening step is harmless (no-op when files are already flat) and essential forinclude_erts: false. The erlexec binary is moved up one level to match the kernel directory location.
Quality
- Format: ✅ clean
- Credo --strict: ✅ 0 issues
- Compile --warnings-as-errors: ✅ 0 warnings
- Tests: 221 passing, 3 excluded (integration); 14 new tests added (6 Rust, 8 Elixir)
- Smoke tests: 7/7 passing (test_cli, test_tui, test_daemon, test_escript, test_release_otp27, test_release_nif, test_escript_otp26)
[1.5.1] - 2026-06-10
Added
- Umbrella Projects Support: New
umbrella: trueconfig option to build standalone binaries for umbrella sub-apps. Batamanta detects sub-apps withbatamanta:config inapps/, builds releases/escripts once, and packages only configured apps.find_umbrella_apps/1to detect sub-apps with batamanta configpartition_apps_by_format/2to split apps by release/escript formatrun_umbrella_release/6to build releases for umbrella sub-appsrun_umbrella_escripts/6to build escripts for umbrella sub-appsread_umbrella_app_config/2to read per-app batamanta configurationbuild_umbrella_banner/6for umbrella-specific build banner
- Banner images: Six PNG banner assets added to
priv/assets/ - Banner fallback text: Informational message when banner image file is not found
Changed
- Banner image resolution: Expanded search candidates to include
priv/assets/paths for both dev and prod builds - Host detection fallback: Default to
:ubuntu_22_04_x86_64when host detection fails - mtime_to_age_seconds/1: Extracted duplicate datetime arithmetic into a shared helper with safe fallback for non-tuple inputs
- Documentation: Added comprehensive umbrella project guide in English and Spanish
Fixed
- Banner render with
show_banner: false: Removed redundantprotocol == :asciicheck that skipped banner context initialization whenshow_bannerwas false
Quality
- Format: ✅ clean
- Tests: 199 passing, 3 excluded (integration); 5 new umbrella-related tests added
[1.5.0] - 2026-05-19
Changed
- Development versions upgraded: Erlang 28.1 + Elixir 1.19.5 (OTP 28)
- Internal development now uses latest stable OTP/Elixir
- Minimum packagable OTP remains at 25 (ERTS repository unchanged)
- Minimum OTP to run
mix batamantaremains at 25
- CI matrix updated: Elixir 1.15.8/OTP 26.2.5 + Elixir 1.19.5/OTP 28.1
- CI caching: Mix and Cargo dependency caching added for faster runs
- CI artifacts: Built binaries are now uploaded as artifacts for debugging
- CI cleanup: Simplified cleanup step — only clears ERTS cache, not project build artifacts
Fixed
- Escript wrapper args (critical): Arguments wrapped by the shell wrapper
script no longer carry literal double-quote characters.
\"$arg\"in the wrapper injected"status"(with literal quotes) instead ofstatus. Replaced withshift/set --pattern using"$@"— fixes all CLI subcommands in escript-mode binaries. - Release daemon args: Missing
-extra --separator before user arguments in the daemon spawn path causederlexecto interpret user args as its own flags. Added-extra --before forwarding, matching the non-daemon path. - Unless-else style: Three
unless condition do :ok else ... endblocks inEscriptPackagerinverted toif condition do ... else :ok end(Credo compliance). - LibcDetector:
ldd --versiondetection now works on CachyOS and other rolling-release distributions (OTP 28 handles edge cases gracefully) - RustTemplate: Removed stale P1 FIX markers;
build.rsnow panics with a clear error if the payload is missing instead of silently skipping - mix.exs: Removed stale P2 FIX comment about
rust.testalias (implementation was already correct) - ex_doc: Updated from
~> 0.34to~> 0.40
Quality
- Format: ✅ clean
- Credo --strict: ✅ 0 issues (340 mods/funs)
- Compile --warnings-as-errors: ✅ 0 warnings
- Tests: 199 passing, 3 excluded (integration)
[1.4.0] - 2026-04-07
Added
- Build Environment Isolation: Introduced
Batamanta.EnvCleanerto isolate the build process from version managers (asdf,mise,kerl, etc.). This ensures that the Erlang/Elixir version used for compilation matches the target ERTS, preventing "corrupt atom table" errors. - Shared Environment Logic: Both Escript and Release pipelines now share a sanitized environment containing only essential system variables (
HOME,USER,TMPDIR,LANG,SHELL,TERM,SSH_AUTH_SOCK). - Detailed Build Logs: Improved error reporting for
mix releasefailures by capturing and displaying the full compiler output in case of status non-zero.
Fixed
- Version Manager Interference: Fixed a critical bug where
asdfshims in thePATHwould causemixto use a different ERTS version than the one intended for packaging. - Legacy Elixir Compatibility: Replaced
File.executable?/1(introduced in Elixir 1.16) withFile.regular?/1to maintain compatibility with Elixir 1.15.x. - Typo cleanup: Corrected multiple instances of
BatmanMantanamespace typos toBatamanta. - Credo & Code Quality: Refactored
system_paths/0inEnvCleanerto reduce cyclomatic complexity and flattened nested logic inclean_mix_build_artifacts.
[1.3.0] - 2026-03-25
Added
- Temporary Files Cleanup: Batamanta now automatically cleans up internal temporary artifacts (
bat_cargo_cache,bat_pkg_*,bat_build_*) after each compilation to keep/tmpempty while strictly preserving the ERTS cache.
Fixed
- Daemon Initialization Crash (Crítical /
undef): Reimplemented thespawn_detachedhook in Rust to fully inherit the parent environment (std::env::vars()) and properly mapargv[0]. Fixing an elusive bug where the BEAM VM crashed on spawn in Daemon mode due to a missing environment block. - Daemon Logging Isolation:
spawn_detachedno longer strictly forces adup2redirect to/dev/nullfor standard file descriptors, allowing application logs to correctly print to the terminal prior to background detachment. Perfectly compatible with CI Smoke Tests. - Dialyzer & Compiler Specs: Resolved compiler typing violations related to
{error, _}ondetect_hostinTargetand removed unused legacy branches. - Cleaned Test Coverage: Updated multiple test namespaces (
Baton->Batamanta) and expanded coverage for internal functions.
[1.2.1] - 2026-03-23
Fixed
- Execution Mode vs Format Nomenclature: Corrected confusing naming - renamed
execution_modetoformatin configuration and documentation - Daemon Mode macOS Support: Fixed daemon spawning to work on both Linux and macOS (uses
fork()+setsid()via libc) - Banner Positioning: Improved banner display to work consistently whether terminal is fresh or has existing output
[1.2.0] - 2026-03-23
Added
- Escript Support: New
format: :escriptoption to build lightweight escripts instead of full OTP releases - Auto-detection: Automatically detects escript format when project has
:escriptconfig inmix.exs - CLI Override:
--formatoption to override format detection from command line - Dynamic Version Detection: Fixed hardcoded version
0.1.0in Rust wrapper, now reads version fromstart_erl.data - Smoke Tests: Added
test_escriptsmoke test project for escript builds - Retry Logic for Downloads: ERTS downloads now retry up to 3 times with exponential backoff (1s, 2s, 4s) on network failures
- Cache Lock Mechanism: File-based locking prevents race conditions when multiple processes try to download ERTS simultaneously
- Improved Tar Error Parsing: Extract-specific error messages for tar failures (permission denied, disk full, corrupted archive, etc.)
- zstd Dependency Check: Packager now raises a clear error with installation instructions if zstd is not found
- Integration Tests: New
FetcherIntegrationTestmodule for tests requiring network access (excluded by default, run withmix test --include integration)
Changed
- Compilation Without --warnings-as-errors:
EscriptBuilderno longer fails on compiler warnings, improving build reliability across different OTP versions - EscriptBuilder Validation: Improved escript validation using
File.read/1with proper ELF/shebang magic byte detection - LibcDetector Refactoring: Consolidated regex patterns for OS detection, renamed
is_musl_distro?tomusl_distro?for Credo compliance - Smaller Binaries: Escript format produces ~60-70% smaller binaries than release format
- EscriptPackager: New module for packaging escripts with minimal ERTS
- EscriptBuilder: New module for building escripts via
mix escript.build - Rust Template: Updated to support both
:releaseand:escriptoutput formats viaBATAMANTA_FORMATenv var - Banner Positioning: Improved banner display to work consistently whether terminal is fresh or has existing output
Fixed
- Cache Race Conditions: TOCTOU race condition in
check_erts_cachenow protected by file locks - Tar Error Messages: Better error messages when tar extraction fails, including "Permission denied", "Disk full", etc.
- Download Retry Pattern: Fixed pattern matching to handle both
:ok(file downloads) and{:ok, body}(manifest downloads) return values - Version Detection: Release version is now dynamically detected from
releases/start_erl.datainstead of being hardcoded
[1.1.0] - 2026-03-19
Added
- OTP Version Control: Users can specify exact OTP versions in config (
otp_version: "28.1") or via CLI (--otp-version) - Explicit vs Auto Mode: Explicit mode uses exact version (fails if unavailable), auto mode uses conservative fallback
- Smoke Tests: Added
test_cli,test_tui, andtest_daemonsmoke test projects - CI Matrix: Comprehensive CI with tests on Elixir 1.15/1.18 and OTP 25/28
Fixed
- MANIFEST JSON Parser: Rewrote broken parser that incorrectly handled nested JSON structures
- Release Path: Fixed
get_release_path/1to correctly use_build/prod/rel/<app> - Duplicate Logging: Removed duplicate ERTS cached messages during build
- Version Resolution: Improved
generate_version_variants/1with proper fallbacks - TUI Key Handling: Fixed crash when pressing keys (handles
<<key, "\n">>pattern) - Application Start: Fixed Application behaviour to return proper
{:ok, pid}tuple
Changed
generate_version_variants/1refactored to reduce nesting depth (Credo compliance)- CI simplified to focus on reliable tests (removed problematic macOS ARM64 cross-compile)
[1.0.1] - 2026-03-09
Fixed
- Linux auto-detection: Automatically detects between glibc and musl based on distribution
- Arch Linux support: Fixed compilation on Arch-based distributions (CachyOS, Manjaro, etc.)
- Terminal cleanup: Improved ANSI sequence cleanup on exit
- ERTS embedded: Now uses the ERTS embedded in the release instead of downloading external one, fixing "Exec format error"
Changed
- Default Linux target changed from musl to gnu for better compatibility
- Uses ctrlc instead of signal-hook for better cross-platform support
[1.0.0] - 2026-03-16
Added
- Monolithic Binary Generation: Core capability to wrap Elixir releases and the Erlang Runtime System (ERTS) into a single, static executable.
- Dynamic ERTS Management: Automatically fetches and caches compatible ERTS versions from Hex.pm or Beam Machine based on the target system.
- Cross-Platform Support: Built-in support for multiple targets including
x86_64-linux-musl,x86_64-pc-windows-msvc,x86_64-apple-darwin, andaarch64-apple-darwin. - Rust-powered Dispenser: A high-performance Rust wrapper that handles payload extraction, signal proxying, and secure execution.
- Static Compilation: Generates binaries with zero external dependencies (no Erlang or Elixir needed on the target host).
- Binary Minification: Integrated support for
stripandupxto significantly reduce the final executable size. - Smart Native Fallback: Intelligent detection to use the local native ERTS when building for the same host OS to ensure perfect compatibility.
- Clean Task: Provided
mix batamanta.cleanto manage and clear the local ERTS cache. - CLI Arg Handling: Support for passing plain arguments directly to the Erlang VM for portable CLI tools.
- RAII Cleanup: Support for automatically removing temporary extraction files when the application exits.
Improved
- Idiomatic Refactor: Completely refactored the codebase to use modern Elixir patterns (pipelines, pattern matching,
withstatements). - Documentation: Comprehensive documentation in both English (primary) and Spanish, including detailed architecture guides and usage examples.
Error Handling: Migrated to result-tuple based error propagation (
{:ok, term} | {:error, reason}) for more reliable orchestration.- Unit Testing: Full test suite covering target resolution, packaging logic, and cache management.
- CI/CD Integration: Pre-configured GitHub Actions to validate compatibility across multiple Elixir and OTP versions.