mirror of
https://github.com/wireadmin/wireadmin
synced 2025-02-26 05:48:44 +00:00
adds features for adding/removing peers and a few minor changes.
This commit is contained in:
parent
e79f52da9f
commit
19b525d1e6
@ -10,14 +10,6 @@ export const WgKeySchema = z.object({
|
||||
|
||||
export type WgKey = z.infer<typeof WgKeySchema>
|
||||
|
||||
export interface WgPeerConfig {
|
||||
publicKey: string
|
||||
preSharedKey: string
|
||||
endpoint: string
|
||||
allowedIps: string[]
|
||||
persistentKeepalive: number | null
|
||||
}
|
||||
|
||||
const WgPeerSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
name: z.string().regex(/^[A-Za-z\d\s]{3,32}$/),
|
||||
@ -39,7 +31,7 @@ export type WgPeer = z.infer<typeof WgPeerSchema>
|
||||
export const WgServerSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
confId: z.number(),
|
||||
type: z.enum([ 'default', 'bridge', 'tor' ]),
|
||||
type: z.enum([ 'direct', 'bridge', 'tor' ]),
|
||||
name: z.string().regex(/^[A-Za-z\d\s]{3,32}$/),
|
||||
address: z.string().regex(IPV4_REGEX),
|
||||
listen: z.number(),
|
||||
@ -48,7 +40,12 @@ export const WgServerSchema = z.object({
|
||||
preDown: z.string().nullable(),
|
||||
postDown: z.string().nullable(),
|
||||
dns: z.string().regex(IPV4_REGEX).nullable(),
|
||||
peers: z.array(WgPeerSchema),
|
||||
peers: z.array(z.object({
|
||||
publicKey: z.string(),
|
||||
preSharedKey: z.string().nullable(),
|
||||
allowedIps: z.string().regex(IPV4_REGEX),
|
||||
persistentKeepalive: z.number().nullable(),
|
||||
})),
|
||||
createdAt: z.string().datetime(),
|
||||
updatedAt: z.string().datetime(),
|
||||
status: z.enum([ 'up', 'down' ]),
|
||||
|
@ -2,7 +2,7 @@ import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
import { WG_PATH } from "@lib/constants";
|
||||
import Shell from "@lib/shell";
|
||||
import { WgKey, WgPeer, WgServer } from "@lib/typings";
|
||||
import { WgKey, WgServer } from "@lib/typings";
|
||||
import { client, WG_SEVER_PATH } from "@lib/redis";
|
||||
import { isJson } from "@lib/utils";
|
||||
import deepmerge from "deepmerge";
|
||||
@ -67,6 +67,176 @@ export class WGServer {
|
||||
return res === 'OK'
|
||||
}
|
||||
|
||||
static async findAttachedUuid(confId: number): Promise<string | undefined> {
|
||||
const server = await getServers()
|
||||
return server.find((s) => s.confId === confId)?.id
|
||||
}
|
||||
|
||||
static async addPeer(id: string, peer: WgServer['peers'][0]): Promise<boolean> {
|
||||
const server = await findServer(id)
|
||||
if (!server) {
|
||||
console.error('server could not be updated (reason: not exists)')
|
||||
return false
|
||||
}
|
||||
const peerLines = [
|
||||
`[Peer]`,
|
||||
`PublicKey = ${peer.publicKey}`,
|
||||
`AllowedIPs = ${peer.allowedIps}/32`
|
||||
]
|
||||
if (peer.persistentKeepalive) {
|
||||
peerLines.push(`PersistentKeepalive = ${peer.persistentKeepalive}`)
|
||||
}
|
||||
if (peer.preSharedKey) {
|
||||
peerLines.push(`PresharedKey = ${peer.preSharedKey}`)
|
||||
}
|
||||
const confPath = path.join(WG_PATH, `wg${server.confId}.conf`)
|
||||
const conf = await fs.readFile(confPath, 'utf-8')
|
||||
const lines = conf.split('\n')
|
||||
lines.push(...peerLines)
|
||||
await fs.writeFile(confPath, lines.join('\n'))
|
||||
await WGServer.update(server.id, {
|
||||
peers: [ ...server.peers, peer ]
|
||||
})
|
||||
await WGServer.stop(server.id)
|
||||
await WGServer.start(server.id)
|
||||
return true
|
||||
}
|
||||
|
||||
static async removePeer(id: string, publicKey: string): Promise<boolean> {
|
||||
const server = await findServer(id)
|
||||
if (!server) {
|
||||
console.error('server could not be updated (reason: not exists)')
|
||||
return false
|
||||
}
|
||||
const peers = await wgPeersStr(server.confId)
|
||||
const peerIndex = peers.findIndex((p) => p
|
||||
.replace(/\s/g, '')
|
||||
.includes(`PublicKey=${publicKey}`)
|
||||
)
|
||||
if (peerIndex === -1) {
|
||||
console.warn('no peer found')
|
||||
return false
|
||||
}
|
||||
const confPath = path.join(WG_PATH, `wg${server.confId}.conf`)
|
||||
const conf = await fs.readFile(confPath, 'utf-8')
|
||||
const serverConfStr = conf.includes('[Peer]') ?
|
||||
conf.split('[Peer]')[0] :
|
||||
conf
|
||||
const peersStr = peers.filter((_, i) => i !== peerIndex).join('\n')
|
||||
await fs.writeFile(confPath, `${serverConfStr}\n${peersStr}`)
|
||||
await WGServer.update(server.id, {
|
||||
peers: server.peers.filter((_, i) => i !== peerIndex)
|
||||
})
|
||||
await WGServer.stop(server.id)
|
||||
await WGServer.start(server.id)
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = path.join(WG_PATH, `wg${configId}.conf`)
|
||||
const conf = await fs.readFile(confPath, 'utf-8')
|
||||
const lines = conf.split('\n')
|
||||
const server: WgServer = {
|
||||
id: crypto.randomUUID(),
|
||||
confId: configId,
|
||||
type: 'direct',
|
||||
name: '',
|
||||
address: '',
|
||||
listen: 0,
|
||||
dns: null,
|
||||
privateKey: '',
|
||||
publicKey: '',
|
||||
preUp: null,
|
||||
preDown: null,
|
||||
postDown: null,
|
||||
postUp: null,
|
||||
peers: [],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
status: 'down'
|
||||
}
|
||||
let reachedPeers = false
|
||||
for (const line of lines) {
|
||||
const [ key, value ] = line.split('=').map((s) => s.trim())
|
||||
if (reachedPeers) {
|
||||
if (key === '[Peer]') {
|
||||
server.peers.push({
|
||||
publicKey: '',
|
||||
preSharedKey: null,
|
||||
allowedIps: '',
|
||||
persistentKeepalive: null
|
||||
})
|
||||
}
|
||||
if (key === 'PublicKey') {
|
||||
server.peers[server.peers.length - 1].publicKey = value
|
||||
}
|
||||
if (key === 'PresharedKey') {
|
||||
server.peers[server.peers.length - 1].preSharedKey = value
|
||||
}
|
||||
if (key === 'AllowedIPs') {
|
||||
server.peers[server.peers.length - 1].allowedIps = value
|
||||
}
|
||||
if (key === 'PersistentKeepalive') {
|
||||
server.peers[server.peers.length - 1].persistentKeepalive = parseInt(value)
|
||||
}
|
||||
}
|
||||
if (key === 'PrivateKey') {
|
||||
server.privateKey = value
|
||||
}
|
||||
if (key === 'Address') {
|
||||
server.address = value
|
||||
}
|
||||
if (key === 'ListenPort') {
|
||||
server.listen = parseInt(value)
|
||||
}
|
||||
if (key === 'DNS') {
|
||||
server.dns = value
|
||||
}
|
||||
if (key === 'PreUp') {
|
||||
server.preUp = value
|
||||
}
|
||||
if (key === 'PreDown') {
|
||||
server.preDown = value
|
||||
}
|
||||
if (key === 'PostUp') {
|
||||
server.postUp = value
|
||||
}
|
||||
if (key === 'PostDown') {
|
||||
server.postDown = value
|
||||
}
|
||||
if (key === 'PublicKey') {
|
||||
server.publicKey = value
|
||||
}
|
||||
if (key === '[Peer]') {
|
||||
reachedPeers = true
|
||||
}
|
||||
}
|
||||
server.status = await wgCheckout(configId) ? 'up' : 'down'
|
||||
return server
|
||||
}
|
||||
|
||||
/**
|
||||
* This function checks if a WireGuard config exists in file system
|
||||
* @param configId
|
||||
*/
|
||||
async function wgConfExists(configId: number): Promise<boolean> {
|
||||
const confPath = path.join(WG_PATH, `wg${configId}.conf`)
|
||||
try {
|
||||
await fs.access(confPath)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -74,7 +244,25 @@ export class WGServer {
|
||||
* redis server.
|
||||
*/
|
||||
async function syncServers(): Promise<boolean> {
|
||||
throw new Error('Yet not implanted!');
|
||||
// get files in /etc/wireguard
|
||||
const files = await fs.readdir(WG_PATH)
|
||||
// filter files that start with wg and end with .conf
|
||||
const reg = new RegExp(/^wg(\d+)\.conf$/)
|
||||
const confs = files.filter((f) => reg.test(f))
|
||||
// read all confs
|
||||
const servers = await Promise.all(confs.map((f) => readWgConf(parseInt(f.match(reg)![1]))))
|
||||
// remove old servers
|
||||
await client.del(WG_SEVER_PATH)
|
||||
// save all servers to redis
|
||||
await client.lpush(WG_SEVER_PATH, ...servers.map((s) => JSON.stringify(s)))
|
||||
return true
|
||||
}
|
||||
|
||||
async function wgPeersStr(configId: number): Promise<string[]> {
|
||||
const confPath = path.join(WG_PATH, `wg${configId}.conf`)
|
||||
const conf = await fs.readFile(confPath, 'utf-8')
|
||||
const rawPeers = conf.split('[Peer]')
|
||||
return rawPeers.slice(1).map((p) => `[Peer]\n${p}`)
|
||||
}
|
||||
|
||||
export async function generateWgKey(): Promise<WgKey> {
|
||||
@ -216,12 +404,12 @@ ${server.peers.map(getPeerConf).join('\n')}
|
||||
`
|
||||
}
|
||||
|
||||
export function getPeerConf(peer: WgPeer): string {
|
||||
export function getPeerConf(peer: WgServer['peers'][0]): string {
|
||||
return `
|
||||
[Peer]
|
||||
PublicKey = ${peer.publicKey}
|
||||
${peer.preSharedKey ? `PresharedKey = ${peer.preSharedKey}` : ''}
|
||||
AllowedIPs = ${peer.address}/32
|
||||
AllowedIPs = ${peer.allowedIps}/32
|
||||
${peer.persistentKeepalive ? `PersistentKeepalive = ${peer.persistentKeepalive}` : ''}
|
||||
`
|
||||
}
|
||||
|
@ -8,20 +8,19 @@ export default NextAuth({
|
||||
// get user and password from .env.local
|
||||
// https://next-auth.js.org/configuration/providers#credentials-based-authentication-providers
|
||||
CredentialsProvider({
|
||||
async authorize(credentials, request) {
|
||||
const { HASHED_PASSWORD } = process.env
|
||||
if (!HASHED_PASSWORD) {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
const { password } = request.query || {}
|
||||
if (!password ) {
|
||||
return null
|
||||
}
|
||||
},
|
||||
name: 'Credentials',
|
||||
credentials: {},
|
||||
async authorize(_, request) {
|
||||
const { HASHED_PASSWORD } = process.env
|
||||
if (!HASHED_PASSWORD) {
|
||||
return { id: crypto.randomUUID() }
|
||||
}
|
||||
const { password } = request.query || {}
|
||||
if (!password) {
|
||||
return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
}),
|
||||
|
||||
]
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { Badge, Button, Card, List } from "antd";
|
||||
import { Button, Card, List } from "antd";
|
||||
import BasePage from "@ui/pages/BasePage";
|
||||
import { APIResponse, WgServer } from "@lib/typings";
|
||||
import { PlusOutlined } from "@ant-design/icons";
|
||||
@ -105,7 +105,7 @@ function ServerIcon(props: ServerIconProps) {
|
||||
width={34}
|
||||
height={34}
|
||||
/>
|
||||
{props.type !== 'default' && (
|
||||
{props.type !== 'direct' && (
|
||||
<div className={'absolute -bottom-1 -right-2 rounded-full bg-white'}>
|
||||
{props.type === 'tor' && (
|
||||
<Image
|
||||
|
Loading…
Reference in New Issue
Block a user