Browse Source

migrate to sqlite

pull/1619/head
Bernd Storath 3 months ago
parent
commit
5853c0bcca
  1. 4
      src/nuxt.config.ts
  2. 2
      src/server/database/migrations/0000_faulty_plazm.sql
  3. 3
      src/server/database/migrations/0001_lonely_tusk.sql
  4. 15
      src/server/database/migrations/0001_next_george_stacy.sql
  5. 5
      src/server/database/migrations/meta/0000_snapshot.json
  6. 7
      src/server/database/migrations/meta/0001_snapshot.json
  7. 8
      src/server/database/migrations/meta/_journal.json
  8. 12
      src/server/database/repositories/client/service.ts
  9. 23
      src/server/database/repositories/hooks/service.ts
  10. 44
      src/server/database/repositories/interface/service.ts
  11. 25
      src/server/database/repositories/oneTimeLink/service.ts
  12. 7
      src/server/database/repositories/userConfig/service.ts
  13. 10
      src/server/database/sqlite.ts
  14. 2
      src/server/utils/Database.ts
  15. 205
      src/server/utils/WireGuard.ts
  16. 2
      src/server/utils/ip.ts
  17. 20
      src/server/utils/wgHelper.ts

4
src/nuxt.config.ts

@ -50,4 +50,8 @@ export default defineNuxtConfig({
'#db': fileURLToPath(new URL('./server/database/', import.meta.url)), '#db': fileURLToPath(new URL('./server/database/', import.meta.url)),
}, },
}, },
alias: {
// for typecheck reasons (https://github.com/nuxt/cli/issues/323)
'#db': fileURLToPath(new URL('./server/database/', import.meta.url)),
},
}); });

2
src/server/database/migrations/0000_fantastic_zemo.sql → src/server/database/migrations/0000_faulty_plazm.sql

@ -79,7 +79,7 @@ CREATE TABLE `users_table` (
`email` text, `email` text,
`name` text NOT NULL, `name` text NOT NULL,
`role` integer NOT NULL, `role` integer NOT NULL,
`enabled` integer DEFAULT 1 NOT NULL, `enabled` integer NOT NULL,
`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, `created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
`updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL `updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL
); );

3
src/server/database/migrations/0001_lonely_tusk.sql

@ -1,3 +0,0 @@
-- Custom SQL migration file, put your code below! --
INSERT INTO `general_table` (`setupStep`, `session_password`, `session_timeout`)
VALUES (1, hex(randomblob(256)), 3600);

15
src/server/database/migrations/0001_next_george_stacy.sql

@ -0,0 +1,15 @@
-- Insert default values --
INSERT INTO `general_table` (`setupStep`, `session_password`, `session_timeout`)
VALUES (1, hex(randomblob(256)), 3600);
INSERT INTO `interfaces_table` (`name`, `device`, `port`, `private_key`, `public_key`, `ipv4_cidr`, `ipv6_cidr`, `mtu`, `enabled`)
VALUES ('wg0', 'eth0', 51820, '---default---', '---default---', '10.8.0.0/24', 'fdcc:ad94:bacf:61a4::cafe:0/112', 1420, 1);
INSERT INTO `hooks_table` (`id`, `pre_up`, `post_up`, `pre_down`, `post_down`)
VALUES (
'wg0',
'',
'iptables -t nat -A POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE; iptables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; ip6tables -t nat -A POSTROUTING -s {{ipv6Cidr}} -o {{device}} -j MASQUERADE; ip6tables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -A FORWARD -o wg0 -j ACCEPT;',
'',
'iptables -t nat -D POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE; iptables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; ip6tables -t nat -D POSTROUTING -s {{ipv6Cidr}} -o {{device}} -j MASQUERADE; ip6tables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -D FORWARD -o wg0 -j ACCEPT;'
);

5
src/server/database/migrations/meta/0000_snapshot.json

