# 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 # 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 < [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 - 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 [=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 - set the unit name. Defaults to "invoke-@" for services, "invoke--" 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 < [ [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 - launch in the specified system slice. --env [=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 - set the unit name. --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'