diff --git a/Dockerfile b/Dockerfile index 1a3a1af..2d81384 100644 --- a/Dockerfile +++ b/Dockerfile @@ -50,6 +50,9 @@ RUN npm install --omit dev EXPOSE 3000/tcp +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://127.0.0.1:3000/api/wireguard/healthcheck || exit 1 + COPY docker-entrypoint.sh /usr/bin/entrypoint RUN chmod +x /usr/bin/entrypoint ENTRYPOINT ["/usr/bin/entrypoint"] diff --git a/Dockerfile-Dev b/Dockerfile-Dev index 5c353e2..b9dc094 100644 --- a/Dockerfile-Dev +++ b/Dockerfile-Dev @@ -28,6 +28,9 @@ COPY /config/torrc /etc/tor/torrc EXPOSE 3000/tcp +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://127.0.0.1:3000/api/wireguard/healthcheck || exit 1 + COPY docker-entrypoint.sh /usr/bin/entrypoint RUN chmod +x /usr/bin/entrypoint ENTRYPOINT ["/usr/bin/entrypoint"] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 02bb72a..73319f0 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -91,6 +91,6 @@ echo -e "========================================================\n" sleep 1 # After 10 seconds, export the database to the WireGuard config file -screen -dm bash -c "sleep 10; curl -s -o /dev/null http://127.0.0.1:3000/api/wireguard/regen" +screen -dm bash -c "sleep 10; curl -s -o /dev/null http://127.0.0.1:3000/api/wireguard/healthcheck" exec "$@" diff --git a/package.json b/package.json index 218b51e..b99bdf9 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "", "scripts": { "dev:image": "cross-env DOCKER_BUILDKIT=1 docker build --tag wireadmin -f Dockerfile-Dev .", - "dev": "docker-compose -f docker-compose.yml -f docker-compose.dev.yml up" + "dev": "docker-compose -f docker-compose.yml -f docker-compose.dev.yml up; docker rm -f wireadmin" }, "keywords": [], "author": "Shahrad Elahi ", diff --git a/src/lib/typings.ts b/src/lib/typings.ts index 4d2528a..2b5cfa7 100644 --- a/src/lib/typings.ts +++ b/src/lib/typings.ts @@ -44,6 +44,7 @@ export type Peer = z.infer export const WgServerSchema = z.object({ id: z.string().uuid(), confId: z.number(), + confHash: z.string().nullable(), type: z.enum([ 'direct', 'bridge', 'tor' ]), name: NameSchema, address: z.string().regex(IPV4_REGEX), diff --git a/src/lib/wireguard.ts b/src/lib/wireguard.ts index 83f0b18..9b171ad 100644 --- a/src/lib/wireguard.ts +++ b/src/lib/wireguard.ts @@ -8,6 +8,7 @@ import { dynaJoin, isJson } from "@lib/utils"; import deepmerge from "deepmerge"; import { getPeerConf } from "@lib/wireguard-utils"; import Network from "@lib/network"; +import { SHA256 } from "crypto-js"; export class WGServer { @@ -108,6 +109,7 @@ export class WGServer { peer.persistentKeepalive && `PersistentKeepalive = ${peer.persistentKeepalive}` ])) await fs.writeFile(confPath, lines.join('\n')) + await WGServer.update(id, { confHash: await getConfigHash(id) }); const index = await findServerIndex(id) if (typeof index !== 'number') { @@ -155,6 +157,7 @@ export class WGServer { conf const peersStr = peers.filter((_, i) => i !== peerIndex).join('\n') await fs.writeFile(confPath, `${serverConfStr}\n${peersStr}`) + await WGServer.update(server.id, { confHash: await getConfigHash(server.id) }); await WGServer.stop(server.id) await WGServer.start(server.id) @@ -214,6 +217,7 @@ export class WGServer { const peersStr = peers.filter((_, i) => i !== peerIndex).join('\n') await fs.writeFile(confPath, `${serverConfStr}\n${peersStr}`) + await WGServer.update(sd.id, { confHash: await getConfigHash(sd.id) }); } static async getFreePeerIp(id: string): Promise { @@ -271,6 +275,7 @@ export async function readWgConf(configId: number): Promise { const server: WgServer = { id: crypto.randomUUID(), confId: configId, + confHash: null, type: 'direct', name: '', address: '', @@ -416,6 +421,7 @@ export async function generateWgServer(config: { let server: WgServer = { id: uuid, confId, + confHash: null, type: config.type, name: config.name, address: config.address, @@ -459,9 +465,10 @@ export async function generateWgServer(config: { const CONFIG_PATH = path.join(WG_PATH, `wg${confId}.conf`) // save server config to disk - await fs.writeFile(CONFIG_PATH, await genServerConf(server), { - mode: 0o600, - }) + await fs.writeFile(CONFIG_PATH, await genServerConf(server), { mode: 0o600 }) + + // updating hash of the config + await WGServer.update(uuid, { confHash: await getConfigHash(uuid) }); // to ensure interface does not exists await Shell.exec(`wg-quick down wg${confId}`, true) @@ -473,6 +480,23 @@ export async function generateWgServer(config: { return uuid } +export async function getConfigHash(id: string): Promise { + const server = await findServer(id) + if (!server) { + console.error('getConfigHash: server not found') + return undefined + } + const confPath = path.join(WG_PATH, `wg${server.confId}.conf`) + const conf = await fs.readFile(confPath, 'utf-8') + return CryptoJS.enc.Hex.stringify(SHA256(conf)); +} + +export async function writeConfigFile(wg: WgServer): Promise { + const CONFIG_PATH = path.join(WG_PATH, `wg${wg.confId}.conf`) + await fs.writeFile(CONFIG_PATH, await genServerConf(wg), { mode: 0o600 }) + await WGServer.update(wg.id, { confHash: await getConfigHash(wg.id) }); +} + export async function maxConfId(): Promise { // get files in /etc/wireguard const files = await fs.readdir(WG_PATH) diff --git a/src/pages/api/wireguard/healthcheck.ts b/src/pages/api/wireguard/healthcheck.ts new file mode 100644 index 0000000..5bf03ce --- /dev/null +++ b/src/pages/api/wireguard/healthcheck.ts @@ -0,0 +1,27 @@ +import type { NextApiRequest, NextApiResponse } from 'next' +import safeServe from "@lib/safe-serve"; +import { getConfigHash, getServers, WGServer, writeConfigFile } from "@lib/wireguard"; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + return safeServe(res, async () => { + + const servers = await getServers() + + for (const s of servers) { + + const HASH = await getConfigHash(s.id); + if (s.confId && s.confHash === HASH) { + // Skip, due to no changes on the config + continue; + } + + await writeConfigFile(s); + await WGServer.start(s.id) + } + + return res + .status(200) + .end('OK') + + }) +} diff --git a/src/pages/api/wireguard/regen.ts b/src/pages/api/wireguard/regen.ts deleted file mode 100644 index c4eeb7d..0000000 --- a/src/pages/api/wireguard/regen.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next' -import safeServe from "@lib/safe-serve"; -import { genServerConf, getServers, WGServer } from "@lib/wireguard"; -import fs from "fs"; -import path from "path"; -import { WG_PATH } from "@lib/constants"; - -/** - * This API Endpoint is for recreating WireGuard's configs from the database. - * - * @param req - * @param res - */ -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - return safeServe(res, async () => { - - const servers = await getServers() - - for (const s of servers) { - const CONFIG_PATH = path.join(WG_PATH, `wg${s.confId}.conf`) - fs.writeFileSync(CONFIG_PATH, await genServerConf(s), { mode: 0o600 }) - await WGServer.start(s.id) - } - - return res - .status(200) - .end('OK') - - }) -}