pave

install.bash

## install.bash: `pave install` and `pave install-boot`, which set up the triggers that call `pave run`.
##
## Every trigger's payload is one `pave run` line with absolute paths baked in:
## triggers run with other users' homes, other working dirs, and minimal environments.
## Boot triggers go under $PAVE_SYSROOT (default: the real root), so they can be installed into a fake root.

## _pave_trigger_path_ok fails unless a path can be written into every trigger format unescaped.
## (Each format has its own quoting rules -- shell, systemd, desktop entries, cron --
## and a conservative character set is simpler than four escapers.)
_pave_trigger_path_ok() {
	if [[ ! $1 =~ ^/[A-Za-z0-9._/+@,:=~\ -]*$ ]]; then
		_pave_error "'$1' has characters pave won't put in a trigger; use a path made of letters, digits, spaces, and ._/+@,:=~-"
		return 1
	fi
}

## _pave_payload prints the one command line every trigger runs.
##
## - @param $1 goals dir.
## - @param $2 phase.
_pave_payload() {
	printf '"%s" run "%s" %s' "$PAVE_BIN" "$1" "$2"
}

## _pave_detect_boot prints the boot trigger kind for this machine: the first match in a fixed order.
## The order goes from the most certain evidence (a running systemd) to the least
## (a cron.d directory doesn't prove a cron daemon reads it).
_pave_detect_boot() {
	local r=${PAVE_SYSROOT:-}
	if [ -d "$r/run/systemd/system" ]; then
		printf systemd
	elif [ -d "$r/etc/local.d" ]; then
		printf openrc-local
	elif [ -f "$r/etc/rc.local" ]; then
		printf rc-local
	elif [ -d "$r/etc/cron.d" ]; then
		printf cron-reboot
	else
		_pave_error "found no boot hook pave knows: no systemd, /etc/local.d, /etc/rc.local, or /etc/cron.d"
		return 1
	fi
}

_pave_trigger_systemd() {
	local d="${PAVE_SYSROOT:-}/etc/systemd/system"
	ensure_file "$d/pave-boot.service" 644 "[Unit]
Description=pave boot goals
After=local-fs.target

[Service]
Type=oneshot
ExecStart=$(_pave_payload "$1" boot)

[Install]
WantedBy=multi-user.target" || return 1
	# What `systemctl enable` would do, without needing systemctl.
	ensure_link ../pave-boot.service "$d/multi-user.target.wants/pave-boot.service"
}

_pave_trigger_openrc_local() {
	ensure_file "${PAVE_SYSROOT:-}/etc/local.d/pave.start" 755 "#!/bin/sh
exec $(_pave_payload "$1" boot)"
}

_pave_trigger_rc_local() {
	local f="${PAVE_SYSROOT:-}/etc/rc.local"
	# `|| :` because some rc.local files run under `sh -e`, and pave's failures shouldn't stop the rest of it.
	ensure_block "$f" top trigger "$(_pave_payload "$1" boot) || :" || return 1
	if [ ! -x "$f" ]; then
		chmod +x -- "$f" || return 1
		changed "chmod +x $f"
	fi
}

_pave_trigger_cron_reboot() {
	ensure_file "${PAVE_SYSROOT:-}/etc/cron.d/pave" 644 "@reboot root $(_pave_payload "$1" boot)"
}

_pave_trigger_xdg_autostart() {
	ensure_file "${XDG_CONFIG_HOME:-$HOME/.config}/autostart/pave-session.desktop" 644 "[Desktop Entry]
Type=Application
Name=pave session goals
Exec=$(_pave_payload "$1" session)
NoDisplay=true"
}

## _pave_trigger_profile adds the login trigger to whichever file bash reads for login shells:
## the first of these that exists, else ~/.profile.
_pave_trigger_profile() {
	local f
	for f in "$HOME/.bash_profile" "$HOME/.bash_login" "$HOME/.profile"; do
		[ -e "$f" ] && break
	done
	ensure_block "$f" bottom trigger "$(_pave_payload "$1" login)"
}

## _pave_resolve_goals prints the goals dir as an absolute path, keeping symlinks as given,
## so repointing a symlinked goals dir needs no reinstall.
_pave_resolve_goals() {
	if ! (cd -- "$1" 2>/dev/null && pwd); then
		_pave_error "goals dir '$1' not found"
		return 1
	fi
}

## _pave_cmd_install implements `pave install GOALS_DIR`:
## installs this user's triggers, then the boot trigger via sudo.
##
## - @return 0, or an exit code from the table in the README.
_pave_cmd_install() {
	if [ "$#" -ne 1 ]; then
		_pave_error "usage: pave install GOALS_DIR"
		return 4
	fi
	if [ "$EUID" -eq 0 ]; then
		_pave_error "run pave install as the user whose session and login triggers it should install; it uses sudo for the boot trigger"
		return 13
	fi
	local dir
	dir=$(_pave_resolve_goals "$1") || return 5
	_pave_trigger_path_ok "$PAVE_BIN" || return 9
	_pave_trigger_path_ok "$dir" || return 9

	_PAVE_TALLY=/dev/null # install reports changes, but doesn't count them
	local code=0
	_pave_trigger_xdg_autostart "$dir" || code=11
	_pave_trigger_profile "$dir" || code=11

	echo "pave: installing the boot trigger, via sudo"
	if ! sudo -- "$PAVE_BIN" install-boot "$dir"; then
		_pave_error "sudo pave install-boot failed"
		[ "$code" -ne 0 ] || code=12
	fi
	return "$code"
}

## _pave_cmd_install_boot implements `pave install-boot GOALS_DIR`:
## installs the boot trigger for this machine, then runs the boot phase,
## so its goals take effect now rather than at next boot.
## Normally called by `pave install`, via sudo.
##
## - @return 0, or an exit code from the table in the README.
_pave_cmd_install_boot() {
	if [ "$#" -ne 1 ]; then
		_pave_error "usage: pave install-boot GOALS_DIR"
		return 4
	fi
	local dir kind
	dir=$(_pave_resolve_goals "$1") || return 5
	_pave_trigger_path_ok "$PAVE_BIN" || return 9
	_pave_trigger_path_ok "$dir" || return 9
	kind=$(_pave_detect_boot) || return 10

	_PAVE_TALLY=/dev/null # install reports changes, but doesn't count them
	echo "pave: boot trigger kind: $kind"
	"_pave_trigger_${kind//-/_}" "$dir" || return 11
	"$PAVE_BIN" run "$dir" boot
}

lib/install.bash · 5.3 KiB · 7240887182