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:
@@ -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 ""
|
||||
@@ -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"
|
||||
Executable
+243
@@ -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
|
||||
@@ -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!
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 ""
|
||||
Executable
+197
@@ -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 ""
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user