#!/bin/sh
#
# collect-praxis-diag.sh
#
# Collects Praxis Collector diagnostics on Linux into a single archive for
# attaching to a support ticket. Linux counterpart of the Windows
# collect-praxis-diag.ps1 script (build/vm/install/windows/) - same sections,
# same secrets policy, adapted to systemd/journald and the Linux install
# layout (/opt/praxis-collector, /var/log/praxis-collector).
#
# Read-only: never stops the service, never modifies collector state.
# Works on any systemd-based distro this package supports.
#
# Secrets policy (mirrors the Windows script):
#   * *.enc files (machine-key-encrypted config/credentials) are NEVER
#     collected - they are undecryptable off-box and pure liability.
#   * file_storage / file_offsets data stores are NEVER collected (may
#     contain buffered customer telemetry); only a directory listing is.
#   * Copied config-like text files (yaml/yml/json/conf/cfg/env) pass
#     through a best-effort redactor (passwords, tokens, keys, community
#     strings, bearer headers, url credentials, PEM keys). Review the
#     archive before sending if your policies require it.
#
# Usage:
#   sudo ./collect-praxis-diag.sh
#   sudo ./collect-praxis-diag.sh --output-dir /tmp
#   sudo ./collect-praxis-diag.sh --from-date "2026-07-01 00:00:00" --to-date "2026-07-05 23:59:59"
#   sudo ./collect-praxis-diag.sh --since-hours 336
#
unalias -a 2>/dev/null || true
set -u

# ---------------------------------------------------------------------------
# Colors (same helper pattern as linux_install.sh)
# ---------------------------------------------------------------------------
supports_colors() {
	if [ -t 1 ] && [ -n "${TERM:-}" ] && [ "$TERM" != "dumb" ]; then
		if command -v tput >/dev/null 2>&1; then
			tput colors >/dev/null 2>&1
			return $?
		fi
	fi
	return 1
}

if supports_colors; then
	RED=$(tput setaf 1)
	GREEN=$(tput setaf 2)
	YELLOW=$(tput setaf 3)
	RESET=$(tput sgr0)
else
	RED=""
	GREEN=""
	YELLOW=""
	RESET=""
fi

colored_message() {
	printf "%s%s%s\n" "$2" "$1" "$RESET"
}
error_message() {
	printf "%sError: %s%s\n" "$RED" "$1" "$RESET" >&2
}
info_message() {
	colored_message "$1" "${2:-$GREEN}"
}

show_help() {
	cat <<EOF
Usage: collect-praxis-diag.sh [options]

  -o, --output-dir DIR      Where to write the bundle. Default: current directory.
      --from-date DATE      Start of the event-log window, local time (e.g.
                             "2026-07-01" or "2026-07-01 08:00:00"). Must be
                             supplied together with --to-date.
      --to-date DATE        End of the event-log window, local time. Must be
                             supplied together with --from-date.
      --since-hours HOURS   Event-log lookback window in hours, used only when
                             --from-date/--to-date are not supplied. Default: 96
                             (4 days). Support may request a larger window (e.g.
                             --since-hours 336) for older incidents.
  -h, --help                Show this help and exit.

Examples:
  sudo ./collect-praxis-diag.sh
  sudo ./collect-praxis-diag.sh --from-date "2026-07-01 00:00:00" --to-date "2026-07-05 23:59:59"
EOF
	exit 0
}

# ---------------------------------------------------------------------------
# Constants (keep in sync with the installer / collector)
# ---------------------------------------------------------------------------
PACKAGE_NAME="praxis-collector"
SYSTEMD_SERVICE="praxis-collector"
INSTALL_DIR="/opt/praxis-collector"
LOG_DIR="/var/log/praxis-collector"
COPY_CAP_BYTES=$((50 * 1024 * 1024))
TAIL_BYTES=$((10 * 1024 * 1024))

# ---------------------------------------------------------------------------
# Parse command line
# ---------------------------------------------------------------------------
OUTPUT_DIR="$(pwd)"
FROM_DATE=""
TO_DATE=""
SINCE_HOURS=96

