{"id":49273485,"url":"https://github.com/real-fruit-snacks/mainsail","last_synced_at":"2026-04-25T15:01:11.686Z","repository":{"id":353471686,"uuid":"1219556102","full_name":"Real-Fruit-Snacks/mainsail","owner":"Real-Fruit-Snacks","description":"Single-file BusyBox-like multi-call binary, in Python — 50 POSIX utilities, native Windows support, freezable with Nuitka","archived":false,"fork":false,"pushed_at":"2026-04-24T04:19:41.000Z","size":150,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-24T04:23:19.217Z","etag":null,"topics":["busybox","cli-tools","coreutils","cross-platform","nuitka","posix","python","single-binary","utility-toolkit","windows"],"latest_commit_sha":null,"homepage":"https://real-fruit-snacks.github.io/mainsail/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Real-Fruit-Snacks.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-24T01:56:19.000Z","updated_at":"2026-04-24T04:19:44.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Real-Fruit-Snacks/mainsail","commit_stats":null,"previous_names":["real-fruit-snacks/mainsail"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/Real-Fruit-Snacks/mainsail","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Real-Fruit-Snacks%2Fmainsail","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Real-Fruit-Snacks%2Fmainsail/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Real-Fruit-Snacks%2Fmainsail/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Real-Fruit-Snacks%2Fmainsail/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Real-Fruit-Snacks","download_url":"https://codeload.github.com/Real-Fruit-Snacks/mainsail/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Real-Fruit-Snacks%2Fmainsail/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32265977,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-25T09:15:33.318Z","status":"ssl_error","status_checked_at":"2026-04-25T09:15:31.997Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["busybox","cli-tools","coreutils","cross-platform","nuitka","posix","python","single-binary","utility-toolkit","windows"],"created_at":"2026-04-25T15:01:10.228Z","updated_at":"2026-04-25T15:01:11.669Z","avatar_url":"https://github.com/Real-Fruit-Snacks.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/Real-Fruit-Snacks/mainsail/main/docs/assets/logo-dark.svg\"\u003e\n  \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://raw.githubusercontent.com/Real-Fruit-Snacks/mainsail/main/docs/assets/logo-light.svg\"\u003e\n  \u003cimg alt=\"mainsail\" src=\"https://raw.githubusercontent.com/Real-Fruit-Snacks/mainsail/main/docs/assets/logo-dark.svg\" width=\"560\"\u003e\n\u003c/picture\u003e\n\n![Python](https://img.shields.io/badge/language-Python-3776ab.svg)\n![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20Windows%20%7C%20macOS-lightgrey)\n![Arch](https://img.shields.io/badge/arch-x86__64%20%7C%20ARM64-blue)\n![License](https://img.shields.io/badge/license-MIT-blue.svg)\n![Tests](https://img.shields.io/badge/tests-361%20passing-brightgreen.svg)\n\n**Single-file BusyBox-like multi-call binary, in Python.**\n\n**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.\n\n[Download Latest](https://github.com/Real-Fruit-Snacks/mainsail/releases/latest)\n\u0026nbsp;·\u0026nbsp;\n[GitHub Pages](https://real-fruit-snacks.github.io/mainsail/)\n\u0026nbsp;·\u0026nbsp;\n[Changelog](CHANGELOG.md)\n\n\u003c/div\u003e\n\n---\n\n## Quick Start\n\n**From a release** — no Python required:\n\n```bash\n# Linux (glibc — Ubuntu, Debian, RHEL, …)\ncurl -LO https://github.com/Real-Fruit-Snacks/mainsail/releases/latest/download/mainsail-linux-x64\nchmod +x mainsail-linux-x64\n./mainsail-linux-x64 --version\n```\n\n**From source** — Python 3.10+:\n\n```bash\ngit clone https://github.com/Real-Fruit-Snacks/mainsail.git\ncd mainsail\npip install -e .\nmainsail --list\n```\n\n---\n\n## Pre-built artifacts\n\nEvery release tag (`v0.1.x`) ships **13 artifacts** built and verified by GitHub Actions:\n\n### Native binaries\n\n| Target                            | Full                                | Slim _(no archives/hashing)_              |\n|-----------------------------------|-------------------------------------|-------------------------------------------|\n| Linux x86_64 (glibc 2.35+)        | `mainsail-linux-x64`                | `mainsail-linux-x64-slim`                 |\n| Linux ARM64 (glibc 2.39+)         | `mainsail-linux-arm64`              | `mainsail-linux-arm64-slim`               |\n| Linux x86_64 **musl** (Alpine)    | `mainsail-linux-x64-musl` ✱         | _(use the full one or build slim locally)_ |\n| Windows x86_64                    | `mainsail-windows-x64.exe`          | `mainsail-windows-x64-slim.exe`           |\n| Windows ARM64                     | `mainsail-windows-arm64.exe`        | `mainsail-windows-arm64-slim.exe`         |\n| macOS ARM64 (Apple Silicon)       | `mainsail-macos-arm64`              | `mainsail-macos-arm64-slim`               |\n\n✱ 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.\n\nDrop any binary anywhere on `PATH` and run.\n\n### Portable zipapp\n\n| Artifact            | Size    | Applets | Notes                                         |\n|---------------------|---------|---------|-----------------------------------------------|\n| `mainsail.pyz`      | ~80 KB  | 51      | runs on any host with Python 3.8+             |\n| `mainsail-slim.pyz` | ~68 KB  | 39      | smaller; same applets as the slim binaries    |\n\nUseful for ESXi (which bundles Python 3 since 7.0U3), exotic architectures, jailbroken routers, and corporate machines where installing a native binary isn't practical:\n\n```bash\nscp mainsail.pyz host:/tmp/\nssh host 'python3 /tmp/mainsail.pyz find /var/log -mtime -7'\n```\n\n### Build your own\n\nPick exactly the applets you need:\n\n```bash\npython build.py --preset slim                      # 39 applets, no archives/hashing\npython build.py --preset minimal                   # 18 applets, scripting essentials\npython build.py --applets ls,cat,grep,sed,awk      # hand-picked\npython build.py --pyz --applets ls,cat,grep,sed    # smallest zipapp (~18 KB for 5 applets)\npython build.py --list-presets                     # see what's in each preset\n```\n\nSavings 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-\u003csuffix\u003e` (with matching `.exe`/`.pyz` extension).\n\n\u003e **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.\n\u003e\n\u003e **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.\n\u003e\n\u003e **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.\n\n---\n\n## Features\n\n### One binary, seventy-three utilities\n\nEvery 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 \u003capplet\u003e` or symlink/hardlink to call the applet directly.\n\n```bash\nmainsail ls -la                          # GNU-style flags\nmainsail cat file.txt | mainsail grep -C 2 pattern\nmainsail find . -name '*.py' -size +1k -mtime -7\nmainsail seq 100 | mainsail sort -rn | mainsail head -5\n```\n\n### Native Windows\n\nNo WSL, no Cygwin, no git-bash. `mainsail.exe` runs on bare Windows and recognises Windows-native command names as aliases.\n\n```cmd\nmainsail dir .                           :: == ls\nmainsail type file.txt                   :: == cat\nmainsail copy a.txt b.txt                :: == cp\nmainsail del old.txt                     :: == rm\nmainsail where python                    :: == which\n```\n\n### Real applets, not stubs\n\nEach applet implements the common POSIX flags and edge cases.\n\n- `find` — expression tree with `-exec`, `-prune`, `-and`/`-or`, parens, size/time predicates, `-delete`\n- `sed` — `s///`, `d`, `p`, `q`, `=`, `y///`, addresses, ranges, negation, `-i` in-place edit, BRE + ERE\n- `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`)\n- `jq` — practical subset: pipes, comma, alternatives, comparison/arithmetic, object \u0026 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`)\n- `http` — `GET`/`POST`/`PUT`/`DELETE`/`HEAD`, custom headers, body literal or `@file`, `--json` shortcut, redirect-following on by default, `--fail` for HTTP errors\n- `dig` — direct UDP DNS queries: A, AAAA, MX, TXT, CNAME, NS, SOA, PTR; `+short`; reverse lookups via `-x`\n- `sort` — `-k` key fields, `-t` custom separator, `-o` output file, numeric/reverse/unique\n- `tar` — create/extract/list with gzip/bzip2/xz filters; accepts traditional (`cvfz`) and dashed (`-cvfz`) flag forms\n\n```bash\nmainsail find . -name '*.tmp' -delete\nmainsail sed -i 's/foo/bar/g' *.txt\nmainsail awk -F, '{s+=$3} END{print s/NR}' data.csv\nmainsail jq '.servers[] | select(.region == \"us\") | .name' inventory.json\nmainsail http -H 'Authorization: Bearer $TOKEN' https://api.example.com/me\nmainsail dig MX gmail.com +short\nmainsail sort -k 3,3n -t , data.csv\nmainsail tar -czf src.tar.gz src/ --exclude='*.pyc'\n```\n\n### Pipeline-grade I/O\n\nBinary-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.\n\n```bash\nmainsail find . -type f -print0 | mainsail xargs -0 mainsail sha256sum\nmainsail tail -f /var/log/app.log\nmainsail gzip -c data.bin | mainsail gunzip \u003e data.bin.copy\n```\n\n### Cross-platform integrity\n\nSame 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.\n\n---\n\n## Supported applets\n\n| Category    | Applets |\n|-------------|---------|\n| File ops    | `ls` `cp` `mv` `rm` `mkdir` `touch` `find` `chmod` `ln` `stat` `truncate` `mktemp` `dd` |\n| 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` |\n| **JSON**    | **`jq`** _(practical subset: pipes, filters, select/map/sort_by, object \u0026 array constructors, 40+ built-in functions)_ |\n| **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)_ |\n| Hashing     | `md5sum` `sha1sum` `sha256sum` `sha512sum` |\n| Archives    | `tar` `gzip` `gunzip` `zip` `unzip` |\n| Filesystem  | `du` `df` |\n| Paths       | `basename` `dirname` `realpath` `pwd` `which` |\n| System      | `uname` `hostname` `whoami` `date` `env` `sleep` `getopt` |\n| Control     | `true` `false` `yes` `seq` |\n\nRun `mainsail --list` for the full set with one-line descriptions, or `mainsail \u003capplet\u003e --help` for per-applet usage and flags.\n\n---\n\n## Architecture\n\n```\nmainsail/\n├── __main__.py      # python -m mainsail\n├── cli.py           # dispatch: argv[0] multi-call + subcommand modes\n├── registry.py      # auto-discovery of applet modules\n├── usage.py         # per-applet --help text\n├── common.py        # shared helpers: err, user_name, should_overwrite\n└── applets/         # one module per applet, all implement\n    ├── ls.py        #   NAME, ALIASES, HELP, main(argv) -\u003e int\n    ├── cat.py\n    └── ...          # 51 modules total\n```\n\n**Four-layer flow:**\n\n1. **Entry** — `__main__.py` or the installed `mainsail` script enters `cli.main(argv)`.\n2. **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`).\n3. **Registry** — `registry.py` lazy-loads `mainsail.applets.*` once via `pkgutil.iter_modules`, registering each module's `NAME` + `ALIASES` → `main` function.\n4. **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`.\n\nAdding a new applet means dropping a module into `mainsail/applets/` with the four-symbol contract. Auto-discovery picks it up on next launch.\n\n---\n\n## Development\n\n```bash\npip install -e \".[dev]\"            # install with test deps\npython -m pytest -q                # 268 unit tests\npython scripts/stress.py           # 23-case stress harness\npython scripts/stress.py dist/mainsail.exe --quick   # against a frozen binary\n```\n\n### Building\n\n```bash\npip install \"Nuitka[onefile]\"\npython build.py                                    # full Nuitka binary\npython build.py --pyz                              # portable zipapp\npython build.py --preset slim                      # binary, slim preset\npython build.py --pyz --applets ls,cat,grep,awk    # custom zipapp\npython build.py --list-presets                     # show preset contents\n```\n\nOutput 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.\n\nCI builds **ten native glibc binaries** (five full + five slim), **two zipapps** (full + slim), and **one musl-linked Linux x64 binary** on every release tag.\n\n---\n\n## License\n\nMIT.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freal-fruit-snacks%2Fmainsail","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Freal-fruit-snacks%2Fmainsail","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freal-fruit-snacks%2Fmainsail/lists"}