cloud-integrity-check 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ """cloud-integrity-check — hash files into a manifest and verify integrity later.
2
+
3
+ Detects silent corruption, truncation or tampering after a cloud round-trip
4
+ (upload/download/sync). Pure standard library (hashlib) — no custom crypto.
5
+ """
6
+ from .core import compute_manifest, verify, hash_file, Report
7
+
8
+ __all__ = ["compute_manifest", "verify", "hash_file", "Report"]
9
+ __version__ = "0.1.0"
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+ import argparse, json, sys
3
+ from .core import compute_manifest, verify
4
+
5
+
6
+ def main(argv=None) -> int:
7
+ p = argparse.ArgumentParser(prog="cloud-integrity-check",
8
+ description="Hash files into a manifest and verify integrity after a cloud round-trip.")
9
+ sub = p.add_subparsers(dest="cmd", required=True)
10
+ c = sub.add_parser("create", help="create a manifest of a file or directory")
11
+ c.add_argument("path"); c.add_argument("-o", "--out", default="integrity.json")
12
+ c.add_argument("--algo", default="sha256")
13
+ v = sub.add_parser("verify", help="verify a path against a manifest")
14
+ v.add_argument("manifest"); v.add_argument("path"); v.add_argument("--algo", default="sha256")
15
+ a = p.parse_args(argv)
16
+
17
+ if a.cmd == "create":
18
+ m = compute_manifest(a.path, a.algo)
19
+ with open(a.out, "w", encoding="utf-8") as f:
20
+ json.dump({"algo": a.algo, "files": m}, f, indent=2)
21
+ print(f"Wrote {a.out} ({len(m)} file(s), {a.algo}).")
22
+ return 0
23
+
24
+ with open(a.manifest, encoding="utf-8") as f:
25
+ data = json.load(f)
26
+ m = data.get("files", data)
27
+ rep = verify(m, a.path, data.get("algo", a.algo))
28
+ print(f"OK: {len(rep.ok)} Changed: {len(rep.changed)} Missing: {len(rep.missing)} Added: {len(rep.added)}")
29
+ for label, items in (("CHANGED", rep.changed), ("MISSING", rep.missing), ("ADDED", rep.added)):
30
+ for it in items:
31
+ print(f" {label}: {it}")
32
+ return 0 if rep.intact else 2
33
+
34
+
35
+ if __name__ == "__main__":
36
+ raise SystemExit(main())
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+ import hashlib
3
+ import os
4
+ from dataclasses import dataclass, field
5
+ from typing import Dict, List
6
+
7
+ CHUNK = 1024 * 1024 # 1 MiB
8
+
9
+
10
+ def hash_file(path: str, algo: str = "sha256") -> str:
11
+ """Return the hex digest of a file, read in chunks (memory-safe for big files)."""
12
+ h = hashlib.new(algo)
13
+ with open(path, "rb") as f:
14
+ for block in iter(lambda: f.read(CHUNK), b""):
15
+ h.update(block)
16
+ return h.hexdigest()
17
+
18
+
19
+ def compute_manifest(root: str, algo: str = "sha256") -> Dict[str, str]:
20
+ """Map every file under root to its hash, keyed by POSIX-style relative path.
21
+
22
+ A single file yields a one-entry manifest keyed by its basename.
23
+ """
24
+ if os.path.isfile(root):
25
+ return {os.path.basename(root): hash_file(root, algo)}
26
+ manifest: Dict[str, str] = {}
27
+ for dirpath, _dirs, files in os.walk(root):
28
+ for name in files:
29
+ full = os.path.join(dirpath, name)
30
+ rel = os.path.relpath(full, root).replace(os.sep, "/")
31
+ manifest[rel] = hash_file(full, algo)
32
+ return manifest
33
+
34
+
35
+ @dataclass
36
+ class Report:
37
+ ok: List[str] = field(default_factory=list) # unchanged
38
+ changed: List[str] = field(default_factory=list) # hash differs
39
+ missing: List[str] = field(default_factory=list) # in manifest, not on disk
40
+ added: List[str] = field(default_factory=list) # on disk, not in manifest
41
+
42
+ @property
43
+ def intact(self) -> bool:
44
+ return not (self.changed or self.missing)
45
+
46
+
47
+ def verify(manifest: Dict[str, str], root: str, algo: str = "sha256") -> Report:
48
+ """Compare a stored manifest against the current state of root."""
49
+ current = compute_manifest(root, algo)
50
+ rep = Report()
51
+ for path, digest in manifest.items():
52
+ if path not in current:
53
+ rep.missing.append(path)
54
+ elif current[path] != digest:
55
+ rep.changed.append(path)
56
+ else:
57
+ rep.ok.append(path)
58
+ for path in current:
59
+ if path not in manifest:
60
+ rep.added.append(path)
61
+ return rep
@@ -0,0 +1,68 @@
1
+ Metadata-Version: 2.4
2
+ Name: cloud-integrity-check
3
+ Version: 0.1.0
4
+ Summary: Hash files into a manifest and verify their integrity after a cloud upload/download/sync — detect silent corruption, truncation or tampering. Standard-library only.
5
+ Project-URL: Homepage, https://priviy.com
6
+ Project-URL: Documentation, https://priviy.com
7
+ Author: Eric Gerard
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: backup,checksum,cloud,integrity,manifest,sha256,tamper,verification
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Security
18
+ Classifier: Topic :: System :: Archiving :: Backup
19
+ Classifier: Topic :: Utilities
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+
23
+ # cloud-integrity-check
24
+
25
+ Hash your files into a manifest, then **verify they are byte-for-byte intact**
26
+ after a cloud upload, download or sync. Catches silent corruption, truncation
27
+ or tampering that you would otherwise not notice. **Standard library only**
28
+ (SHA-256 via `hashlib`) — no dependencies, no custom crypto.
29
+
30
+ ## Install
31
+
32
+ ```
33
+ pip install cloud-integrity-check
34
+ ```
35
+
36
+ ## Use
37
+
38
+ ```
39
+ # Before uploading: snapshot the integrity of a file or folder
40
+ cloud-integrity-check create ~/Documents -o documents.json
41
+
42
+ # After downloading from your cloud: verify nothing changed
43
+ cloud-integrity-check verify documents.json ~/Documents
44
+ ```
45
+
46
+ Output reports per-file `OK / CHANGED / MISSING / ADDED`, and exits non-zero if
47
+ anything changed or went missing — handy in scripts and CI.
48
+
49
+ ## As a library
50
+
51
+ ```python
52
+ from cloud_integrity_check import compute_manifest, verify
53
+
54
+ m = compute_manifest("/data")
55
+ report = verify(m, "/data")
56
+ print(report.intact, report.changed, report.missing)
57
+ ```
58
+
59
+ ## Why
60
+
61
+ End-to-end encrypted clouds protect *confidentiality*; this verifies
62
+ *integrity* — that what you get back is exactly what you stored. A practical
63
+ companion to a zero-knowledge backup. More on private, verifiable cloud storage:
64
+ **[priviy.com](https://priviy.com)**.
65
+
66
+ ## License
67
+
68
+ MIT
@@ -0,0 +1,8 @@
1
+ cloud_integrity_check/__init__.py,sha256=P7GTEG9y8hwUUYy8Zfvc4ub-QnTRS1Iz82eNHM5nLRA,392
2
+ cloud_integrity_check/cli.py,sha256=hT6X77PfXIiFaECx_KjXM-NHnLFEkrcpvaGYG1MUjHo,1566
3
+ cloud_integrity_check/core.py,sha256=FthM1BY20QYsnZfoKEkKD-UBeUDiL9EJispCNXPwd_A,2073
4
+ cloud_integrity_check-0.1.0.dist-info/METADATA,sha256=Fjx9i7OTHpRZSXb-K3XbCz6knDgrxm9lHzf4OBU2lgY,2193
5
+ cloud_integrity_check-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
6
+ cloud_integrity_check-0.1.0.dist-info/entry_points.txt,sha256=Id1SVBgQYFxf5y5f653l0l2otyE4Ju-MA0JqTVcU5cQ,73
7
+ cloud_integrity_check-0.1.0.dist-info/licenses/LICENSE,sha256=8lsqmsA61fjwx9wiQPaJIcsgHocgr0Lih-on1Kfz0xI,486
8
+ cloud_integrity_check-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ cloud-integrity-check = cloud_integrity_check.cli:main
@@ -0,0 +1,11 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eric Gerard
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including the rights to use, copy, modify,
8
+ merge, publish, distribute, sublicense, and/or sell copies, subject to the
9
+ above copyright notice and this permission notice being included.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.