ignore emhttp/plugins/dynamix.my.servers

This commit is contained in:
Pujit Mehrotra
2025-06-24 16:56:28 -04:00
parent 2a6aae4fa2
commit 0897e68830
8 changed files with 7 additions and 521 deletions

10
.gitignore vendored
View File

@@ -82,7 +82,7 @@ unraid-api/
.node-version
# Node.js built-in's like npm & corepack
lib/
lib/node_modules
# unraid-api & node binaries
bin/
@@ -91,11 +91,15 @@ sbin/unraid-api
# Unraid API readme/changelog
emhttp/plugins/dynamix.unraid.net
# API / Web component / Connect functionality
emhttp/plugins/dynamix.my.servers
# rc scripts
etc/rc.d/rc.flash_backup
etc/rc.d/rc.unraid-api
etc/rc.d/rc0.d
etc/rc.d/rc6.d
etc/rc.d/rc6.d/K10flash-backup
etc/rc.d/rc6.d/K20unraid-api
# API helper scripts
share/dynamix.unraid.net/

View File

@@ -1,12 +0,0 @@
# Provided by Unraid API
Connect.page
scripts/
unraid-components/
include/UpdateFlashBackup.php
include/sso-login.php
include/unraid-api.php
include/welcome-modal.php
include/myservers1.php
include/state.php
include/translations.php

View File

@@ -1,23 +0,0 @@
Menu="About:30"
Type="xmenu"
Title="Registration"
Icon="icon-registration"
Tag="pencil"
---
<?php
/* Copyright 2005-2023, Lime Technology
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
require_once "$docroot/plugins/dynamix/include/ReplaceKey.php";
$replaceKey = new ReplaceKey();
$replaceKey->check(true);
?>
<unraid-i18n-host>
<unraid-registration></unraid-registration>
</unraid-i18n-host>

View File

@@ -1,10 +0,0 @@
<?php
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
$var = (array)parse_ini_file('state/var.ini'); // required for state.php - don't remove unless you've refactored the code to work without it
require_once "$docroot/plugins/dynamix.my.servers/include/state.php";
$serverState = new ServerState();
header('Content-type: application/json');
echo $serverState->getServerStateJson();

View File

@@ -1,166 +0,0 @@
<?php
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
class ActivationCodeExtractor {
public const DIR = '/boot/config/activation';
public const FILE_PATTERN = '/activation_code_([A-Za-z0-9]+)\.activationcode/';
public const DOCROOT = '/usr/local/emhttp';
public const WEBGUI_IMAGES_BASE_DIR = '/webGui/images';
public const PARTNER_LOGO_FILE_NAME = 'partner-logo.svg';
public const DEFAULT_LOGO = self::DOCROOT . self::WEBGUI_IMAGES_BASE_DIR . '/UN-logotype-gradient.svg';
/** @var array{
* code: string,
* partnerName: string,
* partnerUrl?: string,
* sysModel?: string,
* comment?: string,
* caseIcon: string,
* partnerLogo?: boolean,
* header?: string,
* headermetacolor?: string,
* background?: string,
* showBannerGradient?: string,
* theme?: "azure" | "black" | "gray" | "white
* }
*/
private array $data = [];
private string $partnerName = '';
private string $partnerUrl = 'https://unraid.net';
private string $partnerLogoPath = '';
/**
* Constructor to automatically fetch JSON data from all matching files.
*/
public function __construct() {
$this->data = $this->fetchJsonData();
}
/**
* Fetch JSON data from all files matching the pattern.
*
* @return array Array of extracted JSON data.
*/
private function fetchJsonData(): array {
$data = [];
if (!is_dir(self::DIR)) {
return $data;
}
$files = scandir(self::DIR);
if ($files === false || count($files) === 0) {
return $data;
}
foreach ($files as $file) {
$filePath = self::DIR . DIRECTORY_SEPARATOR . $file;
if (preg_match(self::FILE_PATTERN, $file, $matches)) {
// $activationCode = $matches[1];
$fileContent = file_get_contents($filePath);
$jsonData = json_decode($fileContent, true);
if (json_last_error() === JSON_ERROR_NONE) {
$data = $jsonData;
} else {
$data = ['error' => 'Invalid JSON format'];
}
break; // Stop after the first match
}
}
if (isset($data['partnerName'])) {
$this->partnerName = $data['partnerName'];
}
if (isset($data['partnerUrl'])) {
$this->partnerUrl = $data['partnerUrl'];
}
/**
* During the plg install, the partner logo asset is copied to the webgui images dir.
*/
$logo = self::DOCROOT . self::WEBGUI_IMAGES_BASE_DIR . '/' . self::PARTNER_LOGO_FILE_NAME;
if (file_exists($logo)) {
$this->partnerLogoPath = $logo;
}
return $data;
}
/**
* Get the partner logo path.
*
* @return string
*/
public function getPartnerLogoPath(): string {
return $this->partnerLogoPath;
}
/**
* Get the extracted data.
*
* @return array
*/
public function getData(): array {
return $this->data;
}
/**
* Retrieve the activation code data as JSON string with converted special characters to HTML entities
*
* @return string
*/
public function getDataForHtmlAttr(): string {
$json = json_encode($this->getData());
return htmlspecialchars($json, ENT_QUOTES, 'UTF-8');
}
/**
* Get the partner logo render string.
*
* @return string
*/
public function getPartnerLogoRenderString(): string {
if (empty($this->partnerLogoPath)) { // default logo
return file_get_contents(self::DEFAULT_LOGO);
}
return file_get_contents($this->partnerLogoPath);
}
/**
* Get the partner name.
*
* @return string
*/
public function getPartnerName(): string {
return $this->partnerName;
}
/**
* Get the partner URL.
*
* @return string
*/
public function getPartnerUrl(): string {
return $this->partnerUrl;
}
/**
* Output for debugging
* @return void
*/
public function debug(): void {
echo "data: "; var_dump($this->data);
echo "partnerName: "; var_dump($this->partnerName);
echo "partnerUrl: "; var_dump($this->partnerUrl);
echo "partnerLogoPath: "; var_dump($this->partnerLogoPath);
echo $this->getPartnerLogoRenderString();
}
}

