Integrations
SSH banner
Vortexa shows a custom banner before authentication on every SSH VPN account. This is the text a client's SSH tool prints immediately on connect, before any credentials are exchanged — useful for branding, support contact info, and letting a user self-check their expiry/usage without logging in anywhere.
Format
The banner (sshBanner setting) is a raw, real-newline-separated multi-line text template — not HTML/Markdown, because pre-auth SSH banners collapse HTML and only render plain text with literal line breaks.
Default template
Server: {SERVER_LINE}
Node: {SERVER_NODE}
Account: {USERNAME}
Expires: {EXPIRES} ({DAYS_LEFT}d left)
Usage: {USED_GB} / {QUOTA_GB} GB
Status: {STATUS}Editing controls
- Edit the template directly in the Banner template textarea.
- Reset to default restores SERVER_BANNER_BLOCK exactly as shown above (still gets the attribution line appended).
- Save banner both persists the setting and mirrors the raw template to every node — see Delivery below.
Per-user variables (resolved by the panel)
These are filled in by the panel before the account bundle ships to its node — they differ per SSH account.
| Variable | Meaning |
|---|---|
| {USERNAME} | The SSH account's username. |
| {EXPIRES} | The account's expiry date/time. |
| {DAYS_LEFT} | Days remaining until expiry (integer). |
| {USED_GB} | Data used so far, in GB. |
| {QUOTA_GB} | The account's data quota, in GB (0 typically means unlimited upstream). |
| {STATUS} | Account status label, e.g. active/expired/suspended. |
Per-node variables (resolved on the node itself)
These are filled in locally on each node by that node's own banner.py process, not by the panel — so the same template renders differently per node.
| Variable | Meaning |
|---|---|
| {SERVER_LINE} | Composite line, e.g. 🇮🇹 Italy - Milan - 01 (flag + country + city + node sequence number). |
| {SERVER_COUNTRY} | The node's configured country. |
| {SERVER_FLAG} | Emoji flag for that country. |
| {SERVER_NODE} | The node's display name. |
| {SERVER_CITY} | The node's configured city. |
| {SERVER_SEQ} | The node's sequence number (e.g. 01, 02 — used to disambiguate multiple nodes in the same city). |
Cannot be removed from the UI
BANNER_ATTRIBUTION, from src/lib/brand) is always appended to whatever you type, both in the live preview and on save (withAttribution(...)). It is reapplied server-side on every node refresh too, per the source code comment — so it will reappear even if a node's local file is edited by hand.Save flow
- 1You edit the template and click Save banner.
- 2The panel calls
api.settings.save(payload)with the attributed banner (withAttribution(f.sshBanner)) so it is stored as the canonical setting. - 3The panel additionally POSTs the raw template to
/api/panel/banner, mirroring it into the panel-side key-value store (api/panel/kv.$key.ts/api/panel/state.tsproxies) so the node-heartbeat pipeline can push it to every node without a second admin action. - 4This mirror call is best-effort: if it fails, it is silently retried on the next save rather than blocking the settings save itself.
- 5Nodes pick up the latest banner via their periodic heartbeat / RPC channel (see
/docs/node-agent), where each node's ownbanner.pyresolves the per-node variables and writes the rendered banner to the appropriate SSH pre-auth banner file (e.g./etc/issue.netplus the correspondingsshd_config Bannerdirective) and reloads/restarts the SSH service so it takes effect immediately.
Nodes resolve, not the panel
Live preview
The Settings page renders a live preview pane showing withAttribution(f.sshBanner) verbatim (with placeholders still visible, unresolved) so you can see exactly what will be sent, including the locked attribution line, before saving.
Troubleshooting
- Banner not updating on a node
- Confirm the node is actually heartbeating (see /docs/node-agent); the mirror POST to /api/panel/banner is best-effort and retries on the next save if it failed.
- Placeholders showing literally (e.g. {USERNAME})
- That variable name is misspelled or unsupported — check the exact variable list above; unknown tokens are not substituted and print as-is.
- Attribution line missing on a node
- The node is running a stale banner file from before this feature was enabled, or its refresh cycle has not run yet — trigger a manual re-pin/heartbeat.
- HTML/formatting looks broken
- SSH pre-auth banners are plain text only; remove any HTML tags or Markdown from the template — use real newlines for line breaks.
