From a998f6be8a0c54a5daffe70a0fc3d8b9ed53a960 Mon Sep 17 00:00:00 2001 From: Vadim Babadzhanyan Date: Mon, 19 Aug 2024 21:05:41 +0300 Subject: [PATCH] Add Prometheus metrics [Feat]: Simple Stats API #1285 --- README.md | 15 ++++++--- docker-compose.dev.yml | 2 +- docker-compose.yml | 1 + src/config.js | 1 + src/lib/Server.js | 15 +++++++++ src/lib/WireGuard.js | 73 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 101 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a282d2d6..71168146 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,13 @@ You have found the easiest way to install & manage WireGuard on any Linux host! +

- + screenshot

## Features + * All-in-one: WireGuard + Web UI. * Easy installation, simple to use. * List, create, edit, delete, enable & disable clients. @@ -26,6 +28,7 @@ You have found the easiest way to install & manage WireGuard on any Linux host! * UI_TRAFFIC_STATS (default off) * UI_SHOW_LINKS (default off) * WG_ENABLE_EXPIRES_TIME (default off) +* Prometheus metrics support ## Requirements @@ -34,7 +37,7 @@ You have found the easiest way to install & manage WireGuard on any Linux host! ## Versions -We provide more then 1 docker image to get, this will help you decide which one is best for you.
+We provide more then 1 docker image to get, this will help you decide which one is best for you. For **stable** versions instead of nightly or development please read **README** from the **production** branch! | tag | Branch | Example | Description | @@ -62,7 +65,7 @@ And log in again. To automatically install & run wg-easy, simply run: -``` +```bash docker run -d \ --name=wg-easy \ -e LANG=de \ @@ -87,6 +90,8 @@ To automatically install & run wg-easy, simply run: The Web UI will now be available on `http://0.0.0.0:51821`. +The Prometheus metrics will now be available on `http://0.0.0.0:51821/metrics`. Grafana dashboard [21733](https://grafana.com/grafana/dashboards/21733-wireguard/) + > 💡 Your configuration files will be saved in `~/.wg-easy` WireGuard Easy can be launched with Docker Compose as well - just download @@ -109,7 +114,7 @@ These options can be configured by setting environment variables using `-e KEY=" | `WG_HOST` | - | `vpn.myserver.com` | The public hostname of your VPN server. | | `WG_DEVICE` | `eth0` | `ens6f0` | Ethernet device the wireguard traffic should be forwarded through. | | `WG_PORT` | `51820` | `12345` | The public UDP port of your VPN server. WireGuard will listen on that (othwise default) inside the Docker container. | -| `WG_CONFIG_PORT`| `51820` | `12345` | The UDP port used on [Home Assistant Plugin](https://github.com/adriy-be/homeassistant-addons-jdeath/tree/main/wgeasy) +| `WG_CONFIG_PORT`| `51820` | `12345` | The UDP port used on [Home Assistant Plugin](https://github.com/adriy-be/homeassistant-addons-jdeath/tree/main/wgeasy) | `WG_MTU` | `null` | `1420` | The MTU the clients will use. Server uses default WG MTU. | | `WG_PERSISTENT_KEEPALIVE` | `0` | `25` | Value in seconds to keep the "connection" open. If this value is 0, then connections won't be kept alive. | | `WG_DEFAULT_ADDRESS` | `10.8.0.x` | `10.6.0.x` | Clients IP address range. | @@ -126,7 +131,7 @@ These options can be configured by setting environment variables using `-e KEY=" | `UI_SHOW_LINKS` | `false` | `true` | Enable display of a short download link in Web UI | | `MAX_AGE` | `0` | `1440` | The maximum age of Web UI sessions in minutes. `0` means that the session will exist until the browser is closed. | | `UI_ENABLE_SORT_CLIENTS` | `false` | `true` | Enable UI sort clients by name | - +| `ENABLE_PROMETHEUS_METRICS` | `true` | `true` | Enable Prometheus metrics `http://0.0.0.0:51821/metrics` and `http://0.0.0.0:51821/metrics/json`| > If you change `WG_PORT`, make sure to also change the exposed port. diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index bd4a836d..2bc01ff0 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -13,5 +13,5 @@ services: - NET_ADMIN - SYS_MODULE environment: - # - PASSWORD=p + # - PASSWORD_HASH=$$2y$$10$$hBCoykrB95WSzuV4fafBzOHWKu9sbyVa34GJr8VV5R/pIelfEMYyG # 'foobar123' - WG_HOST=192.168.1.233 diff --git a/docker-compose.yml b/docker-compose.yml index 9de55e32..7d1e9d3f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,6 +30,7 @@ services: # - UI_SHOW_LINKS=true # - UI_ENABLE_SORT_CLIENTS=true # - WG_ENABLE_EXPIRES_TIME=true + # - ENABLE_PROMETHEUS_METRICS=false image: ghcr.io/wg-easy/wg-easy container_name: wg-easy diff --git a/src/config.js b/src/config.js index 6168e589..068caa39 100644 --- a/src/config.js +++ b/src/config.js @@ -41,3 +41,4 @@ module.exports.UI_CHART_TYPE = process.env.UI_CHART_TYPE || 0; module.exports.UI_SHOW_LINKS = process.env.UI_SHOW_LINKS || 'false'; module.exports.UI_ENABLE_SORT_CLIENTS = process.env.UI_ENABLE_SORT_CLIENTS || 'false'; module.exports.WG_ENABLE_EXPIRES_TIME = process.env.WG_ENABLE_EXPIRES_TIME || 'false'; +module.exports.ENABLE_PROMETHEUS_METRICS = process.env.ENABLE_PROMETHEUS_METRICS || 'true'; diff --git a/src/lib/Server.js b/src/lib/Server.js index 40b9bb1f..8d5aebcd 100644 --- a/src/lib/Server.js +++ b/src/lib/Server.js @@ -36,6 +36,7 @@ const { UI_SHOW_LINKS, UI_ENABLE_SORT_CLIENTS, WG_ENABLE_EXPIRES_TIME, + ENABLE_PROMETHEUS_METRICS, } = require('../config'); const requiresPassword = !!PASSWORD_HASH; @@ -286,6 +287,20 @@ module.exports = class Server { const { expireDate } = await readBody(event); await WireGuard.updateClientExpireDate({ clientId, expireDate }); return { success: true }; + })) + .get('/metrics', defineEventHandler(async (event) => { + setHeader(event, 'Content-Type', 'text/plain'); + if (ENABLE_PROMETHEUS_METRICS === 'true') { + return WireGuard.getMetrics(); + } + return ''; + })) + .get('/metrics/json', defineEventHandler(async (event) => { + setHeader(event, 'Content-Type', 'application/json'); + if (ENABLE_PROMETHEUS_METRICS === 'true') { + return WireGuard.getMetricsJSON(); + } + return ''; })); const safePathJoin = (base, target) => { diff --git a/src/lib/WireGuard.js b/src/lib/WireGuard.js index 120dd238..613190fa 100644 --- a/src/lib/WireGuard.js +++ b/src/lib/WireGuard.js @@ -158,6 +158,7 @@ ${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : '' latestHandshakeAt: null, transferRx: null, transferTx: null, + endpoint: null, })); // Loop WireGuard status @@ -186,6 +187,7 @@ ${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : '' client.latestHandshakeAt = latestHandshakeAt === '0' ? null : new Date(Number(`${latestHandshakeAt}000`)); + client.endpoint = endpoint === '(none)' ? null : endpoint; client.transferRx = Number(transferRx); client.transferTx = Number(transferTx); client.persistentKeepalive = persistentKeepalive; @@ -398,4 +400,75 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`; } } + async getMetrics() { + const clients = await this.getClients(); + let wireguardPeerCount = 0; + let wireguardEnabledPeersCount = 0; + let wireguardConnectedPeersCount = 0; + let wireguardSentBytes = ''; + let wireguardReceivedBytes = ''; + let wireguardLatestHandshakeSeconds = ''; + for (const client of Object.values(clients)) { + wireguardPeerCount++; + if (client.enabled === true) { + wireguardEnabledPeersCount++; + } + if (client.endpoint !== null) { + wireguardConnectedPeersCount++; + } + wireguardSentBytes += `wireguard_sent_bytes{interface="wg0",enabled="${client.enabled}",address="${client.address}",name="${client.name}"} ${Number(client.transferTx)}\n`; + wireguardReceivedBytes += `wireguard_received_bytes{interface="wg0",enabled="${client.enabled}",address="${client.address}",name="${client.name}"} ${Number(client.transferRx)}\n`; + wireguardLatestHandshakeSeconds += `wireguard_latest_handshake_seconds{interface="wg0",enabled="${client.enabled}",address="${client.address}",name="${client.name}"} ${client.latestHandshakeAt ? (new Date().getTime() - new Date(client.latestHandshakeAt).getTime()) / 1000 : 0}\n`; + } + + let returnText = '# HELP wg-easy and wireguard metrics\n'; + + returnText += '\n# HELP wireguard_configured_peers\n'; + returnText += '# TYPE wireguard_configured_peers gauge\n'; + returnText += `wireguard_configured_peers{interface="wg0"} ${Number(wireguardPeerCount)}\n`; + + returnText += '\n# HELP wireguard_enabled_peers\n'; + returnText += '# TYPE wireguard_enabled_peers gauge\n'; + returnText += `wireguard_enabled_peers{interface="wg0"} ${Number(wireguardEnabledPeersCount)}\n`; + + returnText += '\n# HELP wireguard_connected_peers\n'; + returnText += '# TYPE wireguard_connected_peers gauge\n'; + returnText += `wireguard_connected_peers{interface="wg0"} ${Number(wireguardConnectedPeersCount)}\n`; + + returnText += '\n# HELP wireguard_sent_bytes Bytes sent to the peer\n'; + returnText += '# TYPE wireguard_sent_bytes counter\n'; + returnText += `${wireguardSentBytes}`; + + returnText += '\n# HELP wireguard_received_bytes Bytes received from the peer\n'; + returnText += '# TYPE wireguard_received_bytes counter\n'; + returnText += `${wireguardReceivedBytes}`; + + returnText += '\n# HELP wireguard_latest_handshake_seconds UNIX timestamp seconds of the last handshake\n'; + returnText += '# TYPE wireguard_latest_handshake_seconds gauge\n'; + returnText += `${wireguardLatestHandshakeSeconds}`; + + return returnText; + } + + async getMetricsJSON() { + const clients = await this.getClients(); + let wireguardPeerCount = 0; + let wireguardEnabledPeersCount = 0; + let wireguardConnectedPeersCount = 0; + for (const client of Object.values(clients)) { + wireguardPeerCount++; + if (client.enabled === true) { + wireguardEnabledPeersCount++; + } + if (client.endpoint !== null) { + wireguardConnectedPeersCount++; + } + } + return { + wireguard_configured_peers: Number(wireguardPeerCount), + wireguard_enabled_peers: Number(wireguardEnabledPeersCount), + wireguard_connected_peers: Number(wireguardConnectedPeersCount), + }; + } + };