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.txtThis 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/directoryExamples:
# 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 --verboseRe-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/directoryExamples:
# 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. |
- 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