#!/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 "$@"