Verifies that files have not been corrupted, changed unintentionally, or subject to bitrot. Created to warn administrators if important files get modified.
Unaltered scans a directory tree, computes SHA-256 hashes for files, and stores results in a local SQLite database. You can later re-run verification to compare current file hashes against the stored values.
- Python 3.x (uses only the standard library for the main script)
- For running tests: pytest
Clone or download this repository, then optionally install the development dependencies:
pip install -r requirements.txt
This installs pytest for running the test suite. The main script (unaltered.py) has no external dependencies beyond the Python standard library.
Unaltered has two commands: index (build or update the hash database) and verify (compare current file hashes to the stored database).
Scan a directory and store SHA-256 hashes. Only new or changed files (by size/mtime) are hashed; unchanged files are skipped for speed.
python unaltered.py index --root /path/to/directory
Examples:
# Basic index of a photo library (creates integrity.db and report.json) python unaltered.py index --root /path/to/photos # Use a custom database and report path python unaltered.py index --root /path/to/photos --db /path/to/integrity.db --report my_report.json # Exclude certain extensions (e.g. temp files, databases) python unaltered.py index --root /path/to/photos --exclude-ext .tmp,.db,.log # Ignore macOS resource forks (._* files < 4KB) python unaltered.py index --root /path/to/photos --ignore-deleted # Use multiple worker threads for faster hashing on large trees python unaltered.py index --root /path/to/photos --workers 4 # Write log output to a file python unaltered.py index --root /path/to/photos --log run.log --verbose
Re-hash files under the given root and compare to the stored database. Exits with code 0 if all files match; exits 1 if there are mismatches, missing files, or errors.
python unaltered.py verify --root /path/to/directory
Examples:
# Basic verification (same root as index) python unaltered.py verify --root /path/to/photos --db integrity.db # Verify a backup tree (different root): index was built from source, root is the backup # Uses hash-based matching so folder structure can differ python unaltered.py verify --root /path/to/backup --db integrity.db --cross-root # Parallel verification python unaltered.py verify --root /path/to/photos --workers 4
| Option | Description |
|---|---|
--root |
Root directory to scan (required) |
--db |
Path to SQLite database (default: integrity.db) |
--exclude-ext |
Extensions to exclude, comma-separated or repeatable (e.g. .tmp,.db) |
--report |
JSON report path (default: report.json for index, verify.json for verify) |
--ignore-deleted |
Ignore ._* files smaller than 4KB (macOS resource forks) |
--workers |
Number of parallel hashing threads (default: 1) |
--log |
Write log output to this file |
--verbose |
Enable debug logging |
| Option | Description |
|---|---|
--cross-root |
Root is a different tree (e.g. backup). Verification is hash-only; "missing" = hashes in DB not found under root. |
If you prefer a graphical interface, run:
python unaltered_ui.py
The UI includes separate Index and Verify tabs with fields for:
- root directory
- database path
- report path
- exclude extensions
- worker count
- ignore-deleted flag
- optional log file
- verbose logging
- cross-root mode (verify only)
Runs execute in the background so the window stays responsive.
Verify runs also render a folder tree that highlights differences (changed hash, added, deleted, moved-from, moved-to) and rolls those counts up to parent folders so you can quickly identify affected areas.
A progress bar shows processed files and an estimate of how many files are left.
- Initial index: Run
indexon the directory you want to protect. This createsintegrity.dbandreport.json. - Periodic verify: Run
verifyregularly (e.g. via cron) to check for changes. If the exit code is non-zero, inspect the report for mismatched or missing files. - Update index: After legitimate changes, run
indexagain to update the database.
pytest test_unaltered.py -v