Fix unbound BASH_SOURCE under bash -c invocation, the actual documented run command

BASH_SOURCE has zero elements when a script arrives as `bash -c "$(curl ...)"`
(no backing file at all) rather than a real path or /dev/fd process-
substitution handle. Under set -u this crashed immediately on the REPO_DIR
line, and the bottom sourcing guard's $BASH_SOURCE/$0 comparison would have
silently never run main() either way (bash -c sets $0 to "bash", which never
matches an empty BASH_SOURCE default).

Guarded the REPO_DIR line with a safe default, and replaced the sourcing
guard with the standard `return 0` idiom, which correctly distinguishes
sourced (bats tests) from directly-executed (file, bash -c, process
substitution) in every invocation style. Added a regression test that runs
the actual `bash -c "$(cat install.sh)"` command end-to-end against the real
public repo, not just sourced functions.
This commit is contained in:
edemilov committed 2026-08-29 15:19:23 +03:00
1 parent 478450b209
commit e6b03153df
2 files changed
+48 -2

No files matched your search

+12 -2
View File
@@ -1,7 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# ${BASH_SOURCE[0]} is unset (not just empty) when this script arrives as a
# `bash -c "$(curl ...)"` command string rather than a real file -- guard it
# so `set -u` doesn't blow up. `dirname ""` -> "." (current dir), which
# ensure_repo_files() below correctly treats as "no local checkout, fetch."
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-}")" && pwd)"
CONFIG_HOME="${CONFIG_HOME:-${XDG_CONFIG_HOME:-$HOME/.config}}"
LOCAL_BIN="${LOCAL_BIN:-$HOME/.local/bin}"
HYPREVO_RAW_BASE="${HYPREVO_RAW_BASE:-https://git.edemilov.ru/edemilov/hyprevo/raw/branch/master}"
@@ -266,6 +270,12 @@ main() {
echo "hyprevo: done."
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
# `return` only succeeds at top level when the script is being sourced (as
# tests/install.bats does). Direct execution -- a real file, `bash -c "..."`,
# or `bash <(curl ...)` process substitution -- all fail `return`, so `main`
# runs. This is more robust here than comparing $BASH_SOURCE/$0: under
# `bash -c "$(curl ...)"` there's no backing file at all, so BASH_SOURCE is
# empty and $0 is just "bash" -- they'd never match even with a safe default.
if ! (return 0 2>/dev/null); then
main "$@"
fi
+36
View File
@@ -298,3 +298,39 @@ EOF
rm -rf "$tmp_home" "$stub_bin"
}
@test "install.sh runs end-to-end via bash -c \"\$(cat install.sh)\" (the real distribution command)" {
# Regression test: BASH_SOURCE has zero elements under `bash -c "..."`
# (no backing file at all), which broke both the REPO_DIR line and the
# sourcing guard under `set -u`. Exercise the actual documented install
# command, not just sourced functions, so this can't silently regress.
local tmp_home
tmp_home="$(mktemp -d)"
local stub_bin
stub_bin="$(mktemp -d)"
for cmd in pacman systemctl hyprctl sudo; do
cat > "$stub_bin/$cmd" <<EOF
#!/usr/bin/env bash
echo "$cmd \$*" >> "$tmp_home/log"
if [ "$cmd" = "sudo" ]; then exec "\$@"; fi
exit 0
EOF
chmod +x "$stub_bin/$cmd"
done
PATH="$stub_bin:$PATH" \
HOME="$tmp_home" \
HYPREVO_NONINTERACTIVE=1 \
HYPREVO_TERMINAL=Konsole \
HYPREVO_FILEMANAGER=Dolphin \
HYPREVO_MAIL=Thunderbird \
HYPREVO_CLOUDSYNC=Nextcloud \
run bash -c "$(cat "${BATS_TEST_DIRNAME}/../install.sh")"
[ "$status" -eq 0 ]
[[ "$output" == *"hyprevo: done."* ]]
[ -f "$tmp_home/.config/hypr/hyprland.conf" ]
rm -rf "$tmp_home" "$stub_bin"
}