feat: add awg 2.0 (#51)

* feat: update awg tools

* feat: update kmod-awg

* feat: update luci awg

* feat: update build-module.yml

* feat: update amneziawg-install.sh

* feat: update README.md

* fix: fix paths

* fix: fix adding packages

* fix: fix regexp

* fix: fix feeds install

* chore: disable run-release.yml

* feat: add rus lang package

* fix: install python3-pyelftools in build job

* feat: add manual start for build

* chore: remove build to all snapshots
This commit is contained in:
Slava-Shchipunov
2025-10-04 00:20:19 +07:00
committed by GitHub
parent d3372b1535
commit bad6a2005d
23 changed files with 860 additions and 1399 deletions

View File

@@ -0,0 +1,13 @@
include $(TOPDIR)/rules.mk
LUCI_TITLE:=Support for AmneziaWG VPN
LUCI_DESCRIPTION:=Provides support and Web UI for AmneziaWG VPN
PKG_VERSION:=2.0.4
LUCI_DEPENDS:=+amneziawg-tools +ucode +luci-lib-uqr +resolveip
LUCI_PKGARCH:=all
PKG_LICENSE:=Apache-2.0
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,175 @@
'use strict';
'require view';
'require rpc';
'require poll';
'require dom';
'require ui';
var callgetAwgInstances = rpc.declare({
object: 'luci.amneziawg',
method: 'getAwgInstances'
});
function timestampToStr(timestamp) {
if (timestamp < 1)
return _('Never', 'No AmneziaWG peer handshake yet');
var seconds = (Date.now() / 1000) - timestamp;
var ago;
if (seconds < 60)
ago = _('%ds ago').format(seconds);
else if (seconds < 3600)
ago = _('%dm ago').format(seconds / 60);
else if (seconds < 86401)
ago = _('%dh ago').format(seconds / 3600);
else
ago = _('over a day ago');
return (new Date(timestamp * 1000)).toUTCString() + ' (' + ago + ')';
}
function handleInterfaceDetails(iface) {
ui.showModal(_('Instance Details'), [
ui.itemlist(E([]), [
_('Name'), iface.name,
_('Public Key'), E('code', [ iface.public_key ]),
_('Listen Port'), iface.listen_port,
_('Firewall Mark'), iface.fwmark != 'off' ? iface.fwmark : E('em', _('none'))
]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn cbi-button',
'click': ui.hideModal
}, [ _('Dismiss') ])
])
]);
}
function handlePeerDetails(peer) {
ui.showModal(_('Peer Details'), [
ui.itemlist(E([]), [
_('Description'), peer.name,
_('Public Key'), E('code', [ peer.public_key ]),
_('Endpoint'), peer.endpoint,
_('Allowed IPs'), (Array.isArray(peer.allowed_ips) && peer.allowed_ips.length) ? peer.allowed_ips.join(', ') : E('em', _('none')),
_('Received Data'), '%1024mB'.format(peer.transfer_rx),
_('Transmitted Data'), '%1024mB'.format(peer.transfer_tx),
_('Latest Handshake'), timestampToStr(+peer.latest_handshake),
_('Keep-Alive'), (peer.persistent_keepalive != 'off') ? _('every %ds', 'AmneziaWG keep alive interval').format(+peer.persistent_keepalive) : E('em', _('none')),
]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn cbi-button',
'click': ui.hideModal
}, [ _('Dismiss') ])
])
]);
}
function renderPeerTable(instanceName, peers) {
var t = new L.ui.Table(
[
_('Peer'),
_('Endpoint'),
_('Data Received'),
_('Data Transmitted'),
_('Latest Handshake')
],
{
id: 'peers-' + instanceName
},
E('em', [
_('No peers connected')
])
);
t.update(peers.map(function(peer) {
return [
[
peer.name || '',
E('div', {
'style': 'cursor:pointer',
'click': ui.createHandlerFn(this, handlePeerDetails, peer)
}, [
E('p', [
peer.name ? E('span', [ peer.name ]) : E('em', [ _('Untitled peer') ])
]),
E('span', {
'class': 'ifacebadge hide-sm',
'data-tooltip': _('Public key: %h', 'Tooltip displaying full AmneziaWG peer public key').format(peer.public_key)
}, [
E('code', [ peer.public_key.replace(/^(.{5}).+(.{6})$/, '$1…$2') ])
])
])
],
peer.endpoint,
[ +peer.transfer_rx, '%1024mB'.format(+peer.transfer_rx) ],
[ +peer.transfer_tx, '%1024mB'.format(+peer.transfer_tx) ],
[ +peer.latest_handshake, timestampToStr(+peer.latest_handshake) ]
];
}));
return t.render();
}
return view.extend({
renderIfaces: function(ifaces) {
var res = [
E('h2', [ _('AmneziaWG Status') ])
];
for (var instanceName in ifaces) {
res.push(
E('h3', [ _('Instance "%h"', 'AmneziaWG instance heading').format(instanceName) ]),
E('p', {
'style': 'cursor:pointer',
'click': ui.createHandlerFn(this, handleInterfaceDetails, ifaces[instanceName])
}, [
E('span', { 'class': 'ifacebadge' }, [
E('img', { 'src': L.resource('icons', 'amneziawg.svg') }),
'\xa0',
instanceName
]),
E('span', { 'style': 'opacity:.8' }, [
' · ',
_('Port %d', 'AmneziaWG listen port').format(ifaces[instanceName].listen_port),
' · ',
E('code', { 'click': '' }, [ ifaces[instanceName].public_key ])
])
]),
renderPeerTable(instanceName, ifaces[instanceName].peers)
);
}
if (res.length == 1)
res.push(E('p', { 'class': 'center', 'style': 'margin-top:5em' }, [
E('em', [ _('No AmneziaWG interfaces configured.') ])
]));
return E([], res);
},
render: function() {
poll.add(L.bind(function () {
return callgetAwgInstances().then(L.bind(function(ifaces) {
dom.content(
document.querySelector('#view'),
this.renderIfaces(ifaces)
);
}, this));
}, this), 5);
return E([], [
E('h2', [ _('AmneziaWG Status') ]),
E('p', { 'class': 'center', 'style': 'margin-top:5em' }, [
E('em', [ _('Loading data…') ])
])
]);
},
handleReset: null,
handleSaveApply: null,
handleSave: null
});

