Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions backend/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,27 @@ if have_runit
install_mode: 'rwxr-xr-x'
)
endif

# nitro backend
if have_nitro
install_data(
'nitro',
install_dir: join_paths(get_option('libexecdir'), 'turnstile'),
install_mode: 'rwxr-xr-x'
)

install_data(
'nitro.conf',
install_dir: join_paths(get_option('sysconfdir'), 'turnstile/backend'),
install_mode: 'rw-r--r--'
)

configure_file(
input: 'turnstile-update-nitro-env.in',
output: 'turnstile-update-nitro-env',
configuration: conf_data,
install: true,
install_dir: get_option('bindir'),
install_mode: 'rwxr-xr-x'
)
endif
100 changes: 100 additions & 0 deletions backend/nitro
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/bin/sh
#
# This is the turnstile nitro backend. It accepts the action as its first
# argument, which is either "ready", "run", or "stop". In case of "run", it's
# invoked directly through /bin/sh as if it was a login shell, and therefore
# it has acccess to shell profile, and the shebang is functionally useless but
# should be preserved as a convention. For "ready", it's a regular shell.
#
# Arguments for "ready":
#
# srvstr: path to the readiness indicator temporary file
#
# Arguments for "run":
#
# ready_p: readiness pipe (fifo). has the path to a temporary file created by
# nitro SYS/setup written to it.
# srvdir: the nitro control socket goes here, as well as the envdir for chpst.
# the envdir then gets destructively symlinked to the service_env_dir.
# confdir: the path where turnstile's configuration data resides, used
# to source the configuration file
#
# Arguments for "stop":
#
# pid: the PID of the service manager to stop (gracefully); it should
# terminate the services it's running and then stop itself
#
# Copyright 2026 toast <code@toast.bunkerlabs.net>
# License: BSD-2-Clause

log() { echo "$@"; } >&2
die() { log "$1"; exit "$2"; }

case "$1" in
ready) [ -f "$2" ] && exec rm "$2" ||
die "nitro: invalid readiness indicator '$2'" 64 ;;
run) ;; # continued after esac
# nitro gracefully terminates on SIGTERM
stop) exec kill "$2" ;;
*) exit 32 ;;
esac

READYP=${2:?readiness pipe unset}
SRVDIR=${3:?srvdir unset}
CONFIG=${4:?confdir unset}/nitro.conf

if [ ! -p "$READYP" ]; then
die "nitro: invalid input argument(s)" 64
fi

if [ ! -d "${HOME:?}" ]; then
dir "nitro: invalid home directory" 65
fi

shift $#

# source system profile mainly for profile.d
# do it before switching to set -e etc.
[ -r /etc/profile ] && . /etc/profile
set -e
[ -r "$CONFIG" ] && . "$CONFIG"

# config defaults in case it's mangled or missing
: ${services_dir:="${HOME}/.config/nitro.session"} \
${service_env_dir:="${XDG_RUNTIME_DIR:?}/turnstile.env"}

envd="$SRVDIR/env"
mkdir -pm 0700 "$envd"
NITRO_SOCK="$SRVDIR/nitro.sock"
printf "$NITRO_SOCK" >"$envd/NITRO_SOCK"

# destructively symlink the per-manager envdir to the configured one
if [ -n "$service_env_dir" ]; then
[ -d "$(dirname "$service_env_dir")" ] ||
die 'nitro: invalid location for service env_dir'
# unless we're not managing it
if [ ! -e "$service_env_dir" ] || [ -h "$service_env_dir" ]; then
ln -sf "$envd" "$service_env_dir"
else
die "nitro: refusing to overwrite '$service_env_dir'" 73
fi
fi

# this must succeed
mkdir -p "$services_dir/SYS"
cat << EOF > "$services_dir/SYS/setup"
#!/bin/sh
# DO NOT MODIFY: THIS WILL GET OVERWRITTEN
# PLACE YOUR SETUP IN ./rc
[ -r ./rc ] && . ./rc # nitroctl start my core services
# signal readiness
if [ -p "$READYP" ]; then
touch "$SRVDIR/ready"
printf '%s' "$SRVDIR/ready" > "$READYP"
fi
EOF
chmod +x "$services_dir/SYS/setup"

# use envd directly so another manager can run at the same time
exec env TURNSTILE_ENV_DIR="$envd" NITRO_SOCK="$NITRO_SOCK" \
nitro "$services_dir"
13 changes: 13 additions & 0 deletions backend/nitro.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# This is the configuration file for turnstile's nitro backend.
#
# It follows the POSIX shell syntax (being sourced into a script).
# The complete launch environment available to dinit can be used.
#
# It is a low-level configuration file. In most cases, it should
# not be modified by the user.

# the directory user service files are read from.
services_dir="${HOME}/.config/nitro.session"

# the environment variable directory user service files can read from.
service_env_dir="${XDG_RUNTIME_DIR}/turnstile.env"
33 changes: 33 additions & 0 deletions backend/turnstile-update-nitro-env.in
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make sense for this to be generic across backends that need it?