@ -1,7 +1,7 @@
{ {
"version": "6", "version": "6",
"dialect": "sqlite", "dialect": "sqlite",
"id": "52812a10-9028-40dc-b1dc-69e4e641aa9e", "id": "37203c54-1625-40bd-89fd-9be0f7140fae",
"prevId": "00000000-0000-0000-0000-000000000000", "prevId": "00000000-0000-0000-0000-000000000000",
"tables": { "tables": {
"clients_table": { "clients_table": {
@ -542,8 +542,7 @@
"type": "integer", "type": "integer",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false, "autoincrement": false
"default": 1
}, },
"created_at": { "created_at": {
"name": "created_at", "name": "created_at",

7
src/server/database/migrations/meta/0001_snapshot.json

@ -1,6 +1,6 @@
{ {
"id": "5eee8e4e-69d5-4c68-b5cd-a221ad7649de", "id": "1bc0abb2-1297-42d2-ba8e-213bbfd92835",
"prevId": "52812a10-9028-40dc-b1dc-69e4e641aa9e", "prevId": "37203c54-1625-40bd-89fd-9be0f7140fae",
"version": "6", "version": "6",
"dialect": "sqlite", "dialect": "sqlite",
"tables": { "tables": {
@ -542,8 +542,7 @@
"type": "integer", "type": "integer",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false, "autoincrement": false
"default": 1
}, },
"created_at": { "created_at": {
"name": "created_at", "name": "created_at",

8
src/server/database/migrations/meta/_journal.json

@ -5,15 +5,15 @@
{ {
"idx": 0, "idx": 0,
"version": "6", "version": "6",
"when": 1737101897688, "when": 1737107311477,
"tag": "0000_fantastic_zemo", "tag": "0000_faulty_plazm",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 1, "idx": 1,
"version": "6", "version": "6",
"when": 1737101904944, "when": 1737107315085,
"tag": "0001_lonely_tusk", "tag": "0001_next_george_stacy",
"breakpoints": true "breakpoints": true
} }
] ]

12
src/server/database/repositories/client/service.ts

@ -2,6 +2,7 @@ import type { DBType } from '#db/sqlite';
import { eq, sql } from 'drizzle-orm'; import { eq, sql } from 'drizzle-orm';
import { client } from './schema'; import { client } from './schema';
import type { ClientCreateType } from './types'; import type { ClientCreateType } from './types';
import type { ID } from '../../schema';
import { wgInterface, userConfig } from '../../schema'; import { wgInterface, userConfig } from '../../schema';
import { parseCidr } from 'cidr-tools'; import { parseCidr } from 'cidr-tools';
@ -17,6 +18,11 @@ function createPreparedStatement(db: DBType) {
findById: db.query.client findById: db.query.client
.findFirst({ where: eq(client.id, sql.placeholder('id')) }) .findFirst({ where: eq(client.id, sql.placeholder('id')) })
.prepare(), .prepare(),
toggle: db
.update(client)
.set({ enabled: sql.placeholder('enabled') as never as boolean })
.where(eq(client.id, sql.placeholder('id')))
.prepare(),
}; };
} }
@ -38,7 +44,7 @@ export class ClientService {
})); }));
} }
async get(id: number) { async get(id: ID) {
return this.#statements.findById.execute({ id }); return this.#statements.findById.execute({ id });
} }
@ -103,4 +109,8 @@ export class ClientService {
.execute(); .execute();
}); });
} }
async toggle(id: ID, enabled: boolean) {
return this.#statements.toggle.execute({ id, enabled });
}
} }

23
src/server/database/repositories/hooks/service.ts

@ -0,0 +1,23 @@
import type { DBType } from '#db/sqlite';
import { eq, sql } from 'drizzle-orm';
import { hooks } from './schema';
function createPreparedStatement(db: DBType) {
return {
get: db.query.hooks
.findFirst({ where: eq(hooks.id, sql.placeholder('interface')) })
.prepare(),
};
}
export class HooksService {
#statements: ReturnType<typeof createPreparedStatement>;
constructor(db: DBType) {
this.#statements = createPreparedStatement(db);
}
get(wgInterface: string) {
return this.#statements.get.execute({ interface: wgInterface });
}
}

44
src/server/database/repositories/interface/service.ts