View File

@@ -0,0 +1,158 @@
msgid "Imports settings from an existing AmneziaWG configuration file"
msgstr "Импортирует настройки из существующего файла конфигурации AmneziaWG"
msgid ""
"Drag or paste a valid <em>*.conf</em> file below to configure the local "
"AmneziaWG interface."
msgstr ""
"Перетащите или вставьте правильный файл <em>*.conf</em> ниже, чтобы "
"настроить локальный интерфейс AmneziaWG."
msgid ""
"Paste or drag a AmneziaWG configuration (commonly <em>wg0.conf</em>) from "
"another system below to create a matching peer entry allowing that system to "
"connect to the local AmneziaWG interface."
msgstr ""
"Вставьте или перетащите конфигурацию AmneziaWG (обычно <em>wg0.conf</em>) из "
"другой системы ниже, чтобы создать соответствующую запись узла, позволяющую "
"этой системе подключиться к локальному интерфейсу AmneziaWG."
msgid ""
"To configure fully the local AmneziaWG interface from an existing (e.g. "
"provider supplied) configuration file, use the <strong><a class=\"full-"
"import\" href=\"#\">configuration import</a></strong> instead."
msgstr ""
"Чтобы полностью настроить локальный интерфейс AmneziaWG из существующего "
"(например, предоставленного провайдером) файла конфигурации, используйте "
"вместо этого <strong><a class=\"full-import\" href=\"#\">импорт "
"конфигурации</a></strong>."
msgid "Paste or drag supplied AmneziaWG configuration file…"
msgstr "Вставьте или перетащите имеющийся файл конфигурации AmneziaWG…"
msgid "Paste or drag AmneziaWG peer configuration (wg0.conf) file…"
msgstr "Вставьте или перетащите файл конфигурации узлов AmneziaWG (wg0.conf)…"
msgid "Recommended. IP addresses of the AmneziaWG interface."
msgstr "Рекомендуемый. IP адреса интерфейса AmneziaWG."
msgid "Imports settings from an existing AmneziaWG configuration file"
msgstr "Импортирует настройки из существующего файла конфигурации AmneziaWG"
msgid "AmneziaWG Settings"
msgstr "Настройки AmneziaWG"
msgid "Imports settings from an existing AmneziaWG configuration file"
msgstr "Импортирует настройки из существующего файла конфигурации AmneziaWG"
msgid "Imports settings from an existing AmneziaWG configuration file"
msgstr "Импортирует настройки из существующего файла конфигурации AmneziaWG"
msgid "Imports settings from an existing AmneziaWG configuration file"
msgstr "Импортирует настройки из существующего файла конфигурации AmneziaWG"
msgid ""
"Further information about AmneziaWG interfaces and peers at <a href='https://"
"docs.amnezia.org/documentation/amnezia-wg'>amnezia.org</a>."
msgstr ""
"Дополнительная информация об AmneziaWG интерфейсах и узлах приведена по "
"адресу <a href='https://docs.amnezia.org/documentation/amnezia-wg'>amnezia.org</a>."
msgid "Junk packet count."
msgstr "Количество мусорных пакетов."
msgid "Junk packet minimum size."
msgstr "Минимальный размер мусорного пакета."
msgid "Junk packet maximum size."
msgstr "Максимальный размер мусорного пакета."
msgid "Handshake initiation packet junk header size."
msgstr "Размер мусорного заголовка пакета инициации рукопожатия."
msgid "Handshake response packet junk header size."
msgstr "Размер мусорного заголовка пакета ответа на рукопожатие."
msgid "Cookie reply packet junk header size."
msgstr "Размер мусорного заголовка пакета ответа cookie."
msgid "Transport packet junk header size."
msgstr "Размер мусорного заголовка транспортного пакета."
msgid "Handshake initiation packet type header."
msgstr "Тип заголовка пакета инициации рукопожатия."
msgid "Handshake response packet type header."
msgstr "Тип заголовка пакета ответа на рукопожатие."
msgid "Handshake cookie packet type header."
msgstr "Тип заголовка пакета под нагрузкой."
msgid "Transport packet type header."
msgstr "Тип заголовка транспортного пакета."
msgid "First special junk packet signature."
msgstr "Сигнатура первого special junk пакета."
msgid "Second special junk packet signature."
msgstr "Сигнатура второго special junk пакета."
msgid "Third special junk packet signature."
msgstr "Сигнатура третьего special junk пакета."
msgid "Fourth special junk packet signature."
msgstr "Сигнатура четвертого special junk пакета."
msgid "Fifth special junk packet signature."
msgstr "Сигнатура пятого special junk пакета."
msgid "Enable / Disable peer. Restart amneziawg interface to apply changes."
msgstr ""
"Включить/выключить узел. Перезапустите интерфейс AmneziaWG, чтобы применить "
"изменения."
msgid "AmneziaWG peer is disabled"
msgstr "Узел AmneziaWG отключён"
msgctxt "Label indicating that AmneziaWG peer is disabled"
msgid "Disabled"
msgstr "Отключено"
msgctxt "Label indicating that AmneziaWG peer lacks public key"
msgid "Key missing"
msgstr "Отсутствует ключ"
msgctxt "Tooltip displaying full AmneziaWG peer public key"
msgid "Public key: %h"
msgstr "Публичный ключ: %h"
msgctxt "Label indicating that AmneziaWG peer private key is stored"
msgid "Private"
msgstr "Private"
msgctxt "Label indicating that AmneziaWG peer uses a PSK"
msgid "PSK"
msgstr "PSK"
msgid "Required. Public key of the AmneziaWG peer."
msgstr "Обязательно. Публичный ключ AmneziaWG узла."
"Optional. Private key of the AmneziaWG peer. The key is not required for "
"establishing a connection but allows generating a peer configuration or QR "
"code if available. It can be removed after the configuration has been "
"exported."
msgstr ""
"Необязательно. Закрытый ключ узла AmneziaWG. Ключ не требуется для "
"установления соединения, но позволяет сгенерировать конфигурацию узла или QR-"
"код, если он доступен. Он может быть удален после экспорта конфигурации."
msgid "Generates a configuration suitable for import on a AmneziaWG peer"
msgstr "Создает конфигурацию, подходящую для импорта на узле AmneziaWG"
msgid ""
"No fixed interface listening port defined, peers might not be able to "
"initiate connections to this AmneziaWG instance!"
msgstr ""
"Не определен фиксированный порт прослушивания интерфейса, поэтому узлы могут "
"оказаться не в состоянии инициировать соединения с этим экземпляром "
"AmneziaWG!"

