From 6980bf120b926711bb474ed71d0c33b115a24c6e Mon Sep 17 00:00:00 2001 From: Bernd Storath <32197462+kaaax0815@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:22:52 +0100 Subject: [PATCH] Chore: Remove multi interface support (#1657) * streamline references to wg0 database wg0 name makes no sense anymore wg0 only in database, could be easily replaced, or support for custom name added * fix default key gen --- .gitignore | 4 - src/.gitignore | 2 +- src/drizzle.config.ts | 2 +- src/server/api/admin/hooks.get.ts | 5 +- src/server/api/admin/hooks.post.ts | 2 +- src/server/api/admin/interface/cidr.post.ts | 2 +- src/server/api/admin/interface/index.get.ts | 6 +- src/server/api/admin/interface/index.post.ts | 2 +- src/server/api/admin/userconfig.get.ts | 5 +- src/server/api/admin/userconfig.post.ts | 2 +- src/server/api/setup/5.post.ts | 2 +- .../database/repositories/hooks/service.ts | 12 +- .../database/repositories/interface/schema.ts | 1 + .../repositories/interface/service.ts | 27 ++-- .../repositories/userConfig/service.ts | 18 ++- src/server/database/sqlite.ts | 2 +- src/server/routes/metrics/prometheus.get.ts | 11 +- src/server/utils/WireGuard.ts | 116 ++++++++---------- 18 files changed, 107 insertions(+), 114 deletions(-) diff --git a/.gitignore b/.gitignore index e6fce2a6..f31b3e29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,2 @@ -/config -/wg0.conf -/wg0.json -/src/node_modules .DS_Store *.swp diff --git a/src/.gitignore b/src/.gitignore index 7e0a1716..2c23449e 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -23,4 +23,4 @@ logs .env.* !.env.example -wg0.db +wg-easy.db diff --git a/src/drizzle.config.ts b/src/drizzle.config.ts index 3d0550cc..257ccff7 100644 --- a/src/drizzle.config.ts +++ b/src/drizzle.config.ts @@ -5,6 +5,6 @@ export default defineConfig({ schema: './server/database/schema.ts', dialect: 'sqlite', dbCredentials: { - url: 'file:./wg0.db', + url: 'file:./wg-easy.db', }, }); diff --git a/src/server/api/admin/hooks.get.ts b/src/server/api/admin/hooks.get.ts index 470ba3c2..ec493ce0 100644 --- a/src/server/api/admin/hooks.get.ts +++ b/src/server/api/admin/hooks.get.ts @@ -1,7 +1,4 @@ export default definePermissionEventHandler(actions.ADMIN, async () => { - const hooks = await Database.hooks.get('wg0'); - if (!hooks) { - throw new Error('Hooks not found'); - } + const hooks = await Database.hooks.get(); return hooks; }); diff --git a/src/server/api/admin/hooks.post.ts b/src/server/api/admin/hooks.post.ts index 1a291438..e3c02bea 100644 --- a/src/server/api/admin/hooks.post.ts +++ b/src/server/api/admin/hooks.post.ts @@ -7,7 +7,7 @@ export default definePermissionEventHandler( event, validateZod(HooksUpdateSchema, event) ); - await Database.hooks.update('wg0', data); + await Database.hooks.update(data); await WireGuard.saveConfig(); return { success: true }; } diff --git a/src/server/api/admin/interface/cidr.post.ts b/src/server/api/admin/interface/cidr.post.ts index 7df34297..2d48d874 100644 --- a/src/server/api/admin/interface/cidr.post.ts +++ b/src/server/api/admin/interface/cidr.post.ts @@ -8,7 +8,7 @@ export default definePermissionEventHandler( validateZod(InterfaceCidrUpdateSchema, event) ); - await Database.interfaces.updateCidr('wg0', data); + await Database.interfaces.updateCidr(data); await WireGuard.saveConfig(); return { success: true }; } diff --git a/src/server/api/admin/interface/index.get.ts b/src/server/api/admin/interface/index.get.ts index 16536d6e..f060637f 100644 --- a/src/server/api/admin/interface/index.get.ts +++ b/src/server/api/admin/interface/index.get.ts @@ -1,9 +1,5 @@ export default definePermissionEventHandler(actions.ADMIN, async () => { - const wgInterface = await Database.interfaces.get('wg0'); - - if (!wgInterface) { - throw new Error('Interface not found'); - } + const wgInterface = await Database.interfaces.get(); return { ...wgInterface, diff --git a/src/server/api/admin/interface/index.post.ts b/src/server/api/admin/interface/index.post.ts index 11538d2e..24fd041a 100644 --- a/src/server/api/admin/interface/index.post.ts +++ b/src/server/api/admin/interface/index.post.ts @@ -7,7 +7,7 @@ export default definePermissionEventHandler( event, validateZod(InterfaceUpdateSchema, event) ); - await Database.interfaces.update('wg0', data); + await Database.interfaces.update(data); await WireGuard.saveConfig(); return { success: true }; } diff --git a/src/server/api/admin/userconfig.get.ts b/src/server/api/admin/userconfig.get.ts index d6315be3..81211580 100644 --- a/src/server/api/admin/userconfig.get.ts +++ b/src/server/api/admin/userconfig.get.ts @@ -1,7 +1,4 @@ export default definePermissionEventHandler(actions.ADMIN, async () => { - const userConfig = await Database.userConfigs.get('wg0'); - if (!userConfig) { - throw new Error('User config not found'); - } + const userConfig = await Database.userConfigs.get(); return userConfig; }); diff --git a/src/server/api/admin/userconfig.post.ts b/src/server/api/admin/userconfig.post.ts index afbc7499..623dd463 100644 --- a/src/server/api/admin/userconfig.post.ts +++ b/src/server/api/admin/userconfig.post.ts @@ -7,7 +7,7 @@ export default definePermissionEventHandler( event, validateZod(UserConfigUpdateSchema, event) ); - await Database.userConfigs.update('wg0', data); + await Database.userConfigs.update(data); await WireGuard.saveConfig(); return { success: true }; } diff --git a/src/server/api/setup/5.post.ts b/src/server/api/setup/5.post.ts index b62d42d9..4a2de39e 100644 --- a/src/server/api/setup/5.post.ts +++ b/src/server/api/setup/5.post.ts @@ -5,7 +5,7 @@ export default defineSetupEventHandler(async ({ event }) => { event, validateZod(UserConfigSetupSchema, event) ); - await Database.userConfigs.updateHostPort('wg0', host, port); + await Database.userConfigs.updateHostPort(host, port); await Database.general.setSetupStep(0); return { success: true }; }); diff --git a/src/server/database/repositories/hooks/service.ts b/src/server/database/repositories/hooks/service.ts index 64d6c27c..4401e172 100644 --- a/src/server/database/repositories/hooks/service.ts +++ b/src/server/database/repositories/hooks/service.ts @@ -20,15 +20,19 @@ export class HooksService { this.#statements = createPreparedStatement(db); } - get(infName: string) { - return this.#statements.get.execute({ interface: infName }); + async get() { + const hooks = await this.#statements.get.execute({ interface: 'wg0' }); + if (!hooks) { + throw new Error('Hooks not found'); + } + return hooks; } - update(infName: string, data: HooksUpdateType) { + update(data: HooksUpdateType) { return this.#db .update(hooks) .set(data) - .where(eq(hooks.id, infName)) + .where(eq(hooks.id, 'wg0')) .execute(); } } diff --git a/src/server/database/repositories/interface/schema.ts b/src/server/database/repositories/interface/schema.ts index aa7a5b5e..6f84bc69 100644 --- a/src/server/database/repositories/interface/schema.ts +++ b/src/server/database/repositories/interface/schema.ts @@ -13,6 +13,7 @@ export const wgInterface = sqliteTable('interfaces_table', { ipv4Cidr: text('ipv4_cidr').notNull(), ipv6Cidr: text('ipv6_cidr').notNull(), mtu: int().notNull(), + // does nothing yet enabled: int({ mode: 'boolean' }).notNull(), createdAt: text('created_at') .notNull() diff --git a/src/server/database/repositories/interface/service.ts b/src/server/database/repositories/interface/service.ts index 7361f617..5cc21c73 100644 --- a/src/server/database/repositories/interface/service.ts +++ b/src/server/database/repositories/interface/service.ts @@ -11,7 +11,6 @@ function createPreparedStatement(db: DBType) { get: db.query.wgInterface .findFirst({ where: eq(wgInterface.name, sql.placeholder('interface')) }) .prepare(), - getAll: db.query.wgInterface.findMany().prepare(), updateKeyPair: db .update(wgInterface) .set({ @@ -32,31 +31,33 @@ export class InterfaceService { this.#statements = createPreparedStatement(db); } - get(infName: string) { - return this.#statements.get.execute({ interface: infName }); - } - - getAll() { - return this.#statements.getAll.execute(); + async get() { + const wgInterface = await this.#statements.get.execute({ + interface: 'wg0', + }); + if (!wgInterface) { + throw new Error('Interface not found'); + } + return wgInterface; } - updateKeyPair(infName: string, privateKey: string, publicKey: string) { + updateKeyPair(privateKey: string, publicKey: string) { return this.#statements.updateKeyPair.execute({ - interface: infName, + interface: 'wg0', privateKey, publicKey, }); } - update(infName: string, data: InterfaceUpdateType) { + update(data: InterfaceUpdateType) { return this.#db .update(wgInterface) .set(data) - .where(eq(wgInterface.name, infName)) + .where(eq(wgInterface.name, 'wg0')) .execute(); } - updateCidr(infName: string, data: InterfaceCidrUpdateType) { + updateCidr(data: InterfaceCidrUpdateType) { if (!isCidr(data.ipv4Cidr) || !isCidr(data.ipv6Cidr)) { throw new Error('Invalid CIDR'); } @@ -64,7 +65,7 @@ export class InterfaceService { await tx .update(wgInterface) .set(data) - .where(eq(wgInterface.name, infName)) + .where(eq(wgInterface.name, 'wg0')) .execute(); const clients = await tx.query.client.findMany().execute(); diff --git a/src/server/database/repositories/userConfig/service.ts b/src/server/database/repositories/userConfig/service.ts index 42bdb99b..9ecc7692 100644 --- a/src/server/database/repositories/userConfig/service.ts +++ b/src/server/database/repositories/userConfig/service.ts @@ -28,23 +28,29 @@ export class UserConfigService { this.#statements = createPreparedStatement(db); } - get(infName: string) { - return this.#statements.get.execute({ interface: infName }); + async get() { + const userConfig = await this.#statements.get.execute({ interface: 'wg0' }); + + if (!userConfig) { + throw new Error('User config not found'); + } + + return userConfig; } - updateHostPort(infName: string, host: string, port: number) { + updateHostPort(host: string, port: number) { return this.#statements.updateHostPort.execute({ - interface: infName, + interface: 'wg0', host, port, }); } - update(infName: string, data: UserConfigUpdateType) { + update(data: UserConfigUpdateType) { return this.#db .update(userConfig) .set(data) - .where(eq(userConfig.id, infName)) + .where(eq(userConfig.id, 'wg0')) .execute(); } } diff --git a/src/server/database/sqlite.ts b/src/server/database/sqlite.ts index 222fea3b..c1062e4c 100644 --- a/src/server/database/sqlite.ts +++ b/src/server/database/sqlite.ts @@ -14,7 +14,7 @@ import { OneTimeLinkService } from './repositories/oneTimeLink/service'; const DB_DEBUG = debug('Database'); -const client = createClient({ url: 'file:/etc/wireguard/wg0.db' }); +const client = createClient({ url: 'file:/etc/wireguard/wg-easy.db' }); const db = drizzle({ client, schema }); export async function connect() { diff --git a/src/server/routes/metrics/prometheus.get.ts b/src/server/routes/metrics/prometheus.get.ts index 762e6663..bf4d9e80 100644 --- a/src/server/routes/metrics/prometheus.get.ts +++ b/src/server/routes/metrics/prometheus.get.ts @@ -4,6 +4,7 @@ export default defineMetricsHandler('prometheus', async ({ event }) => { }); async function getPrometheusResponse() { + const wgInterface = await Database.interfaces.get(); const clients = await WireGuard.getClients(); let wireguardPeerCount = 0; let wireguardEnabledPeersCount = 0; @@ -21,7 +22,7 @@ async function getPrometheusResponse() { wireguardConnectedPeersCount++; } - const id = `interface="wg0",enabled="${client.enabled}",ipv4Address="${client.ipv4Address}",ipv6Address="${client.ipv6Address}",name="${client.name}"`; + const id = `interface="${wgInterface.name}",enabled="${client.enabled}",ipv4Address="${client.ipv4Address}",ipv6Address="${client.ipv6Address}",name="${client.name}"`; wireguardSentBytes.push( `wireguard_sent_bytes{${id}} ${client.transferTx ?? 0}` @@ -35,20 +36,22 @@ async function getPrometheusResponse() { ); } + const id = `interface="${wgInterface.name}"`; + const returnText = [ '# HELP wg-easy and wireguard metrics', '', '# HELP wireguard_configured_peers', '# TYPE wireguard_configured_peers gauge', - `wireguard_configured_peers{interface="wg0"} ${wireguardPeerCount}`, + `wireguard_configured_peers{${id}} ${wireguardPeerCount}`, '', '# HELP wireguard_enabled_peers', '# TYPE wireguard_enabled_peers gauge', - `wireguard_enabled_peers{interface="wg0"} ${wireguardEnabledPeersCount}`, + `wireguard_enabled_peers{${id}} ${wireguardEnabledPeersCount}`, '', '# HELP wireguard_connected_peers', '# TYPE wireguard_connected_peers gauge', - `wireguard_connected_peers{interface="wg0"} ${wireguardConnectedPeersCount}`, + `wireguard_connected_peers{${id}} ${wireguardConnectedPeersCount}`, '', '# HELP wireguard_sent_bytes Bytes sent to the peer', '# TYPE wireguard_sent_bytes counter', diff --git a/src/server/utils/WireGuard.ts b/src/server/utils/WireGuard.ts index 86cd338e..2b37b2d1 100644 --- a/src/server/utils/WireGuard.ts +++ b/src/server/utils/WireGuard.ts @@ -2,6 +2,7 @@ import fs from 'node:fs/promises'; import debug from 'debug'; import QRCode from 'qrcode'; import type { ID } from '#db/schema'; +import type { InterfaceType } from '#db/repositories/interface/types'; const WG_DEBUG = debug('WireGuard'); @@ -10,21 +11,19 @@ class WireGuard { * Save and sync config */ async saveConfig() { - await this.#saveWireguardConfig('wg0'); - await this.#syncWireguardConfig('wg0'); + const wgInterface = await Database.interfaces.get(); + await this.#saveWireguardConfig(wgInterface); + await this.#syncWireguardConfig(wgInterface); } /** - * Generates and saves WireGuard config from database as wg0 + * Generates and saves WireGuard config from database + * + * Make sure to pass an updated InterfaceType object */ - async #saveWireguardConfig(infName: string) { - const wgInterface = await Database.interfaces.get(infName); + async #saveWireguardConfig(wgInterface: InterfaceType) { 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 hooks = await Database.hooks.get(); const result = []; result.push(wg.generateServerInterface(wgInterface, hooks)); @@ -37,19 +36,24 @@ class WireGuard { } WG_DEBUG('Saving Config...'); - await fs.writeFile(`/etc/wireguard/${infName}.conf`, result.join('\n\n'), { - mode: 0o600, - }); + await fs.writeFile( + `/etc/wireguard/${wgInterface.name}.conf`, + result.join('\n\n'), + { + mode: 0o600, + } + ); WG_DEBUG('Config saved successfully.'); } - async #syncWireguardConfig(infName: string) { + async #syncWireguardConfig(wgInterface: InterfaceType) { WG_DEBUG('Syncing Config...'); - await wg.sync(infName); + await wg.sync(wgInterface.name); WG_DEBUG('Config synced successfully.'); } async getClients() { + const wgInterface = await Database.interfaces.get(); const dbClients = await Database.clients.getAll(); const clients = dbClients.map((client) => ({ ...client, @@ -60,7 +64,7 @@ class WireGuard { })); // Loop WireGuard status - const dump = await wg.dump('wg0'); + const dump = await wg.dump(wgInterface.name); dump.forEach( ({ publicKey, latestHandshakeAt, endpoint, transferRx, transferTx }) => { const client = clients.find((client) => client.publicKey === publicKey); @@ -79,12 +83,8 @@ class WireGuard { } async getClientConfiguration({ clientId }: { clientId: ID }) { - 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 wgInterface = await Database.interfaces.get(); + const userConfig = await Database.userConfigs.get(); const client = await Database.clients.get(clientId); @@ -124,47 +124,41 @@ class WireGuard { async Startup() { WG_DEBUG('Starting WireGuard...'); - const wgInterfaces = await Database.interfaces.getAll(); - for (const wgInterface of wgInterfaces) { - if (wgInterface.enabled !== true) { - continue; - } - // default interface has no keys + // let as it has to refetch if keys change + let wgInterface = await Database.interfaces.get(); + + // default interface has no keys + if ( + wgInterface.privateKey === '---default---' && + wgInterface.publicKey === '---default---' + ) { + WG_DEBUG('Generating new Wireguard Keys...'); + const privateKey = await wg.generatePrivateKey(); + const publicKey = await wg.getPublicKey(privateKey); + + await Database.interfaces.updateKeyPair(privateKey, publicKey); + wgInterface = await Database.interfaces.get(); + WG_DEBUG('New Wireguard Keys generated successfully.'); + } + WG_DEBUG(`Starting Wireguard Interface ${wgInterface.name}...`); + await this.#saveWireguardConfig(wgInterface); + await wg.down(wgInterface.name).catch(() => {}); + await wg.up(wgInterface.name).catch((err) => { if ( - wgInterface.privateKey === '---default---' && - wgInterface.publicKey === '---default---' + err && + err.message && + err.message.includes(`Cannot find device "${wgInterface.name}"`) ) { - WG_DEBUG('Generating new Wireguard Keys...'); - const privateKey = await wg.generatePrivateKey(); - const publicKey = await wg.getPublicKey(privateKey); - - await Database.interfaces.updateKeyPair( - wgInterface.name, - privateKey, - publicKey + 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 } ); - WG_DEBUG('New Wireguard Keys generated successfully.'); } - WG_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; - }); - await this.#syncWireguardConfig(wgInterface.name); - WG_DEBUG(`Wireguard Interface ${wgInterface.name} started successfully.`); - } + throw err; + }); + await this.#syncWireguardConfig(wgInterface); + WG_DEBUG(`Wireguard Interface ${wgInterface.name} started successfully.`); WG_DEBUG('Starting Cron Job...'); await this.startCronJob(); @@ -184,10 +178,8 @@ class WireGuard { // Shutdown wireguard async Shutdown() { - const wgInterfaces = await Database.interfaces.getAll(); - for (const wgInterface of wgInterfaces) { - await wg.down(wgInterface.name).catch(() => {}); - } + const wgInterface = await Database.interfaces.get(); + await wg.down(wgInterface.name).catch(() => {}); } async cronJob() {