Commit Graph
41 Commits
Author SHA1 Message Date
sysopsandClaude Sonnet 5 0dbb916866 Fix nil-pointer crash generating QR codes for non-default servers
GetClients, GetClientByID, and SendRequestedConfigsToTelegram all
looked up the legacy "wg0" server unconditionally when building a
client's QR code / config, ignoring which server the client actually
belongs to. On any multi-server setup without a migrated wg0, the
server lookup returned a zero-value model.Server (error discarded),
and BuildClientConfig then dereferenced its nil KeyPair pointer,
panicking whenever a client's QR code was rendered.

Now looks up the client's own ServerID (falling back to wg0 only for
legacy clients with no ServerID set) and surfaces the lookup error
instead of silently continuing with an empty server.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 23:42:36 +02:00
sysopsandClaude Sonnet 5 067d0af323 Fix "no such file" error creating clients on non-default servers
SuggestIPAllocation always looked up the legacy "wg0" server regardless
of which server the request was for, so any setup without a migrated
wg0 (i.e. every fresh multi-server install) failed with
"open db/servers/wg0.json: no such file or directory" when adding a
new client.

Now accepts an optional server_id query param (falling back to wg0 for
the legacy bare routes), and the per-server clients page passes its
own server ID.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 23:39:26 +02:00
sysopsandClaude Sonnet 5 8a92958a84 Update DEVLOG session log
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 23:27:13 +02:00
sysopsandClaude Sonnet 5 fc0d192e59 Port upstream security fixes and features from ngoduykhanh/wireguard-ui
- Escape HTML in client list and wake-on-LAN names to prevent XSS
- Log successful/failed login attempts with remote address
- Fix leading-comma bug in AllowedIPs template when only extra allowed IPs are set
- Add PreUp script support for server interfaces (alongside existing PostUp/PreDown/PostDown)
- Fix endpoint parsing to support IPv6 addresses (upstream PR #223)

Cherry-picked from upstream PRs #656, #653, #680, #673, #223.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 23:26:30 +02:00
sysopsandClaude Sonnet 5 5a7709bc6e Add enable/disable toggle for the global firewall allow/block lists
New firewall.DisableGlobal() removes the wireguard_ui_global nftables
table without touching stored IP list entries, and firewall.IsGlobalEnabled()
reports whether it's currently loaded. New GET /firewall-lists/status and
POST /firewall-lists/disable endpoints (admin-only), plus a status badge
and "Toggle enable/disable" button on the Global Firewall Lists page -
one click to turn the whole thing off without losing the list contents,
and back on again (re-applies the current ruleset).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 20:52:02 +02:00
sysopsandClaude Sonnet 5 00d084a188 Fix nftables auto-merge syntax and block private/reserved ranges
auto-merge is a standalone set statement, not an nft "flags" value -
"flags interval, auto-merge;" is a syntax error; fixed to "flags
interval;" followed by "auto-merge" on its own line. Needed because large
public blocklists (FireHOL, Spamhaus) contain overlapping CIDRs that
nftables otherwise refuses as "conflicting intervals".

Also add a hard guard: reject any "block" entry that overlaps a private/
reserved/bogon range (RFC1918, CGNAT, loopback, link-local, etc.) in both
the single-entry and bulk-import paths. Public feeds like FireHOL level1
routinely include ranges like 10.0.0.0/8 and 172.16.0.0/12, meant for
WAN-only edge firewalls - applied host-wide here (where WireGuard/LAN
subnets legitimately live in that same private space), those entries
would silently block a server's own internal/VPN traffic instead of
actual bad actors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 20:44:29 +02:00
sysopsandClaude Sonnet 5 a9649f6306 Add .gitignore for private/ - local-only files never pushed to gitea
Moved two real OPNsense firewall/NAT rule exports (real internal IPs and
ports) into private/ so they can stay on disk for reference without ever
being committed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 20:02:16 +02:00
sysopsandClaude Sonnet 5 0f92317abb Fix "no such file or directory" on first use of new collections
scribble.ReadAll only returns its own ErrMissingCollection when the
collection name is empty - when the collection's directory simply
doesn't exist yet (e.g. before the first firewall rule or IP list entry
is ever created), it returns a raw os.IsNotExist error instead, which
GetFirewallRules/GetIPListEntries/GetServers didn't handle, surfacing as
"open db/ip_list_entries: no such file or directory" on a fresh install.
New isMissingCollectionErr() helper treats both cases as "empty
collection", not a real failure.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 17:55:37 +02:00
sysopsandClaude Sonnet 5 77ba2b4799 Add bulk import for global firewall allow/block lists
Paste-in import: one CIDR/IP per line, optional comment after '#',
blank/comment-only lines ignored - matches the format used by common
public blocklist feeds (Spamhaus DROP, blocklist.de, etc.) so those can
mostly be pasted in directly. Whole batch gets one list_type (allow or
block). Reuses the existing single-entry validation, skips duplicates
(by list_type+CIDR, including within the same paste), caps at 5000 lines,
and reports imported/skipped/invalid counts plus per-line errors.

