Merge pull request 'Feature: Restore-Skript für DB, Uploads und Migrationen' (#76) from fds2610-bot/Steuerkram:main into main

Reviewed-on: https://codeberg.org/fds2610/Steuerkram/pulls/76
This commit is contained in:
fds2610 2026-05-17 15:41:21 +02:00
commit 942dd8fadd
2 changed files with 112 additions and 25 deletions

102
scripts/restore.sh Executable file
View file

@ -0,0 +1,102 @@
#!/bin/bash
# Steuerkram – Restore-Script
# Spielt ein Backup-Paar (DB + Uploads) ein und führt anschließend alle
# ausstehenden Migrationen aus.
#
# Usage:
# ./scripts/restore.sh # neuestes vollständiges Backup
# ./scripts/restore.sh 20260517_134945 # bestimmten Timestamp
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
BACKUP_DIR="$PROJECT_DIR/backups"
COMPOSE="docker compose -f $PROJECT_DIR/docker-compose.yml --project-directory $PROJECT_DIR"
# --- Timestamp ermitteln ---
if [ -n "$1" ]; then
TS="$1"
else
# Neuesten Timestamp finden, für den BEIDE Dateien existieren
TS=""
for DB_FILE in $(ls -t "$BACKUP_DIR"/steuerkram_db_????????_??????.sql 2>/dev/null); do
CANDIDATE=$(basename "$DB_FILE" .sql | sed 's/steuerkram_db_//')
UPL_FILE="$BACKUP_DIR/steuerkram_uploads_${CANDIDATE}.tar.gz"
if [ -f "$UPL_FILE" ]; then
TS="$CANDIDATE"
break
fi
done
fi
DB_FILE="$BACKUP_DIR/steuerkram_db_${TS}.sql"
UPL_FILE="$BACKUP_DIR/steuerkram_uploads_${TS}.tar.gz"
# --- Dateien prüfen ---
if [ -z "$TS" ]; then
echo "FEHLER: Kein vollständiges Backup-Paar (DB + Uploads) gefunden." >&2
exit 1
fi
if [ ! -f "$DB_FILE" ]; then
echo "FEHLER: DB-Backup nicht gefunden: $DB_FILE" >&2
exit 1
fi
if [ ! -f "$UPL_FILE" ]; then
echo "FEHLER: Uploads-Backup nicht gefunden: $UPL_FILE" >&2
exit 1
fi
echo "========================================"
echo " Steuerkram Restore – $TS"
echo "========================================"
echo " DB: $DB_FILE"
echo " Uploads: $UPL_FILE"
echo ""
# --- Sicherheitsabfrage ---
read -r -p "Vorhandene Datenbank und Uploads werden ÜBERSCHRIEBEN. Fortfahren? [j/N] " CONFIRM
if [[ ! "$CONFIRM" =~ ^[jJyY]$ ]]; then
echo "Abgebrochen."
exit 0
fi
# --- Container-Status prüfen ---
if ! $COMPOSE ps --status running db | grep -q "db"; then
echo "FEHLER: DB-Container läuft nicht. Bitte erst 'docker compose up -d' ausführen." >&2
exit 1
fi
if ! $COMPOSE ps --status running web | grep -q "web"; then
echo "FEHLER: Web-Container läuft nicht." >&2
exit 1
fi
# --- 1. Datenbank einspielen ---
echo ""
echo "[1/3] Datenbank importieren..."
$COMPOSE exec -T db mariadb -u root -prootpass steuerkram < "$DB_FILE"
echo " OK"
# --- 2. Uploads einspielen ---
echo "[2/3] Uploads entpacken..."
$COMPOSE exec -T web sh -c "
cat > /tmp/uploads_restore.tar.gz
tar -xzf /tmp/uploads_restore.tar.gz -C /var/www/html/public/static/
rm /tmp/uploads_restore.tar.gz
" < "$UPL_FILE"
echo " OK"
# --- 3. Migrationen ---
echo "[3/3] Migrationen prüfen und einspielen..."
MIGRATE_OUT=$($COMPOSE exec -T web php scripts/migrate.php 2>&1)
echo "$MIGRATE_OUT" | sed 's/^/ /'
if echo "$MIGRATE_OUT" | grep -q "FEHLER"; then
echo ""
echo "WARNUNG: Mindestens eine Migration ist fehlgeschlagen!" >&2
exit 1
fi
echo ""
echo "========================================"
echo " Restore abgeschlossen."
echo "========================================"

View file

@ -139,10 +139,7 @@
<th>Belegdatum</th> <th>Belegdatum</th>
<th>Lieferant</th> <th>Lieferant</th>
<th>Rechnungsnr.</th> <th>Rechnungsnr.</th>
<th>Dateiname</th>
<th>Bewirtungsgrund / Teilnehmer</th>
<th class="td-right">Brutto</th> <th class="td-right">Brutto</th>
<th>Status</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
@ -154,24 +151,7 @@
title="Klicken für Vorschau"> title="Klicken für Vorschau">
<td onclick="event.stopPropagation()"> <td onclick="event.stopPropagation()">
<a href="/belege/<?= $h($b['id']) ?>" class="font-mono"><?= $h($b['beleg_nr']) ?></a> <a href="/belege/<?= $h($b['id']) ?>" class="font-mono"><?= $h($b['beleg_nr']) ?></a>
</td> <div style="margin-top:.2em">
<td class="td-muted"><?= View::formatDate($b['beleg_datum']) ?></td>
<td class="td-muted"><?= $h($b['supplier_name'] ?? '–') ?></td>
<td class="td-muted"><?= $h($b['rechnungs_nr'] ?? '–') ?></td>
<td class="text-sm text-muted"><?= $h(mb_strimwidth($b['filename'], 0, 35, '…')) ?></td>
<td style="max-width:220px">
<?php if (!empty($b['bewirtungsgrund'])): ?>
<div style="font-size:.8em;white-space:pre-wrap;word-break:break-word"><?= $h($b['bewirtungsgrund']) ?></div>
<?php endif; ?>
<?php if (!empty($b['teilnehmerliste'])): ?>
<div style="font-size:.75em;color:var(--color-muted);white-space:pre-wrap;word-break:break-word;margin-top:.15em"><?= $h($b['teilnehmerliste']) ?></div>
<?php endif; ?>
<?php if (empty($b['bewirtungsgrund']) && empty($b['teilnehmerliste'])): ?>
<span class="text-muted">–</span>
<?php endif; ?>
</td>
<td class="td-right td-mono"><?= $b['betrag_brutto'] !== null ? View::formatMoney($b['betrag_brutto']) : '–' ?></td>
<td>
<?php if ($b['locked']): ?> <?php if ($b['locked']): ?>
<span class="badge badge-purple">Gesperrt</span> <span class="badge badge-purple">Gesperrt</span>
<?php elseif ($b['booked']): ?> <?php elseif ($b['booked']): ?>
@ -179,14 +159,19 @@
<?php else: ?> <?php else: ?>
<span class="badge badge-yellow">Offen</span> <span class="badge badge-yellow">Offen</span>
<?php endif; ?> <?php endif; ?>
</div>
</td> </td>
<td class="td-muted"><?= View::formatDate($b['beleg_datum']) ?></td>
<td class="td-muted"><?= $h($b['supplier_name'] ?? '–') ?></td>
<td class="td-muted"><?= $h($b['rechnungs_nr'] ?? '–') ?></td>
<td class="td-right td-mono"><?= $b['betrag_brutto'] !== null ? View::formatMoney($b['betrag_brutto']) : '–' ?></td>
<td onclick="event.stopPropagation()"> <td onclick="event.stopPropagation()">
<a href="/belege/<?= $h($b['id']) ?>" class="btn btn-secondary btn-sm">Öffnen</a> <a href="/belege/<?= $h($b['id']) ?>" class="btn btn-secondary btn-sm">Öffnen</a>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
<?php if (empty($rows)): ?> <?php if (empty($rows)): ?>
<tr><td colspan="10" class="text-center text-muted" style="padding:2rem">Keine Belege gefunden.</td></tr> <tr><td colspan="6" class="text-center text-muted" style="padding:2rem">Keine Belege gefunden.</td></tr>
<?php endif; ?> <?php endif; ?>
</tbody> </tbody>
</table> </table>