feat: add dnsmasq DNS record admin UI
- store records in SQLite via built-in node:sqlite, no native deps - wrap DB reads in await io() so they stay out of the static shell - add CRUD pages backed by Server Actions with per-request data - generate managed config and restart container via Docker socket - add /resolve page to query dnsmasq for A and CNAME records - add Dockerfile and compose setup with shared config volume
This commit is contained in:
27 files changed
+1344
-130
No files matched your search
@@ -0,0 +1,52 @@
|
||||
import { mkdirSync, renameSync, writeFileSync } from "node:fs"
|
||||
import path from "node:path"
|
||||
import type { DnsRecord } from "./db"
|
||||
import { env } from "./env"
|
||||
|
||||
const HEADER = "# Generated by dnsmasq-admin — 请勿手动编辑,每次「应用并重启」都会覆盖此文件"
|
||||
|
||||
/**
|
||||
* dnsmasq's `address=/domain/ip` already answers for the domain *and* every subdomain, so
|
||||
* there is no need for a separate wildcard syntax.
|
||||
*/
|
||||
export function renderRecord(record: DnsRecord): string {
|
||||
return record.type === "A"
|
||||
? `address=/${record.domain}/${record.value}`
|
||||
: `cname=${record.domain},${record.value}`
|
||||
}
|
||||
|
||||
export function renderConfig(records: DnsRecord[]): string {
|
||||
const lines = records
|
||||
.filter((record) => record.enabled)
|
||||
.map(renderRecord)
|
||||
|
||||
if (lines.length === 0) {
|
||||
return `${HEADER}\n\n# 当前没有启用的记录\n`
|
||||
}
|
||||
|
||||
return `${HEADER}\n\n${lines.join("\n")}\n`
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate the managed config file for the given records.
|
||||
*
|
||||
* Written to a sibling temp file first and then renamed, so dnsmasq can never read a
|
||||
* half-written file.
|
||||
*/
|
||||
export async function writeManagedConfig(records: DnsRecord[]): Promise<{
|
||||
path: string
|
||||
recordCount: number
|
||||
}> {
|
||||
const dir = path.dirname(env.confPath)
|
||||
mkdirSync(dir, { recursive: true })
|
||||
|
||||
const content = renderConfig(records)
|
||||
const tempPath = `${env.confPath}.${process.pid}.tmp`
|
||||
writeFileSync(tempPath, content, "utf8")
|
||||
renameSync(tempPath, env.confPath)
|
||||
|
||||
return {
|
||||
path: env.confPath,
|
||||
recordCount: records.filter((record) => record.enabled).length,
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user