install pino logger and update deps

This commit is contained in:
Shahrad Elahi 2023-12-19 08:30:54 +03:30
parent 0706f13216
commit 75013113b0
19 changed files with 1171 additions and 242 deletions

View File

@ -15,7 +15,7 @@ RUN apk update && apk upgrade \
# Install required packages # Install required packages
&& apk add -U --no-cache \ && apk add -U --no-cache \
iproute2 iptables net-tools \ iproute2 iptables net-tools \
screen vim curl bash \ screen curl bash \
wireguard-tools \ wireguard-tools \
openssl \ openssl \
tor \ tor \
@ -63,8 +63,9 @@ HEALTHCHECK --interval=60s --timeout=3s --start-period=20s --retries=3 \
RUN mkdir -p /data && chmod 700 /data RUN mkdir -p /data && chmod 700 /data
RUN mkdir -p /etc/torrc.d && chmod -R 400 /etc/torrc.d RUN mkdir -p /etc/torrc.d && chmod -R 400 /etc/torrc.d
RUN mkdir -p /var/vlogs && chmod -R 600 /var/vlogs && touch /var/vlogs/web.log
VOLUME ["/etc/torrc.d", "/data"] VOLUME ["/etc/torrc.d", "/data", "/var/vlogs"]
# run the app # run the app
EXPOSE 3000/tcp EXPOSE 3000/tcp

View File

@ -47,8 +47,9 @@ ENTRYPOINT ["/entrypoint.sh"]
RUN mkdir -p /data && chmod 700 /data RUN mkdir -p /data && chmod 700 /data
RUN mkdir -p /etc/torrc.d && chmod -R 400 /etc/torrc.d RUN mkdir -p /etc/torrc.d && chmod -R 400 /etc/torrc.d
RUN mkdir -p /var/vlogs && chmod -R 600 /var/vlogs && touch /var/vlogs/web.log
VOLUME ["/etc/torrc.d", "/data"] VOLUME ["/etc/torrc.d", "/data", "/var/vlogs"]
# run the appc # run the appc
EXPOSE 5173/tcp EXPOSE 5173/tcp

5
web/.mocharc.json Normal file
View File

@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/mocharc.json",
"require": ["tsx", "chai/register-expect"],
"timeout": 10000
}

View File

