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:
13
luci-proto-amneziawg/Makefile
Normal file
13
luci-proto-amneziawg/Makefile
Normal 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
@@ -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
|
||||
});
|
||||
158
luci-proto-amneziawg/po/ru/amneziawg.po
Normal file
158
luci-proto-amneziawg/po/ru/amneziawg.po
Normal 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!"
|
||||
25
luci-proto-amneziawg/po/ru/status.po
Normal file
25
luci-proto-amneziawg/po/ru/status.po
Normal 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 не настроены."
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
127
luci-proto-amneziawg/root/usr/share/rpcd/ucode/luci.amneziawg
Normal file
127
luci-proto-amneziawg/root/usr/share/rpcd/ucode/luci.amneziawg
Normal 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 };
|
||||
Reference in New Issue
Block a user