feat: update luci awg

This commit is contained in:
s.shchipunov
2025-09-29 00:38:30 +07:00
parent 7740f55534
commit 9bc094ec4d
10 changed files with 511 additions and 166 deletions

View File

@@ -1,17 +0,0 @@
#
# Copyright (C) 2016 Dan Luedtke <mail@danrl.com>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=Support for AmneziaWG VPN
LUCI_DEPENDS:=+amneziawg-tools +ucode +qrencode
LUCI_PKGARCH:=all
PKG_PROVIDES:=luci-app-amneziawg
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

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

View File

@@ -7,6 +7,7 @@
'require form'; 'require form';
'require network'; 'require network';
'require validation'; 'require validation';
'require uqr';
var generateKey = rpc.declare({ var generateKey = rpc.declare({
object: 'luci.amneziawg', object: 'luci.amneziawg',
@@ -64,42 +65,16 @@ function generateDescription(name, texts) {
]); ]);
} }
function invokeQREncode(data, div) { function buildSVGQRCode(data, code) {
// pixel size larger than 4 clips right and bottom edges of complex configs
var code = div.children[0]; const options = {
var btn = div.children[1]; pixelSize: 4,
whiteColor: 'white',
dom.content(btn, [ blackColor: 'black'
E('a', { };
'class': 'btn cbi-button-action', const svg = uqr.renderSVG(data, options);
'style': 'text-align: center', code.style.opacity = '';
'href': 'data:text/plain;charset=utf-8,' + encodeURIComponent(data), dom.content(code, Object.assign(E(svg), { style: 'width:100%;height:auto' }));
'download': 'amneziawg.conf'
}, ['Download Configuration']),
]);
return fs.exec_direct('/usr/bin/qrencode', [
'--inline', '--8bit', '--type=SVG',
'--output=-', '--', data
]).then(function(svg) {
div.style.opacity = '';
dom.content(code, Object.assign(E(svg), { style: 'width:100%;height:auto' }));
}).catch(function(error) {
div.style.opacity = '';
if (L.isObject(error) && error.name == 'NotFoundError') {
dom.content(code, [
Object.assign(E(qrIcon), { style: 'width:32px;height:32px;opacity:.2' }),
E('p', _('The <em>qrencode</em> package is required for generating an QR code image of the configuration.'))
]);
}
else {
dom.content(code, [
_('Unable to generate QR code: %s').format(L.isObject(error) ? error.message : error)
]);
}
});
} }
var cbiKeyPairGenerate = form.DummyValue.extend({ var cbiKeyPairGenerate = form.DummyValue.extend({
@@ -111,9 +86,6 @@ var cbiKeyPairGenerate = form.DummyValue.extend({
pub = this.section.getUIElement(section_id, 'public_key'), pub = this.section.getUIElement(section_id, 'public_key'),
map = this.map; map = this.map;
if ((prv.getValue() || pub.getValue()) && !confirm(_('Do you want to replace the current keys?')))
return;
return generateKey().then(function(keypair) { return generateKey().then(function(keypair) {
prv.setValue(keypair.priv); prv.setValue(keypair.priv);
pub.setValue(keypair.pub); pub.setValue(keypair.pub);
@@ -137,7 +109,7 @@ return network.registerProtocol('amneziawg', {
return this._ubus('l3_device') || this.sid; return this._ubus('l3_device') || this.sid;
}, },
getOpkgPackage: function() { getPackageName: function() {
return 'amneziawg-tools'; return 'amneziawg-tools';
}, },
@@ -212,7 +184,7 @@ return network.registerProtocol('amneziawg', {
o.placeholder = '1420'; o.placeholder = '1420';
o.optional = true; o.optional = true;
o = s.taboption('advanced', form.Value, 'fwmark', _('Firewall Mark'), _('Optional. 32-bit mark for outgoing encrypted packets. Enter value in hex, starting with <code>0x</code>.')); o = s.taboption('advanced', form.Value, 'fwmark', _('Firewall Mark'), _('Optional. 32-bit mark for packets during firewall processing. Enter value in hex, starting with <code>0x</code>.'));
o.optional = true; o.optional = true;
o.validate = function(section_id, value) { o.validate = function(section_id, value) {
if (value.length > 0 && !value.match(/^0x[a-fA-F0-9]{1,8}$/)) if (value.length > 0 && !value.match(/^0x[a-fA-F0-9]{1,8}$/))
@@ -224,62 +196,102 @@ return network.registerProtocol('amneziawg', {
// AmneziaWG // AmneziaWG
try { try {
s.tab('amneziawg', _('AmneziaWG Settings'), _('Further information about AmneziaWG interfaces and peers at <a href=\'http://amnezia.org\'>amnezia.org</a>.')); s.tab('amneziawg', _('AmneziaWG Settings'), _('Further information about AmneziaWG interfaces and peers at <a href=\'https://docs.amnezia.org/documentation/amnezia-wg\'>amnezia.org</a>.'));
} }
catch(e) {} catch(e) {}
o = s.taboption('amneziawg', form.Value, 'awg_jc', _('Jc'), _('Junk packet count.')); o = s.taboption('amneziawg', form.Value, 'awg_jc', _('Jc'), _('Junk packet count.'));
o.datatype = 'uinteger'; o.datatype = 'uinteger';
o.placeholder = '0';
o.optional = true; o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_jmin', _('Jmin'), _('Junk packet minimum size.')); o = s.taboption('amneziawg', form.Value, 'awg_jmin', _('Jmin'), _('Junk packet minimum size.'));
o.datatype = 'uinteger'; o.datatype = 'uinteger';
o.placeholder = '0';
o.optional = true; o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_jmax', _('Jmax'), _('Junk packet maximum size.')); o = s.taboption('amneziawg', form.Value, 'awg_jmax', _('Jmax'), _('Junk packet maximum size.'));
o.datatype = 'uinteger'; o.datatype = 'uinteger';
o.placeholder = '0';
o.optional = true; o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_s1', _('S1'), _('Handshake initiation packet junk header size.')); o = s.taboption('amneziawg', form.Value, 'awg_s1', _('S1'), _('Handshake initiation packet junk header size.'));
o.datatype = 'uinteger'; o.datatype = 'uinteger';
o.placeholder = '0';
o.optional = true; o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_s2', _('S2'), _('Handshake response packet junk header size.')); o = s.taboption('amneziawg', form.Value, 'awg_s2', _('S2'), _('Handshake response packet junk header size.'));
o.datatype = 'uinteger'; o.datatype = 'uinteger';
o.placeholder = '0';
o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_s3', _('S3'), _('Cookie reply packet junk header size.'));
o.datatype = 'uinteger';
o.placeholder = '0';
o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_s4', _('S4'), _('Transport packet junk header size.'));
o.datatype = 'uinteger';
o.placeholder = '0';
o.optional = true; o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_h1', _('H1'), _('Handshake initiation packet type header.')); o = s.taboption('amneziawg', form.Value, 'awg_h1', _('H1'), _('Handshake initiation packet type header.'));
o.datatype = 'uinteger'; o.datatype = 'string';
o.placeholder = '1';
o.optional = true; o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_h2', _('H2'), _('Handshake response packet type header.')); o = s.taboption('amneziawg', form.Value, 'awg_h2', _('H2'), _('Handshake response packet type header.'));
o.datatype = 'uinteger'; o.datatype = 'string';
o.placeholder = '2';
o.optional = true; o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_h3', _('H3'), _('Handshake cookie packet type header.')); o = s.taboption('amneziawg', form.Value, 'awg_h3', _('H3'), _('Handshake cookie packet type header.'));
o.datatype = 'uinteger'; o.datatype = 'string';
o.placeholder = '3';
o.optional = true; o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_h4', _('H4'), _('Transport packet type header.')); o = s.taboption('amneziawg', form.Value, 'awg_h4', _('H4'), _('Transport packet type header.'));
o.datatype = 'uinteger'; o.datatype = 'string';
o.placeholder = '4';
o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_i1', _('I1'), _('First special junk packet signature.'));
o.datatype = 'string';
o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_i2', _('I2'), _('Second special junk packet signature.'));
o.datatype = 'string';
o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_i3', _('I3'), _('Third special junk packet signature.'));
o.datatype = 'string';
o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_i4', _('I4'), _('Fourth special junk packet signature.'));
o.datatype = 'string';
o.optional = true;
o = s.taboption('amneziawg', form.Value, 'awg_i5', _('I5'), _('Fifth special junk packet signature.'));
o.datatype = 'string';
o.optional = true; o.optional = true;
// -- peers ----------------------------------------------------------------------- // -- peers -----------------------------------------------------------------------
try { try {
s.tab('peers', _('Peers'), _('Further information about AmneziaWG interfaces and peers at <a href=\'http://amneziawg.com\'>amneziawg.com</a>.')); s.tab('peers', _('Peers'), _('Further information about AmneziaWG interfaces and peers at <a href=\'https://docs.amnezia.org/documentation/amnezia-wg\'>amnezia.org</a>.'));
} }
catch(e) {} catch(e) {}
o = s.taboption('peers', form.SectionValue, '_peers', form.GridSection, 'amneziawg_%s'.format(s.section)); o = s.taboption('peers', form.SectionValue, '_peers', form.GridSection, 'amneziawg_%s'.format(s.section));
o.depends('proto', 'amneziawg'); o.depends('proto', 'amneziawg');
ss = o.subsection; ss = o.subsection;
ss.anonymous = true; ss.anonymous = true;
ss.addremove = true; ss.addremove = true;
ss.addbtntitle = _('Add peer'); ss.addbtntitle = _('Add peer');
ss.nodescriptions = true; ss.nodescriptions = true;
ss.modaltitle = _('Edit peer'); ss.modaltitle = _('Edit peer');
ss.sortable = true;
ss.handleDragConfig = function(ev) { ss.handleDragConfig = function(ev) {
ev.stopPropagation(); ev.stopPropagation();
@@ -416,15 +428,22 @@ return network.registerProtocol('amneziawg', {
s.getOption('public_key').getUIElement(s.section).setValue(keypair.pub); s.getOption('public_key').getUIElement(s.section).setValue(keypair.pub);
s.getOption('listen_port').getUIElement(s.section).setValue(config.interface_listenport || ''); s.getOption('listen_port').getUIElement(s.section).setValue(config.interface_listenport || '');
s.getOption('addresses').getUIElement(s.section).setValue(config.interface_address); s.getOption('addresses').getUIElement(s.section).setValue(config.interface_address);
s.getOption('awg_jc').getUIElement(s.section).setValue(config.interface_jc); s.getOption('awg_jc').getUIElement(s.section).setValue(config.interface_jc || '');
s.getOption('awg_jmin').getUIElement(s.section).setValue(config.interface_jmin); s.getOption('awg_jmin').getUIElement(s.section).setValue(config.interface_jmin || '');
s.getOption('awg_jmax').getUIElement(s.section).setValue(config.interface_jmax); s.getOption('awg_jmax').getUIElement(s.section).setValue(config.interface_jmax || '');
s.getOption('awg_s1').getUIElement(s.section).setValue(config.interface_s1); s.getOption('awg_s1').getUIElement(s.section).setValue(config.interface_s1 || '');
s.getOption('awg_s2').getUIElement(s.section).setValue(config.interface_s2); s.getOption('awg_s2').getUIElement(s.section).setValue(config.interface_s2 || '');
s.getOption('awg_h1').getUIElement(s.section).setValue(config.interface_h1); s.getOption('awg_s3').getUIElement(s.section).setValue(config.interface_s3 || '');
s.getOption('awg_h2').getUIElement(s.section).setValue(config.interface_h2); s.getOption('awg_s4').getUIElement(s.section).setValue(config.interface_s4 || '');
s.getOption('awg_h3').getUIElement(s.section).setValue(config.interface_h3); s.getOption('awg_h1').getUIElement(s.section).setValue(config.interface_h1 || '');
s.getOption('awg_h4').getUIElement(s.section).setValue(config.interface_h4); s.getOption('awg_h2').getUIElement(s.section).setValue(config.interface_h2 || '');
s.getOption('awg_h3').getUIElement(s.section).setValue(config.interface_h3 || '');
s.getOption('awg_h4').getUIElement(s.section).setValue(config.interface_h4 || '');
s.getOption('awg_i1').getUIElement(s.section).setValue(config.interface_i1 || '');
s.getOption('awg_i2').getUIElement(s.section).setValue(config.interface_i2 || '');
s.getOption('awg_i3').getUIElement(s.section).setValue(config.interface_i3 || '');
s.getOption('awg_i4').getUIElement(s.section).setValue(config.interface_i4 || '');
s.getOption('awg_i5').getUIElement(s.section).setValue(config.interface_i5 || '');
if (config.interface_dns) if (config.interface_dns)
s.getOption('dns').getUIElement(s.section).setValue(config.interface_dns); s.getOption('dns').getUIElement(s.section).setValue(config.interface_dns);
@@ -500,7 +519,7 @@ return network.registerProtocol('amneziawg', {
E('p', _('Drag or paste a valid <em>*.conf</em> file below to configure the local AmneziaWG interface.')) E('p', _('Drag or paste a valid <em>*.conf</em> file below to configure the local AmneziaWG interface.'))
] : [ ] : [
E('p', _('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.')), E('p', _('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.')),
E('p', _('To fully configure 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.')) E('p', _('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.'))
]), ]),
E('p', [ E('p', [
E('textarea', { E('textarea', {
@@ -575,9 +594,10 @@ return network.registerProtocol('amneziawg', {
return E('em', _('No peers defined yet.')); return E('em', _('No peers defined yet.'));
}; };
o = ss.option(form.Flag, 'disabled', _('Peer disabled'), _('Enable / Disable peer. Restart amneziawg interface to apply changes.')); o = ss.option(form.Flag, 'disabled', _('Disabled'), _('Enable / Disable peer. Restart amneziawg interface to apply changes.'));
o.modalonly = true; o.editable = true;
o.optional = true; o.optional = true;
o.width = '5%';
o = ss.option(form.Value, 'description', _('Description'), _('Optional. Description of peer.')); o = ss.option(form.Value, 'description', _('Description'), _('Optional. Description of peer.'));
o.placeholder = 'My Peer'; o.placeholder = 'My Peer';
@@ -675,9 +695,6 @@ return network.registerProtocol('amneziawg', {
var psk = this.section.getUIElement(section_id, 'preshared_key'), var psk = this.section.getUIElement(section_id, 'preshared_key'),
map = this.map; map = this.map;
if (psk.getValue() && !confirm(_('Do you want to replace the current PSK?')))
return;
return generatePsk().then(function(key) { return generatePsk().then(function(key) {
psk.setValue(key); psk.setValue(key);
map.save(null, true); map.save(null, true);
@@ -757,69 +774,78 @@ return network.registerProtocol('amneziawg', {
o.modalonly = true; o.modalonly = true;
o.createPeerConfig = function (section_id, endpoint, ips, eips, dns) { o.createPeerConfig = function(section_id, endpoint, ips, eips, dns) {
var pub = s.formvalue(s.section, 'public_key'), var pub = s.formvalue(s.section, 'public_key'),
port = s.formvalue(s.section, 'listen_port') || '51820', port = s.formvalue(s.section, 'listen_port') || '51820',
prv = this.section.formvalue(section_id, 'private_key'), jc = s.formvalue(s.section, 'awg_jc'),
psk = this.section.formvalue(section_id, 'preshared_key'), jmin = s.formvalue(s.section, 'awg_jmin'),
eport = this.section.formvalue(section_id, 'endpoint_port'), jmax = s.formvalue(s.section, 'awg_jmax'),
keep = this.section.formvalue(section_id, 'persistent_keepalive'), s1 = s.formvalue(s.section, 'awg_s1'),
jc = s.formvalue(s.section, 'awg_jc'), s2 = s.formvalue(s.section, 'awg_s2'),
jmin = s.formvalue(s.section, 'awg_jmin'), s3 = s.formvalue(s.section, 'awg_s3'),
jmax = s.formvalue(s.section, 'awg_jmax'), s4 = s.formvalue(s.section, 'awg_s4'),
s1 = s.formvalue(s.section, 'awg_s1'), h1 = s.formvalue(s.section, 'awg_h1'),
s2 = s.formvalue(s.section, 'awg_s2'), h2 = s.formvalue(s.section, 'awg_h2'),
h1 = s.formvalue(s.section, 'awg_h1'), h3 = s.formvalue(s.section, 'awg_h3'),
h2 = s.formvalue(s.section, 'awg_h2'), h4 = s.formvalue(s.section, 'awg_h4'),
h3 = s.formvalue(s.section, 'awg_h3'), i1 = s.formvalue(s.section, 'awg_i1'),
h4 = s.formvalue(s.section, 'awg_h4'); i2 = s.formvalue(s.section, 'awg_i2'),
i3 = s.formvalue(s.section, 'awg_i3'),
i4 = s.formvalue(s.section, 'awg_i4'),
i5 = s.formvalue(s.section, 'awg_i5'),
prv = this.section.formvalue(section_id, 'private_key'),
psk = this.section.formvalue(section_id, 'preshared_key'),
eport = this.section.formvalue(section_id, 'endpoint_port'),
keep = this.section.formvalue(section_id, 'persistent_keepalive');
// If endpoint is IPv6 we must escape it with []
if (endpoint.indexOf(':') > 0) { if (endpoint.indexOf(':') > 0) {
endpoint = '[' + endpoint + ']'; endpoint = '['+endpoint+']';
} }
var configLines = [
return [
'[Interface]', '[Interface]',
'PrivateKey = ' + prv, 'PrivateKey = ' + prv,
eport ? 'ListenPort = ' + eport : '# ListenPort not defined',
eips && eips.length ? 'Address = ' + eips.join(', ') : '# Address not defined', eips && eips.length ? 'Address = ' + eips.join(', ') : '# Address not defined',
eport ? 'ListenPort = ' + eport : '# ListenPort not defined',
dns && dns.length ? 'DNS = ' + dns.join(', ') : '# DNS not defined', dns && dns.length ? 'DNS = ' + dns.join(', ') : '# DNS not defined',
'' jc ? 'Jc = ' + jc : '# Jc not defined',
]; jmin ? 'Jmin = ' + jmin : '# Jmin not defined',
jmax ? 'Jmax = ' + jmax : '# Jmax not defined',
if (jc) configLines.push('jc = ' + jc); s1 ? 'S1 = ' + s1 : '# S1 not defined',
if (jmin) configLines.push('jmin = ' + jmin); s2 ? 'S2 = ' + s2 : '# S2 not defined',
if (jmax) configLines.push('jmax = ' + jmax); s3 ? 'S3 = ' + s3 : '# S3 not defined',
if (s1) configLines.push('s1 = ' + s1); s4 ? 'S4 = ' + s4 : '# S4 not defined',
if (s2) configLines.push('s2 = ' + s2); h1 ? 'H1 = ' + h1 : '# H1 not defined',
if (h1) configLines.push('h1 = ' + h1); h2 ? 'H2 = ' + h2 : '# H2 not defined',
if (h2) configLines.push('h2 = ' + h2); h3 ? 'H3 = ' + h3 : '# H3 not defined',
if (h3) configLines.push('h3 = ' + h3); h4 ? 'H4 = ' + h4 : '# H4 not defined',
if (h4) configLines.push('h4 = ' + h4); i1 ? 'I1 = ' + i1 : '# I1 not defined',
i2 ? 'I2 = ' + i2 : '# I2 not defined',
configLines.push( i3 ? 'I3 = ' + i3 : '# I3 not defined',
i4 ? 'I4 = ' + i4 : '# I4 not defined',
i5 ? 'I5 = ' + i5 : '# I5 not defined',
'', '',
'[Peer]', '[Peer]',
'PublicKey = ' + pub, 'PublicKey = ' + pub,
psk ? 'PresharedKey = ' + psk : '# PresharedKey not used', psk ? 'PresharedKey = ' + psk : '# PresharedKey not used',
ips && ips.length ? 'AllowedIPs = ' + ips.join(', ') : '# AllowedIPs not defined', ips && ips.length ? 'AllowedIPs = ' + ips.join(', ') : '# AllowedIPs not defined',
endpoint ? 'Endpoint = ' + endpoint + ':' + port : '# Endpoint not defined', endpoint ? 'Endpoint = ' + endpoint + ':' + port : '# Endpoint not defined',
keep ? 'PersistentKeepAlive = ' + keep : '# PersistentKeepAlive not defined', keep ? 'PersistentKeepAlive = ' + keep : '# PersistentKeepAlive not defined'
'' ].join('\n');
); };
return configLines.join('\n');
};
o.handleGenerateQR = function(section_id, ev) { o.handleGenerateQR = function(section_id, ev) {
var mapNode = ss.getActiveModalMap(), var mapNode = ss.getActiveModalMap(),
headNode = mapNode.parentNode.querySelector('h4'), headNode = mapNode.parentNode.querySelector('h4'),
configGenerator = this.createPeerConfig.bind(this, section_id), configGenerator = this.createPeerConfig.bind(this, section_id),
parent = this.map, parent = this.map,
eips = this.section.formvalue(section_id, 'allowed_ips'); eips = this.section.formvalue(section_id, 'allowed_ips');
return Promise.all([ return Promise.all([
network.getWANNetworks(), network.getWANNetworks(),
network.getWAN6Networks(), network.getWAN6Networks(),
network.getNetwork('lan'),
L.resolveDefault(uci.load('ddns')), L.resolveDefault(uci.load('ddns')),
L.resolveDefault(uci.load('system')), L.resolveDefault(uci.load('system')),
parent.save(null, true) parent.save(null, true)
@@ -827,12 +853,12 @@ return network.registerProtocol('amneziawg', {
var hostnames = []; var hostnames = [];
uci.sections('ddns', 'service', function(s) { uci.sections('ddns', 'service', function(s) {
if (typeof(s.lookup_host) == 'string' && s.enabled == '1') if (typeof(s?.lookup_host) == 'string' && s?.enabled == '1')
hostnames.push(s.lookup_host); hostnames.push(s.lookup_host);
}); });
uci.sections('system', 'system', function(s) { uci.sections('system', 'system', function(s) {
if (typeof(s.hostname) == 'string' && s.hostname.indexOf('.') > 0) if (typeof(s?.hostname) == 'string' && s?.hostname?.indexOf('.') > 0)
hostnames.push(s.hostname); hostnames.push(s.hostname);
}); });
@@ -846,34 +872,34 @@ return network.registerProtocol('amneziawg', {
var dns = []; var dns = [];
var lan = data[2];
if (lan) {
var lanIp = lan.getIPAddr();
if (lanIp) {
dns.unshift(lanIp)
}
}
var qrm, qrs, qro; var qrm, qrs, qro;
qrm = new form.JSONMap({ qrm = new form.JSONMap({ config: { endpoint: hostnames[0], allowed_ips: ips, addresses: eips, dns_servers: dns } }, null, _('The generated configuration can be imported into a WireGuard client application to set up a connection towards this device.'));
config: {
endpoint: hostnames[0],
allowed_ips: ips,
addresses: eips,
dns_servers: dns
}
}, null, _('The generated configuration can be imported into a AmneziaWG client application to set up a connection towards this device.'));
qrm.parent = parent; qrm.parent = parent;
qrs = qrm.section(form.NamedSection, 'config'); qrs = qrm.section(form.NamedSection, 'config');
function handleConfigChange(ev, section_id, value) { function handleConfigChange(ev, section_id, value) {
var code = this.map.findElement('.qr-code'), var code = this.map.findElement('.qr-code'),
conf = this.map.findElement('.client-config'), conf = this.map.findElement('.client-config'),
endpoint = this.section.getUIElement(section_id, 'endpoint'), endpoint = this.section.getUIElement(section_id, 'endpoint'),
ips = this.section.getUIElement(section_id, 'allowed_ips'); ips = this.section.getUIElement(section_id, 'allowed_ips');
eips = this.section.getUIElement(section_id, 'addresses'); eips = this.section.getUIElement(section_id, 'addresses');
dns = this.section.getUIElement(section_id, 'dns_servers'); dns = this.section.getUIElement(section_id, 'dns_servers');
if (this.isValid(section_id)) { if (this.isValid(section_id)) {
conf.firstChild.data = configGenerator(endpoint.getValue(), ips.getValue(), eips.getValue(), dns.getValue()); conf.firstChild.data = configGenerator(endpoint.getValue(), ips.getValue(), eips.getValue(), dns.getValue());
code.style.opacity = '.5'; code.style.opacity = '.5';
invokeQREncode(conf.firstChild.data, code); buildSVGQRCode(conf.firstChild.data, code);
} }
}; };
@@ -897,7 +923,7 @@ return network.registerProtocol('amneziawg', {
qro.datatype = 'ipaddr'; qro.datatype = 'ipaddr';
qro.default = eips; qro.default = eips;
eips.forEach(function(eip) { qro.value(eip) }); eips.forEach(function(eip) { qro.value(eip) });
qro.onchange = handleConfigChange; qro.onchange = handleConfigChange;
qro = qrs.option(form.DummyValue, 'output'); qro = qrs.option(form.DummyValue, 'output');
qro.renderWidget = function() { qro.renderWidget = function() {
@@ -908,16 +934,9 @@ return network.registerProtocol('amneziawg', {
}, [ }, [
E('div', { E('div', {
'class': 'qr-code', 'class': 'qr-code',
'style': 'display:flex; flex-direction: column; text-align: center', 'style': 'width:320px;flex:0 1 320px;text-align:center'
}, [ }, [
E('div', { E('em', { 'class': 'spinning' }, [ _('Generating QR code…') ])
'style': 'width:320px;flex:0 1 320px;text-align:center'
}, [
E('em', { 'class': 'spinning' }, [ _('Generating QR code…') ])
]),
E('div', {
}, ['Download Configuration']),
]), ]),
E('pre', { E('pre', {
'class': 'client-config', 'class': 'client-config',
@@ -934,7 +953,7 @@ return network.registerProtocol('amneziawg', {
}, [ peer_config ]) }, [ peer_config ])
]); ]);
invokeQREncode(peer_config, node.firstChild); buildSVGQRCode(peer_config, node.firstChild);
return node; return node;
}; };

View File

@@ -6,9 +6,9 @@
'require ui'; 'require ui';
var callGetWgInstances = rpc.declare({ var callgetAwgInstances = rpc.declare({
object: 'luci.amneziawg', object: 'luci.amneziawg',
method: 'getWgInstances' method: 'getAwgInstances'
}); });
function timestampToStr(timestamp) { function timestampToStr(timestamp) {
@@ -128,7 +128,7 @@ return view.extend({
'click': ui.createHandlerFn(this, handleInterfaceDetails, ifaces[instanceName]) 'click': ui.createHandlerFn(this, handleInterfaceDetails, ifaces[instanceName])
}, [ }, [
E('span', { 'class': 'ifacebadge' }, [ E('span', { 'class': 'ifacebadge' }, [
E('img', { 'src': L.resource('icons', 'tunnel.svg') }), E('img', { 'src': L.resource('icons', 'amneziawg.svg') }),
'\xa0', '\xa0',
instanceName instanceName
]), ]),
@@ -153,7 +153,7 @@ return view.extend({
render: function() { render: function() {
poll.add(L.bind(function () { poll.add(L.bind(function () {
return callGetWgInstances().then(L.bind(function(ifaces) { return callgetAwgInstances().then(L.bind(function(ifaces) {
dom.content( dom.content(
document.querySelector('#view'), document.querySelector('#view'),
this.renderIfaces(ifaces) this.renderIfaces(ifaces)

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

@@ -2,15 +2,12 @@
"luci-proto-amneziawg": { "luci-proto-amneziawg": {
"description": "Grant access to LuCI AmneziaWG procedures", "description": "Grant access to LuCI AmneziaWG procedures",
"read": { "read": {
"file": {
"/usr/bin/qrencode --inline --8bit --type=SVG --output=- -- *": [ "exec" ]
},
"ubus": { "ubus": {
"luci.amneziawg": [ "luci.amneziawg": [
"getWgInstances" "getAwgInstances"
] ]
}, },
"uci": [ "ddns", "system" ] "uci": [ "ddns", "system", "network" ]
}, },
"write": { "write": {
"ubus": { "ubus": {

View File

@@ -15,18 +15,33 @@ function command(cmd) {
return trim(popen(cmd)?.read?.('all')); 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 = { const methods = {
generatePsk: { generatePsk: {
call: function() { call: function() {
return { psk: command('amneziawg genpsk 2>/dev/null') }; return { psk: command('awg genpsk 2>/dev/null') };
} }
}, },
generateKeyPair: { generateKeyPair: {
call: function() { call: function() {
const priv = command('amneziawg genkey 2>/dev/null'); const priv = command('awg genkey 2>/dev/null');
const pub = command(`echo ${shellquote(priv)} | amneziawg pubkey 2>/dev/null`); const pub = command(`echo ${shellquote(priv)} | awg pubkey 2>/dev/null`);
return { keys: { priv, pub } }; return { keys: { priv, pub } };
} }
@@ -36,20 +51,20 @@ const methods = {
args: { privkey: "privkey" }, args: { privkey: "privkey" },
call: function(req) { call: function(req) {
const priv = req.args?.privkey; const priv = req.args?.privkey;
const pub = command(`echo ${shellquote(priv)} | amneziawg pubkey 2>/dev/null`); const pub = command(`echo ${shellquote(priv)} | awg pubkey 2>/dev/null`);
return { keys: { priv, pub } }; return { keys: { priv, pub } };
} }
}, },
getWgInstances: { getAwgInstances: {
call: function() { call: function() {
const data = {}; const data = {};
let last_device; let last_device;
let qr_pubkey = {}; let qr_pubkey = {};
const uci = cursor(); const uci = cursor();
const wg_dump = popen("amneziawg show all dump 2>/dev/null"); const wg_dump = popen("awg show all dump 2>/dev/null");
if (wg_dump) { if (wg_dump) {
uci.load("network"); uci.load("network");
@@ -74,12 +89,17 @@ const methods = {
} }
else { else {
let peer_name; let peer_name;
let peer_name_legacy;
uci.foreach('network', `amneziawg_${last_device}`, (s) => { uci.foreach('network', `amneziawg_${last_device}`, (s) => {
if (s.public_key == record[1]) if (!s.disabled && s.public_key == record[1] && (!s.endpoint_host || checkPeerHost(s.endpoint_host, s.endpoint_port, record[3])))
peer_name = s.description; 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 = { const peer = {
name: peer_name, name: peer_name,
public_key: record[1], public_key: record[1],