@ -0,0 +1,44 @@
import type { DBType } from '#db/sqlite';
import { eq, sql } from 'drizzle-orm';
import { wgInterface } from './schema';
function createPreparedStatement(db: DBType) {
return {
get: db.query.wgInterface
.findFirst({ where: eq(wgInterface.name, sql.placeholder('interface')) })
.prepare(),
getAll: db.query.wgInterface.findMany().prepare(),
updateKeyPair: db
.update(wgInterface)
.set({
privateKey: sql.placeholder('privateKey') as never as string,
publicKey: sql.placeholder('publicKey') as never as string,
})
.where(eq(wgInterface.name, sql.placeholder('interface')))
.prepare(),
};
}
export class InterfaceService {
#statements: ReturnType<typeof createPreparedStatement>;
constructor(db: DBType) {
this.#statements = createPreparedStatement(db);
}
get(infName: string) {
return this.#statements.get.execute({ interface: infName });
}
getAll() {
return this.#statements.getAll.execute();
}
updateKeyPair(infName: string, privateKey: string, publicKey: string) {
return this.#statements.updateKeyPair.execute({
interface: infName,
privateKey,
publicKey,
});
}
}

25
src/server/database/repositories/oneTimeLink/service.ts

@ -0,0 +1,25 @@
import type { DBType } from '#db/sqlite';
import { eq, sql } from 'drizzle-orm';
import { oneTimeLink } from './schema';
import type { ID } from '../../schema';
function createPreparedStatement(db: DBType) {
return {
delete: db
.delete(oneTimeLink)
.where(eq(oneTimeLink.id, sql.placeholder('id')))
.prepare(),
};
}
export class OneTimeLinkService {
#statements: ReturnType<typeof createPreparedStatement>;
constructor(db: DBType) {
this.#statements = createPreparedStatement(db);
}
delete(id: ID) {
return this.#statements.delete.execute({ id });
}
}

7
src/server/database/repositories/userConfig/service.ts

