3 let fs = require("fs");
5 let script_dir = sourcepath(0, true);
6 if (fs.basename(script_dir) == "scripts") {
7 unet_tool = fs.dirname(script_dir) + "/unet-tool";
8 if (!fs.access(unet_tool, "x")) {
9 warn("unet-tool missing\n");
13 unet_tool = "unet-tool";
25 warn("Usage: ",fs.basename(sourcepath())," [<flags>] <file> <command> [<args>] [<option>=<value> ...]\n",
28 " - create: Create a new network file\n",
29 " - set-config: Change network config parameters\n",
30 " - add-host <name>: Add a host\n",
31 " - add-ssh-host <name> <host>: Add a remote OpenWrt host via SSH\n",
32 " (<host> can contain SSH options as well)\n",
33 " - set-host <name>: Change host settings\n",
34 " - set-ssh-host <name> <host>: Update local and remote host settings\n",
35 " - add-service <name>: Add a service\n",
36 " - set-service <name>: Change service settings\n",
37 " - sign Sign network data\n",
40 " -p: Print modified JSON instead of updating file\n",
43 " - config options (create, set-config):\n",
44 " port=<val> set tunnel port (default: ", defaults.port, ")\n",
45 " pex_port=<val> set peer-exchange port (default: ", defaults.pex_port, ")\n",
46 " keepalive=<val> set keepalive interval (seconds, 0: off, default: ", defaults.keepalive,")\n",
47 " host options (add-host, add-ssh-host, set-host):\n",
48 " key=<val> set host public key (required for add-host)\n",
49 " port=<val> set host tunnel port number\n",
50 " groups=[+|-]<val>[,<val>...] set/add/remove groups that the host is a member of\n",
51 " ipaddr=[+|-]<val>[,<val>...] set/add/remove host ip addresses\n",
52 " subnet=[+|-]<val>[,<val>...] set/add/remove host announced subnets\n",
53 " endpoint=<val> set host endpoint address\n",
54 " gateway=<name> set host gateway (using name of other host)\n",
55 " ssh host options (add-ssh-host, set-ssh-host)\n",
56 " auth_key=<key> use <key> as public auth key on the remote host\n",
57 " priv_key=<key> use <key> as private host key on the remote host (default: generate a new key)\n",
58 " interface=<name> use <name> as interface in /etc/config/network on the remote host\n",
59 " domain=<name> use <name> as hosts file domain on the remote host (default: unet)\n",
60 " connect=<val>[,<val>...] set IP addresses that the host will contact for network updates\n",
61 " tunnels=<ifname>:<service>[,...] set active tunnel devices\n",
62 " service options (add-service, set-service):\n",
63 " type=<val> set service type (required for add-service)\n",
64 " members=[+|-]<val>[,<val>...] set/add/remove service member hosts/groups\n",
65 " vxlan service options (add-service, set-service):\n",
66 " id=<val> set VXLAN ID\n",
67 " port=<val> set VXLAN port\n",
68 " mtu=<val> set VXLAN device MTU\n",
69 " forward_ports=[+|-]<val>[,<val>...] set members allowed to receive broadcast/multicast/unknown-unicast\n",
71 " upload=<ip>[,<ip>...] upload signed file to hosts\n",
80 command = shift(ARGV);
83 int: function(object, name, val) {
84 object[name] = int(val);
86 string: function(object, name, val) {
89 array: function(object, name, val) {
90 let op = substr(val, 0, 1);
92 if (op == "+" || op == "-") {
100 let vals = split(val, ",");
102 object[name] = filter(object[name], function(v) {
106 push(object[name], val);
109 if (!length(object[name]))
114 service_field_types = {
119 forward_ports: "array",
131 if [ -n "$first" ]; then
136 uci $cmd "network.$INTERFACE.$field=$cur"
140 set_interface_attrs() {
141 [ -n "$AUTH_KEY" ] && uci set "network.$INTERFACE.auth_key=$AUTH_KEY"
142 set_list connect "$CONNECT"
143 set_list tunnels "$TUNNELS"
144 uci set "network.$INTERFACE.domain=$DOMAIN"
148 [ "$(uci -q get "network.$INTERFACE")" = "interface" -a "$(uci -q get "network.$INTERFACE.proto")" = "unet" ] && return 0
150 set network.$INTERFACE=interface
151 set network.$INTERFACE.proto=unet
152 set network.$INTERFACE.device=$INTERFACE
156 check_interface_key() {
157 key="$(uci -q get "network.$INTERFACE.key" | unet-tool -q -H -K -)"
159 uci set "network.$INTERFACE.key=$(unet-tool -G)"
160 key="$(uci get "network.$INTERFACE.key" | unet-tool -H -K -)"
176 function fetch_args() {
178 vals = match(arg, /^(.[[:alnum:]_-]*)=(.*)$/);
180 warn("Invalid argument: ", arg, "\n");
183 args[vals[1]] = vals[2]
187 function set_field(typename, object, name, val) {
188 if (!field_types[typename]) {
189 warn("Invalid type ", type, "\n");
193 if (type(val) != "string")
201 field_types[typename](object, name, val);
204 function set_fields(object, list) {
206 set_field(list[f], object, f, args[f]);
209 function set_host(name) {
210 let host = net_data.hosts[name];
223 function set_service(name) {
224 let service = net_data.services[name];
226 set_fields(service, {
231 if (service_field_types[service.type])
232 set_fields(service.config, service_field_types[service.type]);
235 function sync_ssh_host(host) {
236 let interface = args.interface ?? "unet";
237 let connect = replace(args.connect ?? "", ",", " ");
238 let auth_key = args.auth_key;
239 let tunnels = replace(replace(args.tunnels ?? "", ",", " "), ":", "=");
240 let domain = args.domain ?? "unet";
243 let fh = fs.mkstemp();
244 system(unet_tool + " -q -P -K " + file + ".key >&" + fh.fileno());
246 auth_key = fh.read("line");
248 auth_key = replace(auth_key, "\n", "");
249 if (auth_key == "") {
250 warn("Could not read auth key\n");
255 let fh = fs.mkstemp();
256 fh.write("INTERFACE='" + interface + "'\n");
257 fh.write("CONNECT='" + connect + "'\n");
258 fh.write("AUTH_KEY='" + auth_key + "'\n");
259 fh.write("TUNNELS='" + tunnels + "'\n");
260 fh.write("DOMAIN='" + domain + "'\n");
261 fh.write(ssh_script);
266 system(sprintf("ssh "+host+" sh <&%d >&%d", fh.fileno(), fh2.fileno()));
272 while (line = fh2.read("line")) {
273 let vals = match(line, /^(.[[:alnum:]_-]*)=(.*)\n$/);
275 warn("Invalid argument: ", arg, "\n");
278 data[vals[1]] = vals[2]
283 warn("Could not read host key from SSH host\n");
290 while (substr(ARGV[0], 0, 1) == "-") {
294 else if (opt == "-p")
300 if (command == "add-host" || command == "set-host" ||
301 command == "add-ssh-host" || command == "set-ssh-host") {
302 hostname = shift(ARGV);
304 warn("Missing host name argument\n");
309 if (command == "add-ssh-host" || command == "set-ssh-host") {
310 ssh_host = shift(ARGV);
312 warn("Missing SSH host/user argument\n");
317 if (command == "add-service" || command == "set-service") {
318 servicename = shift(ARGV);
320 warn("Missing service name argument\n");
327 if (command == "add-ssh-host" || command == "set-ssh-host") {
328 sync_ssh_host(ssh_host);
329 command = replace(command, "ssh-", "");
332 if (command == "create") {
341 warn("Could not open input file ", file, "\n");
347 warn("Could not parse input file ", file, "\n");
352 if (command == "create") {
353 for (key in keys(defaults))
354 args[key] ??= "" + defaults[key];
355 if (!fs.access(file + ".key"))
356 system(unet_tool + " -G > " + file + ".key");
359 if (command == "sign") {
360 ret = system(unet_tool + " -S -K " + file + ".key -o " + file + ".bin " + file);
365 hosts = split(args.upload, ",");
366 for (host in hosts) {
367 warn("Uploading " + file + ".bin to " + host + "\n");
368 ret = system(unet_tool + " -U " + host + " -K "+ file + ".key " + file + ".bin");
370 warn("Upload failed\n");
376 if (command == "create" || command == "set-config") {
377 set_fields(net_data.config, {
381 set_field("int", net_data.config, "peer-exchange-port", args.pex_port);
382 } else if (command == "add-host") {
383 net_data.hosts[hostname] = {};
385 warn("Missing host key\n");
389 } else if (command == "set-host") {
390 if (!net_data.hosts[hostname]) {
391 warn("Host '", hostname, "' does not exist\n");
395 } else if (command == "add-service") {
396 net_data.services[servicename] = {
401 warn("Missing service type\n");
404 set_service(servicename);
405 } else if (command == "set-service") {
406 if (!net_data.services[servicename]) {
407 warn("Service '", servicename, "' does not exist\n");
410 set_service(servicename);
412 warn("Unknown command\n");
416 net_data_json = sprintf("%.J\n", net_data);
418 print(net_data_json);
420 fs.writefile(file, net_data_json);