while [ $# -gt 0 ]; do
	case "$1" in
	-h | --help)
		show_help
		;;
	-o | --output-dir)
		OUTPUT_DIR="$2"
		shift 2
		;;
	--from-date)
		FROM_DATE="$2"
		shift 2
		;;
	--to-date)
		TO_DATE="$2"
		shift 2
		;;
	--since-hours)
		SINCE_HOURS="$2"
		shift 2
		;;
	*)
		echo "Unknown option: $1"
		echo "Use -h or --help for usage information"
		exit 1
		;;
	esac
done

# ---------------------------------------------------------------------------
# Resolve the event-log window: explicit --from-date/--to-date (local time)
# takes priority over --since-hours; both must be supplied together. Done up
# front, before the staging directory is created, so bad input exits cleanly
# with nothing left on disk.
# ---------------------------------------------------------------------------
if [ -n "$FROM_DATE" ] || [ -n "$TO_DATE" ]; then
	if [ -z "$FROM_DATE" ] || [ -z "$TO_DATE" ]; then
		error_message "--from-date and --to-date must be supplied together."
		exit 1
	fi
	FROM_EPOCH=$(date -d "$FROM_DATE" +%s 2>/dev/null) || {
		error_message "could not parse --from-date '$FROM_DATE' (use e.g. '2026-07-01' or '2026-07-01 08:00:00')"
		exit 1
	}
	TO_EPOCH=$(date -d "$TO_DATE" +%s 2>/dev/null) || {
		error_message "could not parse --to-date '$TO_DATE' (use e.g. '2026-07-01' or '2026-07-01 08:00:00')"
		exit 1
	}
	if [ "$TO_EPOCH" -le "$FROM_EPOCH" ]; then
		error_message "--to-date must be after --from-date."
		exit 1
	fi
	EVENT_RANGE_MODE="explicit (--from-date/--to-date)"
else
	TO_EPOCH=$(date +%s)
	FROM_EPOCH=$((TO_EPOCH - SINCE_HOURS * 3600))
	EVENT_RANGE_MODE="since-hours fallback (${SINCE_HOURS}h)"
fi
FROM_DISPLAY=$(date -d "@$FROM_EPOCH" '+%Y-%m-%d %H:%M:%S')
TO_DISPLAY=$(date -d "@$TO_EPOCH" '+%Y-%m-%d %H:%M:%S')
FROM_UTC_DISPLAY=$(date -u -d "@$FROM_EPOCH" '+%Y-%m-%dT%H:%M:%SZ')
TO_UTC_DISPLAY=$(date -u -d "@$TO_EPOCH" '+%Y-%m-%dT%H:%M:%SZ')

# ---------------------------------------------------------------------------
# Setup: staging directory + manifest
# ---------------------------------------------------------------------------
STAMP=$(date +%Y%m%d-%H%M%S)
HOSTNAME_VAL=$(hostname 2>/dev/null || cat /proc/sys/kernel/hostname 2>/dev/null || echo unknown-host)
BUNDLE_TAG="praxis-collector-support-${HOSTNAME_VAL}-${STAMP}"
STAGE="$OUTPUT_DIR/$BUNDLE_TAG"
mkdir -p "$STAGE/logs" "$STAGE/state" "$STAGE/config" "$STAGE/system" "$STAGE/events"
MANIFEST="$STAGE/manifest.txt"

add_manifest() {
	# $1 status, $2 item, $3 note
	printf '%s\t%s\t%s\n' "$(echo "$1" | tr '[:lower:]' '[:upper:]')" "$2" "${3:-}" >>"$MANIFEST"
	case "$1" in
	ok) info_message "  [ok]      $2" "$GREEN" ;;
	skipped) info_message "  [skipped] $2 - ${3:-}" "$YELLOW" ;;
	*) error_message "  [error]   $2 - ${3:-}" ;;
	esac
}

