ZMB Webui: Complete Project – Rebrand & Initial Clean Commit

ARCHITECTURE
============
Backend: FastAPI + uvicorn (port 8000)
  - JWT authentication with PAM system users
  - ZFS CLI wrapper with caching (30-60s TTL)
  - WebSocket pool status broadcaster (30s interval)
  - Services: auth, zfs_runner, file_manager, shares, identities, system_info
  - Routers: pools, datasets, snapshots, shares, identities, navigator, system

Frontend: Next.js 15 + TypeScript (static export)
  - Incremental Static Regeneration (ISR) for weak hardware
  - Type-safe API client (lib/api.ts)
  - Dark mode + custom Tailwind theme
  - Pages: Dashboard, Login, Snapshots, Datasets, Shares, etc.

DEPLOYMENT
==========
Test Target: 192.168.1.179:8090 (Debian LXC)
Production: 10.66.120.3:9090 (Raspberry Pi 4GB ARM64)
Updater: Automated Gitea-based deployment (update-test.sh, update-pi.sh)

FEATURES COMPLETED
==================
Phase 3a: Dashboard Quick Stats (System, CPU, Memory, Storage)
  - Real-time stats with color-coded progress bars
  - Responsive grid layout (mobile: 1, tablet: 2, desktop: 4 columns)
  - ISR-optimized for fast loads on weak hardware

REBRANDING
==========
Renamed throughout:
  - Project: 'ZFS Manager' → 'ZMB Webui'
  - Services: 'zfs-manager' → 'zmb-webui'
  - Systemd units: zfs-manager-backend → zmb-webui-backend
  - Configuration files and documentation

