brainstate

Encapsul/daedalus · source

Snapshot 0 · 2026-11-04T12:47:28Z · Rust · MIT

Content fingerprintfe4dfd41b3c8de636a4df150e1791b6fc34fd42fe5710a78ed53005044c09324

GitHub description at capture

Package an app into one self-extracting Linux ELF. CLI on Linux/macOS/Windows; artifact runs on Linux.

README at capture

# daedalus

[![CI](https://img.shields.io/github/actions/workflow/status/Encapsul/daedalus/ci.yml?branch=main&label=build)](https://github.com/Encapsul/daedalus/actions)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Version](https://img.shields.io/badge/version-0.7.1-green.svg)](https://github.com/Encapsul/daedalus/releases)
[![Rust](https://img.shields.io/badge/rust-2021-orange.svg)](https://www.rust-lang.org/)
[![Platform](https://img.shields.io/badge/artifact-Linux%20%7C%20macOS%20%7C%20Windows-lightgrey.svg)]()
[![Runtimes](https://img.shields.io/badge/runtimes%20detected-20-purple.svg)](#supported-runtimes)

## What it is

daedalus packages an app into one self-extracting binary. The file carries a
launcher stub, the app, and a runtime when the build embedded one. You run that
file on the target OS — no Docker daemon, no separate runtime install for the
end user.

The CLI and the stub both build for Linux, macOS, and Windows. CI smoke-tests
`examples/hello-web` on all three (`.de` on Unix, `.exe` on Windows). Cross
targets verified in CI: `linux-x64`, `linux-arm64`, `darwin-x64`, `darwin-arm64`,
`win-x64` (`win-arm64` is accepted but not built by CI).

On every launch the stub reads the footer, checks SHA-256, extracts into the
daedalus cache (`~/.cache/daedalus/<hash>/` on Unix), then execs the entrypoint.
That extract is why the first start is slower than AppImage or pkg. One measured
pass of a 312-byte Node server
(`benchmarks/comparison/results/codespaces-ba69ea`, Node 24):

| Packager | Artifact | On disk after launch | Cold start |
|----------|----------|----------------------|------------|
| AppImage | 24.4 MiB | 24.4 MiB | 29 ms |
| pkg | 70.2 MiB | 70.2 MiB | 109 ms |
| daedalus | 44.0 MiB | 122.6 MiB | 2.1 s |
| Docker | 76.7 MiB | 76.7 MiB | 6.1 s |

daedalus warm start on that pass was 96 ms (Linux comparison bench). Docker
needs a daemon. Mounting a SquashFS without extracting is not implemented.
Details and how to reproduce:
[benchmarks/comparison](benchmarks/comparison/README.md).

Signing is on by default with a dev key generated on the build machine and
trusted there. `daedalus verify` on that same machine succeeds because of
that auto-trust. Another machine trusts the file only after `daedalus trust`
of the public key. Optional AES-256-GCM keeps its decryption key in the file
metadata so the stub can decrypt. The Ed25519 private key is not in the file.

SISR can ship a delta instead of the whole file. The "~90% smaller" number in
older notes is a synthetic 200 MiB blob with 10% of its bytes flipped. A bench
on a real pair of model weights exists and is marked `--ignored`.

Names: the command is `daedalus`. The crates.io and PyPI packages are
`daedalux` because `daedalus` was already taken. `daedalus build` writes
`app.de` unless `-o` says otherwise. `scan`, `publish`, and `registry` accept
`.de` and `.daedalus`.

## Quick start

```bash
# Install - any one of these. The shell installer checks sha256 and refuses
# to continue if checksums.txt has no matching line.
cargo install daedalux
brew install encapsul/daedalus/daedalus
pip install daedalux
curl -fsSL https://raw.githubusercontent.com/Encapsul/daedalus/main/install.sh | sh

# Package the app the CI smokes on Linux, macOS, and Windows.
# Default output name is app.de; use .exe on Windows so the OS runs it directly.
daedalus build ./examples/hello-web -o hello-web.de   # Unix
# Windows CI: daedalus.exe build examples\hello-web -o hello-web.exe

./hello-web.de    # Linux / macOS
# .\hello-web.exe # Windows
```

`cargo install daedalux` ships only the CLI; on first `daedalus build` it downloads
the matching stub for the host (or `--target`) from the GitHub release (SHA-256
verified against `checksums.txt`) and caches it under the daedalus cache
(`~/.cache/daedalus/stubs/` on Unix, `%LOCALAPPDATA%\daedalus\…` on Windows).
The Homebrew tap and PyPI package ship the CLI, stub, and crypto tools together.

## Supported runtimes

Detection order matches `Runtime` in
[`daedalus-core/src/detect.rs`](daedalus-core/src/detect.rs). Keep the two in
sync. "Detected" means the builder recognizes the project. It does not mean a
framework listed in the third column has a passing end-to-end test in CI.

Evidence, as of this tree:

- **CI smoke**: `examples/hello-web` on Linux, macOS, and Windows (stdlib HTTP). Linux also smokes `examples/bottle-web` (vendored `site-packages`, HTTP) and `examples/field-kit` (adjacent-domain modes on stdout).
- **Workspace e2e, when the toolchain exists**: Rust (`daedalus-cli/tests/rust_build.rs`, cargo is on the CI image). Java/Gradle (`java_build.rs`) skips unless `java` and Gradle ≥ 7 are installed. CI does not install them. Trust/courier chaos lives in `daedalus-cli/tests/chaos_trust.rs`.
- **Source in `examples/`, not the CI smoke**: `hello-node`, `bottle-web-pip`, `hello-zig`, `hello-dart`, `hello-lua`.
- **Scaffold**: `examples/offline-health-agri` is a standard-library script plus a `Modelfile`. It does not contain model weights. If Ollama is already running it queries `localhost:11434`. Otherwise it prints the prompt it would have sent.
- **Local model kit** (weights optional): `examples/local-model-kit` — run `./fetch_weights.sh` to pull Gemma 3 270M IT Q4_K_M (~241 MB GGUF) from Hugging Face into a gitignored `models/` path, then `daedalus build`. Without that file the artifact is not an offline LLM. Host inference smoke needs Ollama ≥0.40.5.

| Runtime | Detection | What the code looks for | Evidence |
|---------|-----------|-------------------------|----------|
| Python | `requirements.txt`, `pyproject.toml`, `Pipfile` | Django, FastAPI, Flask, Streamlit markers | CI smoke is stdlib HTTP only |
| Deno | `deno.json`, `deno.jsonc` | Fresh | Detection |
| Node.js | `package.json` | Next.js, Express, NestJS, Fastify, Hono | Example + one comparison bench |
| Electron | `package.json` with `electron` dep | Generic Electron app | Detection |
| Flutter | `pubspec.yaml` with `flutter` SDK dep | Release bundle from `lib/main.dart` | Needs the Flutter SDK on PATH. No SDK download |
| Dart | `pubspec.yaml` without the `flutter` SDK dep | AOT-compiled `bin/main.dart` | Example in repo |
| Java | `pom.xml`, `build.gradle` | Spring Boot | e2e test, skipped without java+gradle |
| Ruby | `Gemfile`, `_config.yml` | Rails, Sinatra, Jekyll | Detection |
| .NET/C# | `*.csproj` | ASP.NET | Detection |
| Rust | `Cargo.toml` | Static binary | Workspace e2e |
| Zig | `build.zig`, `build.zig.zon` | `zig build` -> static binary | Example in repo |
| Go | `go.mod` | Static binary | Detection |
| PHP | `composer.json` | Laravel, Symfony, WordPress | Detection |
| Perl | `Makefile.PL`, `cpanfile` | Mojolicious layout only | Detection |
| Lua | `*.lua`, `lua` in package.json | OpenResty, Neovim plugins | Example in repo |
| Hugo | `hugo.toml`, `config.toml` | Static sites | Detection |
| Ollama | `Modelfile`, `models/*.gguf`, `ollama` in `package.json`, or `DAEDALUS_OLLAMA=1` | `ollama serve` | Detection. Weights are whatever is in the project |
| Gemma | `Modelfile` with a Gemma `FROM`, or `models/gemma*.gguf` | `ollama run <model-id>` | Detection. Clinic has no weights; `local-model-kit` embeds them only after `fetch_weights.sh` |
| Wasm | `*.wasm` | WASI (wasmtime) | Detection |
| Binary | ELF/PE executable | Any native binary | Detection |

## Binary format

```
[stub][payload][metadata][footer]
```

- **Stub**: platform launcher prepended to the payload (Linux musl ELF, macOS Mach-O,
  Windows PE). It reads its own binary, verifies integrity, extracts the payload,
  and starts the entrypoint (`execvp` on Unix; process spawn on Windows). Linux
  musl stubs avoid host libc; isolation extras (namespaces / seccomp) are Linux-only
- **Payload**: zstd-compressed tar archive (or squashfs) of the application + runtime
- **Metadata**: JSON with runtime info, entrypoint, layers, capabilities
- **Footer**: magic `0xBEEF_CAFE`, format magic `DAE\x01`, format version, integrity SHA-256 hash

Format versions: v2 (plain), v3 (signed), v4 (encrypted), v5 (squashfs).

## CLI commands

| Command | Description |
|---------|-------------|
| `build <dir>` | Package an app directory into a `.de` file |
| `run <file>` | Execute a `.de` file |
| `inspect <file>` | Read metadata from a `.de` file |
| `scan [dir]` | Find `.de` files and display metadata |
| `sign <file>` | Sign a `.de` with an Ed25519 private key |
| `verify <file>` | Verify the signature against trusted keys |
| `keygen` | Generate an Ed25519 keypair |
| `trust <keyfile>` | Add a public key to trusted keys |
| `doctor` | Check system prerequisites |
| `clean` | Remove daedalus cache and build artifacts |
| `selftest <file>` | Test a `.de` file in an ephemeral sandbox |
| `upgrade` | Self-update the daedalus binary |
| `migrate <input> <output>` | Migrate legacy v1 binary to SISR-enabled v2 |
| `swap <binary> <layer> <file>` | Hot-swap a layer in a `.de` binary |
| `publish <file>` | Publish a `.de` file to a registry |
| `registry push|pull|list` | Push/pull/list layers in a content-addressable registry |
| `env` | Show daedalus environment info |
| `completion <shell>` | Generate shell completions |
| `man [dir]` | Generate man pages |

## Global flags

| Flag | Description |
|------|-------------|
| `--verbose` / `-v` | Enable verbose output |
| `--quiet` / `-q` | Suppress non-error output |
| `--no-color` | Disable colored output |
| `--plain` | Machine-readable output (no ANSI, no pager, no box drawing) |
| `--no-input` | Disable all interactive prompts (for CI/scripts) |
| `--json` | Output as JSON (where supported) |

## Common options

| Flag | Commands | Description |
|------|----------|-------------|
| `--target` | build | Cross-compile target. CI-verified (stub built + smoke-tested): `linux-x64`, `linux-arm64`, `darwin-x64`, `darwin-arm64`, `win-x64`. `win-arm64` is accepted but not built by CI. Any Rust triple is parsed; the short forms are conveniences. |
| `--sign --key <file>` | build | Sign the binary with Ed25519 |
| `--encrypt <keyfile>` | build | Encrypt payload with AES-256-GCM |
| `--enable-sisr --update-url <url>` | build | Enable delta updates |
| `--dry-run` | build, scan | Preview without doing anything |
| `--output` / `-o` | build, inspect, scan | Output file/directory |
| `--strict` | doctor | Exit with error if any check fails |
| `--force` | clean, sign, migrate | Skip confirmation prompts |
| `--local <dir>` | registry | Use local directory instead of HTTP registry |

## Build from source

### Prerequisites

- Rust toolchain (stable, 2021 edition): <https://rustup.rs>
- C compiler (`gcc` or `clang`)
- On Linux: `musl-tools` for static linking
  ```bash
  # Ubuntu/Debian
  sudo apt install musl-tools gcc
  rustup target add x86_64-unknown-linux-musl

  # Fedora
  sudo dnf install musl-gcc
  rustup target add x86_64-unknown-linux-musl
  ```

### Build

```bash
git clone https://github.com/Encapsul/daedalus.git
cd daedalus
cargo build --release
# Binary at target/release/daedalus
```

### Workspace

| Crate | Purpose |
|-------|---------|
| `daedalux-core` | Shared library: format, compression, detection, signing, assembly (published crates.io as `daedalux-core`) |
| `daedalus-stub` | Self-extracting launcher for Linux / macOS / Windows (not published to crates.io) |
| `daedalux` | CLI tool (same platforms; published crates.io as `daedalux`, installs the `daedalus` binary) |

## Configuration

Place a `.daedalus.toml` in your app directory. CLI flags override config file values.

```toml
[package]
version = "1.0.0"
author = "Your Name"
description = "My awesome app"

[build]
isolation = "sandbox"
seccomp = true
target = "x86_64"
no_install = false
env_file = ".env"
```

## Security

Give another machine the public key. Verification on the build machine is not
that proof: the default dev key is written into that machine's trust store
during `daedalus build`.

```bash
daedalus keygen --key-dir ./keys
daedalus build ./examples/hello-web -o hello-web.de --key ./keys/<id>.key
# on the other machine, with only the .pub file:
daedalus trust ./keys/<id>.pub
daedalus verify hello-web.de
```

`daedalus verify` with an empty trust directory fails. That is the check you
want before you hand the file to someone else.

- **Ed25519**: strict verification (`verify_strict`, ZIP-215). Signing is on
  by default. `--skip-sign` opts out. `--key` selects the key.
- **SHA-256**: the stub checks the payload hash before extraction. Release
  downloads (stub, upgrade, Homebrew, PyPI, `install.sh`) are checked against
  `checksums.txt`. Prefer the root `install.sh`. `scripts/install.sh` only
  forwards to it.
- **AES-256-GCM**: optional (`--encrypt`). The decryption material is in the
  file metadata, because the stub has to decrypt. It stops a casual `tar` of
  the payload. It does not hide the app from whoever can run the file.
- **Isolation**: on Linux, user and mount namespaces plus `pivot_root` when
  the kernel allows it. seccomp is a denylist of 18 syscalls (`ptrace`, `bpf`,
  `userfaultfd`, `mount`, module loading, `reboot`, …). Networking, file I/O,
  and `exec` stay available unless capabilities say otherwise. This is not an
  application sandbox. If seccomp cannot be installed, the launcher warns and
  continues. macOS has an App Sandbox profile path. Windows has process
  isolation. See [docs/src/security.md](docs/src/security.md).
- **`selftest`**: runs the file from a temporary directory so you can see it
  start before you keep it.
- **SISR**: a delta is applied only when its Ed25519 signature checks.
- **SBOM**: `daedalus inspect --sbom` lists the recorded contents.
- **Attestations**: `daedalus attest app.de` signs an in-toto statement
  over the SBOM and the SHA-256 of the whole file, stub included.
  `daedalus attest app.de --verify` checks that statement against the
  local trust store.

### Attestation threat model

What `daedalus attest` actually guarantees, and what it does not:

- **Covers the SBOM.** The signature is computed over a canonical envelope of
  `(file digest, SBOM)`, so changing a version string in the SBOM invalidates the
  signature. An attacker cannot substitute a false inventory.
- **Covers the whole file, stub included.** The digest is over every byte of the
  `.de`, not just payload and metadata, so a substituted launcher is detected.
  (`daedalus sign` signs `payload || metadata || footer` only; the attestation is
  deliberately broader.)
- **Strict verification.** `verify_strict` rejects small-order keys and non-canonical
  signatures per ZIP-215.
- **Offline.** Verification needs only a trusted public key. No CA, no network, no
  transparency log, no clock.
- **Not a timestamp or a transparency guarantee.** An attestation says who signed
  *what bytes*; it does not prove the key was not compromised, nor that no other
  valid signature exists for different bytes. There is no revocation list. If you
  need key lifecycle, manage keys outside the tool.

Tests that enforce the above: `signature_covers_sbom_so_tampering_breaks_verification`,
`file_digest_covers_the_stub_region`, `strict_verification_rejects_small_order_key_zip215`
in `daedalus-cli/src/commands/attest.rs`.

Trust model: distributes through the same channels auditors already trust
(crates.io, Homebrew, PyPI) rather than piping random binaries, and ships the
verify/sign/trust story built in, not as an afterthought.

## Development

```bash
# Format check
cargo fmt --check

# Lint (per-crate, see AGENTS.md)
cargo clippy -p daedalux-core --all-targets -- -D warnings
cargo clippy -p daedalus-stub --all-targets -- -D warnings
cargo clippy -p daedalux --all-targets -- -D warnings

# Tests
cargo test --workspace
```

See [CODE_STYLE.md](CODE_STYLE.md) for Rust style guidelines and [AGENTS.md](AGENTS.md) for build constraints.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md). Short version: branch from `main`, keep
the PR focused, run the verification loop, open against `main`.

Internal agent notes live in [AGENTS.md](AGENTS.md) (`CLAUDE.md` just points
there). Style detail is in [CODE_STYLE.md](CODE_STYLE.md).

## Security

Report vulnerabilities in private. See [SECURITY.md](SECURITY.md).

## License

MIT - [LICENSE](LICENSE).

## Community

- [Code of Conduct](CODE_OF_CONDUCT.md)
- [Issues](https://github.com/Encapsul/daedalus/issues)
- [Discussions](https://github.com/Encapsul/daedalus/discussions)
- `daedalus feedback --browser`