@ -8,43 +8,49 @@
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test": "vitest", "test": "mocha",
"lint": "prettier --plugin-search-dir . --check .", "lint": "prettier --plugin-search-dir . --check .",
"format": "prettier --plugin-search-dir . --write .", "format": "prettier --plugin-search-dir . --write .",
"start": "node ./build/index.js" "start": "node ./build/index.js"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-node": "^1.3.1", "@sveltejs/adapter-node": "^2.0.0",
"@sveltejs/kit": "^1.27.7", "@sveltejs/kit": "^2.0.3",
"@types/crypto-js": "^4.2.1", "@types/chai": "^4.3.11",
"@types/jsonwebtoken": "^9.0.5", "@types/jsonwebtoken": "^9.0.5",
"@types/node": "^20.10.4", "@types/mocha": "^10.0.6",
"@types/node": "^20.10.5",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"chai": "^4.3.10",
"mocha": "^10.2.0",
"postcss": "^8.4.32", "postcss": "^8.4.32",
"postcss-load-config": "^5.0.2", "postcss-load-config": "^4.0.2",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2", "prettier-plugin-svelte": "^3.1.2",
"svelte": "^4.2.8", "svelte": "^4.2.8",
"svelte-check": "^3.6.2", "svelte-check": "^3.6.2",
"sveltekit-superforms": "^1.11.0", "svelte-preprocess": "^5.1.3",
"tailwindcss": "^3.3.6", "sveltekit-superforms": "^1.12.0",
"tailwindcss": "^3.3.7",
"tslib": "^2.6.2", "tslib": "^2.6.2",
"tsx": "^4.6.2",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vite": "^5.0.7", "vite": "^5.0.10",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"bits-ui": "^0.11.6", "bits-ui": "^0.11.6",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"crypto-js": "^4.2.0",
"deepmerge": "^4.3.1", "deepmerge": "^4.3.1",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"formsnap": "^0.4.1", "formsnap": "^0.4.1",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"lucide-svelte": "^0.294.0", "lucide-svelte": "^0.294.0",
"pino": "^8.17.1",
"pino-pretty": "^10.3.0",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"tailwind-merge": "^2.1.0", "tailwind-merge": "^2.1.0",
"tailwind-variants": "^0.1.18" "tailwind-variants": "^0.1.18"

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,10 @@
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import 'dotenv/config';
import Hex from 'crypto-js/enc-hex';
import { randomUUID } from 'node:crypto'; import { randomUUID } from 'node:crypto';
import SHA256 from 'crypto-js/sha256';
import { client } from '$lib/redis'; import { client } from '$lib/redis';
import { sha256 } from '$lib/hash';
import 'dotenv/config';
export const AUTH_SECRET = process.env.AUTH_SECRET || Hex.stringify(SHA256(randomUUID())); export const AUTH_SECRET = process.env.AUTH_SECRET || sha256(randomUUID());
export async function generateToken(): Promise<string> { export async function generateToken(): Promise<string> {
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);

15
web/src/lib/fs-extra.ts Normal file
View File

@ -0,0 +1,15 @@
import { promises } from 'fs';
export async function fsAccess(path: string): Promise<boolean> {
try {
await promises.access(path);
return true;
} catch (error) {
return false;
}
}
export async function fsTouch(filePath: string): Promise<void> {
const fd = await promises.open(filePath, 'a');
await fd.close();
}

7
web/src/lib/hash.ts Normal file
View File

@ -0,0 +1,7 @@
import { createHash } from 'crypto';
export function sha256(data: Buffer | string): string {
const hash = createHash('sha256');
hash.update(data);
return hash.digest('hex');
}

44
web/src/lib/logger.ts Normal file
View File

@ -0,0 +1,44 @@
import pino, { type Logger } from 'pino';
import pretty from 'pino-pretty';
import { createWriteStream } from 'node:fs';
import { resolve } from 'node:path';
import { fsAccess, fsTouch } from '$lib/fs-extra';
const LOG_LEVEL = process.env.LOG_LEVEL || 'trace';
const LOG_FILE_PATH = process.env.LOG_FILE_PATH || '/var/vlogs/web.log';
const LOG_COLORS = process.env.LOG_COLORS || 'true';
const prettyStream = pretty({
colorize: LOG_COLORS === 'true',
});
let logger: Logger = pino(
{
level: LOG_LEVEL,
},
pino.multistream([prettyStream]),
);
fsTouch(LOG_FILE_PATH)
.then(() => fsAccess(LOG_FILE_PATH))
.then((ok) => {
if (!ok) {
logger.warn('Log file is not accessible');
}
logger = pino(
{
level: LOG_LEVEL,
},
pino.multistream([
prettyStream,
createWriteStream(resolve(LOG_FILE_PATH), {
flags: 'a',
}),
]),
);
})
.catch((error) => {
logger.error(error);
});
export default logger;

View File

@ -4,6 +4,4 @@ export const client = new IORedis({
port: 6479, port: 6479,
}); });
export type RedisClient = typeof client;
export const WG_SEVER_PATH = `WG::SERVERS`; export const WG_SEVER_PATH = `WG::SERVERS`;

View File

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

View File

