using execa for accessing cli tools

This commit is contained in:
Shahrad Elahi 2023-12-21 18:28:22 +03:30
parent 9dd427f90c
commit ac5275168c
6 changed files with 46 additions and 100 deletions

View File

@ -1,43 +1,31 @@
import Shell from '$lib/shell';
import { execaCommand } from 'execa';
import logger from '$lib/logger';
export default class Network {
public static async createInterface(inet: string, address: string): Promise<boolean> {
// First, check if the interface already exists.
const interfaces = await Shell.exec(`ip link show | grep ${inet}`, true);
if (interfaces.includes(`${inet}`)) {
console.error(`failed to create interface, ${inet} already exists!`);
return false;
}
const o2 = await Shell.exec(`ip address add dev ${inet} ${address}`);
// check if it has any error
if (o2 !== '') {
console.error(`failed to assign ip to interface, ${o2}`);
console.log(`removing interface ${inet} due to errors`);
await Shell.exec(`ip link delete dev ${inet}`, true);
return false;
}
return true;
}
public static async dropInterface(inet: string) {
await Shell.exec(`ip link delete dev ${inet}`, true);
await execaCommand(`ip link delete dev ${inet}`);
}
public static async defaultInterface(): Promise<string> {
return await Shell.exec(`ip route list default | awk '{print $5}'`);
const { stdout: o } = await execaCommand(`ip route list default | awk '{print $5}'`);
return o.trim();
}
public static async checkInterfaceExists(inet: string): Promise<boolean> {
return await Shell.exec(`ip link show | grep ${inet}`, true).then((o) => o.trim() !== '');
public static async interfaceExists(inet: string): Promise<boolean> {
try {
const { stdout: o } = await execaCommand(`ip link show | grep ${inet}`);
console.log(o);
return o.trim() !== '';
} catch (e) {
logger.debug('Interface does not exist:', inet);
return false;
}
}
public static async getInUsePorts(): Promise<number[]> {
public static async inUsePorts(): Promise<number[]> {
const ports = [];
const output = await Shell.exec(
const { stdout: output } = await execaCommand(
`netstat -tulpn | grep LISTEN | awk '{print $4}' | awk -F ':' '{print $NF}'`,
true,
);
for (const line of output.split('\n')) {
const clean = Number(line.trim());

View File

@ -1,42 +0,0 @@
import { exec } from 'child_process';
import logger from '$lib/logger';
export default class Shell {
public static async exec(
command: string,
safe: boolean = false,
...args: string[]
): Promise<string> {
if (process.platform !== 'linux') {
throw new Error('This program is not meant to run non UNIX systems');
}
return new Promise(async (resolve, reject) => {
const cmd = `${command}${args.length > 0 ? ` ${args.join(' ')}` : ''}`;
exec(cmd, { shell: 'bash' }, (err, stdout, stderr) => {
if (err) {
const message = `Command Failed: ${JSON.stringify(
{
cmd,
code: err.code,
killed: err.killed,
stderr,
},
null,
2,
)}`;
if (safe) {
logger.debug(message);
return resolve(stderr);
}
logger.error(message);
return reject(err);
}
return resolve(String(stdout).trim());
});
});
}
}

View File

@ -3,7 +3,6 @@ import path from 'path';
import deepmerge from 'deepmerge';
import type { Peer, WgKey, WgServer } from '$lib/typings';
import Network from '$lib/network';
import Shell from '$lib/shell';
import { WG_PATH, WG_SEVER_PATH } from '$lib/constants';
import { dynaJoin, isJson } from '$lib/utils';
import { getPeerConf } from '$lib/wireguard/utils';
@ -51,8 +50,8 @@ export class WGServer {
async stop(): Promise<boolean> {
const server = await this.get();
if (await Network.checkInterfaceExists(`wg${server.confId}`)) {
await Shell.exec(`wg-quick down wg${server.confId}`, true);
if (await Network.interfaceExists(`wg${server.confId}`)) {
await execaCommand(`wg-quick down wg${server.confId}`);
}
await this.update({ status: 'down' });
@ -67,11 +66,14 @@ export class WGServer {
await this.writeConfigFile(server);
}
if (await Network.checkInterfaceExists(`wg${server.confId}`)) {
await Shell.exec(`wg-quick down wg${server.confId}`, true);
const isAlreadyUp = await this.isUp();
logger.debug('WGServer:Start: isAlreadyUp:', isAlreadyUp);
if (isAlreadyUp) {
logger.debug('WGServer:Start: interface already up... taking down');
await execaCommand(`wg-quick down wg${server.confId}`);
}
await Shell.exec(`wg-quick up wg${server.confId}`);
await execaCommand(`wg-quick up wg${server.confId}`);
await this.update({ status: 'up' });
return true;
@ -132,7 +134,7 @@ export class WGServer {
await this.update({ confHash: getConfigHash(wg.confId) });
}
async hasInterface(): Promise<boolean> {
async isUp(): Promise<boolean> {
const server = await this.get();
try {
const res = await execaCommand(`wg show wg${server.confId}`);
@ -144,7 +146,7 @@ export class WGServer {
async getUsage(): Promise<WgUsage> {
const server = await this.get();
const hasInterface = await this.hasInterface();
const hasInterface = await this.isUp();
const usages: WgUsage = {
total: { rx: 0, tx: 0 },
@ -371,14 +373,6 @@ function resolveConfigPath(confId: number): string {
return path.resolve(path.join(WG_PATH, `wg${confId}.conf`));
}
/**
* This function is for checking out WireGuard server is running
*/
async function wgCheckout(configId: number): Promise<boolean> {
const res = await Shell.exec(`ip link show | grep wg${configId}`, true);
return res.includes(`wg${configId}`);
}
export async function readWgConf(configId: number): Promise<WgServer> {
const confPath = resolveConfigPath(configId);
const conf = fs.readFileSync(confPath, 'utf-8');
@ -462,7 +456,10 @@ export async function readWgConf(configId: number): Promise<WgServer> {
reachedPeers = true;
}
}
server.status = (await wgCheckout(configId)) ? 'up' : 'down';
const hasInterface = await Network.interfaceExists(`wg${configId}`);
server.status = hasInterface ? 'up' : 'down';
return server;
}
@ -505,9 +502,9 @@ function wgPeersStr(configId: number): string[] {
}
export async function generateWgKey(): Promise<WgKey> {
const privateKey = await Shell.exec('wg genkey');
const publicKey = await Shell.exec(`echo ${privateKey} | wg pubkey`);
const preSharedKey = await Shell.exec('wg genkey');
const { stdout: privateKey } = await execaCommand('wg genkey');
const { stdout: publicKey } = await execaCommand(`echo ${privateKey} | wg pubkey`);
const { stdout: preSharedKey } = await execaCommand('wg genkey');
return { privateKey, publicKey, preSharedKey };
}
@ -579,10 +576,10 @@ export async function generateWgServer(config: GenerateWgServerParams): Promise<
await wg.update({ confHash: getConfigHash(confId) });
// to ensure interface does not exists
await Shell.exec(`wg-quick down wg${confId}`, true);
await wg.stop();
// restart WireGuard
await Shell.exec(`wg-quick up wg${confId}`);
await wg.start();
// return server id
return uuid;
@ -595,7 +592,7 @@ export async function isIPReserved(ip: string): Promise<boolean> {
export async function isPortReserved(port: number): Promise<boolean> {
const inUsePorts = [
await Network.getInUsePorts(),
await Network.inUsePorts(),
(await getServers()).map((s) => Number(s.listen)),
].flat();
return inUsePorts.includes(port);
@ -676,7 +673,7 @@ export async function findServer(
export async function makeWgIptables(s: WgServer): Promise<{ up: string; down: string }> {
const inet = await Network.defaultInterface();
const inet_address = await Shell.exec(`hostname -i | awk '{print $1}'`);
const { stdout: inet_address } = await execaCommand(`hostname -i | awk '{print $1}'`);
const source = `${s.address}/24`;
const wg_inet = `wg${s.confId}`;

View File

@ -15,7 +15,7 @@ export const load: PageServerLoad = async ({ params }) => {
const server = await wg.get();
if (server.status === 'up') {
const hasInterface = await wg.hasInterface();
const hasInterface = await wg.isUp();
if (!hasInterface) {
await wg.start();
}
@ -143,7 +143,10 @@ export const actions: Actions = {
return { ok: true };
} catch (e) {
logger.error('Exception: ChangeState:', e);
logger.error({
message: 'Exception: ChangeState',
exception: e,
});
throw error(500, 'Unhandled Exception');
}
},

View File

@ -7,7 +7,7 @@ export const GET: RequestHandler = async () => {
for (const { id } of await getServers()) {
const wg = new WGServer(id);
const server = await wg.get();
const hasInterface = await wg.hasInterface();
const hasInterface = await wg.isUp();
// If the server is up and the interface doesn't exist, start it
if (server.status === 'up' && !hasInterface) {

View File

@ -1,14 +1,14 @@
import type { RequestHandler } from '@sveltejs/kit';
import Shell from '$lib/shell';
import 'dotenv/config';
import logger from '$lib/logger';
import { execaCommand } from 'execa';
import 'dotenv/config';
export const GET: RequestHandler = async () => {
let { WG_HOST } = process.env;
// if the host is not set, then we are using the server's public IP
if (!WG_HOST) {
const resp = await Shell.exec('curl -s ifconfig.me', true);
const { stdout: resp } = await execaCommand('curl -s ifconfig.me');
WG_HOST = resp.trim();
}