βββββββ βββ ββββββββββββββββββββββββββββββ βββββββββββββββ
ββββββββββββ βββββββββββββββββββββββββββββββββββββββββββββββ
βββ ββββββ βββ βββ ββββββββββββββ βββββββββ ββββββββ
βββ ββββββ βββ βββ ββββββββββββββ βββββββββ ββββββββ
ββββββββββββββββββ βββ ββββββββββββββββββββββββββββββ βββ
βββββββ βββββββ βββ ββββββββββββββββββ βββββββββββ βββ
βββ βββββββ βββ ββββββββββββββ
ββββββββββββββββ βββββββββββββββ
βββ βββ ββββββ βββββββββββ βββ
βββ βββ ββββββ βββββββββββ βββ
ββββββββββββββββββββββββββββββββββ
βββ βββββββ βββββββ βββββββββββ ββ - ββββββββββββββββββββ
Outsider (OUS) is an automated source-to-archive build engine designed specifically for the Cudane Linux ecosystem (also available for GNU-based distributions). It reads declarative JSON manifests that can contain an unlimited number of package recipes, isolates execution within localized workspaces, builds whatever target you want from source, scans dependencies, packages everything cleanly into .xcs binary packages, and automatically writes per-architecture index.<arch>.json files for your own repository of packages (with auto-updating support). It detects Cesar service files, multi-service declarations, binary entries, and component tiers β writing all of this into the package metadata for MCX to consume.
ββ - ββββββββββββββββββββ
Contents
- [Architecture]
- [Manifest]
- [Packages]
- [Symlinks]
- [Metadata]
- [Consolidation]
- [Entry]
- [Core]
- [Binary Reader]
- [ELF Target Iterator]
- [Dynamic Symbol Parser]
- [Dependency Matcher]
- [Metadata Synchronization & Dependency Signature]
- [Resolution by ldd]
- [Build / Resume]
- [Starting]
- [Guide]
- [CLI]
- [Format]
- [Indexing]
- [Requirements]
- [Cudane]
- [Manualizing]
- [Lifecycle]
- [License]
- [Credits]
Architecture
Outsider is built around a single principle: declarative input, deterministic output. You provide a JSON manifest describing what to build and how, and Outsider handles the rest β fetching source code, executing builds in isolation, scanning for runtime dependencies, generating metadata, and producing compressed archives.
The codebase is split into two files:
src/main.rsβ The CLI argument parser and entry point. It parses command-line flags, reads the manifest, and iterates over each package, calling into the library.src/lib.rsβ The core engine. Contains all data structures, the fetch/build/install pipeline, metadata generation, dependency injection, license detection, hashing, archiving, component scanning, service detection, sandbox profiling, repository indexing, and the Binary Reader dependency scanner.
The engine uses anyhow for error handling with context propagation, serde for JSON serialization and deserialization, sha2 for cryptographic hashing, chrono for timestamp generation, and regex for license pattern matching. External system tools (git, curl, tar, ldd, file) are invoked via std::process::Command rather than being linked as libraries, keeping the Rust binary lightweight and delegating specialized work to mature system utilities.
Manifest
The manifest is the single input file that drives the entire build pipeline. It is deserialized into the Manifest struct:
#[[derive(Deserialize, Serialize, Clone)]]
pub struct Manifest {
pub packages: Vec<Package>,
}The
Manifeststruct is a thin wrapper around aVec<Package>. There is no limit on the number of packages β a single manifest can define one package or orchestrate an entire operating system bootstrap with hundreds of sequential recipes. Thepackagesfield is the only top-level key; everything else is expressed within each individualPackageentry.
Packages
Each element in the packages array deserializes into a Package:
#[[derive(Deserialize, Serialize, Clone)]]
pub struct Package {
pub name: String,
pub version: String,
pub source: String,
pub build_type: String,
pub build_cmd: String,
pub install_cmd: String,
pub links: Option<std::collections::HashMap<String, String>>,
pub arch: String,
pub services: Option<Vec<ServiceDecl>>,
pub components: Option<Vec<String>>,
pub binaries: Option<Vec<BinaryEntry>>,
}Every field is explained below:
The package identifier. This becomes part of the output filename (<name>-<version>.xcs) and is used as the key in the repository index. It should be a simple alphanumeric string, typically lowercase with hyphens (for example, "hello", "shared-mime-info").
The package version string. Combined with name to form the unique package identity. Versions are treated as opaque strings β no semantic versioning parsing is performed. The version is embedded in the output filename and in the metadata.
The origin of the source code. This field is processed by the fetch() function and supports multiple formats:
- Remote archive URLs:
https://example.com/releases/pkg-1.0.0.tar.gz,.tar.bz2,.tar.xzβ downloaded viacurland extracted viatar. - Git repository URLs: Any URL ending in
.gitβ cloned viagit clone --depth 1for a shallow, bandwidth-efficient checkout. - Local filesystem paths:
/home/user/projects/my-pkgor../relative/pathβ copied recursively into the workspace. file://URIs:file:///absolute/path/to/sourceβ thefile://prefix is stripped and the path is treated as a local source.
The fetch() function can also check for local existence first (before any protocol-based logic), so local paths always take precedence and avoid network access entirely.
A classifier that influences how Outsider handles the package when build_cmd is empty. Common values include:
"rust"β Triggers automaticcargo build --releasewith Cudane-specificRUSTFLAGSwhenbuild_cmdis empty."meson"β Informational; the actual build command must be provided inbuild_cmd."make"β Informational; the actual build command must be provided inbuild_cmd."custom"β Explicitly signals a custom build process;build_cmdandinstall_cmdare expected to be provided.
The build_type is also stored in the output metadata for downstream tools to reference.
The shell command to execute for building the package. The behavior depends on the content:
- If the trimmed value equals
"none","skip", or"nothing"(case-insensitive): The build step is completely skipped. No command runs, no automatic fallback occurs. - If empty (
"") andOUS_NO_AUTOis set: The build step is skipped (returns empty log). - If empty (
"") andbuild_typeis"rust": Automatic Rust build is triggered. The engine runs:
RUSTFLAGS="-C linker=clang -C link-arg=-target \
-C link-arg=x86_64-unknown-linux-musl -C link-arg=--sysroot=/system \
-C target-feature=+crt-static" \
cargo build --target x86_64-unknown-linux-musl --releaseBoth stdout and stderr are captured into a capture.log file inside the source directory. If the build succeeds, the log content is returned for dependency scanning. If it fails, the error includes the full log output.
- If empty (
"") andbuild_typeis not"rust": The build step returns an empty string (no-op). - If non-empty: The command is executed via
sh -cinside the source directory. Both stdout and stderr are captured usingteeintocapture.log, which is then read back and deleted. The log content is returned for dependency scanning.
The shell command to install built artifacts into the staging directory. The behavior depends on the content:
- If the trimmed value equals
"none","skip", or"nothing"(case-insensitive): The install step is skipped, but any symlinks declared in thelinksmap are still created. - If empty (
"") andOUS_NO_AUTOis set: The install step is skipped (symlinks still processed). - If empty (
"") andbuild_typeis"rust": Automatic Rust install is triggered. The engine copies all files fromtarget/release/inside the source directory into the package staging directory. This provides a sensible default for Rust projects where the compiled binaries are placed intarget/release/. - If empty (
"") andbuild_typeis not"rust": Falls through to execute the empty string as a command (which would do nothing), then processes symlinks. - If non-empty: The command is executed via
sh -cwith theCUDANE_DESTenvironment variable set to the package staging directory path. The command runs with the source directory as its working directory. After the command completes, any symlinks in thelinksmap are created.
The target architecture for the package. Supports multi-arch builds β common values are "amd64", "arm64", or "native" (which means build for the host architecture). When omitted from the manifest, it defaults to "native" for backward compatibility. The architecture is propagated into the package metadata and can be used by downstream tools to select the correct package variant for a given target platform.
An optional list of Cesar service declarations. When present, Outsider scans the staging directory for service files and records them in the package metadata. MCX uses this to automatically register/unregister services on install/remove.
An optional list of component tier classifications (e.g., "required", "recommended", "optional", "development"). MCX uses this for partial install/upgrade/remove operations.
An optional list of binary entries discovered in system/bin/. Each entry maps a command name to its binary path. MCX uses this for command-not-found resolution.
An optional map of symbolic links to create inside the package staging directory after installation. The map keys are the target paths (what the symlink points to) and the values are the link paths (where the symlink is placed). For example:
"links": {
"system/lib/libexample.so.1": "system/lib/libexample.so"
}This creates a symlink at pkg_root/system/lib/libexample.so that points to system/lib/libexample.so.1. The symlink() function handles the path logic: it strips leading slashes from the link path to keep it relative to the staging root, creates parent directories as needed, removes any existing file at the link location, and then creates the symlink using std::osunix::fs::symlink.
Symlinks
Although the Package struct uses a raw HashMap<String, String> for links, there is also a dedicated Symlink struct in the codebase:
#[[derive(Deserialize, Serialize, Clone)]]
pub struct Symlink {
pub target: String,
pub link: String,
}This struct is available for serialization and deserialization but is not currently used by the main pipeline β the links field in Package uses the HashMap directly. And the Symlink struct exists as a potential future expansion point for more structured symlink definitions.
Metadata
Every built package generates a metadata.json file embedded inside the archive. The structure is:
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct PackageMetadata {
pub pkg_name: String,
pub version: String,
pub license: String,
pub source: String,
pub arch: String,
pub checksum: Checksum,
pub dependencies: Vec<Dependency>,
pub files: Vec<PathBuf>,
pub provides: Option<Vec<String>>,
pub conflicts: Option<Vec<String>>,
pub services: Option<Vec<ServiceDecl>>,
pub components: Option<Vec<String>>,
pub binaries: Option<Vec<BinaryEntry>>,
}Each field is populated as follows:
pkg_name: Mirrored directly from the manifest'snamefield.version: Mirrored directly from the manifest'sversionfield.source: Mirrored directly from the manifest'ssourcefield.arch: Mirrored directly from the manifest'sarchfield. Defaults to""when absent (backward compat with older metadata).license: Determined by thelicense()function, which scans the source directory for license files and extracts the license name using regex pattern matching.build_type: Mirrored directly from the manifest'sbuild_typefield.build_date: An ISO 8601 UTC timestamp generated at runtime viachrono::Utc::now().to_rfc3339(), recording exactly when the metadata was created.checksum: A SHA-256 hex digest of the entire package staging directory, computed byhash()which pipes the directory throughtar -cf -and hashes the resulting byte stream.provides: Usinglibdepandnormalizeto list the libraries that the package provides.conflicts: Usingscanto scan for any conflicting links and list it.
Each element in the dependencies array deserializes into:
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct Dependency {
pub name: String,
pub dep_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub libraries: Option<Vec<String>>,
}name: The name of the depended-on package (or a raw library name if the library could not be mapped to any known package).dep_type: A human-readable description of how the dependency was discovered. Values include:"Build"β discovered from build log parsing (e.g.,pkg-config --libs foo,checking for foo...,dependency foo found)"Library (libfoo.so.1)"β discovered by scanning ELF binaries in the staging directory and resolving the library to a known package"Library"β a raw library that could not be mapped to any known package in the workspace"Transitive"β discovered via transitive dependency resolution against the repository index
libraries(optional): If the dependency on a single package arises from 2 or more distinct libraries (all provided by the same package), this field lists those specific library names. This is the Consolidation feature β see the [Consolidation] section.
The PartialEq derive is used by index() to compare metadata entries and avoid unnecessary updates when the content has not changed.
Consolidation
When a package depends on multiple shared libraries that are all provided by a single external package, Outsider consolidates them into a single dependency entry with a libraries field listing the specific libraries that triggered the dependency.
Consider a scenario where package my-app links against libssl.so.3 and libcrypto.so.3, both provided by the openssl package. Without consolidation, the dependency list would contain a single entry like:
{
"name": "openssl",
"dep_type": "Library (libssl.so.3) & Library (libcrypto.so.3)"
}With consolidation, when 2 or more libraries map to the same package, the entry becomes:
{
"name": "openssl",
"dep_type": "Library (libssl.so.3) & Library (libcrypto.so.3)",
"libraries": ["libcrypto.so.3", "libssl.so.3"]
}The libraries field is only present when there are at least 2 distinct libraries that resolve to the same package. If only one library maps to a package, no libraries field is emitted β keeping the JSON clean and backward compatible.
The consolidation happens in the scan() function in src/lib.rs. During dependency resolution:
- Library enumeration:
libdep()scans all ELF binaries and.sofiles in the staging directory to build a set of needed library filenames. - Package resolution: Each library name is normalized (e.g.,
libfoo.so.1β["libfoo.so.1", "libfoo.so", "foo.so.1"]) and looked up in the workspace library index (mltp()) to find which packages provide a matching library. - Consolidation:
pkg_libsβ aHashMap<String, Vec<String>>β tracks every libraryβpackage mapping. When converting the dependency map to the finalVec<Dependency>, any package with 2 or more entries inpkg_libsgets itslibrariesfield populated with the sorted, deduplicated list of depended-upon libraries.
Only the actually depended-upon libraries are listed in the libraries field β not every library that the providing package ships. This gives a precise picture of why the dependency exists.
Given a package media-player that links against libavcodec.so.60, libavformat.so.60, and libavutil.so.58 (all from ffmpeg):
{
"dependencies": [
{
"name": "ffmpeg",
"dep_type": "Library (libavcodec.so.60) & Library (libavformat.so.60) & Library (libavutil.so.58)",
"libraries": ["libavcodec.so.60", "libavformat.so.60", "libavutil.so.58"]
}
]
}Instead of three separate ffmpeg entries (one per library), a single consolidated entry is produced with the specific libraries enumerated.
Entry
The main() function in src/main.rs is the command-line interface. It uses std::env::args() to collect arguments and processes them with a peekable iterator. The function signature is:
fn main() -> Result<()>The anyhow::Result return type allows the use of the ? operator for error propagation, with any errors printed to stderr by Rust's default panic and error handling.
The parser uses a while let Some(arg) = args.next() loop with a match on each argument. It maintains two mutable strings: manifest_path and output_dir. The parsing follows these rules:
-
Flags with values (for example,
-m,-o,-j,-z,-p,-t): The flag consumes the next argument as its value. For example,-m manifest.jsonsetsmanifest_pathto"manifest.json". -
Boolean flags (for example,
-n,-f,-c,-s,-q,-d,-y,-k,-l): These set environment variables usingunsafe { env::set_var(...) }. Theunsafeblock is required becauseset_varis unsafe in the Rust standard library (it can cause data races in multi-threaded contexts, thoughOutsideris single-threaded). -
Positional arguments: Any argument that does not start with
-is treated as a positional argument. The first positional argument fillsmanifest_path, the second fillsoutput_dir. -
-h/--help: Prints the help message and exits with code 0. -
-v/--version: Prints the version string"Outsider 0.5.0"and exits with code 0. -
-a/--archive: Takes two positional arguments (staging directory and output package path) and runstardirectly to create an.xcsarchive manually. -
-x/--extract: Takes one or two positional arguments (package path or directory, and destination root). If the input is a directory, it scans for all.xcsfiles inside. Each package is extracted usingtarfirst; if that fails, it falls back totar --zstd -xforzstd -dc | tar -xf. This provides compatibility withtar.zstdarchives. -
-w/--write: Takes two positional arguments (source directory and destination directory). Generates ametadata.jsonfor the destination directory without archiving it. The package name is derived from the source directory filename. -
-i/--inspect: Takes one positional argument (path to an.xcspackage). Prints the file size, type (via thefilecommand).
After argument parsing, if both manifest_path and output_dir are non-empty, the program:
- Reads the manifest file from disk using
fs::read_to_string. - Deserializes it into a
Manifeststruct usingserde_json::from_str. - Creates the output directory with
fs::create_dir_all. - Iterates over each package in
manifest.packagesand callsprocess(pkg, &output_dir). - If
OUS_QUIETis not set, prints"OK: <path>"for each successfully built package. - If any package fails, the error is propagated with
?and the program exits immediately (fail-fast behavior).
Core
The library file contains all the data structures and functions that implement the build pipeline. Each function is designed to be independently callable, allowing for flexible composition and testing.
One of the most powerful features of Outsider is its Binary Reader β a zero-bloat dependency scanner that replaces the simplistic ldd-only approach with a sophisticated, two-phase analysis engine. Rather than including bulky disassembler libraries (like libbfd, capstone, or llvm) that would bloat the engine's binary, Outsider reads ELF binary files directly as raw byte streams and extracts meaningful dependency information from them.
The Binary Reader consists of four essential components, designed to work together like a mobile application's tightly integrated architecture:
Source function: elf(dir: &str) -> Result<Vec<PathBuf>>
The ELF Target Iterator is the entry point for the Binary Reader. It performs a recursive directory traversal, typically scanning system/bin and system/lib directories inside the package staging area.
Key design decisions:
- Magic byte validation: Rather than relying on file extensions (which scripts and non-ELF files can fake), the iterator reads the first 4 bytes of every file it encounters. ELF binaries begin with the magic bytes
\x7f E L F(0x7f 0x45 0x4c 0x46). Only files matching this signature are included in the results. - Script exclusion: Shell scripts, Python scripts, and other text-based executables begin with
#!(shebang) and are automatically filtered out by the ELF magic check. This prevents false positives from non-binary files. - Stack-based traversal: The iterator uses an explicit
Vec<PathBuf>as a stack for directory traversal rather than recursion, avoiding potential stack overflow on deeply nested directory trees. - Error tolerance: Directories that cannot be read (permission denied, broken symlinks) are silently skipped. This ensures that a single inaccessible directory does not block the entire dependency scan.
Algorithm:
1. If the root directory does not exist, return an empty vector
2. Push the root directory onto a stack
3. While the stack is not empty:
a. Pop a directory from the stack
b. Read all entries in the directory
c. For each entry:
- If it is a directory, push it onto the stack
- If it is a file, open it and read the first 4 bytes
- If the bytes match the ELF magic (0x7f, 0x45, 0x4c, 0x46), add the path to the result
4. Return the collected ELF binary paths
Source function: strings(path: &Path) -> Result<Vec<String>>
The Dynamic Symbol Parser is the core innovation of the Binary Reader. It reads an ELF binary as a raw byte stream and extracts printable ASCII strings of length >= 4 characters. This is the "zero-bloat" approach β no disassembler libraries, no heavy parsing infrastructure, just the binary's own byte content.
What this catches:
- DT_NEEDED entries from the
.dynstrsection of the ELF dynamic symbol table. These are the compile-time declared library dependencies thatlddalso shows. The library names (e.g.,libfoo.so,libbar.so.1.0.0) appear as printable strings in the.dynstrsection. dlopen()runtime calls: When a binary callsdlopen("/system/lib/libfoo.so", RTLD_NOW)ordlopen("libbar.so", RTLD_LAZY), the library path or name string is embedded in the.rodatasection of the binary.ldddoes NOT see these because they are not DT_NEEDED entries β they are runtime decisions. The Byte Stream Reader catches them because it scans all printable strings regardless of section.dlsym()patterns: Similar todlopen, any library name or path string indlsym()arguments is captured.- Embedded path strings: Strings like
/system/lib/libquux.soor/usr/lib/libextra.sothat may be embedded in configuration data or string tables.
Key design considerations:
- Minimum length filter: Only strings of length >= 4 characters are captured. This filters out noise from single-byte characters and short garbage sequences that happen to align with printable ASCII.
- Printable character set: The parser collects bytes that are ASCII graphic (alphanumeric and punctuation) plus the characters
/,.,-,_, and space. These are the characters commonly found in library paths and filenames. - Numeric noise filter: Purely numeric strings (e.g., version numbers, addresses) are filtered out, as they cannot be library names.
- Zero dependency footprint: The entire string extraction is done with Rust's standard library file I/O plus basic byte iteration. No external parsing libraries are needed.
- Streaming chunk-based reading: To handle very large binaries (hundreds of megabytes or more) without exhausting memory on constrained devices, the function reads the file in 64KB chunks rather than loading the entire file at once. A carryover buffer ensures that strings split across chunk boundaries are correctly reassembled.
Algorithm:
1. Open the binary file for reading
2. Initialize a carryover buffer (empty)
3. While not at EOF:
a. Read 64KB chunk from file
b. Combine carryover + current chunk
c. Iterate over each byte in combined buffer:
- If printable ASCII (graphic, /, ., -, _, or space):
* Append to current string buffer
- Otherwise (non-printable byte):
* If current buffer length >= 4:
- Convert to UTF-8 string
- Skip if purely numeric
- Add to results
* Clear current buffer
d. Save incomplete string (if any) to carryover for next iteration
4. Process any remaining carryover at EOF (same logic as above)
5. Return all extracted strings
Source function: bds(dirs: &[[&str]]) -> Result<Vec<String>>
The Zero-Bloat Dependency Matcher orchestrates Components 1 and 2 together. Given one or more directory paths, it:
- Calls
elf()on each directory to find all ELF binaries - For each binary found, calls
strings()to extract printable strings - Passes the strings to
lib()to extract library names - Aggregates all results into a single, sorted, deduplicated list
This function is designed to be called on multiple directories in sequence β first on system/bin (the package's own binaries), then on system/lib (any already-bundles libraries, for transitive dependency scanning).
Core system library classification function: is_core_system_lib(lib_name: &str) -> bool
Libraries that match known Cudane core prefixes are considered part of the base system and are NOT bundles into the package. This prevents unnecessary bloat while ensuring that third-party/supplemental libraries are still captured. The CORE_SYSTEM_LIBS constant contains an extensive list of known system libraries:
- C standard library:
libc.so,libm.so,libpthread.so,libdl.so,librt.so,libutil.so - C++ runtime:
libstdc++.so,libgcc_s.so,libatomic.so,libgomp.so,libquadmath.so - Sanitizers:
libasan.so,libubsan.so,liblsan.so,libtsan.so - Compression:
libz.so,libzstd.so,liblzma.so,libbz2.so - Cryptography:
libssl.so,libcrypto.so - Regex:
libpcre.so,libpcre2.so - XML, Unicode:
libexpat.so,libffi.so,libiconv.so,libintl.so - Terminal:
libncurses.so,libtinfo.so,libreadline.so,libhistory.so - Dynamic linker:
ld-linux,ld-musl,ld-musl-x86_64 - NSS:
libnss_,libnss3.so,libnssutil3.so - Security:
libselinux.so,libsepol.so,libpam.so,libcap.so - Filesystem:
libacl.so,libattr.so,libmount.so,libblkid.so,libuuid.so - JSON:
libjson-c.so - IPC:
libdbus-1.so - Graphics:
libEGL.so,libGL.so,libdrm_*.so,libX11.so,libxcb.so,libwayland-*.so - Audio:
libpulse.so,libasound.so,libsndfile.so - Fonts:
libfreetype.so,libfontconfig.so,libharfbuzz.so - Images:
libpng,libjpeg,libwebp,libtiff,libgif - Scripting:
libpython,libperl.so - Database:
libsqlite3.so - And many more...
The matching uses both prefix checks and substring-in-name checks, so libfoo.so.1.0.0 matches libfoo.so via the prefix test, and libpthread-2.33.so matches libpthread.so via a sliding window comparison.
Source function: compute(deps: &[[String]]) -> String
This component is the final piece of the Binary Reader architecture. It generates a deterministic SHA-256 hash over the sorted external dependency list. This serves as a cryptographic signature that:
- Ensures the package's dependency metadata has not been tampered with
- Allows verification that all expected bundles libraries are present at install time
- Provides a unique fingerprint that can be compared against the manifest
Algorithm:
SHA-256( join("|", sorted_deps) )Where sorted_deps is the alphabetically sorted list of all external library names discovered by the two-phase scan. The pipe character (|) is used as the delimiter because it is highly unlikely to appear in a library name.
The resulting 64-character hex string is stored in PackageMetadata.depsig.
This signature is computed during process() after the dependency resolution phase and before the metadata is written to disk. It becomes a permanent part of the package metadata, embedded inside the .xcs archive alongside metadata.json.
The true power of Outsider's dependency analysis comes from its two-phase approach:
Phase 1 (ldd β compile-time DT_NEEDED):
The traditional ldd command is still used as the first phase. It captures all compile-time declared shared library dependencies β i.e., the DT_NEEDED entries in the ELF dynamic section. These are the libraries listed in the binary's .dynamic section.
ldd works by:
- Reading the ELF binary's
.dynamicsection to findDT_NEEDEDentries - Resolving each entry name (e.g.,
libfoo.so.6) to an actual file path using the system's library search paths (/etc/ld.so.conf,LD_LIBRARY_PATH, default paths like/usr/lib,/lib) - Recursively repeating the process for each resolved library
The output is a line-by-line listing showing each library name, its resolved path, and its load address. For example:
linux-vdso.so.1 (0x00007ffd5a3e0000)
libfoo.so.6 => /usr/lib/x86_64-linux-gnu/libfoo.so.6 (0x00007f8a12340000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8a12000000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8a12400000)Phase 2 (Binary Reader β runtime dlopen/dlsym):
The Binary Reader opens each ELF binary (both executables and shared libraries) and reads it as a raw byte stream, extracting all printable strings of length >= 4 characters. This catches:
- Runtime dynamic loading: Libraries loaded via
dlopen(),dlsym(), or similar mechanisms. These are NOT visible tolddbecause they are not declared inDT_NEEDED. - Plugin architectures: Programs that dynamically discover and load plugins (e.g., libpurple protocol plugins, Apache modules, GStreamer plugins) often embed plugin library names in string tables that are readable via byte scanning.
- Configuration-embedded paths: Path strings embedded in
.rodataor configuration data that reference shared libraries. - Transitive dependencies: When scanning already-bundles libraries in
system/lib, the Binary Reader can discover dependencies of those libraries, whichlddmay not resolve if the libraries are not on the system's library path.
Why two phases are necessary:
| Aspect | ldd Only |
Binary Reader Only | Combined |
|---|---|---|---|
| Compile-time DT_NEEDED | β | β (via .dynstr) | β |
| Runtime dlopen/dlsym | β | β | β |
| Recursive transitive deps | β | β | β |
| Path resolution to real files | β | β (names only) | β |
| Cross-platform compatibility | β | β | β |
| Works with statically-linked bins | β | β (finds strings) | β |
Starting
All build systems auto-detect x86_64/aarch64 and select the correct musl target. Cross-compilation files are in env.mk, toolchain.cmake, and cross.txt (generated via gen-cross.sh).
cargo build --release
# Binary: target/release/ous
# Install:
install -Dm755 target/release/ous /system/bin/ousmake build # auto-detects arch, builds for host
make install # installs to /system/bin/ous
make install DESTDIR=/mnt # staged installmeson setup builddir --cross-file cross.txt --prefix=/system
meson compile -C builddir
meson install -C builddirninja -f build.ninja # build
ninja -f build.ninja install DESTDIR=/mnt # staged installcmake -B build -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_INSTALL_PREFIX=/system
cmake --build build
cmake --install build./target/release/ous manifest.json output
./target/release/ous manifest.json output --no-auto[!TIP] When
--no-autois passed, the program sets theOUS_NO_AUTOenvironment variable to disable the auto behaviors for emptybuild_cmdandinstall_cmdfields.
in install_cmd, type:
DESTDIR=/your/dest/pathin install_cmd, type:
DESTDIR=$CUDANE_DEST[!TIP] Why
$CUDANE_DEST? Outsider uses this variable to configure the package setup by yourbuild_cmd+install_cmdcommands, and passing the output to thearchivefunction.
[!WARNING] Ignoring or not setting a destination variable causes the engine to install the package inside the temporary working folder as the final system destination, so better for you choose one of these two options.
Guide
For Outsider packages to be truly 100% independent and benefit fully from the Binary Reader's dependency freezing, the staging directory (pkg/) must follow a strict root directory tree structure. This structure mirrors the Cudane Linux filesystem layout.
pkg/ # Package staging root
βββ metadata.json # Generated automatically (populated by Outsider)
βββ system/
β βββ bin/ # Executable binaries
β β βββ my-app # Main executable
β β βββ helper-tool # Associated utility
β β βββ ... # Any other binaries
β βββ lib/ # Shared libraries (frozen here by Binary Reader)
β β βββ libfoo.so.1.0.0 # Discovered + bundles automatically
β β βββ libbar.so.2 # Discovered + bundles automatically
β β βββ ... # Any other supplemental libs
β βββ dinit.d/ # Dinit service files (optional)
β β βββ my-app.service # Service definition
β βββ share/ # Read-only architecture-independent data
β β βββ doc/ # Documentation
β β β βββ my-app/
β β βββ man/ # Manual pages
β β β βββ man1/
β β βββ licenses/ # License files
β β βββ MIT
β βββ etc/ # Configuration files (defaults)
β β βββ my-app.conf
β βββ include/ # Header files (for -dev packages)
β βββ my-app/
β βββ my-app.h
βββ config/ # Package-specific mutable config (optional)
β βββ my-app/
βββ data/ # Package-specific mutable data (optional)
βββ my-app/-
Everything under
system/: All system-level content belongs insidesystem/. This mirrors the Cudane root filesystem where/systemis the base prefix. -
Binaries go in
system/bin/: All executables must reside insystem/bin/. The Binary Reader scans this directory for ELF binaries to analyze their dependencies. -
Libraries go in
system/lib/: All shared libraries must be placed insystem/lib/. The Binary Reader:- Copies discovered supplemental libraries into this directory.
- Also scans this directory for transitive dependency analysis (already-bundles libraries may themselves depend on other libraries).
-
.xcssub-packages go anywhere: Embedded.xcspackages are discovered recursively by thecomponents()function. -
Service files go in
system/dinit.d/: Dinit service files placed here are automatically detected and listed in the package metadata. -
Symlinks follow the same structure: The
linksfield in the manifest should reference paths relative to the given structure (e.g.,"system/lib/libexample.so.1": "system/lib/libexample.so").
The simplest way to ensure correct organization is to use install commands that target the CUDANE_DEST environment variable. For example:
{
"name": "my-app",
"version": "1.0.0",
"build_type": "make",
"build_cmd": "make -j$(nproc)",
"install_cmd": "make DESTDIR=$CUDANE_DEST install prefix=/system",
"links": {
"system/bin/my-app": "system/bin/my-app-v1"
}
}The $CUDANE_DEST environment variable is automatically set by Outsider to the absolute path of the package staging directory (pkg/). By installing with DESTDIR=$CUDANE_DEST prefix=/system, the build system places files under $CUDANE_DEST/system/, which becomes pkg/system/ β exactly matching the expected layout.
Outsider supports a fallback extraction chain for packages that may have been created with alternative compression methods:
# Primary (tar + zstd):
tar --zstd -xf <package.xcs> -C <target>
# Fallback (zstd pipe + tar):
zstd -dc <package.xcs> | tar -xf - -C <target>| Algorithm | Outsider Default? | Speed | Ratio | Best For |
|---|---|---|---|---|
| zstd | β | Fast (native) | Good | General purpose; best overall trade-off |
| gzip | β | Medium | Medium | Maximum compatibility (older systems) |
| lzo | β | Very fast | Low | Fast boot times (live systems) |
| lz4 | β | Fastest | Lowest | Very fast random access |
| xz | β | Slow | Best | Maximum compression for distribution |
The default zstd at level 3 provides roughly 2-3Γ compression on typical mixed data with near-native decompression speed, making it ideal for package distribution.
pub fn fetch(src: &str, dir: &str) -> Result<()>This function is responsible for getting source code into the workspace. It takes a source string and a destination directory path.
Logic:
-
file://prefix stripping: If the source starts withfile://, the prefix is stripped to get the actual filesystem path. This allows users to write"source": "file:///home/user/project"and have it treated as a local path. -
Local path check: The function checks if the (possibly stripped) path exists on the local filesystem using
Path::new(src_path).exists(). If it does, the source is handled locally:- If it is a directory: A recursive copy is performed using the inner
copy_dir_recursive()closure. This closure creates the destination directory, iterates over all entries in the source, and recursively copies directories or directly copies files. The closure is defined inline withinfetch()to keep the recursive logic scoped. - If it is a file: The destination directory is created, and the single file is copied into it, preserving the original filename.
- If it is a directory: A recursive copy is performed using the inner
-
Git clone: If the source ends with
.git, a shallow clone is performed:
git clone --depth 1 <src> <dir>The --depth 1 flag limits the clone to only the most recent commit, minimizing bandwidth and disk usage. If the clone fails, an error is returned.
- Remote archive download: For all other sources (assumed to be URLs pointing to compressed archives), the function:
- Determines the archive name based on the file extension:
.xzbecomestemp.tar.xz,.bz2becomestemp.tar.bz2, everything else becomestemp.tar.gz. - Downloads the file using
curl -fSL -o <archive_path> <src>. The flags mean:-f(fail silently on HTTP errors),-S(show errors),-L(follow redirects). - Extracts the archive using
tarwith the appropriate decompression flag:-xJffor.xz,-xjffor.bz2,-xzffor.gz. The--strip-components=1flag removes the top-level directory from the archive, so the contents are placed directly in the destination directory. - Deletes the downloaded archive file after extraction.
- Returns an error if either
curlortarfails.
- Determines the archive name based on the file extension:
pub fn build(pkg: &Package, dir: &str) -> Result<String>This function executes the build command for a package and returns the captured build log as a String. The return value is used by the dependency scanner (though the current codebase does not perform deep dependency scanning from logs β the log is captured for potential future use or external tooling).
Decision tree:
-
Skip keywords: If
build_cmd(trimmed) equals"none","skip", or"nothing"(case-insensitive comparison viaeq_ignore_ascii_case), the function returns an empty string immediately. No build occurs. -
Empty command with
OUS_NO_AUTO: Ifbuild_cmdis empty and theOUS_NO_AUTOenvironment variable is set, the function returns an empty string. This gives users explicit control to disable automatic behaviors. -
Empty command with
build_type == "rust": The automatic Rust build is triggered:- The
RUSTFLAGSenvironment variable is set to Cudane-specific values: linker isclang, target isx86_64-unknown-linux-musl, sysroot is/system, and static CRT is enabled. cargo build --target x86_64-unknown-linux-musl --releaseis executed in the source directory.- Both stdout and stderr are captured into a single
log_contentstring. - The log is written to
capture.login the source directory. - If the build succeeds, the log content is returned. If it fails, the error includes the full log output for debugging.
- The
-
Empty command with other build types: Returns an empty string (no-op).
-
Non-empty command: The command is executed via:
sh -c "(<command>) 2>&1 | tee capture.log"The command is wrapped in parentheses to capture all output, 2>&1 redirects stderr to stdout, and tee writes the output to capture.log while also displaying it (though in a non-interactive context, the display effect is minimal). After execution, the log file is read into memory and deleted. If the command succeeds, the log content is returned; otherwise, an error is returned.
pub fn install(pkg: &Package, src: &str, dest: &str) -> Result<()>This function installs built artifacts from the source directory into the package staging directory (dest). The dest path is the pkg subdirectory under the workspace.
Decision tree:
-
Skip keywords: If
install_cmd(trimmed) equals"none","skip", or"nothing"(case-insensitive), the install command is skipped. However, any symlinks declared inpkg.linksare still created. This allows packages to declare symlinks without running any install command. -
Empty command with
OUS_NO_AUTO: Same as above β install is skipped, symlinks are still processed. -
Empty command with
build_type == "rust": The automatic Rust install is triggered:- The function looks for
target/release/inside the source directory. - If the directory exists, it iterates over all entries.
- For each file (not directory) in
target/release/, it copies the file to the package staging directory, overwriting any existing file with the same name. - After copying, any symlinks in
pkg.linksare created.
- The function looks for
-
Non-empty command: The command is executed via:
sh -c "<install_cmd>"The CUDANE_DEST environment variable is set to the package staging directory path, and CUDANE_PREFIX is set to the package's prefix (e.g., "system", "usr"). The command runs with the source directory as its working directory (current_dir(src)). This allows install commands to reference $CUDANE_DEST as the target root and $CUDANE_PREFIX to target the correct installation prefix. After the command completes, any symlinks in pkg.links are created.
- Empty command with other build types: Falls through to execute the empty string as a command (which effectively does nothing in a shell), then processes symlinks.
pub fn symlink(target: &str, link_path: &str, root_dir: &str) -> Result<()>This function creates a symbolic link inside the package staging directory.
Logic:
-
Path sanitization: The
link_pathis stripped of any leading/usingtrim_start_matches('/'). This ensures the link path is relative to the staging root, preventing accidental absolute paths that could escape the staging directory. -
Full path construction: The full link path is constructed as
{root_dir}/{safe_link_path}. -
Parent directory creation: The parent directory of the link path is created using
fs::create_dir_all. This ensures that nested link paths (for example,system/lib/libexample.so) work even if the intermediate directories do not exist yet. -
Existing file removal: Any existing file at the link path is removed with
fs::remove_file. This prevents "File exists" errors when updating symlinks. -
Symlink creation: The symlink is created using
std::osunix::fs::symlink(target, &full_link_path). This is a Unix-specific system call that creates a symbolic link. Thetargetis stored as-is β it can be relative or absolute, depending on what the manifest specifies.
pub fn hash(dir: &str) -> Result<String>This function computes a SHA-256 hash of the entire contents of a directory. The hash is deterministic β the same directory contents always produce the same hash.
Implementation:
-
The function runs
tar -cf - -C <dir> .which creates a tar archive of the directory contents on stdout. The-C <dir>flag changes to the target directory before archiving, and.includes everything in that directory. -
The raw byte stream from
taris piped through a SHA-256 hasher (sha2::Sha256::digest()). -
The resulting 32-byte hash is converted to a 64-character hexadecimal string using
format!("{:02x}", b)for each byte.
The use of tar as an intermediate format ensures that the hash covers the complete directory structure, including file contents, permissions, and metadata that tar preserves. This provides a reliable fingerprint for verifying package integrity.
fn license(src_dir: &str) -> StringThis function attempts to automatically determine the software license of a package by scanning its source directory. It is a private function (no pub visibility) called internally by process().
Logic:
-
File name matching: The function checks for files with specific names (case-insensitive after uppercasing):
LICENSE,COPYING,LICENSE.MD,COPYING.MD,MIT-LICENSE,UNLICENSE. These are the most common license file names used in open-source projects. -
Regex pattern matching: For each matching file, the content is read and scanned against a comprehensive regex pattern:
(?i)(gnu\s+general\s+public\s+license|gpl|lgpl|agpl|apache|mit|bsd|mpl|\
mozilla\s+public\s+license|unlicense|isc)\s*(v(?:ersion)?\s*\d+(?:\.\d+)?|\
\d+[[---]]clause|\d+(?:\.\d+)?\b)?This regex captures:
- The license name (group 1): Supports GPL, LGPL, AGPL, Apache, MIT, BSD, MPL, Mozilla Public License, Unlicense, ISC, and their variations.
- The version or clause (group 2, optional): Captures version numbers (for example, v2, version 3, 2.0), clause specifications (for example, 2-clause, 3-clause), or bare version numbers.
-
Name formatting: The captured license name is formatted:
- If the name is 4 characters or fewer (for example,
MIT,GPL,BSD), it is uppercased. - Otherwise, it is converted to Title Case (each word capitalized).
- If a version is captured, it is appended: versions starting with
vare formatted asv<number>, others are appended as-is.
- If the name is 4 characters or fewer (for example,
-
Fallback to first line: If no regex match is found, the function reads the first non-empty line of the license file, strips common comment characters (
*,#,/), and if the result is non-empty and under 60 characters, returns it as the license string. -
Default: If no license files are found or none of them yield a recognizable license string, the function returns
"Unknown".
pub fn meta(meta: &PackageMetadata, dest: &str) -> Result<()>This function writes a PackageMetadata struct to disk as a JSON file named metadata.json inside the specified destination directory.
Implementation:
-
The
PackageMetadatastruct is serialized to a pretty-printed JSON string usingserde_json::to_string_pretty(meta). -
The JSON string is written to
{dest}/metadata.jsonusingfs::write.
The metadata.json file becomes part of the package archive and can be read by downstream tools (like the MCX Package Manager) to understand the package provenance, dependencies, and integrity.
pub fn index(index_root: &str, meta: &PackageMetadata) -> Result<()>This function maintains a per-architecture index of all built packages. The index is stored as index.<arch>.json in the output directory, where <arch> is the target architecture (e.g., x86_64, aarch64, or native).
Logic:
-
Index file location: The index is stored at
{index_root}/index.<arch>.json. The directory is created if it does not exist. -
Existing index loading: If the index file already exists, it is read and deserialized into a
Vec<PackageMetadata>. If deserialization fails (for example, corrupted file), an empty vector is used as a fallback. -
Entry matching: The function searches for an existing entry with the same
pkg_nameandversionas the new metadata. If found:- If the existing entry is identical to the new one (using
PartialEqcomparison), the function returns early β no update needed. - If different, the existing entry is replaced with the new metadata.
- If the existing entry is identical to the new one (using
-
New entry: If no matching entry exists, the new metadata is appended to the vector.
-
Sorting: The entries are sorted by
(pkg_name, version)using.sort_by()with a tuple comparison. This ensures deterministic ordering in the index file. -
Writing: The sorted vector is serialized to pretty-printed JSON and written to
index.<arch>.json.
This mechanism allows repository management tools to quickly discover all available packages and their versions without scanning individual .xcs files.
pub fn archive(dest: &str, out: &str) -> Result<()>This function compresses a package staging directory into an .xcs file using tar -c -C <dest> . | zstd -3 <out> with Zstandard compression.
Implementation:
-
Any existing file at the output path is removed (to prevent
tarfrom complaining about overwriting). -
The
tarcommand is invoked with:
tar -c -C <dest> . | zstd -3 <out><dest>: The package staging directory to compress.<out>: The output.xcsfile path.
- If
tarsucceeds, the function returnsOk(()). Otherwise, it returns an error.
The .xcs extension is a convention used by Cudane for .xcs Packages compressed with Zstandard. The -noappend flag ensures each build produces a clean, independent archive.
The compression level defaults to 3 but can be overridden via OUS_ZSTD_LEVEL env var or -z <NUM> flag.
pub fn sort_packages(dir: &str, arch: &str) -> Result<()>Sorts .xcs files from a flat output directory into the per-architecture pool structure pool/<arch>/<name>/. Parses package names from filenames using the pattern <name>-<version>.xcs.
Usage: ous --sort output x86_64
pub fn validate(index_path: &str, packages_dir: &str) -> Result<usize>Validates that the index file and built .xcs packages are consistent. Returns the number of problems found (0 = all checks pass).
Checks performed:
- Index is valid JSON and a flat array
- No duplicate package names
- All required fields present
- Every
.xcshas a matching index entry - Every index entry has a matching
.xcs - All
.xcsfiles are valid zstd archives (magic bytes28 B5 2F FD)
Usage: ous --validate index.x86_64.json output/
pub fn checksum_index(index_path: &str, pkg_dir: &str, base_url: &str, arch: &str) -> Result<()>Computes SHA-256 checksums for each .xcs file and updates the corresponding index entry. Simultaneously rewrites the source URL to point to the pool path: <base_url>/pool/<arch>/<name>/<name>-<version>.xcs.
Usage: ous --checksum index.x86_64.json pool/ --base-url https://raw.codeberg.org/Cudane/Repository --arch x86_64
pub fn rewrite_source(index_path: &str, base_url: &str, arch: &str) -> Result<()>Rewrites the source field in every index entry to point to the pool path without computing checksums. Useful when only URLs need updating.
Usage: ous --source index.x86_64.json --base-url https://raw.codeberg.org/Cudane/Repository --arch x86_64
pub fn sign_packages(index_path: &str, packages_dir: &str, key_id: &str) -> Result<()>Signs the index file and all .xcs packages with GPG detached ASCII-armored signatures. Also exports the public key as pubkey.asc.
Usage: ous -g index.x86_64.json pool/ --key ABCDEF1234567890
Requires gpg to be available on the system.
Outsider features a resumable build pipeline that allows it to continue from the exact byte it stopped on, provided the workspace directory (.ous/) has not been deleted.
Each package's workspace contains a state file at .ous/{package_name}/.state.json:
#[derive(Serialize, Deserialize, Clone, Default)]
struct BuildProgress {
completed_steps: HashSet<String>,
}The following step identifiers are tracked:
| Step | Description |
|---|---|
fetch |
Source code successfully fetched into src/ |
build |
Build command executed successfully |
install |
Artifacts installed into pkg/ |
hash |
Checksums computed for pkg/ |
metadata |
metadata.json written and index updated |
archive |
Final .xcs package compressed |
- When
process()starts, it checks for.state.jsonin the package workspace. - If the file exists and
OUS_CLEANis not set, the state is loaded and only steps not incompleted_stepsare executed. - After each successful step, the state file is updated atomically (written via
serde_json+fs::write). - Intermediate outputs are persisted:
- Build log β
build_log.txt - Checksums β
checksums.json
- Build log β
-f/--force(setsOUS_FORCE=1): Ignores the existing.xcsoutput file and rebuilds from scratch (or resumes from workspace state).-c/--clean(setsOUS_CLEAN=1): Deletes the entire workspace directory before starting, ensuring a completely fresh build.
If a build is interrupted (power failure, network timeout, crash) after the fetch and build steps but before archiving, running the same command again will:
- Detect the existing state file
- Skip fetch (already complete)
- Skip build (already complete)
- Re-run install β hash β metadata β archive
This avoids re-downloading source and recompiling, saving significant time on large packages.
pub fn process(pkg: &Package, out_dir: &str) -> Result<String>This is the main orchestrator function that ties together the entire build pipeline for a single package. It is called by main.rs for each package in the manifest. It features a resume/overwrite mechanism that allows builds to continue from where they left off after interruption.
Walkthrough:
-
Output directory resolution: The function gets the current working directory and joins it with the provided
out_dirto create an absolute output directory path. This ensures that all paths are absolute and unambiguous. -
Short-circuit check: The function constructs the expected output path:
{absolute_out_dir}/{name}-{version}.xcs. If this file already exists and theOUS_FORCEenvironment variable is not set, the function returns immediately with the existing path. This prevents unnecessary rebuilds of already-built packages. To force a rebuild, users pass--force(which setsOUS_FORCE=1). -
Workspace directory setup: The workspace is at
.ous/{package_name}. Inside this:src/β Where source code is fetched and built.pkg/β Where installed artifacts are staged before archiving..state.jsonβ State file tracking completed build steps (see [Build / Resume]).build_log.txtβ Persisted build log for dependency re-scanning on resume.checksums.jsonβ Persisted checksum results for resume.
-
State loading / workspace initialization:
- If
OUS_CLEANis set or no.state.jsonexists: the workspace is deleted and created fresh. - Otherwise: the existing state file is loaded, and only incomplete steps are re-executed.
- If
-
Source fetch:
fetch(&pkg.source, src_str)is called to populate thesrc/directory if thefetchstep is not marked complete. If the step was already completed (from a previous partial run), it is skipped. -
Build execution:
build(pkg, src_str)is called to compile the source if thebuildstep is not marked complete. The build log is saved tobuild_log.txtfor potential resume. On resume, if the build is complete, the log is read from disk. -
Installation:
install(pkg, src_str, root_str)is called to copy built artifacts into thepkg/staging directory if theinstallstep is not marked complete. -
Directory hashing:
hash(root_str)computes checksums (SHA-256, SHA-1, MD5) of the entire staging directory if thehashstep is not marked complete. Results are persisted tochecksums.json. -
Metadata construction:
mtd()generates thePackageMetadatastruct β which internally calls:scan()for dependency resolution (including the two-phase Binary Reader scan and the Consolidation feature)files()for file listingprovides()for library discoverylicense()for license detection
-
Metadata writing:
write(&metadata, root_str)writesmetadata.jsoninto the staging directory. -
Index update:
index()appends or updates the entry inindex.<arch>.jsonin the output root. -
Archiving:
archive(root_str, &final_path)compresses the staging directory into the final.xcsfile. -
State finalization: After archiving, all steps are marked complete in
.state.json. The next run will find the.xcsfile and short-circuit.
Python Plugin System
Outsider uses a TOML configuration file at etc/ous/p.desc to define plugins. The plugin system is completely open β any Python code is accepted. The only validation is a syntax check (python3 -c "compile(...)"). There are no restrictions on what your plugin can do.
[plugin.1]
name = "My Plugin"
path = "/path/to/plugin1.py"
ls = "ls -la"
build-all = "cargo build --release && cargo test"
custom = "some-tool --flag && another-tool"
[plugin.2]
name = "Another Plugin"
path = "~/plugins/plugin2.py"| Field | Required | Description |
|---|---|---|
name |
yes | Display name for the plugin. Does not need to match the Python file's content or filename. |
path |
yes | Path to the .py file. Supports absolute paths and ~ for home directory expansion. |
<alias> |
no | Unlimited. Any extra key is treated as an alias. The key is the alias name, the value is the shell command to execute. Supports && for chaining. Any characters are allowed in the key and value: -, /, @, #, $, %, ^, &, *, (, ), [, ], {, }, ', ", ;, :, \, ` |
- Primary: If
etc/ous/p.descexists, Outsider reads it and loads every[plugin.*]section. Each section becomes a plugin. - Fallback: If
p.descdoes not exist, Outsider scansvar/lib/ous/plugins/for any.pyfiles and loads them automatically.
Aliases are unlimited per plugin and have no naming restrictions. The key can be any string, and the value is a shell command executed via sh -c.
[plugin.tools]
name = "Toolbox"
path = "/opt/plugins/tools.py"
# Simple aliases
ls = "ls -la"
find = "find / -name"
# Chained commands (&&)
build-all = "cargo build --release && cargo test && cargo clippy"
# Complex shell pipelines
deploy = "rsync -avz ./dist/ user@host:/app/ && ssh user@host 'systemctl restart app'"
# Commands with special characters
test = "cargo test && cargo clippy"
grep-logs = "grep -r 'ERROR' /var/log/ | head -20"
backup = "tar -czf /tmp/backup-$(date +%Y%m%d).tar.gz /etc/ous/"
# Any characters work
weird-path = "/opt/my tool/bin/run.sh --config='path with spaces'"
json-tool = "python3 -c \"import json; print(json.dumps({'key': 'value'}))\""Run any alias via:
ous --plugin run <plugin-name> <alias-name>
# Example:
ous --plugin run tools build-all
ous --plugin run tools deployYour plugin can be any valid Python code. There is no required structure, no base class, no imports you must use. The only thing Outsider provides is a global variable MCX_EVENT containing JSON data about the current operation.
# /opt/ous/var/lib/ous/plugins/hello.py
import json, os
# MCX_EVENT is injected as a global variable containing event JSON
event_str = os.environ.get("MCX_EVENT", "{}")
event = json.loads(event_str)
pkg = event.get("package", "unknown")
print(f"Hello from plugin! Package: {pkg}")# /etc/ous/p.desc
[plugin.1]
name = "Hello Plugin"
path = "/opt/ous/var/lib/ous/plugins/hello.py"
hello = "echo 'Hello from alias!'"
multi = "echo step1 && echo step2 && echo step3"Or place the .py file in var/lib/ous/plugins/ and it will be auto-discovered.
ous --plugin run helloWhen Outsider fires a hook, it passes a JSON event to your plugin. The event is available as the global variable MCX_EVENT (a Python dict after json.loads()).
| Field | Type | Description |
|---|---|---|
hook |
string | The hook name (e.g., "pre-build", "post-fetch") |
package |
string or null | Package name being built |
version |
string or null | Package version |
source |
string or null | Source URL or path |
build_type |
string or null | Build type (e.g., "rust", "make", "custom") |
work_dir |
string or null | Working directory for the build |
root_dir |
string or null | Target root directory |
output_path |
string or null | Output path for archives |
arch |
string or null | Target architecture |
| Hook | When it fires |
|---|---|
pre-fetch |
Before fetching source code |
post-fetch |
After fetching source code |
pre-build |
Before building the package |
post-build |
After building the package |
pre-install |
Before installing artifacts to staging |
post-install |
After installing artifacts to staging |
pre-hash |
Before computing checksums |
post-hash |
After computing checksums |
pre-metadata |
Before generating metadata |
post-metadata |
After generating metadata |
pre-archive |
Before compressing to .xcs |
post-archive |
After compressing to .xcs |
import json, os
event = json.loads(os.environ.get("MCX_EVENT", "{}"))
hook = event.get("hook", "")
package = event.get("package", "")
version = event.get("version", "")
arch = event.get("arch", "")
if hook == "post-build":
print(f"Built {package} v{version} for {arch}")
if hook == "pre-fetch":
print(f"About to fetch source for {package}")Plugins communicate results via exit code:
- Exit
0: success - Exit non-zero: failure (stderr is captured as the error message)
If your plugin prints JSON to stdout with {"success": true, "message": "..."}, Outsider will parse it. Otherwise, stdout is treated as the message.
ous --plugin list # list all loaded plugins with their aliases
ous --plugin run <name> <alias> # run a specific alias
ous --plugin reload # re-scan plugins directory
ous --plugin reload-config # reload from p.desc TOML config- Discovery: Outsider reads
etc/ous/p.desc(or scansvar/lib/ous/plugins/). - Loading: Python syntax is validated via
python3 -c "compile(open(path).read(), path, 'exec')". If invalid, the plugin is rejected with an error. - Wiring: Each plugin is registered for all hooks. Aliases are stored for CLI invocation.
- Execution: When a hook fires, Outsider runs
python3 -c "import json, sys; MCX_EVENT = json.loads('...'); exec(open('plugin.py').read())". The plugin has full access to the Python standard library and any installed packages. - Hot-swap:
reloadandreload-configallow loading new plugins without restarting Outsider.
# /opt/ous/var/lib/ous/plugins/build_monitor.py
import json, os, subprocess, datetime
event = json.loads(os.environ.get("MCX_EVENT", "{}"))
hook = event.get("hook", "")
pkg = event.get("package", "unknown")
version = event.get("version", "")
arch = event.get("arch", "native")
# Log all build events to a file
with open("/var/log/ous-plugins.log", "a") as f:
f.write(f"[{datetime.datetime.now()}] {hook}: {pkg} v{version} ({arch})\n")
# Custom behavior per hook
if hook == "post-build":
# Run tests after every build
subprocess.run(["cargo", "test"], check=False)
elif hook == "pre-archive":
# Strip debug symbols before archiving
subprocess.run(["strip", "--strip-all", f"pkg/usr/bin/{pkg}"], check=False)
elif hook == "post-archive":
# Notify admin after successful package creation
subprocess.run(["wall", f"Package {pkg} v{version} built successfully for {arch}"])
print(json.dumps({"success": True, "message": f"Hook {hook} executed for {pkg}"}))CLI
The CLI supports the following flags, each of which sets a corresponding environment variable or triggers a specific action:
| Flag | Long Flag | Environment Variable | Action |
|---|---|---|---|
-m |
--manifest <FILE> |
--- | Path to manifest.json |
-o |
--output <DIR> |
--- | Path to output directory |
-t |
--target <ARCH> |
OUS_TARGET=<ARCH> |
Define target architecture |
-n |
--no-auto |
OUS_NO_AUTO=1 |
Disable automatic build and install behaviors |
-f |
--force |
OUS_FORCE=1 |
Overwrite existing .xcs packages |
-c |
--clean |
OUS_CLEAN=1 |
Clean workspace before building |
-l |
--parallel |
OUS_PARALLEL=1 |
Enable parallel package processing |
-j |
--jobs <NUM> |
OUS_JOBS=<NUM> |
Set number of parallel make jobs (with -l) |
-z |
--zstd-level <NUM> |
OUS_ZSTD_LEVEL=<NUM> |
Set zstd compression level for tar (default: 3) |
-s |
--strict |
OUS_STRICT=1 |
Fail immediately on dependency mapping errors |
-d |
--debug |
OUS_DEBUG=1 |
Enable verbose debug logging |
-y |
--yes |
OUS_ASSUME_YES=1 |
Assume yes to all prompts |
-k |
--keep-src |
OUS_KEEP_SRC=1 |
Do not delete source directory after build |
-p |
--project <DIR> |
OUS_PROJECT_WORKSPACE=<DIR> |
Define custom project or workspace directory |
| Flag | Long Flag | Action |
|---|---|---|
-a |
--archive <SRC> <OUT> |
Manual archive mode (staging dir, output path) |
-x |
--extract <PKG> <DEST> |
Extract package(s) into target rootfs |
-w |
--write <SRC> <DEST> |
Write metadata.json without archiving |
-i |
--inspect <PKG> |
Inspect package specifications, size, and metadata |
-b |
--hash-type <TYPE> |
Set checksum algorithm (default: sha256) |
| Flag | Long Flag | Action |
|---|---|---|
--sort <DIR> <ARCH> |
Sort .xcs files into pool/<arch>/<name>/ directories |
|
--validate <INDEX> <DIR> |
Validate index + .xcs file consistency |
|
--checksum <INDEX> <DIR> |
Add SHA-256 checksums to index and rewrite source URLs | |
--source <INDEX> |
Rewrite source URLs in index to pool paths | |
-g |
--sign <INDEX> <DIR> |
GPG sign index + all .xcs packages |
Additional flags for repository commands: --base-url <URL>, --arch <ARCH>, --key <KEYID>.
| Flag | Long Flag | Action |
|---|---|---|
-h |
--help |
Print help message and exit |
-v |
--version |
Print version and exit |
Format
The .xcs Package is a zstd Archive compressed with tar at compression level 3. Zstandard is a highly compressed archives for Linux that provides:
- Lowest Compression Sizes: With level
3. - Metadata Preservation: File permissions, ownership, and timestamps are preserved.
- Modern Storage: Zstandard is designed for modern-enough systems.
Inside every .xcs archive, there is a metadata.json file at the root that contains the PackageMetadata structure. This allows downstream tools to inspect package properties without extracting the entire archive.
Licenses
The license() function implements a heuristic scanner that works as follows:
-
File enumeration: The function reads all entries in the source directory and checks their names (uppercased) against a list of known license file names:
LICENSE,COPYING,LICENSE.MD,COPYING.MD,MIT-LICENSE,UNLICENSE. -
Content scanning: For each matching file, the content is read and scanned with a regex pattern designed to match common open-source license declarations. The regex is case-insensitive and captures:
- License names: GPL, LGPL, AGPL, Apache, MIT, BSD, MPL, Mozilla Public License, Unlicense, ISC.
- Version and clause information: Version numbers (for example,
v2,version 3), clause specifications (for example,2-clause,3-clause), or bare numbers.
-
Name normalization: The captured license name is normalized:
- Short names (4 characters or fewer) are uppercased:
mitbecomesMIT,gplbecomesGPL. - Longer names are Title Cased:
gnu general public licensebecomesGNU General Public License. - Version information is appended:
GPL v3,MIT,BSD 2-Clause.
- Short names (4 characters or fewer) are uppercased:
-
Fallback: If no regex match is found, the function reads the first non-empty line of the license file, strips comment characters (
*,#,/), and if the result is under 60 characters, returns it as-is. -
Default: If no license files are found or none yield a recognizable string,
"Unknown"is returned.
Indexing
The index() function maintains a per-architecture index of all packages built into a given output directory. The index is stored as index.<arch>.json and follows this logic:
-
Load existing index: The function reads
{index_root}/index.<arch>.jsonif it exists. If the file is missing or corrupted, an empty list is used. -
Match by identity: The function searches for an existing entry with the same
pkg_nameandversion. This is a compound key β both fields must match for an entry to be considered the same package. -
Update or append:
- If a matching entry is found and it is identical to the new metadata (using
PartialEq), no changes are made. - If a matching entry is found but different, it is replaced with the new metadata.
- If no matching entry is found, the new metadata is appended.
- If a matching entry is found and it is identical to the new metadata (using
-
Sort: The entries are sorted by
(pkg_name, version)for deterministic ordering. -
Write: The sorted list is serialized to pretty-printed JSON and written to
index.<arch>.json.
This design allows the index to be incrementally updated as new packages are built, without requiring a full rebuild of the index each time.
Multi-Architecture Pipeline
Outsider supports building packages for multiple target architectures from a single manifest. The pipeline is driven by the CUDANE_TARGETS environment variable and the included pipeline.sh script.
export CUDANE_TARGETS="x86_64-unknown-linux-musl,aarch64-unknown-linux-musl"
./pipeline.sh manifest.jsonFor each target, the pipeline:
- Sets
OUS_TARGETto the target triple, which is read by the engine's auto-build path to pass--target <triple>tocargo. - Creates
output/<arch>/for built.xcspackages. - Writes
index.<arch>.jsonwith arch-specific package metadata. - Moves packages into
pool/<arch>/<name>/for organized storage.
Outsider uses standard Rustup musl targets β no custom .json target specs required:
| Target Triple | Architecture |
|---|---|
x86_64-unknown-linux-musl |
amd64, x86-64-v3, musl |
aarch64-unknown-linux-musl |
arm64, armv8-a, musl |
Install them with rustup target add <triple>. To add a new architecture, install its target via rustup and add its triple to CUDANE_TARGETS.
Each target has a corresponding section in cargo/config.toml with target-specific rustflags:
[target.x86_64-unknown-linux-musl]
linker = "clang"
rustflags = ["-C", "target-cpu=x86-64-v3", ...]
[target.aarch64-unknown-linux-musl]
linker = "clang"
rustflags = ["-C", "target-cpu=armv8-a", ...]Package.archβ Set per-package in the manifest (defaults to"native"for backward compatibility).OUS_TARGETβ Environment variable read by the auto Rust build to determine the--targettriple.- Arch-aware workspace β Each arch gets its own workspace at
.os/<pkg>/<arch>/to avoid rebuild conflicts when building the same package for multiple architectures. - Arch-specific index β
index()writesindex.<arch>.jsonwhen the architecture is set, keeping per-arch metadata separate.
Requirements
Because Outsider delegates specialized operations to highly optimized system-level utilities, the build host must have the following tools installed and accessible in $PATH:
| Dependency | Purpose within Outsider |
|---|---|
| Rust Toolchain | Required to build the core Outsider compiler itself, alongside fallback execution of cargo for rust build-types. |
sh (POSIX Shell) |
Executing custom user-defined build and install command hooks dynamically. |
git |
Managing external source code repositories via rapid depth-restricted clones. |
curl |
Handling remote archive downloads with robust error and status tracking. |
tar |
Extracting source assets, staging layout tracking, and processing intermediate streams. |
zstd |
Compression backend used by tar |
ldd |
Phase 1 of the two-phase dependency scan: captures compile-time DT_NEEDED entries. |
file |
Used by the -i/--inspect flag to determine the file type of a package. |
Cudane
When building packages to run natively inside the Cudane landscape, compilation routines must adhere strictly to the target distribution standardized structural layouts and compiler configurations.
To protect system integrity and match Cudane custom root-directory paradigm, software components should not target conventional paths like /usr or /usr/local. Instead, specify the official base prefix explicitly:
--prefix=/systemCudane eliminates standard GCC assumptions in favor of a modern, strict LLVM and Clang foundation backed by the lightweight musl C library. Every source compilation command block initialized via a manifest must declare the tracking environment variables and direct cross-compilation target parameters:
CC=\"clang --target x86_64-unknown-linux-musl -march=x86-64-v3 -O3 -flto=full -static\" --sysroot=$DESTDIR/systemCC="clang"/CXX="clang": Defines the primary compiler engine embedded with aggressive target constraints passed as an indivisible string:--target=x86_64-unknown-linux-musl: Mandates code generation tailored strictly to the highly performantmuslC library runtime engine on standard PC architecture instead of standardglibc, passed as a single token with an equals sign (=) to prevent host flag reordering.-march=x86-64-v3: Forces the compiler to exploit advanced microarchitecture extensions (including AVX, AVX2, BMI2, and SSE4.2), doubling general-purpose registers to eliminate memory spilling and maximize raw execution speed.-O3: Activates aggressive compiler optimization loops, vectorizing mathematical operations and restructuring the binary layout for extreme runtime performance.-flto=full: Enables full Link-Time Optimization across both compile and link phases, allowing LLVM to analyze the entire codebase as a single unit to eliminate dead code and aggressively debloat dependencies.-static: Guarantees absolute static linking withmusl-libc, outputting a sovereign, self-contained binary entirely free of runtime dynamic dependencies.--sysroot=$DESTDIR/system: Injected directly inside the compiler definition string to redirect the Clang link editor and preprocessor header mechanics to the Systemfs filesystem tree (/system- alternative to/usr).
Make sure that you have been set up the $DESTDIR variable befor start building, see [Starting] section for more informations about setting the building variable.
Cudane uses standard Rustup musl targets β no custom .json target spec files needed. Install them with:
rustup target add x86_64-unknown-linux-musl
rustup target add aarch64-unknown-linux-muslThe engine reads the OUS_TARGET environment variable to determine which --target triple to pass to cargo during automatic Rust builds. Set it before invoking the engine, or use pipeline.sh with CUDANE_TARGETS for multi-arch builds.
Outsider avoids hardcoded compilation workflows or locked execution loops. By processing commands via raw standard shell execution (sh -c), developers retain absolute toolchain freedom. You can easily build software across any cross-compilation landscape, architecture, or internal system structure simply by passing custom environment flags and wrappers directly into your manifest hooks.
Manualizing
Linux from scratch
Manual Archiving
To manually create an .xcs archive from a directory without going through the full build pipeline:
ous -a /path/to/staging/dir /path/to/output_xcsThis runs tar directly with Zstandard compression level 3.
Manual Extraction
To extract an .xcs package (or multiple packages from a directory) into a target root:
ous -x /path/to/package.xcs /target/rootOr for all packages in a directory:
ous -x /path/to/packages/dir /target/rootThe extractor first tries tar (native Zstd extraction), then falls back to tar --zstd -xf or zstd -dc | tar -xf for compatibility with tar+zstd archives.
Manual POSIX method:
for f in $HOME/path/to/*.xcs; do
echo "Unpacking package: $(basename "$f")"
tar --zstd -xf "$f" -C $HOME/path/to/rootfs/ 2>/dev/null || \
zstd -dc "$f" | tar -xf - -C $HOME/path/to/rootfs/ 2>/dev/null || true
doneManual Metadata Generation
To generate a metadata.json for a directory without archiving it:
ous -w /path/to/source/dir /path/to/dest/dirThis creates a metadata.json in the destination directory with the package name derived from the source directory filename.
Manual Checksum Typing (Optional)
You can choose a checksum type for your package:
ous -g <TYPE> <MANIFEST> <OUT>supported types are: sha-256, sha-1, and md5 (Default: sha-256).
Defferences between Types
| Feature | MD5 (Message Digest 5) | SHA-1 (Secure Hash Algorithm 1) | SHA-256 (Secure Hash Algorithm 2) |
|---|---|---|---|
| Output Size (Bits) | 128 bits | 160 bits | 256 bits |
| Output Size (Hex) | 32 characters | 40 characters | 64 characters |
| Security Status | Broken / Vulnerable | Broken / Vulnerable | Secure / Industry Standard |
| Collision Resistance | Completely Broken | Cryptanalytically Broken | Extremely Strong |
| Performance Speed | Extremely Fast | Fast | Slower (More complex cycles) |
1. Differences
Output Length & Bit Strength
-
MD5 produces a 128-bit hash value. Because of the shorter length, the total space of unique hashes is
$2^{128}$ , making it mathematically easier to find duplicates. -
SHA-1 produces a 160-bit hash value (
$2^{160}$ possibilities). -
SHA-256 produces a 256-bit hash value (
$2^{256}$ possibilities). This keyspace is astronomically massive, providing modern protection against brute-force attacks.
Security & Collision Resistance
A collision occurs when two different inputs produce the exact same output hash.
- MD5: Highly vulnerable to collisions. Attackers can generate fraudulent files with matching MD5 hashes in seconds on standard hardware.
- SHA-1: Shattered by Google in 2017 (the SHAttered attack), proving that practical collisions are achievable with sufficient cloud computing power.
- SHA-256: Currently collision-resistant. No practical or theoretical collision attacks have been successfully executed against it.
Speed and Computational Complexity
- MD5 and SHA-1 require fewer bitwise operations and rounds, making them computationally cheap and fast to execute.
- SHA-256 uses a much more intensive mathematical design with 64 processing rounds, making it more CPU-intensive but far more resilient.
2. Use Cases
MD5: Fast Data Integrity Checks (Non-Security)
Rule: Never use MD5 for security, passwords, or digital signatures.
- Checksums for Legacy Pipelines: Used to verify that a file wasn't corrupted during transfer (e.g., checking if a download finished correctly over an unstable network).
- Databases & Caching: Used as a fast lookup key or hash map index where speed matters and security is irrelevant.
SHA-1: Legacy Compatibility & Git Content Tracking
Rule: Deprecated for SSL/TLS certificates and modern cryptographic chains.
- Git Version Control: Git famously uses SHA-1 not as a security feature, but as a unique fingerprint mechanism to track commits, source trees, and blobs.
- Legacy System Backwards Compatibility: Maintaining links to older embedded infrastructure that does not support modern LLVM/Clang compilation features or newer primitives.
SHA-256: The Security and Production Standard
Rule: The absolute baseline default for cryptographic infrastructure.
-
Package Management Metadata (e.g., Outsider/Cudane Linux): Used to securely anchor manifest files (
metadata.json) ensuring that packages have not been maliciously altered by a third party. - Password Hashing (with Salting): Combined with stretching algorithms (like PBKDF2 or Argon2) to store secure authentication records.
- SSL/TLS Certificates: Used globally to secure HTTP traffic (HTTPS) across the internet.
- Blockchain and Cryptocurrency: The structural foundation for Bitcoin mining block validation and transaction integrity.
Package Inspection
To inspect an .xcs package and view its metadata:
ous -i /path/to/package.xcsThis prints:
- The file path and size (in bytes and MB).
- The file type (from the
filecommand).
Lifecycle
-
Validation:
main.rsreads the JSON manifest and deserializes it into aManifeststruct. If the JSON is malformed or missing required fields, an error is returned immediately. -
Short-Circuit Check:
process()checks if the output file<name>-<version>.xcsalready exists in the output directory. If it does andOUS_FORCEis not set, the package is skipped entirely β no fetch, build, or archive operations occur. -
State Loading:
process()checks for.ous/<package_name>/.state.json. If found andOUS_CLEANis not set, the state is loaded and completed steps are skipped. IfOUS_CLEANis set or no state file exists, the workspace is created fresh. -
Source Fetching (conditional): If the
fetchstep is not marked complete in the state file,fetch()retrieves the source code intosrc/using the appropriate method (local copy, git clone, or curl+tar download). On success, the state file is updated. -
Build Execution (conditional): If the
buildstep is not marked complete,build()executes the build command in thesrc/directory. For Rust packages with emptybuild_cmd, automaticcargo build --releaseis triggered with Cudane-specific flags. The build log is persisted tobuild_log.txtfor resume. -
Installation (conditional): If the
installstep is not marked complete,install()copies built artifacts fromsrc/topkg/. For Rust packages with emptyinstall_cmd, files fromtarget/release/are automatically copied. -
Hashing (conditional): If the
hashstep is not marked complete,hash()computes checksums (SHA-256, SHA-1, MD5) of the entirepkg/directory. Results are persisted tochecksums.json. -
Dependency Resolution:
scan()performs the full dependency analysis:- Build-log parsing (
cdd()): Extracts package names from configure/meson/cmake output. - Library scanning (
libdep()): Reads ELF files and.sofilenames to find needed libraries. - Package resolution (
mltp()): Maps each library to the package that provides it, using the workspace'spkg/directories as a library index. - Consolidation: When 2+ libraries resolve to the same package, the dependency's
librariesfield is populated (see [Consolidation]). - Transitive resolution (
transitive()): Walks the repository index to find transitive dependencies.
- Build-log parsing (
-
Metadata Generation (conditional): If the
metadatastep is not marked complete:mtd()builds aPackageMetadatastruct with license, checksums, dependencies, files, and provides.write()writesmetadata.jsonto thepkg/directory.index()updatesindex.<arch>.jsonin the output root.
-
Archiving (conditional): If the
archivestep is not marked complete,archive()compresses thepkg/directory into<name>-<version>.xcsusingtarwith Zstandard compression. -
State Finalization: After archiving, all steps are marked complete in
.state.json. The next invocation will find the.xcsand skip the entire pipeline.
Development
| Profile | Command | Flags | Use case |
|---|---|---|---|
| Debug | cargo build |
β | Development iteration, fast compile |
| Release | cargo build --release |
opt-level = 3, lto = true, strip = true |
Production binary, minimised size |
| Check | cargo check |
β | Compile-only verification, no artifacts |
# Compile-only verification (fastest)
cargo check
# Debug build
cargo build
# Release build (optimised for size)
cargo build --releaseAll build systems auto-detect x86_64/aarch64 and select the correct musl target. Cross-compilation files are in env.mk, toolchain.cmake, and cross.txt (generated via gen-cross.sh).
cargo build --release
# Binary: target/release/ous
# Install:
install -Dm755 target/release/ous /system/bin/ousmake build # auto-detects arch, builds for host
make install # installs to /system/bin/ous
make install DESTDIR=/mnt # staged installmeson setup builddir --cross-file cross.txt --prefix=/system
meson compile -C builddir
meson install -C builddirninja -f build.ninja # build
ninja -f build.ninja install DESTDIR=/mnt # staged installcmake -B build -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_INSTALL_PREFIX=/system
cmake --build build
cmake --install buildmcx -i outsiderDependencies:
| Crate | Purpose |
|---|---|
| serde | Serialization framework |
| toml | TOML config file parser |
| dirs | Platform-specific config paths |
| glob | File glob pattern matching |
| libc | POSIX syscall bindings |
| serde_json | JSON serialization |
| crossterm | Terminal raw mode, events, colors |
| sha2 | Cryptographic hashing |
| chrono | Timestamp generation |
| regex | License pattern matching |
| anyhow | Error handling with context |
# Run all tests (unit + integration)
cargo test
# Run with stdout/stderr visible
cargo test -- --nocapture
# Run a specific test by name
cargo test -- test_name
# Run with all features and release mode
cargo test --release --all-features# Clippy (lint checks)
cargo clippy -- -D warnings
# Format check
cargo fmt --check
# Format in place
cargo fmt# Check for security advisories in dependencies
cargo audit# Build with debug assertions enabled in release
cargo build --profile release-debug # requires Cargo.toml profile
# Run with RUST_LOG for tracing
RUST_LOG=debug ous
# Run with backtrace on panic
RUST_BACKTRACE=1 ous
# Run under strace for syscall tracing
strace -f -o /tmp/ous.strace ./target/release/ous
# Memory profiling with valgrind
valgrind --tool=massif ./target/release/ous
ms_print massif.out.* | less# perf profiling (Linux)
perf record --call-graph dwarf ./target/release/ous
perf report
# Generate flamegraph
perf script | inferno-collapse-perf > stacks.folded
inferno-flamegraph stacks.folded > flamegraph.svg
# CPU sampling with perf stat
perf stat -e cycles,instructions,cache-misses,faults ./target/release/ous# Expected CI pipeline (GitHub Actions)
steps:
- name: Checkout
run: git checkout ${{ github.ref }}
- name: Build
run: cargo build --release
- name: Test
run: cargo test --release
- name: Lint
run: cargo clippy -- -D warnings
- name: Format
run: cargo fmt --check
- name: Audit
run: cargo audit[profile.release]
opt-level = 3 # Optimise for speed
lto = true # Link-time optimisation
strip = true # Strip symbolsCredits
[Myden]: Cudane, MCX and Outsider Founder - Made with π€ and Rust.
ββ - ββββββββββββββββββββ
Version:0.7.0.
ββ - ββββββββββββββββββββ