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:
20
deploy/.env.example
Normal file
20
deploy/.env.example
Normal file
@@ -0,0 +1,20 @@
|
||||
# Kopiere diese Datei zu ".env" und passe die Werte an.
|
||||
# install.sh erzeugt eine .env automatisch mit sicheren Zufallswerten.
|
||||
|
||||
# Port, unter dem die Web-UI erreichbar ist
|
||||
WEB_PORT=8080
|
||||
|
||||
# Datenbank
|
||||
POSTGRES_USER=pantry
|
||||
POSTGRES_PASSWORD=pantry
|
||||
POSTGRES_DB=pantry
|
||||
|
||||
# Sicherheit – im Betrieb unbedingt ändern!
|
||||
JWT_SECRET=change-me-please
|
||||
|
||||
# Erster Admin-Benutzer (wird nur beim allerersten Start angelegt)
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=changeme
|
||||
|
||||
# CORS (bei Betrieb hinter dem mitgelieferten nginx nicht nötig)
|
||||
CORS_ORIGINS=*
|
||||
41
deploy/docker-compose.yml
Normal file
41
deploy/docker-compose.yml
Normal file
@@ -0,0 +1,41 @@
|
||||
services:
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-pantry}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-pantry}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-pantry}
|
||||
volumes:
|
||||
- pantry_db:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-pantry}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
backend:
|
||||
build: ../backend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DATABASE_URL: postgresql+psycopg2://${POSTGRES_USER:-pantry}:${POSTGRES_PASSWORD:-pantry}@db:5432/${POSTGRES_DB:-pantry}
|
||||
JWT_SECRET: ${JWT_SECRET:-change-me}
|
||||
ADMIN_USERNAME: ${ADMIN_USERNAME:-admin}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-changeme}
|
||||
CORS_ORIGINS: ${CORS_ORIGINS:-*}
|
||||
expose:
|
||||
- "8000"
|
||||
|
||||
web:
|
||||
build: ../web
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- backend
|
||||
ports:
|
||||
- "${WEB_PORT:-8080}:80"
|
||||
|
||||
volumes:
|
||||
pantry_db:
|
||||
135
deploy/install.sh
Normal file
135
deploy/install.sh
Normal 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 "$@"
|
||||
Reference in New Issue
Block a user