turnstile-update-envdir <nitro|runit> <var>[=value]

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would, but figured I'd do something closer to what's already in the repo. I had considered a fully-generic envdir + envfile handler, but I can't quite find my draft for that. I have found a turnstile-envdir draft from a bit earlier than that point, see examples for (some of) the motivation. Let me know what you think!

I'm perfectly willing to adapt the PR to also update both envdir-based backends to use this mechanism or something similar if that's what's desirable.

A draft of `turnstile-envdir` I have lying around.
#!/bin/bash
# bash is pulled in for printf %q (which may become POSIX) and namerefs
# clobbers $envdir, $e, $f, $action, $name
# note that printf %q under bash produces $'' strings
# these are POSIX-2024 compliant, but your shell may not be

: ${envdir:=${ENVDIR:-${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/env}}

usage() {
	cat <<-EOF
	$1 [-LSX] [-d envdir] [VAR...]
	Manages a chpst(8)-compatible env dir.

	-L: Export environment in a quoted format appropriate for evaluating,
	    if any VARs are specified, only export those. Default mode.
	-S: Update env dir with arguments. If VAR follows the VAR=VAL pattern,
	    set VAR to VAL, otherwise take the value from the environment.
	-X: Take VAR... to be a sublist to execute with the environment.
	    Exactly equivalent to exec chpst -e "\$envdir".
	-d: Set envdir explicitly. The actual envdir is a priority rule:
	    the OPTARG of -d > \$envdir > \$ENVDIR > /env under \$XDG_RUNTIME_DIR.
	    With your current invocation, it would be '$envdir'.

	Examples:
	- eval "\$($1 -L)" # set all variables without exporting them
	- set -a; eval "\$($1 -L)"; set +a # ditto, while exporting them
	- $1 -S foo bar=baz # set foo to whatever \$foo is right now, and bar to baz
	- $1 -X my cmd # run 'my cmd' with the envdir variables set and exported
	- $1 -d /some/dir # like '$1 -L' but /some/dir is used as the envdir
	EOF
} >&2

action=list
while getopts LSXd: name
do
	case $name in
		L) action=list ;;
		S) action=set  ;;
		X) action=exec ;;
		d) envdir=$OPTARG ;;
		?) usage "$(basename "$0")"; exit 2 ;;
	esac
done
shift $(( $OPTIND - 1))

case $action in
	list)
		[ -d "$envdir" ] || break
		for f in "$envdir"/*; do
			# WARN: printf %q is not POSIX… yet; see notes under POSIX 2024
			# it's *not* yet available in the dash builtin or busybox
			# use either bash (#!/bin/bash) or coreutils to get this
			printf '%s=%q\n' "$(basename "$f")" "$(cat "$f")"
		done
		;;
	set)
		[ -d "$envdir" ] || mkdir -pm 0700 "$envdir"
		for e; do
			case "$e" in
				*=*) printf '%s' "${e#*=}" > "$envdir/${e%%=*}" ;;
				*)
					declare -n name="$e"
					printf "$name" >"$envdir/$e"
					;;
			esac
		done
		;;
	exec)
		exec chpst -e "$envdir" "$@"
		;;
esac

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/sh
# Copyright 2026 toast <code@toast.bunkerlabs.net>
# License: BSD-2-Clause

usage() {
cat <<-EOF
turnstile-update-nitro-env [VAR] ...
Updates values in the shared chpst(8) env dir.

If VAR is a variable name, the value is taken from the environment.
If VAR is VAR=VAL, sets VAR to VAL.
EOF
}

. @CONF_PATH@/backend/nitro.conf
: ${service_env_dir:="${XDG_RUNTIME_DIR}/turnstile.env"}
envd=${TURNSTILE_ENV_DIR:-${service_env_dir:-${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/turnstile.env}}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think it makes sense for the envdir to be in XDG_RUNTIME_DIR. I put it in ~/.config so users could set persistent vars

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really like doing that because you then end up with stale references. Consider for example:

  1. Service foo also updates the activation environment to include its _SOCK variable, but is not launched on session startup (but rather manually by the user).
  2. User imports the activation environment in their rc.
  3. User starts a session, starts foo, then reboots.
  4. FOO_SOCK is now in the activation environment, even though it shouldn't be (i.e. it is a stale reference).

There are various ways to update the activation environment that is in control of the user (xdg autostart, profile, window manager config, etc). For persistent vars I would prefer those be used, ensuring that the current activation environment narrowly matches the current session.


if [ $# -eq 0 ] || [ "$1" = "-h" ]; then
usage
exit 0
fi

for var; do
case "$var" in
*=*)
eval echo "${var#*=}" > "$envd/${var%%=*}"
;;
*)
eval echo '$'"$var" > "$envd/$var"
;;
esac
done
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ scdoc_dep = dependency(

have_dinit = get_option('dinit').enabled()
have_runit = get_option('runit').enabled()
have_nitro = get_option('nitro').enabled()

conf_data = configuration_data()
conf_data.set_quoted('RUN_PATH', get_option('rundir'))
Expand Down
5 changes: 5 additions & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ option('runit',
description: 'Whether to install runit-related backend and data'
)

option('nitro',
type: 'feature', value: 'disabled',
description: 'Whether to install nitro-related backend and data'
)

option('default_backend',
type: 'string', value: '',
description: 'Override the default backend'
Expand Down