Skip to content

Systemd unit enablement helper functions

Arvid Norlander edited this page Jul 21, 2022 · 2 revisions

Here are some possibly useful helper functions for managing systemd units:

#!/bin/bash

# SystemdEnable
#     [--no-alias|--no-wanted-by]
#     [--name CUSTOM_NAME]
#     [--type system|user]
#     [--from-file|package] unit
#
# Enable a systemd unit file.
#
# Caveat: This will not process Also directives, as it might in theory require
# handling files from other packages. In addition you might not want to install
# both this unit and the Also unit.
#
# --no-alias and --no-wanted-by can be used to disable installing those types of
# links. This is useful if you want to just use socket and dbus activation and
# don't want the unit to start on boot.
#
# --name is to be used for parameterised units ("foo@.service"), to provide the
# parameter.
#
# --type defaults to system but can be used to override and install default user
# units in /etc/systemd/user.
#
# --from-file is used when unit file is installed by aconfmgr instead of pulled
# from a package. In this case the package name MUST be skipped. Otherwise it is
# REQUIRED.
function SystemdEnable() {
	local type=system
	local do_alias=1 do_wanted_by=1 from_file=0

	# Parse arguments
	while [[ $# -gt 0 ]]; do
		case "$1" in
			--no-alias) do_alias=0 ;;
			--no-wanted-by) do_wanted_by=0 ;;
			--from-file) from_file=1 ;;
			--name)
				local name_override=$2
				shift 1
				;;
			--type)
				type=$2
				shift 1
				;;
			*)
				break
				;;
		esac
		shift 1
	done

	if [[ $from_file -eq 0 ]]; then
		[[ $# -ne 2 ]] && FatalError "Expected 2 arguments, got $#."
		local pkg="$1"
		local unit="$2"
	else
		[[ $# -ne 1 ]] && FatalError "Expected 1 argument, got $#."
		local unit="$1"
	fi

	if [[ "$type" != "system" && "$type" != "user" ]]; then
		FatalError "Unkown type ${type}"
	fi

	local filename="${unit##*/}"

	# Find the unit, either from package data or already added to the output
	# directory
	if [[ $from_file -eq 0 ]]; then
		local unit_source="$tmp_dir/systemd_helpers/$pkg/$filename"

		if [[ ! -f "$unit_source" ]]; then
			mkdir -p "$tmp_dir/systemd_helpers/$pkg"
			AconfGetPackageOriginalFile "$pkg" "$unit" > "$unit_source"
		fi
	else
		local unit_source="$output_dir/files/$unit"
	fi

	[[ ! -f "$unit_source" ]] && FatalError "$unit_source not found"

	local target
	local oIFS="$IFS"
	# Process WantedBy lines (if enabled)
	if [[ $do_wanted_by -eq 1 ]]; then
		local name="${name_override:-${filename}}"
		local -a wantedby

		if grep -q WantedBy= "$unit_source"; then
			IFS=$' \n\t'
			wantedby=( $(grep -E '^WantedBy=' "$unit_source" | cut -d= -f2) )
			IFS="$oIFS"
			for target in "${wantedby[@]}"; do
				CreateLink "/etc/systemd/${type}/${target}.wants/${name}" "${unit}"
			done
		fi
	fi

	# Process Alias lines (if enabled)
	if [[ $do_alias -eq 1 ]]; then
		local -a aliases

		if grep -q Alias= "$unit_source"; then
			IFS=$' \n\t'
			aliases=( $(grep -E '^Alias=' "$unit_source" | cut -d= -f2) )
			IFS="$oIFS"
			for target in "${aliases[@]}"; do
				CreateLink "/etc/systemd/${type}/${target}" "${unit}"
			done
		fi
	fi
}

# SystemdMask unit-name [type]
#
# Mask a unit. Defaults to masking system units
function SystemdMask() {
	local unit="$1"
	local type="${2:-system}"

	if [[ "$type" != "system" && "$type" != "user" ]]; then
		FatalError "Unkown type ${type}"
	fi

	CreateLink "/etc/systemd/${type}/${unit}" /dev/null
}

With these you can easily enable unit files as in the following examples:

# Start chrony for time keeping
SystemdEnable chrony /usr/lib/systemd/system/chronyd.service

# Enable avahi, installing dbus aliases and sockets but do not start it automatically at boot
SystemdEnable --no-wanted-by avahi /usr/lib/systemd/system/avahi-daemon.service
SystemdEnable avahi /usr/lib/systemd/system/avahi-daemon.socket

# Install your own custom unit that does not come from a package
CopyFile /etc/systemd/system/mycustomservice.service
SystemdEnable --from-file /etc/systemd/system/mycustomservice.service

# Install a service that is parameterised
SystemdEnable --name syncthing@myusername.service syncthing /usr/lib/systemd/system/syncthing@.service

# Install a user service globally on the system
SystemdEnable --type user wireplumber /usr/lib/systemd/user/wireplumber.service

# Mask a service: Pacman already runs ldconfig after updating/installing packages, so
# this is useless and just slows down the boot.
SystemdMask ldconfig.service