Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
17dcdd1
Prevent lock-service teardown and recover poisoned shells
AFOliveira Aug 29, 2026
3b7e678
Wake and refocus the lock screen from keyboard input
AFOliveira Aug 29, 2026
1136a71
OM-SEC-14: Run update hooks without reusable sudo authority
AFOliveira Aug 31, 2026
87625c2
OM-SEC-01: Make passwordless sudo expiry fail closed
AFOliveira Aug 31, 2026
2f816a8
Document privileged Bash startup exception
AFOliveira Sep 1, 2026
07866fa
Fail closed on unknown lock ownership
AFOliveira Sep 5, 2026
eed9bc2
Merge remote-tracking branch 'upstream/quattro' into local/pr-8930
AFOliveira Sep 5, 2026
be8e140
Close lock recovery and display wake gaps
AFOliveira Sep 5, 2026
f1fedaf
Close relock restart and wake recovery gaps
AFOliveira Sep 5, 2026
145e446
Recover bounded shell launch and display blank failures
AFOliveira Sep 5, 2026
b8194db
Bound shell replacement recovery deadline
AFOliveira Sep 5, 2026
537ebe2
Own ambiguous shell shutdown through replacement
AFOliveira Sep 5, 2026
7f9a401
Merge commit 'b71dcad96e9d0b2962b7d225828a5cb6000ad720' into codex/re…
AFOliveira Sep 6, 2026
35b318e
Complete command-scoped authentication across update phases
AFOliveira Sep 6, 2026
c52381f
Keep update regressions isolated from host authentication
AFOliveira Sep 6, 2026
f2c3925
Revoke before session cleanup and protect standalone inhibition
AFOliveira Sep 6, 2026
1f8f5ba
Integrate current quattro before security review
AFOliveira Sep 6, 2026
a9e9954
Clarify unattended updates still require sudo authorization
AFOliveira Sep 6, 2026
978dffa
Use required gum directly in AI removal prompts
AFOliveira Sep 6, 2026
d41ca88
Wait for the actual Stay Awake launcher
AFOliveira Sep 6, 2026
177d572
Wait for notifications when restarting the shell
AFOliveira Sep 6, 2026
35551b0
Disable the Yay sudo loop with its supported option
AFOliveira Sep 6, 2026
aa422fc
Merge current Quattro while preserving command checks
AFOliveira Sep 7, 2026
5b692c5
Preserve user PATH across updater relaunches
AFOliveira Sep 7, 2026
f37c73f
Bind protected update commands to their source root
AFOliveira Sep 7, 2026
5bc41b2
Resolve security libraries beside canonical entrypoints
AFOliveira Sep 7, 2026
c869740
Keep channel transitions inside command-scoped sudo
AFOliveira Sep 7, 2026
0add43f
Check dev update support before changing the system
AFOliveira Sep 7, 2026
bc235d2
Merge remote-tracking branch 'refs/remotes/portfolio/quattro' into co…
AFOliveira Sep 7, 2026
4ae25cd
Keep temporary sudo grants bounded through lifecycle failures
AFOliveira Sep 7, 2026
a6385e6
Bound sudo policy natively and guard expiry package transactions
AFOliveira Sep 7, 2026
75e58f0
Integrate the shared sudo lifecycle foundation
AFOliveira Sep 7, 2026
23dea4f
Merge lock lifecycle hardening
AFOliveira Sep 7, 2026
4aea51f
Handle Quickshell empty instance registry
AFOliveira Sep 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Three documentation trees, split by genre and audience:
- Prefer `(( ))` over numeric operators inside `[[ ]]` (e.g., `(( count < 50 ))`, not `[[ $count -lt 50 ]]`)
- Prefer a full `if`/`else` conditional for simple two-path control flow; don't rely on `exec` or `exit` in one branch to make following statements unreachable
- For strings/paths with spaces, quote them instead of escaping spaces with `\ ` (e.g., `"$APP_DIR/Disk Usage.desktop"`, not `$APP_DIR/Disk\ Usage.desktop`)
- Shebangs must use `#!/bin/bash` consistently (never `#!/usr/bin/env bash`)
- Shebangs must use `#!/bin/bash` consistently (never `#!/usr/bin/env bash`). A security-sensitive entrypoint may use the exact `#!/bin/bash -p` form only when it must suppress `BASH_ENV` and exported-function startup injection before its first command; that exception must be explained at the boundary and covered by a regression that rejects an ordinary Bash launch with a decoy `-p` argument.
- Scripts under `install/` and `migrations/` may be sourced and intentionally omit shebangs

