Merge quattro into hermes-agent
Catches the branch up on 94 commits so what lands here is reviewed against current quattro, and so #8611 contributes its own five files rather than dragging a partial catch-up in behind it.
This commit is contained in:
@@ -40,6 +40,7 @@ GROUP_DESCRIPTIONS[channel]="Omarchy release channel management"
|
||||
GROUP_DESCRIPTIONS[clipboard]="Clipboard helpers"
|
||||
GROUP_DESCRIPTIONS[cmd]="Command and shortcut helpers"
|
||||
GROUP_DESCRIPTIONS[config]="System configuration helpers"
|
||||
GROUP_DESCRIPTIONS[crash]="Crash notification controls"
|
||||
GROUP_DESCRIPTIONS[debug]="Diagnostics and support logs"
|
||||
GROUP_DESCRIPTIONS[finalize]="Finalize user setup"
|
||||
GROUP_DESCRIPTIONS[default]="Default application selection"
|
||||
|
||||
+3
-1
@@ -99,8 +99,10 @@ omp)
|
||||
;;
|
||||
ori)
|
||||
# Ori is a harness launcher, and `ori code` is the agent it runs itself.
|
||||
# A prompt alone means one headless turn there, printed after the turn ends,
|
||||
# so --interactive is what seeds the session with it and keeps the window.
|
||||
command=(ori code)
|
||||
[[ -n ${prompt:-} ]] && command+=(--prompt "$prompt")
|
||||
[[ -n ${prompt:-} ]] && command+=(--interactive --prompt "$prompt")
|
||||
;;
|
||||
pi)
|
||||
command=(pi)
|
||||
|
||||
@@ -4,7 +4,13 @@
|
||||
# omarchy:args=[--no-osd] [+N%|N%-|N%]
|
||||
# omarchy:examples=omarchy brightness display apple | omarchy brightness display apple +5% | omarchy brightness display apple --no-osd 50%
|
||||
|
||||
device_cache="${XDG_RUNTIME_DIR:-/tmp}/omarchy-brightness-display-apple.device"
|
||||
# Only cache under the user-private runtime dir. With no XDG_RUNTIME_DIR we skip
|
||||
# caching (detect every run) rather than fall back to a predictable, world-writable
|
||||
# /tmp path another user could pre-create.
|
||||
device_cache=""
|
||||
if [[ -n ${XDG_RUNTIME_DIR:-} ]]; then
|
||||
device_cache="$XDG_RUNTIME_DIR/omarchy-brightness-display-apple.device"
|
||||
fi
|
||||
no_osd=0
|
||||
if [[ ${1:-} == "--no-osd" ]]; then
|
||||
no_osd=1
|
||||
@@ -28,9 +34,14 @@ find_apple_display_device() {
|
||||
local cached=""
|
||||
local device=""
|
||||
|
||||
if [[ -r $device_cache ]]; then
|
||||
if [[ -n $device_cache && -r $device_cache ]]; then
|
||||
read -r cached <"$device_cache" || true
|
||||
if [[ -n $cached && -e $cached ]]; then
|
||||
# Trust a cached value only if it still names a hiddev character device. A
|
||||
# stale or unexpected cache (a regular file, a non-hiddev node) is ignored and
|
||||
# we re-detect instead of handing an arbitrary path to asdcontrol. The globs
|
||||
# are left unquoted on purpose: [[ ]] pattern-matches an unquoted right side,
|
||||
# and quoting them would turn the match into a literal string comparison.
|
||||
if [[ ( $cached == /dev/hiddev* || $cached == /dev/usb/hiddev* ) && -c $cached ]]; then
|
||||
printf '%s\n' "$cached"
|
||||
return 0
|
||||
fi
|
||||
@@ -39,7 +50,9 @@ find_apple_display_device() {
|
||||
device="$(detect_apple_display_device)" || return 1
|
||||
[[ -n $device ]] || return 1
|
||||
|
||||
printf '%s\n' "$device" >"$device_cache"
|
||||
if [[ -n $device_cache ]]; then
|
||||
printf '%s\n' "$device" >"$device_cache"
|
||||
fi
|
||||
printf '%s\n' "$device"
|
||||
}
|
||||
|
||||
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Silence crash notifications for one program, or list what is silenced
|
||||
# omarchy:args=[--] [<program>] [on|off|toggle]
|
||||
# omarchy:examples=omarchy crash mute | omarchy crash mute hyprland | omarchy crash mute /usr/bin/hyprland | omarchy crash mute hyprland off
|
||||
|
||||
# The flag omarchy-crash-watch reads before announcing a crash. Muting is per
|
||||
# program; Trigger > Toggle > Crash Capture is the switch for all of them.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
readonly MUTES="$HOME/.local/state/omarchy/toggles/crash-ignore"
|
||||
|
||||
usage() {
|
||||
echo "Usage: omarchy crash mute [--] [<program>] [on|off|toggle]" >&2
|
||||
}
|
||||
|
||||
# Only regular files, because that is all the watcher honours: anything else in
|
||||
# there would be reported as muted while the crashes kept arriving. The dotted
|
||||
# glob is for a program legitimately called .hidden, and `.` and `..` fail the
|
||||
# same -f test that keeps them out.
|
||||
list() {
|
||||
local entry found=0
|
||||
|
||||
for entry in "$MUTES"/* "$MUTES"/.*; do
|
||||
[[ -f $entry ]] || continue
|
||||
printf '%s\n' "${entry##*/}"
|
||||
found=1
|
||||
done
|
||||
|
||||
((found)) || echo "No programs muted. Crashes all notify."
|
||||
}
|
||||
|
||||
# A program may be named -h, and the router answers that with its own help
|
||||
# before this ever runs. `omarchy crash mute -- -h` is the way through.
|
||||
[[ ${1:-} == "--" ]] && shift
|
||||
|
||||
if (($# == 0)); then
|
||||
list
|
||||
exit 0
|
||||
fi
|
||||
|
||||
program=$1
|
||||
action=${2:-on}
|
||||
|
||||
# The watcher keys the mute on the executable's basename, so accept the path it
|
||||
# reports as readily as the name, and reduce either the same way it does.
|
||||
program=${program##*/}
|
||||
|
||||
if [[ -z $program || $program == "." || $program == ".." ]]; then
|
||||
echo "Not a program name: $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$action" in
|
||||
on|off|toggle) ;;
|
||||
*)
|
||||
echo "Not an action: $action" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
omarchy-toggle "crash-ignore/$program" "$action" || exit 1
|
||||
|
||||
# Report what is now true rather than what was asked for: the flag is what the
|
||||
# watcher reads, and a toggle does not say which way it went.
|
||||
if omarchy-toggle-enabled "crash-ignore/$program"; then
|
||||
echo "Muted crash notifications for $program."
|
||||
else
|
||||
echo "Crash notifications for $program are back on."
|
||||
fi
|
||||
+28
-5
@@ -48,12 +48,17 @@ announce() {
|
||||
# -n 0 so a restart does not re-announce crashes already dealt with.
|
||||
journalctl -f -n 0 -o json "MESSAGE_ID=$COREDUMP_MESSAGE_ID" 2>/dev/null |
|
||||
while IFS= read -r entry; do
|
||||
# A dash for a field that is empty as well as one that is missing: tab is
|
||||
# IFS whitespace, so an empty field collapses into the next delimiter and
|
||||
# every field after it shifts along one. A process can set its own comm to
|
||||
# nothing, and that crash used to be read as somebody else's and dropped.
|
||||
IFS=$'\t' read -r uid comm pid exe signal < <(
|
||||
jq -r '[(._UID // "-"),
|
||||
(.COREDUMP_COMM // "-"),
|
||||
(.COREDUMP_PID // "-"),
|
||||
(.COREDUMP_EXE // "-"),
|
||||
(.COREDUMP_SIGNAL_NAME // "-")] | @tsv' <<<"$entry" 2>/dev/null
|
||||
jq -r 'def field: if . == null or . == "" then "-" else . end;
|
||||
[(._UID | field),
|
||||
(.COREDUMP_COMM | field),
|
||||
(.COREDUMP_PID | field),
|
||||
(.COREDUMP_EXE | field),
|
||||
(.COREDUMP_SIGNAL_NAME | field)] | @tsv' <<<"$entry" 2>/dev/null
|
||||
)
|
||||
|
||||
[[ $pid =~ ^[0-9]+$ ]] || continue
|
||||
@@ -71,11 +76,29 @@ journalctl -f -n 0 -o json "MESSAGE_ID=$COREDUMP_MESSAGE_ID" 2>/dev/null |
|
||||
name=$comm
|
||||
[[ $exe == /* ]] && name=${exe##*/}
|
||||
|
||||
# A process can set its own comm to anything prctl takes, slashes included,
|
||||
# and a crash with no recorded executable falls back to it. The mute below
|
||||
# turns this name into a path, so keep it one component: a crash must not
|
||||
# reach a flag outside crash-ignore/, nor have a diagnosis write one there.
|
||||
name=${name##*/}
|
||||
|
||||
# What that leaves is not always a name. "/" leaves nothing, which is no
|
||||
# kind of array subscript and no kind of toast; a dot component names a
|
||||
# directory rather than a flag, so a mute on it would touch that directory
|
||||
# and then never match; and a dash is what the read above puts there when
|
||||
# the crash recorded no name at all.
|
||||
[[ -n $name && $name != "-" && $name != "." && $name != ".." ]] || name=unknown
|
||||
|
||||
[[ -n $ignore_pattern && $name =~ $ignore_pattern ]] && continue
|
||||
|
||||
# Never announce our own machinery, or it notifies about itself.
|
||||
[[ $name == omarchy-crash-* || $name == omarchy-agent-* ]] && continue
|
||||
|
||||
# Muted at the end of a diagnosis, when the user was offered it and said
|
||||
# yes. A flag per program rather than one list, so omarchy-crash-mute can
|
||||
# lift one without reading, rewriting and re-parsing the rest.
|
||||
omarchy-toggle-enabled "crash-ignore/$name" && continue
|
||||
|
||||
now=$EPOCHSECONDS
|
||||
(((now - ${last_notified[$name]:-0}) < dedupe_seconds)) && continue
|
||||
|
||||
|
||||
@@ -34,8 +34,12 @@ systemd, shell, or app-launcher environment; reboot to make every layer agree.
|
||||
Affects only \$OMARCHY_PATH-resolved trees: bin/, default/, shell/,
|
||||
themes/, applications/, config/. Files installed at fixed system paths
|
||||
(/etc/, /usr/lib/systemd/, udev rule bodies, /etc/skel after user
|
||||
creation, /usr/share/plymouth) are NOT covered — for those, use
|
||||
omarchy-dev-pkg-test to build and install the package from the checkout.
|
||||
creation) are NOT covered — for those, use omarchy-dev-pkg-test to build
|
||||
and install the package from the checkout.
|
||||
|
||||
The Plymouth and SDDM themes under /usr/share are the exception: omarchy
|
||||
plymouth set and omarchy refresh plymouth republish them from the checkout,
|
||||
reading this link's authorization out of the root-owned /etc/omarchy.conf.
|
||||
|
||||
Also writes $sudoers_file so sudo resolves omarchy-*
|
||||
from the checkout instead of the packaged copies. That part takes effect
|
||||
|
||||
+11
-17
@@ -6,9 +6,10 @@
|
||||
|
||||
set -e
|
||||
|
||||
setup_policy_directory() {
|
||||
sudo mkdir -p "$1"
|
||||
sudo chmod a+rw "$1"
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
|
||||
setup_chromium_policy_directory() {
|
||||
browser_policy_setup_dir "$1"
|
||||
}
|
||||
|
||||
announce_browser_installed() {
|
||||
@@ -23,13 +24,6 @@ copy_chromium_flags() {
|
||||
omarchy-install-chromium-ytdlp
|
||||
}
|
||||
|
||||
setup_firefox_preferences() {
|
||||
local distribution_dir="$1"
|
||||
|
||||
setup_policy_directory "$distribution_dir"
|
||||
sudo cp -f "$OMARCHY_PATH/default/firefox/policies.json" "$distribution_dir/policies.json"
|
||||
}
|
||||
|
||||
setup_firefox_wayland() {
|
||||
mkdir -p ~/.config/environment.d
|
||||
echo "MOZ_ENABLE_WAYLAND=1" > ~/.config/environment.d/omarchy-firefox-wayland.conf
|
||||
@@ -40,7 +34,7 @@ chromium)
|
||||
echo "Installing Chromium..."
|
||||
omarchy-pkg-add chromium
|
||||
|
||||
setup_policy_directory /etc/chromium/policies/managed
|
||||
setup_chromium_policy_directory /etc/chromium/policies/managed
|
||||
copy_chromium_flags ~/.config/chromium-flags.conf
|
||||
omarchy-theme-set-browser
|
||||
announce_browser_installed "Chromium"
|
||||
@@ -49,7 +43,7 @@ chrome)
|
||||
echo "Installing Chrome..."
|
||||
omarchy-pkg-aur-add google-chrome || exit 1
|
||||
|
||||
setup_policy_directory /etc/opt/chrome/policies/managed
|
||||
setup_chromium_policy_directory /etc/opt/chrome/policies/managed
|
||||
copy_chromium_flags ~/.config/chrome-flags.conf
|
||||
omarchy-theme-set-browser
|
||||
announce_browser_installed "Chrome"
|
||||
@@ -58,7 +52,7 @@ edge)
|
||||
echo "Installing Edge..."
|
||||
omarchy-pkg-aur-add microsoft-edge-stable-bin || exit 1
|
||||
|
||||
setup_policy_directory /etc/opt/edge/policies/managed
|
||||
setup_chromium_policy_directory /etc/opt/edge/policies/managed
|
||||
copy_chromium_flags ~/.config/microsoft-edge-stable-flags.conf
|
||||
omarchy-theme-set-browser
|
||||
announce_browser_installed "Edge"
|
||||
@@ -67,7 +61,7 @@ brave)
|
||||
echo "Installing Brave..."
|
||||
omarchy-pkg-aur-add brave-bin || exit 1
|
||||
|
||||
setup_policy_directory /etc/brave/policies/managed
|
||||
setup_chromium_policy_directory /etc/brave/policies/managed
|
||||
copy_chromium_flags ~/.config/brave-flags.conf
|
||||
omarchy-theme-set-browser
|
||||
announce_browser_installed "Brave"
|
||||
@@ -76,7 +70,7 @@ brave-origin)
|
||||
echo "Installing Brave Origin..."
|
||||
omarchy-pkg-aur-add brave-origin-bin || exit 1
|
||||
|
||||
setup_policy_directory /etc/brave/policies/managed
|
||||
setup_chromium_policy_directory /etc/brave/policies/managed
|
||||
copy_chromium_flags ~/.config/brave-origin-flags.conf
|
||||
omarchy-theme-set-browser
|
||||
announce_browser_installed "Brave Origin"
|
||||
@@ -85,7 +79,7 @@ firefox)
|
||||
echo "Installing Firefox..."
|
||||
omarchy-pkg-add firefox || exit 1
|
||||
|
||||
setup_firefox_preferences /usr/lib/firefox/distribution
|
||||
browser_policy_setup_firefox_distribution /usr/lib/firefox/distribution
|
||||
setup_firefox_wayland
|
||||
announce_browser_installed "Firefox"
|
||||
;;
|
||||
@@ -93,7 +87,7 @@ zen)
|
||||
echo "Installing Zen..."
|
||||
omarchy-pkg-aur-add zen-browser-bin || exit 1
|
||||
|
||||
setup_firefox_preferences /opt/zen-browser/distribution
|
||||
browser_policy_setup_firefox_distribution /opt/zen-browser/distribution
|
||||
setup_firefox_wayland
|
||||
announce_browser_installed "Zen"
|
||||
;;
|
||||
|
||||
@@ -10,4 +10,4 @@ echo "Enabling ONCE background service..."
|
||||
sudo systemctl enable --now once-background.service
|
||||
|
||||
echo -e "\nLaunching ONCE..."
|
||||
once
|
||||
sudo once
|
||||
|
||||
@@ -3,5 +3,7 @@
|
||||
# omarchy:summary=Restore the default Omarchy Plymouth boot theme and SDDM login screen
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
omarchy-refresh-plymouth
|
||||
omarchy-refresh-sddm
|
||||
set -euo pipefail
|
||||
|
||||
"$OMARCHY_PATH/bin/omarchy-refresh-plymouth"
|
||||
"$OMARCHY_PATH/bin/omarchy-refresh-sddm"
|
||||
|
||||
+365
-76
@@ -5,88 +5,377 @@
|
||||
# omarchy:examples=omarchy plymouth set '#1d2021' '#ebdbb2' ~/.local/state/omarchy/current/theme/plymouth/logo.png
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
# Configure the Plymouth boot theme with a custom background color, text color, and logo.
|
||||
# Stages the change in a temp dir, then commits the staged files to /usr/share and
|
||||
# rebuilds the initramfs. Also syncs the SDDM login screen (the post-logout
|
||||
# screen) with the same colors and logo so boot/login stay visually unified.
|
||||
set -euo pipefail
|
||||
|
||||
if (( $# != 3 )); then
|
||||
# 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.
|
||||
|
||||
usage() {
|
||||
echo "Usage: omarchy-plymouth-set <background-hex> <text-hex> <path-to-logo.png>" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
# omarchy-plymouth-set-by-theme passes a theme's unlock.png straight from
|
||||
# ~/.config/omarchy/themes, where an installed theme can make it a symlink to
|
||||
# anything. The copies below land in world-readable /usr/share, so following one
|
||||
# would republish whatever it points at.
|
||||
if [[ -L $logo_path ]]; then
|
||||
echo "Logo file is a symlink, which is not accepted: $logo_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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}')
|
||||
|
||||
theme_dir="/usr/share/plymouth/themes/omarchy"
|
||||
staging_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$staging_dir"' EXIT
|
||||
|
||||
find "$OMARCHY_PATH/default/plymouth" -maxdepth 1 -type f -exec cp -t "$staging_dir/" {} +
|
||||
cp "$logo_path" "$staging_dir/logo.png"
|
||||
|
||||
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);/" \
|
||||
"$staging_dir/omarchy.script"
|
||||
|
||||
for asset in bullet.png entry.png lock.png progress_bar.png; do
|
||||
magick "$staging_dir/$asset" -channel RGB +level-colors "#$text_hex","#$text_hex" "$staging_dir/$asset"
|
||||
done
|
||||
|
||||
sudo cp -a --no-preserve=mode,ownership "$staging_dir/." "$theme_dir/"
|
||||
sudo plymouth-set-default-theme omarchy
|
||||
|
||||
if omarchy-cmd-present limine-mkinitcpio; then
|
||||
sudo limine-mkinitcpio
|
||||
if (( $# == 3 )); then
|
||||
mode=set
|
||||
elif (( $# == 1 )); then
|
||||
case "$1" in
|
||||
--refresh-default)
|
||||
mode=refresh-plymouth
|
||||
;;
|
||||
--refresh-sddm-default)
|
||||
mode=refresh-sddm
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
else
|
||||
sudo mkinitcpio -P
|
||||
usage
|
||||
fi
|
||||
|
||||
# Sync the SDDM login screen with the same colors and logo.
|
||||
sddm_dir="/usr/share/sddm/themes/omarchy"
|
||||
sddm_template="$OMARCHY_PATH/default/sddm/omarchy/Main.qml"
|
||||
if (( EUID == 0 )); then
|
||||
echo "Error: run omarchy-plymouth-set as your user, not under sudo." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sed \
|
||||
-e "s/#1a1b26/#$bg_hex/g" \
|
||||
-e "s/#ffffff/#$text_hex/g" \
|
||||
"$sddm_template" | sudo tee "$sddm_dir/Main.qml" >/dev/null
|
||||
logo_fd=
|
||||
if [[ $mode != "set" ]]; then
|
||||
bg_hex=
|
||||
text_hex=
|
||||
else
|
||||
bg_hex="${1#\#}"
|
||||
text_hex="${2#\#}"
|
||||
logo_path="$3"
|
||||
|
||||
sudo cp "$staging_dir/logo.png" "$sddm_dir/logo.png"
|
||||
for asset in bullet.png entry.png lock.png; do
|
||||
sudo cp "$staging_dir/$asset" "$sddm_dir/$asset"
|
||||
done
|
||||
for asset in entry lock; do
|
||||
magick "$staging_dir/$asset.png" -channel RGB +level-colors "#f7768e","#f7768e" "$staging_dir/$asset-failed.png"
|
||||
sudo cp "$staging_dir/$asset-failed.png" "$sddm_dir/$asset-failed.png"
|
||||
done
|
||||
sudo rm -f "$sddm_dir/logo.svg"
|
||||
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
|
||||
umask 077
|
||||
|
||||
# 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-plymouth" || $mode == "refresh-sddm" ]]
|
||||
[[ $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)
|
||||
sddm_default_assets=("${sddm_theme_assets[@]}" metadata.desktop theme.conf)
|
||||
|
||||
plymouth_assets=()
|
||||
sddm_assets=()
|
||||
case "$mode" in
|
||||
set)
|
||||
plymouth_assets=("${plymouth_theme_assets[@]}")
|
||||
sddm_assets=("${sddm_theme_assets[@]}")
|
||||
;;
|
||||
refresh-plymouth)
|
||||
plymouth_assets=("${plymouth_default_assets[@]}")
|
||||
;;
|
||||
refresh-sddm)
|
||||
sddm_assets=("${sddm_default_assets[@]}")
|
||||
;;
|
||||
esac
|
||||
|
||||
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"
|
||||
install -o 0 -g 0 -m 0600 -- "$source" "$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"
|
||||
|
||||
for asset in "${plymouth_assets[@]}"; 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 -- {} +
|
||||
elif (( ${#sddm_assets[@]} )); then
|
||||
for asset in "${sddm_assets[@]}"; do
|
||||
copy_trusted_file "$source_root/default/sddm/omarchy/$asset" "$sddm_stage/$asset"
|
||||
done
|
||||
fi
|
||||
|
||||
if (( ${#plymouth_assets[@]} )); then
|
||||
validate_trusted_directory "$theme_dir"
|
||||
if [[ $mode == "refresh-plymouth" ]]; then
|
||||
validate_trusted_directory "$theme_dir/logos"
|
||||
fi
|
||||
fi
|
||||
if (( ${#sddm_assets[@]} )); then
|
||||
validate_trusted_directory "$sddm_dir"
|
||||
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")
|
||||
install -o 0 -g 0 -m 0644 -- "$source" "$temporary"
|
||||
copied_size=$(stat -c %s -- "$temporary")
|
||||
(( copied_size == source_size ))
|
||||
cmp -s -- "$source" "$temporary"
|
||||
sync -f -- "$temporary"
|
||||
mv --no-copy -fT -- "$temporary" "$destination"
|
||||
temporary=
|
||||
}
|
||||
|
||||
if (( ${#plymouth_assets[@]} )); then
|
||||
for asset in "${plymouth_assets[@]}"; do
|
||||
publish_asset "$plymouth_stage/$asset" "$theme_dir/$asset"
|
||||
done
|
||||
fi
|
||||
|
||||
if (( ${#sddm_assets[@]} )); then
|
||||
for asset in "${sddm_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 [[ $mode == "set" ]]; then
|
||||
run_root_transaction <&"$logo_fd"
|
||||
else
|
||||
run_root_transaction </dev/null
|
||||
fi
|
||||
|
||||
if [[ $mode != "refresh-sddm" ]]; then
|
||||
sudo plymouth-set-default-theme omarchy
|
||||
|
||||
if omarchy-cmd-present limine-mkinitcpio; then
|
||||
sudo limine-mkinitcpio
|
||||
else
|
||||
sudo mkinitcpio -P
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -742,6 +742,12 @@ create_user() {
|
||||
# for specific commands), and a duplicate grant is harmless.
|
||||
echo "%wheel ALL=(ALL:ALL) ALL" >/etc/sudoers.d/00-omarchy-wheel
|
||||
chmod 440 /etc/sudoers.d/00-omarchy-wheel
|
||||
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
for dir in "${BROWSER_POLICY_MANAGED_DIRS[@]}"; do
|
||||
[[ -d $dir || -L $dir ]] || continue
|
||||
browser_policy_setup_dir "$dir"
|
||||
done
|
||||
}
|
||||
|
||||
install_authorized_keys() {
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
# omarchy:summary=Overwrite the user config for the Plymouth drive decryption and boot sequence with the Omarchy default and rebuild it.
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
sudo cp -r "$OMARCHY_PATH/default/plymouth/." /usr/share/plymouth/themes/omarchy/
|
||||
sudo plymouth-set-default-theme omarchy
|
||||
|
||||
if omarchy-cmd-present limine-mkinitcpio; then
|
||||
sudo limine-mkinitcpio
|
||||
else
|
||||
sudo mkinitcpio -P
|
||||
fi
|
||||
# Reuse the fixed-file publisher so root never resolves the source checkout or
|
||||
# follows a destination symlink while restoring the packaged assets.
|
||||
exec "$OMARCHY_PATH/bin/omarchy-plymouth-set" --refresh-default
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
# omarchy:summary=Refresh the SDDM theme from default
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
sudo rm -rf /usr/share/sddm/themes/omarchy
|
||||
sudo cp -r "$OMARCHY_PATH/default/sddm/omarchy" /usr/share/sddm/themes/omarchy
|
||||
# Reuse the fixed-file publisher so root never resolves an untrusted source or
|
||||
# follows a destination symlink while restoring the packaged SDDM theme.
|
||||
exec "$OMARCHY_PATH/bin/omarchy-plymouth-set" --refresh-sddm-default
|
||||
|
||||
@@ -23,16 +23,26 @@ omarchy-git-url-check "$REPO_URL" || exit 1
|
||||
|
||||
THEMES_DIR="$HOME/.config/omarchy/themes"
|
||||
|
||||
# Strip user@host: prefix from scp-style SSH URLs so basename sees just the path
|
||||
# Strip user@host: prefix from scp-style SSH URLs so basename sees just the path.
|
||||
# git reads a URL as scp-style when a colon appears before any slash, so the path
|
||||
# after it need not hold one: `git@host:omarchy-blue-theme.git` is a repo in that
|
||||
# user's home, and leaving its prefix on names the theme after the whole URL.
|
||||
REPO_PATH="$REPO_URL"
|
||||
[[ $REPO_PATH != *"://"* && $REPO_PATH == *:*/* ]] && REPO_PATH="${REPO_PATH#*:}"
|
||||
[[ $REPO_PATH != *"://"* && $REPO_PATH == *:* && ${REPO_PATH%%:*} != */* ]] && REPO_PATH="${REPO_PATH#*:}"
|
||||
THEME_NAME=$(basename -- "$REPO_PATH" .git | sed -E 's/^omarchy-//; s/-theme$//' | tr '[:upper:]' '[:lower:]')
|
||||
THEME_PATH="$THEMES_DIR/$THEME_NAME"
|
||||
|
||||
# The name comes from the URL and is joined into a path that is about to be
|
||||
# removed, so a repo called `..` would take ~/.config/omarchy with it. A leading
|
||||
# dot is refused with it: `host:-s/foo.git` leaves basename with `.git`.
|
||||
if [[ -z $THEME_NAME || $THEME_NAME == .* || $THEME_NAME == */* ]]; then
|
||||
# The name comes from the URL, is joined into a path that is about to be
|
||||
# removed, and then names a directory the rest of Omarchy passes around by
|
||||
# name: Style > Unlock builds a command line out of the one the picker
|
||||
# returned. So it is held to the characters a theme name needs rather than
|
||||
# screened for the harm of the day -- a repo called `..` would take
|
||||
# ~/.config/omarchy with it, and one called `a';'id` would carry its own
|
||||
# command into that picker. The leading character is kept out of `.` and `-`,
|
||||
# which also covers `host:-s/foo.git` leaving basename with `.git`.
|
||||
# A bracket range follows the locale's collation, not ASCII: `[a-z]` takes in
|
||||
# `é` under en_US.UTF-8. Pin the locale so the set is the one written here.
|
||||
if ! (LC_ALL=C; [[ $THEME_NAME =~ ^[a-z0-9_][a-z0-9._+-]*$ ]]); then
|
||||
echo "Error: '$REPO_URL' does not give a usable theme name."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -3,23 +3,15 @@
|
||||
# omarchy:summary=Apply the current theme color to Chromium, Chrome, Edge, and Brave
|
||||
# omarchy:hidden=true
|
||||
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
|
||||
CHROMIUM_THEME=$HOME/.local/state/omarchy/current/theme/chromium.theme
|
||||
THEME_HEX_COLOR=$BROWSER_POLICY_DEFAULT_COLOR
|
||||
|
||||
if [[ -f $CHROMIUM_THEME ]]; then
|
||||
THEME_RGB_COLOR=$(<$CHROMIUM_THEME)
|
||||
THEME_HEX_COLOR=$(printf '#%02x%02x%02x' ${THEME_RGB_COLOR//,/ })
|
||||
else
|
||||
# Use a default, neutral grey if theme doesn't have a color
|
||||
THEME_HEX_COLOR="#1c2027"
|
||||
THEME_HEX_COLOR=$(browser_policy_theme_hex "$(<$CHROMIUM_THEME)")
|
||||
fi
|
||||
|
||||
set_browser_policy() {
|
||||
local policy_dir="$1"
|
||||
|
||||
[[ -d $policy_dir ]] || return
|
||||
echo "{\"BrowserThemeColor\": \"$THEME_HEX_COLOR\", \"BrowserColorScheme\": \"device\"}" | tee "$policy_dir/color.json" >/dev/null
|
||||
}
|
||||
|
||||
refresh_running_browser() {
|
||||
local process="$1"
|
||||
local command="$2"
|
||||
@@ -30,17 +22,15 @@ refresh_running_browser() {
|
||||
fi
|
||||
}
|
||||
|
||||
set_browser_policy /etc/chromium/policies/managed
|
||||
failed=0
|
||||
omarchy-theme-set-browser-policy "${THEME_HEX_COLOR#\#}" || failed=1
|
||||
|
||||
refresh_running_browser chromium chromium
|
||||
|
||||
set_browser_policy /etc/opt/chrome/policies/managed
|
||||
refresh_running_browser chrome google-chrome-stable || refresh_running_browser chrome google-chrome
|
||||
|
||||
set_browser_policy /etc/opt/edge/policies/managed
|
||||
refresh_running_browser msedge microsoft-edge-stable
|
||||
|
||||
set_browser_policy /etc/brave/policies/managed
|
||||
refresh_running_browser brave brave
|
||||
# Match on the binary path: the running process is named plain "brave", and a
|
||||
# bare -f brave-origin pattern would also match the installer's own terminal.
|
||||
refresh_running_browser /opt/brave-origin-bin/ brave-origin -f
|
||||
|
||||
exit "$failed"
|
||||
|
||||
Executable
+123
@@ -0,0 +1,123 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Write the current theme color into the browser policy directories
|
||||
# omarchy:args=<rrggbb>
|
||||
# omarchy:hidden=true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Whenever this runs as root — invoked directly through the passwordless
|
||||
# sudoers rule, or re-execed by require_root below — sudo's secure_path decides
|
||||
# where a bare helper resolves, and a dev link (etc/sudoers.d/omarchy-dev-path)
|
||||
# prepends a user-writable checkout bin/ to it. Every helper this script calls
|
||||
# by bare name (printf's builtin aside: install, mktemp, rm) is a system tool,
|
||||
# never an omarchy-* command, so pin PATH to trusted system directories and keep
|
||||
# root from resolving one out of that checkout. The unprivileged wrapper phase
|
||||
# keeps the caller's PATH so it can still find sudo/pkexec.
|
||||
if (( EUID == 0 )); then
|
||||
export PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin
|
||||
fi
|
||||
|
||||
# Enterprise policy trust roots. The list is fixed here rather than taken from
|
||||
# the caller: the caller chooses a color, never a path.
|
||||
POLICY_DIRS=(
|
||||
/etc/chromium/policies/managed
|
||||
/etc/opt/chrome/policies/managed
|
||||
/etc/opt/edge/policies/managed
|
||||
/etc/brave/policies/managed
|
||||
)
|
||||
|
||||
# The path etc/sudoers.d/omarchy-theme-browser names. The privileged half always
|
||||
# runs from there rather than from whichever copy was invoked, so the rule
|
||||
# matches even where $OMARCHY_PATH points at a checkout.
|
||||
PACKAGED_PATH=/usr/bin/omarchy-theme-set-browser-policy
|
||||
|
||||
usage() {
|
||||
echo "Usage: omarchy-theme-set-browser-policy <rrggbb>" >&2
|
||||
}
|
||||
|
||||
if (( $# != 1 )); then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
color="$1"
|
||||
|
||||
# Six lowercase hex digits is the whole of what this accepts. The leading "#"
|
||||
# is added when the JSON is written rather than passed in: "#" opens a comment
|
||||
# in sudoers, and keeping it out of argv lets the sudoers rule spell the
|
||||
# argument as a plain six-character glob.
|
||||
if [[ ! $color =~ ^[0-9a-f]{6}$ ]]; then
|
||||
echo "omarchy-theme-set-browser-policy: expected six lowercase hex digits, got '$color'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# True when sudo would run this exact command without stopping for a password.
|
||||
# `sudo -l` on its own reports whether a command is permitted, which the blanket
|
||||
# %wheel rule answers yes to for everything; the long listing prints the matched
|
||||
# entry's tags, so !authenticate is the grant in
|
||||
# etc/sudoers.d/omarchy-theme-browser and nothing else. Listing runs nothing
|
||||
# and, under -n, prompts for nothing.
|
||||
sudo_grants_passwordless() {
|
||||
sudo -n -l -l "$PACKAGED_PATH" "$@" 2>/dev/null | grep -q '!authenticate'
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if (( EUID == 0 )); then
|
||||
return
|
||||
elif [[ -t 0 ]] || sudo_grants_passwordless "$@"; then
|
||||
exec sudo "$PACKAGED_PATH" "$@"
|
||||
else
|
||||
exec pkexec "$PACKAGED_PATH" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
require_root "$color"
|
||||
|
||||
failed=0
|
||||
staged=""
|
||||
# Bash 5.3 makes the EXIT trap's last command decide the script's exit status,
|
||||
# so this handler must not end on a false test. Every successful run clears
|
||||
# staged, and a trailing `[[ -n $staged ]] && ...` would report that as failure.
|
||||
cleanup() {
|
||||
if [[ -n $staged ]]; then
|
||||
rm -f "$staged"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
for policy_dir in "${POLICY_DIRS[@]}"; do
|
||||
# Only browsers Omarchy has installed have a policy directory. Creating one
|
||||
# here would hand a browser a managed-policy root it does not otherwise have.
|
||||
[[ -d $policy_dir && ! -L $policy_dir ]] || continue
|
||||
|
||||
dest=$policy_dir/color.json
|
||||
staged=$(mktemp) || {
|
||||
failed=1
|
||||
continue
|
||||
}
|
||||
printf '{"BrowserThemeColor": "#%s", "BrowserColorScheme": "device"}\n' "$color" >"$staged"
|
||||
|
||||
if [[ -L $dest || -d $dest ]]; then
|
||||
if ! rm -rf -- "$dest"; then
|
||||
rm -f "$staged"
|
||||
staged=""
|
||||
echo "omarchy-theme-set-browser-policy: cannot replace $dest" >&2
|
||||
failed=1
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! install -m 0644 -o root -g root -T "$staged" "$dest"; then
|
||||
rm -f "$staged"
|
||||
staged=""
|
||||
echo "omarchy-theme-set-browser-policy: cannot write $dest" >&2
|
||||
failed=1
|
||||
continue
|
||||
fi
|
||||
|
||||
rm -f "$staged"
|
||||
staged=""
|
||||
done
|
||||
|
||||
exit "$failed"
|
||||
@@ -1312,9 +1312,23 @@ apply_system_transition() {
|
||||
/usr/share/icons/Yaru/scalable/actions/go-next-symbolic.svg
|
||||
as_root gtk-update-icon-cache /usr/share/icons/Yaru >/dev/null 2>&1 || true
|
||||
|
||||
as_root install -d -m 0777 /etc/chromium/policies/managed
|
||||
local browser_policy_helper=/usr/share/omarchy/install/helpers/browser-policy.sh
|
||||
if ! as_root test -f "$browser_policy_helper"; then
|
||||
warn "$browser_policy_helper is unavailable; Chromium policy directories were not hardened."
|
||||
else
|
||||
as_root env OMARCHY_PATH=/usr/share/omarchy \
|
||||
bash -euo pipefail -c '
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
browser_policy_setup_dir /etc/chromium/policies/managed
|
||||
for dir in "${BROWSER_POLICY_MANAGED_DIRS[@]}"; do
|
||||
[[ $dir == "/etc/chromium/policies/managed" ]] && continue
|
||||
[[ -d $dir || -L $dir ]] || continue
|
||||
browser_policy_setup_dir "$dir"
|
||||
done
|
||||
'
|
||||
fi
|
||||
as_root install -d -m 0755 /usr/lib/chromium
|
||||
printf '%s\n' '{"browser":{"theme":{"color_scheme":0,"color_scheme2":0}}}' | \
|
||||
printf '%s\n' '{"distribution":{"require_eula":false},"browser":{"theme":{"color_scheme":0,"color_scheme2":0}}}' | \
|
||||
as_root tee /usr/lib/chromium/initial_preferences >/dev/null
|
||||
|
||||
# Deliberately do NOT add the user to the docker group. That group is
|
||||
@@ -1350,7 +1364,6 @@ EOF
|
||||
as_root systemctl disable docker.service >/dev/null 2>&1 || true
|
||||
|
||||
enable_system_service cups.service
|
||||
enable_system_service cups-browsed.service
|
||||
enable_system_service avahi-daemon.service
|
||||
enable_system_service linux-modules-cleanup.service
|
||||
enable_system_service docker.socket
|
||||
@@ -2306,6 +2319,11 @@ refresh_current_theme_after_upgrade() {
|
||||
# hooks because one of them runs `hyprctl reload`. Still poke terminal
|
||||
# emulators so the active upgrade terminal picks up generated theme files.
|
||||
run_as_user_omarchy omarchy-restart-terminal >/dev/null 2>&1 || true
|
||||
|
||||
# apply_system_transition purged user-owned color.json. Headless theme-set
|
||||
# skipped omarchy-theme-set-browser, so rewrite the colour here.
|
||||
run_as_user_omarchy omarchy-theme-set-browser >/dev/null 2>&1 ||
|
||||
warn "Could not apply browser theme colour. Run 'omarchy theme set \"$theme_name\"' after reboot if Chromium's theme looks stale."
|
||||
}
|
||||
|
||||
# Everything below mutates the system, so a non-zero exit from here on leaves a
|
||||
|
||||
+98
-16
@@ -13,6 +13,18 @@ safe_icon_name() {
|
||||
| sed 's/[^[:alnum:]]\+/-/g; s/^-//; s/-$//'
|
||||
}
|
||||
|
||||
require_plain_name() {
|
||||
# The name becomes a filename. A slash would turn it into directory levels, so
|
||||
# the launcher lands somewhere omarchy-webapp-remove cannot address and the app
|
||||
# is stuck in the launcher; a leading ../ leaves the applications directory
|
||||
# altogether. Refuse rather than silently renaming what the user typed -- most
|
||||
# often it is a URL entered in the name field.
|
||||
if [[ $1 == */* ]]; then
|
||||
echo "App name cannot contain '/': $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
icon_name_from_ref() {
|
||||
local ref="$1"
|
||||
local name
|
||||
@@ -42,6 +54,34 @@ download_icon() {
|
||||
[[ -s $2 && $(file -b --mime-type "$2") == image/* ]]
|
||||
}
|
||||
|
||||
# Chromium --app= treats javascript:, file:, and data: as a document to
|
||||
# run. Prefix schemeless input with https as before, then refuse anything
|
||||
# that is not http(s).
|
||||
normalize_webapp_url() {
|
||||
local url=$1
|
||||
if [[ ! $url =~ ^[a-zA-Z][a-zA-Z0-9+.-]*: ]]; then
|
||||
url="https://$url"
|
||||
fi
|
||||
printf '%s' "$url"
|
||||
}
|
||||
|
||||
# Raw whitespace must be percent-encoded in a URL. Refuse it before serializing
|
||||
# the desktop entry; before Exec argument quoting, it also split browser flags
|
||||
# and additional URLs into separate arguments. Schemes are case-insensitive.
|
||||
require_http_url() {
|
||||
local url=$1
|
||||
|
||||
if [[ $url =~ [[:space:]] ]]; then
|
||||
echo "Error: web app URL must not contain whitespace." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! ${url,,} =~ ^https?:// ]]; then
|
||||
echo "Error: web app URL must be http or https." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
fetch_site_icon() {
|
||||
local site_url="$1" dest="$2"
|
||||
local origin page icon_url
|
||||
@@ -65,13 +105,44 @@ fetch_site_icon() {
|
||||
download_icon "https://www.google.com/s2/favicons?domain=${site_url}&sz=256" "$dest"
|
||||
}
|
||||
|
||||
desktop_string_escape() {
|
||||
# Desktop Entry "string" value (freedesktop Desktop Entry Spec, "Value types"):
|
||||
# a raw newline would start a new key line and let a value inject a second
|
||||
# Exec=. Escape backslash first, then tab/CR/LF and a leading space. Every value
|
||||
# written into the .desktop file passes through here.
|
||||
#
|
||||
# Parameter expansion rather than sed: GNU sed's N auto-prints the pattern space
|
||||
# and exits at end of input, so a `:a;N;$!ba` slurp skips every following s///
|
||||
# for a value with no newline in it - which is every value except the injection
|
||||
# attempt this exists to stop.
|
||||
local value="$1"
|
||||
|
||||
value=${value//\\/\\\\}
|
||||
value=${value//$'\t'/\\t}
|
||||
value=${value//$'\r'/\\r}
|
||||
value=${value//$'\n'/\\n}
|
||||
[[ $value == " "* ]] && value="\\s${value# }"
|
||||
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
desktop_exec_arg() {
|
||||
# One Exec argument, double-quoted per the freedesktop Exec spec: inside quotes
|
||||
# " ` $ \ take a backslash and a literal % becomes %%. Only the default Exec's
|
||||
# URL needs this; $CUSTOM_EXEC stays a whole command line (file-syntax only).
|
||||
local escaped
|
||||
escaped=$(printf '%s' "$1" \
|
||||
| sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/`/\\`/g' -e 's/\$/\\$/g' -e 's/%/%%/g')
|
||||
printf '"%s"' "$escaped"
|
||||
}
|
||||
|
||||
if (( $# < 3 )); then
|
||||
echo -e "\e[32mLet's create a new web app you can start with the app launcher.\n\e[0m"
|
||||
APP_NAME=$(gum input --prompt "Name> " --placeholder "My favorite web app")
|
||||
require_plain_name "$APP_NAME"
|
||||
APP_URL=$(gum input --prompt "URL> " --placeholder "https://example.com")
|
||||
if [[ ! $APP_URL =~ ^[a-zA-Z][a-zA-Z0-9+.-]*: ]]; then
|
||||
APP_URL="https://$APP_URL"
|
||||
fi
|
||||
APP_URL=$(normalize_webapp_url "$APP_URL")
|
||||
require_http_url "$APP_URL"
|
||||
|
||||
# Try to fetch the site's icon automatically first.
|
||||
mkdir -p "$ICON_DIR"
|
||||
@@ -88,10 +159,8 @@ if (( $# < 3 )); then
|
||||
INTERACTIVE_MODE=true
|
||||
else
|
||||
APP_NAME="$1"
|
||||
APP_URL="$2"
|
||||
if [[ ! $APP_URL =~ ^[a-zA-Z][a-zA-Z0-9+.-]*: ]]; then
|
||||
APP_URL="https://$APP_URL"
|
||||
fi
|
||||
APP_URL=$(normalize_webapp_url "$2")
|
||||
require_http_url "$APP_URL"
|
||||
ICON_REF="$3"
|
||||
CUSTOM_EXEC="$4" # Optional custom exec command
|
||||
MIME_TYPES="$5" # Optional mime types
|
||||
@@ -104,6 +173,8 @@ if [[ -z $APP_NAME || -z $APP_URL ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
require_plain_name "$APP_NAME"
|
||||
|
||||
if [[ -z $ICON_REF ]]; then
|
||||
ICON_VALUE=$(safe_icon_name "$APP_NAME")
|
||||
mkdir -p "$ICON_DIR"
|
||||
@@ -128,28 +199,39 @@ else
|
||||
ICON_VALUE=$(icon_name_from_ref "$ICON_REF")
|
||||
fi
|
||||
|
||||
# Use custom exec if provided, otherwise default behavior
|
||||
EXEC_COMMAND="${CUSTOM_EXEC:-omarchy-launch-webapp $APP_URL}"
|
||||
# Default Exec quotes the URL as one Exec-spec argument; the whole line then gets
|
||||
# the file-syntax escaping below (unescaped first at read time per spec, so the
|
||||
# layers compose). $CUSTOM_EXEC is a full command line, so it gets file-syntax only.
|
||||
if [[ -n $CUSTOM_EXEC ]]; then
|
||||
EXEC_COMMAND=$CUSTOM_EXEC
|
||||
else
|
||||
EXEC_COMMAND="omarchy-launch-webapp $(desktop_exec_arg "$APP_URL")"
|
||||
fi
|
||||
|
||||
# Create application .desktop file
|
||||
DESKTOP_FILE="$HOME/.local/share/applications/$APP_NAME.desktop"
|
||||
mkdir -p "$(dirname "$DESKTOP_FILE")"
|
||||
DESKTOP_DIR="$HOME/.local/share/applications"
|
||||
DESKTOP_FILE="$DESKTOP_DIR/$APP_NAME.desktop"
|
||||
mkdir -p "$DESKTOP_DIR"
|
||||
|
||||
name_field=$(desktop_string_escape "$APP_NAME")
|
||||
exec_field=$(desktop_string_escape "$EXEC_COMMAND")
|
||||
icon_field=$(desktop_string_escape "$ICON_VALUE")
|
||||
|
||||
cat >"$DESKTOP_FILE" <<EOF
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Name=$APP_NAME
|
||||
Comment=$APP_NAME
|
||||
Exec=$EXEC_COMMAND
|
||||
Name=$name_field
|
||||
Comment=$name_field
|
||||
Exec=$exec_field
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Icon=$ICON_VALUE
|
||||
Icon=$icon_field
|
||||
StartupNotify=true
|
||||
EOF
|
||||
|
||||
# Add mime types if provided
|
||||
if [[ -n $MIME_TYPES ]]; then
|
||||
echo "MimeType=$MIME_TYPES" >>"$DESKTOP_FILE"
|
||||
printf 'MimeType=%s\n' "$(desktop_string_escape "$MIME_TYPES")" >>"$DESKTOP_FILE"
|
||||
fi
|
||||
|
||||
chmod +x "$DESKTOP_FILE"
|
||||
|
||||
@@ -9,14 +9,31 @@ ICON_DIR="$HOME/.local/share/icons/hicolor/256x256/apps"
|
||||
OLD_ICON_DIR="$HOME/.local/share/applications/icons"
|
||||
DESKTOP_DIR="$HOME/.local/share/applications/"
|
||||
|
||||
if (( $# == 0 )); then
|
||||
# Find all web apps
|
||||
while IFS= read -r -d '' file; do
|
||||
if grep -q '^Exec=.*\(omarchy-launch-webapp\|omarchy-webapp-handler\).*' "$file"; then
|
||||
WEB_APPS+=("$(basename "${file%.desktop}")")
|
||||
fi
|
||||
done < <(find "$DESKTOP_DIR" -name '*.desktop' -print0)
|
||||
# Always index the launchers, so removal deletes the file that was found rather
|
||||
# than a path rebuilt from the displayed name. Installs predating the name
|
||||
# validation could nest the launcher inside directories, and those are exactly
|
||||
# the ones a reconstructed path cannot reach.
|
||||
WEB_APP_PATHS=()
|
||||
while IFS= read -r -d '' file; do
|
||||
if grep -q '^Exec=.*\(omarchy-launch-webapp\|omarchy-webapp-handler\).*' "$file"; then
|
||||
WEB_APPS+=("$(basename "${file%.desktop}")")
|
||||
WEB_APP_PATHS+=("$file")
|
||||
fi
|
||||
done < <(find "$DESKTOP_DIR" -name '*.desktop' -print0 2>/dev/null)
|
||||
|
||||
# The launcher matching a chosen name, or empty when nothing was indexed under
|
||||
# it (an app removed between the scan and the pick, say).
|
||||
path_for_web_app() {
|
||||
local wanted="$1" i
|
||||
for i in "${!WEB_APPS[@]}"; do
|
||||
if [[ ${WEB_APPS[$i]} == "$wanted" ]]; then
|
||||
printf '%s\n' "${WEB_APP_PATHS[$i]}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
if (( $# == 0 )); then
|
||||
if ((${#WEB_APPS[@]})); then
|
||||
mapfile -t SORTED_WEB_APPS < <(printf '%s\n' "${WEB_APPS[@]}" | sort)
|
||||
APP_NAME=$(omarchy-menu-select "Select web app to remove" "${SORTED_WEB_APPS[@]}" -- --width 520 --maxheight 520)
|
||||
@@ -34,7 +51,8 @@ if [[ -z $APP_NAME ]]; then
|
||||
fi
|
||||
|
||||
icon_name=$(printf '%s\n' "$APP_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^[:alnum:]]\+/-/g; s/^-//; s/-$//')
|
||||
rm -f "$DESKTOP_DIR/$APP_NAME.desktop"
|
||||
desktop_file=$(path_for_web_app "$APP_NAME")
|
||||
rm -f "${desktop_file:-$DESKTOP_DIR/$APP_NAME.desktop}"
|
||||
rm -f "$ICON_DIR/$icon_name.png" "$ICON_DIR/$APP_NAME.png" "$OLD_ICON_DIR/$APP_NAME.png"
|
||||
|
||||
if [[ ${OMARCHY_REMOVE_NOTIFY:-true} != "false" ]]; then
|
||||
|
||||
+894
-125
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user