Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ curl -fsSL https://raw.githubusercontent.com/recursive-oss/rec/main/install.sh |
This installs `rec` in `~/.local/bin` and adds it to your PATH for new
terminals. All other data lives under `~/.local/share/rec`.

The installer leaves any other program named `rec`, such as the recorder in
SoX, untouched and lists its path. Whichever comes first on your PATH runs as
`rec`, and `~/.local/bin/rec` always runs this one.


## Usage

Expand Down
13 changes: 8 additions & 5 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
rec_install() (
set -euo pipefail
umask 077
bold='' faint='' green='' red='' blue='' reset='' erase=''
bold='' faint='' green='' red='' yellow='' blue='' reset='' erase=''
if [ -t 1 ] && [ "${TERM:-dumb}" != dumb ]; then
erase=$'\r\033[K'
if [ -z "${NO_COLOR:-}" ]; then
bold=$'\033[1m' faint=$'\033[2m' green=$'\033[32m' red=$'\033[31m' blue=$'\033[38;5;68m' reset=$'\033[0m'
bold=$'\033[1m' faint=$'\033[2m' green=$'\033[32m' red=$'\033[31m' yellow=$'\033[33m' blue=$'\033[38;5;68m' reset=$'\033[0m'
fi
fi
fail() { printf '%s' "$erase"; printf '%srec install:%s %s\n' "$red$bold" "$reset" "$*" >&2; exit 1; }
Expand Down Expand Up @@ -44,15 +44,12 @@ rec_install() (
mkdir "$lock_dir" 2>/dev/null || fail "another install is active; if interrupted, remove $lock_dir"
tmp=$(mktemp -d "$data/.install.XXXXXX")
trap 'rm -rf "$tmp"; rmdir "$lock_dir" 2>/dev/null || true' EXIT
existing=$(command -v rec || true)
if [ -e "$bin/rec" ] || [ -L "$bin/rec" ]; then
[ -L "$bin/rec" ] || fail "$bin/rec exists and is not a managed symlink"
old=$(readlink "$bin/rec")
case "$old" in "$data"/releases/*/rec) [ -f "$(dirname "$old")/.managed" ] || fail 'existing rec is not managed';; *) fail 'existing rec belongs to another program';; esac
[ -z "$existing" ] || [ "$existing" = "$bin/rec" ] || fail "rec already resolves to $existing"
else
old=''
[ -z "$existing" ] || fail "rec already resolves to $existing"
fi
printf '%sInstalling rec%s\n\n' "$bold" "$reset"
begin "Downloading rec ${version:-(latest release)}"
Expand Down Expand Up @@ -129,6 +126,12 @@ rec_install() (
*) printf '\n%sOpen a new terminal, or run: export PATH="$HOME/.local/bin:$PATH"%s\n' "$faint" "$reset";;
esac
fi
# Other programs can be called rec too, such as the recorder in SoX. They stay untouched.
others=$(type -aP rec | while IFS= read -r other; do [ "$other" -ef "$bin/rec" ] || printf ' %s\n' "$other"; done | awk '!seen[$0]++') || true
if [ -n "$others" ]; then
printf '\n%sOther rec commands on your PATH:%s\n%s\nWhichever comes first on PATH runs as rec. This install is always at %s/rec.\n' \
"$yellow$bold" "$reset" "$others" "$bin" >&2
fi
if [ "$serve_after" = 1 ]; then
rm -rf "$tmp"
rmdir "$lock_dir"
Expand Down
36 changes: 35 additions & 1 deletion tests/installer_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,44 @@ def installed(home):
(home / ".local/bin/rec").write_text("unrelated program\n")
install(env, ok=False)
assert (home / ".local/bin/rec").read_text() == "unrelated program\n"

other = root / "other program"
other.mkdir()
(other / "rec").write_text('#!/bin/sh\necho "$*" >> "$HOME/other.log"\n')
(other / "rec").chmod(0o755)
notice = "Other rec commands"
listed = f" {other}/rec\n"

# A conflict ahead of the install warns on the first install and on later updates.
home, env = new_home()
env["PATH"] = f"{other}:{env['PATH']}"
for _ in range(2):
r = install(env)
assert notice in r.stderr and listed in r.stderr, r.stderr
assert installed(home).parent.name == "0.3.0"
install(env, "--serve")
assert (home / "serve.log").read_text() == "serve\n"
assert not (home / "other.log").exists(), "installer ran the other program"

# A conflict behind the install is still listed, once per path, without the install itself.
home, env = new_home()
own = home / ".local/bin"
env["PATH"] = f"{own}/:{other}:{other}:{env['PATH']}"
r = install(env)
assert r.stderr.count(listed) == 1, r.stderr
assert f" {own}/" not in r.stderr, r.stderr

# The install alone is not a conflict, however PATH spells its directory.
home, env = new_home()
assert notice not in install(env).stderr
own = home / ".local/bin"
for entry in [f"{own}", f"{own}/", f"{own}/."]:
r = install(dict(env, PATH=f"{entry}:{env['PATH']}"))
assert notice not in r.stderr, (entry, r.stderr)
server.shutdown()
server.server_close()
print(
"Installer: public default URL, pins, idempotence, checksum/version/device rejection, running-server stop, --serve, platform checks and collision preservation passed"
"Installer: public default URL, pins, idempotence, checksum/version/device rejection, running-server stop, --serve, platform checks, collision preservation and other rec warnings passed"
)


Expand Down