Some checks failed
Build and Deploy Gateway / build-and-deploy (push) Failing after 13s
154 lines
4.6 KiB
JavaScript
154 lines
4.6 KiB
JavaScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { settings } from "./config.js";
|
|
|
|
export const DEVICE_MODES = new Set(["direct", "vpn", "rules", "block"]);
|
|
export const DEFAULT_DEVICE_MODES = new Set(["direct", "vpn", "block"]);
|
|
export const DEFAULT_DEVICE_MODE = "vpn";
|
|
export const DEFAULT_PROXY_MODE = "vpn";
|
|
export const TPROXY_INBOUND = "tproxy-in";
|
|
export const MIXED_INBOUND = "mixed-in";
|
|
|
|
const IPISH_RE = /^[\.\d:/]+$/;
|
|
|
|
function readJson(filePath, fallback) {
|
|
try {
|
|
if (!fs.existsSync(filePath)) return fallback;
|
|
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
function writeJson(filePath, value) {
|
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
fs.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf8");
|
|
}
|
|
|
|
function normalizeDeviceMode(mode, fallback = "rules") {
|
|
const value = String(mode || "").trim().toLowerCase();
|
|
if (value === "bypass") return "direct";
|
|
return DEVICE_MODES.has(value) ? value : fallback;
|
|
}
|
|
|
|
function normalizeDefaultMode(mode) {
|
|
const value = String(mode || "").trim().toLowerCase();
|
|
return DEFAULT_DEVICE_MODES.has(value) ? value : DEFAULT_DEVICE_MODE;
|
|
}
|
|
|
|
function normalizeProxyMode(mode) {
|
|
const value = String(mode || "").trim().toLowerCase();
|
|
return DEFAULT_DEVICE_MODES.has(value) ? value : DEFAULT_PROXY_MODE;
|
|
}
|
|
|
|
function normalizeIp(ip) {
|
|
const value = String(ip || "").trim();
|
|
return value && IPISH_RE.test(value) ? value : "";
|
|
}
|
|
|
|
function normalizeMac(mac) {
|
|
return String(mac || "").trim();
|
|
}
|
|
|
|
function fromLegacyDeviceRules(input) {
|
|
const rules = Array.isArray(input) ? input : [];
|
|
const devices = [];
|
|
|
|
for (const rule of rules) {
|
|
const sourceIps = Array.isArray(rule?.sourceIps) ? rule.sourceIps : [];
|
|
const mode = normalizeDeviceMode(rule?.outbound, "direct");
|
|
sourceIps.forEach((sourceIp, ipIndex) => {
|
|
const ip = normalizeIp(sourceIp);
|
|
if (!ip) return;
|
|
devices.push({
|
|
id: String(rule.id || `dev-${devices.length}`) + `-${ipIndex}`,
|
|
name: String(rule.name || `Устройство ${devices.length + 1}`).trim(),
|
|
enabled: rule.enabled !== false,
|
|
ip,
|
|
mac: "",
|
|
mode,
|
|
lastSeen: null,
|
|
});
|
|
});
|
|
}
|
|
|
|
return {
|
|
defaultTransparentMode: DEFAULT_DEVICE_MODE,
|
|
proxyDefaultMode: DEFAULT_PROXY_MODE,
|
|
devices,
|
|
};
|
|
}
|
|
|
|
export function normalizeDeviceProfiles(input) {
|
|
const raw =
|
|
input && typeof input === "object" && !Array.isArray(input)
|
|
? input
|
|
: { devices: input };
|
|
const rawDevices = Array.isArray(raw.devices) ? raw.devices : [];
|
|
|
|
return {
|
|
defaultTransparentMode: normalizeDefaultMode(
|
|
raw.defaultTransparentMode || raw.defaultMode,
|
|
),
|
|
proxyDefaultMode: normalizeProxyMode(raw.proxyDefaultMode),
|
|
devices: rawDevices.map((device, index) => ({
|
|
id: String(device.id || `dev-${Date.now()}-${index}`),
|
|
name: String(device.name || `Устройство ${index + 1}`).trim(),
|
|
enabled: device.enabled !== false,
|
|
ip: normalizeIp(device.ip || device.sourceIp),
|
|
mac: normalizeMac(device.mac),
|
|
mode: normalizeDeviceMode(device.mode || device.outbound, "rules"),
|
|
lastSeen: device.lastSeen || null,
|
|
})),
|
|
};
|
|
}
|
|
|
|
export function readDeviceProfiles() {
|
|
if (fs.existsSync(settings.devicesPath)) {
|
|
return normalizeDeviceProfiles(readJson(settings.devicesPath, null));
|
|
}
|
|
|
|
if (fs.existsSync(settings.deviceRulesPath)) {
|
|
return normalizeDeviceProfiles(
|
|
fromLegacyDeviceRules(readJson(settings.deviceRulesPath, [])),
|
|
);
|
|
}
|
|
|
|
return {
|
|
defaultTransparentMode: DEFAULT_DEVICE_MODE,
|
|
proxyDefaultMode: DEFAULT_PROXY_MODE,
|
|
devices: [],
|
|
};
|
|
}
|
|
|
|
export function writeDeviceProfiles(value) {
|
|
const normalized = normalizeDeviceProfiles(value);
|
|
writeJson(settings.devicesPath, normalized);
|
|
return normalized;
|
|
}
|
|
|
|
export function normalizeCidr(ip) {
|
|
const value = normalizeIp(ip);
|
|
if (!value) return "";
|
|
return value.includes("/") ? value : `${value}/32`;
|
|
}
|
|
|
|
export function deviceCidrs(devices, modes) {
|
|
const allowedModes = new Set(Array.isArray(modes) ? modes : [modes]);
|
|
return (Array.isArray(devices) ? devices : [])
|
|
.filter((device) => device.enabled !== false && allowedModes.has(device.mode))
|
|
.map((device) => normalizeCidr(device.ip))
|
|
.filter(Boolean);
|
|
}
|
|
|
|
export function legacyDeviceRulesFromProfiles(profiles) {
|
|
const { devices } = normalizeDeviceProfiles(profiles);
|
|
return devices.map((device) => ({
|
|
id: device.id,
|
|
name: device.name,
|
|
enabled: device.enabled,
|
|
sourceIps: device.ip ? [device.ip] : [],
|
|
outbound: device.mode === "rules" ? "direct" : device.mode,
|
|
}));
|
|
}
|