Add plugin documentation

This commit is contained in:
Kivi Kaitaniemi
2025-12-17 19:48:01 +02:00
parent c2bb67dc4e
commit 757edfe7cc
6 changed files with 673 additions and 37 deletions
+17 -7
View File
@@ -1,7 +1,9 @@
# Decman
> THIS README IS FOR AN UNRELEASED VERSION!
>
> There are going to be breaking changes!
> Decman has undergone an architecture rewrite! The new architecture makes decman more expandable and maintainable.
> Decman has undergone an architecture rewrite. The new architecture makes decman more expandable and maintainable.
>
> The AUR package will receive this update after I have tested it enough.
> See this [tag](https://github.com/kiviktnm/decman/tree/0.4.2) for the current version of decman available in the AUR.
@@ -12,8 +14,6 @@ Decman is a declarative package & configuration manager for Arch Linux. It allow
## Overview
[See the complete documentation for using decman.](/docs/README.md)
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`:
@@ -124,6 +124,8 @@ Decman has some CLI options, to see them all run:
decman --help
```
[See the complete documentation for using decman.](/docs/README.md)
## Installation
Clone the decman PKGBUILD:
@@ -213,7 +215,7 @@ import decman
decman.systemd.enabled_units |= {"NetworkManager.service"}
# User specific units
decman.systemd.enabled_user_units.setdefault("user", {}).update({"syncthing.service"})
decman.systemd.enabled_user_units.setdefault("user", set()).update({"syncthing.service"})
```
### Flatpak
@@ -263,16 +265,24 @@ Note that `files` is not a plugin, but is defined here anyways.
Before the core execution order, decman will run hook methods from `Module`s.
1. `before_update`
1. `on_disable`
2. `on_disable`
After the plugin execution, decman will run the following hook methods.
1. `on_enable`
1. `on_change`
1. `atfer_update`
2. `on_change`
3. `atfer_update`
Operations and hooks may be skipped with command line options.
```sh
# 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.
+53 -3
View File
@@ -1,12 +1,53 @@
# Decman documentation
This contains the core documentation for decman. Each plugin has its own documentation.
This contains the documentation for decman. Each plugin has its own documentation. For a quick overview of decman, see the [README](/README.md).
- [pacman](/docs/pacman.md)
- [systemd](/docs/systemd.md)
- [aur](/docs/aur.md)
- [flatpak](/docs/flatpak.md)
## Quick notes
"Decman source" or "source" refers to your system configuration. It is set using the `--source` command line argument with decman.
```sh
sudo decman --source /this/is/your/source.py
```
Decman and decman plugins use sets for most collections to avoid duplicates. Remember to add values to sets instead of reassigning it.
```py
import decman
# GOOD
decman.pacman.packages |= {"vim"}
# BAD, now there is only "vim" in the packages, all previous operations were overridden.
decman.pacman.packages = {"vim"}
```
In Python you should not use from imports with global variables. It can lead to issues.
```py
# DO THIS:
import decman
decman.pacman.packages |= {"vim"}
# THIS MAY NOT WORK
from decman import pacman
pacman.packages |= {"vim"}
```
You can still import classes and functions with from imports safely.
```py
from decman import File
# pacman here refers to the pacman module containing the plugin
# not the plugin instance
from decman.plugins import pacman
```
## Decman Store
Decman stores data in the file `/var/lib/decman/store.json`. This file should not be modified manually. However, if encountering bugs with decman, manual modification may be desirable. The file is JSON so editing it should be easy enough.
@@ -169,6 +210,13 @@ A **Module** is the primary unit for grouping related files, directories, packag
Each module is uniquely identified by its `name`.
Remember to add modules to decman.
```py
import decman
decman.modules |= {MyModule()}
```
### Basic Structure
```python
@@ -354,6 +402,8 @@ Checks if this plugin can be enabled. For example, this could check if a require
This is not useful if the plugin is directly added to `decman.plugins`. However, if using the Python package method for installing plugins, this check is used before adding the plugin automatically to `decman.plugins`.
Please note that this availibility check is executed before **any** decman steps. If a plugin depends on a pacman package, and that package is defined in the source but not yet installed, the plugin will not be available during the first run of decman.
```py
def available(self) -> bool:
return True
@@ -480,6 +530,6 @@ import decman
raise decman.SourceError("boom")
```
#### Decman Core
### Decman Core
Additionally, you can import the modules used by decman. They should be relatively stable and not change too much between decman versions. The module `decman.core.output` is probably the most relevant one, as it provides methods for printing output that decman uses.
Additionally, you can import the modules used by decman. They should be relatively stable and not change too much between decman versions. The module `decman.core.output` is probably the most relevant one, as it provides methods for printing output.
+252 -19
View File
@@ -1,29 +1,262 @@
# AUR
> [!NOTE]
> Building of foreign 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 `ignored_packages` and building it yourself.
> While this plugin exists with the sole purpose of installing foreing packages, this functionality is not the primary purpose of decman. Issues regarding this plugin are not a priority.
> If you can't build a package with this plugin, consider adding it to `ignored_packages` and building it yourself.
Decman can install AUR packages as well as user defined packages. Foreign packages are AUR and user packages combined.
AUR plugin can be used to manage AUR and custom packages. The pacman plugin manages only foreing packages installed from the AUR or elsewhere. All native (pacman repositories) packages are ignored by this plugin with the exception that this plugin will install native dependencies of foreign packages.
Here is an example of a user package. Managing user packages is somewhat cumbersome as you have to declare their versions, dependencies and make dependencies manually. However, you probably won't install many user packages anyway.
It manages packages exactly the same way as the pacman plugin.
> This plugin will ensure that explicitly installed packages match those defined in the decman source. If your system has explicitly installed package A, but it is not included in the source, it will be uninstalled. You don't need to list dependencies in your source as those will be handeled by pacman automatically. However, if you have inluded package B in your source and that package depends on A, this plugin will not remove A. Instead it will demote A to a dependency. This plugin will also remove all orphaned packages automatically.
Building of foreign packages happens in a chroot. This creates some overhead, but ensures clean builds. By default the chroot is created to `/tmp/decman/build`. If `/tmp` is a in-memory filesystem like tmpfs, make sure that the tmpfs-partition is large enough. I recommend at least 6 GB. You can also change the build directory if memory is an issue.
Build packages are by default stored in a cache `/var/cache/decman/aur`. This plugin keeps 3 most recent versions of all packages.
When installing packages from other version control systems than git, you'll need to install the package for that VCS. There is an [issue and a workaround](source) related to fossil packages. Note that the issue's workaround is for an old version of decman. With this version, set the `makepkg_user` with `decman.aur.makepkg_user`.
## Usage
Define AUR packages. These will be installed from the AUR.
```py
# Note, decman now has a aur package, I recommend using that instead.
# Also, this example may be out of date
decman.user_packages.append(
decman.UserPackage(
pkgname="decman-git",
provides=["decman"],
version="0.4.1",
dependencies=["python", "python-requests", "devtools", "pacman", "systemd", "git"],
make_dependencies=[
"python-setuptools", "python-build", "python-installer", "python-wheel"
],
git_url="https://github.com/kiviktnm/decman-pkgbuild.git",
))
import decman
decman.aur.packages |= {"android-studio", "fnm-bin"}
```
Building of foreign packages happens in a chroot. This creates some overhead, but ensures clean builds. By default the chroot is created to `/tmp/decman/build`. I recommend to use a tmpfs for the `/tmp/` directory to speed up builds. Also make sure that the tmpfs-partition is large enough. I recommend at least 6 GB.
Define ignored foreing packages. These can be AUR packages or other foreign packages. These packages will never get installed or removed by the plugin.
Build packages are stored in a cache `/var/cache/decman`. By default decman keeps 3 most recent versions of all packages.
```py
decman.aur.ignored_packages |= {"yay"}
```
Define packages from custom sources. Add a package name and repository / directory containing a PKGBUILD. This plugin will fetch the PKGBUILD, generate .SRCINFO and parse that to find the package details.
```py
from decman.plugins.aur import CustomPackage
decman.aur.custom_packages |= {
CustomPackage("decman", git_url="https://github.com/kiviktnm/decman-pkgbuild.git"),
CustomPackage("my-own-package", pkgbuild_directory="/path/to/directory/"),
}
```
This plugin's execution order step name is `aur`.
### Within modules
Modules can also define AUR packages and custom packages. Decorate a module's method with `@decman.plugins.aur.packages` or `@decman.plugins.aur.custom_packages`. For AUR packages return a `set[str]` of package names from that module. Custom packages should return a `set[CustomPackage]`.
```py
import decman
from decman.plugins import aur
class MyModule(decman.Module):
...
@aur.packages
def aur_packages_defined_in_this_module(self) -> set[str]:
return {"android-studio", "fnm-bin"}
@aur.custom_packages
def custom_packages_defined_in_this_module(self) -> set[aur.CustomPackage]:
return {
CustomPackage("decman", git_url="https://github.com/kiviktnm/decman-pkgbuild.git"),
}
```
If these sets change, this plugin will flag the module as changed. The module's `on_change` method will be executed.
## Configuration
This module has partially the same configuration with pacman. You'll have to define pacman output keywords again.
```py
import decman
# set keywords
decman.aur.keywords = {"pacsave", "pacnew", "warning"}
# disable the feature
decman.aur.print_highlights = False
```
There are some options related to building packages.
```py
# Timeout for fetching information from AUR
decman.aur.aur_rpc_timeout = 30
# User which builds AUR packages
decman.aur.makepkg_user = "nobody"
# Directory used for building packages
decman.aur.build_dir = "/tmp/decman/build"
```
Some AUR packages must be verified with GPG keys. In that case set the `GNUPGHOME` environment variable to the keystore containing imported keys. Set `makepkg_user` user to the owner of that directory.
```py
import os
os.environ["GNUPGHOME"] = "/home/kk/.gnupg/"
decman.aur.makepkg_user = "kk"
```
Additionally it's possible to override the commands this plugin uses. Create your own `AurCommands` class and override methods returning commands. Since this plugin and the pacman plugin have many overlapping commands, `AurCommands` is actually a subclass of `PacmanCommands`. This means that you can use a single override class for both of them. These are the defaults.
```py
from decman.plugins import aur
import decman
class MyAurAndPacmanCommands(aur.AurCommands):
def list_orphans_foreign(self) -> list[str]:
"""
Running this command outputs a newline seperated list of orphaned foreign packages.
"""
return ["pacman", "-Qmdtq", "--color=never"]
def list_foreign_versioned(self) -> list[str]:
"""
Running this command outputs a newline seperated list of installed packages and their
versions that are not from pacman repositories.
"""
return ["pacman", "-Qm", "--color=never"]
def is_installable(self, pkg: str) -> list[str]:
"""
This command exits with code 0 when a package is installable from pacman repositories.
"""
return ["pacman", "-Sddp", pkg]
def install_as_dependencies(self, pkgs: set[str]) -> list[str]:
"""
Running this command installs the given packages from pacman repositories.
The packages are installed as dependencies.
"""
return ["pacman", "-S", "--needed", "--asdeps"] + list(pkgs)
def install_files_as_dependencies(self, pkg_files: list[str]) -> list[str]:
"""
Running this command installs the given packages files as dependencies.
"""
return ["pacman", "-U", "--asdeps"] + pkg_files
def compare_versions(self, installed_version: str, new_version: str) -> list[str]:
"""
Running this command outputs -1 when the installed version is older than the new version.
"""
return ["vercmp", installed_version, new_version]
def git_clone(self, repo: str, dest: str) -> list[str]:
"""
Running this command clones a git repository to the the given destination.
"""
return ["git", "clone", repo, dest]
def git_diff(self, from_commit: str) -> list[str]:
"""
Running this command outputs the difference between the given commit and
the current state of the repository.
"""
return ["git", "diff", from_commit]
def git_get_commit_id(self) -> list[str]:
"""
Running this command outputs the current commit id.
"""
return ["git", "rev-parse", "HEAD"]
def git_log_commit_ids(self) -> list[str]:
"""
Running this command outputs commit hashes of the repository.
"""
return ["git", "log", "--format=format:%H"]
def review_file(self, file: str) -> list[str]:
"""
Running this command outputs a file for the user to see.
"""
return ["less", file]
def make_chroot(self, chroot_dir: str, with_pkgs: set[str]) -> list[str]:
"""
Running this command creates a new arch chroot to the chroot directory and installs the
given packages there.
"""
return ["mkarchroot", chroot_dir] + list(with_pkgs)
def install_chroot(self, chroot_dir: str, packages: list[str]):
"""
Running this command installs the given packages to the given chroot.
"""
return [
"arch-nspawn",
chroot_dir,
"pacman",
"-S",
"--needed",
"--noconfirm",
] + packages
def resolve_real_name_chroot(self, chroot_dir: str, pkg: str) -> list[str]:
"""
This command prints a real name of a package.
For example, it prints the package which provides a virtual package.
"""
return [
"arch-nspawn",
chroot_dir,
"pacman",
"-Sddp",
"--print-format=%n",
pkg,
]
def remove_chroot(self, chroot_dir: str, packages: set[str]):
"""
Running this command removes the given packages from the given chroot.
"""
return ["arch-nspawn", chroot_dir, "pacman", "-Rsu", "--noconfirm"] + list(packages)
def make_chroot_pkg(
self, chroot_wd_dir: str, user: str, pkgfiles_to_install: list[str]
) -> list[str]:
"""
Running this command creates a package file using the given chroot.
The package is created as the user and the pkg_files_to_install are installed
in the chroot before the package is created.
"""
makechrootpkg_cmd = ["makechrootpkg", "-c", "-r", chroot_wd_dir, "-U", user]
for pkgfile in pkgfiles_to_install:
makechrootpkg_cmd += ["-I", pkgfile]
return makechrootpkg_cmd
def print_srcinfo(self) -> list[str]:
"""
Running this command prints SRCINFO generated from the package in the current
working directory.
"""
return ["makepkg", "--printsrcinfo"]
# -------------------------------------------
# Here I override some PacmanCommand methods.
# -------------------------------------------
def set_as_explicit(self, pkgs: set[str]) -> list[str]:
"""
Running this command sets the given as explicitly installed.
"""
return ["pacman", "-D", "--asexplicit"] + list(pkgs)
def set_as_dependencies(self, pkgs: set[str]) -> list[str]:
"""
Running this command sets the given packages as dependencies.
"""
return ["pacman", "-D", "--asdeps"] + list(pkgs)
```
Applying the commands is easy.
```py
import decman
decman.pacman.commands = MyAurAndPacmanCommands()
decman.aur.commands = MyAurAndPacmanCommands()
```
+129
View File
@@ -0,0 +1,129 @@
# Flatpak
The flatpak plugin is used to manage flatpak apps. It manages both systemd-wide and user-specific flatpaks. Flatpaks are still a new addition to decman, so they might not work as well as pacman packages. Flatpak management is disabled by default.
This plugin will ensure that installed flatpak apps match those defined in the decman source. If your system has installed a package, but it is not included in the source, it will be uninstalled. You don't need to list dependencies and runtimes in your source as those will be handeled by flatpak automatically. This plugin will remove unneeded runtimes.
## Usage
Define systemd-wide flatpaks.
```py
import decman
decman.flatpak.packages |= {"org.mozilla.firefox", "org.signal.Signal"}
```
Define user-specific flatpaks.
```py
decman.flatpak.user_packages.setdefault("user", {}).update({"com.valvesoftware.Steam"})
```
Define ignored flatpaks. This plugin won't install them nor remove them. This list affects user and system flatpaks.
```py
decman.flatpak.ignored_packages |= {"dev.zed.Zed"}
```
### Within modules
Modules can also define flatpaks units. Decorate a module's method with `@decman.plugins.flatpaks.packages` and return a `set[str]` of flatpak names from that module. For user flatpaks decorate with `@decman.plugins.flatpak.user_packages` and return a `dict[str, set[str]]` of usernames and flatpaks for that user.
```py
import decman
from decman.plugins import flatpak
class MyModule(decman.Module):
...
@flatpak.packages
def units_defined_in_this_module(self) -> set[str]:
return {"org.signal.Signal", "org.mozilla.firefox"}
@flatpak.user_packages
def user_units_defined_in_this_module(self) -> dict[str, set[str]]:
return {"user": {"com.valvesoftware.Steam"}}
```
If packages or user packages change, this plugin will flag the module as changed. The module's `on_change` method will be executed.
## Configuration
It's possible to override the commands this plugin uses. Create your own `FlatpakCommands` class and override methods returning commands. These are the defaults.
```py
class MyCommands(FlatpakCommands):
def list_apps(self, as_user: bool) -> list[str]:
"""
Running this command outputs a newline separated list of installed flatpak application IDs.
If ``as_user`` is ``True``, run the command as the user whose packages should be listed.
NOTE: The first line says 'Application ID' and should be ignored.
"""
return [
"flatpak",
"list",
"--app",
"--user" if as_user else "--system",
"--columns",
"application",
]
def install(self, pkgs: set[str], as_user: bool) -> list[str]:
"""
Running this command installs all listed packages, and their dependencies/runtimes
automatically.
If ``as_user`` is ``True``, run the command as the user for whom packages are installed.
"""
return [
"flatpak",
"install",
"--user" if as_user else "--system",
] + sorted(pkgs)
def upgrade(self, as_user: bool) -> list[str]:
"""
Updates all installed flatpaks including runtimes and dependencies.
If ``as_user`` is ``True``, run the command as the user whose flatpaks are updated.
"""
return [
"flatpak",
"update",
"--user" if as_user else "--system",
]
def remove(self, pkgs: set[str], as_user: bool) -> list[str]:
"""
Running this command will remove the listed packages.
If ``as_user`` is ``True``, run the command as the user for whom packages are removed.
"""
return [
"flatpak",
"remove",
"--user" if as_user else "--system",
] + sorted(pkgs)
def remove_unused(self, as_user: bool) -> list[str]:
"""
This will remove all unused flatpak dependencies and runtimes.
If ``as_user`` is ``True``, run the command as the user for whom packages are removed.
"""
return [
"flatpak",
"remove",
"--unused",
"--user" if as_user else "--system",
]
```
Then set the commands.
```py
import decman
decman.flatpak.commands = MyCommands()
```
+122 -8
View File
@@ -1,15 +1,129 @@
# Pacman
Decman can be used to install pacman packages. Decman will install all packages defined in the source and **remove** all explicitly installed packages not defined in the source. You don't need to list dependencies as those will be handeled by pacman. You can set packages to be ignored by decman, so that it won't install them nor remove them.
Pacman plugin can be used to manage pacman packages. The pacman plugin manages only native packages found in arch repositories. All foreign (AUR) packages are ignored by this plugin.
This plugin will ensure that explicitly installed packages match those defined in the decman source. If your system has explicitly installed package A, but it is not included in the source, it will be uninstalled. You don't need to list dependencies in your source as those will be handeled by pacman automatically. However, if you have inluded package B in your source and that package depends on A, this plugin will not remove A. Instead it will demote A to a dependency. This plugin will also remove all orphaned packages automatically.
Please keep in mind that decman doesn't play well with package groups, since all packages part of that group will be installed explicitly. After the initial run decman will now try to remove those packages since it only knows that the group itself should be explicitly installed. Instead of package groups, use meta packages.
```py
# Include only pacman packages found in the pacman repositories in here.
decman.pacman.ignored_packages |= {"opendoas"}
## Usage
# Decman will highlight text from pacman commands according to these keywords,
# if the feauture is enabled. These are the defaults.
decman.pacman.print_highlights = True
decman.pacman.keywords = {"pacsave", "pacnew" }
Define system packages.
```py
import decman
decman.pacman.packages |= {"sudo", "vim"}
```
Define ignored packages. This plugin won't install them nor remove them.
```py
# Include only packages found in the pacman repositories in here.
decman.pacman.ignored_packages |= {"opendoas"}
```
This plugin's execution order step name is `pacman`.
### Within modules
Modules can also define pacman packages. Decorate a module's method with `@decman.plugins.pacman.packages` and return a `set[str]` of package names from that module.
```py
import decman
from decman.plugins import pacman
class MyModule(decman.Module):
...
@pacman.packages
def packages_defined_in_this_module(self) -> set[str]:
return {"tmux", "kitty"}
```
If this set changes, this plugin will flag the module as changed. The module's `on_change` method will be executed.
## Configuration
This plugin has a pacman output highlight function. If pacman output contains some keywords, it will be highlighted. You can disable this feature or set the keywords.
```py
import decman
# set keywords
decman.pacman.keywords = {"pacsave", "pacnew", "warning"}
# disable the feature
decman.pacman.print_highlights = False
```
Additionally it's possible to override the commands this plugin uses. Create your own `PacmanCommands` class and override methods returning commands. These are the defaults.
```py
from decman.plugins import pacman
class MyCommands(pacman.PacmanCommands):
def list_explicit_native(self) -> list[str]:
"""
Running this command outputs a newline seperated list of explicitly installed native
packages.
"""
return ["pacman", "-Qeqn", "--color=never"]
def list_explicit_foreign(self) -> list[str]:
"""
Running this command outputs a newline seperated list of explicitly installed foreign
packages.
"""
return ["pacman", "-Qeqm", "--color=never"]
def list_orphans_native(self) -> list[str]:
"""
Running this command outputs a newline seperated list of orphaned native packages.
"""
return ["pacman", "-Qndtq", "--color=never"]
def list_dependants(self, pkg: str) -> list[str]:
"""
Running this command outputs a newline seperated list of packages that depend on the given
package.
"""
return ["pacman", "-Rc", "--print", "--print-format", "%n", pkg]
def install(self, pkgs: set[str]) -> list[str]:
"""
Running this command installs the given packages from pacman repositories.
"""
return ["pacman", "-S", "--needed"] + list(pkgs)
def upgrade(self) -> list[str]:
"""
Running this command upgrades all pacman packages from pacman repositories.
"""
return ["pacman", "-Syu"]
def set_as_dependencies(self, pkgs: set[str]) -> list[str]:
"""
Running this command sets the given packages as dependencies.
"""
return ["pacman", "-D", "--asdeps"] + list(pkgs)
def set_as_explicit(self, pkgs: set[str]) -> list[str]:
"""
Running this command sets the given as explicitly installed.
"""
return ["pacman", "-D", "--asexplicit"] + list(pkgs)
def remove(self, pkgs: set[str]) -> list[str]:
"""
Running this command removes the given packages and their dependencies
(that aren't required by other packages).
"""
return ["pacman", "-Rs"] + list(pkgs)
```
Then set the commands.
```py
import decman
decman.pacman.commands = MyCommands()
```
+100
View File
@@ -0,0 +1,100 @@
# Systemd
The systemd plugin can enable systemd services, system wide or for a specific user. It will enable all units defined in the source, and disable them when they are removed from the source.
This plugin manages systemd units "softly". It only touches units included in the source. So if you install a package that automatically enables a systemd unit, you don't have to include it in the source. If a unit is not defined in the source, the plugin will not touch it.
Decman will only enable and disable systemd services. It will not start or stop them. Starting or stopping them automatically can cause issues.
## Usage
Declare system-wide units.
```py
import decman
decman.systemd.enabled_units |= {"NetworkManager.service", "ufw.service"}
```
Declare user units. Here this `setdefault` method is used to ensure that the key `user` exists. In this case you cannot use the `|=` syntax and instead must call the `update`-method.
```py
decman.systemd.enabled_user_units.setdefault("user", set()).update({"syncthing.service"})
```
This plugin's execution order step name is `systemd`.
### Within modules
Modules can also define systemd units. Decorate a module's method with `@decman.plugins.systemd.units` and return a `set[str]` of package names from that module. For user units decorate with `@decman.plugins.systemd.user_units` and return a `dict[str, set[str]]` of usernames and user units.
```py
import decman
from decman.plugins import systemd
class MyModule(decman.Module):
...
@systemd.units
def units_defined_in_this_module(self) -> set[str]:
return {"NetworkManager.service", "ufw.service"}
@systemd.user_units
def user_units_defined_in_this_module(self) -> dict[str, set[str]]:
return {"user": {"syncthing.service"}}
```
If units or user units change, this plugin will flag the module as changed. The module's `on_change` method will be executed.
## Configuration
It's possible to override the commands this plugin uses. Create your own `SystemdCommands` class and override methods returning commands. These are the defaults.
```py
class MyCommands(SystemdCommands):
"""
Default commands for the Systemd plugin.
"""
def enable_units(self, units: set[str]) -> list[str]:
"""
Running this command enables the given systemd units.
"""
return ["systemctl", "enable"] + list(units)
def disable_units(self, units: set[str]) -> list[str]:
"""
Running this command disables the given systemd units.
"""
return ["systemctl", "disable"] + list(units)
def enable_user_units(self, units: set[str], user: str) -> list[str]:
"""
Running this command enables the given systemd units for the user.
"""
return ["systemctl", "--user", "-M", f"{user}@", "enable"] + list(units)
def disable_user_units(self, units: set[str], user: str) -> list[str]:
"""
Running this command disables the given systemd units for the user.
"""
return ["systemctl", "--user", "-M", f"{user}@", "disable"] + list(units)
def daemon_reload(self) -> list[str]:
"""
Running this command reloads the systemd daemon.
"""
return ["systemctl", "daemon-reload"]
def user_daemon_reload(self, user: str) -> list[str]:
"""
Running this command reloads the systemd daemon for the given user.
"""
return ["systemctl", "--user", "-M", f"{user}@", "daemon-reload"]
```
Then set the commands.
```py
import decman
decman.systemd.commands = MyCommands()
```