Remove privileged files left behind by retired Omarchy installers
Three installers that no longer exist each left a root-owned file on disk, and nothing in Omarchy has ever removed any of them. /etc/sudoers.d/first-run granted the installing account passwordless sudo for the rest of the first boot, unrestricted /usr/bin/systemctl included from 2025-10-14 on. omarchy-first-run clears its first-run.mode guard before eight set -e steps and only deletes the grant after them, so any failure in between strands it with nothing left to retry. /etc/sudoers.d/tsui named whatever $(which tsui) resolved to for the installing user, normally a binary under their own home that the vendor script had just written without sudo. /etc/systemd/system/omarchy-plymouth-shutdown.service ran an ExecStop under the installing user's home as uid 0 on every shutdown. Each file is judged against what the installer that wrote it actually produced. The first-run grant was rewritten eight times and only the last four carry both Cmnd_Alias lines, so rather than key on those, every active line must be one the installer emitted and one of them must be its own self-cleanup. The shutdown unit is disabled but never stopped: stopping it is what would run the ExecStop being taken away. Generalize the migrations.md exception, which framed itself around pre-4 layout transitions and so did not cover installers retired on their own.
This commit is contained in:
+11
-10
@@ -166,13 +166,14 @@ Omarchy 4.0 is upgraded through `bin/omarchy-upgrade-to-quattro`, not through th
|
||||
normal migration runner. Do not add compatibility migrations for old installer
|
||||
layouts; put pre-4 package-layout transition work in the upgrade command instead.
|
||||
|
||||
Clearing a pre-4 layout that is a security defect is the exception, and belongs in
|
||||
a migration. The upgrade command only runs on a machine still making the 3 to 4
|
||||
crossing, so anything put there never reaches an install that crossed already —
|
||||
and a file an old installer wrote with a vulnerability in it is still sitting on
|
||||
those machines. The upgrade command finishes by running `omarchy-migrate`
|
||||
(`run_post_upgrade_migrations`), so one migration reaches both populations;
|
||||
a copy in the upgrade command would only be a second copy of the same predicate
|
||||
to keep correct. Such a migration must name the defect it clears and match the
|
||||
state the old installer actually produced, so a file the user wrote themselves is
|
||||
left alone.
|
||||
Clearing a privileged file that a retired installer left on disk is the exception,
|
||||
and belongs in a migration whether or not that installer was part of a package
|
||||
layout transition. The upgrade command only runs on a machine still making the 3
|
||||
to 4 crossing, so anything put there never reaches an install that crossed
|
||||
already, and it never runs at all for an installer that was retired on its own —
|
||||
while the file the installer wrote is still sitting on those machines. The upgrade
|
||||
command finishes by running `omarchy-migrate` (`run_post_upgrade_migrations`), so
|
||||
one migration reaches every population; a copy in the upgrade command would only
|
||||
be a second copy of the same predicate to keep correct. Such a migration must name
|
||||
the defect it clears and match what the old installer actually produced, so a file
|
||||
an administrator wrote themselves is left alone.
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
echo "Remove privileged files left behind by retired Omarchy installers"
|
||||
|
||||
sudoers_dir="${OMARCHY_SUDOERS_DIR:-/etc/sudoers.d}"
|
||||
systemd_dir="${OMARCHY_SYSTEMD_SYSTEM_DIR:-/etc/systemd/system}"
|
||||
|
||||
as_root() {
|
||||
if (( EUID == 0 )); then
|
||||
"$@"
|
||||
else
|
||||
sudo "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Three installers that no longer exist each left a root-owned file behind, and
|
||||
# nothing in Omarchy has ever removed any of them. Each is judged against what
|
||||
# the installer that wrote it actually produced, so a file of the same name that
|
||||
# an administrator wrote themselves is left alone.
|
||||
#
|
||||
# Emit the lines a parser would act on: comments and blanks dropped, backslash
|
||||
# continuations joined, and runs of whitespace collapsed so a reformatted copy
|
||||
# still compares equal. Reads the body on stdin, because /etc/sudoers.d is 0750
|
||||
# root:root and the caller has to hand us an elevated read.
|
||||
#
|
||||
# Comments are tested before continuations are joined, which is the order every
|
||||
# consumer here uses: udev's parse_file discards a '#' line without looking at a
|
||||
# trailing backslash (`udevadm verify` on "# disabled \" plus a bogus key reports
|
||||
# the error on line 2), sudo's toke.l comment rule consumes to the newline and
|
||||
# clears its continuation flag, and systemd's config_parse tests the comment
|
||||
# characters before appending to a continuation. Joining first would let a
|
||||
# comment ending in a backslash swallow the live line beneath it.
|
||||
#
|
||||
# FORMAT is sudoers or systemd. systemd takes ';' as well as '#'. sudo does not
|
||||
# treat every '#' as a comment: toke.l has INITIAL rules for ^#include and
|
||||
# ^#includedir, and its comment pattern excludes '#' followed by a digit or
|
||||
# -digit so those reach the ID token as a numeric uid user spec. Those lines are
|
||||
# active directives, and a file carrying one must not read as though it held only
|
||||
# generated lines.
|
||||
active_lines() {
|
||||
local format="$1"
|
||||
local comments='#'
|
||||
local line logical=""
|
||||
|
||||
[[ $format == "systemd" ]] && comments='#;'
|
||||
|
||||
while IFS= read -r line || [[ -n $line ]]; do
|
||||
if [[ $line =~ ^[[:space:]]*[$comments] ]] &&
|
||||
! { [[ $format == "sudoers" ]] && sudoers_hash_is_active "$line"; }; then
|
||||
# The two consumers part company here. sudo ends the logical line at a
|
||||
# comment and keeps what came before it, so `visudo -cf` reads a spec
|
||||
# ending in a backslash, then a comment, then a second spec as two live
|
||||
# specs; dropping the pending half would hide an administrator's grant and
|
||||
# let this file read as though the installer had written all of it. systemd
|
||||
# resumes the continuation instead: `systemd-analyze verify` on "ExecStop=\"
|
||||
# + "; c" + a path resolves that path, so the pending half has to stay.
|
||||
if [[ $format == "sudoers" ]]; then
|
||||
emit_logical "$logical"
|
||||
logical=""
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ $line == *\\ ]]; then
|
||||
logical+="${line%\\} "
|
||||
continue
|
||||
fi
|
||||
|
||||
emit_logical "$logical$line"
|
||||
logical=""
|
||||
done
|
||||
}
|
||||
|
||||
# One logical line, whitespace collapsed so a reformatted copy still compares
|
||||
# equal, and nothing at all for a line that held only whitespace.
|
||||
emit_logical() {
|
||||
local -a parts
|
||||
|
||||
read -ra parts <<<"$1"
|
||||
if (( ${#parts[@]} )); then
|
||||
printf '%s\n' "${parts[*]}"
|
||||
fi
|
||||
}
|
||||
|
||||
sudoers_hash_is_active() {
|
||||
local line="$1"
|
||||
|
||||
[[ $line =~ ^[[:space:]]*#include[[:blank:]] ]] && return 0
|
||||
[[ $line =~ ^[[:space:]]*#includedir[[:blank:]] ]] && return 0
|
||||
[[ $line =~ ^[[:space:]]*#-?[0-9] ]] && return 0
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# install/preflight/first-run-mode.sh (2025-08-25 to 2026-05-25) granted the
|
||||
# installing account passwordless sudo for the rest of the first boot, including
|
||||
# an unrestricted /usr/bin/systemctl from 2025-10-14 on -- enough to link and
|
||||
# start a unit of the user's own, which is root. bin/omarchy-first-run was meant
|
||||
# to delete the grant, but it clears its first-run.mode guard as the very first
|
||||
# statement and only reaches the removal after eight set -e steps, two of which
|
||||
# touch the network. Any failure in between leaves the grant on the machine with
|
||||
# nothing left to retry it.
|
||||
#
|
||||
# The installer rewrote this file eight times, and only the last four carry both
|
||||
# Cmnd_Alias lines, so keying on those would walk past the earlier ones. Instead
|
||||
# require every active line to be one the installer itself emitted, plus at least
|
||||
# one line that is unmistakably this grant: its own self-cleanup. One
|
||||
# hand-written line anywhere in the file and it is not ours to delete.
|
||||
first_run_sudoers_is_generated() {
|
||||
local spec_pattern='^[^[:space:]]+ ALL=\(ALL\) NOPASSWD: (.+)$'
|
||||
local marker_pattern='^/bin/rm -f /home/[^/]+/\.local/state/omarchy/first-run\.mode$'
|
||||
local line command
|
||||
local seen_any=0 seen_marker=0
|
||||
|
||||
while IFS= read -r line; do
|
||||
seen_any=1
|
||||
|
||||
case "$line" in
|
||||
"Cmnd_Alias SYMLINK_RESOLVED = /usr/bin/ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf")
|
||||
continue
|
||||
;;
|
||||
"Cmnd_Alias FIRST_RUN_CLEANUP = /bin/rm -f /etc/sudoers.d/first-run" | \
|
||||
"Cmnd_Alias FIRST_RUN_CLEANUP = /bin/rm -f /etc/sudoers.d/first-run, /bin/rm -f /etc/sudoers.d/99-omarchy-installer-reboot")
|
||||
seen_marker=1
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
|
||||
# Everything else the installer wrote is a user spec naming the installing
|
||||
# account, whose name cannot be assumed here: it may since have been renamed
|
||||
# or removed, and a second account runs this migration too.
|
||||
if [[ ! $line =~ $spec_pattern ]]; then
|
||||
return 1
|
||||
fi
|
||||
command=${BASH_REMATCH[1]}
|
||||
|
||||
case "$command" in
|
||||
"/usr/bin/systemctl" | "/usr/bin/ufw" | "/usr/bin/ufw-docker" | \
|
||||
"/usr/bin/gtk-update-icon-cache" | "/usr/bin/udevadm" | \
|
||||
"/usr/bin/tee /etc/udev/rules.d/*" | "SYMLINK_RESOLVED")
|
||||
continue
|
||||
;;
|
||||
"FIRST_RUN_CLEANUP" | "/bin/rm -f /etc/sudoers.d/first-run")
|
||||
seen_marker=1
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ $command =~ $marker_pattern ]]; then
|
||||
seen_marker=1
|
||||
continue
|
||||
fi
|
||||
|
||||
return 1
|
||||
done < <(active_lines sudoers)
|
||||
|
||||
(( seen_any && seen_marker ))
|
||||
}
|
||||
|
||||
# bin/omarchy-install-tailscale (2025-08-22 to 2026-02-02) ran
|
||||
# "echo \"\$USER ALL=(ALL) NOPASSWD: \$(which tsui)\" | sudo tee
|
||||
# /etc/sudoers.d/tsui" one line after installing tsui by piping a vendor script
|
||||
# to bash with no sudo at all, so the path it resolved was usually the user's own
|
||||
# ~/.local/bin. Overwrite that file, run sudo tsui, and you are root. The grant
|
||||
# goes whatever the path turned out to be: the feature was dropped from Omarchy,
|
||||
# and unrestricted NOPASSWD on a TUI that can shell out is an escalation from a
|
||||
# root-owned path too.
|
||||
tsui_sudoers_is_generated() {
|
||||
local spec_pattern='^[^[:space:]]+ ALL=\(ALL\) NOPASSWD: ([^[:space:]]+)$'
|
||||
local line command="" count=0
|
||||
|
||||
while IFS= read -r line; do
|
||||
count=$(( count + 1 ))
|
||||
if (( count > 1 )); then
|
||||
return 1
|
||||
fi
|
||||
if [[ ! $line =~ $spec_pattern ]]; then
|
||||
return 1
|
||||
fi
|
||||
command=${BASH_REMATCH[1]}
|
||||
done < <(active_lines sudoers)
|
||||
|
||||
if (( count == 1 )) && [[ ${command##*/} == "tsui" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# install/plymouth.sh wrote this unit for two days (2025-07-05 to 2025-07-07)
|
||||
# with an unquoted heredoc, so ExecStop names the installing user's home. The
|
||||
# unit is enabled WantedBy=multi-user.target, so systemd runs that path as uid 0
|
||||
# on every shutdown, with no hardware event needed to reach it.
|
||||
plymouth_unit_runs_from_home() {
|
||||
local binary="omarchy-plymouth-shutdown-sync"
|
||||
local exec_stop_pattern='^ExecStop[[:space:]]*=[[:space:]]*(.*)$'
|
||||
local home_pattern="^(/home/[^/]+|/root)/\\.local/share/omarchy/bin/$binary\$"
|
||||
local line word
|
||||
local -a words
|
||||
|
||||
while IFS= read -r line; do
|
||||
if [[ ! $line =~ $exec_stop_pattern ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
read -ra words <<<"${BASH_REMATCH[1]}"
|
||||
if (( ! ${#words[@]} )); then
|
||||
continue
|
||||
fi
|
||||
|
||||
# systemd reads -, @, +, ! and : ahead of the command as flags, not as part
|
||||
# of the path it runs.
|
||||
word=${words[0]}
|
||||
while [[ $word == [-@+!:]* ]]; do
|
||||
word=${word:1}
|
||||
done
|
||||
|
||||
if [[ $word =~ $home_pattern || $word == "$HOME/.local/share/omarchy/bin/$binary" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done < <(active_lines systemd)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# /etc/sudoers.d is 0750 root:root as shipped, and omarchy-migrate runs as the
|
||||
# logged-in user, so an unelevated [[ -f ]] on a file in there is false whether or
|
||||
# not the file exists and an unelevated read returns nothing. Both tests and both
|
||||
# reads have to be elevated or this migration reports success having done nothing.
|
||||
# One combined probe first, so the common case of neither file being present costs
|
||||
# a single sudo call rather than one per file.
|
||||
first_run_sudoers="$sudoers_dir/first-run"
|
||||
tsui_sudoers="$sudoers_dir/tsui"
|
||||
|
||||
if as_root test -e "$first_run_sudoers" -o -e "$tsui_sudoers"; then
|
||||
if as_root test -f "$first_run_sudoers" &&
|
||||
as_root cat "$first_run_sudoers" | first_run_sudoers_is_generated; then
|
||||
as_root rm -f "$first_run_sudoers"
|
||||
fi
|
||||
|
||||
if as_root test -f "$tsui_sudoers" &&
|
||||
as_root cat "$tsui_sudoers" | tsui_sudoers_is_generated; then
|
||||
as_root rm -f "$tsui_sudoers"
|
||||
fi
|
||||
fi
|
||||
|
||||
# /etc/systemd/system is 0755, so this one needs no elevation to look at.
|
||||
plymouth_unit="$systemd_dir/omarchy-plymouth-shutdown.service"
|
||||
if [[ -f $plymouth_unit ]] && plymouth_unit_runs_from_home <"$plymouth_unit"; then
|
||||
# Disable, never stop. Stopping the unit is precisely what runs ExecStop, and
|
||||
# ExecStop is the path this migration exists to keep root away from; disabling
|
||||
# only drops the multi-user.target symlink.
|
||||
as_root systemctl disable omarchy-plymouth-shutdown.service >/dev/null 2>&1 || true
|
||||
as_root rm -f "$plymouth_unit"
|
||||
# systemd keeps serving the copy it already loaded until it rereads the
|
||||
# directory, so without this the unit is still there to run at shutdown.
|
||||
as_root systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
fi
|
||||
+456
@@ -0,0 +1,456 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(dirname "$0")/base-test.sh"
|
||||
|
||||
migration="$ROOT/migrations/1788025225.sh"
|
||||
[[ -f $migration ]] || fail "the retired installer artifact migration exists at $migration"
|
||||
|
||||
test_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$test_dir"' EXIT
|
||||
|
||||
mkdir -p "$test_dir/bin"
|
||||
|
||||
# sudo runs the real command, so the removals act on the redirected directories
|
||||
# below and the elevated calls land in the log beside it.
|
||||
cat >"$test_dir/bin/sudo" <<'STUB'
|
||||
#!/bin/bash
|
||||
|
||||
printf 'sudo %s\n' "$*" >>"$CALLS"
|
||||
exec "$@"
|
||||
STUB
|
||||
|
||||
cat >"$test_dir/bin/systemctl" <<'STUB'
|
||||
#!/bin/bash
|
||||
|
||||
printf 'systemctl %s\n' "$*" >>"$CALLS"
|
||||
STUB
|
||||
|
||||
chmod +x "$test_dir/bin/"*
|
||||
|
||||
export CALLS="$test_dir/calls"
|
||||
|
||||
sudoers_dir="$test_dir/sudoers.d"
|
||||
systemd_dir="$test_dir/systemd"
|
||||
home_dir="$test_dir/home"
|
||||
first_run="$sudoers_dir/first-run"
|
||||
tsui="$sudoers_dir/tsui"
|
||||
plymouth_unit="$systemd_dir/omarchy-plymouth-shutdown.service"
|
||||
|
||||
reset_machine() {
|
||||
rm -rf "$sudoers_dir" "$systemd_dir" "$home_dir"
|
||||
mkdir -p "$sudoers_dir" "$systemd_dir" "$home_dir"
|
||||
}
|
||||
|
||||
run_migration() {
|
||||
: >"$CALLS"
|
||||
|
||||
HOME="$home_dir" \
|
||||
OMARCHY_SUDOERS_DIR="$sudoers_dir" \
|
||||
OMARCHY_SYSTEMD_SYSTEM_DIR="$systemd_dir" \
|
||||
PATH="$test_dir/bin:$PATH" \
|
||||
bash -euo pipefail "$migration" >/dev/null
|
||||
}
|
||||
|
||||
# /etc/sudoers.d is 0750 root:root on a real machine, so the migration has to
|
||||
# escalate merely to see whether either grant is there. An empty call log is
|
||||
# therefore the wrong invariant: what must be absent unless a file really is
|
||||
# Omarchy's is a removal, or a unit being disabled or reloaded.
|
||||
assert_changed_nothing() {
|
||||
local label="$1"
|
||||
|
||||
! grep -qE '^(sudo rm|systemctl disable|systemctl daemon-reload)' "$CALLS" ||
|
||||
fail "$label" "$(cat "$CALLS")"
|
||||
pass "$label"
|
||||
}
|
||||
|
||||
# The reads themselves must be elevated too. A plain [[ -f ]] or cat under a
|
||||
# root-only directory returns nothing as the logged-in user, which would make the
|
||||
# migration report success having looked at nothing at all.
|
||||
assert_read_elevated() {
|
||||
local file="$1" label="$2"
|
||||
|
||||
grep -qF "sudo test -f $file" "$CALLS" ||
|
||||
fail "$label" "$(cat "$CALLS")"
|
||||
grep -qF "sudo cat $file" "$CALLS" ||
|
||||
fail "$label" "$(cat "$CALLS")"
|
||||
pass "$label"
|
||||
}
|
||||
|
||||
write_plymouth_unit() {
|
||||
cat >"$plymouth_unit" <<EOF
|
||||
[Unit]
|
||||
Description=Sync Plymouth Theme on Shutdown
|
||||
DefaultDependencies=yes
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/bin/true
|
||||
ExecStop=$1
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
}
|
||||
|
||||
# Every distinct body install/preflight/first-run-mode.sh wrote across its eight
|
||||
# rewrites, oldest first. Only the last four carry both Cmnd_Alias lines, so a
|
||||
# predicate keyed on those would leave the first four grants on disk.
|
||||
first_run_variants=(
|
||||
'installer ALL=(ALL) NOPASSWD: /usr/bin/ufw
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/ufw-docker
|
||||
installer ALL=(ALL) NOPASSWD: /bin/rm -f /home/installer/.local/state/omarchy/first-run.mode'
|
||||
'installer ALL=(ALL) NOPASSWD: /usr/bin/ufw
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/ufw-docker
|
||||
installer ALL=(ALL) NOPASSWD: /bin/rm -f /etc/sudoers.d/first-run'
|
||||
'Cmnd_Alias FIRST_RUN_CLEANUP = /bin/rm -f /etc/sudoers.d/first-run
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/ufw
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/ufw-docker
|
||||
installer ALL=(ALL) NOPASSWD: FIRST_RUN_CLEANUP'
|
||||
'Cmnd_Alias FIRST_RUN_CLEANUP = /bin/rm -f /etc/sudoers.d/first-run
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/ufw
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/ufw-docker
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/gtk-update-icon-cache
|
||||
installer ALL=(ALL) NOPASSWD: FIRST_RUN_CLEANUP'
|
||||
'Cmnd_Alias FIRST_RUN_CLEANUP = /bin/rm -f /etc/sudoers.d/first-run
|
||||
Cmnd_Alias SYMLINK_RESOLVED = /usr/bin/ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/ufw
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/ufw-docker
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/gtk-update-icon-cache
|
||||
installer ALL=(ALL) NOPASSWD: SYMLINK_RESOLVED
|
||||
installer ALL=(ALL) NOPASSWD: FIRST_RUN_CLEANUP'
|
||||
'Cmnd_Alias FIRST_RUN_CLEANUP = /bin/rm -f /etc/sudoers.d/first-run
|
||||
Cmnd_Alias SYMLINK_RESOLVED = /usr/bin/ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/systemctl
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/ufw
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/ufw-docker
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/gtk-update-icon-cache
|
||||
installer ALL=(ALL) NOPASSWD: SYMLINK_RESOLVED
|
||||
installer ALL=(ALL) NOPASSWD: FIRST_RUN_CLEANUP'
|
||||
'Cmnd_Alias FIRST_RUN_CLEANUP = /bin/rm -f /etc/sudoers.d/first-run
|
||||
Cmnd_Alias SYMLINK_RESOLVED = /usr/bin/ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/systemctl
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/ufw
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/ufw-docker
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/gtk-update-icon-cache
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/tee /etc/udev/rules.d/*
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/udevadm
|
||||
installer ALL=(ALL) NOPASSWD: SYMLINK_RESOLVED
|
||||
installer ALL=(ALL) NOPASSWD: FIRST_RUN_CLEANUP'
|
||||
'Cmnd_Alias FIRST_RUN_CLEANUP = /bin/rm -f /etc/sudoers.d/first-run, /bin/rm -f /etc/sudoers.d/99-omarchy-installer-reboot
|
||||
Cmnd_Alias SYMLINK_RESOLVED = /usr/bin/ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/systemctl
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/ufw
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/ufw-docker
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/gtk-update-icon-cache
|
||||
installer ALL=(ALL) NOPASSWD: SYMLINK_RESOLVED
|
||||
installer ALL=(ALL) NOPASSWD: FIRST_RUN_CLEANUP'
|
||||
)
|
||||
|
||||
variant=0
|
||||
for body in "${first_run_variants[@]}"; do
|
||||
variant=$(( variant + 1 ))
|
||||
reset_machine
|
||||
printf '%s\n' "$body" >"$first_run"
|
||||
run_migration
|
||||
|
||||
[[ ! -e $first_run ]] ||
|
||||
fail "migration removes first-run grant variant $variant" "$(cat "$first_run")"
|
||||
done
|
||||
pass "migration removes every first-run sudoers grant the installer ever wrote"
|
||||
|
||||
grep -q '^sudo rm -f .*/sudoers\.d/first-run$' "$CALLS" ||
|
||||
fail "migration removes the first-run grant with elevated privileges" "$(cat "$CALLS")"
|
||||
pass "migration removes the first-run grant with elevated privileges"
|
||||
|
||||
# The grant is only recognisable as Omarchy's because every line in it is one the
|
||||
# installer emitted. One line an administrator added and the file is theirs.
|
||||
reset_machine
|
||||
cat >"$first_run" <<'EOF'
|
||||
Cmnd_Alias FIRST_RUN_CLEANUP = /bin/rm -f /etc/sudoers.d/first-run
|
||||
Cmnd_Alias SYMLINK_RESOLVED = /usr/bin/ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/systemctl
|
||||
installer ALL=(ALL) NOPASSWD: /usr/local/bin/our-own-deploy-script
|
||||
installer ALL=(ALL) NOPASSWD: FIRST_RUN_CLEANUP
|
||||
EOF
|
||||
before=$(cat "$first_run")
|
||||
run_migration
|
||||
|
||||
[[ -e $first_run ]] || fail "migration keeps a first-run file carrying a hand-written rule"
|
||||
[[ $(cat "$first_run") == "$before" ]] ||
|
||||
fail "migration leaves a hand-written first-run file byte for byte"
|
||||
assert_changed_nothing "migration changes nothing for a hand-written first-run file"
|
||||
pass "migration keeps a first-run file carrying a hand-written rule"
|
||||
|
||||
# Nothing in this file ties it to Omarchy's first run: no self-cleanup line.
|
||||
reset_machine
|
||||
cat >"$first_run" <<'EOF'
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/ufw
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/ufw-docker
|
||||
EOF
|
||||
run_migration
|
||||
|
||||
[[ -e $first_run ]] ||
|
||||
fail "migration keeps a same-named file that never cleaned itself up"
|
||||
pass "migration keeps a same-named file that never cleaned itself up"
|
||||
|
||||
# A rule continued onto the next line is one logical line, and a comment that is
|
||||
# continued stays a comment for the whole of it.
|
||||
reset_machine
|
||||
cat >"$first_run" <<'EOF'
|
||||
# Retired, keeping the old grant here for reference: \
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/systemctl
|
||||
Cmnd_Alias FIRST_RUN_CLEANUP = /bin/rm -f /etc/sudoers.d/first-run
|
||||
installer ALL=(ALL) NOPASSWD: \
|
||||
/usr/local/bin/our-own-deploy-script
|
||||
EOF
|
||||
run_migration
|
||||
|
||||
[[ -e $first_run ]] ||
|
||||
fail "migration reads a continued line as one rule and a continued comment as comment"
|
||||
pass "migration reads a continued line as one rule and a continued comment as comment"
|
||||
|
||||
reset_machine
|
||||
printf 'installer ALL=(ALL) NOPASSWD: %s/.local/bin/tsui\n' "$home_dir" >"$tsui"
|
||||
run_migration
|
||||
|
||||
[[ ! -e $tsui ]] || fail "migration removes the tsui grant pointing into the user's home"
|
||||
grep -q '^sudo rm -f .*/sudoers\.d/tsui$' "$CALLS" ||
|
||||
fail "migration removes the tsui grant with elevated privileges" "$(cat "$CALLS")"
|
||||
pass "migration removes the tsui grant pointing into the user's home"
|
||||
|
||||
# The feature is gone from Omarchy either way, and unrestricted NOPASSWD on a TUI
|
||||
# that can shell out escalates from a root-owned path too.
|
||||
reset_machine
|
||||
printf 'installer ALL=(ALL) NOPASSWD: /usr/bin/tsui\n' >"$tsui"
|
||||
run_migration
|
||||
|
||||
[[ ! -e $tsui ]] || fail "migration removes the tsui grant wherever the path points"
|
||||
pass "migration removes the tsui grant wherever the path points"
|
||||
|
||||
reset_machine
|
||||
cat >"$tsui" <<'EOF'
|
||||
# Kept after Omarchy dropped tsui, extended for our operators
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/tsui
|
||||
operator ALL=(ALL) NOPASSWD: /usr/bin/tsui
|
||||
EOF
|
||||
before=$(cat "$tsui")
|
||||
run_migration
|
||||
|
||||
[[ -e $tsui ]] || fail "migration keeps a tsui file an administrator extended"
|
||||
[[ $(cat "$tsui") == "$before" ]] || fail "migration leaves an extended tsui file byte for byte"
|
||||
assert_changed_nothing "migration changes nothing for an extended tsui file"
|
||||
pass "migration keeps a tsui file an administrator extended"
|
||||
|
||||
reset_machine
|
||||
printf 'installer ALL=(ALL) NOPASSWD: /usr/bin/tailscale\n' >"$tsui"
|
||||
run_migration
|
||||
|
||||
[[ -e $tsui ]] || fail "migration keeps a lone grant for some other command"
|
||||
pass "migration keeps a lone grant for some other command"
|
||||
|
||||
reset_machine
|
||||
write_plymouth_unit "/home/installer/.local/share/omarchy/bin/omarchy-plymouth-shutdown-sync"
|
||||
run_migration
|
||||
|
||||
[[ ! -e $plymouth_unit ]] ||
|
||||
fail "migration removes the shutdown unit that runs out of a user home"
|
||||
pass "migration removes the shutdown unit that runs out of a user home"
|
||||
|
||||
# Stopping the unit is exactly what runs ExecStop, which is the path being taken
|
||||
# away from root. Disabling only drops the multi-user.target symlink.
|
||||
! grep -q '^systemctl stop' "$CALLS" ||
|
||||
fail "migration never stops the unit, which would run ExecStop as root" "$(cat "$CALLS")"
|
||||
pass "migration never stops the unit, which would run ExecStop as root"
|
||||
|
||||
disable_at=$(grep -n '^systemctl disable omarchy-plymouth-shutdown\.service$' "$CALLS" | cut -d: -f1)
|
||||
remove_at=$(grep -n '^sudo rm -f .*omarchy-plymouth-shutdown\.service$' "$CALLS" | cut -d: -f1)
|
||||
reload_at=$(grep -n '^systemctl daemon-reload$' "$CALLS" | cut -d: -f1)
|
||||
[[ -n $disable_at && -n $remove_at && -n $reload_at ]] ||
|
||||
fail "migration disables, removes, then reloads the unit" "$(cat "$CALLS")"
|
||||
(( disable_at < remove_at && remove_at < reload_at )) ||
|
||||
fail "migration disables before removing and reloads last" "$(cat "$CALLS")"
|
||||
pass "migration disables the unit, removes it, then reloads systemd in that order"
|
||||
|
||||
# Homes are not all under /home.
|
||||
reset_machine
|
||||
write_plymouth_unit "$home_dir/.local/share/omarchy/bin/omarchy-plymouth-shutdown-sync"
|
||||
run_migration
|
||||
|
||||
[[ ! -e $plymouth_unit ]] ||
|
||||
fail "migration removes a shutdown unit rooted in a home outside /home"
|
||||
pass "migration removes a shutdown unit rooted in a home outside /home"
|
||||
|
||||
reset_machine
|
||||
write_plymouth_unit "/usr/bin/omarchy-plymouth-shutdown-sync"
|
||||
run_migration
|
||||
|
||||
[[ -e $plymouth_unit ]] ||
|
||||
fail "migration keeps a same-named unit that runs a packaged command"
|
||||
assert_changed_nothing "migration changes nothing for a packaged shutdown unit"
|
||||
pass "migration keeps a same-named unit that runs a packaged command"
|
||||
|
||||
# systemd takes ';' as a comment too, and an ExecStop behind one runs nothing.
|
||||
reset_machine
|
||||
cat >"$plymouth_unit" <<'EOF'
|
||||
[Service]
|
||||
Type=oneshot
|
||||
; ExecStop=/home/installer/.local/share/omarchy/bin/omarchy-plymouth-shutdown-sync
|
||||
ExecStop=/usr/bin/true
|
||||
EOF
|
||||
run_migration
|
||||
|
||||
[[ -e $plymouth_unit ]] || fail "migration keeps a unit whose home ExecStop is commented out"
|
||||
pass "migration keeps a unit whose home ExecStop is commented out"
|
||||
|
||||
reset_machine
|
||||
run_migration
|
||||
|
||||
assert_changed_nothing "migration changes nothing when no retired artifact is present"
|
||||
pass "migration leaves a machine without any retired artifact alone"
|
||||
|
||||
# All three at once, then the same run again: what a second account on the
|
||||
# machine does, and what running omarchy-migrate twice does.
|
||||
reset_machine
|
||||
printf '%s\n' "${first_run_variants[-1]}" >"$first_run"
|
||||
printf 'installer ALL=(ALL) NOPASSWD: /usr/bin/tsui\n' >"$tsui"
|
||||
write_plymouth_unit "/home/installer/.local/share/omarchy/bin/omarchy-plymouth-shutdown-sync"
|
||||
run_migration
|
||||
|
||||
[[ ! -e $first_run && ! -e $tsui && ! -e $plymouth_unit ]] ||
|
||||
fail "migration clears all three retired artifacts in one pass"
|
||||
pass "migration clears all three retired artifacts in one pass"
|
||||
|
||||
run_migration
|
||||
|
||||
assert_changed_nothing "migration changes nothing on a second run"
|
||||
pass "migration is a no-op on a second run"
|
||||
|
||||
# sudo does not treat every '#' as a comment. plugins/sudoers/toke.l matches
|
||||
# ^#include and ^#includedir as directives in the INITIAL state, and its comment
|
||||
# rule excludes '#' followed by a digit or -digit so those reach the ID token as a
|
||||
# numeric uid user spec -- sudoers(5) says the same. Dropping such a line as a
|
||||
# comment would let a file that still carries an active directive read as though
|
||||
# it held only generated lines, and be deleted.
|
||||
for directive in \
|
||||
'#include /etc/sudoers.local' \
|
||||
'#includedir /etc/sudoers.d.local' \
|
||||
'#1000 ALL=(ALL) NOPASSWD: ALL' \
|
||||
'#-1000 ALL=(ALL) NOPASSWD: ALL'; do
|
||||
reset_machine
|
||||
{
|
||||
printf '%s\n' "$directive"
|
||||
printf '%s\n' "${first_run_variants[-1]}"
|
||||
} >"$first_run"
|
||||
before=$(cat "$first_run")
|
||||
run_migration
|
||||
|
||||
[[ -e $first_run ]] ||
|
||||
fail "migration keeps a first-run file carrying the active directive $directive"
|
||||
[[ $(cat "$first_run") == "$before" ]] ||
|
||||
fail "migration leaves a first-run file with $directive byte for byte"
|
||||
pass "migration keeps a first-run file carrying the active directive $directive"
|
||||
|
||||
reset_machine
|
||||
{
|
||||
printf '%s\n' "$directive"
|
||||
printf 'installer ALL=(ALL) NOPASSWD: /usr/bin/tsui\n'
|
||||
} >"$tsui"
|
||||
before=$(cat "$tsui")
|
||||
run_migration
|
||||
|
||||
[[ -e $tsui ]] ||
|
||||
fail "migration keeps a tsui file carrying the active directive $directive"
|
||||
[[ $(cat "$tsui") == "$before" ]] ||
|
||||
fail "migration leaves a tsui file with $directive byte for byte"
|
||||
pass "migration keeps a tsui file carrying the active directive $directive"
|
||||
done
|
||||
|
||||
# A sudoers comment ending in a backslash does not swallow the line beneath it:
|
||||
# toke.l's comment rule consumes to the newline and clears the continuation flag.
|
||||
# Joining before testing for a comment would hide this administrator's grant.
|
||||
reset_machine
|
||||
cat >"$tsui" <<'EOF'
|
||||
# retired, kept for reference \
|
||||
ops ALL=(ALL) NOPASSWD: /usr/bin/tsui
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/tsui
|
||||
EOF
|
||||
before=$(cat "$tsui")
|
||||
run_migration
|
||||
|
||||
[[ -e $tsui ]] ||
|
||||
fail "migration keeps a tsui file whose second grant survives a commented continuation"
|
||||
[[ $(cat "$tsui") == "$before" ]] ||
|
||||
fail "migration leaves that tsui file byte for byte"
|
||||
pass "migration keeps a tsui file whose second grant survives a commented continuation"
|
||||
|
||||
# Both grants live under a root-only directory, so seeing them at all takes
|
||||
# elevation. Pin that the migration reads them elevated rather than silently
|
||||
# reading nothing.
|
||||
reset_machine
|
||||
printf '%s\n' "${first_run_variants[-1]}" >"$first_run"
|
||||
run_migration
|
||||
|
||||
assert_read_elevated "$first_run" "migration reads the first-run grant with elevated privileges"
|
||||
|
||||
reset_machine
|
||||
printf 'installer ALL=(ALL) NOPASSWD: /usr/bin/tsui\n' >"$tsui"
|
||||
run_migration
|
||||
|
||||
assert_read_elevated "$tsui" "migration reads the tsui grant with elevated privileges"
|
||||
|
||||
# sudo ends a logical line at a comment and keeps what came before it: visudo -cf
|
||||
# reads a spec ending in a backslash, then a comment, then a second spec as two
|
||||
# live specs. Dropping the pending half would hide this administrator's grant and
|
||||
# let the file read as though the installer had written all of it.
|
||||
reset_machine
|
||||
cat >"$first_run" <<'EOF'
|
||||
Cmnd_Alias FIRST_RUN_CLEANUP = /bin/rm -f /etc/sudoers.d/first-run
|
||||
operator ALL=(ALL) NOPASSWD: /usr/local/bin/deploy \
|
||||
# kept deliberately
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/systemctl
|
||||
installer ALL=(ALL) NOPASSWD: FIRST_RUN_CLEANUP
|
||||
EOF
|
||||
before=$(cat "$first_run")
|
||||
run_migration
|
||||
|
||||
[[ -e $first_run ]] ||
|
||||
fail "migration keeps a first-run file whose hand-written spec precedes a comment"
|
||||
[[ $(cat "$first_run") == "$before" ]] ||
|
||||
fail "migration leaves that first-run file byte for byte"
|
||||
pass "migration keeps a first-run file whose hand-written spec precedes a comment"
|
||||
|
||||
reset_machine
|
||||
cat >"$tsui" <<'EOF'
|
||||
operator ALL=(ALL) NOPASSWD: /usr/local/bin/deploy \
|
||||
# kept deliberately
|
||||
installer ALL=(ALL) NOPASSWD: /usr/bin/tsui
|
||||
EOF
|
||||
before=$(cat "$tsui")
|
||||
run_migration
|
||||
|
||||
[[ -e $tsui ]] ||
|
||||
fail "migration keeps a tsui file whose hand-written spec precedes a comment"
|
||||
[[ $(cat "$tsui") == "$before" ]] ||
|
||||
fail "migration leaves that tsui file byte for byte"
|
||||
pass "migration keeps a tsui file whose hand-written spec precedes a comment"
|
||||
|
||||
# systemd resumes a continuation across a comment: systemd-analyze verify on
|
||||
# "ExecStop=\" + "; c" + a path resolves that path. The unit is live and has to go.
|
||||
reset_machine
|
||||
cat >"$plymouth_unit" <<'EOF'
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/true
|
||||
ExecStop=\
|
||||
; still one directive
|
||||
/home/installer/.local/share/omarchy/bin/omarchy-plymouth-shutdown-sync
|
||||
EOF
|
||||
run_migration
|
||||
|
||||
[[ ! -e $plymouth_unit ]] ||
|
||||
fail "migration removes a unit whose ExecStop continues across a comment" "$(cat "$plymouth_unit")"
|
||||
pass "migration removes a unit whose ExecStop continues across a comment"
|
||||
Reference in New Issue
Block a user