@ -1,16 +1,10 @@
import { type Actions, error } from '@sveltejs/kit'; import { type Actions, error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { import { findServer, generateWgServer, getServers, isIPReserved, isPortReserved, WGServer } from '$lib/wireguard';
findServer,
generateWgServer,
getServers,
isIPReserved,
isPortReserved,
WGServer,
} from '$lib/wireguard';
import { setError, superValidate } from 'sveltekit-superforms/server'; import { setError, superValidate } from 'sveltekit-superforms/server';
import { CreateServerSchema } from './schema'; import { CreateServerSchema } from './schema';
import { NameSchema } from '$lib/wireguard/schema'; import { NameSchema } from '$lib/wireguard/schema';
import logger from '$lib/logger';
export const load: PageServerLoad = async () => { export const load: PageServerLoad = async () => {
return { return {
@ -27,12 +21,12 @@ export const actions: Actions = {
const server = await findServer(serverId ?? ''); const server = await findServer(serverId ?? '');
if (!server) { if (!server) {
console.error('Server not found'); logger.error('Server not found');
return error(404, 'Not found'); return error(404, 'Not found');
} }
if (!NameSchema.safeParse(name).success) { if (!NameSchema.safeParse(name).success) {
console.error('Peer name is invalid'); logger.error('Peer name is invalid');
return error(400, 'Bad Request'); return error(400, 'Bad Request');
} }
@ -71,7 +65,7 @@ export const actions: Actions = {
serverId, serverId,
}; };
} catch (e: any) { } catch (e: any) {
console.error('Exception:', e); logger.error('Exception:', e);
return setError(form, 'Unhandled Exception'); return setError(form, 'Unhandled Exception');
} }
}, },

View File

@ -4,7 +4,7 @@ import { findServer, generateWgKey, WGServer } from '$lib/wireguard';
import { NameSchema } from '$lib/wireguard/schema'; import { NameSchema } from '$lib/wireguard/schema';
import { setError, superValidate } from 'sveltekit-superforms/server'; import { setError, superValidate } from 'sveltekit-superforms/server';
import { CreatePeerSchema } from './schema'; import { CreatePeerSchema } from './schema';
import { WgServerStatusSchema } from '$lib/typings'; import logger from '$lib/logger';
export const load: PageServerLoad = async ({ params }) => { export const load: PageServerLoad = async ({ params }) => {
const { serverId } = params; const { serverId } = params;
@ -22,7 +22,7 @@ export const actions: Actions = {
const { serverId } = params; const { serverId } = params;
const server = await findServer(serverId ?? ''); const server = await findServer(serverId ?? '');
if (!server) { if (!server) {
console.error('Server not found'); logger.error('Server not found');
throw error(404, 'Not found'); throw error(404, 'Not found');
} }
@ -30,13 +30,13 @@ export const actions: Actions = {
const peerId = (form.get('id') ?? '').toString(); const peerId = (form.get('id') ?? '').toString();
const peer = server.peers.find((p) => p.id === peerId); const peer = server.peers.find((p) => p.id === peerId);
if (!peer) { if (!peer) {
console.error('Peer not found'); logger.error('Peer not found');
throw error(404, 'Not found'); throw error(404, 'Not found');
} }
const name = (form.get('name') ?? '').toString(); const name = (form.get('name') ?? '').toString();
if (!NameSchema.safeParse(name).success) { if (!NameSchema.safeParse(name).success) {
console.error('Peer name is invalid'); logger.error('Peer name is invalid');
throw error(400, 'Bad Request'); throw error(400, 'Bad Request');
} }
@ -45,7 +45,7 @@ export const actions: Actions = {
return { ok: true }; return { ok: true };
} catch (e) { } catch (e) {
console.error('Exception:', e); logger.error('Exception:', e);
throw error(500, 'Unhandled Exception'); throw error(500, 'Unhandled Exception');
} }
}, },

View File

@ -1,5 +1,6 @@
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { getConfigHash, getServers, WGServer } from '$lib/wireguard'; import { getConfigHash, getServers, WGServer } from '$lib/wireguard';
import logger from '$lib/logger';
export const GET: RequestHandler = async () => { export const GET: RequestHandler = async () => {
try { try {
@ -18,7 +19,7 @@ export const GET: RequestHandler = async () => {
} }
} }
} catch (e) { } catch (e) {
console.error('APIFailed: HealthCheck:', e); logger.error('APIFailed: HealthCheck:', e);
return new Response('FAILED', { status: 500, headers: { 'Content-Type': 'text/plain' } }); return new Response('FAILED', { status: 500, headers: { 'Content-Type': 'text/plain' } });
} }

View File

@ -1,10 +1,10 @@
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import Shell from '$lib/shell'; import Shell from '$lib/shell';
import 'dotenv/config'; import 'dotenv/config';
import logger from '$lib/logger';
export const GET: RequestHandler = async () => { export const GET: RequestHandler = async () => {
let { WG_HOST } = process.env;
let { WG_HOST } = process.env
// if the host is not set, then we are using the server's public IP // if the host is not set, then we are using the server's public IP
if (!WG_HOST) { if (!WG_HOST) {
@ -14,7 +14,7 @@ export const GET: RequestHandler = async () => {
// check if WG_HOST is still not set // check if WG_HOST is still not set
if (!WG_HOST) { if (!WG_HOST) {
console.error('WG_HOST is not set'); logger.error('WG_HOST is not set');
return new Response('NOT_SET', { status: 500, headers: { 'Content-Type': 'text/plain' } }); return new Response('NOT_SET', { status: 500, headers: { 'Content-Type': 'text/plain' } });
} }

View File

@ -8,7 +8,7 @@ export const POST: RequestHandler = async ({ request }) => {
const body = await request.text(); const body = await request.text();
const data = await QRCode.toDataURL(body, { const data = await QRCode.toDataURL(body, {
errorCorrectionLevel: 'H', errorCorrectionLevel: 'M',
width: 500, width: 500,
}); });

View File

@ -5,6 +5,7 @@ import { setError, superValidate } from 'sveltekit-superforms/server';
import { formSchema } from './schema'; import { formSchema } from './schema';
import { generateToken } from '$lib/auth'; import { generateToken } from '$lib/auth';
import 'dotenv/config'; import 'dotenv/config';
import logger from '$lib/logger';
export const load: PageServerLoad = () => { export const load: PageServerLoad = () => {
return { return {
@ -14,6 +15,7 @@ export const load: PageServerLoad = () => {
export const actions: Actions = { export const actions: Actions = {
default: async (event) => { default: async (event) => {
const { cookies } = event;
const form = await superValidate(event, formSchema); const form = await superValidate(event, formSchema);
if (!form.valid) { if (!form.valid) {
@ -33,22 +35,19 @@ export const actions: Actions = {
} }
if (!HASHED_PASSWORD) { if (!HASHED_PASSWORD) {
console.warn('No password is set!'); logger.warn('No password is set!');
} }
const token = await generateToken(); const token = await generateToken();
const { ORIGIN } = process.env; const { ORIGIN } = process.env;
if (ORIGIN) {
const secure = ORIGIN.startsWith('https://'); const secure = ORIGIN?.startsWith('https://') ?? false;
event.cookies.set('authorization', token, { cookies.set('authorization', token, {
secure, secure,
httpOnly: true, httpOnly: true,
path: '/', path: '/',
}); });
} else {
event.cookies.set('authorization', token);
}
return { form, ok: true }; return { form, ok: true };
}, },

View File

@ -7,6 +7,8 @@ export const load: PageServerLoad = async ({ cookies }) => {
const token = cookies.get('authorization')!; const token = cookies.get('authorization')!;
await revokeToken(token).catch(() => {}); await revokeToken(token).catch(() => {});
} }
cookies.delete('authorization'); cookies.delete('authorization', {
path: '/',
});
throw redirect(302, '/login'); throw redirect(302, '/login');
}; };

View File

@ -1,21 +1,21 @@
import adapter from '@sveltejs/adapter-node'; import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/kit/vite'; import sveltePreprocess from 'svelte-preprocess';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors // Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors // for more information about preprocessors
preprocess: [ vitePreprocess({}) ], preprocess: [sveltePreprocess()],
kit: { kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter. // If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters. // See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter(), adapter: adapter(),
alias: { alias: {
$lib: './src/lib' $lib: './src/lib',
} },
} },
}; };
export default config; export default config;