From 07ec56227d43d18a600e3fcb6fb18c45201cc608 Mon Sep 17 00:00:00 2001 From: billz Date: Fri, 20 Dec 2024 11:45:15 -0800 Subject: [PATCH 01/87] Minor: mode change --- includes/footer.php | 0 includes/restapi.php | 0 templates/system/plugins.php | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+) mode change 100644 => 100755 includes/footer.php mode change 100644 => 100755 includes/restapi.php create mode 100644 templates/system/plugins.php diff --git a/includes/footer.php b/includes/footer.php old mode 100644 new mode 100755 diff --git a/includes/restapi.php b/includes/restapi.php old mode 100644 new mode 100755 diff --git a/templates/system/plugins.php b/templates/system/plugins.php new file mode 100644 index 00000000..beef0bfa --- /dev/null +++ b/templates/system/plugins.php @@ -0,0 +1,34 @@ + +
+

+ + + +
+
+ +
+ +
+
+
+ +
+
+ +
+ +
+
+
+ + + +
+ From 36b02851582234413a608196f240fb557ccc42c7 Mon Sep 17 00:00:00 2001 From: billz Date: Fri, 20 Dec 2024 13:33:46 -0800 Subject: [PATCH 02/87] Define RASPI_PLUGINS_URL --- config/config.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/config.php b/config/config.php index 2950632d..afd386a2 100755 --- a/config/config.php +++ b/config/config.php @@ -38,6 +38,9 @@ define('RASPI_ACCESS_CHECK_DNS', 'one.one.one.one'); // Constant for the GitHub API latest release endpoint define('RASPI_API_ENDPOINT', 'https://api.github.com/repos/RaspAP/raspap-webgui/releases/latest'); +// Constant for the GitHub plugin submodules URL +define("RASPI_PLUGINS_URL", "https://raw.githubusercontent.com/RaspAP/plugins"); + // Constant for the 5GHz wireless regulatory domain define("RASPI_5GHZ_CHANNEL_MIN", 100); define("RASPI_5GHZ_CHANNEL_MAX", 192); From 66e35c564c38df2840c541e871df19c02da55218 Mon Sep 17 00:00:00 2001 From: billz Date: Fri, 20 Dec 2024 13:34:05 -0800 Subject: [PATCH 03/87] Initial commit --- templates/system/plugins.php | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/templates/system/plugins.php b/templates/system/plugins.php index beef0bfa..2595198d 100644 --- a/templates/system/plugins.php +++ b/templates/system/plugins.php @@ -7,28 +7,15 @@
-
- +
+ Install to download and activate a plugin from the list. Uninstall removes an existing plugin."); ?>
+
-
-
- -
- -
-
-
- -
From 117370efcff60ccd3eeba7ce20a19e17db17a4ea Mon Sep 17 00:00:00 2001 From: billz Date: Fri, 20 Dec 2024 13:34:58 -0800 Subject: [PATCH 04/87] Add plugins tab, render system/plugins template --- templates/system.php | 2 ++ templates/system/tools.php | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/templates/system.php b/templates/system.php index 29950e39..4c17bcf2 100755 --- a/templates/system.php +++ b/templates/system.php @@ -18,6 +18,7 @@ +
@@ -26,6 +27,7 @@ +
diff --git a/templates/system/tools.php b/templates/system/tools.php index a30601db..32eb19ca 100644 --- a/templates/system/tools.php +++ b/templates/system/tools.php @@ -1,4 +1,4 @@ - +

