6.6 KiB
Extras
Decman ships with some built in modules. They implement functionality that is probably useful for declarative management, but for one reason or another don't make sense as plugins.
User and group management module
import decman.extras.users
A decman module for managing system users, groups, and supplementary group membership and subordinate UID/GID ranges for existing users.
The module is additive: it only manages users/groups you explicitly register, and it only manages additional groups/subids you explicitly define. Anything created manually and not tracked by this module is left alone.
Provided types
Group
Represents a managed group.
@dataclass(frozen=True)
class Group:
groupname: str
gid: Optional[int] = None
system: bool = False
Fields:
groupname: Group name.gid: Desired numeric GID. If omitted, system assigns one.system: Only affects creation (groupadd --system). Changing this after creation does nothing.
User
Represents a managed user.
@dataclass(frozen=True)
class User:
username: str
uid: Optional[int] = None
group: Optional[str] = None
home: Optional[str] = None
shell: Optional[str] = None
groups: tuple[str, ...] = ()
system: bool = False
Fields:
username: Login name.uid: Desired numeric UID. If omitted, system assigns one.group: Primary group name.home: Home directory.shell: Login shell.groups: Supplementary groups set.system: Only affects creation (useradd --system). Changing this after creation does nothing.
UserManager module
class UserManager(Module):
Lifecycle
- Before update
- Create/modify managed groups.
- Create/modify managed users.
- Delete previously-managed users/groups that are no longer listed.
- After update
- Apply additional supplementary group membership and subuid/subgid ranges (including removals).
Store keys
The module persists state in decman store under these keys:
usermanager_usersusermanager_groupsusermanager_user_additional_groupsusermanager_user_subuidsusermanager_user_subgids
The module does not parse /etc/subuid or /etc/subgid; it relies on these store keys to compute additions/removals.
Methods
add_user(user: User)
Ensure a user exists with the configured attributes.
Notes:
- If
uidis provided and an existing user matches by UID but has a different name, the module will rename the user (usermod --login) and apply other changes.
add_group(group: Group)
Ensure a group exists with the configured attributes.
add_user_to_group(user: str, group: str)
Ensure user is a member of group.
- This is applied in
after_update. - Both
userandgroupare expected to exist
You should not use this method for users added with add_user.
add_subuids(user: str, first: int, last: int)
Ensure subordinate UID range first-last is present for user.
add_subgids(user: str, first: int, last: int)
Ensure subordinate GID range first-last is present for user.
Example usage
from decman.extras.users import UserManager, User, Group
um = UserManager()
um.add_group(Group("containers", system=True))
um.add_user(User(
username="alice",
uid=1001,
group="users",
home="/home/alice",
groups=(),
shell="/bin/zsh",
))
um.add_user_to_group("bob", "containers")
um.add_subuids("alice", 100000, 165535)
um.add_subgids("alice", 100000, 165535)
import decman
decman.modules += [um]
GPG receiver module
import decman.extras.gpg
Manages importing OpenPGP public keys into per-user GnuPG homes. Tracks imported keys in the decman store and removes keys that were previously managed but are no longer configured.
This module is intentionally limited since it's main usage is for AUR build users. You probably shouldn't manage your primary user’s keyring with it.
Types
OwnerTrust
Valid ownertrust levels:
nevermarginalfullultimate
These map to GnuPG --import-ownertrust numeric levels 1..4.
SourceKind
How a key is imported:
fingerprint: fetch from keyserver via--recv-keysuri: fetch from URI via--fetch-keyfile: import from local file via--import
Key
Represents one managed key entry.
Fields:
fingerprint: OpenPGP fingerprint, validated to be exactly 40 hex chars (spaces allowed in input; normalized by removing spaces and uppercasing).source_kind: one offingerprint | uri | file.source: keyserver (forfingerprint), URI (foruri), or filepath (forfile).trust: optionalOwnerTrustto set via ownertrust import.
Validation behavior:
- Fingerprint is normalized:
replace(" ", "").upper(). - Fingerprint must match
^[0-9A-F]{40}$; otherwiseValueError.
GPGReceiver module
class GPGReceiver(module.Module):
Store keys
The module persists state in decman store under these keys:
gpgreceiver_userhome_keys
It relies on the store to keep track which keys were added by it.
Public API
receive_key(user: str, gpg_home: str, fingerprint: str, keyserver: str, trust: OwnerTrust | None = None)
Receives a key with a fingerprint from a keyserver to a gpg_home owned by user.
If trust is provided, ownertrust is set after import.
fetch_key(user: str, gpg_home: str, fingerprint: str, uri: str, trust: OwnerTrust | None=None)
Receives a key with a fingerprint from a uri to a gpg_home owned by user.
If trust is provided, ownertrust is set after import.
import_key(user: str, gpg_home: str, fingerprint: str, file: str, trust: OwnerTrust | None =None)
Receives a key with a fingerprint from a local file to a gpg_home owned by user.
If trust is provided, ownertrust is set after import.
Example usage
from decman.modules.gpg import GPGReceiver
import decman
gpg = GPGReceiver()
# Receive a key from a keyserver
gpg.receive_key(
user="builduser",
gpg_home="/var/lib/builduser/gnupg",
fingerprint="AAAA AAAA AAAA AAAA AAAA AAAA AAAA AAAA AAAA AAAA",
keyserver="hkps://keyserver.ubuntu.com",
trust="marginal",
)
# Fetch a key from a URI
gpg.fetch_key(
user="alice",
gpg_home="/home/alice/.gnupg",
fingerprint="BBBB BBBB BBBB BBBB BBBB BBBB BBBB BBBB BBBB BBBB",
uri="https://example.org/signing-key.asc",
)
# Import a key from a local file
gpg.import_key(
user="bob",
gpg_home="/home/bob/.gnupg",
fingerprint="CCCC CCCC CCCC CCCC CCCC CCCC CCCC CCCC CCCC CCCC",
file="/etc/decman/keys/custom.asc",
)
decman.modules += [gpg]