Add admin setup page

This commit is contained in:
2026-09-20 18:15:51 +01:00
parent 27676c5a3f
commit f05384d8fe
21 changed files with 1318 additions and 4 deletions
@@ -0,0 +1,266 @@
import grabDirNames from "@/src/utils/grab-dir-names";
import fs from "node:fs";
import path from "node:path";
import { execSync } from "node:child_process";
export type SystemSetupToolStatus = {
name: string;
command: string;
version?: string | null;
installed: boolean;
};
export type SystemSetupLibDirStatus = {
label: string;
path: string;
exists: boolean;
};
export type SystemSetupStatus = {
is_root: boolean;
is_dev: boolean;
environment: string;
distro: string;
distro_pretty_name: string;
init_system: "systemd" | "openrc" | "unknown";
install_dir: string;
app_installed: boolean;
app_version: string | null;
repo_url: string | null;
repo_branch: string | null;
service_name: string;
service_active: boolean | null;
wg_module_loaded: boolean;
wg_quick_helper_installed: boolean;
tools: SystemSetupToolStatus[];
lib_dirs: SystemSetupLibDirStatus[];
};
type ShellResult = {
success: boolean;
output: string;
};
const {
ROOT_DIR,
WGUI_LIB_DIR,
WGUI_LIB_KEYS_DIR,
WGUI_LIB_HOSTS_CONFIGS_DIR,
WGUI_LIB_SCRIPTS_DIR,
WGUI_WG_QUICK_MANAGE_SCRIPT,
} = grabDirNames();
const TOOL_CHECKS: {
name: string;
command: string;
version_command?: string;
}[] = [
{
name: "wireguard",
command: "wg",
version_command: "wg --version",
},
{
name: "wg-quick",
command: "wg-quick",
},
{
name: "ip (iproute2)",
command: "ip",
version_command: "ip -V",
},
{
name: "curl",
command: "curl",
version_command: "curl --version",
},
{
name: "git",
command: "git",
version_command: "git --version",
},
{
name: "bun",
command: "bun",
version_command: "bun --version",
},
];
const tryShell = ({
command,
timeout_ms = 10000,
}: {
command: string;
timeout_ms?: number;
}): ShellResult => {
try {
const result = execSync(command, {
encoding: "utf-8",
stdio: "pipe",
timeout: timeout_ms,
});
return {
success: true,
output: String(result).trim(),
};
} catch (error: any) {
const stdout = error?.stdout ? String(error.stdout).trim() : "";
const stderr = error?.stderr ? String(error.stderr).trim() : "";
return {
success: false,
output: [stdout, stderr].filter(Boolean).join("\n").trim(),
};
}
};
const runCommand = (command: string, timeout_ms = 10000): ShellResult =>
tryShell({ command, timeout_ms });
const grabDistro = (): { id: string; pretty_name: string } => {
try {
const os_release = fs.readFileSync("/etc/os-release", "utf-8");
const id_match = os_release.match(/^ID=(.+)$/m);
const pretty_match = os_release.match(/^PRETTY_NAME=(.+)$/m);
const strip = (value: string) =>
value.replace(/^["']|["']$/g, "").trim();
return {
id: id_match?.[1] ? strip(id_match[1]) : "unknown",
pretty_name: pretty_match?.[1] ? strip(pretty_match[1]) : "",
};
} catch {
return {
id: "unknown",
pretty_name: "",
};
}
};
const grabInitSystem = (): "systemd" | "openrc" | "unknown" => {
if (runCommand(`command -v systemctl`, 5000).success) {
return "systemd";
}
if (
runCommand(`command -v rc-service`, 5000).success ||
fs.existsSync("/etc/alpine-release")
) {
return "openrc";
}
return "unknown";
};
const grabGitValue = ({
install_dir,
command,
env_value,
}: {
install_dir: string;
command: string;
env_value?: string;
}): string | null => {
if (env_value) {
return env_value;
}
const res = runCommand(`git -C "${install_dir}" ${command}`, 10000);
return res.success && res.output ? res.output : null;
};
export default function grabSystemSetupStatus(): SystemSetupStatus {
const IS_DEV = (process.env.NODE_ENV || "development") == "development";
const IS_ROOT =
typeof process.getuid === "function" && process.getuid() === 0;
const INSTALL_DIR = IS_DEV ? ROOT_DIR : path.join(WGUI_LIB_DIR, "webapp");
const APP_INSTALLED = fs.existsSync(path.join(INSTALL_DIR, ".git"));
const tools: SystemSetupToolStatus[] = TOOL_CHECKS.map((tool) => {
const which_res = runCommand(`command -v ${tool.command}`, 5000);
const installed = which_res.success;
let version: string | null = null;
if (installed && tool.version_command) {
const version_res = runCommand(tool.version_command, 5000);
if (version_res.success) {
version = version_res.output.split("\n")[0] || null;
}
}
return {
name: tool.name,
command: tool.command,
installed,
version,
};
});
const { id: distro, pretty_name: distro_pretty_name } = grabDistro();
const init_system = grabInitSystem();
const service_name = process.env.SERVICE_NAME || "wgui";
let service_active: boolean | null = null;
if (!IS_DEV && init_system == "systemd") {
const service_res = runCommand(
`systemctl is-active ${service_name} 2>/dev/null`,
10000,
);
service_active = service_res.success && service_res.output == "active";
}
return {
is_root: IS_ROOT,
is_dev: IS_DEV,
environment: IS_DEV ? "development" : "production",
distro,
distro_pretty_name,
init_system,
install_dir: INSTALL_DIR,
app_installed: APP_INSTALLED,
app_version: APP_INSTALLED
? grabGitValue({
install_dir: INSTALL_DIR,
command: `rev-parse --short HEAD 2>/dev/null`,
})
: null,
repo_url: grabGitValue({
install_dir: INSTALL_DIR,
command: `config --get remote.origin.url 2>/dev/null`,
env_value: process.env.REPO_URL,
}),
repo_branch:
grabGitValue({
install_dir: INSTALL_DIR,
command: `branch --show-current 2>/dev/null`,
env_value: process.env.BRANCH,
}) || "main",
service_name,
service_active,
wg_module_loaded: fs.existsSync("/sys/module/wireguard"),
wg_quick_helper_installed: fs.existsSync(WGUI_WG_QUICK_MANAGE_SCRIPT),
tools,
lib_dirs: [
{
label: "Host configs",
path: WGUI_LIB_HOSTS_CONFIGS_DIR,
exists: fs.existsSync(WGUI_LIB_HOSTS_CONFIGS_DIR),
},
{
label: "Scripts",
path: WGUI_LIB_SCRIPTS_DIR,
exists: fs.existsSync(WGUI_LIB_SCRIPTS_DIR),
},
],
};
}
@@ -0,0 +1,51 @@
import grabDirNames from "@/src/utils/grab-dir-names";
import { execSync } from "node:child_process";
import path from "node:path";
type Params = {
script: string;
timeout_ms?: number;
env?: Record<string, string | undefined>;
};
export type RunSetupScriptResult = {
success: boolean;
output: string;
};
export default function runSetupScript({
script,
timeout_ms = 600000,
env,
}: Params): RunSetupScriptResult {
const { ROOT_DIR } = grabDirNames();
const script_path = path.join(ROOT_DIR, "src", "scripts", script);
try {
const result = execSync(`bash "${script_path}" 2>&1`, {
encoding: "utf-8",
stdio: "pipe",
timeout: timeout_ms,
env: {
...process.env,
...env,
},
});
return {
success: true,
output: String(result).trim(),
};
} catch (error: any) {
const stdout = error?.stdout ? String(error.stdout).trim() : "";
const stderr = error?.stderr ? String(error.stderr).trim() : "";
return {
success: false,
output: [stdout, stderr]
.filter(Boolean)
.join("\n")
.trim(),
};
}
}