Schritt 1: Fundament der Lebensmittel-Lagerverwaltung (Pantry)

Backend (FastAPI + PostgreSQL): Chargen mit MHD, FEFO-Auslagern,
Einheiten-Umrechnung (Stueck/g/ml + Packungen), Open-Food-Facts-Lookup
mit lokalem Fallback, JWT-Auth mit Rollen (Admin/Nutzer), erster Admin
beim Setup, Einkaufsliste, Ablaufwarnung, Lagerorte, pytest fuer FEFO.

Web-UI (React/Vite): Login, Dashboard, Ein-/Auslagern, Produkte,
Lagerorte, Benutzerverwaltung, Einkaufsliste - rollenabhaengig.

Deploy: docker-compose + install.sh (Docker-Autoinstall, Secrets),
README und Roadmap fuer Schritt 2 (iOS) und Schritt 3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-22 09:14:14 +02:00
commit 639126468f
57 changed files with 3460 additions and 0 deletions

135
deploy/install.sh Normal file
View File

@@ -0,0 +1,135 @@
#!/usr/bin/env bash
#
# Pantry Installer für selfhostbare Lebensmittel-Lagerverwaltung.
# Installiert bei Bedarf Docker und startet den kompletten Stack via Docker Compose.
#
# Nutzung:
# ./install.sh interaktiv (fragt Admin-Passwort etc.)
# ./install.sh --yes nicht-interaktiv (erzeugt Zufalls-Admin-Passwort)
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="$SCRIPT_DIR/.env"
ASSUME_YES=0
[[ "${1:-}" == "--yes" || "${1:-}" == "-y" ]] && ASSUME_YES=1
info() { printf '\033[0;32m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[0;33m[!]\033[0m %s\n' "$*"; }
error() { printf '\033[0;31m[x]\033[0m %s\n' "$*" >&2; }
rand() { LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c "${1:-32}"; }
# --- Docker prüfen / installieren ---------------------------------------------
ensure_docker() {
if command -v docker >/dev/null 2>&1; then
info "Docker gefunden: $(docker --version)"
else
warn "Docker ist nicht installiert."
if [[ "$ASSUME_YES" -eq 1 ]]; then
reply="j"
else
read -r -p "Soll Docker jetzt automatisch installiert werden? [J/n] " reply
reply="${reply:-j}"
fi
case "$reply" in
[JjYy]*)
info "Installiere Docker über get.docker.com …"
curl -fsSL https://get.docker.com | sh
;;
*)
error "Docker wird benötigt. Bitte manuell installieren und erneut ausführen."
exit 1
;;
esac
fi
# docker compose (v2) oder docker-compose (v1) ermitteln
if docker compose version >/dev/null 2>&1; then
COMPOSE="docker compose"
elif command -v docker-compose >/dev/null 2>&1; then
COMPOSE="docker-compose"
else
error "Docker Compose wurde nicht gefunden. Bitte Docker Compose v2 installieren."
exit 1
fi
info "Verwende: $COMPOSE"
if ! docker info >/dev/null 2>&1; then
error "Docker-Daemon nicht erreichbar. Läuft Docker? Ggf. 'sudo' nötig oder Nutzer zur 'docker'-Gruppe hinzufügen."
exit 1
fi
}
# --- .env erzeugen ------------------------------------------------------------
create_env() {
if [[ -f "$ENV_FILE" ]]; then
info ".env existiert bereits bestehende Konfiguration wird verwendet."
return
fi
info "Erzeuge Konfiguration (.env) …"
local web_port="8080"
local admin_user="admin"
local admin_pass
admin_pass="$(rand 16)"
if [[ "$ASSUME_YES" -eq 0 ]]; then
read -r -p "Port für die Web-UI [8080]: " input; web_port="${input:-8080}"
read -r -p "Admin-Benutzername [admin]: " input; admin_user="${input:-admin}"
read -r -s -p "Admin-Passwort [leer = zufällig generieren]: " input; echo
[[ -n "$input" ]] && admin_pass="$input"
fi
cat > "$ENV_FILE" <<EOF
WEB_PORT=$web_port
POSTGRES_USER=pantry
POSTGRES_PASSWORD=$(rand 24)
POSTGRES_DB=pantry
JWT_SECRET=$(rand 48)
ADMIN_USERNAME=$admin_user
ADMIN_PASSWORD=$admin_pass
CORS_ORIGINS=*
EOF
chmod 600 "$ENV_FILE"
GENERATED_ADMIN_USER="$admin_user"
GENERATED_ADMIN_PASS="$admin_pass"
GENERATED_WEB_PORT="$web_port"
}
# --- Start --------------------------------------------------------------------
main() {
info "Pantry-Installer startet …"
ensure_docker
create_env
info "Baue Container (das kann beim ersten Mal einige Minuten dauern) …"
( cd "$SCRIPT_DIR" && $COMPOSE --env-file "$ENV_FILE" build )
info "Starte Dienste …"
( cd "$SCRIPT_DIR" && $COMPOSE --env-file "$ENV_FILE" up -d )
# Werte aus .env lesen (auch bei bestehender .env)
# shellcheck disable=SC1090
local web_port admin_user
web_port="$(grep -E '^WEB_PORT=' "$ENV_FILE" | cut -d= -f2-)"
admin_user="$(grep -E '^ADMIN_USERNAME=' "$ENV_FILE" | cut -d= -f2-)"
echo
info "Fertig! 🎉"
echo " Web-UI: http://localhost:${web_port} (bzw. http://<server-ip>:${web_port})"
echo " Admin-Login: ${admin_user}"
if [[ -n "${GENERATED_ADMIN_PASS:-}" ]]; then
echo " Admin-Passwort: ${GENERATED_ADMIN_PASS}"
warn "Bitte dieses Passwort notieren es wird nicht erneut angezeigt."
else
echo " Admin-Passwort: (wie in .env gesetzt)"
fi
echo
echo " Logs: ( cd $SCRIPT_DIR && $COMPOSE logs -f )"
echo " Stoppen: ( cd $SCRIPT_DIR && $COMPOSE down )"
}
main "$@"