Add admin setup page
This commit is contained in:
@@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user