New route: POST /firewall-lists/entries/import (admin-only). UI: a
collapsible "Bulk import" section on the Global Firewall Lists page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 17:50:58 +02:00
sysopsandClaude Sonnet 5 eb1913d400 Add central host-wide firewall allow/block lists
New model.IPListEntry + jsondb CRUD, independent of any single WireGuard
server. firewall.GenerateGlobalRuleset builds an nftables table
(wireguard_ui_global) with allow/block sets evaluated at priority -10 -
before every per-server table - so it applies to all traffic on the host,
not just WireGuard. Allow entries always win over block entries.
firewall.ApplyGlobal loads it live via `nft -f`, scoped to that one table.

New "Global Firewall Lists" page (nav entry under Settings): add/delete
entries, ruleset preview, "Apply now (live)" with an explicit confirm()
warning since this affects the whole host's firewall, not just one server.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 17:45:53 +02:00
sysopsandClaude Sonnet 5 bdcf1ec60c Add read-only OS package update status (apt) on the About page
New system package checks `apt list --upgradable` against the current
package index (no apt update triggered) and the reboot-required marker
file. Read-only - never installs or upgrades anything. Shown as a card
on the About page with a manual "Check now" refresh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 17:35:27 +02:00
sysopsandClaude Sonnet 5 1d080904b0 Add live firewall rule management per server (nftables)
New model.FirewallRule + jsondb CRUD (GetFirewallRules/CreateFirewallRule/
UpdateFirewallRule/DeleteFirewallRule), scoped per server. firewall package
now generates a full ruleset (baseline + enabled custom rules) and can
apply it live via `nft -f` (firewall.Apply), scoped to a per-server
nftables table (wireguard_ui_<serverID>) so applying one server never
touches another server's rules or any pre-existing firewall state.

New endpoints: GET/POST /servers/:id/firewall/rules, POST .../rules/:ruleId,
POST .../rules/:ruleId/delete, POST .../apply (live, admin-only). UI in the
All Servers page: rule table with add/delete, ruleset preview, and an
"Apply now (live)" button with an explicit confirm() warning before it
touches the running firewall.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 17:31:08 +02:00
sysopsandClaude Sonnet 5 28eb08df41 Add nftables firewall ruleset preview per server (review-only)
New firewall package generates an nftables snippet (INPUT accept for the
listen port, FORWARD rules for the WireGuard interface, optional LAN
forwarding via a new ServerSetting.LanInterface field). Text only -
nothing is applied to the live firewall. Exposed as GET
/servers/:id/firewall-preview and a "Firewall Preview" button in the
All Servers page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 16:52:20 +02:00
sysopsandClaude Sonnet 5 0000643187 Add missing server delete: handler, route, and UI button
DeleteServer already existed in the store layer (refuses if clients
still reference the server) but was never wired up anywhere, so there
was no way to actually remove a server from the UI or API.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 16:38:22 +02:00
sysopsandClaude Sonnet 5 9c90e3f49a Rewrite About page for the fork, drop upstream GitHub API calls
Removed the release/contributor/author lookups against
ngoduykhanh/wireguard-ui - this fork no longer tracks that repo's
releases, so those calls were meaningless here and leaked an external
API dependency. Replaced with a short own-product description.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 16:32:03 +02:00
sysopsandClaude Sonnet 5 58db6839c3 Remove legacy default-server page, unify on multi-server registry
Deletes /wg-server (route, handlers, template) entirely and adds generic
/servers/:id/interface and /servers/:id/keypair endpoints + UI in the
All Servers page, so every server (including the default one) is managed
through the same per-server registry. Drops the write-through dual-write
hacks that kept the old single-server collection in sync - the registry
is now the single source of truth. One-time legacy-install migration path
is untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 16:27:17 +02:00
sysopsandClaude Sonnet 5 2212141ba3 Clarify server nav labels (Default Server Interface vs All Servers)
No route/logic changes - pure label cleanup so the legacy default-server
editor and the multi-server management page are no longer confusable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 12:54:29 +02:00
sysops 144cb2982d Add manual, optionally-encrypted backup download (no auto-upload)
New POST /backup/download (admin-only) tars the whole jsondb directory
(all servers/clients/users/settings) and streams it back as a file
download. If a passphrase is given, the archive is encrypted first
(AES-256-GCM, scrypt-derived key, backup/encrypt.go) - a small
self-contained format, not gpg/OpenPGP-compatible, to avoid shelling
out to an external binary or adding a PGP dependency.