# Command Naming
Expand Down
38 changes: 33 additions & 5 deletions bin/omarchy-channel-set
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
#!/bin/bash
#!/bin/bash -p

# omarchy:summary=Set the Omarchy package channel.
# omarchy:args=<stable|rc|edge|dev>
# omarchy:requires-sudo=true

if [[ $- != *p* ]]; then
echo "Refusing an unsafe Bash startup for channel switching." >&2
exit 126
fi
security_entrypoint=$(/usr/bin/readlink -e -- "${BASH_SOURCE[0]}") || exit 126
source "${security_entrypoint%/*}/omarchy-security-functions" || exit 126
omarchy_security_require_privileged_bash_startup || exit 126
set -euo pipefail
omarchy_security_sanitize_bash_environment "$0" "$@"
omarchy_security_require_source_root "$0"
user_path=$PATH
omarchy_security_revoke_sudo_timestamp || exit 1
omarchy_security_install_sudo_cleanup_traps
omarchy_security_enable_no_update_sudo

usage() { echo "Usage: omarchy-channel-set [stable|rc|edge|dev]"; }
fail() { echo "Error: $*" >&2; exit 1; }
Expand Down Expand Up @@ -33,9 +46,18 @@ validate_dev_checkout() {
}

link_dev_checkout() {
local checkout="$1"
local checkout="$1" required
[[ -d $checkout/.git ]] || git clone https://github.com/basecamp/omarchy.git "$checkout"

# Check the destination before changing /etc/omarchy.conf or sudo's path.
# An existing checkout is not pulled automatically and may predate this policy.
for required in bin/omarchy-security-functions bin/omarchy-update bin/omarchy-refresh-pacman default/omarchy/sudo-no-update/sudo; do
if [[ ! -f $checkout/$required || ! -r $checkout/$required ||
( $required != "bin/omarchy-security-functions" && ! -x $checkout/$required ) ]]; then
fail "Update the checkout before switching to dev; missing required update support in $required."
fi
done

omarchy-dev-link "$checkout" --no-reboot
}

Expand Down Expand Up @@ -79,21 +101,27 @@ fi
if [[ -n $dev_checkout ]]; then
link_dev_checkout "$dev_checkout"
export OMARCHY_PATH="$dev_checkout"
export PATH="$OMARCHY_PATH/bin:$PATH"
omarchy_security_enable_no_update_sudo
omarchy-state set reboot-required
fi

omarchy-refresh-pacman "$pacman_channel"
omarchy-refresh-pacman "$pacman_channel" defer-hook
# --ask 4 accepts omarchy <-> omarchy-dev replacement prompts without file overwrites.
sudo env OMARCHY_UPDATE_PACMAN=1 pacman -S --needed --noconfirm --ask 4 "${packages[@]}"

if [[ -z $dev_checkout ]]; then
omarchy-dev-unlink --no-reboot
export OMARCHY_PATH=/usr/share/omarchy
omarchy_security_enable_no_update_sudo

if (( leaving_dev )); then
omarchy-state set reboot-required
fi
fi

omarchy-update -y
OMARCHY_UPDATE_USER_PATH="$user_path" "$OMARCHY_PATH/bin/omarchy-update" -y

# No channel-owned privileged work follows the historical refresh hook.
omarchy_security_revoke_sudo_timestamp
PATH="$OMARCHY_PATH/default/omarchy/sudo-no-update:$user_path" \
"$OMARCHY_PATH/bin/omarchy-refresh-pacman" "$pacman_channel" run-deferred
2 changes: 1 addition & 1 deletion bin/omarchy-hyprland-session-locked
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# on a monitor with no workspace yet, before it ever reaches the lock, so a
# missing LOCK there means nothing was asked. Callers branching only on success
# treat 2 as unlocked.
monitors=$(hyprctl -j monitors 2>/dev/null) || exit 2
monitors=$(/usr/bin/timeout --kill-after=0.1s 1s hyprctl -j monitors 2>/dev/null) || exit 2

