2 Commits
Author SHA1 Message Date
maeneko a2d1be2ed6 Release 0.5.3
release / release (push) Canceled after 0s
ci / check (push) Canceled after 0s
2026-10-30 00:30:37 +03:00
maeneko af357a0a6f Prometheus on a node with its own panel: scrape core through the agent
The installer pointed it at the node's own panel and left AGENT_METRICS_PORT
unset, so the agent refused /prom/… and core never saw it. A node's own panel
now ignores the local Prometheus and shows no install buttons.
2026-10-30 00:30:31 +03:00
11 changed files with 31 additions and 14 deletions
+1 -1
View File
@@ -11,4 +11,4 @@
BRAND=Forgetting BRAND=Forgetting
CHANNEL=Beta CHANNEL=Beta
VERSION=0.5.2 VERSION=0.5.3
+1 -1
View File
@@ -377,7 +377,7 @@ TOKEN=$(curl -s -X POST http://HOST:PORT/login \
| Метод | Ответ | | Метод | Ответ |
|---|---| |---|---|
| `GET /ui/brand` | `{ "brand": "Forgetting", "channel": "Beta", "version": "0.5.2" }` — имя и версия панели для вордмарка; за авторизацией, чтобы сканер не опознал панель | | `GET /ui/brand` | `{ "brand": "Forgetting", "channel": "Beta", "version": "0.5.3" }` — имя и версия панели для вордмарка; за авторизацией, чтобы сканер не опознал панель |
| `GET /health` | `{ "status", "server", "ip", "gen", "awg": { "status", "peers", "module", "tools" } }`. `200`, если интерфейс поднят, иначе `503` и `status: "degraded"` | | `GET /health` | `{ "status", "server", "ip", "gen", "awg": { "status", "peers", "module", "tools" } }`. `200`, если интерфейс поднят, иначе `503` и `status: "degraded"` |
| `GET /awg/status` | `{ "up": true, "peers": 3, "publicKey": "…" }` | | `GET /awg/status` | `{ "up": true, "peers": 3, "publicKey": "…" }` |
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "awg-agent", "name": "awg-agent",
"version": "0.5.2", "version": "0.5.3",
"private": true, "private": true,
"description": "Агент ноды: держит исходящее WSS-соединение к core и исполняет запросы на локальном awg-ctrl", "description": "Агент ноды: держит исходящее WSS-соединение к core и исполняет запросы на локальном awg-ctrl",
"scripts": { "scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "awg-control", "name": "awg-control",
"version": "0.5.2", "version": "0.5.3",
"description": "", "description": "",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
+2 -2
View File
@@ -15,7 +15,7 @@ const now = () => Math.floor(Date.now() / 1000);
const rnd = (n: number) => Math.floor(Math.random() * n); const rnd = (n: number) => Math.floor(Math.random() * n);
const b64 = (n: number) => crypto.randomBytes(n).toString("base64"); const b64 = (n: number) => crypto.randomBytes(n).toString("base64");
const GEN = "3.1"; const GEN = "3.1";
const MOCK_VERSION = "0.5.2"; const MOCK_VERSION = "0.5.3";
// ── пользователи (vpn://) ── // ── пользователи (vpn://) ──
interface U { name: string; ip: string; pub_key: string; vpn_key: string; key_gen: string; online: boolean; lastHandshake: number; rx: number; tx: number } interface U { name: string; ip: string; pub_key: string; vpn_key: string; key_gen: string; online: boolean; lastHandshake: number; rx: number; tx: number }
@@ -40,7 +40,7 @@ const mnodes: MNode[] = [
{ id: 1, name: "Frankfurt", ip: "198.51.100.4", gen: "2", online: true, pending: false, version: "0.5.0" }, { id: 1, name: "Frankfurt", ip: "198.51.100.4", gen: "2", online: true, pending: false, version: "0.5.0" },
{ id: 2, name: "Tokyo", ip: "", gen: "2", online: false, pending: true, version: null }, { id: 2, name: "Tokyo", ip: "", gen: "2", online: false, pending: true, version: null },
{ id: 3, name: "Amsterdam", ip: "192.0.2.10", gen: "3.1", online: false, pending: false, version: "0.4.5" }, // старше 0.5.0: хаб не подключает { id: 3, name: "Amsterdam", ip: "192.0.2.10", gen: "3.1", online: false, pending: false, version: "0.4.5" }, // старше 0.5.0: хаб не подключает
{ id: 4, name: "Helsinki", ip: "192.0.2.44", gen: "3.1", online: true, pending: false, version: "0.5.3" }, // новее панели — жёлтый чип { id: 4, name: "Helsinki", ip: "192.0.2.44", gen: "3.1", online: true, pending: false, version: "0.5.4" }, // новее панели — жёлтый чип
]; ];
let nodeId = 3; let nodeId = 3;
const joinStr = (id: number) => "awgjoin://" + Buffer.from(JSON.stringify({ v: 1, h: "localhost", p: 8443, pin: crypto.randomBytes(32).toString("base64url"), n: id, s: crypto.randomBytes(16).toString("base64url") })).toString("base64url"); const joinStr = (id: number) => "awgjoin://" + Buffer.from(JSON.stringify({ v: 1, h: "localhost", p: 8443, pin: crypto.randomBytes(32).toString("base64url"), n: id, s: crypto.randomBytes(16).toString("base64url") })).toString("base64url");
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "awg-ui", "name": "awg-ui",
"version": "0.5.2", "version": "0.5.3",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -15,6 +15,7 @@ export interface MonitorStatus {
ready: boolean; can_install: boolean; command: string; ready: boolean; can_install: boolean; command: string;
host: { id: number; name: string } | null; // Prometheus на ноде (или будет поставлен туда); null — на этом сервере host: { id: number; name: string } | null; // Prometheus на ноде (или будет поставлен туда); null — на этом сервере
job: { action: 'install' | 'remove'; running: boolean; exit: number | null; log: string } | null; // последняя операция job: { action: 'install' | 'remove'; running: boolean; exit: number | null; log: string } | null; // последняя операция
on_node?: boolean; // панель на ноде другой панели: Prometheus этой машины ставит и читает та
} }
// Прогресс операции из строк «N/M шаг», которые печатает сам установщик (cli/src/monitor.ts). Шаг N считаем // Прогресс операции из строк «N/M шаг», которые печатает сам установщик (cli/src/monitor.ts). Шаг N считаем
@@ -81,6 +82,16 @@ export default function PrometheusInstall({ token, showMsg }: PageProps) {
}; };
if (!status) return <section className="st-section"><p className="st-hint">Загрузка…</p></section>; if (!status) return <section className="st-section"><p className="st-hint">Загрузка…</p></section>;
if (status.on_node) return (
<section className="st-section">
<div className="st-row">
<span className="st-row-text">
<span className="st-row-name">Встроенный Prometheus</span>
<span className="st-hint">Этот сервер подключён нодой к другой панели. Prometheus на нём собирает её метрики, поэтому ставится, удаляется и смотрится («Монитор») из неё, а не отсюда.</span>
</span>
</div>
</section>
);
const job = status.job; const job = status.job;
const working = !!pending || !!job?.running || settle !== null; const working = !!pending || !!job?.running || settle !== null;
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "awg-cli", "name": "awg-cli",
"version": "0.5.2", "version": "0.5.3",
"private": true, "private": true,
"dependencies": { "dependencies": {
"tsx": "^4.7.0", "tsx": "^4.7.0",
+1 -1
View File
@@ -188,7 +188,7 @@ trap 'exec >&- 2>&-; wait "$LOG_PID" 2>/dev/null || true; exec 3>&-
# ⚠️ Дублирует .env из репозитория: установщик собирает URL релиза и печатает # ⚠️ Дублирует .env из репозитория: установщик собирает URL релиза и печатает
# баннер ДО того, как архив с .env скачан, поэтому взять их оттуда не может. # баннер ДО того, как архив с .env скачан, поэтому взять их оттуда не может.
# При бампе версии правь оба места. # При бампе версии правь оба места.
VERSION="0.5.2" VERSION="0.5.3"
CHANNEL="${CHANNEL:-Beta}" CHANNEL="${CHANNEL:-Beta}"
# Откуда брать архив и как называется продукт. По умолчанию — релизы на GitHub; источник # Откуда брать архив и как называется продукт. По умолчанию — релизы на GitHub; источник
+4 -3
View File
@@ -177,9 +177,10 @@ func (c *CLI) monitorInstall() {
if !reDebian.MatchString(osFamily()) { if !reDebian.MatchString(osFamily()) {
mfail("Автоустановка поддерживает только Debian и Ubuntu. На другом дистрибутиве поставьте Prometheus сами и подключите его кнопкой в панели (токен метрик).") mfail("Автоустановка поддерживает только Debian и Ubuntu. На другом дистрибутиве поставьте Prometheus сами и подключите его кнопкой в панели (токен метрик).")
} }
// Нода без своей панели (в том числе сервер, с которого панель переехала): метрики панели // Нода: метрики панели Prometheus получает от агента, а тот — от core. Так и у ноды со своей
// Prometheus получает от агента, а тот — от core. // панелью: Prometheus на машине один, и нужен он core — иначе core его не видит (агент
viaAgent := c.isNode && !contains(c.all, "ui") // отдаёт /prom/… только при AGENT_METRICS_PORT), а панель ноды обходится без «Монитора».
viaAgent := c.isNode
target, newPort := os.Getenv("UI_PORT"), false target, newPort := os.Getenv("UI_PORT"), false
if viaAgent { if viaAgent {
if target = os.Getenv("AGENT_METRICS_PORT"); target == "" { if target = os.Getenv("AGENT_METRICS_PORT"); target == "" {
+7 -2
View File
@@ -15,6 +15,7 @@ import (
"math" "math"
"net/http" "net/http"
"net/url" "net/url"
"os"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@@ -81,6 +82,7 @@ type Monitor struct {
a *App a *App
promURL string promURL string
localEnabled bool localEnabled bool
onNode bool // панель на ноде другой панели: Prometheus этой машины собирает метрики core, не наши
http *http.Client http *http.Client
srcMu sync.Mutex srcMu sync.Mutex
@@ -102,6 +104,7 @@ func newMonitor(a *App) *Monitor {
return &Monitor{ return &Monitor{
a: a, promURL: envOr("PROM_URL", "http://127.0.0.1:9090"), a: a, promURL: envOr("PROM_URL", "http://127.0.0.1:9090"),
localEnabled: strings.ToLower(envOr("LOCAL_NODE", "on")) != "off", localEnabled: strings.ToLower(envOr("LOCAL_NODE", "on")) != "off",
onNode: os.Getenv("CORE_HOST") != "",
http: &http.Client{Transport: &http.Transport{Proxy: nil}}, http: &http.Client{Transport: &http.Transport{Proxy: nil}},
} }
} }
@@ -132,7 +135,7 @@ func (m *Monitor) forgetSource() {
} }
func (m *Monitor) findSource() *promSource { func (m *Monitor) findSource() *promSource {
if m.promReady() { if !m.onNode && m.promReady() {
return &promSource{local: true} return &promSource{local: true}
} }
ids := m.a.nodes.IDs() ids := m.a.nodes.IDs()
@@ -316,6 +319,8 @@ func (m *Monitor) target(src *promSource, qs url.Values) int {
switch { switch {
case src != nil && !src.local: case src != nil && !src.local:
return src.node return src.node
case m.onNode:
return -1 // ставит его core: кнопка здесь поставила бы Prometheus для core
case m.localEnabled: case m.localEnabled:
return 0 return 0
case src != nil: case src != nil:
@@ -377,7 +382,7 @@ func (m *Monitor) router() *Router {
jobOut = jsjson.Object{{Key: "action", Val: job.action}, {Key: "running", Val: job.running}, {Key: "exit", Val: job.exit}, {Key: "log", Val: job.log}} jobOut = jsjson.Object{{Key: "action", Val: job.action}, {Key: "running", Val: job.running}, {Key: "exit", Val: job.exit}, {Key: "log", Val: job.log}}
} }
return c.JSON(200, jsjson.Object{{Key: "ready", Val: ready}, {Key: "can_install", Val: job.can}, {Key: "job", Val: jobOut}, return c.JSON(200, jsjson.Object{{Key: "ready", Val: ready}, {Key: "can_install", Val: job.can}, {Key: "job", Val: jobOut},
{Key: "command", Val: "awg-ctrl monitor install"}, {Key: "host", Val: m.host(target)}}) {Key: "command", Val: "awg-ctrl monitor install"}, {Key: "host", Val: m.host(target)}, {Key: "on_node", Val: m.onNode}})
}) })
// Установка и удаление — одно и то же: просим awg-ctrl сервера, он запускает CLI от root. // Установка и удаление — одно и то же: просим awg-ctrl сервера, он запускает CLI от root.
ask := func(action string) handler { ask := func(action string) handler {