{% /* * 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]+)[ \t]-[ \t](.+)/; for (let l = f.read("line"); length(l); l = f.read("line")) { const m = match(l, re); if (m) { installed_pkgs[m[1]] = true; if (!perm_pkgs[m[1]]) { 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(`
Package installed
`); } else { if (system("/bin/opkg update > /tmp/pkg.log 2>&1") !== 0) { print(`
${log()}
`); } else { if (system(`/bin/opkg -force-overwrite install ${ipk} > /tmp/pkg.log 2>&1`) === 0) { recordPackage("upload", packagename, ipk); print(`
Package installed
`); } else { print(`
${log()}
`); } } } const po = getPackageOptions(); print(``); print(``); fs.unlink(ipk); fs.unlink(request.args.packagename); fs.unlink("/tmp/pkg.log"); return; } else if (request.env.REQUEST_METHOD === "GET" && index(request.env.QUERY_STRING, "d=") === 0) { 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/pkg.log"); const ipk = substr(request.env.QUERY_STRING, 2); uhttpd.send(`event: progress\r\ndata: 10\r\n\r\n`); if (system(`/bin/opkg -force-overwrite install ${ipk} > /dev/null 2>&1`) === 0) { uhttpd.send(`event: progress\r\ndata: 100\r\n\r\n`); recordPackage("download", ipk); uhttpd.send(`event: close\r\ndata: ${sprintf("%J", getPackageOptions())}\r\n\r\n`); } else { uhttpd.send(`event: progress\r\ndata: 20\r\n\r\n`); if (system("/bin/opkg update > /tmp/pkg.log 2>&1") !== 0) { uhttpd.send(`event: error\r\ndata: ${log()}\r\n\r\n`); } else { uhttpd.send(`event: progress\r\ndata: 40\r\n\r\n`); if (system(`/bin/opkg -force-overwrite install ${ipk} > /tmp/pkg.log 2>&1`) === 0) { uhttpd.send(`event: progress\r\ndata: 100\r\n\r\n`); recordPackage("download", ipk); uhttpd.send(`event: close\r\ndata: ${sprintf("%J", getPackageOptions())}\r\n`); } else { uhttpd.send(`event: error\r\ndata: ${log()}\r\n\r\n`); } } } fs.unlink("/tmp/pkg.log"); return; } else if (request.env.REQUEST_METHOD === "GET" && index(request.env.QUERY_STRING, "r=") === 0) { const ipk = substr(request.env.QUERY_STRING, 2); if (system(`/bin/opkg remove ${ipk} > /tmp/pkg.log 2>&1`) === 0) { recordPackage("remove", ipk); print(`
Package removed
`); const po = getPackageOptions(); print(``); print(``); } else { print(`
${log()}
`); } return; } else if (request.env.REQUEST_METHOD === "GET" && index(request.env.QUERY_STRING, "i=") === 0) { const ipk = substr(request.env.QUERY_STRING, 2); if (system(`/bin/opkg info ${ipk} > /tmp/pkg.log 2>&1`) === 0) { print(`
${log()}
`); } 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"); const pulines = 7; const f = fs.popen("/bin/opkg update 2>&1"); if (!f) { uhttpd.send(`event: error\r\ndata: package update failed\r\n\r\n`); return; } let count = 0; const re = /^Updated/; for (let l = f.read("line"); length(l); l = f.read("line")) { if (match(l, re)) { count++; uhttpd.send(`event: progress\r\ndata: ${100 * count / pulines}\r\n\r\n`); } } uhttpd.send(`event: progress\r\ndata: 100\r\n\r\n`); f.close(); uhttpd.send(`event: close\r\ndata: ${sprintf("%J", getPackageOptions())}\r\n\r\n`); return; } else if (request.env.REQUEST_METHOD === "PUT" && "packageurl" in request.args) { if (match(request.args.packageurl, constants.reUrl)) { configuration.prepareChanges(); uciMesh.set("aredn", "@downloads[0]", "packages_default", request.args.packageurl); uciMesh.commit("aredn"); print(_R("changes")); } return; } %} {% const po = getPackageOptions(); %}
{{_R("dialog-header", "Packages")}}
{{_R("dialog-messages")}}
Download Package
Download package from an AREDN server.
{{_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")}}