diff --git a/bin/omarchy-dev-link b/bin/omarchy-dev-link index 3345e29d..959136ba 100755 --- a/bin/omarchy-dev-link +++ b/bin/omarchy-dev-link @@ -1,26 +1,24 @@ #!/bin/bash -# omarchy:summary=Point Omarchy at a local checkout for live editing +# omarchy:summary=Point Omarchy at a local checkout after reboot # omarchy:group=dev # omarchy:args= # omarchy:examples=omarchy dev link ~/Work/omarchy/omarchy-installer set -euo pipefail -# Sudo wipes HYPRLAND_INSTANCE_SIGNATURE, so the live-session refresh below -# can't reach hyprctl. Run as user; we sudo internally for the conf write. -if [[ $EUID -eq 0 ]]; then +if (( EUID == 0 )); then echo "Error: run omarchy-dev-link as your user, not under sudo." >&2 exit 1 fi -if [[ $# -ne 1 || $1 == "-h" || $1 == "--help" ]]; then +if (( $# != 1 )) || [[ $1 == "-h" || $1 == "--help" ]]; then cat < Writes /etc/omarchy.conf so OMARCHY_PATH resolves to -in all new shells, the Hyprland session, and Quickshell. Restarts -omarchy-shell and reloads hyprctl so changes take effect immediately. +after reboot. This intentionally does not rewrite the running Hyprland, +systemd, shell, or app-launcher environment; reboot to make every layer agree. Affects only \$OMARCHY_PATH-resolved trees: bin/, default/, shell/, themes/, applications/, config/. Files installed at fixed system paths @@ -31,50 +29,33 @@ USAGE exit 0 fi +omarchy_conf_quote() { + local value="$1" + value=${value//\\/\\\\} + value=${value//\"/\\\"} + value=${value//\$/\\\$} + value=${value//\`/\\\`} + printf '"%s"' "$value" +} + target=$(realpath -e "$1" 2>/dev/null) || { echo "Error: path does not exist: $1" >&2 exit 1 } for required in bin default shell; do - if [[ ! -d "$target/$required" ]]; then + if [[ ! -d $target/$required ]]; then echo "Warning: $target/$required not found — does this look like an Omarchy source checkout?" >&2 fi done -# Strip any previous dev-link's bin/ from PATH before adding the new one, -# so repeated link calls don't accumulate stale entries. -prior_target="" -if [[ -f /etc/omarchy.conf ]]; then - prior_target=$(sed -n 's/^[[:space:]]*export[[:space:]]\+OMARCHY_PATH="\?\([^"]*\)"\?/\1/p' /etc/omarchy.conf | tail -1) -fi - -echo "Pointing Omarchy at $target" -printf 'export OMARCHY_PATH="%s"\n' "$target" | sudo tee /etc/omarchy.conf >/dev/null - -export OMARCHY_PATH="$target" -if [[ -n $prior_target && $prior_target != "$target" ]]; then - PATH=$(printf '%s' "$PATH" | tr ':' '\n' | grep -vFx "$prior_target/bin" | paste -sd:) -fi -export PATH="$target/bin:$PATH" - -if command -v hyprctl >/dev/null 2>&1 && hyprctl version &>/dev/null; then - hyprctl setenv OMARCHY_PATH "$target" >/dev/null - hyprctl setenv PATH "$PATH" >/dev/null - echo " Updated Hyprland session env." - - systemctl --user import-environment OMARCHY_PATH PATH 2>/dev/null || true - echo " Updated systemd --user env." - - if pgrep -x quickshell >/dev/null 2>&1; then - omarchy-restart-shell - echo " Restarted omarchy-shell." - fi - - hyprctl reload >/dev/null - echo " Reloaded Hyprland config." -fi +{ + printf 'export OMARCHY_PATH=' + omarchy_conf_quote "$target" + printf '\n' +} | sudo tee /etc/omarchy.conf >/dev/null +echo "Pointed Omarchy at $target" echo -echo "Done. Open a new shell (or restart existing ones) to pick up the new" -echo "OMARCHY_PATH. Run 'omarchy dev unlink' to restore the package install." +echo "Reboot to activate the linked checkout in Hyprland, app launchers, and new shells." +echo "Run 'omarchy dev unlink' and reboot to restore the package install." diff --git a/bin/omarchy-dev-status b/bin/omarchy-dev-status index 8cf621a9..07c66093 100755 --- a/bin/omarchy-dev-status +++ b/bin/omarchy-dev-status @@ -3,6 +3,8 @@ # omarchy:summary=Show the current Omarchy dev-link state # omarchy:group=dev +set -euo pipefail + default_target="/usr/share/omarchy" configured="$default_target" conf_present=0 @@ -22,8 +24,9 @@ if [[ -f /etc/omarchy.conf ]]; then fi if (( linked )); then - echo "dev-link: ACTIVE" + echo "dev-link: configured" echo " /etc/omarchy.conf -> OMARCHY_PATH=$configured" + echo " status: reboot required before all session layers use this checkout" else echo "dev-link: inactive" if (( conf_present )); then @@ -33,19 +36,7 @@ fi echo " current shell: OMARCHY_PATH=${OMARCHY_PATH:-}" -if (( linked )) && [[ ${OMARCHY_PATH:-} != "$configured" ]]; then +if [[ ${OMARCHY_PATH:-$default_target} != "$configured" ]]; then echo - echo "Note: this shell predates dev-link. Open a new shell to pick up the change," - echo "or 'export OMARCHY_PATH=$configured' to update just this shell." -elif (( ! linked )) && [[ ${OMARCHY_PATH:-$default_target} != "$default_target" ]]; then - echo - echo "Note: no dev-link is configured, but this shell still has a stale OMARCHY_PATH." - echo "Open a new shell or run 'export OMARCHY_PATH=$default_target'." -fi - -if command -v hyprctl >/dev/null 2>&1 && hyprctl version &>/dev/null; then - hypr_path=$(hyprctl getoption -j env 2>/dev/null | sed -n 's/.*OMARCHY_PATH=\([^"]*\).*/\1/p' | head -1) - if [[ -n "$hypr_path" ]]; then - echo " hyprland session: OMARCHY_PATH=$hypr_path" - fi + echo "Note: the running session does not match /etc/omarchy.conf. Reboot to settle it." fi diff --git a/bin/omarchy-dev-unlink b/bin/omarchy-dev-unlink index 15f5783a..b50000e4 100755 --- a/bin/omarchy-dev-unlink +++ b/bin/omarchy-dev-unlink @@ -1,11 +1,11 @@ #!/bin/bash -# omarchy:summary=Restore Omarchy to the package install (undo omarchy-dev-link) +# omarchy:summary=Restore Omarchy to the package install after reboot # omarchy:group=dev set -euo pipefail -if [[ $EUID -eq 0 ]]; then +if (( EUID == 0 )); then echo "Error: run omarchy-dev-unlink as your user, not under sudo." >&2 exit 1 fi @@ -14,69 +14,17 @@ if [[ ${1:-} == "-h" || ${1:-} == "--help" ]]; then cat <}" - else - echo "/etc/omarchy.conf already points at $default_target." - fi -elif [[ ${OMARCHY_PATH:-$default_target} != "$default_target" ]]; then - prior_target="$OMARCHY_PATH" - echo "Not currently linked: /etc/omarchy.conf is absent." - echo "This shell still has OMARCHY_PATH=$OMARCHY_PATH; writing the default guard." -else - echo "Not currently linked. Writing the default OMARCHY_PATH guard." -fi printf 'export OMARCHY_PATH="%s"\n' "$default_target" | sudo tee /etc/omarchy.conf >/dev/null + echo "Set /etc/omarchy.conf -> OMARCHY_PATH=$default_target" - -export OMARCHY_PATH="$default_target" -if [[ -n $prior_target && $prior_target != "$default_target" ]]; then - PATH=$(printf '%s' "$PATH" | tr ':' '\n' | awk -v drop="$prior_target/bin" '$0 != drop' | paste -sd:) - export PATH -fi - -if command -v systemctl >/dev/null 2>&1; then - if systemctl --user import-environment OMARCHY_PATH PATH 2>/dev/null; then - echo " Updated systemd --user env." - fi -fi - -if command -v hyprctl >/dev/null 2>&1 && hyprctl version &>/dev/null; then - hyprctl setenv OMARCHY_PATH "$default_target" >/dev/null - hyprctl setenv PATH "$PATH" >/dev/null - echo " Updated Hyprland session env." - - if pgrep -x quickshell >/dev/null 2>&1; then - omarchy-restart-shell - echo " Restarted omarchy-shell." - fi - - hyprctl reload >/dev/null - echo " Reloaded Hyprland config." -fi - echo -if (( linked )) || [[ -n $prior_target ]]; then - echo "Done. Existing shells still have the old OMARCHY_PATH until restarted." - echo "For this shell, run: export OMARCHY_PATH=$default_target" -else - echo "Done." -fi +echo "Reboot to restore the package install in Hyprland, app launchers, and new shells." diff --git a/config/hypr/hyprland.lua b/config/hypr/hyprland.lua index a47cf60d..473aed37 100644 --- a/config/hypr/hyprland.lua +++ b/config/hypr/hyprland.lua @@ -1,18 +1,7 @@ -- Learn how to configure Hyprland: https://wiki.hypr.land/Configuring/Start/ --- Drop cached omarchy modules so hyprctl reload re-reads them from disk. -for k in pairs(package.loaded) do - if k:match("^default%.hypr") or k:match("^hypr%.") then - package.loaded[k] = nil - end -end - --- Load user modules from ~/.config and Omarchy defaults from $OMARCHY_PATH. -package.path = os.getenv("HOME") - .. "/.config/?.lua;" - .. (os.getenv("OMARCHY_PATH") or "/usr/share/omarchy") - .. "/?.lua;" - .. package.path +-- Omarchy's bootstrap keeps path setup out of this user config. +dofile((os.getenv("OMARCHY_PATH") or "/usr/share/omarchy") .. "/default/hypr/bootstrap.lua") -- All Omarchy default setups require("default.hypr.omarchy") diff --git a/default/bash/env-bootstrap b/default/bash/env-bootstrap index c1d759de..f9b39891 100644 --- a/default/bash/env-bootstrap +++ b/default/bash/env-bootstrap @@ -17,12 +17,11 @@ export OMARCHY_PATH # Only prepend in dev-link mode. On a production install, the binaries are # already on PATH as /usr/bin/omarchy-* via the omarchy package — prepending -# /usr/share/omarchy/bin would just be noise. omarchy-dev-unlink strips its -# checkout bin/ from the active PATH explicitly, so we don't dedupe here. +# /usr/share/omarchy/bin would just be noise. if [ "$OMARCHY_PATH" != /usr/share/omarchy ]; then case ":$PATH:" in *":${OMARCHY_PATH%/}/bin:"*) ;; - *) PATH="${OMARCHY_PATH%/}/bin:$PATH" ;; + *) PATH="${OMARCHY_PATH%/}/bin${PATH:+:$PATH}" ;; esac export PATH fi diff --git a/default/hypr/bootstrap.lua b/default/hypr/bootstrap.lua new file mode 100644 index 00000000..c2123200 --- /dev/null +++ b/default/hypr/bootstrap.lua @@ -0,0 +1,8 @@ +-- Hyprland bootstrap for Omarchy's Lua module path. + +-- Load user modules from ~/.config and Omarchy defaults from $OMARCHY_PATH. +package.path = os.getenv("HOME") + .. "/.config/?.lua;" + .. (os.getenv("OMARCHY_PATH") or "/usr/share/omarchy") + .. "/?.lua;" + .. package.path diff --git a/default/hypr/paths.lua b/default/hypr/paths.lua index 4990eb81..dd3fc348 100644 --- a/default/hypr/paths.lua +++ b/default/hypr/paths.lua @@ -4,26 +4,9 @@ local home = os.getenv("HOME") --- /etc/omarchy.conf wins over process env so dev-link/unlink survives stale sessions. -local function read_dev_link_omarchy_path() - local f = io.open("/etc/omarchy.conf", "r") - if not f then return nil end - local value - for line in f:lines() do - local v = line:match('^%s*export%s+OMARCHY_PATH=%s*"?([^"\n]+)"?') - if v and #v > 0 then value = v end - end - f:close() - return value -end - -local omarchy_path = read_dev_link_omarchy_path() - or os.getenv("OMARCHY_PATH") - or "/usr/share/omarchy" - return { home = home, config_home = os.getenv("XDG_CONFIG_HOME") or (home .. "/.config"), state_home = os.getenv("XDG_STATE_HOME") or (home .. "/.local/state"), - omarchy_path = omarchy_path, + omarchy_path = os.getenv("OMARCHY_PATH") or "/usr/share/omarchy", } diff --git a/test/shell.d/config-test.sh b/test/shell.d/config-test.sh index 30431d53..e2c6a85e 100755 --- a/test/shell.d/config-test.sh +++ b/test/shell.d/config-test.sh @@ -140,6 +140,11 @@ if errors: PY pass "package-owned defaults live outside config" +grep -F 'dofile((os.getenv("OMARCHY_PATH") or "/usr/share/omarchy") .. "/default/hypr/bootstrap.lua")' "$ROOT/config/hypr/hyprland.lua" >/dev/null +grep -F 'require("default.hypr.omarchy")' "$ROOT/config/hypr/hyprland.lua" >/dev/null +grep -F 'package.path = os.getenv("HOME")' "$ROOT/default/hypr/bootstrap.lua" >/dev/null +pass "Hyprland user entrypoint keeps only package path bootstrap in defaults" + TMPDIR=$(mktemp -d) mkdir -p "$TMPDIR/home/.config/omarchy" diff --git a/test/shell.d/dev-env-path-test.sh b/test/shell.d/dev-env-path-test.sh new file mode 100755 index 00000000..0e012a94 --- /dev/null +++ b/test/shell.d/dev-env-path-test.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "$0")/base-test.sh" + +run_bootstrap() { + local shell_bin="$1" + local bootstrap="$2" + local home="$3" + local path_value="$4" + + HOME="$home" PATH="$path_value" "$shell_bin" -c ' + . "$1" + printf "%s\n%s\n" "$OMARCHY_PATH" "$PATH" + ' sh "$bootstrap" +} + +assert_path_first() { + local path_value="$1" + local entry="$2" + local description="$3" + + [[ ${path_value%%:*} == "$entry" ]] || fail "$description" "expected first PATH entry: $entry\nactual PATH: $path_value" + pass "$description" +} + +assert_path_present() { + local path_value="$1" + local entry="$2" + local description="$3" + + case ":$path_value:" in + *":$entry:"*) pass "$description" ;; + *) fail "$description" "PATH does not contain $entry in $path_value" ;; + esac +} + +tmpdir=$(mktemp -d) +trap 'rm -rf "$tmpdir"' EXIT + +home="$tmpdir/home" +mkdir -p "$tmpdir/active/bin" "$tmpdir/unrelated/bin" + +# Test against a copy so the test controls /etc/omarchy.conf without mutating the host. +bootstrap="$tmpdir/env-bootstrap" +sed "s#/etc/omarchy.conf#$tmpdir/omarchy.conf#g" "$ROOT/default/bash/env-bootstrap" >"$bootstrap" + +printf 'export OMARCHY_PATH="/usr/share/omarchy"\n' >"$tmpdir/omarchy.conf" +mapfile -t default_result < <(run_bootstrap bash "$bootstrap" "$home" "$tmpdir/unrelated/bin:/usr/bin") +default_path=${default_result[1]} + +[[ ${default_result[0]} == /usr/share/omarchy ]] || fail "env-bootstrap resolves default OMARCHY_PATH" "actual: ${default_result[0]}" +pass "env-bootstrap resolves default OMARCHY_PATH" +assert_path_present "$default_path" "$tmpdir/unrelated/bin" "env-bootstrap preserves PATH entries in default mode" + +printf 'export OMARCHY_PATH="%s"\n' "$tmpdir/active" >"$tmpdir/omarchy.conf" +mapfile -t linked_result < <(run_bootstrap bash "$bootstrap" "$home" "$tmpdir/unrelated/bin:/usr/bin") +linked_path=${linked_result[1]} + +[[ ${linked_result[0]} == "$tmpdir/active" ]] || fail "env-bootstrap resolves linked OMARCHY_PATH" "actual: ${linked_result[0]}" +pass "env-bootstrap resolves linked OMARCHY_PATH" +assert_path_first "$linked_path" "$tmpdir/active/bin" "env-bootstrap prepends active checkout bin in linked mode" +assert_path_present "$linked_path" "$tmpdir/unrelated/bin" "env-bootstrap preserves unrelated PATH entries in linked mode" + +mapfile -t linked_duplicate_result < <(run_bootstrap bash "$bootstrap" "$home" "$tmpdir/active/bin:/usr/bin") +linked_duplicate_path=${linked_duplicate_result[1]} +[[ $linked_duplicate_path == "$tmpdir/active/bin:/usr/bin" ]] || fail "env-bootstrap does not duplicate active checkout bin" "actual PATH: $linked_duplicate_path" +pass "env-bootstrap does not duplicate active checkout bin" + +if command -v zsh >/dev/null 2>&1; then + mapfile -t zsh_result < <(run_bootstrap zsh "$bootstrap" "$home" "$tmpdir/unrelated/bin:/usr/bin") + zsh_path=${zsh_result[1]} + assert_path_first "$zsh_path" "$tmpdir/active/bin" "env-bootstrap works when sourced by zsh" + assert_path_present "$zsh_path" "$tmpdir/unrelated/bin" "env-bootstrap zsh mode preserves unrelated PATH entries" +fi