View File

@@ -0,0 +1,25 @@
msgid "AmneziaWG Status"
msgstr "Состояние AmneziaWG"
msgctxt "AmneziaWG instance heading"
msgid "Instance \"%h\""
msgstr "Экземпляр «%h»"
msgctxt "No AmneziaWG peer handshake yet"
msgid "Never"
msgstr "Никогда"
msgctxt "AmneziaWG keep alive interval"
msgid "every %ds"
msgstr "каждые %dс"
msgctxt "Tooltip displaying full AmneziaWG peer public key"
msgid "Public key: %h"
msgstr "Публичный ключ: %h"
msgctxt "AmneziaWG listen port"
msgid "Port %d"
msgstr "Порт %d"
msgid "No AmneziaWG interfaces configured."
msgstr "Интерфейсы AmneziaWG не настроены."

View File

@@ -0,0 +1,14 @@
{
"admin/status/amneziawg": {
"title": "AmneziaWG",
"order": 92,
"action": {
"type": "view",
"path": "amneziawg/status"
},
"depends": {
"acl": [ "luci-proto-amneziawg" ],
"uci": { "network": true }
}
}
}

View File

@@ -0,0 +1,22 @@
{
"luci-proto-amneziawg": {
"description": "Grant access to LuCI AmneziaWG procedures",
"read": {
"ubus": {
"luci.amneziawg": [
"getAwgInstances"
]
},
"uci": [ "ddns", "system", "network" ]
},
"write": {
"ubus": {
"luci.amneziawg": [
"generateKeyPair",
"getPublicAndPrivateKeyFromPrivate",
"generatePsk"
]
}
}
}
}

