- Introduction
- Packages
- Features
- Architecture
- Installation
- Usage
- External API (gpoa-lib)
- Plugin Development
- Project Structure
- Contributing
- License
GPOA (GPO Applier for Linux) is a comprehensive facility to fetch, reinterpret and apply Group Policy Objects (GPOs) from Windows Active Directory domains in Linux environments. Developed by ALT Linux team, it enables seamless integration of Linux machines into corporate Windows infrastructure.
Starting from version 0.15.0, the project is split into two RPM packages:
| Package | Description |
|---|---|
| gpupdate | Entry points, backends, GPT processing. Depends on gpoa-lib. |
| gpoa-lib | Standalone library: policy appliers, storage, plugin framework, utilities. Can be used independently. |
The gpoa/ directory contains thin wrappers that re-export from gpoa_lib/ for backward compatibility. All implementation lives in gpoa_lib/.
- Multi-backend Support: Samba, FreeIPA, and no-domain backends
- Policy Types: Registry settings, files, folders, environment variables, scripts, services, and more
- Display Manager Integration: LightDM, GDM, SDDM with background and theme support
- Plugin System: Extensible architecture for custom policy types
- Targeting Filters: 15 filter types (computer, domain, user, group, date, time, CPU, battery, disk, RAM, language, file, environment variable, IP range, MAC range)
- Privilege Separation: Secure execution with proper privilege contexts
- External API:
StorageAdapter,StorageWriter,ApplierRunner,Result, andApplierResultfor using gpoa-lib as a standalone library - Execution Status: Per-item success/failure/skip tracking across all appliers
- System Configuration: Control facilities, systemd units, environment variables, NTP, firewall
- Desktop Settings: GSettings, KDE configuration
- Security: Polkit policies, LAPS (Local Administrator Password Solution)
- Network: CIFS/autofs network shares
- Applications: Firefox, Chromium, Thunderbird, Yandex Browser
- Files and Folders: File deployment, folder redirection, INI file configuration
- Hardware: Udev rules (via external plugin)
- Samba Backend: Traditional Active Directory integration
- FreeIPA Backend: Enhanced FreeIPA/IdM integration
- No-domain Backend: Local policy application
- Policy Appliers: 20 specialized modules for different policy types
- Plugin Framework: Extensible plugin system with logging and translations
- Targeting Filters: GPP preference-level targeting with caching
- Dconf Registry: Policy data stored in dconf databases
- GPP State: Lifecycle management (applyOnce, removePolicy, disabled)
- File Cache: Secure file storage and retrieval
- Machine Context: Root-privileged system-wide changes
- User Context: User-specific configuration application with privilege dropping
- Message Codes: Structured logging with translation support
- Registry Access: Secure access to policy registry data via
get_dict_registry() - Custom registry_path: Override registry path at construction time
# Clone the repository
git clone https://github.com/altlinux/gpupdate.git
cd gpupdate
# Build RPM packages (produces gpupdate and gpoa-lib)
rpmbuild -ba gpupdate.spec
# Install both packages
rpm -ivh ~/rpmbuild/RPMS/noarch/gpupdate-*.rpm ~/rpmbuild/RPMS/noarch/gpoa-lib-*.rpm
# Or install only the library
rpm -ivh ~/rpmbuild/RPMS/noarch/gpoa-lib-*.rpm- Python 3.8+
- Samba client tools
- FreeIPA client (optional)
- Systemd
- D-Bus
# Run as root for system-wide policies
sudo gpoa# Run as root for user-specific policies
sudo gpoa username# Can be run as regular user
gpupdate --forceUser policy updates during SSH login are enabled by default. Administrators can skip the synchronous PAM update for SSH sessions without affecting graphical or console logins:
control gpupdate-ssh disabledThe setting can be managed by machine policy through
HKLM\Software\BaseALT\Policies\Control, using the value name
gpupdate-ssh. Values 0/disabled disable SSH updates and 1/enabled
enable them. If the value is not configured, gpupdate leaves the local control
state unchanged. Previously applied policy and the policy environment remain
available in SSH sessions.
Plugins are automatically discovered from:
/usr/lib/gpoa/plugins/(system-wide plugins)
gpoa-lib provides a public API for applying policies without the full gpupdate stack.
The gpoa-lib RPM also owns the shared applier templates installed under
/usr/share/gpupdate/templates. Full-stack installations use the matching
gpupdate and gpoa-lib versions; template paths are unchanged on upgrade.
High-level facade for running policy appliers:
from gpoa_lib import ApplierRunner
# From a dconf database
runner = ApplierRunner(db_name='mydb')
result = runner.run('control')
if not result:
print('Error:', result.error)
# From a plain dict (no dconf needed)
runner = ApplierRunner(data={
'Software/BaseALT/Policies/Control': {
'sshd-gssapi-auth': '1',
}
})
runner.run('control')
# Auto-detect applier from key path
runner.run_auto(['Software/BaseALT/Policies/Control/sshd-gssapi-auth'])
# Force re-apply from a specific database
runner = ApplierRunner(db_name='policy', force=True)
runner.run('control')
# List available appliers
print(ApplierRunner.list_appliers())
# Resolve applier name from key path
print(ApplierRunner.resolve('Software/BaseALT/Policies/Control/sshd'))Low-level access to policy data from dconf or dict:
from gpoa_lib import StorageAdapter
# From dconf database
storage = StorageAdapter.from_dconf_db('mydb')
# From dict
storage = StorageAdapter.from_dict({
'Software/BaseALT/Policies/Control': {
'sshd-gssapi-auth': '1',
}
})
# Get raw dict (for passing to plugins)
data = storage.get_dict()
# Query specific entries
entries = storage.filter_hklm_entries('Software/BaseALT/Policies/Control')
value = storage.get_key_value('Software/BaseALT/Policies/Control/sshd-gssapi-auth')Write policy data to arbitrary dconf databases:
from gpoa_lib import StorageWriter
writer = StorageWriter('local')
writer.write_keys({
'Software/BaseALT/Policies/Control/sshd-gssapi-auth': '1',
})
writer.compile()import sys
sys.path.insert(0, '/usr/lib/gpoa/plugins')
from gpoa_lib import StorageAdapter
from udev_applier import UdevApplier
# From dict with default registry path
storage = StorageAdapter.from_dict({
'Software/BaseALT/Policies/Udev': {'BlockUSBAll': '1'}
})
plugin = UdevApplier(storage.get_dict())
plugin.apply()
# With custom registry_path
plugin = UdevApplier(storage.get_dict(), registry_path='My/Custom/Path')
plugin.apply()See EXAMPLES.md for complete examples. See API_REFERENCE.md for the full API reference. See API_REFERENCE_RU.md for the Russian API reference.
GPOA features a comprehensive plugin system. See documentation for detailed information:
- PLUGIN_DEVELOPMENT_GUIDE.md - English version
- PLUGIN_DEVELOPMENT_GUIDE_RU.md - Russian version
Documentation covers:
- Plugin architecture and API
- Creating custom plugins
- Logging and message codes
- Translation support
- Best practices
from gpoa_lib.plugin.plugin_base import FrontendPlugin
class MyPlugin(FrontendPlugin):
domain = 'my_plugin'
def __init__(self, dict_dconf_db, username=None, fs_file_cache=None, registry_path=None):
super().__init__(dict_dconf_db, username, fs_file_cache, registry_path)
self._init_plugin_log(message_dict={
'i': {1: "Plugin initialized"},
'e': {1: "Plugin failed"}
}, domain="my_plugin")
self.config = self.get_dict_registry(
self._registry_path or 'Software/MyOrg/Policies/MyPlugin')
def run(self, **kwargs):
self.log("I1")
return True
def create_machine_applier(dict_dconf_db, username=None, fs_file_cache=None, registry_path=None):
return MyPlugin(dict_dconf_db, username, fs_file_cache, registry_path)gpupdate/
├── gpoa/ # Entry points and thin wrappers → gpoa_lib
│ ├── backend/ # Samba, FreeIPA, nodomain backends
│ ├── frontend/ # Thin wrappers for appliers
│ │ └── appliers/ # Individual applier wrappers
│ ├── frontend_plugins/ # Built-in frontend plugins
│ ├── gpt/ # GPT parsing and filter processing
│ ├── locale/ # Russian translations
│ ├── messages/ # Message code definitions
│ ├── plugin/ # Plugin manager wrapper
│ ├── storage/ # Storage wrapper
│ ├── templates/ # Jinja2 templates (autofs, dconf)
│ ├── util/ # Utility wrappers
│ ├── gpoa # Main GPOA entry point
│ ├── gpupdate # gpupdate CLI entry point
│ └── gpupdate-setup # Enable/disable configuration tool
│
├── gpoa_lib/
│ └── gpoa_lib/ # Standalone library (gpoa-lib RPM)
│ ├── frontend/ # 20 policy appliers
│ │ └── appliers/ # Individual applier implementations
│ ├── frontend_plugins/ # Built-in plugin implementations
│ ├── plugin/ # Plugin framework (base, manager, logging)
│ ├── storage/ # Dconf registry, GPP state, StorageAdapter
│ ├── util/ # Utilities, filters, logging, paths
│ ├── messages/ # Localized message definitions
│ ├── test/ # Unit tests (430+ tests)
│ ├── __init__.py # Public API exports
│ ├── applier_runner.py # ApplierRunner facade
│ └── result.py # Result type (ok/fail pattern)
│
├── dist/ # systemd units, polkit, PAM rules
├── doc/ # Man pages
├── completions/ # Bash completion scripts
├── tools/ # Auxiliary scripts
├── gpupdate.spec # RPM spec (gpupdate + gpoa-lib subpackage)
├── API_REFERENCE.md # API reference (English)
├── API_REFERENCE_RU.md # API reference (Russian)
├── PLUGIN_DEVELOPMENT_GUIDE.md # Plugin development guide (English)
├── PLUGIN_DEVELOPMENT_GUIDE_RU.md # Plugin development guide (Russian)
└── EXAMPLES.md # External API usage examples
gpupdate reports failed oddjob executions with a nonzero exit status. Requested
machine and user updates are still attempted independently. Existing transport
failure codes are retained; execution failures use code 4 (computer) or 5 (user).
gpoa --noupdate applies the selected target's cached policy without contacting
the backend or saving a refreshed database. An unprivileged invocation applies
only its own user-context settings. Existing apply-once markers are honoured;
new markers are not persisted by this read-only-storage mode. Shortcut caches
created before user-context metadata was recorded require a normal refresh.
FreeIPA downloads are validated in separate immutable snapshot directories. A failed retrieval leaves the last accepted policy in place. Old snapshots are retained; this release does not automatically garbage-collect them. Legacy cache metadata without a source identity is refreshed rather than matched by display name.
gpupdate-setup update preserves configured policy sources unless explicitly
overridden. disable stops periodic timers, including those in existing user
managers, and reports managers it could not contact. It does not terminate an
update already running or stop the script services.
scripts_runner reports each failed script and continues running subsequent
scripts. Use --fail-on-error to return exit status 1 if any script or runner
operation fails. Without this option the historical exit behaviour is retained.
The shipped systemd units retain that default: their successful oneshot startup
is needed for the matching shutdown/logoff ExecStop action to run.
See production regression checks for the integration scenarios to verify before deployment.
The main communication channel for GPOA is Samba@ALT Linux mailing lists. The mailing list is in Russian but you may also send e-mail in English or German.
GPOA - GPO Applier for Linux
Copyright (C) 2019-2026 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.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.