Skip to content

Commit 8a728c6

Browse files
rizsottoclaude
andcommitted
add install.sh script and rewrite INSTALL.md
Add scripts/install.sh that handles the full installation layout: binaries, preload library, man page, docs, and a bear entry script. Accepts DESTDIR and INTERCEPT_LIBDIR as environment variables. Writes an install manifest so --uninstall can safely remove only installed files. Refuses DESTDIR=/ as a safety guard. Rewrite INSTALL.md to reference the script instead of listing manual commands. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9bc2947 commit 8a728c6

File tree

2 files changed

+258
-60
lines changed

2 files changed

+258
-60
lines changed

INSTALL.md

Lines changed: 62 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ Ensure `cargo` and `rustc` are available in your PATH:
2626

2727
## Build and Install
2828

29-
To build and install Bear, run the following commands:
30-
3129
1. Clone the repository:
3230
```bash
3331
git clone https://github.com/rizsotto/Bear.git
@@ -41,73 +39,77 @@ To build and install Bear, run the following commands:
4139

4240
3. Install:
4341
```bash
44-
TARGET_DIR=/usr/local
45-
SHARE_DIR=$TARGET_DIR/share
46-
47-
sudo mkdir -p $SHARE_DIR $SHARE_DIR/bear/bin
48-
sudo install -m 755 target/release/bear-driver $SHARE_DIR/bear/bin
49-
sudo install -m 755 target/release/bear-wrapper $SHARE_DIR/bear/bin
50-
sudo install -m 644 man/bear.1 $SHARE_DIR/man/man1
51-
52-
cat > target/release/bear << EOF
53-
#!/bin/sh
54-
$SHARE_DIR/bear/bin/bear-driver "\$@"
55-
EOF
56-
sudo install -m 755 target/release/bear $TARGET_DIR/bin
42+
./scripts/install.sh
43+
```
44+
45+
By default, Bear is installed to `/usr/local` (when run as root) or
46+
`$HOME/.local` (otherwise). You can override this with `DESTDIR`:
47+
48+
```bash
49+
sudo DESTDIR=/usr/local ./scripts/install.sh
5750
```
5851

59-
To install the preload library, you need to determine the library directory name
60-
for your system. Set `INTERCEPT_LIBDIR` to the appropriate value. On glibc-based
61-
Linux, the special value `$LIB` can be used (the dynamic linker expands it at
62-
runtime — see `man ld.so`). On other platforms, use a concrete directory name.
52+
The preload library directory name defaults to `lib`. On systems where a
53+
different directory is needed, set `INTERCEPT_LIBDIR` at both build and
54+
install time:
6355

6456
```bash
65-
# For RedHat, Fedora, Arch based systems
66-
export INTERCEPT_LIBDIR=lib64
67-
# For Debian, Ubuntu based systems
68-
export INTERCEPT_LIBDIR=lib/x86_64-linux-gnu
57+
# Build with the correct library directory compiled in
58+
INTERCEPT_LIBDIR=lib64 cargo build --release
6959