@ -4,6 +4,9 @@ import { userConfig } from './schema';
function createPreparedStatement(db: DBType) { function createPreparedStatement(db: DBType) {
return { return {
get: db.query.userConfig
.findFirst({ where: eq(userConfig.id, sql.placeholder('interface')) })
.prepare(),
updateHostPort: db updateHostPort: db
.update(userConfig) .update(userConfig)
.set({ .set({
@ -22,6 +25,10 @@ export class UserConfigService {
this.#statements = createPreparedStatement(db); this.#statements = createPreparedStatement(db);
} }
async get(wgInterface: string) {
return await this.#statements.get.execute({ interface: wgInterface });
}
async updateHostPort(wgInterface: string, host: string, port: number) { async updateHostPort(wgInterface: string, host: string, port: number) {
return await this.#statements.updateHostPort.execute({ return await this.#statements.updateHostPort.execute({
interface: wgInterface, interface: wgInterface,

10
src/server/database/sqlite.ts

@ -7,6 +7,9 @@ import { ClientService } from './repositories/client/service';
import { GeneralService } from './repositories/general/service'; import { GeneralService } from './repositories/general/service';
import { UserService } from './repositories/user/service'; import { UserService } from './repositories/user/service';
import { UserConfigService } from './repositories/userConfig/service'; import { UserConfigService } from './repositories/userConfig/service';
import { InterfaceService } from './repositories/interface/service';
import { HooksService } from './repositories/hooks/service';
import { OneTimeLinkService } from './repositories/oneTimeLink/service';
const client = createClient({ url: 'file:/etc/wireguard/wg0.db' }); const client = createClient({ url: 'file:/etc/wireguard/wg0.db' });
const db = drizzle({ client, schema }); const db = drizzle({ client, schema });
@ -21,11 +24,17 @@ class DBService {
general: GeneralService; general: GeneralService;
users: UserService; users: UserService;
userConfigs: UserConfigService; userConfigs: UserConfigService;
interfaces: InterfaceService;
hooks: HooksService;
oneTimeLinks: OneTimeLinkService;
constructor(db: DBType) { constructor(db: DBType) {
this.clients = new ClientService(db); this.clients = new ClientService(db);
this.general = new GeneralService(db); this.general = new GeneralService(db);
this.users = new UserService(db); this.users = new UserService(db);
this.userConfigs = new UserConfigService(db); this.userConfigs = new UserConfigService(db);
this.interfaces = new InterfaceService(db);
this.hooks = new HooksService(db);
this.oneTimeLinks = new OneTimeLinkService(db);
} }
} }
@ -38,7 +47,6 @@ async function migrate() {
await drizzleMigrate(db, { await drizzleMigrate(db, {
migrationsFolder: './server/database/migrations', migrationsFolder: './server/database/migrations',
}); });
// TODO: data migration
console.log('Migration complete'); console.log('Migration complete');
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {

2
src/server/utils/Database.ts

@ -18,7 +18,7 @@ let provider = nullObject as never as DBServiceType;
connect().then((db) => { connect().then((db) => {
provider = db; provider = db;
// TODO: start wireguard WireGuard.Startup();
}); });
// TODO: check if old config exists and tell user about migration path // TODO: check if old config exists and tell user about migration path

205
src/server/utils/WireGuard.ts

@ -1,10 +1,7 @@
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import debug from 'debug'; import debug from 'debug';
import QRCode from 'qrcode'; import QRCode from 'qrcode';
import CRC32 from 'crc-32'; import type { ID } from '#db/schema';
import isCidr from 'is-cidr';
import type { UpdateClient } from '~~/services/database/repositories/client';
const DEBUG = debug('WireGuard'); const DEBUG = debug('WireGuard');
@ -13,20 +10,26 @@ class WireGuard {
* Save and sync config * Save and sync config
*/ */
async saveConfig() { async saveConfig() {
await this.#saveWireguardConfig(); await this.#saveWireguardConfig('wg0');
await this.#syncWireguardConfig(); await this.#syncWireguardConfig('wg0');
} }
/** /**
* Generates and saves WireGuard config from database as wg0 * Generates and saves WireGuard config from database as wg0
*/ */
async #saveWireguardConfig() { async #saveWireguardConfig(infName: string) {
const system = await Database.get(); const wgInterface = await Database.interfaces.get(infName);
const clients = await Database.client.findAll(); const clients = await Database.clients.getAll();
const hooks = await Database.hooks.get(infName);
if (!wgInterface || !hooks) {
throw new Error('Interface or Hooks not found');
}
const result = []; const result = [];
result.push(wg.generateServerInterface(system)); result.push(wg.generateServerInterface(wgInterface, hooks));
for (const client of Object.values(clients)) { for (const client of clients) {
if (!client.enabled) { if (!client.enabled) {
continue; continue;
} }
@ -34,15 +37,15 @@ class WireGuard {
} }
DEBUG('Saving Config...'); DEBUG('Saving Config...');
await fs.writeFile('/etc/wireguard/wg0.conf', result.join('\n\n'), { await fs.writeFile(`/etc/wireguard/${infName}.conf`, result.join('\n\n'), {
mode: 0o600, mode: 0o600,
}); });
DEBUG('Config saved successfully.'); DEBUG('Config saved successfully.');
} }
async #syncWireguardConfig() { async #syncWireguardConfig(infName: string) {
DEBUG('Syncing Config...'); DEBUG('Syncing Config...');
await wg.sync(); await wg.sync(infName);
DEBUG('Config synced successfully.'); DEBUG('Config synced successfully.');
} }
@ -57,7 +60,7 @@ class WireGuard {
})); }));
// Loop WireGuard status // Loop WireGuard status
const dump = await wg.dump(); const dump = await wg.dump('wg0');
dump.forEach( dump.forEach(
({ publicKey, latestHandshakeAt, endpoint, transferRx, transferTx }) => { ({ publicKey, latestHandshakeAt, endpoint, transferRx, transferTx }) => {
const client = clients.find((client) => client.publicKey === publicKey); const client = clients.find((client) => client.publicKey === publicKey);
@ -75,14 +78,24 @@ class WireGuard {
return clients; return clients;
} }
async getClientConfiguration({ clientId }: { clientId: number }) { async getClientConfiguration({ clientId }: { clientId: ID }) {
const system = await Database.system.get(); const wgInterface = await Database.interfaces.get('wg0');
const userConfig = await Database.userConfigs.get('wg0');
if (!wgInterface || !userConfig) {
throw new Error('Interface or UserConfig not found');
}
const client = await Database.clients.get(clientId); const client = await Database.clients.get(clientId);
return wg.generateClientConfig(system, client); if (!client) {
throw new Error('Client not found');
}
return wg.generateClientConfig(wgInterface, userConfig, client);
} }
async getClientQRCodeSVG({ clientId }: { clientId: string }) { async getClientQRCodeSVG({ clientId }: { clientId: ID }) {
const config = await this.getClientConfiguration({ clientId }); const config = await this.getClientConfiguration({ clientId });
return QRCode.toString(config, { return QRCode.toString(config, {
type: 'svg', type: 'svg',
@ -90,85 +103,6 @@ class WireGuard {
}); });
} }
async deleteClient({ clientId }: { clientId: string }) {
await Database.client.delete(clientId);
await this.saveConfig();
}
async enableClient({ clientId }: { clientId: string }) {
await Database.client.toggle(clientId, true);
await this.saveConfig();
}
async generateOneTimeLink({ clientId }: { clientId: string }) {
const key = `${clientId}-${Math.floor(Math.random() * 1000)}`;
const oneTimeLink = Math.abs(CRC32.str(key)).toString(16);
const expiresAt = new Date(Date.now() + 5 * 60 * 1000).toISOString();
await Database.client.createOneTimeLink(clientId, {
oneTimeLink,
expiresAt,
});
await this.saveConfig();
}
async eraseOneTimeLink({ clientId }: { clientId: string }) {
await Database.client.deleteOneTimeLink(clientId);
await this.saveConfig();
}
async disableClient({ clientId }: { clientId: string }) {
await Database.client.toggle(clientId, false);
await this.saveConfig();
}
async updateClient({
clientId,
client,
}: {
clientId: string;
client: UpdateClient;
}) {
// TODO: validate ipv4, v6, expire date etc
await Database.client.update(clientId, client);
await this.saveConfig();
}
async updateAddressRange({
address4,
address6,
}: {
address4: string;
address6: string;
}) {
// TODO: be able to revert if error
if (!isCidr(address4) || !isCidr(address6)) {
throw new Error('Invalid CIDR');
}
await Database.system.updateAddressRange(address4, address6);
const systems = await Database.system.get();
const clients = await Database.client.findAll();
for (const _client of Object.values(clients)) {
const clients = await Database.client.findAll();
const client = structuredClone(_client) as DeepWriteable<typeof _client>;
client.address4 = nextIPv4(systems, clients);
client.address6 = nextIPv6(systems, clients);
await Database.client.update(client.id, {
...client,
});
}
await this.saveConfig();
}
// TODO: reimplement database restore // TODO: reimplement database restore
async restoreConfiguration(_config: string) { async restoreConfiguration(_config: string) {
/* DEBUG('Starting configuration restore process.'); /* DEBUG('Starting configuration restore process.');
@ -189,24 +123,47 @@ class WireGuard {
} }
async Startup() { async Startup() {
DEBUG('Starting Wireguard...'); const wgInterfaces = await Database.interfaces.getAll();
await this.#saveWireguardConfig(); for (const wgInterface of wgInterfaces) {
await wg.down().catch(() => {}); if (wgInterface.enabled !== true) {
await wg.up().catch((err) => { continue;
}
// default interface has no keys
if ( if (
err && wgInterface.privateKey === '---default---' &&
err.message && wgInterface.publicKey === '---default---'
err.message.includes('Cannot find device "wg0"')
) { ) {
throw new Error( DEBUG('Generating new Wireguard Keys...');
'WireGuard exited with the error: Cannot find device "wg0"\nThis usually means that your host\'s kernel does not support WireGuard!' const privateKey = await wg.generatePrivateKey();
const publicKey = await wg.getPublicKey(privateKey);
await Database.interfaces.updateKeyPair(
wgInterface.name,
privateKey,
publicKey
); );
DEBUG('New Wireguard Keys generated successfully.');
} }
DEBUG(`Starting Wireguard Interface ${wgInterface.name}...`);
await this.#saveWireguardConfig(wgInterface.name);
await wg.down(wgInterface.name).catch(() => {});
await wg.up(wgInterface.name).catch((err) => {
if (
err &&
err.message &&
err.message.includes(`Cannot find device "${wgInterface.name}"`)
) {
throw new Error(
`WireGuard exited with the error: Cannot find device "${wgInterface.name}"\nThis usually means that your host's kernel does not support WireGuard!`,
{ cause: err.message }
);
}
throw err; throw err;
}); });
await this.#syncWireguardConfig(); await this.#syncWireguardConfig(wgInterface.name);
DEBUG('Wireguard started successfully.'); DEBUG(`Wireguard Interface ${wgInterface.name} started successfully.`);
}
DEBUG('Starting Cron Job.'); DEBUG('Starting Cron Job.');
await this.startCronJob(); await this.startCronJob();
@ -214,7 +171,6 @@ class WireGuard {
} }
// TODO: handle as worker_thread // TODO: handle as worker_thread
// would need a better database aswell
async startCronJob() { async startCronJob() {
await this.cronJob().catch((err) => { await this.cronJob().catch((err) => {
DEBUG('Running Cron Job failed.'); DEBUG('Running Cron Job failed.');
@ -227,31 +183,34 @@ class WireGuard {
// Shutdown wireguard // Shutdown wireguard
async Shutdown() { async Shutdown() {
await wg.down().catch(() => {}); const wgInterfaces = await Database.interfaces.getAll();
for (const wgInterface of wgInterfaces) {
await wg.down(wgInterface.name).catch(() => {});
}
} }
async cronJob() { async cronJob() {
const clients = await Database.client.findAll(); const clients = await Database.clients.getAll();
// Expires Feature // Expires Feature
for (const client of Object.values(clients)) { for (const client of clients) {
if (client.enabled !== true) continue; if (client.enabled !== true) continue;
if ( if (
client.expiresAt !== null && client.expiresAt !== null &&
new Date() > new Date(client.expiresAt) new Date() > new Date(client.expiresAt)
) { ) {
DEBUG(`Client ${client.id} expired.`); DEBUG(`Client ${client.id} expired.`);
await Database.client.toggle(client.id, false); await Database.clients.toggle(client.id, false);
} }
} }
// One Time Link Feature // One Time Link Feature
for (const client of Object.values(clients)) { for (const client of clients) {
if ( if (
client.oneTimeLink !== null && client.oneTimeLink !== null &&
new Date() > new Date(client.oneTimeLink.expiresAt) new Date() > new Date(client.oneTimeLink.expiresAt)
) { ) {
DEBUG(`Client ${client.id} One Time Link expired.`); DEBUG(`Client ${client.id} One Time Link expired.`);
await Database.client.deleteOneTimeLink(client.id); await Database.oneTimeLinks.delete(client.id);
} }
} }
@ -266,7 +225,7 @@ class WireGuard {
let wireguardSentBytes = ''; let wireguardSentBytes = '';
let wireguardReceivedBytes = ''; let wireguardReceivedBytes = '';
let wireguardLatestHandshakeSeconds = ''; let wireguardLatestHandshakeSeconds = '';
for (const client of Object.values(clients)) { for (const client of clients) {
wireguardPeerCount++; wireguardPeerCount++;
if (client.enabled === true) { if (client.enabled === true) {
wireguardEnabledPeersCount++; wireguardEnabledPeersCount++;
@ -274,9 +233,9 @@ class WireGuard {
if (client.endpoint !== null) { if (client.endpoint !== null) {
wireguardConnectedPeersCount++; wireguardConnectedPeersCount++;
} }
wireguardSentBytes += `wireguard_sent_bytes{interface="wg0",enabled="${client.enabled}",address4="${client.address4}",address6="${client.address6}",name="${client.name}"} ${Number(client.transferTx)}\n`; wireguardSentBytes += `wireguard_sent_bytes{interface="wg0",enabled="${client.enabled}",ipv4Address="${client.ipv4Address}",ipv6Address="${client.ipv6Address}",name="${client.name}"} ${Number(client.transferTx)}\n`;
wireguardReceivedBytes += `wireguard_received_bytes{interface="wg0",enabled="${client.enabled}",address4="${client.address4}",address6="${client.address6}",name="${client.name}"} ${Number(client.transferRx)}\n`; wireguardReceivedBytes += `wireguard_received_bytes{interface="wg0",enabled="${client.enabled}",ipv4Address="${client.ipv4Address}",ipv6Address="${client.ipv6Address}",name="${client.name}"} ${Number(client.transferRx)}\n`;
wireguardLatestHandshakeSeconds += `wireguard_latest_handshake_seconds{interface="wg0",enabled="${client.enabled}",address4="${client.address4}",address6="${client.address6}",name="${client.name}"} ${client.latestHandshakeAt ? (new Date().getTime() - new Date(client.latestHandshakeAt).getTime()) / 1000 : 0}\n`; wireguardLatestHandshakeSeconds += `wireguard_latest_handshake_seconds{interface="wg0",enabled="${client.enabled}",ipv4Address="${client.ipv4Address}",ipv6Address="${client.ipv6Address}",name="${client.name}"} ${client.latestHandshakeAt ? (new Date().getTime() - new Date(client.latestHandshakeAt).getTime()) / 1000 : 0}\n`;
} }
let returnText = '# HELP wg-easy and wireguard metrics\n'; let returnText = '# HELP wg-easy and wireguard metrics\n';
@ -315,7 +274,7 @@ class WireGuard {
let wireguardPeerCount = 0; let wireguardPeerCount = 0;
let wireguardEnabledPeersCount = 0; let wireguardEnabledPeersCount = 0;
let wireguardConnectedPeersCount = 0; let wireguardConnectedPeersCount = 0;
for (const client of Object.values(clients)) { for (const client of clients) {
wireguardPeerCount++; wireguardPeerCount++;
if (client.enabled === true) { if (client.enabled === true) {
wireguardEnabledPeersCount++; wireguardEnabledPeersCount++;

2
src/server/utils/ip.ts

@ -13,7 +13,7 @@ export function nextIP(
let address; let address;
for (let i = cidr.start + 2n; i <= cidr.end - 1n; i++) { for (let i = cidr.start + 2n; i <= cidr.end - 1n; i++) {
const currentIp = stringifyIp({ number: i, version: version }); const currentIp = stringifyIp({ number: i, version: version });
const client = Object.values(clients).find((client) => { const client = clients.find((client) => {
return client[`ipv${version}Address`] === currentIp; return client[`ipv${version}Address`] === currentIp;
}); });

20
src/server/utils/wgHelper.ts

@ -5,10 +5,8 @@ import { stringifyIp } from 'ip-bigint';
import type { UserConfigType } from '#db/repositories/userConfig/types'; import type { UserConfigType } from '#db/repositories/userConfig/types';
import type { HooksType } from '#db/repositories/hooks/types'; import type { HooksType } from '#db/repositories/hooks/types';
// TODO: replace wg0 with parameter (to allow multi interface design)
export const wg = { export const wg = {
generateServerPeer: (client: ClientType) => { generateServerPeer: (client: Omit<ClientType, 'createdAt' | 'updatedAt'>) => {
const allowedIps = [ const allowedIps = [
`${client.ipv4Address}/32`, `${client.ipv4Address}/32`,
`${client.ipv6Address}/128`, `${client.ipv6Address}/128`,
@ -79,20 +77,20 @@ Endpoint = ${userConfig.host}:${userConfig.port}`;
return exec('wg genpsk'); return exec('wg genpsk');
}, },
up: () => { up: (infName: string) => {
return exec('wg-quick up wg0'); return exec(`wg-quick up ${infName}`);
}, },
down: () => { down: (infName: string) => {
return exec('wg-quick down wg0'); return exec(`wg-quick down ${infName}`);
}, },
sync: () => { sync: (infName: string) => {
return exec('wg syncconf wg0 <(wg-quick strip wg0)'); return exec(`wg syncconf ${infName} <(wg-quick strip ${infName})`);
}, },
dump: async () => { dump: async (infName: string) => {
const rawDump = await exec('wg show wg0 dump', { const rawDump = await exec(`wg show ${infName} dump`, {
log: false, log: false,
}); });

Loading…
Cancel
Save