Skip to content
whaimanPublic

About

Lightweight secure remote C/C++ compiler with end-to-end encryption for offloading builds from resource-constrained devices to powerful servers

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Errand

Build elsewhere.

┌────────────────────────────────────────┐
│ $ errand compile main.cpp              │
│ Errand: Preparing main.cpp...          │
│ Uploading and compiling...             │
│                                        │
│ Compilation successful!                │
│ Duration: 0.84s                        │
│ Artifacts saved to: dist/              │
└────────────────────────────────────────┘

Run builds on another machine without moving your development environment there. Errand transfers the source tree and build manifest to a configured build server, executes the build remotely, and brings the resulting artifacts back.

No manual SSH sessions, file synchronization, or remote development environment required.

License: AGPL v3 Issues Last Commit Stars


Why this exists

Sometimes you need to build on a machine with a different toolchain, a different target environment, or simply more resources available - without moving your development workflow there.

Without a dedicated solution, this often means setting up SSH access, transferring files manually, and maintaining a remote build environment. Errand takes a narrower approach: hand off the build, let another machine execute it, and bring the resulting artifacts back.

What started as a simple remote compilation tool has grown into a manifest-driven build client and server, with cross-compilation support, encrypted payload transport, and reproducibility checks.


Why Errand?

The name comes from Old English ǣrende, meaning a message, mission, or commission entrusted to a messenger.

Over time, the word evolved from describing formal missions and messages into its familiar everyday meaning: a small task carried out on someone else's behalf.

We chose it because of the way the tool works.

You hand off a build. Another machine carries it out. The result comes back to you.

An errand is not merely sending something away. It is entrusting a task to be completed and expecting something in return. That round-trip is the metaphor behind the name.

The client is errand. The machine carrying out the commission runs errandd.


