Create a new field within WGServer that will monitor any modifications made to the wg configuration files. Furthermore, implement a health check for the container.

This commit is contained in:
Shahrad Elahi 2023-10-08 12:24:39 +03:30
parent 8ab5b0b0fc
commit 3928a84677
8 changed files with 63 additions and 35 deletions

View File

@ -50,6 +50,9 @@ RUN npm install --omit dev
EXPOSE 3000/tcp 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 COPY docker-entrypoint.sh /usr/bin/entrypoint
RUN chmod +x /usr/bin/entrypoint RUN chmod +x /usr/bin/entrypoint
ENTRYPOINT ["/usr/bin/entrypoint"] ENTRYPOINT ["/usr/bin/entrypoint"]

View File

@ -28,6 +28,9 @@ COPY /config/torrc /etc/tor/torrc
EXPOSE 3000/tcp 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 COPY docker-entrypoint.sh /usr/bin/entrypoint
RUN chmod +x /usr/bin/entrypoint RUN chmod +x /usr/bin/entrypoint
ENTRYPOINT ["/usr/bin/entrypoint"] ENTRYPOINT ["/usr/bin/entrypoint"]

View File

@ -91,6 +91,6 @@ echo -e "========================================================\n"
sleep 1 sleep 1
# After 10 seconds, export the database to the WireGuard config file # 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 "$@" exec "$@"

View File

@ -4,7 +4,7 @@
"description": "", "description": "",
"scripts": { "scripts": {
"dev:image": "cross-env DOCKER_BUILDKIT=1 docker build --tag wireadmin -f Dockerfile-Dev .", "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": [], "keywords": [],
"author": "Shahrad Elahi <https://github.com/shahradelahi>", "author": "Shahrad Elahi <https://github.com/shahradelahi>",

View File

@ -44,6 +44,7 @@ export type Peer = z.infer<typeof PeerSchema>
export const WgServerSchema = z.object({ export const WgServerSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
confId: z.number(), confId: z.number(),
confHash: z.string().nullable(),
type: z.enum([ 'direct', 'bridge', 'tor' ]), type: z.enum([ 'direct', 'bridge', 'tor' ]),
name: NameSchema, name: NameSchema,
address: z.string().regex(IPV4_REGEX), address: z.string().regex(IPV4_REGEX),

View File

@ -8,6 +8,7 @@ import { dynaJoin, isJson } from "@lib/utils";
import deepmerge from "deepmerge"; import deepmerge from "deepmerge";
import { getPeerConf } from "@lib/wireguard-utils"; import { getPeerConf } from "@lib/wireguard-utils";
import Network from "@lib/network"; import Network from "@lib/network";
import { SHA256 } from "crypto-js";
export class WGServer { export class WGServer {
@ -108,6 +109,7 @@ export class WGServer {
peer.persistentKeepalive && `PersistentKeepalive = ${peer.persistentKeepalive}` peer.persistentKeepalive && `PersistentKeepalive = ${peer.persistentKeepalive}`
])) ]))
await fs.writeFile(confPath, lines.join('\n')) await fs.writeFile(confPath, lines.join('\n'))
await WGServer.update(id, { confHash: await getConfigHash(id) });
const index = await findServerIndex(id) const index = await findServerIndex(id)
if (typeof index !== 'number') { if (typeof index !== 'number') {
@ -155,6 +157,7 @@ export class WGServer {
conf conf
const peersStr = peers.filter((_, i) => i !== peerIndex).join('\n') const peersStr = peers.filter((_, i) => i !== peerIndex).join('\n')
await fs.writeFile(confPath, `${serverConfStr}\n${peersStr}`) await fs.writeFile(confPath, `${serverConfStr}\n${peersStr}`)
await WGServer.update(server.id, { confHash: await getConfigHash(server.id) });
await WGServer.stop(server.id) await WGServer.stop(server.id)
await WGServer.start(server.id) await WGServer.start(server.id)
@ -214,6 +217,7 @@ export class WGServer {
const peersStr = peers.filter((_, i) => i !== peerIndex).join('\n') const peersStr = peers.filter((_, i) => i !== peerIndex).join('\n')
await fs.writeFile(confPath, `${serverConfStr}\n${peersStr}`) await fs.writeFile(confPath, `${serverConfStr}\n${peersStr}`)
await WGServer.update(sd.id, { confHash: await getConfigHash(sd.id) });
} }
static async getFreePeerIp(id: string): Promise<string | undefined> { static async getFreePeerIp(id: string): Promise<string | undefined> {
@ -271,6 +275,7 @@ export async function readWgConf(configId: number): Promise<WgServer> {
const server: WgServer = { const server: WgServer = {
id: crypto.randomUUID(), id: crypto.randomUUID(),
confId: configId, confId: configId,
confHash: null,
type: 'direct', type: 'direct',
name: '', name: '',
address: '', address: '',
@ -416,6 +421,7 @@ export async function generateWgServer(config: {
let server: WgServer = { let server: WgServer = {
id: uuid, id: uuid,
confId, confId,
confHash: null,
type: config.type, type: config.type,
name: config.name, name: config.name,
address: config.address, address: config.address,
@ -459,9 +465,10 @@ export async function generateWgServer(config: {
const CONFIG_PATH = path.join(WG_PATH, `wg${confId}.conf`) const CONFIG_PATH = path.join(WG_PATH, `wg${confId}.conf`)
// save server config to disk // save server config to disk
await fs.writeFile(CONFIG_PATH, await genServerConf(server), { await fs.writeFile(CONFIG_PATH, await genServerConf(server), { mode: 0o600 })
mode: 0o600,
}) // updating hash of the config
await WGServer.update(uuid, { confHash: await getConfigHash(uuid) });
// to ensure interface does not exists // to ensure interface does not exists
await Shell.exec(`wg-quick down wg${confId}`, true) await Shell.exec(`wg-quick down wg${confId}`, true)
@ -473,6 +480,23 @@ export async function generateWgServer(config: {
return uuid return uuid
} }
export async function getConfigHash(id: string): Promise<string | undefined> {
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<void> {
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<number> { export async function maxConfId(): Promise<number> {
// get files in /etc/wireguard // get files in /etc/wireguard
const files = await fs.readdir(WG_PATH) const files = await fs.readdir(WG_PATH)

View File

@ -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')
})
}

View File

@ -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')
})
}