Deliberately does NOT upload anywhere automatically (e.g. to
Nextcloud) - the archive only ever leaves the server as this one HTTP
response to the requesting admin, who is responsible for storing it
themselves. New "Download Backup" button + passphrase modal on the
Global Settings page.
2026-07-12 00:16:47 +02:00
sysops 31f504e61b Document current product capabilities (multi-server CRUD, per-server access control, confirmed gaps) 2026-07-11 23:53:43 +02:00
sysops 3fe3dc8eeb Add per-server settings editor (step 5)
New servers previously got a bare ConfigFilePath default with no way
to set/edit EndpointAddress, FirewallMark, or Table afterward. Adds
GET/POST /servers/:id/settings (view/save ServerSetting, admin-only
for writes) and a "Settings" button + modal on each server card in
servers.html. CreateServer now seeds FirewallMark/Table with the same
defaults the legacy single-server bootstrap uses, instead of leaving
them blank.
2026-07-11 23:50:47 +02:00
sysops 74389a9d49 Scope client management and config-apply per server (step 4)
Real per-server data isolation, the core ask behind the access-control
work: clients, config generation, and the client-management UI are now
scoped by server ID instead of implicitly operating on one global
"the server".

- util.DefaultServerID ("wg0") is the server every legacy bare route
  now resolves to, so old and new routes share one consistent identity
  instead of drifting apart.
- New /servers/:id/... routes (new-client, update-client, remove-client,
  set-status, download, api/clients, api/client/:cid, api/apply-wg-config)
  reuse the same handlers as the legacy routes via resolveServerID(c),
  gated by RequireServerAccess middleware. Cross-server edits/deletes on
  scoped routes are rejected (403) if a client belongs to a different
  server.
- Fixes a real data leak: ApplyServerConfig previously wrote ALL clients
  from ALL servers into whichever single wg.conf it targeted. It now
  filters clients by server ID before generating a config, and resolves
  each server's own ConfigFilePath/EndpointAddress via the new
  ServerSetting record instead of the app-wide GlobalSetting.
- WireGuardServerInterfaces/WireGuardServerKeyPair/GlobalSettingSubmit
  (the legacy /wg-server and /global-settings edit routes) now write
  through to the new per-server registry record for "wg0" in addition
  to the legacy collection, so the two stay in sync until the legacy
  routes are eventually retired.
- New templates/server_clients.html: per-server clone of clients.html
  wired to the scoped endpoints, with a server name/id heading.
- base.html's shared "New Client" and "Apply Config" actions (used by
  every page's nav buttons) now target the scoped route when a
  serverID is present on the page, instead of always hitting the
  legacy default-server endpoint regardless of which server's client
  page is open.

