Browse Source

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
pull/1657/head
Bernd Storath 6 months ago
parent
commit
9ec6c4af9c
  1. 4
      .gitignore
  2. 2
      src/.gitignore
  3. 2
      src/drizzle.config.ts
  4. 5
      src/server/api/admin/hooks.get.ts
  5. 2
      src/server/api/admin/hooks.post.ts
  6. 2
      src/server/api/admin/interface/cidr.post.ts
  7. 6
      src/server/api/admin/interface/index.get.ts
  8. 2
      src/server/api/admin/interface/index.post.ts
  9. 5
      src/server/api/admin/userconfig.get.ts
  10. 2
      src/server/api/admin/userconfig.post.ts
  11. 2
      src/server/api/setup/5.post.ts
  12. 12
      src/server/database/repositories/hooks/service.ts
  13. 1
      src/server/database/repositories/interface/schema.ts
  14. 27
      src/server/database/repositories/interface/service.ts
  15. 18
      src/server/database/repositories/userConfig/service.ts
  16. 2
      src/server/database/sqlite.ts
  17. 11
      src/server/routes/metrics/prometheus.get.ts
  18. 112
      src/server/utils/WireGuard.ts

4
.gitignore

@ -1,6 +1,2 @@
/config
/wg0.conf
/wg0.json
/src/node_modules
.DS_Store
*.swp

2
src/.gitignore

@ -23,4 +23,4 @@ logs
.env.*
!.env.example
wg0.db
wg-easy.db

2
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',
},
});

5
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;
});

2
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 };
}

2
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 };
}

6
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,

2
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 };
}

5
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;
});

2
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 };
}

2
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 };
});

12
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();
}
}

1
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()

27
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();

18
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();
}
}

2
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() {

11
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',

112
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,17 @@ 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
*/
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 +34,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 +62,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 +81,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 +122,39 @@ 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
const 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);
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 +174,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() {

Loading…
Cancel
Save