mirror of
https://github.com/maeneko/forgetting.git
synced 2026-08-25 15:24:26 +00:00
Add AWG_GEN/AWG_PIN switches and surface module version
Upstream AmneziaWG 3.x is still shifting under active development (unanswered kernel-module issues around header protection and handshake-without-data regressions), so give operators an explicit opt-out/opt-in instead of always auto-selecting 3.1, and pin the installed package so unattended-upgrades can't swap the module under a live server. Surface the running module/tools version and protocol generation through /health, the CLI status command, and the panel's server card so a mismatch is visible instead of silent.
This commit is contained in:
+34
-4
@@ -196,13 +196,36 @@ function genOf(p: AwgParams): "2" | "3.1" {
|
|||||||
return p.HeaderProtectionKey ? "3.1" : "2";
|
return p.HeaderProtectionKey ? "3.1" : "2";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Версия модуля/tools для /health — показывает оператору, что реально работает
|
||||||
|
// в ядре сейчас (см. install.sh: apt может обновить пакет, пока в ядре живёт
|
||||||
|
// старый загруженный модуль, коммит 6fdacd7). Загруженная версия важнее версии
|
||||||
|
// на диске ровно по той же причине. Вызывается на старте и при restartAwg() —
|
||||||
|
// это единственные моменты, когда модуль может реально смениться в течение
|
||||||
|
// жизни процесса; из обработчика /health не вызывается — этот роут без
|
||||||
|
// авторизации, спавнить подпроцессы на каждый его опрос нельзя.
|
||||||
|
function readAwgVersions(): { module: string; tools: string } {
|
||||||
|
let module = "";
|
||||||
|
try {
|
||||||
|
module = readFileSync("/sys/module/amneziawg/version", "utf8").trim();
|
||||||
|
} catch {
|
||||||
|
try { module = run("modinfo -F version amneziawg"); } catch { /* модуль не загружен */ }
|
||||||
|
}
|
||||||
|
let tools = "";
|
||||||
|
try {
|
||||||
|
const out = run("awg --version");
|
||||||
|
tools = out.split(/\s+/).find(t => /^v?\d/.test(t)) ?? "";
|
||||||
|
} catch { /* awg-tools не найден */ }
|
||||||
|
return { module, tools };
|
||||||
|
}
|
||||||
|
|
||||||
// PersistentKeepalive в 3.1 задаётся диапазоном (дефолт клиента AmneziaVPN);
|
// PersistentKeepalive в 3.1 задаётся диапазоном (дефолт клиента AmneziaVPN);
|
||||||
// в 2.0 это одно число. Уходит и в серверные [Peer], и в клиентский конфиг.
|
// в 2.0 это одно число. Уходит и в серверные [Peer], и в клиентский конфиг.
|
||||||
const KEEPALIVE_BY_GEN: Record<"2" | "3.1", string> = { "2": "25", "3.1": "25-35" };
|
const KEEPALIVE_BY_GEN: Record<"2" | "3.1", string> = { "2": "25", "3.1": "25-35" };
|
||||||
|
|
||||||
const runtimeConfig = initConfig();
|
const runtimeConfig = initConfig();
|
||||||
const AWG_PARAMS = readAwgParams();
|
const AWG_PARAMS = readAwgParams();
|
||||||
const AWG_GEN = genOf(AWG_PARAMS);
|
const AWG_GEN = genOf(AWG_PARAMS);
|
||||||
|
const AWG_VERSIONS = readAwgVersions();
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
interface: "awg1",
|
interface: "awg1",
|
||||||
confDir: "/etc/amnezia/amneziawg",
|
confDir: "/etc/amnezia/amneziawg",
|
||||||
@@ -216,6 +239,10 @@ const CONFIG = {
|
|||||||
keepalive: KEEPALIVE_BY_GEN[AWG_GEN],
|
keepalive: KEEPALIVE_BY_GEN[AWG_GEN],
|
||||||
awgParams: AWG_PARAMS,
|
awgParams: AWG_PARAMS,
|
||||||
gen: AWG_GEN,
|
gen: AWG_GEN,
|
||||||
|
// Мутируются в restartAwg() — единственном месте, где модуль может
|
||||||
|
// реально смениться в течение жизни процесса.
|
||||||
|
awgModule: AWG_VERSIONS.module,
|
||||||
|
awgTools: AWG_VERSIONS.tools,
|
||||||
};
|
};
|
||||||
|
|
||||||
interface UserRow {
|
interface UserRow {
|
||||||
@@ -582,7 +609,10 @@ function restartAwg() {
|
|||||||
const up = spawnSync("awg-quick", ["up", CONFIG.interface]);
|
const up = spawnSync("awg-quick", ["up", CONFIG.interface]);
|
||||||
if (up.status !== 0) throw new Error(`awg-quick up failed: ${up.stderr?.toString()}`);
|
if (up.status !== 0) throw new Error(`awg-quick up failed: ${up.stderr?.toString()}`);
|
||||||
syncPeers();
|
syncPeers();
|
||||||
logger.info("AWG restart: done");
|
const versions = readAwgVersions();
|
||||||
|
CONFIG.awgModule = versions.module;
|
||||||
|
CONFIG.awgTools = versions.tools;
|
||||||
|
logger.info("AWG restart: done", versions);
|
||||||
}
|
}
|
||||||
|
|
||||||
function startInterface() {
|
function startInterface() {
|
||||||
@@ -697,7 +727,7 @@ app.get("/health", (_req, res) => {
|
|||||||
server: CONFIG.serverName,
|
server: CONFIG.serverName,
|
||||||
ip: CONFIG.serverIp,
|
ip: CONFIG.serverIp,
|
||||||
gen: CONFIG.gen,
|
gen: CONFIG.gen,
|
||||||
awg: { status: up ? "ok" : "down", peers },
|
awg: { status: up ? "ok" : "down", peers, module: CONFIG.awgModule, tools: CONFIG.awgTools },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -406,6 +406,13 @@ body {
|
|||||||
color: var(--neutral-text);
|
color: var(--neutral-text);
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
.server-gen-wrap { align-self: flex-start; margin-top: 2px; }
|
||||||
|
.server-gen-chip {
|
||||||
|
align-self: flex-start;
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
}
|
||||||
.server-card-peers {
|
.server-card-peers {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--neutral-text);
|
color: var(--neutral-text);
|
||||||
|
|||||||
+4
-1
@@ -36,7 +36,10 @@ export default function App() {
|
|||||||
// вкладок (юзеры, ключи) грузят сами вкладки.
|
// вкладок (юзеры, ключи) грузят сами вкладки.
|
||||||
const startSession = useCallback(async (tok: string) => {
|
const startSession = useCallback(async (tok: string) => {
|
||||||
const { data: h } = await axios.get('/health', { headers: { Authorization: `Bearer ${tok}` } });
|
const { data: h } = await axios.get('/health', { headers: { Authorization: `Bearer ${tok}` } });
|
||||||
setServerInfo({ name: h.server || 'VPN', ip: h.ip || '', peers: h.awg?.peers ?? 0, gen: h.gen || '2' });
|
setServerInfo({
|
||||||
|
name: h.server || 'VPN', ip: h.ip || '', peers: h.awg?.peers ?? 0, gen: h.gen || '2',
|
||||||
|
mod: h.awg?.module ?? '', tools: h.awg?.tools ?? '',
|
||||||
|
});
|
||||||
setStatusText('online:' + (h.server || 'ok') + ' · peers: ' + (h.awg?.peers ?? '?'));
|
setStatusText('online:' + (h.server || 'ok') + ' · peers: ' + (h.awg?.peers ?? '?'));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2026 Ivan Vasilev
|
// Copyright (c) 2026 Ivan Vasilev
|
||||||
// This source code is licensed under the MIT license found in the
|
// This source code is licensed under the MIT license found in the
|
||||||
// LICENSE file in the root directory of this source tree.
|
// LICENSE file in the root directory of this source tree.
|
||||||
import { type ServerInfo } from '../lib/shared';
|
import { type ServerInfo, genLabel } from '../lib/shared';
|
||||||
import { IcoPlus, IcoRefresh } from './icons';
|
import { IcoPlus, IcoRefresh } from './icons';
|
||||||
|
|
||||||
// Сервер-бар: карточка активного сервера + «Добавить сервер» + (на мобильной)
|
// Сервер-бар: карточка активного сервера + «Добавить сервер» + (на мобильной)
|
||||||
@@ -38,6 +38,16 @@ export default function ServerBar({
|
|||||||
<div className="server-card-info">
|
<div className="server-card-info">
|
||||||
<span className="server-card-name">{serverInfo.name}</span>
|
<span className="server-card-name">{serverInfo.name}</span>
|
||||||
<span className="server-card-ip">{serverInfo.ip}</span>
|
<span className="server-card-ip">{serverInfo.ip}</span>
|
||||||
|
{(serverInfo.mod || serverInfo.tools) ? (
|
||||||
|
<span
|
||||||
|
className="tip-wrap server-gen-wrap"
|
||||||
|
data-tip={`модуль ${serverInfo.mod || '?'} · tools ${serverInfo.tools || '?'}`}
|
||||||
|
>
|
||||||
|
<span className="chip chip--offline server-gen-chip">{genLabel(serverInfo.gen)}</span>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="chip chip--offline server-gen-chip">{genLabel(serverInfo.gen)}</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="server-card-peers">{serverInfo.peers} peers</span>
|
<span className="server-card-peers">{serverInfo.peers} peers</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,7 +21,11 @@ export interface ServerInfo {
|
|||||||
ip: string;
|
ip: string;
|
||||||
peers: number;
|
peers: number;
|
||||||
gen: AwgGen;
|
gen: AwgGen;
|
||||||
|
mod: string; // версия загруженного модуля amneziawg, из /health awg.module
|
||||||
|
tools: string; // версия awg-tools, из /health awg.tools
|
||||||
}
|
}
|
||||||
|
// На проводе (users.key_gen, /health gen) поколение — '2', человеку показываем '2.0'.
|
||||||
|
export const genLabel = (g: AwgGen): string => (g === '3.1' ? 'AWG 3.1' : 'AWG 2.0');
|
||||||
export interface ApiKey {
|
export interface ApiKey {
|
||||||
id: number;
|
id: number;
|
||||||
label: string;
|
label: string;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import QRCode from 'qrcode';
|
import QRCode from 'qrcode';
|
||||||
import {
|
import {
|
||||||
apiFetch, vpnKeyToConf, downloadFile, copyText, bytes, timeAgo,
|
apiFetch, vpnKeyToConf, downloadFile, copyText, bytes, timeAgo, genLabel,
|
||||||
type User, type PageProps, type AwgGen,
|
type User, type PageProps, type AwgGen,
|
||||||
} from '../../lib/shared';
|
} from '../../lib/shared';
|
||||||
import { IcoPlus, IcoRefresh, IcoQR, IcoTrash, IcoGlobe } from '../../components/icons';
|
import { IcoPlus, IcoRefresh, IcoQR, IcoTrash, IcoGlobe } from '../../components/icons';
|
||||||
@@ -50,7 +50,7 @@ export default function UsersPage({ token, showMsg }: PageProps) {
|
|||||||
// IP и ключевая пара сохраняются, но клиентам нужно заново импортировать ключ.
|
// IP и ключевая пара сохраняются, но клиентам нужно заново импортировать ключ.
|
||||||
const reissueKeys = useCallback(async () => {
|
const reissueKeys = useCallback(async () => {
|
||||||
if (!confirm(
|
if (!confirm(
|
||||||
`Перевыпустить ключи (${stale.length} шт.) на поколении ${serverGen}?\n\n` +
|
`Перевыпустить ключи (${stale.length} шт.) на поколении ${genLabel(serverGen)}?\n\n` +
|
||||||
'Старые ключи перестанут работать — всем придётся импортировать ключ заново.',
|
'Старые ключи перестанут работать — всем придётся импортировать ключ заново.',
|
||||||
)) return;
|
)) return;
|
||||||
try {
|
try {
|
||||||
@@ -162,11 +162,11 @@ export default function UsersPage({ token, showMsg }: PageProps) {
|
|||||||
{u.key_gen !== serverGen && (
|
{u.key_gen !== serverGen && (
|
||||||
<span
|
<span
|
||||||
className="tip-wrap user-gen"
|
className="tip-wrap user-gen"
|
||||||
data-tip={`Ключ выдан на AWG ${u.key_gen}, сервер работает на ${serverGen}`}
|
data-tip={`Ключ выдан на ${genLabel(u.key_gen)}, сервер работает на ${genLabel(serverGen)}`}
|
||||||
>
|
>
|
||||||
<span className="chip chip--error">
|
<span className="chip chip--error">
|
||||||
<span className="chip-dot" />
|
<span className="chip-dot" />
|
||||||
AWG {u.key_gen}
|
{genLabel(u.key_gen)}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -207,7 +207,7 @@ export default function UsersPage({ token, showMsg }: PageProps) {
|
|||||||
{u.key_gen !== serverGen && (
|
{u.key_gen !== serverGen && (
|
||||||
<span className="chip chip--error">
|
<span className="chip chip--error">
|
||||||
<span className="chip-dot" />
|
<span className="chip-dot" />
|
||||||
AWG {u.key_gen}
|
{genLabel(u.key_gen)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+30
-3
@@ -196,7 +196,27 @@ async function restart(name: SvcName) {
|
|||||||
await start(name);
|
await start(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function status() {
|
// GET /health и разбор JSON-тела. Любая ошибка (не запущен, таймаут, не JSON)
|
||||||
|
// молча даёт null — health-строка в status() тогда просто не печатается.
|
||||||
|
interface HealthBody {
|
||||||
|
gen?: string;
|
||||||
|
awg?: { module?: string; tools?: string };
|
||||||
|
}
|
||||||
|
function fetchHealth(url: string, timeoutMs = 1000): Promise<HealthBody | null> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const req = http.get(url, res => {
|
||||||
|
let body = "";
|
||||||
|
res.on("data", (c: Buffer) => { body += c; });
|
||||||
|
res.on("end", () => {
|
||||||
|
try { resolve(JSON.parse(body) as HealthBody); } catch { resolve(null); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.on("error", () => resolve(null));
|
||||||
|
req.setTimeout(timeoutMs, () => { req.destroy(); resolve(null); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function status() {
|
||||||
console.log("");
|
console.log("");
|
||||||
for (const name of ALL) {
|
for (const name of ALL) {
|
||||||
const running = isRunning(name);
|
const running = isRunning(name);
|
||||||
@@ -205,6 +225,13 @@ function status() {
|
|||||||
const info = running ? green("running") + ` pid ${pid}` : grey("stopped");
|
const info = running ? green("running") + ` pid ${pid}` : grey("stopped");
|
||||||
console.log(` ${dot} ${SERVICES[name].label.padEnd(12)} ${info}`);
|
console.log(` ${dot} ${SERVICES[name].label.padEnd(12)} ${info}`);
|
||||||
}
|
}
|
||||||
|
if (isRunning("awgctrl")) {
|
||||||
|
const h = await fetchHealth(`http://127.0.0.1:${SERVICES.awgctrl.env.PORT}/health`);
|
||||||
|
if (h?.gen) {
|
||||||
|
const gen = h.gen === "3.1" ? "3.1" : "2.0";
|
||||||
|
console.log(` ${dim(`AmneziaWG ${gen} · модуль ${h.awg?.module || "?"} · tools ${h.awg?.tools || "?"}`)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
console.log("");
|
console.log("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +348,7 @@ async function interactiveMenu() {
|
|||||||
case "1": await startAll(); break;
|
case "1": await startAll(); break;
|
||||||
case "2": await stopAll(); break;
|
case "2": await stopAll(); break;
|
||||||
case "3": await restartAll(); break;
|
case "3": await restartAll(); break;
|
||||||
case "4": status(); break;
|
case "4": await status(); break;
|
||||||
case "5": await setCredentials("user"); break;
|
case "5": await setCredentials("user"); break;
|
||||||
case "6": await setCredentials("pass"); break;
|
case "6": await setCredentials("pass"); break;
|
||||||
case "0": rl.close(); process.exit(0);
|
case "0": rl.close(); process.exit(0);
|
||||||
@@ -350,7 +377,7 @@ async function main() {
|
|||||||
case "start": await startAll(); break;
|
case "start": await startAll(); break;
|
||||||
case "stop": await stopAll(); break;
|
case "stop": await stopAll(); break;
|
||||||
case "restart": await restartAll(); break;
|
case "restart": await restartAll(); break;
|
||||||
case "status": status(); break;
|
case "status": await status(); break;
|
||||||
case "credentials":
|
case "credentials":
|
||||||
if (sub === "user") await setCredentials("user");
|
if (sub === "user") await setCredentials("user");
|
||||||
else if (sub === "pass") await setCredentials("pass");
|
else if (sub === "pass") await setCredentials("pass");
|
||||||
|
|||||||
+85
-35
@@ -134,6 +134,22 @@ H2="2122053282-2133204808"
|
|||||||
H3="2133604274-2140756116"
|
H3="2133604274-2140756116"
|
||||||
H4="2143656228-2147444225"
|
H4="2143656228-2147444225"
|
||||||
|
|
||||||
|
# Поколение протокола и закрепление версии пакета — управляются переменными
|
||||||
|
# окружения, задаются ДО запуска: `AWG_GEN=2.0 bash install.sh`.
|
||||||
|
# AWG_GEN=auto (по умолчанию) — как раньше: 3.1, если модуль 3.x и ядро ≥ 5.5,
|
||||||
|
# иначе 2.0, без лишних вопросов.
|
||||||
|
# AWG_GEN=2.0 — не трогать модуль (ни апгрейд, ни перезагрузка), сразу 2.0.
|
||||||
|
# AWG_GEN=3.1 — требовать 3.1: если условия не выполняются, install.sh падает
|
||||||
|
# с диагностикой вместо тихого отката (апстрим ещё нестабилен, см. issues
|
||||||
|
# #215/#216/#217 в amneziawg-linux-kernel-module — тихий откат на 2.0 здесь
|
||||||
|
# нежелателен, если оператор явно просил 3.1).
|
||||||
|
AWG_GEN="${AWG_GEN:-auto}"
|
||||||
|
[[ "$AWG_GEN" =~ ^(auto|2\.0|3\.1)$ ]] || fail "AWG_GEN должен быть auto, 2.0 или 3.1 (задано: $AWG_GEN)"
|
||||||
|
# AWG_PIN=y (по умолчанию) — после успешной установки держит amneziawg{,-dkms,-tools}
|
||||||
|
# через `apt-mark hold`: unattended-upgrades не должен подменять модуль под живым
|
||||||
|
# сервером (issue #215 — именно так словили регрессию между dkms-сборками).
|
||||||
|
AWG_PIN="${AWG_PIN:-y}"
|
||||||
|
|
||||||
NET_IFACE=$(ip route show default 2>/dev/null | awk '/default/{print $5; exit}')
|
NET_IFACE=$(ip route show default 2>/dev/null | awk '/default/{print $5; exit}')
|
||||||
[[ -z "$NET_IFACE" ]] && fail "Не могу определить сетевой интерфейс"
|
[[ -z "$NET_IFACE" ]] && fail "Не могу определить сетевой интерфейс"
|
||||||
|
|
||||||
@@ -457,10 +473,11 @@ fi
|
|||||||
# - ядро: header protection использует библиотечный chacha-API
|
# - ядро: header protection использует библиотечный chacha-API
|
||||||
# (chacha_init/chacha20_crypt), которого нет до 5.5 — модуль там не соберётся
|
# (chacha_init/chacha20_crypt), которого нет до 5.5 — модуль там не соберётся
|
||||||
# (upstream issue #210). Проблема с nla_put_uint на ядрах < 6.7 уже исправлена.
|
# (upstream issue #210). Проблема с nla_put_uint на ядрах < 6.7 уже исправлена.
|
||||||
# Если 3.1 недоступен — не падаем, а пишем 2.0-конфиг: awg-ctrl определяет
|
# Если 3.1 недоступен и AWG_GEN=auto — не падаем, а пишем 2.0-конфиг: awg-ctrl
|
||||||
# поколение по наличию HeaderProtectionKey в конфиге, так что всё продолжит
|
# определяет поколение по наличию HeaderProtectionKey в конфиге, так что всё
|
||||||
# работать ровно как раньше.
|
# продолжит работать ровно как раньше. При AWG_GEN=3.1 недоступность — fail
|
||||||
AWG3="y"
|
# (см. AWG_GEN выше): апстрим модуля ещё нестабилен, тихий откат при явном
|
||||||
|
# запросе 3.1 может незаметно подсунуть более слабую обфускацию.
|
||||||
|
|
||||||
kernel_lt_5_5() {
|
kernel_lt_5_5() {
|
||||||
local maj min
|
local maj min
|
||||||
@@ -479,42 +496,59 @@ kernel_lt_5_5() {
|
|||||||
disk_mod_ver() { modinfo -F version amneziawg 2>/dev/null || echo ""; }
|
disk_mod_ver() { modinfo -F version amneziawg 2>/dev/null || echo ""; }
|
||||||
loaded_mod_ver() { cat /sys/module/amneziawg/version 2>/dev/null || echo ""; }
|
loaded_mod_ver() { cat /sys/module/amneziawg/version 2>/dev/null || echo ""; }
|
||||||
|
|
||||||
MOD_VER=$(disk_mod_ver)
|
if [[ "$AWG_GEN" == "2.0" ]]; then
|
||||||
if [[ "${MOD_VER%%.*}" != "3" ]]; then
|
AWG3="n"
|
||||||
warn "Модуль amneziawg версии '${MOD_VER:-неизвестно}' — для AmneziaWG 3.1 нужна 3.x. Пробуем обновить."
|
|
||||||
apt-get update >/dev/null 2>&1 || true
|
|
||||||
DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a \
|
|
||||||
apt-get install -y --only-upgrade amneziawg amneziawg-dkms amneziawg-tools >/dev/null 2>&1 || true
|
|
||||||
MOD_VER=$(disk_mod_ver)
|
MOD_VER=$(disk_mod_ver)
|
||||||
fi
|
ok "Поколение задано вручную (AWG_GEN=2.0) — модуль не трогаем, ставим 2.0."
|
||||||
|
else
|
||||||
|
AWG3="y"
|
||||||
|
|
||||||
# Загруженный отстал от дискового — перезагружаем. Интерфейс держит модуль,
|
MOD_VER=$(disk_mod_ver)
|
||||||
# поэтому сначала опускаем его; awg1 на этом шаге ещё не нужен.
|
if [[ "${MOD_VER%%.*}" != "3" ]]; then
|
||||||
LOADED_VER=$(loaded_mod_ver)
|
warn "Модуль amneziawg версии '${MOD_VER:-неизвестно}' — для AmneziaWG 3.1 нужна 3.x. Пробуем обновить."
|
||||||
if [[ -n "$LOADED_VER" && "$LOADED_VER" != "$MOD_VER" ]]; then
|
# Прошлая установка могла закрепить пакет (apt-mark hold, см. ниже) —
|
||||||
warn "В ядре загружен модуль $LOADED_VER, на диске $MOD_VER — перезагружаем модуль."
|
# снимаем, иначе --only-upgrade молча не тронет held-версию.
|
||||||
awg-quick down "$IFACE" 2>/dev/null || true
|
apt-mark unhold amneziawg amneziawg-dkms amneziawg-tools >/dev/null 2>&1 || true
|
||||||
ip link delete dev "$IFACE" 2>/dev/null || true
|
apt-get update >/dev/null 2>&1 || true
|
||||||
if modprobe -r amneziawg 2>/dev/null && modprobe amneziawg 2>/dev/null; then
|
DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a \
|
||||||
LOADED_VER=$(loaded_mod_ver)
|
apt-get install -y --only-upgrade amneziawg amneziawg-dkms amneziawg-tools >/dev/null 2>&1 || true
|
||||||
ok "Модуль перезагружен: в ядре теперь $LOADED_VER"
|
MOD_VER=$(disk_mod_ver)
|
||||||
else
|
|
||||||
warn "Не удалось перезагрузить модуль (занят?). Нужна перезагрузка сервера."
|
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
# Дальше решает ТОЛЬКО загруженная версия — именно она обслуживает интерфейс.
|
# Загруженный отстал от дискового — перезагружаем. Интерфейс держит модуль,
|
||||||
[[ -n "$LOADED_VER" ]] && MOD_VER="$LOADED_VER"
|
# поэтому сначала опускаем его; awg1 на этом шаге ещё не нужен.
|
||||||
|
LOADED_VER=$(loaded_mod_ver)
|
||||||
|
if [[ -n "$LOADED_VER" && "$LOADED_VER" != "$MOD_VER" ]]; then
|
||||||
|
warn "В ядре загружен модуль $LOADED_VER, на диске $MOD_VER — перезагружаем модуль."
|
||||||
|
awg-quick down "$IFACE" 2>/dev/null || true
|
||||||
|
ip link delete dev "$IFACE" 2>/dev/null || true
|
||||||
|
if modprobe -r amneziawg 2>/dev/null && modprobe amneziawg 2>/dev/null; then
|
||||||
|
LOADED_VER=$(loaded_mod_ver)
|
||||||
|
ok "Модуль перезагружен: в ядре теперь $LOADED_VER"
|
||||||
|
else
|
||||||
|
warn "Не удалось перезагрузить модуль (занят?). Нужна перезагрузка сервера."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "${MOD_VER%%.*}" != "3" ]]; then
|
# Дальше решает ТОЛЬКО загруженная версия — именно она обслуживает интерфейс.
|
||||||
AWG3="n"
|
[[ -n "$LOADED_VER" ]] && MOD_VER="$LOADED_VER"
|
||||||
warn "В ядре модуль версии '${MOD_VER:-неизвестно}' — ставим сервер на AmneziaWG 2.0."
|
|
||||||
[[ "$(disk_mod_ver)" == 3.* ]] && \
|
if [[ "${MOD_VER%%.*}" != "3" ]]; then
|
||||||
warn "На диске уже 3.x — после перезагрузки сервера можно переустановить и получить 3.1."
|
AWG3="n"
|
||||||
elif kernel_lt_5_5; then
|
if [[ "$AWG_GEN" == "3.1" ]]; then
|
||||||
AWG3="n"
|
fail "AWG_GEN=3.1 запрошен явно, но в ядре модуль версии '${MOD_VER:-неизвестно}' (на диске $(disk_mod_ver)) — нужна 3.x."
|
||||||
warn "Ядро $KERNEL старше 5.5 — header protection не соберётся (upstream issue #210)."
|
fi
|
||||||
warn "Ставим сервер на AmneziaWG 2.0."
|
warn "В ядре модуль версии '${MOD_VER:-неизвестно}' — ставим сервер на AmneziaWG 2.0."
|
||||||
|
[[ "$(disk_mod_ver)" == 3.* ]] && \
|
||||||
|
warn "На диске уже 3.x — после перезагрузки сервера можно переустановить и получить 3.1."
|
||||||
|
elif kernel_lt_5_5; then
|
||||||
|
AWG3="n"
|
||||||
|
if [[ "$AWG_GEN" == "3.1" ]]; then
|
||||||
|
fail "AWG_GEN=3.1 запрошен явно, но ядро $KERNEL старше 5.5 — header protection не соберётся (upstream issue #210)."
|
||||||
|
fi
|
||||||
|
warn "Ядро $KERNEL старше 5.5 — header protection не соберётся (upstream issue #210)."
|
||||||
|
warn "Ставим сервер на AmneziaWG 2.0."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$AWG3" == "y" ]]; then
|
if [[ "$AWG3" == "y" ]]; then
|
||||||
@@ -696,6 +730,8 @@ if ! awg-quick up "$IFACE"; then
|
|||||||
sed -i -E "/$AWG3_KEY_RE/d" "$AWG_CONF"
|
sed -i -E "/$AWG3_KEY_RE/d" "$AWG_CONF"
|
||||||
AWG3="n"; NEW_GEN="2.0"
|
AWG3="n"; NEW_GEN="2.0"
|
||||||
warn "Откатываемся на AmneziaWG 2.0. Конфиг 3.1 сохранён: ${AWG_CONF}.awg31"
|
warn "Откатываемся на AmneziaWG 2.0. Конфиг 3.1 сохранён: ${AWG_CONF}.awg31"
|
||||||
|
[[ "$AWG_GEN" == "3.1" ]] && \
|
||||||
|
warn "Запрошено AWG_GEN=3.1, но интерфейс поднялся на 2.0 — см. ${AWG_CONF}.awg31 и dmesg выше."
|
||||||
|
|
||||||
awg-quick up "$IFACE" \
|
awg-quick up "$IFACE" \
|
||||||
|| fail "awg-quick up $IFACE не работает даже на 2.0 — смотри dmesg выше"
|
|| fail "awg-quick up $IFACE не работает даже на 2.0 — смотри dmesg выше"
|
||||||
@@ -719,6 +755,20 @@ else
|
|||||||
fail "Ключ на $IFACE не совпал: ${RUNNING_PUB:-none} ≠ $PUB_KEY"
|
fail "Ключ на $IFACE не совпал: ${RUNNING_PUB:-none} ≠ $PUB_KEY"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
TOOLS_VER=$(awg --version 2>/dev/null | awk '{print $2; exit}')
|
||||||
|
|
||||||
|
# Закрепляем версию пакета, чтобы unattended-upgrades не подменил модуль под
|
||||||
|
# живым сервером (issue #215: между двумя dkms-сборками словили регрессию —
|
||||||
|
# хендшейк проходит, данные не идут). Unhold перед апгрейдом уже сделан выше;
|
||||||
|
# hold ставим только теперь, когда версия проверена и интерфейс реально поднялся.
|
||||||
|
if [[ "$AWG_PIN" == "y" ]]; then
|
||||||
|
apt-mark hold amneziawg amneziawg-dkms amneziawg-tools >/dev/null 2>&1 || true
|
||||||
|
ok "Версия пакета закреплена (apt-mark hold) — снять: apt-mark unhold amneziawg amneziawg-dkms amneziawg-tools"
|
||||||
|
fi
|
||||||
|
|
||||||
|
GEN_LABEL=$([[ "$AWG3" == "y" ]] && echo "3.1" || echo "2.0")
|
||||||
|
ok "AmneziaWG $GEN_LABEL · модуль ${MOD_VER:-?} · tools ${TOOLS_VER:-?}$([[ "$AWG_PIN" == "y" ]] && echo " · версия закреплена")"
|
||||||
|
|
||||||
# UFW. PostUp делает `iptables -A FORWARD` — правило ДОПИСЫВАЕТСЯ в конец
|
# UFW. PostUp делает `iptables -A FORWARD` — правило ДОПИСЫВАЕТСЯ в конец
|
||||||
# цепочки, а UFW вставляет свои переходы в начало, поэтому до нашего ACCEPT
|
# цепочки, а UFW вставляет свои переходы в начало, поэтому до нашего ACCEPT
|
||||||
# дело не доходит и трафик клиентов наружу режется: в dmesg сыплется
|
# дело не доходит и трафик клиентов наружу режется: в dmesg сыплется
|
||||||
|
|||||||
Reference in New Issue
Block a user