Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Changed

- `scripts/build-release.sh` names the archive `oremap-v<version>-<target>.tar.gz`, matching the tag and the other OpenForge tools. Assets of 0.4.3 and earlier keep their published names.

## 0.4.3 - 2026-10-26

### Fixed
Expand Down
7 changes: 7 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ cargo run -- scan --systemd-scope off --no-cron --repo-root . --format json
- Add focused tests for new parsing or classification behavior.
- Keep runtime dependencies minimal and pinned to exact versions; add one only when it removes meaningful risk or code.

## Releasing

1. Update `version` in `Cargo.toml` and add the `CHANGELOG.md` entry.
2. Run the development checks above.
3. Tag `vX.Y.Z`, run `./scripts/build-release.sh`, and attach `dist/oremap-vX.Y.Z-<target>.tar.gz` and its `.sha256` to the GitHub release.
4. Update `VER=`, the `PKG=` name and `--tag` in the README Install section, and check that the snippet downloads and verifies.

## Pull Requests

Open a PR with:
Expand Down
218 changes: 106 additions & 112 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,167 +2,157 @@

> Build a read-only keep/retire matrix for local automation surfaces.

[![CI](https://github.com/GreyforgeLabs/oremap/actions/workflows/ci.yml/badge.svg)](https://github.com/GreyforgeLabs/oremap/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/GreyforgeLabs/oremap)](https://github.com/GreyforgeLabs/oremap/releases/latest)
[![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL--3.0-blue.svg)](LICENSE)
[![Rust 1.88+](https://img.shields.io/badge/Rust-1.88%2B-orange.svg)](https://www.rust-lang.org)

<p align="center">
<img src="docs/assets/openforge-oremap.webp" alt="oremap OpenForge project artwork" width="720">
</p>

## Greyforge Citation
A machine that has been in service for a year carries systemd timers nobody remembers enabling, a crontab line that calls a script in a repository you thought was archived, and a `~/bin` full of wrappers. oremap walks those surfaces and writes one table: what exists, when its source last changed, and whether it looks like something to keep, retire or ask about. It only reads. Nothing is started, stopped, enabled or deleted, and the retire decision stays with you.

`oremap` is an OpenForge utility from [Greyforge Labs](https://greyforge.tech). Use [OpenForge](https://greyforge.tech/openforge), the [oremap Chronicle](https://greyforge.tech/chronicles/oremap-runtime-inventory), and [Greyforge llms.txt](https://greyforge.tech/llms.txt) as the canonical public context for citation and model retrieval.

## Why This Exists
<p align="center">
<img src="docs/assets/oremap-terminal.svg" alt="oremap scan over a source folder and a wrapper directory, printing a summary and a keep/retire matrix with one keep candidate, two retire candidates and two items to review">
</p>

Small systems accumulate systemd units, cron entries, wrapper scripts, local repositories, and environment files faster than operators can remember who owns them. `oremap` inventories those surfaces without starting, stopping, enabling, or deleting anything, then produces a review matrix. It never retires an item itself.
## Install

## Quick Start
Linux x86_64, prebuilt (no Rust toolchain needed):

```bash
git clone https://github.com/GreyforgeLabs/oremap.git
cd oremap
./scripts/setup.sh
VER=0.4.3
PKG=oremap-$VER-x86_64-unknown-linux-gnu
curl -fsSLO https://github.com/GreyforgeLabs/oremap/releases/download/v$VER/$PKG.tar.gz
curl -fsSLO https://github.com/GreyforgeLabs/oremap/releases/download/v$VER/$PKG.tar.gz.sha256
sha256sum -c $PKG.tar.gz.sha256
tar xzf $PKG.tar.gz
install -Dm755 $PKG/oremap ~/.local/bin/oremap
oremap --version
```

Install the binary from a checkout with Cargo:
From source, with Rust 1.88 or newer:

```bash
cargo install --path .
cargo install --locked --git https://github.com/GreyforgeLabs/oremap --tag v0.4.3
```

Or build it in place; the binary is written to `target/release/oremap`:
If the shell answers `oremap: command not found`, add `~/.local/bin` (or `~/.cargo/bin` for `cargo install`) to your `PATH`.

## Quick start

Scan the usual places and read the matrix:

```bash
cargo build --release --locked
oremap scan
```

To build a release archive with a SHA-256 checksum in `dist/`:
With no options oremap looks at your user systemd units, your crontab, wrapper scripts in `~/bin` and `~/.local/bin`, Git repositories under the current directory, and env-file metadata. Point it somewhere else, or teach it your naming, with a few flags:

```bash
./scripts/build-release.sh
oremap scan \
--repo-root ~/work \
--wrapper-dir ~/bin \
--keep-keyword invoice \
--retire-keyword legacy
```

Version 0.4.0 is a Rust rewrite of the 0.3.x Python tool, and the first
release under the name `oremap` (0.3.x and earlier were released as
`service-cartographer`). It keeps the same command line, report formats,
privacy rules, and exit codes; only the program name and the Markdown report
title change, and JSON reports keep the
`service-cartographer.report.v1` schema identifier. On the
reference host it starts in about 2 ms instead of about 57 ms, and a full
default scan takes 137 ms instead of 266 ms. When no external tools run, the
scanner's own peak memory drops from about 18 MB to about 3 MB. See
[docs/benchmarks.md](docs/benchmarks.md) for the commands and numbers.

The old command name still works for this release: `cargo install` also
installs a deprecated `service-cartographer` alias that prints a one-line
notice to stderr and then behaves exactly like `oremap`. Update scripts and
cron entries to `oremap`; the alias will be removed in the next release.

## Usage
Save a report for a script or a spreadsheet:

```bash
# Markdown keep/retire matrix for the current checkout
oremap scan

# JSON report for automation
oremap scan --format json --output report.json
oremap scan --format csv --output report.csv
```

## Usage

```bash
# Focus on a few workflow names across services, wrappers, repos, and env files
oremap scan \
--repo-root ~/work \
--wrapper-dir ~/bin \
--focus worker \
--focus ingest \
--format markdown
oremap scan --repo-root ~/work --wrapper-dir ~/bin --focus worker --focus ingest

# Avoid systemd and cron when scanning a fixture or CI workspace
oremap scan --systemd-scope off --no-cron --repo-root .

# Only one kind of item
oremap scan --kind repository --repo-root ~/src
```

CSV output is spreadsheet-safe by default: string cells beginning with `=`, `+`,
`-`, or `@`, including after leading whitespace, are prefixed so spreadsheet
imports treat them as text. Use JSON when exact machine values are required.

External inventory commands are selected only from the reviewed absolute-path
allowlist. Each collected item records the executable path and bounded version
diagnostic. Subprocesses receive a minimal environment; `systemctl --user`
also receives the current user bus address and runtime directory so it can
query the live user manager, and `git` runs with `GIT_OPTIONAL_LOCKS=0` and
`-c core.fsmonitor=` so `git status` never rewrites a repository index or
starts a file-system monitor daemon. Filter drivers configured for a
scanned repository (for example `git lfs`, or a `clean` command in a crafted
`.git/config`) are switched off for its `git status`, and so are the drivers
configured in every populated submodule that `git status` checks, at any
depth. Submodules are found from the gitlinks in the index, with or without a
`.gitmodules` entry; a repository whose index lists no gitlink costs no extra
`git` call. A submodule whose `.git` is a symbolic link or leads outside the
scanned repository, or more than 8 levels of nesting or 512 submodules, is
not followed: the repository is reported `unknown` with a warning and its
`git status` is not run. `git log` never checks commit signatures, so a
repository's `log.showSignature` cannot make it run `gpg.program`, and
`GIT_NO_LAZY_FETCH=1` stops a partial clone from fetching missing objects
through its configured remote. External commands read standard input from
`/dev/null`. A caller-controlled `PATH` cannot replace `git`, `systemctl`, or
`crontab`.

Every JSON report includes `complete`. A failed or partial requested collector
sets it to `false`, records a warning, and makes the command exit 1. Check both
the exit code and warnings before using a report to make decisions. `--output`
writes the report through a temporary file and publishes it with private `0600`
permissions, including when replacing an older report.

Environment-file reads are capped at 64 KiB and 1,000 lines. Symlinked env files
are skipped, and disappearance or permission races are recorded as warnings so
unrelated inventory can still complete. Git worktree files and bare repositories
are recognized without following `.git` marker symlinks.

## Example Matrix

| Action | Kind | Name | Status | Path | Reason |
|---|---|---|---|---|---|
| `keep_candidate` | `systemd_unit` | `backup.timer` | `active=active, enabled=enabled` | `~/.config/systemd/user/backup.timer` | active or enabled |
| `review` | `cron_job` | `user-crontab:4` | `status=present` | | scheduled command needs owner decision |
| `review` | `wrapper` | `old-sync` | `status=executable` | `~/bin/old-sync` | source timestamp is 173 days old; verify actual use |

File modification time and last commit time are source-change timestamps, not
proof of last execution or use. They only prompt review. Retirement hints
require an explicit `--retire-keyword`; active or enabled units and dirty
repositories take precedence over those hints.

## What It Scans

- User or system systemd units through unit files and `systemctl` read-only listing commands.
- Current user's crontab through `crontab -l`.
- Executable wrappers from explicit directories, defaulting to `~/bin` and `~/.local/bin` when present.
- Git repositories under requested roots with branch, dirty count, and last commit timestamp. Bare repositories are listed with status `bare`.
- Environment-like files by metadata only. Values are never emitted.

## Privacy Defaults

- Hostname is redacted unless `--show-hostname` is passed.
- Home paths are shortened to `~` unless `--absolute-paths` is passed.
`oremap scan --help` lists every option. [docs/usage.md](docs/usage.md) has more examples.

**Reading the matrix.** Active or enabled units and dirty repositories are keep candidates. A retire candidate needs an explicit `--retire-keyword` match, and keep signals win over it. An old timestamp alone only asks for review: file modification and last-commit times show when the source changed, not when it last ran.

**Reports.** Every JSON report includes `complete`. If a requested collector fails or returns a partial result, `complete` is `false`, a warning is recorded and the command exits 1, so check both before acting on a report. `--output` writes through a temporary file and publishes the report with `0600` permissions. CSV is spreadsheet-safe: string cells that begin with `=`, `+`, `-` or `@` (after any leading whitespace) are prefixed so imports treat them as text. Use JSON when you need the exact values.

## What it scans

- User or system systemd units, through unit files and read-only `systemctl` listings.
- The current user's crontab, through `crontab -l`.
- Executable wrappers in the directories you name, defaulting to `~/bin` and `~/.local/bin` when present.
- Git repositories under the requested roots, with branch, dirty count and last commit time. Bare repositories are listed with status `bare`.
- Environment-like files, by metadata only. Values are never emitted.

## Privacy defaults

- The hostname is redacted unless you pass `--show-hostname`.
- Home paths are shortened to `~` unless you pass `--absolute-paths`.
- Cron command previews redact obvious inline secret assignments and the standard input that follows an unescaped `%`.
- Env files report variable counts by default, not values or names. `--include-env-names` adds names only; lines inside multi-line quoted values are never read as names.
- Env files report variable counts by default, not names or values. `--include-env-names` adds names only; lines inside multi-line quoted values are never read as names.

## Safety

oremap runs `git`, `systemctl` and `crontab` on whatever it finds, including repositories you did not write. It treats them accordingly:

## Exit Codes
- External commands come only from a reviewed allowlist of absolute paths, so a caller-controlled `PATH` cannot swap in another `git`, `systemctl` or `crontab`. Each item records the executable path and a bounded version diagnostic.
- Subprocesses get a minimal environment and read standard input from `/dev/null`. `systemctl --user` also gets the user bus address and runtime directory so it can query the live user manager.
- `git` runs with `GIT_OPTIONAL_LOCKS=0` and `-c core.fsmonitor=`, so `git status` never rewrites an index or starts a file-system monitor daemon.
- Filter drivers (`git lfs`, or a `clean` command planted in a `.git/config`) are switched off for `git status`, in the scanned repository and in every populated submodule it checks, at any depth. Submodules are found from the gitlinks in the index, with or without `.gitmodules`; a repository with no gitlinks costs no extra `git` call.
- A submodule whose `.git` is a symbolic link or points outside the scanned repository is not followed, and neither is nesting deeper than 8 levels or more than 512 submodules. The repository is then reported `unknown` with a warning, and its `git status` does not run.
- `git log` never checks commit signatures, so `log.showSignature` cannot make it run `gpg.program`. `GIT_NO_LAZY_FETCH=1` stops a partial clone from fetching missing objects through its remote.
- Env-file reads stop at 64 KiB and 1,000 lines. Symlinked env files are skipped, and files that vanish or change permissions mid-scan become warnings, so the rest of the inventory still completes. Git worktree files and bare repositories are recognized without following `.git` marker symlinks.

## Exit codes

| Code | Meaning |
|------|---------|
| 0 | All requested collectors completed without warnings |
| 1 | Report is incomplete; inspect `warnings` |
| 2 | Usage error |

## Renamed from service-cartographer

Releases up to 0.3.x shipped as the Python tool `service-cartographer`. 0.4.0 is a Rust rewrite under the name oremap, with the same command line, report formats, privacy rules and exit codes. Only the program name and the Markdown report title changed; JSON reports keep the `service-cartographer.report.v1` schema identifier, so existing parsers keep working.

The rewrite starts in about 2 ms instead of about 57 ms, and a full default scan takes 137 ms instead of 266 ms. When no external tools run, the scanner's own peak memory drops from about 18 MB to about 3 MB. [docs/benchmarks.md](docs/benchmarks.md) has the commands and numbers.

The old command still works. The release tarball and `cargo install` both include a `service-cartographer` alias that prints a one-line notice to stderr and then behaves exactly like `oremap`. With the prebuilt tarball, install it next to `oremap` with `install -Dm755 $PKG/service-cartographer ~/.local/bin/service-cartographer`. Point scripts and cron entries at `oremap` when convenient.

## Requirements

- Rust 1.88+ to build from source (no runtime interpreter needed)
- Linux for systemd and cron collectors
- One small runtime crate (`rustix`, for `access(2)`, `uname(2)`, and the terminal width)
- Linux for the systemd and cron collectors.
- Rust 1.88+ only when building from source. The binary needs no runtime interpreter and has one small runtime crate (`rustix`, for `access(2)`, `uname(2)` and the terminal width).

## Uninstall

## Documentation
```bash
rm -f ~/.local/bin/oremap ~/.local/bin/service-cartographer # prebuilt install
cargo uninstall oremap # cargo install (removes both binaries)
```

oremap keeps no state of its own. Reports you wrote with `--output` stay where you put them.

## Development

```bash
git clone https://github.com/GreyforgeLabs/oremap.git
cd oremap
./scripts/setup.sh # contributor setup: checks the toolchain, builds, runs the tests
cargo install --path . # install your checkout
./scripts/build-release.sh # reproducible release tarball and SHA-256 checksum in dist/
```

- [STARTHERE.md](STARTHERE.md) - coding assistant bootstrap
- [CONTRIBUTING.md](CONTRIBUTING.md) - contribution workflow
- [CONTRIBUTING.md](CONTRIBUTING.md) - contribution workflow and release checklist
- [CHANGELOG.md](CHANGELOG.md) - version history
- [docs/usage.md](docs/usage.md) - usage notes
- [docs/benchmarks.md](docs/benchmarks.md) - Python vs Rust, and 0.4.0 vs 0.4.1 measurements
Expand All @@ -174,4 +164,8 @@ AGPL-3.0. See [LICENSE](LICENSE) for details.

---

Built by [Greyforge](https://greyforge.tech)
<p align="center">
<b>Useful?</b> <a href="https://github.com/GreyforgeLabs/oremap">Star oremap on GitHub</a> · <a href="https://x.com/GreyforgeLabs">Follow @GreyforgeLabs</a> · <a href="https://greyforge.tech/open-source">More OpenForge tools at greyforge.tech</a>
</p>

<p align="center"><sub>Background: the <a href="https://greyforge.tech/chronicles/oremap-runtime-inventory">oremap Chronicle</a> · <a href="https://greyforge.tech/llms.txt">Greyforge llms.txt</a></sub></p>
Loading
Loading