Skip to content

Commit 44323f1

Browse files
authored
Create install-safexec.sh
Signed-off-by: Hasan ÇALIŞIR <hasan.calisir@psauxit.com>
1 parent 160ae5d commit 44323f1

File tree

1 file changed

+207
-0
lines changed

1 file changed

+207
-0
lines changed

safexec/helpers/install-safexec.sh

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
#!/usr/bin/env sh
2+
set -eu
3+
4+
# ---- Config ----
5+
BASE_URL="${BASE_URL:-https://raw.githubusercontent.com/psaux-it/nginx-fastcgi-cache-purge-and-preload/main/safexec}"
6+
INSTALL_PATH="${INSTALL_PATH:-/usr/local/bin/safexec}"
7+
ALLOW_NOSUID="${ALLOW_NOSUID:-0}" # set to 1 to allow install on nosuid fs (will run without setuid)
8+
SKIP_CHECKSUM="${SKIP_CHECKSUM:-0}" # set to 1 to skip checksum verification even if available
9+
10+
# ---- Colors & UI (auto-disable on non-TTY or NO_COLOR; FORCE_COLOR=1 to force) ----
11+
if { [ -t 1 ] || [ "${FORCE_COLOR:-0}" = "1" ]; } \
12+
&& [ -z "${NO_COLOR:-}" ] \
13+
&& [ "${TERM:-dumb}" != "dumb" ]; then
14+
BOLD="$(printf '\033[1m')"; CYAN="$(printf '\033[36m')"; RESET="$(printf '\033[0m')"
15+
else
16+
BOLD=; CYAN=; RESET=
17+
fi
18+
19+
# ---- Helpers ----
20+
die() { printf >&2 "Error: %s\n" "$*"; exit 1; }
21+
warn() { printf >&2 "Warning: %s\n" "$*"; }
22+
23+
need_cmd() { command -v "$1" >/dev/null 2>&1 || die "missing required command: $1"; }
24+
is_linux() { [ "$(uname -s 2>/dev/null)" = "Linux" ]; }
25+
26+
arch_triplet() {
27+
m="$(uname -m 2>/dev/null || true)"
28+
case "$m" in
29+
x86_64|amd64) echo "x86_64-linux-musl" ;;
30+
aarch64|arm64) echo "aarch64-linux-musl" ;;
31+
*) die "unsupported arch: $m" ;;
32+
esac
33+
}
34+
35+
# Find mount options for the filesystem containing $1 (prefix-longest match)
36+
mount_opts_for() {
37+
tgt="$1"
38+
awk -v p="$tgt" '
39+
BEGIN{L=0;O=""}
40+
{
41+
mp=$2; gsub("\\\\040"," ",mp);
42+
if (index(p, mp)==1 && length(mp)>L) { L=length(mp); O=$4; }
43+
}
44+
END{print O}
45+
' /proc/self/mounts 2>/dev/null
46+
}
47+
48+
# Generic mount-flag check
49+
fs_has_flag() {
50+
opts="$(mount_opts_for "$1")"
51+
[ -n "$opts" ] && printf "%s" "$opts" | tr ',' '\n' | grep -qx "$2"
52+
}
53+
fs_has_nosuid() { fs_has_flag "$1" nosuid; }
54+
55+
# Portable dirname->abs path
56+
dir_abs() {
57+
_d="$1"
58+
[ -z "$_d" ] && _d=.
59+
( cd "$_d" 2>/dev/null && pwd -P ) || echo "$_d"
60+
}
61+
62+
# ---- Preflight ----
63+
is_linux || die "this installer is Linux-only"
64+
65+
# require root ASAP
66+
[ "$(id -u)" -eq 0 ] || die "please run as root (or with sudo) to install to ${INSTALL_PATH}"
67+
68+
# required tools
69+
for c in uname curl chmod chown awk grep mv mkdir sha256sum id; do
70+
need_cmd "$c"
71+
done
72+
73+
# arch sanity check
74+
if command -v file >/dev/null 2>&1; then
75+
HAVE_FILE=1
76+
else
77+
HAVE_FILE=0
78+
warn "'file' command not found; skipping ELF/arch sanity check"
79+
fi
80+
81+
# sha256 verification if checksum file is present upstream
82+
if command -v sha256sum >/dev/null 2>&1; then
83+
HAVE_SHA=1
84+
else
85+
HAVE_SHA=0
86+
fi
87+
88+
[ -r /proc/self/status ] || die "/proc not mounted or unreadable (needed for --kill mode)"
89+
90+
TRIPLET="$(arch_triplet)"
91+
FILE="safexec-${TRIPLET}"
92+
URL_BIN="${BASE_URL}/${FILE}"
93+
URL_SHA="${URL_BIN}.sha256"
94+
95+
# nosuid check on the target filesystem
96+
TARGET_DIR="$(dir_abs "$(dirname "$INSTALL_PATH")")"
97+
if fs_has_nosuid "$TARGET_DIR"; then
98+
if [ "$ALLOW_NOSUID" != "1" ]; then
99+
die "filesystem for ${TARGET_DIR} is mounted with 'nosuid' (setuid will NOT work).
100+
Hint: re-mount without nosuid or set ALLOW_NOSUID=1 to install anyway (reduced isolation)."
101+
else
102+
warn "nosuid detected on ${TARGET_DIR}; safexec will run without setuid privileges (reduced isolation)."
103+
fi
104+
fi
105+
106+
# noexec check (cannot run binaries from here)
107+
if fs_has_flag "$TARGET_DIR" noexec; then
108+
die "filesystem for ${TARGET_DIR} is mounted with 'noexec' (cannot execute ${INSTALL_PATH})."
109+
fi
110+
111+
# cgroup v2 info
112+
if [ -r /sys/fs/cgroup/cgroup.controllers ]; then
113+
echo "Info: cgroup v2 detected."
114+
else
115+
echo "Info: cgroup v2 not detected (feature will be a no-op)."
116+
fi
117+
118+
# 'nobody' user info
119+
if ! (getent passwd nobody >/dev/null 2>&1 || id -u nobody >/dev/null 2>&1); then
120+
warn "no 'nobody' user found; safexec will fall back to the FPM user."
121+
fi
122+
123+
# warn if destination is a symlink (it will be atomically replaced)
124+
if [ -L "$INSTALL_PATH" ]; then
125+
warn "${INSTALL_PATH} is a symlink; it will be replaced atomically."
126+
fi
127+
128+
# ---- download ----
129+
td="$(mktemp -d)"
130+
trap 'rm -rf "$td"' EXIT
131+
tmp="$td/$FILE"
132+
tmp_sha="$td/$FILE.sha256"
133+
134+
echo "Fetching ${BOLD}${CYAN}safexec${RESET} ..."
135+
curl -fsSL "$URL_BIN" -o "$tmp" || die "download failed: ${URL_BIN}"
136+
137+
# checksum verify (only if available and not skipped)
138+
if [ "$SKIP_CHECKSUM" != "1" ] && [ "$HAVE_SHA" -eq 1 ]; then
139+
if curl -fsSL "$URL_SHA" -o "$tmp_sha"; then
140+
echo "Verifying SHA256 ..."
141+
( cd "$td" && sha256sum -c "$FILE.sha256" ) \
142+
|| die "checksum mismatch for ${FILE}"
143+
else
144+
warn "no checksum file found at ${URL_SHA}; continuing without verification"
145+
fi
146+
elif [ "$SKIP_CHECKSUM" != "1" ]; then
147+
warn "sha256sum not found; skipping checksum verification"
148+
fi
149+
150+
# Basic sanity: ELF + arch
151+
if [ "$HAVE_FILE" -eq 1 ]; then
152+
desc="$(file -b "$tmp" || true)"
153+
printf "Binary looks like: %s\n" "$desc"
154+
case "$TRIPLET" in
155+
x86_64-*) echo "$desc" | grep -qi 'ELF 64-bit .*x86-64' || die "downloaded binary is not x86_64 ELF" ;;
156+
aarch64-*) echo "$desc" | grep -qi 'ELF 64-bit .*aarch64' || die "downloaded binary is not aarch64 ELF" ;;
157+
esac
158+
fi
159+
160+
# ---- install atomically ----
161+
mkdir -p "$TARGET_DIR"
162+
tmp_in_place="${TARGET_DIR}/.$(basename "$INSTALL_PATH").new.$$"
163+
164+
if command -v install >/dev/null 2>&1; then
165+
# Use 'install' to set owner+mode in one go
166+
if fs_has_nosuid "$TARGET_DIR"; then
167+
# nosuid FS: no point setting setuid bit
168+
install -m 0755 -o root -g root "$tmp" "$tmp_in_place"
169+
else
170+
# setuid root when allowed
171+
install -m 4755 -o root -g root "$tmp" "$tmp_in_place"
172+
fi
173+
else
174+
# Fallback: cp + chown + chmod
175+
cp "$tmp" "$tmp_in_place"
176+
chown root:root "$tmp_in_place"
177+
if fs_has_nosuid "$TARGET_DIR"; then
178+
chmod 0755 "$tmp_in_place"
179+
else
180+
chmod 4755 "$tmp_in_place"
181+
fi
182+
fi
183+
184+
# Atomic replace within the same filesystem
185+
mv -f "$tmp_in_place" "$INSTALL_PATH"
186+
187+
# ---- quick smoke test ----
188+
if "$INSTALL_PATH" --version >/dev/null 2>&1; then
189+
echo "Smoke test: --version OK"
190+
elif "$INSTALL_PATH" --help >/dev/null 2>&1; then
191+
echo "Smoke test: --help OK"
192+
else
193+
warn "binary installed but a basic self-test failed; please run '$INSTALL_PATH --help' manually."
194+
fi
195+
196+
# ---- summary ----
197+
echo "Summary:"
198+
echo " Arch: ${TRIPLET}"
199+
echo " Installed: ${INSTALL_PATH}"
200+
if fs_has_nosuid "$TARGET_DIR"; then
201+
echo " nosuid: yes (setuid disabled)"
202+
else
203+
echo " nosuid: no (setuid enabled)"
204+
fi
205+
[ -r /sys/fs/cgroup/cgroup.controllers ] && echo " cgroup v2: yes" || echo " cgroup v2: no"
206+
( getent passwd nobody >/dev/null 2>&1 || id -u nobody >/dev/null 2>&1 ) && echo " nobody user: yes" || echo " nobody user: no"
207+
echo "Installed."

0 commit comments

Comments
 (0)