# Same-named files can arrive from different source dirs. Never overwrite -
# prefix a counter instead (mirrors Get-UniquePath in the Windows script).
unique_path() {
	p="$1"
	if [ ! -e "$p" ]; then
		echo "$p"
		return
	fi
	dir=$(dirname "$p")
	leaf=$(basename "$p")
	i=2
	while [ -e "$dir/$i-$leaf" ]; do
		i=$((i + 1))
	done
	echo "$dir/$i-$leaf"
}

# Copies whole file up to $COPY_CAP_BYTES, else only the trailing
# $TAIL_BYTES (dest gets ".tail" suffix) - mirrors Copy-BoundedFile.
copy_bounded() {
	src="$1"
	dest_dir="$2"
	if [ ! -f "$src" ]; then
		add_manifest "skipped" "$src" "not found"
		return
	fi
	name=$(basename "$src")
	size=$(wc -c <"$src" 2>/dev/null | tr -d ' ')
	dest="$dest_dir/$name"
	if [ -n "$size" ] && [ "$size" -gt "$COPY_CAP_BYTES" ] 2>/dev/null; then
		dest=$(unique_path "${dest}.tail")
		tail -c "$TAIL_BYTES" "$src" >"$dest" 2>/dev/null
		add_manifest "ok" "$src" "tailed last $TAIL_BYTES of $size bytes"
	else
		dest=$(unique_path "$dest")
		cp "$src" "$dest" 2>/dev/null
		add_manifest "ok" "$src" "${size:-0} bytes"
	fi
}

# Best-effort secret redaction for config-like text files. NOT a guarantee -
# a regex pass mirroring the PowerShell script's rule set.
redact_copy() {
	src="$1"
	dest_dir="$2"
	if [ ! -f "$src" ]; then
		add_manifest "skipped" "$src" "not found"
		return
	fi
	dest=$(unique_path "$dest_dir/$(basename "$src").redacted")
	sed -E \
		-e 's/^([[:space:]]*"?[A-Za-z0-9_.-]*(password|passwd|secret|token|key|credential[A-Za-z]*|connection_string|community|passphrase|authorization)[A-Za-z0-9_.-]*"?[[:space:]]*[:=][[:space:]]*).+$/\1[REDACTED]/I' \
		-e 's/([Aa]uthorization[[:space:]]*:[[:space:]]*[Bb]earer[[:space:]]+)[^[:space:]]+/\1[REDACTED]/' \
		-e 's/([Bb]earer[[:space:]]+)[A-Za-z0-9_.~+\/=-]{16,}/\1[REDACTED]/' \
		-e 's#(://[^/[:space:]:@]+:)[^@[:space:]/]+(@)#\1[REDACTED]\2#' \
		-e 's/AKIA[0-9A-Z]{16}/[REDACTED-AWS-KEY-ID]/g' \
		"$src" 2>/dev/null | awk '
			/-----BEGIN [A-Z ]*PRIVATE KEY-----/ { print "[REDACTED PEM PRIVATE KEY]"; inkey=1; next }
			/-----END [A-Z ]*PRIVATE KEY-----/   { inkey=0; next }
			inkey { next }
			{ print }
		' >"$dest"
	add_manifest "ok" "$src" "copied with regex redaction"
}

# ---------------------------------------------------------------------------
# Banner + root check
# ---------------------------------------------------------------------------
echo ""
info_message "Praxis Collector diagnostic collector  ->  $STAGE" "$GREEN"
IS_ROOT=false
[ "$(id -u)" = "0" ] && IS_ROOT=true
if [ "$IS_ROOT" != "true" ]; then
	colored_message "WARNING: not running as root. Service state, some logs, and journal entries may be unreadable. Re-run with sudo for a complete bundle." "$YELLOW"
fi