Legacy bare routes (/, /new-client, /wg-server, ...) are untouched and
still fully functional against the default "wg0" server - nothing was
removed yet, per the incremental-delivery approach for this project.
2026-07-11 23:47:57 +02:00
sysops e3534625c3 Remove Docker files (Dockerfile, docker-compose.yaml, examples/docker-compose)
This fork is native-only (no Docker) per README; these files were
unused dead weight left over from upstream.
2026-07-11 23:35:59 +02:00
sysops bb72a0a3ee Rewrite README to be fork-only, drop all Docker/upstream content
Removes the entire upstream-repo documentation section (Docker
compose, Docker build/env vars, Docker Hub links, upstream repo
links) - this fork is native-only, so those sections were dead
weight and confusing. Fixes BIND_ADDRESS default in the env var
table (was documented as 0.0.0.0:80, actual default in this fork's
systemd unit is 0.0.0.0:5000). License now points at the local
LICENSE file instead of the upstream repo.
2026-07-11 23:31:47 +02:00
sysops c7df129fdd Escape server name before HTML interpolation in servers.html
obj.name is free text (unlike id/interface, which are already
regex-validated server-side) and was being template-literal'd
straight into innerHTML - stored-XSS risk if a server name contains
markup. Escape via jQuery .text()/.html() round-trip.
2026-07-11 23:24:45 +02:00
sysops 3842c7a534 Add server creation UI + user-server access assignment (step 3)
New admin-only page at /servers-settings (templates/servers.html) lists
servers and creates new ones via POST /servers (ID/name/interface/
addresses/port, key pair generated server-side). Nav gets a "Servers"
link.

templates/users_settings.html gains a multi-select "Server Access"
field wired to the server_ids support added to create-user/update-user
in the previous commit, so admins can now actually assign non-admin
users to specific servers through the UI.
2026-07-11 23:24:05 +02:00
sysops 5b72a7c118 Add per-server access control (User.ServerIDs)
Non-admin users are now restricted to servers explicitly listed in
their new ServerIDs field; empty means no access (secure by default).
Admins always have full access. Migration backfills existing users'
ServerIDs with the migrated legacy server so nobody is locked out on
upgrade. New RequireServerAccess middleware enforces this on
/servers/:id/... routes (applied to GET /servers/:id/clients so far);
GET /servers also filters its list for non-admins.
2026-07-11 23:20:52 +02:00
sysops a946c059c3 Add read-only multi-server API (step 2) + dedupe serverID validation
GET /servers lists registered servers; GET /servers/:id/clients returns
that server's client list (filtered in-handler, store.GetClients isn't
server-scoped yet - that's a later step). Old routes untouched.

Also factors the repeated serverID validation guard in jsondb.go's new
server-scoped methods into one validateServerID() helper, per a code
simplification review.
2026-07-11 22:41:10 +02:00
sysops 7eca2d01b8 Add server-registry store methods with record-ID validation (step 1)
New IStore methods for a per-server registry (GetServers, GetServerByID,
CreateServer, DeleteServer, GetServerSettings/SaveServerSettings,
GetServerHashes/SaveServerHashes), all additive - existing single-server
methods untouched. Adds util.ValidateRecordID/ValidateInterfaceName and
applies them to every new method taking a server ID, closing a path-
traversal gap before serverID is ever driven by user input (flagged by
a security review pass).
2026-07-11 22:39:53 +02:00
sysops 40d7862702 Add multi-server data model + legacy DB migration (step 0)
Adds Server.ID/Name, ServerInterface.Name, Client.ServerID, and a new
ServerSetting type as additive fields so existing single-server code
paths keep working unchanged. jsondb.Init() now detects a pre-existing
single-server db/server/ layout and mirrors it into new servers/,
server_settings/, server_hashes/ collections plus backfills ServerID
on existing clients, without touching/removing the legacy files yet.
2026-07-11 22:35:45 +02:00
sysopsandClaude Sonnet 5 e5c0a5888f Adapt installers to upstream wireguard-ui build (Go+embed, yarn assets)
Upstream builds a single static binary (main.go at repo root, no
cmd/... subpackage) that go:embeds templates/ and assets/ at compile
time, and uses a relative ./db directory (jsondb) instead of SQLite -
so WorkingDirectory in the systemd unit now matters for the DB path,
not for template/static serving like before.

Frontend assets (admin-lte, jquery plugins) need yarn + prepare_assets.sh
before go build, so all installers now also install nodejs/npm/yarn and
run prepare_assets.sh when assets/dist is missing or package.json changed.