View File

@@ -1,36 +0,0 @@
<?php
/* Copyright 2005-2023, Lime Technology
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once("$docroot/plugins/dynamix.my.servers/include/state.php");
require_once("$docroot/plugins/dynamix.my.servers/include/translations.php");
$serverState = new ServerState();
$wCTranslations = new WebComponentTranslations();
?>
<script>
window.LOCALE_DATA = '<?= $wCTranslations->getTranslationsJson(true) ?>';
/**
* So we're not needing to modify DefaultLayout with an additional include, we'll add the Modals web component to the bottom of the body.
*/
const i18nHostWebComponent = 'unraid-i18n-host';
const modalsWebComponent = 'unraid-modals';
if (!document.getElementsByTagName(modalsWebComponent).length) {
const $body = document.getElementsByTagName('body')[0];
const $i18nHost = document.createElement(i18nHostWebComponent);
const $modals = document.createElement(modalsWebComponent);
$body.appendChild($i18nHost);
$i18nHost.appendChild($modals);
}
</script>
<unraid-i18n-host>
<unraid-user-profile server="<?= $serverState->getServerStateJsonForHtmlAttr() ?>"></unraid-user-profile>
</unraid-i18n-host>

View File

@@ -1,168 +0,0 @@
<?php
/* Copyright 2005-2023, Lime Technology
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
/**
* RebootDetails class is responsible for detecting the type and version of a system reboot required in the context of an unRAID server.
*
* Usage:
* ```
* $rebootDetails = new RebootDetails();
* $rebootType = $rebootDetails->rebootType;
* ```
*/
class RebootDetails
{
const CURRENT_CHANGES_TXT_PATH = '/boot/changes.txt';
const CURRENT_README_RELATIVE_PATH = 'plugins/unRAIDServer/README.md';
const CURRENT_VERSION_PATH = '/etc/unraid-version';
const PREVIOUS_BZ_ROOT_PATH = '/boot/previous/bzroot';
const PREVIOUS_CHANGES_TXT_PATH = '/boot/previous/changes.txt';
private $currentVersion = '';
public $rebootType = ''; // 'update', 'downgrade', 'thirdPartyDriversDownloading'
public $rebootReleaseDate = '';
public $rebootVersion = '';
public $previousReleaseDate = '';
public $previousVersion = '';
/**
* Constructs a new RebootDetails object and automatically detects the reboot type during initialization.
*/
public function __construct()
{
$this->detectRebootType();
}
/**
* Detects the type of reboot required based on the contents of the unRAID server's README.md file.
* Sets the $rebootType property accordingly.
*/
private function detectRebootType()
{
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
/**
* Read the reboot readme, and see if it says "REBOOT REQUIRED" or "DOWNGRADE"
* only relying on the README.md file to save reads from the flash drive.
* because we started allowing downgrades from the account.unraid.net Update OS page, we can't
* fully rely on the README.md value of being accurate.
* For instance if on 6.13.0-beta.2.1 then chose to "Downgrade" to 6.13.0-beta.1.10 from the account app
* the README.md file would still say "REBOOT REQUIRED".
*/
$rebootReadme = @file_get_contents("$docroot/" . self::CURRENT_README_RELATIVE_PATH, false, null, 0, 20) ?: '';
$rebootDetected = preg_match("/^\*\*(REBOOT REQUIRED|DOWNGRADE)/", $rebootReadme);
if (!$rebootDetected) {
return;
}
/**
* if a reboot is required, then:
* get current Unraid version from /etc/unraid-version
* then get the version of the last update from self::CURRENT_CHANGES_TXT_PATH
* if they're different, then a reboot is required
* if the version in self::CURRENT_CHANGES_TXT_PATH is less than the current version, then a downgrade is required
* if the version in self::CURRENT_CHANGES_TXT_PATH is greater than the current version, then an update is required
*/
$this->setCurrentVersion();
$this->setRebootDetails();
if ($this->currentVersion == '' || $this->rebootVersion == '') {
return; // return to prevent potential incorrect outcome
}
$compareVersions = version_compare($this->rebootVersion, $this->currentVersion);
switch ($compareVersions) {
case -1:
$this->setRebootType('downgrade');
break;
case 0:
// we should never get here, but if we do, then no reboot is required and just return
return;
case 1:
$this->setRebootType('update');
break;
}
// Detect if third-party drivers were part of the update process
$processWaitingThirdPartyDrivers = "inotifywait -q " . self::CURRENT_CHANGES_TXT_PATH . " -e move_self,delete_self";
// Run the ps command to list processes and check if the process is running
$ps_command = "ps aux | grep -E \"$processWaitingThirdPartyDrivers\" | grep -v \"grep -E\"";
$output = shell_exec($ps_command) ?? '';
if ($this->rebootType != '' && strpos($output, $processWaitingThirdPartyDrivers) !== false) {
$this->setRebootType('thirdPartyDriversDownloading');
}
}
/**
* Detects and retrieves the version information related to the system reboot based on the contents of the '/boot/changes.txt' file.
*
* @return string The system version information or 'Not found' if not found, or 'File not found' if the file is not present.
*/
private function readChangesTxt(string $file_path = self::CURRENT_CHANGES_TXT_PATH)
{
// Check if the file exists
if (file_exists($file_path)) {
exec("head -n4 $file_path", $rows);
foreach ($rows as $row) {
$i = stripos($row,'version');
if ($i !== false) {
[$version, $releaseDate] = explode(' ', trim(substr($row, $i+7)));
break;
}
}
return [
'releaseDate' => $releaseDate ?? 'Not found',
'version' => $version ?? 'Not found',
];
} else {
return 'File not found';
}
}
/**
* Sets the current version of the Unraid server for comparison with the reboot version.
*/
private function setCurrentVersion() {
// output ex: version="6.13.0-beta.2.1"
$raw = @file_get_contents(self::CURRENT_VERSION_PATH) ?: '';
// Regular expression to match the version between the quotes
$pattern = '/version="([^"]+)"/';
if (preg_match($pattern, $raw, $matches)) {
$this->currentVersion = $matches[1];
}
}
private function setRebootDetails()
{
$rebootDetails = $this->readChangesTxt();
$this->rebootReleaseDate = $rebootDetails['releaseDate'];
$this->rebootVersion = $rebootDetails['version'];
}
private function setRebootType($rebootType)
{
$this->rebootType = $rebootType;
}
/**
* If self::PREVIOUS_BZ_ROOT_PATH exists, then the user has the option to downgrade to the previous version.
* Parse the text file /boot/previous/changes.txt to get the version number of the previous version.
* Then we move some files around and reboot.
*/
public function setPrevious()
{
if (@file_exists(self::PREVIOUS_BZ_ROOT_PATH) && @file_exists(self::PREVIOUS_CHANGES_TXT_PATH)) {
$parseOutput = $this->readChangesTxt(self::PREVIOUS_CHANGES_TXT_PATH);
$this->previousVersion = $parseOutput['version'];
$this->previousReleaseDate = $parseOutput['releaseDate'];
}
}
}