{
	printf 'bundle: %s\n' "$BUNDLE_TAG"
	printf 'generated_at: %s (local)\n' "$(date '+%Y-%m-%d %H:%M:%S')"
	printf 'elevated: %s\n' "$IS_ROOT"
	printf 'event_range_mode: %s\n' "$EVENT_RANGE_MODE"
	printf 'event_range_from: %s (local)  %s (UTC)\n' "$FROM_DISPLAY" "$FROM_UTC_DISPLAY"
	printf 'event_range_to: %s (local)  %s (UTC)\n' "$TO_DISPLAY" "$TO_UTC_DISPLAY"
	printf 'shell: %s\n' "$(readlink -f /proc/$$/exe 2>/dev/null || echo /bin/sh)"
	printf 'scope_note: events/ contains journald/syslog excerpts from the selected date/time window that include system-wide package-manager and service activity (unit names, timestamps, exit codes as logged by systemd/journald), not only praxis-collector ones - concurrent package updates are a common cause of update failures. No other application'"'"'s files, logs, or data are collected.\n'
	printf 'status\titem\tnote\n'
} >"$MANIFEST"

# ---------------------------------------------------------------------------
# 1. Version / package registration
# ---------------------------------------------------------------------------
report="$STAGE/version.txt"
{
	echo "sha256/size/mtime below identify the exact binary in place (half-swapped-upgrade check)."
	if [ -x "$INSTALL_DIR/bin/praxis-collector" ]; then
		echo ""
		echo "===== $INSTALL_DIR/bin/praxis-collector ====="
		ls -la "$INSTALL_DIR/bin/praxis-collector" 2>&1
		command -v sha256sum >/dev/null 2>&1 && sha256sum "$INSTALL_DIR/bin/praxis-collector" 2>&1
	fi
	echo ""
	echo "===== Package manager registration ====="
	if command -v dpkg >/dev/null 2>&1; then
		dpkg -s "$PACKAGE_NAME" 2>&1
	fi
	if command -v rpm >/dev/null 2>&1; then
		rpm -q --info "$PACKAGE_NAME" 2>&1
	fi
} >"$report" 2>&1
add_manifest "ok" "version.txt" ""

# ---------------------------------------------------------------------------
# 2. Service configuration + state
# ---------------------------------------------------------------------------
report="$STAGE/system/service-status.txt"
{
	echo "===== systemctl status $SYSTEMD_SERVICE.service ====="
	systemctl status "$SYSTEMD_SERVICE.service" --no-pager -l 2>&1
	echo ""
	echo "===== systemctl show $SYSTEMD_SERVICE.service ====="
	systemctl show "$SYSTEMD_SERVICE.service" --no-pager 2>&1
} >"$report"
add_manifest "ok" "system/service-status.txt" ""

# ---------------------------------------------------------------------------
# 3. Collector logs (LOG_DIR) - live-safe bounded copies, newest 10 files
# ---------------------------------------------------------------------------
if [ -d "$LOG_DIR" ]; then
	files=$(ls -1t "$LOG_DIR" 2>/dev/null | head -10)
	if [ -n "$files" ]; then
		echo "$files" | while IFS= read -r f; do
			[ -f "$LOG_DIR/$f" ] && copy_bounded "$LOG_DIR/$f" "$STAGE/logs"
		done
	fi
else
	add_manifest "skipped" "$LOG_DIR" "not found"
fi

# ---------------------------------------------------------------------------
# 4. Package manager logs mentioning praxis (bounded by file mtime, same
#    "loose" approach as the Windows script's generic MSI*.log scan)
# ---------------------------------------------------------------------------
pkg_report="$STAGE/logs/package-manager-praxis-mentions.txt"
: >"$pkg_report"
found_pkg_mention=false
for f in /var/log/dpkg.log /var/log/apt/history.log /var/log/yum.log /var/log/rpm.log; do
	[ -f "$f" ] || continue
	mtime=$(date -r "$f" +%s 2>/dev/null) || continue
	if [ "$mtime" -ge "$FROM_EPOCH" ] 2>/dev/null; then
		if grep -qi "praxis" "$f" 2>/dev/null; then
			{
				echo "===== $f ====="
				grep -i "praxis" "$f" 2>/dev/null
			} >>"$pkg_report"
			found_pkg_mention=true
		fi
	fi
