Files
omarchycn/bin/omarchy-plymouth-set
T
David Heinemeier HanssonandClaude Opus 5 e47784be11 Name the dev-link authorization when it is the thing that failed
Validating /etc/omarchy.conf walks its parent chain to /, and that walk leaves
its own subject behind in the global failure_context. The comparison that
follows -- the one deciding whether the authorization actually names this
checkout -- sets no context of its own, so an unauthorized development tree
refused with:

  refusing to publish: directory / (must be root-owned and not group- or
  world-writable) failed validation

naming a directory that had just passed, and pointing the reader at a
filesystem problem that is not there. Seen on a worker VM running an
unauthorized checkout, where / is 0555 root-owned.

The hint line underneath was already right, which is what kept this from being
worse. Restore the context before the comparison so the first line agrees with
it, and assert both halves: that the refusal names the authorization, and that
it does not blame the root directory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115LngksSpXLD9NSXBEP3ki
2026-08-29 21:03:02 +02:00

339 lines
12 KiB
Bash
Executable File

#!/bin/bash
# omarchy:summary=Set the Plymouth boot theme colors and logo
# omarchy:args=<background-hex> <text-hex> <path-to-logo.png>
# omarchy:examples=omarchy plymouth set '#1d2021' '#ebdbb2' ~/.local/state/omarchy/current/theme/plymouth/logo.png
# omarchy:requires-sudo=true
set -euo pipefail
# Build the authoritative theme in a root-owned directory, then publish each
# fixed destination atomically. The caller opens the selected logo before sudo,
# so the privileged process never resolves a user-controlled input path.
refresh_default=false
if (( $# == 1 )) && [[ $1 == "--refresh-default" ]]; then
refresh_default=true
elif (( $# != 3 )); then
echo "Usage: omarchy-plymouth-set <background-hex> <text-hex> <path-to-logo.png>" >&2
exit 1
fi
logo_fd=
if $refresh_default; then
mode=refresh
bg_hex=
text_hex=
else
mode=set
bg_hex="${1#\#}"
text_hex="${2#\#}"
logo_path="$3"
if ! [[ $bg_hex =~ ^[0-9a-fA-F]{6}$ ]]; then
echo "Invalid background color: $1 (expected #RRGGBB)" >&2
exit 1
fi
if ! [[ $text_hex =~ ^[0-9a-fA-F]{6}$ ]]; then
echo "Invalid text color: $2 (expected #RRGGBB)" >&2
exit 1
fi
if [[ ! -f $logo_path ]]; then
echo "Logo file not found: $logo_path" >&2
exit 1
fi
if [[ -L $logo_path ]]; then
echo "Logo file is a symlink, which is not accepted: $logo_path" >&2
exit 1
fi
# Open the logo while still unprivileged. A replacement symlink to a root-only
# file therefore fails here instead of being followed after sudo starts.
if ! exec {logo_fd}<"$logo_path"; then
echo "Unable to open logo file as the current user: $logo_path" >&2
exit 1
fi
if [[ ! -f /proc/$$/fd/$logo_fd ]]; then
echo "Logo input is no longer a regular file: $logo_path" >&2
exit 1
fi
fi
run_root_transaction() {
sudo /bin/bash -c '
set -eEuo pipefail
PATH=/usr/bin:/bin
export PATH
# Every check below is a bare assertion that aborts under set -e. Name the
# subject of each one so a refusal reaches the user instead of exiting mute.
failure_context="the privileged Plymouth transaction"
failure_reported=
report_failure() {
[[ -z $failure_reported ]] || return 0
failure_reported=1
printf "omarchy-plymouth-set: refusing to publish: %s failed validation\n" "$failure_context" >&2
if [[ -n ${failure_hint:-} ]]; then
printf "omarchy-plymouth-set: %s\n" "$failure_hint" >&2
fi
}
trap report_failure ERR
mode=$1
source_root=$2
bg_hex=$3
text_hex=$4
max_asset_size=$5
failure_context="the arguments of the privileged transaction"
[[ $mode == "set" || $mode == "refresh" ]]
[[ $source_root == /* ]]
[[ $max_asset_size =~ ^[0-9]+$ ]]
(( max_asset_size > 0 ))
failure_context="the Omarchy source tree $source_root"
canonical_source_root=$(realpath -e -- "$source_root")
[[ $canonical_source_root == "$source_root" ]]
validate_trusted_directory() {
local directory=$1 canonical uid directory_mode
failure_context="directory $directory"
canonical=$(realpath -e -- "$directory")
[[ $canonical == "$directory" && -d $directory && ! -L $directory ]]
while :; do
failure_context="directory $directory (must be root-owned and not group- or world-writable)"
uid=$(stat -c %u -- "$directory")
directory_mode=$(stat -c %a -- "$directory")
(( uid == 0 ))
(( (8#$directory_mode & 0022) == 0 ))
[[ $directory == "/" ]] && break
directory=${directory%/*}
[[ -n $directory ]] || directory=/
done
}
validate_trusted_configuration_file() {
local configuration=$1 canonical uid configuration_mode size
failure_context="root configuration $configuration"
[[ -f $configuration && ! -L $configuration ]]
canonical=$(realpath -e -- "$configuration")
[[ $canonical == "$configuration" ]]
validate_trusted_directory "${configuration%/*}"
uid=$(stat -c %u -- "$configuration")
configuration_mode=$(stat -c %a -- "$configuration")
size=$(stat -c %s -- "$configuration")
(( uid == 0 ))
(( (8#$configuration_mode & 0022) == 0 ))
(( size > 0 && size <= 4096 ))
}
# A packaged tree must be root-owned. A development checkout is the one
# deliberate exception: omarchy dev link records its canonical path in a
# root-owned /etc/omarchy.conf. That is already an explicit decision to run
# privileged Omarchy commands from user-editable code in the checkout, so
# reading its packaged assets does not widen the development trust boundary.
development_source=false
source_root_uid=$(stat -c %u -- "$source_root")
if (( source_root_uid != 0 )); then
omarchy_conf=/etc/omarchy.conf
failure_context="$source_root is user-owned and $omarchy_conf must contain its trusted dev-link authorization; run omarchy dev link to authorize it"
failure_hint="$source_root is user-owned; run omarchy dev link to authorize this development checkout, or omarchy dev unlink to use the packaged tree"
validate_trusted_configuration_file "$omarchy_conf"
# validate_trusted_configuration_file walks /etc up to / and leaves its own
# subject behind in failure_context. Without restoring ours, a checkout
# that simply is not the authorized one refuses with "directory / must be
# root-owned and not group- or world-writable" -- naming a directory that
# passed, and sending the reader after a filesystem problem that is not
# there.
failure_context="the dev-link authorization in $omarchy_conf, which must name $source_root"
quoted_source_root=$source_root
quoted_source_root=${quoted_source_root//\\/\\\\}
quoted_source_root=${quoted_source_root//\"/\\\"}
quoted_source_root=${quoted_source_root//\$/\\\$}
quoted_source_root=${quoted_source_root//\`/\\\`}
expected_config_line="export OMARCHY_PATH=\"$quoted_source_root\""
mapfile -t omarchy_config_lines <"$omarchy_conf"
(( ${#omarchy_config_lines[@]} == 1 ))
[[ ${omarchy_config_lines[0]} == "$expected_config_line" ]]
development_source=true
failure_hint=
fi
if [[ $mode == "set" ]]; then
[[ $bg_hex =~ ^[0-9a-fA-F]{6}$ ]]
[[ $text_hex =~ ^[0-9a-fA-F]{6}$ ]]
fi
theme_dir=/usr/share/plymouth/themes/omarchy
sddm_dir=/usr/share/sddm/themes/omarchy
plymouth_theme_assets=(
bullet.png
entry.png
lock.png
logo.png
omarchy.plymouth
omarchy.script
preview-unlock.png
progress_bar.png
progress_box.png
)
plymouth_default_assets=("${plymouth_theme_assets[@]}" logos/oma.png)
sddm_theme_assets=(Main.qml bullet.png entry-failed.png entry.png lock-failed.png lock.png logo.png)
validate_trusted_file() {
local source=$1 canonical uid file_mode size
failure_context="packaged source file $source"
[[ -f $source && ! -L $source ]]
canonical=$(realpath -e -- "$source")
[[ $canonical == "$source" ]]
file_mode=$(stat -c %a -- "$source")
size=$(stat -c %s -- "$source")
(( size > 0 && size <= max_asset_size ))
if ! $development_source; then
validate_trusted_directory "${source%/*}"
uid=$(stat -c %u -- "$source")
(( uid == 0 ))
(( (8#$file_mode & 0022) == 0 ))
fi
}
copy_trusted_file() {
local source=$1 destination=$2
validate_trusted_file "$source"
cp --reflink=never -- "$source" "$destination"
chown 0:0 -- "$destination"
chmod 0600 -- "$destination"
}
staging_dir=$(mktemp -d /tmp/omarchy-plymouth.XXXXXXXX)
temporary=
cleanup() {
[[ -z $temporary ]] || rm -f -- "$temporary"
rm -rf -- "$staging_dir"
}
trap cleanup EXIT HUP INT TERM
chown 0:0 -- "$staging_dir"
chmod 0700 -- "$staging_dir"
plymouth_stage=$staging_dir/plymouth
sddm_stage=$staging_dir/sddm
mkdir -m 0700 -p -- "$plymouth_stage/logos" "$sddm_stage"
if [[ $mode == "refresh" ]]; then
assets_to_stage=("${plymouth_default_assets[@]}")
else
assets_to_stage=("${plymouth_theme_assets[@]}")
fi
for asset in "${assets_to_stage[@]}"; do
copy_trusted_file "$source_root/default/plymouth/$asset" "$plymouth_stage/$asset"
done
if [[ $mode == "set" ]]; then
# stdin was opened by the unprivileged caller. Read no more than the
# documented limit into the root-owned stage before doing other work.
failure_context="the selected logo (expected 1 to $max_asset_size bytes)"
head -c "$((max_asset_size + 1))" >"$plymouth_stage/logo.png"
logo_size=$(stat -c %s -- "$plymouth_stage/logo.png")
(( logo_size > 0 && logo_size <= max_asset_size ))
chown 0:0 -- "$plymouth_stage/logo.png"
chmod 0600 -- "$plymouth_stage/logo.png"
cp --reflink=never -- "$plymouth_stage/logo.png" "$sddm_stage/logo.png"
bg_r=$(awk -v n=$((16#${bg_hex:0:2})) "BEGIN{printf \"%.3f\", n/255}")
bg_g=$(awk -v n=$((16#${bg_hex:2:2})) "BEGIN{printf \"%.3f\", n/255}")
bg_b=$(awk -v n=$((16#${bg_hex:4:2})) "BEGIN{printf \"%.3f\", n/255}")
sed -i \
-e "s/^Window.SetBackgroundTopColor.*/Window.SetBackgroundTopColor($bg_r, $bg_g, $bg_b);/" \
-e "s/^Window.SetBackgroundBottomColor.*/Window.SetBackgroundBottomColor($bg_r, $bg_g, $bg_b);/" \
"$plymouth_stage/omarchy.script"
for asset in bullet.png entry.png lock.png progress_bar.png; do
magick "$plymouth_stage/$asset" -channel RGB +level-colors "#$text_hex","#$text_hex" "$plymouth_stage/$asset"
done
copy_trusted_file "$source_root/default/sddm/omarchy/Main.qml" "$sddm_stage/Main.qml"
sed -i \
-e "s/#1a1b26/#__OMARCHY_SDDM_BG__/g" \
-e "s/#ffffff/#__OMARCHY_SDDM_TEXT__/g" \
-e "s/#__OMARCHY_SDDM_BG__/#$bg_hex/g" \
-e "s/#__OMARCHY_SDDM_TEXT__/#$text_hex/g" \
"$sddm_stage/Main.qml"
for asset in bullet.png entry.png lock.png; do
cp --reflink=never -- "$plymouth_stage/$asset" "$sddm_stage/$asset"
done
for asset in entry lock; do
magick "$plymouth_stage/$asset.png" -channel RGB +level-colors "#f7768e","#f7768e" "$sddm_stage/$asset-failed.png"
done
chown -R 0:0 -- "$staging_dir"
find "$staging_dir" -type f -exec chmod 0600 -- {} +
fi
publish_asset() {
local source=$1 destination=$2 parent filename source_size copied_size
failure_context="destination $destination"
[[ -f $source && ! -L $source ]]
(( $(stat -c %u -- "$source") == 0 ))
source_size=$(stat -c %s -- "$source")
(( source_size > 0 && source_size <= max_asset_size ))
[[ $destination == /* && $destination != */ && $destination != *"/../"* ]]
parent=${destination%/*}
filename=${destination##*/}
[[ -n $parent && -n $filename && $filename != "." && $filename != ".." ]]
validate_trusted_directory "$parent"
temporary=$(mktemp --tmpdir="$parent" ".$filename.omarchy-new.XXXXXXXX")
cp --reflink=never -- "$source" "$temporary"
copied_size=$(stat -c %s -- "$temporary")
(( copied_size == source_size ))
cmp -s -- "$source" "$temporary"
chown 0:0 -- "$temporary"
chmod 0644 -- "$temporary"
sync -f -- "$temporary"
mv --no-copy -fT -- "$temporary" "$destination"
temporary=
}
for asset in "${assets_to_stage[@]}"; do
publish_asset "$plymouth_stage/$asset" "$theme_dir/$asset"
done
if [[ $mode == "set" ]]; then
for asset in "${sddm_theme_assets[@]}"; do
publish_asset "$sddm_stage/$asset" "$sddm_dir/$asset"
done
validate_trusted_directory "$sddm_dir"
rm -f -- "$sddm_dir/logo.svg"
fi
' bash "$mode" "$OMARCHY_PATH" "$bg_hex" "$text_hex" "$((64 * 1024 * 1024))"
}
if $refresh_default; then
run_root_transaction </dev/null
else
run_root_transaction <&"$logo_fd"
fi
sudo plymouth-set-default-theme omarchy
if omarchy-cmd-present limine-mkinitcpio; then
sudo limine-mkinitcpio
else
sudo mkinitcpio -P
fi