fix: manifest SHA256 from git blobs (CRLF/LF) — release 2.2.2-SAC

Manifest was hashed from Windows working tree while Linux checkout uses
git blobs; verify failed with sha256 mismatch. build-release-manifest.sh
now hashes :index/HEAD; .gitattributes enforces LF for agent scripts.
This commit is contained in:
PTah
2026-07-08 12:00:51 +10:00
parent 6df83f1838
commit 0399a6d384
7 changed files with 50 additions and 13 deletions
+4
View File
@@ -0,0 +1,4 @@
# Агентские скрипты — LF в git и на Linux (manifest SHA256 = git blob).
ssh-monitor text eol=lf
*.sh text eol=lf
version.txt text eol=lf
+1 -1
View File
@@ -1,6 +1,6 @@
# ssh-monitor # ssh-monitor
**Версия:** `2.2.1-SAC` **Версия:** `2.2.2-SAC`
Bash-мониторинг **SSH**, **sudo**, **systemd-logind**: Telegram/email, бан IP (ipset), ежедневный отчёт, heartbeat. Bash-мониторинг **SSH**, **sudo**, **systemd-logind**: Telegram/email, бан IP (ipset), ежедневный отчёт, heartbeat.
+5 -5
View File
@@ -91,13 +91,13 @@ Updater читает **`GIT_REF`**, **`GIT_VERIFY_MODE`**, **`GIT_ALLOW_RESET`**
- Файл не найден → WARN, update продолжается (или exit 1 при **`RELEASE_MANIFEST_STRICT=1`**). - Файл не найден → WARN, update продолжается (или exit 1 при **`RELEASE_MANIFEST_STRICT=1`**).
- Копирование **`update_ssh_monitor.sh`** в `/opt/scripts/` и re-exec — только если manifest для версии был и прошёл verify. - Копирование **`update_ssh_monitor.sh`** в `/opt/scripts/` и re-exec — только если manifest для версии был и прошёл verify.
Генерация при релизе (после **финального** коммита кода, чтобы `git_commit` в manifest совпал с HEAD): Генерация при релизе (хеши **из git**, как на Linux после `checkout` — не с диска Windows):
```bash ```bash
git commit -m "release: 2.2.0-SAC" git add --renormalize ssh-monitor *.sh # при смене .gitattributes
./scripts/build-release-manifest.sh 2.2.0-SAC ./scripts/build-release-manifest.sh 2.2.2-SAC
git add release/manifest-2.2.0-SAC.json && git commit -m "release: manifest 2.2.0-SAC" git add release/manifest-2.2.2-SAC.json && git commit -m "release: manifest 2.2.2-SAC"
git tag 2.2.0-SAC git tag 2.2.2-SAC
``` ```
### Примеры для прода ### Примеры для прода
+11
View File
@@ -0,0 +1,11 @@
{
"version": "2.2.2-SAC",
"git_commit": "",
"files": {
"ssh-monitor": "sha256:be35da47fb1cf370628795bfdfe34195a7ba098c666bd8aa092929a168a10ef6",
"sac-client.sh": "sha256:4c481effd0e50b3db8cfa3baf3c5f228892530939df6f534a5d3d3d8a79db048",
"update_ssh_monitor.sh": "sha256:510d63b43ea02e5aaa4385bcc6c9396ad99637c377c9880125cb2168e9d422cd",
"ssh-monitor-watchdog": "sha256:41aa139cab0fb275249780953fceb3f8ebe01c49bf96f901dd3cf4d2fb3a9274",
"ssh-monitor-perms.sh": "sha256:53ebf15138d59be1610e5bc14a107b942d9a7fd039683abfd2fdd0aa0b54b933"
}
}
+27 -5
View File
@@ -1,5 +1,6 @@
#!/bin/bash #!/bin/bash
# Генерация release/manifest-VERSION.json (SHA256 файлов + git commit HEAD). # Генерация release/manifest-VERSION.json (SHA256 файлов + git commit HEAD).
# Хеши считаются по содержимому git (HEAD), как после checkout на Linux-хосте.
# Запуск из корня репозитория: ./scripts/build-release-manifest.sh [VERSION] # Запуск из корня репозитория: ./scripts/build-release-manifest.sh [VERSION]
set -euo pipefail set -euo pipefail
@@ -27,8 +28,8 @@ FILES=(
) )
for f in "${FILES[@]}"; do for f in "${FILES[@]}"; do
[ -f "$f" ] || { git rev-parse "HEAD:$f" >/dev/null 2>&1 || [ -f "$f" ] || {
echo "ERROR: нет файла $f" >&2 echo "ERROR: нет файла $f (ни в git HEAD, ни на диске)" >&2
exit 1 exit 1
} }
done done
@@ -51,6 +52,7 @@ python3 <<'PY'
import hashlib import hashlib
import json import json
import os import os
import subprocess
root = os.environ["MANIFEST_ROOT"] root = os.environ["MANIFEST_ROOT"]
version = os.environ["MANIFEST_VERSION"] version = os.environ["MANIFEST_VERSION"]
@@ -58,21 +60,41 @@ git_commit = os.environ.get("MANIFEST_GIT_COMMIT", "")
files = os.environ["MANIFEST_FILES"].split() files = os.environ["MANIFEST_FILES"].split()
out = os.environ["MANIFEST_OUT"] out = os.environ["MANIFEST_OUT"]
def sha256_file(path: str) -> str:
def sha256_bytes(data: bytes) -> str:
h = hashlib.sha256()
h.update(data)
return h.hexdigest()
def file_bytes_from_git(name: str) -> bytes | None:
for ref in (f":{name}", f"HEAD:{name}"):
try:
return subprocess.check_output(["git", "-C", root, "show", ref])
except subprocess.CalledProcessError:
continue
return None
def sha256_release_file(name: str) -> str:
blob = file_bytes_from_git(name)
if blob is not None:
return sha256_bytes(blob)
path = os.path.join(root, name)
h = hashlib.sha256() h = hashlib.sha256()
with open(path, "rb") as f: with open(path, "rb") as f:
for chunk in iter(lambda: f.read(1024 * 1024), b""): for chunk in iter(lambda: f.read(1024 * 1024), b""):
h.update(chunk) h.update(chunk)
return h.hexdigest() return h.hexdigest()
manifest = { manifest = {
"version": version, "version": version,
"git_commit": git_commit, "git_commit": git_commit,
"files": {}, "files": {},
} }
for name in files: for name in files:
path = os.path.join(root, name) manifest["files"][name] = f"sha256:{sha256_release_file(name)}"
manifest["files"][name] = f"sha256:{sha256_file(path)}"
with open(out, "w", encoding="utf-8") as f: with open(out, "w", encoding="utf-8") as f:
json.dump(manifest, f, indent=2, ensure_ascii=False) json.dump(manifest, f, indent=2, ensure_ascii=False)
+1 -1
View File
@@ -7,7 +7,7 @@ IFS=$'\n\t'
# ============================================ # ============================================
CONFIG_FILE="/etc/ssh-monitor.conf" CONFIG_FILE="/etc/ssh-monitor.conf"
SSH_MONITOR_VERSION="2.2.1-SAC" SSH_MONITOR_VERSION="2.2.2-SAC"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -f "$SCRIPT_DIR/ssh-monitor-perms.sh" ]; then if [ -f "$SCRIPT_DIR/ssh-monitor-perms.sh" ]; then
+1 -1
View File
@@ -1 +1 @@
2.2.1-SAC 2.2.2-SAC