New app: luci-app-sshtunnel for SSH tunnels (#6424)
authorSergey Ponomarev <stokito@gmail.com>
Mon, 4 Dec 2023 18:27:53 +0000 (20:27 +0200)
committerGitHub <noreply@github.com>
Mon, 4 Dec 2023 18:27:53 +0000 (19:27 +0100)
* luci-app-sshtunnel: SSH tunnels

The app helps to configure SSH tunnels.
You can also generate an SSH key and see known hosts.

Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
applications/luci-app-sshtunnel/Makefile [new file with mode: 0644]
applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_hosts.js [new file with mode: 0644]
applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js [new file with mode: 0644]
applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js [new file with mode: 0644]
applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js [new file with mode: 0644]
applications/luci-app-sshtunnel/po/ru/sshtunnel.po [new file with mode: 0644]
applications/luci-app-sshtunnel/po/templates/sshtunnel.pot [new file with mode: 0644]
applications/luci-app-sshtunnel/root/usr/share/luci/menu.d/luci-app-sshtunnel.json [new file with mode: 0644]
applications/luci-app-sshtunnel/root/usr/share/rpcd/acl.d/luci-app-sshtunnel.json [new file with mode: 0644]

diff --git a/applications/luci-app-sshtunnel/Makefile b/applications/luci-app-sshtunnel/Makefile
new file mode 100644 (file)
index 0000000..4298b61
--- /dev/null
@@ -0,0 +1,15 @@
+# See /LICENSE for more information.
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=LuCI support for SSH Tunnels (sshtunnel package)
+
+PKG_MAINTAINER:=Sergey Ponomarev <stokito@gmail.com>
+LUCI_DEPENDS:=+luci-base +sshtunnel
+PKG_VERSION:=1.0.0
+PKG_RELEASE:=1
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_hosts.js b/applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_hosts.js
new file mode 100644 (file)
index 0000000..b82eae2
--- /dev/null
@@ -0,0 +1,64 @@
+'use strict';
+'require form';
+'require fs';
+'require ui';
+'require view';
+
+
+return view.extend({
+       load: function () {
+               return Promise.all([
+                       fs.lines('/root/.ssh/known_hosts'),
+               ]);
+       },
+
+       render: function (data) {
+               var knownHosts = data[0];
+
+               var m, s, o;
+
+               m = new form.Map('sshtunnel', _('SSH Tunnels'),
+                       _('This configures <a %s>SSH Tunnels</a>')
+                               .format('href="https://openwrt.org/docs/guide-user/services/ssh/sshtunnel"')
+               );
+
+               s = m.section(form.GridSection, '_known_hosts');
+               s.render = L.bind(_renderKnownHosts, this, knownHosts);
+
+               return m.render();
+       },
+});
+
+function _renderKnownHosts(knownHosts) {
+       var table = E('table', {'class': 'table cbi-section-table', 'id': 'known_hosts'}, [
+               E('tr', {'class': 'tr table-titles'}, [
+                       E('th', {'class': 'th'}, _('Hostname')),
+                       E('th', {'class': 'th'}, _('Public Key')),
+               ])
+       ]);
+
+       var rows = _splitKnownHosts(knownHosts);
+       cbi_update_table(table, rows);
+
+       return E('div', {'class': 'cbi-section cbi-tblsection'}, [
+               E('h3', _('Known hosts ')),
+               E('div', {'class': 'cbi-section-descr'},
+                       _('Keys of SSH servers found in %s.').format('<code>/root/.ssh/known_hosts</code>')
+               ),
+               table
+       ]);
+}
+
+function _splitKnownHosts(knownHosts) {
+       var knownHostsMap = [];
+       for (var i = 0; i < knownHosts.length; i++) {
+               var sp = knownHosts[i].indexOf(' ');
+               if (sp < 0) {
+                       continue;
+               }
+               var hostname = knownHosts[i].substring(0, sp);
+               var pub = knownHosts[i].substring(sp + 1);
+               knownHostsMap.push([hostname, '<small><code>' + pub + '</code></small>']);
+       }
+       return knownHostsMap;
+}
diff --git a/applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js b/applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js
new file mode 100644 (file)
index 0000000..6c83454
--- /dev/null
@@ -0,0 +1,131 @@
+'use strict';
+'require form';
+'require fs';
+'require ui';
+'require view';
+
+var allSshKeys = {};
+var hasSshKeygen = false;
+
+return view.extend({
+       load: function () {
+               return L.resolveDefault(fs.list('/root/.ssh/'), []).then(function (entries) {
+                       var tasks = [
+                               L.resolveDefault(fs.stat('/usr/bin/ssh-keygen'), {}),
+                       ];
+                       // read pub keys
+                       for (var i = 0; i < entries.length; i++) {
+                               if (entries[i].type === 'file' && entries[i].name.match(/\.pub$/)) {
+                                       tasks.push(Promise.resolve(entries[i].name));
+                                       tasks.push(fs.lines('/root/.ssh/' + entries[i].name));
+                               }
+                       }
+                       return Promise.all(tasks);
+               });
+       },
+
+       render: function (data) {
+               hasSshKeygen = data[0].type === 'file';
+               var sshKeys = _splitSshKeys(data.splice(1));
+
+               var m, s, o;
+
+               m = new form.Map('sshtunnel', _('SSH Tunnels'),
+                       _('This configures <a %s>SSH Tunnels</a>')
+                               .format('href="https://openwrt.org/docs/guide-user/services/ssh/sshtunnel"')
+               );
+
+               s = m.section(form.GridSection, '_keys');
+               s.render = L.bind(_renderSshKeys, this, sshKeys);
+
+               return m.render();
+       },
+});
+
+function _splitSshKeys(sshFiles) {
+       var sshKeys = {};
+       for (var i = 0; i < sshFiles.length; i++) {
+               var sshPubKeyName = sshFiles[i];
+               var sshKeyName = sshPubKeyName.substring(0, sshPubKeyName.length - 4);
+               i++;
+               var sshPub = sshFiles[i];
+               sshKeys[sshKeyName] = '<small><code>' + sshPub + '</code></small>';
+       }
+       allSshKeys = sshKeys;
+       return sshKeys;
+}
+
+function _renderSshKeys(sshKeys) {
+       var table = E('table', {'class': 'table cbi-section-table', 'id': 'keys_table'}, [
+               E('tr', {'class': 'tr table-titles'}, [
+                       E('th', {'class': 'th'}, _('Name')),
+                       E('th', {'class': 'th'}, _('Public Key')),
+               ])
+       ]);
+
+       var rows = Object.entries(sshKeys);
+       cbi_update_table(table, rows, null);
+
+       var keyGenBtn = E('div', {}, [
+               E('form', {
+                       'submit': _handleKeyGenSubmit,
+               }, [
+                       E('label', {}, _('Generate a new key') + ': '),
+                       E('span', {'class': 'control-group'}, [
+                               E('input', {
+                                       'type': 'text',
+                                       'name': 'keyName',
+                                       'value': 'id_ed25519',
+                                       'pattern': '^[a-zA-Z][a-zA-Z0-9_\.]+',
+                                       'required': 'required',
+                                       'maxsize': '35',
+                                       'autocomplete': 'off',
+                               }),
+                               E('button', {
+                                       'id': 'btnGenerateKey',
+                                       'type': 'submit',
+                                       'class': 'btn cbi-button cbi-button-action',
+                               }, [_('Generate')])
+                       ])
+               ])
+       ]);
+       return E('div', {'class': 'cbi-section cbi-tblsection'}, [
+               E('h3', _('SSH Keys')),
+               E('div', {'class': 'cbi-section-descr'},
+                       _('Add the pub key to %s or %s.')
+                               .format('<code>/root/.ssh/authorized_keys</code>', '<code>/etc/dropbear/authorized_keys</code>') + ' ' +
+                       _('In LuCI you can do that with <a %s>System / Administration / SSH-Keys</a>')
+                               .format('href="/cgi-bin/luci/admin/system/admin/sshkeys"')
+               ),
+               keyGenBtn, table
+       ]);
+}
+
+function _handleKeyGenSubmit(event) {
+       event.preventDefault();
+       var keyName = document.querySelector('input[name="keyName"]').value;
+       if (allSshKeys[keyName]) {
+               document.body.scrollTop = document.documentElement.scrollTop = 0;
+               ui.addNotification(null, E('p', _('A key with that name already exists.'), 'error'));
+               return false;
+       }
+
+       let command = '/usr/bin/ssh-keygen';
+       let commandArgs = ['-t', 'ed25519', '-q', '-N', '', '-f', '/root/.ssh/' + keyName];
+       if (!hasSshKeygen) {
+               command = '/usr/bin/dropbearkey';
+               commandArgs = ['-t', 'ed25519', '-f', '/root/.ssh/' + keyName];
+       }
+       fs.exec(command, commandArgs).then(function (res) {
+               if (res.code === 0) {
+                       // refresh the page to see the new key
+                       location.reload();
+               } else {
+                       throw new Error(res.stdout + ' ' + res.stderr);
+               }
+       }).catch(function (e) {
+               document.body.scrollTop = document.documentElement.scrollTop = 0;
+               ui.addNotification(null, E('p', _('Unable to generate a key: %s').format(e.message)), 'error');
+       });
+       return false;
+}
diff --git a/applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js b/applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js
new file mode 100644 (file)
index 0000000..5aa33e6
--- /dev/null
@@ -0,0 +1,147 @@
+'use strict';
+'require form';
+'require fs';
+'require uci';
+'require ui';
+'require view';
+
+var allSshKeys = {};
+
+return view.extend({
+       load: function () {
+               return L.resolveDefault(fs.list('/root/.ssh/'), []).then(function (entries) {
+                       var tasks = [];
+                       for (var i = 0; i < entries.length; i++) {
+                               if (entries[i].type === 'file' && entries[i].name.match(/\.pub$/)) {
+                                       tasks.push(Promise.resolve(entries[i].name));
+                               }
+                       }
+                       return Promise.all(tasks);
+               });
+       },
+
+       render: function (data) {
+               var sshKeys = _splitSshKeys(data);
+               if (sshKeys.length === 0) {
+                       ui.addNotification(null, E('p', _('No SSH keys found, <a %s>generate a new one</a>').format('href="./ssh_keys"')), 'warning');
+               }
+
+               var m, s, o;
+
+               m = new form.Map('sshtunnel', _('SSH Tunnels'),
+                       _('This configures <a %s>SSH Tunnels</a>')
+                               .format('href="https://openwrt.org/docs/guide-user/services/ssh/sshtunnel"')
+               );
+
+               s = m.section(form.GridSection, 'server', _('Servers'));
+               s.anonymous = false;
+               s.addremove = true;
+               s.nodescriptions = true;
+
+               o = s.tab('general', _('General Settings'));
+               o = s.tab('advanced', _('Advanced Settings'));
+
+               o = s.taboption('general', form.Value, 'hostname', _('Hostname'));
+               o.placeholder = 'example.com';
+               o.datatype = 'host';
+               o.rmempty = false;
+
+               o = s.taboption('general', form.Value, 'port', _('Port'));
+               o.placeholder = '22';
+               o.datatype = 'port';
+
+               o = s.taboption('general', form.Value, 'user', _('User'));
+               o.default = 'root';
+
+               o = s.taboption('general', form.ListValue, 'IdentityFile', _('Key file'),
+                       _('Private key file with authentication identity. ' +
+                               'See <em>ssh_config IdentityFile</em>')
+               );
+               o.value('');
+               Object.keys(sshKeys).forEach(function (keyName) {
+                       o.value('/root/.ssh/' + keyName, keyName);
+               });
+               o.optional = true;
+
+
+               o = s.taboption('advanced', form.ListValue, 'LogLevel', _('Log level'), 'See <em>ssh_config LogLevel</em>');
+               o.value('QUIET', 'QUIET');
+               o.value('FATAL', 'FATAL');
+               o.value('ERROR', 'ERROR');
+               o.value('INFO', 'INFO');
+               o.value('VERBOSE', 'VERBOSE');
+               o.value('DEBUG', 'DEBUG');
+               o.value('DEBUG2', 'DEBUG2');
+               o.value('DEBUG3', 'DEBUG3');
+               o.default = 'INFO';
+               o.modalonly = true;
+
+               o = s.taboption('advanced', form.ListValue, 'Compression', _('Use compression'),
+                       _('Compression may be useful on slow connections. ' +
+                               'See <em>ssh_config Compression</em>')
+               );
+               o.value('yes', _('Yes'));
+               o.value('no', _('No'));
+               o.default = 'no';
+               o.modalonly = true;
+
+               o = s.taboption('advanced', form.Value, 'retrydelay', _('Retry delay'),
+                       _('Delay after a connection failure before trying to reconnect.')
+               );
+               o.placeholder = '10';
+               o.default = '10';
+               o.datatype = 'uinteger';
+               o.optional = true;
+               o.modalonly = true;
+
+               o = s.taboption('advanced', form.Value, 'ServerAliveCountMax', _('Server alive count max'),
+                       _('The number of server alive messages which may be sent before SSH disconnects from the server. ' +
+                               'See <em>ssh_config ServerAliveCountMax</em>')
+               );
+               o.placeholder = '3';
+               o.datatype = 'uinteger';
+               o.optional = true;
+               o.modalonly = true;
+
+               o = s.taboption('advanced', form.Value, 'ServerAliveInterval', _('Server alive interval'),
+                       _('Keep-alive interval (seconds). ' +
+                               'See <em>ssh_config ServerAliveInterval</em>')
+               );
+               o.optional = true;
+               o.default = '60';
+               o.datatype = 'uinteger';
+               o.modalonly = true;
+
+               o = s.taboption('advanced', form.ListValue, 'CheckHostIP', _('Check host IP'),
+                       _('Check the host IP address in the <code>known_hosts</code> file. ' +
+                               'This allows ssh to detect whether a host key changed due to DNS spoofing. ' +
+                               'See <em>ssh_config CheckHostIP</em>')
+               );
+               o.value('yes', _('Yes'));
+               o.value('no', _('No'));
+               o.default = 'no';
+               o.modalonly = true;
+
+               o = s.taboption('advanced', form.ListValue, 'StrictHostKeyChecking', _('Strict host key checking'),
+                       _('Refuse to connect to hosts whose host key has changed. ' +
+                               'See <em>ssh_config StrictHostKeyChecking</em>'));
+               o.value('accept-new', _('Accept new and check if not changed'));
+               o.value('yes', _('Yes'));
+               o.value('no', _('No'));
+               o.default = 'accept-new';
+               o.modalonly = true;
+
+               return m.render();
+       },
+});
+
+function _splitSshKeys(sshFiles) {
+       var sshKeys = {};
+       for (var i = 0; i < sshFiles.length; i++) {
+               var sshPubKeyName = sshFiles[i];
+               var sshKeyName = sshPubKeyName.substring(0, sshPubKeyName.length - 4);
+               sshKeys[sshKeyName] = '';
+       }
+       allSshKeys = sshKeys;
+       return sshKeys;
+}
diff --git a/applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js b/applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js
new file mode 100644 (file)
index 0000000..34fceab
--- /dev/null
@@ -0,0 +1,164 @@
+'use strict';
+'require form';
+'require fs';
+'require uci';
+'require ui';
+'require view';
+
+return view.extend({
+       load: function () {
+               return Promise.all([
+                       uci.load('sshtunnel'),
+               ]);
+       },
+
+       render: function (data) {
+               var m, s, o;
+
+               m = new form.Map('sshtunnel', _('SSH Tunnels'),
+                       _('This configures <a %s>SSH Tunnels</a>')
+                               .format('href="https://openwrt.org/docs/guide-user/services/ssh/sshtunnel"')
+               );
+
+               s = m.section(form.GridSection, 'tunnelR', _('Remote Tunnels'),
+                       _('Forward a port on the remote host to a service on the local host.')
+               );
+               s.anonymous = true;
+               s.addremove = true;
+               s.nodescriptions = true;
+
+               o = s.option(form.Flag, 'enabled', _('Enabled'));
+               o.default = '1';
+
+               o = _addServerOption(s);
+
+               o = s.option(form.Value, 'remoteaddress', _('Remote address'),
+                       _('Bind IP address e.g. <code>192.168.1.1</code> or hostname e.g. <code>localhost</code>.') + '<br/>' +
+                       _('<code>*</code> means to listen all interfaces <b>including public</b>.')
+               );
+               o.datatype = 'or(host, "*")';
+               o.default = '*';
+               o.rmempty = false;
+
+               o = s.option(form.Value, 'remoteport', _('Remote port'));
+               o.placeholder = '80';
+               o.datatype = 'port';
+               o.rmempty = false;
+
+               o = s.option(form.Value, 'localaddress', _('Local address'),
+                       _('Bind IP address e.g. <code>192.168.1.1</code> or hostname e.g. <code>localhost</code>.')
+               );
+               o.datatype = 'host';
+               o.default = 'localhost';
+               o.rmempty = false;
+
+               o = s.option(form.Value, 'localport', _('Local port'));
+               o.datatype = 'port';
+               o.placeholder = '80';
+               o.rmempty = false;
+
+
+               s = m.section(form.GridSection, 'tunnelL', _('Local Tunnels'),
+                       _('Forward a port on the local host to a service on the remote host.')
+               );
+               s.anonymous = true;
+               s.addremove = true;
+               s.nodescriptions = true;
+
+               o = s.option(form.Flag, 'enabled', _('Enabled'));
+               o.default = '1';
+
+               o = _addServerOption(s);
+
+               o = s.option(form.Value, 'localaddress', _('Local address'),
+                       _('Bind IP address e.g. <code>192.168.1.1</code> or hostname e.g. <code>localhost</code>.') + '<br/>' +
+                       _('<code>*</code> means to listen all interfaces <b>including public</b>.')
+               );
+               o.datatype = 'or(host, "*")';
+               o.placeholder = '192.168.1.1'; // not the default * public iface because a user must explicitly configure it
+               o.rmempty = false;
+
+               o = s.option(form.Value, 'localport', _('Local port'));
+               o.datatype = 'port';
+               o.placeholder = '80';
+               o.rmempty = false;
+
+               o = s.option(form.Value, 'remoteaddress', _('Remote address'),
+                       _('Bind IP address e.g. <code>192.168.1.1</code> or hostname e.g. <code>localhost</code>.')
+               );
+               o.datatype = 'host';
+               o.default = 'localhost';
+               o.rmempty = false;
+
+               o = s.option(form.Value, 'remoteport', _('Remote port'));
+               o.datatype = 'port';
+               o.default = '80';
+               o.rmempty = false;
+
+
+               s = m.section(form.GridSection, 'tunnelD', _('Dynamic Tunnels'),
+                       _('SOCKS proxy via remote host.')
+               );
+               s.anonymous = true;
+               s.addremove = true;
+               s.nodescriptions = true;
+
+               o = s.option(form.Flag, 'enabled', _('Enabled'));
+               o.default = '1';
+
+               o = _addServerOption(s);
+
+               o = s.option(form.Value, 'localaddress', _('Local address'),
+                       _('Bind IP address e.g. <code>192.168.1.1</code> or hostname e.g. <code>localhost</code>.') + '<br/>' +
+                       _('<code>*</code> means to listen all interfaces <b>including public</b>.')
+               );
+               o.datatype = 'or(host, "*")';
+               o.placeholder = '192.168.1.1'; // not the default * public iface because a user must explicitly configure it
+               o.rmempty = false;
+
+               o = s.option(form.Value, 'localport', _('Local port'));
+               o.datatype = 'port';
+               o.default = '1080';
+               o.rmempty = false;
+
+
+               s = m.section(form.GridSection, 'tunnelW', _('VPN Tunnels'),
+                       _('Configure TUN/TAP devices for VPN tunnels.')
+               );
+               s.anonymous = true;
+               s.addremove = true;
+               s.nodescriptions = true;
+
+               o = s.option(form.Flag, 'enabled', _('Enabled'));
+               o.default = '1';
+
+               o = _addServerOption(s);
+
+               o = s.option(form.ListValue, 'vpntype', _('VPN type'));
+               o.value('point-to-point', 'TUN (point-to-point)');
+               o.value('ethernet', 'TAP (ethernet)');
+               o.default = 'point-to-point';
+
+               o = s.option(form.Value, 'localdev', _('Local dev'));
+               o.default = 'any';
+               o.datatype = 'or("any", min(0))';
+               o.rmempty = false;
+
+               o = s.option(form.Value, 'remotedev', _('Remote dev'));
+               o.default = 'any';
+               o.datatype = 'or("any", min(0))';
+               o.rmempty = false;
+
+               return m.render();
+       },
+});
+
+function _addServerOption(s) {
+       var o = s.option(form.ListValue, 'server', _('Server'));
+       o.datatype = 'uciname';
+       o.rmempty = false;
+       uci.sections('sshtunnel', 'server', function (s, sectionName) {
+               o.value(sectionName, s.hostname ? '%s (%s)'.format(sectionName, s.hostname) : sectionName);
+       });
+       return o;
+}
diff --git a/applications/luci-app-sshtunnel/po/ru/sshtunnel.po b/applications/luci-app-sshtunnel/po/ru/sshtunnel.po
new file mode 100644 (file)
index 0000000..71f7cff
--- /dev/null
@@ -0,0 +1,303 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:37
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:75
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:113
+msgid "<code>*</code> means to listen all interfaces <b>including public</b>."
+msgstr ""
+"<code>*</code> значит принимать соединения на всех интерфейсах <b>включая "
+"публичные</b>"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:109
+msgid "A key with that name already exists."
+msgstr "Ключ с таким именем уже существует."
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:128
+msgid "Accept new and check if not changed"
+msgstr "Принимать новый и проверять что не изменился"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:95
+msgid "Add the pub key to %s or %s."
+msgstr "Добавьте этот публичный ключ в %s или в %s."
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:42
+msgid "Advanced Settings"
+msgstr "Расширенные настройки"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:36
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:49
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:74
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:87
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:112
+msgid ""
+"Bind IP address e.g. <code>192.168.1.1</code> or hostname e.g. "
+"<code>localhost</code>."
+msgstr ""
+"Принимающий соединения IP адрес н.п. <code>192.168.1.1</code> или имя хоста "
+"н.п. <code>localhost</code>"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:115
+msgid "Check host IP"
+msgstr "Проверять IP хоста"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:116
+msgid ""
+"Check the host IP address in the <code>known_hosts</code> file. This allows "
+"ssh to detect whether a host key changed due to DNS spoofing. See "
+"<em>ssh_config CheckHostIP</em>"
+msgstr ""
+"Проверять IP-адрес хоста в файле <code>known_hosts</code>. Это позволяет ssh "
+"определить, изменился ли ключ хоста из-за спуфинга DNS."
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:80
+msgid ""
+"Compression may be useful on slow connections. See <em>ssh_config "
+"Compression</em>"
+msgstr ""
+"Сжатие может быть полезным на медленном соединении. См. <em>ssh_config "
+"Compression</em>"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:126
+msgid "Configure TUN/TAP devices for VPN tunnels."
+msgstr "Конифгурация устройств TUN/TAP VPN-туннелей."
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:89
+msgid "Delay after a connection failure before trying to reconnect."
+msgstr "Задержка перед переподключением"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:99
+msgid "Dynamic Tunnels"
+msgstr "Динамические туннели"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:30
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:68
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:106
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:132
+msgid "Enabled"
+msgstr "Включен"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:62
+msgid "Forward a port on the local host to a service on the remote host."
+msgstr "Перенаправить порт с локального хоста на сервис на удалённом хосте."
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:24
+msgid "Forward a port on the remote host to a service on the local host."
+msgstr "Перенаправить порт с удалённого хоста на сервис на локальном хосте."
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:41
+msgid "General Settings"
+msgstr "Общие настройки"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:88
+msgid "Generate"
+msgstr "Сгенерировать"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:73
+msgid "Generate a new key"
+msgstr "Сгенерировать новый ключ"
+
+#: applications/luci-app-sshtunnel/root/usr/share/rpcd/acl.d/luci-app-sshtunnel.json:3
+msgid "Grant UCI access for luci-app-sshtunnel"
+msgstr "Предоставить UCI доступ для luci-app-sshtunnel"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_hosts.js:35
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:44
+msgid "Hostname"
+msgstr "Имя хоста"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:97
+msgid ""
+"In LuCI you can do that with <a %s>System / Administration / SSH-Keys</a>"
+msgstr ""
+"В LuCI вы можете это сделать через <a %s>Система / Администрирование / SSH "
+"ключи</a>"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:107
+msgid ""
+"Keep-alive interval (seconds). See <em>ssh_config ServerAliveInterval</em>"
+msgstr ""
+"Интервал проверки подключения (секунды). См. See <em>ssh_config "
+"ServerAliveInterval</em>"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:56
+msgid "Key file"
+msgstr "Файл ключа"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_hosts.js:46
+msgid "Keys of SSH servers found in %s."
+msgstr "Ключи SSH уже известных серверов из %s."
+
+#: applications/luci-app-sshtunnel/root/usr/share/luci/menu.d/luci-app-sshtunnel.json:38
+msgid "Known Hosts"
+msgstr "Знакомые хосты"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_hosts.js:44
+msgid "Known hosts"
+msgstr "Знакомые хосты"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:61
+msgid "Local Tunnels"
+msgstr "Локальные туннели"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:48
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:73
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:111
+msgid "Local address"
+msgstr "Локальный адрес"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:142
+msgid "Local dev"
+msgstr "Локальное устройство dev"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:55
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:81
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:119
+msgid "Local port"
+msgstr "Локальный порт"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:67
+msgid "Log level"
+msgstr "Уровень логирования"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:61
+msgid "Name"
+msgstr "Название"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:84
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:121
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:130
+msgid "No"
+msgstr "Нет"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:26
+msgid "No SSH keys found, <a %s>generate a new one</a>"
+msgstr ""
+"Нет ни одного ключа, пожалуйста <a href=\"./ssh_keys\">сгенерируйте новый</"
+"a>."
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:49
+msgid "Port"
+msgstr "Порт"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:57
+msgid ""
+"Private key file with authentication identity. See <em>ssh_config "
+"IdentityFile</em>"
+msgstr ""
+"Файл приватного ключа для авторизации. См. <em>ssh_config IdentityFile</em>"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_hosts.js:36
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:62
+msgid "Public Key"
+msgstr "Публичный ключ"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:126
+msgid ""
+"Refuse to connect to hosts whose host key has changed. See <em>ssh_config "
+"StrictHostKeyChecking</em>"
+msgstr ""
+"Отказывать в подключении к хосту если его ключ поменялся. См. <em>ssh_config "
+"StrictHostKeyChecking</em>"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:23
+msgid "Remote Tunnels"
+msgstr "Удалённые туннели"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:35
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:86
+msgid "Remote address"
+msgstr "Удалённый адресс"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:147
+msgid "Remote dev"
+msgstr "Удалённое устройство dev"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:43
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:93
+msgid "Remote port"
+msgstr "Удалённый порт"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:88
+msgid "Retry delay"
+msgstr "Задержка попытки"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:100
+msgid "SOCKS proxy via remote host."
+msgstr "SOCKS прокси через удалённый хост."
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:93
+#: applications/luci-app-sshtunnel/root/usr/share/luci/menu.d/luci-app-sshtunnel.json:14
+msgid "SSH Keys"
+msgstr "SSH Ключи"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_hosts.js:20
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:33
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:31
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:18
+#: applications/luci-app-sshtunnel/root/usr/share/luci/menu.d/luci-app-sshtunnel.json:3
+#: applications/luci-app-sshtunnel/root/usr/share/luci/menu.d/luci-app-sshtunnel.json:30
+msgid "SSH Tunnels"
+msgstr "SSH туннели"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:157
+msgid "Server"
+msgstr "Сервер"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:97
+msgid "Server alive count max"
+msgstr "Попыток проверки соединения"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:106
+msgid "Server alive interval"
+msgstr "Интервал проверки соединения"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:36
+#: applications/luci-app-sshtunnel/root/usr/share/luci/menu.d/luci-app-sshtunnel.json:22
+msgid "Servers"
+msgstr "Серверы"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:125
+msgid "Strict host key checking"
+msgstr "Строгая проверка ключа хоста"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:98
+msgid ""
+"The number of server alive messages which may be sent before SSH disconnects "
+"from the server. See <em>ssh_config ServerAliveCountMax</em>"
+msgstr ""
+"Сколько проверочных сообщений на сервер отправить прежде чем отключиться.См. "
+"See <em>ssh_config ServerAliveCountMax</em>"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_hosts.js:21
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:34
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:32
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:19
+msgid "This configures <a %s>SSH Tunnels</a>"
+msgstr "Настройка <a %s>SSH туннелей</a>."
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:128
+msgid "Unable to generate a key: %s"
+msgstr "Ошибка при генерации ключа: %s"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:79
+msgid "Use compression"
+msgstr "Использовать сжатие"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:53
+msgid "User"
+msgstr "Пользователь"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:125
+msgid "VPN Tunnels"
+msgstr "VPN туннели"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:137
+msgid "VPN type"
+msgstr "Тип VPN"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:83
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:120
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:129
+msgid "Yes"
+msgstr "Да"
diff --git a/applications/luci-app-sshtunnel/po/templates/sshtunnel.pot b/applications/luci-app-sshtunnel/po/templates/sshtunnel.pot
new file mode 100644 (file)
index 0000000..9da1f07
--- /dev/null
@@ -0,0 +1,284 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:37
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:75
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:113
+msgid "<code>*</code> means to listen all interfaces <b>including public</b>."
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:109
+msgid "A key with that name already exists."
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:128
+msgid "Accept new and check if not changed"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:95
+msgid "Add the pub key to %s or %s."
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:42
+msgid "Advanced Settings"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:36
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:49
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:74
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:87
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:112
+msgid ""
+"Bind IP address e.g. <code>192.168.1.1</code> or hostname e.g. "
+"<code>localhost</code>."
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:115
+msgid "Check host IP"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:116
+msgid ""
+"Check the host IP address in the <code>known_hosts</code> file. This allows "
+"ssh to detect whether a host key changed due to DNS spoofing. See "
+"<em>ssh_config CheckHostIP</em>"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:80
+msgid ""
+"Compression may be useful on slow connections. See <em>ssh_config "
+"Compression</em>"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:126
+msgid "Configure TUN/TAP devices for VPN tunnels."
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:89
+msgid "Delay after a connection failure before trying to reconnect."
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:99
+msgid "Dynamic Tunnels"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:30
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:68
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:106
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:132
+msgid "Enabled"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:62
+msgid "Forward a port on the local host to a service on the remote host."
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:24
+msgid "Forward a port on the remote host to a service on the local host."
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:41
+msgid "General Settings"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:88
+msgid "Generate"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:73
+msgid "Generate a new key"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/root/usr/share/rpcd/acl.d/luci-app-sshtunnel.json:3
+msgid "Grant UCI access for luci-app-sshtunnel"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_hosts.js:35
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:44
+msgid "Hostname"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:97
+msgid ""
+"In LuCI you can do that with <a %s>System / Administration / SSH-Keys</a>"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:107
+msgid ""
+"Keep-alive interval (seconds). See <em>ssh_config ServerAliveInterval</em>"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:56
+msgid "Key file"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_hosts.js:46
+msgid "Keys of SSH servers found in %s."
+msgstr ""
+
+#: applications/luci-app-sshtunnel/root/usr/share/luci/menu.d/luci-app-sshtunnel.json:38
+msgid "Known Hosts"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_hosts.js:44
+msgid "Known hosts"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:61
+msgid "Local Tunnels"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:48
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:73
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:111
+msgid "Local address"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:142
+msgid "Local dev"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:55
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:81
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:119
+msgid "Local port"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:67
+msgid "Log level"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:61
+msgid "Name"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:84
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:121
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:130
+msgid "No"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:26
+msgid "No SSH keys found, <a %s>generate a new one</a>"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:49
+msgid "Port"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:57
+msgid ""
+"Private key file with authentication identity. See <em>ssh_config "
+"IdentityFile</em>"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_hosts.js:36
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:62
+msgid "Public Key"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:126
+msgid ""
+"Refuse to connect to hosts whose host key has changed. See <em>ssh_config "
+"StrictHostKeyChecking</em>"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:23
+msgid "Remote Tunnels"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:35
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:86
+msgid "Remote address"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:147
+msgid "Remote dev"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:43
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:93
+msgid "Remote port"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:88
+msgid "Retry delay"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:100
+msgid "SOCKS proxy via remote host."
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:93
+#: applications/luci-app-sshtunnel/root/usr/share/luci/menu.d/luci-app-sshtunnel.json:14
+msgid "SSH Keys"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_hosts.js:20
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:33
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:31
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:18
+#: applications/luci-app-sshtunnel/root/usr/share/luci/menu.d/luci-app-sshtunnel.json:3
+#: applications/luci-app-sshtunnel/root/usr/share/luci/menu.d/luci-app-sshtunnel.json:30
+msgid "SSH Tunnels"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:157
+msgid "Server"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:97
+msgid "Server alive count max"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:106
+msgid "Server alive interval"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:36
+#: applications/luci-app-sshtunnel/root/usr/share/luci/menu.d/luci-app-sshtunnel.json:22
+msgid "Servers"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:125
+msgid "Strict host key checking"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:98
+msgid ""
+"The number of server alive messages which may be sent before SSH disconnects "
+"from the server. See <em>ssh_config ServerAliveCountMax</em>"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_hosts.js:21
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:34
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:32
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:19
+msgid "This configures <a %s>SSH Tunnels</a>"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_keys.js:128
+msgid "Unable to generate a key: %s"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:79
+msgid "Use compression"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:53
+msgid "User"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:125
+msgid "VPN Tunnels"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_tunnels.js:137
+msgid "VPN type"
+msgstr ""
+
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:83
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:120
+#: applications/luci-app-sshtunnel/htdocs/luci-static/resources/view/sshtunnel/ssh_servers.js:129
+msgid "Yes"
+msgstr ""
diff --git a/applications/luci-app-sshtunnel/root/usr/share/luci/menu.d/luci-app-sshtunnel.json b/applications/luci-app-sshtunnel/root/usr/share/luci/menu.d/luci-app-sshtunnel.json
new file mode 100644 (file)
index 0000000..b32c53a
--- /dev/null
@@ -0,0 +1,45 @@
+{
+       "admin/services/sshtunnel": {
+               "title": "SSH Tunnels",
+               "order": 50,
+               "action": {
+                       "type": "alias",
+                       "path": "admin/services/sshtunnel/ssh_tunnels"
+               },
+               "depends": {
+                       "acl": [ "luci-app-sshtunnel" ]
+               }
+       },
+       "admin/services/sshtunnel/ssh_keys": {
+               "title": "SSH Keys",
+               "order": 10,
+               "action": {
+                       "type": "view",
+                       "path": "sshtunnel/ssh_keys"
+               }
+       },
+       "admin/services/sshtunnel/ssh_servers": {
+               "title": "Servers",
+               "order": 20,
+               "action": {
+                       "type": "view",
+                       "path": "sshtunnel/ssh_servers"
+               }
+       },
+       "admin/services/sshtunnel/ssh_tunnels": {
+               "title": "SSH Tunnels",
+               "order": 30,
+               "action": {
+                       "type": "view",
+                       "path": "sshtunnel/ssh_tunnels"
+               }
+       },
+       "admin/services/sshtunnel/ssh_hosts": {
+               "title": "Known Hosts",
+               "order": 40,
+               "action": {
+                       "type": "view",
+                       "path": "sshtunnel/ssh_hosts"
+               }
+       }
+}
diff --git a/applications/luci-app-sshtunnel/root/usr/share/rpcd/acl.d/luci-app-sshtunnel.json b/applications/luci-app-sshtunnel/root/usr/share/rpcd/acl.d/luci-app-sshtunnel.json
new file mode 100644 (file)
index 0000000..5dfb140
--- /dev/null
@@ -0,0 +1,20 @@
+{
+       "luci-app-sshtunnel": {
+               "description": "Grant UCI access for luci-app-sshtunnel",
+               "read": {
+                       "file": {
+                               "/root/.ssh/": [ "list" ],
+                               "/root/.ssh/*.pub": [ "read" ],
+                               "/root/.ssh/known_hosts": [ "read" ],
+                               "/usr/bin/ssh-keygen": [ "list" ],
+                               "/usr/bin/ssh-keygen *": [ "exec" ],
+                               "/usr/bin/dropbearkey": [ "list" ],
+                               "/usr/bin/dropbearkey *": [ "exec" ]
+                       },
+                       "uci": [ "sshtunnel" ]
+               },
+               "write": {
+                       "uci": [ "sshtunnel" ]
+               }
+       }
+}