dotfiles/.config/zsh/00-systemd.zsh

245 lines
7.1 KiB
Bash

# Move shell into its own systemd scope if possible
if ! hash busctl &>/dev/null; then
return
fi
if [[ $_scope_loaded == 1 ]]; then
# shell reload, ignore
return
fi
_scope_loaded=1
# Load new scope unit
busctl -q --user call org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager StartTransientUnit 'ssa(sv)a(sa(sv))' \
"zsh-$$.scope" \
replace \
2 \
PIDs au 1 $$ \
Slice s ${SHELL_SLICE:-app-shell.slice} \
0 \
&>/dev/null # this call fails on a shell re-exec, but we don't really care since the scope will already exist in that case, so forward to dave null
# Environment variable possibly passed by parent, but we don't want it to be inherited, so unset it
unset SHELL_SLICE
function env_merge {
# usage: env_merge <ENVIRON> <VALUE>
# If ENVIRON is not already set, set it to VALUE. Then export ENVIRON.
: "${(P)1=$2}"
export $1
}
# import environment from systemd user daemon.
# this is necessary when running in a context not descended from the user manager (e.g. tty login, ssh login, etc.)
# use the "env_merge" function defined above to not override existing environment (e.g. SSH-forwarded SSH_AUTH_SOCK or DISPLAY)
eval $(systemctl --user show-environment | awk 'match($0, /^([a-zA-Z_]+)=(.*)$/, ary) { print "env_merge " ary[1] " " ary[2] }')
# discard the env_merge function
unset -f env_merge
# Set and unset variables
export INVOCATION_ID=$(systemctl --user show "zsh-$$.scope" | awk -F= '/InvocationID=/{ print $2 }')
export SYSTEMD_UNIT="zsh-$$.scope"
unset SYSTEMD_EXEC_PID
unset JOURNAL_STREAM
unset MEMORY_PRESSURE_WATCH
unset MEMORY_PRESSURE_WRITE
function lift-constraints() {
# Don't lift the PID constraint since there's really no reason a shell should have more than 100 active PIDs. Reap what you sow!
env systemctl --user set-property zsh-$$.scope CPUQuota=
env systemctl --user set-property zsh-$$.scope MemoryHigh=
env systemctl --user set-property zsh-$$.scope MemoryMax=
}
function run() {
local args slice type unit passed_envs
passed_envs=(SUDO_PROMPT EDITOR SUDO_EDITOR VISUAL)
args=(--user --same-dir -q --collect)
slice="app-shell.slice"
type="scope"
while true; do
case $1 in
--help)
cat <<EOF
Usage: $0 [OPTIONS] <COMMAND> [ARGS...]
Invoke a command outside of the shell's execution context, detaching it from any resource
constraints on the shell itself.
It will be run by the user service manager using systemd-run. By default, the command
is invoked in the app-shells slice, so is still subject to any restrictions placed
on the set of all user shells.
This is intended to simplify the act of intentionally launching resource-intensive programs
(such as compilers) while still limiting most programs in terms of how much they are allowed
to consume.
Some environment variables are inherited by default, which can be disabled with --clean-env:
$passed_envs
OPTIONS:
--help - display this help message and exit
--app - launch in the main app slice rather than the app-shell slice. Equivalent to --slice app.slice
--background - launch in the background slice. Equivalent to --slice background.slice
--slice <SLICE> - launch in the specified user slice.
--service - create a service unit, rather than a scope, producing a truly clean environment. This separates
the program from the shell's process tree, preventing the shell's job management from managing it.
--env <VAR>[=VALUE] - inherit the given environment variable from the shell, or set it if a value is given.
--clean-env - disable the default set of inherited environment variables
--unit <UNIT> - set the unit name. Defaults to "invoke-<shell PID>@<random>" for services,
"invoke-<shell PID>-<random>" for scopes.
EOF
return 0
;;
--app)
slice="app.slice"
;;
--background)
slice="background.slice"
;;
--slice)
slice=$2
shift
;;
--service)
type="service"
;;
--env)
args+="-E"
args+=$2
shift
;;
--clean-env)
passed_envs=()
;;
--unit)
unit=$2
shift
;;
*)
break
;;
esac
shift
done
for env in $passed_envs; do
args+="--setenv=$env"
done
case $type in
service)
if [[ $unit == '' ]]; then
unit="invoke-$$@$RANDOM.service"
fi
args+=(--service-type=exec --pty --pipe --wait)
;;
scope)
if [[ $unit == '' ]]; then
unit="invoke-$$-$RANDOM.scope"
fi
args+="--scope"
;;
esac
if [[ $unit != *.$type ]]; then
echo "Unit suffix does not match unit type! Should end in .$type!"
return 1
fi
systemd-run $args --slice=${slice} --unit=${unit} -- "$@"
}
function with-group {
local args slice unit passed_envs user passed_envs_when_user_is_us
passed_envs=(EDITOR VISUAL)
passed_envs_when_user_is_us=(DBUS_SESSION_BUS_ADDRESS DISPLAY WAYLAND_DISPLAY XDG_RUNTIME_DIR)
args=(--same-dir -q --collect)
slice="system.slice"
user=$USER
groups=${1//,/ }
shift
while true; do
case $1 in
--help)
cat <<EOF
Usage: with-group [OPTIONS] <GROUPS> [<COMMAND> [ARGS...]]
Invoke a command as the current user with additional supplementary groups. Provides a convenient
way to temporarily gain permissions afforded to groups the user does not have, while still retaining
regular user permissions (i.e. without switching to another user or fully elevating to root).
Multiple groups may be specified in a comma-separated list (similar to the -G flag of useradd and usermod).
It will be run by the system service manager using systemd-run as a transient service. As it has to be
invoked by the service manager to gain the additional permissions, there is no way to run it as a scope.
If no command is specified, the current shell will be launched instead.
Some environment variables are inherited by default, which can be disabled with --clean-env:
$passed_envs
OPTIONS:
--help - display this help message and exit.
--slice <SLICE> - launch in the specified system slice.
--env <VAR>[=VALUE] - inherit the given environment variable from the shell, or set it if a value is given.
--clean-env - disable the default set of inherited environment variables.
--unit <UNIT> - set the unit name.
--user <USER> - run as the given user rather than the current user.
EOF
return 0
;;
--slice)
slice=$2
shift
;;
--env)
args+="-E"
args+=$2
shift
;;
--clean-env)
passed_envs=()
;;
--unit)
unit=$2
shift
;;
--user)
user=$2
shift
;;
*)
break
;;
esac
shift
done
for env in $passed_envs; do
args+="--setenv=$env"
done
if [[ $user == $USER ]]; then
for env in $passed_envs_when_user_is_us; do
args+="--setenv=$env"
done
fi
args+=(--service-type=exec --pty --pipe --wait -p "User=$user" -p "SupplementaryGroups=$groups" -p "PAMName=login")
if [[ $unit != "" ]]; then
args+=("--unit=$unit")
fi
if [[ $1 == '' ]]; then
systemd-run $args --slice=${slice} -- $SHELL
else
systemd-run $args --slice=${slice} -- "$@"
fi
}
for cmd in $always_detach; do
eval $(echo alias $cmd=\"run $cmd\")
done
for cmd in $always_detach_as_service; do
eval $(echo alias $cmd=\"run --service $cmd\")
done
alias ssh='run --slice app-ssh.slice --unit ssh-client-$RANDOM.scope ssh'