diff --git a/How_to_generate_an_bcrypt_hash.md b/How_to_generate_an_bcrypt_hash.md index d2b69f59..46cb2275 100644 --- a/How_to_generate_an_bcrypt_hash.md +++ b/How_to_generate_an_bcrypt_hash.md @@ -31,9 +31,9 @@ pip3 install bcrypt --break-system-packages ```bash sudo dnf update sudo dnf install python3 python3-pip -# If you use have install python using apt +# If you use have install python using dnf sudo dnf install python3-bcrypt -# If don't install python using apt +# If don't install python using dnf pip3 install bcrypt # If you got externally-managed-environment error pip3 install bcrypt --break-system-packages @@ -43,9 +43,18 @@ pip3 install bcrypt --break-system-packages ```bash sudo pacman -Syy sudo pacman -S python python-pip -# If you use have install python using apt +# If you use have install python using pacman sudo pacman -S python-bcrypt -# If don't install python using apt +# If don't install python using pacman +pip3 install bcrypt +# If you got externally-managed-environment error +pip3 install bcrypt --break-system-packages +``` + +### macOS +```bash +brew install bcrypt +# If don't install bcrypt using homebrew pip3 install bcrypt # If you got externally-managed-environment error pip3 install bcrypt --break-system-packages @@ -54,7 +63,7 @@ pip3 install bcrypt --break-system-packages ## Generating bcrypt hash from the command line You can use the following one-liner command to generate a bcrypt hash directly in the cmd/ terminal: ```bash -python3 -c "import bcrypt; password = b'your_password_here'; assert len(password) < 72, 'Password must be less than 72 bytes due to bcrypt limitation'; hashed = bcrypt.hashpw(password, bcrypt.gensalt()); print(f'The hashed password is: {hashed.decode()}'); docker_interpolation = hashed.decode().replace('$', '$$'); print(f'The hashed password for a Docker env is: {docker_interpolation}')" # or python if you run this on Windows. CHANGE your_password_here BY YOUR PASSWORD +python3 -c "import bcrypt; password = b'your_password_here'; assert len(password) < 72, 'Password must be less than 72 bytes due to bcrypt limitation'; hashed = bcrypt.hashpw(password, bcrypt.gensalt()); print(f'The hashed password is: {hashed.decode()}'); docker_interpolation = hashed.decode().replace('$', '$'*2); print(f'The hashed password for a Docker env is: {docker_interpolation}')" # or python if you run this on Windows. CHANGE your_password_here BY YOUR PASSWORD ``` Please change ``your_password_here`` in the line by your own password. @@ -98,4 +107,4 @@ The hashed password for an docker env is: $$2b$$12$$NRiL4Kw4dKid.ix2WvZltOmaQBZj The docker line ``PASSWORD_HASH`` will be: ```txt PASSWORD_HASH=$$2b$$12$$NRiL4Kw4dKid.ix2WvZltOmaQBZjoX30shjHJXRVdEGshAxYWXXMe -``` \ No newline at end of file +``` diff --git a/README.md b/README.md index bc36ad2f..5d4b8e51 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ These options can be configured by setting environment variables using `-e KEY=" | `WG_HOST` | - | `vpn.myserver.com` | The public hostname of your VPN server. | | `WG_DEVICE` | `eth0` | `ens6f0` | Ethernet device the wireguard traffic should be forwarded through. | | `WG_PORT` | `51820` | `12345` | The public UDP port of your VPN server. WireGuard will listen on that (othwise default) inside the Docker container. | -| `WG_CONFIG_PORT`| `51820` | `12345` | The UDP port used on [Home Assistent Plugin](https://github.com/adriy-be/homeassistant-addons-jdeath/tree/main/wgeasy) +| `WG_CONFIG_PORT`| `51820` | `12345` | The UDP port used on [Home Assistant Plugin](https://github.com/adriy-be/homeassistant-addons-jdeath/tree/main/wgeasy) | `WG_MTU` | `null` | `1420` | The MTU the clients will use. Server uses default WG MTU. | | `WG_PERSISTENT_KEEPALIVE` | `0` | `25` | Value in seconds to keep the "connection" open. If this value is 0, then connections won't be kept alive. | | `WG_DEFAULT_ADDRESS` | `10.8.0.x` | `10.6.0.x` | Clients IP address range. | diff --git a/assets/screenshot.png b/assets/screenshot.png index c5b73ccb..5f54cfe5 100644 Binary files a/assets/screenshot.png and b/assets/screenshot.png differ diff --git a/src/lib/Server.js b/src/lib/Server.js index c3e41598..0b010fce 100644 --- a/src/lib/Server.js +++ b/src/lib/Server.js @@ -274,6 +274,23 @@ module.exports = class Server { }); }; + // backup_restore + const router3 = createRouter(); + app.use(router3); + + router3 + .get('/api/wireguard/backup', defineEventHandler(async (event) => { + const config = await WireGuard.backupConfiguration(); + setHeader(event, 'Content-Disposition', 'attachment; filename="wg0.json"'); + setHeader(event, 'Content-Type', 'text/json'); + return config; + })) + .put('/api/wireguard/restore', defineEventHandler(async (event) => { + const { file } = await readBody(event); + await WireGuard.restoreConfiguration(file); + return { success: true }; + })); + // Static assets const publicDir = '/app/www'; app.use( diff --git a/src/lib/WireGuard.js b/src/lib/WireGuard.js index 49568d2f..b56a3b5f 100644 --- a/src/lib/WireGuard.js +++ b/src/lib/WireGuard.js @@ -29,60 +29,60 @@ const { module.exports = class WireGuard { + async __buildConfig() { + this.__configPromise = Promise.resolve().then(async () => { + if (!WG_HOST) { + throw new Error('WG_HOST Environment Variable Not Set!'); + } + + debug('Loading configuration...'); + let config; + try { + config = await fs.readFile(path.join(WG_PATH, 'wg0.json'), 'utf8'); + config = JSON.parse(config); + debug('Configuration loaded.'); + } catch (err) { + const privateKey = await Util.exec('wg genkey'); + const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`, { + log: 'echo ***hidden*** | wg pubkey', + }); + const address = WG_DEFAULT_ADDRESS.replace('x', '1'); + + config = { + server: { + privateKey, + publicKey, + address, + }, + clients: {}, + }; + debug('Configuration generated.'); + } + + return config; + }); + + return this.__configPromise; + } + async getConfig() { if (!this.__configPromise) { - this.__configPromise = Promise.resolve().then(async () => { - if (!WG_HOST) { - throw new Error('WG_HOST Environment Variable Not Set!'); - } + const config = await this.__buildConfig(); - debug('Loading configuration...'); - let config; - try { - config = await fs.readFile(path.join(WG_PATH, 'wg0.json'), 'utf8'); - config = JSON.parse(config); - debug('Configuration loaded.'); - } catch (err) { - const privateKey = await Util.exec('wg genkey'); - const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`, { - log: 'echo ***hidden*** | wg pubkey', - }); - const address = WG_DEFAULT_ADDRESS.replace('x', '1'); - const address6 = WG_DEFAULT_ADDRESS6.replace('x', '1'); - - config = { - server: { - privateKey, - publicKey, - address, - address6, - }, - clients: {}, - }; - debug('Configuration generated.'); + await this.__saveConfig(config); + await Util.exec('wg-quick down wg0').catch(() => {}); + await Util.exec('wg-quick up wg0').catch((err) => { + if (err && err.message && err.message.includes('Cannot find device "wg0"')) { + throw new Error('WireGuard exited with the error: Cannot find device "wg0"\nThis usually means that your host\'s kernel does not support WireGuard!'); } - await this.__saveConfig(config); - await Util.exec('wg-quick down wg0').catch(() => { }); - await Util.exec('wg-quick up wg0').catch((err) => { - if (err && err.message && err.message.includes('Cannot find device "wg0"')) { - throw new Error('WireGuard exited with the error: Cannot find device "wg0"\nThis usually means that your host\'s kernel does not support WireGuard!'); - } - - throw err; - }); - // await Util.exec(`iptables -t nat -A POSTROUTING -s ${WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ' + WG_DEVICE + ' -j MASQUERADE`); - // await Util.exec('iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT'); - // await Util.exec('iptables -A FORWARD -i wg0 -j ACCEPT'); - // await Util.exec('iptables -A FORWARD -o wg0 -j ACCEPT'); - // await Util.exec(`ip6tables -t nat -A POSTROUTING -s ${WG_DEFAULT_ADDRESS6.replace('x', '')}/120 -o eth0 -j MASQUERADE`); - // await Util.exec('ip6tables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT'); - // await Util.exec('ip6tables -A FORWARD -i wg0 -j ACCEPT'); - // await Util.exec('ip6tables -A FORWARD -o wg0 -j ACCEPT'); - await this.__syncConfig(); - - return config; + throw err; }); + // await Util.exec(`iptables -t nat -A POSTROUTING -s ${WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ' + WG_DEVICE + ' -j MASQUERADE`); + // await Util.exec('iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT'); + // await Util.exec('iptables -A FORWARD -i wg0 -j ACCEPT'); + // await Util.exec('iptables -A FORWARD -o wg0 -j ACCEPT'); + await this.__syncConfig(); } return this.__configPromise; @@ -237,7 +237,9 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`; const config = await this.getConfig(); const privateKey = await Util.exec('wg genkey'); - const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`); + const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`, { + log: 'echo ***hidden*** | wg pubkey', + }); const preSharedKey = await Util.exec('wg genpsk'); // Calculate next IP @@ -346,22 +348,30 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`; await this.saveConfig(); } - async updateClientAddress6({ clientId, address6 }) { - const client = await this.getClient({ clientId }); - - if (!Util.isValidIPv6(address6)) { - throw new ServerError(`Invalid Address6: ${address6}`, 400); - } + async __reloadConfig() { + await this.__buildConfig(); + await this.__syncConfig(); + } - client.address6 = address6; - client.updatedAt = new Date(); + async restoreConfiguration(config) { + debug('Starting configuration restore process.'); + const _config = JSON.parse(config); + await this.__saveConfig(_config); + await this.__reloadConfig(); + debug('Configuration restore process completed.'); + } - await this.saveConfig(); + async backupConfiguration() { + debug('Starting configuration backup.'); + const config = await this.getConfig(); + const backup = JSON.stringify(config, null, 2); + debug('Configuration backup completed.'); + return backup; } // Shutdown wireguard async Shutdown() { - await Util.exec('wg-quick down wg0').catch(() => { }); + await Util.exec('wg-quick down wg0').catch(() => {}); } }; diff --git a/src/package-lock.json b/src/package-lock.json index 86c3e277..2862da04 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -358,9 +358,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { @@ -1917,9 +1917,9 @@ } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "peer": true, "dependencies": { @@ -2827,16 +2827,13 @@ "dev": true }, "node_modules/jackspeak": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", - "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=14" - }, "funding": { "url": "https://github.com/sponsors/isaacs" }, @@ -2972,13 +2969,10 @@ "peer": true }, "node_modules/lru-cache": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.3.0.tgz", - "integrity": "sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/merge2": { "version": "1.4.1", @@ -4239,9 +4233,9 @@ } }, "node_modules/sucrase/node_modules/glob": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", - "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", @@ -4254,9 +4248,6 @@ "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } diff --git a/src/www/css/app.css b/src/www/css/app.css index ead64396..c5112366 100644 --- a/src/www/css/app.css +++ b/src/www/css/app.css @@ -802,6 +802,11 @@ video { display: none; } +.size-6 { + width: 1.5rem; + height: 1.5rem; +} + .h-1 { height: 0.25rem; } @@ -1458,6 +1463,10 @@ video { border-bottom-width: 0px; } +.hover\:cursor-pointer:hover { + cursor: pointer; +} + .hover\:border-red-800:hover { --tw-border-opacity: 1; border-color: rgb(153 27 27 / var(--tw-border-opacity)); diff --git a/src/www/index.html b/src/www/index.html index cbe8b4ff..db2d8559 100644 --- a/src/www/index.html +++ b/src/www/index.html @@ -91,6 +91,24 @@
{{$t("clients")}}