@@ -36,4 +36,3 @@
- From 89c4f16e45a315118f973e058bc40c7df2e7866e Mon Sep 17 00:00:00 2001 From: billz Date: Fri, 20 Dec 2024 13:42:53 -0800 Subject: [PATCH 05/87] Get plugin submodules, fetch manifest details + format output --- includes/system.php | 236 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 192 insertions(+), 44 deletions(-) diff --git a/includes/system.php b/includes/system.php index 31feaf79..868082d5 100755 --- a/includes/system.php +++ b/includes/system.php @@ -85,53 +85,22 @@ function DisplaySystem(&$extraFooterScripts) $kernel = $system->kernelVersion(); $systime = $system->systime(); $revision = $system->rpiRevision(); - - // mem used + + // memory use $memused = $system->usedMemory(); - $memused_status = "primary"; - if ($memused > 90) { - $memused_status = "danger"; - $memused_led = "service-status-down"; - } elseif ($memused > 75) { - $memused_status = "warning"; - $memused_led = "service-status-warn"; - } elseif ($memused > 0) { - $memused_status = "success"; - $memused_led = "service-status-up"; - } + $memStatus = getMemStatus($memused); + $memused_status = $memStatus['status']; + $memused_led = $memStatus['led']; // cpu load $cpuload = $system->systemLoadPercentage(); - if ($cpuload > 90) { - $cpuload_status = "danger"; - } elseif ($cpuload > 75) { - $cpuload_status = "warning"; - } elseif ($cpuload >= 0) { - $cpuload_status = "success"; - } + $cpuload_status = getCPULoadStatus($cpuload); // cpu temp $cputemp = $system->systemTemperature(); - if ($cputemp > 70) { - $cputemp_status = "danger"; - $cputemp_led = "service-status-down"; - } elseif ($cputemp > 50) { - $cputemp_status = "warning"; - $cputemp_led = "service-status-warn"; - } else { - $cputemp_status = "success"; - $cputemp_led = "service-status-up"; - } - - // hostapd status - $hostapd = $system->hostapdStatus(); - if ($hostapd[0] == 1) { - $hostapd_status = "active"; - $hostapd_led = "service-status-up"; - } else { - $hostapd_status = "inactive"; - $hostapd_led = "service-status-down"; - } + $cpuStatus = getCPUTempStatus($cputemp); + $cputemp_status = $cpuStatus['status']; + $cputemp_led = $cpuStatus['led']; // theme options $themes = [ @@ -147,6 +116,9 @@ function DisplaySystem(&$extraFooterScripts) $extraFooterScripts[] = array('src'=>'app/js/huebee.js', 'defer'=>false); $logLimit = isset($_SESSION['log_limit']) ? $_SESSION['log_limit'] : RASPI_LOG_SIZE_LIMIT; + $plugins = getUserPlugins(); + $pluginsTable = getHTMLPluginsTable($plugins); + echo renderTemplate("system", compact( "arrLocales", "status", @@ -167,11 +139,187 @@ function DisplaySystem(&$extraFooterScripts) "cputemp", "cputemp_status", "cputemp_led", - "hostapd", - "hostapd_status", - "hostapd_led", "themes", "selectedTheme", - "logLimit" + "logLimit", + "pluginsTable" )); } + +/** + * Returns user plugin details from associated manifest.json files + * + * @return array $plugins + */ +function getUserPlugins() +{ + try { + $submodules = getSubmodules(RASPI_PLUGINS_URL); + $plugins = []; + foreach ($submodules as $submodule) { + $manifestUrl = $submodule['url'] .'/blob/master/manifest.json?raw=true'; + $manifest = getPluginManifest($manifestUrl); + + if ($manifest) { + $plugins[] = [ + 'version' => $manifest['version'] ?? 'unknown', + 'name' => $manifest['name'] ?? 'unknown', + 'description' => $manifest['description'] ?? 'No description provided', + 'plugin_uri' => $manifest['plugin_uri'] ?? $submodule['url'], + 'fa-icon' => $manifest['icon'] ?? 'fas fa-plug', + ]; + } + } + return $plugins; + } catch (Exception $e) { + echo "An error occured: " .$e->getMessage(); + } +} + +/** + * Returns git submodules for the specified repository + * + * @param string $repoURL + * @return array $submodules + */ +function getSubmodules(string $repoUrl): array +{ + $gitmodulesUrl = $repoUrl . '/refs/heads/master/.gitmodules'; + $gitmodulesContent = file_get_contents($gitmodulesUrl); + + if ($gitmodulesContent === false) { + throw new Exception('Unable to fetch .gitmodules file from the repository'); + } + + $submodules = []; + $lines = explode("\n", $gitmodulesContent); + $currentSubmodule = []; + + foreach ($lines as $line) { + $line = trim($line); + + if (strpos($line, '[submodule "') === 0) { + if (!empty($currentSubmodule)) { + $submodules[] = $currentSubmodule; + } + $currentSubmodule = []; + } elseif (strpos($line, 'path = ') === 0) { + $currentSubmodule['path'] = substr($line, strlen('path = ')); + } elseif (strpos($line, 'url = ') === 0) { + $currentSubmodule['url'] = substr($line, strlen('url = ')); + } + } + + if (!empty($currentSubmodule)) { + $submodules[] = $currentSubmodule; + } + + return $submodules; +} + +/** + * Returns a plugin's associated manifest in JSON format + * + * @param string $url + * @return array $json + */ +function getPluginManifest(string $url): ?array +{ + $options = [ + 'http' => [ + 'method' => 'GET', + 'follow_location' => 1, + ], + ]; + + $context = stream_context_create($options); + $content= file_get_contents($url, false, $context); + + if ($content === false) { + return null; + } + $json = json_decode($content, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + return null; + } + return $json; +} + +/** + * Returns a list of available plugins formatted as an HTML table + * + * @param array $plugins + * @return string $html + */ +function getHTMLPluginsTable(array $plugins): string +{ + $html = ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + + foreach ($plugins as $plugin) { + $name = ''. htmlspecialchars($plugin['name']). ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + $html .= '
NameVersionDescription
' . $name . '' . htmlspecialchars($plugin['version']) . '' . htmlspecialchars($plugin['description']) . '
'; + return $html; +} + +function getMemStatus($memused): array +{ + $memused_status = "primary"; + $memused_led = ""; + + if ($memused > 90) { + $memused_status = "danger"; + $memused_led = "service-status-down"; + } elseif ($memused > 75) { + $memused_status = "warning"; + $memused_led = "service-status-warn"; + } elseif ($memused > 0) { + $memused_status = "success"; + $memused_led = "service-status-up"; + } + + return [ + 'status' => $memused_status, + 'led' => $memused_led + ]; +} + +function getCPULoadStatus($cpuload): string +{ + if ($cpuload > 90) { + $status = "danger"; + } elseif ($cpuload > 75) { + $status = "warning"; + } elseif ($cpuload >= 0) { + $status = "success"; + } + return $status; +} + +function getCPUTempStatus($cputemp): array +{ + if ($cputemp > 70) { + $cputemp_status = "danger"; + $cputemp_led = "service-status-down"; + } elseif ($cputemp > 50) { + $cputemp_status = "warning"; + $cputemp_led = "service-status-warn"; + } else { + $cputemp_status = "success"; + $cputemp_led = "service-status-up"; + } + return [ + 'status' => $cputemp_status, + 'led' => $cputemp_led + ]; +} + From 247b35b254fe32c329bb20407aaff58a15a1611f Mon Sep 17 00:00:00 2001 From: billz Date: Fri, 20 Dec 2024 18:52:09 -0800 Subject: [PATCH 06/87] Initial commit --- src/RaspAP/Plugins/PluginInstaller.php | 52 ++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/RaspAP/Plugins/PluginInstaller.php diff --git a/src/RaspAP/Plugins/PluginInstaller.php b/src/RaspAP/Plugins/PluginInstaller.php new file mode 100644 index 00000000..74a90d70 --- /dev/null +++ b/src/RaspAP/Plugins/PluginInstaller.php @@ -0,0 +1,52 @@ + + * @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace RaspAP\Plugins; + +class PluginInstaller +{ + private static $instance = null; + + public function __construct() + { + $this->pluginPath = 'plugins'; + } + + // Returns a single instance of PluginInstaller + public static function getInstance(): PluginInstaller + { + if (self::$instance === null) { + self::$instance = new PluginInstaller(); + } + return self::$instance; + } + + public function getPlugins(): array + { + $plugins = []; + if (file_exists($this->pluginPath)) { + $directories = scandir($this->pluginPath); + + foreach ($directories as $directory) { + $pluginClass = "RaspAP\\Plugins\\$directory\\$directory"; + $pluginFile = $this->pluginPath . "/$directory/$directory.php"; + + if (file_exists($pluginFile) && class_exists($pluginClass)) { + $plugins[] = $pluginClass; + } + } + } + return $plugins; + } + +} + From 9b087f88a75be9e24d41fed73573eb110073bf60 Mon Sep 17 00:00:00 2001 From: billz Date: Fri, 20 Dec 2024 18:53:35 -0800 Subject: [PATCH 07/87] Call PluginInstaller::getInstance(), set install option --- includes/system.php | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/includes/system.php b/includes/system.php index 868082d5..09467cf8 100755 --- a/includes/system.php +++ b/includes/system.php @@ -153,6 +153,9 @@ function DisplaySystem(&$extraFooterScripts) */ function getUserPlugins() { + $pluginInstaller = \RaspAP\Plugins\PluginInstaller::getInstance(); + $installedPlugins = $pluginInstaller->getPlugins(); + try { $submodules = getSubmodules(RASPI_PLUGINS_URL); $plugins = []; @@ -161,12 +164,24 @@ function getUserPlugins() $manifest = getPluginManifest($manifestUrl); if ($manifest) { + $namespace = $manifest['namespace'] ?? ''; + $installed = false; + + foreach ($installedPlugins as $plugin) { + if (str_contains($plugin, $namespace)) { + $installed = true; + break; + } + } + $plugins[] = [ 'version' => $manifest['version'] ?? 'unknown', 'name' => $manifest['name'] ?? 'unknown', 'description' => $manifest['description'] ?? 'No description provided', 'plugin_uri' => $manifest['plugin_uri'] ?? $submodule['url'], + 'namespace' => $namespace, 'fa-icon' => $manifest['icon'] ?? 'fas fa-plug', + 'installed' => $installed ]; } } @@ -218,7 +233,8 @@ function getSubmodules(string $repoUrl): array } /** - * Returns a plugin's associated manifest in JSON format + * Decodes a plugin's associated manifest JSON. + * Returns an array of key-value pairs * * @param string $url * @return array $json @@ -233,7 +249,7 @@ function getPluginManifest(string $url): ?array ]; $context = stream_context_create($options); - $content= file_get_contents($url, false, $context); + $content = file_get_contents($url, false, $context); if ($content === false) { return null; @@ -259,13 +275,26 @@ function getHTMLPluginsTable(array $plugins): string $html .= 'Name'; $html .= 'Version'; $html .= 'Description'; + $html .= ''; $html .= ''; foreach ($plugins as $plugin) { - $name = ''. htmlspecialchars($plugin['name']). ''; - $html .= '' . $name . ''; - $html .= '' . htmlspecialchars($plugin['version']) . ''; - $html .= '' . htmlspecialchars($plugin['description']) . ''; + $installed = $plugin['installed']; + if ($installed === true ) { + $status = 'Installed'; + } else { + $status = ''; + } + $name = '' + . htmlspecialchars($plugin['name']). ''; + $html .= '' .$name. ''; + $html .= '' .htmlspecialchars($plugin['version']). ''; + $html .= '' .htmlspecialchars($plugin['description']). ''; + $html .= '' .$status. ''; } $html .= ''; return $html; From ee38614334676c3abe3ead428e3a412f1833ad97 Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 21 Dec 2024 10:39:46 -0800 Subject: [PATCH 08/87] Initial commit --- ajax/plugins/get_manifest.php | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100755 ajax/plugins/get_manifest.php diff --git a/ajax/plugins/get_manifest.php b/ajax/plugins/get_manifest.php new file mode 100755 index 00000000..70b18c9f --- /dev/null +++ b/ajax/plugins/get_manifest.php @@ -0,0 +1,32 @@ +getPluginManifest($manifestUrl); + if ($manifest) { + echo json_encode($manifest); + } else { + http_response_code(404); + echo json_encode(['error' => 'Plugin manifest not found']); + } + } catch (Exception $e) { + http_response_code(500); + echo json_encode(['error' => 'An unexpected error occurred']); + } +} else { + http_response_code(400); + echo json_encode(['error' => 'Plugin URI is required']); + exit; +} + From 6785cc1104ed658be06befcea7326e03613f27f6 Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 21 Dec 2024 10:40:41 -0800 Subject: [PATCH 09/87] Move getPluginManifest() to PluginInstaller class --- includes/system.php | 36 +++----------------------- src/RaspAP/Plugins/PluginInstaller.php | 34 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/includes/system.php b/includes/system.php index 09467cf8..b0f7e21d 100755 --- a/includes/system.php +++ b/includes/system.php @@ -161,7 +161,7 @@ function getUserPlugins() $plugins = []; foreach ($submodules as $submodule) { $manifestUrl = $submodule['url'] .'/blob/master/manifest.json?raw=true'; - $manifest = getPluginManifest($manifestUrl); + $manifest = $pluginInstaller->getPluginManifest($manifestUrl); if ($manifest) { $namespace = $manifest['namespace'] ?? ''; @@ -232,36 +232,6 @@ function getSubmodules(string $repoUrl): array return $submodules; } -/** - * Decodes a plugin's associated manifest JSON. - * Returns an array of key-value pairs - * - * @param string $url - * @return array $json - */ -function getPluginManifest(string $url): ?array -{ - $options = [ - 'http' => [ - 'method' => 'GET', - 'follow_location' => 1, - ], - ]; - - $context = stream_context_create($options); - $content = file_get_contents($url, false, $context); - - if ($content === false) { - return null; - } - $json = json_decode($content, true); - - if (json_last_error() !== JSON_ERROR_NONE) { - return null; - } - return $json; -} - /** * Returns a list of available plugins formatted as an HTML table * @@ -284,8 +254,8 @@ function getHTMLPluginsTable(array $plugins): string $status = 'Installed'; } else { $status = ''; + name="install-plugin" data-bs-toggle="modal" data-bs-target="#install-user-plugin" + data-record-id="'.htmlspecialchars($plugin['plugin_uri']).'" />' . _("Install now") .''; } $name = 'profile)` : '') : 'Unknown' + ); + $('#plugin-license').text(manifestData.license || 'Unknown'); + $('#plugin-locale').text(manifestData.default_locale || 'Unknown'); + $('#plugin-configuration').html(formatProperty(manifestData.configuration || {})); + $('#plugin-dependencies').html(formatProperty(manifestData.dependencies || {})); + $('#plugin-sudoers').html(formatProperty(manifestData.sudoers || [])); + $('#plugin-user-name').html(manifestData.user_nonprivileged.name || {}); + console.log(manifestData); + } +}); + +function formatProperty(prop) { + if (Array.isArray(prop)) { + if (typeof prop[0] === 'object') { + return prop.map(item => { + return Object.entries(item) + .map(([key, value]) => `${key}: ${value}`) + .join('
'); + }).join('

'); + } + return prop.map(line => `${line}
`).join(''); + } + if (typeof prop === 'object') { + return Object.entries(prop) + .map(([key, value]) => `${key}: ${value}`) + .join('
'); + } + return prop || 'None'; +} + $(document).ready(function(){ $("#PanelManual").hide(); $('.ip_address').mask('0ZZ.0ZZ.0ZZ.0ZZ', { @@ -507,7 +549,6 @@ $('#wg-upload,#wg-manual').on('click', function (e) { } }); -// Add the following code if you want the name of the file appear on select $(".custom-file-input").on("change", function() { var fileName = $(this).val().split("\\").pop(); $(this).siblings(".custom-file-label").addClass("selected").html(fileName); From c3968ba42e62f14ab45c5850091cfd8c66d622a5 Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 21 Dec 2024 22:40:55 -0800 Subject: [PATCH 11/87] Encapsulate plugin related functions in PluginInstaller class --- includes/system.php | 129 +------------------------ src/RaspAP/Plugins/PluginInstaller.php | 123 ++++++++++++++++++++++- 2 files changed, 124 insertions(+), 128 deletions(-) diff --git a/includes/system.php b/includes/system.php index b0f7e21d..6ef6b74e 100755 --- a/includes/system.php +++ b/includes/system.php @@ -9,6 +9,7 @@ require_once 'config.php'; function DisplaySystem(&$extraFooterScripts) { $status = new \RaspAP\Messages\StatusMessage; + $pluginInstaller = \RaspAP\Plugins\PluginInstaller::getInstance(); if (isset($_POST['SaveLanguage'])) { if (isset($_POST['locale'])) { @@ -116,8 +117,8 @@ function DisplaySystem(&$extraFooterScripts) $extraFooterScripts[] = array('src'=>'app/js/huebee.js', 'defer'=>false); $logLimit = isset($_SESSION['log_limit']) ? $_SESSION['log_limit'] : RASPI_LOG_SIZE_LIMIT; - $plugins = getUserPlugins(); - $pluginsTable = getHTMLPluginsTable($plugins); + $plugins = $pluginInstaller->getUserPlugins(); + $pluginsTable = $pluginInstaller->getHTMLPluginsTable($plugins); echo renderTemplate("system", compact( "arrLocales", @@ -146,130 +147,6 @@ function DisplaySystem(&$extraFooterScripts) )); } -/** - * Returns user plugin details from associated manifest.json files - * - * @return array $plugins - */ -function getUserPlugins() -{ - $pluginInstaller = \RaspAP\Plugins\PluginInstaller::getInstance(); - $installedPlugins = $pluginInstaller->getPlugins(); - - try { - $submodules = getSubmodules(RASPI_PLUGINS_URL); - $plugins = []; - foreach ($submodules as $submodule) { - $manifestUrl = $submodule['url'] .'/blob/master/manifest.json?raw=true'; - $manifest = $pluginInstaller->getPluginManifest($manifestUrl); - - if ($manifest) { - $namespace = $manifest['namespace'] ?? ''; - $installed = false; - - foreach ($installedPlugins as $plugin) { - if (str_contains($plugin, $namespace)) { - $installed = true; - break; - } - } - - $plugins[] = [ - 'version' => $manifest['version'] ?? 'unknown', - 'name' => $manifest['name'] ?? 'unknown', - 'description' => $manifest['description'] ?? 'No description provided', - 'plugin_uri' => $manifest['plugin_uri'] ?? $submodule['url'], - 'namespace' => $namespace, - 'fa-icon' => $manifest['icon'] ?? 'fas fa-plug', - 'installed' => $installed - ]; - } - } - return $plugins; - } catch (Exception $e) { - echo "An error occured: " .$e->getMessage(); - } -} - -/** - * Returns git submodules for the specified repository - * - * @param string $repoURL - * @return array $submodules - */ -function getSubmodules(string $repoUrl): array -{ - $gitmodulesUrl = $repoUrl . '/refs/heads/master/.gitmodules'; - $gitmodulesContent = file_get_contents($gitmodulesUrl); - - if ($gitmodulesContent === false) { - throw new Exception('Unable to fetch .gitmodules file from the repository'); - } - - $submodules = []; - $lines = explode("\n", $gitmodulesContent); - $currentSubmodule = []; - - foreach ($lines as $line) { - $line = trim($line); - - if (strpos($line, '[submodule "') === 0) { - if (!empty($currentSubmodule)) { - $submodules[] = $currentSubmodule; - } - $currentSubmodule = []; - } elseif (strpos($line, 'path = ') === 0) { - $currentSubmodule['path'] = substr($line, strlen('path = ')); - } elseif (strpos($line, 'url = ') === 0) { - $currentSubmodule['url'] = substr($line, strlen('url = ')); - } - } - - if (!empty($currentSubmodule)) { - $submodules[] = $currentSubmodule; - } - - return $submodules; -} - -/** - * Returns a list of available plugins formatted as an HTML table - * - * @param array $plugins - * @return string $html - */ -function getHTMLPluginsTable(array $plugins): string -{ - $html = ''; - $html .= ''; - $html .= ''; - $html .= ''; - $html .= ''; - $html .= ''; - $html .= ''; - - foreach ($plugins as $plugin) { - $installed = $plugin['installed']; - if ($installed === true ) { - $status = 'Installed'; - } else { - $status = ''; - } - $name = '' - . htmlspecialchars($plugin['name']). ''; - $html .= ''; - $html .= ''; - $html .= ''; - $html .= ''; - } - $html .= '
NameVersionDescription
' .$name. '' .htmlspecialchars($plugin['version']). '' .htmlspecialchars($plugin['description']). '' .$status. '
'; - return $html; -} - function getMemStatus($memused): array { $memused_status = "primary"; diff --git a/src/RaspAP/Plugins/PluginInstaller.php b/src/RaspAP/Plugins/PluginInstaller.php index 20188b98..f6a0f96f 100644 --- a/src/RaspAP/Plugins/PluginInstaller.php +++ b/src/RaspAP/Plugins/PluginInstaller.php @@ -31,8 +31,45 @@ class PluginInstaller } /** - * Decodes a plugin's associated manifest JSON. - * Returns an array of key-value pairs + * Returns user plugin details from associated manifest.json files + * + * @return array $plugins + */ + public function getUserPlugins() + { + $installedPlugins = $this->getPlugins(); + + try { + $submodules = $this->getSubmodules(RASPI_PLUGINS_URL); + $plugins = []; + foreach ($submodules as $submodule) { + $manifestUrl = $submodule['url'] .'/blob/master/manifest.json?raw=true'; + $manifest = $this->getPluginManifest($manifestUrl); + + if ($manifest) { + $installed = false; + + foreach ($installedPlugins as $plugin) { + if (str_contains($plugin, $plugins['manifest']['namespace'])) { + $installed = true; + break; + } + } + + $plugins[] = [ + 'manifest' => $manifest, + 'installed' => $installed + ]; + } + } + return $plugins; + } catch (Exception $e) { + echo "An error occured: " .$e->getMessage(); + } + } + + /** + * Retrieves a plugin's associated manifest JSON * * @param string $url * @return array $json @@ -60,6 +97,88 @@ class PluginInstaller return $json; } + /** + * Returns git submodules for the specified repository + * + * @param string $repoURL + * @return array $submodules + */ + public function getSubmodules(string $repoUrl): array + { + $gitmodulesUrl = $repoUrl . '/refs/heads/master/.gitmodules'; + $gitmodulesContent = file_get_contents($gitmodulesUrl); + + if ($gitmodulesContent === false) { + throw new Exception('Unable to fetch .gitmodules file from the repository'); + } + + $submodules = []; + $lines = explode("\n", $gitmodulesContent); + $currentSubmodule = []; + + foreach ($lines as $line) { + $line = trim($line); + + if (strpos($line, '[submodule "') === 0) { + if (!empty($currentSubmodule)) { + $submodules[] = $currentSubmodule; + } + $currentSubmodule = []; + } elseif (strpos($line, 'path = ') === 0) { + $currentSubmodule['path'] = substr($line, strlen('path = ')); + } elseif (strpos($line, 'url = ') === 0) { + $currentSubmodule['url'] = substr($line, strlen('url = ')); + } + } + + if (!empty($currentSubmodule)) { + $submodules[] = $currentSubmodule; + } + + return $submodules; + } + + /** + * Returns a list of available plugins formatted as an HTML table + * + * @param array $plugins + * @return string $html + */ + public function getHTMLPluginsTable(array $plugins): string + { + $html = ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + + foreach ($plugins as $plugin) { + + $manifest = htmlspecialchars(json_encode($plugin['manifest']), ENT_QUOTES, 'UTF-8'); + $installed = $plugin['installed']; + if ($installed === true ) { + $status = 'Installed'; + } else { + $button = ''; + } + $name = '' + . htmlspecialchars($plugin['manifest']['name']). ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + $html .= '
NameVersionDescription
' .$name. '' .htmlspecialchars($plugin['manifest']['version']). '' .htmlspecialchars($plugin['manifest']['description']). '' .$button. '
'; + return $html; + } + + /** Returns an array of installed plugins in pluginPath * * @return array $plugins From 2cb66660c5d810aac843eb079515e12afa921373 Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 21 Dec 2024 22:42:34 -0800 Subject: [PATCH 12/87] Create modal install-user-plugin dialog, update template text --- ajax/plugins/get_manifest.php | 32 ------------------- templates/system.php | 59 +++++++++++++++++++++++++++++++++++ templates/system/plugins.php | 6 ++-- 3 files changed, 61 insertions(+), 36 deletions(-) delete mode 100755 ajax/plugins/get_manifest.php diff --git a/ajax/plugins/get_manifest.php b/ajax/plugins/get_manifest.php deleted file mode 100755 index 70b18c9f..00000000 --- a/ajax/plugins/get_manifest.php +++ /dev/null @@ -1,32 +0,0 @@ -getPluginManifest($manifestUrl); - if ($manifest) { - echo json_encode($manifest); - } else { - http_response_code(404); - echo json_encode(['error' => 'Plugin manifest not found']); - } - } catch (Exception $e) { - http_response_code(500); - echo json_encode(['error' => 'An unexpected error occurred']); - } -} else { - http_response_code(400); - echo json_encode(['error' => 'Plugin URI is required']); - exit; -} - diff --git a/templates/system.php b/templates/system.php index 4c17bcf2..dcc4679d 100755 --- a/templates/system.php +++ b/templates/system.php @@ -107,3 +107,62 @@ + + + diff --git a/templates/system/plugins.php b/templates/system/plugins.php index 2595198d..71677d19 100644 --- a/templates/system/plugins.php +++ b/templates/system/plugins.php @@ -3,19 +3,17 @@

-
-
- Install to download and activate a plugin from the list. Uninstall removes an existing plugin."); ?> +
+ Details for more information and to install a plugin. Uninstall removes an existing plugin."); ?>
-
From e5987a6b591a8b0e537642719233672632372b14 Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 22 Dec 2024 09:27:40 -0800 Subject: [PATCH 13/87] Update w/ link to plugin_uri --- app/js/custom.js | 5 ++++- templates/system.php | 8 ++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/js/custom.js b/app/js/custom.js index 354f9cd6..1b73de31 100644 --- a/app/js/custom.js +++ b/app/js/custom.js @@ -473,6 +473,10 @@ $('#install-user-plugin').on('shown.bs.modal', function (e) { var manifestData = button.data('plugin-manifest'); if (manifestData) { + $('#plugin-uri').html(manifestData.plugin_uri + ? `${manifestData.plugin_uri}` + : 'Unknown' + ); $('#plugin-icon').attr('class', `${manifestData.icon || 'fas fa-plug'} link-secondary h5 me-2`); $('#plugin-name').text(manifestData.name || 'Unknown'); $('#plugin-version').text(manifestData.version || 'Unknown'); @@ -487,7 +491,6 @@ $('#install-user-plugin').on('shown.bs.modal', function (e) { $('#plugin-dependencies').html(formatProperty(manifestData.dependencies || {})); $('#plugin-sudoers').html(formatProperty(manifestData.sudoers || [])); $('#plugin-user-name').html(manifestData.user_nonprivileged.name || {}); - console.log(manifestData); } }); diff --git a/templates/system.php b/templates/system.php index dcc4679d..bee431d8 100755 --- a/templates/system.php +++ b/templates/system.php @@ -112,7 +112,7 @@ From 92ba7df9c615c658ed970d190620401e82282386 Mon Sep 17 00:00:00 2001 From: billz Date: Wed, 25 Dec 2024 19:03:41 -0800 Subject: [PATCH 22/87] Create plugin install event handler --- app/js/custom.js | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/app/js/custom.js b/app/js/custom.js index 1b73de31..30f397b5 100644 --- a/app/js/custom.js +++ b/app/js/custom.js @@ -490,10 +490,42 @@ $('#install-user-plugin').on('shown.bs.modal', function (e) { $('#plugin-configuration').html(formatProperty(manifestData.configuration || {})); $('#plugin-dependencies').html(formatProperty(manifestData.dependencies || {})); $('#plugin-sudoers').html(formatProperty(manifestData.sudoers || [])); - $('#plugin-user-name').html(manifestData.user_nonprivileged.name || {}); + $('#plugin-user-name').html(manifestData.user_nonprivileged.name || 'None'); } }); +$('#js-install-plugin-confirm').on('click', function (e) { + var progressText = $('#js-install-plugin-confirm').attr('data-message'); + var successHtml = $('#plugin-install-message').attr('data-message'); + var closeHtml = $('#js-system-reset-cancel').attr('data-message'); + var pluginUri = $('#plugin-uri a').attr('href'); + var pluginVersion = $('#plugin-version').text(); + var csrfToken = $('meta[name=csrf_token]').attr('content'); + + $("#install-user-plugin").modal('hide'); + $("#install-plugin-progress").modal('show'); + + $.post('ajax/plugins/do_plugin_install.php?',{'plugin_uri': pluginUri, 'plugin_version': pluginVersion, 'csrf_token': csrfToken},function(data){ + setTimeout(function(){ + response = JSON.parse(data); + if(response === true) { + $('#plugin-install-message').text(successHtml); + $('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary').addClass('fas fa-check'); + $('#js-install-plugin-ok').removeAttr("disabled"); + } else { + $('#plugin-install-message').text('An error occurred installing the plugin.'); + $('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary'); + $('#js-install-plugin-ok').removeAttr("disabled"); + } + },300); + }); +}); + +$('#js-install-plugin-ok').on('click', function (e) { + $("#install-plugin-progress").modal('hide'); + window.location.reload(); +}); + function formatProperty(prop) { if (Array.isArray(prop)) { if (typeof prop[0] === 'object') { From ad3669522474cd9fc2370d5d682c280ed8cb48b1 Mon Sep 17 00:00:00 2001 From: billz Date: Thu, 26 Dec 2024 08:04:16 -0800 Subject: [PATCH 23/87] Set/read plugin-installed flag --- app/js/custom.js | 81 +++++++++++++++----------- installers/plugin_helper.sh | 2 +- src/RaspAP/Plugins/PluginInstaller.php | 2 +- templates/system.php | 2 +- 4 files changed, 49 insertions(+), 38 deletions(-) diff --git a/app/js/custom.js b/app/js/custom.js index 30f397b5..d68c94f1 100644 --- a/app/js/custom.js +++ b/app/js/custom.js @@ -471,54 +471,65 @@ $('#js-sys-reboot, #js-sys-shutdown').on('click', function (e) { $('#install-user-plugin').on('shown.bs.modal', function (e) { var button = $(e.relatedTarget); var manifestData = button.data('plugin-manifest'); + var installed = button.data('plugin-installed'); - if (manifestData) { - $('#plugin-uri').html(manifestData.plugin_uri - ? `${manifestData.plugin_uri}` - : 'Unknown' + if (manifestData) { + $('#plugin-uri').html(manifestData.plugin_uri + ? `${manifestData.plugin_uri}` + : 'Unknown' + ); + $('#plugin-icon').attr('class', `${manifestData.icon || 'fas fa-plug'} link-secondary h5 me-2`); + $('#plugin-name').text(manifestData.name || 'Unknown'); + $('#plugin-version').text(manifestData.version || 'Unknown'); + $('#plugin-description').text(manifestData.description || 'No description provided'); + $('#plugin-author').html(manifestData.author + ? manifestData.author + (manifestData.author_uri + ? ` (profile)` : '') : 'Unknown' ); - $('#plugin-icon').attr('class', `${manifestData.icon || 'fas fa-plug'} link-secondary h5 me-2`); - $('#plugin-name').text(manifestData.name || 'Unknown'); - $('#plugin-version').text(manifestData.version || 'Unknown'); - $('#plugin-description').text(manifestData.description || 'No description provided'); - $('#plugin-author').html(manifestData.author - ? manifestData.author + (manifestData.author_uri - ? ` (profile)` : '') : 'Unknown' - ); - $('#plugin-license').text(manifestData.license || 'Unknown'); - $('#plugin-locale').text(manifestData.default_locale || 'Unknown'); - $('#plugin-configuration').html(formatProperty(manifestData.configuration || {})); - $('#plugin-dependencies').html(formatProperty(manifestData.dependencies || {})); - $('#plugin-sudoers').html(formatProperty(manifestData.sudoers || [])); - $('#plugin-user-name').html(manifestData.user_nonprivileged.name || 'None'); - } + $('#plugin-license').text(manifestData.license || 'Unknown'); + $('#plugin-locale').text(manifestData.default_locale || 'Unknown'); + $('#plugin-configuration').html(formatProperty(manifestData.configuration || {})); + $('#plugin-dependencies').html(formatProperty(manifestData.dependencies || {})); + $('#plugin-sudoers').html(formatProperty(manifestData.sudoers || [])); + $('#plugin-user-name').html(manifestData.user_nonprivileged.name || 'None'); + } + if (installed) { + $('#js-install-plugin-confirm').html('OK'); + } else { + $('#js-install-plugin-confirm').html('Install now'); + } }); $('#js-install-plugin-confirm').on('click', function (e) { var progressText = $('#js-install-plugin-confirm').attr('data-message'); var successHtml = $('#plugin-install-message').attr('data-message'); - var closeHtml = $('#js-system-reset-cancel').attr('data-message'); var pluginUri = $('#plugin-uri a').attr('href'); var pluginVersion = $('#plugin-version').text(); var csrfToken = $('meta[name=csrf_token]').attr('content'); $("#install-user-plugin").modal('hide'); - $("#install-plugin-progress").modal('show'); - $.post('ajax/plugins/do_plugin_install.php?',{'plugin_uri': pluginUri, 'plugin_version': pluginVersion, 'csrf_token': csrfToken},function(data){ - setTimeout(function(){ - response = JSON.parse(data); - if(response === true) { - $('#plugin-install-message').text(successHtml); - $('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary').addClass('fas fa-check'); - $('#js-install-plugin-ok').removeAttr("disabled"); - } else { - $('#plugin-install-message').text('An error occurred installing the plugin.'); - $('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary'); - $('#js-install-plugin-ok').removeAttr("disabled"); - } - },300); - }); + if ($('#js-install-plugin-confirm').text() === 'Install now') { + $("#install-plugin-progress").modal('show'); + + $.post('ajax/plugins/do_plugin_install.php?',{'plugin_uri': pluginUri, + 'plugin_version': pluginVersion, 'csrf_token': csrfToken},function(data){ + setTimeout(function(){ + response = JSON.parse(data); + if (response === true) { + $('#plugin-install-message').contents().first().replaceWith(successHtml); + $('#plugin-install-message').find('i') + .removeClass('fas fa-cog fa-spin link-secondary') + .addClass('fas fa-check'); + $('#js-install-plugin-ok').removeAttr("disabled"); + } else { + $('#plugin-install-message').contents().first().replaceWith('An error occurred installing the plugin.'); + $('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary'); + $('#js-install-plugin-ok').removeAttr("disabled"); + } + },200); + }); + } }); $('#js-install-plugin-ok').on('click', function (e) { diff --git a/installers/plugin_helper.sh b/installers/plugin_helper.sh index 08c424a3..2d946e36 100755 --- a/installers/plugin_helper.sh +++ b/installers/plugin_helper.sh @@ -1,7 +1,7 @@ #!/bin/bash # # PluginInstaller helper for RaspAP -# # @author billz +# @author billz # license: GNU General Public License v3.0 # Exit on error diff --git a/src/RaspAP/Plugins/PluginInstaller.php b/src/RaspAP/Plugins/PluginInstaller.php index 3157e3fe..b5ba43af 100644 --- a/src/RaspAP/Plugins/PluginInstaller.php +++ b/src/RaspAP/Plugins/PluginInstaller.php @@ -428,7 +428,7 @@ class PluginInstaller if ($installed === true ) { $button = ''; + data-plugin-manifest="' .$manifest. '" data-plugin-installed="' .$installed. '"> ' . _("Installed") .''; } else { $button = ' - + From 361a2f75311cfa1c9b9de0cbf942a1c43ba89cf2 Mon Sep 17 00:00:00 2001 From: billz Date: Thu, 26 Dec 2024 09:17:12 -0800 Subject: [PATCH 24/87] Use text() instead of replaceWith() --- app/js/custom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/js/custom.js b/app/js/custom.js index d68c94f1..396313e6 100644 --- a/app/js/custom.js +++ b/app/js/custom.js @@ -517,7 +517,7 @@ $('#js-install-plugin-confirm').on('click', function (e) { setTimeout(function(){ response = JSON.parse(data); if (response === true) { - $('#plugin-install-message').contents().first().replaceWith(successHtml); + $('#plugin-install-message').contents().first().text(successHtml); $('#plugin-install-message').find('i') .removeClass('fas fa-cog fa-spin link-secondary') .addClass('fas fa-check'); From 3c61954971ba4c9f55a1191c738940bd9a4d558d Mon Sep 17 00:00:00 2001 From: billz Date: Thu, 26 Dec 2024 09:17:33 -0800 Subject: [PATCH 25/87] Minor: update label --- templates/system/plugins.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/system/plugins.php b/templates/system/plugins.php index 71677d19..194e161e 100644 --- a/templates/system/plugins.php +++ b/templates/system/plugins.php @@ -9,7 +9,7 @@
- Details for more information and to install a plugin. Uninstall removes an existing plugin."); ?> + Details for more information and to install a plugin."); ?>
From 9bb2075b77f1141455c66362a6a514f0f17817b8 Mon Sep 17 00:00:00 2001 From: billz Date: Thu, 26 Dec 2024 09:41:52 -0800 Subject: [PATCH 26/87] Add strings to en_US locale, update template --- app/js/custom.js | 3 +- locale/en_US/LC_MESSAGES/messages.mo | Bin 42621 -> 43927 bytes locale/en_US/LC_MESSAGES/messages.po | 63 +++++++++++++++++++++++++++ templates/system.php | 2 +- 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/app/js/custom.js b/app/js/custom.js index 396313e6..eac5e17b 100644 --- a/app/js/custom.js +++ b/app/js/custom.js @@ -503,6 +503,7 @@ $('#install-user-plugin').on('shown.bs.modal', function (e) { $('#js-install-plugin-confirm').on('click', function (e) { var progressText = $('#js-install-plugin-confirm').attr('data-message'); var successHtml = $('#plugin-install-message').attr('data-message'); + var successText = $('
').text(successHtml).text(); var pluginUri = $('#plugin-uri a').attr('href'); var pluginVersion = $('#plugin-version').text(); var csrfToken = $('meta[name=csrf_token]').attr('content'); @@ -517,7 +518,7 @@ $('#js-install-plugin-confirm').on('click', function (e) { setTimeout(function(){ response = JSON.parse(data); if (response === true) { - $('#plugin-install-message').contents().first().text(successHtml); + $('#plugin-install-message').contents().first().replaceWith(successText); $('#plugin-install-message').find('i') .removeClass('fas fa-cog fa-spin link-secondary') .addClass('fas fa-check'); diff --git a/locale/en_US/LC_MESSAGES/messages.mo b/locale/en_US/LC_MESSAGES/messages.mo index fd8a0a6552a77a33ce2529228bd94e52e68d9042..23d44ab78cdf1a7d985ab1637c4cea170dd46994 100644 GIT binary patch delta 12251 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 NsJq|){~q&w{tu+gcD?`r diff --git a/locale/en_US/LC_MESSAGES/messages.po b/locale/en_US/LC_MESSAGES/messages.po index 95fa1281..b6b6e88b 100644 --- a/locale/en_US/LC_MESSAGES/messages.po +++ b/locale/en_US/LC_MESSAGES/messages.po @@ -920,6 +920,69 @@ msgstr "Changing log limit size to %s KB" msgid "Information provided by raspap.sysinfo" msgstr "Information provided by raspap.sysinfo" +msgid "The following user plugins are available to extend RaspAP's functionality." +msgstr "The following user plugins are available to extend RaspAP's functionality." + +msgid "Choose Details for more information and to install a plugin." +msgstr "Choose Details for more information and to install a plugin." + +msgid "Plugins" +msgstr "Plugins" + +msgid "Plugin details" +msgstr "Plugin details" + +msgid "Name" +msgstr "Name" + +msgid "Version" +msgstr "Version" + +msgid "Description" +msgstr "Description" + +msgid "Plugin source" +msgstr "Plugin source" + +msgid "Author" +msgstr "Author" + +msgid "License" +msgstr "License" + +msgid "Language locale" +msgstr "Language locale" + +msgid "Configuration files" +msgstr "Configuration files" + +msgid "Dependencies" +msgstr "Dependencies" + +msgid "Permissions" +msgstr "Permissions" + +msgid "Non-privileged users" +msgstr "Non-privileged users" + +msgid "Install now" +msgstr "Install now" + +msgid "Installing plugin" +msgstr "Installing plugin" + +msgid "Plugin installation in progress..." +msgstr "Plugin installation in progress..." + +msgid "Plugin install completed." +msgstr "Plugin install completed." + +msgid "Details" +msgstr "Details" + +msgid "Installed" +msgstr "Installed" + #: includes/data_usage.php msgid "Data usage" msgstr "Data usage" diff --git a/templates/system.php b/templates/system.php index cd4dbaf1..1891bda0 100755 --- a/templates/system.php +++ b/templates/system.php @@ -164,7 +164,7 @@
From 7dcc177424a7088991268e52da5caedec00e190b Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 4 Jan 2025 09:33:01 -0800 Subject: [PATCH 27/87] Initial commit --- ajax/plugins/do_plugin_install.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100755 ajax/plugins/do_plugin_install.php diff --git a/ajax/plugins/do_plugin_install.php b/ajax/plugins/do_plugin_install.php new file mode 100755 index 00000000..08bf8a40 --- /dev/null +++ b/ajax/plugins/do_plugin_install.php @@ -0,0 +1,29 @@ +installPlugin($archiveUrl); + echo json_encode($return); + + } catch (Exception $e) { + http_response_code(500); + echo json_encode(['error' => $e->getMessage()]); + } +} else { + http_response_code(400); + echo json_encode(['error' => 'Plugin URI and version are required']); + exit; +} + From 3e91f50966c94693b42120352e6cca4b5c2b9385 Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 5 Jan 2025 02:12:45 -0800 Subject: [PATCH 28/87] Create callbackTimeout() --- includes/functions.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/includes/functions.php b/includes/functions.php index 3099defc..02cce1c6 100755 --- a/includes/functions.php +++ b/includes/functions.php @@ -1036,3 +1036,25 @@ function renderStatus($hostapd_led, $hostapd_status, $memused_led, $memused, $cp $interval) { + throw new \Exception('Operation timed out'); + } + + return $result; +} + From ff7e674b2eaffba291d43030b786496a653073c8 Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 5 Jan 2025 02:14:16 -0800 Subject: [PATCH 29/87] Use callbackTimeout w/ getUserPlugins(), handle err if >2000ms --- includes/system.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/includes/system.php b/includes/system.php index 6ef6b74e..1bf9c3b5 100755 --- a/includes/system.php +++ b/includes/system.php @@ -117,8 +117,20 @@ function DisplaySystem(&$extraFooterScripts) $extraFooterScripts[] = array('src'=>'app/js/huebee.js', 'defer'=>false); $logLimit = isset($_SESSION['log_limit']) ? $_SESSION['log_limit'] : RASPI_LOG_SIZE_LIMIT; - $plugins = $pluginInstaller->getUserPlugins(); - $pluginsTable = $pluginInstaller->getHTMLPluginsTable($plugins); + try { + $plugins = callbackTimeout(fn() => $pluginInstaller->getUserPlugins(), 2000); + $pluginsTable = $pluginInstaller->getHTMLPluginsTable($plugins); + } catch (\Exception $e) { + $errResponse = sprintf( + '
%s: %s. %s %s.
', + _('Error'), + _('Unable to load plugins'), + _('Reload'), + _('and try again') + ); + $errResponse.= ''; @@ -434,14 +377,23 @@ class PluginInstaller name="install-plugin" data-bs-toggle="modal" data-bs-target="#install-user-plugin" data-plugin-manifest="' .$manifest. '"> ' . _("Details") .''; } - $name = '' - . htmlspecialchars($plugin['manifest']['name']). ''; + . $nameText. ''; + + $version = htmlspecialchars($manifestData['version'] ?? 'N/A'); + $description = htmlspecialchars($manifestData['description'] ?? 'No description available'); + $html .= '' .$name. ''; - $html .= '' .htmlspecialchars($plugin['manifest']['version']). ''; - $html .= '' .htmlspecialchars($plugin['manifest']['description']). ''; - $html .= '' .$button. ''; + $html .= '' .$version. ''; + $html .= '' .$description. ''; + $html .= '' .$button. ''; } $html .= ''; return $html; From 36c3e5036fd103688cfc6c2b956120ef14d0402a Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 25 Jan 2025 09:42:17 -0800 Subject: [PATCH 37/87] Ignore compiled .mo files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 12888e08..18aba9ae 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ plugins/ rootCA.pem vendor .env +locale/**/*.mo From 75bb3e4a3451401597f7f9ae921a9b398e1690c1 Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 25 Jan 2025 09:48:58 -0800 Subject: [PATCH 38/87] Resolve conflict: update .mo from master + ignore --- locale/en_US/LC_MESSAGES/messages.mo | Bin 44147 -> 43019 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/locale/en_US/LC_MESSAGES/messages.mo b/locale/en_US/LC_MESSAGES/messages.mo index 5706506b09ab0bc1f31855a381f403e9f90179fa..d1c38e1f3fe2e4aea23d38c09db8dde0ac69c255 100644 GIT binary patch delta 11525 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 delta 12553 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 Date: Sun, 26 Jan 2025 01:23:42 -0800 Subject: [PATCH 39/87] Catch HTTP 442 unprocessable content response --- ajax/plugins/do_plugin_install.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ajax/plugins/do_plugin_install.php b/ajax/plugins/do_plugin_install.php index 010cc535..f05ccb75 100755 --- a/ajax/plugins/do_plugin_install.php +++ b/ajax/plugins/do_plugin_install.php @@ -17,13 +17,12 @@ if (isset($plugin_uri) && isset($plugin_version)) { try { $return = $pluginInstaller->installPlugin($archiveUrl); echo json_encode($return); - } catch (Exception $e) { - http_response_code(500); + http_response_code(422); // Unprocessable Content echo json_encode(['error' => $e->getMessage()]); } } else { - http_response_code(400); + http_response_code(400); // Bad Request echo json_encode(['error' => 'Plugin URI and version are required']); exit; } From 47c509277c5fc9eb0d4de27f9c2a38d4e7eb5746 Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 26 Jan 2025 01:25:12 -0800 Subject: [PATCH 40/87] Add checks for empty vars, improve error propagation --- src/RaspAP/Plugins/PluginInstaller.php | 43 ++++++++++++++++++-------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/RaspAP/Plugins/PluginInstaller.php b/src/RaspAP/Plugins/PluginInstaller.php index 976b2c3f..9fcaf11a 100644 --- a/src/RaspAP/Plugins/PluginInstaller.php +++ b/src/RaspAP/Plugins/PluginInstaller.php @@ -84,7 +84,8 @@ class PluginInstaller } return $plugins; } catch (\Exception $e) { - echo "An error occurred: " . $e->getMessage(); + error_log("An error occurred: " . $e->getMessage()); + throw $e; // re-throw to global ExceptionHandler return []; } } @@ -117,9 +118,14 @@ class PluginInstaller * * @param string $archiveUrl * @return boolean + * @throws \Exception */ public function installPlugin($archiveUrl): bool { + $tempFile = null; + $extractDir = null; + $pluginDir = null; + try { list($tempFile, $extractDir, $pluginDir) = $this->getPluginArchive($archiveUrl); @@ -151,18 +157,19 @@ class PluginInstaller } catch (\Exception $e) { //$this->rollback($rollbackStack, $manifest, $pluginDir); + throw new \Exception('Installation step failed: ' . $e->getMessage()); error_log('Plugin installation failed: ' . $e->getMessage()); - return false; } } catch (\Exception $e) { - throw new \Exception('error: ' .$e->getMessage()); + error_log('An error occured: ' .$e->getMessage()); + throw new \Exception( $e->getMessage()); + //throw $e; } finally { - // cleanup tmp files - if (file_exists($tempFile)) { + if (!empty($tempFile) && file_exists($tempFile)) { unlink($tempFile); } - if (is_dir($extractDir)) { + if (!empty($extractDir) && is_dir($extractDir)) { $this->deleteDir($extractDir); } } @@ -295,17 +302,21 @@ class PluginInstaller * * @param string $archiveUrl * @return array + * @throws \Exception */ private function getPluginArchive(string $archiveUrl): array { - try { + $tempFile = ''; + $extractDir = ''; + try { $tempFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('plugin_', true) . '.zip'; $extractDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('plugin_', true); - $data = file_get_contents($archiveUrl); + $data = @file_get_contents($archiveUrl); // suppress PHP warnings for better exception handling if ($data === false) { - throw new \Exception('Failed to download archive.'); + $error = error_get_last(); + throw new \Exception('Failed to download archive: ' . ($error['message'] ?? 'Unknown error')); } file_put_contents($tempFile, $data); @@ -317,19 +328,25 @@ class PluginInstaller $cmd = escapeshellcmd("unzip -o $tempFile -d $extractDir"); $output = shell_exec($cmd); if ($output === null) { - throw new \Exception('Failed to extract archive.'); + throw new \Exception('Failed to extract plugin archive.'); } $extractedDirs = glob($extractDir . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR); if (empty($extractedDirs)) { - throw new \Exception('No directories found in archive.'); + throw new \Exception('No directories found in plugin archive.'); } + $pluginDir = $extractedDirs[0]; return [$tempFile, $extractDir, $pluginDir]; - } catch (\Exception $e) { - throw new \Exception('Error occurred: ' .$e->getMessage()); + if (!empty($tempFile) && file_exists($tempFile)) { + unlink($tempFile); + } + if (!empty($extractDir) && is_dir($extractDir)) { + rmdir($extractDir); + } + throw new \Exception('Error occurred during plugin archive retrieval: ' . $e->getMessage()); } } From e423b7f4d37ce1992647846e712080930472dde9 Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 26 Jan 2025 01:31:40 -0800 Subject: [PATCH 41/87] Display diagnostic error message on plugin install fail --- app/js/custom.js | 57 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/app/js/custom.js b/app/js/custom.js index eac5e17b..d0b83762 100644 --- a/app/js/custom.js +++ b/app/js/custom.js @@ -513,22 +513,47 @@ $('#js-install-plugin-confirm').on('click', function (e) { if ($('#js-install-plugin-confirm').text() === 'Install now') { $("#install-plugin-progress").modal('show'); - $.post('ajax/plugins/do_plugin_install.php?',{'plugin_uri': pluginUri, - 'plugin_version': pluginVersion, 'csrf_token': csrfToken},function(data){ - setTimeout(function(){ - response = JSON.parse(data); - if (response === true) { - $('#plugin-install-message').contents().first().replaceWith(successText); - $('#plugin-install-message').find('i') - .removeClass('fas fa-cog fa-spin link-secondary') - .addClass('fas fa-check'); - $('#js-install-plugin-ok').removeAttr("disabled"); - } else { - $('#plugin-install-message').contents().first().replaceWith('An error occurred installing the plugin.'); - $('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary'); - $('#js-install-plugin-ok').removeAttr("disabled"); - } - },200); + $.post( + 'ajax/plugins/do_plugin_install.php', + { + 'plugin_uri': pluginUri, + 'plugin_version': pluginVersion, + 'csrf_token': csrfToken + }, + function (data) { + setTimeout(function () { + response = JSON.parse(data); + if (response === true) { + $('#plugin-install-message').contents().first().replaceWith(successText); + $('#plugin-install-message') + .find('i') + .removeClass('fas fa-cog fa-spin link-secondary') + .addClass('fas fa-check'); + $('#js-install-plugin-ok').removeAttr("disabled"); + } else { + const errorMessage = jsonData.error || 'An unknown error occurred.'; + var errorLog = ''; + $('#plugin-install-message') + .contents() + .first() + .replaceWith('An error occurred installing the plugin:'); + $('#plugin-install-message').append(errorLog); + $('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary'); + $('#js-install-plugin-ok').removeAttr("disabled"); + } + }, 200); + } + ).fail(function (xhr) { + const jsonData = JSON.parse(xhr.responseText); + const errorMessage = jsonData.error || 'An unknown error occurred.'; + $('#plugin-install-message') + .contents() + .first() + .replaceWith('An error occurred installing the plugin:'); + var errorLog = ''; + $('#plugin-install-message').append(errorLog); + $('#plugin-install-message').find('i').removeClass('fas fa-cog fa-spin link-secondary'); + $('#js-install-plugin-ok').removeAttr("disabled"); }); } }); From a8bd85cc80a2a4fd5e2eedcafac227c161bfa3e3 Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 26 Jan 2025 01:32:18 -0800 Subject: [PATCH 42/87] Create textarea.plugin-log class --- app/css/all.css | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/css/all.css b/app/css/all.css index 7efc2f20..e4c8c56c 100644 --- a/app/css/all.css +++ b/app/css/all.css @@ -332,3 +332,15 @@ button > i.fas { animation: heart 1000ms infinite; } +textarea.plugin-log { + width: 100%; + height: 150px; + resize: none; + border: 1px solid #dee2e6; + border-radius: 0.25rem; + padding: 0.5rem; + background-color: #f8f9fa; + font-family: monospace; + font-size: 0.9rem; +} + From 0b0f4bc06dd3e5b1616ea0554e75b397ca3f4437 Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 26 Jan 2025 02:45:47 -0800 Subject: [PATCH 43/87] Update plugin installed check --- src/RaspAP/Plugins/PluginInstaller.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/RaspAP/Plugins/PluginInstaller.php b/src/RaspAP/Plugins/PluginInstaller.php index 9fcaf11a..0da29cf2 100644 --- a/src/RaspAP/Plugins/PluginInstaller.php +++ b/src/RaspAP/Plugins/PluginInstaller.php @@ -67,12 +67,13 @@ class PluginInstaller $installedPlugins = $this->getPlugins(); $plugins = []; + foreach ($manifestData as $pluginManifest) { $installed = false; // Check if the plugin is installed foreach ($installedPlugins as $plugin) { - if (str_contains($plugin, $pluginManifest['namespace'])) { + if (str_contains($plugin, $pluginManifest[0]['namespace'])) { $installed = true; break; } From 5d01fa59b1e47998daf2a036fe393b98fc4ffa66 Mon Sep 17 00:00:00 2001 From: Bill Zimmerman Date: Sun, 26 Jan 2025 11:50:33 +0100 Subject: [PATCH 44/87] Fix for DOM text reinterpreted as HTML Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- app/js/custom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/js/custom.js b/app/js/custom.js index 842494b8..cbf26fa9 100644 --- a/app/js/custom.js +++ b/app/js/custom.js @@ -524,7 +524,7 @@ $('#js-install-plugin-confirm').on('click', function (e) { setTimeout(function () { response = JSON.parse(data); if (response === true) { - $('#plugin-install-message').contents().first().replaceWith(successText); + $('#plugin-install-message').contents().first().text(successText); $('#plugin-install-message') .find('i') .removeClass('fas fa-cog fa-spin link-secondary') From 538e37ceb707fd370d12fbb8012c8987a5a80252 Mon Sep 17 00:00:00 2001 From: billz Date: Mon, 27 Jan 2025 04:29:07 -0800 Subject: [PATCH 45/87] Add --recurse-submodules to git clone operation --- installers/common.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/installers/common.sh b/installers/common.sh index bd78ef00..3c7f034e 100755 --- a/installers/common.sh +++ b/installers/common.sh @@ -583,14 +583,14 @@ function _download_latest_files() { if [ "$repo" == "RaspAP/raspap-insiders" ]; then if [ -n "$username" ] && [ -n "$acctoken" ]; then insiders_source_url="https://${username}:${acctoken}@github.com/$repo" - git clone --branch $branch --depth 1 -c advice.detachedHead=false $insiders_source_url $source_dir || clone=false + git clone --branch $branch --depth 1 --recurse-submodules -c advice.detachedHead=false $insiders_source_url $source_dir || clone=false else _install_status 3 echo "Insiders please read this: https://docs.raspap.com/insiders/#authentication" fi fi if [ -z "$insiders_source_url" ]; then - git clone --branch $branch --depth 1 -c advice.detachedHead=false $git_source_url $source_dir || clone=false + git clone --branch $branch --depth 1 --recurse-submodules -c advice.detachedHead=false $git_source_url $source_dir || clone=false fi if [ "$clone" = false ]; then _install_status 1 "Unable to download files from GitHub" From 7def2d6da1c89407a088bb0dbc119788a3ea9953 Mon Sep 17 00:00:00 2001 From: billz Date: Mon, 27 Jan 2025 04:43:01 -0800 Subject: [PATCH 46/87] Remove plugins/ to allow submodule usage --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 18aba9ae..a0642d6e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ node_modules yarn-error.log *.swp includes/config.php -plugins/ rootCA.pem vendor .env From 5fbafeb4552368506ed03c94186cbbc8dab395a8 Mon Sep 17 00:00:00 2001 From: billz Date: Mon, 27 Jan 2025 04:49:31 -0800 Subject: [PATCH 47/87] Re-add plugins submodule --- plugins | 1 + 1 file changed, 1 insertion(+) create mode 160000 plugins diff --git a/plugins b/plugins new file mode 160000 index 00000000..825f74ed --- /dev/null +++ b/plugins @@ -0,0 +1 @@ +Subproject commit 825f74edf49ae34705deb4625d3f3c571dc69bda From 0efbe2b3260bf4e5d26b4161decb14e7b82fd837 Mon Sep 17 00:00:00 2001 From: billz Date: Tue, 4 Feb 2025 23:44:04 -0800 Subject: [PATCH 48/87] Define RASPI_PLUGINS_ENABLED, add condition to template --- config/config.php | 1 + includes/defaults.php | 1 + templates/system.php | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/config/config.php b/config/config.php index 17c40cb4..178081b1 100755 --- a/config/config.php +++ b/config/config.php @@ -63,6 +63,7 @@ define('RASPI_VNSTAT_ENABLED', true); define('RASPI_SYSTEM_ENABLED', true); define('RASPI_MONITOR_ENABLED', false); define('RASPI_RESTAPI_ENABLED', false); +define('RASPI_PLUGINS_ENABLED', true); // Locale settings define('LOCALE_ROOT', 'locale'); diff --git a/includes/defaults.php b/includes/defaults.php index 4ed1860d..b54ec3fa 100755 --- a/includes/defaults.php +++ b/includes/defaults.php @@ -65,6 +65,7 @@ $defaults = [ 'RASPI_SYSTEM_ENABLED' => true, 'RASPI_MONITOR_ENABLED' => false, 'RASPI_RESTAPI_ENABLED' => false, + 'RASPI_PLUGINS_ENABLED' => true, // Locale settings 'LOCALE_ROOT' => 'locale', diff --git a/templates/system.php b/templates/system.php index 1891bda0..7f22be09 100755 --- a/templates/system.php +++ b/templates/system.php @@ -18,7 +18,9 @@ + +
@@ -27,7 +29,9 @@ + +
From 8a8be213f740a887bfee69534fe15c440ae6fee1 Mon Sep 17 00:00:00 2001 From: billz Date: Tue, 4 Feb 2025 23:45:06 -0800 Subject: [PATCH 49/87] Display plugins in monitor mode, suppress details dialog --- src/RaspAP/Plugins/PluginInstaller.php | 2 +- templates/system/plugins.php | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/RaspAP/Plugins/PluginInstaller.php b/src/RaspAP/Plugins/PluginInstaller.php index 0da29cf2..6ff6ebf8 100644 --- a/src/RaspAP/Plugins/PluginInstaller.php +++ b/src/RaspAP/Plugins/PluginInstaller.php @@ -390,7 +390,7 @@ class PluginInstaller $button = ''; - } else { + } elseif (!RASPI_MONITOR_ENABLED) { $button = ''; diff --git a/templates/system/plugins.php b/templates/system/plugins.php index 194e161e..4d875c49 100644 --- a/templates/system/plugins.php +++ b/templates/system/plugins.php @@ -1,19 +1,19 @@

+ +
+
+ - -
-
- -
- Details for more information and to install a plugin."); ?> -
- -
+
+ Details for more information and to install a plugin."); ?>
+ +
+
From 7a880d563f82ac0c413fd528e190bbff50569045 Mon Sep 17 00:00:00 2001 From: billz Date: Fri, 7 Feb 2025 23:48:30 -0800 Subject: [PATCH 50/87] Add _create_plugin_scripts to _update_raspap() --- installers/common.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/installers/common.sh b/installers/common.sh index d2e2f69f..f884ad73 100755 --- a/installers/common.sh +++ b/installers/common.sh @@ -75,6 +75,7 @@ function _update_raspap() { _download_latest_files _change_file_ownership _patch_system_files + _create_plugin_scripts _install_complete } From 3417115a84ac7c435613c9b34a15ea0c32d9bff9 Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 8 Feb 2025 00:11:43 -0800 Subject: [PATCH 51/87] Update release version --- README.md | 2 +- includes/defaults.php | 2 +- index.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d6bff4c3..2da9f662 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ![](https://i.imgur.com/xeKD93p.png) -[![Release 3.2.5](https://img.shields.io/badge/release-v3.2.5-green)](https://github.com/raspap/raspap-webgui/releases) [![Awesome](https://awesome.re/badge.svg)](https://github.com/thibmaek/awesome-raspberry-pi) [![Join Insiders](https://img.shields.io/static/v1?label=Insiders&message=%E2%9D%A4&logo=GitHub&color=ff69b4)](https://github.com/sponsors/RaspAP) [![Build Status](https://app.travis-ci.com/RaspAP/raspap-webgui.svg?branch=master)](https://app.travis-ci.com/RaspAP/raspap-webgui) [![Crowdin](https://badges.crowdin.net/raspap/localized.svg)](https://crowdin.com/project/raspap) [![Twitter URL](https://img.shields.io/twitter/url?label=%40RaspAP&logoColor=%23d8224c&url=https%3A%2F%2Ftwitter.com%2Frasp_ap)](https://twitter.com/rasp_ap) [![Reddit](https://img.shields.io/badge/%2Fr%2FRaspAP-e05d44?style=flat&logo=Reddit&logoColor=white&labelColor=e05d44&color=b14835)](https://reddit.com/r/RaspAP) [![Discord](https://img.shields.io/discord/642436993451819018?color=7289DA&label=Discord&logo=discord&style=flat)](https://discord.gg/KVAsaAR) +[![Release 3.2.6](https://img.shields.io/badge/release-v3.2.6-green)](https://github.com/raspap/raspap-webgui/releases) [![Awesome](https://awesome.re/badge.svg)](https://github.com/thibmaek/awesome-raspberry-pi) [![Join Insiders](https://img.shields.io/static/v1?label=Insiders&message=%E2%9D%A4&logo=GitHub&color=ff69b4)](https://github.com/sponsors/RaspAP) [![Build Status](https://app.travis-ci.com/RaspAP/raspap-webgui.svg?branch=master)](https://app.travis-ci.com/RaspAP/raspap-webgui) [![Crowdin](https://badges.crowdin.net/raspap/localized.svg)](https://crowdin.com/project/raspap) [![Twitter URL](https://img.shields.io/twitter/url?label=%40RaspAP&logoColor=%23d8224c&url=https%3A%2F%2Ftwitter.com%2Frasp_ap)](https://twitter.com/rasp_ap) [![Reddit](https://img.shields.io/badge/%2Fr%2FRaspAP-e05d44?style=flat&logo=Reddit&logoColor=white&labelColor=e05d44&color=b14835)](https://reddit.com/r/RaspAP) [![Discord](https://img.shields.io/discord/642436993451819018?color=7289DA&label=Discord&logo=discord&style=flat)](https://discord.gg/KVAsaAR) RaspAP is feature-rich wireless router software that _just works_ on many popular [Debian-based devices](#supported-operating-systems), including the Raspberry Pi. Our popular [Quick installer](#quick-installer) and [Docker container](#docker-support) create a known-good default configuration for all current Raspberry Pis with onboard wireless. A fully responsive, mobile-ready interface gives you control over the relevant services and networking options. Advanced DHCP settings, WireGuard and OpenVPN support, [SSL certificates](https://docs.raspap.com/ssl/), security audits, [captive portal integration](https://docs.raspap.com/captive/), themes and [multilingual options](https://docs.raspap.com/translations/) are included. diff --git a/includes/defaults.php b/includes/defaults.php index b54ec3fa..48c624eb 100755 --- a/includes/defaults.php +++ b/includes/defaults.php @@ -7,7 +7,7 @@ if (!defined('RASPI_CONFIG')) { $defaults = [ 'RASPI_BRAND_TEXT' => 'RaspAP', 'RASPI_BRAND_TITLE' => RASPI_BRAND_TEXT.' Admin Panel', - 'RASPI_VERSION' => '3.2.5', + 'RASPI_VERSION' => '3.2.6', 'RASPI_CONFIG_NETWORK' => RASPI_CONFIG.'/networking/defaults.json', 'RASPI_CONFIG_PROVIDERS' => 'config/vpn-providers.json', 'RASPI_CONFIG_API' => RASPI_CONFIG.'/api', diff --git a/index.php b/index.php index 50eeec73..59c1b985 100755 --- a/index.php +++ b/index.php @@ -14,7 +14,7 @@ * @author Lawrence Yau * @author Bill Zimmerman * @license GNU General Public License, version 3 (GPL-3.0) - * @version 3.2.5 + * @version 3.2.6 * @link https://github.com/RaspAP/raspap-webgui/ * @link https://raspap.com/ * @see http://sirlagz.net/2013/02/08/raspap-webgui/ From bd0b226f61a14d90ae41e2a8777d6955ecd761b4 Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 8 Feb 2025 01:18:47 -0800 Subject: [PATCH 52/87] Adds -k --check option to install loader --- installers/raspbian.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/installers/raspbian.sh b/installers/raspbian.sh index 8d8c669a..0835f48b 100755 --- a/installers/raspbian.sh +++ b/installers/raspbian.sh @@ -54,6 +54,7 @@ OPTIONS: -p, --path Used with -d, --update, sets the existing install path -i, --insiders Installs from the Insiders Edition (RaspAP/raspap-insiders) -m, --minwrite Configures a microSD card for minimum write operation +-k, --check Sets the connectivity check flag (default is 1=perform check) -v, --version Outputs release info and exits -n, --uninstall Loads and executes the uninstaller -h, --help Outputs usage notes and exits @@ -81,13 +82,15 @@ EOF set -eo pipefail function _main() { + _parse_params "$@" _setup_colors - _check_internet + if [ "${check}" == 1 ]; then + _check_internet + fi # set defaults repo="RaspAP/raspap-webgui" # override with -r, --repo option repo_common="$repo" - _parse_params "$@" _log_output _load_installer } @@ -105,6 +108,7 @@ function _parse_params() { minwrite=0 acctoken="" path="" + check=1 while :; do case "${1-}" in @@ -175,6 +179,10 @@ function _parse_params() { path="$2" shift ;; + -k|--check) + check="$2" + shift + ;; -v|--version) _version ;; From 9876ab1e3e6acf9248f6b4cf0ced04c2dbec496b Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 8 Feb 2025 01:22:52 -0800 Subject: [PATCH 53/87] Specify --check 0 to skip connectivity check --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4eac8c5c..66c952ee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: cat > stage-raspap/package-raspap/00-run-chroot.sh <<-EOF #!/bin/bash apt-get update -y && apt-get install -y curl dhcpcd5 iptables procps - curl -sL https://install.raspap.com | bash -s -- --yes --openvpn 1 --restapi 1 --adblock 1 --wireguard 1 --tcp-bbr 1 + curl -sL https://install.raspap.com | bash -s -- --yes --openvpn 1 --restapi 1 --adblock 1 --wireguard 1 --tcp-bbr 1 --check 0 EOF } && chmod +x stage-raspap/package-raspap/00-run-chroot.sh && From 41ce72775ecd28e53ed8930b2b71eb4b4f4cad86 Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 8 Feb 2025 01:41:43 -0800 Subject: [PATCH 54/87] Set default repo at top of _main() --- installers/raspbian.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/installers/raspbian.sh b/installers/raspbian.sh index 0835f48b..27f576fb 100755 --- a/installers/raspbian.sh +++ b/installers/raspbian.sh @@ -82,15 +82,16 @@ EOF set -eo pipefail function _main() { + # set defaults + repo="RaspAP/raspap-webgui" # override with -r, --repo option + repo_common="$repo" + _parse_params "$@" _setup_colors if [ "${check}" == 1 ]; then _check_internet fi - # set defaults - repo="RaspAP/raspap-webgui" # override with -r, --repo option - repo_common="$repo" _log_output _load_installer } From 33f66fdae5c8e4915646809738ce999aac7f468c Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 8 Feb 2025 10:52:26 -0800 Subject: [PATCH 55/87] Update naming to reflect clarity in arch, OS + purpose --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 66c952ee..cbfa871e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,7 +47,7 @@ jobs: id: build uses: usimd/pi-gen-action@v1 with: - image-name: "raspap-${{ github.ref_name }}-${{ matrix.arch }}" + image-name: "raspap-bookworm-${{ matrix.arch == '32-bit' && 'armhf' || 'arm64' }}-lite-${{ github.event.inputs.tag || github.ref_name }}" enable-ssh: 1 stage-list: stage0 stage1 stage2 ./stage-raspap verbose-output: true @@ -57,8 +57,8 @@ jobs: - name: Upload Artifact uses: svenstaro/upload-release-action@v2 with: - asset_name: raspap-image-${{ github.ref_name }}-${{ matrix.arch }}.zip + asset_name: "raspap-bookworm-${{ matrix.arch == '32-bit' && 'armhf' || 'arm64' }}-lite-${{ github.event.inputs.tag || github.ref_name }}.img.zip" file: ${{ steps.build.outputs.image-path }} repo_token: ${{ secrets.GITHUB_TOKEN }} - tag: ${{ github.ref }} + tag: ${{ github.event.inputs.tag || github.ref }} overwrite: true From c34cbeca8d03ff6b647e85086b9672ade494b6b5 Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 9 Feb 2025 00:46:27 -0800 Subject: [PATCH 56/87] Exec raspi-config noint do_wifi_country --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cbfa871e..ede5446e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,6 +30,9 @@ jobs: #!/bin/bash apt-get update -y && apt-get install -y curl dhcpcd5 iptables procps curl -sL https://install.raspap.com | bash -s -- --yes --openvpn 1 --restapi 1 --adblock 1 --wireguard 1 --tcp-bbr 1 --check 0 + + # Set Wi-Fi country to prevent RF kill + raspi-config nonint do_wifi_country "US" EOF } && chmod +x stage-raspap/package-raspap/00-run-chroot.sh && From 362a08c00f75af6618cc8600e03803b54373e58f Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 9 Feb 2025 00:54:27 -0800 Subject: [PATCH 57/87] Update release version --- README.md | 2 +- includes/defaults.php | 2 +- index.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2da9f662..aff659b1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ![](https://i.imgur.com/xeKD93p.png) -[![Release 3.2.6](https://img.shields.io/badge/release-v3.2.6-green)](https://github.com/raspap/raspap-webgui/releases) [![Awesome](https://awesome.re/badge.svg)](https://github.com/thibmaek/awesome-raspberry-pi) [![Join Insiders](https://img.shields.io/static/v1?label=Insiders&message=%E2%9D%A4&logo=GitHub&color=ff69b4)](https://github.com/sponsors/RaspAP) [![Build Status](https://app.travis-ci.com/RaspAP/raspap-webgui.svg?branch=master)](https://app.travis-ci.com/RaspAP/raspap-webgui) [![Crowdin](https://badges.crowdin.net/raspap/localized.svg)](https://crowdin.com/project/raspap) [![Twitter URL](https://img.shields.io/twitter/url?label=%40RaspAP&logoColor=%23d8224c&url=https%3A%2F%2Ftwitter.com%2Frasp_ap)](https://twitter.com/rasp_ap) [![Reddit](https://img.shields.io/badge/%2Fr%2FRaspAP-e05d44?style=flat&logo=Reddit&logoColor=white&labelColor=e05d44&color=b14835)](https://reddit.com/r/RaspAP) [![Discord](https://img.shields.io/discord/642436993451819018?color=7289DA&label=Discord&logo=discord&style=flat)](https://discord.gg/KVAsaAR) +[![Release 3.2.7](https://img.shields.io/badge/release-v3.2.7-green)](https://github.com/raspap/raspap-webgui/releases) [![Awesome](https://awesome.re/badge.svg)](https://github.com/thibmaek/awesome-raspberry-pi) [![Join Insiders](https://img.shields.io/static/v1?label=Insiders&message=%E2%9D%A4&logo=GitHub&color=ff69b4)](https://github.com/sponsors/RaspAP) [![Build Status](https://app.travis-ci.com/RaspAP/raspap-webgui.svg?branch=master)](https://app.travis-ci.com/RaspAP/raspap-webgui) [![Crowdin](https://badges.crowdin.net/raspap/localized.svg)](https://crowdin.com/project/raspap) [![Twitter URL](https://img.shields.io/twitter/url?label=%40RaspAP&logoColor=%23d8224c&url=https%3A%2F%2Ftwitter.com%2Frasp_ap)](https://twitter.com/rasp_ap) [![Reddit](https://img.shields.io/badge/%2Fr%2FRaspAP-e05d44?style=flat&logo=Reddit&logoColor=white&labelColor=e05d44&color=b14835)](https://reddit.com/r/RaspAP) [![Discord](https://img.shields.io/discord/642436993451819018?color=7289DA&label=Discord&logo=discord&style=flat)](https://discord.gg/KVAsaAR) RaspAP is feature-rich wireless router software that _just works_ on many popular [Debian-based devices](#supported-operating-systems), including the Raspberry Pi. Our popular [Quick installer](#quick-installer) and [Docker container](#docker-support) create a known-good default configuration for all current Raspberry Pis with onboard wireless. A fully responsive, mobile-ready interface gives you control over the relevant services and networking options. Advanced DHCP settings, WireGuard and OpenVPN support, [SSL certificates](https://docs.raspap.com/ssl/), security audits, [captive portal integration](https://docs.raspap.com/captive/), themes and [multilingual options](https://docs.raspap.com/translations/) are included. diff --git a/includes/defaults.php b/includes/defaults.php index 48c624eb..3a12671c 100755 --- a/includes/defaults.php +++ b/includes/defaults.php @@ -7,7 +7,7 @@ if (!defined('RASPI_CONFIG')) { $defaults = [ 'RASPI_BRAND_TEXT' => 'RaspAP', 'RASPI_BRAND_TITLE' => RASPI_BRAND_TEXT.' Admin Panel', - 'RASPI_VERSION' => '3.2.6', + 'RASPI_VERSION' => '3.2.7', 'RASPI_CONFIG_NETWORK' => RASPI_CONFIG.'/networking/defaults.json', 'RASPI_CONFIG_PROVIDERS' => 'config/vpn-providers.json', 'RASPI_CONFIG_API' => RASPI_CONFIG.'/api', diff --git a/index.php b/index.php index 59c1b985..7afd1684 100755 --- a/index.php +++ b/index.php @@ -14,7 +14,7 @@ * @author Lawrence Yau * @author Bill Zimmerman * @license GNU General Public License, version 3 (GPL-3.0) - * @version 3.2.6 + * @version 3.2.7 * @link https://github.com/RaspAP/raspap-webgui/ * @link https://raspap.com/ * @see http://sirlagz.net/2013/02/08/raspap-webgui/ From eb27ba2c6620bd8c9e74794bc6ce7fbb1866e4c9 Mon Sep 17 00:00:00 2001 From: billz Date: Wed, 12 Feb 2025 08:30:33 -0800 Subject: [PATCH 58/87] Fetch RaspAP version and set MOTD --- .github/workflows/release.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ede5446e..95cacb8f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,6 +33,19 @@ jobs: # Set Wi-Fi country to prevent RF kill raspi-config nonint do_wifi_country "US" + + # Fetch RaspAP version and set MOTD + RASPAP_VERSION=\$(curl -sL https://install.raspap.com | bash -s -- --version) + echo "\$RASPAP_VERSION" | tee /etc/motd + EOF + } && + chmod +x stage-raspap/package-raspap/00-run-chroot.sh && + { + cat > stage-raspap/prerun.sh <<-EOF + #!/bin/bash -e + if [ ! -d "\${ROOTFS_DIR}" ]; then + copy_previous + fi EOF } && chmod +x stage-raspap/package-raspap/00-run-chroot.sh && From f576b028588608056fa24cf6b1dcd89503a354ee Mon Sep 17 00:00:00 2001 From: Bill Zimmerman Date: Wed, 12 Feb 2025 23:20:49 +0100 Subject: [PATCH 59/87] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aff659b1..4f3b4d3f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![](https://i.imgur.com/xeKD93p.png) +![RaspAP Custom OS images](https://github.com/user-attachments/assets/e871adf1-123c-450b-94eb-80a185c242cc) [![Release 3.2.7](https://img.shields.io/badge/release-v3.2.7-green)](https://github.com/raspap/raspap-webgui/releases) [![Awesome](https://awesome.re/badge.svg)](https://github.com/thibmaek/awesome-raspberry-pi) [![Join Insiders](https://img.shields.io/static/v1?label=Insiders&message=%E2%9D%A4&logo=GitHub&color=ff69b4)](https://github.com/sponsors/RaspAP) [![Build Status](https://app.travis-ci.com/RaspAP/raspap-webgui.svg?branch=master)](https://app.travis-ci.com/RaspAP/raspap-webgui) [![Crowdin](https://badges.crowdin.net/raspap/localized.svg)](https://crowdin.com/project/raspap) [![Twitter URL](https://img.shields.io/twitter/url?label=%40RaspAP&logoColor=%23d8224c&url=https%3A%2F%2Ftwitter.com%2Frasp_ap)](https://twitter.com/rasp_ap) [![Reddit](https://img.shields.io/badge/%2Fr%2FRaspAP-e05d44?style=flat&logo=Reddit&logoColor=white&labelColor=e05d44&color=b14835)](https://reddit.com/r/RaspAP) [![Discord](https://img.shields.io/discord/642436993451819018?color=7289DA&label=Discord&logo=discord&style=flat)](https://discord.gg/KVAsaAR) From 91c535ddbb9bea1b5b7c28eca0d24152de6b7f22 Mon Sep 17 00:00:00 2001 From: Bill Zimmerman Date: Wed, 12 Feb 2025 23:41:33 +0100 Subject: [PATCH 60/87] Update README.md --- README.md | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 4f3b4d3f..07fbf2f9 100644 --- a/README.md +++ b/README.md @@ -18,15 +18,13 @@ We hope you enjoy using RaspAP as much as we do creating it. Tell us how you use ## Contents - - [Prerequisites](#prerequisites) - - [Quick installer](#quick-installer) + - [Quick start](#quick-start) - [Join Insiders](#join-insiders) - [WireGuard support](#wireguard-support) - [OpenVPN support](#openvpn-support) - [VPN Provider support](#vpn-provider-support) - [Ad Blocking](#ad-blocking) - [Bridged AP](#bridged-ap) - - [Simultaneous AP and Wifi client](#simultaneous-ap-and-wifi-client) - [Manual installation](#manual-installation) - [802.11ac 5GHz support](#80211ac-5ghz-support) - [Supported operating systems](#supported-operating-systems) @@ -38,30 +36,43 @@ We hope you enjoy using RaspAP as much as we do creating it. Tell us how you use - [Reporting issues](#reporting-issues) - [License](#license) -## Prerequisites -Start with a clean install of the [latest release of Raspberry Pi OS Lite](https://www.raspberrypi.com/software/operating-systems/). Both the 32- and 64-bit Lite versions are supported. The Raspberry Pi OS desktop distro is [unsupported](https://docs.raspap.com/faq/#distros). +## Quick start +RaspAP gives you two different ways to get up and running quickly. The simplest and recommended approach is to use a custom Raspberry Pi OS image with RaspAP preinstalled. This option eliminates guesswork and gives you a base upon which to build. Alternatively, you may execute the Quick installer on an existing [compatible OS](https://docs.raspap.com/#compatible-operating-systems). + +### Pre-built image +Custom Raspberry Pi OS Lite images with the latest RaspAP are available for [direct download](https://github.com/RaspAP/raspap-webgui/releases/latest). This includes both 32- and 64-bit builds for ARM architectures. + +| Operating system | Debian version | Kernel version | RaspAP version | Size | +| ---------------------| ---------------|-----------------|----------------|-------| +| Raspberry Pi OS (64-bit) Lite | 12 (bookworm) | 6.6 | Latest | 777 MB| +| Raspberry Pi OS (32-bit) Lite | 12 (bookworm) | 6.6 | Latest | 805 MB| + +These images are automatically generated with each release of RaspAP. You may choose between an `arm64` or `armhf` (32-bit) based build. Refer to [this resource](https://www.raspberrypi.com/software/operating-systems/) to ensure compatibility with your hardware. + +After downloading your desired image from the [latest release page](https://github.com/RaspAP/raspap-webgui/releases/latest), use a utility such as the Raspberry Pi Imager or [balenaEtcher](https://www.balena.io/etcher) to flash the OS image onto a microSD card. Insert the card into your device and boot it up. The latest RaspAP release version with the most popular optional components will be active and ready for you to configure. + +### Quick installer +Alternatively, start with a clean install of a [latest release of Raspberry Pi OS](https://www.raspberrypi.org/software/operating-systems/). Both the 32- and 64-bit release versions are supported, as well as the latest 64-bit Desktop distribution. + +Update RPi OS to its latest version, including the kernel and firmware, followed by a reboot: -1. Update Raspbian, including the kernel and firmware, followed by a reboot: ``` sudo apt-get update sudo apt-get full-upgrade sudo reboot ``` -2. Set the "WLAN country" option in `raspi-config`'s **Localisation Options**: `sudo raspi-config` +Set the WiFi country in raspi-config's **Localisation Options**: `sudo raspi-config`. -3. If you have a device without an onboard wireless chipset, the [**Edimax Wireless 802.11b/g/n nano USB adapter**](https://www.edimax.com/edimax/merchandise/merchandise_detail/data/edimax/global/wireless_adapters_n150/ew-7811un) is an excellent option – it's small, cheap and has good driver support. - -With the prerequisites done, you can proceed with either the Quick installer or Manual installation steps below. - -## Quick installer Install RaspAP from your device's shell prompt: ```sh curl -sL https://install.raspap.com | bash ``` -The [installer](https://docs.raspap.com/quick/) will complete the steps in the manual installation (below) for you. -After the reboot at the end of the installation the wireless network will be -configured as an access point as follows: +The Quick installer will respond to several [command line arguments](https://docs.raspap.com/quick/), or switches, to customize your installation in a variety of ways, or install one of RaspAP's optional helper tools. + +### Initial settings +After completing either of these setup options, the wireless AP network will be configured as follows: + * IP address: 10.3.141.1 * Username: admin * Password: secret @@ -69,7 +80,7 @@ configured as an access point as follows: * SSID: `raspi-webgui` * Password: ChangeMe -**Note:** As the name suggests, the Quick Installer is a great way to quickly setup a new AP. However, it does not automagically detect the unique configuration of your system. Best results are obtained by connecting to ethernet (`eth0`) or as a WiFi client, also known as managed mode, with `wlan0`. For the latter, refer to [this FAQ](https://docs.raspap.com/faq/#headless). Special instructions for the Pi Zero W are [available here](https://docs.raspap.com/ap-sta/). +It's _strongly recommended_ that your first post-install action is to change the default admin [authentication](https://docs.raspap.com/authentication/) settings. Thereafter, your AP's [basic settings](https://docs.raspap.com/ap-basics/) and many [advanced options](https://docs.raspap.com/ap-basics#advanced-options) are now ready to be modified by RaspAP. Please [read this](https://docs.raspap.com/issues/) before reporting an issue. @@ -118,11 +129,6 @@ By default RaspAP configures a routed AP for your clients to connect to. A bridg More information on Bridged AP mode is provided [in our documentation](https://docs.raspap.com/bridged/). -## Simultaneous AP and Wifi client -RaspAP lets you create an AP with a Wifi client configuration, often called [AP-STA mode](https://docs.raspap.com/ap-sta/). With your system configured in managed mode, enable the AP from the **Advanced** tab of **Configure hotspot** by sliding the **Wifi client AP mode** toggle. Save settings and start the hotspot. The managed mode AP is functional without restart. - -**Note:** This option is disabled until you configure your system as a wireless client. For a device operating in [managed mode](https://docs.raspap.com/faq/#headless) without an `eth0` connection, this configuration must be enabled [_before_ a reboot](https://docs.raspap.com/ap-sta/). - ## Manual installation Detailed manual setup instructions are provided [on our documentation site](https://docs.raspap.com/manual/). From dc86f15b590fb42132429488524eede01ca9886a Mon Sep 17 00:00:00 2001 From: Bill Zimmerman Date: Wed, 12 Feb 2025 23:45:26 +0100 Subject: [PATCH 61/87] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 07fbf2f9..daa7ae51 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ RaspAP was originally made for Raspbian, but now also installs on the following | Raspberry Pi OS | (64-bit) Desktop Bookworm | ARM | Official | | Raspberry Pi OS | (64-bit) Lite Bullseye | ARM | Official | | Raspberry Pi OS | (32-bit) Lite Bullseye | ARM | Official | -| Armbian | 23.11 (Jammy) | [ARM](https://docs.armbian.com/#supported-socs) | Official | +| Armbian | 23.11 (Jammy) | [ARM](https://docs.armbian.com/#supported-socs) | Beta | | Debian | Bookworm | ARM / x86_64 | Beta | | Ubuntu | Server 23.04 (Lunar) | ARM / x86_64 | Beta | From 4a89cb3b0704093204ecf3a28894fc1db1b4806e Mon Sep 17 00:00:00 2001 From: Bill Zimmerman Date: Thu, 13 Feb 2025 07:59:38 +0100 Subject: [PATCH 62/87] Update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index daa7ae51..2861c64a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ ![RaspAP Custom OS images](https://github.com/user-attachments/assets/e871adf1-123c-450b-94eb-80a185c242cc) [![Release 3.2.7](https://img.shields.io/badge/release-v3.2.7-green)](https://github.com/raspap/raspap-webgui/releases) [![Awesome](https://awesome.re/badge.svg)](https://github.com/thibmaek/awesome-raspberry-pi) [![Join Insiders](https://img.shields.io/static/v1?label=Insiders&message=%E2%9D%A4&logo=GitHub&color=ff69b4)](https://github.com/sponsors/RaspAP) [![Build Status](https://app.travis-ci.com/RaspAP/raspap-webgui.svg?branch=master)](https://app.travis-ci.com/RaspAP/raspap-webgui) [![Crowdin](https://badges.crowdin.net/raspap/localized.svg)](https://crowdin.com/project/raspap) [![Twitter URL](https://img.shields.io/twitter/url?label=%40RaspAP&logoColor=%23d8224c&url=https%3A%2F%2Ftwitter.com%2Frasp_ap)](https://twitter.com/rasp_ap) [![Reddit](https://img.shields.io/badge/%2Fr%2FRaspAP-e05d44?style=flat&logo=Reddit&logoColor=white&labelColor=e05d44&color=b14835)](https://reddit.com/r/RaspAP) [![Discord](https://img.shields.io/discord/642436993451819018?color=7289DA&label=Discord&logo=discord&style=flat)](https://discord.gg/KVAsaAR) +RaspAP is feature-rich wireless router software that _just works_ on many popular [Debian-based devices](#supported-operating-systems), including the Raspberry Pi. Our [custom OS images](#pre-built-image), [Quick installer](#quick-installer) and [Docker container](#docker-support) create a known-good default configuration for all current Raspberry Pis with onboard wireless. A fully responsive, mobile-ready interface gives you control over the relevant services and networking options. Advanced DHCP settings, WireGuard and OpenVPN support, [SSL certificates](https://docs.raspap.com/ssl/), [ad blocking](#ad-blocking), security audits, [captive portal integration](https://docs.raspap.com/captive/), themes and [multilingual options](https://docs.raspap.com/translations/) are included. -RaspAP is feature-rich wireless router software that _just works_ on many popular [Debian-based devices](#supported-operating-systems), including the Raspberry Pi. Our popular [Quick installer](#quick-installer) and [Docker container](#docker-support) create a known-good default configuration for all current Raspberry Pis with onboard wireless. A fully responsive, mobile-ready interface gives you control over the relevant services and networking options. Advanced DHCP settings, WireGuard and OpenVPN support, [SSL certificates](https://docs.raspap.com/ssl/), security audits, [captive portal integration](https://docs.raspap.com/captive/), themes and [multilingual options](https://docs.raspap.com/translations/) are included. - -RaspAP has been featured on sites such as [Instructables](http://www.instructables.com/id/Raspberry-Pi-As-Completely-Wireless-Router/), [Adafruit](https://blog.adafruit.com/2016/06/24/raspap-wifi-configuration-portal-piday-raspberrypi-raspberry_pi/), [Raspberry Pi Weekly](https://www.raspberrypi.org/weekly/commander/) and [Awesome Raspberry Pi](https://project-awesome.org/thibmaek/awesome-raspberry-pi) and implemented in countless projects. +RaspAP has been featured by [PC World](https://www.pcwelt.de/article/1789512/raspberry-pi-als-wlan-router.html), [Adafruit](https://blog.adafruit.com/2016/06/24/raspap-wifi-configuration-portal-piday-raspberrypi-raspberry_pi/), [Raspberry Pi Weekly](https://www.raspberrypi.org/weekly/commander/), and [Awesome Raspberry Pi](https://project-awesome.org/thibmaek/awesome-raspberry-pi) and implemented in [countless projects](https://github.com/RaspAP/raspap-awesome#projects). We hope you enjoy using RaspAP as much as we do creating it. Tell us how you use this with [your own projects](https://github.com/raspap/raspap-awesome). From 6e0cf0b085b07392955d1dbb8e416246f9de0afe Mon Sep 17 00:00:00 2001 From: billz Date: Mon, 17 Feb 2025 02:34:20 -0800 Subject: [PATCH 63/87] Update w/ javascript support --- installers/plugin_helper.sh | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/installers/plugin_helper.sh b/installers/plugin_helper.sh index 2d946e36..b3abe5e3 100755 --- a/installers/plugin_helper.sh +++ b/installers/plugin_helper.sh @@ -77,6 +77,29 @@ case "$action" in echo "OK" ;; + "javascript") + [ $# -lt 2 ] && { echo "Usage: $0 javascript "; exit 1; } + + source=$1 + destination=$2 + + if [ ! -f "$source" ]; then + echo "Source file $source does not exist." + exit 1 + fi + + plugin_js_dir=$(dirname "$destination") + if [ ! -d "$plugin_js_dir" ]; then + mkdir -p "$plugin_js_dir" + fi + + cp "$source" "$destination" + chown -R $raspap_user:$raspap_user "$plugin_js_dir" + + echo "OK" + ;; + + "plugin") [ $# -lt 2 ] && { echo "Usage: $0 plugin "; exit 1; } @@ -89,7 +112,7 @@ case "$action" in fi plugin_dir=$(dirname "$destination") - if [ ! -d "$lugin_dir" ]; then + if [ ! -d "$plugin_dir" ]; then mkdir -p "$plugin_dir" fi @@ -103,11 +126,12 @@ case "$action" in echo "Invalid action: $action" echo "Usage: $0 [parameters...]" echo "Actions:" - echo " sudoers Install a sudoers file" - echo " packages Install aptitude package(s)" - echo " user Add user non-interactively" - echo " config Applies a config file" - echo " plugin Copies a plugin directory" + echo " sudoers Install a sudoers file" + echo " packages Install aptitude package(s)" + echo " user Add user non-interactively" + echo " config Applies a config file" + echo " javascript Applies a JavaScript file" + echo " plugin Copies a plugin directory" exit 1 ;; esac From 4374e78bbb2dc6324c0ed5ee3c54206771d41754 Mon Sep 17 00:00:00 2001 From: billz Date: Mon, 17 Feb 2025 02:35:42 -0800 Subject: [PATCH 64/87] Add JavaScript handling to installPlugin(), fix bug w/ manifest parsing --- src/RaspAP/Plugins/PluginInstaller.php | 64 ++++++++++++++++++-------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/src/RaspAP/Plugins/PluginInstaller.php b/src/RaspAP/Plugins/PluginInstaller.php index 6ff6ebf8..63d9654d 100644 --- a/src/RaspAP/Plugins/PluginInstaller.php +++ b/src/RaspAP/Plugins/PluginInstaller.php @@ -63,31 +63,36 @@ class PluginInstaller if (json_last_error() !== JSON_ERROR_NONE) { throw new \Exception("Error parsing manifest.json: " . json_last_error_msg()); } + // fetch installed plugins $installedPlugins = $this->getPlugins(); - $plugins = []; foreach ($manifestData as $pluginManifest) { - $installed = false; + $pluginEntries = []; - // Check if the plugin is installed - foreach ($installedPlugins as $plugin) { - if (str_contains($plugin, $pluginManifest[0]['namespace'])) { - $installed = true; - break; + foreach ($pluginManifest as $plugin) { + $installed = false; + + if (!empty($plugin['namespace'])) { + foreach ($installedPlugins as $installedPlugin) { + if (str_contains($installedPlugin, $plugin['namespace'])) { + $installed = true; + break; + } + } } + $pluginEntries[] = [ + 'manifest' => $plugin, + 'installed' => $installed + ]; } - $plugins[] = [ - 'manifest' => $pluginManifest, - 'installed' => $installed - ]; + $plugins[] = $pluginEntries; } - return $plugins; + return array_merge(...$plugins); } catch (\Exception $e) { error_log("An error occurred: " . $e->getMessage()); - throw $e; // re-throw to global ExceptionHandler - return []; + throw $e; // re-throw to global ExceptionHandler } } @@ -151,6 +156,10 @@ class PluginInstaller $this->copyConfigFiles($manifest['configuration'], $pluginDir); $rollbackStack[] = 'removeConfigFiles'; } + if (!empty($manifest['javascript'])) { + $this->copyJavaScriptFiles($manifest['javascript'], $this->rootPath); + $rollbackStack[] = 'removeJavaScript'; + } $this->copyPluginFiles($pluginDir, $this->rootPath); $rollbackStack[] = 'removePluginFiles'; @@ -260,6 +269,26 @@ class PluginInstaller } } + /** + * Copies plugin JavaScript files to their destination + * + * @param array $javascript + * @param string $pluginDir + */ + private function copyJavaScriptFiles(array $javascript, string $destination): void + { + foreach ($javascript as $js) { + $source = escapeshellarg($pluginDir . DIRECTORY_SEPARATOR . $js['source']); + $destination = escapeshellarg($destination . DIRECTORY_SEPARATOR . 'app/js/plugins' . DIRECTORY_SEPARATOR); + $cmd = sprintf('sudo /etc/raspap/plugins/plugin_helper.sh javascript %s %s', $source, $destination); + $return = shell_exec($cmd); + if (strpos(strtolower($return), 'ok') === false) { + throw new \Exception("Failed to copy JavaScript file: $source"); + } + } + } + + /** * Copies an extracted plugin directory from /tmp to /plugins * @@ -381,11 +410,10 @@ class PluginInstaller $html .= ''; foreach ($plugins as $plugin) { - - $manifestData = $plugin['manifest'][0] ?? []; // Access the first manifest entry or default to an empty array - + $manifestData = $plugin['manifest'] ?? []; + $installed = $plugin['installed'] ?? false; $manifest = htmlspecialchars(json_encode($manifestData), ENT_QUOTES, 'UTF-8'); - $installed = $plugin['installed']; + if ($installed === true) { $button = '