diff --git a/configs/common.config b/configs/common.config
index 84701b70..a8865d9b 100644
--- a/configs/common.config
+++ b/configs/common.config
@@ -3,6 +3,8 @@ CONFIG_BUSYBOX_CONFIG_ARPING=y
CONFIG_BUSYBOX_CONFIG_CROND=n
CONFIG_BUSYBOX_CONFIG_FEATURE_IPV6=n
CONFIG_BUSYBOX_CONFIG_FEATURE_TOP_INTERACTIVE=y
+CONFIG_BUSYBOX_CONFIG_DIFF=y
+CONFIG_BUSYBOX_CONFIG_MKPASSWD=y
CONFIG_BUSYBOX_CONFIG_MKSWAP=n
CONFIG_BUSYBOX_CONFIG_NTPD=y
CONFIG_BUSYBOX_CONFIG_SETSID=y
@@ -98,6 +100,7 @@ CONFIG_PACKAGE_libext2fs=m
CONFIG_PACKAGE_libiwinfo-lua=y
CONFIG_PACKAGE_liblua=y
CONFIG_PACKAGE_liblucihttp-lua=y
+CONFIG_PACKAGE_liblucihttp-ucode=y
CONFIG_PACKAGE_liblucihttp=y
CONFIG_PACKAGE_liblzo=m
CONFIG_PACKAGE_libncurses=m
@@ -154,7 +157,11 @@ CONFIG_PACKAGE_socat=m
CONFIG_PACKAGE_sysfsutils=m
CONFIG_PACKAGE_tcpdump-mini=m
CONFIG_PACKAGE_ubi-utils=y
+CONFIG_PACKAGE_ucode-mod-log=y
+CONFIG_PACKAGE_ucode-mod-math=y
+CONFIG_PACKAGE_ucode-mod-resolv=y
CONFIG_PACKAGE_uhttpd=y
+CONFIG_PACKAGE_uhttpd-mod-ucode=y
CONFIG_PACKAGE_vtun=y
CONFIG_PACKAGE_wireguard=y
CONFIG_PACKAGE_wireguard-tools=y
diff --git a/files/app/config.uc b/files/app/config.uc
new file mode 100755
index 00000000..0b58f4e1
--- /dev/null
+++ b/files/app/config.uc
@@ -0,0 +1,53 @@
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+
+import * as hardware from "aredn.hardware";
+
+export const debug = false;
+
+export const application = "/app";
+export let preload = true;
+export let compress = true;
+export let resourcehash = true;
+export let authenable = true;
+
+
+if (hardware.isLowMemNode()) {
+ preload = false;
+}
+if (debug) {
+ preload = false;
+ compress = false;
+ resourcehash = false;
+ authenable = false;
+}
diff --git a/files/app/constants.uc b/files/app/constants.uc
new file mode 100755
index 00000000..3868774c
--- /dev/null
+++ b/files/app/constants.uc
@@ -0,0 +1,43 @@
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+
+export const reUrl = /^http(s)?:\/\/.+$/;
+export const patUrl = "http(s)?:\/\/.+";
+export const reIP = /^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])\.?\b){4}$/;
+export const patIP = "((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])\\.?\\b){4}";
+export const rePort = /^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-4])$/;
+export const patPort = "([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-4])";
+export const reNetmask = /^((128|192|224|240|248|252|254)\.0\.0\.0)|(255\.(((0|128|192|224|240|248|252|254)\.0\.0)|(255\.(((0|128|192|224|240|248|252|254)\.0)|255\.(0|128|192|224|240|248|252|254)))))$/;
+export const patNetmask = "((128|192|224|240|248|252|254)\\.0\\.0\\.0)|(255\\.(((0|128|192|224|240|248|252|254)\\.0\\.0)|(255\\.(((0|128|192|224|240|248|252|254)\\.0)|255\\.(0|128|192|224|240|248|252|254)))))";
+export const reNodename = /^[0-9]?[A-Z]{1,2}[0-9]{1,4}[A-Z]{1,3}-[\-_a-zA-Z0-9]+$/;
+export const patNodename = "[0-9]?[A-Z]{1,2}[0-9]{1,4}[A-Z]{1,3}-[\\-_a-zA-Z0-9]+";
diff --git a/files/app/main/app.ut b/files/app/main/app.ut
new file mode 100755
index 00000000..557cd668
--- /dev/null
+++ b/files/app/main/app.ut
@@ -0,0 +1,106 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{% if (!request.headers["hx-boosted"]) { %}
+
+
+
+{% } %}
diff --git a/files/app/main/authenticate.ut b/files/app/main/authenticate.ut
new file mode 100755
index 00000000..0f7b0fc1
--- /dev/null
+++ b/files/app/main/authenticate.ut
@@ -0,0 +1,56 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+response.headers["Content-Type"] = "application/json";
+if (request.env.REQUEST_METHOD === "POST") {
+ try {
+ const j = json(uhttpd.recv(1024));
+ if (j.version === 1) {
+ if (j.logout) {
+ auth.deauthenticate();
+ print('{"authenticated":false}\n');
+ return;
+ }
+ if (auth.authenticate(j.password)) {
+ print('{"authenticated":true}\n');
+ return;
+ }
+ }
+ }
+ catch (_) {
+ }
+}
+print('{"authenticated":false}\n');
+%}
diff --git a/files/app/main/changes.ut b/files/app/main/changes.ut
new file mode 100755
index 00000000..39db6ea7
--- /dev/null
+++ b/files/app/main/changes.ut
@@ -0,0 +1,35 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{{_R("changes")}}
diff --git a/files/app/main/dhcp.ut b/files/app/main/dhcp.ut
new file mode 100755
index 00000000..0f89d799
--- /dev/null
+++ b/files/app/main/dhcp.ut
@@ -0,0 +1,35 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{{_R("dhcp")}}
diff --git a/files/app/main/firstuse-ram.ut b/files/app/main/firstuse-ram.ut
new file mode 100755
index 00000000..cfd24061
--- /dev/null
+++ b/files/app/main/firstuse-ram.ut
@@ -0,0 +1,104 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+if (request.env.REQUEST_METHOD === "POST") {
+ print(_R("reboot-firstuse-ram"));
+ response.upgrade = `/usr/local/bin/aredn_sysupgrade -n -q ${request.args.firmwarefile}`;
+ return;
+}
+%}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
AREDNTM
+
Amateur Radio Emergency Data Network
+
+
+
Welcome
+
+
Congratulations on booting AREDN®
+
AREDN® is currently running in RAM. The next step is to install AREDN® into Flash.
+
Download the sysupgrade.bin file for this device (it should be at the same place your found this
+ kernel.bin file) and upload it using the file selector below
+
+
+
+
Select Firmware File
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/files/app/main/firstuse.ut b/files/app/main/firstuse.ut
new file mode 100755
index 00000000..117590c5
--- /dev/null
+++ b/files/app/main/firstuse.ut
@@ -0,0 +1,132 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+if (request.env.REQUEST_METHOD === "PUT") {
+ system(`/usr/local/bin/firstuse-setup '${request.args.name}' '${request.args.passwd}'`);
+ response.reboot = true;
+ print(_R("reboot-firstuse"));
+ return;
+}
+%}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
AREDNTM
+
Amateur Radio Emergency Data Network
+
+
+
Welcome
+
+
Congratulations on installing AREDN®
+
There's a few pieces of basic information we need to start setting up your node.
+
+
+
+
Node Name
+
+
+
+ This is the unique name given to your node. It must start with your callsign. For example, K6AH-Home
+
+
+
New Password
+
+
+
+
Retype Password
+
+
+
+ Enter a password, twice, to assign to your node for access to configuration information later
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/files/app/main/health.ut b/files/app/main/health.ut
new file mode 100755
index 00000000..0dabe6ce
--- /dev/null
+++ b/files/app/main/health.ut
@@ -0,0 +1,35 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{{_R("health")}}
diff --git a/files/app/main/info.ut b/files/app/main/info.ut
new file mode 100755
index 00000000..8cd8aa42
--- /dev/null
+++ b/files/app/main/info.ut
@@ -0,0 +1,48 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+response.headers["Content-Type"] = "text/plain";
+for (k in request.env) {
+ if (k !== "headers") {
+ print("request.env." + k + ": " + request.env[k] + "\r\n");
+ }
+}
+for (k in request.headers) {
+ print("request.headers." + k + ": " + request.headers[k] + "\r\n");
+}
+for (k in uhttpd) {
+ print("uhttpd." + k + ": " + uhttpd[k] + "\r\n");
+}
+%}
diff --git a/files/app/main/local-and-neighbor-devices.ut b/files/app/main/local-and-neighbor-devices.ut
new file mode 100755
index 00000000..1fa99f76
--- /dev/null
+++ b/files/app/main/local-and-neighbor-devices.ut
@@ -0,0 +1,35 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{{_R("local-and-neighbor-devices")}}
diff --git a/files/app/main/mesh-summary.ut b/files/app/main/mesh-summary.ut
new file mode 100755
index 00000000..3b323c2d
--- /dev/null
+++ b/files/app/main/mesh-summary.ut
@@ -0,0 +1,35 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{{_R("mesh-summary")}}
diff --git a/files/app/main/messages.ut b/files/app/main/messages.ut
new file mode 100755
index 00000000..47ef9b23
--- /dev/null
+++ b/files/app/main/messages.ut
@@ -0,0 +1,40 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{% if (messages.haveMessages() || (auth.isAdmin && messages.haveToDos())) { %}
+
+ {{_R("messages")}}
+
+
+{% } %}
diff --git a/files/app/main/packages.ut b/files/app/main/packages.ut
new file mode 100755
index 00000000..dd63ce0f
--- /dev/null
+++ b/files/app/main/packages.ut
@@ -0,0 +1,37 @@
+<{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+div id="packages" hx-swap-oob="innerHTML">
+{{_R("packages", "oob")}}
+
diff --git a/files/app/main/status/e/basics.ut b/files/app/main/status/e/basics.ut
new file mode 100755
index 00000000..f5e761cd
--- /dev/null
+++ b/files/app/main/status/e/basics.ut
@@ -0,0 +1,340 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+function getSshKeys()
+{
+ let options = "";
+ const f = fs.open("/etc/dropbear/authorized_keys");
+ if (f) {
+ const re = /^(.+) (.+) (.+)$/;
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ const m = match(trim(l), re);
+ if (m) {
+ options += ``;
+ }
+ }
+ f.close();
+ }
+ return options;
+}
+if (request.env.REQUEST_METHOD === "PUT") {
+ if ("theme" in request.args) {
+ const theme = request.args.theme;
+ if (fs.access(`${config.application}/resource/css/themes/${theme}.css`)) {
+ fs.unlink(`${config.application}/resource/css/theme.css`);
+ fs.symlink(`themes/${theme}.css`, `${config.application}/resource/css/theme.css`);
+ if (config.resourcehash) {
+ const themes = fs.lsdir(`${config.application}/resource/css/themes`);
+ const re = regexp(`^${theme}\.css\.(.+)\.gz`);
+ for (let i = 0; i < length(themes); i++) {
+ const m = match(themes[i], re);
+ if (m) {
+ fs.unlink(`${config.application}/resource/css/theme.version`);
+ fs.symlink(m[1], `${config.application}/resource/css/theme.version`);
+ break;
+ }
+ }
+ }
+ }
+ return;
+ }
+ configuration.prepareChanges();
+ if ("description_node" in request.args) {
+ configuration.setSetting("description_node", replace(request.args.description_node || "", "'", "’"));
+ }
+ if ("notes" in request.args) {
+ uciMesh.set("aredn", "@notes[0]", "private", replace(request.args.notes || "", "'", "’"));
+ uciMesh.commit("aredn");
+ }
+ if ("node_name" in request.args) {
+ const name = request.args.node_name;
+ if (match(name, constants.reNodename)) {
+ configuration.setName(name);
+ uciMesh.foreach("vtun", "server", s => {
+ const netip = s.netip;
+ if (index(net, ":") === -1) {
+ const np = split(netip, ":");
+ const n = iptoarr(np[0]);
+ uciMesh.set("vtun", s[".name"], "node", `${uc(substr(name, 0, 23))}-${n[0]}-${n[1]}-${n[2]}-${n[3]}:${np[1]}`);
+ }
+ else {
+ const n = iptoarr(netip);
+ uciMesh.set("vtun", s[".name"], "node", `${uc(substr(name, 0, 23))}-${n[0]}-${n[1]}-${n[2]}-${n[3]}`);
+ }
+ });
+ uciMesh.commit("vtun");
+ }
+ }
+ if ("passwd" in request.args) {
+ configuration.setPassword(request.args.passwd);
+ }
+ if ("ssh_remove" in request.args) {
+ print(request.args);
+ const keys = split(fs.readfile("/etc/dropbear/authorized_keys"), "\n");
+ const re = /^(.+) (.+) (.+)$/;
+ for (let i = 0; i < length(keys); i++) {
+ const m = match(keys[i], re);
+ if (m && m[3] === request.args.ssh_remove) {
+ splice(keys, i, 1);
+ break;
+ }
+ }
+ fs.writefile("/etc/dropbear/authorized_keys", join("\n", keys));
+ print(getSshKeys());
+ }
+ if ("ssh_add" in request.args) {
+ configuration.prepareChanges();
+ const key = fs.readfile(request.args.ssh_add);
+ if (key && match(trim(key), /^(ssh-rsa|ecdsa-sha2-nistp256) [a-zA-Z0-9+\/=]+ .+$/)) {
+ const keys = fs.readfile("/etc/dropbear/authorized_keys") || "";
+ fs.writefile("/etc/dropbear/authorized_keys", `${keys}${key}`);
+ }
+ print(getSshKeys());
+ }
+ configuration.saveSettings();
+ print(_R("changes"));
+ return;
+}
+if (request.env.REQUEST_METHOD === "DELETE") {
+ configuration.revertModalChanges();
+ print(_R("changes"));
+ return;
+}
+%}
+
+ {{_R("dialog-header", "Name & Security")}}
+
+
+
+
Node Name
+
This node's unique name
+
+
+
+
+
+ {{_H("Change the node's unique name. The name must start with your callsign and be less than 64 characters long.")}}
+
+
+
Description
+
Information about this node
+
+
+
+
+
+ {{_H("Some optional descriptive text about this node. This can be anything you think relevant.
+ People include various thing, such as some basic information about the location or hardware or
+ the services this node provides. Some people include alternate ways to reach the owner (e.g email
+ address).")}}
+
+
+
Notes
+
Private notes about this node
+
+
+
+
+
+ {{_H("Private notes about this node which are only visible to the operator. This can be anything
+ thought relevant or useful. For example it might include information about custom configurations
+ or attached devices.")}}
+
+ {{_H("Select the display theme for this node. This theme determines how everyone see this node when they visit. The
+ default theme automatically selects either Light or Dark depending on the viewers browser settings.")}}
+
+
+
+
+
New Password
+
Change the node password
+
+
+
+
+
+
+
+
Retype Password
+
Passwords must match
+
+
+
+
+
+ {{_H("Set a new password for this device by entering it twice in the boxes. Don't use the # or any quote character. This password
+ is used for logging into the UI as well as telnet and ssh access.")}}
+
+ {{_R("dialog-advanced")}}
+
+ {% if (includeAdvanced) { %}
+
+
+
Upload SSH Key
+
Add SSH key
+
+
+
+
+
+
+
+
Remove SSH Key
+
Delete SSH key
+
+
+
+
+
+ {{_H("Console access to the node is possible using SSH. To avoid typing a password you can upload an SSH public key which allows
+ password-less login. Multiple keys can be uploaded above, and keys can also be selectively removed.")}}
+
+
+
+
+
+
+ {% } %}
+
+
+ {{_R("dialog-footer")}}
+
+
diff --git a/files/app/main/status/e/changes.ut b/files/app/main/status/e/changes.ut
new file mode 100755
index 00000000..39db6ea7
--- /dev/null
+++ b/files/app/main/status/e/changes.ut
@@ -0,0 +1,35 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{{_R("changes")}}
diff --git a/files/app/main/status/e/dhcp.ut b/files/app/main/status/e/dhcp.ut
new file mode 100755
index 00000000..588be828
--- /dev/null
+++ b/files/app/main/status/e/dhcp.ut
@@ -0,0 +1,623 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+if (request.env.REQUEST_METHOD === "PUT") {
+ if ("options" in request.args) {
+ configuration.prepareChanges();
+ const options = json(request.args.options);
+ const dhcp = configuration.getDHCP();
+ let f = fs.open(dhcp.reservations, "w");
+ if (f) {
+ const base = iptoarr(dhcp.start)[3];
+ for (let i = 0; i < length(options); i++) {
+ const o = options[i];
+ if (o.reserved) {
+ const ip = iptoarr(o.ip)[3];
+ if (length(o.name) > 0 && ip >= base && match(o.mac, /^([0-9a-fA-F][0-9a-fA-F]:){5}[0-9a-fA-F][0-9a-fA-F]$/)) {
+ f.write(`${o.mac} ${ip - base + 2} ${o.name}${o.noprop ? " #NOPROP" : ""}\n`);
+ }
+ }
+ }
+ f.close();
+ }
+ }
+ if ("advtags" in request.args) {
+ configuration.prepareChanges();
+ const advtags = json(request.args.advtags);
+ const dhcp = configuration.getDHCP();
+ let f = fs.open(dhcp.dhcptags, "w");
+ if (f) {
+ for (let i = 0; i < length(advtags); i++) {
+ const t = advtags[i];
+ f.write(`${t.name} ${t.type} ${t.match}\n`);
+ }
+ f.close();
+ }
+ }
+ if ("advoptions" in request.args) {
+ configuration.prepareChanges();
+ const advoptions = json(request.args.advoptions);
+ const dhcp = configuration.getDHCP();
+ let f = fs.open(dhcp.dhcpoptions, "w");
+ if (f) {
+ for (let i = 0; i < length(advoptions); i++) {
+ const o = advoptions[i];
+ f.write(`${o.name} ${o.always ? "force" : "onrequest"} ${o.type} ${o.value}\n`);
+ }
+ f.close();
+ }
+ }
+ print(_R("changes"));
+ return;
+}
+if (request.env.REQUEST_METHOD === "DELETE") {
+ configuration.revertModalChanges();
+ print(_R("changes"));
+ return;
+}
+const dhcp = configuration.getDHCP();
+const start = iptoarr(dhcp.start);
+const end = iptoarr(dhcp.end);
+const leases = [];
+const options = [];
+const advoptions = [];
+const advtags = [];
+for (let i = start[3]; i <= end[3]; i++) {
+ push(options, { mac: "", ip: `${start[0]}.${start[1]}.${start[2]}.${i}`, name: "", noprop: false, reserved: false, leased: false });
+}
+let reservations = 0;
+let active = 0;
+let f = fs.open(dhcp.reservations);
+if (f) {
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ // mac, last-ip, name, flags
+ const v = match(trim(l), /^([^ ]+) ([^ ]+) ([^ ]+) ?(.*)/);
+ if (v) {
+ const o = options[int(v[2]) - 2];
+ if (o) {
+ o.mac = v[1];
+ o.name = v[3];
+ o.noprop = v[4] == "#NOPROP";
+ o.reserved = true;
+ reservations++;
+ }
+ }
+ }
+ f.close();
+}
+f = fs.open(dhcp.leases);
+if (f) {
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ // ?, mac, ip, name, ?
+ const v = match(l, /^(.+) (.+) (.+) (.+) (.+)$/);
+ if (v) {
+ const ip = iptoarr(v[3]);
+ const o = options[ip[3] - start[3]];
+ if (o) {
+ o.leased = true;
+ if (o.mac === "") {
+ o.mac = v[2];
+ }
+ if (o.name === "") {
+ o.name = v[4];
+ }
+ active++;
+ }
+ }
+ }
+ f.close();
+}
+f = fs.open(dhcp.dhcptags);
+if (f) {
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ const m = match(trim(l), /^(.+) (.+) (.+)$/);
+ if (m) {
+ push(advtags, { name: m[1], type: m[2], match: m[3] });
+ }
+ }
+ f.close();
+}
+f = fs.open(dhcp.dhcpoptions);
+if (f) {
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ const m = match(trim(l), /^(.*) (force|onrequest) ([0-9]+) (.+)$/);
+ if (m) {
+ push(advoptions, { name: m[1], always: m[2] === "force", type: int(m[3]), value: m[4] });
+ }
+ }
+ f.close();
+}
+%}
+
+ {{_R("dialog-header", "LAN DHCP")}}
+
+
+
+
+
+
Address Reservations
+
Hostnames with fixed addresses
+
+
+
+
+ {{_H("Creates a permenant mapping between a device MAC address and an IP address on the LAN network.
+ The given hostname is available to everyone on the mesh unless the entry is marked as do not propagate")}}
+
+
+
+
hostname
+
ip address
+
mac address
+
do not propagate
+
+
+
+ {% if (reservations > 0) {
+ for (let i = 0; i < length(options); i++) {
+ const o = options[i];
+ if (o.reserved) {
+ %}
+
+
+
+
+
+
+
+
+
+ {% }
+ }
+ } %}
+
+
+
Active Leases
+
Addresses currently in use
+ {{_H("The list of active leases currently allocated to LAN devices. Any of these leases can be promoted
+ to a permanent mapping to allow IP Addresses to be fixed to specific devices.")}}
+ {% if (active > 0) {
+ %}
+
+
+
hostname
+
ip address
+
mac address
+
+
+
+ {%
+ for (let i = 0; i < length(options); i++) {
+ const o = options[i];
+ if (o.leased) {
+ %}
+
+
+
+
+
+
+
+
+ {% }
+ }
+ } %}
+
+ {{_R("dialog-advanced")}}
+
+ {% if (includeAdvanced) { %}
+
+
+
+
Tags
+
Tags for advanced options
+
+
+
+
+
+
tag
+
type
+
match
+
+
+
{%
+ for (let i = 0; i < length(advtags); i++) {
+ const t = advtags[i];
+ %}
+
+
+
+
+
+
+
{%
+ }
+ %}
+
+
+
+
+
Options
+
Advanced options
+
+
+
+
+
+
tag
+
option
+
value
+
always
+
+
+
{%
+ for (let i = 0; i < length(advoptions); i++) {
+ const o = advoptions[i];
+ %}
+
+
+
+
+
+
+
+
{%
+ }
+ %}
+
+ {% } %}
+
+
+ {{_R("dialog-footer")}}
+
+
diff --git a/files/app/main/status/e/firmware.ut b/files/app/main/status/e/firmware.ut
new file mode 100755
index 00000000..ddfd687a
--- /dev/null
+++ b/files/app/main/status/e/firmware.ut
@@ -0,0 +1,616 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+ function curl(url, filename, start, len)
+ {
+ const name = filename ? filename : `/tmp/download.${time()}`;
+ const f = fs.popen(`/usr/bin/curl --progress-bar -o ${name} ${url} 2>&1`);
+ if (!f) {
+ return null;
+ }
+ uhttpd.send(`event: progress\r\ndata: ${start}\r\n\r\n`);
+ for (;;) {
+ const l = f.read("\r");
+ if (!length(l)) {
+ break;
+ }
+ const m = match(trim(l), /([0-9\.]+)%$/);
+ if (m) {
+ uhttpd.send(`event: progress\r\ndata: ${start + len * m[1] / 100}\r\n\r\n`);
+ }
+ }
+ f.close();
+ if (!fs.access(name)) {
+ return null;
+ }
+ if (filename === name) {
+ return filename;
+ }
+ const f2 = fs.open(name);
+ fs.unlink(name);
+ return f2;
+ };
+ function prepareUpgrade(firmwarefile)
+ {
+ let error = "Failed.";
+ if (firmwarefile) {
+ const f = fs.popen(`/usr/libexec/validate_firmware_image ${firmwarefile}`);
+ if (f) {
+ const info = json(f.read("all"));
+ f.close();
+ if (info.valid) {
+ error = null;
+ }
+ else if (info.forceable && fs.access("/tmp/force-upgrade-this-is-dangerous")) {
+ error = null;
+ }
+ else if (!info.tests.fwtool_device_match) {
+ error = "Firmware not compatible with this device.";
+ }
+ else if (!info.tests.fwtool_signature) {
+ error = "Corrupted firmware, bad signature.";
+ }
+ else {
+ error = "Unknown error validating firmware.";
+ }
+ }
+ else {
+ error = "Failed to validate firmware.";
+ }
+ }
+ if (!error) {
+ fs.unlink("/tmp/arednsysupgradebackup.tgz");
+ if (!fs.access("/tmp/do-not-keep-configuration")) {
+ const fi = fs.open("/etc/arednsysupgrade.conf");
+ if (fi) {
+ const fo = fs.open("/tmp/sysupgradefilelist", "w");
+ if (fo) {
+ for (let l = fi.read("line"); length(l); l = fi.read("line")) {
+ if (!match(l, "^#") && fs.access(trim(l))) {
+ fo.write(l);
+ }
+ }
+ fo.close();
+ configuration.setUpgrade("1");
+ if (system("tar -czf /tmp/arednsysupgradebackup.tgz -T /tmp/sysupgradefilelist > /dev/null 2>&1") < 0) {
+ fs.unlink("/tmp/arednsysupgradebackup.tgz");
+ }
+ configuration.setUpgrade("0");
+ fs.unlink("/tmp/sysupgradefilelist");
+ }
+ fi.close();
+ }
+ if (!fs.access("/tmp/arednsysupgradebackup.tgz")) {
+ error = "Failed to create backup.";
+ }
+ }
+ if (!error) {
+ return { upgrade: `/usr/local/bin/aredn_sysupgrade ${fs.access("/tmp/arednsysupgradebackup.tgz") ? "-f /tmp/arednsysupgradebackup.tgz" : "-n"} -q ${firmwarefile}` };
+ }
+ }
+ return { error: error };
+ };
+ function shutdownServices()
+ {
+ if (hardware.isLowMemNode()) {
+ system([ "/etc/init.d/manager", "stop" ]);
+ system([ "/etc/init.d/telnet", "stop" ]);
+ system([ "/etc/init.d/dropbear", "stop" ]);
+ system([ "/etc/init.d/urngd", "stop" ]);
+ }
+ };
+ function restoreServices()
+ {
+ if (hardware.isLowMemNode()) {
+ system([ "/etc/init.d/urngd", "start" ]);
+ system([ "/etc/init.d/telnet", "start" ]);
+ system([ "/etc/init.d/dropbear", "start" ]);
+ system([ "/etc/init.d/manager", "start" ]);
+ }
+ };
+ if (request.env.REQUEST_METHOD === "PUT") {
+ if (request.args.keepconfig) {
+ if (request.args.keepconfig === "off") {
+ fs.open("/tmp/do-not-keep-configuration", "w").close();
+ }
+ else {
+ fs.unlink("/tmp/do-not-keep-configuration");
+ }
+ }
+ if (request.args.dangerousupgrade) {
+ if (request.args.dangerousupgrade === "on") {
+ fs.open("/tmp/force-upgrade-this-is-dangerous", "w").close();
+ }
+ else {
+ fs.unlink("/tmp/force-upgrade-this-is-dangerous");
+ }
+ }
+ if (request.args.firmwareurl) {
+ if (match(request.args.firmwareurl, constants.reUrl)) {
+ configuration.prepareChanges();
+ uciMesh.set("aredn", "@downloads[0]", "firmware_aredn", request.args.firmwareurl);
+ uciMesh.commit("aredn");
+ print(_R("changes"));
+ }
+ }
+ return;
+ }
+ else if (request.env.REQUEST_METHOD === "POST") {
+ if (request.args.sideload) {
+ const upgrade = prepareUpgrade("/tmp/local_firmware");
+ if (upgrade.error) {
+ print(`
`);
+ print(``);
+ restoreServices();
+ }
+ else {
+ response.upgrade = upgrade.upgrade;
+ print(_R("reboot-firmware"));
+ }
+ }
+ return;
+ }
+ else if (request.env.REQUEST_METHOD === "GET" && request.env.QUERY_STRING === "v=update") {
+ response.override = true;
+ uhttpd.send("Status: 200 OK\r\nContent-Type: text/event-stream\r\nCache-Control: no-store\r\n\r\n");
+
+ fs.unlink("/tmp/firmware.list");
+ const aredn_firmware = uci.get("aredn", "@downloads[0]", "firmware_aredn");
+ if (!aredn_firmware) {
+ uhttpd.send(`event: error\r\ndata: missing firmware download url\r\n\r\n`);
+ return;
+ }
+ let f = curl(`${aredn_firmware}/afs/www/config.js`, null, 0, 10);
+ if (!f) {
+ uhttpd.send(`event: error\r\ndata: missing firmware config\r\n\r\n`);
+ return;
+ }
+ let firmware_versions = {};
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ const m = match(l, /versions: \{(.+)\}/);
+ if (m) {
+ const kvs = split(m[1], ", ");
+ for (let i = 0; i < length(kvs); i++) {
+ const kv = split(kvs[i], ": ");
+ firmware_versions[trim(kv[0], "'")] = trim(kv[1], "'");
+ }
+ break;
+ }
+ }
+ f.close();
+ const firmware_version_count = length(keys(firmware_versions));
+ if (firmware_version_count === 0) {
+ uhttpd.send(`event: error\r\ndata: failed to load firmware versions\r\n\r\n`);
+ return;
+ }
+ const board_type = replace(hardware.getBoard().model.id, ",", "_");
+ const firmware_ulist = {};
+ let count = 0;
+ for (let ver in firmware_versions) {
+ const data = firmware_versions[ver];
+ f = curl(`${aredn_firmware}/afs/www/${data}/overview.json`, null, 10 + count * 90 / firmware_version_count, 90 / firmware_version_count);
+ if (f) {
+ const info = json(f.read("all"));
+ f.close();
+ for (let i = 0; i < length(info.profiles); i++) {
+ const profile = info.profiles[i];
+ if (profile.id === board_type || ((board_type === "qemu" || board_type === "vmware") && profile.id == "generic" && profile.target === "x86/64")) {
+ firmware_ulist[ver] = {
+ overview: `${aredn_firmware}/afs/www/${data}/${profile.target}/${profile.id}.json`,
+ target: replace(info.image_url, "{target}", profile.target)
+ };
+ }
+ }
+ }
+ count++;
+ }
+ const firmware_list = {};
+ const firmware_vers = sort(keys(firmware_ulist), function(a, b) {
+ if (index(a, "-") !== -1) {
+ return 1;
+ }
+ if (index(b, "-") !== -1) {
+ return -1;
+ }
+ const av = split(a, ".");
+ const bv = split(b, ".");
+ for (let i = 0; i < 4; i++) {
+ av[i] = int(av[i]);
+ bv[i] = int(bv[i]);
+ if (av[i] < bv[i]) {
+ return 1
+ }
+ if (av[i] > bv[i]) {
+ return -1
+ }
+ }
+ return 0;
+ });
+ for (let i = 0; i < length(firmware_vers); i++) {
+ const k = firmware_vers[i];
+ firmware_list[k] = firmware_ulist[k];
+ }
+ f = fs.open("/tmp/firmware.list", "w");
+ if (!f) {
+ uhttpd.send(`event: error\r\ndata: failed to create firmware list\r\n\r\n`);
+ return;
+ }
+ f.write(sprintf("%J", firmware_list));
+ f.close();
+
+ let html = ``;
+ for (let k in firmware_list) {
+ html += ``;
+ }
+ uhttpd.send(`event: close\r\ndata: ${html}\r\n\r\n`);
+ return;
+ }
+ else if (request.env.REQUEST_METHOD === "GET" && index(request.env.QUERY_STRING, "v=") === 0) {
+ response.override = true;
+ const version = substr(request.env.QUERY_STRING, 2);
+ uhttpd.send("Status: 200 OK\r\nContent-Type: text/event-stream\r\nCache-Control: no-store\r\n\r\n");
+ let f = fs.open("/tmp/firmware.list");
+ if (!f) {
+ uhttpd.send(`event: error\r\ndata: missing firmware list\r\n\r\n`);
+ return;
+ }
+ const list = json(f.read("all"));
+ f.close();
+ const inst = list[version];
+ if (!inst) {
+ uhttpd.send(`event: error\r\ndata: bad firmware version\r\n\r\n`);
+ return;
+ }
+ f = curl(inst.overview, null, 0, 5);
+ if (!f) {
+ uhttpd.send(`event: error\r\ndata: could not find firmware\r\n\r\n`);
+ return;
+ }
+ const overview = json(f.read("all"));
+ f.close();
+ let fwimage = null;
+
+ let booter_version = null;
+ const bv = fs.open("/sys/firmware/mikrotik/soft_config/bios_version");
+ if (bv) {
+ const v = bv.read("all");
+ bv.close();
+ if (substr(v, 2) === "7.") {
+ booter_version = "v7"
+ }
+ }
+ for (let i = 0; i < length(overview.images); i++) {
+ const image = overview.images[i];
+ if ((!booter_version && (image.type === "sysupgrade" || image.type === "nand-sysupgrade" || image.type == "combined")) ||
+ (booter_version === "v7" && image.type === "sysupgrade-v7")) {
+ fwimage = {
+ url: `${inst.target}/${image.name}`,
+ sha: image.sha256
+ };
+ break;
+ }
+ }
+ if (!fwimage) {
+ uhttpd.send(`event: error\r\ndata: missing firmware\r\n\r\n`);
+ return;
+ }
+ shutdownServices();
+ let r = curl(fwimage.url, "/tmp/firmwarefile", 5, 95);
+ if (!r) {
+ uhttpd.send(`event: error\r\ndata: failed to start firmware download\r\n\r\n`);
+ restoreServices();
+ return;
+ }
+ const upgrade = prepareUpgrade("/tmp/firmwarefile");
+ if (upgrade.error) {
+ fs.unlink("/tmp/firmwarefile");
+ uhttpd.send(`event: error\r\ndata: ${upgrade.error}\r\n\r\n`);
+ restoreServices();
+ }
+ else {
+ response.upgrade = upgrade.upgrade;
+ uhttpd.send(`event: close\r\ndata: ${sprintf("%J", { v:_R("reboot-firmware")})}\r\n\r\n`);
+ }
+ return;
+ }
+ fs.unlink("/tmp/force-upgrade-this-is-dangerous");
+ fs.unlink("/tmp/do-not-keep-configuration");
+
+ const sideload = fs.access("/tmp/local_firmware");
+%}
+
+ {{_R("dialog-header", "Firmware")}}
+
+ {{_R("dialog-messages")}}
+
+
+
Firmware
+
Current firmware version
+
+
+
+
+
+
+
+
Hardware
+
Hardware type
+
+
+
+
+
+ {{_H("The hardware type is useful when finding firmware files to upload or sideload. When using the download feature
+ the node will automatically find the correct firmware.")}}
+
+
+
+
+
Download Firmware
+
Download firmware from an AREDN server.
+
+
+
+
+
+
+
+
+ {{_H("Download firmware directly from a central server, either on the Internet or a locally configured mesh server.
+ Refresh the list of available firmware version using the refresh button to the right of the firmware list. Once a
+ firmware is selected it can be downloaded and installed using the button at the base of the dialog.")}}
+
+
+
+
Upload Firmware
+
Upload a firmware file from your computer.
+
+
+
+
+
+ {{_H("Upload a firmware file from your computer. Once the firmware has been selected it can be uploaded and installed
+ using the button at the base of the dialog.")}}
+
+
+
+
Sideload Firmware
+
Use an alternatve way to load firmware onto the node.
+
+
+
+
+
+ {{_H("Sideload a firmware file by transferring it onto the node by some other means (e.g. scp) and putting it in the /tmp directory
+ with the name local_firmware. It can then be installed using the button at the base of the dialog.")}}
+ {{_R("dialog-advanced")}}
+
+ {% if (includeAdvanced) { %}
+ {% if (fs.access("/rom/etc")) { %}
+
+ {{_H("Keep the current configuration when updating the node's firmware. This is usually what you want to do, but on
+ rare occasions you might want to return the node to its first boot state.")}}
+ {% } %}
+
+
+
Dangerous Upgrade
+
Force the firmware onto the device, even if it fails the safety checks.
+ {{_H("Force the firmware to be installed, even if the system thinks it is not compatible. You almost never
+ want to do this so this should be used with care.")}}
+
+
+
Firmware URL
+
URL for downloading firmware
+
+
+
+
+
+ {{_H("The base URL used to download firmware. By default this points to the main AREDN repository, but you can change this
+ to a local server, especially if you'd like to do this without a connection to the Internet.")}}
+ {% } %}
+
+
+
+
+
+
+
+
+ {{_H(" Depending on how the firmware it to be installed using the options above, this button will initiate the process.")}}
+
+
+ {{_R("dialog-footer", "nocancel" )}}
+
+
diff --git a/files/app/main/status/e/internal-services.ut b/files/app/main/status/e/internal-services.ut
new file mode 100755
index 00000000..9d3e1e49
--- /dev/null
+++ b/files/app/main/status/e/internal-services.ut
@@ -0,0 +1,261 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+if (request.env.REQUEST_METHOD === "PUT") {
+ configuration.prepareChanges();
+ if (request.args.cloudmesh) {
+ uciMesh.set("aredn", "@supernode[0]", "support", request.args.cloudmesh === "on" ? "1" : "0");
+ }
+ if (request.args.iperf) {
+ uciMesh.set("aredn", "@iperf[0]", "enable", request.args.iperf === "on" ? "1" : "0");
+ }
+ if (request.args.ssh_access) {
+ uciMesh.set("aredn", "@wan[0]", "ssh_access", request.args.ssh_access === "on" ? "1" : "0");
+ }
+ if (request.args.telnet_access) {
+ uciMesh.set("aredn", "@wan[0]", "telnet_access", request.args.telnet_access === "on" ? "1" : "0");
+ }
+ if (request.args.web_access) {
+ uciMesh.set("aredn", "@wan[0]", "web_access", request.args.web_access === "on" ? "1" : "0");
+ }
+ if ("remotelog" in request.args) {
+ uciMesh.set("aredn", "@remotelog[0]", "url", request.args.remotelog);
+ }
+ if (request.args.watchdog) {
+ uciMesh.set("aredn", "@watchdog[0]", "enable", request.args.watchdog === "on" ? "1" : "0");
+ }
+ if ("watchdog_ping_address" in request.args) {
+ uciMesh.set("aredn", "@watchdog[0]", "ping_addresses", request.args.watchdog_ping_address);
+ }
+ if ("watchdog_daily" in request.args) {
+ uciMesh.set("aredn", "@watchdog[0]", "daily", request.args.watchdog_daily);
+ }
+ if ("power_poe" in request.args) {
+ uciMesh.set("aredn", "@poe[0]", "passthrough", request.args.power_poe === "on" ? "1" : "0");
+ }
+ if ("power_usb" in request.args) {
+ uciMesh.set("aredn", "@usb[0]", "passthrough", request.args.power_usb === "on" ? "1" : "0");
+ }
+ if ("message_pollrate" in request.args) {
+ uciMesh.set("aredn", "@alerts[0]", "pollrate", request.args.message_pollrate);
+ }
+ if ("message_localpath" in request.args) {
+ uciMesh.set("aredn", "@alerts[0]", "localpath", request.args.message_localpath);
+ }
+ if ("message_groups" in request.args) {
+ uciMesh.set("aredn", "@alerts[0]", "groups", request.args.message_groups);
+ }
+ uciMesh.commit("aredn");
+ print(_R("changes"));
+ return;
+}
+if (request.env.REQUEST_METHOD === "DELETE") {
+ configuration.revertModalChanges();
+ print(_R("changes"));
+ return;
+}
+%}
+
+ {{_H("By default, your node will locate and use any available supernode it finds on your local mesh.
+ This allows you to connect to any node in the AREDN cloud. Disable this option if you don't want to connect.")}}
+
+
+
iPerf3 Server
+
Enable the iperf3 server for easy connection speed testing
+ {{_H("Enable the included iperf3 client and server tools. This makes it easy to perform bandwidth tests between arbitrary nodes
+ in the network. The client and server are only invoked on demand, so there is no performance impact on the node except when tests
+ are performed.")}}
+
+
+
Remote logging
+
Send internal logging information to a remove server
+
+
+
+
+
+ {{_H("Local node logs are kept in a limited amount of RAM which means older information is lost, and all logs are lost on reboot.
+ This options will send the logs to a remote log server using the syslog protocol. The option should be udp://ip-address:port or tcp://ip-adress:port.
+ Leave blank if no remote logging is required.")}}
+
+ {{_H("Allow access to the node via the WAN network using telnet. Disabling this option will not prevent telnet acccess to the node from the mesh.")}}
+
+ {{_H("Allow access to the node's web interface via the WAN network. Disabling this option will not prevent web acccess to the node from the mesh.")}}
+
+
+
+
Watchdog
+
Allow watchdog to reboot the node if it becomes unresponsive
+ {{_H("Enables the hardware watchdog timer. This timer will reboot the device if it becomes unresponsive or various critical AREDN components
+ stop running correctly. Because the watchdog is in the hardware, even if the kernel crashes, the device will still reboot itself.
+ ")}}
+
+
+
+
Watchdog IP address
+
IP address to check periodically
+
+
+
+
+
+ {{_H("As part of the watchdog process, the watchdog periodically makes sure it can reach a given IP address. If it can't the node will be rebooted.
+ It is important that this IP address is easy and quick to reach. Don't try to reach IP addresses on the Internet.")}}
+
+
+
Daily Watchdog hour
+
Reboot the node at a specific hour every day
+
+
+
+
+
+ {{_H("For the node to reboot at a specific hour of the day (between 0 and 23), every day. Hopefully this isn't necessary. but it can be a good fallback for nodes which
+ are unreliable and in places difficult to reach.")}}
+
+
+ {% if (hardware.hasPOE()) { %}
+
+
+
PoE Passthrough
+
Enable power-over-ethernet on ports which support it
+ {{_H("Enable power over ethernet on ethernet ports where this is avaiable. Typically these ports provide passive power,
+ so the voltage out will be the same as whatever is powering the node.")}}
+ {% }
+ if (hardware.hasUSBPower()) { %}
+
+ {{_H("Enable power on the node's USB port. Which ports support this is device dependend, and some devices with USB
+ port may only have some with power available.")}}
+ {% } %}
+
+
+
Message Updates
+
Update messages every so many hours
+
+
+
+
+
+ {{_H("Change the frequency we fetch messsage for this node. By default this happens every hour, but you can decrese the frequency up to
+ every 24 hours.")}}
+
+
+
Local Message URL
+
Configure the local message sources
+
+
+
+
+
+ {{_H("Add a local message server URL. By default messages are fetched from the global AREDN server, but you can also specify a
+ local server.")}}
+
+
+
Message Groups
+
List of message group names
+
+
+
+
+
+ {{_H("A comma seperated list of group names to check for messages.")}}
+
+ {{_R("dialog-footer")}}
+
+
diff --git a/files/app/main/status/e/local-services.ut b/files/app/main/status/e/local-services.ut
new file mode 100755
index 00000000..a84cceb1
--- /dev/null
+++ b/files/app/main/status/e/local-services.ut
@@ -0,0 +1,616 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+if (request.env.REQUEST_METHOD === "PUT") {
+ configuration.prepareChanges();
+ if ("services" in request.args) {
+ const services = json(request.args.services);
+ const dhcp = configuration.getDHCP();
+ let f = fs.open(dhcp.services, "w");
+ if (f) {
+ for (let i = 0; i < length(services); i++) {
+ f.write(`${services[i]}\n`);
+ }
+ f.close();
+ }
+ }
+ if ("aliases" in request.args) {
+ const aliases = json(request.args.aliases);
+ const dhcp = configuration.getDHCP();
+ let f = fs.open(dhcp.aliases, "w");
+ if (f) {
+ for (let i = 0; i < length(aliases); i++) {
+ f.write(`${aliases[i]}\n`);
+ }
+ f.close();
+ }
+ }
+ if ("ports" in request.args) {
+ const ports = json(request.args.ports);
+ const dhcp = configuration.getDHCP();
+ let f = fs.open(dhcp.ports, "w");
+ if (f) {
+ for (let i = 0; i < length(ports); i++) {
+ f.write(`${ports[i]}\n`);
+ }
+ f.close();
+ }
+ }
+ print(_R("changes"));
+ return;
+}
+if (request.env.REQUEST_METHOD === "DELETE") {
+ configuration.revertModalChanges();
+ print(_R("changes"));
+ return;
+}
+const types = {
+ mail: true,
+ router: true,
+ switch: true,
+ radio: true,
+ solar: true,
+ battery: true,
+ power: true,
+ weather: true,
+ wiki: true,
+};
+const templates = [
+ { name: "Generic URL" },
+ { name: "Simple text", link: false },
+ { name: "Ubiquiti camera", type: "camera", protocol: "http", port: 80, path: "snap.jpeg" },
+ { name: "Reolink camera", type: "camera", protocol: "http", port: 80, path: "cgi-bin/api.cgi?cmd=Snap&channel=0&rs=abcdef&user=USERNAME&password=PASSWORD" },
+ { name: "Axis camera", type: "camera", protocol: "http", port: 80, path: "jpg/image.jpg" },
+ { name: "Sunba Lite camera", type: "camera", protocol: "http", port: 80, path: "webcapture.jpg?command=snap&channel=1&user=USERNAME&password=PASSWORD" },
+ { name: "Sunba Performance camera", type: "camera", protocol: "http", port: 80, path: "images/snapshot.jpg" },
+ { name: "Sunba Smart camera", type: "camera", protocol: "http", port: 80, path: "ISAPI/Custom/snapshot?authInfo=USERNAME:PASSWORD" },
+ { name: "NTP Server", type: "time", protocol: "ntp", port: 123 },
+ { name: "Streaming video", type: "video", protocol: "rtsp", port: 554 },
+ { name: "Winlink Server", type: "winlink", protocol: "winlink", port: 8772 },
+ { name: "Phone info", type: "phone", link: false, text: "xYOUR-PHONE-EXTENSION" },
+ { name: "Map", type: "map", protocol: "http", port: 80 },
+ { name: "MeshChat", type: "chat", protocol: "http", port: 80, path: "meshchat" },
+ { name: "WeeWx Weather Station", type: "weather", protocol: "http", port: 80, path: "weewx" },
+ { name: "Proxmox Server", type: "server", protocol: "https", port: 8006 },
+ { name: "Generic HTTP", protocol: "http", port: 80 },
+ { name: "Generic HTTPS", protocol: "https", port: 443 },
+];
+const services = [];
+const dhcp = configuration.getDHCP();
+const as = iptoarr(dhcp.start);
+const ae = iptoarr(dhcp.end);
+let f = fs.open(dhcp.services);
+if (f) {
+ const reService = regexp("^([^|]+)\\|1\\|([^|]+)\\|([^|]+)\\|(\\d+)\\|(.*)$");
+ const reLink = regexp("^([^|]+)\\|0\\|\\|([^|]+)\\|\\|$");
+ const reType = regexp("^(.+) \\[([a-z]+)\\]$");
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ const v = match(trim(l), reService);
+ if (v) {
+ let type = null;
+ const v2 = match(v[1], reType);
+ if (v2) {
+ v[1] = v2[1];
+ type = v2[2];
+ }
+ push(services, { name: v[1], type: type, link: true, protocol: v[2], hostname: v[3], port: v[4], path: v[5] });
+ }
+ else {
+ const k = match(trim(l), reLink);
+ if (k) {
+ let type = null;
+ const k2 = match(k[1], reType);
+ if (k2) {
+ k[1] = k2[1];
+ type = k2[2];
+ }
+ push(services, { name: k[1], type: type, link: false, hostname: k[2] });
+ }
+ }
+ }
+ f.close();
+}
+const hosts = [ configuration.getName() ];
+const aliases = { start: dhcp.start, end: dhcp.end, leases: {}, map: [] };
+f = fs.open(dhcp.reservations);
+if (f) {
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ const v = match(trim(l), /^[^ ]+ ([^ ]+) ([^ ]+) ?(.*)/);
+ if (v) {
+ aliases.leases[`${as[0]}.${as[1]}.${as[2]}.${as[3] - 2 + int(v[1])}`] = v[2];
+ if (v[3] !== "#NOPROP") {
+ push(hosts, v[2]);
+ }
+ }
+ }
+ f.close();
+}
+f = fs.open(dhcp.aliases);
+if (f) {
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ const v = match(trim(l), /^(.+) (.+)$/);
+ if (v) {
+ push(aliases.map, { hostname: v[2], address: v[1] });
+ }
+ }
+ f.close();
+}
+const ports = [];
+f = fs.open(dhcp.ports);
+if (f) {
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ const m = match(trim(l), /^(wan|wifi|both):(tcp|udp|both):([0-9\-]+):([.0-9]+):([0-9]+):([01])$/);
+ if (m) {
+ push(ports, { src: m[1], type: m[2], sports: m[3], dst: m[4], dport: m[5], enabled: m[6] === "1" });
+ }
+ }
+ f.close();
+}
+%}
+
+ {{_R("dialog-header", "Node Services")}}
+
+
+
+
Add service
+
Add a service from a template
+
+
+
+
+
+
+ {{_H("Create a service by selecting from the templates above and hitting +. The two most generic templates are
+ Generic URL and Simple text and can be used for any services. You can also use the other templates to
+ help create services for specific cameras, mail servers, phones, etc. and these will pre-populate various service fields.")}}
+
{%
+ for (let i = 0; i < length(services); i++) {
+ const s = services[i];
+ %}
+
+
+
+
+
+
+
+
+
+
+
+
+ {% if (s.link === false) { %}
+
+
+
+ {% } else { %}
+
+
+ ://
+ :
+ /
+
+ {% } %}
+
+
+ {% } %}
+
+
+
+
Host aliases
+
DNS hostname aliases
+
+
+
+ {{_H("Assign a hostname to an LAN IP address on this node. Multiple hostnames can be assigned to the same IP address. You are
+ encouraged to prefix your hostnames with your callsign so it will be unique on the network.")}}
+
{%
+ for (let i = 0; i < length(aliases.map); i++) {
+ %}
+
+
+
+
+
+
+
+ {%
+ }
+ %}
+
+
+
+
Port Forwarding
+ {% if (dhcp.mode === 0) { %}
+
Forward WAN and Mesh ports to LAN
+ {% } else { %}
+
Forward WAN port to LAN
+ {% } %}
+
+
+
+ {% if (dhcp.mode === 0) { %}
+ {{_H("For specific ports (or port ranges) from the WAN or Mesh network to the LAN network. TCP, UDP or both kinds of
+ traffic may be included. When forwarding a port range, specify the range as start-end.")}}
+ {% } else { %}
+ {{_H("For specific ports (or port ranges) from the WAN network to the LAN network. TCP, UDP or both kinds of
+ traffic may be included. When forwarding a port range, specify the range as start-end.")}}
+ {% } %}
+
+
+
addresses
+
ports
+
protocol
+
enabled
+
+
+
{%
+ for (let i = 0; i < length(ports); i++) {
+ const p = ports[i];
+ %}
diff --git a/files/app/main/status/e/location.ut b/files/app/main/status/e/location.ut
new file mode 100755
index 00000000..48450cc3
--- /dev/null
+++ b/files/app/main/status/e/location.ut
@@ -0,0 +1,242 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+if (request.env.REQUEST_METHOD === "PUT") {
+ configuration.prepareChanges();
+ if ("gridsquare" in request.args) {
+ if (match(request.args.gridsquare, /^[A-X][A-X]\d\d[a-x][a-x]$/)) {
+ uciMesh.set("aredn", "@location[0]", "gridsquare", request.args.gridsquare);
+ }
+ else if (request.args.gridsquare === "") {
+ uciMesh.set("aredn", "@location[0]", "gridsquare", "");
+ }
+ }
+ if ("lat" in request.args) {
+ if (request.args.lat > -90 && request.args.lat < 90) {
+ uciMesh.set("aredn", "@location[0]", "lat", request.args.lat);
+ }
+ else if (request.args.lat === "") {
+ uciMesh.set("aredn", "@location[0]", "lat", "");
+ }
+ uciMesh.set("aredn", "@location[0]", "source", "");
+ }
+ if ("lon" in request.args) {
+ if (request.args.lon > -180 && request.args.lon < 180) {
+ uciMesh.set("aredn", "@location[0]", "lon", request.args.lon);
+ }
+ else if (request.args.lon === "") {
+ uciMesh.set("aredn", "@location[0]", "lon", "");
+ }
+ uciMesh.set("aredn", "@location[0]", "source", "");
+ }
+ if (request.args.gps_enable) {
+ uciMesh.set("aredn", "@location[0]", "gps_enable", request.args.gps_enable === "on" ? "1" : "0");
+ }
+ if ("mapurl" in request.args) {
+ if (match(request.args.mapurl, constants.reUrl)) {
+ uciMesh.set("aredn", "@location[0]", "map", request.args.mapurl);
+ }
+ }
+ uciMesh.commit("aredn");
+ print(_R("changes"));
+ return;
+}
+if (request.env.REQUEST_METHOD === "DELETE") {
+ configuration.revertModalChanges();
+ print(_R("changes"));
+ return;
+}
+const map = uciMesh.get("aredn", "@location[0]", "map");
+const lat = uciMesh.get("aredn", "@location[0]", "lat") ?? 37;
+const lon = uciMesh.get("aredn", "@location[0]", "lon") ?? -122;
+const gridsquare = uciMesh.get("aredn", "@location[0]", "gridsquare");
+const mapurl = map ? replace(replace(map, "(lat)", lat), "(lon)", lon) : null;
+%}
+
+ {{_R("dialog-header", "Location")}}
+
+ {% if (mapurl) { %}
+
+
+
+
+ {% } %}
+
+
+
Latitude
+
Node's latitude
+
+
+
+
+
+ {{_H("The latitude of this node. This information is used to determine the distance between this node and others and is required to
+ optimize connection latency and bandwidth.")}}
+
+
+
Longitude
+
Node's longitude
+
+
+
+
+
+ {{_H("The longitude of this node. This information is used to determine the distance between this node and others and is required to
+ optimize connection latency and bandwidth.")}}
+
+
+
Gridsquare
+
Maidenhead gridsquare
+
+
+
+
+
+ {{_H("A gridsquare is a 6 character (2 uppercase letters, 2 digits, 2 lowercase letters) designation of the node's location.
+ A gridsquare is approximately 3x4 miles in size.")}}
+ {{_R("dialog-advanced")}}
+
+ {{_H("Use either a local GPS devices to set the location, or search for a GPS device on another local node, and use its
+ GPS to set our location.")}}
+
+
+
Map URL
+
URL for embedded map
+
+
+
+
+
+ {{_H("The map URL is used to embed maps in this page (see above) and in other node pages. The (lat) and (lon) parameters in
+ the URL are expanded before the URL is used.")}}
+ {% } %}
+
+
+ {{_R("dialog-footer")}}
+
+
diff --git a/files/app/main/status/e/neighbor-device.ut b/files/app/main/status/e/neighbor-device.ut
new file mode 100755
index 00000000..d2a5f71c
--- /dev/null
+++ b/files/app/main/status/e/neighbor-device.ut
@@ -0,0 +1,371 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{% if (request.env.REQUEST_METHOD === "PUT") {
+ configuration.prepareChanges();
+ const userb = split(uciMesh.get("aredn", "@lqm[0]", "user_blocks"), ",");
+ const blockedmacs = {};
+ for (let i = 0; i < length(userb); i++) {
+ blockedmacs[uc(userb[i])] = true;
+ }
+ const usera = split(uciMesh.get("aredn", "@lqm[0]", "user_allows"), ",");
+ const allowedmacs = {};
+ for (let i = 0; i < length(usera); i++) {
+ allowedmacs[uc(usera[i])] = true;
+ }
+ for (let mac in request.args) {
+ if (request.args[mac] === "always") {
+ delete blockedmacs[uc(mac)];
+ allowedmacs[uc(mac)] = true;
+ }
+ else if (request.args[mac] === "user") {
+ blockedmacs[uc(mac)] = true;
+ delete allowedmacs[uc(mac)];
+ }
+ else {
+ delete blockedmacs[uc(mac)];
+ delete allowedmacs[uc(mac)];
+ }
+ }
+ uciMesh.set("aredn", "@lqm[0]", "user_blocks", join(",", keys(blockedmacs)));
+ uciMesh.set("aredn", "@lqm[0]", "user_allows", join(",", keys(allowedmacs)));
+ uciMesh.commit("aredn");
+ print(_R("changes"));
+ return;
+}
+if (request.env.REQUEST_METHOD === "DELETE") {
+ configuration.revertModalChanges();
+ print(_R("changes"));
+ return;
+}
+const selected = substr(request.env.QUERY_STRING, 2);
+const blockedmacs = {};
+const user = split(uciMesh.get("aredn", "@lqm[0]", "user_blocks"), ",");
+for (let i = 0; i < length(user); i++) {
+ blockedmacs[uc(user[i])] = true;
+}
+const usera = split(uciMesh.get("aredn", "@lqm[0]", "user_allows"), ",");
+const allowedmacs = {};
+for (let i = 0; i < length(usera); i++) {
+ allowedmacs[uc(usera[i])] = true;
+}
+const lqmInfo = lqm.get();
+const trackers = lqm.getTrackers();
+const tracker = trackers[selected];
+let neighbor = null;
+if (tracker) {
+ if (allowedmacs[uc(tracker.mac)]) {
+ tracker.user_allow = true;
+ }
+ else if (blockedmacs[uc(tracker.mac)]) {
+ tracker.blocks.user = true;
+ }
+ neighbor = { name: tracker.hostname || `|${tracker.ip}`, n: tracker, l: null };
+ const o = olsr.getLinks();
+ for (let i = 0; i < length(o); i++) {
+ if (o[i].remoteIP === tracker.ip) {
+ neighbor.l = o[i];
+ break;
+ }
+ }
+}
+%}
+
+ {{_H("Provides more detailed information about the state of a link from this node to a neighbor. The current blocked
+ state is show in the top/right corner. This can be changed to either always block or never block to override
+ the automatic management of the link's use.")}}
+ {%
+ function state(n)
+ {
+ if (n.lastseen < lqmInfo.now) {
+ return "disconnected";
+ }
+ if (n.blocks.user) {
+ return "blocked by user";
+ }
+ if (n.blocked) {
+ if (n.blocks.signal) {
+ return "blocked: low snr";
+ }
+ if (n.blocks.distance) {
+ return "blocked: too far away";
+ }
+ if (n.blocks.quality) {
+ if ("tx_quality" in n) {
+ if ("ping_quality" in n && n.ping_quality < n.tx_quality) {
+ return "blocked: poor tx latency";
+ }
+ else {
+ return "blocked: too many tx error";
+ }
+ }
+ else {
+ return "blocked: poor tx latency";
+ }
+ }
+ if (n.blocks.dup || n.blocks.dtd) {
+ return "blocked: duplicate link";
+ }
+ return "blocked";
+ }
+ if (n.routable) {
+ if ((n.blocks.signal || n.blocks.distance || n.blocks.quality) && (
+ (n.leaf === "major" && n.rev_leaf === "minor") || (n.leaf === "minor" && n.rev_leaf === "major"))) {
+ return "routing leaf";
+ }
+ return "routing";
+ }
+ return "unused";
+ }
+ function map(n, v)
+ {
+ const map_url = uci.get("aredn", "@location[0]", "map");
+ if (n.lat && n.lon && map_url) {
+ return `${v}`;
+ }
+ return v;
+ }
+ if (neighbor) {
+ const n = neighbor.n;
+ const l = neighbor.l;
+ %}
+
+ {%
+ const snr = `/tmp/snrlog/${uc(selected)}-${lc(n.hostname)}`;
+ if (fs.access(snr)) {
+ let signal = "";
+ noise += "' />";
+ %}
+
+
+
+ {% } %}
+
+ {% } %}
+
+ {{_R("dialog-footer")}}
+
+
diff --git a/files/app/main/status/e/network.ut b/files/app/main/status/e/network.ut
new file mode 100755
index 00000000..9a9c0493
--- /dev/null
+++ b/files/app/main/status/e/network.ut
@@ -0,0 +1,391 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+if (request.env.REQUEST_METHOD === "PUT") {
+ configuration.prepareChanges();
+ if ("mesh_ip" in request.args) {
+ if (match(request.args.mesh_ip, constants.reIP)) {
+ configuration.setSetting("wifi_ip", request.args.mesh_ip);
+ }
+ }
+ if ("dhcp_mode" in request.args) {
+ const mode = int(request.args.dhcp_mode || 3);
+ if (mode === -1) {
+ configuration.setSetting("lan_dhcp", 0);
+ }
+ else if (mode >= 0 && mode <= 5) {
+ configuration.setSetting("lan_dhcp", 1);
+ configuration.setSetting("dmz_mode", mode);
+ }
+ }
+ if ("lan_dhcp_ip" in request.args) {
+ if (match(request.args.lan_dhcp_ip, constants.reIP)) {
+ configuration.setSetting("lan_ip", request.args.lan_dhcp_ip);
+ }
+ }
+ if ("lan_dhcp_netmask" in request.args) {
+ if (match(request.args.lan_dhcp_netmask, constants.reNetmask)) {
+ configuration.setSetting("lan_mask", request.args.lan_dhcp_mask);
+ }
+ }
+ if ("lan_dhcp_start" in request.args) {
+ if (match(request.args.lan_dhcp_start, /^([2-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-4])$/)) {
+ configuration.setSetting("dhcp_start", request.args.lan_dhcp_start);
+ }
+ }
+ if ("lan_dhcp_end" in request.args) {
+ if (match(request.args.lan_dhcp_end, /^([2-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-4])$/)) {
+ configuration.setSetting("dhcp_end", request.args.lan_dhcp_end);
+ }
+ }
+ if ("wan_enable" in request.args) {
+ if (request.args.wan_enable === "off") {
+ configuration.setSetting("wan_proto", "disabled");
+ }
+ else {
+ configuration.setSetting("wan_proto", "dhcp");
+ }
+ }
+ if ("wan_mode" in request.args) {
+ if (request.args.wan_mode === "0") {
+ configuration.setSetting("wan_proto", "dhcp");
+ }
+ else {
+ configuration.setSetting("wan_proto", "static");
+ }
+ }
+ if ("wan_ip" in request.args) {
+ if (match(request.args.wan_ip, /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/)) {
+ configuration.setSetting("wan_ip", request.args.wan_ip);
+ }
+ }
+ if ("wan_mask" in request.args) {
+ if (match(request.args.wan_mask, /^(((255\.){3}(255|254|252|248|240|224|192|128|0+))|((255\.){2}(255|254|252|248|240|224|192|128|0+)\.0)|((255\.)(255|254|252|248|240|224|192|128|0+)(\.0+){2})|((255|254|252|248|240|224|192|128|0+)(\.0+){3}))$/)) {
+ configuration.setSetting("wan_mask", request.args.wan_mask);
+ }
+ }
+ if ("wan_gw" in request.args) {
+ if (match(request.args.wan_gw, /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/)) {
+ configuration.setSetting("wan_gw", request.args.wan_gw);
+ }
+ }
+ if ("wan_dns1" in request.args) {
+ if (match(request.args.wan_dns1, /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/)) {
+ configuration.setSetting("wan_dns1", request.args.wan_dns1);
+ }
+ }
+ if ("wan_dns2" in request.args) {
+ if (match(request.args.wan_dns2, /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/)) {
+ configuration.setSetting("wan_dns2", request.args.wan_dns2);
+ }
+ }
+ if ("wan_vlan" in request.args) {
+ const wan_iface = split(configuration.getSettingAsString("wan_intf", ""), ".");
+ const wan_vlan = int(request.args.wan_vlan || 0);
+ if (wan_vlan === 0) {
+ configuration.setSetting("wan_intf", wan_iface[0]);
+ }
+ else if (wan_vlan >= 3 && wan_vlan <= 4095) {
+ configuration.setSetting("wan_intf", `${wan_iface[0]}.${wan_vlan}`);
+ }
+ }
+ if ("olsrd_gw" in request.args) {
+ uciMesh.set("aredn", "@wan[0]", "olsrd_gw", request.args.olsrd_gw === "on" ? 1 : 0);
+ }
+ if ("lan_dhcp_route" in request.args) {
+ uciMesh.set("aredn", "@wan[0]", "lan_dhcp_route", request.args.lan_dhcp_route === "on" ? 1 : 0);
+ }
+ if ("lan_dhcp_defaultroute" in request.args) {
+ uciMesh.set("aredn", "@wan[0]", "lan_dhcp_defaultroute", request.args.lan_dhcp_defaultroute === "on" ? 1 : 0);
+ }
+ uciMesh.commit("aredn");
+ configuration.saveSettings();
+ print(_R("changes"));
+ return;
+}
+if (request.env.REQUEST_METHOD === "DELETE") {
+ configuration.revertModalChanges();
+ print(_R("changes"));
+ return;
+}
+const dmz_mode = configuration.getSettingAsInt("dmz_mode", 3);
+const dhcp = configuration.getDHCP("nat");
+const wan_proto = configuration.getSettingAsString("wan_proto", "disabled");
+const wan_iface = split(configuration.getSettingAsString("wan_intf", ""), ".");
+const wan_vlan = wan_iface[1] ? wan_iface[1] : "";
+const gateway_nat = dmz_mode !== 1 ? dhcp.gateway : "172.27.0.1";
+const gateway_altnet = dmz_mode === 1 ? dhcp.gateway : "";
+%}
+
+ {{_R("dialog-header", "Network")}}
+
+
+
+
Mesh Address
+
The primary address of this node
+
+
+
+
+
+ {{_H("The primary address of this AREDN node. This will always be of the form 10.X.X.X. The address is generated automatically based on hardware information so it will
+ always be the same even if you reinstall this node from scratch. You shouldn't have to change it.")}}
+
+
+
+
+
LAN Size
+
Size of LAN subnet
+
+
+
+
+
+ {{_H("Select how many hosts you want to support on this nodes LAN network. This determines the size of the netmask associated with that network.
+ You can also select NAT which allows more hosts, firewalls your LAN hosts from the Mesh network, but requires explicity ports to be forwarded when
+ creating services.")}}
+
+
+
+
IP Address
+
Gateway IP address for this LAN network
+
+
+
+
+
+
+
+
Netmask
+
Netmask for this LAN network
+
+
+
+
+
+
+
+
DHCP Start
+
Start of the DHCP range for addresses allocate
+
+
+
+
+
+
+
+
DHCP End
+
Last address of the DHCP range for addresses allocated
+
+
+
+
+
+
+
+
+
+
AltNET IP Address
+
Gateway IP address for AltNET LAN network
+
+
+
+
+
+
+
+
Netmask
+
Netmask for AltNET LAN network
+
+
+
+
+
+
+
+
DHCP Start
+
Start of the DHCP range for addresses allocate
+
+
+
+
+
+
+
+
DHCP End
+
Last address of the DHCP range for addresses allocated
+ {{_H("Enable the WAN interface on this node, to allow it to access the Internet directly.")}}
+
+
+
+
Mode
+
Static or DHCP mode
+
+
+
+
+
+ {{_H("The WAN interface can either use DHCP to retrieve an IP address, or it can be set statically.")}}
+
+
+
+
Address
+
WAN IP address
+
+
+
+
+
+ {{_H("A fixed IP address to assign to the WAN interace on this node.")}}
+
+
+
Netmask
+
WAN netmask
+
+
+
+
+
+ {{_H("The netmask (e.g. 255.255.255.0) for this interface.")}}
+
+
+
Gateway
+
Default gateway
+
+
+
+
+
+ {{_H("The default gateway his node should use to access the Internet.")}}
+
+
+
+
+
+
+
DNS
+
Internet DNS servers
+
+
+
+
+
+
+ {{_H("For hosts not on the Mesh, use these DNS servers to resolve names to IP addresses.")}}
+ {{_R("dialog-advanced")}}
+
+ {% if (includeAdvanced) { %}
+ {% if (length(hardware.getEthernetPorts()) < 2) { %}
+
+
+
WAN VLAN
+
Vlan used for Internet access
+
+
+
+
+
+ {{_H("By default, the WAN uses an untagged VLAN on multi-port devices, and VLAN 1 on single port devices. This can be changed here if your setup requires something different.
+ Enter the VLAN number required, or leave blank for untagged.")}}
+ {% } %}
+
+ {{_H("Provide a default route to a LAN connected device, even if our local WAN is disabled. By default this is only provided to
+ devices if our local WAN is available.")}}
+ {% } %}
+
+
+ {{_R("dialog-footer")}}
+
+
diff --git a/files/app/main/status/e/packages.ut b/files/app/main/status/e/packages.ut
new file mode 100755
index 00000000..3d86437a
--- /dev/null
+++ b/files/app/main/status/e/packages.ut
@@ -0,0 +1,431 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+function log()
+{
+ return replace(fs.readfile("/tmp/pkg.log"), "\n", " ");
+}
+function getPackageOptions()
+{
+ let i = ``;
+ let r = ``;
+ const perm_pkgs = {};
+ const installed_pkgs = {};
+
+ map(split(fs.readfile("/etc/permpkg"), "\n"), p => perm_pkgs[p] = true);
+
+ let f = fs.popen("/bin/opkg list-installed");
+ if (f) {
+ const re = /^[^ \t]+/;
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ const m = match(l, re);
+ if (m) {
+ installed_pkgs[m[0]] = true;
+ if (!perm_pkgs[m[0]]) {
+ r += ``;
+ }
+ }
+ }
+ f.close();
+ }
+
+ f = fs.popen("/bin/opkg list");
+ if (f) {
+ const re = /^([^ ]+) - ([-0-9a-fr\.]+)(.*)$/;
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ let m = match(trim(l), re);
+ if (m && !installed_pkgs[m[1]]) {
+ i += ``;
+ }
+ }
+ f.close();
+ }
+ return { i: i, r: r };
+}
+function recordPackage(op, pkgname, pkgfile)
+{
+ const store = "/etc/package_store";
+ const catfile = `${store}/catalog.json`;
+ fs.mkdir(store);
+ const catalog = json(fs.readfile(catfile) || '{ "installed": {} }');
+ switch (op) {
+ case "upload":
+ const package = split(pkgname, "_")[0];
+ if (package) {
+ fs.writefile(`${store}/${package}.ipk`, fs.readfile(pkgfile));
+ catalog.installed[package] = "local";
+ }
+ break;
+ case "download":
+ const f = fs.popen(`/bin/opkg status ${pkgname} 2>&1`);
+ if (f) {
+ const status = replace(f.read("all"), "\n", " ");
+ f.close();
+ const m = match(status, /Package: ([^ \t]+)/);
+ if (m) {
+ catalog.installed[m[1]] = "global";
+ }
+ }
+ break;
+ case "remove":
+ fs.unlink(`${store}/${pkgname}.ipk`);
+ delete catalog.installed[pkgname];
+ break;
+ default:
+ break;
+ }
+ fs.unlink(catfile);
+ for (let k in catalog.installed) {
+ fs.writefile(catfile, sprintf("%J", catalog));
+ break;
+ }
+}
+if (request.env.REQUEST_METHOD === "POST" && request.args["packagefile.ipk"] && request.args.packagename) {
+ const ipk = request.args["packagefile.ipk"];
+ const packagename = fs.readfile(request.args.packagename);
+ if (system(`/bin/opkg -force-overwrite install ${ipk} > /dev/null 2>&1`) === 0) {
+ recordPackage("upload", packagename, ipk);
+ print(`
+ {{_H("Download packages directly from a central server, either on the Internet or a locally configured mesh server.
+ Refresh the list of available packages using the refresh button to the right of the packages list. Once a
+ package is selected it can be downloaded and installed using the button at the base of the dialog.")}}
+
+
+
+
Upload Package
+
Upload a package file from your computer.
+
+
+
+
+
+ {{_H("Upload a package file from your computer. Once the package has been selected it can be uploaded and installed
+ using the button at the base of the dialog.")}}
+
+
+
+
Remove Package
+
Uninstall package from node.
+
+
+
+
+
+ {{_H("Remove a currently installed package from the node by first selecting it and
+ then using the button at the based of the dialog to remove it.")}}
+ {{_R("dialog-advanced")}}
+
+ {% if (includeAdvanced) { %}
+
+
+
Package URL
+
URL for downloading packages
+
+
+
+
+
+ {{_H("The base URL used to download packages. By default this points to the main AREDN repository, but you can change this
+ to a local server, especially if you'd like to do this without a connection to the Internet.")}}
+ {% } %}
+
+
+
+
+
+
+
+
+
+ {{_H(" Depending on the package option selected above, this button will initiate the download, upload, install or remove process.")}}
+
+ {{_R("dialog-footer", "nocancel")}}
+
+
diff --git a/files/app/main/status/e/ports-and-xlinks.ut b/files/app/main/status/e/ports-and-xlinks.ut
new file mode 100755
index 00000000..8c11f85e
--- /dev/null
+++ b/files/app/main/status/e/ports-and-xlinks.ut
@@ -0,0 +1,473 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+function loadPorts(type, def)
+{
+ const f = fs.open(`/etc/aredn_include/${type}.network.user`);
+ if (!f) {
+ return def;
+ }
+ const r = { vlan: null, ports: {} };
+ let pcount = 0;
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ let m = match(l, /list ports '(.+):([ut])'/);
+ if (m) {
+ r.ports[m[1]] = true;
+ if (m[2] === "u") {
+ r.vlan = 0;
+ }
+ pcount++;
+ }
+ m = match(l, /option vlan '(\d+)'/);
+ if (m) {
+ r.vlan = int(m[1]);
+ }
+ }
+ if (pcount === 0 && ((type === "wan" && r.vlan === 1) || (type === "lan" && r.vlan === 3))) {
+ r.vlan = 0;
+ }
+ f.close();
+ return r;
+}
+if (request.env.REQUEST_METHOD === "PUT") {
+ configuration.prepareChanges();
+ if ("xlinks" in request.args) {
+ const xlinks = {};
+ const xs = json(request.args.xlinks);
+ for (let i = 0; i < length(xs); i++) {
+ xlinks[xs[i].name] = xs[i];
+ }
+ uciMesh.foreach("xlink", "interface", x => {
+ const ux = xlinks[x[".name"]];
+ if (ux) {
+ uciMesh.set("xlink", `${ux.name}bridge`, "vlan", ux.vlan);
+ uciMesh.set("xlink", `${ux.name}bridge`, "ports", [ `${ux.port}:t` ]);
+ uciMesh.commit("xlink");
+
+ uciMesh.set("xlink", ux.name, "ifname", `br0.${ux.vlan}`);
+ uciMesh.set("xlink", ux.name, "ipaddr", ux.ipaddr);
+ if (uciMesh.get("xlink", ux.name, "peer") !== ux.peer) {
+ uciMesh.set("xlink", ux.name, "peer", ux.peer);
+ uciMesh.commit("xlink");
+ uciMesh.set("xlink", ux.name, "peer", ux.peer);
+ if (ux.peer) {
+ uciMesh.set("xlink", `${ux.name}route`, "route");
+ uciMesh.set("xlink", `${ux.name}route`, "interface", ux.name);
+ uciMesh.set("xlink", `${ux.name}route`, "target", ux.peer);
+ }
+ else {
+ uciMesh.delete("xlink", `${name}route`);
+ }
+ uciMesh.commit("xlink");
+ }
+ uciMesh.set("xlink", ux.name, "weight", ux.weight);
+ uciMesh.set("xlink", ux.name, "netmask", network.CIDRToNetmask(ux.cidr));
+ delete xlinks[ux.name];
+ }
+ else {
+ const name = x[".name"];
+ uciMesh.delete("xlink", name);
+ uciMesh.delete("xlink", `${name}bridge`);
+ uciMesh.delete("xlink", `${name}route`);
+ }
+ uciMesh.commit("xlink");
+ });
+ for (let name in xlinks) {
+ const ux = xlinks[name];
+ uciMesh.set("xlink", `${ux.name}bridge`, "bridge-vlan");
+ uciMesh.set("xlink", `${ux.name}bridge`, "device", "br0");
+ uciMesh.set("xlink", `${ux.name}bridge`, "vlan", ux.vlan);
+ uciMesh.set("xlink", `${ux.name}bridge`, "ports", [ `${ux.port}:t` ]);
+ uciMesh.commit("xlink");
+
+ uciMesh.set("xlink", name, "interface");
+ uciMesh.set("xlink", name, "ifname", `br0.${ux.vlan}`);
+ uciMesh.set("xlink", name, "ipaddr", ux.ipaddr);
+ uciMesh.set("xlink", name, "weight", ux.weight);
+ uciMesh.set("xlink", name, "netmask", network.CIDRToNetmask(ux.cidr));
+ if (ux.peer) {
+ uciMesh.set("xlink", name, "peer", ux.peer);
+ uciMesh.set("xlink", `${ux.name}route`, "route");
+ uciMesh.set("xlink", `${ux.name}route`, "interface", ux.name);
+ uciMesh.set("xlink", `${ux.name}route`, "target", ux.peer);
+ uciMesh.commit("xlink");
+ }
+ uciMesh.set("xlink", name, "proto", "static");
+ uciMesh.set("xlink", name, "macaddr", replace("x2:xx:xx:xx:xx:xx", "x", _ => sprintf("%X",math.rand()&15)));
+ uciMesh.commit("xlink");
+ }
+ delete request.args.xlinks;
+ }
+ const k = keys(request.args);
+ if (length(k) > 0) {
+ if (length(hardware.getEthernetPorts()) > 1) {
+ const defnet = hardware.getDefaultNetworkConfiguration();
+ const nets = {
+ wan: loadPorts("wan", defnet.wan),
+ lan: loadPorts("lan", defnet.lan),
+ dtdlink: loadPorts("dtdlink", defnet.dtdlink)
+ };
+ for (let i = 0; i < length(k); i++) {
+ if (k[i] === "port_wan_vlan") {
+ nets.wan.vlan = int(request.args[k[i]] || 0);
+ }
+ else {
+ const ptp = split(k[i], "_");
+ const net = nets[ptp[1]];
+ if (net) {
+ if (request.args[k[i]] === "on") {
+ net.ports[ptp[2]] = true;
+ }
+ else {
+ delete net.ports[ptp[2]];
+ }
+ }
+ }
+ }
+ if (nets.wan.vlan === 0) {
+ for (let p in nets.wan.ports) {
+ if (nets.lan.ports[p]) {
+ delete nets.wan.ports[p];
+ }
+ }
+ }
+ function savePort(type, c)
+ {
+ const f = fs.open(`/etc/aredn_include/${type}.network.user`, "w");
+ if (f) {
+ f.write(`# Generated by advancednetwork
+
+config bridge-vlan
+ option device 'br0'
+ option vlan '${c.vlan ? c.vlan : type == "lan" ? 3 : 1}'
+${join("\n", map(keys(c.ports), p => "\tlist ports '" + p + (c.vlan ? ":t'" : ":u'")))}
+
+config device
+ option name 'br-${type}'
+ option type 'bridge'
+ option macaddr '<${type}_mac>'
+ list ports 'br0.${c.vlan ? c.vlan : type == "lan" ? 3 : 1}'
+
+config interface '${type}'
+ option device 'br-${type}'
+${type === "dtdlink" ? "\toption proto 'static'" : "\toption proto '<" + type + "_proto>'"}
+ option ipaddr '<${type}_ip>'
+${type === "dtdlink" ? "\toption netmask '255.0.0.0'" : "\toption netmask '<" + type + "_mask>'"}
+${type === "wan" ? "\toption gateway ''" : ""}${type === "lan" ? "\toption dns ''" : ""}
+`);
+ f.close();
+ }
+ }
+ savePort("wan", nets.wan);
+ savePort("lan", nets.lan);
+ savePort("dtdlink", nets.dtdlink);
+ }
+ }
+ print(_R("changes"));
+ return;
+}
+if (request.env.REQUEST_METHOD === "DELETE") {
+ configuration.revertModalChanges();
+ print(_R("changes"));
+ return;
+}
+let haveports = false;
+let wan_vlan = 0;
+const ports = hardware.getEthernetPorts();
+if (length(ports) > 1) {
+ haveports = true;
+ const defnet = hardware.getDefaultNetworkConfiguration();
+ const wan = loadPorts("wan", defnet.wan);
+ const lan = loadPorts("lan", defnet.lan);
+ const dtdlink = loadPorts("dtdlink", defnet.dtdlink);
+ for (let i = 0; i < length(ports); i++) {
+ const v = {
+ name: ports[i].k,
+ display: ports[i].d,
+ info: hardware.getEthernetPortInfo(ports[i].k),
+ dtdlink: dtdlink.ports[ports[i].k],
+ lan: lan.ports[ports[i].k],
+ wan: wan.ports[ports[i].k]
+ };
+ ports[i] = v;
+ }
+ wan_vlan = wan.vlan;
+}
+const xlinks = [];
+uciMesh.foreach("xlink", "interface", x => {
+ push(xlinks, {
+ name: x[".name"],
+ vlan: int(split(x.ifname, ".")[1]),
+ ipaddr: x.ipaddr,
+ peer: x.peer || "",
+ weight: int(x.weight),
+ port: match(uciMesh.get("xlink", `${x[".name"]}bridge`, "ports")[0], /^(.+):/)[1],
+ cidr: network.netmaskToCIDR(x.netmask)
+ });
+});
+%}
+
+ {%
+ for (let i = 0; i < length(ports); i++) {
+ const p = ports[i];
+ print(`
${p.display}
`);
+ }
+ %}
+
+
+
+
+
dtd
vlan: 2
+ {%
+ for (let i = 0; i < length(ports); i++) {
+ const p = ports[i];
+ print(`
`);
+ }
+ %}
+
+
+
lan
vlan: untagged
+ {%
+ for (let i = 0; i < length(ports); i++) {
+ const p = ports[i];
+ print(`
`);
+ }
+ %}
+
+
+
wan
vlan:
+ {%
+ for (let i = 0; i < length(ports); i++) {
+ const p = ports[i];
+ print(`
`);
+ }
+ %}
+
+
+
+
+
+ {{_H("
AREDN nodes have three primary networks; DTD, LAN and WAN (shown above in the left column). You can modify the default assignment
+ of these networks to ports (shown across the top) using the checkboxes at the intersection of a network and a port. You can also choose the
+ VLAN to assign to the WAN network. Networks can be assigned to multiple ports, or no ports.
+ Note that on some devices, ports may have names like WAN or LAN. These are just arbitrary names given by the manufacturer
+ and you are not forced to assign networks of the same name to these ports.
+
Active network ports, where a cable is present and attached to another device, are shown in green.")}}
+
+ {% } %}
+
+
+
+
+
XLinks
+
Inter-device links across non-AREDN networks
+
+
+
+
+
+
+
+
vlan
+
ip address
+
peer address
+
cidr
+
weight
+ {% if (haveports) { %}
+
port
+ {% } %}
+
+
+
+ {% for (let i = 0; i < length(xlinks); i++) {
+ const x = xlinks[i];
+ %}
+
+
+
+
+
+
+
+ {% if (haveports) { %}
+
+ {% } %}
+
+
+
+ {% } %}
+
+
+ {{_H("
XLinks provide a way of routing AREDN traffic across external non-AREDN networks. Each is created with a specific VLAN,
+ IP address for both ends, a weight of how likely to link is to be used, and an optional network size and port (on multi-port
+ devices). Think of xlinks as extra dtds between devices. How xlink traffic is routed once it leaves the node is dependent
+ on the non-AREDN network, which allows for the greatest flexibility.")}}
+
+ {{_R("dialog-footer")}}
+
+
diff --git a/files/app/main/status/e/radio-and-antenna.ut b/files/app/main/status/e/radio-and-antenna.ut
new file mode 100755
index 00000000..be3a0f47
--- /dev/null
+++ b/files/app/main/status/e/radio-and-antenna.ut
@@ -0,0 +1,596 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+if (request.env.REQUEST_METHOD === "PUT") {
+ configuration.prepareChanges();
+ if ("radio0_mode" in request.args || "radio1_mode" in request.args) {
+ const wlan = radios.getConfiguration();
+ let radio0_mode = "radio0_mode" in request.args ? int(request.args.radio0_mode) : wlan[0].mode;
+ let radio1_mode = "radio1_mode" in request.args ? int(request.args.radio1_mode) : wlan[1]?.mode;
+ if (radio0_mode === radio1_mode) {
+ if ("radio0_mode" in request.args) {
+ radio1_mode = radios.RADIO_OFF;
+ }
+ else {
+ radio0_mode = radios.RADIO_OFF;
+ }
+ }
+ configuration.setSetting("wifi_enable", "0");
+ configuration.setSetting("wifi2_enable", "0");
+ configuration.setSetting("wifi3_enable", "0");
+ switch (radio0_mode) {
+ case radios.RADIO_MESH:
+ configuration.setSetting("wifi_enable", "1");
+ if (configuration.setSetting("wifi_intf", "wlan0")) {
+ configuration.setSetting("wifi_channel", wlan[0].def.channel);
+ }
+ break;
+ case radios.RADIO_LAN:
+ configuration.setSetting("wifi2_enable", "1");
+ if (configuration.setSetting("wifi2_hwmode", wlan[0].def.band === "5GHz" ? "11a" : "11g")) {
+ configuration.setSetting("wifi2_channel", wlan[0].def.channel);
+ }
+ if (configuration.getSettingAsString("wifi2_key", "") === "") {
+ configuration.setSetting("wifi2_key", hexenc("AREDN"));
+ }
+ break;
+ case radios.RADIO_WAN:
+ configuration.setSetting("wifi3_enable", "1");
+ configuration.setSetting("wifi3_hwmode", wlan[0].def.band === "5GHz" ? "11a" : "11g");
+ break;
+ case radios.RADIO_OFF:
+ default:
+ break;
+ }
+ switch (radio1_mode) {
+ case radios.RADIO_MESH:
+ configuration.setSetting("wifi_enable", "1");
+ if (configuration.setSetting("wifi_intf", "wlan1")) {
+ configuration.setSetting("wifi_channel", wlan[1].def.channel);
+ }
+ break;
+ case radios.RADIO_LAN:
+ configuration.setSetting("wifi2_enable", "1");
+ if (configuration.setSetting("wifi2_hwmode", wlan[1].def.band === "5GHz" ? "11a" : "11g")) {
+ configuration.setSetting("wifi2_channel", wlan[1].def.channel);
+ }
+ if (configuration.getSettingAsString("wifi2_key", "") === "") {
+ configuration.setSetting("wifi2_key", hexenc("AREDN"));
+ }
+ break;
+ case radios.RADIO_WAN:
+ configuration.setSetting("wifi3_enable", "1");
+ configuration.setSetting("wifi3_hwmode", wlan[1].def.band === "5GHz" ? "11a" : "11g");
+ break;
+ case radios.RADIO_OFF:
+ default:
+ break;
+ }
+ }
+ if ("radio_channel" in request.args) {
+ configuration.setSetting("wifi_channel", request.args.radio_channel);
+ }
+ if ("radio0_bandwidth" in request.args || "radio1_bandwidth" in request.args) {
+ configuration.setSetting("wifi_chanbw", request.args.radio0_bandwidth || request.args.radio1_bandwidth);
+ }
+ if ("radio_txpower" in request.args) {
+ configuration.setSetting("wifi_txpower", request.args.radio_txpower);
+ }
+ if ("radio0_ssid" in request.args || "radio1_ssid" in request.args) {
+ configuration.setSetting("wifi_ssid", request.args.radio0_ssid || request.args.radio1_ssid);
+ }
+ if ("radio_minsnr" in request.args) {
+ uciMesh.set("aredn", "@lqm[0]", "min_snr", request.args.radio_minsnr);
+ }
+ if ("radio_maxdistance" in request.args) {
+ uciMesh.set("aredn", "@lqm[0]", "max_distance", units.distance2meters(request.args.radio_maxdistance));
+ }
+ if ("radio_minquality" in request.args) {
+ uciMesh.set("aredn", "@lqm[0]", "min_quality", request.args.radio_minquality);
+ }
+ if ("radio_lan_ssid" in request.args) {
+ configuration.setSetting("wifi2_ssid", hexenc(request.args.radio_lan_ssid));
+ }
+ if ("radio_lan_channel" in request.args) {
+ configuration.setSetting("wifi2_channel", request.args.radio_lan_channel);
+ }
+ if ("radio_lan_encryption" in request.args) {
+ const encrypt = [ "psk", "psk2", "none" ];
+ configuration.setSetting("wifi2_encryption", encrypt[int(request.args.radio_lan_encryption)]);
+ }
+ if ("radio_lan_password" in request.args) {
+ configuration.setSetting("wifi2_key", hexenc(request.args.radio_lan_password));
+ }
+ if ("radio_wan_ssid" in request.args) {
+ configuration.setSetting("wifi3_ssid", hexenc(request.args.radio_wan_ssid));
+ }
+ if ("radio_wan_password" in request.args) {
+ configuration.setSetting("wifi3_key", hexenc(request.args.radio_wan_password));
+ }
+ if ("radio_antenna" in request.args) {
+ uciMesh.set("aredn", "@location[0]", "antenna", request.args.radio_antenna);
+ }
+ if ("radio_azimuth" in request.args) {
+ uciMesh.set("aredn", "@location[0]", "azimuth", request.args.radio_azimuth);
+ }
+ if ("radio_height" in request.args) {
+ uciMesh.set("aredn", "@location[0]", "height", request.args.radio_height);
+ }
+ if ("radio_elevation" in request.args) {
+ uciMesh.set("aredn", "@location[0]", "elevation", request.args.radio_elevation);
+ }
+ if ("radio_lqm_enable" in request.args) {
+ uciMesh.set("aredn", "@lqm[0]", "lqm_enable", request.args.radio_lqm_enable === "on" ? 1 : 0);
+ }
+ if ("radio_mindistance" in request.args) {
+ uciMesh.set("aredn", "@lqm[0]", "min_distance", request.args.radio_mindistance);
+ }
+ if ("radio_rts_threshold" in request.args) {
+ uciMesh.set("aredn", "@lqm[0]", "rts_threshold", request.args.radio_rts_threshold);
+ }
+ if ("radio_mtu" in request.args) {
+ uciMesh.set("aredn", "@lqm[0]", "mtu", request.args.radio_mtu);
+ }
+ if ("radio_margin_snr" in request.args) {
+ uciMesh.set("aredn", "@lqm[0]", "margin_snr", request.args.radio_margin_snr);
+ }
+ if ("radio_margin_quality" in request.args) {
+ uciMesh.set("aredn", "@lqm[0]", "margin_quality", request.args.radio_margin_quality);
+ }
+ if ("radio_ping_penalty" in request.args) {
+ uciMesh.set("aredn", "@lqm[0]", "ping_penalty", request.args.radio_ping_penalty);
+ }
+ if ("radio_min_routes" in request.args) {
+ uciMesh.set("aredn", "@lqm[0]", "min_routes", request.args.radio_min_routes);
+ }
+ uciMesh.commit("aredn");
+ configuration.saveSettings();
+ print(_R("changes"));
+ return;
+}
+if (request.env.REQUEST_METHOD === "DELETE") {
+ configuration.revertModalChanges();
+ print(_R("changes"));
+ return;
+}
+%}
+{% const wlan = radios.getConfiguration(); %}
+
+ {{_R("dialog-header", "Radios & Antennas")}}
+
+ {%
+ const hasradios = length(wlan) > 0;
+ if (hasradios) {
+ for (let w = 0; w < length(wlan); w++) {
+ const prefix = `radio${w}_`;
+ if (w !== 0) {
+ print("");
+ }
+ %}
+
+ {{_H("Select the purpose of the radio. Each radio can be assigned to a specific purpose, but devices with multiple radios
+ cannot have the same purpose for multiple radios (except off).")}}
+
1 ? "style='padding-left:10px'" : ""}}>
+
+
+
Channel
+
Channel and frequency of this connection
+
+
+
+
+
+ {{_H("Select the central channel/frequency for the radio.")}}
+
+
+
Channel Width
+
Channel bandwidth
+
+
+
+
+
+ {{_H("Select the bandwidth of the radio. Be aware that larger bandwidth settings will consume more channels. Avoid overlapping
+ channels as this will impact performance.")}}
+
+
+
Transmit Power
+
Transmit power
+
+
+
+
+
+ {{_H("Select the transmission power for the radio. Ideally use only enough power to maintain the link at the capacity required.")}}
+
+ {{_H("Low SNR results in higher latency, lower bandwidth and high retranmissions. Setting a minimum SNR allows links with
+ these characteristics to be ignored.")}}
+
+
+
Maximum Distance
+
Maximum distance allowed to other nodes in {{units.distanceUnit()}}
+
+
+
+
+
+ {{_H("Distance beyond which neighbor node connections will be ignored. Longer distances to nodes can result in poor performance.
+ This will effect all neighbors, not just the distant ones, so it often makes sense to keep this distance to a minimum.")}}
+
+
+
Minimum Quality
+
Minimum connection quaility percentage
+
+
+
+
+
+ {{_H("The node management system maintains an estimate of how well each neighbor link performs. This link quality is used
+ to determine which links to use. Lowering the minimum quality can impact performance, but may also be necessary under specific
+ circumstances.")}}
+ {% } %}
+
+
1 ? "style='padding-left:10px'" : ""}}>
+ {{_H("In LAN Hotpot mode, the WiFi acts as a wireless hotspot. Any device connecting will appear as a LAN device attached to the node.")}}
+
+
+
SSID
+
Hotspot SSID
+
+
+
+
+
+
+
+
Channel
+
Hotspot channel
+
+
+
+
+
+
+
+
+
Encryption
+
Encryption algorithm
+
+
+
+
+
+
+
+
+
Password
+
Hotspot password
+
+
+
+
+
+
+
+
+
1 ? "style='padding-left:10px'" : ""}}>
+ {{_H("In WAN Client mode, the WiFi connection is used to connect to another wireless network. This network is expected to provide
+ access to the Internet.")}}
+
+ {{_H("Link Quality Management (LQM) is an automatic management system which monitors the efficiency of each neighbor link
+ and optimizes their use for best performance. When disabled, it still gathers data on each link, but this information is
+ not used to effect operation.")}}
+ {% if (hasradios) { %}
+
+
+
Minimum Distance
+
Minimum distance to other nodes in {{units.distanceUnit()}}
+
+
+
+
+
+ {{_H("Exclude nodes which are too close to this node.")}}
+
+
+
RTS Threshold
+
RTS Threshold in bytes before using RTS/CTS when hidden nodes are detected
+
+
+
+
+
+ {{_H("When hidden nodes are detected, the RTS/CTS protocol is automatically enabled to improve performance. By default this is used for all packets
+ being sent, but this can be optimized to only packets over a specific size (between 1 and 2347 bytes). Setting the value to
+ 2347 disables the protocol.")}}
+
+
+
Max Packet Size
+
Maximum packet size in bytes sent over WiFi
+
+
+
+
+
+ {{_H("By default, WiFi uses the same packet size (1500 bytes) as Ethernet. However, in some noisy environment performance can be
+ improved by reducing the packet size. A value must be between 256 and 1500.")}}
+
+
+
SNR Margin
+
SNR Margin in dB above Min SNR a signal must reach to be re-activated
+
+
+
+
+
+ {{_H("The SNR margin avoids a link switching quickly between blocked and unblocked when the SNR is the same as the minimum SNR. Once
+ a link falls below the minimum SNR, it must move above minimum SNR + SNR margin to become active again.")}}
+ {% } %}
+
+
+
Quality Margin
+
Quality Margin percentage increase before neighbor can be re-activated
+
+
+
+
+
+
+
+
Ping Penalty
+
Ping Penalty quality percentage to add when neighbor cannot be pinged
+
+
+
+
+
+
+
+
Minimum Routes
+
Minimum number of routes on a link required to disable blocking
+
+
+
+
+
+ {% } %}
+
+
+ {{_R("dialog-footer")}}
+
+
diff --git a/files/app/main/status/e/reboot.ut b/files/app/main/status/e/reboot.ut
new file mode 100755
index 00000000..10644d7d
--- /dev/null
+++ b/files/app/main/status/e/reboot.ut
@@ -0,0 +1,56 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+ response.reboot = true;
+ const address = configuration.getSettingAsString("wifi_ip", request.env.HTTP_HOST);
+%}
+{{configuration.getName()}} rebooting
+
+
+
+
+
AREDNTM
+
Amateur Radio Emergency Data Network
+
+
+
Rebooting
+
Your node is rebooting. This browser will reconnect automatically once complete.
diff --git a/files/app/main/status/e/time.ut b/files/app/main/status/e/time.ut
new file mode 100755
index 00000000..79bba384
--- /dev/null
+++ b/files/app/main/status/e/time.ut
@@ -0,0 +1,154 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+if (request.env.REQUEST_METHOD === "PUT") {
+ configuration.prepareChanges();
+ if ("timezone" in request.args) {
+ const nt = match(request.args.timezone, /^(.*)\t(.*)$/);
+ if (nt) {
+ configuration.setSetting("time_zone_name", nt[1]);
+ configuration.setSetting("time_zone", nt[2]);
+ }
+ }
+ if ("ntp_server" in request.args) {
+ configuration.setSetting("ntp_server", request.args.ntp_server);
+ }
+ if ("ntp_mode" in request.args) {
+ switch (request.args.ntp_mode) {
+ case "daily":
+ case "hourly":
+ uciMesh.set("aredn", "@ntp[0]", "period", request.args.ntp_mode);
+ break;
+ default:
+ break;
+ }
+ }
+ if (request.args.gps_enable) {
+ uciMesh.set("aredn", "@time[0]", "gps_enable", request.args.gps_enable === "on" ? "1" : "0");
+ }
+ configuration.saveSettings();
+ uciMesh.commit("aredn");
+ print(_R("changes"));
+ return;
+}
+if (request.env.REQUEST_METHOD === "DELETE") {
+ configuration.revertModalChanges();
+ print(_R("changes"));
+ return;
+}
+const time_zone_name = configuration.getSettingAsString("time_zone_name", "UTC");
+const tz_db_names = [];
+const f = fs.open("/etc/zoneinfo");
+if (f) {
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ l = rtrim(l);
+ const nt = split(l, "\t");
+ push(tz_db_names, { name: nt[0], value: l });
+ }
+ f.close();
+}
+const ntp_server = configuration.getSettingAsString("ntp_server", "");
+const ntp_mode = uciMesh.get("aredn", "@ntp[0]", "period") || "daily";
+%}
+
+ {{_R("dialog-header", "Time")}}
+
+
+
+
Timezone
+
Timezone
+
+
+
+
+
+ {{_H("The timezone for this node. Setting this correctly means that timed events will run in the appopriate timezone,
+ logs will have the expected times, etc.")}}
+
+
+
+
NTP Server
+
The ntp server to sync the time
+
+
+
+
+
+ {{_H("The default NTP server to use when syncing the node's time. If this cannot be found, the node will search for one on the mesh.")}}
+
+
+
NTP Updates
+
NTP update frequency
+
+
+
+
+
+ {{_H("NTP is used to keep the node's time up to date. Syncing the time every day is probably sufficient
+ but you can increase the frequency to hourly. Having accurate time means your timed events will run when you expected
+ and log information will show meaningful times.")}}
+ {{_R("dialog-advanced")}}
+
+ {{_H("Use either a local GPS devices to set the time, or search for a GPS device on another local node, and use its
+ GPS to set our time.")}}
+ {% } %}
+
+
+ {{_R("dialog-footer")}}
+
+
diff --git a/files/app/main/status/e/tunnels.ut b/files/app/main/status/e/tunnels.ut
new file mode 100755
index 00000000..3b2ce781
--- /dev/null
+++ b/files/app/main/status/e/tunnels.ut
@@ -0,0 +1,541 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+if (request.env.REQUEST_METHOD === "PUT") {
+ configuration.prepareChanges();
+ if ("tunnel_server" in request.args) {
+ uciMesh.set("vtun", "@network[0]", "dns", request.args.tunnel_server);
+ uciMesh.commit("vtun");
+ }
+ if ("tunnel_range_start" in request.args) {
+ uciMesh.set("vtun", "@network[0]", "start", request.args.tunnel_range_start);
+ uciMesh.commit("vtun");
+ }
+ if ("tunnel_weight" in request.args) {
+ uciMesh.set("aredn", "@tunnel[0]", "weight", request.args.tunnel_weight);
+ uciMesh.commit("aredn");
+ }
+ if ("tunnels" in request.args) {
+ const tunnels = json(request.args.tunnels);
+ const found = { ls: {}, lc: {}, ws: {}, wc: {} };
+ for (let i = 0; i < length(tunnels); i++) {
+ const t = tunnels[i];
+ found[t.type][t.index] = true;
+ switch (t.type) {
+ case "wc":
+ {
+ if (!uciMesh.get("vtun", t.index)) {
+ uciMesh.set("vtun", t.index, "server");
+ }
+ uciMesh.set("vtun", t.index, "enabled", t.enabled ? "1" : "0");
+ uciMesh.set("vtun", t.index, "host", t.name);
+ uciMesh.set("vtun", t.index, "passwd", t.passwd);
+ const np = split(t.network, ":");
+ const n = iptoarr(np[0]);
+ uciMesh.set("vtun", t.index, "node", `${uc(substr(configuration.getName(), 0, 23))}-${n[0]}-${n[1]}-${n[2]}-${n[3]}:${np[1]}`);
+ uciMesh.set("vtun", t.index, "clientip", `${n[0]}.${n[1]}.${n[2]}.${n[3] + 1}`);
+ uciMesh.set("vtun", t.index, "serverip", `${n[0]}.${n[1]}.${n[2]}.${n[3] + 2}`);
+ uciMesh.set("vtun", t.index, "netip", t.network);
+ uciMesh.set("vtun", t.index, "weight", t.weight);
+ break;
+ }
+ case "lc":
+ {
+ if (!uciMesh.get("vtun", t.index)) {
+ uciMesh.set("vtun", t.index, "server");
+ }
+ uciMesh.set("vtun", t.index, "enabled", t.enabled ? "1" : "0");
+ uciMesh.set("vtun", t.index, "host", t.name);
+ uciMesh.set("vtun", t.index, "passwd", t.passwd);
+ const n = iptoarr(t.network);
+ uciMesh.set("vtun", t.index, "node", `${uc(substr(configuration.getName(), 0, 23))}-${n[0]}-${n[1]}-${n[2]}-${n[3]}`);
+ uciMesh.set("vtun", t.index, "clientip", `${n[0]}.${n[1]}.${n[2]}.${n[3] + 1}`);
+ uciMesh.set("vtun", t.index, "serverip", `${n[0]}.${n[1]}.${n[2]}.${n[3] + 2}`);
+ uciMesh.set("vtun", t.index, "netip", t.network);
+ uciMesh.set("vtun", t.index, "weight", t.weight);
+ break;
+ }
+ case "ws":
+ {
+ if (!uciMesh.get("wireguard", t.index)) {
+ uciMesh.set("wireguard", t.index, "client");
+ }
+ uciMesh.set("wireguard", t.index, "enabled", t.enabled ? "1" : "0");
+ uciMesh.set("wireguard", t.index, "name", t.name);
+ uciMesh.set("wireguard", t.index, "key", t.key);
+ uciMesh.set("wireguard", t.index, "clientip", t.network);
+ uciMesh.set("wireguard", t.index, "weight", t.weight);
+ break;
+ }
+ case "ls":
+ {
+ if (!uciMesh.get("vtun", t.index)) {
+ uciMesh.set("vtun", t.index, "client");
+ }
+ uciMesh.set("vtun", t.index, "enabled", t.enabled ? "1" : "0");
+ uciMesh.set("vtun", t.index, "passwd", t.passwd);
+ const n = iptoarr(t.network);
+ uciMesh.set("vtun", t.index, "node", `${uc(substr(t.name, 0, 23))}-${n[0]}-${n[1]}-${n[2]}-${n[3]}`);
+ uciMesh.set("vtun", t.index, "clientip", `${n[0]}.${n[1]}.${n[2]}.${n[3] + 1}`);
+ uciMesh.set("vtun", t.index, "serverip", `${n[0]}.${n[1]}.${n[2]}.${n[3] + 2}`);
+ uciMesh.set("vtun", t.index, "netip", t.network);
+ uciMesh.set("vtun", t.index, "weight", t.weight);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ uciMesh.foreach("vtun", "server", a => {
+ if (!found.lc[a[".name"]] && !found.wc[a[".name"]]) {
+ uciMesh.delete("vtun", a[".name"]);
+ }
+ });
+ uciMesh.foreach("vtun", "client", a => {
+ if (!found.ls[a[".name"]]) {
+ uciMesh.delete("vtun", a[".name"]);
+ }
+ });
+ uciMesh.foreach("wireguard", "client", a => {
+ if (!found.ws[a[".name"]]) {
+ uciMesh.delete("wireguard", a[".name"]);
+ }
+ });
+ uciMesh.commit("vtun");
+ uciMesh.commit("wireguard");
+ }
+ print(_R("changes"));
+ return;
+}
+if (request.env.REQUEST_METHOD === "DELETE") {
+ configuration.revertModalChanges();
+ print(_R("changes"));
+ return;
+}
+const available = { l: {}, w: {} };
+const l = iptoarr(uciMesh.get("vtun", "@network[0]", "start"));
+const w = [ l[0], l[1], (l[2] === 255 ? 0 : l[2] + 1), l[3] ];
+for (let i = 0; i < 62; i++) {
+ available.l[`${l[0]}.${l[1]}.${l[2]}.${l[3]}`] = 1;
+ l[3] += 4;
+ if (l[3] >= 252) {
+ l[2]++;
+ l[3] = 4;
+ }
+}
+let p = uciMesh.get("aredn", "@supernode[0]", "enable") === 1 ? 6526 : 5525;
+for (let i = 0; i < 126; i++) {
+ available.w[`${w[0]}.${w[1]}.${w[2]}.${w[3]}:${p}`] = 1;
+ w[3] += 2;
+ if (w[3] >= 254) {
+ w[2]++;
+ w[3] = 2;
+ }
+ p++;
+}
+const up = { lg: {}, wg: [] };
+let f = fs.popen("ps -w | grep vtun | grep ' tun '");
+if (f) {
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ const m = match(l, /.*:.*-(172-.*) tun tun/);
+ if (m) {
+ up.lg[replace(m[1], "-", ".")] = true;
+ }
+ }
+ f.close();
+}
+const handshaketime = time();
+f = fs.popen("/usr/bin/wg show all latest-handshakes");
+if (f) {
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ const m = split(l, /\t/);
+ if (m && int(m[2]) + 300 > handshaketime) {
+ push(up.wg, m[1]);
+ }
+ }
+ f.close();
+}
+const tunnels = [];
+uciMesh.foreach("vtun", "server", a => {
+ const wireguard = index(a.node, ":") !== -1;
+ push(tunnels, {
+ index: a[".name"],
+ type: wireguard ? "wc" : "lc",
+ name: a.host,
+ enabled: a.enabled === "1",
+ notes: a.comment || "",
+ network: a.netip,
+ passwd: a.passwd,
+ weight: a.weight ?? "",
+ up: wireguard ? (length(filter(up.wg, v => index(a.key, v) !== -1)) === 0 ? false : true) : (up.lg[a.netip] ? true : false)
+ });
+ delete available.l[a.clientip];
+ delete available.w[a.clientip];
+});
+uciMesh.foreach("vtun", "client", a => {
+ const n = iptoarr(a.netip);
+ const remove = `-${n[0]}-${n[1]}-${n[2]}-${n[3]}`;
+ push(tunnels, {
+ index: a[".name"],
+ type: "ls",
+ name: replace(a.name, remove, ""),
+ enabled: a.enabled === "1",
+ notes: a.comment || "",
+ network: a.netip,
+ passwd: a.passwd,
+ weight: a.weight ?? "",
+ up: up.lg[a.netip] ? true : false
+ });
+ delete available.l[a.clientip];
+});
+uciMesh.foreach("wireguard", "client", a => {
+ push(tunnels, {
+ index: a[".name"],
+ type: "ws",
+ name: a.name,
+ enabled: a.enabled === "1",
+ notes: a.comment || "",
+ network: a.clientip,
+ key: a.key,
+ passwd: replace(a.key, /^[A-Za-z0-9+\/]+=/, ""),
+ weight: a.weight ?? "",
+ up: length(filter(up.wg, v => index(a.key, v) !== -1)) === 0 ? false : true
+ });
+ delete available.w[a.clientip];
+});
+function t2t(type)
+{
+ switch (type) {
+ case "ws":
+ return "Wireguard Server";
+ case "wc":
+ return "Wireguard Client";
+ case "ls":
+ return "Legacy Server";
+ case "lc":
+ return "Legacy Client";
+ default:
+ return type;
+ }
+}
+%}
+
+ {{_R("dialog-header", "Tunnels")}}
+
+
+
+
Tunnel Server
+
DNS name of this tunnel server
+
+
+
+
+
+ {{_H("Set either the hostname or the Internet IP Address for this tunnel server. This is the target for any tunnels
+ created which will connect to this node (legacy or wireguard).")}}
+
+
+
+
Add tunnel
+
Add a tunnel from a template
+
+
+
+
+
+
+ {{_H("Create a tunnel by selecting the specific type and hitting the +. The tunnel configuration will auto-populate with as much
+ information as possible. For tunnel clients all the fields can be easily populated by copy-n-pasting the entire server configuration
+ from another node into any field.")}}
+
+
+ {% if (uciMesh.get("aredn", "@supernode[0]", "enable") === "1") { %}
+ {{_H("The range of IP addresses allocated to the tunnels. Tunnels always start 172.30")}}
+ {% } else { %}
+ {{_H("The range of IP addresses allocated to the tunnels. Tunnels always start 172.31")}}
+ {% } %}
+ {% if (uciMesh.get("aredn", "@supernode[0]", "enable") !== "1") { %}
+
+
+
Default Tunnel Weight
+
Default cost of using a tunnel
+
+
+
+
+
+ {{_H("The tunnel weight is the cost of routing traffic via a tunnel. The higher the weight, the less likely a tunnel is used to reach a destination.
+This value is used by default, but each tunnel may overide it.
+")}}
+ {% } %}
+ {% } %}
+
+
+ {{_R("dialog-footer")}}
+
+
diff --git a/files/app/main/tools/e/iperf3.ut b/files/app/main/tools/e/iperf3.ut
new file mode 100755
index 00000000..94aaf8e4
--- /dev/null
+++ b/files/app/main/tools/e/iperf3.ut
@@ -0,0 +1,202 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+const nodes = mesh.getNodeList();
+const mynode = configuration.getName();
+%}
+
+ {{_R("tool-header", "iPerf3")}}
+
+
+
+
Server Address
+
Node name or address
+
+
+
+
+
+
+
+
+
+
Client Address
+
Node name or address
+
+
+
+
+
+
+
+ {{_H("Use iperf3 to measure the bandwidth between a client and a server. The client and server must be nodes on the mesh. By default the client is the current node.")}}
+
+
+
+
+
+
+
+
+
+ {{_R("tool-footer")}}
+
+
diff --git a/files/app/main/tools/e/ping.ut b/files/app/main/tools/e/ping.ut
new file mode 100755
index 00000000..b0754aff
--- /dev/null
+++ b/files/app/main/tools/e/ping.ut
@@ -0,0 +1,204 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+const nodes = mesh.getNodeList();
+const mynode = configuration.getName();
+%}
+
+ {{_R("tool-header", "Ping")}}
+
+
+
+
Target Address
+
IP Address, Hostname or Node
+
+
+
+
+
+
+
+
+
+
Source Address
+
Node name or address
+
+
+
+
+
+
+
+ {{_H("Send ping packets between a source host and a target. The source should be node on the mesh, and by default is the current node.
+ The target can be a node, an IP address, or any hostname that might be reachable.")}}
+
+
+
+
+
+
+
+
+
+ {{_R("tool-footer")}}
+
+
diff --git a/files/app/main/tools/e/supportdata.ut b/files/app/main/tools/e/supportdata.ut
new file mode 100755
index 00000000..76aa2517
--- /dev/null
+++ b/files/app/main/tools/e/supportdata.ut
@@ -0,0 +1,170 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+if (request.env.REQUEST_METHOD === "PUT") {
+ response.headers["HX-Redirect"] = request.env.REQUEST_URI;
+ return;
+}
+
+const wifiiface = uci.get("network", "wifi", "device");
+
+const files = [
+ "/etc/board.json",
+ "/etc/config/",
+ "/etc/config.mesh/",
+ "/etc/local/",
+ "/etc/mesh-release",
+ "/etc/os-release",
+ "/var/run/hosts_olsr",
+ "/var/run/services_olsr",
+ "/tmp/etc/",
+ "/tmp/dnsmasq.d/",
+ "/tmp/lqm.info",
+ "/tmp/wireless_monitor.info",
+ "/tmp/service-validation-state",
+ "/tmp/sysinfo/",
+ "/sys/kernel/debug/ieee80211/phy0/ath9k/ack_to",
+ "/sys/kernel/debug/ieee80211/phy1/ath9k/ack_to"
+];
+const sensitive = [
+ "/etc/config/vtun",
+ "/etc/config.mesh/vtun",
+ "/etc/config/network",
+ "/etc/config.mesh/wireguard",
+ "/etc/config/wireless",
+ "/etc/config.mesh/_setup",
+];
+const cmds = [
+ "cat /proc/cpuinfo",
+ "cat /proc/meminfo",
+ "df -k",
+ "dmesg",
+ "ifconfig",
+ "ethtool eth0",
+ "ethtool eth1",
+ "ip link",
+ "ip addr",
+ "ip neigh",
+ "ip route list",
+ "ip route list table 29",
+ "ip route list table 30",
+ "ip route list table 31",
+ "ip route list table main",
+ "ip route list table default",
+ "ip rule list",
+ "netstat -aln",
+ "iwinfo",
+ `${wifiiface ? "iwinfo " + wifiiface + " assoclist" : null}`,
+ `${wifiiface ? "iw phy " + (replace(wifiiface, "wlan", "phy")) + " info" : null}`,
+ `${wifiiface ? "iw dev " + wifiiface + " info" : null}`,
+ `${wifiiface ? "iw dev " + wifiiface + " scan" : null}`,
+ `${wifiiface ? "iw dev " + wifiiface + " station dump" : null}`,
+ "wg show all",
+ "wg show all latest-handshakes",
+ "nft list ruleset",
+ "md5sum /www/cgi-bin/*",
+ "echo /all | nc 127.0.0.1 2006",
+ "opkg list-installed",
+ "ps -w",
+ "/usr/local/bin/get_hardwaretype",
+ "/usr/local/bin/get_boardid",
+ "/usr/local/bin/get_model",
+ "/usr/local/bin/get_hardware_mfg",
+ "logread",
+];
+if (trim(fs.popen("/usr/local/bin/get_hardware_mfg").read("all")) === "Ubiquiti") {
+ push(cmds, "cat /dev/mtd0|grep 'U-Boot'|head -n1");
+}
+
+system("/bin/rm -rf /tmp/sd");
+system("/bin/mkdir -p /tmp/sd");
+
+for (let i = 0; i < length(files); i++) {
+ const file = files[i];
+ const s = fs.stat(file);
+ if (s) {
+ if (s.type === "directory") {
+ system(`/bin/mkdir -p /tmp/sd${file}`);
+ system(`/bin/cp -rp ${file}/* /tmp/sd/${file}`);
+ }
+ else {
+ system(`/bin/mkdir -p /tmp/sd${fs.dirname(file)}`);
+ system(`/bin/cp -p ${file} /tmp/sd/${file}`);
+ }
+ }
+}
+
+for (let i = 0; i < length(sensitive); i++) {
+ const file = sensitive[i];
+ const f = fs.open(file);
+ if (f) {
+ const lines = [];
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ l = replace(l, /option passwd.+/, "option passwd '***HIDDEN***'\n");
+ l = replace(l, /option public_key.+/, "option public_key '***HIDDEN***'\n");
+ l = replace(l, /option private_key.+/, "option private_key '***HIDDEN***'\n");
+ l = replace(l, /option key.+/, "option key '***HIDDEN***'\n");
+ push(lines, l);
+ }
+ f.close();
+ fs.writefile(`/tmp/sd${file}`, join("", lines));
+ }
+}
+
+const f = fs.open("/tmp/sd/data.txt", "w");
+if (f) {
+ for (let i = 0; i < length(cmds); i++) {
+ const cmd = cmds[i];
+ if (cmd) {
+ const p = fs.popen(`(${cmd}) 2> /dev/null`);
+ if (p) {
+ f.write(`\n===\n========== ${cmd} ==========\n===\n`);
+ f.write(p.read("all"));
+ p.close();
+ }
+ }
+ }
+ f.close();
+}
+
+system("/bin/tar -zcf /tmp/supportdata.tar.gz -C /tmp/sd ./");
+system("/bin/rm -rf /tmp/sd");
+
+const tm = localtime();
+response.override = true;
+uhttpd.send(`Status: 200 OK\r\nContent-Type: application/x-gzip\r\nContent-Disposition: attachment; filename=supportdata-${configuration.getName()}-${tm.year}-${tm.mon}-${tm.mday}-${tm.hour}-${tm.min}.tar.gz\r\nCache-Control: no-store\r\n\r\n`);
+uhttpd.send(fs.readfile("/tmp/supportdata.tar.gz"));
+
+%}
diff --git a/files/app/main/tools/e/traceroute.ut b/files/app/main/tools/e/traceroute.ut
new file mode 100755
index 00000000..38491e0e
--- /dev/null
+++ b/files/app/main/tools/e/traceroute.ut
@@ -0,0 +1,211 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+const nodes = mesh.getNodeList();
+const mynode = configuration.getName();
+%}
+
+ {{_R("tool-header", "Traceroute")}}
+
+
+
+
Target Address
+
IP Address, Hostname or Node
+
+
+
+
+
+
+
+
+
+
Source Address
+
Node name or address
+
+
+
+
+
+
+
+ {{_H("Trace the route between a source host and a target. The source should be node on the mesh, and by default is the current node.
+ The target can be a node, an IP address, or any hostname that might be reachable.")}}
+
+
+
+
+
+
+
+
+
+ {{_R("tool-footer")}}
+
+
diff --git a/files/app/main/tools/e/wifiscan.ut b/files/app/main/tools/e/wifiscan.ut
new file mode 100755
index 00000000..008d5651
--- /dev/null
+++ b/files/app/main/tools/e/wifiscan.ut
@@ -0,0 +1,274 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+const last_scan_file = "/tmp/last-scan.json";
+let last_scan = [];
+let scan_time = "Unknown";
+
+if (request.env.REQUEST_METHOD === "PUT") {
+ const config = radios.getActiveConfiguration();
+ const radio = config[0]?.mode === radios.RADIO_MESH ? config[0] : config[1]?.mode === radios.RADIO_MESH ? config[1] : null;
+ if (!radio) {
+ return;
+ }
+ const radiomode = radio.modes[radios.RADIO_MESH];
+ const wifiiface = radio.iface;
+ const myssid = radiomode.ssid;
+ const mychan = radiomode.channel;
+ const myfreq = hardware.getChannelFrequency(wifiiface, radiomode.channel);
+ const nodename = configuration.getName();
+ const scan = {};
+
+ let ubnt_ac = false;
+ const board_type = hardware.getBoard().model.id;
+ if (index(board_type, "ubnt,") === 0 && index(board_type, "ac") !== -1) {
+ ubnt_ac = true
+ }
+
+ const reArp = /^([\.0-9]+) +0x. +0x. +([0-9a-fA-F:]+)/;
+ const arp = {};
+ let f = fs.open("/proc/net/arp");
+ if (f) {
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ const m = match(l, reArp);
+ if (m) {
+ arp[m[2]] = m[1];
+ }
+ }
+ f.close();
+ }
+
+ f = fs.popen(`/usr/sbin/iw dev ${wifiiface} station dump`);
+ if (f) {
+ const re = regexp(`^Station ([0-9a-fA-F:]+) \\(on ${wifiiface}\\)`);
+ const reSi = /signal:[ \t]+(-[0-9]+)/;
+ let station;
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ let m = match(l, re);
+ if (m) {
+ station = scan[m[1]];
+ if (!station) {
+ const ip = arp[m[1]];
+ const hostname = ip ? network.nslookup(ip) : null;
+ station = {
+ mac: m[1],
+ signal: 9999,
+ chan: { [mychan]: true },
+ key: "",
+ joined: false,
+ mode: "Connected Ad-Hoc Station",
+ ssid: myssid,
+ ip: ip,
+ hostname: hostname
+ };
+ scan[m[1]] = station;
+ }
+ }
+ m = match(l, reSi);
+ if (m) {
+ station.signal = int(m[1]);
+ }
+ }
+ f.close();
+ }
+
+ if (ubnt_ac) {
+ system(`/usr/sbin/iw dev ${wifiiface} ibss leave > /dev/null 2>&1`);
+ system("/sbin/wifi up > /dev/null 2>&1");
+ for (let attempt = 10; attempt > 0; attempt--) {
+ f = fs.popen(`/usr/sbin/iw dev ${wifiiface} scan`);
+ if (f) {
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ if (substr(l, 0, 4) === "BSS ") {
+ attempt = 0;
+ break;
+ }
+ }
+ f.close();
+ }
+ if (attempt > 0) {
+ sleep(2000);
+ }
+ }
+ }
+
+ f = fs.popen(`/usr/sbin/iw dev ${wifiiface} scan passive`);
+ if (f) {
+ const re = /^BSS ([0-9a-fA-F:]+)/;
+ const reF = /freq: ([0-9]+)/;
+ const reSs = /SSID: (.+)\n/;
+ const reSi = /signal: (.+)\n/;
+ const reC = /Group cipher: (.+)\n/;
+ let station = {};
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ let m = match(l, re);
+ if (m) {
+ station = scan[m[1]];
+ if (!station) {
+ const ip = arp[m[1]];
+ const hostname = ip ? network.nslookup(ip) : null;
+ station = {
+ mac: m[1],
+ signal: 9999,
+ chan: {},
+ key: "",
+ joined: false,
+ mode: "AP",
+ ssid: "",
+ ip: ip,
+ hostname: hostname
+ };
+ scan[m[1]] = station;
+ }
+ if (index(l, "joined") !== -1) {
+ station.mode = "My Ad-Hoc Network";
+ station.joined = true;
+ station.hostname = nodename;
+ }
+ }
+ m = match(l, reF);
+ if (m) {
+ if (m[1] == myfreq) {
+ station.mode = "My Ad-Hoc Network";
+ station.joined = true;
+ }
+ const chan = hardware.getChannelFromFrequency(int(m[1]));
+ if (chan) {
+ station.chan[chan] = true;
+ }
+ }
+ m = match(l, reSs);
+ if (m) {
+ station.ssid = m[1];
+ }
+ m = match(l, reSi);
+ if (m) {
+ station.signal = int(m[1]);
+ }
+ m = match(l, reC);
+ if (m) {
+ station.key = m[1];
+ }
+ if (index(l, "capability: IBSS") !== -1 && station.mode === "AP") {
+ station.mode = "Foreign Ad-Hoc Network";
+ }
+ }
+ f.close();
+ }
+
+ for (let k in scan) {
+ scan[k].chan = join(" ", sort(keys(scan[k].chan)));
+ scan[k].hostname = scan[k].hostname ? replace(scan[k].hostname, ".local.mesh", "") : null;
+ }
+
+ last_scan = sort(
+ filter(values(scan), v => v.signal !== 9999 || v.joined),
+ (a, b) => b.signal - a.signal
+ );
+
+ fs.writefile(last_scan_file, sprintf("%J", last_scan));
+ scan_time = "0 seconds ago";
+}
+else {
+ const d = fs.readfile(last_scan_file);
+ if (d) {
+ last_scan = json(d);
+ const last = time() - fs.stat(last_scan_file).mtime;
+ if (last === 1) {
+ scan_time = "1 second ago";
+ }
+ else if (last < 60) {
+ scan_time = `${last} seconds ago`;
+ }
+ else if (last < 120) {
+ scan_time = `1 minute ago`;
+ }
+ else if (last < 3600) {
+ scan_time = `${int(last / 60)} minutes ago`;
+ }
+ else {
+ scan_time = "a long time ago";
+ }
+ }
+}
+%}
+
+ {{_R("tool-header", "WiFi Scan")}}
+
+
+
+
+
SNR
Signal
Chan
Enc
SSID
Hostname
MAC/BSSID
802.11 Mode
+
+
+
+ {% for (let i = 0; i < length(last_scan); i++) {
+ const s = last_scan[i];
+ %}
+
{{95 + s.signal}}
{{s.signal}}
{{s.chan}}
-
{{s.ssid}}
{{s.hostname || s.ip || "-"}}
{{s.mac}}
{{s.mode}}
+ {% } %}
+
+
+
+
+
Last Scan: {{scan_time}}
+
+
+ {{_H(" Scan the appropriate radio spectrum for other nodes and wifi devices. What a node can find while scanning is highly dependent
+ on the hardware itself. Also, due to the nature of wireless scanning and beaconing, multiple scans are something required for a
+ complete pictures of the surrounding radio area.
By default the last scan is shown.")}}
+ {{_R("tool-footer")}}
+
+
diff --git a/files/app/main/tools/e/wifisignal.ut b/files/app/main/tools/e/wifisignal.ut
new file mode 100755
index 00000000..4f804c9f
--- /dev/null
+++ b/files/app/main/tools/e/wifisignal.ut
@@ -0,0 +1,292 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+if (request.env.REQUEST_METHOD === "PUT") {
+ const wifiiface = uci.get("network", "wifi", "device");
+ let f = fs.popen(`iw ${wifiiface} station dump`);
+ if (f) {
+ const signals = {};
+ let mac = null;
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ if (index(l, "Station") === 0) {
+ const m = match(l, / ([0-9a-fA-F:]+) /);
+ if (m) {
+ mac = m[1];
+ }
+ }
+ if (index(l, "signal:") !== -1) {
+ const m = match(l, /[\t ]([0-9\-]+)[\t ]/);
+ if (m) {
+ signals[mac] = int(m[1]);
+ }
+ }
+ }
+ f.close();
+ let noise = -95;
+ f = fs.popen(`iw ${wifiiface} survey dump`);
+ if (f) {
+ const reN = /noise:[ \t]+([0-9\-]+) dBm/;
+ let ff = false;
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ if (index(l, "[in use]") !== -1) {
+ ff = true;
+ }
+ else if (ff) {
+ const m = match(l, reN);
+ if (m) {
+ noise = int(m[1]);
+ break;
+ }
+ }
+ }
+ f.close();
+ }
+ printf("%J", { s: signals, n: noise });
+ }
+ return;
+}
+const stations = fs.lsdir("/tmp/snrlog");
+for (let i = 0; i < length(stations); i++) {
+ const s = stations[i];
+ const m = match(s, /^([0-9A-Fa-f:]+)-(.*)$/);
+ if (m) {
+ stations[i] = { hostname: m[2], mac: lc(m[1]) };
+ }
+ else {
+ stations[i] = null;
+ }
+}
+%}
+
+ {{_R("tool-header", "WiFi Signal")}}
+
+
+
+
Node
+
Select the target node
+
+
+
+
+
+
+
+
+
- dBm snr: -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Sound
+
Enable audible indicator
+
+
+
+
+
+
+
+
Volume
+
+
+
+
+
+
+
+
Pitch
+
+
+
+
+
+
+ {{_H("This tool helps to align the node's antenna with its neighbors for the best signal strength. The indicator on the left
+ shows the current, best, and worst signal strenghts. The graph on the right show the history of the most recent signal strengths.
+ Specific neighbors can be selected, the default being an average of all those currently visible.
A sound indicator is also
+ provided which is useful when aligning antennas without looking at this display.")}}
+
+ {{_R("tool-footer")}}
+
+
diff --git a/files/app/main/tunnels.ut b/files/app/main/tunnels.ut
new file mode 100755
index 00000000..e9015e9a
--- /dev/null
+++ b/files/app/main/tunnels.ut
@@ -0,0 +1,35 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{{_R("tunnels")}}
diff --git a/files/app/partial/changes.ut b/files/app/partial/changes.ut
new file mode 100755
index 00000000..cbc0a6c0
--- /dev/null
+++ b/files/app/partial/changes.ut
@@ -0,0 +1,83 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+ let pwchanged = false;
+ if (request.env.REQUEST_METHOD === "PUT") {
+ if (request.args.revert) {
+ configuration.revertChanges();
+ }
+ else if (request.args.commit) {
+ pwchanged = configuration.isPasswordChanged();
+ configuration.commitChanges();
+ print(`
${_R(request.page)}
`);
+ }
+ }
+%}
+
{%
+ let changes = 0;
+ let reboot = false;
+ if (pwchanged) {
+ %}{%
+ }
+ if (auth.isAdmin) {
+ changes = configuration.countChanges();
+ if (fs.access("/tmp/reboot-required")) {
+ reboot = true;
+ }
+ }
+ if (reboot) {
+ %}
+
+ Reboot required:
+
+
+ {% }
+ else if (changes > 0) {
+ %}
+
+ Pending changes: {{changes}}
+
+
+
+
+ {% } %}
diff --git a/files/app/partial/dhcp.ut b/files/app/partial/dhcp.ut
new file mode 100755
index 00000000..2ff02860
--- /dev/null
+++ b/files/app/partial/dhcp.ut
@@ -0,0 +1,120 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+ const dhcp = configuration.getDHCP();
+ let da = 0;
+ let dr = 0;
+ let at = 0;
+ let ao = 0;
+ if (dhcp.enabled) {
+ let f = fs.open(dhcp.leases);
+ if (f) {
+ while (length(f.read("line"))) {
+ da++;
+ }
+ f.close();
+ }
+ f = fs.open(dhcp.reservations);
+ if (f) {
+ while (length(f.read("line"))) {
+ dr++;
+ }
+ f.close();
+ }
+ f = fs.open(dhcp.dhcptags);
+ if (f) {
+ while (length(f.read("line"))) {
+ at++;
+ }
+ f.close();
+ }
+ f = fs.open(dhcp.dhcpoptions);
+ if (f) {
+ while (length(f.read("line"))) {
+ ao++;
+ }
+ f.close();
+ }
+ }
+%}
+{% if (dhcp.enabled) { %}
+
+
LAN DHCP
+
+
Active
+
status
+
+
+
{{dhcp.gateway}} / {{dhcp.cidr}}
+
gateway
+
+
+
{{dhcp.start}} - {{dhcp.end}}
+
range
+
+
+
+
+
{{dr}}
+
reserved leases
+
+
+
{{da}}
+
active leases
+
+
+
+
+
+
{{at}}
+
tags
+
+
+
{{ao}}
+
options
+
+
+
+
+
+{% } else { %}
+
+
LAN DHCP
+
+
Disabled
+
status
+
+
+{% } %}
diff --git a/files/app/partial/dialog-advanced.ut b/files/app/partial/dialog-advanced.ut
new file mode 100755
index 00000000..f894c5fe
--- /dev/null
+++ b/files/app/partial/dialog-advanced.ut
@@ -0,0 +1,38 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+
+
+
+
diff --git a/files/app/partial/dialog-footer.ut b/files/app/partial/dialog-footer.ut
new file mode 100755
index 00000000..d9b0363d
--- /dev/null
+++ b/files/app/partial/dialog-footer.ut
@@ -0,0 +1,41 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+
diff --git a/files/app/partial/dialog-header.ut b/files/app/partial/dialog-header.ut
new file mode 100755
index 00000000..3b8a6d3d
--- /dev/null
+++ b/files/app/partial/dialog-header.ut
@@ -0,0 +1,46 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+if (request.env.REQUEST_METHOD === "GET" && request.headers["hx-target"] === "ctrl-modal" && !request.headers["include-help"] && !request.headers["include-advanced"]) {
+ configuration.prepareModalChanges();
+}
+%}
+
+
+
+
{{inner}}
+
configuration
+
+
diff --git a/files/app/partial/dialog-messages.ut b/files/app/partial/dialog-messages.ut
new file mode 100755
index 00000000..193ed333
--- /dev/null
+++ b/files/app/partial/dialog-messages.ut
@@ -0,0 +1,38 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+
+
+
+
diff --git a/files/app/partial/firmware.ut b/files/app/partial/firmware.ut
new file mode 100755
index 00000000..1cf4085e
--- /dev/null
+++ b/files/app/partial/firmware.ut
@@ -0,0 +1,68 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+ const firmware = configuration.getFirmwareVersion();
+ let releases = split(fs.readfile("/etc/current_releases"), " ");
+ let message = "uptodate";
+ if (!releases) {
+ message = "";
+ releases = [ firmware, firmware ];
+ }
+ if (match(firmware, /^\d+\.\d+\.\d+\.\d+$/)) {
+ // release
+ if (firmware !== releases[0]) {
+ message = "needupdate";
+ }
+ }
+ else if (match(firmware, /^\d\d\d\d\d\d\d\d-/)) {
+ // nightly
+ if (firmware !== releases[1]) {
+ message = "needupdate";
+ }
+ }
+ else {
+ message = "custom";
+ }
+%}
+
diff --git a/files/app/partial/general.ut b/files/app/partial/general.ut
new file mode 100755
index 00000000..a459be7c
--- /dev/null
+++ b/files/app/partial/general.ut
@@ -0,0 +1,60 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+
+{{_R("firmware")}}
+{% if (auth.isAdmin && !hardware.isLowMemNode()) { %}
+
+{{_R("packages")}}
+
+{% } %}
+
+{{_R("network")}}
diff --git a/files/app/partial/health.ut b/files/app/partial/health.ut
new file mode 100755
index 00000000..1f7126ba
--- /dev/null
+++ b/files/app/partial/health.ut
@@ -0,0 +1,76 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+ const d = split(fs.readfile("/proc/uptime"), " ");
+ const up = int(d[0]);
+ let uptime = sprintf("%d:%02d", int(up / 3600) % 24, int(up / 60) % 60);
+ if (up >= 172800) {
+ uptime = int(up / 86400) + " days, " + uptime;
+ }
+ else if (up > 86400) {
+ uptime = "1 day, " + uptime;
+ }
+ const ld = split(fs.readfile("/proc/loadavg"), " ");
+ const ram = int(match(fs.readfile("/proc/meminfo"), /MemFree: +(\d+) kB/)[1]) / 1000.0;
+ const f = fs.popen("exec /bin/df /");
+ let flash = "-";
+ if (f) {
+ flash = int(split(f.read("all"), /\s+/)[10]) / 1000.0;
+ f.close();
+ }
+ const tm = localtime();
+ const tmsource = fs.readfile("/tmp/timesync");
+%}
+
diff --git a/files/app/partial/internal-services.ut b/files/app/partial/internal-services.ut
new file mode 100755
index 00000000..936f611b
--- /dev/null
+++ b/files/app/partial/internal-services.ut
@@ -0,0 +1,72 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+ const rl = uci.get("aredn", "@remotelog[0]", "url") ? "active" : "disabled";
+ const sn = uci.get("aredn", "@supernode[0]", "enable") === "1" ? "active" : "disabled";
+ const cm = uci.get("aredn", "@supernode[0]", "support") === "0" ? "disabled" : "active";
+ const wd = uci.get("aredn", "@watchdog[0]", "enable") === "1" ? "active" : "disabled";
+ const ip = uci.get("aredn", "@iperf[0]", "enable") === "0" ? "disabled" : "active";
+ const s = fs.stat("/tmp/metrics-ran");
+ const mt = s && time() - s.mtime < 3600000 ? "active" : "inactive";
+ const ws = "active";
+ const ww = "active";
+ const wt = "active";
+ const ws = uci.get("aredn", "@wan[0]", "ssh_access") === "0" ? "disabled" : "active";
+ const ww = uci.get("aredn", "@wan[0]", "web_access") === "0" ? "disabled" : "active";
+ const wt = uci.get("aredn", "@wan[0]", "telnet_access") === "0" ? "disabled" : "active";
+ const poe = uci.get("aredn", "@poe[0]", "passthrough") === "0" ? "disabled" : "active";
+ const pou = uci.get("aredn", "@usb[0]", "passthrough") === "0" ? "disabled" : "active";
+
+%}
+
+
Internal Services
+
+
Cloud Mesh
+
Metrics
+
Watchdog
+
Remote Logging
+
IPerf3 Server
+
Supernode
+
WAN ssh
+
WAN telnet
+
WAN web
+ {% if (hardware.hasPOE()) { %}
+
PoE out
+ {% } %}
+ {% if (hardware.hasUSBPower()) { %}
+
USB power out
+ {% } %}
+
+
diff --git a/files/app/partial/local-and-neighbor-devices.ut b/files/app/partial/local-and-neighbor-devices.ut
new file mode 100755
index 00000000..fc84fd36
--- /dev/null
+++ b/files/app/partial/local-and-neighbor-devices.ut
@@ -0,0 +1,209 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+ let links = {};
+ const o = olsr.getLinks();
+ for (let i = 0; i < length(o); i++) {
+ links[o[i].remoteIP] = o[i];
+ }
+ function calcColor(tracker)
+ {
+ if (tracker.blocked) {
+ return "blocked";
+ }
+ if (!tracker.routable) {
+ return "idle";
+ }
+ const quality = tracker.quality;
+ if (quality < 40) {
+ return "bad";
+ }
+ else if (quality < 50) {
+ return "poor";
+ }
+ else if (quality < 75) {
+ return "okay";
+ }
+ else if (quality < 95) {
+ return "good";
+ }
+ else {
+ return "excellent";
+ }
+ };
+ function calcBitrate(txbitrate, rxbitrate)
+ {
+ if (txbitrate) {
+ if (rxbitrate) {
+ return sprintf("%.1f", ((txbitrate + rxbitrate) * 5 + 0.5) / 10);
+ }
+ else {
+ return sprintf("%.1f", (txbitrate * 10 + 0.5) / 10);
+ }
+ }
+ return "-";
+ }
+%}
+
+{% } %}
diff --git a/files/app/partial/local-services.ut b/files/app/partial/local-services.ut
new file mode 100755
index 00000000..8c2e5b99
--- /dev/null
+++ b/files/app/partial/local-services.ut
@@ -0,0 +1,145 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+ const reService = /^([^|]+)\|1\|([^|]+)\|([^|]+)\|(\d+)\|(.*)$/;
+ const reLink = /^([^|]+)\|0\|\|([^|]+)\|\|$/;
+ const reType = /^(.+) \[([a-z]+)\]$/;
+ const reOlsr = /PlParam "service" ".*\|([^|]+)"/;
+
+ const services = [];
+ const devices = [];
+ const leases = {};
+ const activesvc = {};
+
+ const dhcp = configuration.getDHCP();
+
+ let f = fs.open("/var/etc/olsrd.conf");
+ if (f) {
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ const m = match(l, reOlsr);
+ if (m) {
+ activesvc[m[1]] = true;
+ }
+ }
+ f.close();
+ }
+ f = fs.open(dhcp.services);
+ if (f) {
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ const v = match(trim(l), reService);
+ if (v) {
+ let type = "";
+ if (!activesvc[v[1]]) {
+ type += ` `;
+ }
+ const v2 = match(v[1], reType);
+ if (v2) {
+ v[1] = v2[1];
+ type += ` `;
+ }
+ switch (v[4]) {
+ case "80":
+ push(services, `
");
+ for (let i = 0; i < length(devices); i++) {
+ print(devices[i]);
+ }
+ print("
");
+ }
+ %}
+
+
diff --git a/files/app/partial/location.ut b/files/app/partial/location.ut
new file mode 100755
index 00000000..1e608ddf
--- /dev/null
+++ b/files/app/partial/location.ut
@@ -0,0 +1,65 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+ const map = uci.get("aredn", "@location[0]", "map");
+ const lat = uci.get("aredn", "@location[0]", "lat");
+ const lon = uci.get("aredn", "@location[0]", "lon");
+ const gridsquare = uci.get("aredn", "@location[0]", "gridsquare");
+ const source = uci.get("aredn", "@location[0]", "source");
+ const mapurl = lat && lon && map ? replace(replace(map, "(lat)", lat), "(lon)", lon) : null;
+%}
+
+{% if (mapurl) { %}
+
+
+{% } %}
+ {% if (lat && lon) { %}
+
+
{{lat}}, {{lon}}
+
{{gridsquare}}
+ {% } else if (gridsquare) { %}
+
+
{{gridsquare}}
+ {% } else { %}
+
+
Unknown
+ {% } %}
+
+
location{{source ? " (" + source + ")" : ""}}
+
diff --git a/files/app/partial/login.ut b/files/app/partial/login.ut
new file mode 100755
index 00000000..0fdc8779
--- /dev/null
+++ b/files/app/partial/login.ut
@@ -0,0 +1,109 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{% if (!auth.isAdmin) { %}
+
+
+
+{% } %}
diff --git a/files/app/partial/mesh-data.ut b/files/app/partial/mesh-data.ut
new file mode 100755
index 00000000..33480633
--- /dev/null
+++ b/files/app/partial/mesh-data.ut
@@ -0,0 +1,127 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+print("\n");
+
+%}
diff --git a/files/app/partial/mesh-summary.ut b/files/app/partial/mesh-summary.ut
new file mode 100755
index 00000000..c8ceac55
--- /dev/null
+++ b/files/app/partial/mesh-summary.ut
@@ -0,0 +1,53 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+ const counts = mesh.getNodeCounts();
+%}
+
+
Mesh
+
+
+
+
{{counts.nodes}}
+
nodes
+
+
+
{{counts.devices}}
+
devices
+
+
+
+
+
diff --git a/files/app/partial/mesh.ut b/files/app/partial/mesh.ut
new file mode 100755
index 00000000..36615c98
--- /dev/null
+++ b/files/app/partial/mesh.ut
@@ -0,0 +1,57 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{{_R("mesh-data")}}
+
+
+
+
+
+
+
+ This page shows a list of all the other nodes on the network, as well as what server and services they provide.
+ Nodes which are closer to you (have less radio hops to reach from here) are toward the top of this page, while nodes further
+ away are toward the bottom. As nodes get further away, they often become harder (or impossible) to reach. We group nodes together
+ with a simple colored border where greener is better, and redder is worse.
+
+ The search box above can be used to filter the nodes, servers and services on this page, making it easier to find specific things.
+ For example, typing "cam" in the box will filter out everything except names containsing "cam" ... which are probably cameras.
+
+
+
+{% if (!config.resourcehash) { %}
+
+{% } else { %}
+
+{% } %}
diff --git a/files/app/partial/messages.ut b/files/app/partial/messages.ut
new file mode 100755
index 00000000..ca042eab
--- /dev/null
+++ b/files/app/partial/messages.ut
@@ -0,0 +1,99 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+ const allmsgs = messages.getMessages();
+ const todos = messages.getToDos();
+%}
+
+ {% if (allmsgs.system) { %}
+
System Messages
+
+ {%
+ const msgs = allmsgs.system;
+ for (let i = 0; i < length(msgs); i++) {
+ print(`
${msgs[i]}
`);
+ }
+ delete allmsgs.system;
+ %}
+
+ {% }
+ if (allmsgs.yournode) { %}
+
Your Messages
+
+ {%
+ const msgs = allmsgs.yournode;
+ for (let i = 0; i < length(msgs); i++) {
+ print(`
${msgs[i]}
`);
+ }
+ delete allmsgs.yournode;
+ %}
+
+ {% }
+ if (allmsgs["all nodes"]) { %}
+
All Node Messages
+
+ {%
+ const msgs = allmsgs["all nodes"];
+ for (let i = 0; i < length(msgs); i++) {
+ print(`
${msgs[i]}
`);
+ }
+ delete allmsgs["all nodes"];
+ %}
+
+ {% }
+ for (let _ in allmsgs) {
+ %}
Other Messages
+
{%
+ for (let k in allmsgs) {
+ const msgs = allmsgs[k];
+ for (let i = 0; i < length(msgs); i++) {
+ print(`
${k}: ${msgs[i]}
`);
+ }
+ }
+ %}
{%
+ break;
+ }
+ if (length(todos) > 0) { %}
+
To Do
+
+ {%
+ for (let i = 0; i < length(todos); i++) {
+ print(`
${todos[i]}
`);
+ }
+ %}
+
+ {% }
+ %}
+
diff --git a/files/app/partial/nav-status.ut b/files/app/partial/nav-status.ut
new file mode 100755
index 00000000..24463af9
--- /dev/null
+++ b/files/app/partial/nav-status.ut
@@ -0,0 +1,11 @@
+
diff --git a/files/app/partial/nav.ut b/files/app/partial/nav.ut
new file mode 100755
index 00000000..60217b94
--- /dev/null
+++ b/files/app/partial/nav.ut
@@ -0,0 +1,75 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{{_R("changes")}}
+
+{{_R("nav-status")}}
+
+{{_R("login")}}
+{% if (auth.isAdmin) { %}
+
+{% } %}
diff --git a/files/app/partial/network.ut b/files/app/partial/network.ut
new file mode 100755
index 00000000..5617deae
--- /dev/null
+++ b/files/app/partial/network.ut
@@ -0,0 +1,80 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+
");
+ valueWan = true;
+ }
+ if (validWan) {
+ let v = "-";
+ const dns = split(uci.get("network", "lan", "dns"), " ");
+ if (dns && dns[0]) {
+ v = dns[0];
+ if (dns[1]) {
+ v += " " + dns[1];
+ }
+ }
+ print("
" + v + "
");
+ print("
wan dns
")
+ }
+ %}
+
diff --git a/files/app/partial/oldui.ut b/files/app/partial/oldui.ut
new file mode 100755
index 00000000..68474dc6
--- /dev/null
+++ b/files/app/partial/oldui.ut
@@ -0,0 +1 @@
+Old UI
diff --git a/files/app/partial/open.ut b/files/app/partial/open.ut
new file mode 100755
index 00000000..aa769df3
--- /dev/null
+++ b/files/app/partial/open.ut
@@ -0,0 +1,40 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{
+ const d = document.querySelector('dialog');
+ if (d && !d.open) {
+ d.showModal();
+ }
+}
diff --git a/files/app/partial/packages.ut b/files/app/partial/packages.ut
new file mode 100755
index 00000000..9ef4dee4
--- /dev/null
+++ b/files/app/partial/packages.ut
@@ -0,0 +1,51 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+let count = 0;
+const opkgs = {};
+map(split(fs.readfile("/etc/permpkg"), "\n"), p => opkgs[p] = true);
+const f = fs.popen("/bin/opkg list-installed");
+if (f) {
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ const m = match(l, /^[^ \t]+/);
+ if (m && !opkgs[m[0]]) {
+ count++;
+ }
+ }
+ f.close();
+}
+%}
+
{{count}}
+
installed packages
diff --git a/files/app/partial/ports-and-xlinks.ut b/files/app/partial/ports-and-xlinks.ut
new file mode 100755
index 00000000..eab5151c
--- /dev/null
+++ b/files/app/partial/ports-and-xlinks.ut
@@ -0,0 +1,74 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+ const ports = hardware.getEthernetPorts();
+ let active = 0;
+ for (let i = 0; i < length(ports); i++) {
+ const state = hardware.getEthernetPortInfo(ports[i].k);
+ if (state.active) {
+ active++;
+ }
+ }
+ let xcount = 0;
+ uciMesh.foreach("xlink", "interface", _ => {
+ xcount++;
+ });
+%}
+
+
+ {% if (length(ports) > 1) { %}
+
+
Ethernet Ports
+
+
+
{{length(ports)}}
+
ports
+
+
+
{{active}}
+
active
+
+
+
+ {% } %}
+
+
XLinks
+
+
{{xcount}}
+
xlinks
+
+
+
+
diff --git a/files/app/partial/radio-and-antenna.ut b/files/app/partial/radio-and-antenna.ut
new file mode 100755
index 00000000..41899f94
--- /dev/null
+++ b/files/app/partial/radio-and-antenna.ut
@@ -0,0 +1,122 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+ const radio = radios.getActiveConfiguration();
+ let midx = -1;
+ for (let i = 0; i < length(radio); i++) {
+ if (radio[i].mode === radios.RADIO_MESH) {
+ midx = i;
+ break;
+ }
+ }
+%}
+
diff --git a/files/app/partial/reboot-firmware.ut b/files/app/partial/reboot-firmware.ut
new file mode 100755
index 00000000..85a8f07c
--- /dev/null
+++ b/files/app/partial/reboot-firmware.ut
@@ -0,0 +1,69 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+ const firstuse = fs.access("/tmp/do-not-keep-configuration");
+%}
+{{configuration.getName()}} firmware updating
+
+
+
+
+
+
AREDNTM
+
Amateur Radio Emergency Data Network
+
+
+
Updating Firmware
+
Updating the firmware on your node. DO NOT REMOVE POWER UNTIL THIS IS COMPLETE.
diff --git a/files/app/partial/reboot-firstuse-ram.ut b/files/app/partial/reboot-firstuse-ram.ut
new file mode 100755
index 00000000..ad5dc312
--- /dev/null
+++ b/files/app/partial/reboot-firstuse-ram.ut
@@ -0,0 +1,59 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+
+
+
+
+
+
AREDNTM
+
Amateur Radio Emergency Data Network
+
+
+
Installing Firmware
+
Installing the firmware on your node. DO NOT REMOVE POWER UNTIL THIS IS COMPLETE.
diff --git a/files/app/partial/reboot-firstuse.ut b/files/app/partial/reboot-firstuse.ut
new file mode 100755
index 00000000..c87ef15c
--- /dev/null
+++ b/files/app/partial/reboot-firstuse.ut
@@ -0,0 +1,57 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+configuration.reset();
+const address = configuration.getSettingAsString("wifi_ip", "192.168.1.1");
+%}
+
+
+
+
+
+
AREDNTM
+
Amateur Radio Emergency Data Network
+
+
+
Rebooting
+
Your node is rebooting. After this is complete it will have the address {{address}}
Your browser will attempt to reconnect automatically, but you may need to adjust the network settings on your computer.
diff --git a/files/app/partial/reboot-mon.ut b/files/app/partial/reboot-mon.ut
new file mode 100755
index 00000000..c2aedbf0
--- /dev/null
+++ b/files/app/partial/reboot-mon.ut
@@ -0,0 +1,81 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+
diff --git a/files/app/partial/selection.ut b/files/app/partial/selection.ut
new file mode 100755
index 00000000..97356a13
--- /dev/null
+++ b/files/app/partial/selection.ut
@@ -0,0 +1,62 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+
+
+{{_R("tools")}}
diff --git a/files/app/partial/status.ut b/files/app/partial/status.ut
new file mode 100755
index 00000000..4eca171a
--- /dev/null
+++ b/files/app/partial/status.ut
@@ -0,0 +1,95 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+
diff --git a/files/app/partial/switch.ut b/files/app/partial/switch.ut
new file mode 100755
index 00000000..38feb991
--- /dev/null
+++ b/files/app/partial/switch.ut
@@ -0,0 +1,37 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+
diff --git a/files/app/partial/tool-footer.ut b/files/app/partial/tool-footer.ut
new file mode 100755
index 00000000..bacd47b7
--- /dev/null
+++ b/files/app/partial/tool-footer.ut
@@ -0,0 +1,38 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+
diff --git a/files/app/partial/tool-header.ut b/files/app/partial/tool-header.ut
new file mode 100755
index 00000000..4d131fb3
--- /dev/null
+++ b/files/app/partial/tool-header.ut
@@ -0,0 +1,41 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+
+
+
+
{{inner}}
+
tool
+
+
diff --git a/files/app/partial/tools.ut b/files/app/partial/tools.ut
new file mode 100755
index 00000000..fc2bacd5
--- /dev/null
+++ b/files/app/partial/tools.ut
@@ -0,0 +1,50 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+
+
+
+ {% if (hardware.getRadioCount() > 0) { %}
+
WiFi Scan
+
WiFi Signal
+ {% } %}
+
Ping
+
Traceroute
+
iPerf3
+
Support Data
+
+
diff --git a/files/app/partial/tunnels.ut b/files/app/partial/tunnels.ut
new file mode 100755
index 00000000..7b6d7d10
--- /dev/null
+++ b/files/app/partial/tunnels.ut
@@ -0,0 +1,133 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+ let wc = 0;
+ let ws = 0;
+ let lc = 0;
+ let ls = 0;
+ uciMesh.foreach("wireguard", "client", function()
+ {
+ ws++;
+ });
+ uciMesh.foreach("vtun", "client", function()
+ {
+ ls++;
+ });
+ uciMesh.foreach("vtun", "server", function(s)
+ {
+ if (index(s.netip, ":") !== -1) {
+ wc++;
+ }
+ else {
+ lc++;
+ }
+ });
+ let wac = 0;
+ let was = 0;
+ let lac = 0;
+ let las = 0;
+ const t = fs.popen("ps -w | grep vtun | grep ' tun '");
+ if (t) {
+ for (let l = t.read("line"); length(l); l = t.read("line")) {
+ if (index(l, "vtund[s]") !== -1) {
+ las++;
+ }
+ else if (index(l, "vtund[c]") !== -1) {
+ lac++;
+ }
+ }
+ t.close();
+ }
+ if (fs.access("/usr/bin/wg")) {
+ const w = fs.popen("/usr/bin/wg show all latest-handshakes");
+ if (w) {
+ for (let l = w.read("line"); length(l); l = w.read("line")) {
+ const v = split(trim(l), /\t/);
+ if (v && int(v[2]) + 300 > time()) {
+ if (index(v[0], "wgc") === 0) {
+ was++;
+ }
+ else {
+ wac++;
+ }
+ }
+ }
+ w.close();
+ }
+ }
+%}
+
";
+
+help.addEventListener("click", () => {
+ document.querySelector(".meshpage-help").classList.toggle("visible");
+});
+
+}
+render();
diff --git a/files/app/root.ut b/files/app/root.ut
new file mode 100644
index 00000000..fb134179
--- /dev/null
+++ b/files/app/root.ut
@@ -0,0 +1,654 @@
+{%
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+%}
+{%
+import * as config from "./config.uc";
+import * as fs from "fs";
+import * as math from "math";
+import * as uci from "uci";
+import * as ubus from "ubus";
+import * as log from "log";
+import * as lucihttp from "lucihttp";
+import * as configuration from "aredn.configuration";
+import * as hardware from "aredn.hardware";
+import * as lqm from "aredn.lqm";
+import * as network from "aredn.network";
+import * as olsr from "aredn.olsr";
+import * as units from "aredn.units";
+import * as radios from "aredn.radios";
+import * as messages from "aredn.messages";
+import * as mesh from "aredn.mesh";
+import * as constants from "./constants.uc";
+
+const pageCache = {};
+const resourceVersions = {};
+
+log.openlog("uhttpd.aredn", log.LOG_PID, log.LOG_USER);
+
+const light = fs.readfile(`${config.application}/resource/css/themes/light.css`);
+const dark = fs.readfile(`${config.application}/resource/css/themes/dark.css`);
+fs.writefile(`${config.application}/resource/css/themes/default.css`, `${light}@media (prefers-color-scheme: dark) {\n${dark}\n}`);
+if (!fs.access(`${config.application}/resource/css/theme.css`)) {
+ fs.symlink("themes/default.css", `${config.application}/resource/css/theme.css`);
+}
+
+if (config.preload) {
+ function cp(path) {
+ const dir = fs.opendir(`${config.application}${path}`);
+ for (;;) {
+ const entry = dir.read();
+ if (!entry) {
+ break;
+ }
+ if (match(entry, /\.ut$/)) {
+ const tpath = `${config.application}${path}${entry}`;
+ pageCache[tpath] = loadfile(tpath, { raw_mode: false });
+ }
+ }
+ }
+ cp("/main/");
+ cp("/partial/");
+ cp("/main/status/e/");
+ cp("/main/tools/e/");
+
+ radios.getCommonConfiguration();
+}
+
+if (config.resourcehash) {
+ function prepareResource(id, resource)
+ {
+ const path = `${config.application}/resource/${resource}`;
+ const pathgz = `${config.application}/resource/${resource}.gz`;
+ if (fs.access(path)) {
+ fs.unlink(pathgz);
+ system(`/bin/gzip -k ${path}`);
+ }
+ const md = fs.popen(`/usr/bin/md5sum ${pathgz}`);
+ resourceVersions[id] = match(md.read("all"), /^([0-9a-f]+)/)[1];
+ md.close();
+ fs.symlink(pathgz, `${path}.${resourceVersions[id]}.gz`);
+ }
+ prepareResource("usercss", "css/user.css");
+ prepareResource("admincss", "css/admin.css");
+ prepareResource("themecss", "css/theme.css");
+ prepareResource("htmx", "js/htmx.min.js");
+ prepareResource("meshpage", "js/meshpage.js");
+ let cthemeversion = null;
+ const ctheme = fs.readlink(`${config.application}/resource/css/theme.css`);
+ const themes = fs.lsdir(`${config.application}/resource/css/themes`);
+ for (let i = 0; i < length(themes); i++) {
+ const theme = themes[i];
+ if (match(theme, /^.*\.css$/)) {
+ prepareResource("themecss", `css/themes/${theme}`);
+ if (ctheme === `themes/${theme}`) {
+ cthemeversion = resourceVersions.themecss;
+ }
+ fs.unlink(`${config.application}/resource/css/theme.css.${resourceVersions.themecss}.gz`);
+ fs.symlink(`themes/${theme}.${resourceVersions.themecss}.gz`, `${config.application}/resource/css/theme.css.${resourceVersions.themecss}.gz`);
+ }
+ }
+ resourceVersions.themecss = cthemeversion;
+ fs.unlink(`${config.application}/resource/css/theme.version`);
+ fs.symlink(cthemeversion, `${config.application}/resource/css/theme.version`);
+}
+else {
+ let dir = fs.lsdir(`${config.application}/resource/css`);
+ for (let i = 0; i < length(dir); i++) {
+ if (match(dir[i], /\.gz$/)) {
+ fs.unlink(`${config.application}/resource/css/${dir[i]}`);
+ }
+ }
+ dir = fs.lsdir(`${config.application}/resource/css/themes`);
+ for (let i = 0; i < length(dir); i++) {
+ if (match(dir[i], /\.gz$/)) {
+ fs.unlink(`${config.application}/resource/css/themes/${dir[i]}`);
+ }
+ }
+ dir = fs.lsdir(`${config.application}/resource/js`);
+ for (let i = 0; i < length(dir); i++) {
+ if (match(dir[i], /\.gz$/) && dir[i] !== "htmx.min.js.gz") {
+ fs.unlink(`${config.application}/resource/js/${dir[i]}`);
+ }
+ }
+ fs.unlink(`${config.application}/resource/css/theme.version`);
+}
+
+global._R = function(path, arg)
+{
+ const tpath = `${config.application}/partial/${path}.ut`;
+ const fn = pageCache[tpath] || loadfile(tpath, { raw_mode: false });
+ let old = inner;
+ let r = "";
+ try {
+ inner = arg;
+ r = render(fn);
+ }
+ catch (_) {
+ }
+ inner = old;
+ return r;
+};
+
+global._H = function(str)
+{
+ return includeHelp ? `
`;
+ }
+ if (!response.override) {
+ if (index(env.HTTP_ACCEPT_ENCODING || "", "gzip") === -1 || !config.compress) {
+ response.headers["Content-Length"] = `${length(res)}`;
+ uhttpd.send(
+ `Status: ${response.statusCode} OK\r\n`,
+ join("", map(keys(response.headers), k => k + ": " + response.headers[k] + "\r\n")),
+ "\r\n",
+ res
+ );
+ }
+ else {
+ const r = fs.open("/dev/urandom");
+ let datafile;
+ if (r) {
+ const rid = r.read(8);
+ r.close();
+ datafile = `/tmp/uhttpd.${hexenc(rid)}`;
+ }
+ else {
+ datafile = `/tmp/uhttpd.${time()}${math.rand()}`;
+ }
+ try {
+ fs.writefile(datafile, res);
+ const z = fs.popen("exec /bin/gzip -c " + datafile);
+ try {
+ res = z.read("all");
+ response.headers["Content-Length"] = `${length(res)}`;
+ uhttpd.send(
+ `Status: ${response.statusCode} OK\r\nContent-Encoding: gzip\r\n`,
+ join("", map(keys(response.headers), k => k + ": " + response.headers[k] + "\r\n")),
+ "\r\n",
+ res
+ );
+ }
+ catch (_) {
+ }
+ z.close();
+ }
+ catch (_) {
+ }
+ fs.unlink(datafile);
+ }
+ }
+ if (response.reboot) {
+ system("(sleep 2; exec /sbin/reboot)&");
+ }
+ if (response.upgrade) {
+ system(`(sleep 2; ${response.upgrade})&`);
+ }
+ return;
+ }
+ uhttpd.send("Status: 404 Not Found\r\n\r\n");
+ return;
+ }
+
+ const rpath = `${config.application}/resource/${env.PATH_INFO || "unknown"}`;
+ const gzrpath = `${rpath}.gz`;
+ if (fs.access(gzrpath)) {
+ uhttpd.send("Status: 200 OK\r\nContent-Encoding: gzip\r\n");
+ if (substr(rpath, -3) === ".js") {
+ uhttpd.send("Content-Type: application/javascript\r\n");
+ }
+ else if (substr(rpath, -4) === ".css") {
+ uhttpd.send("Content-Type: text/css\r\n");
+ }
+ if (!config.resourcehash) {
+ uhttpd.send("Cache-Control: no-store\r\n");
+ }
+ else {
+ uhttpd.send("Cache-Control: max-age=604800\r\n");
+ }
+ const res = fs.readfile(gzrpath);
+ uhttpd.send(`Content-Length: ${length(res)}\n`);
+ uhttpd.send("\r\n", res);
+ return;
+ }
+
+ if (fs.access(rpath)) {
+ uhttpd.send("Status: 200 OK\r\n");
+ if (substr(rpath, -3) === ".js") {
+ uhttpd.send("Content-Type: application/javascript\r\n");
+ }
+ else if (substr(rpath, -4) === ".png") {
+ uhttpd.send("Content-Type: image/png\r\n");
+ }
+ else if (substr(rpath, -4) === ".jpg") {
+ uhttpd.send("Content-Type: image/jpeg\r\n");
+ }
+ else if (substr(rpath, -4) === ".css") {
+ uhttpd.send("Content-Type: text/css\r\n");
+ }
+ if (!config.resourcehash) {
+ uhttpd.send("Cache-Control: no-store\r\n");
+ }
+ else {
+ uhttpd.send("Cache-Control: max-age=604800\r\n");
+ }
+ const res = fs.readfile(rpath);
+ uhttpd.send(`Content-Length: ${length(res)}\n`);
+ uhttpd.send("\r\n", res);
+ return;
+ }
+
+ if (!config.resourcehash) {
+ uhttpd.send("Status: 404 Not Found\r\nCache-Control: no-store\r\n\r\n");
+ }
+ else {
+ uhttpd.send("Status: 404 Not Found\r\nCache-Control: max-age=600\r\n\r\n");
+ }
+};
+%}
diff --git a/files/etc/arednsysupgrade.conf b/files/etc/arednsysupgrade.conf
index f8172077..d80c378d 100644
--- a/files/etc/arednsysupgrade.conf
+++ b/files/etc/arednsysupgrade.conf
@@ -27,6 +27,7 @@
/etc/aredn_include/lan.network.user
/etc/aredn_include/wan.network.user
/etc/aredn_include/dtdlink.network.user
+/etc/aredn_include/olsrd.user
/etc/aredn_include/dnsmasq-user.conf
/etc/dropbear/dropbear_dss_host_key
/etc/dropbear/dropbear_rsa_host_key
@@ -41,3 +42,5 @@
/etc/passwd
/etc/shadow
/etc/package_store
+/app/resource/css/theme.css
+/app/resource/css/theme.version
diff --git a/files/etc/config.mesh/_setup b/files/etc/config.mesh/_setup
index a6f20c77..bca2ec27 100644
--- a/files/etc/config.mesh/_setup
+++ b/files/etc/config.mesh/_setup
@@ -9,40 +9,31 @@ wifi_chanbw = 20
wifi_distance = 0
wifi_country = 00
wifi_enable = 1
-
wifi2_enable = 0
wifi2_ssid = NoCall-AREDN
wifi2_channel = 36
wifi2_encryption =
wifi2_key =
wifi2_hwmode = 11a
-
wifi3_enable = 0
wifi3_ssid =
wifi3_key =
wifi3_hwmode = 11a
-
dmz_mode = 3
lan_proto = static
lan_ip = 172.27.0.1
lan_mask = 255.255.255.0
lan_dhcp = 1
-
dhcp_start = 5
dhcp_end = 25
dhcp_limit = 20
-
olsrd_bridge = 0
-
wan_proto = dhcp
wan_dns1 = 8.8.8.8
wan_dns2 = 8.8.4.4
-
dtdlink_ip=10.
-
time_zone = UTC
+time_zone_name = UTC
ntp_server = us.pool.ntp.org
-
description_node =
-
compat_version = 1.0
diff --git a/files/etc/config.mesh/aredn b/files/etc/config.mesh/aredn
index f0503094..467de1de 100644
--- a/files/etc/config.mesh/aredn
+++ b/files/etc/config.mesh/aredn
@@ -1,3 +1,4 @@
+
config downloads
option firmwarepath 'http://downloads.arednmesh.org/firmware'
@@ -12,13 +13,9 @@ config map
option leafletcss 'http://unpkg.com/leaflet@0.7.7/dist/leaflet.css'
option maptiles 'http://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.jpg'
-config meshstatus
-
config dmz
option mode '3'
-config location
-
config tunnel
option weight '1'
diff --git a/files/etc/config.mesh/dhcp b/files/etc/config.mesh/dhcp
index 60f9eb35..01bc936a 100644
--- a/files/etc/config.mesh/dhcp
+++ b/files/etc/config.mesh/dhcp
@@ -1,19 +1,20 @@
+
config dnsmasq
option rebind_protection '0'
- option confdir '/tmp/dnsmasq.d,*.conf'
+ option confdir '/tmp/dnsmasq.d,*.conf'
config dhcp
- option interface lan
- option start
- option limit
- option leasetime 1h
- option force 1
- option ignore
+ option interface 'lan'
+ option start
+ option limit
+ option leasetime '1h'
+ option force '1'
+ option ignore
config dhcp
- option interface wan
- option ignore 1
+ option interface 'wan'
+ option ignore '1'
config dhcp
- option interface wifi
- option ignore 1
+ option interface 'wifi'
+ option ignore '1'
diff --git a/files/etc/config.mesh/dropbear b/files/etc/config.mesh/dropbear
index 2bf01221..0ac01670 100644
--- a/files/etc/config.mesh/dropbear
+++ b/files/etc/config.mesh/dropbear
@@ -1,3 +1,4 @@
+
config dropbear
option PasswordAuth 'on'
- option Port '2222'
+ option Port '2222'
diff --git a/files/etc/config.mesh/firewall b/files/etc/config.mesh/firewall
index e7a94023..1dbe3947 100644
--- a/files/etc/config.mesh/firewall
+++ b/files/etc/config.mesh/firewall
@@ -1,266 +1,264 @@
+
config defaults
- option syn_flood 1
- option input ACCEPT
- option output ACCEPT
- option forward REJECT
+ option syn_flood '1'
+ option input 'ACCEPT'
+ option output 'ACCEPT'
+ option forward 'REJECT'
config zone
- option name lan
- option network 'lan'
- option input ACCEPT
- option output ACCEPT
- option forward REJECT
+ option name 'lan'
+ option network 'lan'
+ option input 'ACCEPT'
+ option output 'ACCEPT'
+ option forward 'REJECT'
config zone
- option name wan
- option network 'wan'
- option input REJECT
- option output ACCEPT
- option forward REJECT
- option masq 1
- option mtu_fix 1
+ option name 'wan'
+ option network 'wan'
+ option input 'REJECT'
+ option output 'ACCEPT'
+ option forward 'REJECT'
+ option masq '1'
+ option mtu_fix '1'
config zone
- option name wifi
- option network 'wifi'
- option input REJECT
- option output ACCEPT
- option forward REJECT
- option mtu_fix 1
+ option name 'wifi'
+ option network 'wifi'
+ option input 'REJECT'
+ option output 'ACCEPT'
+ option forward 'REJECT'
+ option mtu_fix '1'
config zone
- option name dtdlink
-
- option input REJECT
- option output ACCEPT
- option forward REJECT
- option mtu_fix 1
+ option name 'dtdlink'
+
+ option input 'REJECT'
+ option output 'ACCEPT'
+ option forward 'REJECT'
+ option mtu_fix '1'
config zone
- option name vpn
-
- option input REJECT
- option output ACCEPT
- option forward REJECT
- option mtu_fix 1
+ option name 'vpn'
+
+ option input 'REJECT'
+ option output 'ACCEPT'
+ option forward 'REJECT'
+ option mtu_fix '1'
config forwarding
- option src lan
- option dest wan
+ option src 'lan'
+ option dest 'wan'
config forwarding
- option src lan
- option dest wifi
+ option src 'lan'
+ option dest 'wifi'
config forwarding
- option src wifi
- option dest wifi
+ option src 'wifi'
+ option dest 'wifi'
config forwarding
- option src lan
- option dest dtdlink
+ option src 'lan'
+ option dest 'dtdlink'
config forwarding
- option src wifi
- option dest dtdlink
+ option src 'wifi'
+ option dest 'dtdlink'
config forwarding
- option src dtdlink
- option dest wifi
+ option src 'dtdlink'
+ option dest 'wifi'
config forwarding
- option src dtdlink
- option dest dtdlink
+ option src 'dtdlink'
+ option dest 'dtdlink'
config forwarding
- option src vpn
- option dest wifi
+ option src 'vpn'
+ option dest 'wifi'
config forwarding
- option src wifi
- option dest vpn
+ option src 'wifi'
+ option dest 'vpn'
config forwarding
- option src lan
- option dest vpn
+ option src 'lan'
+ option dest 'vpn'
config forwarding
- option src vpn
- option dest dtdlink
+ option src 'vpn'
+ option dest 'dtdlink'
config forwarding
- option src dtdlink
- option dest vpn
+ option src 'dtdlink'
+ option dest 'vpn'
config forwarding
- option src vpn
- option dest vpn
-
-# Allow IPv4 ping
-config rule
- option name Allow-Ping
- option src wifi
- option proto icmp
- option icmp_type echo-request
- option family ipv4
- option target ACCEPT
+ option src 'vpn'
+ option dest 'vpn'
config rule
- option name Allow-Ping
- option src dtdlink
- option proto icmp
- option icmp_type echo-request
- option family ipv4
- option target ACCEPT
+ option name 'Allow-Ping'
+ option src 'wifi'
+ option proto 'icmp'
+ option icmp_type 'echo-request'
+ option family 'ipv4'
+ option target 'ACCEPT'
config rule
- option name Allow-Ping
- option src vpn
- option proto icmp
- option icmp_type echo-request
- option family ipv4
- option target ACCEPT
+ option name 'Allow-Ping'
+ option src 'dtdlink'
+ option proto 'icmp'
+ option icmp_type 'echo-request'
+ option family 'ipv4'
+ option target 'ACCEPT'
+
+config rule
+ option name 'Allow-Ping'
+ option src 'vpn'
+ option proto 'icmp'
+ option icmp_type 'echo-request'
+ option family 'ipv4'
+ option target 'ACCEPT'
config include
- option path /usr/local/bin/mesh-firewall
- option fw4_compatible 1
+ option path '/usr/local/bin/mesh-firewall'
+ option fw4_compatible '1'
config include
- option path /etc/firewall.user
- option fw4_compatible 1
+ option path '/etc/firewall.user'
+ option fw4_compatible '1'
config rule
- option name Allow-Ping
- option src wan
- option proto icmp
- option icmp_type echo-request
- option family ipv4
- option target ACCEPT
+ option name 'Allow-Ping'
+ option src 'wan'
+ option proto 'icmp'
+ option icmp_type 'echo-request'
+ option family 'ipv4'
+ option target 'ACCEPT'
config rule
- option src wifi
- option dest_port 2222
- option proto tcp
- option target ACCEPT
+ option src 'wifi'
+ option dest_port '2222'
+ option proto 'tcp'
+ option target 'ACCEPT'
config rule
- option src wifi
- option dest_port 8080
- option proto tcp
- option target ACCEPT
+ option src 'wifi'
+ option dest_port '8080'
+ option proto 'tcp'
+ option target 'ACCEPT'
config rule
- option src wifi
- option dest_port 80
- option proto tcp
- option target ACCEPT
+ option src 'wifi'
+ option dest_port '80'
+ option proto 'tcp'
+ option target 'ACCEPT'
config rule
- option src wifi
- option dest_port 698
- option proto udp
- option target ACCEPT
+ option src 'wifi'
+ option dest_port '698'
+ option proto 'udp'
+ option target 'ACCEPT'
config rule
- option src wifi
- option dest_port 23
- option proto tcp
- option target ACCEPT
+ option src 'wifi'
+ option dest_port '23'
+ option proto 'tcp'
+ option target 'ACCEPT'
config rule
- option src dtdlink
- option dest_port 2222
- option proto tcp
- option target ACCEPT
+ option src 'dtdlink'
+ option dest_port '2222'
+ option proto 'tcp'
+ option target 'ACCEPT'
config rule
- option src dtdlink
- option dest_port 8080
- option proto tcp
- option target ACCEPT
+ option src 'dtdlink'
+ option dest_port '8080'
+ option proto 'tcp'
+ option target 'ACCEPT'
config rule
- option src dtdlink
- option dest_port 80
- option proto tcp
- option target ACCEPT
+ option src 'dtdlink'
+ option dest_port '80'
+ option proto 'tcp'
+ option target 'ACCEPT'
config rule
- option src dtdlink
- option dest_port 698
- option proto udp
- option target ACCEPT
+ option src 'dtdlink'
+ option dest_port '698'
+ option proto 'udp'
+ option target 'ACCEPT'
config rule
- option src dtdlink
- option dest_port 23
- option proto tcp
- option target ACCEPT
+ option src 'dtdlink'
+ option dest_port '23'
+ option proto 'tcp'
+ option target 'ACCEPT'
config rule
- option src vpn
- option dest_port 2222
- option proto tcp
- option target ACCEPT
+ option src 'vpn'
+ option dest_port '2222'
+ option proto 'tcp'
+ option target 'ACCEPT'
config rule
- option src vpn
- option dest_port 8080
- option proto tcp
- option target ACCEPT
+ option src 'vpn'
+ option dest_port '8080'
+ option proto 'tcp'
+ option target 'ACCEPT'
config rule
- option src vpn
- option dest_port 80
- option proto tcp
- option target ACCEPT
+ option src 'vpn'
+ option dest_port '80'
+ option proto 'tcp'
+ option target 'ACCEPT'
config rule
- option src vpn
- option dest_port 698
- option proto udp
- option target ACCEPT
+ option src 'vpn'
+ option dest_port '698'
+ option proto 'udp'
+ option target 'ACCEPT'
config rule
- option src vpn
- option dest_port 23
- option proto tcp
- option target ACCEPT
-
-#SNMPD
-config rule
- option src wifi
- option dest_port 161
- option proto udp
- option target ACCEPT
+ option src 'vpn'
+ option dest_port '23'
+ option proto 'tcp'
+ option target 'ACCEPT'
config rule
- option src dtdlink
- option dest_port 161
- option proto udp
- option target ACCEPT
+ option src 'wifi'
+ option dest_port '161'
+ option proto 'udp'
+ option target 'ACCEPT'
config rule
- option src vpn
- option dest_port 161
- option proto udp
- option target ACCEPT
-
-# olsr jsoninfo
-config rule
- option src wifi
- option dest_port 9090
- option proto tcp
- option target ACCEPT
+ option src 'dtdlink'
+ option dest_port '161'
+ option proto 'udp'
+ option target 'ACCEPT'
config rule
- option src dtdlink
- option dest_port 9090
- option proto tcp
- option target ACCEPT
+ option src 'vpn'
+ option dest_port '161'
+ option proto 'udp'
+ option target 'ACCEPT'
config rule
- option src vpn
- option dest_port 9090
- option proto tcp
- option target ACCEPT
+ option src 'wifi'
+ option dest_port '9090'
+ option proto 'tcp'
+ option target 'ACCEPT'
+
+config rule
+ option src 'dtdlink'
+ option dest_port '9090'
+ option proto 'tcp'
+ option target 'ACCEPT'
+
+config rule
+ option src 'vpn'
+ option dest_port '9090'
+ option proto 'tcp'
+ option target 'ACCEPT'
diff --git a/files/etc/config.mesh/network b/files/etc/config.mesh/network
index 9bfa6dc4..24a1d4ba 100644
--- a/files/etc/config.mesh/network
+++ b/files/etc/config.mesh/network
@@ -4,20 +4,20 @@
#### Globals
config globals 'globals'
- option packet_steering '1'
+ option packet_steering '1'
#### Loopback configuration
-config interface loopback
- option device "lo"
- option proto static
- option ipaddr 127.0.0.1
- option netmask 255.0.0.0
+config interface 'loopback'
+ option device 'lo'
+ option proto 'static'
+ option ipaddr '127.0.0.1'
+ option netmask '255.0.0.0'
#### WIFI configuration
-config interface wifi_mon
- option proto none
+config interface 'wifi_mon'
+ option proto 'none'
### Bridge configuration
diff --git a/files/etc/config.mesh/olsrd b/files/etc/config.mesh/olsrd
index 7257a898..56f210d4 100644
--- a/files/etc/config.mesh/olsrd
+++ b/files/etc/config.mesh/olsrd
@@ -1,3 +1,4 @@
+
config olsrd
option IpVersion '4'
option MainIp ''
diff --git a/files/etc/config.mesh/snmpd b/files/etc/config.mesh/snmpd
index 636e08dd..6526d2eb 100644
--- a/files/etc/config.mesh/snmpd
+++ b/files/etc/config.mesh/snmpd
@@ -1,42 +1,43 @@
+
config agent
- option agentaddress UDP:161
+ option agentaddress 'UDP:161'
-config com2sec public
- option secname ro
- option source default
- option community public
+config com2sec 'public'
+ option secname 'ro'
+ option source 'default'
+ option community 'public'
-config group public_v1
- option group public
- option version v1
- option secname ro
+config group 'public_v1'
+ option group 'public'
+ option version 'v1'
+ option secname 'ro'
-config group public_v2c
- option group public
- option version v2c
- option secname ro
+config group 'public_v2c'
+ option group 'public'
+ option version 'v2c'
+ option secname 'ro'
-config group public_usm
- option group public
- option version usm
- option secname ro
+config group 'public_usm'
+ option group 'public'
+ option version 'usm'
+ option secname 'ro'
-config view all
- option viewname all
- option type included
- option oid .1
+config view 'all'
+ option viewname 'all'
+ option type 'included'
+ option oid '.1'
-config access public_access
- option group public
- option context none
- option version any
- option level noauth
- option prefix exact
- option read all
- option write none
- option notify none
+config access 'public_access'
+ option group 'public'
+ option context 'none'
+ option version 'any'
+ option level 'noauth'
+ option prefix 'exact'
+ option read 'all'
+ option write 'none'
+ option notify 'none'
config system
- option sysLocation 'Deployed'
- option sysContact ''
- option sysName '.local.mesh'
+ option sysLocation 'Deployed'
+ option sysContact ''
+ option sysName '.local.mesh'
diff --git a/files/etc/config.mesh/system b/files/etc/config.mesh/system
index 34c24197..576335d8 100644
--- a/files/etc/config.mesh/system
+++ b/files/etc/config.mesh/system
@@ -1,29 +1,30 @@
-config 'system'
- option 'hostname' ''
- option 'timezone' ''
- option 'description' ''
- option 'compat_version' ''
- option 'log_ip' ''
- option 'log_port' ''
- option 'log_proto' ''
-config 'timeserver' 'ntp'
- list 'server' ''
- option enable_server 0
- option enabled 0
+config system
+ option hostname ''
+ option timezone ''
+ option description ''
+ option compat_version ''
+ option log_ip ''
+ option log_port ''
+ option log_proto ''
+
+config timeserver 'ntp'
+ list server ''
+ option enable_server '0'
+ option enabled '0'
config button
- option button 'reset'
- option action 'released'
- option handler '/usr/local/bin/recoverymode'
- option min '3'
- option max '7'
+ option button 'reset'
+ option action 'released'
+ option handler '/usr/local/bin/recoverymode'
+ option min '3'
+ option max '7'
config button
- option button 'reset'
- option action 'released'
- option handler 'firstboot -y && reboot'
- option min '12'
- option max '20'
+ option button 'reset'
+ option action 'released'
+ option handler 'firstboot -y && reboot'
+ option min '12'
+ option max '20'
include /etc/aredn_include/system_netled
diff --git a/files/etc/config.mesh/uhttpd b/files/etc/config.mesh/uhttpd
index 6c6afd08..56246669 100644
--- a/files/etc/config.mesh/uhttpd
+++ b/files/etc/config.mesh/uhttpd
@@ -1,11 +1,13 @@
-# Server configuration
-config uhttpd main
+
+config uhttpd 'main'
list listen_http '0.0.0.0:8080'
list listen_http '0.0.0.0:80'
option home '/www'
option rfc1918_filter '1'
option cgi_prefix '/cgi-bin'
+ list ucode_prefix '/a=/app/root.ut'
option script_timeout '240'
option network_timeout '30'
option http_keepalive '0'
list alias '/metrics=/cgi-bin/metrics'
+ option max_requests '3'
diff --git a/files/etc/config.mesh/vtun b/files/etc/config.mesh/vtun
index 48a40ff5..e69de29b 100755
--- a/files/etc/config.mesh/vtun
+++ b/files/etc/config.mesh/vtun
@@ -1,5 +0,0 @@
-config options
-
-config network
-
-config default
diff --git a/files/etc/config/uhttpd b/files/etc/config/uhttpd
index 7e65aafb..3610a9d7 100644
--- a/files/etc/config/uhttpd
+++ b/files/etc/config/uhttpd
@@ -1,13 +1,11 @@
# Server configuration
config uhttpd main
-
- # HTTP listen addresses, multiple allowed
- list listen_http 0.0.0.0:8080
- list listen_http 0.0.0.0:80
- option home /www
- option rfc1918_filter 1
- option cgi_prefix /cgi-bin
- option script_timeout 240
- option network_timeout 30
- option tcp_keepalive 5
-# option config /etc/httpd.conf
+ list listen_http '0.0.0.0:8080'
+ list listen_http '0.0.0.0:80'
+ option home '/www'
+ option rfc1918_filter '1'
+ option cgi_prefix '/cgi-bin'
+ list ucode_prefix '/a=/app/root.ut'
+ option script_timeout '240'
+ option network_timeout '30'
+ option tcp_keepalive '5'
diff --git a/files/etc/cron.boot/boot-fixups b/files/etc/cron.boot/boot-fixups
new file mode 100755
index 00000000..d2e41982
--- /dev/null
+++ b/files/etc/cron.boot/boot-fixups
@@ -0,0 +1,38 @@
+#!/bin/sh
+true <<'LICENSE'
+
+ Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ Copyright (C) 2024 Tim Wilkinson KN6PLV
+ See Contributors file for additional contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation version 3 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ Additional Terms:
+
+ Additional use restrictions exist on the AREDN® trademark and logo.
+ See AREDNLicense.txt for more info.
+
+ Attributions to the AREDN® Project must be retained in the source code.
+ If importing this code into a new or existing project attribution
+ to the AREDN® project must be added to the source code.
+
+ You must not misrepresent the origin of the material contained within.
+
+ Modified versions must be modified to attribute to the original source
+ and be marked in reasonable ways as differentiate it from the original
+ version.
+
+LICENSE
+
+# Until I find out what has left pending vtun changes we do this here
+/sbin/uci -c /etc/config.mesh commit vtun
diff --git a/files/etc/cron.daily/update-clock b/files/etc/cron.daily/update-clock
index cf395762..4cc8b01f 100755
--- a/files/etc/cron.daily/update-clock
+++ b/files/etc/cron.daily/update-clock
@@ -42,6 +42,7 @@ exec 2> /dev/null
candidate=$(uci -q get system.ntp.server)
if [ "${candidate}" != "" ]; then
if $(ntpd -n -q -p ${candidate}); then
+ echo -n "ntp" > /tmp/timesync
logger -p notice -t update-time "Update clock from ${candidate}"
exit 0
fi
@@ -52,10 +53,13 @@ fi
for candidate in $(grep -i ntp /var/run/services_olsr | sed "s/^.*:\/\/\(.*\):.*$/\1/")
do
if $(ntpd -n -q -p ${candidate}); then
+ echo -n "ntp" > /tmp/timesync
logger -p notice -t update-time "Update clock from ${candidate}"
exit 0
fi
done
+rm -f /tmp/timesync
+
logger -p notice -t update-time "Failed to update clock: No reachable ntpd servers."
exit 1
diff --git a/files/etc/cron.hourly/firmware-helper b/files/etc/cron.hourly/firmware-helper
new file mode 100755
index 00000000..0335e045
--- /dev/null
+++ b/files/etc/cron.hourly/firmware-helper
@@ -0,0 +1,83 @@
+#!/usr/bin/lua
+--[[
+
+ Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
+ Copyright (C) 2024 Tim Wilkinson
+ See Contributors file for additional contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation version 3 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ Additional Terms:
+
+ Additional use restrictions exist on the AREDN(TM) trademark and logo.
+ See AREDNLicense.txt for more info.
+
+ Attributions to the AREDN Project must be retained in the source code.
+ If importing this code into a new or existing project attribution
+ to the AREDN project must be added to the source code.
+
+ You must not misrepresent the origin of the material contained within.
+
+ Modified versions must be modified to attribute to the original source
+ and be marked in reasonable ways as differentiate it from the original
+ version
+
+--]]
+
+require("uci")
+
+local update_hour = 13 -- Run at ~1pm UTC
+local current_releases = "/etc/current_releases"
+
+local cursor = uci.cursor()
+
+local do_version_update = false;
+local time = os.date("!*t")
+if time.hour == update_hour then
+ do_version_update = true
+end
+
+local f = io.open(current_releases)
+if f then
+ f:close()
+else
+ do_version_update = true
+end
+
+-- Update firmware version information
+if do_version_update then
+ local config_url = cursor:get("aredn", "@downloads[0]", "firmware_aredn") .. "/afs/www/config.js"
+ local release_version
+ local nightly_version
+ for line in io.popen("/usr/bin/curl -o - " .. config_url .. " 2> /dev/null"):lines()
+ do
+ local v = line:match("versions: {(.+)}")
+ if v then
+ for i in v:gmatch("'(%d+-[^']+)'")
+ do
+ nightly_version = i
+ end
+ end
+ v = line:match('default_version: "(.+)"')
+ if v then
+ release_version = v
+ end
+ end
+ if release_version and nightly_version then
+ local f = io.open(current_releases, "w")
+ if f then
+ f:write(release_version .. " " .. nightly_version)
+ f:close()
+ end
+ end
+end
diff --git a/files/etc/init.d/local b/files/etc/init.d/local
index 723a2e57..119dba6a 100755
--- a/files/etc/init.d/local
+++ b/files/etc/init.d/local
@@ -18,7 +18,6 @@ boot() {
if [ -z "$poevalue" ]; then
local dpval=$(jsonfilter -e '@.gpioswitch.poe_passthrough.default' < /etc/board.json)
if [ ! -z "$dpval" ]; then
- uci -q add aredn poe
uci -q set aredn.@poe[0].passthrough="$dpval"
uci -q commit aredn
poevalue=$dpval
@@ -30,7 +29,6 @@ boot() {
local usbvalue=$(uci -q get aredn.@usb[0].passthrough)
if [ -z "$usbvalue" ]; then
local duval=$(jsonfilter -e '@.gpioswitch.usb_power_switch.default' < /etc/board.json)
- uci -q add aredn usb
uci -q set aredn.@usb[0].passthrough="$duval"
uci -q commit aredn
usbvalue=$duval
@@ -38,27 +36,17 @@ boot() {
/usr/local/bin/usb_passthrough "${usbvalue}"
# package repositories
- local repos="core base arednpackages packages luci routing telephony"
- set -- $repos
- while [ -n "$1" ]; do
- local ucirepo=$(uci -q get aredn.@downloads[0].pkgs_$1)
- local distrepo=$(grep aredn_$1 /etc/opkg/distfeeds.conf | cut -d' ' -f3)
- # get the URLs from distfeeds.conf and set the initial UCI values if not present
- if [ -z $ucirepo ]; then
- uci set aredn.@downloads[0].pkgs_$1=$distrepo
- uci commit aredn
- uci -c /etc/config.mesh set aredn.@downloads[0].pkgs_$1=$distrepo
- uci -c /etc/config.mesh commit aredn
- # check values in distfeeds.conf against UCI settings
- # and change distfeeds.conf if needed (upgrades?)
- elif [ $ucirepo != $distrepo ]; then
- sed -i "s|$distrepo|$ucirepo|g" /etc/opkg/distfeeds.conf
- fi
- shift
- done
-
- if [ -z "$(uci -q get aredn.@alerts[0])" ]; then
- uci -q add aredn alerts
- uci -q commit aredn
+ local packages_default = $(uci -q get "aredn.@downloads[0].packages_default")
+ if [ "${packages_default}" != "" ]; then
+ local repos="core base arednpackages packages luci routing telephony"
+ set -- $repos
+ while [ -n "$1" ]; do
+ local distrepo = $(grep aredn_$1 /etc/opkg/distfeeds.conf | cut -d' ' -f3)
+ local prefixurl = $(echo $distrepo | sed -n 's/\(http[s]\?:\/\/[^/]\+\).*/\1/p')
+ if [ "$packages_default" != "$prefixurl" ]; then
+ sed -i "s|$prefixurl|$packages_default|g" /etc/opkg/distfeeds.conf
+ fi
+ shift
+ done
fi
}
diff --git a/files/etc/radios.json b/files/etc/radios.json
index 5c773ee9..0c2f0aef 100644
--- a/files/etc/radios.json
+++ b/files/etc/radios.json
@@ -1,9 +1,9 @@
{
"meraki mr16": {
- "maxpower": "21"
+ "maxpower": 21
},
"gl.inet gl-ar150": {
- "maxpower": "18",
+ "maxpower": 18,
"antenna": {
"description": "2 dBi Omni",
"gain": 2,
@@ -11,7 +11,7 @@
}
},
"gl.inet gl-ar300m": {
- "maxpower": "23",
+ "maxpower": 23,
"antenna": {
"description": "2.5 dBi Omni",
"gain": 2.5,
@@ -27,7 +27,7 @@
}
},
"gl.inet gl-ar300m16": {
- "maxpower": "23",
+ "maxpower": 23,
"antenna": {
"description": "2.5 dBi Omni",
"gain": 2.5,
@@ -35,7 +35,7 @@
}
},
"gl.inet gl-usb150": {
- "maxpower": "20",
+ "maxpower": 20,
"antenna": {
"description": "Omni",
"gain": 0,
@@ -43,10 +43,10 @@
}
},
"gl.inet gl-ar750": {
- "maxpower": "23"
+ "maxpower": 23
},
"gl.inet gl-ar750s (nor/nand)": {
- "maxpower": "23"
+ "maxpower": 23
},
"gl.inet gl-b1300": {
"wlan0": {
@@ -88,7 +88,7 @@
},
"gl.inet gl-e750": {
"wlan0": {
- "maxpower": "20",
+ "maxpower": 20,
"antenna": {
"description": "3.5 dBi Omni",
"gain": 3.5,
@@ -96,7 +96,7 @@
}
},
"wlan1": {
- "maxpower": "20",
+ "maxpower": 20,
"antenna": {
"description": "1.5 dBi Omni",
"gain": 1.5,
@@ -105,7 +105,7 @@
}
},
"tp-link cpe210 v1": {
- "maxpower": "23",
+ "maxpower": 23,
"chanpower": {
"1": "22",
"14": "23"
@@ -117,7 +117,7 @@
}
},
"tp-link cpe210 v2": {
- "maxpower": "29",
+ "maxpower": 29,
"chanpower": {
"1": "27",
"2": "28",
@@ -131,7 +131,7 @@
}
},
"tp-link cpe210 v3": {
- "maxpower": "25",
+ "maxpower": 25,
"chanpower": {
"1": "21",
"2": "25",
@@ -144,7 +144,7 @@
}
},
"tp-link cpe220 v2": {
- "maxpower": "30",
+ "maxpower": 30,
"chanpower": {
"1": "25",
"2": "28",
@@ -157,7 +157,7 @@
}
},
"tp-link cpe220 v3": {
- "maxpower": "30",
+ "maxpower": 30,
"chanpower": {
"1": "25",
"2": "28",
@@ -170,7 +170,7 @@
}
},
"tp-link cpe510 v1": {
- "maxpower": "23",
+ "maxpower": 23,
"chanpower": {
"48": "10",
"149": "17",
@@ -183,7 +183,7 @@
}
},
"tp-link cpe510 v2": {
- "maxpower": "26",
+ "maxpower": 26,
"chanpower": {
"140": "17",
"184": "26"
@@ -195,7 +195,7 @@
}
},
"tp-link cpe510 v3": {
- "maxpower": "26",
+ "maxpower": 26,
"chanpower": {
"140": "17",
"184": "26"
@@ -207,7 +207,7 @@
}
},
"tp-link cpe605 v1": {
- "maxpower": "30",
+ "maxpower": 30,
"chanpower": {
"133": "30",
"141": "30",
@@ -223,7 +223,7 @@
}
},
"tp-link cpe610 v1": {
- "maxpower": "30",
+ "maxpower": 30,
"chanpower": {
"133": "15",
"141": "26",
@@ -239,7 +239,7 @@
}
},
"tp-link cpe610 v2": {
- "maxpower": "30",
+ "maxpower": 30,
"chanpower": {
"133": "15",
"141": "26",
@@ -255,7 +255,7 @@
}
},
"tp-link cpe710 v1": {
- "maxpower": "30",
+ "maxpower": 30,
"chanpower": {
"133": "30",
"141": "30",
@@ -271,7 +271,7 @@
}
},
"tp-link wbs210 v1": {
- "maxpower": "27",
+ "maxpower": 27,
"chanpower": {
"1": "13",
"10": "18",
@@ -281,7 +281,7 @@
"antenna": "external"
},
"tp-link wbs210 v2": {
- "maxpower": "27",
+ "maxpower": 27,
"chanpower": {
"1": "13",
"10": "18",
@@ -291,7 +291,7 @@
"antenna": "external"
},
"tp-link wbs510 v1": {
- "maxpower": "26",
+ "maxpower": 26,
"chanpower": {
"133": "26",
"149": "24",
@@ -301,7 +301,7 @@
"antenna": "external"
},
"tp-link wbs510 v2": {
- "maxpower": "26",
+ "maxpower": 26,
"chanpower": {
"133": "26",
"149": "24",
@@ -311,23 +311,23 @@
"antenna": "external"
},
"mikrotik routerboard 911g-2hpnd": {
- "maxpower": "30",
+ "maxpower": 30,
"antenna": "external"
},
"mikrotik routerboard rb911g-2hpnd": {
- "maxpower": "30",
+ "maxpower": 30,
"antenna": "external"
},
"mikrotik routerboard 911g-5hpnd": {
- "maxpower": "30",
+ "maxpower": 30,
"antenna": "external"
},
"mikrotik routerboard rb911g-5hpnd": {
- "maxpower": "30",
+ "maxpower": 30,
"antenna": "external"
},
"mikrotik routerboard 911g-5hpnd-qrt": {
- "maxpower": "30",
+ "maxpower": 30,
"antenna": {
"description": "24 dBi 10.5° Panel",
"gain": 24,
@@ -335,7 +335,7 @@
}
},
"mikrotik routerboard 911g-2hpnd-12s": {
- "maxpower": "30",
+ "maxpower": 30,
"antenna": {
"description": "12 dBi 120° Sector",
"gain": 12,
@@ -343,7 +343,7 @@
}
},
"mikrotik routerboard 921gs-5hpacd-15s": {
- "maxpower": "31",
+ "maxpower": 31,
"bandwidths": [ 10, 20 ],
"antenna": {
"description": "15 dBi 120° Sector",
@@ -352,7 +352,7 @@
}
},
"mikrotik routerboard 921gs-5hpacd-19s": {
- "maxpower": "31",
+ "maxpower": 31,
"bandwidths": [ 10, 20 ],
"antenna": {
"description": "19 dBi 120° Sector",
@@ -421,52 +421,52 @@
}
},
"mikrotik routerboard 912uag-2hpnd": {
- "maxpower": "30",
+ "maxpower": 30,
"antenna": "external"
},
"mikrotik routerboard rb912uag-2hpnd": {
- "maxpower": "30",
+ "maxpower": 30,
"antenna": "external"
},
"mikrotik routerboard 912uag-5hpnd": {
- "maxpower": "30",
+ "maxpower": 30,
"antenna": "external"
},
"mikrotik routerboard rb912uag-5hpnd": {
- "maxpower": "30",
+ "maxpower": 30,
"antenna": "external"
},
"mikrotik routerboard ldf-5nd": {
- "maxpower": "25",
+ "maxpower": 25,
"antenna": "external"
},
"mikrotik routerboard ldf 5nd": {
- "maxpower": "25",
+ "maxpower": 25,
"antenna": "external"
},
"mikrotik routerboard ldf-2nd": {
- "maxpower": "28",
+ "maxpower": 28,
"antenna": "external"
},
"mikrotik routerboard ldf 2nd": {
- "maxpower": "28",
+ "maxpower": 28,
"antenna": "external"
},
"mikrotik ldf 5 ac (rbldfg-5acd)": {
- "maxpower": "25",
+ "maxpower": 25,
"bandwidths": [ 10, 20 ],
"antenna": "external"
},
"mikrotik routerboard rbldf-5nd": {
- "maxpower": "25",
+ "maxpower": 25,
"antenna": "external"
},
"mikrotik routerboard rbldf-2nd": {
- "maxpower": "28",
+ "maxpower": 28,
"antenna": "external"
},
"mikrotik routerboard lhg 5nd": {
- "maxpower": "25",
+ "maxpower": 25,
"antenna": {
"description": "24.5 dBi 7° Dish",
"gain": 24.5,
@@ -474,7 +474,7 @@
}
},
"mikrotik routerboard rblhg-5nd": {
- "maxpower": "25",
+ "maxpower": 25,
"antenna": {
"description": "24.5 dBi 7° Dish",
"gain": 24.5,
@@ -482,7 +482,7 @@
}
},
"mikrotik routerboard lhg 2nd": {
- "maxpower": "28",
+ "maxpower": 28,
"antenna": {
"description": "18 dBi 18° Dish",
"gain": 18,
@@ -490,7 +490,7 @@
}
},
"mikrotik routerboard rblhg 2nd": {
- "maxpower": "28",
+ "maxpower": 28,
"antenna": {
"description": "18 dBi 18° Dish",
"gain": 18,
@@ -498,7 +498,7 @@
}
},
"mikrotik routerboard rblhg-2nd": {
- "maxpower": "28",
+ "maxpower": 28,
"antenna": {
"description": "18 dBi 18° Dish",
"gain": 18,
@@ -506,7 +506,7 @@
}
},
"mikrotik routerboard lhg 2nd-xl": {
- "maxpower": "25",
+ "maxpower": 25,
"antenna": {
"description": "21 dBi 18° Dish",
"gain": 21,
@@ -514,7 +514,7 @@
}
},
"mikrotik routerboard rblhg 2nd-xl": {
- "maxpower": "25",
+ "maxpower": 25,
"antenna": {
"description": "21 dBi 18° Dish",
"gain": 21,
@@ -522,7 +522,7 @@
}
},
"mikrotik routerboard rblhg-2nd-xl": {
- "maxpower": "25",
+ "maxpower": 25,
"antenna": {
"description": "21 dBi 18° Dish",
"gain": 21,
@@ -530,7 +530,7 @@
}
},
"mikrotik routerboard lhg 5hpnd": {
- "maxpower": "28",
+ "maxpower": 28,
"antenna": {
"description": "24.5 dBi 10.4° Dish",
"gain": 24.5,
@@ -538,7 +538,7 @@
}
},
"mikrotik routerboard rblhg-5hpnd": {
- "maxpower": "28",
+ "maxpower": 28,
"antenna": {
"description": "24.5 dBi 10.4° Dish",
"gain": 24.5,
@@ -546,7 +546,7 @@
}
},
"mikrotik routerboard lhg 5hpnd-xl": {
- "maxpower": "28",
+ "maxpower": 28,
"antenna": {
"description": "27 dBi 6.4° Dish",
"gain": 27,
@@ -554,7 +554,7 @@
}
},
"mikrotik lhg 5 ac (rblhgg-5acd)": {
- "maxpower": "25",
+ "maxpower": 25,
"bandwidths": [ 10, 20 ],
"antenna": {
"description": "24.5 dBi 7° Dish",
@@ -563,7 +563,7 @@
}
},
"mikrotik lhg 5 ac xl (rblhgg-5acd-xl)": {
- "maxpower": "25",
+ "maxpower": 25,
"bandwidths": [ 10, 20 ],
"antenna": {
"description": "27 dBi 7° Dish",
@@ -572,7 +572,7 @@
}
},
"mikrotik routerboard sxtsq 5nd": {
- "maxpower": "25",
+ "maxpower": 25,
"antenna": {
"description": "16 dBi 23° Panel",
"gain": 16,
@@ -580,7 +580,7 @@
}
},
"mikrotik routerboard rbsxtsq5nd": {
- "maxpower": "25",
+ "maxpower": 25,
"antenna": {
"description": "16 dBi 23° Panel",
"gain": 16,
@@ -588,7 +588,7 @@
}
},
"mikrotik routerboard sxtsq 2nd": {
- "maxpower": "30",
+ "maxpower": 30,
"antenna": {
"description": "10 dBi 60° Panel",
"gain": 10,
@@ -596,7 +596,7 @@
}
},
"mikrotik routerboard rbsxtsq2nd": {
- "maxpower": "30",
+ "maxpower": 30,
"antenna": {
"description": "10 dBi 60° Panel",
"gain": 10,
@@ -604,7 +604,7 @@
}
},
"mikrotik routerboard sxt 2nd (sxt lite2)": {
- "maxpower": "30",
+ "maxpower": 30,
"antenna": {
"description": "10 dBi 60° Panel",
"gain": 10,
@@ -612,7 +612,7 @@
}
},
"mikrotik routerboard sxtsq 5hpnd": {
- "maxpower": "28",
+ "maxpower": 28,
"antenna": {
"description": "16 dBi 23° Panel",
"gain": 16,
@@ -620,7 +620,7 @@
}
},
"mikrotik routerboard rbsxtsq5hpnd": {
- "maxpower": "28",
+ "maxpower": 28,
"antenna": {
"description": "16 dBi 23° Panel",
"gain": 16,
@@ -628,7 +628,7 @@
}
},
"mikrotik routerboard sxt 5nd (sxt lite5)": {
- "maxpower": "25",
+ "maxpower": 25,
"antenna": {
"description": "16 dBi 23° Panel",
"gain": 16,
@@ -636,7 +636,7 @@
}
},
"mikrotik routerboard sxt 5hpnd (sxt 5 high power)": {
- "maxpower": "28",
+ "maxpower": 28,
"antenna": {
"description": "16 dBi 23° Panel",
"gain": 16,
@@ -644,7 +644,7 @@
}
},
"mikrotik sxtsq 5 ac (rbsxtsqg-5acd)": {
- "maxpower": "25",
+ "maxpower": 25,
"bandwidths": [ 10, 20 ],
"antenna": {
"description": "16 dBi 23° Panel",
@@ -694,8 +694,8 @@
},
"0xe005": {
"name": "Ubiquiti NanoStation M5",
- "maxpower": "22",
- "pwroffset": "5",
+ "maxpower": 22,
+ "pwroffset": 5,
"antenna": {
"description": "16 dBi 43° Panel",
"gain": 16,
@@ -705,8 +705,8 @@
},
"0xe009": {
"name": "Ubiquiti NanoStation Loco M9",
- "maxpower": "22",
- "pwroffset": "6",
+ "maxpower": 22,
+ "pwroffset": 6,
"antenna": {
"description": "8 dBi 60° Panel",
"gain": 8,
@@ -715,8 +715,8 @@
},
"0xe012": {
"name": "Ubiquiti NanoStation M2",
- "maxpower": "18",
- "pwroffset": "10",
+ "maxpower": 18,
+ "pwroffset": 10,
"antenna": {
"description": "11 dBi 55° Panel",
"gain": 11,
@@ -725,8 +725,8 @@
},
"0xe035": {
"name": "Ubiquiti NanoStation M3",
- "maxpower": "22",
- "pwroffset": "3",
+ "maxpower": 22,
+ "pwroffset": 3,
"antenna": {
"description": "13 dBi 60° Panel",
"gain": 13,
@@ -735,8 +735,8 @@
},
"0xe0a2": {
"name": "Ubiquiti NanoStation Loco M2",
- "maxpower": "18",
- "pwroffset": "5",
+ "maxpower": 18,
+ "pwroffset": 5,
"antenna": {
"description": "8.5 dBi 60° Panel",
"gain": 8.5,
@@ -745,8 +745,8 @@
},
"0xe0a5": {
"name": "Ubiquiti NanoStation Loco M5",
- "maxpower": "22",
- "pwroffset": "1",
+ "maxpower": 22,
+ "pwroffset": 1,
"antenna": {
"description": "13 dBi 45° Panel",
"gain": 13,
@@ -755,164 +755,164 @@
},
"0xe105": {
"name": "Ubiquiti Rocket M5",
- "maxpower": "22",
- "pwroffset": "5",
+ "maxpower": 22,
+ "pwroffset": 5,
"antenna": "external"
},
"0xe112": {
"name": "Ubiquiti Rocket M2 with USB",
- "maxpower": "18",
- "pwroffset": "10",
+ "maxpower": 18,
+ "pwroffset": 10,
"antenna": "external"
},
"0xe1b2": {
"name": "Ubiquiti Rocket M2",
- "maxpower": "18",
- "pwroffset": "10",
+ "maxpower": 18,
+ "pwroffset": 10,
"antenna": "external"
},
"0xe1b5": {
"name": "Ubiquiti Rocket M5",
- "maxpower": "22",
- "pwroffset": "5",
+ "maxpower": 22,
+ "pwroffset": 5,
"antenna": "external"
},
"0xe1b9": {
"name": "Ubiquiti Rocket M9",
- "maxpower": "22",
- "pwroffset": "6",
+ "maxpower": 22,
+ "pwroffset": 6,
"antenna": "external"
},
"0xe1c3": {
"name": "Ubiquiti Rocket M3",
- "maxpower": "22",
- "pwroffset": "3",
+ "maxpower": 22,
+ "pwroffset": 3,
"antenna": "external"
},
"0xe1c5": {
"name": "Ubiquiti Rocket M5 GPS",
- "maxpower": "22",
- "pwroffset": "5",
+ "maxpower": 22,
+ "pwroffset": 5,
"antenna": "external"
},
"0xe1d2": {
"name": "Ubiquiti Rocket M2 Titanium",
- "maxpower": "18",
- "pwroffset": "10",
+ "maxpower": 18,
+ "pwroffset": 10,
"antenna": "external"
},
"0xe1d5": {
"name": "Ubiquiti Rocket M5 Titanium GPS",
- "maxpower": "22",
- "pwroffset": "5",
+ "maxpower": 22,
+ "pwroffset": 5,
"antenna": "external"
},
"0xe1f5": {
"name": "Ubiquiti Rocket 5AC Lite",
- "maxpower": "23",
- "pwroffset": "4",
+ "maxpower": 23,
+ "pwroffset": 4,
"antenna": "external"
},
"0xe202": {
"name": "Ubiquiti Bullet M2 HP",
- "maxpower": "16",
- "pwroffset": "12",
+ "maxpower": 16,
+ "pwroffset": 12,
"antenna": "external"
},
"0xe205": {
"name": "Ubiquiti Bullet M5",
- "maxpower": "19",
- "pwroffset": "6",
+ "maxpower": 19,
+ "pwroffset": 6,
"antenna": "external"
},
"0xe212": {
"name": "Ubiquiti airGrid M2",
- "maxpower": "28"
+ "maxpower": 28
},
"0xe215": {
"name": "Ubiquiti airGrid M5",
- "maxpower": "19",
- "pwroffset": "1"
+ "maxpower": 19,
+ "pwroffset": 1
},
"0xe232": {
"name": "Ubiquiti NanoBridge M2",
- "maxpower": "21",
- "pwroffset": "2"
+ "maxpower": 21,
+ "pwroffset": 2
},
"0xe235": {
"name": "Ubiquiti NanoBridge M5",
- "maxpower": "22",
- "pwroffset": "1",
+ "maxpower": 22,
+ "pwroffset": 1,
"antenna": "external"
},
"0xe239": {
"name": "Ubiquiti NanoBridge M9",
- "maxpower": "22",
- "pwroffset": "6"
+ "maxpower": 22,
+ "pwroffset": 6
},
"0xe242": {
"name": "airGrid M2 HP",
- "maxpower": "19",
- "pwroffset": "9"
+ "maxpower": 19,
+ "pwroffset": 9
},
"0xe243": {
"name": "Ubiquiti NanoBridge M3",
- "maxpower": "22",
- "pwroffset": "3"
+ "maxpower": 22,
+ "pwroffset": 3
},
"0xe252": {
"name": "Ubiquiti airGrid M2 HP",
- "maxpower": "19",
- "pwroffset": "9"
+ "maxpower": 19,
+ "pwroffset": 9
},
"0xe245": {
"name": "Ubiquiti airGrid M5 HP",
- "maxpower": "19",
- "pwroffset": "6"
+ "maxpower": 19,
+ "pwroffset": 6
},
"0xe255": {
"name": "Ubiquiti airGrid M5 HP",
- "maxpower": "19",
- "pwroffset": "6"
+ "maxpower": 19,
+ "pwroffset": 6
},
"0xe2b5": {
"name": "Ubiquiti NanoBridge M5 (XM)",
- "maxpower": "22",
- "pwroffset": "1",
+ "maxpower": 22,
+ "pwroffset": 1,
"antenna": "external"
},
"0xe2c2": {
"name": "Ubiquiti NanoBeam M2 International",
- "maxpower": "18",
- "pwroffset": "10"
+ "maxpower": 18,
+ "pwroffset": 10
},
"0xe2c4": {
"name": "Ubiquiti Bullet M2 XW",
- "maxpower": "19",
- "pwroffset": "6",
+ "maxpower": 19,
+ "pwroffset": 6,
"antenna": "external"
},
"0xe2d2": {
"name": "Ubiquiti Bullet M2 Titanium HP",
- "maxpower": "16",
- "pwroffset": "12",
+ "maxpower": 16,
+ "pwroffset": 12,
"antenna": "external"
},
"0xe2d5": {
"name": "Ubiquiti Bullet M5 Titanium",
- "maxpower": "19",
- "pwroffset": "6",
+ "maxpower": 19,
+ "pwroffset": 6,
"antenna": "external"
},
"0xe302": {
"name": "Ubiquiti PicoStation M2",
- "maxpower": "16",
- "pwroffset": "12"
+ "maxpower": 16,
+ "pwroffset": 12
},
"0xe3d5": {
"name": "Ubiquiti PowerBeam 5AC 500",
- "maxpower": "23",
- "pwroffset": "1",
+ "maxpower": 23,
+ "pwroffset": 1,
"antenna": {
"description": "27 dBi 10° Dish",
"gain": 27,
@@ -921,8 +921,8 @@
},
"0xe3d6": {
"name": "Ubiquiti PowerBeam 5AC 400",
- "maxpower": "23",
- "pwroffset": "1",
+ "maxpower": 23,
+ "pwroffset": 1,
"antenna": {
"description": "25 dBi 10° Dish",
"gain": 25,
@@ -931,8 +931,8 @@
},
"0xe3d7": {
"name": "Ubiquiti NanoBeam AC Gen2 (XC)",
- "maxpower": "23",
- "pwroffset": "1",
+ "maxpower": 23,
+ "pwroffset": 1,
"antenna": {
"description": "19 dBi 30° Panel",
"gain": 19,
@@ -941,8 +941,8 @@
},
"0xe7f5": {
"name": "Ubiquiti PowerBeam 5AC 400",
- "maxpower": "23",
- "pwroffset": "1",
+ "maxpower": 23,
+ "pwroffset": 1,
"antenna": {
"description": "25 dBi 10° Dish",
"gain": 25,
@@ -951,8 +951,8 @@
},
"0xe3e5": {
"name": "Ubiquiti PowerBeam M5 XW 300",
- "maxpower": "22",
- "pwroffset": "4",
+ "maxpower": 22,
+ "pwroffset": 4,
"antenna": {
"description": "22 dBi 12° Dish",
"gain": 22,
@@ -961,25 +961,25 @@
},
"0xe4a2": {
"name": "Ubiquiti AirRouter",
- "maxpower": "19",
- "pwroffset": "1"
+ "maxpower": 19,
+ "pwroffset": 1
},
"0xe4b2": {
"name": "Ubiquiti AirRouter HP",
- "maxpower": "19",
- "pwroffset": "9",
+ "maxpower": 19,
+ "pwroffset": 9,
"antenna": "external"
},
"0xe4d5": {
"name": "Ubiquiti Rocket M5 Titanium",
- "maxpower": "22",
- "pwroffset": "5",
+ "maxpower": 22,
+ "pwroffset": 5,
"antenna": "external"
},
"0xe4e5": {
"name": "Ubiquiti PowerBeam M5 400",
- "maxpower": "22",
- "pwroffset": "4",
+ "maxpower": 22,
+ "pwroffset": 4,
"antenna": {
"description": "25 dBi 10° Dish",
"gain": 25,
@@ -1001,8 +1001,8 @@
},
"0xe4f5": {
"name": "Ubiquiti NanoBeam AC (XC)",
- "maxpower": "23",
- "pwroffset": "3",
+ "maxpower": 23,
+ "pwroffset": 3,
"antenna": {
"description": "19 dBi 30° Panel",
"gain": 19,
@@ -1011,8 +1011,8 @@
},
"0xe5e5": {
"name": "Ubiquiti PowerBeam M5 300-ISO",
- "maxpower": "22",
- "pwroffset": "4",
+ "maxpower": 22,
+ "pwroffset": 4,
"antenna": {
"description": "22 dBi 12° Dish",
"gain": 22,
@@ -1021,8 +1021,8 @@
},
"0xe5f5": {
"name": "Ubiquiti PowerBeam 5AC 620",
- "maxpower": "23",
- "pwroffset": "1",
+ "maxpower": 23,
+ "pwroffset": 1,
"antenna": {
"description": "29 dBi 6° Dish",
"gain": 29,
@@ -1031,8 +1031,8 @@
},
"0xe6e5": {
"name": "Ubiquiti PowerBeam M5 400-ISO",
- "maxpower": "22",
- "pwroffset": "4",
+ "maxpower": 22,
+ "pwroffset": 4,
"antenna": {
"description": "25 dBi 8° Dish",
"gain": 25,
@@ -1041,7 +1041,7 @@
},
"0xe7fb": {
"name": "Ubiquiti NanoStation AC (WA)",
- "maxpower": "27",
+ "maxpower": 27,
"antenna": {
"description": "16 dBi 43° Dish",
"gain": 16,
@@ -1067,8 +1067,8 @@
},
"0xe7f9": {
"name": "Ubiquiti LiteBeam 5AC Gen2",
- "maxpower": "24",
- "pwroffset": "1",
+ "maxpower": 24,
+ "pwroffset": 1,
"antenna": {
"description": "23 dBi 15° Dish",
"gain": 23,
@@ -1077,8 +1077,8 @@
},
"0xe7fe": {
"name": "Ubiquiti LiteBeam 5AC LR",
- "maxpower": "24",
- "pwroffset": "1",
+ "maxpower": 24,
+ "pwroffset": 1,
"antenna": {
"description": "26 dBi 7° Dish",
"gain": 26,
@@ -1087,8 +1087,8 @@
},
"0xe8f5": {
"name": "Ubiquiti LiteBeam 5AC Gen2",
- "maxpower": "24",
- "pwroffset": "1",
+ "maxpower": 24,
+ "pwroffset": 1,
"antenna": {
"description": "23 dBi 15° Dish",
"gain": 23,
@@ -1097,8 +1097,8 @@
},
"0xe8e5": {
"name": "Ubiquiti LiteAP 5AC",
- "maxpower": "25",
- "pwroffset": "1",
+ "maxpower": 25,
+ "pwroffset": 1,
"antenna": {
"description": "16 dBi 120° Sector",
"gain": 16,
@@ -1107,8 +1107,8 @@
},
"0xe805": {
"name": "Ubiquiti NanoStation M5",
- "maxpower": "22",
- "pwroffset": "5",
+ "maxpower": 22,
+ "pwroffset": 5,
"antenna": {
"description": "16 dBi 43° Panel",
"gain": 16,
@@ -1117,8 +1117,8 @@
},
"0xe825": {
"name": "Ubiquiti NanoBeam M5 19",
- "maxpower": "22",
- "pwroffset": "4",
+ "maxpower": 22,
+ "pwroffset": 4,
"antenna": {
"description": "19 dBi 30° Panel",
"gain": 19,
@@ -1127,13 +1127,13 @@
},
"0xe835": {
"name": "Ubiquiti AirGrid M5 XW",
- "maxpower": "19",
- "pwroffset": "6"
+ "maxpower": 19,
+ "pwroffset": 6
},
"0xe845": {
"name": "Ubiquiti NanoStation Loco M5 XW",
- "maxpower": "22",
- "pwroffset": "1",
+ "maxpower": 22,
+ "pwroffset": 1,
"antenna": {
"description": "13 dBi 45° Panel",
"gain": 13,
@@ -1142,8 +1142,8 @@
},
"0xe855": {
"name": "Ubiquiti NanoStation M5 XW",
- "maxpower": "22",
- "pwroffset": "5",
+ "maxpower": 22,
+ "pwroffset": 5,
"antenna": {
"description": "16 dBi 43° Panel",
"gain": 16,
@@ -1153,8 +1153,8 @@
},
"0xe865": {
"name": "Ubiquiti LiteBeam M5",
- "maxpower": "19",
- "pwroffset": "6",
+ "maxpower": 19,
+ "pwroffset": 6,
"antenna": {
"description": "23 dBi 15° Panel",
"gain": 23,
@@ -1163,8 +1163,8 @@
},
"0xe866": {
"name": "Ubiquiti NanoStation M2 XW",
- "maxpower": "22",
- "pwroffset": "6",
+ "maxpower": 22,
+ "pwroffset": 6,
"antenna": {
"description": "11 dBi 55° Panel",
"gain": 11,
@@ -1173,8 +1173,8 @@
},
"0xe867": {
"name": "Ubiquiti NanoStation Loco M2 XW",
- "maxpower": "21",
- "pwroffset": "2",
+ "maxpower": 21,
+ "pwroffset": 2,
"antenna": {
"description": "8.5 dBi 60° Panel",
"gain": 8.5,
@@ -1183,14 +1183,14 @@
},
"0xe868": {
"name": "Ubiquiti Rocket M2 XW",
- "maxpower": "21",
- "pwroffset": "7",
+ "maxpower": 21,
+ "pwroffset": 7,
"antenna": "external"
},
"0xe885": {
"name": "Ubiquiti PowerBeam M5 620 XW",
- "maxpower": "22",
- "pwroffset": "4",
+ "maxpower": 22,
+ "pwroffset": 4,
"antenna": {
"description": "29 dBi 6° Dish",
"gain": 29,
@@ -1199,8 +1199,8 @@
},
"0xe8a5": {
"name": "Ubiquiti NanoStation Loco M5",
- "maxpower": "22",
- "pwroffset": "1",
+ "maxpower": 22,
+ "pwroffset": 1,
"antenna": {
"description": "13 dBi 45° Panel",
"gain": 13,
@@ -1209,14 +1209,14 @@
},
"0xe6b5": {
"name": "Ubiquiti Rocket M5 XW",
- "maxpower": "22",
- "pwroffset": "5",
+ "maxpower": 22,
+ "pwroffset": 5,
"antenna": "external"
},
"0xe812": {
"name": "Ubiquiti NanoBeam M2 13",
- "maxpower": "22",
- "pwroffset": "6",
+ "maxpower": 22,
+ "pwroffset": 6,
"antenna": {
"description": "13 dBi 45° Panel",
"gain": 13,
@@ -1225,8 +1225,8 @@
},
"0xe815": {
"name": "Ubiquiti NanoBeam M5 16",
- "maxpower": "22",
- "pwroffset": "4",
+ "maxpower": 22,
+ "pwroffset": 4,
"antenna": {
"description": "16 dBi 30° Panel",
"gain": 16,
@@ -1235,8 +1235,8 @@
},
"0xe1a5": {
"name": "Ubiquiti PowerBridge M5",
- "maxpower": "22",
- "pwroffset": "5",
+ "maxpower": 22,
+ "pwroffset": 5,
"antenna": {
"description": "25 dBi 6° Panel",
"gain": 25,
diff --git a/files/etc/uci-defaults/10_dmz_mode_migrate b/files/etc/uci-defaults/10_dmz_mode_migrate
deleted file mode 100644
index f8ab8451..00000000
--- a/files/etc/uci-defaults/10_dmz_mode_migrate
+++ /dev/null
@@ -1,11 +0,0 @@
-#! /bin/sh
-if [ -e /etc/config/dmz-mode ] ; then
- /sbin/uci -q -c /etc/config.mesh add aredn dmz
- /sbin/uci -q -c /etc/config.mesh set aredn.@dmz[0].mode=$(cat /etc/config/dmz-mode)
- /sbin/uci -q -c /etc/config.mesh commit aredn
- rm -f /etc/config/dmz-mode
-elif [ "$(/sbin/uci -q -c /etc/config.mesh get aredn.@dmz[0].mode)" = "" ]; then
- /sbin/uci -q -c /etc/config.mesh add aredn dmz
- /sbin/uci -q -c /etc/config.mesh set aredn.@dmz[0].mode=0
- /sbin/uci -q -c /etc/config.mesh commit aredn
-fi
diff --git a/files/etc/uci-defaults/45_aredn_reset_paths b/files/etc/uci-defaults/45_aredn_reset_paths
deleted file mode 100755
index d95dbd6c..00000000
--- a/files/etc/uci-defaults/45_aredn_reset_paths
+++ /dev/null
@@ -1,66 +0,0 @@
-#!/bin/sh
-
-#check for modified path values and update if needed
-#will not change existing custom entries
-DISTRIB_TARGET=$(grep DISTRIB_TARGET /etc/openwrt_release|cut -d'=' -f2|tr -d "'")
-DISTRIB_RELEASE=$(grep DISTRIB_RELEASE /etc/openwrt_release|cut -d'=' -f2|tr -d "'")
-DISTRIB_ARCH=$(grep DISTRIB_ARCH /etc/openwrt_release|cut -d'=' -f2|tr -d "'")
-SERVER_PREFIX='http://downloads.arednmesh.org/'
-SNAPSHOT_PREFIX='snapshots/'
-
-getReleasePrefix()
-{
- local PREFIX=""
- ccount=`expr "${DISTRIB_RELEASE}" : '[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*'`
- if [ "$ccount" -ne 0 ]; then
- v1=$(echo ${DISTRIB_RELEASE} | cut -d'.' -f1)
- v2=$(echo ${DISTRIB_RELEASE} | cut -d'.' -f2)
- PREFIX="releases/${v1}/${v2}/${DISTRIB_RELEASE}/"
- else
- PREFIX="${SNAPSHOT_PREFIX}"
- fi
- echo $PREFIX
-}
-
-
-defaultPackageRepos()
-{
- repo=$1
-
- if [ "$repo" == "core" ]; then
- echo "${SERVER_PREFIX}${BUILD_PREFIX}targets/${DISTRIB_TARGET}/packages"
- else
- echo "${SERVER_PREFIX}${BUILD_PREFIX}packages/${DISTRIB_ARCH}/${repo}"
- fi
-}
-
-checkPath()
-{
- uciopt=$1
- repo=$2
- uci_val=$(/sbin/uci -c /etc/config.mesh get "aredn.@downloads[0].${uciopt}")
- default_val=$(eval defaultPackageRepos "${repo}")
-
- # is the current value different than the default?
- if [ "${uci_val}" != "${default_val}" ]; then
- # does the non-standard value START with downloads.arednmesh.org? if so, change it, if NOT, leave it.
- count=$(expr "${uci_val}" : "http:\\/\\/downloads.arednmesh.org\\/.*")
- if [ "${count}" -gt 0 ]; then #starts with default server
- /sbin/uci set "aredn.@downloads[0].${uciopt}=${default_val}"
- fi
- fi
-}
-
-BUILD_PREFIX=$(eval getReleasePrefix)
-
-# set
-checkPath pkgs_core core
-checkPath pkgs_base base
-checkPath pkgs_arednpackages arednpackages
-checkPath pkgs_luci luci
-checkPath pkgs_packages packages
-checkPath pkgs_routing routing
-checkPath pkgs_telephony telephony
-
-/sbin/uci commit aredn
-cp /etc/config/aredn /etc/config.mesh
diff --git a/files/etc/uci-defaults/80_aredn_lqm b/files/etc/uci-defaults/80_aredn_lqm
deleted file mode 100755
index 849557d6..00000000
--- a/files/etc/uci-defaults/80_aredn_lqm
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/sh
-for c in /etc/config /etc/config.mesh
-do
- if [ "$(/sbin/uci -c $c -q get aredn.@lqm[0].enable)" = "" ]; then
- /sbin/uci -c $c -m import aredn <<__EOF__
- config lqm
- option enable '0'
- option min_snr '15'
- option margin_snr '1'
- option rts_threshold '1'
- option min_distance '0'
- option max_distance '80467'
- option min_quality '50'
- option ping_penalty '5'
- option margin_quality '1'
-__EOF__
- /sbin/uci -c $c commit aredn
- fi
-done
-
-if [ "$(/sbin/uci -q get aredn.@lqm[0].min_quality)" = "50" ]; then
- /sbin/uci -q set aredn.@lqm[0].min_quality=35
- /sbin/uci commit aredn
- /sbin/uci -q -c /etc/config.mesh set aredn.@lqm[0].min_quality=35
- /sbin/uci -c /etc/config.mesh commit aredn
-fi
diff --git a/files/etc/uci-defaults/81_aredn_migrate_wansettings b/files/etc/uci-defaults/81_aredn_migrate_wansettings
deleted file mode 100755
index ab12e159..00000000
--- a/files/etc/uci-defaults/81_aredn_migrate_wansettings
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/sh
-
-noroute=$(grep "lan_dhcp_noroute" /etc/config.mesh/_setup | sed s/^lan_dhcp_noroute\ =\ //)
-olsrd_gw=$(grep "olsrd_gw" /etc/config.mesh/_setup | sed s/^olsrd_gw\ =\ //)
-
-if [ "$(/sbin/uci -c /etc/config.mesh -q get aredn.@wan[0])" != "wan" ]; then
- /sbin/uci -c /etc/config.mesh -q add aredn wan
-fi
-
-if [ "${noroute}" != "" ]; then
- if [ "${noroute}" = "0" ]; then
- /sbin/uci -c /etc/config.mesh set aredn.@wan[0].lan_dhcp_route=1
- else
- /sbin/uci -c /etc/config.mesh set aredn.@wan[0].lan_dhcp_route=0
- fi
- /sbin/uci -c /etc/config.mesh set aredn.@wan[0].lan_dhcp_defaultroute=0
- /sbin/uci -c /etc/config.mesh commit aredn
- sed -i /^lan_dhcp_noroute\ =/d /etc/config.mesh/_setup
-elif [ "$(/sbin/uci -c /etc/config.mesh -q get aredn.@wan[0].lan_dhcp_route)" = "" ]; then
- /sbin/uci -c /etc/config.mesh set aredn.@wan[0].lan_dhcp_route=1
- /sbin/uci -c /etc/config.mesh set aredn.@wan[0].lan_dhcp_defaultroute=0
- /sbin/uci -c /etc/config.mesh commit aredn
-fi
-
-if [ "${olsrd_gw}" != "" ]; then
- /sbin/uci -c /etc/config.mesh set aredn.@wan[0].olsrd_gw=${olsrd_gw}
- /sbin/uci -c /etc/config.mesh commit aredn
- sed -i /^olsrd_gw\ =/d /etc/config.mesh/_setup
-fi
diff --git a/files/etc/uci-defaults/82_aredn_ntp_period_migrate b/files/etc/uci-defaults/82_aredn_ntp_period_migrate
deleted file mode 100644
index 40ddb97a..00000000
--- a/files/etc/uci-defaults/82_aredn_ntp_period_migrate
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/sh
-
-# get current value of ntp_period if any
-if [ -e /etc/config.mesh/_setup ] ; then
- period=$(grep "ntp_period" /etc/config.mesh/_setup | sed s/^ntp_period\ =\ //)
-fi
-
-# ensure /etc/config.mesh/aredn has ntp values
-if [ "$(/sbin/uci -c /etc/config.mesh -q get aredn.@ntp[0])" != "ntp" ]; then
- /sbin/uci -c /etc/config.mesh -q add aredn ntp
- if [ -n "${period}" ] ; then
- /sbin/uci -c /etc/config.mesh -q set aredn.@ntp[0].period="${period}"
- else
- /sbin/uci -c /etc/config.mesh -q set aredn.@ntp[0].period="daily"
- fi
- /sbin/uci -c /etc/config.mesh -q commit aredn
-fi
-
diff --git a/files/etc/uci-defaults/90_aredn_default_tunnels b/files/etc/uci-defaults/90_aredn_default_tunnels
deleted file mode 100755
index caa20971..00000000
--- a/files/etc/uci-defaults/90_aredn_default_tunnels
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/sh
-if [ "$(/sbin/uci -c /etc/config.mesh -q get aredn.@tunnel[0])" != "tunnel" ]; then
- /sbin/uci -c /etc/config.mesh -q add aredn tunnel
- /sbin/uci -c /etc/config.mesh -q commit aredn
-fi
-
-# Default tunnel weight to 1 (perfect RF)
-if [ "$(/sbin/uci -c /etc/config.mesh -q get aredn.@tunnel[0].weight)" = "" ]; then
- /sbin/uci -c /etc/config.mesh -q set aredn.@tunnel[0].weight=1
- /sbin/uci -c /etc/config.mesh -q commit aredn
-fi
diff --git a/files/etc/uci-defaults/94_fix_wpad b/files/etc/uci-defaults/94_fix_wpad
new file mode 100755
index 00000000..26973082
--- /dev/null
+++ b/files/etc/uci-defaults/94_fix_wpad
@@ -0,0 +1,6 @@
+#! /bin/sh
+# Disable hostapd (etc) on devices without wifi
+
+if [ ! -d /sys/class/ieee80211 ]; then
+ /etc/init.d/wpad disable
+fi
diff --git a/files/etc/uci-defaults/95_aredn_migrate_leafletjs b/files/etc/uci-defaults/95_aredn_migrate_leafletjs
deleted file mode 100755
index d4af01d4..00000000
--- a/files/etc/uci-defaults/95_aredn_migrate_leafletjs
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-if [ "$(/sbin/uci -c /etc/config.mesh -q get aredn.@map[0].leafletjs)" = "http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js" ]; then
- /sbin/uci -c /etc/config.mesh -q set aredn.@map[0].leafletjs="http://unpkg.com/leaflet@0.7.7/dist/leaflet.js"
- /sbin/uci -c /etc/config.mesh -q set aredn.@map[0].leafletcss="http://unpkg.com/leaflet@0.7.7/dist/leaflet.css"
- /sbin/uci -c /etc/config.mesh -q commit aredn
-fi
diff --git a/files/etc/uci-defaults/95_lowmem_devices b/files/etc/uci-defaults/95_lowmem_devices
new file mode 100755
index 00000000..6679f988
--- /dev/null
+++ b/files/etc/uci-defaults/95_lowmem_devices
@@ -0,0 +1,11 @@
+#!/bin/sh
+# Modify configuration for low memory devices
+
+MEM=$(awk '/MemTotal/ {print $2}' /proc/meminfo)
+if [ $MEM -le 32768 ]; then
+ # Reduce parallel requests
+ /sbin/uci -c /etc/config.mesh -q set uhttpd.main.max_requests=1
+ /sbin/uci -c /etc/config.mesh -q commit uhttpd
+ # Disable wpad
+ /etc/init.d/wpad disable
+fi
diff --git a/files/etc/uci-defaults/96_aredn_migrate_latlon b/files/etc/uci-defaults/96_aredn_migrate_latlon
deleted file mode 100755
index d2504fe0..00000000
--- a/files/etc/uci-defaults/96_aredn_migrate_latlon
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/sh
-if [ "$(/sbin/uci -c /etc/config.mesh -q get aredn.@location[0])" = "" ]; then
- /sbin/uci -c /etc/config.mesh add aredn location
- /sbin/uci -c /etc/config.mesh commit aredn
-fi
-
-# read /etc/latlon
-if [ -f /etc/latlon ]
-then
- LAT=$(head -1 /etc/latlon)
- LON=$(tail -1 /etc/latlon)
- /sbin/uci -c /etc/config.mesh -q set aredn.@location[0].lat="$LAT"
- /sbin/uci -c /etc/config.mesh -q set aredn.@location[0].lon="$LON"
- /sbin/uci -c /etc/config.mesh -q commit aredn
- rm -f /etc/latlon
-fi
-
-if [ -f /etc/gridsquare ]
-then
- GRIDSQUARE=$(head -1 /etc/gridsquare)
- /sbin/uci -c /etc/config.mesh -q set aredn.@location[0].gridsquare="$GRIDSQUARE"
- /sbin/uci -c /etc/config.mesh -q commit aredn
- rm -f /etc/gridsquare
-fi
-
-cp /etc/config.mesh/aredn /etc/config/aredn
diff --git a/files/etc/uci-defaults/97_aredn_setup_meshstatus b/files/etc/uci-defaults/97_aredn_setup_meshstatus
deleted file mode 100644
index a337ceb7..00000000
--- a/files/etc/uci-defaults/97_aredn_setup_meshstatus
+++ /dev/null
@@ -1,5 +0,0 @@
-#! /bin/sh
-if [ "$(/sbin/uci -q get aredn.\@meshstatus[0])" = "" ]; then
- /sbin/uci -q add aredn meshstatus
- /sbin/uci -q commit aredn
-fi
diff --git a/files/etc/uci-defaults/98_change_default_maptiles b/files/etc/uci-defaults/98_change_default_maptiles
deleted file mode 100755
index 1125a15b..00000000
--- a/files/etc/uci-defaults/98_change_default_maptiles
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/sh
-#check for old default map tiles and change to the new map tiles if req'd
-#will not change existing custom entries.
-OLDTILES_1='http://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiazVkbHEiLCJhIjoiY2lqMnlieTM4MDAyNXUwa3A2eHMxdXE3MiJ9.BRFvx4q2vi70z5Uu2zRYQw'
-OLDTILES_2='http://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.jpg'
-NEWTILES='http://tile.openstreetmap.org/{z}/{x}/{y}.png'
-MAPTILESERVER=$(/sbin/uci -c /etc/config.mesh get aredn.@map[0].maptiles)
-if [ "$MAPTILESERVER" = "$OLDTILES_1" -o "$MAPTILESERVER" = "$OLDTILES_2" ]; then
- /sbin/uci -c /etc/config.mesh set aredn.@map[0].maptiles="$NEWTILES"
- /sbin/uci -c /etc/config.mesh commit aredn
-fi
-exit 0
diff --git a/files/etc/uci-defaults/99_setup_aredn_include b/files/etc/uci-defaults/98_setup_aredn_include
similarity index 100%
rename from files/etc/uci-defaults/99_setup_aredn_include
rename to files/etc/uci-defaults/98_setup_aredn_include
diff --git a/files/etc/uci-defaults/99_canonical_config b/files/etc/uci-defaults/99_canonical_config
new file mode 100755
index 00000000..5c9c090b
--- /dev/null
+++ b/files/etc/uci-defaults/99_canonical_config
@@ -0,0 +1,191 @@
+#!/bin/sh
+true <<'LICENSE'
+ Part of AREDN -- Used for creating Amateur Radio Emergency Data Networks
+ Copyright (C) 2024 Tim Wilkinson
+ See Contributors file for additional contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation version 3 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ Additional Terms:
+
+ Additional use restrictions exist on the AREDN(TM) trademark and logo.
+ See AREDNLicense.txt for more info.
+
+ Attributions to the AREDN Project must be retained in the source code.
+ If importing this code into a new or existing project attribution
+ to the AREDN project must be added to the source code.
+
+ You must not misrepresent the origin of the material contained within.
+
+ Modified versions must be modified to attribute to the original source
+ and be marked in reasonable ways as differentiate it from the original
+ version.
+
+LICENSE
+
+cat > /tmp/canonical_config << __EOF__
+
+require("nixio")
+require("uci")
+require("aredn.hardware")
+
+local root = "/etc/config.mesh"
+local DELETE = "__DELETE__"
+
+function shell(cmd)
+ local h = io.popen(cmd)
+ local r = h:read("*a")
+ h:close()
+ return r ~= "" and r:gsub("^%s+", ""):gsub("%s+$", "") or null
+end
+
+function vtun_network()
+ local mac = aredn.hardware.get_interface_mac("eth0")
+ local a, b = mac:match("^..:..:..:..:(..):(..)$")
+ return "172.31." .. "" .. tonumber(b, 16) .. "." .. ((tonumber(a, 16) * 4) % 256)
+end
+
+local config = {
+ aredn = {
+ alerts = {},
+ dmz = {
+ mode = shell("cat /etc/config/dmz-mode 2>/dev/null; rm -f /etc/config/dmz-mode") or 0
+ },
+ downloads = {
+ firmware_aredn = "http://downloads.arednmesh.org",
+ firmwarepath = DELETE,
+ packages_default = "http://downloads.arednmesh.org",
+ pkgs_core = DELETE,
+ pkgs_base = DELETE,
+ pkgs_arednpackages = DELETE,
+ pkgs_packages = DELETE,
+ pkgs_luci = DELETE,
+ pkgs_routing = DELETE,
+ pkgs_telephony = DELETE
+ },
+ iperf = {
+ enable = 1
+ },
+ location = {
+ lat = shell("head -1 /etc/latlon 2>/dev/null"),
+ lon = shell("tail -1 /etc/latlon 2>/dev/null; rm -f /etc/latlon"),
+ gridsquare = shell("head -1 /etc/gridsquare 2>/dev/null ; rm -f /etc/gridsquare"),
+ map = "https://worldmap.arednmesh.org/#12/(lat)/(lon)",
+ gps_enable = 1
+ },
+ lqm = {
+ enable = 1,
+ min_snr = 15,
+ margin_snr = 1,
+ rts_threshold = 1,
+ min_distance = 0,
+ max_distance = 80467,
+ min_quality = 35,
+ ping_penalty = 5,
+ margin_quality = 1,
+ min_routes = 8
+ },
+ map = DELETE,
+ meshstatus = DELETE,
+ notes = {},
+ ntp = {
+ period = "daily",
+ },
+ poe = {},
+ remotelog = {},
+ supernode = {
+ enable = 0,
+ support = 1
+ },
+ time = {
+ ntp_enable = 1,
+ gps_enable = 1
+ },
+ tunnel = {
+ weight = 1
+ },
+ usb = {},
+ wan = {
+ lan_dhcp_route = 1,
+ lan_dhcp_defaultroute = 0,
+ olsrd_gw = 0,
+ ssh_access = 1,
+ telnet_access = 1,
+ web_access = 1
+ },
+ watchdog = {
+ enable = 0,
+ }
+ },
+ dhcp = {},
+ dropbear = {},
+ olsrd = {},
+ snmpd = {},
+ uhttpd = {},
+ vtun = {
+ defaults = {},
+ options = {},
+ network = {
+ start = vtun_network()
+ }
+ },
+ wireguard = {},
+ xlink = {}
+}
+
+local cursor = uci.cursor(root)
+for fn, f in pairs(config)
+do
+ if not nixio.fs.stat(root .. "/" .. fn) then
+ io.open(root .. "/" .. fn, "w"):close()
+ else
+ cursor:add(fn, "__dummy__")
+ cursor:delete(fn, "@__dummy__[0]")
+ for k, v in pairs(cursor:get_all(fn))
+ do
+ local c = 0
+ for k1, _ in pairs(v)
+ do
+ if not k1:match("^%.") then
+ c = c + 1
+ end
+ end
+ if c == 0 then
+ cursor:delete(fn, k)
+ end
+ end
+ end
+ for sn, s in pairs(f)
+ do
+ local san = "@" .. sn .. "[0]"
+ if s == DELETE then
+ cursor:delete(fn, san)
+ else
+ if not cursor:get(fn, san) then
+ cursor:add(fn, sn)
+ end
+ for on, o in pairs(s)
+ do
+ if o == DELETE then
+ cursor:delete(fn, san, on)
+ elseif not cursor:get(fn, san, on) then
+ cursor:set(fn, san, on, o)
+ end
+ end
+ end
+ end
+ cursor:commit(fn)
+end
+__EOF__
+/usr/bin/lua /tmp/canonical_config
+rm -f /tmp/canonical_config
diff --git a/files/usr/lib/lua/aredn/hardware.lua b/files/usr/lib/lua/aredn/hardware.lua
index bbe7ddf9..6d5dc6c1 100644
--- a/files/usr/lib/lua/aredn/hardware.lua
+++ b/files/usr/lib/lua/aredn/hardware.lua
@@ -494,6 +494,105 @@ function hardware.get_interface_mac(intf)
return mac
end
+local gpsd = "/usr/sbin/gpsd";
+local gps_ttys = {
+ "/dev/ttyACM0",
+ "/dev/ttyUSB0"
+}
+
+function hardware.gps_find()
+ if nixio.fs.stat(gpsd) then
+ for _, tty in ipairs(gps_ttys)
+ do
+ if nixio.fs.stat(tty) then
+ return tty
+ end
+ end
+ end
+ local l = io.open("/tmp/lqm.info")
+ if l then
+ local lqm = luci.jsonc.parse(l:read("*a"))
+ l:close()
+ for _, tracker in pairs(lqm.trackers)
+ do
+ if tracker.type == "DtD" and tracker.ip then
+ local s = nixio.socket("inet", "stream")
+ s:setopt("socket", "sndtimeo", 1)
+ local r = s:connect(tracker.ip, 2947)
+ s:close()
+ if r then
+ return tracker.ip
+ end
+ end
+ end
+ end
+ return nil
+end
+
+local function gps_open(gps)
+ if gps:match("^/dev/") then
+ gps = "127.0.0.1"
+ end
+ local s = nixio.socket("inet", "stream")
+ local r = s:connect(gps, 2947)
+ if not r then
+ return nil
+ end
+ return s
+end
+
+local function gps_read(s)
+ local l = ""
+ while true
+ do
+ local b = s:read(1)
+ if #b == 0 then
+ return nil
+ elseif b == "\n" then
+ local ok, jsn = pcall(function() return luci.jsonc.parse(l) end)
+ if ok then
+ return jsn
+ end
+ l = ""
+ else
+ l = l .. b
+ end
+ end
+end
+
+local function gps_close(s)
+ s:close()
+end
+
+function hardware.gps_read_llt(gps, maxlines)
+ local info = {
+ lat = nil,
+ lon = nil,
+ time = nil
+ }
+ local s = gps_open(gps)
+ if not s then
+ return nil
+ end
+ s:write('?WATCH={"enable":true,"json":true}\n')
+ if not maxlines then
+ maxlines = 10
+ end
+ while maxlines > 0
+ do
+ local j = gps_read(s)
+ if j.class == "TPV" then
+ info.time = j.time:gsub("T", " "):gsub(".000Z", "")
+ info.lat = j.lat
+ info.lon = j.lon
+ break
+ end
+ maxlines = maxlines - 1
+ end
+ gps_close(s)
+ return info
+end
+
if not aredn then
aredn = {}
end
diff --git a/files/usr/local/bin/mgr/gps.lua b/files/usr/local/bin/mgr/gps.lua
new file mode 100755
index 00000000..0c8a48f1
--- /dev/null
+++ b/files/usr/local/bin/mgr/gps.lua
@@ -0,0 +1,120 @@
+--[[
+
+ Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ Copyright (C) 2024 Tim Wilkinson
+ See Contributors file for additional contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation version 3 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ Additional Terms:
+
+ Additional use restrictions exist on the AREDN® trademark and logo.
+ See AREDNLicense.txt for more info.
+
+ Attributions to the AREDN® Project must be retained in the source code.
+ If importing this code into a new or existing project attribution
+ to the AREDN® project must be added to the source code.
+
+ You must not misrepresent the origin of the material contained within.
+
+ Modified versions must be modified to attribute to the original source
+ and be marked in reasonable ways as differentiate it from the original
+ version
+
+--]]
+
+local app = {}
+local CONFIG0 = "/etc/config.mesh/gpsd"
+local CONFIG1 = "/etc/config/gpsd"
+local CHANGEMARGIN = 0.0001
+
+function app.run()
+
+ wait_for_ticks(60)
+
+ local gps
+ while true
+ do
+ gps = aredn.hardware.gps_find()
+ if gps then
+ break
+ end
+ wait_for_ticks(600) -- 10 minutes
+ end
+
+ -- Create the GPSD daemon if device is local,
+ -- otherwise we get the GPS info from another node on our local network
+ if gps:match("^/dev/") then
+ local f = io.open(CONFIG0, "w")
+ f:write(
+[[config gpsd 'core'
+ option enabled '1'
+ option device ']] .. gps .. [['
+ option port '2947'
+ option listen_globally '1'
+]])
+ f:close()
+ filecopy(CONFIG0, CONFIG1, true)
+ os.execute("nft insert rule ip fw4 input_dtdlink tcp dport 2947 accept comment \"gpsd\" 2> /dev/null")
+ os.execute("/etc/init.d/gpsd restart")
+ end
+
+ while true
+ do
+ local c = uci.cursor()
+ local j = aredn.hardware.gps_read_llt(gps)
+
+ -- Update time and date
+ if c:get("aredn", "@time[0]", "gps_enable") == "1" and j.time then
+ os.execute("/bin/date -u -s '" .. j.time .. "' > /dev/nul 2>&1")
+ write_all("/tmp/timesync", "gps")
+ end
+
+ -- Set location if significantly changed
+ if c:get("aredn", "@location[0]", "gps_enable") == "1" then
+ local clat = tonumber(c:get("aredn", "@location[0]", "lat") or 0)
+ local clon = tonumber(c:get("aredn", "@location[0]", "lon") or 0)
+ if math.abs(clat - j.lat) > CHANGEMARGIN or math.abs(clon - j.lon) > CHANGEMARGIN then
+ -- Calculate gridsquare from lat/lon
+ local alat = j.lat + 90
+ local flat = 65 + math.floor(alat / 10)
+ local slat = math.floor(alat % 10)
+ local ulat = 97 + math.floor((alat - math.floor(alat)) * 60 / 2.5)
+
+ local alon = j.lon + 180
+ local flon = 65 + math.floor(alon / 20)
+ local slon = math.floor((alon / 2) % 10)
+ local ulon = 97 + math.floor((alon - 2 * math.floor(alon / 2)) * 60 / 5)
+
+ local gridsquare = string.format("%c%c%d%d%c%c", flon, flat, slon, slat, ulon, ulat)
+
+ -- Update location information
+ c:set("aredn", "@location[0]", "lat", j.lat)
+ c:set("aredn", "@location[0]", "lon", j.lon)
+ c:set("aredn", "@location[0]", "gridsquare", gridsquare)
+ c:set("aredn", "@location[0]", "source", "gps")
+ c:commit("aredn")
+ local cm = uci.cursor("/etc/config.mesh")
+ cm:set("aredn", "@location[0]", "lat", j.lat)
+ cm:set("aredn", "@location[0]", "lon", j.lon)
+ cm:set("aredn", "@location[0]", "gridsquare", gridsquare)
+ cm:set("aredn", "@location[0]", "source", "gps")
+ cm:commit("aredn")
+ end
+ end
+
+ wait_for_ticks(600) -- 10 minutes
+ end
+end
+
+return app.run
diff --git a/files/usr/local/bin/mgr/lqm.lua b/files/usr/local/bin/mgr/lqm.lua
index 3364c1a9..15c7ee15 100755
--- a/files/usr/local/bin/mgr/lqm.lua
+++ b/files/usr/local/bin/mgr/lqm.lua
@@ -529,6 +529,7 @@ function lqm()
lat = nil,
lon = nil,
distance = nil,
+ localarea = nil,
blocks = {
dtd = false,
signal = false,
@@ -623,8 +624,8 @@ function lqm()
-- Refresh the hostname periodically as it can change
track.hostname = canonical_hostname(nixio.getnameinfo(track.ip)) or track.hostname
- if track.blocked or not track.routable then
- -- Remote is blocked not directly routable
+ if track.blocked then
+ -- Remote is blocked
-- We cannot update so invalidate any information considered stale and set time to attempt refresh
track.refresh = is_pending(track) and 0 or now + refresh_retry_timeout
track.rev_snr = nil
@@ -652,8 +653,17 @@ function lqm()
track.lon = tonumber(info.lon) or track.lon
if track.lat and track.lon and lat and lon then
track.distance = calc_distance(lat, lon, track.lat, track.lon)
+ if track.type == "DtD" and track.distance < dtd_distance then
+ track.localarea = true
+ else
+ track.localarea = false
+ end
end
+ -- Keep some useful info
+ track.model = info.node_details.model
+ track.firmware_version = info.node_details.firmware_version
+
if track.type == "RF" then
rflinks[track.mac] = nil
if info.lqm and info.lqm.enabled and info.lqm.info and info.lqm.info.trackers then
diff --git a/files/usr/local/bin/mgr/snrlog.lua b/files/usr/local/bin/mgr/snrlog.lua
index 9fa226a1..4eb9de29 100644
--- a/files/usr/local/bin/mgr/snrlog.lua
+++ b/files/usr/local/bin/mgr/snrlog.lua
@@ -47,7 +47,6 @@ local AGETIME = 43200
local INACTIVETIMEOUT = 10000
local tmpdir = "/tmp/snrlog"
local lastdat = "/tmp/snr.dat"
-local autolog = "/tmp/AutoDistReset.log"
local defnoise = -95
local cursor = uci.cursor()
@@ -124,9 +123,9 @@ function run_snrlog()
local arp = arpcache[mac]
if arp then
local ip = arp["IP address"]
- local hostname = nslookup(ip)
+ local hostname = nixio.getnameinfo(ip)
if hostname then
- datafile = datafile..hostname:lower()
+ datafile = datafile..hostname:lower():gsub("^dtdlink%.", ""):gsub("^mid%d+%.", ""):gsub("^xlink%d+%.", ""):gsub("%.local%.mesh$", "")
elseif ip then
datafile = datafile..ip
end
@@ -230,12 +229,7 @@ function run_snrlog()
-- trigger auto distancing if necessary
if trigger_auto_distance and cursor:get("aredn", "@lqm[0]", "enable") ~= "1" then
reset_auto_distance()
- file_trim(autolog, MAXLINES)
- f, err = assert(io.open(autolog, "a"),"Cannot open file (autolog) to write!")
- if f then
- f:write(now .. "\n")
- f:close()
- end
+ nixio.syslog("notice", "snrlog: reset_auto_distance, " .. now)
end
end
diff --git a/files/usr/local/bin/node-setup b/files/usr/local/bin/node-setup
index e174a17a..efea100b 100755
--- a/files/usr/local/bin/node-setup
+++ b/files/usr/local/bin/node-setup
@@ -117,7 +117,7 @@ local cfg = {
olsrd_dtd_interface_mode = "ether",
tun_network_config = "",
wireguard_network_config = "",
- dtdlink_interfaces = "list network 'dtdlink'",
+ dtdlink_interfaces = "\tlist network 'dtdlink'",
vpn_interfaces = "",
olsrd_pollrate = "0.05",
tun_devices_config = "",
@@ -137,7 +137,8 @@ local changes = {
firewall = false,
tunnels = false,
wireless = false,
- localservices = false
+ localservices = false,
+ wpad = false
}
function valid_config(config)
@@ -149,6 +150,16 @@ function valid_config(config)
return r == 0 and true or false
end
+function is_nat_mode()
+ return is_null(cfg.dmz_mode)
+end
+function is_dmz_mode()
+ return is_notnull(cfg.dmz_mode) and cfg.dmz_mode ~= "1"
+end
+function is_altnet_mode()
+ return cfg.dmz_mode == "1"
+end
+
function expand_vars(lines)
local nlines = {}
for line in lines:gmatch("([^\n]*\n?)")
@@ -228,7 +239,7 @@ if cfg.wan_proto == "dhcp" then
deleteme.wan_gw = true
deleteme.wan_mask = true
end
-if is_notnull(cfg.dmz_mode) or cfg.wan_proto ~= "disabled" then
+if is_dmz_mode() or is_altnet_mode() or cfg.wan_proto ~= "disabled" then
deleteme.lan_gw = true
end
@@ -285,7 +296,7 @@ if is_null(cfg.dmz_mode) then
end
-- switch to dmz values if needed
-if is_notnull(cfg.dmz_mode) then
+if is_dmz_mode() then
cfg.lan_ip = cfg.dmz_lan_ip
cfg.lan_mask = cfg.dmz_lan_mask
cfg.dhcp_start = cfg.dmz_dhcp_start
@@ -300,7 +311,7 @@ local dhcptagsfile = "/etc/config.mesh/_setup.dhcptags"
local dhcpoptionsfile = "/etc/config.mesh/_setup.dhcpoptions"
local aliasfile = "/etc/config.mesh/aliases"
local servfile = "/etc/config.mesh/_setup.services"
-if is_null(cfg.dmz_mode) then
+if is_nat_mode() then
portfile = portfile .. ".nat"
dhcpfile = dhcpfile .. ".nat"
dhcptagsfile = dhcptagsfile .. ".nat"
@@ -529,12 +540,23 @@ if tun_start or tun_dns then
end
cfg.tun_network_config = cfg.tun_network_config .. "\n"
end
+local def_tun_weight = tonumber(cm:get("aredn", "@tunnel[0]", "weight") or 1) or 0
+local is_supernode = cm:get("aredn", "@supernode[0]", "enable") == "1"
+if is_supernode then
+ def_tun_weight = 0
+end
+local tun_weights = {}
local vtunclients = 0
cm:foreach("vtun", "client",
function(s)
if s.enabled == "1" then
cfg.tun_network_config = cfg.tun_network_config .. string.format("config client\n\toption enabled '1'\n\toption node '%s'\n\toption passwd '%s'\n\toption clientip '%s'\n\toption serverip '%s'\n\toption netip '%s'\n\n",
s.node:upper(), s.passwd, s.clientip, s.serverip, s.netip)
+ local w = s.weight or def_tun_weight
+ if not tun_weights[w] then
+ tun_weights[w] = {}
+ end
+ table.insert(tun_weights[w], string.format("tun%d", 50 + vtunclients))
vtunclients = vtunclients + 1
end
end
@@ -551,12 +573,18 @@ cm:foreach("wireguard", "client",
cfg.wireguard_network_config = cfg.wireguard_network_config ..
string.format("config wireguard_wgc%d\n\toption public_key '%s'\n\toption persistent_keepalive '25'\n\tlist allowed_ips '0.0.0.0/0'\n\n",
wgclients, client_pub)
+ local w = s.weight or def_tun_weight
+ if not tun_weights[w] then
+ tun_weights[w] = {}
+ end
+ table.insert(tun_weights[w], string.format("wgc%d", wgclients))
wgclients = wgclients + 1
end
end
)
local vtunservers = 0
local wgservers = 0
+local vtunclients_roundup = 10 * math.ceil(vtunclients / 10)
cm:foreach("vtun", "server",
function(s)
if s.enabled == "1" then
@@ -570,11 +598,21 @@ cm:foreach("vtun", "server",
cfg.wireguard_network_config = cfg.wireguard_network_config ..
string.format("config wireguard_wgs%d\n\toption public_key '%s'\n\toption endpoint_host '%s'\n\toption endpoint_port '%s'\n\toption persistent_keepalive '25'\n\tlist allowed_ips '0.0.0.0/0'\n\n",
wgservers, server_pub, s.host, p)
+ local w = s.weight or def_tun_weight
+ if not tun_weights[w] then
+ tun_weights[w] = {}
+ end
+ table.insert(tun_weights[w], string.format("wgs%d", wgservers))
wgservers = wgservers + 1
else
cfg.tun_network_config = cfg.tun_network_config ..
string.format("config server\n\toption enabled '1'\n\toption host '%s'\n\toption node '%s'\n\toption passwd '%s'\n\toption clientip '%s'\n\toption serverip '%s'\n\toption netip '%s'\n\n",
s.host, s.node:upper(), s.passwd, s.clientip, s.serverip, s.netip)
+ local w = s.weight or def_tun_weight
+ if not tun_weights[w] then
+ tun_weights[w] = {}
+ end
+ table.insert(tun_weights[w], string.format("tun%d", 50 + vtunclients_roundup + vtunservers))
vtunservers = vtunservers + 1
end
end
@@ -606,15 +644,15 @@ if maxclients + maxservers + wgclients + wgservers > 0 then
vpnzone = true
for i = 50, 50 + maxclients + maxservers - 1
do
- cfg.vpn_interfaces = cfg.vpn_interfaces .. " list network 'tun" .. i .. "'\n"
+ cfg.vpn_interfaces = cfg.vpn_interfaces .. "\tlist network 'tun" .. i .. "'\n"
end
for i = 0, wgclients-1
do
- cfg.vpn_interfaces = cfg.vpn_interfaces .. " list network 'wgc" .. i .. "'\n"
+ cfg.vpn_interfaces = cfg.vpn_interfaces .. "\tlist network 'wgc" .. i .. "'\n"
end
for i = 0, wgservers-1
do
- cfg.vpn_interfaces = cfg.vpn_interfaces .. " list network 'wgs" .. i .. "'\n"
+ cfg.vpn_interfaces = cfg.vpn_interfaces .. "\tlist network 'wgs" .. i .. "'\n"
end
end
@@ -622,7 +660,7 @@ end
if nixio.fs.stat("/etc/config.mesh/xlink") then
uci.cursor("/etc/config.mesh"):foreach("xlink", "interface",
function(section)
- cfg.dtdlink_interfaces = cfg.dtdlink_interfaces .. "\n list network '" .. section[".name"] .. "'"
+ cfg.dtdlink_interfaces = cfg.dtdlink_interfaces .. "\n\tlist network '" .. section[".name"] .. "'"
end
)
end
@@ -652,7 +690,7 @@ local nc = uci.cursor("/tmp/new_config")
-- append to firewall
local fw = io.open("/tmp/new_config/firewall", "a")
if fw then
- if is_notnull(cfg.dmz_mode) then
+ if not is_nat_mode() then
fw:write("\nconfig forwarding\n option src wifi\n option dest lan\n")
fw:write("\nconfig forwarding\n option src dtdlink\n option dest lan\n")
if vpnzone then
@@ -673,7 +711,7 @@ if fw then
do
if not (line:match("^%s*#") or line:match("^%s*$")) then
local dip = line:match("dmz_ip = (%w+)")
- if dip and cfg.dmz_mode ~= 0 then
+ if dip and is_dmz_mode() then
fw:write("\nconfig redirect\n option src wifi\n option proto tcp\n option src_dip " .. cfg.wifi_ip .. "\n option dest_ip " .. dip .. "\n")
fw:write("\nconfig redirect\n option src wifi\n option proto udp\n option src_dip " .. cfg.wifi_ip .. "\n option dest_ip " .. dip .. "\n")
else
@@ -691,7 +729,7 @@ if fw then
if not oport:match("-") then
host = host .. " option dest_port " .. iport .. "\n"
end
- if is_notnull(cfg.dmz_mode) and intf == "both" then
+ if is_dmz_mode() and intf == "both" then
intf = "wan"
end
if intf == "both" then
@@ -701,7 +739,7 @@ if fw then
fw:write("\nconfig redirect\n option src vpn\n option dest lan\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n")
end
fw:write("config redirect\n option src wan\n option dest lan\n " .. match .. " " .. host .. "\n")
- elseif intf == "wifi" and is_null(cfg.dmz_mode) then
+ elseif intf == "wifi" and is_nat_mode() then
fw:write("\nconfig redirect\n option src dtdlink\n option dest lan\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n")
fw:write("\nconfig redirect\n option src wifi\n option dest lan\n " .. match .. " option src_dip " .. cfg.wifi_ip .. "\n " .. host .. "\n")
if vpnzone then
@@ -721,7 +759,7 @@ if fw then
end
-- setup nat
-if is_null(cfg.dmz_mode) then
+if is_nat_mode() then
-- zone[0] = lan, zone[1] = wan, zone[2] = wifi, zone[3] = dtdlink, zone[4] = vpn
local masq_src = cfg.lan_ip .. "/" .. netmask_to_cidr(cfg.lan_mask)
for z = 2, 4
@@ -999,7 +1037,7 @@ if h and e then
if is_notnull(cfg.dtdlink_ip) then
h:write(cfg.dtdlink_ip .. "\tdtdlink." .. node .. ".local.mesh dtdlink." .. node .."\n")
end
- if is_null(cfg.dmz_mode) then
+ if is_nat_mode() then
h:write(decimal_to_ip(ip_to_decimal(cfg.lan_ip) + 1) .. "\tlocalap\n")
end
@@ -1084,10 +1122,14 @@ if nixio.fs.access("/etc/config.mesh/olsrd", "r") then
of:write(line .. "\n")
end
- if is_notnull(cfg.dmz_mode) then
+ if is_dmz_mode() then
local a, b, c, d = cfg.dmz_lan_ip:match("(.*)%.(.*)%.(.*)%.(.*)")
of:write(string.format("\nconfig Hna4\n\toption netaddr %s.%s.%s.%d\n\toption netmask 255.255.255.%d\n\n", a, b, c, d - 1, nixio.bit.band(255 * 2 ^ cfg.dmz_mode, 255)))
end
+ if is_altnet_mode() then
+ local a, b, c, d = cfg.lan_ip:match("(.*)%.(.*)%.(.*)%.(.*)")
+ of:write(string.format("\nconfig Hna4\n\toption netaddr %s.%s.%s.%d\n\toption netmask %s\n\n", a, b, c, d - 1, cfg.lan_mask))
+ end
if cfg.wifi_enable ~= "1" and is_notnull(cfg.wifi_ip) then
of:write(string.format("config Hna4\n\toption netaddr %s\n\toption netmask 255.255.255.255\n\n", cfg.wifi_ip))
@@ -1095,6 +1137,11 @@ if nixio.fs.access("/etc/config.mesh/olsrd", "r") then
if is_supernode then
of:write("config Hna4\n\toption netaddr 10.0.0.0\n\toption netmask 255.0.0.0\n\n")
+ local altnetwork = nc:get("aredn", "@supernode[0]", "altnetwork")
+ local altnetmask = nc:get("aredn", "@supernode[0]", "altnetmask")
+ if altnetwork and altnetmask then
+ of:write("config Hna4\n\toption netaddr " .. altnetwork .. "\n\toption netmask " .. altnetmask .. "\n\n")
+ end
end
if nixio.fs.stat("/etc/config.mesh/xlink") then
@@ -1133,39 +1180,25 @@ if nixio.fs.access("/etc/config.mesh/olsrd", "r") then
-- add all the tunnel interfaces
if vtunclients + vtunservers + wgclients + wgservers > 0 then
- of:write("config Interface\n")
- for dev = 50, 50 + vtunclients - 1
+ for weight, ifaces in pairs(tun_weights)
do
- of:write("\tlist interface 'tun" .. dev .. "'\n")
- end
- for dev = 50 + maxclients, 50 + maxclients + vtunservers - 1
- do
- of:write("\tlist interface 'tun" .. dev .. "'\n")
- end
- if wgclients > 0 then
- for dev = 0, wgclients - 1
+ of:write("\nconfig Interface\n")
+ for _, iface in ipairs(ifaces)
do
- of:write("\tlist interface 'wgc" .. dev .. "'\n")
+ of:write("\tlist interface '" .. iface .. "'\n")
end
- end
- if wgservers > 0 then
- for dev = 0, wgservers - 1
- do
- of:write("\tlist interface 'wgs" .. dev .. "'\n")
+ of:write("\toption Ip4Broadcast '255.255.255.255'\n")
+ weight = tonumber(weight)
+ if weight < 1 then
+ of:write("\toption Mode 'ether'\n")
+ elseif weight > 1 then
+ of:write("\toption LinkQualityMult 'default " .. (1 / weight) .. "'\n")
end
+ of:write("\toption HelloInterval '" .. cfg.hello_interval .. "'\n")
+ of:write("\toption TcInterval '" .. cfg.tc_interval .. "'\n")
+ of:write("\toption MidInterval '" .. cfg.mid_interval .. "'\n")
+ of:write("\toption HnaInterval '" .. cfg.hna_interval .. "'\n")
end
- of:write("\toption Ip4Broadcast '255.255.255.255'\n")
- local tun_weight = tonumber(nc:get("aredn", "@tunnel[0]", "weight") or 1)
- local is_supernode = nc:get("aredn", "@supernode[0]", "enable") == "1"
- if not tun_weight or tun_weight < 1 or is_supernode then
- of:write("\toption Mode 'ether'\n")
- elseif tun_weight > 1 then
- of:write("\toption LinkQualityMult 'default " .. (1 / tun_weight) .. "'\n")
- end
- of:write("\toption HelloInterval '" .. cfg.hello_interval .. "'\n")
- of:write("\toption TcInterval '" .. cfg.tc_interval .. "'\n")
- of:write("\toption MidInterval '" .. cfg.mid_interval .. "'\n")
- of:write("\toption HnaInterval '" .. cfg.hna_interval .. "'\n")
end
nc:set("aredn", "@tunnel[0]", "maxclients", maxclients)
nc:set("aredn", "@tunnel[0]", "maxservers", maxservers)
@@ -1199,6 +1232,12 @@ if nixio.fs.access("/etc/config.mesh/olsrd", "r") then
)
end
+ -- OLSRD user extras
+ if nixio.fs.stat("/etc/aredn_include/olsrd.user") then
+ of:write("\n")
+ of:write(expand_vars(read_all("/etc/aredn_include/olsrd.user")))
+ end
+
of:close()
end
end
@@ -1244,6 +1283,7 @@ end
-- Handle special cases
local config_special = {
+ dmz_mode = c:get("aredn", "@dmz[0]", "mode"),
lqm_enable = c:get("aredn", "@lqm[0]", "enable"),
tunnel_weight = c:get("aredn", "@tunnel[0]", "weight"),
supernode_enable = c:get("aredn", "@supernode[0]", "enable"),
@@ -1279,6 +1319,9 @@ do
changes.system = true
elseif file == "aredn" then
local oc = uci:cursor()
+ if oc:get("aredn", "@dmz[0]", "mode") ~= config_special.dmz_mode then
+ changes.reboot = true
+ end
if oc:get("aredn", "@lqm[0]", "enable") ~= config_special.lqm_enable then
changes.manager = true
end
@@ -1312,6 +1355,13 @@ do
elseif file == "wireless" then
local oc = uci:cursor()
if oc:get("wireless", "@wifi-iface[0]", "mode") ~= config_special.wifi_mode_0 or oc:get("wireless", "@wifi-iface[1]", "mode") ~= config_special.wifi_mode_1 then
+ -- Only start the hostapd (etc) if we need to. This doesn't change what is currently running
+ -- only what automatically runs in the future
+ if oc:get("wireless", "@wifi-iface[0]", "mode") == "ap" or oc:get("wireless", "@wifi-iface[1]", "mode") == "ap" then
+ os.execute("/etc/init.d/wpad enable > /dev/null 2>&1")
+ else
+ os.execute("/etc/init.d/wpad disable > /dev/null 2>&1")
+ end
changes.reboot = true
else
changes.wireless = true
diff --git a/files/usr/share/ucode/aredn/configuration.uc b/files/usr/share/ucode/aredn/configuration.uc
new file mode 100755
index 00000000..fb5e135d
--- /dev/null
+++ b/files/usr/share/ucode/aredn/configuration.uc
@@ -0,0 +1,401 @@
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+
+import * as fs from "fs";
+import * as uci from "uci";
+import * as math from "math";
+import * as network from "aredn.network";
+
+let cursor;
+let setup;
+let setupKeys;
+let setupChanged = false;
+let firmwareVersion = null;
+
+const currentConfig = "/tmp/config.current";
+const modalConfig = "/tmp/config.modal";
+const configDirs = [
+ "/etc",
+ "/etc/config.mesh",
+ "/etc/local",
+ "/etc/local/uci",
+ "/etc/aredn_include",
+ "/etc/dropbear",
+ "/tmp"
+];
+const configFiles = [
+ "/etc/config.mesh/_setup",
+ "/etc/config.mesh/_setup.dhcp.dmz",
+ "/etc/config.mesh/_setup.dhcp.nat",
+ "/etc/config.mesh/_setup.dhcpoptions.dmz",
+ "/etc/config.mesh/_setup.dhcpoptions.nat",
+ "/etc/config.mesh/_setup.dhcptags.dmz",
+ "/etc/config.mesh/_setup.dhcptags.nat",
+ "/etc/config.mesh/_setup.ports.dmz",
+ "/etc/config.mesh/_setup.ports.nat",
+ "/etc/config.mesh/_setup.services.dmz",
+ "/etc/config.mesh/_setup.services.nat",
+ "/etc/config.mesh/aliases.dmz",
+ "/etc/config.mesh/aliases.nat",
+ "/etc/config.mesh/aredn",
+ "/etc/config.mesh/dhcp",
+ "/etc/config.mesh/dropbear",
+ "/etc/config.mesh/firewall",
+ "/etc/config.mesh/firewall.user",
+ "/etc/config.mesh/network",
+ "/etc/config.mesh/olsrd",
+ "/etc/config.mesh/snmpd",
+ "/etc/config.mesh/system",
+ "/etc/config.mesh/uhttpd",
+ "/etc/config.mesh/vtun",
+ "/etc/config.mesh/wireguard",
+ "/etc/config.mesh/xlink",
+ "/etc/local/uci/hsmmmesh",
+ "/etc/aredn_include/dtdlink.network.user",
+ "/etc/aredn_include/lan.network.user",
+ "/etc/aredn_include/wan.network.user",
+ "/etc/dropbear/authorized_keys",
+ "/tmp/newpassword"
+];
+
+function initCursor()
+{
+ if (!cursor) {
+ cursor = uci.cursor("/etc/local/uci");
+ }
+};
+
+function initSetup()
+{
+ if (!setup) {
+ setup = {};
+ setupKeys = [];
+ const f = fs.open("/etc/config.mesh/_setup");
+ if (f) {
+ for (;;) {
+ const line = f.read("line");
+ if (!length(line)) {
+ break;
+ }
+ const kv = split(line, " =");
+ if (length(kv) === 2) {
+ setup[kv[0]] = trim(kv[1]);
+ push(setupKeys, kv[0]);
+ }
+ }
+ f.close();
+ }
+ }
+};
+
+export function reset()
+{
+ setup = null;
+ cursor = null;
+};
+
+export function getSettingAsString(key, def)
+{
+ initSetup();
+ return setup[key] || def;
+};
+
+export function getSettingAsInt(key, def)
+{
+ initSetup();
+ const v = int(setup[key]);
+ if (type(v) === "int") {
+ return v;
+ }
+ return def;
+};
+
+export function setSetting(key, value, def)
+{
+ initSetup();
+ const old = setup[key];
+ setup[key] = `${value || def || ""}`;
+ if (old !== setup[key]) {
+ setupChanged = true;
+ return true;
+ }
+ return false;
+};
+
+export function saveSettings()
+{
+ if (setupChanged) {
+ const f = fs.open("/etc/config.mesh/_setup", "w");
+ if (f) {
+ for (let i = 0; i < length(setupKeys); i++) {
+ const k = setupKeys[i];
+ f.write(`${k} = ${setup[k] || ""}\n`);
+ }
+ f.close();
+ setupChanged = false;
+ }
+ }
+};
+
+export function getName()
+{
+ initCursor();
+ return cursor.get("hsmmmesh", "settings", "node");
+};
+
+export function setName(name)
+{
+ initCursor();
+ cursor.set("hsmmmesh", "settings", "node", name);
+ cursor.commit("hsmmmesh");
+};
+
+export function getFirmwareVersion()
+{
+ if (firmwareVersion === null) {
+ firmwareVersion = trim(fs.readfile("/etc/mesh-release"));
+ }
+ return firmwareVersion;
+};
+
+export function setUpgrade(v)
+{
+ initCursor();
+ cursor.set("hsmmmesh", "settings", "nodeupgraded", v);
+ cursor.commit("hsmmmesh");
+};
+
+export function setPassword(passwd)
+{
+ fs.writefile("/tmp/newpassword", passwd);
+};
+
+export function isPasswordChanged()
+{
+ return fs.access("/tmp/newpassword") ? true : false;
+};
+
+export function getDHCP(mode)
+{
+ initSetup();
+ if (mode === "nat" || (!mode && setup.dmz_mode === "0")) {
+ const root = replace(setup.lan_ip, /\d+$/, "");
+ return {
+ enabled: setup.lan_dhcp ? true : false,
+ mode: 0,
+ start: `${root}${setup.dhcp_start}`,
+ end: `${root}${setup.dhcp_end}`,
+ gateway: setup.lan_ip,
+ mask: setup.lan_mask,
+ cidr: network.netmaskToCIDR(setup.lan_mask),
+ leases: "/tmp/dhcp.leases",
+ reservations: "/etc/config.mesh/_setup.dhcp.nat",
+ services: "/etc/config.mesh/_setup.services.nat",
+ ports: "/etc/config.mesh/_setup.ports.nat",
+ dhcptags: "/etc/config.mesh/_setup.dhcptags.nat",
+ dhcpoptions: "/etc/config.mesh/_setup.dhcpoptions.nat",
+ aliases: "/etc/config.mesh/aliases.nat"
+ };
+ }
+ else if (setup.dmz_mode === "1") {
+ const root = replace(setup.lan_ip, /\d+$/, "");
+ return {
+ enabled: setup.lan_dhcp ? true : false,
+ mode: 1,
+ start: `${root}${setup.dhcp_start}`,
+ end: `${root}${setup.dhcp_end}`,
+ gateway: setup.lan_ip,
+ mask: setup.lan_mask,
+ cidr: network.netmaskToCIDR(setup.lan_mask),
+ leases: "/tmp/dhcp.leases",
+ reservations: "/etc/config.mesh/_setup.dhcp.dmz",
+ services: "/etc/config.mesh/_setup.services.dmz",
+ ports: "/etc/config.mesh/_setup.ports.dmz",
+ dhcptags: "/etc/config.mesh/_setup.dhcptags.dmz",
+ dhcpoptions: "/etc/config.mesh/_setup.dhcpoptions.dmz",
+ aliases: "/etc/config.mesh/aliases.dmz"
+ };
+ }
+ else {
+ const root = replace(setup.dmz_lan_ip, /\d+$/, "");
+ return {
+ enabled: setup.lan_dhcp ? true : false,
+ mode: int(setup.dmz_mode),
+ start: `${root}${setup.dmz_dhcp_start}`,
+ end: `${root}${setup.dmz_dhcp_end}`,
+ gateway: setup.dmz_lan_ip,
+ mask: setup.dmz_lan_mask,
+ cidr: network.netmaskToCIDR(setup.dmz_lan_mask),
+ leases: "/tmp/dhcp.leases",
+ reservations: "/etc/config.mesh/_setup.dhcp.dmz",
+ services: "/etc/config.mesh/_setup.services.dmz",
+ ports: "/etc/config.mesh/_setup.ports.dmz",
+ dhcptags: "/etc/config.mesh/_setup.dhcptags.dmz",
+ dhcpoptions: "/etc/config.mesh/_setup.dhcpoptions.dmz",
+ aliases: "/etc/config.mesh/aliases.dmz"
+ };
+ }
+};
+
+function copyConfig(configRoot)
+{
+ fs.mkdir(configRoot);
+ for (let i = 0; i < length(configDirs); i++) {
+ fs.mkdir(`${configRoot}${configDirs[i]}`);
+ }
+ for (let i = 0; i < length(configFiles); i++) {
+ const entry = configFiles[i];
+ if (fs.access(entry)) {
+ fs.writefile(`${configRoot}${entry}`, fs.readfile(entry));
+ }
+ }
+};
+
+function removeConfig(configRoot)
+{
+ for (let i = 0; i < length(configFiles); i++) {
+ fs.unlink(`${configRoot}${configFiles[i]}`);
+ }
+ for (let i = length(configDirs) - 1; i >= 0; i--) {
+ fs.rmdir(`${configRoot}${configDirs[i]}`);
+ }
+ fs.rmdir(configRoot);
+};
+
+function revertConfig(configRoot)
+{
+ if (fs.access(`${configRoot}/etc/config.mesh/_setup`)) {
+ for (let i = 0; i < length(configFiles); i++) {
+ const to = configFiles[i];
+ const from = `${configRoot}${to}`;
+ if (fs.access(from)) {
+ fs.writefile(to, fs.readfile(from));
+ fs.unlink(from);
+ }
+ else {
+ fs.unlink(to);
+ }
+ }
+ for (let i = length(configDirs) - 1; i >= 0; i--) {
+ fs.rmdir(`${configRoot}${configDirs[i]}`);
+ }
+ fs.rmdir(currentConfig);
+ }
+};
+
+export function prepareChanges()
+{
+ if (!fs.access(`${currentConfig}/etc/config.mesh/_setup`)) {
+ copyConfig(currentConfig);
+ }
+};
+
+export function prepareModalChanges()
+{
+ if (fs.access(`${modalConfig}/etc/config.mesh/_setup`)) {
+ removeConfig(modalConfig);
+ }
+ copyConfig(modalConfig);
+};
+
+function fileChanges(from, to)
+{
+ let count = 0;
+ const p = fs.popen(`exec /usr/bin/diff -NBbdiU0 ${from} ${to}`);
+ if (p) {
+ for (;;) {
+ const l = rtrim(p.read("line"));
+ if (!l) {
+ break;
+ }
+ if (index(l, "@@") === 0) {
+ const v = match(l, /^@@ [+-]\d+,?(\d*) [+-]\d+,?(\d*) @@$/);
+ if (v) {
+ count += max(math.abs(int(v[1] === "" ? 1 : v[1])), math.abs(int(v[2] === "" ? 1 : v[2])));
+ }
+ }
+ }
+ p.close();
+ }
+ return count;
+};
+
+export function commitChanges()
+{
+ const status = {};
+ if (fs.access(`${currentConfig}/etc/config.mesh/_setup`)) {
+ if (fileChanges(`${currentConfig}/etc/local/uci/hsmmmesh`, "/etc/local/uci/hsmmmesh") > 0) {
+ fs.mkdir("/tmp/reboot-required");
+ fs.writefile("/tmp/reboot-required/reboot", "");
+ }
+ removeConfig(modalConfig);
+ removeConfig(currentConfig);
+ if (fs.access("/tmp/newpassword")) {
+ const pw = fs.readfile("/tmp/newpassword");
+ system(`{ echo '${pw}'; sleep 1; echo '${pw}'; } | passwd > /dev/null 2>&1`);
+ fs.unlink("/tmp/newpassword");
+ }
+ const n = fs.popen("exec /usr/local/bin/node-setup");
+ if (n) {
+ status.setup = n.read("all");
+ n.close();
+ const c = fs.popen("exec /usr/local/bin/restart-services.sh");
+ if (c) {
+ status.restart = c.read("all");
+ c.close();
+ }
+ }
+ }
+ return status;
+};
+
+export function revertChanges()
+{
+ revertConfig(currentConfig);
+};
+
+export function revertModalChanges()
+{
+ revertConfig(modalConfig);
+};
+
+export function countChanges()
+{
+ let count = 0;
+ if (fs.access(`${currentConfig}/etc/config.mesh/_setup`)) {
+ for (let i = 0; i < length(configFiles); i++) {
+ count += fileChanges(`${currentConfig}${configFiles[i]}`, configFiles[i]);
+ }
+ }
+ return count;
+};
diff --git a/files/usr/share/ucode/aredn/hardware.uc b/files/usr/share/ucode/aredn/hardware.uc
new file mode 100755
index 00000000..d83c2809
--- /dev/null
+++ b/files/usr/share/ucode/aredn/hardware.uc
@@ -0,0 +1,583 @@
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+
+import * as fs from "fs";
+import * as uci from "uci";
+import * as ubus from "ubus";
+
+let radioJson;
+let boardJson;
+const antennasCache = {};
+const channelsCache = {};
+
+export function getBoard()
+{
+ if (!boardJson) {
+ const f = fs.open("/etc/board.json");
+ if (!f) {
+ return {};
+ }
+ boardJson = json(f.read("all"));
+ f.close();
+ // Collapse virtualized hardware into the two basic types
+ if (index(boardJson.model.id, "qemu-") === 0) {
+ boardJson.model.id = "qemu";
+ boardJson.model.name = "QEMU";
+ }
+ else if (index(lc(boardJson.model.id), "vmware") === 0) {
+ boardJson.model.id = "vmware";
+ boardJson.model.name = "VMware";
+ }
+ }
+ return boardJson;
+};
+
+export function getBoardId()
+{
+ let name = "";
+ const board = getBoard();
+ if (index(board.model.name, "Ubiquiti") === 0) {
+ name = fs.readfile("/sys/devices/pci0000:00/0000:00:00.0/subsystem_device");
+ if (!name || name === "" || name === "0x0000") {
+ const f = fs.open("/dev/mtd7");
+ if (f) {
+ f.seek(12);
+ const d = f.read(2);
+ f.close();
+ name = sprintf("0x%02x%02x", ord(d, 0), ord(d, 1));
+ }
+ }
+ }
+ if (!name || name === "" || name === "0x0000") {
+ name = board.model.name;
+ }
+ return trim(name);
+};
+
+export function getRadio()
+{
+ if (!radioJson) {
+ const f = fs.open("/etc/radios.json");
+ if (!f) {
+ return {};
+ }
+ const radios = json(f.read("all"));
+ f.close();
+ const id = getBoardId();
+ radioJson = radios[lc(id)];
+ if (radioJson && !radioJson.name) {
+ radioJson.name = id;
+ }
+ }
+ return radioJson;
+};
+
+export function getRadioCount()
+{
+ const radio = getRadio();
+ if (radio.wlan0) {
+ if (radio.wlan1) {
+ return 2;
+ }
+ else {
+ return 1;
+ }
+ }
+ else {
+ let count = 0;
+ const d = fs.opendir("/sys/class/ieee80211");
+ if (d) {
+ for (;;) {
+ const l = d.read();
+ if (!l) {
+ break;
+ }
+ if (l !== "." && l !== "..") {
+ count++;
+ }
+ }
+ d.close();
+ }
+ return count;
+ }
+};
+
+function getRadioIntf(wifiIface)
+{
+ const radio = getRadio();
+ if (radio[wifiIface]) {
+ return radio[wifiIface];
+ }
+ else {
+ return radio;
+ }
+};
+
+export function getRfChannels(wifiIface)
+{
+ let channels = channelsCache[wifiIface];
+ if (!channels) {
+ channels = [];
+ const f = fs.popen("/usr/bin/iwinfo " + wifiIface + " freqlist");
+ if (f) {
+ let freq_adjust = 0;
+ let freq_min = 0;
+ let freq_max = 0x7FFFFFFF;
+ if (wifiIface === "wlan0") {
+ const radio = getRadio();
+ if (index(radio.name, "M9") !== -1) {
+ freq_adjust = -1520;
+ freq_min = 907;
+ freq_max = 922;
+ }
+ else if (index(radio.name, "M3") !== -1) {
+ freq_adjust = -2000;
+ freq_min = 3380;
+ freq_max = 3495;
+ }
+ }
+ for (;;) {
+ const line = f.read("line");
+ if (!line) {
+ break;
+ }
+ const fn = match(line, /(\d+\.\d+) GHz \(Band: .*, Channel (-?\d+)\)/);
+ if (fn && index(line, "restricted") == -1 && index(line, "disabled") === -1) {
+ const freq = int(replace(fn[1], ".", "")) + freq_adjust;
+ if (freq >= freq_min && freq <= freq_max) {
+ const num = int(replace(fn[2], "0+", ""));
+ push(channels, {
+ label: freq_adjust === 0 ? num + " (" + freq + ")" : "" + freq,
+ number: num,
+ frequency: freq
+ });
+ }
+ }
+ }
+ sort(channels, (a, b) => a.frequency - b.frequency);
+ f.close();
+ channelsCache[wifiIface] = channels;
+ }
+ }
+ return channels;
+};
+
+export function getRfBandwidths(wifiIface)
+{
+ const radio = getRadioIntf(wifiIface);
+ if (radio.bandwidths) {
+ return radio.bandwidths;
+ }
+ else {
+ return [ 5, 10, 20 ];
+ }
+};
+
+export function getDefaultChannel(wifiIface)
+{
+ const rfchannels = getRfChannels(wifiIface);
+ for (let i = 0; i < length(rfchannels); i++) {
+ const c = rfchannels[i];
+ if (c.frequency == 912) {
+ return { channel: 5, bandwidth: 5, band: "900MHz" };
+ }
+ const bws = {};
+ const b = getRfBandwidths(wifiIface);
+ for (let j = 0; j < length(b); j++) {
+ bws[b[j]] = b[j];
+ }
+ const bw = bws[10] || bws[20] || bws[5] || 0;
+ if (c.frequency === 2397) {
+ return { channel: -2, bandwidth: bw, band: "2.4GHz" };
+ }
+ if (c.frequency === 2412) {
+ return { channel: 1, bandwidth: bw, band: "2.4GHz" };
+ }
+ if (c.frequency === 3420) {
+ return { channel: 84, bandwidth: bw, band: "3GHz" };
+ }
+ if (c.frequency === 5745) {
+ return { channel: 149, bandwidth: bw, band: "5GHz" };
+ }
+ }
+ return null;
+};
+
+export function getAntennas(wifiIface)
+{
+ let ants = antennasCache[wifiIface];
+ if (!ants) {
+ const radio = getRadioIntf(wifiIface);
+ if (radio && radio.antenna) {
+ if (radio.antenna === "external") {
+ const dchan = getDefaultChannel(wifiIface);
+ if (dchan && dchan.band) {
+ const f = fs.open("/etc/antennas.json");
+ if (f) {
+ ants = json(f.read("all"));
+ f.close();
+ ants = ants[dchan.band];
+ }
+ }
+ }
+ else {
+ radio.antenna.builtin = true;
+ ants = [ radio.antenna ];
+ }
+ antennasCache[wifiIface] = ants;
+ }
+ }
+ return ants;
+};
+
+export function getAntennasAux(wifiIface)
+{
+ let ants = antennasCache["aux:" + wifiIface];
+ if (!ants) {
+ const radio = getRadioIntf(wifiIface);
+ if (radio && radio.antenna_aux === "external") {
+ const dchan = getDefaultChannel(wifiIface);
+ if (dchan && dchan.band) {
+ const f = fs.open("/etc/antennas.json");
+ if (f) {
+ ants = json(f.read("all"));
+ f.close();
+ ants = ants[dchan.band];
+ }
+ }
+ antennasCache["aux:" + wifiIface] = ants;
+ }
+ }
+ return ants;
+};
+
+export function getAntennaInfo(wifiIface, antenna)
+{
+ const ants = getAntennas(wifiIface);
+ if (ants) {
+ if (length(ants) === 1) {
+ return ants[0];
+ }
+ if (antenna) {
+ for (let i = 0; i < length(ants); i++) {
+ if (ants[i].model === antenna) {
+ return ants[i];
+ }
+ }
+ }
+ }
+ return null;
+};
+
+export function getAntennaAuxInfo(wifiIface, antenna)
+{
+ const ants = getAntennasAux(wifiIface);
+ if (ants) {
+ if (length(ants) === 1) {
+ return ants[0];
+ }
+ if (antenna) {
+ for (let i = 0; i < length(ants); i++) {
+ if (ants[i].model === antenna) {
+ return ants[i];
+ }
+ }
+ }
+ }
+ return null;
+};
+
+export function getChannelFrequency(wifiIface, channel)
+{
+ const rfchans = getRfChannels(wifiIface);
+ if (rfchans[0]) {
+ for (let i = 0; i < length(rfchans); i++) {
+ const c = rfchans[i];
+ if (c.number === channel) {
+ return c.frequency;
+ }
+ }
+ }
+ return null;
+};
+
+export function getChannelFrequencyRange(wifiIface, channel, bandwidth)
+{
+ const rfchans = getRfChannels(wifiIface);
+ if (rfchans[0]) {
+ for (let i = 0; i < length(rfchans); i++) {
+ const c = rfchans[i];
+ if (c.number === channel) {
+ return (c.frequency - bandwidth / 2) + " - " + (c.frequency + bandwidth / 2) + " MHz";
+ }
+ }
+ }
+ return null;
+};
+
+export function getChannelFromFrequency(freq)
+{
+ if (freq < 256) {
+ return null;
+ }
+ if (freq === 2484) {
+ return 14;
+ }
+ if (freq === 2407) {
+ return 0;
+ }
+ if (freq < 2484) {
+ return (freq - 2407) / 5;
+ }
+ if (freq < 5000) {
+ return null;
+ }
+ if (freq < 5380) {
+ return (freq - 5000) / 5;
+ }
+ if (freq < 5500) {
+ return freq - 2000;
+ }
+ if (freq < 6000) {
+ return (freq - 5000) / 5;
+ }
+};
+
+export function getMaxTxPower(wifiIface, channel)
+{
+ const radio = getRadioIntf(wifiIface);
+ if (radio) {
+ const maxpower = radio.maxpower;
+ const chanpower = radio.chanpower;
+ if (channel && chanpower) {
+ for (let k in chanpower) {
+ if (channel <= k) {
+ return chanpower[k];
+ }
+ }
+ }
+ if (maxpower) {
+ return maxpower;
+ }
+ }
+ return 27;
+};
+
+export function getTxPowerOffset(wifiIface)
+{
+ const radio = getRadioIntf(wifiIface);
+ if (radio && radio.pwroffset) {
+ return radio.pwroffset;
+ }
+ const f = fs.popen("/usr/bin/iwinfo " + wifiIface + " info");
+ if (f) {
+ for (;;) {
+ const line = f.read("line");
+ if (!line) {
+ break;
+ }
+ if (index(line, "TX power offset: ") !== -1) {
+ const pwroff = match(line, /TX power offset: (\d+)/);
+ if (pwroff) {
+ f.close();
+ return int(pwroff[1]);
+ }
+ return 0;
+ }
+ }
+ f.close();
+ }
+ return 0;
+};
+
+export function supportsXLink()
+{
+ switch (getBoard().model.id) {
+ case "mikrotik,hap-ac2":
+ case "mikrotik,hap-ac3":
+ case "glinet,gl-b1300":
+ case "qemu":
+ case "vmware":
+ return true;
+ default:
+ return false;
+ }
+};
+
+const default5PortLayout = [ { k: "wan", d: "port1" }, { k: "lan1", d: "port2" }, { k: "lan2", d: "port3" }, { k: "lan3", d: "port4" }, { k: "lan4", d: "port5" } ];
+const default3PortLayout = [ { k: "lan2", d: "port1" }, { k: "lan1", d: "port2" }, { k: "wan", d: "port3" } ];
+const defaultNPortLayout = [];
+
+export function getEthernetPorts()
+{
+ switch (getBoard().model.id) {
+ case "mikrotik,hap-ac2":
+ case "mikrotik,hap-ac3":
+ return default5PortLayout;
+ case "glinet,gl-b1300":
+ return default3PortLayout;
+ case "qemu":
+ case "vmware":
+ if (length(defaultNPortLayout) === 0) {
+ const dir = fs.opendir("/sys/class/net");
+ if (dir) {
+ for (;;) {
+ const file = dir.read();
+ if (!file) {
+ break;
+ }
+ if (match(file, /^eth\d+$/)) {
+ push(defaultNPortLayout, { k: file, d: file });
+ }
+ }
+ dir.close();
+ sort(defaultNPortLayout, (a, b) => a.d == b.d ? 0 : a.d < b.d ? -1 : 1);
+ }
+ }
+ return defaultNPortLayout;
+ default:
+ return [];
+ }
+};
+
+export function getEthernetPortInfo(port)
+{
+ const s = { active: false };
+ if (fs.readfile(`/sys/class/net/${port}/carrier`, 1) === "1") {
+ s.active = true;
+ }
+ return s;
+};
+
+export function getDefaultNetworkConfiguration()
+{
+ const c = {
+ dtdlink: { vlan: 2, ports: {} },
+ lan: { vlan: 0, ports: {} },
+ wan: { vlan: 0, ports: {} }
+ };
+ const board = getBoard();
+ const network = board.network;
+ for (let k in network) {
+ const net = c[k];
+ if (net) {
+ const devices = split(network[k].device, " ");
+ for (let i = 0; i < length(devices); i++) {
+ const m = match(devices[i], /^([^\.]+)\.?(\d*)$/);
+ if (m) {
+ net.ports[m[1]] = true;
+ if (m[2]) {
+ net.vlan = int(m[2]);
+ }
+ }
+ }
+ const ports = network[k].ports || [];
+ for (let i = 0; i < length(ports); i++) {
+ net.ports[ports[i]] = true;
+ }
+ }
+ }
+ return c;
+};
+
+export function hasPOE()
+{
+ const board = getBoard();
+ if (board?.gpioswitch?.poe_passthrough?.pin) {
+ return true;
+ }
+ const gpios = fs.lsdir("/sys/class/gpio/");
+ for (let i = 0; i < length(gpios); i++) {
+ if (match(gpios[i], /^enable-poe:/)) {
+ return true;
+ }
+ }
+ return false;
+};
+
+export function hasUSBPower()
+{
+ const board = getBoard();
+ if (board?.gpioswitch?.usb_power_switch?.pin) {
+ return true;
+ }
+ if (fs.access("/sys/class/gpio/usb-power")) {
+ return true;
+ }
+ return false;
+};
+
+export function isLowMemNode()
+{
+ const f = fs.open("/proc/meminfo");
+ if (f) {
+ const l = f.read("line");
+ f.close();
+ const m = match(l, /([0-9]+)/);
+ if (m && int(m[1]) <= 32768) {
+ return true;
+ }
+ }
+ return false;
+};
+
+export function getHardwareType()
+{
+ const model = getBoard().model;
+ let targettype = ubus.connect().call("system", "board", {}).release.target;
+ let hardwaretype = model.id;
+ let m = match(hardwaretype, /,(.*)/);
+ if (m) {
+ hardwaretype = m[1];
+ }
+ const mfg = trim(model.name);
+ let mfgprefix = "";
+ if (match(mfg, /[Uu]biquiti/)) {
+ mfgprefix = "ubnt";
+ }
+ else if (match(mfg, /[Mm]ikro[Tt]ik/)) {
+ mfgprefix = "mikrotik";
+ const bv = fs.open("/sys/firmware/mikrotik/soft_config/bios_version");
+ if (bv) {
+ const v = bv.read("all");
+ bv.close();
+ if (substr(v, 2) === "7.") {
+ targettype += "-v7"
+ }
+ }
+ }
+ else if (match(mfg, /[Tt][Pp]-[Ll]ink/)) {
+ mfgprefix = "cpe";
+ }
+ return `(${targettype}) ${mfgprefix ? mfgprefix + " " : ""}(${hardwaretype})`;
+};
diff --git a/files/usr/share/ucode/aredn/lqm.uc b/files/usr/share/ucode/aredn/lqm.uc
new file mode 100755
index 00000000..1b38c136
--- /dev/null
+++ b/files/usr/share/ucode/aredn/lqm.uc
@@ -0,0 +1,70 @@
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+
+import * as fs from "fs";
+
+let lqm;
+
+function initLQM()
+{
+ if (!lqm) {
+ try {
+ lqm = json(fs.readfile("/tmp/lqm.info"));
+ }
+ catch (_) {
+ }
+ }
+}
+
+export function get()
+{
+ initLQM();
+ return lqm || { trackers:{}, hidden_nodes:[], now: 0 };
+};
+
+export function getTrackers()
+{
+ initLQM();
+ return lqm?.trackers || {};
+};
+
+export function getHidden()
+{
+ initLQM();
+ return lqm?.hidden_nodes || [];
+};
+
+export function reset()
+{
+ lqm = null;
+};
diff --git a/files/usr/share/ucode/aredn/mesh.uc b/files/usr/share/ucode/aredn/mesh.uc
new file mode 100755
index 00000000..0e3b26b8
--- /dev/null
+++ b/files/usr/share/ucode/aredn/mesh.uc
@@ -0,0 +1,79 @@
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+
+import * as fs from "fs";
+
+export function getNodeList()
+{
+ const re = /^10.+\tdtdlink\.(.+)\.local\.mesh\t#.+$/;
+ const nodes = [];
+ const f = fs.open("/var/run/hosts_olsr");
+ if (!f) {
+ return nodes;
+ }
+ const hash = {};
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ const m = match(l, re);
+ if (m) {
+ const n = m[1];
+ const ln = lc(n);
+ push(nodes, ln);
+ hash[ln] = n;
+ }
+ }
+ f.close();
+ sort(nodes);
+ return map(nodes, n => hash[n]);
+};
+
+export function getNodeCounts()
+{
+ let nodes = 0;
+ let devices = 0;
+ const f = fs.open("/var/run/hosts_olsr");
+ if (f) {
+ for (let l = f.read("line"); length(l); l = f.read("line")) {
+ if (substr(l, 0, 3) == "10." && index(l, "\tmid") === -1) {
+ devices++;
+ if (index(l, "\tdtdlink.") !== -1) {
+ nodes++;
+ }
+ }
+ }
+ f.close();
+ }
+ return {
+ nodes: nodes,
+ devices: devices
+ };
+};
diff --git a/files/usr/share/ucode/aredn/messages.uc b/files/usr/share/ucode/aredn/messages.uc
new file mode 100755
index 00000000..fcb6caa3
--- /dev/null
+++ b/files/usr/share/ucode/aredn/messages.uc
@@ -0,0 +1,135 @@
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+
+import * as fs from "fs";
+import * as uci from "uci";
+import * as configuration from "aredn.configuration";
+import * as hardware from "aredn.hardware";
+import * as radios from "aredn.radios";
+
+export function haveMessages()
+{
+ if (fs.access("/etc/cron.boot/reinstall-packages") && fs.access("/etc/package_store/catalog.json")) {
+ return true;
+ }
+ if (fs.stat("/tmp/aredn_message")?.size || fs.stat("/tmp/local_message")?.size) {
+ return true;
+ }
+ return false;
+};
+
+function parseMessages(nodename, msgs, text)
+{
+ if (text) {
+ const t = split(text, "");
+ for (let i = 0; i < length(t); i++) {
+ const m = match(t[i], /↣ (.+):<\/strong>(.+)
/);
+ if (m) {
+ const label = m[1] !== nodename ? m[1] : "yournode";
+ if (!msgs[label]) {
+ msgs[label] = [];
+ }
+ push(msgs[label], trim(m[2]));
+ }
+ }
+ }
+}
+
+export function getMessages()
+{
+ const nodename = lc(configuration.getName());
+ const msgs = {};
+ if (fs.access("/etc/cron.boot/reinstall-packages") && fs.access("/etc/package_store/catalog.json")) {
+ msgs.system = [ "Packages are being reinstalled in the background. This can take a few minutes." ];
+ }
+ parseMessages(nodename, msgs, fs.readfile("/tmp/aredn_message"));
+ parseMessages(nodename, msgs, fs.readfile("/tmp/local_message"));
+ return msgs;
+};
+
+export function haveToDos()
+{
+ const cursor = uci.cursor();
+ if (!cursor.get("aredn", "@location[0]", "lat") ||
+ !cursor.get("aredn", "@location[0]", "lon") ||
+ configuration.getSettingAsString("time_zone_name", "Not Set") === "Not Set"
+ ) {
+ return true;
+ }
+ if (hardware.getRadioCount() > 0) {
+ const wlan = cursor.get("network", "wifi", "device");
+ const ants = hardware.getAntennas(wlan);
+ const ant = cursor.get("aredn", "@location[0]", "antenna");
+ if (length(ants) > 1 && !ant) {
+ return true;
+ }
+ if (ant || length(ants) === 1) {
+ if (!cursor.get("aredn", "@location[0]", "azimuth")) {
+ const ainfo = hardware.getAntennaInfo(wlan, ant || ants[0]);
+ if (ainfo.beamwidth !== 360) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+};
+
+export function getToDos()
+{
+ const cursor = uci.cursor();
+ const todos = [];
+ if (!cursor.get("aredn", "@location[0]", "lat") || !cursor.get("aredn", "@location[0]", "lon")) {
+ push(todos, "Set the latitude and longitude");
+ }
+ if (configuration.getSettingAsString("time_zone_name", "Not Set") === "Not Set") {
+ push(todos, "Set the timzeone");
+ }
+ if (hardware.getRadioCount() > 0) {
+ const wlan = cursor.get("network", "wifi", "device");
+ const ants = hardware.getAntennas(wlan);
+ const ant = cursor.get("aredn", "@location[0]", "antenna");
+ if (length(ants) > 1 && !ant) {
+ push(todos, "Select an antenna");
+ }
+ else if (ant || length(ants) === 1) {
+ if (!cursor.get("aredn", "@location[0]", "azimuth")) {
+ const ainfo = hardware.getAntennaInfo(wlan, ant || ants[0]);
+ if (ainfo.beamwidth !== 360) {
+ push(todos, "Set antenna azimuth");
+ }
+ }
+ }
+ }
+ return todos;
+};
diff --git a/files/usr/share/ucode/aredn/network.uc b/files/usr/share/ucode/aredn/network.uc
new file mode 100755
index 00000000..3496d0d6
--- /dev/null
+++ b/files/usr/share/ucode/aredn/network.uc
@@ -0,0 +1,127 @@
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+
+import * as fs from "fs";
+import * as resolv from "resolv";
+
+export function hasInternet()
+{
+ const p = fs.popen("exec /bin/ping -W1 -c1 8.8.8.8");
+ if (p) {
+ const d = p.read("all");
+ p.close();
+ if (index(d, "1 packets received") !== -1) {
+ return true;
+ }
+ }
+ return false;
+};
+
+export function getIPAddressFromHostname(hostname)
+{
+ const p = fs.popen(`exec /usr/bin/nslookup ${hostname}`);
+ if (p) {
+ const d = p.read("all");
+ p.close();
+ const i = match(d, /Address: ([0-9.]+)/);
+ if (i) {
+ return i[1];
+ }
+ }
+ return null;
+};
+
+export function netmaskToCIDR(mask)
+{
+ const m = iptoarr(mask);
+ let cidr = 32;
+ for (let i = 3; i >= 0; i--) {
+ switch (m[i]) {
+ default:
+ case 255:
+ return cidr - 0;
+ case 254:
+ return cidr - 1;
+ case 252:
+ return cidr - 2;
+ case 248:
+ return cidr - 3;
+ case 240:
+ return cidr - 4;
+ case 224:
+ return cidr - 5;
+ case 192:
+ return cidr - 6;
+ case 128:
+ return cidr - 7;
+ case 0:
+ cidr -= 8;
+ break;
+ }
+ }
+ return 0;
+};
+
+export function CIDRToNetmask(cidr)
+{
+ const v = (0xFF00 >> (cidr % 8)) & 0xFF;
+ switch (int(cidr / 8)) {
+ case 0:
+ return `${v}.0.0.0`;
+ case 1:
+ return `255.${v}.0.0`;
+ case 2:
+ return `255.255.${v}.0`;
+ case 3:
+ return `255.255.255.${v}`;
+ default:
+ return "255.255.255.255";
+ }
+};
+
+export function nslookup(aorh)
+{
+ const r = resolv.query([aorh]);
+ if (r) {
+ for (let k in r) {
+ const v = r[k];
+ if (v.PTR) {
+ return v.PTR[0];
+ }
+ if (v.A) {
+ return v.A[0];
+ }
+ }
+ }
+ return null;
+};
diff --git a/files/usr/share/ucode/aredn/olsr.uc b/files/usr/share/ucode/aredn/olsr.uc
new file mode 100755
index 00000000..66c6c79e
--- /dev/null
+++ b/files/usr/share/ucode/aredn/olsr.uc
@@ -0,0 +1,90 @@
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+
+import * as fs from "fs";
+
+export function getLinks()
+{
+ const f = fs.popen("exec /usr/bin/curl http://127.0.0.1:9090/links -o - 2> /dev/null");
+ try {
+ const links = json(f.read("all")).links;
+ f.close();
+ return links;
+ }
+ catch (_) {
+ f.close();
+ return [];
+ }
+};
+
+export function getRoutes()
+{
+ const f = fs.popen("exec /usr/bin/curl http://127.0.0.1:9090/routes -o - 2> /dev/null");
+ try {
+ const routes = json(f.read("all")).routes;
+ f.close();
+ return routes;
+ }
+ catch (_) {
+ f.close();
+ return [];
+ }
+};
+
+export function getHNAs()
+{
+ const f = fs.popen("exec /usr/bin/curl http://127.0.0.1:9090/hna -o - 2> /dev/null");
+ try {
+ const hna = json(f.read("all")).hna;
+ f.close();
+ return hna;
+ }
+ catch (_) {
+ f.close();
+ return [];
+ }
+};
+
+export function getMids()
+{
+ const f = fs.popen("exec /usr/bin/curl http://127.0.0.1:9090/mid -o - 2> /dev/null");
+ try {
+ const mid = json(f.read("all")).mid;
+ f.close();
+ return mid;
+ }
+ catch (_) {
+ f.close();
+ return [];
+ }
+};
diff --git a/files/usr/share/ucode/aredn/radios.uc b/files/usr/share/ucode/aredn/radios.uc
new file mode 100755
index 00000000..05478809
--- /dev/null
+++ b/files/usr/share/ucode/aredn/radios.uc
@@ -0,0 +1,196 @@
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+
+import * as hardware from "aredn.hardware";
+import * as configuration from "aredn.configuration";
+import * as uci from "uci";
+
+export const RADIO_OFF = 0;
+export const RADIO_MESH = 1;
+export const RADIO_LAN = 2;
+export const RADIO_WAN = 3;
+
+export function getCommonConfiguration()
+{
+ const radio = [];
+ const nrradios = hardware.getRadioCount();
+ for (let i = 0; i < nrradios; i++) {
+ const iface = `wlan${i}`;
+ radio[i] = {
+ iface: iface,
+ mode: 0,
+ modes: [],
+ ant: null,
+ antaux: null,
+ def: hardware.getDefaultChannel(iface),
+ bws: hardware.getRfBandwidths(iface),
+ channels: hardware.getRfChannels(iface),
+ ants: hardware.getAntennas(iface),
+ antsaux: hardware.getAntennasAux(iface),
+ txpoweroffset: hardware.getTxPowerOffset(iface),
+ txmaxpower: hardware.getMaxTxPower(iface)
+ };
+ }
+ return radio;
+};
+
+export function getActiveConfiguration()
+{
+ const cursor = uci.cursor();
+ const radio = getCommonConfiguration();
+ const nrradios = length(radio);
+ if (nrradios > 0) {
+ const meshrf = cursor.get("network", "wifi", "device");
+ const widx = match(meshrf, /^wlan(\d+)$/);
+ if (widx) {
+ let device;
+ const mode = {
+ channel: 0,
+ bandwidth: 10,
+ ssid: "AREDN",
+ txpower: configuration.getSettingAsInt("wifi_txpower")
+ };
+ cursor.foreach("wireless", "wifi-iface", function(s)
+ {
+ if (s.network === "wifi" && s.ifname === meshrf) {
+ device = s.device;
+ mode.ssid = s.ssid;
+ return false;
+ }
+ });
+ cursor.foreach("wireless", "wifi-device", function(s)
+ {
+ if (s[".name"] === device) {
+ mode.channel = int(s.channel);
+ mode.bandwidth = int(s.chanbw);
+ return false;
+ }
+ });
+ radio[widx[1]].mode = RADIO_MESH;
+ radio[widx[1]].modes = [ null, mode, null, null ];
+ }
+ }
+ return radio;
+};
+
+export function getConfiguration()
+{
+ const cursor = uci.cursor("/etc/config.mesh");
+ const radio = getCommonConfiguration();
+ const nrradios = length(radio);
+ if (nrradios > 0) {
+ const modes = [ null, {
+ channel: configuration.getSettingAsInt("wifi_channel"),
+ bandwidth: configuration.getSettingAsInt("wifi_chanbw", 10),
+ ssid: configuration.getSettingAsString("wifi_ssid", "AREDN"),
+ txpower: configuration.getSettingAsInt("wifi_txpower", 27)
+ },
+ {
+ channel: configuration.getSettingAsInt("wifi2_channel"),
+ encryption: configuration.getSettingAsString("wifi2_encryption", "psk2"),
+ key: configuration.getSettingAsString("wifi2_key", ""),
+ ssid: configuration.getSettingAsString("wifi2_ssid", "")
+ },
+ {
+ key: configuration.getSettingAsString("wifi3_key", ""),
+ ssid: configuration.getSettingAsString("wifi3_ssid", "")
+ }];
+ for (let i = 0; i < nrradios; i++) {
+ radio[i].modes = modes;
+ }
+
+ radio[0].ant = hardware.getAntennaInfo(radio[0].iface, cursor.get("aredn", "@location[0]", "antenna"));
+ radio[0].antaux = hardware.getAntennaAuxInfo(radio[0].iface, cursor.get("aredn", "@location[0]", "antenna_aux"));
+
+ const wifi_enable = configuration.getSettingAsInt("wifi_enable", 0);
+ const wifi2_enable = configuration.getSettingAsInt("wifi2_enable", 0);
+ const wifi3_enable = configuration.getSettingAsInt("wifi3_enable", 0);
+ if (nrradios === 1) {
+ if (wifi_enable) {
+ radio[0].mode = 1;
+ }
+ else if (wifi2_enable) {
+ radio[0].mode = 2;
+ }
+ else if (wifi3_enable) {
+ radio[0].mode = 3;
+ }
+ }
+ else if (wifi_enable) {
+ const wifi_iface = configuration.getSettingAsString("wifi_intf", "wlan0");
+ if (wifi_iface === "wlan0") {
+ radio[0].mode = 1;
+ if (wifi2_enable) {
+ radio[1].mode = 2;
+ }
+ else if (wifi3_enable) {
+ radio[1].mode = 3;
+ }
+ }
+ else {
+ radio[1].mode = 1;
+ if (wifi2_enable) {
+ radio[0].mode = 2;
+ }
+ else if (wifi3_enable) {
+ radio[0].mode = 3;
+ }
+ }
+ }
+ else if (wifi2_enable) {
+ const wifi2_hwmode = configuration.getSettingAsString("wifi2_hwmode", "11a");
+ if ((wifi2_hwmode === "11a" && radio[0].def.band === "5GHz") || (wifi2_hwmode === "11g" && radio[0].def.band === "2.4GHz")) {
+ radio[0].mode = 2;
+ if (wifi3_enable) {
+ radio[1].mode = 3;
+ }
+ }
+ else {
+ radio[1].mode = 2;
+ if (wifi3_enable) {
+ radio[0].mode = 3;
+ }
+ }
+ }
+ else if (wifi3_enable) {
+ const wifi3_hwmode = configuration.getSettingAsString("wifi3_hwmode", "11a");
+ if ((wifi3_hwmode === "11a" && radio[0].def.band === "5GHz") || (wifi3_hwmode === "11g" && radio[0].def.band === "2.4GHz")) {
+ radio[0].mode = 3;
+ }
+ else {
+ radio[1].mode = 3;
+ }
+ }
+ }
+ return radio;
+};
diff --git a/files/usr/share/ucode/aredn/units.uc b/files/usr/share/ucode/aredn/units.uc
new file mode 100755
index 00000000..d50d1b06
--- /dev/null
+++ b/files/usr/share/ucode/aredn/units.uc
@@ -0,0 +1,75 @@
+/*
+ * Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ * Copyright (C) 2024 Tim Wilkinson
+ * See Contributors file for additional contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Additional Terms:
+ *
+ * Additional use restrictions exist on the AREDN® trademark and logo.
+ * See AREDNLicense.txt for more info.
+ *
+ * Attributions to the AREDN® Project must be retained in the source code.
+ * If importing this code into a new or existing project attribution
+ * to the AREDN® project must be added to the source code.
+ *
+ * You must not misrepresent the origin of the material contained within.
+ *
+ * Modified versions must be modified to attribute to the original source
+ * and be marked in reasonable ways as differentiate it from the original
+ * version
+ */
+
+const meters_to_miles = 0.000621371;
+const meters_to_km = 0.001;
+let metric = null;
+
+function isMetric()
+{
+ if (metric === null) {
+ const lang = request?.env?.HTTP_ACCEPT_LANGUAGE || "en-US";
+ if (index(lang, "-US") !== -1 || index(lang, "-GB") !== -1) {
+ metric = false;
+ }
+ else {
+ metric = true;
+ }
+ }
+ return metric;
+};
+
+export function distanceUnit()
+{
+ return isMetric() ? "km" : "miles";
+};
+
+export function meters2distance(meters)
+{
+ if (isMetric()) {
+ return meters * meters_to_km;
+ }
+ else {
+ return meters * meters_to_miles;
+ }
+};
+
+export function distance2meters(distance)
+{
+ if (isMetric()) {
+ return distance / meters_to_km;
+ }
+ else {
+ return distance / meters_to_miles;
+ }
+};
diff --git a/files/www/cgi-bin/admin b/files/www/cgi-bin/admin
index 00dc57aa..bdcab160 100755
--- a/files/www/cgi-bin/admin
+++ b/files/www/cgi-bin/admin
@@ -155,6 +155,7 @@ if os.getenv("REQUEST_METHOD") == "POST" then
os.execute("/usr/local/bin/upgrade_prepare.sh stop > /dev/null 2>&1")
end
end
+ nixio.fs.mkdir("/tmp/web")
nixio.fs.mkdir("/tmp/web/upload")
fp = io.open("/tmp/web/upload/file", "w")
end
@@ -181,11 +182,16 @@ nixio.fs.mkdir("/tmp/web/admin")
local curl = "curl -A 'node: " .. node .. "' "
local serverpaths = {}
-local uciserverpath = cursor:get("aredn", "@downloads[0]", "firmwarepath")
-if not uciserverpath then
- uciserverpath = ""
+local aredn_firmware = cursor:get("aredn", "@downloads[0]", "firmware_aredn")
+if aredn_firmware then
+ serverpaths[#serverpaths + 1] = aredn_firmware
+else
+ local uciserverpath = cursor:get("aredn", "@downloads[0]", "firmwarepath")
+ if not uciserverpath then
+ uciserverpath = ""
+ end
+ serverpaths[#serverpaths + 1] = uciserverpath:match("^(.*)/firmware") or uciserverpath
end
-serverpaths[#serverpaths + 1] = uciserverpath
local hardwaretype = aredn.hardware.get_type()
local targettype = conn:call("system", "board", {}).release.target
@@ -224,13 +230,13 @@ end
-- refresh fw
if parms.button_refresh_fw then
nixio.fs.remove("/tmp/web/firmware.list")
- if get_default_gw() ~= "none" or uciserverpath:match("%.local%.mesh") then
- fwout("Downloading firmware list from " .. uciserverpath .. "...")
+ if get_default_gw() ~= "none" or serverpaths[1]:match("%.local%.mesh") then
+ fwout("Downloading firmware list from " .. serverpaths[1] .. "...")
local config_versions
local config_serverpath
for _, serverpath in ipairs(serverpaths)
do
- config_serverpath = (serverpath:match("^(.*)/firmware") or serverpath) .. "/afs/www/"
+ config_serverpath = serverpath .. "/afs/www/"
for line in io.popen(curl .. " -o - " .. config_serverpath .. "config.js 2> /dev/null"):lines()
do
local v = line:match("versions: {(.+)}")
@@ -389,7 +395,7 @@ end
-- download fw
if parms.button_dl_fw and parms.dl_fw ~= "default" then
- if get_default_gw() ~= "none" or uciserverpath:match("%.local%.mesh") then
+ if get_default_gw() ~= "none" or serverpaths[1]:match("%.local%.mesh") then
nixio.fs.remove(tmpdir .. "/firmware")
os.execute("/usr/local/bin/uploadctlservices update > /dev/null 2>&1")
diff --git a/files/www/cgi-bin/advancedconfig b/files/www/cgi-bin/advancedconfig
index 538aa6eb..469e9005 100755
--- a/files/www/cgi-bin/advancedconfig
+++ b/files/www/cgi-bin/advancedconfig
@@ -45,43 +45,6 @@ require("aredn.info")
local html = aredn.html
-local urlprefix
-local target = "unknown"
-local arch = "unknown"
-function defaultPackageRepos(repo)
- if not urlprefix then
- urlprefix = "http://downloads.arednmesh.org"
- local release = "unknown"
- for line in io.lines("/etc/openwrt_release")
- do
- local m = line:match("DISTRIB_RELEASE='(.*)'")
- if m then
- release = m
- end
- m = line:match("DISTRIB_TARGET='(.*)'")
- if m then
- target = m
- end
- m = line:match("DISTRIB_ARCH='(.*)'")
- if m then
- arch = m
- end
- end
- local a, b = release:match("^(%d+)%.(%d+)%.")
- if a and b then
- urlprefix = urlprefix .. "/releases/" .. a .. "/" .. b .. "/" .. release
- else
- -- nightly
- urlprefix = urlprefix .. "/snapshots"
- end
- end
- if repo:match("aredn_core") then
- return urlprefix .. "/targets/" .. target .. "/packages"
- else
- return urlprefix .. "/packages/" .. arch .. "/" .. repo
- end
-end
-
local settings = {
{
category = "Link Quality Settings",
@@ -314,95 +277,24 @@ local settings = {
},
{
category = "Map Paths",
- key = "aredn.@map[0].maptiles",
+ key = "aredn.@location[0].map",
type = "string",
- desc = "Map Tiles URL
aredn.@downloads[0].packages_default",
+ default = "http://downloads.arednmesh.org"
},
{
category = "Firmware",
diff --git a/files/www/cgi-bin/iperf b/files/www/cgi-bin/iperf
index 989b283f..b350f299 100755
--- a/files/www/cgi-bin/iperf
+++ b/files/www/cgi-bin/iperf
@@ -46,8 +46,8 @@ local server = q:match("server=([^&]*)")
local protocol = q:match("protocol=([^&]*)") or "tcp"
local kill = q:match("kill=1") and true or false
-print "Content-type: text/html\r"
-print "Cache-Control: no-store\r"
+print("Content-type: text/html\r")
+print("Cache-Control: no-store\r")
print("Access-Control-Allow-Origin: *\r")
print("\r")
if uci.cursor():get("aredn", "@iperf[0]", "enable") == "0" then
diff --git a/files/www/cgi-bin/ping b/files/www/cgi-bin/ping
new file mode 100755
index 00000000..2b307f6d
--- /dev/null
+++ b/files/www/cgi-bin/ping
@@ -0,0 +1,74 @@
+#!/usr/bin/lua
+--[[
+
+ Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ Copyright (C) 2022-2024 Tim Wilkinson
+ See Contributors file for additional contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation version 3 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ Additional Terms:
+
+ Additional use restrictions exist on the AREDN® trademark and logo.
+ See AREDNLicense.txt for more info.
+
+ Attributions to the AREDN® Project must be retained in the source code.
+ If importing this code into a new or existing project attribution
+ to the AREDN® project must be added to the source code.
+
+ You must not misrepresent the origin of the material contained within.
+
+ Modified versions must be modified to attribute to the original source
+ and be marked in reasonable ways as differentiate it from the original
+ version
+
+--]]
+
+require("uci")
+require("nixio")
+require("aredn.utils")
+require("aredn.info")
+
+local node = aredn.info.get_nvram("node")
+
+local q = os.getenv("QUERY_STRING") or ""
+local server = q:match("server=([^&]*)")
+
+print("Content-type: text/html\r")
+print("Cache-Control: no-store\r")
+print("Access-Control-Allow-Origin: *\r")
+print("\r")
+if not server then
+ print("
ERROR
Provide a server name to run a test between this client and a server [/cgi-bin/ping?server=<ServerName>
")
+elseif server:match("[^%w%-%.]") then
+ print("ERROR
Illegal server name
")
+else
+ if not server:match("%.") then
+ server = server .. ".local.mesh"
+ end
+ local running = io.popen("/bin/ping -c 5 -w 10 " .. server .. " 2>&1")
+ if not running then
+ print("ERROR
ping failed
")
+ else
+ print("SUCCESS")
+ print("
Client: " .. node .. "\nServer: " .. server)
+ io.flush()
+ for line in running:lines()
+ do
+ print(line)
+ io.flush()
+ end
+ running:close()
+ print("
")
+ end
+end
diff --git a/files/www/cgi-bin/setup b/files/www/cgi-bin/setup
index 20153338..9a223567 100755
--- a/files/www/cgi-bin/setup
+++ b/files/www/cgi-bin/setup
@@ -937,7 +937,7 @@ function noLocation() {
req.addEventListener("load", function() {
try {
const json = JSON.parse(this.responseText);
- foundLocation({ coords: { latitude: json.lat, longitude: json.lon }})
+ foundLocation({ coords: { latitude: json.lat, longitude: json.lon}})
}
catch (_) {
}
diff --git a/files/www/cgi-bin/status b/files/www/cgi-bin/status
index 05c8843e..19b83771 100755
--- a/files/www/cgi-bin/status
+++ b/files/www/cgi-bin/status
@@ -281,6 +281,8 @@ end
-- nav buttons
html.navbar_user("status", config_mode)
+html.print([[New UI]])
+
html.print("")
if config_mode then
diff --git a/files/www/cgi-bin/traceroute b/files/www/cgi-bin/traceroute
new file mode 100755
index 00000000..003ed340
--- /dev/null
+++ b/files/www/cgi-bin/traceroute
@@ -0,0 +1,74 @@
+#!/usr/bin/lua
+--[[
+
+ Part of AREDN® -- Used for creating Amateur Radio Emergency Data Networks
+ Copyright (C) 2022-2024 Tim Wilkinson
+ See Contributors file for additional contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation version 3 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+ Additional Terms:
+
+ Additional use restrictions exist on the AREDN® trademark and logo.
+ See AREDNLicense.txt for more info.
+
+ Attributions to the AREDN® Project must be retained in the source code.
+ If importing this code into a new or existing project attribution
+ to the AREDN® project must be added to the source code.
+
+ You must not misrepresent the origin of the material contained within.
+
+ Modified versions must be modified to attribute to the original source
+ and be marked in reasonable ways as differentiate it from the original
+ version
+
+--]]
+
+require("uci")
+require("nixio")
+require("aredn.utils")
+require("aredn.info")
+
+local node = aredn.info.get_nvram("node")
+
+local q = os.getenv("QUERY_STRING") or ""
+local server = q:match("server=([^&]*)")
+
+print("Content-type: text/html\r")
+print("Cache-Control: no-store\r")
+print("Access-Control-Allow-Origin: *\r")
+print("\r")
+if not server then
+ print("ERROR
Provide a server name to run a test between this client and a server [/cgi-bin/traceroute?server=<ServerName>
")
+elseif server:match("[^%w%-%.]") then
+ print("ERROR
Illegal server name
")
+else
+ if not server:match("%.") then
+ server = server .. ".local.mesh"
+ end
+ local running = io.popen("/bin/traceroute -q 1 -w 1 " .. server .. " 2>&1")
+ if not running then
+ print("ERROR
traceroute failed
")
+ else
+ print("SUCCESS")
+ print("
Client: " .. node .. "\nServer: " .. server)
+ io.flush()
+ for line in running:lines()
+ do
+ print(line)
+ io.flush()
+ end
+ running:close()
+ print("
")
+ end
+end
diff --git a/files/www/index.html b/files/www/index.html
index 94784c0a..84087d73 100644
--- a/files/www/index.html
+++ b/files/www/index.html
@@ -5,10 +5,10 @@
-
+
Mesh Node Administrative Console
-Redirecting to status page
+Redirecting to status page