View File

@@ -1,103 +0,0 @@
<?php
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
class WebComponentsExtractor
{
private const PREFIXED_PATH = '/plugins/dynamix.my.servers/unraid-components/';
private const RICH_COMPONENTS_ENTRY = 'unraid-components.client.mjs';
private const UI_ENTRY = 'src/register.ts';
private const UI_STYLES_ENTRY = 'style.css';
public function __construct() {}
private function findManifestFiles(string $manifestName): array
{
$basePath = '/usr/local/emhttp' . self::PREFIXED_PATH;
$escapedBasePath = escapeshellarg($basePath);
$escapedManifestName = escapeshellarg($manifestName);
$command = "find {$escapedBasePath} -name {$escapedManifestName}";
exec($command, $files);
return $files;
}
public function getAssetPath(string $asset, string $subfolder = ''): string
{
return self::PREFIXED_PATH . ($subfolder ? $subfolder . '/' : '') . $asset;
}
private function getRelativePath(string $fullPath): string
{
$basePath = '/usr/local/emhttp' . self::PREFIXED_PATH;
$relative = str_replace($basePath, '', $fullPath);
return dirname($relative);
}
public function getManifestContents(string $manifestPath): array
{
$contents = @file_get_contents($manifestPath);
return $contents ? json_decode($contents, true) : [];
}
private function getRichComponentsFile(): string
{
$manifestFiles = $this->findManifestFiles('manifest.json');
foreach ($manifestFiles as $manifestPath) {
$manifest = $this->getManifestContents($manifestPath);
$subfolder = $this->getRelativePath($manifestPath);
foreach ($manifest as $key => $value) {
if (strpos($key, self::RICH_COMPONENTS_ENTRY) !== false && isset($value["file"])) {
return ($subfolder ? $subfolder . '/' : '') . $value["file"];
}
}
}
return '';
}
private function getRichComponentsScript(): string
{
$jsFile = $this->getRichComponentsFile();
if (empty($jsFile)) {
return '<script>console.error("%cNo matching key containing \'' . self::RICH_COMPONENTS_ENTRY . '\' found.", "font-weight: bold; color: white; background-color: red");</script>';
}
return '<script src="' . $this->getAssetPath($jsFile) . '"></script>';
}
private function getUnraidUiScriptHtml(): string
{
$manifestFiles = $this->findManifestFiles('ui.manifest.json');
if (empty($manifestFiles)) {
error_log("No ui.manifest.json found");
return '';
}
$manifestPath = $manifestFiles[0]; // Use the first found manifest
$manifest = $this->getManifestContents($manifestPath);
$subfolder = $this->getRelativePath($manifestPath);
if (!isset($manifest[self::UI_ENTRY]) || !isset($manifest[self::UI_STYLES_ENTRY])) {
error_log("Required entries not found in ui.manifest.json");
return '';
}
$jsFile = ($subfolder ? $subfolder . '/' : '') . $manifest[self::UI_ENTRY]['file'];
$cssFile = ($subfolder ? $subfolder . '/' : '') . $manifest[self::UI_STYLES_ENTRY]['file'];
return '<script defer type="module">
import { registerAllComponents } from "' . $this->getAssetPath($jsFile) . '";
registerAllComponents({ pathToSharedCss: "' . $this->getAssetPath($cssFile) . '" });
</script>';
}
public function getScriptTagHtml(): string
{
try {
return $this->getRichComponentsScript() . $this->getUnraidUiScriptHtml();
} catch (\Exception $e) {
error_log("Error in WebComponentsExtractor::getScriptTagHtml: " . $e->getMessage());
return "";
}
}
}