Initial commit
This commit is contained in:
commit
8641464a6c
44 files changed
+2192
No files matched your search
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Euo pipefail
|
||||
|
||||
CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance/config.env"
|
||||
failures=0
|
||||
|
||||
ok() { printf '✓ %s\n' "$*"; }
|
||||
bad() { printf '✗ %s\n' "$*"; failures=$((failures + 1)); }
|
||||
|
||||
[[ -r "$CONFIG_FILE" ]] || {
|
||||
bad "$CONFIG_FILE fehlt"
|
||||
exit 1
|
||||
}
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
for command in git curl jq tar zstd systemctl hermes; do
|
||||
command -v "$command" >/dev/null 2>&1 && ok "$command vorhanden" || bad "$command fehlt"
|
||||
done
|
||||
|
||||
for script in "$HOME/.local/libexec"/hermes-stack-*; do
|
||||
[[ -f "$script" ]] || continue
|
||||
bash -n "$script" && ok "Syntax: ${script##*/}" || bad "Syntaxfehler: ${script##*/}"
|
||||
done
|
||||
|
||||
if [[ -d "$HERMES_REPO/.git" ]]; then
|
||||
ok "Hermes-Git-Checkout vorhanden"
|
||||
dirty="$(git -C "$HERMES_REPO" status --porcelain --untracked-files=all |
|
||||
grep -vxF '?? .install_method' || true)"
|
||||
[[ -z "$dirty" ]] && ok "Hermes-Checkout sauber" || bad "Hermes-Checkout enthält lokale Änderungen"
|
||||
else
|
||||
bad "Hermes-Git-Checkout fehlt"
|
||||
fi
|
||||
|
||||
systemctl --user is-active --quiet "$GATEWAY_SERVICE" && ok "Gateway aktiv" || bad "Gateway inaktiv"
|
||||
hermes gateway status >/dev/null 2>&1 && ok "Gateway gesund" || bad "Gateway-Status fehlgeschlagen"
|
||||
|
||||
if curl --fail --silent --max-time 5 "$LLAMA_URL/health" |
|
||||
jq -e '.status == "ok"' >/dev/null 2>&1; then
|
||||
ok "llama.cpp-Endpunkt gesund"
|
||||
else
|
||||
bad "llama.cpp-Endpunkt nicht gesund"
|
||||
fi
|
||||
|
||||
set +u
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$HERMES_ENV" 2>/dev/null || true
|
||||
set +a
|
||||
set -u
|
||||
if [[ -n "${SIGNAL_HTTP_URL:-}" ]]; then
|
||||
status="$(curl --silent --output /dev/null --max-time 5 --write-out '%{http_code}' \
|
||||
"${SIGNAL_HTTP_URL%/}/api/v1/check" || true)"
|
||||
[[ "$status" =~ ^2[0-9][0-9]$ ]] && ok "signal-cli HTTP erreichbar" || bad "signal-cli HTTP nicht erreichbar"
|
||||
fi
|
||||
|
||||
printf '\nFehler: %d\n' "$failures"
|
||||
((failures == 0))
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Euo pipefail
|
||||
|
||||
CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance/config.env"
|
||||
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/hermes-stack-maintenance"
|
||||
NOTIFIER="$HOME/.local/libexec/hermes-stack-notify"
|
||||
mkdir -p "$STATE_DIR"
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
# Niemals den absichtlich gestoppten Gateway mitten im Update neu starten.
|
||||
exec 9>"${XDG_RUNTIME_DIR:-/tmp}/hermes-stack-hermes-update.lock"
|
||||
flock -n 9 || exit 0
|
||||
|
||||
state_file="$STATE_DIR/health-state"
|
||||
problems=()
|
||||
|
||||
if ! systemctl --user is-active --quiet "$GATEWAY_SERVICE"; then
|
||||
problems+=("$GATEWAY_SERVICE war inaktiv; Neustart wurde versucht")
|
||||
systemctl --user restart "$GATEWAY_SERVICE" || true
|
||||
fi
|
||||
|
||||
if ! hermes gateway status >/dev/null 2>&1; then
|
||||
problems+=("Hermes-Gateway antwortete nicht gesund; Neustart wurde versucht")
|
||||
systemctl --user restart "$GATEWAY_SERVICE" || true
|
||||
fi
|
||||
|
||||
if [[ "${LLAMA_HEALTH_AUTORESTART:-0}" == 1 ]] &&
|
||||
! systemctl --user is-active --quiet "$LLAMA_SERVICE"; then
|
||||
problems+=("$LLAMA_SERVICE war inaktiv; Neustart wurde versucht")
|
||||
systemctl --user restart "$LLAMA_SERVICE" || true
|
||||
fi
|
||||
|
||||
if ! curl --fail --silent --max-time 10 "$LLAMA_URL/health" |
|
||||
jq -e '.status == "ok"' >/dev/null 2>&1; then
|
||||
problems+=("llama-server antwortete nicht gesund; kein HTTP-bedingter Neustart laufender Inferenz")
|
||||
fi
|
||||
|
||||
if ((${#problems[@]} == 0)); then
|
||||
current="healthy"
|
||||
previous="$(cat "$state_file" 2>/dev/null || true)"
|
||||
if [[ "$previous" == unhealthy ]]; then
|
||||
"$NOTIFIER" "Hermes-Stack: Dienste sind wieder gesund." || true
|
||||
fi
|
||||
else
|
||||
current="unhealthy"
|
||||
previous="$(cat "$state_file" 2>/dev/null || true)"
|
||||
if [[ "$previous" != unhealthy ]]; then
|
||||
message="$(IFS='; '; printf '%s' "${problems[*]}")"
|
||||
"$NOTIFIER" "Hermes-Stack: Gesundheitsprüfung fehlgeschlagen. $message. Details: journalctl --user -u $LLAMA_SERVICE -u $GATEWAY_SERVICE" || true
|
||||
fi
|
||||
fi
|
||||
|
||||
printf '%s\n' "$current" >"$state_file"
|
||||
[[ "$current" == healthy ]]
|
||||
Executable
+236
@@ -0,0 +1,236 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance/config.env"
|
||||
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/hermes-stack-maintenance"
|
||||
NOTIFIER="$HOME/.local/libexec/hermes-stack-notify"
|
||||
mkdir -p "$STATE_DIR"
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
log() {
|
||||
printf '%s\n' "$*" | systemd-cat -t hermes-stack-maintenance
|
||||
}
|
||||
|
||||
notify_user() {
|
||||
local text="$1"
|
||||
if [[ -x "$NOTIFIER" ]]; then
|
||||
"$NOTIFIER" "$text" || true
|
||||
else
|
||||
log "$text"
|
||||
fi
|
||||
}
|
||||
|
||||
fetch_origin_main_with_retry() {
|
||||
local -a retry_delays=(0 5 20)
|
||||
local -a git_options=()
|
||||
local attempt delay
|
||||
|
||||
for ((attempt = 1; attempt <= ${#retry_delays[@]}; attempt++)); do
|
||||
delay="${retry_delays[attempt - 1]}"
|
||||
if ((delay > 0)); then
|
||||
log "Hermes-Git-Fetch: neuer Versuch $attempt/${#retry_delays[@]} in ${delay}s."
|
||||
sleep "$delay"
|
||||
fi
|
||||
|
||||
git_options=()
|
||||
if ((attempt == ${#retry_delays[@]})); then
|
||||
git_options=(-c http.version=HTTP/1.1)
|
||||
log "Hermes-Git-Fetch: letzter Versuch mit HTTP/1.1."
|
||||
fi
|
||||
|
||||
if GIT_TERMINAL_PROMPT=0 timeout --foreground 90s \
|
||||
git "${git_options[@]}" -C "$HERMES_REPO" fetch --no-tags \
|
||||
"$HERMES_REMOTE" \
|
||||
"+refs/heads/$HERMES_BRANCH:refs/remotes/$HERMES_REMOTE/$HERMES_BRANCH"; then
|
||||
((attempt == 1)) ||
|
||||
log "Hermes-Git-Fetch war im Versuch $attempt/${#retry_delays[@]} erfolgreich."
|
||||
return 0
|
||||
fi
|
||||
log "Hermes-Git-Fetch fehlgeschlagen (Versuch $attempt/${#retry_delays[@]})."
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
run_hermes_update_attempt() {
|
||||
local attempt="$1"
|
||||
local -a pipe_status
|
||||
|
||||
printf '\n===== Hermes update: Versuch %s/2 (%s) =====\n' \
|
||||
"$attempt" "$(date --iso-8601=seconds)" >>"$update_log"
|
||||
|
||||
set +e
|
||||
hermes update 2>&1 | tee -a "$update_log"
|
||||
pipe_status=("${PIPESTATUS[@]}")
|
||||
set -e
|
||||
|
||||
((pipe_status[1] == 0)) || return "${pipe_status[1]}"
|
||||
return "${pipe_status[0]}"
|
||||
}
|
||||
|
||||
is_retryable_update_fetch_failure() {
|
||||
local current_commit dirty_after
|
||||
current_commit="$(git -C "$HERMES_REPO" rev-parse HEAD)" || return 1
|
||||
[[ "$current_commit" == "$old_commit" ]] || return 1
|
||||
|
||||
dirty_after="$(
|
||||
git -C "$HERMES_REPO" status --porcelain --untracked-files=all |
|
||||
grep -vxF '?? .install_method' || true
|
||||
)"
|
||||
[[ -z "$dirty_after" ]] || return 1
|
||||
grep -Fq 'Fetching updates' "$update_log" || return 1
|
||||
grep -Fq 'Pulling updates' "$update_log" && return 1
|
||||
grep -Eqi \
|
||||
'HTTP (429|500|502|503|504)|returned error: (429|500|502|503|504)|rate.?limit|could not read Username|Authentication failed|Could not resolve host|Failed to connect|Connection (timed out|reset)|TLS|early EOF|Failed to fetch updates|Network error' \
|
||||
"$update_log"
|
||||
}
|
||||
|
||||
for command in git tar zstd jq curl systemctl timeout flock hermes; do
|
||||
command -v "$command" >/dev/null 2>&1 || {
|
||||
notify_user "Hermes-Update abgebrochen: benötigtes Kommando fehlt: $command"
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
LOCK_FILE="${XDG_RUNTIME_DIR:-/tmp}/hermes-stack-hermes-update.lock"
|
||||
exec 9>"$LOCK_FILE"
|
||||
flock -n 9 || exit 0
|
||||
|
||||
if [[ ! -d "$HERMES_REPO/.git" ]]; then
|
||||
notify_user "Hermes-Update abgebrochen: Git-Installation nicht gefunden."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$HERMES_REPO" in
|
||||
"$HOME"/*/hermes-agent) ;;
|
||||
*)
|
||||
notify_user "Hermes-Update abgebrochen: unsicherer Repository-Pfad: $HERMES_REPO"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
dirty="$(
|
||||
git -C "$HERMES_REPO" status --porcelain --untracked-files=all |
|
||||
grep -vxF '?? .install_method' || true
|
||||
)"
|
||||
if [[ -n "$dirty" ]]; then
|
||||
notify_user "Hermes-Update abgebrochen: lokale Änderungen im Hermes-Repository:
|
||||
$dirty"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
old_commit="$(git -C "$HERMES_REPO" rev-parse HEAD)"
|
||||
|
||||
if ! fetch_origin_main_with_retry; then
|
||||
notify_user "Hermes-Updateprüfung fehlgeschlagen: $HERMES_REMOTE/$HERMES_BRANCH ist nicht erreichbar. Gateway und Installation bleiben unverändert."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
remote_commit="$(git -C "$HERMES_REPO" rev-parse "refs/remotes/$HERMES_REMOTE/$HERMES_BRANCH")"
|
||||
if [[ ! "$remote_commit" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
notify_user "Hermes-Updateprüfung fehlgeschlagen: Remote lieferte keinen gültigen Commit."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$remote_commit" == "$old_commit" ]]; then
|
||||
notify_user "Hermes ist bereits aktuell: $old_commit"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
backup_dir="$STATE_DIR/backups"
|
||||
mkdir -p "$backup_dir"
|
||||
timestamp="$(date +%Y%m%d-%H%M%S)"
|
||||
backup="$backup_dir/hermes-$timestamp.tar.zst"
|
||||
update_log="$STATE_DIR/hermes-update-$timestamp.log"
|
||||
|
||||
paths=("${HERMES_REPO#"$HOME/"}")
|
||||
for path in \
|
||||
"$HOME/.hermes/config.yaml" "$HOME/.hermes/.env" "$HOME/.hermes/auth.json" \
|
||||
"${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user/$GATEWAY_SERVICE"; do
|
||||
[[ -e "$path" && "$path" == "$HOME/"* ]] && paths+=("${path#"$HOME/"}")
|
||||
done
|
||||
|
||||
tar --zstd -cf "$backup" -C "$HOME" "${paths[@]}"
|
||||
|
||||
rollback() {
|
||||
trap - ERR
|
||||
set +e
|
||||
systemctl --user stop "$GATEWAY_SERVICE"
|
||||
if [[ "$HERMES_REPO" == "$HOME/"*/hermes-agent ]]; then
|
||||
rm -rf -- "$HERMES_REPO"
|
||||
tar --zstd -xf "$backup" -C "$HOME"
|
||||
fi
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user restart "$GATEWAY_SERVICE"
|
||||
notify_user "Hermes-Update zurückgerollt auf $old_commit. Backup: $backup"
|
||||
exit 1
|
||||
}
|
||||
trap rollback ERR
|
||||
|
||||
systemctl --user stop "$GATEWAY_SERVICE"
|
||||
|
||||
if run_hermes_update_attempt 1; then
|
||||
update_exit=0
|
||||
else
|
||||
update_exit=$?
|
||||
log "Hermes-Updater meldete Exitcode $update_exit."
|
||||
if is_retryable_update_fetch_failure; then
|
||||
log "Transienter Fetch-Fehler erkannt; Hermes-Update wird einmal wiederholt."
|
||||
sleep 20
|
||||
if run_hermes_update_attempt 2; then
|
||||
update_exit=0
|
||||
else
|
||||
update_exit=$?
|
||||
log "Hermes-Updater-Retry meldete Exitcode $update_exit."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! hermes config check >/dev/null 2>&1; then
|
||||
hermes config migrate
|
||||
hermes config check
|
||||
fi
|
||||
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user reset-failed "$GATEWAY_SERVICE" || true
|
||||
systemctl --user restart "$GATEWAY_SERVICE"
|
||||
|
||||
gateway_ready=0
|
||||
for ((attempt = 1; attempt <= ${GATEWAY_HEALTH_ATTEMPTS:-12}; attempt++)); do
|
||||
if systemctl --user is-active --quiet "$GATEWAY_SERVICE" &&
|
||||
hermes gateway status >/dev/null 2>&1; then
|
||||
gateway_ready=1
|
||||
break
|
||||
fi
|
||||
sleep "${GATEWAY_HEALTH_DELAY:-5}"
|
||||
done
|
||||
|
||||
if ((gateway_ready == 0)); then
|
||||
notify_user "Hermes-Gateway wurde nach dem Update nicht gesund. Rollback wird ausgeführt."
|
||||
false
|
||||
fi
|
||||
|
||||
if ! curl --fail --silent --max-time 10 "$LLAMA_URL/health" |
|
||||
jq -e '.status == "ok"' >/dev/null; then
|
||||
notify_user "Hermes und Gateway sind aktualisiert, aber der lokale Modell-Endpunkt ist nicht gesund: $LLAMA_URL. Kein Hermes-Rollback, da llama-server unabhängig ist."
|
||||
fi
|
||||
|
||||
trap - ERR
|
||||
|
||||
mapfile -t old_backups < <(
|
||||
find "$backup_dir" -maxdepth 1 -type f -name 'hermes-*.tar.zst' \
|
||||
-printf '%T@ %p\n' | sort -nr | tail -n "+$((${BACKUP_KEEP:-3} + 1))" |
|
||||
cut -d' ' -f2-
|
||||
)
|
||||
((${#old_backups[@]} == 0)) || rm -f -- "${old_backups[@]}"
|
||||
|
||||
new_commit="$(git -C "$HERMES_REPO" rev-parse HEAD)"
|
||||
if [[ "$new_commit" == "$old_commit" ]]; then
|
||||
notify_user "Hermes-Update wurde nicht installiert (Updater-Exitcode $update_exit); der bisherige Commit $new_commit und der frisch gestartete Gateway sind gesund."
|
||||
exit 1
|
||||
elif ((update_exit != 0)); then
|
||||
notify_user "Hermes erfolgreich aktualisiert: $old_commit -> $new_commit. Eine Updater-Restartwarnung wurde durch den frischen Gateway-Neustart behoben."
|
||||
else
|
||||
notify_user "Hermes erfolgreich aktualisiert: $old_commit -> $new_commit"
|
||||
fi
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
umask 077
|
||||
config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance"
|
||||
runner="$(dirname -- "${BASH_SOURCE[0]}")/hermes-stack-llama-run"
|
||||
|
||||
case "${1:-list}" in
|
||||
list)
|
||||
printf '%-12s %8s %10s\n' Profil Kontext Fit-MiB \
|
||||
baseline 65536 1536 balanced 49152 1536 vram-plus 49152 1280 compact 32768 1536
|
||||
exit 0 ;;
|
||||
baseline|balanced|vram-plus|compact) selected="$1" ;;
|
||||
*) printf 'Aufruf: %s [list|baseline|balanced|vram-plus|compact]\n' "$0" >&2; exit 2 ;;
|
||||
esac
|
||||
[[ $# -eq 1 ]] || exit 2
|
||||
# Profilwechsel nur für bewusst migrierte Konfigurationen erlauben.
|
||||
# shellcheck disable=SC1090
|
||||
source "$config_dir/llama.env"
|
||||
[[ -n "${LLAMA_PROFILE:-}" ]] || {
|
||||
printf 'Zuerst llama.env nach docs/WORKSTATION.md migrieren. Keine Änderung.\n' >&2; exit 1;
|
||||
}
|
||||
"$runner" --profile "$selected" --check
|
||||
exec 9>"${XDG_RUNTIME_DIR:-/tmp}/hermes-stack-hermes-update.lock"
|
||||
flock -n 9 || { printf 'Maintenance läuft. Später erneut versuchen.\n' >&2; exit 1; }
|
||||
target="$config_dir/llama-profile.env"
|
||||
if [[ -e "$target" ]]; then
|
||||
backup="$(mktemp "$config_dir/llama-profile.backup.XXXXXX")"
|
||||
cp -p -- "$target" "$backup"
|
||||
printf 'Vorherige Profilwahl: %s\n' "$backup"
|
||||
fi
|
||||
candidate="$(mktemp "$config_dir/llama-profile.env.XXXXXX")"
|
||||
printf 'LLAMA_PROFILE=%q\n' "$selected" >"$candidate"
|
||||
mv -T -- "$candidate" "$target"
|
||||
printf 'Profil %s gespeichert. Laufende Jobs bleiben unverändert.\n' "$selected"
|
||||
printf 'Erst nach Jobende: hermes-stack-llama-restart (siehe docs/WORKSTATION.md).\n'
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# Nur lesen: kein Modellstart, keine Inferenz, kein Neustart.
|
||||
set -Euo pipefail
|
||||
config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance"
|
||||
if [[ -r "$config_dir/config.env" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$config_dir/config.env"
|
||||
fi
|
||||
printf '\nGPU (Gesamtspeicher, belegt, frei in MiB):\n'
|
||||
nvidia-smi --query-gpu=name,memory.total,memory.used,memory.free,utilization.gpu,power.draw,temperature.gpu \
|
||||
--format=csv || true
|
||||
printf '\nDienstzustand:\n'
|
||||
systemctl --user show "${LLAMA_SERVICE:-llama-qwen.service}" \
|
||||
-p ActiveState -p SubState -p MainPID -p ExecMainStartTimestamp --no-pager || true
|
||||
printf '\nLetzte Speicher-/Leistungsdaten (keine vollständigen Prompts):\n'
|
||||
journalctl --user -u "${LLAMA_SERVICE:-llama-qwen.service}" -n 1000 --no-pager |
|
||||
rg 'offload|buffer size|n_ctx|prompt eval time|eval time|sleeping|waking' |
|
||||
tail -n 35 || true
|
||||
printf '\nDas ist eine Momentaufnahme, kein Spitzenlast-Benchmark.\n'
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
if [[ "${1:-}" != --jobs-finished || $# -ne 1 ]]; then
|
||||
printf 'Stoppt laufende Inferenz! Erst nach Jobende ausführen:\n %s --jobs-finished\n' "$0" >&2
|
||||
exit 2
|
||||
fi
|
||||
config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance"
|
||||
# shellcheck disable=SC1090
|
||||
source "$config_dir/config.env"
|
||||
exec 9>"${XDG_RUNTIME_DIR:-/tmp}/hermes-stack-hermes-update.lock"
|
||||
flock -n 9 || { printf 'Maintenance läuft; kein Neustart.\n' >&2; exit 1; }
|
||||
"$(dirname -- "${BASH_SOURCE[0]}")/hermes-stack-llama-run" --check
|
||||
systemctl --user restart "$LLAMA_SERVICE"
|
||||
printf 'Neustart ausgelöst. Modell kann noch laden; Logs und Inferenz prüfen.\n'
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance"
|
||||
CONFIG_FILE="${HERMES_LLAMA_CONFIG:-$config_dir/llama.env}"
|
||||
LLAMA_ARGS=()
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
mode=run
|
||||
profile_override=""
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
--check) mode=check ;;
|
||||
--print) mode=print ;;
|
||||
--profile) [[ $# -ge 2 ]] || exit 2; profile_override="$2"; shift ;;
|
||||
*) printf 'Aufruf: %s [--check|--print] [--profile NAME]\n' "$0" >&2; exit 2 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
if [[ -n "${LLAMA_PROFILE:-}" && -r "$config_dir/llama-profile.env" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$config_dir/llama-profile.env"
|
||||
fi
|
||||
[[ -z "$profile_override" ]] || LLAMA_PROFILE="$profile_override"
|
||||
|
||||
if [[ -z "${LLAMA_SERVER_BIN:-}" ]]; then
|
||||
LLAMA_SERVER_BIN="$HOME/.local/opt/llama.cpp/current/bin/llama-server"
|
||||
if [[ ! -x "$LLAMA_SERVER_BIN" ]]; then
|
||||
LLAMA_SERVER_BIN="$(command -v llama-server || true)"
|
||||
fi
|
||||
fi
|
||||
[[ -x "$LLAMA_SERVER_BIN" ]] || {
|
||||
printf 'llama-server nicht ausführbar: %s\n' "$LLAMA_SERVER_BIN" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
model_args=()
|
||||
if [[ -n "${LLAMA_MODEL:-}" && -n "${LLAMA_HF_REPO:-}" ]]; then
|
||||
printf 'Nur LLAMA_MODEL ODER LLAMA_HF_REPO setzen.\n' >&2
|
||||
exit 1
|
||||
elif [[ -n "${LLAMA_MODEL:-}" ]]; then
|
||||
[[ -r "$LLAMA_MODEL" ]] || { printf 'Modell nicht lesbar.\n' >&2; exit 1; }
|
||||
model_args=(--model "$LLAMA_MODEL")
|
||||
elif [[ -n "${LLAMA_HF_REPO:-}" ]]; then
|
||||
model_args=(--hf-repo "$LLAMA_HF_REPO")
|
||||
else
|
||||
printf 'LLAMA_MODEL oder LLAMA_HF_REPO fehlt.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "${LLAMA_MMPROJ:-}" ]]; then
|
||||
[[ -r "$LLAMA_MMPROJ" ]] || { printf 'Projektor nicht lesbar.\n' >&2; exit 1; }
|
||||
model_args+=(--mmproj "$LLAMA_MMPROJ")
|
||||
fi
|
||||
[[ -z "${LLAMA_ALIAS:-}" ]] || model_args+=(--alias "$LLAMA_ALIAS")
|
||||
|
||||
profile_args=()
|
||||
if [[ -n "${LLAMA_PROFILE:-}" ]]; then
|
||||
case "$LLAMA_PROFILE" in
|
||||
baseline) ctx=65536; margin=1536 ;;
|
||||
balanced) ctx=49152; margin=1536 ;;
|
||||
vram-plus) ctx=49152; margin=1280 ;;
|
||||
compact) ctx=32768; margin=1536 ;;
|
||||
*) printf 'Unbekanntes Profil: %s\n' "$LLAMA_PROFILE" >&2; exit 2 ;;
|
||||
esac
|
||||
profile_args=(
|
||||
--ctx-size "$ctx" --parallel 1 --jinja
|
||||
--reasoning auto --reasoning-budget 512 --reasoning-preserve
|
||||
--flash-attn auto --threads 6 --threads-batch 16
|
||||
--n-gpu-layers auto --fit on --fit-target "$margin"
|
||||
--cache-type-k q8_0 --cache-type-v q8_0
|
||||
--sleep-idle-seconds 300 --image-min-tokens 1024
|
||||
--cors-origins localhost --no-ui
|
||||
)
|
||||
# Alte vollständige LLAMA_ARGS nicht still mit einem Profil vermischen.
|
||||
if ((${#LLAMA_ARGS[@]})); then
|
||||
printf 'Bei Profilbetrieb LLAMA_ARGS leeren; eigene vollständige Args nur ohne LLAMA_PROFILE.\n' >&2
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
args=("${model_args[@]}" --host "${LLAMA_HOST:-127.0.0.1}" --port "${LLAMA_PORT:-8080}"
|
||||
"${profile_args[@]}" "${LLAMA_ARGS[@]}")
|
||||
if [[ "$mode" == print ]]; then
|
||||
printf '%q ' "$LLAMA_SERVER_BIN" "${args[@]}"
|
||||
printf '\n'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Kein Download/Modellstart: nur prüfen, ob die installierte Version die Namen kennt.
|
||||
help_text="$(timeout 30 "$LLAMA_SERVER_BIN" --help 2>&1)" || {
|
||||
printf 'llama-server --help fehlgeschlagen.\n' >&2; exit 1;
|
||||
}
|
||||
for arg in "${args[@]}"; do
|
||||
[[ "$arg" == --* ]] || continue
|
||||
option="${arg%%=*}"
|
||||
if ! grep -Eq -- "(^|[[:space:],])${option}([[:space:],=]|$)" <<<"$help_text"; then
|
||||
printf 'Die installierte llama.cpp-Version kennt %s nicht. Kein automatisches Weglassen.\n' "$option" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
if [[ "$mode" == check ]]; then
|
||||
printf 'Konfiguration und Optionsnamen OK; GPU/Modell/Optionswerte noch nicht laufzeitgetestet.\n'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exec "$LLAMA_SERVER_BIN" "${args[@]}"
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Euo pipefail
|
||||
|
||||
CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance/config.env"
|
||||
|
||||
log() {
|
||||
printf '%s\n' "$*" | systemd-cat -t hermes-stack-maintenance 2>/dev/null || true
|
||||
}
|
||||
|
||||
text="${*:-}"
|
||||
if [[ -z "$text" ]]; then
|
||||
printf 'Verwendung: %s TEXT\n' "${0##*/}" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
log "$text"
|
||||
|
||||
if [[ ! -r "$CONFIG_FILE" ]]; then
|
||||
log "Benachrichtigung übersprungen: $CONFIG_FILE ist nicht lesbar."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
set +u
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE" || {
|
||||
set -u
|
||||
log "Benachrichtigung übersprungen: Maintenance-Konfiguration ist ungültig."
|
||||
exit 0
|
||||
}
|
||||
|
||||
telegram_notify="${TELEGRAM_NOTIFY:-0}"
|
||||
signal_notify="${SIGNAL_NOTIFY:-0}"
|
||||
|
||||
if [[ -r "${HERMES_ENV:-}" ]] &&
|
||||
[[ "$telegram_notify" == 1 || "$signal_notify" == 1 ]]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$HERMES_ENV" || true
|
||||
set +a
|
||||
fi
|
||||
set -u
|
||||
|
||||
if [[ "$telegram_notify" == 1 &&
|
||||
-n "${TELEGRAM_BOT_TOKEN:-}" &&
|
||||
-n "${TELEGRAM_HOME_CHANNEL:-}" ]]; then
|
||||
if ! curl --fail --silent --show-error --max-time 15 \
|
||||
--request POST \
|
||||
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||
--data-urlencode "chat_id=${TELEGRAM_HOME_CHANNEL}" \
|
||||
--data-urlencode "text=${text}" >/dev/null; then
|
||||
log "Telegram-Benachrichtigung konnte nicht zugestellt werden."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$signal_notify" == 1 &&
|
||||
-n "${SIGNAL_HTTP_URL:-}" &&
|
||||
-n "${SIGNAL_ACCOUNT:-}" &&
|
||||
-n "${SIGNAL_HOME_CHANNEL:-}" ]]; then
|
||||
if [[ "$SIGNAL_HOME_CHANNEL" == group:* ]]; then
|
||||
signal_payload="$(
|
||||
jq -nc --arg account "$SIGNAL_ACCOUNT" --arg message "$text" \
|
||||
--arg group_id "${SIGNAL_HOME_CHANNEL#group:}" \
|
||||
'{jsonrpc:"2.0",method:"send",params:{account:$account,message:$message,groupId:$group_id},id:"hermes-maintenance"}'
|
||||
)" || signal_payload=""
|
||||
else
|
||||
signal_payload="$(
|
||||
jq -nc --arg account "$SIGNAL_ACCOUNT" --arg message "$text" \
|
||||
--arg recipient "$SIGNAL_HOME_CHANNEL" \
|
||||
'{jsonrpc:"2.0",method:"send",params:{account:$account,message:$message,recipient:[$recipient]},id:"hermes-maintenance"}'
|
||||
)" || signal_payload=""
|
||||
fi
|
||||
|
||||
if [[ -z "$signal_payload" ]] ||
|
||||
! curl --fail --silent --show-error --max-time 30 \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-binary "$signal_payload" \
|
||||
"${SIGNAL_HTTP_URL%/}/api/v1/rpc" |
|
||||
jq -e '((.error // null) == null) and (.result != null)' >/dev/null; then
|
||||
log "Signal-Benachrichtigung konnte nicht zugestellt werden."
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Euo pipefail
|
||||
|
||||
CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance/config.env"
|
||||
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/hermes-stack-maintenance"
|
||||
NOTIFIER="$HOME/.local/libexec/hermes-stack-notify"
|
||||
mkdir -p "$STATE_DIR"
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
set +u
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$HERMES_ENV" || true
|
||||
set +a
|
||||
set -u
|
||||
|
||||
state_file="$STATE_DIR/signal-health-state"
|
||||
signal_url="${SIGNAL_HTTP_URL:-}"
|
||||
[[ -n "$signal_url" ]] || exit 0
|
||||
|
||||
check_signal() {
|
||||
local status
|
||||
status="$(curl --silent --output /dev/null --max-time 5 \
|
||||
--write-out '%{http_code}' "${signal_url%/}/api/v1/check" || true)"
|
||||
[[ "$status" =~ ^2[0-9][0-9]$ ]]
|
||||
}
|
||||
|
||||
if check_signal; then
|
||||
current=healthy
|
||||
else
|
||||
systemctl --user restart "$SIGNAL_SERVICE" || true
|
||||
sleep 5
|
||||
check_signal && current=healthy || current=unhealthy
|
||||
fi
|
||||
|
||||
previous="$(cat "$state_file" 2>/dev/null || true)"
|
||||
if [[ "$current" == unhealthy && "$previous" != unhealthy ]]; then
|
||||
"$NOTIFIER" "Hermes-Stack: signal-cli ist nicht erreichbar; Neustart wurde versucht." || true
|
||||
elif [[ "$current" == healthy && "$previous" == unhealthy ]]; then
|
||||
"$NOTIFIER" "Hermes-Stack: signal-cli ist wieder erreichbar." || true
|
||||
fi
|
||||
|
||||
printf '%s\n' "$current" >"$state_file"
|
||||
[[ "$current" == healthy ]]
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance/signal-cli.env"
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
[[ -x "$SIGNAL_CLI_BIN" ]] || {
|
||||
printf 'signal-cli nicht ausführbar: %s\n' "$SIGNAL_CLI_BIN" >&2
|
||||
exit 1
|
||||
}
|
||||
[[ -n "$SIGNAL_ACCOUNT" ]] || {
|
||||
printf 'SIGNAL_ACCOUNT ist nicht konfiguriert.\n' >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
args=(--account "$SIGNAL_ACCOUNT")
|
||||
if [[ -n "${SIGNAL_CONFIG_DIR:-}" ]]; then
|
||||
mkdir -p "$SIGNAL_CONFIG_DIR"
|
||||
args=(--config "$SIGNAL_CONFIG_DIR" "${args[@]}")
|
||||
fi
|
||||
|
||||
exec "$SIGNAL_CLI_BIN" "${args[@]}" daemon \
|
||||
--http "${SIGNAL_HTTP_BIND:-127.0.0.1:8090}"
|
||||
Executable
+108
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance/config.env"
|
||||
SIGNAL_CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/hermes-stack-maintenance/signal-cli.env"
|
||||
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/hermes-stack-maintenance"
|
||||
NOTIFIER="$HOME/.local/libexec/hermes-stack-notify"
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
# shellcheck disable=SC1090
|
||||
source "$SIGNAL_CONFIG_FILE"
|
||||
|
||||
version="${1:-}"
|
||||
if [[ -z "$version" ]]; then
|
||||
latest_url="$(curl -LsS --max-time 30 -o /dev/null -w '%{url_effective}' \
|
||||
https://github.com/AsamK/signal-cli/releases/latest)"
|
||||
version="${latest_url##*/v}"
|
||||
fi
|
||||
|
||||
[[ "$version" =~ ^[0-9]+(\.[0-9]+)+$ ]] || {
|
||||
printf 'Ungültige signal-cli-Version: %s\n' "$version" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
old_version="unbekannt"
|
||||
if [[ -x "$SIGNAL_CLI_BIN" ]]; then
|
||||
old_version="$("$SIGNAL_CLI_BIN" --version 2>/dev/null |
|
||||
grep -Eo '[0-9]+(\.[0-9]+)+' | head -n1 || true)"
|
||||
fi
|
||||
[[ -n "$old_version" ]] || old_version="unbekannt"
|
||||
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf -- "$tmp_dir"' EXIT
|
||||
archive="$tmp_dir/signal-cli.tar.gz"
|
||||
url="https://github.com/AsamK/signal-cli/releases/download/v${version}/signal-cli-${version}.tar.gz"
|
||||
|
||||
curl --fail --location --show-error "$url" -o "$archive"
|
||||
tar -xzf "$archive" -C "$tmp_dir"
|
||||
binary="$(find "$tmp_dir" -type f -path '*/bin/signal-cli' -print -quit)"
|
||||
[[ -n "$binary" ]] || {
|
||||
printf 'signal-cli-Binary wurde im Archiv nicht gefunden.\n' >&2
|
||||
exit 1
|
||||
}
|
||||
source_root="$(dirname "$(dirname "$binary")")"
|
||||
|
||||
timestamp="$(date +%Y%m%d-%H%M%S)"
|
||||
backup_dir="$STATE_DIR/backups/signal-cli/$timestamp"
|
||||
mkdir -p "$backup_dir"
|
||||
|
||||
old_target=""
|
||||
if [[ -e "$SIGNAL_CLI_BIN" || -L "$SIGNAL_CLI_BIN" ]]; then
|
||||
old_target="$(readlink -f "$SIGNAL_CLI_BIN" 2>/dev/null || true)"
|
||||
if [[ -n "$old_target" ]]; then
|
||||
old_root="$(dirname "$(dirname "$old_target")")"
|
||||
[[ -d "$old_root" ]] && cp -a "$old_root" "$backup_dir/program"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -d "${SIGNAL_CONFIG_DIR:-}" ]]; then
|
||||
tar --zstd -cf "$backup_dir/signal-data.tar.zst" \
|
||||
-C "$(dirname "$SIGNAL_CONFIG_DIR")" "$(basename "$SIGNAL_CONFIG_DIR")"
|
||||
fi
|
||||
|
||||
was_active=0
|
||||
systemctl --user is-active --quiet "$SIGNAL_SERVICE" && was_active=1
|
||||
((was_active == 0)) || systemctl --user stop "$SIGNAL_SERVICE"
|
||||
|
||||
target="$HOME/.local/opt/signal-cli-$version"
|
||||
mkdir -p "$target" "$(dirname "$SIGNAL_CLI_BIN")"
|
||||
cp -a "$source_root/." "$target/"
|
||||
ln -sfn "$target/bin/signal-cli" "$SIGNAL_CLI_BIN"
|
||||
|
||||
if ((was_active)); then
|
||||
systemctl --user restart "$SIGNAL_SERVICE"
|
||||
sleep 5
|
||||
|
||||
set +u
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$HERMES_ENV" || true
|
||||
set +a
|
||||
set -u
|
||||
|
||||
signal_http_url="${SIGNAL_HTTP_URL:-}"
|
||||
status=""
|
||||
if [[ -n "$signal_http_url" ]]; then
|
||||
status="$(curl --silent --output /dev/null --max-time 10 \
|
||||
--write-out '%{http_code}' "${signal_http_url%/}/api/v1/check" || true)"
|
||||
fi
|
||||
if [[ ! "$status" =~ ^2[0-9][0-9]$ ]]; then
|
||||
systemctl --user stop "$SIGNAL_SERVICE" || true
|
||||
if [[ -n "$old_target" ]]; then
|
||||
ln -sfn "$old_target" "$SIGNAL_CLI_BIN"
|
||||
systemctl --user restart "$SIGNAL_SERVICE" || true
|
||||
fi
|
||||
"$NOTIFIER" "signal-cli-Update auf $version fehlgeschlagen; Binary wurde auf $old_version zurückgestellt. Backup: $backup_dir" || true
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mapfile -t old_backups < <(
|
||||
find "$STATE_DIR/backups/signal-cli" -mindepth 1 -maxdepth 1 -type d \
|
||||
-printf '%T@ %p\n' | sort -nr | tail -n +4 | cut -d' ' -f2-
|
||||
)
|
||||
((${#old_backups[@]} == 0)) || rm -rf -- "${old_backups[@]}"
|
||||
|
||||
"$NOTIFIER" "signal-cli wurde erfolgreich aktualisiert: $old_version -> $version. Daten- und Binary-Backup: $backup_dir" || true
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Euo pipefail
|
||||
|
||||
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/hermes-stack-maintenance"
|
||||
NOTIFIER="$HOME/.local/libexec/hermes-stack-notify"
|
||||
mkdir -p "$STATE_DIR"
|
||||
|
||||
command -v signal-cli >/dev/null 2>&1 || exit 0
|
||||
installed="$(signal-cli --version 2>/dev/null | grep -Eo '[0-9]+(\.[0-9]+)+' | head -n1)"
|
||||
latest_url="$(curl -LsS --max-time 30 -o /dev/null -w '%{url_effective}' \
|
||||
https://github.com/AsamK/signal-cli/releases/latest || true)"
|
||||
latest="${latest_url##*/v}"
|
||||
|
||||
[[ "$installed" =~ ^[0-9]+(\.[0-9]+)+$ ]] || exit 1
|
||||
[[ "$latest" =~ ^[0-9]+(\.[0-9]+)+$ ]] || exit 1
|
||||
|
||||
if [[ "$(printf '%s\n%s\n' "$installed" "$latest" | sort -V | tail -n1)" == "$latest" &&
|
||||
"$installed" != "$latest" ]]; then
|
||||
"$NOTIFIER" "signal-cli-Update verfügbar: $installed -> $latest. Manuell ausführen: ~/.local/libexec/hermes-stack-signal-update $latest" || true
|
||||
fi
|
||||
Reference in new issue
Block a user