From bd5396fb921169cba3c4db929432d72c94b25760 Mon Sep 17 00:00:00 2001 From: Tom Elliott Date: Tue, 1 Sep 2026 08:23:01 -0500 Subject: [PATCH] Retire the persistentgroups plugin Core now does what it existed to fake, and does it without the copying. A group holds a snapin grant of its own (fogproject #1604, #1611, #1612), so a host added to a group later is covered by it and a host removed stops being covered -- which is the thing this plugin could never manage, because it acted once, at the moment of joining, and left rows behind. The plugin was already substantially non-functional on 1.6 data. Its printer and module copies carry no ON DUPLICATE KEY UPDATE, so a collision raised a duplicate-key error inside an AFTER INSERT trigger and rolled back the groupMembers row that fired it: the host was not added to the group at all. DELETING THIS DIRECTORY DOES NOT REMOVE THE PLUGIN FROM A SERVER, and that is the whole reason the retirement is a schema step rather than a file removal. The work is a database TRIGGER, which outlives the files completely -- sites that deleted this plugin years ago have had it firing ever since, copying 13 hosts columns including hostADPass onto member hosts, below the PHP layer and outside the audit trail. fogproject schema step 402 drops the trigger and removes the registration row; this commit only stops shipping the source that would re-create it. Removed alongside the directory: the README row, and a comment in tests/plugin-layout.test.php that named this plugin as the example of one with models but no pages. capone still carries the other half of that comment's point, so the check itself is unchanged. Co-Authored-By: Claude Opus 5 --- README.md | 1 - persistentgroups/config/plugin.config.php | 29 ---- .../src/Items/PersistentGroups.php | 27 ---- .../src/Managers/PersistentGroupsManager.php | 144 ------------------ tests/plugin-layout.test.php | 4 +- 5 files changed, 2 insertions(+), 203 deletions(-) delete mode 100644 persistentgroups/config/plugin.config.php delete mode 100644 persistentgroups/src/Items/PersistentGroups.php delete mode 100644 persistentgroups/src/Managers/PersistentGroupsManager.php diff --git a/README.md b/README.md index 4c1d865..4dbd380 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,6 @@ the rest. | `ntfy` | 1.6.0 | Notifications via ntfy.sh or a self-hosted ntfy server | | `oidc` | 1.6.0 | Sign in through an OpenID Connect provider; the reference for a plugin that adds a route rather than a resource | | `ou` | 1.6.0 | Predefine Active Directory OUs and associate them with hosts | -| `persistentgroups` | 1.6.0 | On joining a group, copy image, AD, printer and location settings from a template host named after that group | | `pushbullet` | 1.6.0 | Pushbullet notifications | | `slack` | 1.6.0 | Slack API integration | | `subnetgroup` | 1.6.0 | Assign hosts to groups automatically by IP subnet | diff --git a/persistentgroups/config/plugin.config.php b/persistentgroups/config/plugin.config.php deleted file mode 100644 index 03cc29f..0000000 --- a/persistentgroups/config/plugin.config.php +++ /dev/null @@ -1,29 +0,0 @@ - - * @license http://opensource.org/licenses/gpl-3.0 GPLv3 - * @link https://fogproject.org - */ -/** - * Plugin configuration file. - * - * @category Config - * @package FOGProject - * @author Tom Elliott - * @license http://opensource.org/licenses/gpl-3.0 GPLv3 - * @link https://fogproject.org - */ -$fog_plugin = []; -$fog_plugin['name'] = 'persistentgroups'; -$fog_plugin['description'] = 'Enable persistent groups.'; -$fog_plugin['menuicon'] = 'fas fa-layer-group fa-fw'; -$fog_plugin['version'] = '1.6.0'; -$fog_plugin['fog_min'] = '1.6.0'; -$fog_plugin['author'] = 'Tom Elliott'; -$fog_plugin['homepage'] = 'https://fogproject.org'; diff --git a/persistentgroups/src/Items/PersistentGroups.php b/persistentgroups/src/Items/PersistentGroups.php deleted file mode 100644 index 82e4151..0000000 --- a/persistentgroups/src/Items/PersistentGroups.php +++ /dev/null @@ -1,27 +0,0 @@ - - * @license http://opensource.org/licenses/gpl-3.0 GPLv3 - * @link https://fogproject.org - */ - -namespace FOG\Plugins\PersistentGroups\Items; - -/** - * Persistent group class. - * - * @category PersistentGroups - * @package FOGProject - * @author Tom Elliott - * @license http://opensource.org/licenses/gpl-3.0 GPLv3 - * @link https://fogproject.org - */ -class PersistentGroups extends \FOG\Base\FOGController -{ -} diff --git a/persistentgroups/src/Managers/PersistentGroupsManager.php b/persistentgroups/src/Managers/PersistentGroupsManager.php deleted file mode 100644 index a79a3ff..0000000 --- a/persistentgroups/src/Managers/PersistentGroupsManager.php +++ /dev/null @@ -1,144 +0,0 @@ - - * @license http://opensource.org/licenses/gpl-3.0 GPLv3 - * @link https://fogproject.org - */ - -namespace FOG\Plugins\PersistentGroups\Managers; - -/** - * The example mass manager class. - * - * Enables persistent groups. - * - * @category PersistentGroupsManager - * @package FOGProject - * @author Tom Elliott - * @license http://opensource.org/licenses/gpl-3.0 GPLv3 - * @link https://fogproject.org - */ -class PersistentGroupsManager extends \FOG\Base\FOGManagerController -{ - /** - * Returns the CREATE TRIGGER statement for this plugin. - * - * @return string - */ - public function triggerSql() - { - return "CREATE TRIGGER `persistentGroups` - AFTER INSERT ON `groupMembers` - FOR EACH ROW - BEGIN - - SET @myHostID = `NEW`.`gmHostID`; - SET @myGroupID = `NEW`.`gmGroupID`; - - SET @myTemplateID = (SELECT `hostID` FROM `groups` INNER JOIN `hosts` ON (`groupName` = `hostName`) WHERE `groupID`=@myGroupID); - - IF (@myTemplateID IS NOT NULL) AND (@myHostID <> @myTemplateID) THEN - UPDATE `hosts` `d`, (SELECT `hostImage`, `hostBuilding`, `hostUseAD`, `hostADDomain`, `hostADOU`, - `hostADUser`, `hostADPass`, `hostProductKey`, `hostPrinterLevel`, `hostKernelArgs`, - `hostExitBios`, `hostExitEfi`, `hostEnforce` FROM `hosts` WHERE `hostID`=@myTemplateID) `s` - SET `d`.`hostImage`=`s`.`hostImage`, `d`.`hostBuilding`=`s`.`hostBuilding`, `d`.`hostUseAD`=`s`.`hostUseAD`, `d`.`hostADDomain`=`s`.`hostADDomain`, - `d`.`hostADOU`=`s`.`hostADOU`, `d`.`hostADUser`=`s`.`hostADUser`, `d`.`hostADPass`=`s`.`hostADPass`, - `d`.`hostProductKey`=`s`.`hostProductKey`, `d`.`hostPrinterLevel`=`s`.`hostPrinterLevel`, `d`.`hostKernelArgs`=`s`.`hostKernelArgs`, - `d`.`hostExitBios`=`s`.`hostExitBios`, `d`.`hostExitEfi`=`s`.`hostExitEfi`, `d`.`hostEnforce`=`s`.`hostEnforce` - WHERE `d`.`hostID`=@myHostID; - - SET @myDBTest = (SELECT count(`table_name`) FROM information_schema.tables WHERE `table_schema` = 'fog' AND `table_name` = 'locationAssoc' LIMIT 1); - if (@myDBTest > 0) THEN - INSERT INTO `locationAssoc` (`laHostID`,`laLocationID`) - SELECT @myHostID as `laHostID`,`laLocationID` - FROM `locationAssoc` WHERE `laHostID`=@myTemplateID; - END IF; - - INSERT INTO `printerAssoc` (`paHostID`,`paPrinterID`,`paIsDefault`,`paAnon1`,`paAnon2`,`paAnon3`,`paAnon4`) - SELECT @myHostID as `paHostID`,`paPrinterID`,`paIsDefault`,`paAnon1`,`paAnon2`,`paAnon3`,`paAnon4` - FROM `printerAssoc` WHERE `paHostID`=@myTemplateID; - - SET @myDBTest = (SELECT count(`saSnapinID`) FROM `snapinAssoc` WHERE `saHostID`=@myTemplateID LIMIT 1); - IF (@myDBTest > 0) THEN - INSERT INTO `snapinAssoc` (`saHostID`,`saSnapinID`) - SELECT @myHostID as `saHostID`,`saSnapinID` - FROM `snapinAssoc` WHERE `saHostID`=@myTemplateID - ON DUPLICATE KEY UPDATE `saHostID`=VALUES(`saHostID`),`saSnapinID`=VALUES(`saSnapinID`); - - -- Check if there's already an active snapin job for this host - SET @existingJob = (SELECT `sjID` FROM `snapinJobs` WHERE `sjHostID`=@myHostID AND `sjStateID` IN (1,2) LIMIT 1); - - IF (@existingJob IS NULL) THEN - INSERT INTO `snapinJobs` (`sjHostID`,`sjStateID`,`sjCreateTime`) - VALUES (@myHostID, 1, NOW()); - SET @mysjID = LAST_INSERT_ID(); - ELSE - SET @mysjID = @existingJob; - END IF; - - -- Add tasks for any snapins that don't already have tasks in this job - INSERT INTO `snapinTasks` (`stJobID`,`stState`,`stCheckinDate`,`stSnapinID`) - SELECT @mysjID as `stJobID`, 1 as `stState`, NOW() as `stCheckinDate`, `saSnapinID` as `stSnapinID` - FROM `snapinAssoc` WHERE `saHostID`=@myHostID - AND `saSnapinID` NOT IN (SELECT `stSnapinID` FROM `snapinTasks` WHERE `stJobID`=@mysjID); - END IF; - - INSERT INTO `moduleStatusByHost` (`msHostID`,`msModuleID`,`msState`) - SELECT @myHostID as `msHostID`,`msModuleID`,`msState` - FROM `moduleStatusByHost` WHERE `msHostID`=@myTemplateID; - - END IF; - - END;"; - } - /** - * The plugin's ordered, append-only schema migration list. - * - * This plugin manages a trigger (no data), so each step drops-if-exists - * then creates. A future trigger change ships as a NEW appended step that - * drops-and-recreates with the new definition; pSchema tracks which step - * has been applied so it runs exactly once. - * - * @return array - */ - public function schema() - { - return [ - // 0 - function () { - self::$DB->query('DROP TRIGGER IF EXISTS `persistentGroups`'); - if (false !== self::$DB->query($this->triggerSql())->error) { - return self::$DB->error; - } - return true; - }, - ]; - } - /** - * Installs the trigger non-destructively (idempotent drop+create). - * - * @return bool - */ - public function install() - { - $res = \FOG\Items\Schema::applyUpdates($this->schema(), 0); - return $res['error'] === null; - } - /** - * Uninstalls the plugin. - * Drops our trigger. - * - * @return bool - */ - public function uninstall() - { - $sql = 'DROP TRIGGER IF EXISTS `persistentGroups`'; - return self::$DB->query($sql); - } -} diff --git a/tests/plugin-layout.test.php b/tests/plugin-layout.test.php index 143d9f3..029feb6 100644 --- a/tests/plugin-layout.test.php +++ b/tests/plugin-layout.test.php @@ -89,8 +89,8 @@ $srcDir = $pdir . '/src'; if (!is_dir($srcDir)) { - // persistentgroups is models only and capone has no tasks, but every - // plugin has SOMETHING, so an absent src/ is a broken plugin. + // capone has no tasks, but every plugin has SOMETHING, so an + // absent src/ is a broken plugin. $failures[] = sprintf('%s has no src/ directory', $entry); continue; }