70-
sudo mkdir -p $SHARE_DIR/bear/$INTERCEPT_LIBDIR
71-
sudo install -m 755 target/release/libexec.so $SHARE_DIR/bear/$INTERCEPT_LIBDIR
60+
# Install with the same value so the file is placed where bear-driver expects it
61+
INTERCEPT_LIBDIR=lib64 ./scripts/install.sh
7262
```
7363

74-
# How to package
64+
On glibc-based Linux, the special value `$LIB` can be used — the dynamic
65+
linker expands it at runtime (see `man ld.so`). On other platforms (macOS,
66+
musl, FreeBSD), use a concrete directory name.
7567

76-
If you are a package maintainer for a distribution, there are a few extra
77-
things you might want to know:
78-
79-
- Package the release build of this software. You can run both the unit and
80-
integration tests as part of the package build. Consult the CI configuration
81-
in `.github/workflows/build_rust.yml` for details.
82-
- The preload mode is only enabled on UNIX at the moment. Including
83-
`libexec.so` only makes sense on this OS. This might be extended to other
84-
operating systems in the future. Consult `intercept-preload/build.rs` for
85-
details.
86-
- The final install should look like this. Where `bear` is a shell script,
87-
and the only program that uses absolute path to call `bear-driver`. The
88-
`bear-driver` is referencing `bear-wrapper` or `libexec.so` with relative
89-
path. (Using `./bear-wrapper` and `../$INTERCEPT_LIBDIR/libexec.so` to reach
90-
these files.) This allows the installation process to choose the destination
91-
directory.
68+
## Uninstall
9269

9370
```bash
94-
$ tree /usr/local
95-
.
96-
├── bin
97-
│ └── bear
98-
└── share
99-
├── man
100-
│ └── man1
101-
│ └── bear.1
102-
└── bear
103-
├── lib64
104-
│ └── libexec.so
105-
└── bin
106-
├── bear-driver
107-
└── bear-wrapper
71+
./scripts/install.sh --uninstall
10872
```
10973

110-
- The preload library path contains the value of `INTERCEPT_LIBDIR` (set at
111-
build time, defaults to `lib`). On glibc-based Linux, packagers can set this
112-
to `$LIB` so the dynamic linker resolves it at runtime — useful in a multilib
113-
context. Consult the `ld.so` man page (`man ld.so`) for details.
74+
The install script writes a manifest at `$DESTDIR/share/bear/install-manifest.txt`.
75+
The `--uninstall` flag reads this manifest and removes only the files that were
76+
previously installed.
77+
78+
# How to package
79+
80+
If you are a package maintainer for a distribution:
81+
82+
- Build and install with explicit values for `DESTDIR` and `INTERCEPT_LIBDIR`:
83+
```bash
84+
INTERCEPT_LIBDIR=lib64 cargo build --release
85+
INTERCEPT_LIBDIR=lib64 DESTDIR=$pkgdir/usr ./scripts/install.sh
86+
```
87+
88+
- The preload library (`libexec.so`) is only built on Unix. Windows builds
89+
only produce `bear-driver` and `bear-wrapper`. Consult
90+
`intercept-preload/build.rs` for details.
91+
92+
- `bear-driver` locates its siblings using relative paths:
93+
`./bear-wrapper` and `../$INTERCEPT_LIBDIR/libexec.so`. The `bear` entry
94+
script in `$DESTDIR/bin/` is the only artifact that uses an absolute path.
95+
96+
- The expected installation layout:
97+
```
98+
$DESTDIR/
99+
├── bin/
100+
│ └── bear (shell script)
101+
└── share/
102+
├── doc/
103+
│ └── bear/
104+
│ ├── README.md
105+
│ └── COPYING
106+
├── man/
107+
│ └── man1/
108+
│ └── bear.1
109+
└── bear/
110+
├── bin/
111+
│ ├── bear-driver
112+
│ └── bear-wrapper
113+
└── $INTERCEPT_LIBDIR/
114+
└── libexec.so
115+
```

scripts/install.sh

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
#!/bin/sh
2+
# SPDX-License-Identifier: GPL-3.0-or-later
3+
#
4+
# Install script for Bear.
5+
#
6+
# Environment variables:
7+
# DESTDIR — installation prefix (default: /usr/local if root, $HOME/.local otherwise)
8+
# INTERCEPT_LIBDIR — library directory name (default: lib)
9+
#
10+
# Usage:
11+
# ./scripts/install.sh # install with defaults
12+
# ./scripts/install.sh --uninstall # remove previously installed files
13+
#
14+
# DESTDIR=/usr INTERCEPT_LIBDIR=lib64 ./scripts/install.sh
15+
16+
set -euxo pipefail
17+
18+
# --- configuration -----------------------------------------------------------
19+
20+
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
21+
22+
DESTDIR="${DESTDIR:-}"
23+
if [ -z "$DESTDIR" ]; then
24+
if [ "$(id -u)" -eq 0 ]; then
25+
DESTDIR="/usr/local"
26+
else
27+
DESTDIR="$HOME/.local"
28+
fi
29+
fi
30+
31+
# Resolve to absolute path
32+
DESTDIR="$(cd "$DESTDIR" 2>/dev/null && pwd || echo "$DESTDIR")"
33+
34+
INTERCEPT_LIBDIR="${INTERCEPT_LIBDIR:-lib}"
35+
36+
MANIFEST="$DESTDIR/share/bear/install-manifest.txt"
37+
38+
# --- safety guards ------------------------------------------------------------
39+
40+
refuse_root_destdir() {
41+
if [ "$DESTDIR" = "/" ]; then
42+
echo "error: refusing to operate with DESTDIR=/ (would clobber the root filesystem)" >&2
43+
exit 1
44+
fi
45+
}
46+
47+
# --- artifact discovery -------------------------------------------------------
48+
49+
# When run from the source repo, artifacts are in target/release/.
50+
# When run from a release archive, artifacts are next to the script.
51+
find_source_dir() {
52+
if [ -d "$REPO_ROOT/target/release" ]; then
53+
echo "$REPO_ROOT/target/release"
54+
elif [ -f "$REPO_ROOT/bin/bear-driver" ] || [ -f "$REPO_ROOT/bin/bear-driver.exe" ]; then
55+
echo "$REPO_ROOT"
56+
else
57+
echo "error: cannot find build artifacts in target/release/ or next to the script" >&2
58+
exit 1
59+
fi
60+
}
61+
62+
# --- platform detection -------------------------------------------------------
63+
64+
detect_platform() {
65+
OS="$(uname -s)"
66+
case "$OS" in
67+
Linux|FreeBSD|NetBSD|OpenBSD|DragonFly)
68+
PRELOAD_NAME="libexec.so"
69+
HAS_PRELOAD=true
70+
;;
71+
Darwin)
72+
PRELOAD_NAME="libexec.dylib"
73+
HAS_PRELOAD=true
74+
;;
75+
MINGW*|MSYS*|CYGWIN*)
76+
PRELOAD_NAME=""
77+
HAS_PRELOAD=false
78+
;;
79+
*)
80+
PRELOAD_NAME="libexec.so"
81+
HAS_PRELOAD=true
82+
;;
83+
esac
84+
}
85+
86+
# --- install ------------------------------------------------------------------
87+
88+
do_install() {
89+
refuse_root_destdir
90+
detect_platform
91+
92+
SOURCE_DIR="$(find_source_dir)"
93+
94+
# Start fresh manifest
95+
mkdir -p "$DESTDIR/share/bear"
96+
: > "$MANIFEST"
97+
98+
# bear-driver and bear-wrapper
99+
mkdir -p "$DESTDIR/share/bear/bin"
100+
install -m 755 "$SOURCE_DIR/bear-driver" "$DESTDIR/share/bear/bin/bear-driver"
101+
echo "$DESTDIR/share/bear/bin/bear-driver" >> "$MANIFEST"
102+
install -m 755 "$SOURCE_DIR/bear-wrapper" "$DESTDIR/share/bear/bin/bear-wrapper"
103+
echo "$DESTDIR/share/bear/bin/bear-wrapper" >> "$MANIFEST"
104+
105+
# preload library (Unix only)
106+
if [ "$HAS_PRELOAD" = true ] && [ -f "$SOURCE_DIR/$PRELOAD_NAME" ]; then
107+
mkdir -p "$DESTDIR/share/bear/$INTERCEPT_LIBDIR"
108+
install -m 644 "$SOURCE_DIR/$PRELOAD_NAME" "$DESTDIR/share/bear/$INTERCEPT_LIBDIR/$PRELOAD_NAME"
109+
echo "$DESTDIR/share/bear/$INTERCEPT_LIBDIR/$PRELOAD_NAME" >> "$MANIFEST"
110+
fi
111+
112+
# bear entry script
113+
mkdir -p "$DESTDIR/bin"
114+
cat > "$DESTDIR/bin/bear" <<ENTRY_SCRIPT
115+
#!/bin/sh
116+
$DESTDIR/share/bear/bin/bear-driver "\$@"
117+
ENTRY_SCRIPT
118+
chmod 755 "$DESTDIR/bin/bear"
119+
echo "$DESTDIR/bin/bear" >> "$MANIFEST"
120+
121+
# man page
122+
if [ -f "$REPO_ROOT/man/bear.1" ]; then
123+
mkdir -p "$DESTDIR/share/man/man1"
124+
install -m 644 "$REPO_ROOT/man/bear.1" "$DESTDIR/share/man/man1/bear.1"
125+
echo "$DESTDIR/share/man/man1/bear.1" >> "$MANIFEST"
126+
fi
127+
128+
# documentation
129+
mkdir -p "$DESTDIR/share/doc/bear"
130+
if [ -f "$REPO_ROOT/README.md" ]; then
131+
install -m 644 "$REPO_ROOT/README.md" "$DESTDIR/share/doc/bear/README.md"
132+
echo "$DESTDIR/share/doc/bear/README.md" >> "$MANIFEST"
133+
fi
134+
if [ -f "$REPO_ROOT/COPYING" ]; then
135+
install -m 644 "$REPO_ROOT/COPYING" "$DESTDIR/share/doc/bear/COPYING"
136+
echo "$DESTDIR/share/doc/bear/COPYING" >> "$MANIFEST"
137+
fi
138+
139+
# record the manifest itself
140+
echo "$MANIFEST" >> "$MANIFEST"
141+
142+
echo "Bear installed to $DESTDIR"
143+
echo "Manifest written to $MANIFEST"
144+
}
145+
146+
# --- uninstall ----------------------------------------------------------------
147+
148+
do_uninstall() {
149+
refuse_root_destdir
150+
151+
if [ ! -f "$MANIFEST" ]; then
152+
echo "error: no install manifest found at $MANIFEST" >&2
153+
echo "Cannot uninstall without a manifest." >&2
154+
exit 1
155+
fi
156+
157+
# Remove each file listed in the manifest
158+
while IFS= read -r file; do
159+
if [ -f "$file" ]; then
160+
rm -f "$file"
161+
echo "removed: $file"
162+
fi
163+
done < "$MANIFEST"
164+
165+
# Remove empty directories (deepest first)
166+
for dir in \
167+
"$DESTDIR/share/bear/bin" \
168+
"$DESTDIR/share/bear/$INTERCEPT_LIBDIR" \
169+
"$DESTDIR/share/bear" \
170+
"$DESTDIR/share/doc/bear" \
171+
"$DESTDIR/share/man/man1" \
172+
"$DESTDIR/share/man" \
173+
"$DESTDIR/share/doc" \
174+
"$DESTDIR/share" \
175+
"$DESTDIR/bin" \
176+
; do
177+
rmdir "$dir" 2>/dev/null || true
178+
done
179+
180+
echo "Bear uninstalled from $DESTDIR"
181+
}
182+
183+
# --- main ---------------------------------------------------------------------
184+
185+
case "${1:-}" in
186+
--uninstall)
187+
do_uninstall
188+
;;
189+
"")
190+
do_install
191+
;;
192+
*)
193+
echo "usage: $0 [--uninstall]" >&2
194+
exit 1
195+
;;
196+
esac

0 commit comments

Comments
 (0)