CallMeTechie
DE Login
Home Products Blog About Contact

VPN Peers & Clients

v1.x · Updated 1 month ago

Overview

Peers can be organized in two ways: Tags (freely chosen, many-to-many labels) and Peer Groups (structured one-to-many assignment). Both systems share the settings page but have different data models and lifecycles.

Tags were historically pure CSV strings in peers.tags — the admin edited a comma-separated field, that was all. With v1.48, the registry table tags was added so that tag names can be managed centrally: create new, rename, delete (including stripping from all peer CSVs), usage counts in the admin UI. The hybrid model lives on: the CSV per peer is still the source for the assignment, the registry is the central list.

Peer Groups are the younger, simpler system: one table, one foreign key in peers, display color, description. Groups are intended for permanent roles (e.g. "Admin", "Family", "Mobile"), tags for ad-hoc labels (e.g. "Desktop", "nas-stack", "Test").

Tags

Data model

-- Registry (ab Migration 39)
CREATE TABLE tags (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL UNIQUE,
  created_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX idx_tags_name ON tags(name);

-- Peer-seitig (ab Migration 10)
ALTER TABLE peers ADD COLUMN tags TEXT DEFAULT '';
-- CSV-Form: "nas, desktop, prod"

The registry stores tag names in their original case, as the admin first entered them. Uniqueness against existing registry entries runs case-insensitively via COLLATE NOCASE. That is: "NAS" and "nas" end up in a single registry row with the original case "NAS".

Why hybrid instead of a pure M:N table?

A classic peer_tags(peer_id, tag_id) table would be cleaner, but it would have meant rewriting the existing UI, imports, backup exports, and all API clients. Instead of forcing a migration, the registry holds its own lifecycle and the peer CSV remains untouched:

Property CSV Registry
Peer↔tag assignment Yes, per peer No, only central list
Pure orphans (tag without peer) No Yes
Rename Would write many peers Simple (one registry row) — but not implemented
Delete propagation Strip from all peer CSVs
Auto-registration on peer save Yes (ensureRegistered)

Tag name validation

src/services/tags.js:validateName:

  • Max 64 characters.
  • No , (the CSV parser would tokenize), no < > " (HTML safety), no \n \r \t (newline injection).

The field is checked before registry INSERT and before peer CSV write.

list() — merge view

The admin UI shows a merged list: all registry entries AND all actually assigned tags, with a registered flag and the peer_count:

[
  {id: 7,    name: "NAS",     peer_count: 3, registered: true },
  {id: null, name: "legacy",  peer_count: 1, registered: false },
  {id: 12,   name: "pool",    peer_count: 0, registered: true }
]
  • id=null, registered:false — tag appears on peer CSVs but is not in the registry. The UI can offer to register it.
  • peer_count=0, registered:true — orphan tag. Admin can delete without affecting peers.

Sorting: localeCompare with sensitivity:'base' — case-insensitive but locale-aware.

Auto-registration

On every peers.create and peers.update, the peer service calls tags.ensureRegistered(data.tags). The function:

  • Accepts a CSV string or array.
  • Filters each token against validateName rules; invalid tokens are silently skipped (a broken token does not block the peer save).
  • INSERT OR IGNORE — idempotent.
  • Runs in a transaction.

Goal: an admin who types "NAS, prod" into the peer form sees both tags immediately in the registry view; no extra setup step.

Startup backfill

tags.backfillFromPeers() is called once at server boot (in server.js):

  • Scans peers.tags for distinct tokens.
  • Registers all those not yet in there.

Purpose: after the upgrade to the registry version, existing installations have a populated registry catalog immediately, without the admin having to do anything.

remove(name) — the interesting operation

Deleting a tag means: registry entry gone AND token stripped from all peer CSVs.

DELETE FROM tags WHERE name = ? COLLATE NOCASE;
-- Dann in einer Transaktion:
for each peer with non-empty tags:
  tokens = splitCsv(peer.tags)
  filtered = tokens.filter(t => t.toLowerCase() !== name.toLowerCase())
  if filtered.length < tokens.length:
    UPDATE peers SET tags = filtered.join(', ') WHERE id = peer.id

Two subtleties:

  1. Whole-token match — deleting the tag "prod" must not hit the token "production-backup". The CSV is compared token by token, not via SQL LIKE '%prod%'.
  2. Case-insensitive comparison, case-preserving peers — the peer has "NAS, Prod", we delete "nas". The result is "Prod" (case preserved), "NAS" is gone.

Activity log: tag_deleted with peers_affected and removed_from_registry.

splitCsv(csv) — the parser

  • Splits on ,, trims each token.
  • Deduplicates case-insensitively, but keeps the first case seen.
  • Empty tokens are dropped.

This turns "NAS, nas, PROD, ," into ["NAS", "PROD"].

UI

  • Settings → tags admin card (public/js/tags-admin.js) — next to the peer groups card.
  • Renders the merge view from GET /api/v1/tags.
  • Create input + delete button per row.
  • Peers page: autocomplete from the registry, on save the CSV line ends up in peers.tags.

Peer groups

Data model

CREATE TABLE peer_groups (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL UNIQUE,
  color TEXT DEFAULT '#6b7280',
  description TEXT,
  created_at TEXT DEFAULT (datetime('now'))
);

-- FK auf peers (Migration 25)
ALTER TABLE peers ADD COLUMN group_id INTEGER
  REFERENCES peer_groups(id) ON DELETE SET NULL;
CREATE INDEX idx_peers_group_id ON peers(group_id);

A peer has exactly one group or none (NULL). ON DELETE SET NULL: when a group is deleted, the peers remain, only their group_id is set to NULL — peers do not disappear along with the group.

Color

color is a hex string (#rrggbb), displayed in the UI as a color chip next to the peer name. Default #6b7280 (gray).

API and UI

  • GET/POST/PATCH/DELETE /api/v1/peer-groups — standard CRUD (src/routes/api/peerGroups.js).
  • Settings → peer groups card (public/js/peer-groups-admin.js).
  • The peers edit form has a "Group" dropdown with all groups + "— none —".

Tags vs groups — when to use which

Criterion Tags Groups
Cardinality many-to-many one-to-many
Lifecycle short-lived, ad-hoc permanent, structural
Typical use cases "Desktop", "Test", "backup-2026" "Admin", "Family", "Mobile"
Delete behavior Removed from all peers Peers remain, group_id=NULL
Color Yes
Description Yes

A peer is, for example, in the "Family" group but tagged with "Desktop, nas-backup". The group determines default roles (possibly route-auth rules in the future), the tags are for filtering and display.

3. Managing peers / clients

The Peers & Clients page is split in two:

  • Home Gateways — at the top, as expandable cards with telemetry.
  • Peers / Clients — at the bottom, as a table with search, group filter and tag filter. Gateway peers are filtered out of this table so you don't see them twice.

3.1 Create a new peer

Open the + Add dialog. The most important fields:

  • Name — unique, 1–63 characters, a-z/0-9/hyphen. Also used as the WireGuard peer label.
  • Description — free, max. 255 characters. For example, the client-version label from the GateControl desktop client is automatically stored here.
  • Internal hostname (Pro) — see Domains & DNS. Only if you use Internal-DNS.
  • Group — optional, see 3.4 Groups.
  • Tags — comma-separated; e.g. server, production, nas.
  • DNS server (override) — overrides the global DNS entry only for this peer. If the field is empty, the global value from Settings applies.
  • Expiration date — after this date the peer is automatically disabled (background job every 60 seconds).
  • Home Gateway (checkbox) — turns the peer into a gateway (see 4. Home-Gateway). Cannot be changed afterwards.

After Create you get config, QR code and — for gateways — additionally the gateway.env as a one-time display.

3.2 Set hostname (Internal DNS)

In the peer table a small badge appears in each peer row: manual (set by the admin), auto (reported by the agent) or stale (not yet re-reported after master-key rotation).

Sticky-admin rule: If you as admin have set the hostname, no agent heartbeat will overwrite this value. If you want automatic adoption again, delete the hostname in the peer modal — then the next agent heartbeat may set it again.

You can override agent-set names at any time as admin. The flag then flips to manual and stays there.

3.3 Tags

Tags are free, non-hierarchical labels. You can manage them under Settings → Tags. There you have a registry — tags you've created can already be autocompleted. A tag that is attached to peers but is not in the registry shows the badge not registered.

Usage:

  • In the peer table you can filter by tag (chip bar above the table).
  • In the peer modal you enter tags comma-separated.
  • On save, unknown tags are automatically added to the registry so they show up in autocomplete in the future.

3.4 Groups

Peer groups are visible categories with name, color and description (e.g. "Office", "Family", "Servers"). Each peer belongs to exactly one or no group.

  • Create and maintain groups: Settings → Peer Groups
  • Assign peer: in the peer dialog dropdown Group
  • Filter: on the Peers page in the dropdown All / Ungrouped / <Group>

3.5 Disable vs. delete

  • Disable (toggle in the peer row, or batch action):
    • Peer stays in the DB, keys are preserved.
    • WireGuard config is rewritten without this peer, the active connection is disconnected immediately.
    • Can be re-enabled at any time.
  • Delete (trash icon, with confirmation):
    • Peer is permanently removed, keys are gone.
    • Routes belonging to this peer show a peer offline badge — active connections are disconnected.
    • Cannot be undone. If you recreate the peer, it gets a new IP and new keys.

3.6 Expiration dates

In the peer dialog under Expiration date you can choose 1d / 7d / 30d / 90d or a custom date. After expiry the peer is automatically disabled. In the table you see:

  • Expiring soon (yellow badge, < 7 days)
  • Expired (red badge, already disabled)

3.7 Batch actions

Top right on Select, then checkboxes in the rows. At the bottom an action bar appears with Enable, Disable, Delete.


Cookie Settings

We use cookies to improve your experience. Essential cookies are always active.

Privacy Policy
ESC
↑↓ navigate open esc close