2026-08-19 20:24:56 -07:00
#!/usr/bin/env bash
# Build socktop + socktop_agent from source and install them.
#
# Works on Linux (x86_64, arm64/armv7, riscv64) and macOS. Handles fresh
# installs and upgrades; if a systemd socktop-agent service is present, its
# binary is replaced in place and the service restarted.
#
# ./scripts/install.sh # build HEAD of the repo you're in
# ./scripts/install.sh --ref v1.60.0 # build a tag/branch (clones if needed)
# ./scripts/install.sh --ref housekeeping-p2
# ./scripts/install.sh --prefix ~/.local/bin --no-service
#
set -euo pipefail
REPO_URL = "https://github.com/jasonwitty/socktop.git"
REF = ""
PREFIX = ""
NO_SERVICE = 0
SRC_DIR = " ${ SOCKTOP_SRC_DIR :- $HOME /.cache/socktop-src } "
while [ $# -gt 0 ] ; do
case " $1 " in
--ref) REF = " $2 " ; shift 2 ; ;
--prefix) PREFIX = " $2 " ; shift 2 ; ;
--no-service) NO_SERVICE = 1; shift ; ;
-h| --help) grep '^#' " $0 " | sed 's/^# \{0,1\}//' ; exit 0 ; ;
*) echo " unknown argument: $1 " >& 2; exit 2 ; ;
esac
done
say( ) { printf '\033[1;36m==>\033[0m %s\n' " $* " ; }
warn( ) { printf '\033[1;33mwarn:\033[0m %s\n' " $* " >& 2; }
die( ) { printf '\033[1;31merror:\033[0m %s\n' " $* " >& 2; exit 1; }
OS = " $( uname -s) "
ARCH = " $( uname -m) "
# ---------- toolchain ----------
command -v git >/dev/null || die "git is required"
if ! command -v cargo >/dev/null; then
# rustup may be installed but not on PATH in this shell
[ -f " $HOME /.cargo/env " ] && . " $HOME /.cargo/env "
fi
if ! command -v cargo >/dev/null; then
say "Rust toolchain not found — installing via rustup (stable, default profile)"
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
. " $HOME /.cargo/env "
fi
command -v cc >/dev/null || warn "no C compiler found (apt: build-essential / brew: xcode-select --install) — the build may fail"
case " $ARCH " in
riscv64*)
# protoc-bin-vendored ships no riscv64 binary; the build falls back to
# the system protoc (see build.rs).
command -v protoc >/dev/null || die "riscv64 needs a system protoc: sudo apt install protobuf-compiler"
; ;
esac
# ---------- source ----------
# If run from inside a socktop checkout and no --ref given, build that tree
# as-is (whatever is checked out, including local changes).
if [ -z " $REF " ] && git rev-parse --show-toplevel >/dev/null 2>& 1 \
&& grep -qs '^name = "socktop"' " $( git rev-parse --show-toplevel) /socktop/Cargo.toml " 2>/dev/null; then
SRC_DIR = " $( git rev-parse --show-toplevel) "
say " Building the current checkout: $SRC_DIR ( $( git -C " $SRC_DIR " describe --always --dirty 2>/dev/null) ) "
else
REF = " ${ REF :- master } "
if [ ! -d " $SRC_DIR /.git " ] ; then
say " Cloning $REPO_URL -> $SRC_DIR "
git clone " $REPO_URL " " $SRC_DIR "
fi
say " Checking out $REF "
git -C " $SRC_DIR " fetch --tags origin
git -C " $SRC_DIR " checkout -q " $REF "
# fast-forward when REF is a branch
git -C " $SRC_DIR " merge --ff-only " origin/ $REF " >/dev/null 2>& 1 || true
fi
# ---------- build ----------
say "Building release binaries (this can take a while on SBCs)"
( cd " $SRC_DIR " && cargo build --release -p socktop -p socktop_agent )
CLIENT = " $SRC_DIR /target/release/socktop "
AGENT = " $SRC_DIR /target/release/socktop_agent "
# ---------- install ----------
if [ -z " $PREFIX " ] ; then
PREFIX = "/usr/local/bin"
fi
SUDO = ""
if [ ! -w " $PREFIX " ] ; then
if command -v sudo >/dev/null; then SUDO = "sudo" ; else
PREFIX = " $HOME /.local/bin " ; mkdir -p " $PREFIX "
warn " no sudo — installing to $PREFIX (ensure it is on your PATH) "
fi
fi
say " Installing to $PREFIX "
$SUDO install -m 755 " $CLIENT " " $PREFIX /socktop "
$SUDO install -m 755 " $AGENT " " $PREFIX /socktop_agent "
2026-08-20 10:23:45 -07:00
# Update every other copy on PATH as well. A stale `cargo install` in
# ~/.cargo/bin would otherwise SHADOW the fresh binary (~/.cargo/bin
# usually precedes /usr/local/bin on PATH), leaving `socktop --version`
# stuck on the old release after a "successful" install.
update_path_copies( ) {
local name = " $1 " src = " $2 " copy dir
# type -ap lists every match on PATH (bash builtin, symlinks not resolved)
for copy in $( type -ap " $name " | sort -u) ; do
[ " $copy " = " $PREFIX / $name " ] && continue
dir = " $( dirname " $copy " ) "
say " Updating additional copy on PATH: $copy "
if [ -w " $copy " ] || [ -w " $dir " ] ; then
install -m 755 " $src " " $copy "
else
# Non-fatal: an un-updatable extra copy shouldn't kill the install,
# but the user must know it may shadow the fresh binary.
$SUDO install -m 755 " $src " " $copy " || warn " could not update $copy — it may shadow $PREFIX / $name "
fi
done
}
update_path_copies socktop " $CLIENT "
update_path_copies socktop_agent " $AGENT "
2026-08-19 20:24:56 -07:00
# ---------- systemd service (Linux only) ----------
2026-08-20 12:25:46 -07:00
# System-level operations (unit files, users, service control) need root no
# matter where the binaries were installed — decide independently of PREFIX.
SYS_SUDO = ""
if [ " $( id -u) " -ne 0 ] ; then
if command -v sudo >/dev/null; then SYS_SUDO = "sudo" ; else SYS_SUDO = "__none__" ; fi
fi
if [ " $SYS_SUDO " = "__none__" ] && [ " $NO_SERVICE " -eq 0 ] ; then
warn "no sudo available — skipping systemd service management"
NO_SERVICE = 1
fi
if [ " $OS " = "Linux" ] && [ " $NO_SERVICE " -eq 0 ] && command -v systemctl >/dev/null; then
if systemctl list-unit-files 2>/dev/null | grep -q '^socktop-agent\.service' ; then
# UPGRADE: the unit file is the operator's (SSL, tokens, ports may be
# configured there) — never overwrite it. Only the binary it points at
# is replaced, then the service is restarted.
say "Existing socktop-agent.service found — preserving unit file, refreshing binary"
UNIT_BIN = " $( systemctl show -p ExecStart socktop-agent.service 2>/dev/null \
| sed -n 's/.*path=\([^ ;]*\).*/\1/p' | head -1) "
if [ -n " $UNIT_BIN " ] && [ " $UNIT_BIN " != " $PREFIX /socktop_agent " ] ; then
$SYS_SUDO systemctl stop socktop-agent.service
$SYS_SUDO install -m 755 " $AGENT " " $UNIT_BIN "
$SYS_SUDO systemctl start socktop-agent.service
else
$SYS_SUDO systemctl restart socktop-agent.service
fi
2026-08-19 20:24:56 -07:00
else
2026-08-20 12:25:46 -07:00
# FRESH INSTALL: unit + the system user it runs as + its state dir,
# then enable and start. Mirrors the deb package's postinst and
# https://www.socktop.io/assets/docs/installation/agent-service.html
say "No socktop-agent.service found — installing and enabling it"
if ! getent group socktop >/dev/null; then
$SYS_SUDO groupadd --system socktop
fi
if ! getent passwd socktop >/dev/null; then
NOLOGIN = " $( command -v nologin || echo /usr/sbin/nologin) "
$SYS_SUDO useradd --system -g socktop -d /var/lib/socktop -M -s " $NOLOGIN " socktop
fi
$SYS_SUDO mkdir -p /var/lib/socktop
$SYS_SUDO chown socktop:socktop /var/lib/socktop
$SYS_SUDO chmod 755 /var/lib/socktop
UNIT_TMP = " $( mktemp) "
if [ -f " $SRC_DIR /docs/socktop-agent.service " ] ; then
cp " $SRC_DIR /docs/socktop-agent.service " " $UNIT_TMP "
else
# Fallback for refs that predate docs/socktop-agent.service
cat > " $UNIT_TMP " <<'UNIT'
[Unit]
Description=Socktop agent
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/socktop_agent --port 3000
Environment=RUST_LOG=info
# Optional auth:
# Environment=SOCKTOP_TOKEN=changeme
# TLS (self-signed cert on first run, default port 8443):
# Environment=SOCKTOP_ENABLE_SSL=1
Restart=on-failure
User=socktop
Group=socktop
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
UNIT
fi
# Point ExecStart at wherever this run installed the agent.
sed -i.bak " s|^ExecStart=[^ ]*socktop_agent|ExecStart= $PREFIX /socktop_agent| " " $UNIT_TMP "
rm -f " $UNIT_TMP .bak "
$SYS_SUDO install -o root -g root -m 0644 " $UNIT_TMP " /etc/systemd/system/socktop-agent.service
rm -f " $UNIT_TMP "
$SYS_SUDO systemctl daemon-reload
$SYS_SUDO systemctl enable --now socktop-agent.service
say "Service installed. To enable TLS or a token, edit /etc/systemd/system/socktop-agent.service, then: sudo systemctl daemon-reload && sudo systemctl restart socktop-agent"
2026-08-19 20:24:56 -07:00
fi
sleep 1
systemctl --no-pager -l status socktop-agent.service | head -5 || true
fi
say "Installed:"
" $PREFIX /socktop " --version
" $PREFIX /socktop_agent " --version
2026-08-20 10:23:45 -07:00
say " Active on PATH: $( type -p socktop || true ) / $( type -p socktop_agent || true ) "
socktop --version