feat(wireguard-dashboard): Persist peer private keys in UCI for QR code generation
Store the client private key in UCI config (_client_private_key) when a peer is created, so QR codes and config files can be generated after page refresh without prompting the user to manually re-enter the key. Old peers without stored keys still get the manual entry fallback. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2d810a2e95
commit
2ab0965917
@ -358,7 +358,7 @@ return view.extend({
|
||||
|
||||
var privkey = document.getElementById('peer-privkey').value;
|
||||
|
||||
API.addPeer(iface, name, allowed_ips, pubkey, psk, endpoint, keepalive).then(function(result) {
|
||||
API.addPeer(iface, name, allowed_ips, pubkey, psk, endpoint, keepalive, privkey).then(function(result) {
|
||||
ui.hideModal();
|
||||
if (result.success) {
|
||||
// Store private key for QR generation
|
||||
@ -460,37 +460,47 @@ return view.extend({
|
||||
generateAndShowQR: function(peer, ifaceObj, privateKey, serverEndpoint) {
|
||||
var self = this;
|
||||
|
||||
// Build WireGuard client config
|
||||
var config = '[Interface]\n' +
|
||||
'PrivateKey = ' + privateKey + '\n' +
|
||||
'Address = ' + (peer.allowed_ips || '10.0.0.2/32') + '\n' +
|
||||
'DNS = 1.1.1.1, 1.0.0.1\n\n' +
|
||||
'[Peer]\n' +
|
||||
'PublicKey = ' + (ifaceObj.public_key || '') + '\n' +
|
||||
'Endpoint = ' + serverEndpoint + ':' + (ifaceObj.listen_port || 51820) + '\n' +
|
||||
'AllowedIPs = 0.0.0.0/0, ::/0\n' +
|
||||
'PersistentKeepalive = 25';
|
||||
var buildLocalConfig = function(privKey) {
|
||||
return '[Interface]\n' +
|
||||
'PrivateKey = ' + privKey + '\n' +
|
||||
'Address = ' + (peer.allowed_ips || '10.0.0.2/32') + '\n' +
|
||||
'DNS = 1.1.1.1, 1.0.0.1\n\n' +
|
||||
'[Peer]\n' +
|
||||
'PublicKey = ' + (ifaceObj.public_key || '') + '\n' +
|
||||
'Endpoint = ' + serverEndpoint + ':' + (ifaceObj.listen_port || 51820) + '\n' +
|
||||
'AllowedIPs = 0.0.0.0/0, ::/0\n' +
|
||||
'PersistentKeepalive = 25';
|
||||
};
|
||||
|
||||
// First try backend QR generation
|
||||
API.generateQR(peer.interface, peer.public_key, privateKey, serverEndpoint).then(function(result) {
|
||||
// Try backend QR generation (it will look up stored key if privateKey is empty)
|
||||
API.generateQR(peer.interface, peer.public_key, privateKey || '', serverEndpoint).then(function(result) {
|
||||
if (result && result.qrcode && !result.error) {
|
||||
var config = result.config || buildLocalConfig(privateKey);
|
||||
self.displayQRModal(peer, result.qrcode, config, false);
|
||||
} else {
|
||||
// Fall back to JavaScript QR generation
|
||||
} else if (privateKey) {
|
||||
// Backend failed but we have a key - fall back to JavaScript QR generation
|
||||
var config = buildLocalConfig(privateKey);
|
||||
var svg = qrcode.generateSVG(config, 250);
|
||||
if (svg) {
|
||||
self.displayQRModal(peer, svg, config, true);
|
||||
} else {
|
||||
ui.addNotification(null, E('p', _('Failed to generate QR code')), 'error');
|
||||
}
|
||||
} else {
|
||||
ui.addNotification(null, E('p', result.error || _('Failed to generate QR code')), 'error');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
// Fall back to JavaScript QR generation
|
||||
var svg = qrcode.generateSVG(config, 250);
|
||||
if (svg) {
|
||||
self.displayQRModal(peer, svg, config, true);
|
||||
if (privateKey) {
|
||||
// Fall back to JavaScript QR generation
|
||||
var config = buildLocalConfig(privateKey);
|
||||
var svg = qrcode.generateSVG(config, 250);
|
||||
if (svg) {
|
||||
self.displayQRModal(peer, svg, config, true);
|
||||
} else {
|
||||
ui.addNotification(null, E('p', _('Failed to generate QR code')), 'error');
|
||||
}
|
||||
} else {
|
||||
ui.addNotification(null, E('p', _('Failed to generate QR code')), 'error');
|
||||
ui.addNotification(null, E('p', _('Error: %s').format(err.message || err)), 'error');
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -552,55 +562,91 @@ return view.extend({
|
||||
]);
|
||||
},
|
||||
|
||||
showPrivateKeyPrompt: function(peer, ifaceObj, callback) {
|
||||
var self = this;
|
||||
ui.showModal(_('Private Key Required'), [
|
||||
E('p', {}, _('To generate a QR code, the peer\'s private key is needed.')),
|
||||
E('p', { 'style': 'color: #666; font-size: 0.9em;' },
|
||||
_('The private key was not found on the server. This can happen for peers created before key persistence was enabled.')),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Private Key')),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
E('input', {
|
||||
'type': 'text',
|
||||
'id': 'manual-private-key',
|
||||
'class': 'cbi-input-text',
|
||||
'placeholder': 'Base64 private key (44 characters)',
|
||||
'style': 'font-family: monospace;'
|
||||
})
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'right', 'style': 'margin-top: 1em;' }, [
|
||||
E('button', {
|
||||
'class': 'btn',
|
||||
'click': ui.hideModal
|
||||
}, _('Cancel')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-action',
|
||||
'click': function() {
|
||||
var key = document.getElementById('manual-private-key').value.trim();
|
||||
if (!key || key.length !== 44) {
|
||||
ui.addNotification(null, E('p', _('Please enter a valid private key (44 characters)')), 'error');
|
||||
return;
|
||||
}
|
||||
self.storePrivateKey(peer.public_key, key);
|
||||
ui.hideModal();
|
||||
callback(key);
|
||||
}
|
||||
}, _('Continue'))
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
handleShowQR: function(peer, interfaces, ev) {
|
||||
var self = this;
|
||||
var privateKey = this.getStoredPrivateKey(peer.public_key);
|
||||
var ifaceObj = interfaces.find(function(i) { return i.name === peer.interface; }) || {};
|
||||
|
||||
if (!privateKey) {
|
||||
// Private key not stored - ask user to input it
|
||||
ui.showModal(_('Private Key Required'), [
|
||||
E('p', {}, _('To generate a QR code, the peer\'s private key is needed.')),
|
||||
E('p', { 'style': 'color: #666; font-size: 0.9em;' },
|
||||
_('Private keys are only stored in your browser session immediately after peer creation. If you closed or refreshed the page, you\'ll need to enter it manually.')),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Private Key')),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
E('input', {
|
||||
'type': 'text',
|
||||
'id': 'manual-private-key',
|
||||
'class': 'cbi-input-text',
|
||||
'placeholder': 'Base64 private key (44 characters)',
|
||||
'style': 'font-family: monospace;'
|
||||
})
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'right', 'style': 'margin-top: 1em;' }, [
|
||||
E('button', {
|
||||
'class': 'btn',
|
||||
'click': ui.hideModal
|
||||
}, _('Cancel')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-action',
|
||||
'click': function() {
|
||||
var key = document.getElementById('manual-private-key').value.trim();
|
||||
if (!key || key.length !== 44) {
|
||||
ui.addNotification(null, E('p', _('Please enter a valid private key (44 characters)')), 'error');
|
||||
return;
|
||||
}
|
||||
// Store for future use
|
||||
self.storePrivateKey(peer.public_key, key);
|
||||
ui.hideModal();
|
||||
self.promptForEndpointAndShowQR(peer, ifaceObj, key);
|
||||
}
|
||||
}, _('Continue'))
|
||||
])
|
||||
]);
|
||||
if (privateKey) {
|
||||
this.promptForEndpointAndShowQR(peer, ifaceObj, privateKey);
|
||||
return;
|
||||
}
|
||||
|
||||
this.promptForEndpointAndShowQR(peer, ifaceObj, privateKey);
|
||||
// Try backend with empty private key - it will look up the stored key
|
||||
var savedEndpoint = sessionStorage.getItem('wg_server_endpoint') || '';
|
||||
if (savedEndpoint) {
|
||||
API.generateQR(peer.interface, peer.public_key, '', savedEndpoint).then(function(result) {
|
||||
if (result && result.qrcode && !result.error) {
|
||||
self.displayQRModal(peer, result.qrcode, result.config, false);
|
||||
} else {
|
||||
self.showPrivateKeyPrompt(peer, ifaceObj, function(key) {
|
||||
self.promptForEndpointAndShowQR(peer, ifaceObj, key);
|
||||
});
|
||||
}
|
||||
}).catch(function() {
|
||||
self.showPrivateKeyPrompt(peer, ifaceObj, function(key) {
|
||||
self.promptForEndpointAndShowQR(peer, ifaceObj, key);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// No saved endpoint yet - need to prompt for endpoint first
|
||||
// Try a test call to see if backend has the key
|
||||
API.generateConfig(peer.interface, peer.public_key, '', 'test').then(function(result) {
|
||||
if (result && result.config && !result.error) {
|
||||
// Backend has the key, proceed with endpoint prompt
|
||||
self.promptForEndpointAndShowQR(peer, ifaceObj, '');
|
||||
} else {
|
||||
self.showPrivateKeyPrompt(peer, ifaceObj, function(key) {
|
||||
self.promptForEndpointAndShowQR(peer, ifaceObj, key);
|
||||
});
|
||||
}
|
||||
}).catch(function() {
|
||||
self.showPrivateKeyPrompt(peer, ifaceObj, function(key) {
|
||||
self.promptForEndpointAndShowQR(peer, ifaceObj, key);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
handleDownloadConfig: function(peer, interfaces, ev) {
|
||||
@ -608,6 +654,17 @@ return view.extend({
|
||||
var privateKey = this.getStoredPrivateKey(peer.public_key);
|
||||
var ifaceObj = interfaces.find(function(i) { return i.name === peer.interface; }) || {};
|
||||
|
||||
var downloadConfig = function(config) {
|
||||
var blob = new Blob([config], { type: 'text/plain' });
|
||||
var url = URL.createObjectURL(blob);
|
||||
var a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = peer.interface + '-' + (peer.short_key || 'peer') + '.conf';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
ui.addNotification(null, E('p', _('Configuration file downloaded')), 'info');
|
||||
};
|
||||
|
||||
var showConfigModal = function(privKey) {
|
||||
var savedEndpoint = sessionStorage.getItem('wg_server_endpoint') || '';
|
||||
|
||||
@ -640,27 +697,31 @@ return view.extend({
|
||||
return;
|
||||
}
|
||||
sessionStorage.setItem('wg_server_endpoint', endpoint);
|
||||
|
||||
var config = '[Interface]\n' +
|
||||
'PrivateKey = ' + privKey + '\n' +
|
||||
'Address = ' + (peer.allowed_ips || '10.0.0.2/32') + '\n' +
|
||||
'DNS = 1.1.1.1, 1.0.0.1\n\n' +
|
||||
'[Peer]\n' +
|
||||
'PublicKey = ' + (ifaceObj.public_key || '') + '\n' +
|
||||
'Endpoint = ' + endpoint + ':' + (ifaceObj.listen_port || 51820) + '\n' +
|
||||
'AllowedIPs = 0.0.0.0/0, ::/0\n' +
|
||||
'PersistentKeepalive = 25';
|
||||
|
||||
var blob = new Blob([config], { type: 'text/plain' });
|
||||
var url = URL.createObjectURL(blob);
|
||||
var a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = peer.interface + '-' + (peer.short_key || 'peer') + '.conf';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', _('Configuration file downloaded')), 'info');
|
||||
|
||||
if (privKey) {
|
||||
var config = '[Interface]\n' +
|
||||
'PrivateKey = ' + privKey + '\n' +
|
||||
'Address = ' + (peer.allowed_ips || '10.0.0.2/32') + '\n' +
|
||||
'DNS = 1.1.1.1, 1.0.0.1\n\n' +
|
||||
'[Peer]\n' +
|
||||
'PublicKey = ' + (ifaceObj.public_key || '') + '\n' +
|
||||
'Endpoint = ' + endpoint + ':' + (ifaceObj.listen_port || 51820) + '\n' +
|
||||
'AllowedIPs = 0.0.0.0/0, ::/0\n' +
|
||||
'PersistentKeepalive = 25';
|
||||
downloadConfig(config);
|
||||
} else {
|
||||
// Use backend to generate config (it has the stored key)
|
||||
API.generateConfig(peer.interface, peer.public_key, '', endpoint).then(function(result) {
|
||||
if (result && result.config && !result.error) {
|
||||
downloadConfig(result.config);
|
||||
} else {
|
||||
ui.addNotification(null, E('p', result.error || _('Failed to generate config')), 'error');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
ui.addNotification(null, E('p', _('Error: %s').format(err.message || err)), 'error');
|
||||
});
|
||||
}
|
||||
}
|
||||
}, _('Download'))
|
||||
])
|
||||
@ -668,42 +729,22 @@ return view.extend({
|
||||
};
|
||||
|
||||
if (!privateKey) {
|
||||
// Private key not stored - ask user to input it
|
||||
ui.showModal(_('Private Key Required'), [
|
||||
E('p', {}, _('Enter the peer\'s private key to generate the configuration file:')),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Private Key')),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
E('input', {
|
||||
'type': 'text',
|
||||
'id': 'cfg-private-key',
|
||||
'class': 'cbi-input-text',
|
||||
'placeholder': 'Base64 private key (44 characters)',
|
||||
'style': 'font-family: monospace;'
|
||||
})
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'right', 'style': 'margin-top: 1em;' }, [
|
||||
E('button', {
|
||||
'class': 'btn',
|
||||
'click': ui.hideModal
|
||||
}, _('Cancel')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-action',
|
||||
'click': function() {
|
||||
var key = document.getElementById('cfg-private-key').value.trim();
|
||||
if (!key || key.length !== 44) {
|
||||
ui.addNotification(null, E('p', _('Please enter a valid private key (44 characters)')), 'error');
|
||||
return;
|
||||
}
|
||||
self.storePrivateKey(peer.public_key, key);
|
||||
ui.hideModal();
|
||||
showConfigModal(key);
|
||||
}
|
||||
}, _('Continue'))
|
||||
])
|
||||
]);
|
||||
// Try backend first - it may have the stored key
|
||||
API.generateConfig(peer.interface, peer.public_key, '', 'test').then(function(result) {
|
||||
if (result && result.config && !result.error) {
|
||||
// Backend has the key, show config modal with backend-generated config
|
||||
showConfigModal('');
|
||||
} else {
|
||||
// Fallback to manual prompt
|
||||
self.showPrivateKeyPrompt(peer, ifaceObj, function(key) {
|
||||
showConfigModal(key);
|
||||
});
|
||||
}
|
||||
}).catch(function() {
|
||||
self.showPrivateKeyPrompt(peer, ifaceObj, function(key) {
|
||||
showConfigModal(key);
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -34,49 +34,65 @@ return view.extend({
|
||||
return null;
|
||||
},
|
||||
|
||||
showPrivateKeyPrompt: function(iface, peer, serverEndpoint) {
|
||||
var self = this;
|
||||
ui.showModal(_('Private Key Required'), [
|
||||
E('p', {}, _('To generate a QR code, you need the peer\'s private key.')),
|
||||
E('p', {}, _('The private key was not found on the server. This can happen for peers created before key persistence was enabled.')),
|
||||
E('div', { 'class': 'wg-form-group' }, [
|
||||
E('label', {}, _('Enter Private Key:')),
|
||||
E('input', {
|
||||
'type': 'text',
|
||||
'id': 'wg-private-key-input',
|
||||
'class': 'cbi-input-text',
|
||||
'placeholder': 'Base64 private key...',
|
||||
'style': 'width: 100%; font-family: monospace;'
|
||||
})
|
||||
]),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', {
|
||||
'class': 'btn',
|
||||
'click': ui.hideModal
|
||||
}, _('Cancel')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-action',
|
||||
'click': function() {
|
||||
var input = document.getElementById('wg-private-key-input');
|
||||
var key = input ? input.value.trim() : '';
|
||||
if (key && key.length === 44) {
|
||||
ui.hideModal();
|
||||
self.showQRCode(iface, peer, key, serverEndpoint);
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, _('Please enter a valid private key (44 characters, base64)')), 'error');
|
||||
}
|
||||
}
|
||||
}, _('Generate QR'))
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
generateQRForPeer: function(iface, peer, serverEndpoint) {
|
||||
var self = this;
|
||||
var privateKey = this.getStoredPrivateKey(peer.public_key);
|
||||
|
||||
if (!privateKey) {
|
||||
ui.showModal(_('Private Key Required'), [
|
||||
E('p', {}, _('To generate a QR code, you need the peer\'s private key.')),
|
||||
E('p', {}, _('Private keys are only available immediately after peer creation for security reasons.')),
|
||||
E('div', { 'class': 'wg-form-group' }, [
|
||||
E('label', {}, _('Enter Private Key:')),
|
||||
E('input', {
|
||||
'type': 'text',
|
||||
'id': 'wg-private-key-input',
|
||||
'class': 'cbi-input-text',
|
||||
'placeholder': 'Base64 private key...',
|
||||
'style': 'width: 100%; font-family: monospace;'
|
||||
})
|
||||
]),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', {
|
||||
'class': 'btn',
|
||||
'click': ui.hideModal
|
||||
}, _('Cancel')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-action',
|
||||
'click': function() {
|
||||
var input = document.getElementById('wg-private-key-input');
|
||||
var key = input ? input.value.trim() : '';
|
||||
if (key && key.length === 44) {
|
||||
ui.hideModal();
|
||||
self.showQRCode(iface, peer, key, serverEndpoint);
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, _('Please enter a valid private key (44 characters, base64)')), 'error');
|
||||
}
|
||||
}
|
||||
}, _('Generate QR'))
|
||||
])
|
||||
]);
|
||||
if (privateKey) {
|
||||
this.showQRCode(iface, peer, privateKey, serverEndpoint);
|
||||
return;
|
||||
}
|
||||
|
||||
this.showQRCode(iface, peer, privateKey, serverEndpoint);
|
||||
// Try backend first with empty private key - it will look up the stored key
|
||||
api.generateQR(iface.name, peer.public_key, '', serverEndpoint).then(function(result) {
|
||||
if (result && result.qrcode && !result.error) {
|
||||
// Backend found the stored key and generated QR
|
||||
self.displayQRModal(iface, peer, result.qrcode, result.config);
|
||||
} else {
|
||||
// Backend doesn't have the key - prompt user
|
||||
self.showPrivateKeyPrompt(iface, peer, serverEndpoint);
|
||||
}
|
||||
}).catch(function() {
|
||||
self.showPrivateKeyPrompt(iface, peer, serverEndpoint);
|
||||
});
|
||||
},
|
||||
|
||||
showQRCode: function(iface, peer, privateKey, serverEndpoint) {
|
||||
|
||||
@ -44,7 +44,7 @@ var callCreateInterface = rpc.declare({
|
||||
var callAddPeer = rpc.declare({
|
||||
object: 'luci.wireguard-dashboard',
|
||||
method: 'add_peer',
|
||||
params: ['interface', 'name', 'allowed_ips', 'public_key', 'preshared_key', 'endpoint', 'persistent_keepalive'],
|
||||
params: ['interface', 'name', 'allowed_ips', 'public_key', 'preshared_key', 'endpoint', 'persistent_keepalive', 'private_key'],
|
||||
expect: { }
|
||||
});
|
||||
|
||||
|
||||
@ -506,6 +506,7 @@ add_peer() {
|
||||
json_get_var psk preshared_key
|
||||
json_get_var endpoint endpoint
|
||||
json_get_var keepalive persistent_keepalive
|
||||
json_get_var client_privkey private_key
|
||||
|
||||
json_init
|
||||
|
||||
@ -572,6 +573,11 @@ add_peer() {
|
||||
uci set network.$section_name.persistent_keepalive="$keepalive"
|
||||
fi
|
||||
|
||||
# Store client private key for QR/config generation
|
||||
if [ -n "$client_privkey" ]; then
|
||||
uci set network.$section_name._client_private_key="$client_privkey"
|
||||
fi
|
||||
|
||||
# Route allowed IPs
|
||||
uci set network.$section_name.route_allowed_ips="1"
|
||||
|
||||
@ -655,6 +661,24 @@ generate_config() {
|
||||
|
||||
json_init
|
||||
|
||||
# If private key not provided, look it up from UCI
|
||||
if [ -z "$peer_privkey" ]; then
|
||||
local sections=$(uci show network 2>/dev/null | grep "=wireguard_$iface$" | cut -d'.' -f2 | cut -d'=' -f1)
|
||||
for section in $sections; do
|
||||
local key=$(uci -q get network.$section.public_key)
|
||||
if [ "$key" = "$peer_key" ]; then
|
||||
peer_privkey=$(uci -q get network.$section._client_private_key)
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -z "$peer_privkey" ]; then
|
||||
json_add_string "error" "Private key not available. Please provide it manually."
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
# Get interface details
|
||||
local server_pubkey=$($WG_CMD show $iface public-key 2>/dev/null)
|
||||
local server_port=$($WG_CMD show $iface listen-port 2>/dev/null)
|
||||
@ -709,6 +733,24 @@ generate_qr() {
|
||||
return
|
||||
fi
|
||||
|
||||
# If private key not provided, look it up from UCI
|
||||
if [ -z "$peer_privkey" ]; then
|
||||
local sections=$(uci show network 2>/dev/null | grep "=wireguard_$iface$" | cut -d'.' -f2 | cut -d'=' -f1)
|
||||
for section in $sections; do
|
||||
local key=$(uci -q get network.$section.public_key)
|
||||
if [ "$key" = "$peer_key" ]; then
|
||||
peer_privkey=$(uci -q get network.$section._client_private_key)
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -z "$peer_privkey" ]; then
|
||||
json_add_string "error" "Private key not available. Please provide it manually."
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
# Get interface details
|
||||
local server_pubkey=$($WG_CMD show $iface public-key 2>/dev/null)
|
||||
local server_port=$($WG_CMD show $iface listen-port 2>/dev/null)
|
||||
@ -1008,7 +1050,7 @@ get_bandwidth_rates() {
|
||||
# Main dispatcher
|
||||
case "$1" in
|
||||
list)
|
||||
echo '{"status":{},"interfaces":{},"peers":{},"traffic":{},"config":{},"generate_keys":{},"create_interface":{"name":"str","private_key":"str","listen_port":"str","addresses":"str","mtu":"str"},"add_peer":{"interface":"str","name":"str","allowed_ips":"str","public_key":"str","preshared_key":"str","endpoint":"str","persistent_keepalive":"str"},"remove_peer":{"interface":"str","public_key":"str"},"generate_config":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"generate_qr":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"bandwidth_history":{},"endpoint_info":{"endpoint":"str"},"ping_peer":{"ip":"str"},"interface_control":{"interface":"str","action":"str"},"peer_descriptions":{},"bandwidth_rates":{}}'
|
||||
echo '{"status":{},"interfaces":{},"peers":{},"traffic":{},"config":{},"generate_keys":{},"create_interface":{"name":"str","private_key":"str","listen_port":"str","addresses":"str","mtu":"str"},"add_peer":{"interface":"str","name":"str","allowed_ips":"str","public_key":"str","preshared_key":"str","endpoint":"str","persistent_keepalive":"str","private_key":"str"},"remove_peer":{"interface":"str","public_key":"str"},"generate_config":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"generate_qr":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"bandwidth_history":{},"endpoint_info":{"endpoint":"str"},"ping_peer":{"ip":"str"},"interface_control":{"interface":"str","action":"str"},"peer_descriptions":{},"bandwidth_rates":{}}'
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
|
||||
Loading…
Reference in New Issue
Block a user