chore: require connect plugin to enable flash backup (#1419)

## Summary by CodeRabbit

- **New Features**
- Added a check to ensure the "unraid-api-plugin-connect" plugin is
enabled before allowing flash backup functionality.
- Introduced a utility to directly verify if specific API plugins are
enabled.

- **Refactor**
- Updated internal logic to use a centralized class and script-based
checks for plugin status and version instead of manual config parsing.
- Improved script command-line interface for easier plugin status and
version checks.

- **Bug Fixes**
- Flash Backup feature and service now only activate when the required
API plugin is enabled, preventing unintended usage.
- Flash Backup UI is conditionally displayed based on the presence of
the API plugin.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1209357561531351
  - https://app.asana.com/0/0/1210541992642236
This commit is contained in:
Pujit Mehrotra
2025-06-18 10:20:59 -04:00
committed by GitHub
parent 642a220c3a
commit d9ab58eb83
6 changed files with 196 additions and 42 deletions
@@ -1,6 +1,7 @@
#!/bin/bash
# This file is /etc/rc.d/rc.flash_backup
# use at queue "f" for flash backup
scripts_dir="/usr/local/share/dynamix.unraid.net/scripts"
QUEUE=" -q f "
TASKNAME="/etc/rc.d/rc.flash_backup watch"
TASKACTION="/usr/local/emhttp/plugins/dynamix.my.servers/scripts/UpdateFlashBackup update"
@@ -153,7 +154,10 @@ _enabled() {
local output
output=$(git -C /boot config --get remote.origin.url 2>&1)
if [[ "${output}" == *"backup.unraid.net"* ]]; then
return 0
# Also check if the connect API plugin is enabled
if "$scripts_dir/api_utils.sh" is_api_plugin_enabled unraid-api-plugin-connect; then
return 0
fi
fi
return 1
}
@@ -28,7 +28,7 @@ uninstall() {
# Service control functions
start() {
echo "Starting Unraid API service..."
# Restore vendored API plugins if they were installed
if [ -x "$scripts_dir/dependencies.sh" ]; then
"$scripts_dir/dependencies.sh" restore || {
@@ -37,21 +37,24 @@ start() {
else
echo "Warning: dependencies.sh script not found or not executable"
fi
# Create log directory if it doesn't exist
mkdir -p /var/log/unraid-api
# Copy env file if needed
if [ -f "${api_base_dir}/.env.production" ] && [ ! -f "${api_base_dir}/.env" ]; then
cp "${api_base_dir}/.env.production" "${api_base_dir}/.env"
fi
# Start the flash backup service if available
# Start the flash backup service if available and connect plugin is enabled
if [ -x "/etc/rc.d/rc.flash_backup" ]; then
echo "Starting flash backup service..."
/etc/rc.d/rc.flash_backup start
# Check if connect plugin is enabled before starting flash backup
if [ -x "$scripts_dir/api_utils.sh" ] && "$scripts_dir/api_utils.sh" is_api_plugin_enabled "unraid-api-plugin-connect"; then
echo "Starting flash backup service..."
/etc/rc.d/rc.flash_backup start
fi
fi
# Start the API service
if [ -x "${unraid_binary_path}" ]; then
"${unraid_binary_path}" start
@@ -96,7 +99,7 @@ case "$1" in
'stop')
stop
;;
'restart'|'reload')
'restart' | 'reload')
restart
;;
'status')
@@ -14,6 +14,7 @@ Tag="globe"
* all copies or substantial portions of the Software.
*/
require_once "$docroot/plugins/dynamix.my.servers/include/state.php";
require_once "$docroot/plugins/dynamix.my.servers/include/api-config.php";
require_once "$docroot/webGui/include/Wrappers.php";
$serverState = new ServerState();
@@ -28,6 +29,8 @@ $hasMyUnraidNetCert = preg_match('/.*\.myunraid\.net$/', $serverState->nginxCfg[
$isRegistered = $serverState->registered;
$isMiniGraphConnected = $serverState->myServersMiniGraphConnected;
$isConnectPluginInstalled = ApiConfig::isConnectPluginEnabled();
$flashbackup_status = $serverState->flashbackupStatus;
$passwd_result = exec('/usr/bin/passwd --status root');
@@ -486,6 +489,7 @@ $('body').on('click', '.js-setCurrentHostExtraOrigins', function(e) {
});
</script>
<?if($isConnectPluginInstalled):?>
<div markdown="1" class="<?=$shade?>"><!-- begin Flash Backup section -->
_(Flash backup)_:
<?if(!$isRegistered):?>
@@ -563,6 +567,7 @@ $(function() {
</script>
<?endif // end show flash backup form ?>
</div><!-- end Flash Backup section -->
<?endif // end connect plugin check ?>
<!-- legacy search compatibility -->
<div style="display:none;">
@@ -0,0 +1,97 @@
<?php
/**
* API Configuration utilities
* Centralized functions for checking API plugin status
*/
class ApiConfig
{
private static $scriptsDir = "/usr/local/share/dynamix.unraid.net/scripts";
/**
* Get the path to api_utils.sh script
* @return string
*/
private static function getApiUtilsScript()
{
return self::$scriptsDir . "/api_utils.sh";
}
/**
* Execute a command safely with proper error handling
* @param string $command The command to execute
* @param int &$exitCode Reference to store the exit code
* @return string The command output
*/
private static function executeCommand($command, &$exitCode = null)
{
$output = [];
$exitCode = 0;
exec($command, $output, $exitCode);
return implode("\n", $output);
}
/**
* Check if a specific API plugin is enabled
* @param string $pluginName The name of the plugin to check
* @return bool True if plugin is enabled, false otherwise
*/
public static function isApiPluginEnabled($pluginName)
{
if (empty($pluginName) || !is_string($pluginName)) {
return false;
}
$apiUtilsScript = self::getApiUtilsScript();
if (!is_executable($apiUtilsScript)) {
return false;
}
$escapedScript = escapeshellarg($apiUtilsScript);
$escapedPlugin = escapeshellarg($pluginName);
$command = "$escapedScript is_api_plugin_enabled $escapedPlugin 2>/dev/null";
$exitCode = 0;
self::executeCommand($command, $exitCode);
return $exitCode === 0;
}
/**
* Check if the unraid-api-plugin-connect is enabled
* @return bool True if connect plugin is enabled, false otherwise
*/
public static function isConnectPluginEnabled()
{
return self::isApiPluginEnabled('unraid-api-plugin-connect');
}
/**
* Get API version from api_utils.sh
* @return string The API version or 'unknown' if not found
*/
public static function getApiVersion()
{
$apiUtilsScript = self::getApiUtilsScript();
if (!is_executable($apiUtilsScript)) {
return 'unknown';
}
$escapedScript = escapeshellarg($apiUtilsScript);
$command = "$escapedScript get_api_version 2>/dev/null";
$exitCode = 0;
$output = self::executeCommand($command, $exitCode);
if ($exitCode !== 0) {
return 'unknown';
}
$version = trim($output);
return !empty($version) ? $version : 'unknown';
}
}
@@ -17,6 +17,7 @@ $docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
require_once "$docroot/plugins/dynamix.my.servers/include/reboot-details.php";
require_once "$docroot/plugins/dynamix.plugin.manager/include/UnraidCheck.php";
require_once "$docroot/plugins/dynamix.my.servers/include/api-config.php";
/**
* ServerState class encapsulates server-related information and settings.
*
@@ -146,31 +147,12 @@ class ServerState
private function setConnectValues()
{
$apiConfigPath = '/boot/config/plugins/dynamix.my.servers/configs/api.json';
if (!file_exists($apiConfigPath)) {
if (!ApiConfig::isConnectPluginEnabled()) {
return; // plugin is not installed; exit early
}
$apiConfig = @json_decode(file_get_contents($apiConfigPath), true);
$pluginName = 'unraid-api-plugin-connect'; // name of connect plugin's npm package
if ($apiConfig && is_array($apiConfig['plugins'])) {
foreach ($apiConfig['plugins'] as $plugin) {
// recognize npm version identifiers inside $apiConfig['plugins']
if ($plugin === $pluginName || strpos($plugin, $pluginName . '@') === 0) {
$this->connectPluginInstalled = 'dynamix.unraid.net.plg';
break;
}
}
}
// exit early if the plugin is not installed
if (!$this->connectPluginInstalled) {
return;
}
// Get version directly using api_utils.sh get_api_version function
$this->connectPluginVersion = trim(@exec('/usr/local/share/dynamix.unraid.net/scripts/api_utils.sh get_api_version 2>/dev/null')) ?: 'unknown';
$this->connectPluginInstalled = 'dynamix.unraid.net.plg';
$this->connectPluginVersion = ApiConfig::getApiVersion();
$this->getMyServersCfgValues();
$this->getConnectKnownOrigins();
$this->getFlashBackupStatus();
@@ -9,7 +9,7 @@ CONFIG_FILE="/usr/local/share/dynamix.unraid.net/config/vendor_archive.json"
# Returns the API version string or empty if not found
get_api_version() {
local api_version=""
# Get version from config file
if [ -f "$CONFIG_FILE" ] && command -v jq >/dev/null 2>&1; then
api_version=$(jq -r '.api_version' "$CONFIG_FILE")
@@ -18,23 +18,23 @@ get_api_version() {
return 0
fi
fi
# No version found
return 1
}
# Get vendor archive configuration information
# Get vendor archive configuration information
# Returns an array of values: api_version, vendor_store_url, vendor_store_path
get_archive_information() {
# Define all local variables at the top
local api_version=""
local vendor_store_path=""
if [ ! -f "$CONFIG_FILE" ]; then
echo "Vendor archive config file not found at $CONFIG_FILE" >&2
return 1
fi
# Read values from JSON config using jq
if command -v jq >/dev/null 2>&1; then
api_version=$(jq -r '.api_version' "$CONFIG_FILE")
@@ -43,20 +43,83 @@ get_archive_information() {
echo "jq not found, can't parse config file" >&2
return 1
fi
# Validate that all required values exist and are not null
if [ -z "$api_version" ] || [ "$api_version" = "null" ]; then
echo "Invalid or missing api_version in config file" >&2
return 1
fi
if [ -z "$vendor_store_path" ] || [ "$vendor_store_path" = "null" ]; then
echo "Invalid or missing vendor_store_path in config file" >&2
return 1
fi
# Return the values
echo "$api_version"
echo "$vendor_store_path"
return 0
}
}
# Check if an API plugin is enabled
# Usage: is_api_plugin_enabled <plugin_name>
# Returns 0 if enabled, 1 if not enabled or error
is_api_plugin_enabled() {
local plugin_name="$1"
local api_config_path="/boot/config/plugins/dynamix.my.servers/configs/api.json"
# Check if plugin name is provided
if [ -z "$plugin_name" ]; then
return 1
fi
# Check if API config file exists
if [ ! -f "$api_config_path" ]; then
return 1
fi
# Check if jq is available
if ! command -v jq >/dev/null 2>&1; then
return 1
fi
# Parse JSON and check for plugin
local plugins
plugins=$(jq -r '.plugins[]?' "$api_config_path" 2>/dev/null)
if [ $? -ne 0 ]; then
return 1
fi
# Check each plugin entry
while IFS= read -r plugin; do
if [ "$plugin" = "$plugin_name" ] || [[ "$plugin" == "$plugin_name@"* ]]; then
return 0
fi
done <<<"$plugins"
return 1
}
# Main execution when script is called directly
# Handle command line arguments
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
case "$1" in
"get_api_version")
get_api_version
;;
"get_archive_information")
get_archive_information
;;
"is_api_plugin_enabled")
if [ -z "$2" ]; then
echo "Error: Plugin name required" >&2
exit 1
fi
is_api_plugin_enabled "$2"
;;
*)
echo "Usage: $0 {get_api_version|get_archive_information|is_api_plugin_enabled <plugin_name>}" >&2
exit 1
;;
esac
fi