moos runs from two layouts: a source checkout (what ./moos and
./moos-tui build and exec) and a packaged install (the relocatable
tree tools/pack/pack.sh produces). One crate answers every path question
for both, so a packaged run never guesses at a checkout and a source run
never writes outside the repository.
Linux tarball (moos-<version>-linux-<arch>/):
| Path | Contents |
|---|---|
bin/moos |
desktop shell (GUI) |
bin/moos-tui |
terminal UI |
bin/moos-maker |
the daemon both UIs spawn |
libexec/tor |
bundled Tor Expert Bundle binary |
libexec/*.so* |
only the shared objects tor links — the expert bundle's pluggable_transports/ are left out (torrc policy is NoExec 1) |
libexec/monero-wallet-rpc |
Monero wallet RPC sidecar |
share/moos/webview.py |
WebKit2GTK window helper for the GUI |
share/doc/moos/ |
README.md, AGENTS.md, docs/ (CONTRACT.md, METRICS.md, OPERATIONS.md, this file), LICENSE* |
macOS ships the same pieces inside moos.app: binaries in
Contents/MacOS/, everything else in Contents/Resources/ (libexec,
moos/webview.py, doc/moos/).
Installing means unpacking that tree anywhere and running
bin/moos-tui / bin/moos. A packaged install is mainnet-only, and there is
no system-wide state: the identity, the swap wallet, the tor DataDirectory
and per-run logs live under the user's data dir.
The desktop shell (bin/moos) additionally needs the GUI runtime on the
host: python3 with PyGObject and the GTK 3 + WebKit2GTK 4.1 typelibs
(Debian: python3-gi gir1.2-gtk-3.0 gir1.2-webkit2-4.1). The artifact
ships only the webview.py helper. packaging/verify.sh prints a
warning when they are missing; bin/moos then serves the UI headless
instead of opening a window.
| What | Packaged | Source checkout |
|---|---|---|
| state root | $MOOS_STATE_DIR, else $XDG_DATA_HOME/moos, else $HOME/.local/share/moos (macOS: ~/Library/Application Support/moos) |
same, with in-place reuse of an existing checkout dir (below) |
| identity (ETH key + swap wallet) | <state>/identity/ |
<repo>/rundir-mainnet/identity/ if it exists, else <state>/identity/ |
| tor root (DataDirectory, cookie, logs) | <state>/tor/ |
<repo>/rundir-mainnet/tor/ if it exists, else <state>/tor/ |
| per-run logs | <state>/run/<network>/moos-<secs>-<pid>/ |
same |
Funds safety: an existing source identity is reused in place, never copied or split. A packaged install never reads a checkout; if it has to mint a fresh identity it says so once, and the old one is not imported silently — move it yourself:
# The state root: $MOOS_STATE_DIR when set, else $XDG_DATA_HOME/moos, else
# $HOME/.local/share/moos. macOS: "$HOME/Library/Application Support/moos".
state="${MOOS_STATE_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/moos}"
mv <old>/rundir-mainnet/identity "$state/identity" # same files, same keys
MOOS_MAINNET_IDENTITY_DIR relocates the identity for any layout (and is
what lets two instances on one box run side by side).
Every resolved path has an explicit override, checked before the bundled or checkout location:
| Variable | Chooses |
|---|---|
MOOS_INSTALL_ROOT |
<root>/bin + <root>/libexec (tarball-style tree) |
MOOS_SOURCE_ROOT |
the checkout to fall back to (debug binaries first) |
MOOS_DAEMON_BIN |
moos-maker |
MOOS_TOR_BIN |
tor |
MOOS_MONERO_BIN |
monero-wallet-rpc |
MOOS_TOR_DIR |
the tor root (a DataDirectory is single-writer) |
MOOS_STATE_DIR |
the state root (must be absolute) |
bin/moos-tui --check-runtime
prints the resolved state root and each binary with its --version, and
exits non-zero when anything is missing or cannot answer. Use it on a
packaged install before trying to swap.
The pipeline lives in packaging/; tools/pack/pack.sh only assembles.
No artifact is published: this repository has no download path, so the tree
below is for local use only. The mainnet SwapCreator address is baked
(moos_core::contract::MAINNET_SWAPCREATOR) and the launch gate refuses it
unless the code there matches the pinned runtime.
packaging/fetch.sh # download + verify sidecars (assets.lock)
packaging/build.sh # offline build, assemble, smoke-test
packaging/verify.sh dist/moos-*.tar.gz
| Script | Does |
|---|---|
fetch.sh |
downloads the Tor Expert Bundle and Monero CLI listed in assets.lock, rejects any sha256 mismatch, extracts into packaging/assets/<platform>/ (host-runnable tor is executed once) |
build.sh |
resolves packaging/assets/<platform>, then runs tools/pack/pack.sh |
docker.sh |
the same build inside the pinned rust image (MOOS_DOCKER_IMAGE, default rust:1.85-bookworm); the tag is pinned but mutable, and the distro packages it apt-installs float with Debian. git gets a safe.directory entry for the root-owned bind mount |
macos.sh |
fetches the macOS assets, builds moos.app, signs it when MOOS_CODESIGN_ID is set |
appimage.sh |
wraps an assembled Linux stage into an AppImage (appimagetool required) |
verify.sh |
runs an artifact's sidecars, --check-runtime, the packaged default (mainnet), and its README's relative links; requires all three shipped binaries to carry one clean rev; warns when the host lacks the GUI runtime (python3, GTK 3, WebKit2 4.1) |
assets.lock is the only pinned-input list: platform, component, HTTPS
URL, sha256. A version bump edits that file and re-runs verify.sh;
nothing downloads implicitly during a build.
tools/pack/pack.sh --tor-bundle <dir> expects the extracted Expert
Bundle root (the directory holding tor/tor plus its sibling libraries)
and --monero-dir a directory holding monero-wallet-rpc; both are
produced by fetch.sh. Binaries are built offline from engine/vendor
with cargo build --offline --release -p moos -p moos-gui -p moos-daemon
--bins (--no-build reuses them).
Before assembling, the script runs the bundled tor --version with only
its sibling libraries on the loader path (the exact configuration the
runtime creates with LD_LIBRARY_PATH / DYLD_LIBRARY_PATH), and after
assembling it runs --check-runtime from the staged tree. Both must pass
or nothing is written to dist/.
Before building, pack.sh re-verifies its sidecar inputs against
assets.lock: the fetch cache archive is re-hashed against the lock,
and every file the pack consumes (tor, the libraries beside it,
monero-wallet-rpc, the license texts) must be byte-identical to the
member of the same name inside that pinned archive. A mutated input tree
or a missing cache refuses the pack loudly; pack.sh never downloads.
Tarball modes are normalized — dirs 0755, files 0644, executables 0755
(the libexec objects ship executable: tor loads them as code) — so the
bundle's upstream 0700 modes never leak into an artifact, and every tar
entry is rooted (--owner=0 --group=0 --numeric-owner; bsdtar's
--uid 0 --gid 0 on macOS), so the packager's identity never appears in
the headers. Upstream license texts (LICENSE in the
Monero tarball, the bundle's when present) are copied into
share/doc/moos/; see packaging/licenses/README.md.
engine/vendor/ is build input only: a package never contains crate
sources.
No download is produced: the escrow address is the missing input, and until a v3 deployment is published there is nothing to stage. The gate below is what a source release must pass; the tree it can assemble stays local.
git status --porcelain # 1. clean tree
cd engine
cargo build --offline --bins # 2. build gate
cargo test --offline -p moos-core -p moos-daemon -p moos # 2. test gate
# 3. soak per docs/OPERATIONS.md §5 (--direction=eth-to-xmr)
cd ..
git tag v0.1.1 # 4. tag
packaging/fetch.sh # 5. sidecars
packaging/build.sh # 5. build + pack
packaging/verify.sh dist/moos-*.tar.gz # 6. revs, links, sidecars
tar --numeric-owner -tvzf dist/moos-*.tar.gz | head # 6. owners all 0/0
packaging/contract-verify.sh # 7. bytecode pins