Fix Athena recovery and installation consistency

This commit is contained in:
Mikei386
2026-09-01 10:19:13 +02:00
parent 7e14da77f9
commit e30f4f024c
10 changed files with 179 additions and 27 deletions
+6 -6
View File
@@ -4,7 +4,7 @@ MODEL_DIR=/data/models
ROUTER_API_KEY=GENERATED_BY_INSTALLER ROUTER_API_KEY=GENERATED_BY_INSTALLER
CONTROLLER_TOKEN=GENERATED_BY_INSTALLER CONTROLLER_TOKEN=GENERATED_BY_INSTALLER
FLUX_MODEL_DIR=/data/models/FLUX.2-klein-4B FLUX_MODEL_DIR=/data/models/FLUX.2-klein-4B
IMAGE_GPU_DEVICES=1 IMAGE_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe
PIPER_TTS_VERSION=1.6.0 PIPER_TTS_VERSION=1.6.0
PIPER_VOICE=de_DE-thorsten-high PIPER_VOICE=de_DE-thorsten-high
XTTS_IMAGE=ghcr.io/coqui-ai/xtts-streaming-server:latest-cuda121@sha256:f7fb3b1f9d4bc88af94da1b5959d8002f1e0b003c97557164034eb8a29f01b90 XTTS_IMAGE=ghcr.io/coqui-ai/xtts-streaming-server:latest-cuda121@sha256:f7fb3b1f9d4bc88af94da1b5959d8002f1e0b003c97557164034eb8a29f01b90
@@ -36,14 +36,14 @@ ULTRA_UBATCH_SIZE=128
UNCENSORED_CONTEXT=80000 UNCENSORED_CONTEXT=80000
UNCENSORED_BATCH_SIZE=2048 UNCENSORED_BATCH_SIZE=2048
UNCENSORED_UBATCH_SIZE=128 UNCENSORED_UBATCH_SIZE=128
FAST_GPU_DEVICES=0,1 FAST_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe,GPU-4834d9d7-5b61-3004-1fb3-4ae49d482d4b
MEDIUM_GPU_DEVICES=0,1 MEDIUM_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe,GPU-4834d9d7-5b61-3004-1fb3-4ae49d482d4b
MEDIUM_TENSOR_SPLIT=85,15 MEDIUM_TENSOR_SPLIT=85,15
LARGE_GPU_DEVICES=0,1 LARGE_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe,GPU-4834d9d7-5b61-3004-1fb3-4ae49d482d4b
LARGE_TENSOR_SPLIT=86,14 LARGE_TENSOR_SPLIT=86,14
ULTRA_GPU_DEVICES=0,1 ULTRA_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe,GPU-4834d9d7-5b61-3004-1fb3-4ae49d482d4b
ULTRA_TENSOR_SPLIT=80,20 ULTRA_TENSOR_SPLIT=80,20
UNCENSORED_GPU_DEVICES=0,1 UNCENSORED_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe,GPU-4834d9d7-5b61-3004-1fb3-4ae49d482d4b
UNCENSORED_TENSOR_SPLIT=90,10 UNCENSORED_TENSOR_SPLIT=90,10
UNCENSORED_MTP_MAX=2 UNCENSORED_MTP_MAX=2
LLAMA_THREADS=6 LLAMA_THREADS=6
+1
View File
@@ -109,6 +109,7 @@ Nach einer frischen Debian-Installation und erneut eingehängtem `/data`:
```bash ```bash
sudo ./install.sh --config /root/mike-ai-install.env sudo ./install.sh --config /root/mike-ai-install.env
sudo ./restore.sh --check /data/docker-backups/athena-latest.tar.gz
sudo ./restore.sh /data/docker-backups/athena-latest.tar.gz sudo ./restore.sh /data/docker-backups/athena-latest.tar.gz
sudo ./smoke-test.sh sudo ./smoke-test.sh
``` ```
+6 -3
View File
@@ -12,9 +12,12 @@ INSTALL_NVIDIA_DRIVER=true
# festlegen (z. B. 610). Leer lassen, um dem aktuellen stabilen Zweig zu folgen. # festlegen (z. B. 610). Leer lassen, um dem aktuellen stabilen Zweig zu folgen.
NVIDIA_DRIVER_BRANCH= NVIDIA_DRIVER_BRANCH=
NVIDIA_MIN_DRIVER_MAJOR=570 NVIDIA_MIN_DRIVER_MAJOR=570
TEXT_GPU_DEVICES=0 # Stable UUID order: CUDA0 = RTX 5080, CUDA1 = RTX 3060. Positional host
SECONDARY_GPU_DEVICES=1 # indices are intentionally avoided because nvidia-smi currently enumerates
IMAGE_GPU_DEVICES=1 # the cards in the opposite order.
TEXT_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe
SECONDARY_GPU_DEVICES=GPU-4834d9d7-5b61-3004-1fb3-4ae49d482d4b
IMAGE_GPU_DEVICES=GPU-8ad38c6c-5a01-9d8e-1dfa-ed662ad78fbe
FLUX_MODEL_DIR=/data/models/FLUX.2-klein-4B FLUX_MODEL_DIR=/data/models/FLUX.2-klein-4B
# Headless remote reachability. Firmware power-loss recovery is configured # Headless remote reachability. Firmware power-loss recovery is configured
+82
View File
@@ -0,0 +1,82 @@
import io
import subprocess
import tarfile
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
class RecoveryScriptTests(unittest.TestCase):
def _archive(self, members: list[str]) -> Path:
tmp = tempfile.NamedTemporaryFile(suffix=".tar.gz", delete=False)
tmp.close()
archive = Path(tmp.name)
with tarfile.open(archive, "w:gz") as handle:
for name in members:
payload = b"test\n"
info = tarfile.TarInfo(name)
info.size = len(payload)
handle.addfile(info, io.BytesIO(payload))
self.addCleanup(archive.unlink, missing_ok=True)
return archive
def test_current_backup_layout_is_accepted(self) -> None:
archive = self._archive([
"/backup/etc-mike-ai/stack.env",
"/backup/volumes/router-state/state.json",
])
result = subprocess.run(
[str(ROOT / "restore.sh"), "--check", str(archive)],
check=False,
capture_output=True,
text=True,
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("ATHENA_BACKUP_CHECK_OK", result.stdout)
def test_unexpected_tree_is_rejected(self) -> None:
archive = self._archive([
"/backup/etc-mike-ai/stack.env",
"/backup/volumes/router-state/state.json",
"/etc/shadow",
])
result = subprocess.run(
[str(ROOT / "restore.sh"), "--check", str(archive)],
check=False,
capture_output=True,
text=True,
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("Unerwarteter Pfad", result.stderr)
def test_backup_prefix_extracts_to_restore_layout(self) -> None:
archive = self._archive([
"/backup/etc-mike-ai/stack.env",
"/backup/volumes/router-state/state.json",
])
with tempfile.TemporaryDirectory() as target:
result = subprocess.run(
[
"tar", "-xzf", str(archive), "-C", target,
],
check=False,
capture_output=True,
text=True,
)
self.assertEqual(result.returncode, 0, result.stderr)
root = Path(target, "backup")
self.assertTrue(Path(root, "etc-mike-ai", "stack.env").is_file())
self.assertTrue(Path(root, "volumes", "router-state", "state.json").is_file())
def test_installer_uses_current_profiles(self) -> None:
installer = (ROOT / "install.sh").read_text(encoding="utf-8")
self.assertNotIn("llama-experimental", installer)
for profile in ("fast", "medium", "large", "ultra", "uncensored"):
self.assertIn(f"llama-{profile}", installer)
if __name__ == "__main__":
unittest.main()
+4 -2
View File
@@ -1,6 +1,6 @@
# Aktueller temporärer Laufzustand # Aktueller produktiver Laufzustand
Stand: 31. August 2026 Stand: 1. September 2026
## Produktive llama.cpp-Runtime ## Produktive llama.cpp-Runtime
@@ -20,6 +20,8 @@ beendet.
Dabei gilt ausdrücklich: Dabei gilt ausdrücklich:
- Der Gesamtkontext bleibt bei **160.000 Tokens**. - Der Gesamtkontext bleibt bei **160.000 Tokens**.
- Das Profil verwendet wieder **einen Slot**. Die getestete Zwei-Slot-Variante
ist nicht produktiv.
- **MTP / Speculative Decoding bleibt aktiviert**; MTP wurde nicht entfernt. - **MTP / Speculative Decoding bleibt aktiviert**; MTP wurde nicht entfernt.
- Modell, Quantisierung, GPU-Aufteilung und KV-Cache-Quantisierung bleiben - Modell, Quantisierung, GPU-Aufteilung und KV-Cache-Quantisierung bleiben
unverändert. unverändert.
+5 -2
View File
@@ -21,9 +21,12 @@ Full-context results with the selected settings:
The larger logical batches 3072 and 4096 did not improve Medium at ubatch 128. The original one-slot benchmark selected 2048 / 128 at 90:10. The larger logical batches 3072 and 4096 did not improve Medium at ubatch 128. The original one-slot benchmark selected 2048 / 128 at 90:10.
## Medium two-slot benchmark ## Historischer Medium-Zwei-Slot-Test
Medium now uses two parallel slots with unified KV, so both chats dynamically share one total 160K-token pool. The model weights remain loaded only once. To fit the additional scheduler buffers, the production GPU split is 85:15 while batch / ubatch remains 2048 / 128. This was an A/B candidate, not the current production configuration. Production
was returned to **one slot** because concurrent Hermes requests did not behave
reliably enough. The 85:15 GPU split and batch / ubatch 2048 / 128 remain in
production because they also work with the single-slot profile.
Identical fresh 100,297-token prompt with a deterministic 256-token completion: Identical fresh 100,297-token prompt with a deterministic 256-token completion:
+7 -1
View File
@@ -33,10 +33,16 @@ Router, Modelle oder Dashboard-Daten zu verlieren.
4. Letztes Datenarchiv einspielen: 4. Letztes Datenarchiv einspielen:
```bash ```bash
sudo ./restore.sh --check /data/docker-backups/athena-latest.tar.gz
sudo ./restore.sh /data/docker-backups/athena-latest.tar.gz sudo ./restore.sh /data/docker-backups/athena-latest.tar.gz
sudo ./smoke-test.sh sudo ./smoke-test.sh
``` ```
`--check` liest das komplette gzip-Archiv und prüft dessen sichere
`/backup`-Struktur sowie die benötigten Konfigurations- und Volume-Bäume, ohne
Container oder Dateien zu verändern. Der reguläre Restore extrahiert und
verwendet anschließend ausschließlich diesen einen geprüften Baum.
Das Restore verändert weder SSH noch LAN, WireGuard, Kernel, Partitionen oder Das Restore verändert weder SSH noch LAN, WireGuard, Kernel, Partitionen oder
Mounts. Mounts.
@@ -58,7 +64,7 @@ Appdata-Restore plus Neuerstellung über die jeweilige Template-XML.
```bash ```bash
docker compose --env-file /etc/mike-ai/stack.env ps docker compose --env-file /etc/mike-ai/stack.env ps
test -s /data/docker-backups/athena-latest.tar.gz sudo ./restore.sh --check /data/docker-backups/athena-latest.tar.gz
curl -fsS http://192.168.1.212:8099/health curl -fsS http://192.168.1.212:8099/health
sudo ./smoke-test.sh sudo ./smoke-test.sh
``` ```
+21 -1
View File
@@ -42,6 +42,9 @@ required=(AI_HOSTNAME ADMIN_USER MODEL_DIR FAST_MODEL_FILE
FAST_MODEL_URL FAST_MODEL_SHA256 MEDIUM_MODEL_FILE MEDIUM_MODEL_URL FAST_MODEL_URL FAST_MODEL_SHA256 MEDIUM_MODEL_FILE MEDIUM_MODEL_URL
MEDIUM_MODEL_SHA256 LARGE_MODEL_FILE LARGE_MODEL_URL LARGE_MODEL_SHA256 MEDIUM_MODEL_SHA256 LARGE_MODEL_FILE LARGE_MODEL_URL LARGE_MODEL_SHA256
ULTRA_MODEL_FILE ULTRA_MODEL_URL ULTRA_MODEL_SHA256 ULTRA_MODEL_FILE ULTRA_MODEL_URL ULTRA_MODEL_SHA256
UNCENSORED_MODEL_FILE UNCENSORED_MODEL_URL UNCENSORED_MODEL_SHA256
UNCENSORED_PROJECTOR_FILE UNCENSORED_PROJECTOR_URL
UNCENSORED_PROJECTOR_SHA256
VISION_PROJECTOR_FILE VISION_PROJECTOR_URL VISION_PROJECTOR_SHA256) VISION_PROJECTOR_FILE VISION_PROJECTOR_URL VISION_PROJECTOR_SHA256)
for name in "${required[@]}"; do for name in "${required[@]}"; do
[[ -n "${!name:-}" ]] || die "Pflichtwert $name fehlt." [[ -n "${!name:-}" ]] || die "Pflichtwert $name fehlt."
@@ -322,10 +325,25 @@ UNCENSORED_MODEL_FILE=$UNCENSORED_MODEL_FILE
UNCENSORED_PROJECTOR_FILE=$UNCENSORED_PROJECTOR_FILE UNCENSORED_PROJECTOR_FILE=$UNCENSORED_PROJECTOR_FILE
VISION_PROJECTOR_FILE=$VISION_PROJECTOR_FILE VISION_PROJECTOR_FILE=$VISION_PROJECTOR_FILE
FAST_CONTEXT=${FAST_CONTEXT:-76800} FAST_CONTEXT=${FAST_CONTEXT:-76800}
FAST_BATCH_SIZE=${FAST_BATCH_SIZE:-64}
FAST_UBATCH_SIZE=${FAST_UBATCH_SIZE:-32}
FAST_PARALLEL_SLOTS=${FAST_PARALLEL_SLOTS:-1}
MEDIUM_CONTEXT=${MEDIUM_CONTEXT:-160000} MEDIUM_CONTEXT=${MEDIUM_CONTEXT:-160000}
MEDIUM_BATCH_SIZE=${MEDIUM_BATCH_SIZE:-2048}
MEDIUM_UBATCH_SIZE=${MEDIUM_UBATCH_SIZE:-128}
MEDIUM_PARALLEL_SLOTS=${MEDIUM_PARALLEL_SLOTS:-1}
LARGE_CONTEXT=${LARGE_CONTEXT:-192000} LARGE_CONTEXT=${LARGE_CONTEXT:-192000}
LARGE_BATCH_SIZE=${LARGE_BATCH_SIZE:-2048}
LARGE_UBATCH_SIZE=${LARGE_UBATCH_SIZE:-128}
LARGE_PARALLEL_SLOTS=${LARGE_PARALLEL_SLOTS:-1}
ULTRA_CONTEXT=${ULTRA_CONTEXT:-262144} ULTRA_CONTEXT=${ULTRA_CONTEXT:-262144}
ULTRA_BATCH_SIZE=${ULTRA_BATCH_SIZE:-2048}
ULTRA_UBATCH_SIZE=${ULTRA_UBATCH_SIZE:-128}
ULTRA_PARALLEL_SLOTS=${ULTRA_PARALLEL_SLOTS:-1}
UNCENSORED_CONTEXT=${UNCENSORED_CONTEXT:-80000} UNCENSORED_CONTEXT=${UNCENSORED_CONTEXT:-80000}
UNCENSORED_BATCH_SIZE=${UNCENSORED_BATCH_SIZE:-2048}
UNCENSORED_UBATCH_SIZE=${UNCENSORED_UBATCH_SIZE:-128}
UNCENSORED_PARALLEL_SLOTS=${UNCENSORED_PARALLEL_SLOTS:-1}
FAST_GPU_DEVICES=${TEXT_GPU_DEVICES:-0}${SECONDARY_GPU_DEVICES:+,$SECONDARY_GPU_DEVICES} FAST_GPU_DEVICES=${TEXT_GPU_DEVICES:-0}${SECONDARY_GPU_DEVICES:+,$SECONDARY_GPU_DEVICES}
MEDIUM_GPU_DEVICES=${TEXT_GPU_DEVICES:-0}${SECONDARY_GPU_DEVICES:+,$SECONDARY_GPU_DEVICES} MEDIUM_GPU_DEVICES=${TEXT_GPU_DEVICES:-0}${SECONDARY_GPU_DEVICES:+,$SECONDARY_GPU_DEVICES}
MEDIUM_TENSOR_SPLIT=${MEDIUM_TENSOR_SPLIT:-90,10} MEDIUM_TENSOR_SPLIT=${MEDIUM_TENSOR_SPLIT:-90,10}
@@ -340,6 +358,8 @@ IMAGE_GPU_DEVICES=${IMAGE_GPU_DEVICES:-${TEXT_GPU_DEVICES:-0}}
FLUX_MODEL_DIR=${FLUX_MODEL_DIR:-/data/models/FLUX.2-klein-4B} FLUX_MODEL_DIR=${FLUX_MODEL_DIR:-/data/models/FLUX.2-klein-4B}
LLAMA_THREADS=${LLAMA_THREADS:-6} LLAMA_THREADS=${LLAMA_THREADS:-6}
LLAMA_THREADS_BATCH=${LLAMA_THREADS_BATCH:-6} LLAMA_THREADS_BATCH=${LLAMA_THREADS_BATCH:-6}
LLAMA_CACHE_RAM_MIB=${LLAMA_CACHE_RAM_MIB:-24576}
DEFAULT_REASONING_EFFORT=${DEFAULT_REASONING_EFFORT:-off}
EOF EOF
chmod 0600 $SECRETS_DIR/stack.env chmod 0600 $SECRETS_DIR/stack.env
} }
@@ -468,7 +488,7 @@ build_and_start() {
# Portable MCPs and Hermes live on Unraid and are restored through Appdata. # Portable MCPs and Hermes live on Unraid and are restored through Appdata.
"$STACK_DIR/platform/mcp/install-tools.sh" "$STACK_DIR/platform/mcp/install-tools.sh"
docker compose --env-file "$SECRETS_DIR/stack.env" --profile inference create \ docker compose --env-file "$SECRETS_DIR/stack.env" --profile inference create \
llama-fast llama-medium llama-large llama-ultra llama-experimental llama-fast llama-medium llama-large llama-ultra llama-uncensored
docker compose --env-file "$SECRETS_DIR/stack.env" --profile image create image-worker docker compose --env-file "$SECRETS_DIR/stack.env" --profile image create image-worker
docker compose --env-file "$SECRETS_DIR/stack.env" up -d --build \ docker compose --env-file "$SECRETS_DIR/stack.env" up -d --build \
wireguard-gateway xtts piper tts-gateway profile-controller router llama-dashboard backup wireguard-gateway xtts piper tts-gateway profile-controller router llama-dashboard backup
+45 -11
View File
@@ -4,23 +4,57 @@ set -Eeuo pipefail
umask 077 umask 077
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CHECK_ONLY=false
if [[ ${1:-} == --check ]]; then
CHECK_ONLY=true
shift
fi
ARCHIVE=${1:-/data/docker-backups/athena-latest.tar.gz} ARCHIVE=${1:-/data/docker-backups/athena-latest.tar.gz}
die() { printf 'FEHLER: %s\n' "$*" >&2; exit 1; } die() { printf 'FEHLER: %s\n' "$*" >&2; exit 1; }
[[ $EUID -eq 0 ]] || die "Bitte als root ausführen."
[[ -s $ARCHIVE ]] || die "Backup fehlt: $ARCHIVE" [[ -s $ARCHIVE ]] || die "Backup fehlt: $ARCHIVE"
validate_archive() {
local listing normalized entry required
gzip -t "$ARCHIVE" || die "Backup ist beschädigt: $ARCHIVE"
listing=$(tar -tzf "$ARCHIVE") || die "Backup-Inhalt kann nicht gelesen werden."
# docker-volume-backup stores the mounted sources below /backup. GNU tar
# removes the leading slash while extracting; accept exactly that tree and
# reject every unrelated or traversing member before extracting as root.
while IFS= read -r entry; do
normalized=${entry#./}
normalized=${normalized#/}
[[ $normalized == backup || $normalized == backup/* ]] || \
die "Unerwarteter Pfad im Backup: $entry"
[[ /$normalized/ != */../* ]] || die "Unsicherer Pfad im Backup: $entry"
done <<<"$listing"
for required in backup/etc-mike-ai backup/volumes; do
grep -Eq "^/?${required}(/|$)" <<<"$listing" || \
die "Backup enthält $required nicht."
done
}
validate_archive
if [[ $CHECK_ONLY == true ]]; then
printf 'ATHENA_BACKUP_CHECK_OK %s\n' "$ARCHIVE"
exit 0
fi
[[ $EUID -eq 0 ]] || die "Bitte als root ausführen."
command -v docker >/dev/null || die "Docker fehlt. Zuerst ./install.sh ausführen." command -v docker >/dev/null || die "Docker fehlt. Zuerst ./install.sh ausführen."
work=$(mktemp -d /tmp/athena-restore.XXXXXX) work=$(mktemp -d /tmp/athena-restore.XXXXXX)
trap 'rm -rf "$work"' EXIT trap 'rm -rf "$work"' EXIT
# Refuse absolute paths and parent traversal before extracting as root. # GNU tar removes the leading slash and extracts the single validated tree as
if tar -tzf "$ARCHIVE" | grep -Eq '(^/|(^|/)\.\.(/|$))'; then # $work/backup. Keeping that root explicit avoids platform-dependent
die "Unsichere Pfade im Backup." # --strip-components behaviour between GNU tar and bsdtar.
fi
tar -xzf "$ARCHIVE" -C "$work" tar -xzf "$ARCHIVE" -C "$work"
[[ -d $work/etc-mike-ai ]] || die "Backup enthält etc-mike-ai nicht." backup_root=$work/backup
[[ -d $work/volumes ]] || die "Backup enthält keine Docker-Volumes." [[ -d $backup_root/etc-mike-ai ]] || die "Backup enthält etc-mike-ai nicht."
[[ -d $backup_root/volumes ]] || die "Backup enthält keine Docker-Volumes."
# Stop only users of the restored volumes. WireGuard, SSH and networking stay up. # Stop only users of the restored volumes. WireGuard, SSH and networking stay up.
for container in mike-ai-router mike-ai-profile-controller mike-ai-piper; do for container in mike-ai-router mike-ai-profile-controller mike-ai-piper; do
@@ -30,7 +64,7 @@ for container in mike-ai-router mike-ai-profile-controller mike-ai-piper; do
done done
install -d -m 0700 /etc/mike-ai install -d -m 0700 /etc/mike-ai
rsync -a --delete "$work/etc-mike-ai/" /etc/mike-ai/ rsync -a --delete "$backup_root/etc-mike-ai/" /etc/mike-ai/
restore_volume() { restore_volume() {
local volume=$1 source=$2 mountpoint local volume=$1 source=$2 mountpoint
@@ -41,9 +75,9 @@ restore_volume() {
rsync -a --delete "$source/" "$mountpoint/" rsync -a --delete "$source/" "$mountpoint/"
} }
restore_volume mike-ai_piper-data "$work/volumes/piper-data" restore_volume mike-ai_piper-data "$backup_root/volumes/piper-data"
restore_volume mike-ai_router-state "$work/volumes/router-state" restore_volume mike-ai_router-state "$backup_root/volumes/router-state"
restore_volume mike-ai_router-images "$work/volumes/router-images" restore_volume mike-ai_router-images "$backup_root/volumes/router-images"
cd "$ROOT_DIR" cd "$ROOT_DIR"
./manage.sh deploy core ./manage.sh deploy core
+2 -1
View File
@@ -83,6 +83,7 @@ pass "Router-Liveness und OpenAI-Modellliste funktionieren"
latest=/data/docker-backups/athena-latest.tar.gz latest=/data/docker-backups/athena-latest.tar.gz
[[ -s $latest ]] || fail "Kein gueltiges Athena-Backup: $latest" [[ -s $latest ]] || fail "Kein gueltiges Athena-Backup: $latest"
pass "Automatisches Athena-Backup ist vorhanden" ./restore.sh --check "$latest" >/dev/null || fail "Athena-Backup ist nicht wiederherstellbar"
pass "Automatisches Athena-Backup ist vorhanden und strukturell wiederherstellbar"
printf '\nATHENA_E2E_OK\n' printf '\nATHENA_E2E_OK\n'