From 322ffaea62662e52d2e38e89ac274cb0d79af433 Mon Sep 17 00:00:00 2001 From: twothatit Date: Sat, 7 Feb 2026 11:44:50 +0100 Subject: [PATCH] First Project description --- CLAUDE_CODE_SPEC.md | 459 +++++++++++++++++++++++++++++++ PROJECT_SUMMARY.md | 240 ++++++++++++++++ QUICKSTART.md | 148 ++++++++++ README.md | 519 ++++++++++++++++++++++++++++++++++- VS_CODE_SETUP.md | 215 +++++++++++++++ netbird-msp-appliance.tar.gz | Bin 0 -> 23608 bytes 6 files changed, 1580 insertions(+), 1 deletion(-) create mode 100644 CLAUDE_CODE_SPEC.md create mode 100644 PROJECT_SUMMARY.md create mode 100644 QUICKSTART.md create mode 100644 VS_CODE_SETUP.md create mode 100644 netbird-msp-appliance.tar.gz diff --git a/CLAUDE_CODE_SPEC.md b/CLAUDE_CODE_SPEC.md new file mode 100644 index 0000000..1bc5cf4 --- /dev/null +++ b/CLAUDE_CODE_SPEC.md @@ -0,0 +1,459 @@ +# NetBird MSP Appliance - Claude Code Specification + +## Project Overview +Build a complete, production-ready multi-tenant NetBird management platform that runs entirely in Docker containers. This is an MSP (Managed Service Provider) tool to manage 100+ isolated NetBird instances from a single web interface. + +## Technology Stack +- **Backend**: Python 3.11+ with FastAPI +- **Frontend**: HTML5 + Bootstrap 5 + Vanilla JavaScript (no frameworks) +- **Database**: SQLite +- **Containerization**: Docker + Docker Compose +- **Templating**: Jinja2 for Docker Compose generation +- **Integration**: Docker Python SDK, Nginx Proxy Manager API + +## Project Structure + +``` +netbird-msp-appliance/ +├── README.md # Main documentation +├── QUICKSTART.md # Quick start guide +├── ARCHITECTURE.md # Architecture documentation +├── LICENSE # MIT License +├── .gitignore # Git ignore file +├── .env.example # Environment variables template +├── install.sh # One-click installation script +├── docker-compose.yml # Main application container +├── Dockerfile # Application container definition +├── requirements.txt # Python dependencies +│ +├── app/ # Python application +│ ├── __init__.py +│ ├── main.py # FastAPI entry point +│ ├── models.py # SQLAlchemy models +│ ├── database.py # Database setup +│ ├── dependencies.py # FastAPI dependencies +│ │ +│ ├── routers/ # API endpoints +│ │ ├── __init__.py +│ │ ├── auth.py # Authentication endpoints +│ │ ├── customers.py # Customer CRUD +│ │ ├── deployments.py # Deployment management +│ │ ├── monitoring.py # Status & health checks +│ │ └── settings.py # System configuration +│ │ +│ ├── services/ # Business logic +│ │ ├── __init__.py +│ │ ├── docker_service.py # Docker container management +│ │ ├── npm_service.py # NPM API integration +│ │ ├── netbird_service.py # NetBird deployment orchestration +│ │ └── port_manager.py # UDP port allocation +│ │ +│ └── utils/ # Utilities +│ ├── __init__.py +│ ├── config.py # Configuration management +│ ├── security.py # Encryption, hashing +│ └── validators.py # Input validation +│ +├── templates/ # Jinja2 templates +│ ├── docker-compose.yml.j2 # Per-customer Docker Compose +│ ├── management.json.j2 # NetBird management config +│ └── relay.env.j2 # Relay environment variables +│ +├── static/ # Frontend files +│ ├── index.html # Main dashboard +│ ├── css/ +│ │ └── styles.css # Custom styles +│ └── js/ +│ └── app.js # Frontend JavaScript +│ +├── tests/ # Unit & integration tests +│ ├── __init__.py +│ ├── test_customer_api.py +│ ├── test_deployment.py +│ └── test_docker_service.py +│ +└── docs/ # Additional documentation + ├── API.md # API documentation + ├── DEPLOYMENT.md # Deployment guide + └── TROUBLESHOOTING.md # Common issues +``` + +## Key Features to Implement + +### 1. Customer Management +- **Create Customer**: Web form → API → Deploy NetBird instance +- **List Customers**: Paginated table with search/filter +- **Customer Details**: Status, logs, setup URL, actions +- **Delete Customer**: Remove all containers, NPM entries, data + +### 2. Automated Deployment +**Workflow when creating customer:** +1. Validate inputs (subdomain unique, email valid) +2. Allocate ports (Management internal, Relay UDP public) +3. Generate configs from Jinja2 templates +4. Create instance directory: `/opt/netbird-instances/kunde{id}/` +5. Write `docker-compose.yml`, `management.json`, `relay.env` +6. Start Docker containers via Docker SDK +7. Wait for health checks (max 60s) +8. Create NPM proxy hosts via API (with SSL) +9. Update database with deployment info +10. Return setup URL to user + +### 3. Web-Based Configuration +**All settings in database, editable via UI:** +- Base Domain +- Admin Email +- NPM API URL & Token +- NetBird Docker Images +- Port Ranges +- Data Directories + +No manual config file editing required! + +### 4. Nginx Proxy Manager Integration +**Per customer, create proxy host:** +- Domain: `{subdomain}.{base_domain}` +- Forward to: `netbird-kunde{id}-dashboard:80` +- SSL: Automatic Let's Encrypt +- Advanced config: Route `/api/*` to management, `/signalexchange.*` to signal, `/relay` to relay + +### 5. Port Management +**UDP Ports for STUN/Relay (publicly accessible):** +- Customer 1: 3478 +- Customer 2: 3479 +- ... +- Customer 100: 3577 + +**Algorithm:** +- Find next available port starting from 3478 +- Check if port not in use (via `netstat` or database) +- Assign to customer +- Store in database + +### 6. Monitoring & Health Checks +- Container status (running/stopped/failed) +- Health check endpoints (HTTP checks to management service) +- Resource usage (via Docker stats API) +- Relay connectivity test + +## Database Schema + +### Table: customers +```sql +CREATE TABLE customers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + company TEXT, + subdomain TEXT UNIQUE NOT NULL, + email TEXT NOT NULL, + max_devices INTEGER DEFAULT 20, + notes TEXT, + status TEXT DEFAULT 'active' CHECK(status IN ('active', 'inactive', 'deploying', 'error')), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +### Table: deployments +```sql +CREATE TABLE deployments ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + customer_id INTEGER NOT NULL UNIQUE, + container_prefix TEXT NOT NULL, + relay_udp_port INTEGER UNIQUE NOT NULL, + npm_proxy_id INTEGER, + relay_secret TEXT NOT NULL, + setup_url TEXT, + deployment_status TEXT DEFAULT 'pending' CHECK(deployment_status IN ('pending', 'running', 'stopped', 'failed')), + deployed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + last_health_check TIMESTAMP, + FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE +); +``` + +### Table: system_config +```sql +CREATE TABLE system_config ( + id INTEGER PRIMARY KEY CHECK (id = 1), + base_domain TEXT NOT NULL, + admin_email TEXT NOT NULL, + npm_api_url TEXT NOT NULL, + npm_api_token_encrypted TEXT NOT NULL, + netbird_management_image TEXT DEFAULT 'netbirdio/management:latest', + netbird_signal_image TEXT DEFAULT 'netbirdio/signal:latest', + netbird_relay_image TEXT DEFAULT 'netbirdio/relay:latest', + netbird_dashboard_image TEXT DEFAULT 'netbirdio/dashboard:latest', + data_dir TEXT DEFAULT '/opt/netbird-instances', + docker_network TEXT DEFAULT 'npm-network', + relay_base_port INTEGER DEFAULT 3478, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +### Table: deployment_logs +```sql +CREATE TABLE deployment_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + customer_id INTEGER NOT NULL, + action TEXT NOT NULL, + status TEXT NOT NULL CHECK(status IN ('success', 'error', 'info')), + message TEXT, + details TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE +); +``` + +### Table: users (simple auth) +```sql +CREATE TABLE users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + email TEXT, + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +## API Endpoints to Implement + +### Authentication +``` +POST /api/auth/login # Login and get token +POST /api/auth/logout # Logout +GET /api/auth/me # Get current user +POST /api/auth/change-password +``` + +### Customers +``` +POST /api/customers # Create + auto-deploy +GET /api/customers # List all (pagination, search, filter) +GET /api/customers/{id} # Get details +PUT /api/customers/{id} # Update +DELETE /api/customers/{id} # Delete + cleanup +``` + +### Deployments +``` +POST /api/customers/{id}/deploy # Manual deploy +POST /api/customers/{id}/start # Start containers +POST /api/customers/{id}/stop # Stop containers +POST /api/customers/{id}/restart # Restart containers +GET /api/customers/{id}/logs # Get container logs +GET /api/customers/{id}/health # Health check +``` + +### Monitoring +``` +GET /api/monitoring/status # System overview +GET /api/monitoring/customers # All customers status +GET /api/monitoring/resources # Host resource usage +``` + +### Settings +``` +GET /api/settings/system # Get system config +PUT /api/settings/system # Update system config +GET /api/settings/test-npm # Test NPM connectivity +``` + +## Docker Compose Template (Per Customer) + +```yaml +version: '3.8' + +networks: + npm-network: + external: true + +services: + netbird-management: + image: {{ netbird_management_image }} + container_name: netbird-kunde{{ customer_id }}-management + restart: unless-stopped + networks: + - npm-network + volumes: + - {{ instance_dir }}/data/management:/var/lib/netbird + - {{ instance_dir }}/management.json:/etc/netbird/management.json + command: ["--port", "80", "--log-file", "console", "--log-level", "info", + "--single-account-mode-domain={{ subdomain }}.{{ base_domain }}", + "--dns-domain={{ subdomain }}.{{ base_domain }}"] + + netbird-signal: + image: {{ netbird_signal_image }} + container_name: netbird-kunde{{ customer_id }}-signal + restart: unless-stopped + networks: + - npm-network + volumes: + - {{ instance_dir }}/data/signal:/var/lib/netbird + + netbird-relay: + image: {{ netbird_relay_image }} + container_name: netbird-kunde{{ customer_id }}-relay + restart: unless-stopped + networks: + - npm-network + ports: + - "{{ relay_udp_port }}:3478/udp" + env_file: + - {{ instance_dir }}/relay.env + environment: + - NB_ENABLE_STUN=true + - NB_STUN_PORTS=3478 + - NB_LISTEN_ADDRESS=:80 + - NB_EXPOSED_ADDRESS=rels://{{ subdomain }}.{{ base_domain }}:443 + - NB_AUTH_SECRET={{ relay_secret }} + + netbird-dashboard: + image: {{ netbird_dashboard_image }} + container_name: netbird-kunde{{ customer_id }}-dashboard + restart: unless-stopped + networks: + - npm-network + environment: + - NETBIRD_MGMT_API_ENDPOINT=https://{{ subdomain }}.{{ base_domain }} + - NETBIRD_MGMT_GRPC_API_ENDPOINT=https://{{ subdomain }}.{{ base_domain }} +``` + +## Frontend Requirements + +### Main Dashboard (index.html) +**Layout:** +- Navbar: Logo, "New Customer" button, User menu (settings, logout) +- Stats Cards: Total customers, Active, Inactive, Errors +- Customer Table: Name, Subdomain, Status, Devices, Actions +- Pagination: 25 customers per page +- Search bar: Filter by name, subdomain, email +- Status filter dropdown: All, Active, Inactive, Error + +**Customer Table Actions:** +- View Details (→ customer detail page) +- Start/Stop/Restart (inline buttons) +- Delete (with confirmation modal) + +### Customer Detail Page +**Tabs:** +1. **Info**: All customer details, edit button +2. **Deployment**: Status, Setup URL (copy button), Container status +3. **Logs**: Real-time logs from all containers (auto-refresh) +4. **Health**: Health check results, relay connectivity test + +### Settings Page +**Tabs:** +1. **System Configuration**: All system settings, save button +2. **NPM Integration**: API URL, Token, Test button +3. **Images**: NetBird Docker image tags +4. **Security**: Change admin password + +### Modal Dialogs +- New/Edit Customer Form +- Delete Confirmation +- Deployment Progress (with spinner) +- Error Display + +## Security Requirements + +1. **Password Hashing**: Use bcrypt for admin password +2. **Secret Encryption**: Encrypt NPM token and relay secrets with Fernet +3. **Input Validation**: Pydantic models for all API inputs +4. **SQL Injection Prevention**: Use SQLAlchemy ORM (no raw queries) +5. **CSRF Protection**: Token-based authentication +6. **Rate Limiting**: Prevent brute force on login endpoint + +## Error Handling + +All operations should have comprehensive error handling: + +```python +try: + # Deploy customer + result = deploy_customer(customer_id) +except DockerException as e: + # Rollback: Stop containers + # Log error + # Update status to 'failed' + # Return error to user +except NPMException as e: + # Rollback: Remove containers + # Log error + # Update status to 'failed' +except Exception as e: + # Generic rollback + # Log error + # Alert admin +``` + +## Testing Requirements + +1. **Unit Tests**: All services (docker_service, npm_service, etc.) +2. **Integration Tests**: Full deployment workflow +3. **API Tests**: All endpoints with different scenarios +4. **Mock External Dependencies**: Docker API, NPM API + +## Deployment Process + +1. Clone repository +2. Run `./install.sh` +3. Access `http://server-ip:8000` +4. Complete setup wizard +5. Deploy first customer + +## System Requirements Documentation + +**Include in README.md:** + +### For 100 Customers: +- **CPU**: 16 cores (minimum 8) +- **RAM**: 64 GB (minimum) - 128 GB (recommended) + - Formula: `(100 customers × 600 MB) + 8 GB overhead = 68 GB` +- **Disk**: 500 GB SSD (minimum) - 1 TB recommended +- **Network**: 1 Gbps dedicated connection +- **OS**: Ubuntu 22.04 LTS or 24.04 LTS + +### Port Requirements: +- **TCP 8000**: Web UI +- **UDP 3478-3577**: Relay/STUN (100 ports for 100 customers) + +## Success Criteria + +✅ One-command installation via `install.sh` +✅ Web-based configuration (no manual file editing) +✅ Customer deployment < 2 minutes +✅ All settings in database +✅ Automatic NPM integration +✅ Comprehensive error handling +✅ Clean, professional UI +✅ Full API documentation (auto-generated) +✅ Health monitoring +✅ Easy to deploy on fresh Ubuntu VM + +## Special Notes for Claude Code + +- **Use type hints** throughout Python code +- **Document all functions** with docstrings +- **Follow PEP 8** style guidelines +- **Create modular code**: Each service should be independently testable +- **Use async/await** where appropriate (FastAPI endpoints) +- **Provide comprehensive comments** for complex logic +- **Include error messages** that help users troubleshoot + +## File Priorities + +Create in this order: +1. Basic structure (directories, requirements.txt, Dockerfile, docker-compose.yml) +2. Database models and setup (models.py, database.py) +3. Core services (docker_service.py, port_manager.py) +4. API routers (start with customers.py) +5. NPM integration (npm_service.py) +6. Templates (Jinja2 files) +7. Frontend (HTML, CSS, JS) +8. Installation script +9. Documentation +10. Tests + +This specification provides everything needed to build a production-ready NetBird MSP Appliance! diff --git a/PROJECT_SUMMARY.md b/PROJECT_SUMMARY.md new file mode 100644 index 0000000..5a3d005 --- /dev/null +++ b/PROJECT_SUMMARY.md @@ -0,0 +1,240 @@ +# NetBird MSP Appliance - Project Summary + +## 📦 Was ist enthalten? + +Dieses Repository enthält ein **vollständiges, produktionsreifes Multi-Tenant NetBird Management System** für MSPs (Managed Service Provider). + +## 🎯 Hauptziel + +Ermöglicht MSPs, **100+ isolierte NetBird-Instanzen** für ihre Kunden von einer einzigen Web-Oberfläche aus zu verwalten - komplett in Docker containerisiert und mit einem Befehl deploybar. + +## 📁 Repository-Struktur + +``` +netbird-msp-appliance/ +├── 📖 README.md # Vollständige Dokumentation +├── 🚀 QUICKSTART.md # 10-Minuten Quick Start +├── 🏗️ ARCHITECTURE.md # System-Architektur +├── 💻 VS_CODE_SETUP.md # Guide für VS Code + Claude Code +├── 📋 CLAUDE_CODE_SPEC.md # Vollständige Spezifikation für Claude Code +├── 🛠️ install.sh # One-Click Installation +├── 🐳 docker-compose.yml # Docker Container Definition +├── 📦 Dockerfile # Application Container +├── 📝 requirements.txt # Python Dependencies +├── ⚙️ .env.example # Environment Variables Template +├── 📜 LICENSE # MIT License +├── 🙋 CONTRIBUTING.md # Contribution Guidelines +│ +├── app/ # Python FastAPI Application +│ ├── main.py # Entry Point +│ ├── models.py # Database Models (ERSTELLT) +│ ├── database.py # DB Setup (ERSTELLT) +│ ├── routers/ # API Endpoints (ZU ERSTELLEN) +│ ├── services/ # Business Logic (ZU ERSTELLEN) +│ └── utils/ # Utilities (ZU ERSTELLEN) +│ +├── templates/ # Jinja2 Templates (ZU ERSTELLEN) +├── static/ # Frontend Files (ZU ERSTELLEN) +└── tests/ # Tests (ZU ERSTELLEN) +``` + +## ✅ Was ist bereits fertig? + +- ✅ **Dokumentation**: README, Quickstart, Architecture Guide +- ✅ **Docker Setup**: docker-compose.yml, Dockerfile +- ✅ **Installation Script**: install.sh (funktionsbereit) +- ✅ **Database Models**: Vollständige SQLAlchemy Models +- ✅ **Database Setup**: Database configuration +- ✅ **FastAPI Entry Point**: main.py mit Routing-Struktur +- ✅ **Claude Code Spezifikation**: Detaillierte Implementierungs-Anleitung +- ✅ **Environment Template**: .env.example +- ✅ **Git Setup**: .gitignore + +## 🔨 Was muss noch implementiert werden? + +Diese Aufgaben sind für **Claude Code** vorbereitet (siehe CLAUDE_CODE_SPEC.md): + +### 1. Backend (Python) +- [ ] **API Routers** (app/routers/): + - auth.py - Authentication + - customers.py - Customer CRUD + - deployments.py - Deployment Management + - monitoring.py - Status & Health + - settings.py - System Config + +- [ ] **Services** (app/services/): + - docker_service.py - Docker Container Management + - npm_service.py - Nginx Proxy Manager API + - netbird_service.py - Deployment Orchestration + - port_manager.py - UDP Port Allocation + +- [ ] **Utils** (app/utils/): + - config.py - Configuration Management + - security.py - Encryption, Hashing + - validators.py - Input Validation + +### 2. Templates (Jinja2) +- [ ] docker-compose.yml.j2 - Per-Customer Docker Compose +- [ ] management.json.j2 - NetBird Management Config +- [ ] relay.env.j2 - Relay Environment Variables + +### 3. Frontend (HTML/CSS/JS) +- [ ] index.html - Main Dashboard +- [ ] styles.css - Custom Styling +- [ ] app.js - Frontend Logic + +### 4. Tests +- [ ] Unit Tests for Services +- [ ] Integration Tests +- [ ] API Tests + +## 🚀 Wie geht es weiter? + +### Option 1: Mit Claude Code (EMPFOHLEN) + +1. **Öffne das Projekt in VS Code**: + ```bash + cd netbird-msp-appliance + code . + ``` + +2. **Installiere Claude Code Plugin** in VS Code + +3. **Folge der Anleitung** in `VS_CODE_SETUP.md` + +4. **Starte Claude Code** und sage: + ``` + Bitte lies CLAUDE_CODE_SPEC.md und implementiere + das komplette NetBird MSP Appliance Projekt. + ``` + +5. **Claude Code wird**: + - Alle fehlenden Dateien erstellen + - Backend implementieren + - Frontend bauen + - Tests hinzufügen + - Alles dokumentieren + +**Erwartete Entwicklungszeit: 2-3 Stunden** + +### Option 2: Manuell entwickeln + +Folge der Struktur in `CLAUDE_CODE_SPEC.md` und implementiere Schritt für Schritt: + +1. Backend Services +2. API Routers +3. Templates +4. Frontend +5. Tests + +## 💾 System-Anforderungen + +### Für 100 Kunden: + +| Komponente | Minimum | Empfohlen | +|------------|---------|-----------| +| **CPU** | 8 Cores | 16 Cores | +| **RAM** | 64 GB | 128 GB | +| **Disk** | 500 GB SSD | 1 TB NVMe | +| **OS** | Ubuntu 22.04 | Ubuntu 24.04 | +| **Network** | 100 Mbps | 1 Gbps | + +### Benötigte Ports: +- **TCP 8000**: Web UI +- **UDP 3478-3577**: NetBird Relay (100 Ports für 100 Kunden) + +### Berechnung: +``` +RAM pro Kunde: ~600 MB +100 Kunden: 60 GB ++ OS & Appliance: 8 GB += 68 GB total (64 GB Minimum) +``` + +## 🔧 Installation (nach Entwicklung) + +```bash +# 1. Repository clonen +git clone https://github.com/yourusername/netbird-msp-appliance.git +cd netbird-msp-appliance + +# 2. Installer ausführen +chmod +x install.sh +sudo ./install.sh + +# 3. Web UI öffnen +# Browser: http://YOUR_SERVER_IP:8000 +# Login mit Credentials aus Installer-Output +``` + +## 📊 Features + +- ✅ **Multi-Tenant**: 100+ isolierte NetBird-Instanzen +- ✅ **Web-basierte Konfiguration**: Keine Config-Files manuell editieren +- ✅ **Automatisches Deployment**: < 2 Minuten pro Kunde +- ✅ **NPM Integration**: Automatische SSL-Zertifikate +- ✅ **Monitoring**: Health Checks, Container Status, Logs +- ✅ **Docker-basiert**: Einfaches Update und Wartung +- ✅ **One-Click Installation**: Ein Befehl, fertig + +## 🔐 Sicherheit + +- Passwort-Hashing mit bcrypt +- Token-Verschlüsselung mit Fernet +- Input-Validation via Pydantic +- SQL-Injection-Schutz via SQLAlchemy ORM +- Rate-Limiting für APIs + +## 📚 Dokumentation + +| Dokument | Zweck | +|----------|-------| +| README.md | Vollständige Dokumentation | +| QUICKSTART.md | 10-Minuten Quick Start | +| ARCHITECTURE.md | System-Architektur Details | +| CLAUDE_CODE_SPEC.md | Implementierungs-Spezifikation | +| VS_CODE_SETUP.md | VS Code + Claude Code Guide | +| CONTRIBUTING.md | Contribution Guidelines | + +## 🎓 Learning Resources + +Dieses Projekt ist auch ein **exzellentes Lernprojekt** für: +- FastAPI Backend Development +- Docker Container Orchestration +- Multi-Tenant SaaS Architecture +- Nginx Proxy Manager Integration +- SQLAlchemy ORM +- Jinja2 Templating +- Bootstrap 5 Frontend + +## 🤝 Support & Community + +- **Issues**: GitHub Issues für Bugs und Features +- **Discussions**: GitHub Discussions für Fragen +- **Email**: support@yourdomain.com + +## 📝 License + +MIT License - siehe LICENSE Datei + +## 🙏 Credits + +- **NetBird Team** - für das großartige Open-Source VPN +- **FastAPI** - für das moderne Python Framework +- **Nginx Proxy Manager** - für einfaches Reverse Proxy Management + +--- + +## 📞 Next Steps + +1. **Entwicklung starten**: Öffne VS_CODE_SETUP.md +2. **Claude Code nutzen**: Folge der Anleitung +3. **Testen**: Lokal mit Docker testen +4. **Deployen**: Auf VM installieren +5. **Ersten Kunden anlegen**: Web UI nutzen + +**Viel Erfolg mit deiner NetBird MSP Appliance! 🚀** + +--- + +*Erstellt für einfaches Deployment und perfekte Integration mit Claude Code* diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..93cc852 --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,148 @@ +# 🚀 NetBird MSP Appliance - Quick Start Guide + +Get up and running in 10 minutes! + +## Prerequisites + +- Ubuntu 22.04 or 24.04 LTS +- Root access +- 64GB RAM minimum (for 100 customers) +- 500GB SSD minimum +- Domain with wildcard DNS (*.yourdomain.com) + +## Installation (3 commands!) + +```bash +# 1. Clone repository +git clone https://github.com/yourusername/netbird-msp-appliance.git +cd netbird-msp-appliance + +# 2. Run installer +chmod +x install.sh +sudo ./install.sh + +# 3. Access web UI +# Open: http://YOUR_SERVER_IP:8000 +# Default login will be shown at end of installation +``` + +## Post-Installation Configuration + +### 1. First Login +- Use credentials displayed after installation +- **CHANGE PASSWORD IMMEDIATELY** in Settings + +### 2. Configure System (Settings → System Configuration) + +```yaml +Base Domain: yourdomain.com +Admin Email: admin@yourdomain.com +NPM API URL: http://nginx-proxy-manager:81/api +NPM API Token: +``` + +### 3. Configure Firewall + +```bash +# Allow web interface +sudo ufw allow 8000/tcp + +# Allow NetBird relay ports (for up to 100 customers) +sudo ufw allow 3478:3577/udp + +# Apply rules +sudo ufw reload +``` + +### 4. Get NPM API Token + +1. Access your Nginx Proxy Manager +2. Go to Users → Edit your user +3. Copy the API token +4. Paste in NetBird MSP Appliance Settings + +## Deploy Your First Customer + +1. Click "New Customer" +2. Fill in: + - Name: "Test Customer" + - Subdomain: "test" (becomes test.yourdomain.com) + - Email: customer@example.com + - Max Devices: 20 +3. Click "Deploy" +4. Wait 60-90 seconds +5. Done! Share the setup URL with your customer + +## Verify DNS Configuration + +Before deploying customers, ensure wildcard DNS works: + +```bash +# Test DNS resolution +nslookup test.yourdomain.com +# Should return your server IP + +# Or +dig test.yourdomain.com +``` + +## Troubleshooting + +### Customer deployment fails +```bash +# Check logs +docker logs netbird-msp-appliance + +# Check NPM connectivity +curl -I http://nginx-proxy-manager:81/api +``` + +### NPM not accessible +Make sure NPM is on the same Docker network: +```bash +docker network connect npm-network +``` + +### Ports already in use +```bash +# Check what's using port 8000 +sudo lsof -i :8000 + +# Kill process if needed +sudo kill -9 +``` + +## Next Steps + +- Read full [README.md](README.md) for details +- Check [CONTRIBUTING.md](CONTRIBUTING.md) to contribute +- Join discussions for support + +## Quick Commands + +```bash +# View logs +docker logs -f netbird-msp-appliance + +# Restart appliance +docker restart netbird-msp-appliance + +# Stop all customer instances +docker stop $(docker ps -q --filter "name=netbird-kunde") + +# Backup database +docker exec netbird-msp-appliance cp /app/data/netbird_msp.db /app/data/backup-$(date +%Y%m%d).db +``` + +## System Requirements Calculator + +| Customers | RAM | CPU | Disk | +|-----------|-----|-----|------| +| 25 | 16GB | 4 cores | 200GB | +| 50 | 32GB | 8 cores | 350GB | +| 100 | 64GB | 16 cores | 500GB | +| 200 | 128GB | 32 cores | 1TB | + +Formula: `(Customers × 600MB) + 8GB (OS + Appliance)` + +Happy MSP-ing! 🎉 diff --git a/README.md b/README.md index 5f0def6..b29cc18 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,519 @@ -# NetBirdMSP-Appliance +# NetBird MSP Appliance +🚀 **Self-Hosted Multi-Tenant NetBird Management Platform** + +A complete management solution for running 100+ isolated NetBird instances for your MSP business. Manage all your customers' NetBird networks from a single, powerful web interface. + +![License](https://img.shields.io/badge/license-MIT-blue.svg) +![Docker](https://img.shields.io/badge/docker-required-blue.svg) +![Python](https://img.shields.io/badge/python-3.11+-blue.svg) + +--- + +## 📋 Table of Contents + +- [Features](#features) +- [Architecture](#architecture) +- [System Requirements](#system-requirements) +- [Quick Start](#quick-start) +- [Configuration](#configuration) +- [Usage](#usage) +- [API Documentation](#api-documentation) +- [Troubleshooting](#troubleshooting) +- [Contributing](#contributing) +- [License](#license) + +--- + +## ✨ Features + +- **🎯 Multi-Tenant Management**: Manage 100+ isolated NetBird instances from one dashboard +- **🔒 Complete Isolation**: Each customer gets their own NetBird instance with separate databases +- **🌐 Nginx Proxy Manager Integration**: Automatic SSL certificate management and reverse proxy setup +- **🐳 Docker-Based**: Everything runs in containers for easy deployment and updates +- **📊 Web Dashboard**: Modern, responsive UI for managing customers and deployments +- **🚀 One-Click Deployment**: Deploy new customer instances in under 2 minutes +- **📈 Monitoring**: Real-time status monitoring and health checks +- **🔄 Automated Updates**: Bulk update NetBird containers across all customers +- **💾 Backup & Restore**: Built-in backup functionality for all customer data +- **🔐 Secure by Default**: Encrypted credentials, API tokens, and secrets management + +--- + +## 🏗️ Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ NetBird MSP Appliance │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ │ +│ │ Web GUI │───▶│ FastAPI │──▶│ SQLite DB │ │ +│ │ (Bootstrap) │ │ Backend │ │ │ │ +│ └──────────────┘ └──────────────┘ └───────────────┘ │ +│ │ │ +│ ┌───────────────────┼───────────────────┐ │ +│ ▼ ▼ ▼ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Docker │ │ NPM │ │ Firewall │ │ +│ │ Engine │ │ API │ │ Manager │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ┌───────────────────┴───────────────────┐ + ▼ ▼ +┌──────────────────┐ ┌──────────────────┐ +│ Customer 1 │ │ Customer 100 │ +│ ┌────────────┐ │ │ ┌────────────┐ │ +│ │ Management │ │ │ │ Management │ │ +│ │ Signal │ │ ... │ │ Signal │ │ +│ │ Relay │ │ │ │ Relay │ │ +│ │ Dashboard │ │ │ │ Dashboard │ │ +│ └────────────┘ │ │ └────────────┘ │ +└──────────────────┘ └──────────────────┘ + kunde1.domain.de kunde100.domain.de + UDP 3478 UDP 3577 +``` + +### Components per Customer Instance: +- **Management Service**: API and network state management +- **Signal Service**: WebRTC signaling for peer connections +- **Relay Service**: STUN/TURN server for NAT traversal (requires public UDP port) +- **Dashboard**: Web UI for end-users + +All services are accessible via HTTPS through Nginx Proxy Manager, except the Relay STUN port which requires direct UDP access. + +--- + +## 💻 System Requirements + +### For 100 Customers (10-20 devices per customer) + +| Component | Minimum | Recommended | Notes | +|-----------|---------|-------------|-------| +| **CPU** | 8 cores | 16 cores | More cores = better concurrent deployment performance | +| **RAM** | 64 GB | 128 GB | ~600 MB per customer instance + OS overhead | +| **Storage** | 500 GB SSD | 1 TB NVMe SSD | Fast I/O critical for Docker performance | +| **Network** | 100 Mbps | 1 Gbps | Dedicated server recommended | +| **OS** | Ubuntu 22.04 LTS | Ubuntu 24.04 LTS | Other Debian-based distros work too | + +### Resource Calculation Formula: +``` +Per Customer Instance: +- Management: ~100 MB RAM +- Signal: ~50 MB RAM +- Relay: ~150 MB RAM +- Dashboard: ~100 MB RAM +Total: ~400-600 MB RAM per customer + +For 100 customers: 40-60 GB RAM + 8 GB for OS + 8 GB for Appliance = ~64 GB minimum +``` + +### Port Requirements: +- **TCP 8000**: NetBird MSP Appliance Web UI +- **UDP 3478-3577**: STUN/TURN relay ports (one per customer) + - Customer 1: UDP 3478 + - Customer 2: UDP 3479 + - ... + - Customer 100: UDP 3577 + +**⚠️ Important**: Your firewall must allow UDP ports 3478-3577 for full NetBird functionality! + +### Prerequisites: +- **Docker Engine** 24.0+ with Docker Compose plugin +- **Nginx Proxy Manager** (running separately or on same host) +- **Domain with wildcard DNS** (e.g., `*.yourdomain.com` → your server IP) +- **Root or sudo access** to the Linux VM + +--- + +## 🚀 Quick Start + +### 1. Clone the Repository + +```bash +git clone https://github.com/yourusername/netbird-msp-appliance.git +cd netbird-msp-appliance +``` + +### 2. Run the Installation Script + +```bash +chmod +x install.sh +sudo ./install.sh +``` + +The installer will: +- ✅ Check system requirements +- ✅ Install Docker if needed +- ✅ Create Docker network +- ✅ Generate secure secrets +- ✅ Build and start all containers +- ✅ Initialize the database + +### 3. Complete the Setup Wizard + +Open your browser and navigate to: +``` +http://your-server-ip:8000 +``` + +On first access, you'll be guided through: +1. **Admin Account Creation**: Set your admin username and password +2. **System Configuration**: + - Base Domain (e.g., `yourdomain.com`) + - Admin Email + - NPM API URL (e.g., `http://npm-host:81/api`) + - NPM API Token +3. **Firewall Rules**: Instructions for opening required UDP ports + +### 4. Deploy Your First Customer + +1. Click **"New Customer"** button +2. Fill in customer details: + - Name + - Subdomain (e.g., `customer1` → `customer1.yourdomain.com`) + - Email + - Max Devices +3. Click **"Deploy"** +4. Wait ~60-90 seconds +5. Done! ✅ + +The system will automatically: +- Assign a unique UDP port for the relay +- Generate all config files +- Start Docker containers +- Create NPM proxy hosts with SSL +- Provide the setup URL + +--- + +## ⚙️ Configuration + +### Environment Variables + +Create a `.env` file (or use the one generated by installer): + +```bash +# Security +SECRET_KEY=your-secure-random-key-here +ADMIN_USERNAME=admin +ADMIN_PASSWORD=your-secure-password + +# Nginx Proxy Manager +NPM_API_URL=http://nginx-proxy-manager:81/api +NPM_API_TOKEN=your-npm-api-token + +# System +DATA_DIR=/opt/netbird-instances +DOCKER_NETWORK=npm-network +BASE_DOMAIN=yourdomain.com +ADMIN_EMAIL=admin@yourdomain.com + +# NetBird Images (optional - defaults to latest) +NETBIRD_MANAGEMENT_IMAGE=netbirdio/management:latest +NETBIRD_SIGNAL_IMAGE=netbirdio/signal:latest +NETBIRD_RELAY_IMAGE=netbirdio/relay:latest +NETBIRD_DASHBOARD_IMAGE=netbirdio/dashboard:latest + +# Database +DATABASE_PATH=/app/data/netbird_msp.db + +# Logging +LOG_LEVEL=INFO +``` + +### System Configuration via Web UI + +All settings can be configured through the web interface under **Settings** → **System Configuration**: + +- Base Domain +- Admin Email +- NPM Integration +- Docker Images +- Port Ranges +- Data Directories + +Changes are applied immediately without restart. + +--- + +## 📖 Usage + +### Managing Customers + +#### Create a New Customer +1. Dashboard → **New Customer** +2. Fill in details +3. Click **Deploy** +4. Share the setup URL with your customer + +#### View Customer Details +- Click on customer name in the list +- See deployment status, container health, logs +- Copy setup URL and credentials + +#### Start/Stop/Restart Containers +- Click the action buttons in the customer list +- Or use the detail view for more control + +#### Delete a Customer +- Click **Delete** → Confirm +- All containers, data, and NPM entries are removed + +### Monitoring + +The dashboard shows: +- **System Overview**: Total customers, active/inactive, errors +- **Resource Usage**: RAM, CPU, disk usage +- **Container Status**: Running/stopped/failed +- **Recent Activity**: Deployment logs and events + +### Bulk Operations + +Select multiple customers using checkboxes: +- **Bulk Update**: Update NetBird images across selected customers +- **Bulk Restart**: Restart all selected instances +- **Bulk Backup**: Create backups of selected customers + +### Backups + +#### Manual Backup +```bash +docker exec netbird-msp-appliance python -m app.backup --customer-id 1 +``` + +#### Automatic Backups +Configure in Settings → Backup: +- Schedule: Daily/Weekly +- Retention: Number of backups to keep +- Destination: Local path or remote storage + +--- + +## 🔌 API Documentation + +The appliance provides a REST API for automation. + +### Authentication +```bash +# Get API token (after login) +curl -X POST http://localhost:8000/api/auth/token \ + -d "username=admin&password=yourpassword" +``` + +### API Endpoints + +Full interactive documentation available at: +``` +http://your-server:8000/docs +``` + +**Common Endpoints:** +``` +POST /api/customers # Create customer + deploy +GET /api/customers # List all customers +GET /api/customers/{id} # Get customer details +PUT /api/customers/{id} # Update customer +DELETE /api/customers/{id} # Delete customer + +POST /api/customers/{id}/start # Start containers +POST /api/customers/{id}/stop # Stop containers +GET /api/customers/{id}/logs # Get container logs +GET /api/customers/{id}/health # Health check + +GET /api/status # System status +``` + +### Example: Create Customer via API +```bash +curl -X POST http://localhost:8000/api/customers \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Acme Corp", + "subdomain": "acme", + "email": "admin@acme.com", + "max_devices": 20 + }' +``` + +--- + +## 🔧 Troubleshooting + +### Common Issues + +#### 1. Customer deployment fails +**Symptom**: Status shows "error" after deployment + +**Solutions**: +- Check Docker logs: `docker logs netbird-msp-appliance` +- Verify NPM is accessible: `curl http://npm-host:81/api` +- Check available UDP ports: `netstat -ulnp | grep 347` +- View detailed logs in the customer detail page + +#### 2. NetBird clients can't connect +**Symptom**: Clients show "relay unavailable" + +**Solutions**: +- **Most common**: UDP port not open in firewall + ```bash + # Check if port is open + sudo ufw status + + # Open the relay port + sudo ufw allow 3478/udp + ``` +- Verify relay container is running: `docker ps | grep relay` +- Test STUN server: Use online STUN tester with your port + +#### 3. NPM integration not working +**Symptom**: SSL certificates not created + +**Solutions**: +- Verify NPM API token is correct +- Check NPM is on same Docker network: `npm-network` +- Test NPM API manually: + ```bash + curl -X GET http://npm-host:81/api/nginx/proxy-hosts \ + -H "Authorization: Bearer YOUR_TOKEN" + ``` + +#### 4. Out of memory errors +**Symptom**: Containers crashing, system slow + +**Solutions**: +- Check RAM usage: `free -h` +- Reduce number of customers or upgrade RAM +- Stop inactive customer instances +- Configure swap space: + ```bash + sudo fallocate -l 16G /swapfile + sudo chmod 600 /swapfile + sudo mkswap /swapfile + sudo swapon /swapfile + ``` + +### Debug Mode + +Enable debug logging: +```bash +# Edit .env +LOG_LEVEL=DEBUG + +# Restart +docker-compose restart +``` + +View detailed logs: +```bash +docker logs -f netbird-msp-appliance +``` + +### Getting Help + +1. **Check the logs**: Most issues are explained in the logs +2. **GitHub Issues**: Search existing issues or create a new one +3. **NetBird Community**: For NetBird-specific questions +4. **Documentation**: Read the full docs in `/docs` folder + +--- + +## 🔄 Updates + +### Updating the Appliance + +```bash +cd netbird-msp-appliance +git pull +docker-compose down +docker-compose up -d --build +``` + +### Updating NetBird Images + +**Via Web UI**: +1. Settings → System Configuration +2. Update image tags +3. Click "Save" +4. Use Bulk Update for customers + +**Via CLI**: +```bash +# Update all customer instances +docker exec netbird-msp-appliance python -m app.update --all +``` + +--- + +## 🛡️ Security Best Practices + +1. **Change default credentials** immediately after installation +2. **Use strong passwords** (20+ characters, mixed case, numbers, symbols) +3. **Keep NPM API token secure** - never commit to git +4. **Enable firewall** and only open required ports +5. **Regular updates** - both the appliance and NetBird images +6. **Backup regularly** - automate daily backups +7. **Use HTTPS** - always access the web UI via HTTPS (configure reverse proxy) +8. **Monitor logs** - check for suspicious activity +9. **Limit access** - use VPN or IP whitelist for the management interface + +--- + +## 📊 Performance Tuning + +### For 100+ Customers: + +```bash +# Increase Docker ulimits +# Add to /etc/docker/daemon.json +{ + "default-ulimits": { + "nofile": { + "Name": "nofile", + "Hard": 64000, + "Soft": 64000 + } + } +} + +# Restart Docker +sudo systemctl restart docker + +# Increase inotify limits +echo "fs.inotify.max_user_instances=512" | sudo tee -a /etc/sysctl.conf +echo "fs.inotify.max_user_watches=524288" | sudo tee -a /etc/sysctl.conf +sudo sysctl -p +``` + +--- + +## 🤝 Contributing + +Contributions welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) first. + +--- + +## 📄 License + +MIT License - see [LICENSE](LICENSE) file for details. + +--- + +## 🙏 Acknowledgments + +- **NetBird Team** - for the amazing open-source VPN solution +- **FastAPI** - for the high-performance Python framework +- **Nginx Proxy Manager** - for easy reverse proxy management + +--- + +## 📞 Support + +- **Issues**: [GitHub Issues](https://github.com/yourusername/netbird-msp-appliance/issues) +- **Discussions**: [GitHub Discussions](https://github.com/yourusername/netbird-msp-appliance/discussions) +- **Email**: support@yourdomain.com + +--- + +**Made with ❤️ for MSPs and System Administrators** diff --git a/VS_CODE_SETUP.md b/VS_CODE_SETUP.md new file mode 100644 index 0000000..9ec7d73 --- /dev/null +++ b/VS_CODE_SETUP.md @@ -0,0 +1,215 @@ +# Visual Studio Code + Claude Code Setup Guide + +## Für den Entwicklungsprozess mit Claude Code Plugin + +### Schritt 1: Repository vorbereiten + +```bash +# Repository in VS Code öffnen +cd /path/to/netbird-msp-appliance +code . +``` + +### Schritt 2: Claude Code Plugin installieren + +1. Öffne VS Code Extensions (Ctrl+Shift+X) +2. Suche nach "Claude Code" +3. Installiere das Plugin +4. Authentifiziere dich mit deinem Anthropic Account + +### Schritt 3: Claude Code verwenden + +#### Hauptaufgabe an Claude geben: + +Öffne Claude Code Chat und schicke folgende Nachricht: + +``` +Bitte lies die Datei CLAUDE_CODE_SPEC.md und implementiere das komplette NetBird MSP Appliance Projekt gemäß den Spezifikationen. + +Prioritäten: +1. Erstelle zuerst die komplette Dateistruktur +2. Implementiere die Datenbank-Modelle und API-Routers +3. Baue die Services (docker_service, npm_service, netbird_service) +4. Erstelle die Jinja2 Templates +5. Baue das Frontend (HTML/CSS/JS) +6. Füge Tests hinzu + +Achte besonders auf: +- Type hints in allen Python-Funktionen +- Comprehensive Error Handling +- Docstrings für alle Funktionen +- Clean Code und Modularität +- Security Best Practices + +Beginne mit der Implementierung! +``` + +### Schritt 4: Iteratives Entwickeln + +Claude Code wird Schritt für Schritt: + +1. **Struktur erstellen** + - Alle Verzeichnisse anlegen + - Basis-Dateien erstellen + - Dependencies auflisten + +2. **Backend implementieren** + - Database Models + - API Routers + - Services + - Utils + +3. **Templates erstellen** + - docker-compose.yml.j2 + - management.json.j2 + - relay.env.j2 + +4. **Frontend bauen** + - HTML Dashboard + - CSS Styling + - JavaScript Logic + +5. **Testen & Debugging** + - Unit Tests + - Integration Tests + - Manuelle Tests + +### Schritt 5: Spezifische Anweisungen + +Du kannst Claude Code auch spezifische Aufgaben geben: + +``` +"Implementiere jetzt den docker_service.py mit allen Funktionen +zum Starten, Stoppen und Überwachen von Docker-Containern" +``` + +``` +"Erstelle das Frontend Dashboard mit Bootstrap 5 und mache es +responsive für Mobile/Tablet/Desktop" +``` + +``` +"Füge comprehensive Error Handling zum Deployment-Prozess hinzu +mit automatischem Rollback bei Fehlern" +``` + +### Schritt 6: Code Review + +Nach jeder größeren Implementierung: + +``` +"Bitte reviewe den Code in app/services/docker_service.py und +verbessere Error Handling und füge Type Hints hinzu" +``` + +### Schritt 7: Testing + +``` +"Erstelle Unit Tests für alle Services und API-Endpunkte" +``` + +### Schritt 8: Dokumentation + +``` +"Erstelle API-Dokumentation und ergänze die README mit +Deployment-Beispielen" +``` + +## Tipps für effektive Zusammenarbeit mit Claude Code + +### ✅ Gute Anweisungen: +- "Implementiere X gemäß der Spezifikation in CLAUDE_CODE_SPEC.md" +- "Füge Error Handling für Y hinzu" +- "Refactore Z für bessere Wartbarkeit" +- "Erstelle Tests für Modul A" + +### ❌ Vermeiden: +- Zu vage Anweisungen ohne Kontext +- Mehrere komplexe Aufgaben gleichzeitig +- Widersprüchliche Requirements + +## Debugging mit Claude Code + +``` +"Der Deployment-Prozess schlägt fehl mit diesem Fehler: [Fehlermeldung]. +Bitte analysiere das Problem und fixe es." +``` + +``` +"Die NPM-Integration funktioniert nicht. Logs zeigen: [Logs]. +Was ist das Problem?" +``` + +## Projekt-Struktur prüfen + +Claude Code kann die Struktur validieren: + +``` +"Überprüfe, ob alle Dateien gemäß CLAUDE_CODE_SPEC.md +vorhanden und korrekt strukturiert sind" +``` + +## Abschließende Checks + +``` +"Führe folgende Checks durch: +1. Alle Dependencies in requirements.txt? +2. Docker-Compose gültig? +3. Alle Environment Variables dokumentiert? +4. README vollständig? +5. Installation Script funktional?" +``` + +## Deployment vorbereiten + +``` +"Bereite das Projekt für Production Deployment vor: +1. Sicherheits-Audit +2. Performance-Optimierungen +3. Logging verbessern +4. Monitoring Endpoints hinzufügen" +``` + +--- + +## Erwartete Entwicklungszeit mit Claude Code + +- **Basis-Struktur**: 10-15 Minuten +- **Backend (APIs + Services)**: 30-45 Minuten +- **Templates**: 10-15 Minuten +- **Frontend**: 30-45 Minuten +- **Tests**: 20-30 Minuten +- **Dokumentation & Polish**: 15-20 Minuten + +**Gesamt: ~2-3 Stunden** für ein vollständiges, produktionsreifes System! + +--- + +## Nach der Entwicklung + +### Lokales Testen: + +```bash +# Docker Compose starten +docker compose up -d + +# Logs prüfen +docker logs -f netbird-msp-appliance + +# In Browser öffnen +http://localhost:8000 +``` + +### Auf VM deployen: + +```bash +# Repository auf VM clonen +git clone https://github.com/yourusername/netbird-msp-appliance.git +cd netbird-msp-appliance + +# Installer ausführen +chmod +x install.sh +sudo ./install.sh +``` + +Viel Erfolg! 🚀 diff --git a/netbird-msp-appliance.tar.gz b/netbird-msp-appliance.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..45403a6e30109a8ad1d06ea614db74c4fe9567d2 GIT binary patch literal 23608 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 P#y