sync: WIN10-I9-64G 2026-10-19

This commit is contained in:
Myfro9
2026-10-19 22:44:51 +08:00
parent 47faf8bbb7
commit 479769eff0
15 changed files with 1794 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
"""merge_rootfs.py -- assemble a release root filesystem tarball from a JSON spec.
python merge_rootfs.py <spec.json> <out.tar.gz> [--verify]
Works on Windows or Linux without root: tar members are copied as TarInfo, so
owners, modes, symlinks, hard links and device nodes survive exactly (an
extract-and-retar on Windows, or as non-root on Linux, would lose them).
Later layers override earlier ones by path; the output is deterministic
(fixed mtime for added entries, gzip header mtime 0), so the same spec gives
the same archive bytes.
spec.json (paths relative to the spec file unless absolute):
{
"mtime": 1792022400, # added entries; optional
"layers": [ "rootfs.tar.gz", "overlay.tar.gz" ], # applied in order
"remove": [ "etc/dropbear/dropbear_rsa_host_key" ], # paths (and their subtrees)
"files": [ {"path": "usr/bin/app", "src": "bin/app", "mode": "0755"} ],
"links": [ {"path": "etc/app/current.cfg", "target": "/etc/app/a.cfg"} ],
"dirs": [ "aw/dpd_curve" ],
"expect": { "usr/bin/app": "<sha256 or a prefix>" } # checked with --verify
}
"""
import gzip, hashlib, io, json, os, sys, tarfile
def norm(n):
while n.startswith("./"):
n = n[2:]
n = n.strip("/")
return "./" + n if n not in ("", ".") else "./"
class Tree:
def __init__(self):
self.m, self.order = {}, []
def put(self, ti, data):
ti.name = norm(ti.name)
if ti.islnk():
ti.linkname = norm(ti.linkname)
if ti.name not in self.m:
self.order.append(ti.name)
self.m[ti.name] = (ti, data)
def remove(self, p):
p = norm(p)
for n in list(self.m):
if n == p or n.startswith(p + "/"):
del self.m[n]
self.order = [n for n in self.order if n in self.m]
def info(name, kind, mode, mtime):
ti = tarfile.TarInfo(name); ti.type = kind; ti.mode = mode; ti.mtime = mtime
ti.uid = ti.gid = 0; ti.uname = ti.gname = "root"
return ti
def main(argv):
if len(argv) < 3:
sys.exit(__doc__)
spec_path, out = argv[1], argv[2]
base = os.path.dirname(os.path.abspath(spec_path))
rel = lambda p: p if os.path.isabs(p) else os.path.join(base, p)
spec = json.load(open(spec_path))
mt = int(spec.get("mtime", 1700000000))
t = Tree()
for layer in spec.get("layers", []):
with tarfile.open(rel(layer), "r:*") as tf:
for ti in tf.getmembers():
t.put(ti, tf.extractfile(ti).read() if ti.isfile() else None)
for p in spec.get("remove", []):
t.remove(p)
for f in spec.get("files", []):
d = open(rel(f["src"]), "rb").read()
ti = info(f["path"], tarfile.REGTYPE, int(f.get("mode", "0644"), 8), mt); ti.size = len(d)
t.put(ti, d)
for l in spec.get("links", []):
ti = info(l["path"], tarfile.SYMTYPE, 0o777, mt); ti.linkname = l["target"]
t.put(ti, None)
for d in spec.get("dirs", []):
parts = norm(d)[2:].split("/")
for k in range(1, len(parts) + 1): # parents first, keep existing ones
p = norm("/".join(parts[:k]))
if p not in t.m:
t.put(info(p, tarfile.DIRTYPE, 0o755, mt), None)
raw = io.BytesIO()
with tarfile.open(fileobj=raw, mode="w", format=tarfile.GNU_FORMAT) as tf:
for n in t.order:
ti, data = t.m[n]
tf.addfile(ti, io.BytesIO(data) if data is not None else None)
with open(out, "wb") as fo, gzip.GzipFile(filename="", mode="wb", fileobj=fo, mtime=0) as gz:
gz.write(raw.getvalue())
n_dev = sum(1 for n in t.order if t.m[n][0].isdev())
print(f"{len(t.order)} members ({n_dev} device nodes) -> {out}")
print("sha256", hashlib.sha256(open(out, "rb").read()).hexdigest())
if "--verify" in argv:
bad = 0
for p, want in spec.get("expect", {}).items():
e = t.m.get(norm(p))
got = hashlib.sha256(e[1]).hexdigest() if e and e[1] is not None else None
ok = got is not None and got.startswith(want.lower())
bad += not ok
print(("OK " if ok else "BAD ") + (got or "missing")[:16], p)
for l in spec.get("links", []):
e = t.m.get(norm(l["path"]))
ok = e is not None and e[0].issym() and e[0].linkname == l["target"]
bad += not ok
print(("OK " if ok else "BAD ") + "link", l["path"], "->", l["target"])
if bad:
sys.exit(f"{bad} check(s) failed")
if __name__ == "__main__":
main(sys.argv)