Files
ssh-monitor/contrib/manifest/generate.py
T

118 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""Генерация release/manifest-VERSION.json (SHA256 git blob = checkout на Linux)."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import subprocess
import sys
from pathlib import Path
MANIFEST_FILES = (
"ssh-monitor",
"sac-client.sh",
"update_ssh_monitor.sh",
"ssh-monitor-watchdog",
"ssh-monitor-perms.sh",
)
def repo_root() -> Path:
return Path(
subprocess.check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip()
)
def read_version(root: Path, explicit: str | None) -> str:
if explicit:
return explicit.strip()
vt = root / "version.txt"
if vt.is_file():
v = vt.read_text(encoding="utf-8").strip()
if v:
return v
ssh = root / "ssh-monitor"
if ssh.is_file():
for line in ssh.read_text(encoding="utf-8", errors="replace").splitlines():
if line.strip().startswith("SSH_MONITOR_VERSION="):
v = line.split("=", 1)[1].strip().strip("\"'")
if v:
return v
raise SystemExit("ERROR: укажите VERSION или заполните version.txt / SSH_MONITOR_VERSION")
def file_bytes_from_git(root: Path, name: str) -> bytes | None:
for ref in (f":{name}", f"HEAD:{name}"):
try:
return subprocess.check_output(["git", "-C", str(root), "show", ref])
except subprocess.CalledProcessError:
continue
return None
def sha256_bytes(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def sha256_release_file(root: Path, name: str) -> str:
blob = file_bytes_from_git(root, name)
if blob is not None:
return sha256_bytes(blob)
path = root / name
if not path.is_file():
raise SystemExit(f"ERROR: нет файла {name} (ни в git index/HEAD, ни на диске)")
h = hashlib.sha256()
with path.open("rb") as f:
for chunk in iter(lambda: f.read(1024 * 1024), b""):
h.update(chunk)
return h.hexdigest()
def main() -> None:
parser = argparse.ArgumentParser(description="Сгенерировать release/manifest-VERSION.json")
parser.add_argument("version", nargs="?", help="Версия (по умолчанию из version.txt)")
parser.add_argument(
"--pin-commit",
action="store_true",
help="Записать git_commit=HEAD (после коммита; в pre-commit не используется)",
)
args = parser.parse_args()
root = repo_root()
version = read_version(root, args.version)
for name in MANIFEST_FILES:
if file_bytes_from_git(root, name) is None and not (root / name).is_file():
raise SystemExit(f"ERROR: нет файла {name}")
git_commit = ""
if args.pin_commit:
try:
git_commit = subprocess.check_output(
["git", "-C", str(root), "rev-parse", "HEAD"], text=True
).strip()
except subprocess.CalledProcessError:
pass
manifest = {
"version": version,
"git_commit": git_commit,
"files": {name: f"sha256:{sha256_release_file(root, name)}" for name in MANIFEST_FILES},
}
out = root / "release" / f"manifest-{version}.json"
out.parent.mkdir(parents=True, exist_ok=True)
with out.open("w", encoding="utf-8") as f:
json.dump(manifest, f, indent=2, ensure_ascii=False)
f.write("\n")
pin = git_commit[:12] if git_commit else "(empty)"
print(f"Wrote {out.relative_to(root)} git_commit={pin}")
if __name__ == "__main__":
main()