## 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 }