#!/bin/sh
# Logthread Agent installer for Linux (Debian/Ubuntu, RHEL/Rocky/Alma/Amazon/Fedora/SUSE, any other
# systemd or plain Linux via the tarball).
#
# curl -sSL https://downloads.logthread.com/agent/install.sh | sudo sh -s -- --key YOUR_INGEST_KEY --profiles linux-host,docker
#
# Options:
# --key KEY ingest key (or LOGTHREAD_INGEST_KEY, or --key-file FILE; never logged)
# --profiles a,b profiles to enable (default: linux-host). See: logthread-agent-profile list
# --endpoint URL gateway (default https://ingest.logthread.com)
# --fleet register with Logthread Fleet (OpAMP) and accept pushed configs
# --labels k=v,k=v Fleet labels, for example env=prod,role=web
# --version V install a specific version (default: the latest published)
# --base-url URL download location (default https://downloads.logthread.com/agent)
# --local DIR install from artifacts in DIR (no download; used for offline installs)
# --format deb|rpm|tar force the package format (default: detected from the package manager)
# --no-start install and configure but do not start the service
# --uninstall remove the agent (keeps the key file and state)
# --purge with --uninstall: also remove /etc/logthread-agent and /var/lib/logthread-agent
#
# Re-running with different options reconfigures an existing install. Every download is verified
# against SHA256SUMS.txt before anything is installed.
set -eu
BASE_URL=${LOGTHREAD_AGENT_BASE_URL:-https://downloads.logthread.com/agent}
KEY=${LOGTHREAD_INGEST_KEY:-}
ENDPOINT=${LOGTHREAD_ENDPOINT:-}
PROFILES=
FLEET=
LABELS=
VERSION=
LOCAL=
FORMAT=
START=1
UNINSTALL=0
PURGE=0
ENVFILE=/etc/logthread-agent/agent.env
say() { printf '%s\n' "$*"; }
die() { printf 'logthread-agent install: %s\n' "$*" >&2; exit 1; }
usage() {
cat <<'USAGE'
Options:
--key KEY ingest key (or LOGTHREAD_INGEST_KEY, or --key-file FILE; never logged)
--profiles a,b profiles to enable (default: linux-host). See: logthread-agent-profile list
--endpoint URL gateway (default https://ingest.logthread.com)
--fleet register with Logthread Fleet (OpAMP) and accept pushed configs
--labels k=v,k=v Fleet labels, for example env=prod,role=web
--version V install a specific version (default: the latest published)
--base-url URL download location (default https://downloads.logthread.com/agent)
--local DIR install from artifacts in DIR (no download; used for offline installs)
--format deb|rpm|tar force the package format (default: detected from the package manager)
--no-start install and configure but do not start the service
--uninstall remove the agent (keeps the key file and state)
--purge with --uninstall: also remove /etc/logthread-agent and /var/lib/logthread-agent
Re-running with different options reconfigures an existing install. Every download is verified
against SHA256SUMS.txt before anything is installed.
USAGE
}
main() {
while [ $# -gt 0 ]; do
case "$1" in
--key) KEY=${2:?--key needs a value}; shift 2 ;;
--key-file) KEY=$(tr -d '\r\n' < "${2:?--key-file needs a path}"); shift 2 ;;
--profiles) PROFILES=${2:?--profiles needs a value}; shift 2 ;;
--endpoint) ENDPOINT=${2:?--endpoint needs a value}; shift 2 ;;
--fleet) FLEET=true; shift ;;
--labels) LABELS=${2:?--labels needs a value}; shift 2 ;;
--version) VERSION=${2:?--version needs a value}; shift 2 ;;
--base-url) BASE_URL=${2:?--base-url needs a value}; shift 2 ;;
--local) LOCAL=${2:?--local needs a directory}; shift 2 ;;
--format) FORMAT=${2:?--format needs deb, rpm or tar}; shift 2 ;;
--no-start) START=0; shift ;;
--uninstall) UNINSTALL=1; shift ;;
--purge) PURGE=1; shift ;;
-h|--help) usage; exit 0 ;;
*) die "unknown option: $1 (try --help)" ;;
esac
done
[ "$(uname -s)" = Linux ] || die "this installer is for Linux. macOS: use the tarball (see README); Windows: install.ps1"
[ "$(id -u)" -eq 0 ] || die "run as root (for example: curl ... | sudo sh -s -- ...)"
# ---- uninstall -----------------------------------------------------------------------------
if [ "$UNINSTALL" -eq 1 ]; then
if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then
systemctl stop logthread-agent logthread-agent-autotrace 2>/dev/null || true
systemctl disable logthread-agent logthread-agent-autotrace 2>/dev/null || true
fi
if command -v dpkg >/dev/null 2>&1 && dpkg -s logthread-agent >/dev/null 2>&1; then
if [ "$PURGE" -eq 1 ]; then dpkg --purge logthread-agent; else dpkg --remove logthread-agent; fi
elif command -v rpm >/dev/null 2>&1 && rpm -q logthread-agent >/dev/null 2>&1; then
rpm -e logthread-agent
else
rm -rf /usr/lib/logthread-agent /usr/share/logthread-agent /usr/share/doc/logthread-agent
rm -f /usr/bin/logthread-agent /usr/bin/logthread-agent-profile /etc/systemd/system/logthread-agent.service \
/etc/systemd/system/logthread-agent-autotrace.service /usr/lib/systemd/system/logthread-agent-autotrace.service
fi
rm -rf /usr/lib/logthread-agent/autotrace
rm -rf /etc/systemd/system/logthread-agent.service.d
[ -d /run/systemd/system ] && systemctl daemon-reload 2>/dev/null || true
if [ "$PURGE" -eq 1 ]; then
rm -rf /etc/logthread-agent /var/lib/logthread-agent
userdel logthread-agent 2>/dev/null || true
say "Logthread Agent removed, including its key file and state."
else
say "Logthread Agent removed. Kept /etc/logthread-agent (key file) and /var/lib/logthread-agent; add --purge to delete them."
fi
exit 0
fi
# ---- inputs --------------------------------------------------------------------------------
if [ -z "$KEY" ] && [ -r "$ENVFILE" ]; then
KEY=$(sed -n 's/^LOGTHREAD_INGEST_KEY=//p' "$ENVFILE" | tail -n 1)
fi
[ -n "$KEY" ] || die "an ingest key is required: --key lt_... (create one in Logthread, Keys)"
case "$KEY" in *[!A-Za-z0-9_.-]*) die "the ingest key contains unexpected characters" ;; esac
case "$(uname -m)" in
x86_64|amd64) ARCH=amd64 ;;
aarch64|arm64) ARCH=arm64 ;;
*) die "unsupported CPU architecture: $(uname -m) (amd64 and arm64 are published)" ;;
esac
if command -v dpkg >/dev/null 2>&1 && command -v apt-get >/dev/null 2>&1; then PKG=deb
elif command -v rpm >/dev/null 2>&1; then PKG=rpm
else PKG=tar; fi
[ -z "$FORMAT" ] || PKG=$FORMAT
case "$PKG" in deb|rpm|tar) ;; *) die "invalid --format: $PKG" ;; esac
# ---- fetch and verify ----------------------------------------------------------------------
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
fetch() { # fetch URL DEST
if command -v curl >/dev/null 2>&1; then curl -fsSL --retry 3 -o "$2" "$1"
elif command -v wget >/dev/null 2>&1; then wget -q -O "$2" "$1"
else die "curl or wget is required"; fi
}
sha256() {
if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1
elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | cut -d' ' -f1
else die "sha256sum or shasum is required to verify the download"; fi
}
get() { # get NAME (into $tmp)
if [ -n "$LOCAL" ]; then cp "$LOCAL/$1" "$tmp/$1"; else fetch "$BASE_URL/$1" "$tmp/$1"; fi
}
getv() { # getv NAME: a file of the chosen version, published under //
if [ -n "$LOCAL" ]; then cp "$LOCAL/$1" "$tmp/$1"; else fetch "$BASE_URL/$VERSION/$1" "$tmp/$1"; fi
}
if [ -z "$VERSION" ]; then
get latest.txt 2>/dev/null || die "cannot read $BASE_URL/latest.txt (use --version, or check the network)"
VERSION=$(tr -d ' \r\n' < "$tmp/latest.txt")
fi
case "$VERSION" in *[!0-9A-Za-z.+-]*|"") die "invalid version: $VERSION" ;; esac
case "$PKG" in
deb) FILE=logthread-agent_${VERSION}_linux_${ARCH}.deb ;;
rpm) FILE=logthread-agent_${VERSION}_linux_${ARCH}.rpm ;;
tar) FILE=logthread-agent_${VERSION}_linux_${ARCH}.tar.gz ;;
esac
say "Downloading Logthread Agent $VERSION ($PKG, $ARCH)"
getv "$FILE"
getv SHA256SUMS.txt
want=$(grep " $FILE\$" "$tmp/SHA256SUMS.txt" | cut -d' ' -f1 | head -n 1)
[ -n "$want" ] || die "$FILE is not listed in SHA256SUMS.txt"
have=$(sha256 "$tmp/$FILE")
[ "$want" = "$have" ] || die "SHA-256 mismatch for $FILE (expected $want, got $have); nothing was installed"
say "SHA-256 verified"
# ---- install -------------------------------------------------------------------------------
case "$PKG" in
deb) DEBIAN_FRONTEND=noninteractive dpkg -i "$tmp/$FILE" ;;
rpm) rpm -U --replacepkgs "$tmp/$FILE" ;;
tar)
mkdir -p "$tmp/x" && tar -xzf "$tmp/$FILE" -C "$tmp/x"
d=$(ls -d "$tmp"/x/logthread-agent_*/ | head -n 1)
install -d /usr/lib/logthread-agent /usr/share/logthread-agent/profiles /usr/share/doc/logthread-agent /etc/logthread-agent
install -m 0755 "$d"logthread-agent "$d"logthread-agent-launch "$d"logthread-agent-supervisor /usr/lib/logthread-agent/
ln -sf /usr/lib/logthread-agent/logthread-agent /usr/bin/logthread-agent
install -m 0755 "$d"logthread-agent-profile /usr/bin/logthread-agent-profile
cp -f "$d"profiles/*.yaml /usr/share/logthread-agent/profiles/
install -m 0644 "$d"README.md "$d"NOTICE /usr/share/doc/logthread-agent/
[ -e "$ENVFILE" ] || install -m 0600 "$d"agent.env "$ENVFILE"
if [ -d /usr/lib/systemd/system ]; then unit=/usr/lib/systemd/system; else unit=/etc/systemd/system; fi
install -d "$unit"; install -m 0644 "$d"logthread-agent.service "$unit/logthread-agent.service"
if [ -d "$d"autotrace ]; then
install -d /usr/share/logthread-agent/autotrace
install -m 0644 "$d"autotrace/obi.yaml /usr/share/logthread-agent/autotrace/
[ -e /etc/logthread-agent/autotrace.env ] || install -m 0644 "$d"autotrace/autotrace.env /etc/logthread-agent/
install -m 0644 "$d"logthread-agent-autotrace.service "$unit/logthread-agent-autotrace.service"
fi
sh "$d"postinstall.sh ;;
esac
# ---- autotrace add-on (OBI, eBPF; only with --profiles ...,autotrace) ------------------------
case ",$PROFILES," in *,autotrace,*)
[ -r /sys/kernel/btf/vmlinux ] || die "autotrace needs a kernel with BTF (/sys/kernel/btf/vmlinux): Linux 5.8+ or RHEL 8+. Install without it, or see https://logthread.com/docs/opentelemetry"
ADDON=logthread-agent-autotrace_${VERSION}_linux_${ARCH}.tar.gz
say "Downloading the autotrace add-on (OpenTelemetry eBPF Instrumentation)"
getv "$ADDON"
want=$(grep " $ADDON\$" "$tmp/SHA256SUMS.txt" | cut -d' ' -f1 | head -n 1)
[ -n "$want" ] || die "$ADDON is not listed in SHA256SUMS.txt"
[ "$want" = "$(sha256 "$tmp/$ADDON")" ] || die "SHA-256 mismatch for $ADDON; autotrace was not installed"
rm -rf "$tmp/a" /usr/lib/logthread-agent/autotrace; mkdir -p "$tmp/a" /usr/lib/logthread-agent/autotrace
tar -xzf "$tmp/$ADDON" -C "$tmp/a"
cp -R "$tmp"/a/logthread-agent-autotrace_*/. /usr/lib/logthread-agent/autotrace/
chmod 0755 /usr/lib/logthread-agent/autotrace/obi
say "SHA-256 verified; autotrace installed" ;;
esac
# ---- configure (idempotent) ----------------------------------------------------------------
set_env() { # set_env KEY VALUE : replace the line, or append it
k=$1; v=$2
umask 077
K="$k" V="$v" awk 'BEGIN{k=ENVIRON["K"]; v=ENVIRON["V"]; done=0} $0 ~ "^#?"k"=" && !done && $0 !~ "^#" {print k"="v; done=1; next} {print} END{if(!done) print k"="v}' "$ENVFILE" > "$ENVFILE.new"
# a commented example line becomes a real setting when none exists
if ! grep -q "^$k=" "$ENVFILE.new"; then printf '%s=%s\n' "$k" "$v" >> "$ENVFILE.new"; fi
mv "$ENVFILE.new" "$ENVFILE"; chmod 0600 "$ENVFILE"
}
[ -f "$ENVFILE" ] || install -m 0600 /dev/null "$ENVFILE"
set_env LOGTHREAD_INGEST_KEY "$KEY"
[ -n "$ENDPOINT" ] && set_env LOGTHREAD_ENDPOINT "$ENDPOINT"
[ -n "$FLEET" ] && set_env LOGTHREAD_FLEET true
[ -n "$LABELS" ] && set_env LOGTHREAD_FLEET_LABELS "$LABELS"
chmod 0600 "$ENVFILE"
[ -n "$PROFILES" ] || PROFILES=linux-host
profile_args=$(printf '%s' "$PROFILES" | tr ',' ' ')
# shellcheck disable=SC2086
/usr/bin/logthread-agent-profile set $profile_args
# ---- start ---------------------------------------------------------------------------------
if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then
systemctl daemon-reload
systemctl enable logthread-agent >/dev/null 2>&1 || true
if [ "$START" -eq 1 ]; then
systemctl restart logthread-agent
sleep 2
if systemctl is-active --quiet logthread-agent; then
say "Logthread Agent $VERSION is running (profiles: $PROFILES)."
say "Status: systemctl status logthread-agent Logs: journalctl -u logthread-agent -f"
else
say "The service did not stay up. See: journalctl -u logthread-agent -n 50" >&2; exit 1
fi
else
say "Installed. Start it with: systemctl start logthread-agent"
fi
else
say "Installed (no systemd here). Run it with:"
say " set -a; . $ENVFILE; set +a; /usr/lib/logthread-agent/logthread-agent-launch"
fi
}
# The whole script is read before anything runs, so child processes cannot swallow it when it
# is piped into sh.
main "$@"