done
if [ "$found_pkg_mention" = "true" ]; then
	add_manifest "ok" "$pkg_report" "package manager log entries mentioning praxis"
else
	add_manifest "ok" "package-manager-scan" "no package manager log (modified within window) mentioned praxis"
fi

# ---------------------------------------------------------------------------
# 5. Persisted state: metadata dir (excluding data stores and *.enc),
#    upgrade lock + staged upgrade/uninstall request/processing files
# ---------------------------------------------------------------------------
meta_dir="$INSTALL_DIR/metadata"
if [ -d "$meta_dir" ]; then
	listing="$STAGE/state/dir-listing-metadata.txt"
	find "$meta_dir" -type f -exec ls -la {} \; >"$listing" 2>/dev/null
	add_manifest "ok" "listing $meta_dir" ""
	find "$meta_dir" -maxdepth 1 -type f ! -name "*.enc" -size -1M 2>/dev/null | while IFS= read -r sf; do
		base=$(basename "$sf")
		case "$base" in
		*filestorage* | *file_storage* | *file_offsets*) continue ;;
		esac
		case "$sf" in
		*.yaml | *.yml | *.json | *.conf | *.cfg | *.env) redact_copy "$sf" "$STAGE/state" ;;
		*) copy_bounded "$sf" "$STAGE/state" ;;
		esac
	done
else
	add_manifest "skipped" "$meta_dir" "not found"
fi
add_manifest "ok" "enc-exclusion" "*.enc files intentionally NOT collected (machine-bound encrypted secrets)"

LOCK_DIR="${PRAXIS_COLLECTOR_LOCK_DIR:-$INSTALL_DIR/run}"
lock_report="$STAGE/state/upgrade-lock.txt"
{
	printf 'PRAXIS_COLLECTOR_LOCK_DIR (env): %s\n' "${PRAXIS_COLLECTOR_LOCK_DIR:-<unset, using default $INSTALL_DIR/run>}"
	for fname in upgrade.lock upgrade.request upgrade.processing uninstall.request uninstall.processing; do
		p="$LOCK_DIR/$fname"
		printf '\n===== %s =====\n' "$p"
		if [ -f "$p" ]; then
			size=$(wc -c <"$p" 2>/dev/null | tr -d ' ')
			mtime=$(date -r "$p" '+%Y-%m-%d %H:%M:%S' 2>/dev/null)
			printf 'exists: true  size: %s  mtime: %s\n' "$size" "$mtime"
			cat "$p" 2>/dev/null
			pid=$(grep -oE 'pid[=:] ?[0-9]+' "$p" 2>/dev/null | grep -oE '[0-9]+' | head -1)
			if [ -n "$pid" ]; then
				if [ -d "/proc/$pid" ]; then
					printf 'lock pid %s is RUNNING\n' "$pid"
				else
					printf 'lock pid %s is NOT running (stale lock)\n' "$pid"
				fi
			fi
		else
			printf 'exists: false\n'
		fi
	done
} >"$lock_report"
add_manifest "ok" "state/upgrade-lock.txt" ""

# ---------------------------------------------------------------------------
# 6. Config files in cfg/ (redacted copies)
# ---------------------------------------------------------------------------
cfg_dir="$INSTALL_DIR/cfg"
if [ -d "$cfg_dir" ]; then
	find "$cfg_dir" -type f -exec ls -la {} \; >"$STAGE/config/dir-listing-cfg.txt" 2>/dev/null
	add_manifest "ok" "listing $cfg_dir" ""
	find "$cfg_dir" -maxdepth 1 -type f -size -5M 2>/dev/null | while IFS= read -r c; do
		case "$c" in
		*.yaml | *.yml | *.json | *.conf | *.cfg | *.env) redact_copy "$c" "$STAGE/config" ;;
		esac
	done
	add_manifest "ok" "config" "runtime.enc/creds.enc/opamp.enc excluded by policy; plaintext configs copied redacted"
else
	add_manifest "skipped" "$cfg_dir" "not found"
fi

