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