Co-Authored-By: Patrick <patrick@perlbach24.de>
This commit is contained in:
Claude Code
2026-04-22 00:26:23 +02:00
committed by Patrick
commit 6d74d874b6
104 changed files with 28836 additions and 0 deletions
+162
View File
@@ -0,0 +1,162 @@
#!/bin/bash
# ZMB Webui Frontend Static Export Deployment
# Für RAM-optimierte Lösung: next build → out/ → nginx serve (kein Node.js Runtime)
set -e
echo "=== ZMB Webui Frontend Static Export Build ==="
# Configuration
FRONTEND_DIR="/opt/zmb-webui/frontend"
FRONTEND_TEMP="/tmp/frontend"
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
# Check frontend directory exists
echo -e "${YELLOW}1. Checking frontend source...${NC}"
if [ ! -d "$FRONTEND_TEMP" ]; then
echo -e "${RED}✗ Frontend source not found: $FRONTEND_TEMP${NC}"
echo -e "${RED} Make sure frontend was copied to container first${NC}"
exit 1
fi
echo -e "${GREEN}✓ Frontend source found${NC}"
# Create/prepare frontend directory
echo -e "${YELLOW}2. Preparing frontend directory...${NC}"
mkdir -p "$FRONTEND_DIR"
rm -rf "$FRONTEND_DIR"/*
cp -r "$FRONTEND_TEMP"/* "$FRONTEND_DIR/"
cd "$FRONTEND_DIR"
echo -e "${GREEN}✓ Frontend prepared${NC}"
# Install dependencies with memory optimization
echo -e "${YELLOW}4. Installing dependencies (memory-optimized)...${NC}"
npm install --prefer-offline --no-audit --production=false 2>&1 | grep -E "added|up to date|npm WARN" || true
echo -e "${GREEN}✓ Dependencies installed${NC}"
# Build static export
echo -e "${YELLOW}5. Building static export...${NC}"
npm run build 2>&1 | tail -20
echo -e "${GREEN}✓ Build complete${NC}"
# Verify out/ directory
echo -e "${YELLOW}6. Verifying export directory...${NC}"
if [ ! -d "$FRONTEND_DIR/out" ]; then
echo -e "${RED}✗ Export directory 'out' not found${NC}"
exit 1
fi
PAGES_COUNT=$(find out -name "*.html" | wc -l)
echo -e "${GREEN}✓ Export successful ($PAGES_COUNT pages)${NC}"
# Configure nginx
echo -e "${YELLOW}7. Configuring nginx...${NC}"
sudo tee /etc/nginx/sites-available/zmb-webui > /dev/null <<'NGINX_CONF'
server {
listen 9090;
server_name _;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Compression
gzip on;
gzip_min_length 1000;
gzip_types text/plain text/css application/json application/javascript;
gzip_comp_level 5;
# Static assets cache (7 days)
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
root /opt/zmb-webui/frontend/out;
expires 7d;
add_header Cache-Control "public, max-age=604800, immutable";
access_log off;
}
# Frontend static pages
location / {
root /opt/zmb-webui/frontend/out;
try_files $uri $uri/ /index.html;
add_header Cache-Control "public, max-age=3600";
}
# API routes → Backend
location /api/ {
proxy_pass http://localhost:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 10s;
proxy_read_timeout 30s;
}
# Health check
location /health {
proxy_pass http://localhost:8000;
access_log off;
}
# Logging
access_log /var/log/nginx/zmb-webui-access.log combined;
error_log /var/log/nginx/zmb-webui-error.log warn;
}
NGINX_CONF
echo -e "${GREEN}✓ Nginx configured${NC}"
# Enable nginx site
echo -e "${YELLOW}8. Enabling nginx site...${NC}"
sudo rm -f /etc/nginx/sites-enabled/default
sudo ln -sf /etc/nginx/sites-available/zmb-webui /etc/nginx/sites-enabled/zmb-webui
echo -e "${GREEN}✓ Nginx site enabled${NC}"
# Test and reload nginx
echo -e "${YELLOW}9. Testing and reloading nginx...${NC}"
sudo nginx -t
sudo systemctl reload nginx
echo -e "${GREEN}✓ Nginx reloaded${NC}"
# Clean up systemd service (no longer needed for Node.js)
echo -e "${YELLOW}10. Cleaning up systemd services...${NC}"
if [ -f /etc/systemd/system/zmb-webui-frontend.service ]; then
sudo systemctl disable zmb-webui-frontend 2>/dev/null || true
sudo systemctl stop zmb-webui-frontend 2>/dev/null || true
sudo rm -f /etc/systemd/system/zmb-webui-frontend.service
sudo systemctl daemon-reload
echo -e "${GREEN}✓ Frontend Node.js service removed${NC}"
fi
# Verify connectivity
echo -e "${YELLOW}11. Testing connectivity...${NC}"
sleep 1
if curl -s http://localhost:9090 > /dev/null 2>&1; then
echo -e "${GREEN}✓ Frontend accessible at http://localhost:9090${NC}"
else
echo -e "${YELLOW}⚠ Frontend not yet responding (nginx warming up)${NC}"
fi
# Summary
echo ""
echo -e "${GREEN}=== Deployment Complete ===${NC}"
echo ""
echo "Frontend (Static):"
echo " URL: http://$(hostname -I | awk '{print $1}'):9090"
echo ""
echo "Backend API:"
echo " URL: http://localhost:8000"
echo " Health: curl http://localhost:8000/health"
echo ""
echo "Service Management:"
echo " systemctl status nginx"
echo " systemctl restart nginx"
echo ""
echo "Logs:"
echo " tail -f /var/log/nginx/zmb-webui-access.log"
echo " tail -f /var/log/nginx/zmb-webui-error.log"
echo ""
+102
View File
@@ -0,0 +1,102 @@
#!/bin/bash
# ZMB Webui Frontend Deployment Script
set -e
echo "=== ZMB Webui Frontend Deployment ==="
# Configuration
FRONTEND_DIR="/opt/zmb-webui/frontend"
NODE_VERSION="v20.19.2"
NPM_VERSION="9.2.0"
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Check Node.js and npm
echo -e "${YELLOW}1. Checking Node.js and npm...${NC}"
NODE_INSTALLED=$(node --version 2>/dev/null || echo "")
if [ -z "$NODE_INSTALLED" ]; then
echo -e "${RED}✗ Node.js not installed${NC}"
exit 1
fi
echo -e "${GREEN}✓ Node.js $NODE_INSTALLED installed${NC}"
NPM_INSTALLED=$(npm --version 2>/dev/null || echo "")
if [ -z "$NPM_INSTALLED" ]; then
echo -e "${RED}✗ npm not installed${NC}"
exit 1
fi
echo -e "${GREEN}✓ npm $NPM_INSTALLED installed${NC}"
# Check frontend directory
echo -e "${YELLOW}2. Checking frontend directory...${NC}"
if [ ! -d "$FRONTEND_DIR" ]; then
echo -e "${RED}✗ Frontend directory not found: $FRONTEND_DIR${NC}"
exit 1
fi
echo -e "${GREEN}✓ Frontend directory found${NC}"
# Install dependencies
echo -e "${YELLOW}3. Installing dependencies...${NC}"
cd "$FRONTEND_DIR"
npm install --prefer-offline --no-audit
echo -e "${GREEN}✓ Dependencies installed${NC}"
# Build project
echo -e "${YELLOW}4. Building Next.js project...${NC}"
npm run build
echo -e "${GREEN}✓ Build successful${NC}"
# Create systemd service
echo -e "${YELLOW}5. Setting up systemd service...${NC}"
sudo cp /tmp/zmb-webui-frontend.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable zmb-webui-frontend
echo -e "${GREEN}✓ Systemd service configured${NC}"
# Start service
echo -e "${YELLOW}6. Starting frontend service...${NC}"
sudo systemctl restart zmb-webui-frontend
sleep 2
# Verify service
if sudo systemctl is-active --quiet zmb-webui-frontend; then
echo -e "${GREEN}✓ Frontend service running${NC}"
else
echo -e "${RED}✗ Frontend service failed to start${NC}"
sudo systemctl status zmb-webui-frontend
exit 1
fi
# Test connectivity
echo -e "${YELLOW}7. Testing connectivity...${NC}"
sleep 2
if curl -s http://localhost:3000 > /dev/null 2>&1; then
echo -e "${GREEN}✓ Frontend accessible at http://localhost:3000${NC}"
else
echo -e "${YELLOW}⚠ Frontend not yet responding (may be starting)${NC}"
fi
# Summary
echo ""
echo -e "${GREEN}=== Deployment Complete ===${NC}"
echo ""
echo "Frontend is running at:"
echo " Local: http://localhost:3000"
echo " Remote: http://$(hostname -I | awk '{print $1}'):9090"
echo ""
echo "Admin credentials:"
echo " Username: admin"
echo " Password: testpass123"
echo ""
echo "Service management:"
echo " systemctl status zmb-webui-frontend"
echo " systemctl restart zmb-webui-frontend"
echo " systemctl stop zmb-webui-frontend"
echo ""
echo "Logs:"
echo " journalctl -u zmb-webui-frontend -f"
+243
View File
@@ -0,0 +1,243 @@
#!/bin/bash
# ZMB Webui Deployment Script
# Unterstützt Frontend, Backend und Full-Deployment auf Remote-Host
# Usage: ./deploy.sh [--target HOST] [--backend-only|--frontend-only]
set -e
# Default values
TARGET="192.168.1.179"
DEPLOY_BACKEND=true
DEPLOY_FRONTEND=true
BACKEND_SERVICE="zmb-webui-backend"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--target)
TARGET="$2"
shift 2
;;
--backend-only)
DEPLOY_FRONTEND=false
shift
;;
--frontend-only)
DEPLOY_BACKEND=false
shift
;;
*)
echo -e "${RED}Unknown option: $1${NC}"
exit 1
;;
esac
done
# Helper functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
check_prerequisites() {
log_info "Checking prerequisites..."
if ! command -v scp &> /dev/null; then
log_error "scp not found. Please install openssh-client"
exit 1
fi
if ! command -v ssh &> /dev/null; then
log_error "ssh not found. Please install openssh-client"
exit 1
fi
if ! command -v curl &> /dev/null; then
log_error "curl not found. Please install curl"
exit 1
fi
if [ "$DEPLOY_FRONTEND" = true ] && ! command -v npm &> /dev/null; then
log_error "npm not found. Please install Node.js/npm"
exit 1
fi
log_info "All prerequisites met"
}
check_ssh_connection() {
log_info "Testing SSH connection to $TARGET..."
if ! ssh -o ConnectTimeout=5 root@$TARGET "echo 'Connection OK'" > /dev/null 2>&1; then
log_error "Cannot connect to $TARGET via SSH"
exit 1
fi
log_info "SSH connection successful"
}
deploy_frontend() {
log_info "Building frontend..."
if [ ! -d "frontend" ]; then
log_error "frontend directory not found"
exit 1
fi
cd frontend
if [ ! -f "package.json" ]; then
log_error "package.json not found in frontend directory"
exit 1
fi
npm run build
if [ ! -d "out" ]; then
log_error "Frontend build failed - 'out' directory not created"
exit 1
fi
log_info "Frontend build successful"
cd ..
log_info "Uploading frontend to $TARGET..."
if ! scp -r frontend/out/* root@$TARGET:/opt/zmb-webui/backend/static/ 2>/dev/null; then
log_error "Failed to upload frontend files"
exit 1
fi
log_info "Frontend deployed successfully"
}
deploy_backend() {
log_info "Deploying backend services..."
if [ ! -d "backend/services" ]; then
log_error "backend/services directory not found"
exit 1
fi
if [ ! -f "backend/main.py" ]; then
log_error "backend/main.py not found"
exit 1
fi
log_info "Uploading backend services to $TARGET..."
if ! scp backend/services/*.py root@$TARGET:/opt/zmb-webui/backend/services/ 2>/dev/null; then
log_error "Failed to upload backend services"
exit 1
fi
log_info "Uploading main.py to $TARGET..."
if ! scp backend/main.py root@$TARGET:/opt/zmb-webui/backend/ 2>/dev/null; then
log_error "Failed to upload main.py"
exit 1
fi
log_info "Backend files uploaded successfully"
}
restart_backend() {
log_info "Restarting backend service on $TARGET..."
if ! ssh root@$TARGET "systemctl restart $BACKEND_SERVICE" 2>/dev/null; then
log_error "Failed to restart backend service"
exit 1
fi
log_info "Backend service restarted"
# Wait a moment for service to start
sleep 2
}
reload_nginx() {
log_info "Testing Nginx configuration on $TARGET..."
if ! ssh root@$TARGET "nginx -t" 2>/dev/null; then
log_error "Nginx configuration test failed"
exit 1
fi
log_info "Reloading Nginx..."
if ! ssh root@$TARGET "systemctl reload nginx" 2>/dev/null; then
log_error "Failed to reload Nginx"
exit 1
fi
log_info "Nginx reloaded successfully"
}
health_check() {
log_info "Running health check..."
local max_retries=5
local retry_count=0
local http_code
while [ $retry_count -lt $max_retries ]; do
http_code=$(curl -s -o /dev/null -w "%{http_code}" "http://$TARGET/api/status" || echo "000")
if [ "$http_code" = "200" ]; then
log_info "Health check passed (HTTP $http_code)"
return 0
fi
retry_count=$((retry_count + 1))
if [ $retry_count -lt $max_retries ]; then
log_warn "Health check returned HTTP $http_code, retrying... ($retry_count/$max_retries)"
sleep 2
fi
done
log_error "Health check failed after $max_retries attempts (last HTTP code: $http_code)"
exit 1
}
# Main execution
main() {
log_info "Starting deployment to $TARGET"
log_info "Frontend: $DEPLOY_FRONTEND | Backend: $DEPLOY_BACKEND"
check_prerequisites
check_ssh_connection
if [ "$DEPLOY_FRONTEND" = true ]; then
deploy_frontend
fi
if [ "$DEPLOY_BACKEND" = true ]; then
deploy_backend
restart_backend
fi
if [ "$DEPLOY_BACKEND" = true ] || [ "$DEPLOY_FRONTEND" = true ]; then
reload_nginx
fi
health_check
log_info "Deployment completed successfully!"
exit 0
}
main
+274
View File
@@ -0,0 +1,274 @@
# ZMB Webui in LXC Container
## Setup für LXC
### Scenario
- **Host**: ZFS Pool (tank) mit ZFS Tools
- **LXC Container**: Backend läuft in **privilegiertem Container** (für ZFS Management)
- **Storage**: ZFS Pool direkt im Container sichtbar, kein Mount nötig
- **Netzwerk**: Container auf eigenem IP, Port-Mapping zu Host
### LXC Container erstellen
```bash
# 1. Container erstellen (PRIVILEGIERT für ZFS Management!)
lxc launch images:debian/bookworm zmb-webui \
--config security.privileged=true \
--config security.nesting=true
# 2. Container IP prüfen
lxc exec zmb-webui -- ip addr show eth0
# 3. Host-Port zu Container portmappen (9090 → 8000)
lxc config device add zmb-webui http proxy \
listen=tcp:0.0.0.0:9090 \
connect=tcp:127.0.0.1:8000
```
**Warum privilegiert?**
- ✓ ZFS Kernel-Modul wird sichtbar im Container
-`zpool` und `zfs` Commands funktionieren voll
- ✓ Pool-Management direkt im Container möglich
- ✓ Snapshots, Scrub, alles native im Container
- ⚠️ Sicherheits-Trade-off: Container hat Root-ähnliche Zugriffe
### Installation im Container
```bash
# 1. In Container einsteigen
lxc exec zmb-webui -- bash
# 2. Update & Grundtools
apt update && apt upgrade -y
apt install -y python3 python3-pip python3-venv git curl
# 3. Backend klonen/kopieren
cd /opt
git clone <repo-url> zmb-webui
cd zmb-webui/backend
# 4. Installation durchführen
bash install.sh
# 5. Service starten
systemctl start zmb-webui-backend
systemctl enable zmb-webui-backend
# 6. Prüfen
curl http://localhost:8000/health
```
### API-Zugriff vom Host
```bash
# Vom Host aus:
curl http://<container-ip>:9090/health
# oder via Port-Mapping:
curl http://localhost:9090/health
```
### File Manager Zugriff
```bash
# Files liegen im Container unter /tank/share
# Das ist ein Mount vom Host (/tank/share)
# Änderungen sind direkt auf dem Host sichtbar
TOKEN=$(curl -s -X POST http://localhost:9090/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"password"}' | jq -r .access_token)
curl http://localhost:9090/api/files/browse?path=/ \
-H "Authorization: Bearer $TOKEN"
```
## Container-spezifische Anpassungen
### 1. ZFS-Operationen im Container
**ZFS funktioniert nativ!** (weil Container privilegiert ist)
```bash
# Im privilegierten Container direkt:
lxc exec zmb-webui -- zpool list
# → Zeigt Host-Pools (tank, etc.)
lxc exec zmb-webui -- zfs list
# → Zeigt alle Datasets inklusive Snapshots
lxc exec zmb-webui -- zpool status tank
# → Zeigt VDEV-Status vom Host-Pool
```
**Wie funktioniert das?**
- Host hat ZFS Kernel-Modul geladen
- Privilegierter Container hat Zugriff auf `/dev/zfs`
- `zpool`/`zfs` Commands funktionieren wie auf Bare Metal
- Backend im Container kann direkt ZFS managen (kein SSH nötig!)
### 2. System Users im Container
**Im privilegierten Container:**
```bash
# im Container:
useradd -m newuser # User im Container erstellen
# Host sieht das auch! (weil privilegiert)
# Beispiel: /etc/passwd wird synchronisiert
cat /etc/passwd | grep newuser # existiert im Container
```
### 3. Samba/NFS im Container
```bash
# Im Container:
apt install -y samba
# Samba konfigurieren (Shares zeigen auf ZFS Datasets)
[tank_share]
path = /tank/share
browseable = yes
read only = no
public = yes
# NFS auch möglich
apt install -y nfs-kernel-server
# /etc/exports konfigurieren
```
## LXC Resources begrenzen
```bash
# Memory limit: 2GB (für Backend + ZFS)
lxc config set zmb-webui limits.memory 2GB
# CPU limit: 2 cores
lxc config set zmb-webui limits.cpu 2
# Disk limit (falls auf separate Volume)
lxc config device set zmb-webui root size 50GB
# ZFS im Container hat direkten Zugriff auf Host-Disks
# (keine extra device-einbindung nötig wenn privilegiert)
```
## Backup/Restore in LXC
```bash
# Container snapshot
lxc snapshot zmb-webui backup-2026-04-14
# Restore
lxc restore zmb-webui backup-2026-04-14
# Export Container
lxc export zmb-webui zmb-webui-backup.tar.gz
# Import
lxc import zmb-webui-backup.tar.gz
```
## Debugging im Container
```bash
# Shell zugriff
lxc exec zmb-webui -- bash
# Logs anschauen
lxc exec zmb-webui -- journalctl -u zmb-webui-backend -f
# Network prüfen
lxc exec zmb-webui -- ip addr
# Port check
lxc exec zmb-webui -- netstat -tlnp | grep 8000
# Host Zugriff testen
lxc exec zmb-webui -- curl http://localhost:8000/health
```
## Networking Setup
### Option A: Bridge (Recommended)
```bash
# Container automatisch im Host-Netzwerk
lxc launch images:debian/bookworm zmb-webui
# Container kriegt automatisch IP vom DHCP/LXD-Bridge
```
### Option B: Port-Forward via Host
```bash
lxc config device add zmb-webui http proxy \
listen=tcp:0.0.0.0:9090 \
connect=tcp:127.0.0.1:8000
# Dann von außen:
curl http://<host-ip>:9090/health
```
### Option C: macvlan (Direct Network)
```bash
# Für Production Container kriegt eigne MAC + IP im Netzwerk
lxc config device add zmb-webui eth0 nic \
nictype=macvlan \
parent=eth0
```
## Performance im LXC
### CPU-Performance
- **x86/AMD64**: ~5-10% Overhead vs Bare Metal
- **ARM64 (Pi)**: ~5-10% Overhead
- **LXC ist sehr effizient** Python FastAPI läuft ohne Probleme
### Memory-Performance
- **Nachteil**: Container hat seinen eigenen Memory Space
- **Vorteil**: Memory-Limits können pro Container gesetzt werden
- **Empfehlung**: Min 1GB für Backend, besser 2GB
### ZFS im Container
- **Vorteil**: Host verwaltet ZFS, Container nutzt Snapshots
- **Nachteil**: Container kann Pool selbst nicht managen (nur CLI)
## Sicherheit
### Unprivilegiert vs Privilegiert
```bash
# Unprivilegiert (Recommended)
security.privileged=false # Standard
# User namespacing activ
# Weniger Sicherheits-Risiken
# Privilegiert (nur wenn ZFS management im Container nötig)
security.privileged=true # Risikanter!
# Volle Root-Zugriffe im Container
```
### Für diesen Use-Case
**Unprivilegiert ist OK** weil:
- ZFS Management bleibt auf Host
- File Manager hat nur Read/Write auf `/tank/share`
- System Users sind container-lokal
## Zusammenfassung
```
Host (Bare Metal / VM)
├── ZFS Pool (tank) - Host verwaltet
│ ├── /tank/share - gemountet in LXC
│ └── Snapshots, Scrub - Host macht alles
└── LXC Container (zmb-webui)
├── FastAPI Backend :8000
├── Zugriff auf /tank/share (R/W)
├── File Manager funktioniert
├── System Users lokal
└── Port 9090 → Host Port Mapping
```
**Ergebnis**: Backend läuft überall Pi, x86, AMD64, oder im LXC!
+73
View File
@@ -0,0 +1,73 @@
# Nginx configuration for ZMB Webui
# Reverse proxy for both backend (FastAPI :8000) and frontend (Next.js :3000)
upstream backend {
server localhost:8000;
}
upstream frontend {
server localhost:3000;
}
server {
listen 9090 http2;
server_name _;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# Compression
gzip on;
gzip_min_length 1000;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json;
# API routes → Backend
location /api/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Health check
location /health {
proxy_pass http://backend;
proxy_http_version 1.1;
access_log off;
}
# Frontend (Next.js)
location / {
proxy_pass http://frontend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://frontend;
proxy_cache_valid 200 7d;
add_header Cache-Control "public, max-age=604800, immutable";
}
}
# Logging
access_log /var/log/nginx/zmb-webui-access.log combined;
error_log /var/log/nginx/zmb-webui-error.log warn;
}
+122
View File
@@ -0,0 +1,122 @@
#!/bin/bash
# Minimales Nginx-Setup für statische Frontend Dateien
set -e
echo "=== Nginx Static Frontend Setup ==="
FRONTEND_DIR="/opt/zmb-webui/frontend/out"
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
# Verify frontend export exists
echo -e "${YELLOW}1. Checking frontend export...${NC}"
if [ ! -d "$FRONTEND_DIR" ]; then
echo -e "${RED}✗ Frontend export not found: $FRONTEND_DIR${NC}"
exit 1
fi
FILE_COUNT=$(find "$FRONTEND_DIR" -type f | wc -l)
echo -e "${GREEN}✓ Frontend export found ($FILE_COUNT files)${NC}"
# Configure nginx
echo -e "${YELLOW}2. Configuring nginx...${NC}"
mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled
sudo tee /etc/nginx/sites-available/zmb-webui > /dev/null <<'NGINX_CONF'
server {
listen 9090;
server_name _;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Compression
gzip on;
gzip_min_length 1000;
gzip_types text/plain text/css application/json application/javascript;
gzip_comp_level 5;
# Static assets cache (7 days)
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
root /opt/zmb-webui/frontend/out;
expires 7d;
add_header Cache-Control "public, max-age=604800, immutable";
access_log off;
}
# Frontend static pages (SPA routing)
location / {
root /opt/zmb-webui/frontend/out;
try_files $uri $uri/ /index.html;
add_header Cache-Control "public, max-age=3600";
}
# API routes → Backend
location /api/ {
proxy_pass http://localhost:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 10s;
proxy_read_timeout 30s;
}
# Health check
location /health {
proxy_pass http://localhost:8000;
access_log off;
}
# Logging
access_log /var/log/nginx/zmb-webui-access.log combined;
error_log /var/log/nginx/zmb-webui-error.log warn;
}
NGINX_CONF
echo -e "${GREEN}✓ Nginx configured${NC}"
# Enable nginx site
echo -e "${YELLOW}3. Enabling nginx site...${NC}"
sudo rm -f /etc/nginx/sites-enabled/default
sudo ln -sf /etc/nginx/sites-available/zmb-webui /etc/nginx/sites-enabled/zmb-webui
echo -e "${GREEN}✓ Nginx site enabled${NC}"
# Test and reload nginx
echo -e "${YELLOW}4. Testing and reloading nginx...${NC}"
sudo nginx -t 2>&1 | grep -v "warning\|redundant"
sudo systemctl reload nginx
echo -e "${GREEN}✓ Nginx reloaded${NC}"
# Clean up old services
echo -e "${YELLOW}5. Cleaning up Node.js service (if exists)...${NC}"
if sudo systemctl is-enabled zmb-webui-frontend 2>/dev/null; then
sudo systemctl disable zmb-webui-frontend
sudo systemctl stop zmb-webui-frontend 2>/dev/null || true
sudo rm -f /etc/systemd/system/zmb-webui-frontend.service
sudo systemctl daemon-reload
fi
echo -e "${GREEN}✓ Cleanup complete${NC}"
# Verify
echo -e "${YELLOW}6. Testing connectivity...${NC}"
sleep 1
if curl -s http://localhost:9090 > /dev/null 2>&1; then
echo -e "${GREEN}✓ Frontend accessible on port 9090${NC}"
else
echo -e "${YELLOW}⚠ Testing with curl (may need time to warm up)${NC}"
fi
# Summary
echo ""
echo -e "${GREEN}=== Setup Complete ===${NC}"
echo ""
echo "Frontend: http://$(hostname -I | awk '{print $1}'):9090"
echo "API: http://localhost:8000"
echo ""
echo "Service: systemctl reload nginx"
echo "Logs: tail -f /var/log/nginx/zmb-webui-*.log"
echo ""
+197
View File
@@ -0,0 +1,197 @@
#!/bin/bash
# ZMB Webui Updater Lädt vom Gitea, baut neu und deployt
# Unterstützt: update-179 (Test) und update-pi (Produktion)
set -e
GITEA_URL="https://gitea.perlbach24.de/scripte/zmb-webui.git"
BRANCH="${1:-master}"
TARGET="${2:-179}" # 179 oder pi
# Farben für Output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}${NC} $1"; }
log_warn() { echo -e "${YELLOW}${NC} $1"; }
log_error() { echo -e "${RED}${NC} $1"; }
# Target validieren
if [[ ! "$TARGET" =~ ^(179|pi)$ ]]; then
log_error "Invalid target. Use: 179 (test) or pi (production)"
exit 1
fi
# Deploy-Parameter basierend auf Target
if [ "$TARGET" = "179" ]; then
REMOTE_HOST="192.168.1.179"
BACKEND_PORT="8000"
FRONTEND_PORT="8090"
BACKEND_PATH="/opt/zmb-webui/backend"
FRONTEND_PATH="/opt/zmb-webui/frontend"
else
REMOTE_HOST="10.66.120.3"
BACKEND_PORT="8000"
FRONTEND_PORT="9090"
BACKEND_PATH="/opt/zmb-webui/backend"
FRONTEND_PATH="/opt/zmb-webui/frontend"
fi
echo ""
echo "═══════════════════════════════════════════════════════"
echo " ZMB Webui Updater Target: $TARGET ($REMOTE_HOST)"
echo "═══════════════════════════════════════════════════════"
echo ""
# 1. Gitea Pull
echo "📥 Pulling from Gitea ($BRANCH)..."
git fetch origin "$BRANCH" || {
log_error "Git fetch failed. Token valid?"
exit 1
}
if [ "$(git rev-parse HEAD)" != "$(git rev-parse origin/$BRANCH)" ]; then
git pull origin "$BRANCH" || {
log_error "Git pull failed"
exit 1
}
log_info "Repository updated"
else
log_warn "Already up to date"
fi
# 2. Frontend Build
echo ""
echo "🔨 Building frontend..."
cd frontend
rm -rf .next out 2>/dev/null || true
if ! npm run build > /tmp/npm-build.log 2>&1; then
log_error "Frontend build failed"
cat /tmp/npm-build.log | tail -20
exit 1
fi
# next.config.js has output: 'export', so build creates ./out automatically
if [ ! -d "out" ]; then
log_error "Frontend export failed (out/ directory not created)"
exit 1
fi
log_info "Frontend built and exported to ./out"
cd ..
# 3. Backend Files
echo ""
echo "📦 Preparing backend files..."
BACKEND_FILES=(
"main.py"
"requirements.txt"
"services/auth.py"
"services/zfs_runner.py"
"services/file_manager.py"
"services/identities.py"
"services/shares.py"
"services/system_info.py"
"services/system_users.py"
"routers/auth.py"
"routers/pools.py"
"routers/datasets.py"
"routers/snapshots.py"
"routers/shares.py"
"routers/identities.py"
"routers/navigator.py"
"routers/system.py"
"models/auth.py"
"models/pool.py"
"models/dataset.py"
"models/snapshot.py"
)
# 4. Sync zu Remote
echo ""
echo "🚀 Deploying to $REMOTE_HOST..."
# SSH Connection test
if ! ssh -o ConnectTimeout=5 root@"$REMOTE_HOST" "echo 'SSH OK'" > /dev/null 2>&1; then
log_error "Cannot connect to $REMOTE_HOST via SSH"
exit 1
fi
# Backend sync
log_info "Syncing backend files..."
for file in "${BACKEND_FILES[@]}"; do
src="backend/$file"
dst_dir="${BACKEND_PATH}/${file%/*}"
# Only sync if file exists
if [ -f "$src" ]; then
ssh root@"$REMOTE_HOST" "mkdir -p $dst_dir" 2>/dev/null || true
scp -q "$src" "root@$REMOTE_HOST:$dst_dir/" 2>/dev/null || {
log_warn "Could not sync $file"
}
fi
done
# Frontend sync
log_info "Syncing frontend..."
rsync -q -r --delete frontend/out/ "root@$REMOTE_HOST:$FRONTEND_PATH/" || {
log_error "Frontend rsync failed"
exit 1
}
# 5. Services Restart
echo ""
echo "🔄 Restarting services..."
ssh root@"$REMOTE_HOST" "systemctl restart zmb-webui-backend 2>/dev/null || true; sleep 2; systemctl restart nginx 2>/dev/null || true; sleep 1" || {
log_error "Service restart failed"
exit 1
}
log_info "Backend restarted"
log_info "Nginx restarted"
# 6. Health Check
echo ""
echo "🏥 Health check..."
BACKEND_UP=false
FRONTEND_UP=false
# Backend check (max 10 Sekunden)
for i in {1..10}; do
if ssh root@"$REMOTE_HOST" "curl -s http://localhost:$BACKEND_PORT/health >/dev/null 2>&1" 2>/dev/null; then
BACKEND_UP=true
log_info "Backend responding"
break
fi
sleep 1
done
# Frontend check
if ssh root@"$REMOTE_HOST" "curl -s http://localhost:$FRONTEND_PORT/ | grep -q '<html'" 2>/dev/null; then
FRONTEND_UP=true
log_info "Frontend responding"
else
log_warn "Frontend health check incomplete (may still be syncing)"
fi
# 7. Summary
echo ""
echo "═══════════════════════════════════════════════════════"
if [ "$BACKEND_UP" = true ]; then
echo -e "${GREEN}✅ Update complete!${NC}"
echo ""
echo " Backend: http://$REMOTE_HOST:$BACKEND_PORT"
echo " Frontend: http://$REMOTE_HOST:$FRONTEND_PORT"
echo " Branch: $BRANCH"
echo ""
else
echo -e "${YELLOW}⚠ Update deployed but backend not responding yet${NC}"
echo " Check: ssh root@$REMOTE_HOST journalctl -u zmb-webui-backend -f"
fi
echo "═══════════════════════════════════════════════════════"
echo ""
+46
View File
@@ -0,0 +1,46 @@
[Unit]
Description=ZMB Webui Backend API
After=network.target
Wants=network-online.target
[Service]
Type=notify
User=root
WorkingDirectory=/opt/zmb-webui/backend
Environment="PYTHONUNBUFFERED=1"
Environment="PYTHONDONTWRITEBYTECODE=1"
# Start command with gunicorn
ExecStart=/usr/bin/python3 -m uvicorn main:app \
--host 0.0.0.0 \
--port 8000 \
--workers 2 \
--worker-class uvicorn.workers.UvicornWorker \
--timeout 30 \
--access-logfile -
# Process management
Restart=always
RestartSec=10
KillSignal=SIGTERM
KillMode=process
# Resource limits
MemoryLimit=512M
MemoryMax=768M
CPUQuota=75%
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=zmb-webui-backend
# Security
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ReadWritePaths=/opt/zmb-webui/backend/config
[Install]
WantedBy=multi-user.target
+33
View File
@@ -0,0 +1,33 @@
[Unit]
Description=ZMB Webui Frontend (Next.js)
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/zmb-webui/frontend
Environment="NODE_ENV=production"
Environment="PORT=3000"
# Start command - use npm start for production
ExecStart=/usr/bin/npm start
# Restart policy
Restart=on-failure
RestartSec=10
# Resource limits
MemoryMax=512M
CPUQuota=50%
# Timeout
TimeoutStartSec=60
TimeoutStopSec=10
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=zmb-webui-frontend
[Install]
WantedBy=multi-user.target