op-packages/homeproxy-hiddify/root/usr/share/homeproxy/scripts/import_conf.uc
github-actions[bot] ae7b9f4d28 🎁 Sync 2026-07-31 00:21:49
2026-07-31 00:21:49 +08:00

121 lines
3.9 KiB
Ucode

#!/usr/bin/ucode
/*
* SPDX-License-Identifier: GPL-2.0-only
*
* Headless import of a WireGuard / AmneziaWG `.conf` file into a homeproxy
* `node` section. This is the SSH/agent-callable equivalent of the browser-only
* `parseWireGuardConf()` + "Import .conf" button in the LuCI node view — kept
* field-for-field in sync with it.
*
* Usage: ucode import_conf.uc <path-to-.conf> [label]
* Output: one JSON line, e.g. { "result": true, "section": "awg…", "label": "…", "type": "amneziawg" }
* or { "error": "…" } with a non-zero exit code.
*/
'use strict';
import { readfile } from 'fs';
import { cursor } from 'uci';
const uciconfig = 'homeproxy';
function emit(o) { printf('%s\n', o); }
/* Dependency-free stable hash (FNV-1a, 32-bit) for an idempotent section name.
* Deliberately NOT using `digest`/md5: ucode-mod-digest is absent on OpenWrt 23.05
* (the legacy build strips it), and that target's build patch only rewrites
* update_subscriptions.uc — not this script. Pure ucode works everywhere. */
function strhash(s) {
let h = 2166136261;
const n = length(s);
for (let i = 0; i < n; i++) {
h = (h ^ ord(s, i)) & 0xFFFFFFFF;
h = (h * 16777619) & 0xFFFFFFFF;
}
return sprintf('%08x', h);
}
const path = ARGV[0];
const want_label = ARGV[1];
if (!path) {
emit({ error: 'usage: import_conf.uc <path-to-.conf> [label]' });
exit(1);
}
const text = readfile(path);
if (!text) {
emit({ error: `cannot read file: ${path}` });
exit(1);
}
/* ---- parse (mirror of parseWireGuardConf in node.js) ---- */
const rawlines = split(text, '\n');
let section = null;
let iface = {}, peer = {};
for (let i = 0; i < length(rawlines); i++) {
let line = trim(rawlines[i]);
if (!length(line) || substr(line, 0, 1) === '#')
continue;
if (line === '[Interface]') { section = 'interface'; continue; }
if (line === '[Peer]') { section = 'peer'; continue; }
let eq = index(line, '=');
if (eq < 0)
continue;
let key = trim(substr(line, 0, eq));
let val = trim(substr(line, eq + 1));
if (section === 'interface') iface[key] = val;
else if (section === 'peer') peer[key] = val;
}
if (!iface.PrivateKey || !peer.PublicKey || !peer.Endpoint) {
emit({ error: 'not a valid WireGuard/AmneziaWG .conf (missing PrivateKey / PublicKey / Endpoint)' });
exit(1);
}
/* split Endpoint host:port on the LAST colon (IPv6 literal safe) */
const ep = peer.Endpoint;
const lc = rindex(ep, ':');
let host = (lc >= 0) ? substr(ep, 0, lc) : ep;
const port = (lc >= 0) ? substr(ep, lc + 1) : '';
host = replace(host, '[', '');
host = replace(host, ']', '');
const isAWG = !!(iface.Jc || iface.Jmin || iface.Jmax || iface.H1);
let node = {
label: want_label || (isAWG ? 'AmneziaWG' : 'WireGuard'),
type: isAWG ? 'amneziawg' : 'wireguard',
address: host,
port: port,
wireguard_private_key: iface.PrivateKey,
wireguard_peer_public_key: peer.PublicKey
};
if (peer.PresharedKey) node.wireguard_pre_shared_key = peer.PresharedKey;
if (iface.Address) node.wireguard_local_address = map(split(iface.Address, ','), (a) => trim(a));
if (iface.MTU) node.wireguard_mtu = iface.MTU;
if (isAWG) {
const awg = {
amnezia_jc: iface.Jc, amnezia_jmin: iface.Jmin, amnezia_jmax: iface.Jmax,
amnezia_s1: iface.S1, amnezia_s2: iface.S2, amnezia_s3: iface.S3, amnezia_s4: iface.S4,
amnezia_h1: iface.H1, amnezia_h2: iface.H2, amnezia_h3: iface.H3, amnezia_h4: iface.H4,
amnezia_i1: iface.I1, amnezia_i2: iface.I2, amnezia_i3: iface.I3, amnezia_i4: iface.I4, amnezia_i5: iface.I5
};
for (let k in awg)
if (awg[k] != null && awg[k] !== '')
node[k] = awg[k];
}
/* ---- write the node section (idempotent: same file → same section name) ---- */
const uci = cursor();
uci.load(uciconfig);
const sid = 'awg' + strhash(text);
uci.set(uciconfig, sid, 'node');
for (let k in node)
uci.set(uciconfig, sid, k, node[k]);
uci.commit(uciconfig);
emit({ result: true, section: sid, label: node.label, type: node.type });