Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
346e69e1ce | ||
|
|
0e1ce609d8 | ||
|
|
3cb3b2d20b | ||
|
|
f8e35ec654 | ||
|
|
f802ae107d | ||
|
|
6f8b62f04c | ||
|
|
9aa0545c2a | ||
|
|
ebd6480387 | ||
|
|
07579f5e94 | ||
|
|
ff4cf8afbc | ||
|
|
cf3d69c38d | ||
|
|
47ce81ebca | ||
|
|
2421e76fb8 | ||
|
|
bffc6a2a5d | ||
|
|
7678e5b0d8 | ||
|
|
be730884f6 | ||
|
|
a73a312e1b | ||
|
|
159163bb16 | ||
|
|
521779b114 | ||
|
|
51af7bf794 | ||
|
|
92c8f87730 | ||
|
|
3bb9867245 | ||
|
|
da0fe6d89f | ||
|
|
2645b82bcd | ||
|
|
9c1ec524cd | ||
|
|
880605f22b | ||
|
|
294c38a2f0 | ||
|
|
1082b7864d | ||
|
|
13f18b2cb7 | ||
|
|
2e989e35e5 | ||
|
|
c6d676f23c | ||
|
|
0f836c0deb | ||
|
|
07adef8a53 | ||
|
|
e713ff3166 | ||
|
|
c7af36d0aa | ||
|
|
b3028f9bd9 | ||
|
|
0f4abeec8e | ||
|
|
7b89780810 | ||
|
|
c0b593b349 | ||
|
|
7fa32bb98c | ||
|
|
2b1d3c4606 | ||
|
|
286b8c2b1c | ||
|
|
f2a2973de8 | ||
|
|
64c5c02f55 | ||
|
|
d0aefc1f79 | ||
|
|
1626405638 | ||
|
|
9b192256e7 | ||
|
|
2487fbcce1 | ||
|
|
9434f220f0 | ||
|
|
9e79b24342 | ||
|
|
6245965cb6 | ||
|
|
f6b4183e4f | ||
|
|
39c2797d3b | ||
|
|
8b979e0c4c | ||
|
|
5c2fb68c0a | ||
|
|
2d3826aff3 | ||
|
|
eb201d0bd3 | ||
|
|
ac1bdbfbf5 | ||
|
|
9f7cdf6983 | ||
|
|
1867dcaa50 | ||
|
|
33d7363c33 | ||
|
|
84336759cc | ||
|
|
5f0704a0f8 |
No files matched your search
@@ -61,19 +61,18 @@ its screenshots are also hosted.
|
|||||||
- [Prompt](manual/40-prompt.md)
|
- [Prompt](manual/40-prompt.md)
|
||||||
- [Branding](manual/41-branding.md)
|
- [Branding](manual/41-branding.md)
|
||||||
- [Common tweaks](manual/42-common-tweaks.md)
|
- [Common tweaks](manual/42-common-tweaks.md)
|
||||||
- [Extra themes](manual/43-extra-themes.md)
|
- [Making your own theme](manual/43-making-your-own-theme.md)
|
||||||
- [Making your own theme](manual/44-making-your-own-theme.md)
|
|
||||||
|
|
||||||
**The Rest**
|
**The Rest**
|
||||||
|
|
||||||
- [Mac support](manual/45-mac-support.md)
|
- [Mac support](manual/44-mac-support.md)
|
||||||
- [Troubleshooting](manual/46-troubleshooting.md)
|
- [Troubleshooting](manual/45-troubleshooting.md)
|
||||||
- [FAQ](manual/47-faq.md)
|
- [FAQ](manual/46-faq.md)
|
||||||
- [System snapshots](manual/48-system-snapshots.md)
|
- [System snapshots](manual/47-system-snapshots.md)
|
||||||
- [Security](manual/49-security.md)
|
- [Security](manual/48-security.md)
|
||||||
- [Omarchy on...](manual/50-omarchy-on.md)
|
- [Omarchy on...](manual/49-omarchy-on.md)
|
||||||
- [Dual Boot Install](manual/51-dual-boot-install.md)
|
- [Dual Boot Install](manual/50-dual-boot-install.md)
|
||||||
- [Unattended Installs](manual/52-unattended-installs.md)
|
- [Unattended Installs](manual/51-unattended-installs.md)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Version=1.0
|
Version=1.0
|
||||||
Name=Docker
|
Name=Docker
|
||||||
Exec=xdg-terminal-exec --app-id=TUI.tile -e lazydocker
|
Exec=xdg-terminal-exec --app-id=TUI.tile -e omarchy-launch-docker-tui
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Icon=docker
|
Icon=docker
|
||||||
|
|||||||
+7
-3
@@ -74,12 +74,16 @@ crush)
|
|||||||
command=(crush --yolo)
|
command=(crush --yolo)
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
claude | grok)
|
claude)
|
||||||
command=("$agent" --permission-mode bypassPermissions)
|
command=(claude --permission-mode auto)
|
||||||
|
[[ -n ${prompt:-} ]] && command+=(-- "$prompt")
|
||||||
|
;;
|
||||||
|
grok)
|
||||||
|
command=(grok --permission-mode bypassPermissions)
|
||||||
[[ -n ${prompt:-} ]] && command+=(-- "$prompt")
|
[[ -n ${prompt:-} ]] && command+=(-- "$prompt")
|
||||||
;;
|
;;
|
||||||
codex)
|
codex)
|
||||||
command=(codex --dangerously-bypass-approvals-and-sandbox)
|
command=(codex --approve-for-me)
|
||||||
[[ -n ${prompt:-} ]] && command+=(-- "$prompt")
|
[[ -n ${prompt:-} ]] && command+=(-- "$prompt")
|
||||||
;;
|
;;
|
||||||
omp)
|
omp)
|
||||||
|
|||||||
@@ -528,7 +528,7 @@ def fetch_codex_rpc():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
[codex, "-s", "read-only", "-a", "untrusted", "app-server"],
|
[codex, "-s", "read-only", "-a", "on-request", "app-server"],
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
|
|||||||
@@ -4,7 +4,13 @@
|
|||||||
# omarchy:args=[--no-osd] [+N%|N%-|N%]
|
# 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%
|
# 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
|
no_osd=0
|
||||||
if [[ ${1:-} == "--no-osd" ]]; then
|
if [[ ${1:-} == "--no-osd" ]]; then
|
||||||
no_osd=1
|
no_osd=1
|
||||||
@@ -28,9 +34,14 @@ find_apple_display_device() {
|
|||||||
local cached=""
|
local cached=""
|
||||||
local device=""
|
local device=""
|
||||||
|
|
||||||
if [[ -r $device_cache ]]; then
|
if [[ -n $device_cache && -r $device_cache ]]; then
|
||||||
read -r cached <"$device_cache" || true
|
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"
|
printf '%s\n' "$cached"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -39,7 +50,9 @@ find_apple_display_device() {
|
|||||||
device="$(detect_apple_display_device)" || return 1
|
device="$(detect_apple_display_device)" || return 1
|
||||||
[[ -n $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"
|
printf '%s\n' "$device"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ stop_screenrecording() {
|
|||||||
|
|
||||||
omarchy-notification-send "Screen recording saved" "Open with Super + Alt + , (or click this)" \
|
omarchy-notification-send "Screen recording saved" "Open with Super + Alt + , (or click this)" \
|
||||||
-t 10000 --image "${preview:-$filename}" \
|
-t 10000 --image "${preview:-$filename}" \
|
||||||
--exec "$(printf 'mpv %q' "$filename")"
|
--exec mpv -- "$filename"
|
||||||
|
|
||||||
# The shell loads the thumbnail into memory when the toast appears and never
|
# The shell loads the thumbnail into memory when the toast appears and never
|
||||||
# re-reads the file, so the preview only has to outlive that load -- not the
|
# re-reads the file, so the preview only has to outlive that load -- not the
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ case "$PROCESSING" in
|
|||||||
# notification outage must not report the capture itself as failed.
|
# notification outage must not report the capture itself as failed.
|
||||||
omarchy-notification-send "Screenshot saved to clipboard and file" "Edit with Super + Alt + , (or click this)" \
|
omarchy-notification-send "Screenshot saved to clipboard and file" "Edit with Super + Alt + , (or click this)" \
|
||||||
--image "$FILEPATH" \
|
--image "$FILEPATH" \
|
||||||
--exec "$(printf '%q %q' "$SCREENSHOT_EDITOR" "$FILEPATH")" || true
|
--exec "$SCREENSHOT_EDITOR" "$FILEPATH" || true
|
||||||
;;
|
;;
|
||||||
copy)
|
copy)
|
||||||
grim -g "$SELECTION" - | wl-copy --type image/png
|
grim -g "$SELECTION" - | wl-copy --type image/png
|
||||||
|
|||||||
@@ -25,6 +25,51 @@ valid_url() {
|
|||||||
[[ $1 =~ ^https?:// ]]
|
[[ $1 =~ ^https?:// ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# A printed path is only usable if it is a regular file inside DOWNLOAD_DIR.
|
||||||
|
# Forged records (leading-dash mpv options, paths with control chars, or
|
||||||
|
# anything that escaped the download directory) must not reach the click command.
|
||||||
|
resolve_download_file() {
|
||||||
|
local candidate=$1 file_real dir_real
|
||||||
|
|
||||||
|
[[ -n $candidate ]] || return 1
|
||||||
|
[[ $candidate != *$'\n'* && $candidate != *$'\r'* && $candidate != *$'\t'* ]] || return 1
|
||||||
|
[[ -f $candidate ]] || return 1
|
||||||
|
|
||||||
|
# Read to a NUL: command substitution strips trailing newlines, which would
|
||||||
|
# resolve a name ending in one to a different file that may well exist.
|
||||||
|
IFS= read -r -d '' file_real < <(realpath -ze -- "$candidate") || return 1
|
||||||
|
IFS= read -r -d '' dir_real < <(realpath -ze -- "$DOWNLOAD_DIR") || return 1
|
||||||
|
|
||||||
|
[[ $file_real != *$'\n'* && $file_real != *$'\r'* && $file_real != *$'\t'* ]] || return 1
|
||||||
|
# Trim the slash so a download directory of "/" still leaves a usable prefix.
|
||||||
|
[[ $file_real == "${dir_real%/}"/* ]] || return 1
|
||||||
|
|
||||||
|
printf '%s' "$file_real"
|
||||||
|
}
|
||||||
|
|
||||||
|
# yt-dlp prints the title JSON-encoded, so a newline or tab in page metadata is an
|
||||||
|
# escape sequence rather than a record boundary. This is toast text, never a command.
|
||||||
|
decode_title() {
|
||||||
|
local decoded
|
||||||
|
|
||||||
|
decoded=$(jq -r 'if type == "string" then . else empty end' <<<"$1" 2>/dev/null) || return 1
|
||||||
|
decoded=${decoded%%[[:cntrl:]]*} # keep what a person would read, drop the forgery
|
||||||
|
[[ -n $decoded && $decoded != -* ]] || return 1
|
||||||
|
|
||||||
|
printf '%s' "$decoded"
|
||||||
|
}
|
||||||
|
|
||||||
|
title_from_file() {
|
||||||
|
local name=${1##*/}
|
||||||
|
name=${name%.*}
|
||||||
|
name=${name//[$'\n\r\t']/}
|
||||||
|
if [[ -z $name || $name == -* ]]; then
|
||||||
|
printf '%s' "Video"
|
||||||
|
else
|
||||||
|
printf '%s' "$name"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Drive the Quickshell OSD — a single overlay that updates in place (like the
|
# Drive the Quickshell OSD — a single overlay that updates in place (like the
|
||||||
# volume/brightness bar), so download progress never stacks like notifications.
|
# volume/brightness bar), so download progress never stacks like notifications.
|
||||||
osd_progress() {
|
osd_progress() {
|
||||||
@@ -41,16 +86,19 @@ download_url() {
|
|||||||
mkdir -p "$DOWNLOAD_DIR"
|
mkdir -p "$DOWNLOAD_DIR"
|
||||||
|
|
||||||
# Don't show anything until yt-dlp confirms there's actually a video to grab.
|
# Don't show anything until yt-dlp confirms there's actually a video to grab.
|
||||||
if ! yt-dlp --no-playlist --simulate --quiet --no-warnings "$url" >/dev/null 2>&1; then
|
if ! yt-dlp --no-playlist --simulate --quiet --no-warnings --no-exec --no-exec-before-download -- "$url" >/dev/null 2>&1; then
|
||||||
omarchy-notification-send -u critical -g "No video found for download" "$url"
|
omarchy-notification-send -u critical -g "No video found for download" "$url"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
osd_progress 0
|
osd_progress 0
|
||||||
|
|
||||||
# Stream the download: OMARCHY_PROG carries the percent (drives the OSD),
|
# Stream the download: OMARCHY_PROG carries the percent (drives the OSD), and
|
||||||
# OMARCHY_FILE (printed only after a successful move) carries title + path.
|
# OMARCHY_FILE and OMARCHY_TITLE (printed only after a successful move) carry the
|
||||||
local line rest pct intpct last="" er nowms lastms=0 title="" filepath=""
|
# path and the title. The title is JSON-encoded so metadata cannot forge a record,
|
||||||
|
# and the file is named after it: yt-dlp strips control characters from a filename
|
||||||
|
# with or without --restrict-filenames, so a record is still only ever one line.
|
||||||
|
local line pct intpct last="" er nowms lastms=0 title="" filepath="" resolved
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
case $line in
|
case $line in
|
||||||
OMARCHY_PROG*)
|
OMARCHY_PROG*)
|
||||||
@@ -67,22 +115,26 @@ download_url() {
|
|||||||
osd_progress "$intpct"
|
osd_progress "$intpct"
|
||||||
;;
|
;;
|
||||||
OMARCHY_FILE*)
|
OMARCHY_FILE*)
|
||||||
rest=${line#OMARCHY_FILE$'\t'}
|
resolved=$(resolve_download_file "${line#OMARCHY_FILE$'\t'}") || continue
|
||||||
title=${rest%%$'\t'*}
|
filepath=$resolved
|
||||||
filepath=${rest#*$'\t'}
|
;;
|
||||||
|
OMARCHY_TITLE*)
|
||||||
|
title=$(decode_title "${line#OMARCHY_TITLE$'\t'}") || title=""
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done < <(PYTHONUNBUFFERED=1 yt-dlp --no-playlist --restrict-filenames --no-simulate \
|
done < <(PYTHONUNBUFFERED=1 yt-dlp --no-playlist --no-simulate \
|
||||||
--quiet --no-warnings --progress --newline \
|
--quiet --no-warnings --no-exec --no-exec-before-download --progress --newline \
|
||||||
--progress-template $'download:OMARCHY_PROG\t%(progress._percent_str)s' \
|
--progress-template $'download:OMARCHY_PROG\t%(progress._percent_str)s' \
|
||||||
--paths "$DOWNLOAD_DIR" -o '%(title)s [%(id)s].%(ext)s' \
|
--paths "$DOWNLOAD_DIR" -o '%(title)s.%(ext)s' \
|
||||||
--print $'after_move:OMARCHY_FILE\t%(title)s\t%(filepath)s' \
|
--print $'after_move:OMARCHY_FILE\t%(filepath)s' \
|
||||||
"$url" 2>&1)
|
--print $'after_move:OMARCHY_TITLE\t%(title)j' \
|
||||||
|
-- "$url" 2>&1)
|
||||||
|
|
||||||
osd_close
|
osd_close
|
||||||
|
|
||||||
# after_move only prints on a successful download+move, so a captured path == success.
|
# after_move only prints on a successful download+move, so a captured path == success.
|
||||||
if [[ -n $filepath ]]; then
|
if [[ -n $filepath ]]; then
|
||||||
|
[[ -n $title ]] || title=$(title_from_file "$filepath")
|
||||||
((${#title} > 50)) && title="${title:0:50}…" # keep the toast compact
|
((${#title} > 50)) && title="${title:0:50}…" # keep the toast compact
|
||||||
|
|
||||||
# Square, center-cropped thumbnail so the notification preview isn't stretched.
|
# Square, center-cropped thumbnail so the notification preview isn't stretched.
|
||||||
@@ -94,9 +146,11 @@ download_url() {
|
|||||||
|
|
||||||
# Best-effort: the download already succeeded, and under `set -e` a failed
|
# Best-effort: the download already succeeded, and under `set -e` a failed
|
||||||
# toast would exit before the thumbnail cleanup below is ever scheduled.
|
# toast would exit before the thumbnail cleanup below is ever scheduled.
|
||||||
|
# `--` keeps mpv from parsing a leading-dash filename as an option; the path
|
||||||
|
# is one discrete argument, so it never reaches a shell.
|
||||||
omarchy-notification-send -g "Download complete" "$title" \
|
omarchy-notification-send -g "Download complete" "$title" \
|
||||||
-t 10000 --image "${preview:-$filepath}" \
|
-t 10000 --image "${preview:-$filepath}" \
|
||||||
--exec "$(printf 'mpv %q' "$filepath")" || true
|
--exec mpv -- "$filepath" || true
|
||||||
|
|
||||||
# The shell loads the thumbnail into memory when the toast appears and never
|
# The shell loads the thumbnail into memory when the toast appears and never
|
||||||
# re-reads the file, so the preview only has to outlive that load, not the
|
# re-reads the file, so the preview only has to outlive that load, not the
|
||||||
|
|||||||
@@ -24,9 +24,7 @@ readonly ignore_pattern=${OMARCHY_CRASH_IGNORE:-}
|
|||||||
declare -A last_notified
|
declare -A last_notified
|
||||||
|
|
||||||
announce() {
|
announce() {
|
||||||
local comm=$1 pid=$2 exe=$3 signal=$4 exec_command
|
local comm=$1 pid=$2 exe=$3 signal=$4
|
||||||
|
|
||||||
exec_command=$(printf 'omarchy-agent-crash %q %q %q %q' "$pid" "$comm" "$exe" "$signal")
|
|
||||||
|
|
||||||
# The shell owns org.freedesktop.Notifications, so a shell crash takes the
|
# The shell owns org.freedesktop.Notifications, so a shell crash takes the
|
||||||
# notification server down with it and a toast sent into that gap is lost.
|
# notification server down with it and a toast sent into that gap is lost.
|
||||||
@@ -35,14 +33,16 @@ announce() {
|
|||||||
omarchy-notification-wait || return 1
|
omarchy-notification-wait || return 1
|
||||||
|
|
||||||
# --exec rather than a libnotify action: the shell runs clicks from its own
|
# --exec rather than a libnotify action: the shell runs clicks from its own
|
||||||
# omarchy-exec hint and never emits ActionInvoked. Keeps the default
|
# hint and never emits ActionInvoked. Keeps the default "omarchy-action" app
|
||||||
# "omarchy-action" app name too, the only one shouldBypassDnd() lets through.
|
# name, the only one shouldBypassDnd() lets through. Crash details ride as
|
||||||
|
# discrete argv words, so a hostile process name can't be reparsed as a
|
||||||
|
# command. --exec consumes the rest of the line, so it comes last.
|
||||||
omarchy-notification-send \
|
omarchy-notification-send \
|
||||||
--urgency critical \
|
--urgency critical \
|
||||||
--glyph "$CRASH_GLYPH" \
|
--glyph "$CRASH_GLYPH" \
|
||||||
--exec "$exec_command" \
|
|
||||||
"Process crashed: $comm" \
|
"Process crashed: $comm" \
|
||||||
"Click to diagnose with AI"
|
"Click to diagnose with AI" \
|
||||||
|
--exec omarchy-agent-crash "$pid" "$comm" "$exe" "$signal"
|
||||||
}
|
}
|
||||||
|
|
||||||
# -n 0 so a restart does not re-announce crashes already dealt with.
|
# -n 0 so a restart does not re-announce crashes already dealt with.
|
||||||
|
|||||||
@@ -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/,
|
Affects only \$OMARCHY_PATH-resolved trees: bin/, default/, shell/,
|
||||||
themes/, applications/, config/. Files installed at fixed system paths
|
themes/, applications/, config/. Files installed at fixed system paths
|
||||||
(/etc/, /usr/lib/systemd/, udev rule bodies, /etc/skel after user
|
(/etc/, /usr/lib/systemd/, udev rule bodies, /etc/skel after user
|
||||||
creation, /usr/share/plymouth) are NOT covered — for those, use
|
creation) are NOT covered — for those, use omarchy-dev-pkg-test to build
|
||||||
omarchy-dev-pkg-test to build and install the package from the checkout.
|
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-*
|
Also writes $sudoers_file so sudo resolves omarchy-*
|
||||||
from the checkout instead of the packaged copies. That part takes effect
|
from the checkout instead of the packaged copies. That part takes effect
|
||||||
|
|||||||
+38
-7
@@ -6,6 +6,18 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
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 (dirname, install, tee, rm, nmcli, systemctl, awk) 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
|
||||||
|
|
||||||
NM_DNS_CONF=/etc/NetworkManager/conf.d/20-omarchy-dns.conf
|
NM_DNS_CONF=/etc/NetworkManager/conf.d/20-omarchy-dns.conf
|
||||||
|
|
||||||
provider_from_arg() {
|
provider_from_arg() {
|
||||||
@@ -28,19 +40,32 @@ provider_from_arg() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
self_path() {
|
# The path etc/sudoers.d/omarchy-dns names. The privileged half always runs from
|
||||||
readlink -f "$OMARCHY_PATH/bin/${BASH_SOURCE[0]##*/}"
|
# 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-dns
|
||||||
|
|
||||||
|
# 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-dns and
|
||||||
|
# nothing else. Listing runs nothing and, under -n, prompts for nothing, so a
|
||||||
|
# machine whose omarchy-settings predates that file falls through to polkit
|
||||||
|
# instead of dying on a password prompt it has no terminal to show.
|
||||||
|
sudo_grants_passwordless() {
|
||||||
|
sudo -n -l -l "$PACKAGED_PATH" "$@" 2>/dev/null | grep -q '!authenticate'
|
||||||
}
|
}
|
||||||
|
|
||||||
require_root() {
|
require_root() {
|
||||||
if (( EUID == 0 )); then
|
if (( EUID == 0 )); then
|
||||||
return
|
return
|
||||||
fi
|
elif [[ -t 0 ]] || sudo_grants_passwordless "$@"; then
|
||||||
|
# A terminal can carry sudo's own password prompt. Without one, sudo is
|
||||||
if [[ -t 0 ]]; then
|
# right only where the grant reaches; polkit can at least put a prompt on
|
||||||
exec sudo "$(self_path)" "$@"
|
# screen, and offer to authenticate as someone else.
|
||||||
|
exec sudo "$PACKAGED_PATH" "$@"
|
||||||
else
|
else
|
||||||
exec pkexec "$(self_path)" "$@"
|
exec pkexec "$PACKAGED_PATH" "$@"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +149,9 @@ write_networkmanager_dns() {
|
|||||||
local servers="$1"
|
local servers="$1"
|
||||||
|
|
||||||
install -d -m 0755 "$(dirname "$NM_DNS_CONF")"
|
install -d -m 0755 "$(dirname "$NM_DNS_CONF")"
|
||||||
|
# omarchy:heredoc-expands paths=none -- $servers is a normalized, single-line
|
||||||
|
# DNS server list written as data, not a path or command; nothing user-writable
|
||||||
|
# is resolved or executed from the root-owned drop-in.
|
||||||
cat >"$NM_DNS_CONF" <<EOF
|
cat >"$NM_DNS_CONF" <<EOF
|
||||||
# Managed by omarchy-dns. Remove this file or run omarchy dns DHCP to use DHCP DNS again.
|
# Managed by omarchy-dns. Remove this file or run omarchy dns DHCP to use DHCP DNS again.
|
||||||
[global-dns]
|
[global-dns]
|
||||||
@@ -277,6 +305,9 @@ Custom)
|
|||||||
split_dns_servers "$dns_servers"
|
split_dns_servers "$dns_servers"
|
||||||
write_networkmanager_dns "$dns_servers"
|
write_networkmanager_dns "$dns_servers"
|
||||||
set_connection_dns "$ipv4_dns" "$ipv6_dns"
|
set_connection_dns "$ipv4_dns" "$ipv6_dns"
|
||||||
|
# omarchy:heredoc-expands paths=none -- $dns_servers is a normalized,
|
||||||
|
# single-line DNS server list; the //,/ turns its comma separators into the
|
||||||
|
# spaces resolved.conf wants. It is written as data, not a path or command.
|
||||||
tee /etc/systemd/resolved.conf >/dev/null <<EOF
|
tee /etc/systemd/resolved.conf >/dev/null <<EOF
|
||||||
[Resolve]
|
[Resolve]
|
||||||
DNS=${dns_servers//,/ }
|
DNS=${dns_servers//,/ }
|
||||||
|
|||||||
Executable
+50
@@ -0,0 +1,50 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Check that a git URL names a repository, not a transport helper
|
||||||
|
# omarchy:args=<git-url>
|
||||||
|
# omarchy:hidden=true
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# git picks a remote helper -- an executable it runs at clone time -- out of a URL
|
||||||
|
# in exactly two shapes, and no others: `<helper>::<address>`, and
|
||||||
|
# `<scheme>://<address>` for any scheme git does not handle itself. A single
|
||||||
|
# colon is always scp-style ssh, and a bare path is always a path; neither can
|
||||||
|
# reach a helper. So constraining those two shapes covers the whole surface.
|
||||||
|
#
|
||||||
|
# The `::` shape is refused outright, because no helper reachable that way is one
|
||||||
|
# a theme or plugin URL has business naming, and `ext::` runs a shell command.
|
||||||
|
# The `://` shape cannot be refused the same way, since it is also how every
|
||||||
|
# legitimate URL arrives -- so it is allowlisted instead. The list is the
|
||||||
|
# transports git still connects itself, `git+ssh` and `ssh+git` included: those
|
||||||
|
# two are spelled like a helper but are read as plain ssh. `ext` and `fd` are
|
||||||
|
# left out deliberately -- git ships a helper for each, and `ext` runs whatever
|
||||||
|
# command the URL carries.
|
||||||
|
TRANSPORTS=(ssh git git+ssh ssh+git http https ftp ftps file)
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "omarchy-git-url-check: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
url="${1-}"
|
||||||
|
|
||||||
|
if [[ -z $url ]]; then
|
||||||
|
fail "a git URL is required"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $url == -* || $url =~ ^[A-Za-z0-9][A-Za-z0-9+.-]*:: ]]; then
|
||||||
|
fail "'$url' names a git option or transport helper, not a repository."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $url =~ ^([A-Za-z0-9][A-Za-z0-9+.-]*):// ]]; then
|
||||||
|
scheme="${BASH_REMATCH[1]}"
|
||||||
|
|
||||||
|
for transport in "${TRANSPORTS[@]}"; do
|
||||||
|
if [[ $scheme == "$transport" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
fail "'$url' names the '$scheme' transport, which Omarchy does not clone from."
|
||||||
|
fi
|
||||||
@@ -11,6 +11,14 @@ MONITOR_LUA="$HOME/.config/hypr/monitors.lua"
|
|||||||
|
|
||||||
INTERNAL=$(omarchy-hyprland-monitor-laptop)
|
INTERNAL=$(omarchy-hyprland-monitor-laptop)
|
||||||
|
|
||||||
|
# INTERNAL is written into generated Lua and hyprctl eval/dispatch below, so a
|
||||||
|
# name that is not a plain connector string could execute on the next reload.
|
||||||
|
# Names come from hyprctl; a user-created headless output can carry anything.
|
||||||
|
if [[ -n $INTERNAL && ! $INTERNAL =~ ^[A-Za-z0-9._-]+$ ]]; then
|
||||||
|
echo "Refusing unsafe internal monitor name" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
valid_scale() {
|
valid_scale() {
|
||||||
[[ $1 =~ ^[0-9]+([.][0-9]+)?$ ]]
|
[[ $1 =~ ^[0-9]+([.][0-9]+)?$ ]]
|
||||||
}
|
}
|
||||||
@@ -118,9 +126,17 @@ remember_internal_scale() {
|
|||||||
store_internal_scale "$scale"
|
store_internal_scale "$scale"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# $1 is the configured scale when the caller has already read it, so one sync
|
||||||
|
# does not parse the config twice.
|
||||||
read_monitor_scale() {
|
read_monitor_scale() {
|
||||||
local scale
|
local scale
|
||||||
scale=$(configured_monitor_scale)
|
|
||||||
|
if (( $# )); then
|
||||||
|
scale="$1"
|
||||||
|
else
|
||||||
|
scale=$(configured_monitor_scale)
|
||||||
|
fi
|
||||||
|
|
||||||
if valid_scale "$scale"; then
|
if valid_scale "$scale"; then
|
||||||
echo "$scale"
|
echo "$scale"
|
||||||
return
|
return
|
||||||
@@ -159,11 +175,25 @@ enable_internal_output() {
|
|||||||
|
|
||||||
sync_internal_scale() {
|
sync_internal_scale() {
|
||||||
[[ -n $INTERNAL ]] || return 0
|
[[ -n $INTERNAL ]] || return 0
|
||||||
|
local configured_scale
|
||||||
local desired_scale
|
local desired_scale
|
||||||
local active_scale
|
local active_scale
|
||||||
|
|
||||||
desired_scale=$(read_monitor_scale)
|
configured_scale=$(configured_monitor_scale)
|
||||||
active_scale=$(current_internal_scale)
|
active_scale=$(current_internal_scale)
|
||||||
|
|
||||||
|
# A config without a usable number -- the default "auto", or an expression
|
||||||
|
# only Hyprland's Lua can evaluate -- delegates the scale to the compositor,
|
||||||
|
# so whatever it resolved for the enabled panel IS the configured scale.
|
||||||
|
# There is no number to correct it toward: substituting one makes the scale
|
||||||
|
# flap between that number and the compositor's own value on every idle-wake.
|
||||||
|
# Only a panel that is off altogether still gets a hand below, from the
|
||||||
|
# remembered scale.
|
||||||
|
if ! valid_scale "$configured_scale" && valid_scale "$active_scale"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
desired_scale=$(read_monitor_scale "$configured_scale")
|
||||||
scales_match "$active_scale" "$desired_scale" && return 0
|
scales_match "$active_scale" "$desired_scale" && return 0
|
||||||
|
|
||||||
enable_internal_output "$desired_scale"
|
enable_internal_output "$desired_scale"
|
||||||
|
|||||||
@@ -28,6 +28,13 @@ off() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# The name is written into generated Lua below, so only a plain connector
|
||||||
|
# name may pass; anything else could execute on the next reload.
|
||||||
|
if [[ ! $INTERNAL =~ ^[A-Za-z0-9._-]+$ ]]; then
|
||||||
|
omarchy-notification-send -g "Refusing unsafe monitor name"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if ! omarchy-hyprland-monitor-external-active; then
|
if ! omarchy-hyprland-monitor-external-active; then
|
||||||
omarchy-notification-send -g "Can't disable the only active display"
|
omarchy-notification-send -g "Can't disable the only active display"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -22,6 +22,15 @@ on() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Both names are written into generated Lua below, so only plain connector
|
||||||
|
# names may pass; a user-created headless output can carry any name.
|
||||||
|
for output in "$INTERNAL" "$EXTERNAL"; do
|
||||||
|
if [[ ! $output =~ ^[A-Za-z0-9._-]+$ ]]; then
|
||||||
|
omarchy-notification-send -g "Refusing unsafe monitor name"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
omarchy-hyprland-toggle $DISABLE_TOGGLE off
|
omarchy-hyprland-toggle $DISABLE_TOGGLE off
|
||||||
|
|
||||||
if omarchy-hyprland-toggle-disabled $TOGGLE; then
|
if omarchy-hyprland-toggle-disabled $TOGGLE; then
|
||||||
|
|||||||
@@ -80,6 +80,14 @@ set_scale() {
|
|||||||
local width="$(echo "$monitor_info" | jq -r '.width')"
|
local width="$(echo "$monitor_info" | jq -r '.width')"
|
||||||
local height="$(echo "$monitor_info" | jq -r '.height')"
|
local height="$(echo "$monitor_info" | jq -r '.height')"
|
||||||
local refresh_rate="$(echo "$monitor_info" | jq -r '.refreshRate')"
|
local refresh_rate="$(echo "$monitor_info" | jq -r '.refreshRate')"
|
||||||
|
|
||||||
|
# active_monitor is written into the Lua string eval'd below, so only a plain
|
||||||
|
# connector name may pass; a hostile output name could execute otherwise.
|
||||||
|
if [[ ! $active_monitor =~ ^[A-Za-z0-9._-]+$ ]]; then
|
||||||
|
echo "Refusing unsafe monitor name" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
local new_scale="$(clean_scale "$requested_scale" "$width" "$height")"
|
local new_scale="$(clean_scale "$requested_scale" "$width" "$height")"
|
||||||
# GTK only honors integer GDK_SCALE values, so persist the nearest whole
|
# GTK only honors integer GDK_SCALE values, so persist the nearest whole
|
||||||
# factor even when the monitor scale itself is fractional.
|
# factor even when the monitor scale itself is fractional.
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ fi
|
|||||||
printf -v install_message '%q' "Installing ${name}..."
|
printf -v install_message '%q' "Installing ${name}..."
|
||||||
printf -v desktop_id_arg '%q' "$desktop_id"
|
printf -v desktop_id_arg '%q' "$desktop_id"
|
||||||
|
|
||||||
|
# The list has to reach omarchy-pkg-add as several words, so each word is quoted
|
||||||
|
# rather than the whole string; -d '' reads past newlines and always ends at EOF.
|
||||||
|
read -r -d '' -a package_list <<<"$packages" || true
|
||||||
|
printf -v packages_arg '%q ' "${package_list[@]}"
|
||||||
|
packages_arg="${packages_arg% }"
|
||||||
|
|
||||||
# The subshell keeps & from backgrounding the package installation too.
|
# The subshell keeps & from backgrounding the package installation too.
|
||||||
exec omarchy-launch-floating-terminal-with-presentation \
|
exec omarchy-launch-floating-terminal-with-presentation \
|
||||||
"echo ${install_message}; omarchy-pkg-add ${packages} && (setsid uwsm-app -- gtk-launch ${desktop_id_arg} >/dev/null 2>&1 &)"
|
"echo ${install_message}; omarchy-pkg-add ${packages_arg} && (setsid uwsm-app -- gtk-launch ${desktop_id_arg} >/dev/null 2>&1 &)"
|
||||||
+10
-1
@@ -12,4 +12,13 @@ if [[ -z $name || -z $packages ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec omarchy-launch-floating-terminal-with-presentation "echo 'Installing ${name}...'; omarchy-pkg-add ${packages}"
|
printf -v install_message '%q' "Installing ${name}..."
|
||||||
|
|
||||||
|
# The list has to reach omarchy-pkg-add as several words, so each word is quoted
|
||||||
|
# rather than the whole string; -d '' reads past newlines and always ends at EOF.
|
||||||
|
read -r -d '' -a package_list <<<"$packages" || true
|
||||||
|
printf -v packages_arg '%q ' "${package_list[@]}"
|
||||||
|
packages_arg="${packages_arg% }"
|
||||||
|
|
||||||
|
exec omarchy-launch-floating-terminal-with-presentation \
|
||||||
|
"echo ${install_message}; omarchy-pkg-add ${packages_arg}"
|
||||||
+10
-16
@@ -4,9 +4,10 @@
|
|||||||
# omarchy:args=<chrome|brave|brave-origin|edge|firefox|zen>
|
# omarchy:args=<chrome|brave|brave-origin|edge|firefox|zen>
|
||||||
# omarchy:examples=omarchy install browser firefox | omarchy install browser brave
|
# omarchy:examples=omarchy install browser firefox | omarchy install browser brave
|
||||||
|
|
||||||
setup_policy_directory() {
|
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||||
sudo mkdir -p "$1"
|
|
||||||
sudo chmod a+rw "$1"
|
setup_chromium_policy_directory() {
|
||||||
|
browser_policy_setup_dir "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
announce_browser_installed() {
|
announce_browser_installed() {
|
||||||
@@ -21,13 +22,6 @@ copy_chromium_flags() {
|
|||||||
omarchy-install-chromium-ytdlp
|
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() {
|
setup_firefox_wayland() {
|
||||||
mkdir -p ~/.config/environment.d
|
mkdir -p ~/.config/environment.d
|
||||||
echo "MOZ_ENABLE_WAYLAND=1" > ~/.config/environment.d/omarchy-firefox-wayland.conf
|
echo "MOZ_ENABLE_WAYLAND=1" > ~/.config/environment.d/omarchy-firefox-wayland.conf
|
||||||
@@ -38,7 +32,7 @@ chrome)
|
|||||||
echo "Installing Chrome..."
|
echo "Installing Chrome..."
|
||||||
omarchy-pkg-aur-add google-chrome || exit 1
|
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
|
copy_chromium_flags ~/.config/chrome-flags.conf
|
||||||
omarchy-theme-set-browser
|
omarchy-theme-set-browser
|
||||||
announce_browser_installed "Chrome"
|
announce_browser_installed "Chrome"
|
||||||
@@ -47,7 +41,7 @@ edge)
|
|||||||
echo "Installing Edge..."
|
echo "Installing Edge..."
|
||||||
omarchy-pkg-aur-add microsoft-edge-stable-bin || exit 1
|
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
|
copy_chromium_flags ~/.config/microsoft-edge-stable-flags.conf
|
||||||
omarchy-theme-set-browser
|
omarchy-theme-set-browser
|
||||||
announce_browser_installed "Edge"
|
announce_browser_installed "Edge"
|
||||||
@@ -56,7 +50,7 @@ brave)
|
|||||||
echo "Installing Brave..."
|
echo "Installing Brave..."
|
||||||
omarchy-pkg-aur-add brave-bin || exit 1
|
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
|
copy_chromium_flags ~/.config/brave-flags.conf
|
||||||
omarchy-theme-set-browser
|
omarchy-theme-set-browser
|
||||||
announce_browser_installed "Brave"
|
announce_browser_installed "Brave"
|
||||||
@@ -65,7 +59,7 @@ brave-origin)
|
|||||||
echo "Installing Brave Origin..."
|
echo "Installing Brave Origin..."
|
||||||
omarchy-pkg-aur-add brave-origin-bin || exit 1
|
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
|
copy_chromium_flags ~/.config/brave-origin-flags.conf
|
||||||
omarchy-theme-set-browser
|
omarchy-theme-set-browser
|
||||||
announce_browser_installed "Brave Origin"
|
announce_browser_installed "Brave Origin"
|
||||||
@@ -74,7 +68,7 @@ firefox)
|
|||||||
echo "Installing Firefox..."
|
echo "Installing Firefox..."
|
||||||
omarchy-pkg-add firefox || exit 1
|
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
|
setup_firefox_wayland
|
||||||
announce_browser_installed "Firefox"
|
announce_browser_installed "Firefox"
|
||||||
;;
|
;;
|
||||||
@@ -82,7 +76,7 @@ zen)
|
|||||||
echo "Installing Zen..."
|
echo "Installing Zen..."
|
||||||
omarchy-pkg-aur-add zen-browser-bin || exit 1
|
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
|
setup_firefox_wayland
|
||||||
announce_browser_installed "Zen"
|
announce_browser_installed "Zen"
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -13,5 +13,9 @@ if [[ -z $name || -z $package || -z $family ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
printf -v install_message '%q' "Installing ${name}..."
|
||||||
|
printf -v package_arg '%q' "$package"
|
||||||
|
printf -v family_arg '%q' "$family"
|
||||||
|
|
||||||
exec omarchy-launch-floating-terminal-with-presentation \
|
exec omarchy-launch-floating-terminal-with-presentation \
|
||||||
"echo 'Installing ${name}...'; omarchy-pkg-add ${package} && sleep 2 && omarchy-font-set '${family}'"
|
"echo ${install_message}; omarchy-pkg-add ${package_arg} && sleep 2 && omarchy-font-set ${family_arg}"
|
||||||
Executable
+20
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Open the Docker TUI (lazydocker) with access to the Docker daemon
|
||||||
|
# omarchy:hidden=true
|
||||||
|
|
||||||
|
# By default the install user is NOT in the docker group: membership is
|
||||||
|
# root-equivalent (a container can bind-mount / and rewrite the host as root),
|
||||||
|
# so a single process running as the user could otherwise escalate to root with
|
||||||
|
# no prompt. lazydocker needs the root-owned Docker socket, so when the group is
|
||||||
|
# absent, gate that access behind a polkit prompt. If the user has opted into
|
||||||
|
# sudoless Docker (omarchy-setup-security-sudoless-docker), the socket is already
|
||||||
|
# reachable, so run lazydocker directly — omarchy-sudo-docker answers that for
|
||||||
|
# this session, so the prompt stays until the reboot that grants the group.
|
||||||
|
# pkexec sanitizes the environment, so carry TERM through for the TUI to render
|
||||||
|
# and run lazydocker from root's PATH.
|
||||||
|
if omarchy-sudo-docker; then
|
||||||
|
exec pkexec /usr/bin/env TERM="${TERM:-xterm-256color}" lazydocker
|
||||||
|
else
|
||||||
|
exec lazydocker
|
||||||
|
fi
|
||||||
@@ -38,7 +38,7 @@ id=$(cut -f2 <<<"$selection")
|
|||||||
|
|
||||||
if [[ $1 == "clone" ]]; then
|
if [[ $1 == "clone" ]]; then
|
||||||
omarchy-launch-floating-terminal-with-presentation \
|
omarchy-launch-floating-terminal-with-presentation \
|
||||||
"omarchy-plugin-clone --edit $(printf '%q' "$id")"
|
"omarchy-plugin-clone $(printf '%q' "$id") --edit"
|
||||||
elif [[ $1 == "remove" ]]; then
|
elif [[ $1 == "remove" ]]; then
|
||||||
omarchy-launch-floating-terminal-with-presentation "omarchy-plugin-remove $(printf '%q' "$id")"
|
omarchy-launch-floating-terminal-with-presentation "omarchy-plugin-remove $(printf '%q' "$id")"
|
||||||
else
|
else
|
||||||
|
|||||||
+3
-3
@@ -85,16 +85,16 @@ wait_for_pacman_transaction
|
|||||||
mkdir -p "$STATE_DIR"
|
mkdir -p "$STATE_DIR"
|
||||||
[[ -d $MIGRATIONS_DIR ]] || exit 0
|
[[ -d $MIGRATIONS_DIR ]] || exit 0
|
||||||
|
|
||||||
while IFS=$'\t' read -r name file marker; do
|
while IFS=$'\t' read -r name file marker <&3; do
|
||||||
[[ -n $name ]] || continue
|
[[ -n $name ]] || continue
|
||||||
|
|
||||||
if [[ ! -f $marker ]]; then
|
if [[ ! -f $marker ]]; then
|
||||||
echo -e "\e[32m\nRunning migration (${name%.sh})\e[0m"
|
echo -e "\e[32m\nRunning migration (${name%.sh})\e[0m"
|
||||||
OMARCHY_PATH="$OMARCHY_PATH" bash -euo pipefail "$file"
|
OMARCHY_PATH="$OMARCHY_PATH" bash -euo pipefail "$file" 3<&-
|
||||||
mkdir -p "$(dirname "$marker")"
|
mkdir -p "$(dirname "$marker")"
|
||||||
touch "$marker"
|
touch "$marker"
|
||||||
fi
|
fi
|
||||||
done < <(migration_entries)
|
done 3< <(migration_entries)
|
||||||
|
|
||||||
# Clear a login-time notification the user left sitting there and then resolved
|
# Clear a login-time notification the user left sitting there and then resolved
|
||||||
# by running migrations some other way. The substring matches both the current
|
# by running migrations some other way. The substring matches both the current
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ fi
|
|||||||
# The shell keeps the click command with the toast, so this oneshot can hand the
|
# The shell keeps the click command with the toast, so this oneshot can hand the
|
||||||
# invitation over and exit instead of staying activated until it is answered.
|
# invitation over and exit instead of staying activated until it is answered.
|
||||||
omarchy-notification-send -u critical -g "Pending Omarchy Migrations" "$message" \
|
omarchy-notification-send -u critical -g "Pending Omarchy Migrations" "$message" \
|
||||||
--exec "omarchy-launch-floating-terminal-with-presentation omarchy-migrate" && exit 0
|
--exec omarchy-launch-floating-terminal-with-presentation omarchy-migrate && exit 0
|
||||||
|
|
||||||
# Reached when the notification could not be handed off, so fall back to telling
|
# Reached when the notification could not be handed off, so fall back to telling
|
||||||
# the user in the terminal.
|
# the user in the terminal.
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ rm -f "$HOME/.local/bin/$command"
|
|||||||
cat >"$HOME/.local/bin/$command" <<EOF
|
cat >"$HOME/.local/bin/$command" <<EOF
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
export MISE_MINIMUM_RELEASE_AGE=0
|
export MISE_MINIMUM_RELEASE_AGE=0
|
||||||
mise use -g "$package" || exit 1
|
mise use -g --quiet "$package" || exit 1
|
||||||
exec mise x "$package" -- "$bin" "\$@"
|
exec mise x "$package" -- "$bin" "\$@"
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|||||||
+151
-63
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# omarchy:summary=Send an Omarchy desktop notification
|
# omarchy:summary=Send an Omarchy desktop notification
|
||||||
# omarchy:args=[--exec <command>] [--app-name <app-name>] [-g <glyph>] [-u <low|normal|critical>] [--image <path-or-uri>] <headline> [description] [notify-send options]
|
# omarchy:args=[--app-name <app-name>] [-g <glyph>] [-u <low|normal|critical>] [-i <icon>] [-t <ms>] [-r <id>] [-p] [--image <path-or-uri>] <headline> [description] [--exec <program> [args...]]
|
||||||
# omarchy:examples=omarchy notification send "Reminder" "5 minutes are up" -g
|
# omarchy:examples=omarchy notification send "Reminder" "5 minutes are up" -g
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -11,61 +11,75 @@ description=""
|
|||||||
glyph=
|
glyph=
|
||||||
urgency="low"
|
urgency="low"
|
||||||
app_name="omarchy-action"
|
app_name="omarchy-action"
|
||||||
|
app_icon=""
|
||||||
image=
|
image=
|
||||||
exec_command=
|
expire_timeout=-1
|
||||||
args=()
|
replaces_id=0
|
||||||
|
print_id=0
|
||||||
|
exec_args=()
|
||||||
|
exec_present=0
|
||||||
parsed_option_args=0
|
parsed_option_args=0
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: omarchy-notification-send [--app-name <app-name>] [-g <glyph>] [-u <low|normal|critical>] [-i <icon>] [-t <ms>] [-r <id>] [-p] [--image <path-or-uri>] <headline> [description] [--exec <program> [args...]]" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Recognize a known option, in both `--flag value` and `--flag=value` forms.
|
||||||
|
# Returns 1 for anything unrecognized so the caller can decide (headline, or a
|
||||||
|
# hard error in option position).
|
||||||
parse_omarchy_option() {
|
parse_omarchy_option() {
|
||||||
case $1 in
|
local opt val nargs
|
||||||
-g | --glyph)
|
if [[ $1 == --?*=* ]]; then
|
||||||
if (($# < 2)); then
|
opt=${1%%=*}
|
||||||
echo "Missing value for $1" >&2
|
val=${1#*=}
|
||||||
exit 1
|
nargs=1
|
||||||
fi
|
else
|
||||||
glyph=$2
|
opt=$1
|
||||||
parsed_option_args=2
|
val=${2-}
|
||||||
|
nargs=2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -p/--print-id is a flag; it takes no value.
|
||||||
|
if [[ $opt == -p || $opt == --print-id ]]; then
|
||||||
|
print_id=1
|
||||||
|
parsed_option_args=1
|
||||||
return 0
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case $opt in
|
||||||
|
-g | --glyph | -u | --urgency | --app-name | -i | --icon | --image | -r | --replace-id | -t | --expire-time) ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if ((nargs == 2)) && (($# < 2)); then
|
||||||
|
echo "Missing value for $opt" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case $opt in
|
||||||
|
-g | --glyph) glyph=$val ;;
|
||||||
|
-u | --urgency) urgency=$val ;;
|
||||||
|
--app-name) app_name=$val ;;
|
||||||
|
-i | --icon) app_icon=$val ;;
|
||||||
|
--image) image=$val ;;
|
||||||
|
-r | --replace-id)
|
||||||
|
[[ $val =~ ^[0-9]+$ ]] || {
|
||||||
|
echo "Invalid $opt value (numeric id expected): $val" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
replaces_id=$val
|
||||||
;;
|
;;
|
||||||
-u | --urgency)
|
-t | --expire-time)
|
||||||
if (($# < 2)); then
|
[[ $val =~ ^-?[0-9]+$ ]] || {
|
||||||
echo "Missing value for $1" >&2
|
echo "Invalid $opt value (milliseconds expected): $val" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
}
|
||||||
urgency="$2"
|
expire_timeout=$val
|
||||||
parsed_option_args=2
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
--app-name)
|
|
||||||
if (($# < 2)); then
|
|
||||||
echo "Missing value for $1" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
app_name=$2
|
|
||||||
parsed_option_args=2
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
--image)
|
|
||||||
if (($# < 2)); then
|
|
||||||
echo "Missing value for $1" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
image=$2
|
|
||||||
parsed_option_args=2
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
--exec)
|
|
||||||
if (($# < 2)); then
|
|
||||||
echo "Missing value for $1" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
exec_command=$2
|
|
||||||
parsed_option_args=2
|
|
||||||
return 0
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
return 1
|
parsed_option_args=$nargs
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
while (($# > 0)); do
|
while (($# > 0)); do
|
||||||
@@ -77,47 +91,121 @@ while (($# > 0)); do
|
|||||||
done
|
done
|
||||||
|
|
||||||
if (($# < 1)); then
|
if (($# < 1)); then
|
||||||
echo "Usage: omarchy-notification-send [--exec <command>] [--app-name <app-name>] [-g <glyph>] [-u <low|normal|critical>] [--image <path-or-uri>] <headline> [description] [notify-send options]"
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
headline=$1
|
headline=$1
|
||||||
shift
|
shift
|
||||||
|
|
||||||
if (($# > 0)) && [[ $1 != -* ]]; then
|
# The description is the next positional, taken as text even when it begins with
|
||||||
|
# a dash — a body like "-50% off" or a negative number is content, not options.
|
||||||
|
# Only a recognized option flag or --exec in that slot is not the description.
|
||||||
|
known_flag() {
|
||||||
|
case $1 in
|
||||||
|
-g | --glyph | -u | --urgency | --app-name | -i | --icon | -t | --expire-time | --image | -r | --replace-id | -p | --print-id | --exec) return 0 ;;
|
||||||
|
--glyph=* | --urgency=* | --app-name=* | --icon=* | --expire-time=* | --image=* | --replace-id=*) return 0 ;;
|
||||||
|
esac
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($# > 0)) && ! known_flag "$1"; then
|
||||||
description=$1
|
description=$1
|
||||||
shift
|
shift
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while (($# > 0)); do
|
while (($# > 0)); do
|
||||||
if parse_omarchy_option "$@"; then
|
if [[ $1 == "--exec" ]]; then
|
||||||
|
# --exec consumes the rest of the line as the click command's argv. The
|
||||||
|
# caller's shell already tokenized those words into discrete arguments, and
|
||||||
|
# the shell runs them as-is (never re-parsed), so untrusted data in an
|
||||||
|
# argument is only ever one argument and can never become a command.
|
||||||
|
# Detected only here, after the headline/description positionals are
|
||||||
|
# captured, so an untrusted headline that is literally "--exec" is taken as
|
||||||
|
# text and can't be mistaken for the delimiter. --exec therefore comes last.
|
||||||
|
shift
|
||||||
|
exec_args=("$@")
|
||||||
|
exec_present=1
|
||||||
|
break
|
||||||
|
elif parse_omarchy_option "$@"; then
|
||||||
shift "$parsed_option_args"
|
shift "$parsed_option_args"
|
||||||
else
|
else
|
||||||
args+=("$1")
|
echo "Unknown option: $1" >&2
|
||||||
shift
|
usage
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Tag as a user-action toast so it pops through DND.
|
case $urgency in
|
||||||
args+=("-a" "$app_name" "-u" "$urgency")
|
low) urgency_byte=0 ;;
|
||||||
|
normal) urgency_byte=1 ;;
|
||||||
|
critical) urgency_byte=2 ;;
|
||||||
|
*)
|
||||||
|
echo "Unknown urgency: $urgency (use low, normal, or critical)" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# a{sv} hints, as busctl triples (key, variant type, value). urgency is a byte;
|
||||||
|
# the rest are strings. The click command rides here as omarchy-exec-argv, built
|
||||||
|
# only from --exec below.
|
||||||
|
hints=(urgency y "$urgency_byte")
|
||||||
|
|
||||||
if [[ -n $glyph ]]; then
|
if [[ -n $glyph ]]; then
|
||||||
args+=("--hint=string:omarchy-glyph:$glyph")
|
hints+=(omarchy-glyph s "$glyph")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n $image ]]; then
|
if [[ -n $image ]]; then
|
||||||
args+=("--hint=string:image-path:$image")
|
hints+=(image-path s "$image")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# The shell runs the click command itself, from a copy it keeps alongside the
|
if ((exec_present)); then
|
||||||
# on-screen popup. A libnotify action would instead keep this process blocked
|
if ((${#exec_args[@]} == 0)) || [[ -z ${exec_args[0]} ]]; then
|
||||||
# until the click, and die unanswered whenever the shell restarts underneath it.
|
echo "--exec needs a command: --exec <program> [args...]" >&2
|
||||||
if [[ -n $exec_command ]]; then
|
exit 1
|
||||||
args+=("--hint=string:omarchy-exec:$exec_command")
|
fi
|
||||||
|
# A single word with a space is almost always a whole command passed as one
|
||||||
|
# quoted string — which would run a program literally named that. Splitting it
|
||||||
|
# ourselves is exactly the injection we avoid, so reject it and point at the
|
||||||
|
# unquoted form instead.
|
||||||
|
if ((${#exec_args[@]} == 1)) && [[ ${exec_args[0]} == *[[:space:]]* ]]; then
|
||||||
|
echo "--exec takes the command as separate words, not one quoted string." >&2
|
||||||
|
echo "Write: --exec ${exec_args[0]}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# NUL-delimit into jq so every byte survives as data: jq's own --args would eat
|
||||||
|
# a bare "--", and a newline in an arg must not split the vector.
|
||||||
|
exec_argv_json=$(printf '%s\0' "${exec_args[@]}" | jq -Rsc 'split("\u0000")[:-1]')
|
||||||
|
hints+=(omarchy-exec-argv s "$exec_argv_json")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n $description ]]; then
|
hint_count=$((${#hints[@]} / 3))
|
||||||
notify-send "${args[@]}" "$headline" "$description"
|
|
||||||
|
# Call org.freedesktop.Notifications.Notify directly — never notify-send. Its
|
||||||
|
# argv parsing is the surface that reinterprets a relayed headline like
|
||||||
|
# `--hint=…` or `-rf` as options or hints; busctl takes each value as one typed
|
||||||
|
# D-Bus parameter instead, and the leading `--` keeps a dash-leading value
|
||||||
|
# (headline, description, a negative timeout) positional rather than a busctl
|
||||||
|
# option. So the summary and body are strings that can never become a hint, and
|
||||||
|
# omarchy-exec-argv is set only from --exec.
|
||||||
|
#
|
||||||
|
# Signature susssasa{sv}i: app_name, replaces_id, app_icon, summary, body,
|
||||||
|
# actions (empty), hints, expire_timeout. replaces_id (from -r) updates a toast
|
||||||
|
# in place; -p prints the returned id so a caller can reuse it.
|
||||||
|
notify_cmd=(
|
||||||
|
busctl --user -- call
|
||||||
|
org.freedesktop.Notifications /org/freedesktop/Notifications
|
||||||
|
org.freedesktop.Notifications Notify susssasa{sv}i
|
||||||
|
"$app_name" "$replaces_id" "$app_icon" "$headline" "$description"
|
||||||
|
0
|
||||||
|
"$hint_count" "${hints[@]}"
|
||||||
|
"$expire_timeout"
|
||||||
|
)
|
||||||
|
|
||||||
|
if ((print_id)); then
|
||||||
|
# busctl prints the UINT32 return as "u <id>"; emit just the id.
|
||||||
|
out=$("${notify_cmd[@]}")
|
||||||
|
printf '%s\n' "${out##* }"
|
||||||
else
|
else
|
||||||
notify-send "${args[@]}" "$headline"
|
"${notify_cmd[@]}" >/dev/null
|
||||||
fi
|
fi
|
||||||
@@ -93,6 +93,13 @@ if [[ -z $url ]]; then
|
|||||||
[[ -n $url ]] || fail "a git URL is required"
|
[[ -n $url ]] || fail "a git URL is required"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Refuse a URL that names a git option or a transport helper before cloning, so
|
||||||
|
# an untrusted URL cannot run a command before the plugin is validated or
|
||||||
|
# enabled. The check is shared with omarchy-theme-install and explains itself; a
|
||||||
|
# missing checker leaves this non-zero, which refuses the URL rather than
|
||||||
|
# cloning it.
|
||||||
|
omarchy-git-url-check "$url" || exit 1
|
||||||
|
|
||||||
if (( ! ASSUME_YES )); then
|
if (( ! ASSUME_YES )); then
|
||||||
cat >&2 <<WARN
|
cat >&2 <<WARN
|
||||||
|
|
||||||
|
|||||||
@@ -3,5 +3,7 @@
|
|||||||
# omarchy:summary=Restore the default Omarchy Plymouth boot theme and SDDM login screen
|
# omarchy:summary=Restore the default Omarchy Plymouth boot theme and SDDM login screen
|
||||||
# omarchy:requires-sudo=true
|
# omarchy:requires-sudo=true
|
||||||
|
|
||||||
omarchy-refresh-plymouth
|
set -euo pipefail
|
||||||
omarchy-refresh-sddm
|
|
||||||
|
"$OMARCHY_PATH/bin/omarchy-refresh-plymouth"
|
||||||
|
"$OMARCHY_PATH/bin/omarchy-refresh-sddm"
|
||||||
+365
-67
@@ -5,79 +5,377 @@
|
|||||||
# omarchy:examples=omarchy plymouth set '#1d2021' '#ebdbb2' ~/.local/state/omarchy/current/theme/plymouth/logo.png
|
# omarchy:examples=omarchy plymouth set '#1d2021' '#ebdbb2' ~/.local/state/omarchy/current/theme/plymouth/logo.png
|
||||||
# omarchy:requires-sudo=true
|
# omarchy:requires-sudo=true
|
||||||
|
|
||||||
# Configure the Plymouth boot theme with a custom background color, text color, and logo.
|
set -euo pipefail
|
||||||
# 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.
|
|
||||||
|
|
||||||
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
|
echo "Usage: omarchy-plymouth-set <background-hex> <text-hex> <path-to-logo.png>" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
}
|
||||||
|
|
||||||
bg_hex="${1#\#}"
|
if (( $# == 3 )); then
|
||||||
text_hex="${2#\#}"
|
mode=set
|
||||||
logo_path="$3"
|
elif (( $# == 1 )); then
|
||||||
|
case "$1" in
|
||||||
if ! [[ $bg_hex =~ ^[0-9a-fA-F]{6}$ ]]; then
|
--refresh-default)
|
||||||
echo "Invalid background color: $1 (expected #RRGGBB)" >&2
|
mode=refresh-plymouth
|
||||||
exit 1
|
;;
|
||||||
fi
|
--refresh-sddm-default)
|
||||||
|
mode=refresh-sddm
|
||||||
if ! [[ $text_hex =~ ^[0-9a-fA-F]{6}$ ]]; then
|
;;
|
||||||
echo "Invalid text color: $2 (expected #RRGGBB)" >&2
|
*)
|
||||||
exit 1
|
usage
|
||||||
fi
|
;;
|
||||||
|
esac
|
||||||
if [[ ! -f $logo_path ]]; then
|
|
||||||
echo "Logo file not found: $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
|
|
||||||
else
|
else
|
||||||
sudo mkinitcpio -P
|
usage
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Sync the SDDM login screen with the same colors and logo.
|
if (( EUID == 0 )); then
|
||||||
sddm_dir="/usr/share/sddm/themes/omarchy"
|
echo "Error: run omarchy-plymouth-set as your user, not under sudo." >&2
|
||||||
sddm_template="$OMARCHY_PATH/default/sddm/omarchy/Main.qml"
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
sed \
|
logo_fd=
|
||||||
-e "s/#1a1b26/#$bg_hex/g" \
|
if [[ $mode != "set" ]]; then
|
||||||
-e "s/#ffffff/#$text_hex/g" \
|
bg_hex=
|
||||||
"$sddm_template" | sudo tee "$sddm_dir/Main.qml" >/dev/null
|
text_hex=
|
||||||
|
else
|
||||||
|
bg_hex="${1#\#}"
|
||||||
|
text_hex="${2#\#}"
|
||||||
|
logo_path="$3"
|
||||||
|
|
||||||
sudo cp "$logo_path" "$sddm_dir/logo.png"
|
if ! [[ $bg_hex =~ ^[0-9a-fA-F]{6}$ ]]; then
|
||||||
for asset in bullet.png entry.png lock.png; do
|
echo "Invalid background color: $1 (expected #RRGGBB)" >&2
|
||||||
sudo cp "$staging_dir/$asset" "$sddm_dir/$asset"
|
exit 1
|
||||||
done
|
fi
|
||||||
for asset in entry lock; do
|
|
||||||
magick "$staging_dir/$asset.png" -channel RGB +level-colors "#f7768e","#f7768e" "$staging_dir/$asset-failed.png"
|
if ! [[ $text_hex =~ ^[0-9a-fA-F]{6}$ ]]; then
|
||||||
sudo cp "$staging_dir/$asset-failed.png" "$sddm_dir/$asset-failed.png"
|
echo "Invalid text color: $2 (expected #RRGGBB)" >&2
|
||||||
done
|
exit 1
|
||||||
sudo rm -f "$sddm_dir/logo.svg"
|
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
|
||||||
@@ -141,7 +141,8 @@ say() {
|
|||||||
notice() {
|
notice() {
|
||||||
clear_logo
|
clear_logo
|
||||||
echo
|
echo
|
||||||
gum spin --spinner "pulse" --title "$1" -- sleep "${2:-2}"
|
printf '%*s\033[32m● \033[0m%s\n' "$PADDING_LEFT" '' "$1"
|
||||||
|
sleep "${2:-2}"
|
||||||
echo
|
echo
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,6 +677,15 @@ user_groups() {
|
|||||||
if [[ -f $PROVISIONING_DIR/groups ]]; then
|
if [[ -f $PROVISIONING_DIR/groups ]]; then
|
||||||
while IFS= read -r group; do
|
while IFS= read -r group; do
|
||||||
[[ -n $group ]] || continue
|
[[ -n $group ]] || continue
|
||||||
|
# Never replay old privileged group defaults. Docker is always opt-in.
|
||||||
|
# Input is only retained when the factory image has one of the features
|
||||||
|
# whose installer deliberately grants access to raw input devices.
|
||||||
|
[[ $group == "docker" ]] && continue
|
||||||
|
if [[ $group == "input" ]] &&
|
||||||
|
! pacman -Qq xpadneo-dkms &>/dev/null &&
|
||||||
|
! pacman -Qq ydotool &>/dev/null; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
getent group "$group" >/dev/null || continue
|
getent group "$group" >/dev/null || continue
|
||||||
[[ ",$groups," == *",$group,"* ]] || groups+=",$group"
|
[[ ",$groups," == *",$group,"* ]] || groups+=",$group"
|
||||||
done <"$PROVISIONING_DIR/groups"
|
done <"$PROVISIONING_DIR/groups"
|
||||||
@@ -736,6 +746,12 @@ create_user() {
|
|||||||
# for specific commands), and a duplicate grant is harmless.
|
# for specific commands), and a duplicate grant is harmless.
|
||||||
echo "%wheel ALL=(ALL:ALL) ALL" >/etc/sudoers.d/00-omarchy-wheel
|
echo "%wheel ALL=(ALL:ALL) ALL" >/etc/sudoers.d/00-omarchy-wheel
|
||||||
chmod 440 /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() {
|
install_authorized_keys() {
|
||||||
@@ -776,7 +792,7 @@ configure_login() {
|
|||||||
# After=) is what makes it deterministic — no sleep/race against SDDM's startup.
|
# After=) is what makes it deterministic — no sleep/race against SDDM's startup.
|
||||||
install_autologin_once_cleanup() {
|
install_autologin_once_cleanup() {
|
||||||
local unit=omarchy-provision-autologin-once.service
|
local unit=omarchy-provision-autologin-once.service
|
||||||
cat >"/etc/systemd/system/$unit" <<UNIT
|
sed "s|@UNIT@|$unit|g" >"/etc/systemd/system/$unit" <<'UNIT'
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Drop the first-boot autologin before the next login
|
Description=Drop the first-boot autologin before the next login
|
||||||
Before=display-manager.service
|
Before=display-manager.service
|
||||||
@@ -785,7 +801,7 @@ ConditionPathExists=/etc/sddm.conf.d/autologin.conf
|
|||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=/usr/bin/rm -f /etc/sddm.conf.d/autologin.conf
|
ExecStart=/usr/bin/rm -f /etc/sddm.conf.d/autologin.conf
|
||||||
ExecStartPost=/usr/bin/rm -f /etc/systemd/system/graphical.target.wants/$unit /etc/systemd/system/$unit
|
ExecStartPost=/usr/bin/rm -f /etc/systemd/system/graphical.target.wants/@UNIT@ /etc/systemd/system/@UNIT@
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=graphical.target
|
WantedBy=graphical.target
|
||||||
|
|||||||
@@ -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:summary=Overwrite the user config for the Plymouth drive decryption and boot sequence with the Omarchy default and rebuild it.
|
||||||
# omarchy:requires-sudo=true
|
# omarchy:requires-sudo=true
|
||||||
|
|
||||||
sudo cp -r "$OMARCHY_PATH/default/plymouth/." /usr/share/plymouth/themes/omarchy/
|
# Reuse the fixed-file publisher so root never resolves the source checkout or
|
||||||
sudo plymouth-set-default-theme omarchy
|
# follows a destination symlink while restoring the packaged assets.
|
||||||
|
exec "$OMARCHY_PATH/bin/omarchy-plymouth-set" --refresh-default
|
||||||
if omarchy-cmd-present limine-mkinitcpio; then
|
|
||||||
sudo limine-mkinitcpio
|
|
||||||
else
|
|
||||||
sudo mkinitcpio -P
|
|
||||||
fi
|
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
# omarchy:summary=Refresh the SDDM theme from default
|
# omarchy:summary=Refresh the SDDM theme from default
|
||||||
# omarchy:requires-sudo=true
|
# omarchy:requires-sudo=true
|
||||||
|
|
||||||
sudo rm -rf /usr/share/sddm/themes/omarchy
|
# Reuse the fixed-file publisher so root never resolves an untrusted source or
|
||||||
sudo cp -r "$OMARCHY_PATH/default/sddm/omarchy" /usr/share/sddm/themes/omarchy
|
# follows a destination symlink while restoring the packaged SDDM theme.
|
||||||
|
exec "$OMARCHY_PATH/bin/omarchy-plymouth-set" --refresh-sddm-default
|
||||||
@@ -24,9 +24,14 @@ echo -e "\e[32mRemoving FIDO2 device from authentication.\n\e[0m"
|
|||||||
|
|
||||||
remove_pam_config
|
remove_pam_config
|
||||||
|
|
||||||
if [[ -d /etc/fido2 ]]; then
|
authdir=/etc/fido2
|
||||||
|
|
||||||
|
# -d follows symlinks, so a dangling link at /etc/fido2 would survive this and
|
||||||
|
# a later setup would install the authfile through it. rm -rf on a symlink
|
||||||
|
# removes the link itself, never the directory it points at.
|
||||||
|
if [[ -e $authdir || -L $authdir ]]; then
|
||||||
echo "Removing FIDO2 configuration..."
|
echo "Removing FIDO2 configuration..."
|
||||||
sudo rm -rf /etc/fido2
|
sudo rm -rf "$authdir"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Removing FIDO2 packages..."
|
echo "Removing FIDO2 packages..."
|
||||||
|
|||||||
Executable
+34
@@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Disable sudoless Docker by removing your user from the docker group
|
||||||
|
# omarchy:requires-sudo=true
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Ask about the configured groups, not this session's: right after enabling,
|
||||||
|
# sudoless Docker is on for the account even though the running session still
|
||||||
|
# needs a prompt, and this command is what turns it back off.
|
||||||
|
if omarchy-sudo-docker --configured; then
|
||||||
|
echo "Sudoless Docker is not enabled: $USER is not in the docker group."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Removing $USER from the docker group..."
|
||||||
|
sudo gpasswd -d "$USER" docker >/dev/null
|
||||||
|
|
||||||
|
# Group membership is only re-read by a fresh session, and in practice logging
|
||||||
|
# out or newgrp isn't enough — only a reboot reliably applies it. Record it so a
|
||||||
|
# later `omarchy update` still prompts (omarchy-update-restart reads this), then
|
||||||
|
# offer to do it now.
|
||||||
|
omarchy-state set reboot-required
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Sudoless Docker DISABLED. Docker access goes through a polkit/sudo prompt"
|
||||||
|
echo "again: the Docker TUI (Super + Shift + D) and the Windows VM ask when they"
|
||||||
|
echo "need it, and the plain 'docker' CLI runs under sudo. It takes effect after a reboot."
|
||||||
|
echo ""
|
||||||
|
# The migration reuses this command during 'omarchy update' and defers the
|
||||||
|
# reboot to omarchy-update-restart, so it doesn't cut the update short.
|
||||||
|
if [[ -z ${OMARCHY_DEFER_REBOOT:-} ]] && gum confirm "Reboot now to apply?"; then
|
||||||
|
omarchy-system-reboot
|
||||||
|
fi
|
||||||
@@ -63,7 +63,6 @@ relock_session() {
|
|||||||
|
|
||||||
# Each kill stops the oldest matching instance and only returns once it has
|
# Each kill stops the oldest matching instance and only returns once it has
|
||||||
# fully exited, so the no-duplicate launch below can't race a dying shell.
|
# fully exited, so the no-duplicate launch below can't race a dying shell.
|
||||||
# Requires our quickshell-git build; 0.3.0's kill returns immediately.
|
|
||||||
while timeout 5 quickshell kill -p "$CONFIG_DIR" --any-display >/dev/null 2>&1; do :; done
|
while timeout 5 quickshell kill -p "$CONFIG_DIR" --any-display >/dev/null 2>&1; do :; done
|
||||||
|
|
||||||
# Spawn from Hyprland so the shell inherits the canonical session environment,
|
# Spawn from Hyprland so the shell inherits the canonical session environment,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
# omarchy:requires-sudo=true
|
# omarchy:requires-sudo=true
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
|
||||||
check_fido2_hardware() {
|
check_fido2_hardware() {
|
||||||
@@ -50,13 +51,79 @@ if ! check_fido2_hardware; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Create the pamu2fcfg file
|
# Create the pamu2fcfg file
|
||||||
if [[ ! -f /etc/fido2/fido2 ]]; then
|
authdir=/etc/fido2
|
||||||
sudo mkdir -p /etc/fido2
|
authfile=/etc/fido2/fido2
|
||||||
|
|
||||||
|
# install -d follows a symlink here and applies the mode and ownership to
|
||||||
|
# whatever it points at, so the credential would be staged and published inside
|
||||||
|
# the link target and that directory reopened to root:root 755. This is the
|
||||||
|
# threat omarchy-remove-security-fido2 already names on its side.
|
||||||
|
if [[ -L $authdir || ( -e $authdir && ! -d $authdir ) ]]; then
|
||||||
|
echo -e "\e[31m\n$authdir is not a FIDO2 configuration directory.\e[0m"
|
||||||
|
echo "Run omarchy-remove-security-fido2 first, then set FIDO2 up again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -f follows symlinks, so the already-registered check below reads a symlinked
|
||||||
|
# authfile as a registration and leaves it in place, and is false for a
|
||||||
|
# directory, so it tries to register over one. Only a regular file is a valid
|
||||||
|
# pam_u2f authfile.
|
||||||
|
if [[ -L $authfile || ( -e $authfile && ! -f $authfile ) ]]; then
|
||||||
|
echo -e "\e[31m\n$authfile is not a FIDO2 registration file.\e[0m"
|
||||||
|
echo "Run omarchy-remove-security-fido2 first, then set FIDO2 up again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f $authfile ]]; then
|
||||||
|
sudo install -d -m 755 -o root -g root "$authdir"
|
||||||
echo -e "\e[32m\nLet's setup your device by confirming on the device now.\e[0m"
|
echo -e "\e[32m\nLet's setup your device by confirming on the device now.\e[0m"
|
||||||
echo -e "Touch your FIDO2 key when it lights up...\n"
|
echo -e "Touch your FIDO2 key when it lights up...\n"
|
||||||
|
|
||||||
if pamu2fcfg >/tmp/fido2; then
|
# A unique sibling created by root cannot be replaced by another process
|
||||||
sudo mv /tmp/fido2 /etc/fido2/fido2
|
# running as this user. Stream pamu2fcfg into it instead of asking root to
|
||||||
|
# reopen a caller-owned path: an observed temporary name could otherwise be
|
||||||
|
# replaced with a symlink before the privileged copy. The final rename is
|
||||||
|
# atomic, and -T refuses a directory at the destination. Mode 644 keeps the
|
||||||
|
# root-owned global authfile readable when pam_u2f uses openasuser; only root
|
||||||
|
# can still rewrite it.
|
||||||
|
stage=""
|
||||||
|
|
||||||
|
# mktemp's output is an operand for four privileged commands below, one of
|
||||||
|
# them an rm. Take only the name this script asked for rather than whatever
|
||||||
|
# came back on stdout.
|
||||||
|
safe_stage_path() {
|
||||||
|
local candidate=$1
|
||||||
|
local prefix="$authfile.new."
|
||||||
|
local suffix
|
||||||
|
|
||||||
|
[[ $candidate == "$prefix"* ]] || return 1
|
||||||
|
suffix=${candidate#"$prefix"}
|
||||||
|
[[ $suffix =~ ^[[:alnum:]]{6}$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_stage() {
|
||||||
|
local status=$?
|
||||||
|
|
||||||
|
if safe_stage_path "$stage"; then
|
||||||
|
sudo rm -f -- "$stage" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
return "$status"
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup_stage EXIT
|
||||||
|
stage=$(sudo mktemp "$authfile.new.XXXXXX")
|
||||||
|
|
||||||
|
if ! safe_stage_path "$stage" || [[ ! -f $stage || -L $stage ]]; then
|
||||||
|
echo -e "\e[31m\nCould not create a safe staging file beside $authfile.\e[0m"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if pamu2fcfg | sudo tee "$stage" >/dev/null && [[ -s $stage ]]; then
|
||||||
|
sudo chmod 644 "$stage"
|
||||||
|
sudo mv -Tf "$stage" "$authfile"
|
||||||
|
stage=""
|
||||||
|
trap - EXIT
|
||||||
echo -e "\e[32mFIDO2 device registered successfully!\e[0m"
|
echo -e "\e[32mFIDO2 device registered successfully!\e[0m"
|
||||||
else
|
else
|
||||||
echo -e "\e[31m\nFIDO2 registration failed. Please try again.\e[0m"
|
echo -e "\e[31m\nFIDO2 registration failed. Please try again.\e[0m"
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ setup_pam_config() {
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "Creating polkit configuration with fingerprint authentication..."
|
echo "Creating polkit configuration with fingerprint authentication..."
|
||||||
|
# omarchy:heredoc-expands paths=none -- $fprintd_gate is the literal PAM
|
||||||
|
# line defined above, shared with the two sed insertions so the gate cannot
|
||||||
|
# drift between files. The only path in it is the fixed /usr/bin one.
|
||||||
sudo tee /etc/pam.d/polkit-1 >/dev/null <<EOF
|
sudo tee /etc/pam.d/polkit-1 >/dev/null <<EOF
|
||||||
$fprintd_gate
|
$fprintd_gate
|
||||||
auth sufficient pam_fprintd.so
|
auth sufficient pam_fprintd.so
|
||||||
|
|||||||
@@ -107,6 +107,50 @@ authorize_pasted_key() {
|
|||||||
authorize_key "$key" || exit 1
|
authorize_key "$key" || exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Only called after a key is authorized. Disabling password authentication
|
||||||
|
# before then could lock the owner out of the machine.
|
||||||
|
disable_password_auth() {
|
||||||
|
local config=/etc/ssh/sshd_config.d/10-omarchy-hardening.conf
|
||||||
|
local effective_config
|
||||||
|
|
||||||
|
if [[ ! -s $AUTHORIZED_KEYS ]]; then
|
||||||
|
echo -e "\e[31mCannot disable SSH password authentication without an authorized key.\e[0m" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Disabling SSH password authentication, now that a key is authorized..."
|
||||||
|
sudo install -Dm644 /dev/stdin "$config" <<'CONF'
|
||||||
|
# Written by omarchy-setup-security-sshd once an SSH key was authorized.
|
||||||
|
# Delete this file and reload sshd to allow password logins again.
|
||||||
|
PasswordAuthentication no
|
||||||
|
KbdInteractiveAuthentication no
|
||||||
|
CONF
|
||||||
|
|
||||||
|
# Validate before reloading: a config sshd rejects would otherwise take the
|
||||||
|
# service down on its next restart, potentially stranding a remote owner.
|
||||||
|
if ! sudo sshd -t; then
|
||||||
|
echo -e "\e[31msshd rejected the hardening config; removing it and leaving passwords on.\e[0m" >&2
|
||||||
|
sudo rm -f "$config"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Syntax alone is insufficient because sshd uses the first value it reads for
|
||||||
|
# these settings. An earlier administrator rule could leave passwords enabled.
|
||||||
|
# Match keywords case-insensitively: OpenSSH 9.x dumps them lowercase, 10.x
|
||||||
|
# in CamelCase.
|
||||||
|
if ! effective_config=$(sudo sshd -T) ||
|
||||||
|
! grep -qixF "passwordauthentication no" <<<"$effective_config" ||
|
||||||
|
! grep -qixF "kbdinteractiveauthentication no" <<<"$effective_config"; then
|
||||||
|
echo -e "\e[31msshd did not apply the password-authentication restrictions; removing the ineffective config.\e[0m" >&2
|
||||||
|
sudo rm -f "$config"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Reload rather than restart so an administrator already connected keeps
|
||||||
|
# their session.
|
||||||
|
sudo systemctl reload sshd.service
|
||||||
|
}
|
||||||
|
|
||||||
echo -e "\e[32mSetting up SSH server access with key-based authentication.\n\e[0m"
|
echo -e "\e[32mSetting up SSH server access with key-based authentication.\n\e[0m"
|
||||||
|
|
||||||
setup_sshd
|
setup_sshd
|
||||||
@@ -123,5 +167,8 @@ else
|
|||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
disable_password_auth
|
||||||
|
|
||||||
echo -e "\e[32m\nPerfect! The SSH server is running and your key is authorized.\e[0m"
|
echo -e "\e[32m\nPerfect! The SSH server is running and your key is authorized.\e[0m"
|
||||||
|
echo "Password logins are off; this machine now accepts authorized keys only."
|
||||||
echo "You can now connect with: ssh $USER@$(hostname)"
|
echo "You can now connect with: ssh $USER@$(hostname)"
|
||||||
Executable
+49
@@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Enable sudoless Docker by adding your user to the docker group (root-equivalent!)
|
||||||
|
# omarchy:requires-sudo=true
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Ask about the configured groups, not this session's: once enabled it stays
|
||||||
|
# enabled for the account even before the reboot that lets this session use it.
|
||||||
|
if ! omarchy-sudo-docker --configured; then
|
||||||
|
echo "Sudoless Docker is already enabled: $USER is in the docker group."
|
||||||
|
echo "To disable it again, run: omarchy-remove-security-sudoless-docker"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "⚠️ WARNING: Enabling sudoless Docker adds you to the 'docker' group."
|
||||||
|
echo ""
|
||||||
|
echo "The Docker daemon runs as root, so membership in the docker group is"
|
||||||
|
echo "equivalent to passwordless root. Any process running as your user could"
|
||||||
|
echo "then run, for example:"
|
||||||
|
echo ""
|
||||||
|
echo " docker run -v /:/host alpine # full read/write of the host, as root"
|
||||||
|
echo ""
|
||||||
|
echo "and take over the machine with no password prompt. A single rogue script,"
|
||||||
|
echo "dependency, or plugin running as you is enough. It is convenient for"
|
||||||
|
echo "development, but it removes the protection Omarchy keeps by default, where"
|
||||||
|
echo "Docker access goes through a polkit/sudo prompt."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if gum confirm "Enable sudoless Docker? This gives anything running as you passwordless root."; then
|
||||||
|
sudo usermod -aG docker "$USER"
|
||||||
|
# A new docker group membership is only picked up by a fresh session, and in
|
||||||
|
# practice logging out or newgrp isn't enough — only a reboot reliably applies
|
||||||
|
# it. Record it so a later `omarchy update` still prompts
|
||||||
|
# (omarchy-update-restart reads this), then offer to do it now.
|
||||||
|
omarchy-state set reboot-required
|
||||||
|
echo ""
|
||||||
|
echo "Sudoless Docker ENABLED. It takes effect after a reboot."
|
||||||
|
echo "To disable it again: Setup > Security > Sudoless Docker."
|
||||||
|
echo ""
|
||||||
|
# The migration reuses this command during 'omarchy update' and defers the
|
||||||
|
# reboot to omarchy-update-restart, so it doesn't cut the update short.
|
||||||
|
if [[ -z ${OMARCHY_DEFER_REBOOT:-} ]] && gum confirm "Reboot now to apply?"; then
|
||||||
|
omarchy-system-reboot
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Aborted. No changes made. Docker access still goes through a prompt."
|
||||||
|
fi
|
||||||
+15
-3
@@ -1,6 +1,18 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# omarchy:summary=Display a "Done!" message with a spinner and wait for user to press any key.
|
# omarchy:summary=Display a "Done!" message and wait for user to press any key.
|
||||||
|
|
||||||
echo
|
# The device node is there whether or not a terminal is behind it, so opening
|
||||||
gum spin --spinner "globe" --title "Done! Press any key to close..." -- bash -c 'read -n 1 -s'
|
# it is the only test that means anything.
|
||||||
|
: 2>/dev/null <>/dev/tty || exit 0
|
||||||
|
|
||||||
|
# gum 2.0 no longer lets a spun command read the terminal, so the wait has to
|
||||||
|
# happen here. Its own query replies are still queued on the tty; drop those or
|
||||||
|
# they answer the keypress for the user.
|
||||||
|
while read -rsn 1 -t 0.1 _ </dev/tty; do :; done
|
||||||
|
|
||||||
|
# Prompt on the terminal rather than stdout, or a caller that redirects us
|
||||||
|
# leaves the user waiting on a prompt they were never shown.
|
||||||
|
printf '\n\033[32m● \033[0mDone! Press any key to close...' >/dev/tty
|
||||||
|
read -rsn 1 </dev/tty
|
||||||
|
echo >/dev/tty
|
||||||
Executable
+44
@@ -0,0 +1,44 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Succeed when Docker needs sudo, fail when it can be used directly
|
||||||
|
# omarchy:args=[--configured]
|
||||||
|
# omarchy:examples=omarchy-sudo-docker && echo "needs sudo" | omarchy-sudo-docker --configured
|
||||||
|
# omarchy:hidden=true
|
||||||
|
|
||||||
|
# The docker group is root-equivalent, so Omarchy leaves users out of it by
|
||||||
|
# default and reaches the daemon through a prompt instead. Everything that has
|
||||||
|
# to make that choice asks here rather than testing group membership itself.
|
||||||
|
#
|
||||||
|
# Two questions, because they have different answers between toggling sudoless
|
||||||
|
# Docker and the reboot that applies it (group membership is fixed when the
|
||||||
|
# session is created):
|
||||||
|
#
|
||||||
|
# (default) Does Docker need sudo *right now*? Answered by whether this
|
||||||
|
# process can actually reach the socket, which is what decides
|
||||||
|
# if a command must elevate. Still true in the window after
|
||||||
|
# sudoless Docker is enabled but before the reboot.
|
||||||
|
# --configured Will it need sudo once the account's groups take effect?
|
||||||
|
# Answered from the account's configured groups, so the menu
|
||||||
|
# offers the toggle that can actually change state.
|
||||||
|
#
|
||||||
|
# Succeeds (exit 0) when sudo is needed, so it reads as `if omarchy-sudo-docker`.
|
||||||
|
|
||||||
|
DOCKER_SOCKET="${OMARCHY_DOCKER_SOCKET:-/var/run/docker.sock}"
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
--configured)
|
||||||
|
# An account in the docker group will not need sudo after the next login.
|
||||||
|
id -nG "$USER" 2>/dev/null | grep -qw docker && exit 1
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
"")
|
||||||
|
# A socket we can write is a daemon we can drive without elevating. A missing
|
||||||
|
# socket counts as needing sudo: reaching it means starting it as root anyway.
|
||||||
|
[[ -w $DOCKER_SOCKET ]] && exit 1
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: omarchy-sudo-docker [--configured]" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# omarchy:summary=Reset the sudo lockout/faillock for the current user.
|
|
||||||
|
|
||||||
su -c "faillock --reset --user $USER"
|
|
||||||
@@ -59,7 +59,7 @@ claim_path() {
|
|||||||
announce() {
|
announce() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
local name="${path##*/}"
|
local name="${path##*/}"
|
||||||
local args=("Received $name" "Saved to ${dir/#$HOME/~}")
|
local args=("Received $name" "Saved to ${dir/#$HOME/~}" -u critical)
|
||||||
|
|
||||||
case "${name,,}" in
|
case "${name,,}" in
|
||||||
*.png | *.jpg | *.jpeg | *.gif | *.webp | *.avif | *.bmp | *.tif | *.tiff)
|
*.png | *.jpg | *.jpeg | *.gif | *.webp | *.avif | *.bmp | *.tif | *.tiff)
|
||||||
@@ -72,7 +72,7 @@ announce() {
|
|||||||
|
|
||||||
# Announcing is best-effort: the file is already delivered, and under `set -e`
|
# Announcing is best-effort: the file is already delivered, and under `set -e`
|
||||||
# a notification outage would otherwise kill the long-running receiver.
|
# a notification outage would otherwise kill the long-running receiver.
|
||||||
omarchy-notification-send "${args[@]}" --exec "$(printf 'xdg-open %q' "$path")" || true
|
omarchy-notification-send "${args[@]}" --exec xdg-open "$path" || true
|
||||||
}
|
}
|
||||||
|
|
||||||
deliver() {
|
deliver() {
|
||||||
|
|||||||
Executable
+17
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=List the user-installed themes that came from a git clone
|
||||||
|
# omarchy:examples=omarchy theme extras
|
||||||
|
|
||||||
|
# Exits nonzero when there are none, so a caller can ask whether any exist
|
||||||
|
# without reading the list. A symlinked theme is someone's working copy and a
|
||||||
|
# `.git` file is a worktree pointing elsewhere; neither is ours to pull.
|
||||||
|
status=1
|
||||||
|
|
||||||
|
for theme in ~/.config/omarchy/themes/*; do
|
||||||
|
[[ ! -L $theme && -d $theme/.git ]] || continue
|
||||||
|
echo "$theme"
|
||||||
|
status=0
|
||||||
|
done
|
||||||
|
|
||||||
|
exit $status
|
||||||
@@ -16,24 +16,48 @@ if [[ -z $REPO_URL ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Refuse a URL that names a git option or a transport helper before cloning. The
|
||||||
|
# check is shared with omarchy-plugin-add and explains itself; a missing checker
|
||||||
|
# leaves this non-zero, which refuses the URL rather than cloning it.
|
||||||
|
omarchy-git-url-check "$REPO_URL" || exit 1
|
||||||
|
|
||||||
THEMES_DIR="$HOME/.config/omarchy/themes"
|
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_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_NAME=$(basename -- "$REPO_PATH" .git | sed -E 's/^omarchy-//; s/-theme$//' | tr '[:upper:]' '[:lower:]')
|
||||||
THEME_PATH="$THEMES_DIR/$THEME_NAME"
|
THEME_PATH="$THEMES_DIR/$THEME_NAME"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
# Remove existing theme if present
|
# Remove existing theme if present
|
||||||
if [[ -d $THEME_PATH ]]; then
|
if [[ -d $THEME_PATH ]]; then
|
||||||
rm -rf "$THEME_PATH"
|
rm -rf "$THEME_PATH"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Clone the repo directly to ~/.config/omarchy/themes
|
# Clone the repo directly to ~/.config/omarchy/themes
|
||||||
if ! git clone "$REPO_URL" "$THEME_PATH"; then
|
if ! git clone -- "$REPO_URL" "$THEME_PATH"; then
|
||||||
echo "Error: Failed to clone theme repo."
|
echo "Error: Failed to clone theme repo."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Apply the new theme with omarchy-theme-set
|
# Apply the new theme with omarchy-theme-set, which stages only the files an
|
||||||
omarchy-theme-set $THEME_NAME
|
# extra theme is allowed to contribute and names anything it dropped.
|
||||||
|
omarchy-theme-set "$THEME_NAME"
|
||||||
@@ -21,8 +21,9 @@ fi
|
|||||||
THEMES_DIR="$HOME/.config/omarchy/themes"
|
THEMES_DIR="$HOME/.config/omarchy/themes"
|
||||||
THEME_PATH="$THEMES_DIR/$THEME_NAME"
|
THEME_PATH="$THEMES_DIR/$THEME_NAME"
|
||||||
|
|
||||||
# Ensure a theme was set
|
# Ensure a theme was set, and that the name cannot climb out of THEMES_DIR
|
||||||
if [[ -z $THEME_NAME ]]; then
|
# on its way into the rm below.
|
||||||
|
if [[ -z $THEME_NAME || $THEME_NAME == .* || $THEME_NAME == */* ]]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+130
-2
@@ -17,6 +17,19 @@ THEME_SET_LOCK="${XDG_RUNTIME_DIR:-/tmp}/omarchy-theme-set.lock"
|
|||||||
USER_THEMES_PATH="$HOME/.config/omarchy/themes"
|
USER_THEMES_PATH="$HOME/.config/omarchy/themes"
|
||||||
OMARCHY_THEMES_PATH="$OMARCHY_PATH/themes"
|
OMARCHY_THEMES_PATH="$OMARCHY_PATH/themes"
|
||||||
|
|
||||||
|
# What a theme installed from a git repo may not ship, because these run code.
|
||||||
|
# Hyprland requires a theme's hyprland.lua and gum_env.lua at login and Neovim
|
||||||
|
# loads its neovim.lua at startup, so no .lua from such a theme is staged at all.
|
||||||
|
# Each terminal config names the program the terminal launches, and vscode.json
|
||||||
|
# names an extension omarchy-theme-set-vscode installs, a VS Code extension being
|
||||||
|
# arbitrary JavaScript. Everything else a theme ships is colour and is kept.
|
||||||
|
#
|
||||||
|
# Adding a template for another terminal, or for another editor that loads Lua,
|
||||||
|
# means adding it here. test/shell.d/theme-staging-test.sh fails on a generated
|
||||||
|
# theme file that is neither denied here nor recorded there as colour-only.
|
||||||
|
INSTALLED_THEME_DENIED=(alacritty.toml foot.ini ghostty.conf kitty.conf vscode.json)
|
||||||
|
IGNORED_THEME_FILES=()
|
||||||
|
|
||||||
run_parallel() {
|
run_parallel() {
|
||||||
local pid
|
local pid
|
||||||
local pids=()
|
local pids=()
|
||||||
@@ -122,12 +135,121 @@ set_theme_background() {
|
|||||||
ln -nsf "$new_background" "$CURRENT_BACKGROUND_LINK"
|
ln -nsf "$new_background" "$CURRENT_BACKGROUND_LINK"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_denied_installed_file() {
|
||||||
|
local name="$1"
|
||||||
|
local denied
|
||||||
|
|
||||||
|
[[ $name == *.lua ]] && return 0
|
||||||
|
|
||||||
|
for denied in "${INSTALLED_THEME_DENIED[@]}"; do
|
||||||
|
[[ $name == "$denied" ]] && return 0
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Themes older than colors.toml still get their palette, but their
|
||||||
|
# alacritty.toml never reaches the staged theme: an Alacritty config names the
|
||||||
|
# program the terminal launches.
|
||||||
|
stage_installed_colors_from_alacritty() {
|
||||||
|
local source="$1"
|
||||||
|
local scratch
|
||||||
|
|
||||||
|
if [[ -f $NEXT_THEME_PATH/colors.toml ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f $source/alacritty.toml || -L $source/alacritty.toml ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
scratch=$(mktemp -d)
|
||||||
|
cp "$source/alacritty.toml" "$scratch/alacritty.toml"
|
||||||
|
omarchy-theme-colors-from-alacritty "$scratch"
|
||||||
|
|
||||||
|
if [[ -f $scratch/colors.toml ]]; then
|
||||||
|
cp "$scratch/colors.toml" "$NEXT_THEME_PATH/colors.toml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$scratch"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copies a directory without ever following a symlink: in an installed theme one
|
||||||
|
# points wherever the theme author chose, which is how an unlock.png becomes a
|
||||||
|
# copy of any file the session can read.
|
||||||
|
stage_installed_dir() {
|
||||||
|
local source="$1"
|
||||||
|
local dest="$2"
|
||||||
|
local entry name
|
||||||
|
|
||||||
|
mkdir -p "$dest"
|
||||||
|
|
||||||
|
for entry in "$source"/*; do
|
||||||
|
[[ -e $entry && ! -L $entry ]] || continue
|
||||||
|
name=${entry##*/}
|
||||||
|
|
||||||
|
if [[ -d $entry ]]; then
|
||||||
|
stage_installed_dir "$entry" "$dest/$name"
|
||||||
|
else
|
||||||
|
cp "$entry" "$dest/$name"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# `omarchy theme install` clones into ~/.config/omarchy/themes, so a .git
|
||||||
|
# directory there means the contents came from a stranger and are held to the
|
||||||
|
# list above. A directory the user wrote themselves, and a symlink to their own
|
||||||
|
# working copy, are theirs to fill however they like -- the same distinction
|
||||||
|
# omarchy-theme-extras draws when it decides which themes it may pull.
|
||||||
|
theme_came_from_a_repo() {
|
||||||
|
local source="$1"
|
||||||
|
|
||||||
|
[[ ! -L $source && -d $source/.git ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
stage_installed_theme() {
|
||||||
|
local source="$1"
|
||||||
|
local entry name
|
||||||
|
|
||||||
|
[[ -d $source ]] || return 0
|
||||||
|
|
||||||
|
for entry in "$source"/*; do
|
||||||
|
[[ -e $entry ]] || continue
|
||||||
|
name=${entry##*/}
|
||||||
|
|
||||||
|
if [[ -L $entry ]] || is_denied_installed_file "$name"; then
|
||||||
|
case "${name,,}" in
|
||||||
|
readme* | license* | changelog* | *.md | *.txt) ;;
|
||||||
|
*) IGNORED_THEME_FILES+=("$name") ;;
|
||||||
|
esac
|
||||||
|
elif [[ -d $entry ]]; then
|
||||||
|
stage_installed_dir "$entry" "$NEXT_THEME_PATH/$name"
|
||||||
|
else
|
||||||
|
cp "$entry" "$NEXT_THEME_PATH/$name"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
stage_installed_colors_from_alacritty "$source"
|
||||||
|
}
|
||||||
|
|
||||||
|
report_ignored_theme_files() {
|
||||||
|
(( ${#IGNORED_THEME_FILES[@]} > 0 )) || return 0
|
||||||
|
|
||||||
|
echo "Ignored in $USER_THEMES_PATH/$THEME_NAME: ${IGNORED_THEME_FILES[*]}" >&2
|
||||||
|
echo "A theme installed from a git repo cannot supply Lua, a terminal config, or vscode.json." >&2
|
||||||
|
}
|
||||||
|
|
||||||
THEME_NAME=$(echo "$1" | sed -E 's/<[^>]+>//g' | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
|
THEME_NAME=$(echo "$1" | sed -E 's/<[^>]+>//g' | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
|
||||||
THEME_HEADLESS=0
|
THEME_HEADLESS=0
|
||||||
if [[ ${OMARCHY_THEME_HEADLESS:-} == "1" || ${OMARCHY_THEME_OFFLINE:-} == "1" ]]; then
|
if [[ ${OMARCHY_THEME_HEADLESS:-} == "1" || ${OMARCHY_THEME_OFFLINE:-} == "1" ]]; then
|
||||||
THEME_HEADLESS=1
|
THEME_HEADLESS=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ -z $THEME_NAME || $THEME_NAME == .* || $THEME_NAME == */* ]]; then
|
||||||
|
echo "Invalid theme name: $1"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ ! -d $OMARCHY_THEMES_PATH/$THEME_NAME ]] && [[ ! -d $USER_THEMES_PATH/$THEME_NAME ]]; then
|
if [[ ! -d $OMARCHY_THEMES_PATH/$THEME_NAME ]] && [[ ! -d $USER_THEMES_PATH/$THEME_NAME ]]; then
|
||||||
echo "Theme '$THEME_NAME' does not exist"
|
echo "Theme '$THEME_NAME' does not exist"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -143,9 +265,15 @@ flock 9
|
|||||||
rm -rf "$NEXT_THEME_PATH"
|
rm -rf "$NEXT_THEME_PATH"
|
||||||
mkdir -p "$NEXT_THEME_PATH"
|
mkdir -p "$NEXT_THEME_PATH"
|
||||||
|
|
||||||
# Copy official theme first, then overlay user customizations on top
|
# Copy official theme first, then overlay the user's theme on top
|
||||||
cp -r "$OMARCHY_THEMES_PATH/$THEME_NAME/"* "$NEXT_THEME_PATH/" 2>/dev/null
|
cp -r "$OMARCHY_THEMES_PATH/$THEME_NAME/"* "$NEXT_THEME_PATH/" 2>/dev/null
|
||||||
cp -r "$USER_THEMES_PATH/$THEME_NAME/"* "$NEXT_THEME_PATH/" 2>/dev/null
|
|
||||||
|
if theme_came_from_a_repo "$USER_THEMES_PATH/$THEME_NAME"; then
|
||||||
|
stage_installed_theme "$USER_THEMES_PATH/$THEME_NAME"
|
||||||
|
report_ignored_theme_files
|
||||||
|
else
|
||||||
|
cp -r "$USER_THEMES_PATH/$THEME_NAME/"* "$NEXT_THEME_PATH/" 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
# Generate colors.toml from alacritty.toml if theme is missing colors.toml
|
# Generate colors.toml from alacritty.toml if theme is missing colors.toml
|
||||||
if [[ ! -f $NEXT_THEME_PATH/colors.toml && -f $NEXT_THEME_PATH/alacritty.toml ]]; then
|
if [[ ! -f $NEXT_THEME_PATH/colors.toml && -f $NEXT_THEME_PATH/alacritty.toml ]]; then
|
||||||
|
|||||||
@@ -3,23 +3,15 @@
|
|||||||
# omarchy:summary=Apply the current theme color to Chromium, Chrome, Edge, and Brave
|
# omarchy:summary=Apply the current theme color to Chromium, Chrome, Edge, and Brave
|
||||||
# omarchy:hidden=true
|
# omarchy:hidden=true
|
||||||
|
|
||||||
|
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||||
|
|
||||||
CHROMIUM_THEME=$HOME/.local/state/omarchy/current/theme/chromium.theme
|
CHROMIUM_THEME=$HOME/.local/state/omarchy/current/theme/chromium.theme
|
||||||
|
THEME_HEX_COLOR=$BROWSER_POLICY_DEFAULT_COLOR
|
||||||
|
|
||||||
if [[ -f $CHROMIUM_THEME ]]; then
|
if [[ -f $CHROMIUM_THEME ]]; then
|
||||||
THEME_RGB_COLOR=$(<$CHROMIUM_THEME)
|
THEME_HEX_COLOR=$(browser_policy_theme_hex "$(<$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"
|
|
||||||
fi
|
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() {
|
refresh_running_browser() {
|
||||||
local process="$1"
|
local process="$1"
|
||||||
local command="$2"
|
local command="$2"
|
||||||
@@ -30,17 +22,15 @@ refresh_running_browser() {
|
|||||||
fi
|
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
|
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
|
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
|
refresh_running_browser msedge microsoft-edge-stable
|
||||||
|
|
||||||
set_browser_policy /etc/brave/policies/managed
|
|
||||||
refresh_running_browser brave brave
|
refresh_running_browser brave brave
|
||||||
# Match on the binary path: the running process is named plain "brave", and a
|
# 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.
|
# bare -f brave-origin pattern would also match the installer's own terminal.
|
||||||
refresh_running_browser /opt/brave-origin-bin/ brave-origin -f
|
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"
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
# omarchy:summary=Update user-installed git themes
|
# omarchy:summary=Update user-installed git themes
|
||||||
|
|
||||||
for dir in ~/.config/omarchy/themes/*/; do
|
mapfile -t themes < <(omarchy-theme-extras)
|
||||||
if [[ -d $dir ]] && [[ ! -L ${dir%/} ]] && [[ -d $dir/.git ]]; then
|
|
||||||
echo "Updating: $(basename "$dir")"
|
for theme in "${themes[@]}"; do
|
||||||
git -C "$dir" pull
|
echo "Updating: $(basename "$theme")"
|
||||||
fi
|
git -C "$theme" pull
|
||||||
done
|
done
|
||||||
@@ -5,3 +5,9 @@
|
|||||||
# omarchy:examples=omarchy toggle bar | omarchy toggle bar off | omarchy toggle bar on
|
# omarchy:examples=omarchy toggle bar | omarchy toggle bar off | omarchy toggle bar on
|
||||||
|
|
||||||
omarchy-toggle bar-off "${1:-toggle}"
|
omarchy-toggle bar-off "${1:-toggle}"
|
||||||
|
|
||||||
|
# The shell's watch on the toggles directory can miss flag changes that land in
|
||||||
|
# quick succession, stranding the bar off screen until the shell restarts.
|
||||||
|
# Nudge the bar to re-read the flag; quiet best-effort so the toggle still
|
||||||
|
# works when the shell is not up.
|
||||||
|
omarchy-shell -q omarchy.bar syncHidden
|
||||||
@@ -7,44 +7,70 @@
|
|||||||
KIND="${1:-}"
|
KIND="${1:-}"
|
||||||
ACTION="${2:-toggle}"
|
ACTION="${2:-toggle}"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: omarchy-toggle-input-device <touchpad|touchscreen> [on|off|toggle]" >&2
|
||||||
|
}
|
||||||
|
|
||||||
case "$KIND" in
|
case "$KIND" in
|
||||||
touchpad) LABEL="Touchpad" ICON="touchpad" ;;
|
touchpad) LABEL="Touchpad" ICON="touchpad" ;;
|
||||||
touchscreen) LABEL="Touchscreen" ICON="touch" ;;
|
touchscreen) LABEL="Touchscreen" ICON="touch" ;;
|
||||||
*)
|
*)
|
||||||
echo "Usage: omarchy-toggle-input-device <touchpad|touchscreen> [on|off|toggle]" >&2
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Hyprland sources this directory on reload, so the disabled state survives restarts
|
# The persisted disable is the device name stored as plain data; on every
|
||||||
STATE_FILE="$HOME/.local/state/omarchy/toggles/hypr/$KIND-disabled.lua"
|
# reload default/hypr/disabled-input-device.lua reads it back and disables the
|
||||||
|
# device. Names come from USB descriptors and must not be interpolated into
|
||||||
|
# shell or Lua. The path is hardcoded to ~/.local/state like the sibling
|
||||||
|
# toggle tools, so it keeps working when XDG_STATE_HOME diverges.
|
||||||
|
NAME_FILE="$HOME/.local/state/omarchy/toggles/hypr/$KIND-disabled-name"
|
||||||
|
|
||||||
device="$("omarchy-hw-$KIND")"
|
device="$("omarchy-hw-$KIND")"
|
||||||
|
|
||||||
if [[ -z $device ]]; then
|
require_device() {
|
||||||
echo "No $KIND device found" >&2
|
if [[ -z $device ]]; then
|
||||||
exit 1
|
echo "No $KIND device found" >&2
|
||||||
fi
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $device == *[[:cntrl:]]* ]]; then
|
||||||
|
echo "Invalid $KIND device name" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_device() {
|
||||||
|
local enabled=$1
|
||||||
|
local quoted=${device//\\/\\\\}
|
||||||
|
quoted=${quoted//\"/\\\"}
|
||||||
|
hyprctl eval "hl.device({ name = \"$quoted\", enabled = $enabled })" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
enable() {
|
enable() {
|
||||||
hyprctl eval "hl.device({ name = \"$device\", enabled = true })" >/dev/null
|
# Clear the persisted state before requiring a usable device, so a device
|
||||||
rm -f "$STATE_FILE"
|
# that stops reporting a valid name can never wedge the disable in place.
|
||||||
|
rm -f "$NAME_FILE"
|
||||||
|
require_device
|
||||||
|
apply_device true
|
||||||
omarchy-osd -i "$ICON" -m "$LABEL enabled"
|
omarchy-osd -i "$ICON" -m "$LABEL enabled"
|
||||||
}
|
}
|
||||||
|
|
||||||
disable() {
|
disable() {
|
||||||
hyprctl eval "hl.device({ name = \"$device\", enabled = false })" >/dev/null
|
require_device
|
||||||
mkdir -p "$(dirname "$STATE_FILE")"
|
apply_device false
|
||||||
printf 'hl.device({ name = "%s", enabled = false })\n' "$device" >"$STATE_FILE"
|
mkdir -p "$(dirname "$NAME_FILE")"
|
||||||
|
printf '%s\n' "$device" >"$NAME_FILE"
|
||||||
omarchy-osd -i "$ICON" -m "$LABEL disabled"
|
omarchy-osd -i "$ICON" -m "$LABEL disabled"
|
||||||
}
|
}
|
||||||
|
|
||||||
case "$ACTION" in
|
case "$ACTION" in
|
||||||
on) enable ;;
|
on) enable ;;
|
||||||
off) disable ;;
|
off) disable ;;
|
||||||
toggle) if [[ -f $STATE_FILE ]]; then enable; else disable; fi ;;
|
toggle) if [[ -f $NAME_FILE ]]; then enable; else disable; fi ;;
|
||||||
*)
|
*)
|
||||||
echo "Usage: omarchy-toggle-input-device <touchpad|touchscreen> [on|off|toggle]" >&2
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -597,6 +597,9 @@ preserve_kernel_cmdline_root() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
log "Preserving the kernel cmdline root parameters in $default_conf"
|
log "Preserving the kernel cmdline root parameters in $default_conf"
|
||||||
|
# omarchy:heredoc-expands paths=none -- ${boot_params[*]} is the kernel
|
||||||
|
# cmdline fragment assembled above from findmnt output (root=, rootflags=),
|
||||||
|
# not a filesystem path this script controls.
|
||||||
as_root tee -a "$default_conf" >/dev/null <<EOF
|
as_root tee -a "$default_conf" >/dev/null <<EOF
|
||||||
# Written by omarchy-upgrade-to-quattro. The += drop-ins in
|
# Written by omarchy-upgrade-to-quattro. The += drop-ins in
|
||||||
# /etc/limine-entry-tool.d/ stop limine-entry-tool from reading
|
# /etc/limine-entry-tool.d/ stop limine-entry-tool from reading
|
||||||
@@ -1187,11 +1190,19 @@ ensure_sleep_lock_service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
run_post_upgrade_migrations() {
|
run_post_upgrade_migrations() {
|
||||||
PATH="$package_path" command -v omarchy-migrate >/dev/null 2>&1 || return 0
|
local pending_status
|
||||||
|
|
||||||
log "Running Omarchy migrations"
|
log "Running Omarchy migrations"
|
||||||
if ! run_as_user_omarchy OMARCHY_UPGRADE_TO_QUATTRO_LIVE=1 omarchy-migrate; then
|
if ! run_as_user_omarchy OMARCHY_UPGRADE_TO_QUATTRO_LIVE=1 omarchy-migrate; then
|
||||||
warn "Could not run Omarchy migrations; the user may be prompted to run them after login."
|
fail "Omarchy migrations did not complete. Fix the error above and rerun the upgrade before rebooting."
|
||||||
|
fi
|
||||||
|
if run_as_user_omarchy omarchy-migrate --pending >/dev/null; then
|
||||||
|
fail "Omarchy migrations are still pending. Rerun the upgrade before rebooting."
|
||||||
|
else
|
||||||
|
pending_status=$?
|
||||||
|
if (( pending_status != 1 )); then
|
||||||
|
fail "Could not verify that Omarchy migrations completed. Rerun the upgrade before rebooting."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1312,14 +1323,28 @@ apply_system_transition() {
|
|||||||
/usr/share/icons/Yaru/scalable/actions/go-next-symbolic.svg
|
/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 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
|
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
|
as_root tee /usr/lib/chromium/initial_preferences >/dev/null
|
||||||
|
|
||||||
if getent group docker >/dev/null; then
|
# Deliberately do NOT add the user to the docker group. That group is
|
||||||
as_root usermod -aG docker "$target_user"
|
# root-equivalent, so it is opt-in now (Setup > Security > Sudoless Docker);
|
||||||
fi
|
# re-granting it on upgrade would silently undo that default for everyone.
|
||||||
|
|
||||||
if [[ -f /usr/bin/powerprofilesctl ]]; then
|
if [[ -f /usr/bin/powerprofilesctl ]]; then
|
||||||
as_root sed -i '/env python3/ c\#!/bin/python3' /usr/bin/powerprofilesctl || true
|
as_root sed -i '/env python3/ c\#!/bin/python3' /usr/bin/powerprofilesctl || true
|
||||||
@@ -1350,7 +1375,6 @@ EOF
|
|||||||
as_root systemctl disable docker.service >/dev/null 2>&1 || true
|
as_root systemctl disable docker.service >/dev/null 2>&1 || true
|
||||||
|
|
||||||
enable_system_service cups.service
|
enable_system_service cups.service
|
||||||
enable_system_service cups-browsed.service
|
|
||||||
enable_system_service avahi-daemon.service
|
enable_system_service avahi-daemon.service
|
||||||
enable_system_service linux-modules-cleanup.service
|
enable_system_service linux-modules-cleanup.service
|
||||||
enable_system_service docker.socket
|
enable_system_service docker.socket
|
||||||
@@ -1382,6 +1406,9 @@ EOF
|
|||||||
autologin_user=$(as_root awk -F= '/^User=/ { print $2; exit }' /etc/sddm.conf.d/autologin.conf 2>/dev/null || true)
|
autologin_user=$(as_root awk -F= '/^User=/ { print $2; exit }' /etc/sddm.conf.d/autologin.conf 2>/dev/null || true)
|
||||||
fi
|
fi
|
||||||
[[ -n ${autologin_user:-} ]] || autologin_user="$target_user"
|
[[ -n ${autologin_user:-} ]] || autologin_user="$target_user"
|
||||||
|
# omarchy:heredoc-expands paths=none -- $autologin_user is a username, read
|
||||||
|
# back from the root-owned drop-in or falling back to $target_user. Same
|
||||||
|
# mechanism as the old getty override: a name expands, no path does.
|
||||||
cat <<EOF | as_root tee /etc/sddm.conf.d/autologin.conf >/dev/null
|
cat <<EOF | as_root tee /etc/sddm.conf.d/autologin.conf >/dev/null
|
||||||
[Autologin]
|
[Autologin]
|
||||||
User=$autologin_user
|
User=$autologin_user
|
||||||
@@ -1392,6 +1419,8 @@ EOF
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
as_root install -d -m 0755 -o sddm -g sddm /var/lib/sddm 2>/dev/null || as_root install -d -m 0755 /var/lib/sddm
|
as_root install -d -m 0755 -o sddm -g sddm /var/lib/sddm 2>/dev/null || as_root install -d -m 0755 /var/lib/sddm
|
||||||
|
# omarchy:heredoc-expands paths=none -- $target_user is a username, not a
|
||||||
|
# path; SDDM's state file records who logged in last.
|
||||||
cat <<EOF | as_root tee /var/lib/sddm/state.conf >/dev/null
|
cat <<EOF | as_root tee /var/lib/sddm/state.conf >/dev/null
|
||||||
[Last]
|
[Last]
|
||||||
Session=omarchy.desktop
|
Session=omarchy.desktop
|
||||||
@@ -2306,6 +2335,11 @@ refresh_current_theme_after_upgrade() {
|
|||||||
# hooks because one of them runs `hyprctl reload`. Still poke terminal
|
# hooks because one of them runs `hyprctl reload`. Still poke terminal
|
||||||
# emulators so the active upgrade terminal picks up generated theme files.
|
# emulators so the active upgrade terminal picks up generated theme files.
|
||||||
run_as_user_omarchy omarchy-restart-terminal >/dev/null 2>&1 || true
|
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
|
# Everything below mutates the system, so a non-zero exit from here on leaves a
|
||||||
@@ -2340,8 +2374,8 @@ run_as_user_omarchy omarchy-bar defaults ||
|
|||||||
cleanup_retired_services
|
cleanup_retired_services
|
||||||
ensure_sleep_lock_service
|
ensure_sleep_lock_service
|
||||||
remove_retired_default_packages
|
remove_retired_default_packages
|
||||||
run_post_upgrade_migrations
|
|
||||||
run_final_system_package_upgrade
|
run_final_system_package_upgrade
|
||||||
|
run_post_upgrade_migrations
|
||||||
run_post_upgrade_update_steps
|
run_post_upgrade_update_steps
|
||||||
refresh_current_theme_after_upgrade
|
refresh_current_theme_after_upgrade
|
||||||
# Do not force-reload Hyprland in the live upgraded session. The legacy
|
# Do not force-reload Hyprland in the live upgraded session. The legacy
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ fi
|
|||||||
|
|
||||||
if grep -q "https://pkgs.omarchy.org/stable/" /etc/pacman.conf; then
|
if grep -q "https://pkgs.omarchy.org/stable/" /etc/pacman.conf; then
|
||||||
pkgs="stable"
|
pkgs="stable"
|
||||||
|
elif grep -q "https://pkgs.omarchy.org/rc/" /etc/pacman.conf; then
|
||||||
|
pkgs="rc"
|
||||||
elif grep -q "https://pkgs.omarchy.org/edge/" /etc/pacman.conf; then
|
elif grep -q "https://pkgs.omarchy.org/edge/" /etc/pacman.conf; then
|
||||||
pkgs="edge"
|
pkgs="edge"
|
||||||
else
|
else
|
||||||
|
|||||||
+98
-16
@@ -13,6 +13,18 @@ safe_icon_name() {
|
|||||||
| sed 's/[^[:alnum:]]\+/-/g; s/^-//; s/-$//'
|
| 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() {
|
icon_name_from_ref() {
|
||||||
local ref="$1"
|
local ref="$1"
|
||||||
local name
|
local name
|
||||||
@@ -42,6 +54,34 @@ download_icon() {
|
|||||||
[[ -s $2 && $(file -b --mime-type "$2") == image/* ]]
|
[[ -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() {
|
fetch_site_icon() {
|
||||||
local site_url="$1" dest="$2"
|
local site_url="$1" dest="$2"
|
||||||
local origin page icon_url
|
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"
|
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
|
if (( $# < 3 )); then
|
||||||
echo -e "\e[32mLet's create a new web app you can start with the app launcher.\n\e[0m"
|
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")
|
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")
|
APP_URL=$(gum input --prompt "URL> " --placeholder "https://example.com")
|
||||||
if [[ ! $APP_URL =~ ^[a-zA-Z][a-zA-Z0-9+.-]*: ]]; then
|
APP_URL=$(normalize_webapp_url "$APP_URL")
|
||||||
APP_URL="https://$APP_URL"
|
require_http_url "$APP_URL"
|
||||||
fi
|
|
||||||
|
|
||||||
# Try to fetch the site's icon automatically first.
|
# Try to fetch the site's icon automatically first.
|
||||||
mkdir -p "$ICON_DIR"
|
mkdir -p "$ICON_DIR"
|
||||||
@@ -88,10 +159,8 @@ if (( $# < 3 )); then
|
|||||||
INTERACTIVE_MODE=true
|
INTERACTIVE_MODE=true
|
||||||
else
|
else
|
||||||
APP_NAME="$1"
|
APP_NAME="$1"
|
||||||
APP_URL="$2"
|
APP_URL=$(normalize_webapp_url "$2")
|
||||||
if [[ ! $APP_URL =~ ^[a-zA-Z][a-zA-Z0-9+.-]*: ]]; then
|
require_http_url "$APP_URL"
|
||||||
APP_URL="https://$APP_URL"
|
|
||||||
fi
|
|
||||||
ICON_REF="$3"
|
ICON_REF="$3"
|
||||||
CUSTOM_EXEC="$4" # Optional custom exec command
|
CUSTOM_EXEC="$4" # Optional custom exec command
|
||||||
MIME_TYPES="$5" # Optional mime types
|
MIME_TYPES="$5" # Optional mime types
|
||||||
@@ -104,6 +173,8 @@ if [[ -z $APP_NAME || -z $APP_URL ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
require_plain_name "$APP_NAME"
|
||||||
|
|
||||||
if [[ -z $ICON_REF ]]; then
|
if [[ -z $ICON_REF ]]; then
|
||||||
ICON_VALUE=$(safe_icon_name "$APP_NAME")
|
ICON_VALUE=$(safe_icon_name "$APP_NAME")
|
||||||
mkdir -p "$ICON_DIR"
|
mkdir -p "$ICON_DIR"
|
||||||
@@ -128,28 +199,39 @@ else
|
|||||||
ICON_VALUE=$(icon_name_from_ref "$ICON_REF")
|
ICON_VALUE=$(icon_name_from_ref "$ICON_REF")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Use custom exec if provided, otherwise default behavior
|
# Default Exec quotes the URL as one Exec-spec argument; the whole line then gets
|
||||||
EXEC_COMMAND="${CUSTOM_EXEC:-omarchy-launch-webapp $APP_URL}"
|
# 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
|
# Create application .desktop file
|
||||||
DESKTOP_FILE="$HOME/.local/share/applications/$APP_NAME.desktop"
|
DESKTOP_DIR="$HOME/.local/share/applications"
|
||||||
mkdir -p "$(dirname "$DESKTOP_FILE")"
|
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
|
cat >"$DESKTOP_FILE" <<EOF
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Version=1.0
|
Version=1.0
|
||||||
Name=$APP_NAME
|
Name=$name_field
|
||||||
Comment=$APP_NAME
|
Comment=$name_field
|
||||||
Exec=$EXEC_COMMAND
|
Exec=$exec_field
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Icon=$ICON_VALUE
|
Icon=$icon_field
|
||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Add mime types if provided
|
# Add mime types if provided
|
||||||
if [[ -n $MIME_TYPES ]]; then
|
if [[ -n $MIME_TYPES ]]; then
|
||||||
echo "MimeType=$MIME_TYPES" >>"$DESKTOP_FILE"
|
printf 'MimeType=%s\n' "$(desktop_string_escape "$MIME_TYPES")" >>"$DESKTOP_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
chmod +x "$DESKTOP_FILE"
|
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"
|
OLD_ICON_DIR="$HOME/.local/share/applications/icons"
|
||||||
DESKTOP_DIR="$HOME/.local/share/applications/"
|
DESKTOP_DIR="$HOME/.local/share/applications/"
|
||||||
|
|
||||||
if (( $# == 0 )); then
|
# Always index the launchers, so removal deletes the file that was found rather
|
||||||
# Find all web apps
|
# than a path rebuilt from the displayed name. Installs predating the name
|
||||||
while IFS= read -r -d '' file; do
|
# validation could nest the launcher inside directories, and those are exactly
|
||||||
if grep -q '^Exec=.*\(omarchy-launch-webapp\|omarchy-webapp-handler\).*' "$file"; then
|
# the ones a reconstructed path cannot reach.
|
||||||
WEB_APPS+=("$(basename "${file%.desktop}")")
|
WEB_APP_PATHS=()
|
||||||
fi
|
while IFS= read -r -d '' file; do
|
||||||
done < <(find "$DESKTOP_DIR" -name '*.desktop' -print0)
|
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
|
if ((${#WEB_APPS[@]})); then
|
||||||
mapfile -t SORTED_WEB_APPS < <(printf '%s\n' "${WEB_APPS[@]}" | sort)
|
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)
|
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
|
fi
|
||||||
|
|
||||||
icon_name=$(printf '%s\n' "$APP_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^[:alnum:]]\+/-/g; s/^-//; s/-$//')
|
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"
|
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
|
if [[ ${OMARCHY_REMOVE_NOTIFY:-true} != "false" ]]; then
|
||||||
|
|||||||
+1263
-120
File diff suppressed because it is too large.
Load diff
@@ -4,4 +4,4 @@
|
|||||||
# To put it into use, remove .sample from this file name.
|
# To put it into use, remove .sample from this file name.
|
||||||
|
|
||||||
# Example: Show the name of the font that was just set.
|
# Example: Show the name of the font that was just set.
|
||||||
# notify-send -u low "New font" "Your new font is $1"
|
# omarchy-notification-send -u low "New font" "Your new font is $1"
|
||||||
@@ -6,5 +6,5 @@
|
|||||||
weather=$(omarchy-weather-status 2>/dev/null) || true
|
weather=$(omarchy-weather-status 2>/dev/null) || true
|
||||||
|
|
||||||
if [[ -n $weather && $weather != "Weather unavailable" ]]; then
|
if [[ -n $weather && $weather != "Weather unavailable" ]]; then
|
||||||
notify-send -u low "$weather"
|
omarchy-notification-send -u low "$weather"
|
||||||
fi
|
fi
|
||||||
@@ -4,4 +4,4 @@
|
|||||||
# To put it into use, remove .sample from this file name.
|
# To put it into use, remove .sample from this file name.
|
||||||
|
|
||||||
# Example: Show notification after the system has been updated.
|
# Example: Show notification after the system has been updated.
|
||||||
# notify-send -u low "Update Performed" "Your system is now up to date"
|
# omarchy-notification-send -u low "Update Performed" "Your system is now up to date"
|
||||||
@@ -4,4 +4,4 @@
|
|||||||
# To put it into use, remove .sample from this file name.
|
# To put it into use, remove .sample from this file name.
|
||||||
|
|
||||||
# Example: Show the name of the theme that was just set.
|
# Example: Show the name of the theme that was just set.
|
||||||
# notify-send -u low "New theme" "Your new theme is $1"
|
# omarchy-notification-send -u low "New theme" "Your new theme is $1"
|
||||||
@@ -22,11 +22,34 @@ omarchy theme install <url> # Install from git repo
|
|||||||
Additional user backgrounds for any theme (stock or custom) go in
|
Additional user backgrounds for any theme (stock or custom) go in
|
||||||
`~/.config/omarchy/backgrounds/<theme-slug>/`.
|
`~/.config/omarchy/backgrounds/<theme-slug>/`.
|
||||||
|
|
||||||
|
## What a Theme Installed From a Repo May Not Contain
|
||||||
|
|
||||||
|
A theme the user wrote by hand in `~/.config/omarchy/themes` is unrestricted, as
|
||||||
|
are Omarchy's own themes. From a theme cloned by `omarchy theme install`, Omarchy
|
||||||
|
drops only what runs code: any `*.lua` (Hyprland requires a theme's
|
||||||
|
`hyprland.lua` and `gum_env.lua` at login, Neovim loads `neovim.lua` at startup),
|
||||||
|
the terminal configs `alacritty.toml`, `foot.ini`, `ghostty.conf` and
|
||||||
|
`kitty.conf` (each names the program the terminal launches), and `vscode.json`
|
||||||
|
(names a VS Code extension to install). Those are regenerated from `colors.toml`
|
||||||
|
through `$OMARCHY_PATH/default/themed/*.tpl`, and named on stderr.
|
||||||
|
|
||||||
|
Everything else a cloned theme ships is kept, including `btop.theme`,
|
||||||
|
`chromium.theme`, `helix.toml`, `icons.theme`, `keyboard.rgb` and `shell.toml`.
|
||||||
|
Omarchy tells a cloned theme from the user's own by the `.git` directory a clone
|
||||||
|
leaves behind.
|
||||||
|
|
||||||
|
To change how Omarchy themes an app for every theme, write the template rather
|
||||||
|
than the theme: `~/.config/omarchy/themed/<config-name>.tpl` overrides the
|
||||||
|
built-in one. See `docs/theming.md` in the Omarchy repo.
|
||||||
|
|
||||||
## Customizing a Stock Theme
|
## Customizing a Stock Theme
|
||||||
|
|
||||||
Never edit stock themes under `/usr/share/omarchy/themes/` — changes are lost
|
Never edit stock themes under `/usr/share/omarchy/themes/` — changes are lost
|
||||||
on update. Two safe options:
|
on update. Two safe options:
|
||||||
|
|
||||||
|
Both write into `~/.config/omarchy/themes`, where a theme the user wrote is
|
||||||
|
unrestricted — the list above applies only to a theme cloned from a repo.
|
||||||
|
|
||||||
**Overlay (preferred for small tweaks):** create a user theme directory with
|
**Overlay (preferred for small tweaks):** create a user theme directory with
|
||||||
the SAME slug containing only the files you want to change. When the theme is
|
the SAME slug containing only the files you want to change. When the theme is
|
||||||
applied, the stock theme is copied first and your files win on top:
|
applied, the stock theme is copied first and your files win on top:
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ alias ....='cd ../../..'
|
|||||||
# Tools
|
# Tools
|
||||||
alias a='omarchy-agent --inline'
|
alias a='omarchy-agent --inline'
|
||||||
alias c='opencode --auto'
|
alias c='opencode --auto'
|
||||||
alias cx='printf "\033[2J\033[3J\033[H" && claude --permission-mode bypassPermissions'
|
alias cx='printf "\033[2J\033[3J\033[H" && claude --permission-mode auto'
|
||||||
alias cy='codex -s danger-full-access -a never'
|
alias cy='codex --approve-for-me'
|
||||||
alias d='docker'
|
alias d='docker'
|
||||||
alias r='rails'
|
alias r='rails'
|
||||||
alias t='tmux attach || tmux new -s Work'
|
alias t='tmux attach || tmux new -s Work'
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ if o.preinstalled_bindings_enabled() then
|
|||||||
o.bind("SUPER + CTRL + RETURN", "Herdr", { omarchy = "terminal-herdr" })
|
o.bind("SUPER + CTRL + RETURN", "Herdr", { omarchy = "terminal-herdr" })
|
||||||
o.bind("SUPER + SHIFT + M", "Music", { omarchy = "spotify" })
|
o.bind("SUPER + SHIFT + M", "Music", { omarchy = "spotify" })
|
||||||
o.bind("SUPER + SHIFT + ALT + M", "Music TUI", { tui = "cliamp", focus = true })
|
o.bind("SUPER + SHIFT + ALT + M", "Music TUI", { tui = "cliamp", focus = true })
|
||||||
o.bind("SUPER + SHIFT + D", "Docker", { tui = "lazydocker" })
|
o.bind("SUPER + SHIFT + D", "Docker", { tui = "omarchy-launch-docker-tui" })
|
||||||
o.bind("SUPER + SHIFT + G", "Signal", { omarchy = "signal" })
|
o.bind("SUPER + SHIFT + G", "Signal", { omarchy = "signal" })
|
||||||
o.bind("SUPER + SHIFT + O", "Obsidian", { launch = "obsidian", focus = "^obsidian$" })
|
o.bind("SUPER + SHIFT + O", "Obsidian", { launch = "obsidian", focus = "^obsidian$" })
|
||||||
o.bind("SUPER + SHIFT + W", "Omawrite", { launch = "omawrite" })
|
o.bind("SUPER + SHIFT + W", "Omawrite", { launch = "omawrite" })
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
-- Disable a Hyprland input device whose name was stored as data, not Lua.
|
||||||
|
-- Device names come from USB descriptors and must never be loaded as code.
|
||||||
|
|
||||||
|
local paths = require("default.hypr.paths")
|
||||||
|
|
||||||
|
return function(kind)
|
||||||
|
-- Hardcoded to ~/.local/state to match omarchy-toggle-input-device and the
|
||||||
|
-- sibling bash toggle tools, which all write there regardless of
|
||||||
|
-- XDG_STATE_HOME.
|
||||||
|
local file = io.open(paths.home .. "/.local/state/omarchy/toggles/hypr/" .. kind .. "-disabled-name", "r")
|
||||||
|
if not file then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local name = file:read("*l")
|
||||||
|
file:close()
|
||||||
|
|
||||||
|
if name and name ~= "" then
|
||||||
|
hl.device({ name = name, enabled = false })
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -18,9 +18,20 @@ local function file_exists(path)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Hyprland reaps its own children, so os.execute() can't retrieve an exit status
|
||||||
|
-- from inside the compositor. Read a marker off stdout instead.
|
||||||
function o.shell_succeeds(command)
|
function o.shell_succeeds(command)
|
||||||
local ok, _, code = os.execute(command .. " >/dev/null 2>&1")
|
-- Subshell, so the redirection covers every command rather than binding to
|
||||||
return ok == true or ok == 0 or code == 0
|
-- the last one and letting an earlier one write its own OK into the pipe.
|
||||||
|
local pipe = io.popen("( " .. command .. " ) >/dev/null 2>&1 && echo OK")
|
||||||
|
if not pipe then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local output = pipe:read("*a") or ""
|
||||||
|
pipe:close()
|
||||||
|
|
||||||
|
return output:find("OK", 1, true) ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function o.cmd_present(command)
|
function o.cmd_present(command)
|
||||||
@@ -125,7 +136,7 @@ function o.bind_toggle(keys, description, toggle, options)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function o.notify(message)
|
function o.notify(message)
|
||||||
return "notify-send -u low " .. shell_quote(message)
|
return "omarchy-notification-send -u low " .. shell_quote(message)
|
||||||
end
|
end
|
||||||
|
|
||||||
function o.window(match, rules)
|
function o.window(match, rules)
|
||||||
|
|||||||
+13
-3
@@ -4,9 +4,19 @@
|
|||||||
|
|
||||||
local home = os.getenv("HOME")
|
local home = os.getenv("HOME")
|
||||||
|
|
||||||
|
-- A variable that is set but empty means "unset" (XDG Base Directory spec);
|
||||||
|
-- bash's ${VAR:-fallback} in the sibling tools treats it the same way.
|
||||||
|
local function env_or(name, fallback)
|
||||||
|
local value = os.getenv(name)
|
||||||
|
if value == nil or value == "" then
|
||||||
|
return fallback
|
||||||
|
end
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
home = home,
|
home = home,
|
||||||
config_home = os.getenv("XDG_CONFIG_HOME") or (home .. "/.config"),
|
config_home = env_or("XDG_CONFIG_HOME", home .. "/.config"),
|
||||||
state_home = os.getenv("XDG_STATE_HOME") or (home .. "/.local/state"),
|
state_home = env_or("XDG_STATE_HOME", home .. "/.local/state"),
|
||||||
omarchy_path = os.getenv("OMARCHY_PATH") or "/usr/share/omarchy",
|
omarchy_path = env_or("OMARCHY_PATH", "/usr/share/omarchy"),
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,8 @@
|
|||||||
-- Pass a module prefix for normal package.path modules, e.g.
|
-- Pass a module prefix for normal package.path modules, e.g.
|
||||||
-- require_all.files(paths.omarchy_path .. "/default/hypr/apps", "default.hypr.apps")
|
-- require_all.files(paths.omarchy_path .. "/default/hypr/apps", "default.hypr.apps")
|
||||||
-- Pass nil as the prefix when the directory itself has been added to package.path.
|
-- Pass nil as the prefix when the directory itself has been added to package.path.
|
||||||
|
-- Pass options.exclude as a set of base names (without ".lua") to skip; a legacy
|
||||||
|
-- file that must never be loaded as code stays on disk for a migration to remove.
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
@@ -12,19 +14,23 @@ local function shell_quote(path)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function M.files(dir, module_prefix, options)
|
function M.files(dir, module_prefix, options)
|
||||||
|
local exclude = options and options.exclude or {}
|
||||||
local handle = io.popen("find " .. shell_quote(dir) .. " -maxdepth 1 -type f -name '*.lua' -printf '%f\\n' 2>/dev/null | sort")
|
local handle = io.popen("find " .. shell_quote(dir) .. " -maxdepth 1 -type f -name '*.lua' -printf '%f\\n' 2>/dev/null | sort")
|
||||||
if handle then
|
if handle then
|
||||||
for filename in handle:lines() do
|
for filename in handle:lines() do
|
||||||
local module = filename:gsub("%.lua$", "")
|
local name = filename:gsub("%.lua$", "")
|
||||||
if module_prefix then
|
if not exclude[name] then
|
||||||
module = module_prefix .. "." .. module
|
local module = name
|
||||||
end
|
if module_prefix then
|
||||||
|
module = module_prefix .. "." .. module
|
||||||
|
end
|
||||||
|
|
||||||
if options and options.reload then
|
if options and options.reload then
|
||||||
package.loaded[module] = nil
|
package.loaded[module] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
require(module)
|
require(module)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
handle:close()
|
handle:close()
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,6 +4,20 @@ local require_all = require("default.hypr.require_all")
|
|||||||
local toggles_dir = paths.state_home .. "/omarchy/toggles/hypr"
|
local toggles_dir = paths.state_home .. "/omarchy/toggles/hypr"
|
||||||
package.path = toggles_dir .. "/?.lua;" .. package.path
|
package.path = toggles_dir .. "/?.lua;" .. package.path
|
||||||
|
|
||||||
require_all.files(toggles_dir, nil, { reload = true })
|
-- touchpad-disabled.lua / touchscreen-disabled.lua were generated Lua in older
|
||||||
|
-- versions and could carry an injected USB device name. They must never be loaded
|
||||||
|
-- as code again: exclude them so a not-yet-migrated install cannot execute a
|
||||||
|
-- leftover payload on reload. The migration recovers the name and deletes them.
|
||||||
|
require_all.files(toggles_dir, nil, {
|
||||||
|
reload = true,
|
||||||
|
exclude = {
|
||||||
|
["touchpad-disabled"] = true,
|
||||||
|
["touchscreen-disabled"] = true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
local disabled_input_device = require("default.hypr.disabled-input-device")
|
||||||
|
disabled_input_device("touchpad")
|
||||||
|
disabled_input_device("touchscreen")
|
||||||
|
|
||||||
require("default.hypr.workspace-layouts")
|
require("default.hypr.workspace-layouts")
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
// Style
|
// Style
|
||||||
"style.theme": {"icon":"","label":"Theme","aliases":["theme","themes"],"action":"theme=$(omarchy-theme-switcher); [[ -n $theme ]] && omarchy-theme-set \"$theme\""},
|
"style.theme": {"icon":"","label":"Theme","aliases":["theme","themes"],"action":"theme=$(omarchy-theme-switcher); [[ -n $theme ]] && omarchy-theme-set \"$theme\""},
|
||||||
"style.background": {"icon":"","label":"Background","aliases":["background","wallpaper"],"action":"background=$(omarchy-theme-bg-switcher); [[ -n $background ]] && omarchy-theme-bg-set \"$background\""},
|
"style.background": {"icon":"","label":"Background","aliases":["background","wallpaper"],"action":"background=$(omarchy-theme-bg-switcher); [[ -n $background ]] && omarchy-theme-bg-set \"$background\""},
|
||||||
"style.unlock": {"icon":"","label":"Unlock","aliases":["unlock"],"action":"unlock=$(omarchy-plymouth-switcher); if [[ $unlock == default ]]; then omarchy-launch-floating-terminal-with-presentation omarchy-plymouth-reset; elif [[ -n $unlock ]]; then omarchy-launch-floating-terminal-with-presentation \"omarchy-plymouth-set-by-theme '$unlock'\"; fi"},
|
"style.unlock": {"icon":"","label":"Unlock","aliases":["unlock"],"action":"unlock=$(omarchy-plymouth-switcher); if [[ $unlock == default ]]; then omarchy-launch-floating-terminal-with-presentation omarchy-plymouth-reset; elif [[ -n $unlock ]]; then omarchy-launch-floating-terminal-with-presentation \"omarchy-plymouth-set-by-theme $(printf %q \"$unlock\")\"; fi"},
|
||||||
"style.font": {"icon":"","label":"Font","provider":"fonts"},
|
"style.font": {"icon":"","label":"Font","provider":"fonts"},
|
||||||
"style.bar": {"icon":"","label":"Menu Bar"},
|
"style.bar": {"icon":"","label":"Menu Bar"},
|
||||||
"style.bar.position": {"icon":"","label":"Position"},
|
"style.bar.position": {"icon":"","label":"Position"},
|
||||||
@@ -173,6 +173,7 @@
|
|||||||
"setup.security.fido2": {"icon":"","label":"Fido2","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fido2"},
|
"setup.security.fido2": {"icon":"","label":"Fido2","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fido2"},
|
||||||
"setup.security.sshd": {"icon":"","label":"SSHD","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-sshd"},
|
"setup.security.sshd": {"icon":"","label":"SSHD","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-sshd"},
|
||||||
"setup.security.passwordless-sudo": {"icon":"","label":"Passwordless Sudo","action":"omarchy-launch-floating-terminal-with-presentation omarchy-sudo-passwordless"},
|
"setup.security.passwordless-sudo": {"icon":"","label":"Passwordless Sudo","action":"omarchy-launch-floating-terminal-with-presentation omarchy-sudo-passwordless"},
|
||||||
|
"setup.security.sudoless-docker": {"icon":"","label":"Sudoless Docker","when":"omarchy-sudo-docker --configured","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-sudoless-docker"},
|
||||||
"setup.config.hyprland": {"icon":"","label":"Hyprland","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/hyprland.lua\""},
|
"setup.config.hyprland": {"icon":"","label":"Hyprland","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/hyprland.lua\""},
|
||||||
"setup.config.hyprsunset": {"icon":"","label":"Hyprsunset","action":"omarchy-launch-config-editor ~/.config/hypr/hyprsunset.conf && omarchy-restart-hyprsunset"},
|
"setup.config.hyprsunset": {"icon":"","label":"Hyprsunset","action":"omarchy-launch-config-editor ~/.config/hypr/hyprsunset.conf && omarchy-restart-hyprsunset"},
|
||||||
"setup.config.xcompose": {"icon":"","label":"XCompose","action":"omarchy-launch-config-editor ~/.XCompose && omarchy-restart-xcompose"},
|
"setup.config.xcompose": {"icon":"","label":"XCompose","action":"omarchy-launch-config-editor ~/.XCompose && omarchy-restart-xcompose"},
|
||||||
@@ -283,6 +284,7 @@
|
|||||||
"remove.security.fingerprint": {"icon":"","label":"Fingerprint","when":"omarchy-pkg-present fprintd","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-fingerprint"},
|
"remove.security.fingerprint": {"icon":"","label":"Fingerprint","when":"omarchy-pkg-present fprintd","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-fingerprint"},
|
||||||
"remove.security.fido2": {"icon":"","label":"Fido2","when":"omarchy-pkg-present pam-u2f","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-fido2"},
|
"remove.security.fido2": {"icon":"","label":"Fido2","when":"omarchy-pkg-present pam-u2f","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-fido2"},
|
||||||
"remove.security.sshd": {"icon":"","label":"SSHD","when":"systemctl is-enabled --quiet sshd","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-sshd"},
|
"remove.security.sshd": {"icon":"","label":"SSHD","when":"systemctl is-enabled --quiet sshd","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-sshd"},
|
||||||
|
"remove.security.sudoless-docker": {"icon":"","label":"Sudoless Docker","when":"! omarchy-sudo-docker --configured","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-sudoless-docker"},
|
||||||
"remove.browser.chrome": {"icon":"","label":"Chrome","when":"omarchy-pkg-present google-chrome","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-browser chrome'"},
|
"remove.browser.chrome": {"icon":"","label":"Chrome","when":"omarchy-pkg-present google-chrome","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-browser chrome'"},
|
||||||
"remove.browser.edge": {"icon":"","label":"Edge","when":"omarchy-pkg-present microsoft-edge-stable-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-browser edge'"},
|
"remove.browser.edge": {"icon":"","label":"Edge","when":"omarchy-pkg-present microsoft-edge-stable-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-browser edge'"},
|
||||||
"remove.browser.brave": {"icon":"","label":"Brave","when":"omarchy-pkg-present brave-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-browser brave'"},
|
"remove.browser.brave": {"icon":"","label":"Brave","when":"omarchy-pkg-present brave-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-browser brave'"},
|
||||||
@@ -326,7 +328,7 @@
|
|||||||
"update.omarchy": {"icon":"","iconFont":"omarchy","label":"Omarchy","action":"omarchy-launch-floating-terminal-with-presentation omarchy-update"},
|
"update.omarchy": {"icon":"","iconFont":"omarchy","label":"Omarchy","action":"omarchy-launch-floating-terminal-with-presentation omarchy-update"},
|
||||||
"update.channel": {"icon":"","label":"Channel"},
|
"update.channel": {"icon":"","label":"Channel"},
|
||||||
"update.config": {"icon":"","label":"Config","title":"Reset to default"},
|
"update.config": {"icon":"","label":"Config","title":"Reset to default"},
|
||||||
"update.themes": {"icon":"","label":"Extra Themes","action":"omarchy-launch-floating-terminal-with-presentation omarchy-theme-update"},
|
"update.themes": {"icon":"","label":"Extra Themes","when":"omarchy-theme-extras","action":"omarchy-launch-floating-terminal-with-presentation omarchy-theme-update"},
|
||||||
"update.process": {"icon":"","label":"Process","title":"Restart"},
|
"update.process": {"icon":"","label":"Process","title":"Restart"},
|
||||||
"update.hardware": {"icon":"","label":"Hardware","title":"Restart"},
|
"update.hardware": {"icon":"","label":"Hardware","title":"Restart"},
|
||||||
"update.firmware": {"icon":"","label":"Firmware","action":"omarchy-launch-floating-terminal-with-presentation omarchy-update-firmware"},
|
"update.firmware": {"icon":"","label":"Firmware","action":"omarchy-launch-floating-terminal-with-presentation omarchy-update-firmware"},
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ Include = /etc/pacman.d/mirrorlist
|
|||||||
Include = /etc/pacman.d/mirrorlist
|
Include = /etc/pacman.d/mirrorlist
|
||||||
|
|
||||||
[omarchy]
|
[omarchy]
|
||||||
SigLevel = Optional TrustAll
|
|
||||||
Server = https://pkgs.omarchy.org/edge/$arch
|
Server = https://pkgs.omarchy.org/edge/$arch
|
||||||
|
|
||||||
# Repositories for debug symbol packages.
|
# Repositories for debug symbol packages.
|
||||||
|
|||||||
@@ -26,5 +26,4 @@ Include = /etc/pacman.d/mirrorlist
|
|||||||
Include = /etc/pacman.d/mirrorlist
|
Include = /etc/pacman.d/mirrorlist
|
||||||
|
|
||||||
[omarchy]
|
[omarchy]
|
||||||
SigLevel = Optional TrustAll
|
Server = https://pkgs.omarchy.org/rc/$arch
|
||||||
Server = https://pkgs.omarchy.org/edge/$arch
|
|
||||||
@@ -26,5 +26,4 @@ Include = /etc/pacman.d/mirrorlist
|
|||||||
Include = /etc/pacman.d/mirrorlist
|
Include = /etc/pacman.d/mirrorlist
|
||||||
|
|
||||||
[omarchy]
|
[omarchy]
|
||||||
SigLevel = Optional TrustAll
|
|
||||||
Server = https://pkgs.omarchy.org/stable/$arch
|
Server = https://pkgs.omarchy.org/stable/$arch
|
||||||
@@ -124,6 +124,7 @@ New migration format:
|
|||||||
- Start with an `echo` describing what the migration does.
|
- Start with an `echo` describing what the migration does.
|
||||||
- Use `$OMARCHY_PATH` to reference the Omarchy directory.
|
- Use `$OMARCHY_PATH` to reference the Omarchy directory.
|
||||||
- Be idempotent. Check existing state before changing it.
|
- Be idempotent. Check existing state before changing it.
|
||||||
|
- Migrations are strictly ordered and synchronous. A migration that cannot finish must exit non-zero, remain pending, and stop the queue; never mark later migrations complete against state an earlier migration has not established.
|
||||||
- Use helper commands such as `omarchy-cmd-present`, `omarchy-cmd-missing`,
|
- Use helper commands such as `omarchy-cmd-present`, `omarchy-cmd-missing`,
|
||||||
`omarchy-pkg-add`, `omarchy-pkg-drop`, `omarchy-pkg-present`, and
|
`omarchy-pkg-add`, `omarchy-pkg-drop`, `omarchy-pkg-present`, and
|
||||||
`omarchy-pkg-missing` when appropriate.
|
`omarchy-pkg-missing` when appropriate.
|
||||||
@@ -163,3 +164,5 @@ omarchy-migrate
|
|||||||
Omarchy 4.0 is upgraded through `bin/omarchy-upgrade-to-quattro`, not through the
|
Omarchy 4.0 is upgraded through `bin/omarchy-upgrade-to-quattro`, not through the
|
||||||
normal migration runner. Do not add compatibility migrations for old installer
|
normal migration runner. Do not add compatibility migrations for old installer
|
||||||
layouts; put pre-4 package-layout transition work in the upgrade command instead.
|
layouts; put pre-4 package-layout transition work in the upgrade command instead.
|
||||||
|
|
||||||
|
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 before deleting it. Leave safe administrator-authored files alone; if one still contains the vulnerable privileged action, preserve it under an inactive name rather than discarding custom content or leaving the action executable. A user config that depends on the same retired compatibility path may be repaired in that migration when doing so eliminates an overlapping migration, but only by matching and replacing the exact legacy path while preserving the rest of the file.
|
||||||
+39
-1
@@ -6,13 +6,22 @@ Omarchy themes live under `themes/<name>/` in the source tree (installed at
|
|||||||
`colors.toml`; Omarchy generates the active theme files from
|
`colors.toml`; Omarchy generates the active theme files from
|
||||||
`default/themed/*.tpl` when `omarchy-theme-set <name>` runs.
|
`default/themed/*.tpl` when `omarchy-theme-set <name>` runs.
|
||||||
|
|
||||||
|
Beyond `colors.toml` and hand-written config overrides, a first-party theme can
|
||||||
|
ship `backgrounds/` (users overlay their own via
|
||||||
|
`~/.config/omarchy/backgrounds/<name>/`; the active image is the
|
||||||
|
`~/.local/state/omarchy/current/background` symlink), `preview.png` and
|
||||||
|
`preview-unlock.png` for the theme switcher, `icons.theme`, `keyboard.rgb`,
|
||||||
|
`unlock.png`, and a `light.mode` marker file.
|
||||||
|
|
||||||
|
A theme installed from a git repo is held to a much shorter list; see [What an installed theme may not ship](#what-an-installed-theme-may-not-ship).
|
||||||
|
|
||||||
## Theme activation flow
|
## Theme activation flow
|
||||||
|
|
||||||
`omarchy-theme-set <name>` builds a clean staging directory at
|
`omarchy-theme-set <name>` builds a clean staging directory at
|
||||||
`~/.local/state/omarchy/current/next-theme`:
|
`~/.local/state/omarchy/current/next-theme`:
|
||||||
|
|
||||||
1. Copy the first-party theme from `themes/<name>/`.
|
1. Copy the first-party theme from `themes/<name>/`.
|
||||||
2. Overlay any user theme files from `~/.config/omarchy/themes/<name>/`.
|
2. Overlay `~/.config/omarchy/themes/<name>/`, in full when the user wrote it and filtered when it came from a git repo, naming anything it dropped on stderr.
|
||||||
3. If needed, generate `colors.toml` from `alacritty.toml`.
|
3. If needed, generate `colors.toml` from `alacritty.toml`.
|
||||||
4. Run `omarchy-theme-set-templates` to render templates into the staging
|
4. Run `omarchy-theme-set-templates` to render templates into the staging
|
||||||
theme.
|
theme.
|
||||||
@@ -28,6 +37,34 @@ User templates in `~/.config/omarchy/themed/*.tpl` are processed before the
|
|||||||
built-in templates. If a user template has the same output filename as a
|
built-in templates. If a user template has the same output filename as a
|
||||||
built-in template, the built-in output is skipped.
|
built-in template, the built-in output is skipped.
|
||||||
|
|
||||||
|
After activation, `omarchy-theme-set` fires the `theme-set` hook
|
||||||
|
(`~/.config/omarchy/hooks/theme-set*`, theme name in `$1`) and dispatches a
|
||||||
|
parallel retint of running apps — terminals, Hyprland, btop, browser, editors,
|
||||||
|
and the rest of the `post_theme_commands` list in `bin/omarchy-theme-set`.
|
||||||
|
Making a new app follow theme changes means adding its restart/retint command
|
||||||
|
to that list. Runs serialize on a `flock`, so scripted theme changes queue
|
||||||
|
instead of racing.
|
||||||
|
|
||||||
|
## What an installed theme may not ship
|
||||||
|
|
||||||
|
`themes/<name>/` in this repo is Omarchy's own code and is trusted. So is a theme the user wrote by hand in `~/.config/omarchy/themes/<name>/`: it is their machine and their file, and both stage in full.
|
||||||
|
|
||||||
|
`omarchy theme install <url>` is different. It clones a stranger's git repo straight into that same directory, so the contents are whatever the theme author pushed. `omarchy-theme-set` tells the two apart the way `omarchy-theme-extras` already does — a `.git` directory means it was cloned, while a plain directory or a symlink to a working copy is the user's own — and from a cloned one it drops only what can run code:
|
||||||
|
|
||||||
|
- any `*.lua` — Hyprland `require`s a theme's `hyprland.lua` and `gum_env.lua` at login, and Neovim loads its `neovim.lua` at startup
|
||||||
|
- `alacritty.toml`, `foot.ini`, `ghostty.conf`, `kitty.conf` — each names the program the terminal launches
|
||||||
|
- `vscode.json` — names the extension `omarchy-theme-set-vscode` installs, and a VS Code extension is arbitrary JavaScript
|
||||||
|
|
||||||
|
Symlinks are dropped with them, at any depth; in a cloned theme they point wherever the theme author chose. Everything a cloned theme ships that is colour is kept, including files Omarchy would otherwise have generated — `btop.theme`, `chromium.theme`, `helix.toml`, `shell.toml`, `icons.theme`, `keyboard.rgb` and the rest — so a theme can still say exactly how it wants each app to look. What is dropped gets generated from `default/themed/*.tpl` instead, and is named on stderr.
|
||||||
|
|
||||||
|
A denylist is only right while it is maintained. Adding a template for another terminal, or for another editor that loads Lua, means adding it to `INSTALLED_THEME_DENIED` in `bin/omarchy-theme-set`; `test/shell.d/theme-staging-test.sh` fails on any `default/themed/*.tpl` whose output is recorded as neither code nor colour, so a new template cannot be added without that decision being made.
|
||||||
|
|
||||||
|
A theme predating `colors.toml` is not left without a palette: its `alacritty.toml` is read through `omarchy-theme-colors-from-alacritty` into a scratch directory and only the resulting `colors.toml` is staged, so the colors survive and the terminal config does not.
|
||||||
|
|
||||||
|
The restriction lives in `omarchy-theme-set` rather than in `omarchy-theme-install` on purpose. Filtering at staging also covers themes installed before the rule existed and files a theme gains later through `omarchy theme update`.
|
||||||
|
|
||||||
|
What this does not cover: a theme distributed as an archive rather than a git repo, extracted into `~/.config/omarchy/themes/` by hand, is indistinguishable from one the user wrote and stages in full. `omarchy theme install` only takes git URLs, so the supported path is always filtered, but the check is a statement about where a theme came from and not a sandbox.
|
||||||
|
|
||||||
## `colors.toml`
|
## `colors.toml`
|
||||||
|
|
||||||
`colors.toml` provides the palette keys used by templates. Keys are grouped
|
`colors.toml` provides the palette keys used by templates. Keys are grouped
|
||||||
@@ -335,6 +372,7 @@ local active_border_color = { colors = { "rgba(33ccffee)", "rgba(00ff99ee)" }, a
|
|||||||
## Adding or overriding theme files
|
## Adding or overriding theme files
|
||||||
|
|
||||||
- Add palette values to `themes/<name>/colors.toml`.
|
- Add palette values to `themes/<name>/colors.toml`.
|
||||||
|
- Hand-written overrides work everywhere except a `.lua`, a terminal config or a `vscode.json` in a theme cloned from a git repo; see [What an installed theme may not ship](#what-an-installed-theme-may-not-ship).
|
||||||
- Prefer generated files when the theme can be expressed with templates.
|
- Prefer generated files when the theme can be expressed with templates.
|
||||||
- Add a hand-written file in `themes/<name>/` only when that theme needs to
|
- Add a hand-written file in `themes/<name>/` only when that theme needs to
|
||||||
override the generated output entirely.
|
override the generated output entirely.
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
# Omarchy override of cups-browsed's shipped config. The only behavioural
|
# Keep state away from /var/cache/cups, which is writable by the account CUPS
|
||||||
# change vs the upstream default (all-commented) is enabling auto-registration
|
# uses for print filters. cups-browsed is the only writer to this directory.
|
||||||
# of remote IPP printers discovered via Avahi/mDNS.
|
CacheDir /var/cache/cups-browsed
|
||||||
CreateRemotePrinters Yes
|
|
||||||
|
# Auto-create queues only for modern driverless IPP printers. Remote queues
|
||||||
|
# exported by another CUPS server can still be added manually when needed.
|
||||||
|
CreateIPPPrinterQueues Driverless
|
||||||
|
CreateRemoteCUPSPrinterQueues No
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#
|
||||||
|
# File/directory/user/group configuration file for the CUPS scheduler.
|
||||||
|
# See "man cups-files.conf" for a complete description of this file.
|
||||||
|
#
|
||||||
|
|
||||||
|
# List of events that are considered fatal errors for the scheduler...
|
||||||
|
#FatalErrors config
|
||||||
|
|
||||||
|
# Strip domain in local username?
|
||||||
|
#StripUserDomain No
|
||||||
|
|
||||||
|
# Do we call fsync() after writing configuration or status files?
|
||||||
|
#SyncOnClose No
|
||||||
|
|
||||||
|
# Default user and group for filters/backends/helper programs; this cannot be
|
||||||
|
# any user or group that resolves to ID 0 for security reasons...
|
||||||
|
User 209
|
||||||
|
Group 209
|
||||||
|
|
||||||
|
# Administrator user group, used to match @SYSTEM in cupsd.conf policy rules...
|
||||||
|
# This cannot contain the Group value for security reasons...
|
||||||
|
SystemGroup cups-browsed sys root
|
||||||
|
|
||||||
|
|
||||||
|
# Are Unix domain socket peer credentials used for authorization?
|
||||||
|
PeerCred on
|
||||||
|
|
||||||
|
# User that is substituted for unauthenticated (remote) root accesses...
|
||||||
|
#RemoteRoot remroot
|
||||||
|
|
||||||
|
# Do we allow file: device URIs other than to /dev/null?
|
||||||
|
#FileDevice No
|
||||||
|
|
||||||
|
# Permissions for configuration and log files...
|
||||||
|
#ConfigFilePerm 0640
|
||||||
|
#LogFilePerm 0644
|
||||||
|
|
||||||
|
# Location of the file logging all access to the scheduler; may be the name
|
||||||
|
# "syslog". If not an absolute path, the value of ServerRoot is used as the
|
||||||
|
# root directory. Also see the "AccessLogLevel" directive in cupsd.conf.
|
||||||
|
AccessLog /var/log/cups/access_log
|
||||||
|
|
||||||
|
# Location of cache files used by the scheduler...
|
||||||
|
#CacheDir /var/cache/cups
|
||||||
|
|
||||||
|
# Location of data files used by the scheduler...
|
||||||
|
#DataDir /usr/share/cups
|
||||||
|
|
||||||
|
# Location of the static web content served by the scheduler...
|
||||||
|
#DocumentRoot /usr/share/cups/doc
|
||||||
|
|
||||||
|
# Location of the file logging all messages produced by the scheduler and any
|
||||||
|
# helper programs; may be the name "syslog". If not an absolute path, the value
|
||||||
|
# of ServerRoot is used as the root directory. Also see the "LogLevel"
|
||||||
|
# directive in cupsd.conf.
|
||||||
|
ErrorLog /var/log/cups/error_log
|
||||||
|
|
||||||
|
# Location of the file logging all pages printed by the scheduler and any
|
||||||
|
# helper programs; may be the name "syslog". If not an absolute path, the value
|
||||||
|
# of ServerRoot is used as the root directory. Also see the "PageLogFormat"
|
||||||
|
# directive in cupsd.conf.
|
||||||
|
PageLog /var/log/cups/page_log
|
||||||
|
|
||||||
|
# Location of the file listing all of the local printers...
|
||||||
|
#Printcap /etc/printcap
|
||||||
|
|
||||||
|
# Format of the Printcap file...
|
||||||
|
#PrintcapFormat bsd
|
||||||
|
#PrintcapFormat plist
|
||||||
|
#PrintcapFormat solaris
|
||||||
|
|
||||||
|
# Location of all spool files...
|
||||||
|
#RequestRoot /var/spool/cups
|
||||||
|
|
||||||
|
# Location of helper programs...
|
||||||
|
#ServerBin /usr/lib/cups
|
||||||
|
|
||||||
|
# SSL/TLS keychain for the scheduler...
|
||||||
|
#ServerKeychain ssl
|
||||||
|
|
||||||
|
# Location of other configuration files...
|
||||||
|
#ServerRoot /etc/cups
|
||||||
|
|
||||||
|
# Location of scheduler state files...
|
||||||
|
#StateDir /run/cups
|
||||||
|
|
||||||
|
# Location of scheduler/helper temporary files. This directory is emptied on
|
||||||
|
# scheduler startup and cannot be one of the standard (public) temporary
|
||||||
|
# directory locations for security reasons...
|
||||||
|
#TempDir /var/spool/cups/tmp
|
||||||
@@ -1 +0,0 @@
|
|||||||
%wheel ALL=(ALL) NOPASSWD: /usr/bin/asdcontrol
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Switching between the stock providers is a one-click toggle in the network
|
||||||
|
# panel, so it must not stop for a password. Custom is deliberately absent: it
|
||||||
|
# points the machine at servers the caller supplies, and it already runs in a
|
||||||
|
# terminal that can ask.
|
||||||
|
%wheel ALL=(root) NOPASSWD: /usr/bin/omarchy-dns Cloudflare, /usr/bin/omarchy-dns Google, /usr/bin/omarchy-dns DHCP
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Theme switching is a menu action with no terminal to carry a password prompt,
|
||||||
|
# and it repaints the browser accent on every switch, so this one write must not
|
||||||
|
# stop for a password. The argument is spelled out as six hex digits rather than
|
||||||
|
# a wildcard: the grant covers a color and nothing else, and sudoers matches a
|
||||||
|
# command's arguments exactly, so it cannot be stretched into extra ones. The
|
||||||
|
# helper revalidates the same shape, since the terminal path does not come
|
||||||
|
# through this rule.
|
||||||
|
%wheel ALL=(root) NOPASSWD: /usr/bin/omarchy-theme-set-browser-policy [0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]
|
||||||
@@ -1 +1 @@
|
|||||||
%wheel ALL=(root) NOPASSWD: /usr/bin/timedatectl set-timezone *
|
%wheel ALL=(root) NOPASSWD: /usr/bin/timedatectl ^set-timezone [A-Za-z0-9_+][A-Za-z0-9_+.-]*(/[A-Za-z0-9_+][A-Za-z0-9_+.-]*)*$
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[Service]
|
||||||
|
User=cups-browsed
|
||||||
|
Group=cups-browsed
|
||||||
|
CacheDirectory=cups-browsed
|
||||||
|
CacheDirectoryMode=0750
|
||||||
|
UMask=0027
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
RestrictSUIDSGID=yes
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
u cups-browsed - "CUPS printer discovery" / -
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
run_logged "$OMARCHY_INSTALL/config/theme-system.sh"
|
run_logged "$OMARCHY_INSTALL/config/theme-system.sh"
|
||||||
|
run_logged "$OMARCHY_INSTALL/config/browser-policy.sh"
|
||||||
run_logged "$OMARCHY_INSTALL/config/increase-lockout-limit.sh"
|
run_logged "$OMARCHY_INSTALL/config/increase-lockout-limit.sh"
|
||||||
run_logged "$OMARCHY_INSTALL/config/lockscreen-pam.sh"
|
run_logged "$OMARCHY_INSTALL/config/lockscreen-pam.sh"
|
||||||
run_logged "$OMARCHY_INSTALL/config/fix-powerprofilesctl-shebang.sh"
|
run_logged "$OMARCHY_INSTALL/config/fix-powerprofilesctl-shebang.sh"
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||||
|
browser_policy_setup_dir /etc/chromium/policies/managed
|
||||||
+16
-10
@@ -1,10 +1,16 @@
|
|||||||
# Record the docker group for provisioning first-boot user creation and factory reset,
|
# The Docker daemon runs as root and its socket is root-owned, so membership in
|
||||||
# then grant it directly when the install user already exists (deferred-provisioning
|
# the docker group is equivalent to passwordless root: any process in it can
|
||||||
# installs create the user at first boot instead).
|
# `docker run -v /:/host` and rewrite the host as root. We therefore do NOT add
|
||||||
provisioning_dir="${OMARCHY_PROVISIONING_DIR:-/var/lib/omarchy/provisioning}"
|
# the install user to the docker group by default, so a single rogue process
|
||||||
mkdir -p "$provisioning_dir"
|
# running as the user cannot silently escalate to root.
|
||||||
grep -qxF docker "$provisioning_dir/groups" 2>/dev/null || echo docker >>"$provisioning_dir/groups"
|
#
|
||||||
|
# The daemon is still enabled (docker.socket, in enable-services.sh) for system
|
||||||
if [[ -n ${OMARCHY_INSTALL_USER:-} ]] && getent passwd "$OMARCHY_INSTALL_USER" >/dev/null; then
|
# use. The Docker TUI (Super + Shift + D) and the Windows VM reach it through a
|
||||||
usermod -aG docker "$OMARCHY_INSTALL_USER"
|
# polkit prompt, and the plain `docker` CLI runs under sudo. Users who want the
|
||||||
fi
|
# convenience back can opt in, behind a warning, with:
|
||||||
|
#
|
||||||
|
# omarchy-setup-security-sudoless-docker (Setup > Security > Sudoless Docker)
|
||||||
|
#
|
||||||
|
# Nothing to do here now that the group is no longer granted, but the file stays
|
||||||
|
# as the recorded home of this decision and a hook for future daemon config.
|
||||||
|
:
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
# Enable services only. Installs are followed by reboot, so don't start/reload
|
# Enable services only. Installs are followed by reboot, so don't start/reload
|
||||||
# daemons mid-install. UFW and hardware-gated services stay in their own scripts.
|
# daemons mid-install. UFW and hardware-gated services stay in their own scripts.
|
||||||
systemctl enable cups.service
|
systemctl enable cups.service
|
||||||
systemctl enable cups-browsed.service
|
|
||||||
systemctl enable avahi-daemon.service
|
systemctl enable avahi-daemon.service
|
||||||
systemctl enable linux-modules-cleanup.service
|
systemctl enable linux-modules-cleanup.service
|
||||||
systemctl enable docker.socket
|
systemctl enable docker.socket
|
||||||
systemctl enable systemd-resolved.service
|
systemctl enable systemd-resolved.service
|
||||||
systemctl enable NetworkManager.service
|
systemctl enable NetworkManager.service
|
||||||
# Don't let network-online.target (pulled in by cups-browsed) hold up
|
# Don't let network-online.target hold up graphical.target waiting for
|
||||||
# graphical.target waiting for DHCP/Wi-Fi association. Nothing in the session
|
# DHCP/Wi-Fi association. Nothing in the session needs to block on the network.
|
||||||
# needs to block on the network. Mirrors the systemd-networkd-wait-online mask
|
# Mirrors the systemd-networkd-wait-online mask in install/hardware/network.sh.
|
||||||
# in install/hardware/network.sh.
|
|
||||||
systemctl mask NetworkManager-wait-online.service
|
systemctl mask NetworkManager-wait-online.service
|
||||||
systemctl enable power-profiles-daemon.service
|
systemctl enable power-profiles-daemon.service
|
||||||
systemctl enable sddm.service
|
systemctl enable sddm.service
|
||||||
|
|||||||
@@ -6,11 +6,8 @@ ln -snf /usr/share/icons/Adwaita/symbolic/actions/go-next-symbolic.svg \
|
|||||||
/usr/share/icons/Yaru/scalable/actions/go-next-symbolic.svg
|
/usr/share/icons/Yaru/scalable/actions/go-next-symbolic.svg
|
||||||
gtk-update-icon-cache /usr/share/icons/Yaru &>/dev/null || true
|
gtk-update-icon-cache /usr/share/icons/Yaru &>/dev/null || true
|
||||||
|
|
||||||
# Chromium policy directory for theme
|
# Seed Chromium's first run: follow system appearance ("device") instead of dark,
|
||||||
mkdir -p /etc/chromium/policies/managed
|
# and skip the terms-of-service dialog Chromium 151 turned on by default.
|
||||||
chmod a+rw /etc/chromium/policies/managed
|
|
||||||
|
|
||||||
# Default Chromium to follow system appearance ("device") instead of dark
|
|
||||||
mkdir -p /usr/lib/chromium
|
mkdir -p /usr/lib/chromium
|
||||||
echo '{"browser":{"theme":{"color_scheme":0,"color_scheme2":0}}}' > \
|
echo '{"distribution":{"require_eula":false},"browser":{"theme":{"color_scheme":0,"color_scheme2":0}}}' > \
|
||||||
/usr/lib/chromium/initial_preferences
|
/usr/lib/chromium/initial_preferences
|
||||||
@@ -4,7 +4,6 @@ run_logged "$OMARCHY_INSTALL/hardware/dell-xps-touchpad-haptics.sh"
|
|||||||
run_logged "$OMARCHY_INSTALL/hardware/surface.sh"
|
run_logged "$OMARCHY_INSTALL/hardware/surface.sh"
|
||||||
|
|
||||||
run_logged "$OMARCHY_INSTALL/hardware/network.sh"
|
run_logged "$OMARCHY_INSTALL/hardware/network.sh"
|
||||||
run_logged "$OMARCHY_INSTALL/hardware/input-group.sh"
|
|
||||||
run_logged "$OMARCHY_INSTALL/hardware/set-wireless-regdom.sh"
|
run_logged "$OMARCHY_INSTALL/hardware/set-wireless-regdom.sh"
|
||||||
run_logged "$OMARCHY_INSTALL/hardware/fix-fkeys.sh"
|
run_logged "$OMARCHY_INSTALL/hardware/fix-fkeys.sh"
|
||||||
run_logged "$OMARCHY_INSTALL/hardware/fix-synaptic-touchpad.sh"
|
run_logged "$OMARCHY_INSTALL/hardware/fix-synaptic-touchpad.sh"
|
||||||
|
|||||||
@@ -1,6 +1,25 @@
|
|||||||
# Enable Synaptics InterTouch for confirmed touchpads if not already loaded
|
# Enable Synaptics InterTouch for confirmed touchpads if not already loaded
|
||||||
|
#
|
||||||
if grep -qi synaptics /proc/bus/input/devices \
|
# Only when modprobe can resolve psmouse for the running kernel, and never
|
||||||
&& ! lsmod | grep -q '^psmouse'; then
|
# fatally. Installs run this under arch-chroot, where uname -r still names the
|
||||||
modprobe psmouse synaptics_intertouch=1
|
# live ISO's kernel while /lib/modules holds the target's -- the two differ on
|
||||||
|
# every machine that is not a T2 Mac, so modprobe failed with "Module psmouse
|
||||||
|
# not found in directory /lib/modules/<live kernel>" and took the whole install
|
||||||
|
# down with it. Asking modprobe itself first (-qn resolves a module without
|
||||||
|
# loading it) keeps that mismatch quiet, and the warning below covers every
|
||||||
|
# other reason it declines. An optional touchpad improvement must not be able to
|
||||||
|
# halt an install.
|
||||||
|
#
|
||||||
|
# Loading a module into the live kernel does nothing for the installed system
|
||||||
|
# either way, so this only takes effect when it runs on the booted machine.
|
||||||
|
# Persisting the switch instead would mean writing options psmouse
|
||||||
|
# synaptics_intertouch=1 to /etc/modprobe.d, which forces the SMBus transport
|
||||||
|
# past the kernel's own allowlist on every touchpad merely named "synaptics" in
|
||||||
|
# /proc/bus/input/devices -- a wider change than this one, and not one to make
|
||||||
|
# blind.
|
||||||
|
if grep -qi synaptics "${OMARCHY_SYNAPTIC_INPUT_DEVICES:-/proc/bus/input/devices}" \
|
||||||
|
&& ! lsmod | grep -q '^psmouse' \
|
||||||
|
&& modprobe -qn psmouse; then
|
||||||
|
modprobe psmouse synaptics_intertouch=1 ||
|
||||||
|
echo "Warning: could not enable Synaptics InterTouch on psmouse" >&2
|
||||||
fi
|
fi
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# Give this user privileged input access for dictation tools + xbox controllers to work.
|
|
||||||
# Recorded for provisioning first-boot user creation and factory reset, granted directly
|
|
||||||
# when the install user already exists (deferred-provisioning installs create the user at
|
|
||||||
# first boot instead).
|
|
||||||
provisioning_dir="${OMARCHY_PROVISIONING_DIR:-/var/lib/omarchy/provisioning}"
|
|
||||||
mkdir -p "$provisioning_dir"
|
|
||||||
grep -qxF input "$provisioning_dir/groups" 2>/dev/null || echo input >>"$provisioning_dir/groups"
|
|
||||||
|
|
||||||
if [[ -n ${OMARCHY_INSTALL_USER:-} ]] && getent passwd "$OMARCHY_INSTALL_USER" >/dev/null; then
|
|
||||||
usermod -aG input "$OMARCHY_INSTALL_USER"
|
|
||||||
fi
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
as_root() {
|
||||||
|
if (( EUID == 0 )); then
|
||||||
|
"$@"
|
||||||
|
else
|
||||||
|
sudo "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
# Chromium-family machine policy is mandatory for every profile. Directories
|
||||||
|
# stay 0755 root:root; omarchy-theme-set-browser-policy is the privileged
|
||||||
|
# write for color.json.
|
||||||
|
|
||||||
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/as-root.sh"
|
||||||
|
|
||||||
|
BROWSER_POLICY_MANAGED_DIRS=(
|
||||||
|
/etc/chromium/policies/managed
|
||||||
|
/etc/opt/chrome/policies/managed
|
||||||
|
/etc/opt/edge/policies/managed
|
||||||
|
/etc/brave/policies/managed
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ancestors of the managed dirs, shortest first. A writable or attacker-owned
|
||||||
|
# parent can rename the leaf aside; install -d follows a planted symlink.
|
||||||
|
BROWSER_POLICY_PARENT_DIRS=(
|
||||||
|
/etc/chromium
|
||||||
|
/etc/chromium/policies
|
||||||
|
/etc/opt/chrome
|
||||||
|
/etc/opt/chrome/policies
|
||||||
|
/etc/opt/edge
|
||||||
|
/etc/opt/edge/policies
|
||||||
|
/etc/brave
|
||||||
|
/etc/brave/policies
|
||||||
|
)
|
||||||
|
|
||||||
|
BROWSER_POLICY_FIREFOX_DIRS=(
|
||||||
|
/usr/lib/firefox/distribution
|
||||||
|
/opt/zen-browser/distribution
|
||||||
|
)
|
||||||
|
|
||||||
|
BROWSER_POLICY_DEFAULT_COLOR="#1c2027"
|
||||||
|
|
||||||
|
browser_policy_purge_dir() {
|
||||||
|
local dir=$1
|
||||||
|
|
||||||
|
as_root find "$dir" -mindepth 1 -maxdepth 1 ! -user root -exec rm -rf -- {} +
|
||||||
|
}
|
||||||
|
|
||||||
|
browser_policy_parent_hardened() {
|
||||||
|
local dir=$1
|
||||||
|
|
||||||
|
[[ -d $dir && ! -L $dir ]] || return 1
|
||||||
|
[[ $(stat -c '%a' "$dir") == "755" ]] || return 1
|
||||||
|
[[ $(stat -c '%U' "$dir") == "root" ]] || return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
browser_policy_dir_hardened() {
|
||||||
|
browser_policy_parent_hardened "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
browser_policy_parents_hardened() {
|
||||||
|
local dir=$1
|
||||||
|
local parent
|
||||||
|
|
||||||
|
for parent in "${BROWSER_POLICY_PARENT_DIRS[@]}"; do
|
||||||
|
[[ $dir == "$parent"/* ]] || continue
|
||||||
|
[[ -e $parent || -L $parent ]] || continue
|
||||||
|
browser_policy_parent_hardened "$parent" || return 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
browser_policy_setup_parent() {
|
||||||
|
local dir=$1
|
||||||
|
|
||||||
|
if [[ -L $dir || ( -e $dir && ! -d $dir ) ]]; then
|
||||||
|
as_root rm -rf -- "$dir"
|
||||||
|
fi
|
||||||
|
as_root install -d -m 0755 -o root -g root "$dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
browser_policy_setup_parents_for() {
|
||||||
|
local dir=$1
|
||||||
|
local parent
|
||||||
|
|
||||||
|
for parent in "${BROWSER_POLICY_PARENT_DIRS[@]}"; do
|
||||||
|
[[ $dir == "$parent"/* ]] || continue
|
||||||
|
browser_policy_setup_parent "$parent"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
browser_policy_setup_dir() {
|
||||||
|
local dir=$1
|
||||||
|
|
||||||
|
browser_policy_setup_parents_for "$dir"
|
||||||
|
browser_policy_setup_parent "$dir"
|
||||||
|
browser_policy_purge_dir "$dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Themes are user-installed. Accept only three 0-255 components.
|
||||||
|
browser_policy_theme_hex() {
|
||||||
|
local theme_rgb=$1
|
||||||
|
|
||||||
|
if [[ $theme_rgb =~ ^[[:space:]]*([0-9]{1,3})[[:space:]]*,[[:space:]]*([0-9]{1,3})[[:space:]]*,[[:space:]]*([0-9]{1,3})[[:space:]]*$ ]] &&
|
||||||
|
(( 10#${BASH_REMATCH[1]} < 256 && 10#${BASH_REMATCH[2]} < 256 && 10#${BASH_REMATCH[3]} < 256 )); then
|
||||||
|
printf '#%02x%02x%02x' "$((10#${BASH_REMATCH[1]}))" "$((10#${BASH_REMATCH[2]}))" "$((10#${BASH_REMATCH[3]}))"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s' "$BROWSER_POLICY_DEFAULT_COLOR"
|
||||||
|
}
|
||||||
|
|
||||||
|
browser_policy_install_color() {
|
||||||
|
local policy_dir=$1
|
||||||
|
local hex=$2
|
||||||
|
local dest=$policy_dir/color.json
|
||||||
|
local tmp
|
||||||
|
|
||||||
|
[[ -d $policy_dir && ! -L $policy_dir ]] || return 0
|
||||||
|
[[ $hex =~ ^#[0-9a-f]{6}$ ]] || return 1
|
||||||
|
|
||||||
|
tmp=$(mktemp) || return 1
|
||||||
|
printf '{"BrowserThemeColor": "%s", "BrowserColorScheme": "device"}\n' "$hex" >"$tmp"
|
||||||
|
|
||||||
|
if [[ -L $dest || -d $dest ]]; then
|
||||||
|
if ! rm -rf -- "$dest" 2>/dev/null; then
|
||||||
|
rm -f "$tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if install -m 0644 -T "$tmp" "$dest" 2>/dev/null; then
|
||||||
|
rm -f "$tmp"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$tmp"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
browser_policy_firefox_policy_file_ok() {
|
||||||
|
local file=$1
|
||||||
|
local mode
|
||||||
|
local group_write
|
||||||
|
local other_write
|
||||||
|
|
||||||
|
[[ -f $file && ! -L $file ]] || return 1
|
||||||
|
[[ $(stat -c '%U' "$file") == "root" ]] || return 1
|
||||||
|
mode=$(stat -c '%a' "$file")
|
||||||
|
group_write=$((8#${mode: -2:1}))
|
||||||
|
other_write=$((8#${mode: -1}))
|
||||||
|
(( (group_write & 2) == 0 && (other_write & 2) == 0 ))
|
||||||
|
}
|
||||||
|
|
||||||
|
browser_policy_firefox_hardened() {
|
||||||
|
local dir=$1
|
||||||
|
|
||||||
|
[[ -d $dir && ! -L $dir ]] || return 1
|
||||||
|
[[ $(stat -c '%a' "$dir") == "755" ]] || return 1
|
||||||
|
[[ $(stat -c '%U' "$dir") == "root" ]] || return 1
|
||||||
|
browser_policy_firefox_policy_file_ok "$dir/policies.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
browser_policy_install_firefox_policies() {
|
||||||
|
local distribution_dir=$1
|
||||||
|
local policies=${2:-$OMARCHY_PATH/default/firefox/policies.json}
|
||||||
|
|
||||||
|
as_root install -m 644 -o root -g root -T "$policies" "$distribution_dir/policies.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
browser_policy_setup_firefox_distribution() {
|
||||||
|
local distribution_dir=$1
|
||||||
|
local policies=${2:-$OMARCHY_PATH/default/firefox/policies.json}
|
||||||
|
|
||||||
|
browser_policy_setup_parent "$distribution_dir"
|
||||||
|
browser_policy_purge_dir "$distribution_dir"
|
||||||
|
browser_policy_install_firefox_policies "$distribution_dir" "$policies"
|
||||||
|
}
|
||||||
@@ -17,9 +17,8 @@ chromium
|
|||||||
clang
|
clang
|
||||||
cliamp
|
cliamp
|
||||||
cups
|
cups
|
||||||
cups-browsed
|
|
||||||
cups-filters
|
cups-filters
|
||||||
cups-pdf
|
cups-pk-helper
|
||||||
ddcutil
|
ddcutil
|
||||||
docker
|
docker
|
||||||
docker-buildx
|
docker-buildx
|
||||||
@@ -78,7 +77,7 @@ lua51
|
|||||||
luarocks
|
luarocks
|
||||||
man-db
|
man-db
|
||||||
mariadb-libs
|
mariadb-libs
|
||||||
mise
|
mise-bin
|
||||||
moonlight-qt
|
moonlight-qt
|
||||||
mpv
|
mpv
|
||||||
mpv-mpris
|
mpv-mpris
|
||||||
@@ -108,7 +107,8 @@ python-poetry-core
|
|||||||
ttfx
|
ttfx
|
||||||
qemu-user-static-binfmt
|
qemu-user-static-binfmt
|
||||||
qrencode
|
qrencode
|
||||||
quickshell-git
|
qt6-imageformats
|
||||||
|
quickshell
|
||||||
ripgrep
|
ripgrep
|
||||||
ruby
|
ruby
|
||||||
tensaku
|
tensaku
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
cp -f "$OMARCHY_PATH/default/pacman/pacman-${OMARCHY_MIRROR:-stable}.conf" /etc/pacman.conf
|
cp -f "$OMARCHY_PATH/default/pacman/pacman-${OMARCHY_MIRROR:-stable}.conf" /etc/pacman.conf
|
||||||
cp -f "$OMARCHY_PATH/default/pacman/mirrorlist-${OMARCHY_MIRROR:-stable}" /etc/pacman.d/mirrorlist
|
cp -f "$OMARCHY_PATH/default/pacman/mirrorlist-${OMARCHY_MIRROR:-stable}" /etc/pacman.d/mirrorlist
|
||||||
|
|
||||||
# omarchy-settings skips this override until cups-browsed is actually present
|
# Wait for CUPS to own the file, the way omarchy-settings does, so pacman does
|
||||||
# to avoid pacman creating cups-browsed.conf.pacnew during ISO package install.
|
# not turn the override into a .pacnew during ISO package installation.
|
||||||
if [[ -f $OMARCHY_PATH/etc-overrides/cups-cups-browsed.conf && -d /etc/cups ]]; then
|
if [[ -f $OMARCHY_PATH/etc-overrides/cups-cups-files.conf && -f /etc/cups/cups-files.conf ]]; then
|
||||||
cp -f "$OMARCHY_PATH/etc-overrides/cups-cups-browsed.conf" /etc/cups/cups-browsed.conf
|
install -m 0640 -o root -g cups "$OMARCHY_PATH/etc-overrides/cups-cups-files.conf" /etc/cups/cups-files.conf
|
||||||
rm -f /etc/cups/cups-browsed.conf.pacnew
|
rm -f /etc/cups/cups-files.conf.pacnew
|
||||||
fi
|
fi
|
||||||
|
|
||||||
source "$OMARCHY_INSTALL/hardware/pacman.sh"
|
source "$OMARCHY_INSTALL/hardware/pacman.sh"
|
||||||
@@ -79,7 +79,7 @@ Turkish|trq
|
|||||||
Ukrainian|ua'
|
Ukrainian|ua'
|
||||||
|
|
||||||
OMARCHY_USERNAME_PATTERN='^[a-z_][a-z0-9_-]*[$]?$'
|
OMARCHY_USERNAME_PATTERN='^[a-z_][a-z0-9_-]*[$]?$'
|
||||||
OMARCHY_RESERVED_USERNAMES='^(root|bin|daemon|mail|ftp|http|nobody|dbus|systemd-coredump|systemd-network|systemd-oom|systemd-journal-remote|systemd-resolve|systemd-timesync|tss|uuidd|alpm|git|avahi|cups|lp|_talkd|polkitd|rtkit|qemu|brltty|gluster|rpc|libvirt-qemu|pcscd|nvidia-persistenced|sddm)$'
|
OMARCHY_RESERVED_USERNAMES='^(root|bin|daemon|mail|ftp|http|nobody|dbus|systemd-coredump|systemd-network|systemd-oom|systemd-journal-remote|systemd-resolve|systemd-timesync|tss|uuidd|alpm|git|avahi|cups|cups-browsed|lp|_talkd|polkitd|rtkit|qemu|brltty|gluster|rpc|libvirt-qemu|pcscd|nvidia-persistenced|sddm)$'
|
||||||
OMARCHY_HOSTNAME_PATTERN='^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?$'
|
OMARCHY_HOSTNAME_PATTERN='^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?$'
|
||||||
OMARCHY_HOSTNAME_DEFAULT='omarchy'
|
OMARCHY_HOSTNAME_DEFAULT='omarchy'
|
||||||
|
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ set -e
|
|||||||
if omarchy-done ensure voxtype-install-invitation; then
|
if omarchy-done ensure voxtype-install-invitation; then
|
||||||
omarchy-notification-send -u critical -g "Install Dictation with Voxtype" \
|
omarchy-notification-send -u critical -g "Install Dictation with Voxtype" \
|
||||||
"Click to install voice dictation for Omarchy." \
|
"Click to install voice dictation for Omarchy." \
|
||||||
--exec "omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install"
|
--exec omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install
|
||||||
fi
|
fi
|
||||||
@@ -7,5 +7,5 @@ set -e
|
|||||||
if [[ -z $(omarchy-default-agent) ]] && omarchy-done ensure agent-setup-invitation; then
|
if [[ -z $(omarchy-default-agent) ]] && omarchy-done ensure agent-setup-invitation; then
|
||||||
omarchy-notification-send -u critical -g "Set your default agent" \
|
omarchy-notification-send -u critical -g "Set your default agent" \
|
||||||
"Let your favorite agent help with Omarchy." \
|
"Let your favorite agent help with Omarchy." \
|
||||||
--exec "omarchy menu summon setup.default.agent"
|
--exec omarchy menu summon setup.default.agent
|
||||||
fi
|
fi
|
||||||
@@ -8,5 +8,5 @@ if omarchy-hw-fingerprint && [[ ! -f /etc/pam.d/omarchy-lock-fingerprint ]] &&
|
|||||||
omarchy-done ensure fingerprint-setup-invitation; then
|
omarchy-done ensure fingerprint-setup-invitation; then
|
||||||
omarchy-notification-send -u critical -g "Setup Fingerprint Reader" \
|
omarchy-notification-send -u critical -g "Setup Fingerprint Reader" \
|
||||||
"Enable sudo and unlocking with your fingerprint." \
|
"Enable sudo and unlocking with your fingerprint." \
|
||||||
--exec "omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fingerprint"
|
--exec omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fingerprint
|
||||||
fi
|
fi
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
notify_update() {
|
notify_update() {
|
||||||
omarchy-notification-send -u critical -g "Update System" "Click to update the system." \
|
omarchy-notification-send -u critical -g "Update System" "Click to update the system." \
|
||||||
--exec "omarchy-launch-floating-terminal-with-presentation omarchy-update"
|
--exec omarchy-launch-floating-terminal-with-presentation omarchy-update
|
||||||
}
|
}
|
||||||
|
|
||||||
notify_wifi() {
|
notify_wifi() {
|
||||||
omarchy-notification-send -u critical -g "Setup Wi-Fi" "Click to configure the wireless network." \
|
omarchy-notification-send -u critical -g "Setup Wi-Fi" "Click to configure the wireless network." \
|
||||||
--exec "omarchy-shell shell toggle omarchy.network"
|
--exec omarchy-shell shell toggle omarchy.network
|
||||||
}
|
}
|
||||||
|
|
||||||
announce_network() {
|
announce_network() {
|
||||||
|
|||||||
Loaded 100 of 265 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user