245 lines
7.1 KiB
Bash
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'
|
|
|