The modal was showing '#2' instead of the customer name when opened
from the customer detail view, because the dashboard table row was
not visible. Now the name is passed directly from the button's onclick
context where data.name is already available.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After a container update, browsers serve stale app.js and lang/*.json
from cache, causing old UI code and missing translations to appear.
- serve_index() now reads the git commit hash and injects ?v=COMMIT into
all static asset URLs (app.js, i18n.js, styles.css) in index.html
- window.STATIC_VERSION is injected into the page so i18n.js can append
the same version to lang/*.json fetch calls
- index.html itself is served with Cache-Control: no-cache so the browser
always revalidates it and picks up new asset URLs on next load
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a confirmation modal when clicking Redeploy that lets the user choose:
- Keep Data: containers are recreated without wiping the instance directory.
NetBird database, peer configs, and encryption keys are preserved.
- Fresh Deploy: full undeploy (removes all data) then redeploy from scratch.
Backend changes:
- POST /customers/{id}/deploy accepts keep_data query param (default false)
- When keep_data=true, undeploy_customer is skipped entirely
- deploy_customer now reuses existing npm_proxy_id/stream_id when the
deployment record is still present (avoids duplicate NPM proxy entries)
- DNS record creation is skipped on keep_data redeploy (already exists)
Frontend changes:
- customerAction('deploy') opens the redeploy modal instead of calling API
- showRedeployModal(id) shows the two-option confirmation card dialog
- confirmRedeploy(keepData) calls the API with the correct parameter
- i18n keys added in en.json and de.json
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Docker Hub REST API returns per-platform manifest digests, while
docker image inspect RepoDigests stores the manifest list digest.
These two values never match, causing update_available to always be
True even after a fresh pull.
Fix: use registry-1.docker.io/v2/{name}/manifests/{tag} with anonymous
auth and read the Docker-Content-Digest response header, which is the
exact same digest that docker pull stores in RepoDigests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add NetBird Container Updates section (digest check, pull, bulk update)
- Add update indicators, dark mode, LDAP/AD, Windows DNS to features
- Correct Settings table: add all tabs, remove incorrect Monitoring entry
- Split Docker Images tab out of System tab
- Add sudo install note for fresh Debian minimal
- Rewrite Updating NetBird Images section with new UI-based workflow
- Add new monitoring/image API endpoints to API docs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Promotes alpha-1.25 to beta-1.0 (stable branch).
Highlights:
- NetBird container update management (check / pull / update per customer + bulk)
- Visual update badges on dashboard and customer detail
- Dark mode toggle with localStorage persistence
- User role management for Azure AD / LDAP users
- Branding logo persistence across updates (Docker volume)
- Favicon, NPM stream removal, MFA (TOTP)
- LDAP / Active Directory and Azure AD SSO
- Windows DNS integration
- Settings restructure and Git branch dropdown
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dashboard: update badge (orange) injected lazily into customer Status cell
after table renders via GET /monitoring/customers/local-update-status
(local-only Docker inspect, no Hub call on every page load)
- Customer detail Deployment tab: "Update Images" button with spinner,
shows success/error inline without page reload
- Monitoring Update All: now synchronous + sequential (one customer at a
time), shows live spinner + per-customer results table on completion
- Settings > Docker Images: "Pull from Docker Hub" button with spinner
and inline status message
- /monitoring/customers/local-update-status: new lightweight endpoint
(no network, pure local Docker inspect)
- /monitoring/customers/update-all: removed BackgroundTasks, now awaits
each customer sequentially and returns detailed per-customer results
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New image_service.py: Docker Hub digest check (no pull), local digest/ID
comparison, pull_all_images, per-customer container image status, and
update_customer_containers (docker compose up -d, data-safe)
- Monitoring endpoints: GET /images/check (hub vs local + per-customer
needs_update), POST /images/pull (background), POST /customers/update-all
- Deployment endpoint: POST /{id}/update-images (single-customer update)
- Monitoring page: "NetBird Container Updates" card with Check / Pull / Update
All buttons; image status table and per-customer update table with inline
update buttons
- i18n: added keys in en.json and de.json
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Backend: add admin-only guard + role validation to PUT /users/{id}
- Backend: prevent admins from changing their own role
- Frontend: role toggle button (person-check / person-dash) per user row
- Frontend: admin badge green, viewer badge secondary, ldap badge blue
- i18n: add makeAdmin / makeViewer translations (de + en)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mount ./data/uploads into /app/static/uploads so uploaded logos
survive image rebuilds during the update process.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses Bootstrap 5.3 native data-bs-theme with localStorage persistence.
Inline script in <head> prevents flash on page load.
Moon/sun icon in top-right navbar switches between light and dark.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In stop_customer, start_customer and restart_customer the local variable
'customer' was referenced on the instance_dir line before it was assigned
(it was only queried after the docker compose call). This caused an
UnboundLocalError (HTTP 500) on every stop/start/restart action.
Fix: move the customer query to the top of each function alongside the
deployment and config queries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Register SOURCE_DIR as git safe.directory before pulling so the
process (root inside container) can access repos owned by a host user
- Run 'git fetch --tags' after pull so git describe always finds the
latest tag for version.json — git pull does not reliably fetch all tags
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The project name was hardcoded as 'netbirdmsp-appliance' but Docker Compose
derives the project name from the install directory name ('netbird-msp').
This caused Phase A to build an image under the wrong project name and
Phase B to start the replacement container under a mismatched project,
leaving the old container running indefinitely.
Fix: read the 'com.docker.compose.project' label from the running container
at update time. Both Phase A (build) and Phase B (docker compose up) now
use the detected project name. Falls back to SOURCE_DIR basename if the
inspect fails.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add /relay* location block to Caddyfile template so that NetBird relay
WebSocket connections (rels://) are correctly forwarded to the relay
container instead of falling through to the dashboard handler.
Without this fix, all relay WebSocket connections silently hit the
dashboard container, causing STUN/relay connectivity failures for all
deployed NetBird instances.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Settings page: 3 new tabs (Windows DNS, LDAP / AD, Updates)
- Windows DNS tab: enable toggle, server/zone/username/password/record-IP,
save + test connection button
- LDAP tab: enable toggle, server/port/SSL/bind-DN/password/base-DN/
user-filter/group-DN, save + test connection button
- Updates tab: current + latest version info card with update-available
badge, one-click update button (git pull + rebuild), git repo/branch/
token settings form
- Azure AD tab: added Allowed Group Object ID field
- app.js: settings-dns-form, settings-ldap-form, settings-git-form
submit handlers; testDnsConnection(), testLdapConnection(),
loadVersionInfo(), triggerUpdate() functions; loadSettings() extended
for all new fields
- en.json: all new translation keys
- de.json: complete German translation (was mostly empty before)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Bake version info (commit, branch, date) into /app/version.json at build time
via Docker ARG GIT_COMMIT/GIT_BRANCH/GIT_COMMIT_DATE
- Mount source directory as /app-source for in-container git operations
- Add git config safe.directory for /app-source (ownership mismatch fix)
- Add SystemConfig fields: git_repo_url, git_branch, git_token_encrypted
- Add DB migrations for the three new columns
- Add git_token encryption in update_settings() handler
- New endpoints:
GET /api/settings/version — current version + latest from Gitea API
POST /api/settings/update — DB backup + git pull + docker compose rebuild
- New service: app/services/update_service.py
get_current_version() — reads /app/version.json
check_for_updates() — queries Gitea API for latest commit on branch
backup_database() — timestamped SQLite copy to /app/backups/
trigger_update() — git pull + fire-and-forget compose rebuild
- New script: update.sh — SSH-based manual update with health check
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Windows DNS (WinRM):
- New dns_service.py: create/delete A-records via PowerShell over WinRM (NTLM)
- Idempotent create (removes existing record first), graceful delete
- DNS failures are non-fatal — deployment continues, error logged
- test-dns endpoint: GET /api/settings/test-dns
- Integrated into deploy_customer() and undeploy_customer()
LDAP / Active Directory auth:
- New ldap_service.py: service-account bind + user search + user bind (ldap3)
- Optional AD group restriction via ldap_group_dn
- Login flow: LDAP first → local fallback (prevents admin lockout)
- LDAP users auto-created with auth_provider="ldap" and role="viewer"
- test-ldap endpoint: GET /api/settings/test-ldap
- reset-password/reset-mfa guards extended to block LDAP users
All credentials (dns_password, ldap_bind_password) encrypted with Fernet.
New DB columns added via backwards-compatible migrations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CORS: remove allow_origins=["*"]; restrict to ALLOWED_ORIGINS env var
(comma-separated list); default is no cross-origin access. Removed
allow_credentials=True and method/header wildcards.
- Security headers middleware: add X-Content-Type-Options, X-Frame-Options,
X-XSS-Protection, Referrer-Policy, Strict-Transport-Security to all
responses.
- users.py: guard POST /api/users so only users with role="admin" can
create new accounts (prevents privilege escalation by non-admin roles).
- auth.py: remove raw exception detail from Azure AD 500 response to
avoid leaking internal error messages / stack traces to clients.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract shared SlowAPI limiter to app/limiter.py to break circular
import between app.main and app.routers.auth
- Seed default SystemConfig row (id=1) on first DB init so settings
page works out of the box
- Make all docker_service.compose_* functions async (run_in_executor)
so long docker pulls/stops no longer block the async event loop
- Propagate async to netbird_service stop/start/restart and await
callers in deployments router
- Move customer delete to BackgroundTasks so the HTTP response returns
immediately and avoids frontend "Network error" on slow machines
- docker-compose: add :z SELinux labels, mount docker.sock directly,
add security_opt label:disable for socket access, extra_hosts for
host.docker.internal, enable DELETE/VOLUMES on socket proxy
- npm_service: auto-detect outbound host IP via UDP socket when
HOST_IP env var is not set
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>