https://github.com/real-fruit-snacks/mainsail
Single-file BusyBox-like multi-call binary, in Python — 50 POSIX utilities, native Windows support, freezable with Nuitka
https://github.com/real-fruit-snacks/mainsail
busybox cli-tools coreutils cross-platform nuitka posix python single-binary utility-toolkit windows
Last synced: 3 days ago
JSON representation
Single-file BusyBox-like multi-call binary, in Python — 50 POSIX utilities, native Windows support, freezable with Nuitka
- Host: GitHub
- URL: https://github.com/real-fruit-snacks/mainsail
- Owner: Real-Fruit-Snacks
- License: mit
- Created: 2026-04-24T01:56:19.000Z (4 days ago)
- Default Branch: main
- Last Pushed: 2026-04-24T04:19:41.000Z (4 days ago)
- Last Synced: 2026-04-24T04:23:19.217Z (4 days ago)
- Topics: busybox, cli-tools, coreutils, cross-platform, nuitka, posix, python, single-binary, utility-toolkit, windows
- Language: Python
- Homepage: https://real-fruit-snacks.github.io/mainsail/
- Size: 146 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
Awesome Lists containing this project
README






**Single-file BusyBox-like multi-call binary, in Python.**
**73 utilities** — the POSIX coreutils (`ls`, `cat`, `grep`, `sed`, `awk`, `tar`, …) plus a real `jq` for JSON, an HTTP client (`http`), a DNS resolver (`dig`), a TCP `nc`, and the parity gap-fillers BusyBox users miss (`tac`, `rev`, `nl`, `paste`, `split`, `cmp`, `comm`, `expand`, `unexpand`, `mktemp`, `truncate`, `getopt`, `dd`, `od`, `hexdump`, `diff`, `join`, `fmt`). Bundled into a ~5 MB executable. Native Windows support without WSL, Cygwin, or git-bash. Six native binaries (glibc Linux × x86_64/ARM64, Windows × x86_64/ARM64, macOS ARM64, Alpine/musl Linux x64) plus an ~90 KB portable zipapp that runs anywhere Python 3.8+ is installed — including ESXi.
[Download Latest](https://github.com/Real-Fruit-Snacks/mainsail/releases/latest)
·
[GitHub Pages](https://real-fruit-snacks.github.io/mainsail/)
·
[Changelog](CHANGELOG.md)
---
## Quick Start
**From a release** — no Python required:
```bash
# Linux (glibc — Ubuntu, Debian, RHEL, …)
curl -LO https://github.com/Real-Fruit-Snacks/mainsail/releases/latest/download/mainsail-linux-x64
chmod +x mainsail-linux-x64
./mainsail-linux-x64 --version
```
**From source** — Python 3.10+:
```bash
git clone https://github.com/Real-Fruit-Snacks/mainsail.git
cd mainsail
pip install -e .
mainsail --list
```
---
## Pre-built artifacts
Every release tag (`v0.1.x`) ships **13 artifacts** built and verified by GitHub Actions:
### Native binaries
| Target | Full | Slim _(no archives/hashing)_ |
|-----------------------------------|-------------------------------------|-------------------------------------------|
| Linux x86_64 (glibc 2.35+) | `mainsail-linux-x64` | `mainsail-linux-x64-slim` |
| Linux ARM64 (glibc 2.39+) | `mainsail-linux-arm64` | `mainsail-linux-arm64-slim` |
| Linux x86_64 **musl** (Alpine) | `mainsail-linux-x64-musl` ✱ | _(use the full one or build slim locally)_ |
| Windows x86_64 | `mainsail-windows-x64.exe` | `mainsail-windows-x64-slim.exe` |
| Windows ARM64 | `mainsail-windows-arm64.exe` | `mainsail-windows-arm64-slim.exe` |
| macOS ARM64 (Apple Silicon) | `mainsail-macos-arm64` | `mainsail-macos-arm64-slim` |
✱ The `-musl` build runs on Alpine and any musl-libc Linux (distroless containers, `gcr.io/distroless/static`, etc.). It does **not** run on glibc systems — use the regular `mainsail-linux-x64` for those.
Drop any binary anywhere on `PATH` and run.
### Portable zipapp
| Artifact | Size | Applets | Notes |
|---------------------|---------|---------|-----------------------------------------------|
| `mainsail.pyz` | ~80 KB | 51 | runs on any host with Python 3.8+ |
| `mainsail-slim.pyz` | ~68 KB | 39 | smaller; same applets as the slim binaries |
Useful for ESXi (which bundles Python 3 since 7.0U3), exotic architectures, jailbroken routers, and corporate machines where installing a native binary isn't practical:
```bash
scp mainsail.pyz host:/tmp/
ssh host 'python3 /tmp/mainsail.pyz find /var/log -mtime -7'
```
### Build your own
Pick exactly the applets you need:
```bash
python build.py --preset slim # 39 applets, no archives/hashing
python build.py --preset minimal # 18 applets, scripting essentials
python build.py --applets ls,cat,grep,sed,awk # hand-picked
python build.py --pyz --applets ls,cat,grep,sed # smallest zipapp (~18 KB for 5 applets)
python build.py --list-presets # see what's in each preset
```
Savings are real for the zipapp (minimal drops it from 80 KB to 45 KB) but modest for the Nuitka binary (~3 %) — the Python runtime dominates the payload, not our code. Non-full builds land as `dist/mainsail-` (with matching `.exe`/`.pyz` extension).
> **Why no fully-static Linux binary?** We tried. `LDFLAGS=-static` and `--static-libpython=yes` both link cleanly, but Python then refuses to load any C extension at runtime with `ImportError: Dynamic loading not supported` — a fully-static Python interpreter can't `dlopen()`. A truly self-contained Python binary requires baking every extension into `libpython` at compile time, which `python-build-standalone` doesn't ship. So we offer the musl-linked variant for Alpine/distroless users and the dynamic glibc binary for everyone else.
>
> **Why no `linux-arm64-musl`?** GitHub Actions doesn't support Node.js actions inside Alpine containers on ARM64 runners — only x64. Until that changes, ARM64 users have the dynamic glibc binary, the portable `mainsail.pyz`, or can build a musl variant locally on Alpine.
>
> **Why no `mainsail-macos-x64`?** GitHub's free-tier `macos-13` runner queue is effectively unavailable to this project (30+ minute queues, never dispatched). Apple stopped shipping Intel Macs in 2023; the ARM64 binary covers the supported lineup. Intel-Mac users can use the portable `mainsail.pyz` or build from source.
---
## Features
### One binary, seventy-three utilities
Every common POSIX tool you'd reach for in a shell pipeline — plus `jq` for JSON, `http` for HTTP, `dig` for DNS, `nc` for TCP, and the BusyBox parity gap-fillers (`dd`, `od`, `hexdump`, `diff`, `join`, `fmt`, …). Dispatch via `mainsail ` or symlink/hardlink to call the applet directly.
```bash
mainsail ls -la # GNU-style flags
mainsail cat file.txt | mainsail grep -C 2 pattern
mainsail find . -name '*.py' -size +1k -mtime -7
mainsail seq 100 | mainsail sort -rn | mainsail head -5
```
### Native Windows
No WSL, no Cygwin, no git-bash. `mainsail.exe` runs on bare Windows and recognises Windows-native command names as aliases.
```cmd
mainsail dir . :: == ls
mainsail type file.txt :: == cat
mainsail copy a.txt b.txt :: == cp
mainsail del old.txt :: == rm
mainsail where python :: == which
```
### Real applets, not stubs
Each applet implements the common POSIX flags and edge cases.
- `find` — expression tree with `-exec`, `-prune`, `-and`/`-or`, parens, size/time predicates, `-delete`
- `sed` — `s///`, `d`, `p`, `q`, `=`, `y///`, addresses, ranges, negation, `-i` in-place edit, BRE + ERE
- `awk` — BEGIN/END, `/regex/` and expression patterns, range patterns, `print`/`printf`, full control flow, associative arrays, the standard built-ins (`length`, `substr`, `index`, `split`, `sub`, `gsub`, `match`, `toupper`, `tolower`, `sprintf`, `int`)
- `jq` — practical subset: pipes, comma, alternatives, comparison/arithmetic, object & array constructors, slices and iterators, `if`/`then`/`elif`/`else`/`end`, **40+ built-in functions** (`select`, `map`, `sort_by`, `unique_by`, `to_entries`, `with_entries`, `paths`, `split`, `join`, `startswith`, …), raw output (`-r`), compact (`-c`), slurp (`-s`)
- `http` — `GET`/`POST`/`PUT`/`DELETE`/`HEAD`, custom headers, body literal or `@file`, `--json` shortcut, redirect-following on by default, `--fail` for HTTP errors
- `dig` — direct UDP DNS queries: A, AAAA, MX, TXT, CNAME, NS, SOA, PTR; `+short`; reverse lookups via `-x`
- `sort` — `-k` key fields, `-t` custom separator, `-o` output file, numeric/reverse/unique
- `tar` — create/extract/list with gzip/bzip2/xz filters; accepts traditional (`cvfz`) and dashed (`-cvfz`) flag forms
```bash
mainsail find . -name '*.tmp' -delete
mainsail sed -i 's/foo/bar/g' *.txt
mainsail awk -F, '{s+=$3} END{print s/NR}' data.csv
mainsail jq '.servers[] | select(.region == "us") | .name' inventory.json
mainsail http -H 'Authorization: Bearer $TOKEN' https://api.example.com/me
mainsail dig MX gmail.com +short
mainsail sort -k 3,3n -t , data.csv
mainsail tar -czf src.tar.gz src/ --exclude='*.pyc'
```
### Pipeline-grade I/O
Binary-safe through `cat`/`tee`/`gzip`. CRLF survives Windows text-mode round-trips. `tail -f` follows files and detects rotation. `xargs` accepts `-print0`/`-0` to handle Windows backslashes.
```bash
mainsail find . -type f -print0 | mainsail xargs -0 mainsail sha256sum
mainsail tail -f /var/log/app.log
mainsail gzip -c data.bin | mainsail gunzip > data.bin.copy
```
### Cross-platform integrity
Same SHA-256 of `"abc"` (`ba7816bf…015ad`) on every supported platform. `tar` archives are interchangeable. The CI suite runs 268 unit tests on Linux/macOS/Windows and a 23-case stress harness covering large inputs, Unicode, binary-safe streams, deep trees, pipelines, round-trips, and edge cases.
---
## Supported applets
| Category | Applets |
|-------------|---------|
| File ops | `ls` `cp` `mv` `rm` `mkdir` `touch` `find` `chmod` `ln` `stat` `truncate` `mktemp` `dd` |
| Text | `cat` `tac` `rev` `grep` `head` `tail` `wc` `nl` `sort` `uniq` `cut` `paste` `tr` `sed` `awk` `tee` `xargs` `printf` `echo` `expand` `unexpand` `split` `cmp` `comm` `diff` `join` `fmt` `od` `hexdump` |
| **JSON** | **`jq`** _(practical subset: pipes, filters, select/map/sort_by, object & array constructors, 40+ built-in functions)_ |
| **Network** | **`http`** _(curl-style GET/POST with headers, body, JSON, redirects)_ • **`dig`** _(DNS A/AAAA/MX/TXT/CNAME/NS/SOA/PTR via direct UDP queries)_ • **`nc`** _(TCP netcat: connect, listen, port-scan)_ |
| Hashing | `md5sum` `sha1sum` `sha256sum` `sha512sum` |
| Archives | `tar` `gzip` `gunzip` `zip` `unzip` |
| Filesystem | `du` `df` |
| Paths | `basename` `dirname` `realpath` `pwd` `which` |
| System | `uname` `hostname` `whoami` `date` `env` `sleep` `getopt` |
| Control | `true` `false` `yes` `seq` |
Run `mainsail --list` for the full set with one-line descriptions, or `mainsail --help` for per-applet usage and flags.
---
## Architecture
```
mainsail/
├── __main__.py # python -m mainsail
├── cli.py # dispatch: argv[0] multi-call + subcommand modes
├── registry.py # auto-discovery of applet modules
├── usage.py # per-applet --help text
├── common.py # shared helpers: err, user_name, should_overwrite
└── applets/ # one module per applet, all implement
├── ls.py # NAME, ALIASES, HELP, main(argv) -> int
├── cat.py
└── ... # 51 modules total
```
**Four-layer flow:**
1. **Entry** — `__main__.py` or the installed `mainsail` script enters `cli.main(argv)`.
2. **Dispatch** — `cli.py` checks `argv[0]` basename for multi-call (e.g. `ls -la` when hardlinked); otherwise treats `argv[1]` as the applet name. Intercepts `--help` (long form only — `-h` is reserved for applet flags like `df -h`).
3. **Registry** — `registry.py` lazy-loads `mainsail.applets.*` once via `pkgutil.iter_modules`, registering each module's `NAME` + `ALIASES` → `main` function.
4. **Applet** — receives `argv` as a list, returns an exit code (`0` success, `1` runtime error, `2` usage error). Reads stdin via `sys.stdin.buffer` for binary safety; writes via `sys.stdout` / `sys.stdout.buffer`.
Adding a new applet means dropping a module into `mainsail/applets/` with the four-symbol contract. Auto-discovery picks it up on next launch.
---
## Development
```bash
pip install -e ".[dev]" # install with test deps
python -m pytest -q # 268 unit tests
python scripts/stress.py # 23-case stress harness
python scripts/stress.py dist/mainsail.exe --quick # against a frozen binary
```
### Building
```bash
pip install "Nuitka[onefile]"
python build.py # full Nuitka binary
python build.py --pyz # portable zipapp
python build.py --preset slim # binary, slim preset
python build.py --pyz --applets ls,cat,grep,awk # custom zipapp
python build.py --list-presets # show preset contents
```
Output is a single self-contained executable: ~4.7 MB on Windows, ~5.5 MB on Linux glibc, ~6.3 MB on Linux musl. Compressed with zstandard. No Python needed at runtime.
CI builds **ten native glibc binaries** (five full + five slim), **two zipapps** (full + slim), and **one musl-linked Linux x64 binary** on every release tag.
---
## License
MIT.