# CleanInstall (CLI prototype) CleanInstall is the Pro tier of the same App Janitor product cluster as the sibling tool AppJanitor: instead of one ad-hoc invocation where you pass directory/pattern/age flags directly on the command line for a single cleanup pass, CleanInstall reads named cleanup POLICIES from a config file (each with its own target directory, junk-glob patterns, and age threshold -- similar in spirit to the sibling tool ActionForge's multi-rule config) and records every file it EVER quarantines, across every `clean` run against every policy, in a persistent, append-only JSON-lines ledger. That ledger is the point of the "Pro" tier: the product concept's own market gap is that maintenance suites are bloated and trust-poor, and the fix is making every proposed change explainable, reversible, and logged -- not just for one run's report, but so an admin can answer "what has CleanInstall ever touched on this machine, and when" months later by reading one file. The actual file-matching engine (junk-pattern glob + age threshold + quarantine-not-delete, never a hard delete) is AppJanitor's exact logic, reused as-is; CleanInstall's new work is entirely the policy layer and the cumulative ledger on top of it. Real deep-uninstall/registry integration, startup control, restore points, and driver rollback (the rest of the "CleanInstall" product concept) need privileged, OS-specific APIs (the Windows registry, WMI, Task Scheduler, System Restore, Device Manager) with no portable stdlib equivalent on macOS/Linux, so none of that is implemented here -- it is on the roadmap. See ../plan.md for the full product plan. ## Build from source Requires Go 1.24+, no external dependencies. go build -o cleaninstall . Cross-compile for another platform: GOOS=windows GOARCH=amd64 go build -o cleaninstall.exe . GOOS=darwin GOARCH=arm64 go build -o cleaninstall . ## Usage Policy file format (JSON), passed to every command via --policy: { "policies": [ { "name": "temp-files", "target_dir": "/path/to/dir", "patterns": ["*.tmp", "*.bak"], "older_than": "0d" } ] } - name unique identifier used on the command line - target_dir directory tree the policy walks (must exist) - patterns comma-free JSON array of glob patterns matched against the base filename (filepath.Match), e.g. "*.tmp" - older_than digits + d/h/m suffix (e.g. "180d", "12h", "45m"), same convention as AppJanitor; "0d" or omitted means no age filter -- only patterns are matched A file matches a policy if its base filename matches ANY of the policy's patterns, OR (when older_than is set to a non-zero duration) its mtime is older than that threshold. Each match is reported with the specific reason it matched (junk-pattern:*.tmp, stale:212d old). cleaninstall scan --policy [--json] Read-only. Walks the named policy's target_dir and reports every current match (path, size, reason) with a running reclaimable-bytes total. Touches nothing on disk. Pass --json for machine-readable output instead of the human-readable listing. cleaninstall clean --policy --quarantine --ledger [--apply] Uses the exact same matching logic as scan. Without --apply it is a dry run: it prints exactly what scan would print, plus a "(dry run -- re-run with --apply)" footer, and touches nothing -- no files move, no ledger record is written. With --apply: - every matched file is moved into , preserving its path relative to the policy's target_dir (parent directories are created as needed). Files are always quarantined via a move, never deleted -- a mistaken match is always one `mv` away from undone, never gone. - one JSON-lines record is APPENDED to --ledger for every file just quarantined: {"path", "size", "policy", "quarantine", "timestamp"} (timestamp is UTC, RFC3339). The ledger file is opened with O_APPEND|O_CREATE and never O_TRUNC, so this is a true append across independent process invocations -- running `clean` against a second policy, in a completely separate invocation, against the same --ledger file adds new lines after the existing ones and never rewrites, reorders, or drops a single byte of what earlier runs (by this or any other policy) already recorded. --quarantine and --ledger are both required when --apply is given. A policy matching zero files is not an error: scan prints "nothing matched" and clean prints "nothing to clean", both with exit code 0. Naming an unknown policy is an error: exit code 1, no files touched, no panic. cleaninstall ledger --ledger [--policy ] [--json] Read-only. Reads every record ever appended to --ledger (a missing ledger file is treated as empty, not an error) and reports the total files and bytes ever quarantined by ANY clean run, broken down per policy name. Pass --policy to restrict the summary to one policy's records. Pass --json for a machine-readable {total_files, total_bytes, by_policy: [...]} object instead of the human-readable table. ## Prebuilt binaries See ../downloads/ for prebuilt binaries (Windows/macOS/Linux) and CHECKSUMS.txt for their SHA-256 hashes. Unsigned indie builds -- Windows SmartScreen and macOS Gatekeeper will warn on first run, expected until a code-signing certificate is in place.