# ---------------------------------------------------------------------------
# 7. Companion systemd units (update/uninstall path+service) - Linux
#    equivalent of the Windows scheduled-tasks check
# ---------------------------------------------------------------------------
report="$STAGE/system/systemd-units.txt"
: >"$report"
for unit in "$SYSTEMD_SERVICE-update.path" "$SYSTEMD_SERVICE-update.service" "$SYSTEMD_SERVICE-uninstall.path" "$SYSTEMD_SERVICE-uninstall.service"; do
	{
		printf '\n===== systemctl status %s =====\n' "$unit"
		systemctl status "$unit" --no-pager -l 2>&1
		printf '\n===== systemctl cat %s =====\n' "$unit"
		systemctl cat "$unit" 2>&1
	} >>"$report"
done
add_manifest "ok" "system/systemd-units.txt" "check ActiveState/Result of $SYSTEMD_SERVICE-update.service"

# ---------------------------------------------------------------------------
# 8. Events via journald (works on any systemd distro this package supports)
#    journalctl's --since/--until with @epoch is a first-class, well-tested
#    primitive (unlike Windows wevtutil's ad-hoc XPath time predicates) -
#    no post-filter re-check is needed here the way the Windows script needs
#    one for its @SystemTime range queries. Count caps (-n, newest-first via
#    --reverse) mirror the Windows script's /c:500 /c:1000 /c:300 limits, for
#    the same reason: an unbounded query on a busy box could produce a huge
#    file, or never finish.
# ---------------------------------------------------------------------------
if command -v journalctl >/dev/null 2>&1; then
	svc_report="$STAGE/events/journal-praxis-collector.txt"
	journalctl -u "$SYSTEMD_SERVICE.service" --since "@$FROM_EPOCH" --until "@$TO_EPOCH" --no-pager -o short-iso --reverse -n 1000 >"$svc_report" 2>&1
	add_manifest "ok" "events/journal-praxis-collector.txt" "range $FROM_DISPLAY to $TO_DISPLAY"

	companion_report="$STAGE/events/journal-companion-units.txt"
	journalctl -u "$SYSTEMD_SERVICE-update.service" -u "$SYSTEMD_SERVICE-uninstall.service" --since "@$FROM_EPOCH" --until "@$TO_EPOCH" --no-pager -o short-iso --reverse -n 500 >"$companion_report" 2>&1
	add_manifest "ok" "events/journal-companion-units.txt" "update/uninstall systemd unit run history in window"

	# ALL systemd unit start/stop/transition messages system-wide (not just
	# praxis) - mirrors Windows' system-scm-all.txt: lets support correlate a
	# failed update with some OTHER service/package restarting at the same
	# moment, which is a common root cause and invisible if you only look at
	# praxis-mentioning lines.
	sysall_report="$STAGE/events/journal-systemd-all.txt"
	journalctl --identifier=systemd --since "@$FROM_EPOCH" --until "@$TO_EPOCH" --no-pager -o short-iso --reverse -n 1000 >"$sysall_report" 2>&1
	add_manifest "ok" "events/journal-systemd-all.txt" "range $FROM_DISPLAY to $TO_DISPLAY (system-wide, not just praxis)"

	sys_praxis_report="$STAGE/events/journal-systemd-praxis-mentions.txt"
	grep -i "praxis" "$sysall_report" >"$sys_praxis_report" 2>/dev/null
	add_manifest "ok" "events/journal-systemd-praxis-mentions.txt" "praxis-mentioning lines extracted from journal-systemd-all.txt"

	err_report="$STAGE/events/journal-errors.txt"
	journalctl -p err --since "@$FROM_EPOCH" --until "@$TO_EPOCH" --no-pager -o short-iso --reverse -n 300 >"$err_report" 2>&1
	add_manifest "ok" "events/journal-errors.txt" "system-wide err-priority-and-above entries in window"

	# Crash-dump report folders (systemd-coredump / Ubuntu apport / RHEL abrt)
	# outlive the event-log window, same as Windows' WER report listing - a
	# names-and-dates listing gives crash history well beyond the requested
	# window without collecting any dump contents.
	crash_report="$STAGE/events/crash-dump-reports.txt"
	: >"$crash_report"
	for crash_dir in /var/lib/systemd/coredump /var/crash /var/spool/abrt; do
		[ -d "$crash_dir" ] || continue
		{
			echo "===== $crash_dir ====="
			find "$crash_dir" -iname "*praxis*" -exec ls -la {} \; 2>/dev/null
		} >>"$crash_report"
	done
	if [ -s "$crash_report" ]; then
		add_manifest "ok" "events/crash-dump-reports.txt" "crash-report folder names beyond the event window"
	else
		add_manifest "ok" "events/crash-dump-reports.txt" "no systemd-coredump/apport/abrt entries mentioned praxis"
	fi
