GPOA (GPO Applier for Linux) supports a plugin system for extending group policy application functionality. Plugins allow adding support for new policy types and system settings without modifying the core code.
plugin- Abstract base class with final methodsapply()andapply_user()FrontendPlugin- Simplified class for plugins with logging support
plugin_manager- Loads and executes plugins from directories:/usr/lib/gpupdate/plugins/- system pluginsgpoa/frontend_plugins/- development plugins
#!/usr/bin/env python3
#
# GPOA - GPO Applier for Linux
#
# Copyright (C) 2025 BaseALT Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
from gpoa.plugin.plugin_base import FrontendPlugin
class ExampleApplier(FrontendPlugin):
"""
Example simple plugin with logging and registry access.
"""
# Domain for translations
domain = 'example_applier'
def __init__(self, dict_dconf_db, username=None, fs_file_cache=None):
"""
Initialize the plugin.
Args:
dict_dconf_db (dict): Dictionary with registry data
username (str): Username
fs_file_cache: File system cache
"""
super().__init__(dict_dconf_db, username, fs_file_cache)
# Initialize logging system
self._init_plugin_log(
message_dict={
'i': { # Informational messages
1: "Example Applier initialized",
2: "Configuration applied successfully"
},
'w': { # Warnings
10: "No configuration found in registry"
},
'e': { # Errors
20: "Failed to apply configuration"
}
},
domain="example_applier"
)
def run(self):
"""
Main plugin execution method.
Returns:
bool: True if successful, False on error
"""
try:
self.log("I1") # Plugin initialized
# Get data from registry
self.config = self.get_dict_registry('Software/BaseALT/Policies/Example')
if not self.config:
self.log("W10") # No configuration found in registry
return True
# Log registry data
self.log("I2") # Configuration applied successfully
return True
except Exception as e:
self.log("E20", {"error": str(e)})
return False
def create_machine_applier(dict_dconf_db, username=None, fs_file_cache=None):
"""
Factory function for creating plugin instance for machine context.
Args:
dict_dconf_db (dict): Dictionary with registry data
username (str): Username
fs_file_cache: File system cache
Returns:
ExampleApplier: Plugin instance
"""
return ExampleApplier(dict_dconf_db, username, fs_file_cache)
def create_user_applier(dict_dconf_db, username=None, fs_file_cache=None):
"""
Factory function for creating plugin instance for user context.
Args:
dict_dconf_db (dict): Dictionary with registry data
username (str): Username
fs_file_cache: File system cache
Returns:
ExampleApplier: Plugin instance
"""
return ExampleApplier(dict_dconf_db, username, fs_file_cache)Plugins use a logging system with message codes:
self._init_plugin_log(
message_dict={
'i': { # Informational messages
1: "Example Applier initialized",
2: "Configuration applied successfully"
},
'w': { # Warnings
10: "No configuration found in registry"
},
'e': { # Errors
20: "Failed to apply configuration"
}
},
domain="example_applier"
)Access registry data through get_dict_registry() method:
self.config = self.get_dict_registry('Software/BaseALT/Policies/Example')Using registered message codes:
self.log("I1") # Simple message
self.log("E20", {"error": str(e)}) # Message with dataPlugins must provide factory functions:
create_machine_applier()- for machine contextcreate_user_applier()- for user context
GPOA supports automatic localization of plugin messages. The system uses standard GNU gettext.
gpoa/locale/
├── ru/
│ └── LC_MESSAGES/
│ ├── gpoa.mo
│ └── gpoa.po
└── en/
└── LC_MESSAGES/
├── gpoa.mo
└── gpoa.po
- Define translation domain:
class MyPlugin(FrontendPlugin):
domain = 'my_plugin' # Domain for translation files- Initialize logger with translation support:
self._init_plugin_log(
message_dict={
'i': {
1: "Plugin initialized",
2: "Configuration applied successfully"
},
'e': {
10: "Configuration error"
}
},
domain="my_plugin" # Domain for translation file lookup
)- Usage in code:
# Messages are automatically translated when logged
self.log("I1") # Will be displayed in system language- Extract strings for translation:
# Extract strings from plugin code
xgettext -d my_plugin -o my_plugin.po my_plugin.py- Create translation file:
# my_plugin.po
msgid "Plugin initialized"
msgstr ""
msgid "Configuration applied successfully"
msgstr ""- Compile translations:
# Compile .po to .mo
msgfmt my_plugin.po -o my_plugin.mo
# Place in correct directory
mkdir -p /usr/share/locale/ru/LC_MESSAGES/
cp my_plugin.mo /usr/share/locale/ru/LC_MESSAGES/- Use complete sentences - don't split strings into parts
- Avoid string concatenation - this complicates translation
- Provide context - add comments for translators
- Test translations - verify display in different languages
- Update translations - update .po files when messages change
my_plugin/
├── my_plugin.py # Main plugin code
├── locale/
│ ├── ru/
│ │ └── LC_MESSAGES/
│ │ ├── my_plugin.mo
│ │ └── my_plugin.po
│ └── en/
│ └── LC_MESSAGES/
│ ├── my_plugin.mo
│ └── my_plugin.po
└── README.md
__init__(dict_dconf_db, username=None, fs_file_cache=None)- initializationrun()- main execution method (abstract)apply()- execute with current privileges (final)apply_user(username)- execute with user privileges (final)get_dict_registry(prefix='')- get registry data_init_plugin_log(message_dict=None, locale_dir=None, domain=None)- initialize loggerlog(message_code, data=None)- logging with message codes
Message codes:
- I - Informational messages
- W - Warnings
- E - Errors
- D - Debug messages
- F - Fatal errors
dict_dconf_db- dictionary with registry datausername- username (for user context)fs_file_cache- file system cache for file operations
- Executed with root privileges
- Applies system-wide settings
- Uses factory function
create_machine_applier()
- Executed with specified user privileges
- Applies user-specific settings
- Uses factory function
create_user_applier()
- Security: Always validate input data
- Idempotence: Repeated execution should produce the same result
- Logging: Use message codes for all operations
- Error Handling: Plugin should not crash on errors
- Transactional: Changes should be atomic
- Translations: Support message localization