Binary is now literally named "wireguard-ui" (matches upstream), listens
on 0.0.0.0:5000 by default. systemd unit rewritten accordingly with
WorkingDirectory=/var/lib/wireguard-ui-multi.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 18:38:02 +02:00
sysopsandClaude Sonnet 5 94f0e69676 Remove stray tool-generated tmp file accidentally committed
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 18:34:51 +02:00
sysopsandClaude Sonnet 5 867dc7740a Replace from-scratch rewrite with real ngoduykhanh/wireguard-ui fork
The from-scratch Go rewrite had unresolved bugs (missing go.sum, UI
404s, path issues) from being built without a working local Go
toolchain to verify against. Switching strategy: use the actual
upstream wireguard-ui codebase (proven, battle-tested single-server
manager) as the base, and extend it for multi-server support instead
of re-deriving everything from zero.

Kept our own installers (bootstrap.sh, update.sh, scripts/install.sh,
scripts/proxmox-install.sh) - these still apply, just need updating
to build/install the upstream module layout instead of the old
cmd/wireguard-ui-multi structure.

Module path intentionally left as upstream's own
(github.com/ngoduykhanh/wireguard-ui) for now to avoid touching every
internal import; revisit if this needs to be fully rebranded.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 18:34:44 +02:00
sysopsandClaude Sonnet 5 6eeea65ede Fix "Text file busy" on reinstall while service is running
cp truncates the destination in place, which fails with ETXTBSY when
the binary is currently executing (e.g. running update.sh while the
systemd service is active). Copy to a temp file and mv it into place
instead - mv is a rename within the same filesystem, which the
kernel allows even for a running binary.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 18:23:20 +02:00
sysopsandClaude Sonnet 5 41894e67c6 Fix 404s: serve UI templates/static from configurable ui-root, not CWD
The web UI 404'd in production because templates/static were loaded
via relative paths ("internal/ui/templates", "internal/ui/static"),
which only resolved when running from the repo checkout. systemd sets
WorkingDirectory=/var/lib/wireguard-ui-multi, so those paths never
existed there.

Add a -ui-root flag (default /usr/local/share/wireguard-ui-multi/ui),
have install.sh copy internal/ui there, and resolve templates/static
paths through it instead of hardcoded relative strings.

Also add release-binary fast path to bootstrap.sh (falls back to
source build with CGO_ENABLED=0/-trimpath if no release exists yet),
and document real hardware/build-RAM requirements in the README.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 18:18:48 +02:00
sysopsandClaude Sonnet 5 b2b6b82f58 Add update.sh, persist Go on PATH via profile.d
update.sh pulls the latest main, rebuilds, reinstalls, and restarts
the service in one step. install.sh/bootstrap.sh now also drop
/etc/profile.d/go-path.sh so Go stays on PATH in future shells, not
just within the running install script.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 17:59:04 +02:00
sysopsandClaude Sonnet 5 f333623035 Run go mod tidy before build in all installers
go.sum was never committed (no local Go toolchain to generate it),
causing "missing go.sum entry" build failures on target hosts.
go mod tidy regenerates it automatically before go build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 17:53:20 +02:00
sysopsandClaude Sonnet 5 6e0e6813f1 Fix bootstrap.sh: go not found on PATH after install
/usr/local/bin wasn't guaranteed to be on PATH in non-interactive
shells, so the symlinked go binary was invisible to later steps.
Export PATH explicitly and fail fast if go is still missing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 17:48:22 +02:00
sysopsandClaude Sonnet 5 18188f44b8 Move bootstrap installer to project root, document one-liner install
Simplifies the download URL for the curl-pipe-bash quickstart.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 17:42:26 +02:00
sysopsandClaude Sonnet 5 9a1d8112e7 Add one-shot bootstrap installer script
Clones repo, installs deps (Go, wireguard-tools, nftables), builds
binary, and runs the native installer end-to-end via curl-pipe-bash.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 17:31:11 +02:00
sysopsandClaude Sonnet 5 3b3ffd8ebf Add wireguard-ui-multi core: multi-server DB, WireGuard manager, REST API, UI, installers
Implements the from-scratch multi-server WireGuard management fork per
CLAUDE.md spec: sqlite schema (servers/peers/audit_log/users), Curve25519
key generation, per-interface config rendering + wg-quick/systemd control,
nftables hook scaffolding, session+CSRF-protected REST API with QR code
and config download endpoints, a minimal vanilla-JS web UI, legacy
wg0.conf migration, and both a native installer and a Proxmox LXC
provisioning script (with auto-detected latest Debian template).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 02:53:14 +02:00
sysops 3d6608ef80 first commit 2026-07-10 02:16:24 +02:00