Key Features

  • Unified package - one pip install gives you both errand (client) and errandd (server).
  • Manifest-driven builds - full control over standard, defines, flags, and link targets via build.json.
  • Cross-compilation - --target triples and --sysroot overrides for Linux/Windows/macOS targets.
  • Encrypted transport - ECDH key exchange + AES-256-GCM for the payload (see Security Model for what this does and doesn't protect against).
  • Reproducibility checks - --verify-reproducible runs two independent builds and diffs the binary hash.
  • Shell autocompletion - Bash, Zsh, Fish, PowerShell.

How it works

  1. errand compile main.cpp collects the source tree, generates a build manifest, and packs it into a tarball.
  2. The client performs an ECDH handshake with the server (authenticated by a shared auth_token), deriving a session key.
  3. The tarball is encrypted (AES-256-GCM) and uploaded.
  4. The server validates the manifest, filters compiler/linker flags against a safety allowlist, and runs the actual compiler in a subprocess (no shell).
  5. The resulting binary + logs + build info are packed, encrypted, and sent back.
  6. The client decrypts and extracts the artifacts into dist/.

Security Model

Read this before exposing the server beyond localhost.

What is protected:

  • The build payload (source code, flags, resulting binary) is encrypted in transit with AES-256-GCM, keyed via an ECDH exchange tied to your auth_token.
  • The server only executes a whitelisted set of compiler binaries (gcc, g++, clang, …), never an arbitrary path from the client.
  • Compiler and linker flags are filtered against a list of known dangerous flags (-B, -specs, -wrapper, etc.) before being passed to the subprocess.
  • Archive extraction on both ends is hardened against path traversal (Tar-slip).

What is not protected:

  • There is no TLS and no server identity verification. The ECDH handshake authenticates the session using the shared auth_token, but that token itself is sent as a bearer header during the handshake. An attacker who can position themselves on the network path (rogue AP, ARP spoofing, compromised router) can intercept the token and either read the handshake or man-in-the-middle the whole session.
  • This means "encrypted" here is not a substitute for TLS. It protects against passive network logging (e.g., a nosy ISP or a shared network snooping traffic), not against an active attacker.
  • The server has no built-in rate limiting. Don't expose it to the open internet without a reverse proxy in front that handles this.

Practical guidance:

Deployment Recommendation
Same machine / 127.0.0.1 only Fine as-is.
Same trusted LAN (client + build server) Acceptable for most people. If your Wi-Fi isn't trusted, tunnel it (see below).
Different networks / over the internet Do not expose port 4444 directly. Put it behind a TLS reverse proxy (Caddy, nginx) or a VPN/tunnel (WireGuard, Tailscale, ssh -L).

Additional hardening:

  • errandd.yaml and errand.yaml contain the auth token in plaintext - the tool sets chmod 600 on them automatically, but double-check on shared systems.
  • Run the server container as a non-root user (the provided Dockerfile.server already does this) and drop capabilities: see the Docker section.
  • This project has not undergone a formal third-party security audit. Treat it as solid for personal/trusted-network use, and apply the guidance above before anything more exposed.

Installation

Errand runs on Linux, macOS, and Windows. Both entry points - errand (client) and errandd (server) - ship as a single Python package. Install it with an isolated-environment tool such as pipx or uv; a plain pip install into the system interpreter is not a supported path: on PEP 668 systems (Arch, Debian 12+, Fedora, Homebrew's Python) pip refuses outright, and everywhere else it scatters the package and its dependencies across the global environment.

Option 1: pipx

pipx install "errande @ git+https://github.com/whaiman/errand.git"

Don't have pipx yet?

OS Command
Arch sudo pacman -S pipx
Debian/Ubuntu sudo apt install pipx
Fedora sudo dnf install pipx
macOS brew install pipx
Windows py -m pip install --user pipx (or scoop / choco)
Any OS with Python python -m pip install --user pipx (fallback if none of the above apply)

Option 2: uv

uv tool install "errande @ git+https://github.com/whaiman/errand.git"

Don't have uv yet?

OS Command
Arch sudo pacman -S uv
macOS brew install uv
Windows winget install --id=astral-sh.uv -e

Or the standalone installers, no package manager needed:

macOS / Linux

curl -LsSf https://astral.sh/uv/install.sh | sh

Windows (PowerShell)

powershell -ExecutionPolicy ByPass -c "irm <https://astral.sh/uv/install.ps1> | iex"

Option 3: run without installing (uv only)

uvx executes the tool straight from cache, no install step:

uvx --from "errande @ git+https://github.com/whaiman/errand.git" errand --help

Updating and removing

# pipx
pipx reinstall errande     # always fetches the latest commit
pipx uninstall errande

# uv
uv tool upgrade errande    # add --reinstall to force the latest commit
uv tool uninstall errande

Notes:

  • pipx and uv put errand and errandd on your PATH (~/.local/bin on Linux/macOS, %USERPROFILE%\.local\bin on Windows). If the commands are not found, run pipx ensurepath or uv tool update-shell and restart the terminal.
  • The machine running errandd needs a compiler toolchain; a client-only device does not:
    • Linux: sudo pacman -S gcc / sudo apt install build-essential / sudo dnf install gcc-c++
    • macOS: xcode-select --install (Apple clang) or brew install gcc
    • Windows: MinGW-w64 via MSYS2, or winget install LLVM.LLVM
  • Server-only machine? The Docker image ships errandd with GCC, Clang, and MinGW preinstalled - the client still has to be installed on the device you compile from.
  • Termux (the phone-client case): Termux's Python isn't externally managed, so pkg install python && pip install "errande @ git+https://github.com/whaiman/errand.git" is the normal install path there.
  • Not yet packaged for distro repositories (AUR, nixpkgs, Homebrew) - if you package it, a README PR is welcome.

To work on Errand itself, see Development.


Quickstart

1. Build server

On the machine that will do the compiling, make sure gcc, g++, or clang is installed, then:

errandd

On first run this generates errandd.yaml with a random auth token (chmod 600 applied automatically):

server:
  auth_token: <auto-generated> # copy this to the client
  host: 0.0.0.0
  port: 4444

Override host/port if needed:

errandd --host 127.0.0.1 --port 5000   # loopback only, no warning
errandd --reload                        # dev mode, auto-restart

If you bind to anything other than 127.0.0.1/localhost, errandd prints a warning reminding you that the handshake isn't TLS-authenticated. See Security Model.

2. Client

On the machine where you want to initiate builds:

# errand.yaml
client:
  endpoint: http://<server-ip>:4444
  auth_token: <paste-from-errandd.yaml>
errand compile main.cpp

Your binary lands in ./dist/.


CLI Usage

# Basic compile, platform/standard auto-detected
errand compile main.cpp

# Custom output name and standard
errand compile main.cpp -o my_app --std c++20

# Cross-compile linux -> windows
errand compile main.cpp --target x86_64-w64-mingw32

# Custom sysroot for less common targets
errand compile main.cpp --target aarch64-linux-gnu --sysroot /usr/aarch64-linux-gnu

# Compile only, don't link
errand compile main.cpp --compile-only

# Pack and inspect the payload locally without sending it
errand compile main.cpp --dry-run

# Two independent remote builds, diff the output hash
errand compile main.cpp --verify-reproducible

# Interactive setup, saved to build.json
errand compile main.cpp -i

build.json (created via errand init or -i) persists your build configuration:

{
  "compiler": "clang++",
  "standard": "c++20",
  "entry_point": "main.cpp",
  "output": "main.exe",
  "platform": "win64",
  "flags": ["-Wall", "-O3", "-static"],
  "defines": ["DEBUG=1"]
}

Docker

Dockerfile.server builds a self-contained build daemon with GCC, Clang, and MinGW preinstalled, running as a non-root user.

make docker-build

# Create your server config first (see Quickstart), then:
docker run -d \
  --name errand-server \
  --read-only \
  --tmpfs /tmp \
  --cap-drop=ALL \
  --security-opt no-new-privileges \
  --pids-limit 256 \
  --memory 2g \
  --cpus 2 \
  -p 127.0.0.1:4444:4444 \
  -v "$(pwd)/errandd.yaml:/app/errandd.yaml:ro" \
  errand-server:latest

Note the -p 127.0.0.1:4444:4444 - this publishes the port on loopback only. Put a TLS reverse proxy in front if you need to reach it from elsewhere (see Security Model).

To publish images to a registry (GHCR, Docker Hub, etc.), set up CI for it - this repository doesn't currently ship an automated build/publish workflow, so images have to be built locally with the command above until one is added.


Development

git clone https://github.com/whaiman/errand.git
cd errand
python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e ".[dev]"

(Debian/Ubuntu: if python -m venv fails, install it first - sudo apt install python3-venv.)

Already using uv? It does the same thing in one step:

uv sync --extra dev
make install         Install the unified package (-e .)
make install-dev      Install + dev tools (-e ".[dev]")
make dev-client       Dry-run compile of sample/main.cpp
make dev-server       Run server on 127.0.0.1:4444 (reload mode)
make docker-build      Build the server image
make docker-run-server Run the server container
make test             Run the test suite
make lint              ruff + black --check
make format             ruff format + fix
make clean               Remove build artifacts and caches

uv sync --extra dev reads pyproject.toml, creates .venv if it doesn't exist yet, and installs the project in editable mode together with [project.optional-dependencies].dev - the same PEP-668-safe approach as the install options above, so there's nothing extra to remember between "using Errand" and "hacking on Errand".

Don't have uv? See Option 2 above.

Repository structure

errand/
├── errand/
│   ├── core/       # crypto, config, manifest, checksum
│   ├── client/     # CLI, source collection, transport
│   └── server/     # HTTP daemon, compiler runner, job store
├── pyproject.toml
└── Makefile

Contributing

PRs welcome. For anything touching errand/core/security.py, errand/server/compiler/runner.py, or the crypto/auth path, please open an issue first to discuss before submitting - that's the code where a mistake actually matters.

  1. Fork the repo
  2. git checkout -b feature/your-thing
  3. make test && make lint
  4. Open a PR describing what changed and why

License

AGPL-3.0. Modified versions - including ones run as a network service - must also release their source. See LICENSE.

About

Lightweight secure remote C/C++ compiler with end-to-end encryption for offloading builds from resource-constrained devices to powerful servers

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages