Windows Group Policy Objects (GPOs) arrived with Active Directory in February 2000. 25 years later, Linux gets lgpo – a configuration management system that helps organizations to programmatically enforce compliance across fleets of Linux workstations.
In contrast to Windows, Linux is gloriously diverse: different distros (Debian, Fedora, etc.) different desktops (GNOME, KDE, etc.), and different configuration systems (polkit, dconf/gsettings, systemd, kernel modules, etc.). That diversity makes “Linux GPOs” inherently harder. There is no single Registry, and many subsystems each speak their own language. Many organizations currently use server configuration tools like Ansible or even bash scripts to manage Linux laptops. While Ansible is "agentless" (no new agent as it leverages sshd), lgpo is "serverless" (no new server as the agent pulls instructions from a Git repo).
lgpo is powered by
- a simple unified YAML-based policy language that is rendered into different native Linux config systems by
- a small, security-first agent (
lgpod) that pulls policies and device configurations from - a private Git repository that acts as single source of truth
lgopd simply matches tags of your policy objects, such as this example policy that blocks usb storage devices on laptops and kiosk devices
apiVersion: lgpo.io/v1
kind: ModprobePolicy
metadata: { name: block-removable-storage }
selector:
tags:
group: ["laptops", "kiosk"]
spec:
blacklist: ["usb_storage", "uas", "firewire_ohci", "sbp2"]
installFalse: true # install <mod> /bin/false → hard-block
updateInitramfs: true # rebuild so block applies earlywith tags of inventory objects, such as this example that defines a device in the laptops group
apiVersion: lgpo.io/v1
kind: DeviceInventory
items:
- device_pub_sha256: "7a93be12cd34ef56ab78cd90ef12ab34cd56ef78ab90cd12ef34ab56cd78ef90"
identity: "alice@example.com"
tags:
group: "laptops"
ou: "finance"
site: "vienna"Please visit the GitOps example repo to learn more about policies and inventory mangement.
- 🏛️ Many organizations already trust in GitOps to secure their crown-jewels such as k8s clusters. Use this well established process to manage Linux workstations too.
- 👀 With GitOps, it's easy to enforce the four-eyes principle and status checks (CI, linters, YAML/schema validators, policy render tests) before merge.
- 🔐 Change control: every edit is a PR with history, reviews, and a merge commit you can audit.
- 🔁 Reproducibility: endpoints apply a specific commit; you can correlate any host’s state with the exact Git SHA.
- ⏪ Easy rollback:
git revert(or restore a previous commit) → agents reset to that state on the next interval. - 🧱 Smaller attack surface: agents pull over HTTPS directly from Git; fewer privileged services and credentials to defend.
Fork the GitOps example repo that includes example policy and inventory files. Change the repo visibility to private in Settings / Danger Zone.
Copy the command below, change POLICY_REPO_URL="git@github.com:your-org/your-lgpo-gitops-repo.git" to your new private repo and run the command.
curl -fsSL https://raw.githubusercontent.com/lgpo-org/lgpod/main/scripts/install-lgpo.sh \
| sudo env POLICY_REPO_URL="git@github.com:your-org/your-lgpo-gitops-repo.git" POLICY_BRANCH="main" bashYour device's public key starting with ssh-ed25519 AAAAC3NzaC1lZD... and your device id, a hash such as 7a93be12cd34ef56ab78cd90ef12ab34cd56ef78ab90cd12ef34ab56cd78ef90 will be displayed at the end of the install process.
- Copy the public key and paste it as a new deploy key (in your GitOps repo's settings, click "deploy keys", grant READ-ONLY access, you can use the hash as name)
- Copy the hash and paste it into your GitOps repo's devices.yml file in the "inventory" folder to enroll the device.
Example devices.yml from the GitOps example repo:
apiVersion: lgpo.io/v1
kind: DeviceInventory
items:
- device_pub_sha256: "7a93be12cd34ef56ab78cd90ef12ab34cd56ef78ab90cd12ef34ab56cd78ef90"
identity: "alice@example.com"
tags:
group: "laptops"
ou: "finance"
site: "vienna"sudo lgpod --sub run --once --dry-run # preview (no writes)
sudo lgpod --sub run --once # enforce now (or wait for the service interval)Verify:
sudo lgpod -sub tags
sudo lgpod -sub facts
systemctl status lgpod --no-pager
cat /var/lib/lgpo/status.json
tail -n 3 /var/log/lgpo/audit.jsonl/etc/lgpo/agent.yaml:
repo: git@github.com:your-org/your-lgpo-gitops-repo.git # policy and inventory repo
branch: main # branch name
policiesPath: policies # policy path in repo
interval: 5m # how often to sync/apply
jitter: 1m # small randomness to avoid herd behavior
auditLog: /var/log/lgpo/audit.jsonl # audit logs path
statusFile: /var/lib/lgpo/status.json # status file path
cacheDir: /var/lib/lgpo/repo # cached repo path
tagsDir: /etc/lgpo/tags.d # local tags folder# Current facts
sudo lgpod --sub facts | jq
# Current tags
sudo lgpod --sub tags | jq
# Dry-run (no writes)
sudo lgpod --sub run --once --dry-run
# One-shot apply (writes + post-steps)
sudo lgpod --sub run --once
# Status (last apply, changed count, commit)
sudo lgpod --sub status | jq
# Service logs
journalctl -u lgpod -n 50 --no-pager- PolkitPolicy →
/etc/polkit-1/rules.d/60-lgpo-<name>.rules - DconfPolicy →
/etc/dconf/db/local.d/60-lgpo-<name>and/etc/dconf/db/local.d/locks/60-lgpo-<name> - ModprobePolicy →
/etc/modprobe.d/60-lgpo-<name>.conf - State →
/var/lib/lgpo/status.json - Audit →
/var/log/lgpo/audit.jsonl - Managed manifest →
/var/lib/lgpo/managed.json(for drift cleanup)
Writes are atomic (tmp + rename). Paths outside the allowlist are ignored.
If a device stops matching a policy (e.g., you change its group tag from laptops to desktops), the next run removes previously managed files that are no longer desired. The audit log includes a removed count, and dconf update / update-initramfs -u are triggered when needed.
On each run, the agent ensures the cache is at the branch tip:
# first time
git clone --depth 1 --branch <branch> <repo> <cacheDir>
# updates
git -C <cacheDir> fetch --depth 1 origin <branch>
git -C <cacheDir> reset --hard origin/<branch>This keeps bandwidth minimal (no history) and makes the working tree an exact mirror.
The commit SHA is recorded in status and audit.
- Add more policy kinds (e.g. KConfig)
- SSO/OIDC → tags mapping for identity-aware policies
- Richer facts (inventory, posture signals)
If anything doesn’t behave as described:
sudo lgpod --sub run --once --dry-run
journalctl -u lgpod -n 100 --no-pagerOpen an issue with the output and your policy YAML.