Decman
Declarative package & configuration manager for Arch Linux.
Install / Use
/learn @kiviktnm/DecmanREADME
Decman
Decman has breaking changes! Decman has undergone an architecture rewrite. The new architecture makes decman more expandable and maintainable.
Migration guide is here.
Decman is a declarative package & configuration manager for Arch Linux. It allows you to manage installed packages, your dotfiles, enabled systemd units, and run commands automatically. Your system is configured using Python so your configuration can be very adaptive.
Overview
See the example for a quick tutorial.
To use decman, you need a source file that declares your system installation. I recommend you put this file in source control, for example in a git repository.
/home/user/config/source.py:
import decman
from decman import File, Directory
# Declare installed pacman packages
decman.pacman.packages |= {"base", "linux", "linux-firmware", "networkmanager", "ufw", "neovim"}
# Declare installed aur packages
decman.aur.packages |= {"decman"}
# Declare configuration files
# Inline
decman.files["/etc/vconsole.conf"] = File(content="KEYMAP=us")
# From files within your source repository
# (full path here would be /home/user/config/dotfiles/pacman.conf)
decman.files["/etc/pacman.conf"] = File(source_file="./dotfiles/pacman.conf")
# Declare a whole directory
decman.directories["/home/user/.config/nvim"] = Directory(source_directory="./dotfiles/nvim",
owner="user")
# Ensure that a systemd unit is enabled.
decman.systemd.enabled_units |= {"NetworkManager.service"}
To better organize your system configuration, you can create modules.
/home/user/config/syncthing.py:
from decman import Module, Store, prg
from decman.plugins import pacman, systemd
# Your custom modules are child classes of the module class.
# They can override methods of the Module-class.
class Syncthing(Module):
def __init__(self):
super().__init__(name="syncthing")
# Run code when a module is first enabled
def on_enable(self, store: Store):
# Note: store is a key-value store that will persist between decman runs.
# You can use it to store your own data as well. Here it is not needed.
# Call a program
prg(["ufw", "allow", "syncthing"])
# Run any python code
print("Remember to setup syncthing with the browser UI!")
# On disable is a special method, it will get executed when this module no longer exists.
# Therefore it must be static, take no parameters, and inline all imports.
# Imported modules should be available everywhere.
@staticmethod
def on_disable():
# Run code when a module is disabled
import decman
decman.prg(["ufw", "deny", "syncthing"])
# Decorate a function with @pacman.packages to indicate it returns a set of pacman packages
# to be installed
@pacman.packages
def pacman_packages(self) -> set[str]:
return {"syncthing"}
# Systemd units are declared in a similiar fashion
@systemd.user_units
def systemd_user_units(self) -> dict[str, set[str]]:
# Systemd user units part of this module
return {"user": {"syncthing.service"}}
Then import your module in your main source file.
/home/user/config/source.py:
import decman
from syncthing import Syncthing
decman.modules += [Syncthing()]
Then run decman.
[!WARNING] Decman runs as root. This means that your
source.pywill be executed as root as well.
sudo decman --source /home/user/config/source.py
When you first run decman, you must define the source file, but subsequent runs remember the previous value.
sudo decman
Decman has some CLI options, to see them all run:
decman --help
For troubleshooting and submitting issues, you should use the --debug option.
sudo decman --debug
See the complete documentation for using decman.
Installation
Clone the decman PKGBUILD:
git clone https://aur.archlinux.org/decman.git
Review the PKGBUILD and install it.
cd decman
makepkg -si
Remember to add decman to its own configuration.
import decman
decman.aur.packages |= {"decman"}
What decman manages?
Decman has built-in functionality for managing files and directories. Additionally decman manages system state using plugins. By default decman ships with the following plugins:
Additionally management of users, groups and PGP keys is provided by built-in modules.
Plugins can be disabled if desired and flatpaks are disabled by default.
Please read the documentation to understand the functionality of those plugins in detail. Here are quick examples to show what the default plugins are capable of.
Pacman
Pacman plugins manages native packages. Native packages can be installed from the pacman repositories. This plugin will never touch AUR packages.
import decman
# Packages that decman ensures are installed to the system
decman.pacman.packages |= {"firefox", "reflector"}
# These packages will never get installed or removed by decman.
decman.pacman.ignored_packages |= {"opendoas"}
AUR
[!NOTE] Building of AUR or custom packages is not the primary function of decman. There are some issues that I may or may not fix. If you can't build a package using decman, consider adding it to
decman.aur.ignored_packagesand building it yourself.
AUR plugins manages foreign packages. Foreign packages are installed from the AUR or other sources. This plugin will never touch native packages.
import decman
from decman.plugins.aur import CustomPackage
# AUR Packages that decman ensures are installed to the system
decman.aur.packages |= {"android-studio", "fnm-bin"}
# These foreign packages will never get installed or removed by decman.
decman.aur.ignored_packages |= {"yay"}
# You can add packages from custom sources.
# Just add a package name and repository / directory containing a PKGBUILD
decman.aur.custom_packages |= {
CustomPackage("decman", git_url="https://github.com/kiviktnm/decman-pkgbuild.git"),
CustomPackage("my-own-package", pkgbuild_directory="/path/to/directory/"),
}
Systemd units
[!NOTE] Decman will only enable and disable systemd services. It will not start or stop them.
Decman can enable systemd services, system wide or for a specific user. Decman will enable all units defined in the source, and disable them when they are removed from the source. If a unit is not defined in the source, decman will not touch it.
import decman
# System-wide units
decman.systemd.enabled_units |= {"NetworkManager.service"}
# User specific units
decman.systemd.enabled_user_units.setdefault("user", set()).update({"syncthing.service"})
Flatpak
import decman
# Flatpaks that decman ensures are installed to the system
decman.flatpak.packages |= {"org.mozilla.firefox", "org.signal.Signal"}
# Flatpaks can be installed to specific users only
decman.flatpak.user_packages.setdefault("user", {}).update({"com.valvesoftware.Steam"})
# These flatpaks will never get installed or removed by decman.
decman.flatpak.ignored_packages |= {"dev.zed.Zed"}
Users and PGP keys
Decman ships with built-in modules for managing users, groups and PGP keys. The modules don't support all features. In particular the PGP module is inteded only for AUR packages. However, they still allow managing users declaratively. Read more about them here.
Here these modules are used to create a builduser for AUR packages.
import decman
import os
from decman.extras.gpg import GPGReceiver
from decman.extras.users import User, UserManager
um = UserManager()
gpg = GPGReceiver()
# Add a normal user
um.add_user(User(
username="alice",
groups=("libvirt"),
shell="/usr/bin/fish",
))
# Create builduser
um.add_user(User(
username="builduser",
home="/var/lib/builduser",
system=True,
))
# Receive desired PGP keys to that account (Spotify as an example)
gpg.fetch_key(
user="builduser",
gpg_home="/var/lib/builduser/gnupg",
fingerprint="E1096BCBFF6D418796DE78515384CE82BA52C83A",
uri="https://download.spotify.com/debian/pubkey_5384CE82BA52C83A.gpg",
)
# Configure aur to use builduser and the GNUPGHOME.
os.environ["GNUPGHOME"] = "/var/lib/builduser/gnupg"
decman.aur.makepkg_user = "builduser"
# Add version control systems required by the packages
decman.pacman.packages |= {"fossil"}
# Add AUR packages that require PGP keys or builduser setup
decman.aur.packages |= {"spotify", "pikchr-fossil"}
# Order matters here, users should be added before gpg keys
decman.modules += [um, gpg]
Managing plugins and the order of operations
The order of operations is managed by setting decman.execution_order. This is also the default.
import decman
decman.execution_order = [
"files",
"pacman",
"aur",
"systemd",
]
This variable also manages which plugins are enabled. To enable flatpaks, simply add the plugin to the execution order.
import decman
decman.execution_order = [
"files",
"pacman",
"aur",
"flatpak",
"systemd",
]
Note that files is not a plugin, but is defined here anyways.
Before the core execution order, decman will run hook methods from Modules.
before_updateon_disable
After the plugin execution, decman will run the following hook methods.
on_enableon_changeatfer_update
Operations and hooks may be skipped with command line options.
# Skip the aur plugin
sudo decman --skip aur
# Only apply file operations
sudo decman --no-hooks --only files
Why use decman?
Here are some reasons why I created decman for myself.
Configuration as documentation
You can consult yo