else
	fallback="$STAGE/events/syslog-praxis-mentions.txt"
	: >"$fallback"
	for f in /var/log/syslog /var/log/messages; do
		[ -f "$f" ] || continue
		grep -i "praxis" "$f" 2>/dev/null >>"$fallback"
	done
	add_manifest "ok" "events/syslog-praxis-mentions.txt" "journalctl unavailable - grepped syslog/messages (not precisely time-bounded)"
fi

# ---------------------------------------------------------------------------
# 9. Installer / reboot state + relevant processes
# ---------------------------------------------------------------------------
report="$STAGE/system/installer-state.txt"
{
	echo "===== Pending reboot markers ====="
	if [ -f /var/run/reboot-required ]; then
		echo "reboot-required: true"
		cat /var/run/reboot-required 2>/dev/null
		[ -f /var/run/reboot-required.pkgs ] && cat /var/run/reboot-required.pkgs 2>/dev/null
	else
		echo "reboot-required: false (or not applicable on this distro)"
	fi
	if command -v needs-restarting >/dev/null 2>&1; then
		echo ""
		echo "===== needs-restarting -r ====="
		needs-restarting -r 2>&1
	fi
	echo ""
	echo "===== Relevant processes ====="
	if command -v pgrep >/dev/null 2>&1; then
		pgrep -af 'praxis-collector|praxis-update|praxis-updater' 2>/dev/null
	fi
} >"$report"
add_manifest "ok" "system/installer-state.txt" ""

# ---------------------------------------------------------------------------
# 10. Host info + collector service environment
# ---------------------------------------------------------------------------
report="$STAGE/system/host-info.txt"
{
	echo "===== OS ====="
	[ -f /etc/os-release ] && cat /etc/os-release
	echo ""
	echo "===== uname ====="
	uname -a
	echo ""
	echo "===== Uptime ====="
	uptime 2>/dev/null
	echo ""
	echo "===== Disk usage ====="
	df -h 2>/dev/null
	echo ""
	echo "===== Service environment ($SYSTEMD_SERVICE.service) ====="
	systemctl show "$SYSTEMD_SERVICE.service" --property=Environment 2>&1
} >"$report"
add_manifest "ok" "system/host-info.txt" ""

# ---------------------------------------------------------------------------
# Archive
# ---------------------------------------------------------------------------
echo "-- archive"
ARCHIVE="$OUTPUT_DIR/$BUNDLE_TAG.tar.gz"
if tar -czf "$ARCHIVE" -C "$OUTPUT_DIR" "$BUNDLE_TAG" 2>/dev/null; then
	rm -rf "$STAGE"
	echo ""
	colored_message "DONE. Bundle written to: $ARCHIVE" "$GREEN"
else
	echo ""
	colored_message "Could not create the archive automatically. The collected files are in: $STAGE" "$YELLOW"
	echo "Run: tar -czf '$BUNDLE_TAG.tar.gz' '$BUNDLE_TAG'   (from $OUTPUT_DIR), then attach that archive."
fi
if [ "$IS_ROOT" != "true" ]; then
	colored_message "Reminder: this run was NOT root - some items were likely skipped (see manifest.txt in the bundle)." "$YELLOW"
fi
