2 Commits

15 changed files with 162 additions and 65 deletions

15
.gitignore vendored
View File

@@ -69,5 +69,20 @@ PROJECT_SUMMARY.md
QUICKSTART.md QUICKSTART.md
VS_CODE_SETUP.md VS_CODE_SETUP.md
# Gemini / Antigravity
.gemini/
# Windows artifacts # Windows artifacts
nul nul
# Debug / temp files (generated during development & testing)
out.txt
containers.txt
helper.txt
logs.txt
port.txt
env.txt
network.txt
update_helper.txt
state.txt
hostpath.txt

View File

@@ -334,6 +334,19 @@ async def get_version(
return result return result
@router.get("/branches")
async def get_branches(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Return a list of available branches from the configured git remote."""
config = get_system_config(db)
if not config or not config.git_repo_url:
return []
branches = await update_service.get_remote_branches(config)
return branches
@router.post("/update") @router.post("/update")
async def trigger_update( async def trigger_update(
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),

View File

@@ -5,6 +5,7 @@ import logging
import os import os
import shutil import shutil
import subprocess import subprocess
import httpx
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@@ -103,8 +104,8 @@ async def check_for_updates(config: Any) -> dict:
"tag": latest_tag, "tag": latest_tag,
"commit": short_sha, "commit": short_sha,
"commit_full": full_sha, "commit_full": full_sha,
"message": latest_commit.get("commit", {}).get("message", "").split("\n")[0], "message": latest_commit.get("commit", {}).get("message", "").split("\n")[0] if latest_commit.get("commit") else "",
"date": latest_commit.get("commit", {}).get("committer", {}).get("date", ""), "date": latest_commit.get("timestamp", ""),
"branch": branch, "branch": branch,
} }
@@ -130,6 +131,42 @@ async def check_for_updates(config: Any) -> dict:
} }
async def get_remote_branches(config: Any) -> list[str]:
"""Query the Gitea API for available branches on the configured repository.
Returns a list of branch names (e.g., ['main', 'unstable', 'development']).
If the repository URL is not configured or an error occurs, returns an empty list.
"""
if not config.git_repo_url:
return []
repo_url = config.git_repo_url.rstrip("/")
parts = repo_url.split("/")
if len(parts) < 5:
return []
base_url = "/".join(parts[:-2])
owner = parts[-2]
repo = parts[-1]
branches_api = f"{base_url}/api/v1/repos/{owner}/{repo}/branches?limit=100"
headers = {}
if config.git_token:
headers["Authorization"] = f"token {config.git_token}"
try:
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.get(branches_api, headers=headers)
if resp.status_code == 200:
data = resp.json()
if isinstance(data, list):
return [branch.get("name") for branch in data if "name" in branch]
except Exception as exc:
logger.error("Error fetching branches: %s", exc)
return []
def backup_database(db_path: str) -> str: def backup_database(db_path: str) -> str:
"""Create a timestamped backup of the SQLite database. """Create a timestamped backup of the SQLite database.

View File

@@ -1,9 +0,0 @@
NAMES STATUS IMAGE
netbird-msp-appliance Up 3 minutes (healthy) netbirdmsp-appliance-netbird-msp-appliance
msp-updater Exited (0) 3 minutes ago netbirdmsp-appliance-netbird-msp-appliance:latest
netbird-kunde1-caddy Up 2 hours caddy:2-alpine
netbird-kunde1-signal Up 2 hours netbirdio/signal:latest
netbird-kunde1-dashboard Up 2 hours netbirdio/dashboard:latest
netbird-kunde1-relay Up 2 hours netbirdio/relay:latest
netbird-kunde1-management Up 2 hours netbirdio/management:latest
docker-socket-proxy Up 2 hours tecnativa/docker-socket-proxy:latest

View File

View File

@@ -1 +0,0 @@
Error response from daemon: No such container: msp-updater

View File

@@ -1,9 +0,0 @@
INFO: Started server process [1]
INFO: Waiting for application startup.
2026-02-22 14:53:59,694 [INFO] app.main: Starting NetBird MSP Appliance...
2026-02-22 14:53:59,744 [INFO] app.main: Database initialized.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: 127.0.0.1:45370 - "GET /api/health HTTP/1.1" 200 OK
INFO: 127.0.0.1:57724 - "GET /api/health HTTP/1.1" 200 OK
INFO: 127.0.0.1:56212 - "GET /api/health HTTP/1.1" 200 OK

View File

View File

@@ -1 +0,0 @@
msp-updater

View File

@@ -1,2 +0,0 @@
8000/tcp -> 0.0.0.0:8000
8000/tcp -> [::]:8000

View File

@@ -363,48 +363,54 @@
<!-- Sidebar --> <!-- Sidebar -->
<div class="col-md-3 mb-4 mb-md-0 border-end pe-3"> <div class="col-md-3 mb-4 mb-md-0 border-end pe-3">
<ul class="nav nav-pills flex-column" id="settings-tabs"> <ul class="nav nav-pills flex-column" id="settings-tabs">
<li class="nav-item"><a class="nav-link active" data-bs-toggle="pill"
href="#settings-system"><i class="bi bi-pc-display me-2"></i><span
data-i18n="settings.tabSystem">System</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-npm"><i
class="bi bi-router me-2"></i><span data-i18n="settings.tabNpm">NPM
Proxy</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-images"><i
class="bi bi-box me-2"></i><span
data-i18n="settings.tabImages">Images</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-branding"><i
class="bi bi-palette me-2"></i><span
data-i18n="settings.tabBranding">Branding</span></a></li>
<hr class="my-3 border-secondary opacity-25">
<div class="text-uppercase text-muted fw-bold mb-2 ps-3" <div class="text-uppercase text-muted fw-bold mb-2 ps-3"
style="font-size: 0.75rem; letter-spacing: 0.05em;"><i style="font-size: 0.75rem; letter-spacing: 0.05em;"><i
class="bi bi-shield-check me-1"></i>Auth & Users</div> class="bi bi-shield-check me-1"></i><span
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-users" data-i18n="settings.groupUsers">Benutzerverwaltung</span></div>
onclick="loadUsers()"><i class="bi bi-people me-2"></i><span
data-i18n="settings.tabUsers">Users</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-azure"><i <li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-azure"><i
class="bi bi-microsoft me-2"></i><span data-i18n="settings.tabAzure">Azure class="bi bi-microsoft me-2"></i><span data-i18n="settings.tabAzure">Azure
AD</span></a></li> AD</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-users"
onclick="loadUsers()"><i class="bi bi-people me-2"></i><span
data-i18n="settings.tabUsers">Benutzer</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-ldap"><i <li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-ldap"><i
class="bi bi-diagram-3 me-2"></i><span data-i18n="settings.tabLdap">LDAP / class="bi bi-diagram-3 me-2"></i><span data-i18n="settings.tabLdap">LDAP /
AD</span></a></li> AD</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-security"><i
class="bi bi-shield-lock me-2"></i><span
data-i18n="settings.tabSecurity">Sicherheit</span></a></li>
<hr class="my-3 border-secondary opacity-25"> <hr class="my-3 border-secondary opacity-25">
<div class="text-uppercase text-muted fw-bold mb-2 ps-3" <div class="text-uppercase text-muted fw-bold mb-2 ps-3"
style="font-size: 0.75rem; letter-spacing: 0.05em;"><i style="font-size: 0.75rem; letter-spacing: 0.05em;"><i
class="bi bi-gear-wide-connected me-1"></i>System</div> class="bi bi-gear-wide-connected me-1"></i><span
data-i18n="settings.groupSystem">Systemkonfiguration</span></div>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-branding"><i
class="bi bi-palette me-2"></i><span
data-i18n="settings.tabBranding">Branding</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-images"><i
class="bi bi-box me-2"></i><span data-i18n="settings.tabImages">NetBird Docker
Images</span></a></li>
<li class="nav-item"><a class="nav-link active" data-bs-toggle="pill"
href="#settings-system"><i class="bi bi-pc-display me-2"></i><span
data-i18n="settings.tabSystem">NetBird MSP System</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-update"
onclick="loadVersionInfo()"><i class="bi bi-cloud-arrow-down me-2"></i><span
data-i18n="settings.tabUpdate">NetBird MSP Updates</span></a></li>
<hr class="my-3 border-secondary opacity-25">
<div class="text-uppercase text-muted fw-bold mb-2 ps-3"
style="font-size: 0.75rem; letter-spacing: 0.05em;"><i
class="bi bi-link-45deg me-1"></i><span
data-i18n="settings.groupExternal">Umsysteme</span></div>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-npm"><i
class="bi bi-router me-2"></i><span data-i18n="settings.tabNpm">NPM
Proxy</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-dns"><i <li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-dns"><i
class="bi bi-hdd-network me-2"></i><span data-i18n="settings.tabDns">Windows class="bi bi-hdd-network me-2"></i><span data-i18n="settings.tabDns">Windows
DNS</span></a></li> DNS</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-update"
onclick="loadVersionInfo()"><i class="bi bi-cloud-arrow-down me-2"></i><span
data-i18n="settings.tabUpdate">Updates</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-security"><i
class="bi bi-shield-lock me-2"></i><span
data-i18n="settings.tabSecurity">Security</span></a></li>
</ul> </ul>
</div> </div>
@@ -1004,8 +1010,15 @@
<div class="col-md-4"> <div class="col-md-4">
<label class="form-label" <label class="form-label"
data-i18n="settings.gitBranch">Branch</label> data-i18n="settings.gitBranch">Branch</label>
<input type="text" class="form-control" id="cfg-git-branch" <div class="input-group">
placeholder="main"> <select class="form-select" id="cfg-git-branch">
<option value="main">main</option>
</select>
<button class="btn btn-outline-secondary" type="button"
onclick="loadGitBranches()" title="Aktualisieren">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
</div> </div>
<div class="col-md-8"> <div class="col-md-8">
<label class="form-label" data-i18n="settings.gitToken">Access Token <label class="form-label" data-i18n="settings.gitToken">Access Token

View File

@@ -872,6 +872,9 @@ async function loadSettings() {
} catch (err) { } catch (err) {
showSettingsAlert('danger', t('errors.failedToLoadSettings', { error: err.message })); showSettingsAlert('danger', t('errors.failedToLoadSettings', { error: err.message }));
} }
// Automatically fetch branches once the base config is populated
await loadGitBranches();
} }
function updateLogoPreview(logoPath) { function updateLogoPreview(logoPath) {
@@ -1183,6 +1186,42 @@ async function testLdapConnection() {
} }
} }
async function loadGitBranches() {
const branchSelect = document.getElementById('cfg-git-branch');
const currentVal = branchSelect.value;
// Disable mapping while loading
branchSelect.disabled = true;
branchSelect.innerHTML = `<option value="${currentVal}">${currentVal} (Loading...)</option>`;
try {
const branches = await api('GET', '/settings/branches');
branchSelect.innerHTML = '';
// Always ensure the currently saved branch is an option
if (currentVal && !branches.includes(currentVal)) {
branches.unshift(currentVal);
}
if (branches.length === 0) {
branchSelect.innerHTML = `<option value="main">main</option>`;
} else {
branches.forEach(b => {
const opt = document.createElement('option');
opt.value = b;
opt.textContent = b;
if (b === currentVal) opt.selected = true;
branchSelect.appendChild(opt);
});
}
} catch (err) {
showSettingsAlert('warning', `Failed to load branches: ${err.message}`);
branchSelect.innerHTML = `<option value="${currentVal}">${currentVal}</option>`;
} finally {
branchSelect.disabled = false;
}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Update / Version Management // Update / Version Management
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@@ -93,16 +93,19 @@
}, },
"settings": { "settings": {
"title": "Systemeinstellungen", "title": "Systemeinstellungen",
"tabSystem": "Systemkonfiguration", "tabSystem": "NetBird MSP System",
"tabNpm": "NPM Integration", "tabNpm": "NPM Proxy",
"tabImages": "Docker Images", "tabImages": "NetBird Docker Images",
"tabBranding": "Branding", "tabBranding": "Branding",
"tabUsers": "Benutzer", "tabUsers": "Benutzer",
"tabAzure": "Azure AD", "tabAzure": "Azure AD",
"tabDns": "Windows DNS", "tabDns": "Windows DNS",
"tabLdap": "LDAP / AD", "tabLdap": "LDAP / AD",
"tabUpdate": "Updates", "tabUpdate": "NetBird MSP Updates",
"tabSecurity": "Sicherheit", "tabSecurity": "Sicherheit",
"groupUsers": "Benutzerverwaltung",
"groupSystem": "Systemkonfiguration",
"groupExternal": "Umsysteme",
"baseDomain": "Basis-Domain", "baseDomain": "Basis-Domain",
"baseDomainPlaceholder": "ihredomain.com", "baseDomainPlaceholder": "ihredomain.com",
"baseDomainHint": "Kunden erhalten Subdomains: kunde.ihredomain.com", "baseDomainHint": "Kunden erhalten Subdomains: kunde.ihredomain.com",

View File

@@ -114,16 +114,19 @@
}, },
"settings": { "settings": {
"title": "System Settings", "title": "System Settings",
"tabSystem": "System Configuration", "tabSystem": "NetBird MSP System",
"tabNpm": "NPM Integration", "tabNpm": "NPM Proxy",
"tabImages": "Docker Images", "tabImages": "NetBird Docker Images",
"tabBranding": "Branding", "tabBranding": "Branding",
"tabUsers": "Users", "tabUsers": "Users",
"tabAzure": "Azure AD", "tabAzure": "Azure AD",
"tabDns": "Windows DNS", "tabDns": "Windows DNS",
"tabLdap": "LDAP / AD", "tabLdap": "LDAP / AD",
"tabUpdate": "Updates", "tabUpdate": "NetBird MSP Updates",
"tabSecurity": "Security", "tabSecurity": "Security",
"groupUsers": "User Management",
"groupSystem": "System Configuration",
"groupExternal": "External Systems",
"baseDomain": "Base Domain", "baseDomain": "Base Domain",
"baseDomainPlaceholder": "yourdomain.com", "baseDomainPlaceholder": "yourdomain.com",
"baseDomainHint": "Customers get subdomains: customer.yourdomain.com", "baseDomainHint": "Customers get subdomains: customer.yourdomain.com",
@@ -370,4 +373,4 @@
"confirmDeleteUser": "Delete user '{username}'? This cannot be undone.", "confirmDeleteUser": "Delete user '{username}'? This cannot be undone.",
"confirmResetPassword": "Reset password for '{username}'? A new random password will be generated." "confirmResetPassword": "Reset password for '{username}'? A new random password will be generated."
} }
} }

View File

@@ -1,4 +0,0 @@
Container netbird-msp-appliance Recreate
Container netbird-msp-appliance Recreated
Container netbird-msp-appliance Starting
Container netbird-msp-appliance Started