View File

@@ -0,0 +1,127 @@
// Copyright 2022 Jo-Philipp Wich <jo@mein.io>
// Licensed to the public under the Apache License 2.0.
'use strict';
import { cursor } from 'uci';
import { popen } from 'fs';
function shellquote(s) {
return `'${replace(s ?? '', "'", "'\\''")}'`;
}
function command(cmd) {
return trim(popen(cmd)?.read?.('all'));
}
function checkPeerHost(configHost, configPort, wgHost) {
const ips = popen(`resolveip ${shellquote(configHost)} 2>/dev/null`);
const hostIp = replace(wgHost, /\[|\]/g, "");
if (ips) {
for (let line = ips.read('line'); length(line); line = ips.read('line')) {
const ip = rtrim(line, '\n');
if (configPort && (ip + ":" + configPort == hostIp)) {
return true;
} else if (ip == substr(hostIp, 0, rindex(hostIp, ":"))) {
return true;
}
}
}
return false;
}
const methods = {
generatePsk: {
call: function() {
return { psk: command('awg genpsk 2>/dev/null') };
}
},
generateKeyPair: {
call: function() {
const priv = command('awg genkey 2>/dev/null');
const pub = command(`echo ${shellquote(priv)} | awg pubkey 2>/dev/null`);
return { keys: { priv, pub } };
}
},
getPublicAndPrivateKeyFromPrivate: {
args: { privkey: "privkey" },
call: function(req) {
const priv = req.args?.privkey;
const pub = command(`echo ${shellquote(priv)} | awg pubkey 2>/dev/null`);
return { keys: { priv, pub } };
}
},
getAwgInstances: {
call: function() {
const data = {};
let last_device;
let qr_pubkey = {};
const uci = cursor();
const wg_dump = popen("awg show all dump 2>/dev/null");
if (wg_dump) {
uci.load("network");
for (let line = wg_dump.read('line'); length(line); line = wg_dump.read('line')) {
const record = split(rtrim(line, '\n'), '\t');
if (last_device != record[0]) {
last_device = record[0];
data[last_device] = {
name: last_device,
public_key: record[2],
listen_port: record[3],
fwmark: record[4],
peers: []
};
if (!length(record[2]) || record[2] == '(none)')
qr_pubkey[last_device] = '';
else
qr_pubkey[last_device] = `PublicKey = ${record[2]}`;
}
else {
let peer_name;
let peer_name_legacy;
uci.foreach('network', `amneziawg_${last_device}`, (s) => {
if (!s.disabled && s.public_key == record[1] && (!s.endpoint_host || checkPeerHost(s.endpoint_host, s.endpoint_port, record[3])))
peer_name = s.description;
if (s.public_key == record[1])
peer_name_legacy = s.description;
});
if (!peer_name) peer_name = peer_name_legacy;
const peer = {
name: peer_name,
public_key: record[1],
endpoint: record[3],
allowed_ips: [],
latest_handshake: record[5],
transfer_rx: record[6],
transfer_tx: record[7],
persistent_keepalive: record[8]
};
if (record[3] != '(none)' && length(record[4]))
push(peer.allowed_ips, ...split(record[4], ','));
push(data[last_device].peers, peer);
}
}
}
return data;
}
}
};
return { 'luci.amneziawg': methods };