state=$(jq '
def blockers: .solitaryBlockedBy // [];
Expand Down
54 changes: 38 additions & 16 deletions bin/omarchy-refresh-pacman
Original file line number Diff line number Diff line change
@@ -1,26 +1,48 @@
#!/bin/bash
#!/bin/bash -p

# omarchy:summary=Overwrite the package configuration for /etc/pacman with the Omarchy default of using its dedicated mirrors and repositories, then update all packages.
# omarchy:requires-sudo=true

sudo cp -f /etc/pacman.conf /etc/pacman.conf.bak
sudo cp -f /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.bak
if [[ $- != *p* ]]; then
echo "Refusing an unsafe Bash startup." >&2
exit 126
fi

channel="${1:-stable}"
security_entrypoint=$(/usr/bin/readlink -e -- "${BASH_SOURCE[0]}") || exit 126
source "${security_entrypoint%/*}/omarchy-security-functions" || exit 126
omarchy_security_require_privileged_bash_startup || exit 126
set -e
omarchy_security_sanitize_bash_environment "$0" "$@"
omarchy_security_require_source_root "$0"
user_path=$PATH
omarchy_security_revoke_sudo_timestamp || exit 1
omarchy_security_install_sudo_cleanup_traps
omarchy_security_enable_no_update_sudo

channel="${1:-stable}"
hook_mode="${2:-normal}"
if [[ $channel != "stable" && $channel != "rc" && $channel != "edge" ]]; then
echo "Error: Invalid channel '$channel'. Must be one of: stable, rc, edge"
exit 1
echo "Invalid channel: $channel" >&2
exit 2
fi
if [[ $hook_mode != "normal" && $hook_mode != "defer-hook" && $hook_mode != "run-deferred" ]]; then
echo "Invalid refresh hook mode: $hook_mode" >&2
exit 2
fi

echo "Setting channel to $channel"
echo

sudo cp -f "$OMARCHY_PATH/default/pacman/pacman-$channel.conf" /etc/pacman.conf
sudo cp -f "$OMARCHY_PATH/default/pacman/mirrorlist-$channel" /etc/pacman.d/mirrorlist

# Allow user customization of /etc/pacman.conf before the upgrade runs
omarchy-hook pre-refresh-pacman
if [[ $hook_mode != "run-deferred" ]]; then
sudo cp -f /etc/pacman.conf /etc/pacman.conf.bak
sudo cp -f /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.bak
echo "Setting channel to $channel"
sudo cp -f "$OMARCHY_PATH/default/pacman/pacman-$channel.conf" /etc/pacman.conf
sudo cp -f "$OMARCHY_PATH/default/pacman/mirrorlist-$channel" /etc/pacman.d/mirrorlist
sudo env OMARCHY_UPDATE_PACMAN=1 pacman -Syyuu --noconfirm
fi

# Reset all package DBs and then update
sudo env OMARCHY_UPDATE_PACMAN=1 pacman -Syyuu --noconfirm
# Keep the historical hook name, but finish every privileged refresh operation
# before running user code. Callers with later root work can defer the hook.
if [[ $hook_mode != "defer-hook" ]]; then
omarchy_security_revoke_sudo_timestamp
PATH="$OMARCHY_PATH/default/omarchy/sudo-no-update:$user_path" \
"$OMARCHY_PATH/bin/omarchy-hook" pre-refresh-pacman
fi
2 changes: 1 addition & 1 deletion bin/omarchy-remove-ai-hermes
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ fi
# never owned, a yes takes that with it, and saying so is the prompt's job.
# Without a terminal to ask in, keeping everything is the answer.
data_removed=false
if [[ -d $HOME/.hermes || -d $HOME/.config/Hermes ]] && [[ -t 0 ]] && omarchy-cmd-present gum; then
if [[ -d $HOME/.hermes || -d $HOME/.config/Hermes ]] && [[ -t 0 ]]; then
# du answers non-zero when either directory is missing, and pipefail would
# turn that into an aborted removal; the size is worth no such thing.
size=$(du -shc "$HOME/.hermes" "$HOME/.config/Hermes" 2>/dev/null | tail -1 | cut -f1 || true)
Expand Down
2 changes: 1 addition & 1 deletion bin/omarchy-remove-ai-openclaw
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ gtk-update-icon-cache "$HOME/.local/share/icons/hicolor" &>/dev/null || true
# front of the user with the size rather than left silent. Without a terminal
# to ask in, keeping it is the answer.
state_removed=false
if [[ -d $HOME/.openclaw && -t 0 ]] && omarchy-cmd-present gum; then
if [[ -d $HOME/.openclaw && -t 0 ]]; then
size=$(du -sh "$HOME/.openclaw" 2>/dev/null | cut -f1)
if gum confirm --default=false "Also delete ~/.openclaw ($size: chats, memories, credentials, and downloaded plugins)?"; then
rm -rf "$HOME/.openclaw"
Expand Down
Loading