This commit is contained in:
Shahrad Elahi
2023-12-22 00:34:07 +03:30
parent ac5275168c
commit 0dc9e89b6c
16 changed files with 156 additions and 85 deletions

View File

@@ -19,13 +19,8 @@ let logger: Logger = pino(
pino.multistream([prettyStream]),
);
fsTouch(LOG_FILE_PATH)
.then(() => fsAccess(LOG_FILE_PATH))
.then((ok) => {
if (!ok) {
logger.warn('Log file is not accessible');
return;
}
if (fsAccess(LOG_FILE_PATH)) {
fsTouch(LOG_FILE_PATH).then(() => {
logger = pino(
{
level: LOG_LEVEL,
@@ -37,7 +32,9 @@ fsTouch(LOG_FILE_PATH)
}),
]),
);
})
.catch(console.error);
});
} else {
logger.warn('Log file is not accessible');
}
export default logger;

View File

@@ -1,20 +1,21 @@
import { execaCommand } from 'execa';
import { execa } from 'execa';
import logger from '$lib/logger';
import { ip } from 'node-netkit';
export default class Network {
public static async dropInterface(inet: string) {
await execaCommand(`ip link delete dev ${inet}`);
await execa(`ip link delete dev ${inet}`, { shell: true });
}
public static async defaultInterface(): Promise<string> {
const { stdout: o } = await execaCommand(`ip route list default | awk '{print $5}'`);
return o.trim();
const route = await ip.route.defaultRoute();
if (!route) throw new Error('No default route found');
return route.dev;
}
public static async interfaceExists(inet: string): Promise<boolean> {
try {
const { stdout: o } = await execaCommand(`ip link show | grep ${inet}`);
console.log(o);
const { stdout: o } = await execa(`ip link show | grep ${inet}`, { shell: true });
return o.trim() !== '';
} catch (e) {
logger.debug('Interface does not exist:', inet);
@@ -24,12 +25,13 @@ export default class Network {
public static async inUsePorts(): Promise<number[]> {
const ports = [];
const { stdout: output } = await execaCommand(
const { stdout: output } = await execa(
`netstat -tulpn | grep LISTEN | awk '{print $4}' | awk -F ':' '{print $NF}'`,
{ shell: true },
);
for (const line of output.split('\n')) {
const clean = Number(line.trim());
if (!isNaN(clean)) ports.push(clean);
if (!isNaN(clean) && clean !== 0) ports.push(clean);
}
return ports;

View File

@@ -16,7 +16,7 @@ export function setClient(redis: RedisClient): void {
client = redis;
}
if (process.env.NODE_ENV !== 'build') {
if (process.env.NODE_ENV && ['development', 'production'].includes(process.env.NODE_ENV)) {
setClient(
new Redis({
port: 6479,

View File

@@ -10,7 +10,7 @@ import logger from '$lib/logger';
import { sha256 } from '$lib/hash';
import { fsAccess } from '$lib/fs-extra';
import { getClient } from '$lib/redis';
import { execaCommand } from 'execa';
import { execa } from 'execa';
export class WGServer {
readonly id: string;
@@ -51,7 +51,7 @@ export class WGServer {
const server = await this.get();
if (await Network.interfaceExists(`wg${server.confId}`)) {
await execaCommand(`wg-quick down wg${server.confId}`);
await execa(`wg-quick down wg${server.confId}`, { shell: true });
}
await this.update({ status: 'down' });
@@ -70,10 +70,10 @@ export class WGServer {
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 execa(`wg-quick down wg${server.confId}`, { shell: true });
}
await execaCommand(`wg-quick up wg${server.confId}`);
await execa(`wg-quick up wg${server.confId}`, { shell: true });
await this.update({ status: 'up' });
return true;
@@ -137,7 +137,7 @@ export class WGServer {
async isUp(): Promise<boolean> {
const server = await this.get();
try {
const res = await execaCommand(`wg show wg${server.confId}`);
const res = await execa(`wg show wg${server.confId}`, { shell: true });
return res.stdout.includes('wg');
} catch (e) {
return false;
@@ -158,7 +158,9 @@ export class WGServer {
return usages;
}
const { stdout, stderr } = await execaCommand(`wg show wg${server.confId} transfer`);
const { stdout, stderr } = await execa(`wg show wg${server.confId} transfer`, {
shell: true,
});
if (stderr) {
logger.warn(`WgServer: GetUsage: ${stderr}`);
return usages;
@@ -502,9 +504,11 @@ function wgPeersStr(configId: number): string[] {
}
export async function generateWgKey(): Promise<WgKey> {
const { stdout: privateKey } = await execaCommand('wg genkey');
const { stdout: publicKey } = await execaCommand(`echo ${privateKey} | wg pubkey`);
const { stdout: preSharedKey } = await execaCommand('wg genkey');
const { stdout: privateKey } = await execa('wg genkey', { shell: true });
const { stdout: publicKey } = await execa(`echo ${privateKey} | wg pubkey`, {
shell: true,
});
const { stdout: preSharedKey } = await execa('wg genkey', { shell: true });
return { privateKey, publicKey, preSharedKey };
}
@@ -673,7 +677,9 @@ export async function findServer(
export async function makeWgIptables(s: WgServer): Promise<{ up: string; down: string }> {
const inet = await Network.defaultInterface();
const { stdout: inet_address } = await execaCommand(`hostname -i | awk '{print $1}'`);
const { stdout: inet_address } = await execa(`hostname -i | awk '{print $1}'`, {
shell: true,
});
const source = `${s.address}/24`;
const wg_inet = `wg${s.confId}`;

View File

@@ -1,6 +1,8 @@
<script lang="ts">
import '../app.css';
import '$lib/assets/fontawesome/index.css';
import { Toaster } from 'svelte-french-toast';
</script>
<slot />
<Toaster />

View File

@@ -28,12 +28,12 @@ export const actions: Actions = {
const server = await findServer(serverId ?? '');
if (!server) {
logger.error('Server not found');
logger.error('Actions: RenameServer: Server not found');
return error(404, 'Not found');
}
if (!NameSchema.safeParse(name).success) {
logger.error('Peer name is invalid');
logger.error('Actions: RenameServer: Server name is invalid');
return error(400, 'Bad Request');
}
@@ -69,11 +69,11 @@ export const actions: Actions = {
});
return {
ok: true,
form,
serverId,
};
} catch (e: any) {
logger.error('Exception:', e);
} catch (e) {
logger.error(e);
return setError(form, 'Unhandled Exception');
}
},

View File

@@ -19,22 +19,19 @@
FormSwitch,
FormValidation,
} from '$lib/components/ui/form';
import { goto } from '$app/navigation';
import { FormItem } from '$lib/components/ui/form/index.js';
import { cn } from '$lib/utils';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '$lib/components/ui/collapsible';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '$lib/components/ui/collapsible';
import { Button } from '$lib/components/ui/button';
import toast from 'svelte-french-toast';
let loading: boolean = false;
let dialogOpen = false;
let form: SuperValidated<CreateServerSchemaType>;
</script>
<Dialog>
<Dialog bind:open={dialogOpen}>
<DialogTrigger asChild let:builder>
<slot {builder} />
</DialogTrigger>
@@ -50,7 +47,7 @@
method={'POST'}
let:config
options={{
onSubmit: (s) => {
onSubmit: () => {
loading = true;
},
onError: (e) => {
@@ -60,9 +57,11 @@
onResult: ({ result }) => {
loading = false;
if (result.type === 'success') {
goto(`/${result.data.serverId}`);
dialogOpen = false;
toast.success('Server created successfully!');
} else {
console.error('Server-failure: Result:', result);
toast.error('Server failed to create.');
}
},
}}
@@ -89,7 +88,7 @@
<FormLabel>Port</FormLabel>
<FormInput placeholder={'e.g. 51820'} type={'text'} />
<FormDescription
>This is the port that the WireGuard server will listen on.</FormDescription
>This is the port that the WireGuard server will listen on.</FormDescription
>
<FormValidation />
</FormItem>
@@ -119,7 +118,7 @@
<FormLabel>DNS</FormLabel>
<FormInput placeholder={'e.g. 1.1.1.1'} type={'text'} />
<FormDescription
>Optional. This is the DNS server that will be pushed to clients.</FormDescription
>Optional. This is the DNS server that will be pushed to clients.</FormDescription
>
<FormValidation />
</FormItem>

View File

@@ -10,26 +10,28 @@ export const load: PageServerLoad = async ({ params }) => {
const { serverId } = params;
const exists = await WGServer.exists(serverId ?? '');
if (exists) {
const wg = new WGServer(serverId);
const server = await wg.get();
if (server.status === 'up') {
const hasInterface = await wg.isUp();
if (!hasInterface) {
await wg.start();
}
}
const usage = await wg.getUsage();
return {
server,
usage,
};
if (!exists) {
logger.warn(`Server not found. Redirecting to home page. ServerId: ${serverId}`);
throw redirect(303, '/');
}
throw error(404, 'Not found');
const wg = new WGServer(serverId);
const server = await wg.get();
if (server.status === 'up') {
const hasInterface = await wg.isUp();
if (!hasInterface) {
logger.debug(`Interface not found. Starting WireGuard. ServerId: ${serverId}`);
await wg.start();
}
}
const usage = await wg.getUsage();
return {
server,
usage,
};
};
export const actions: Actions = {
@@ -98,19 +100,19 @@ export const actions: Actions = {
const wg = new WGServer(server.id);
await wg.remove();
}
return { ok: true };
} catch (e) {
console.error('Exception:', e);
logger.error(e);
throw error(500, 'Unhandled Exception');
}
return redirect(303, '/');
},
'change-server-state': async ({ request, params }) => {
const { serverId } = params;
const server = await findServer(serverId ?? '');
if (!server) {
logger.error('Action: ChangeState: Server not found');
logger.warn(`Action: ChangeState: Server not found. ServerId: ${serverId}`);
throw redirect(303, '/');
}
@@ -140,19 +142,20 @@ export const actions: Actions = {
break;
}
}
return { ok: true };
} catch (e) {
logger.error({
message: 'Exception: ChangeState',
message: `Exception: ChangeState. ServerId: ${serverId}`,
exception: e,
});
throw error(500, 'Unhandled Exception');
}
return { ok: true };
},
create: async (event) => {
const form = await superValidate(event, CreatePeerSchema);
if (!form.valid) {
logger.warn('CreatePeer: Bad Request: failed to validate form');
return setError(form, 'Bad Request');
}
@@ -162,13 +165,13 @@ export const actions: Actions = {
try {
const server = await findServer(serverId ?? '');
if (!server) {
console.error('Server not found');
logger.error(`Server not found. ServerId: ${serverId}`);
return setError(form, 'Server not found');
}
const freeAddress = await WGServer.getFreePeerIp(server.id);
if (!freeAddress) {
console.error(`ERR: ServerId: ${serverId};`, 'No free addresses;');
logger.error(`No free addresses. ServerId: ${serverId}`);
return setError(form, 'No free addresses');
}
@@ -186,13 +189,16 @@ export const actions: Actions = {
});
if (!addedPeer) {
console.error(`ERR: ServerId: ${serverId};`, 'Failed to add peer;');
logger.error(`Failed to add peer. ServerId: ${serverId}`);
return setError(form, 'Failed to add peer');
}
return { ok: true };
return { form };
} catch (e) {
console.error('Exception:', e);
logger.error({
message: `Exception: CreatePeer. ServerId: ${serverId}`,
exception: e,
});
return setError(form, 'Unhandled Exception');
}
},

View File

@@ -14,27 +14,27 @@
FormButton,
FormField,
FormInput,
FormItem,
FormLabel,
FormValidation,
} from '$lib/components/ui/form';
import { FormItem } from '$lib/components/ui/form/index.js';
import { cn } from '$lib/utils';
import { invalidateAll } from '$app/navigation';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
import toast from 'svelte-french-toast';
let loading: boolean = false;
let dialogOpen = false;
let form: SuperValidated<CreatePeerSchemaType>;
const handleSuccess = async () => {
await invalidateAll();
dispatch('close', 'OK');
toast.success('Peer created!');
dialogOpen = false;
};
</script>
<Dialog>
<Dialog bind:open={dialogOpen}>
<DialogTrigger asChild let:builder>
<slot {builder} />
</DialogTrigger>
@@ -50,7 +50,7 @@
method={'POST'}
let:config
options={{
onSubmit: (s) => {
onSubmit: () => {
loading = true;
},
onError: (e) => {
@@ -61,6 +61,7 @@
if (result.type === 'success') {
handleSuccess();
} else {
toast.error('Failed to create peer');
console.error('Server-failure: Result:', result);
}
loading = false;

View File

@@ -8,7 +8,7 @@ export const GET: RequestHandler = async () => {
// if the host is not set, then we are using the server's public IP
if (!WG_HOST) {
const { stdout: resp } = await execaCommand('curl -s ifconfig.me');
const { stdout: resp } = await execa('curl -s ifconfig.me', { shell: true });
WG_HOST = resp.trim();
}