#!/bin/bash
set -euo pipefail
# install-procfd-rk3588-debian11.sh
#
# Builds + installs procfd on Debian 11 (Bullseye) ARM64 (RK3588), and patches procfd
# to add a CLI flag:
# --no-unix-peers
# that skips UNIX socket peer endpoint lookup via sock_diag/unix_diag.
#
# Why: Some vendor kernels (including yours) lack unix_diag/CONFIG_UNIX_DIAG, causing
# procfd to fail while collecting UNIX peer endpoints. This flag provides an explicit,
# reliable user-controlled fallback.
#
# Run: execute as a normal user from within ~/ (or any subdirectory under it). Requires sudo.
# ----------------------------
# Config
# ----------------------------
SRC_DIR_DEFAULT="$HOME/deshaw-procfd"
SRC_REPO_URL="https://github.com/deshaw/procfd.git"
INSTALL_PATH="/usr/local/bin/procfd"
RUSTUP_INIT_URL_AARCH64="https://static.rust-lang.org/rustup/dist/aarch64-unknown-linux-gnu/rustup-init"
RUST_2021_MIN_MAJOR=1
RUST_2021_MIN_MINOR=56
SRC_DIR="$SRC_DIR_DEFAULT"
DO_CLONE_IF_MISSING="yes"
DO_VERIFY="yes"
# ----------------------------
# Helpers
# ----------------------------
log() { echo "[INFO] $*"; }
warn() { echo "[WARN] $*"; }
die() { echo "[ERROR] $*" >&2; exit 1; }
confirm_yes() {
local prompt="$1"
local confirmation
read -r -p "$prompt (yes/NO): " confirmation
[[ "$confirmation" == "yes" ]] || die "Operation cancelled by user."
}
run_or_die() {
local desc="$1"; shift
log "$desc"
if ! "$@"; then
die "Failed: $desc"
fi
}
run_or_warn() {
local desc="$1"; shift
log "$desc"
if ! "$@"; then
warn "Non-fatal check failed: $desc"
return 0
fi
}
ensure_in_home_tree() {
if [[ "${PWD}" != "${HOME}"* ]]; then
die "Please run this script from within your home directory tree (~/...). Current dir: ${PWD}"
fi
}
ensure_not_root() {
if [[ "$(id -u)" -eq 0 ]]; then
die "Do not run this script as root. Run as a normal user with sudo privileges."
fi
}
ensure_sudo_works() {
command -v sudo >/dev/null 2>&1 || die "sudo is required but not found."
run_or_die "Validating sudo access (you may be prompted for your password)" sudo -v
}
ensure_debian11_arm64() {
local os_id os_version arch uname_m
os_id="$(. /etc/os-release && echo "${ID:-}")"
os_version="$(. /etc/os-release && echo "${VERSION_ID:-}")"
arch="$(dpkg --print-architecture)"
uname_m="$(uname -m)"
[[ "$os_id" == "debian" ]] || die "Unsupported OS: expected Debian, got ID=$os_id"
[[ "$os_version" == 11* ]] || die "Unsupported Debian version: expected 11.x (Bullseye), got VERSION_ID=$os_version"
if [[ "$arch" != "arm64" && "$uname_m" != "aarch64" ]]; then
die "Unsupported architecture: expected arm64/aarch64, got dpkg=$arch uname=$uname_m"
fi
}
is_pkg_installed() {
local pkg="$1"
dpkg -s "$pkg" >/dev/null 2>&1
}
apt_install_if_missing() {
local pkgs_to_install=()
local pkg
for pkg in "$@"; do
if is_pkg_installed "$pkg"; then
log "Package already installed: $pkg"
else
pkgs_to_install+=("$pkg")
fi
done
if [[ "${#pkgs_to_install[@]}" -gt 0 ]]; then
run_or_die "Updating apt package lists" sudo apt-get update
run_or_die "Installing packages: ${pkgs_to_install[*]}" sudo apt-get install -y "${pkgs_to_install[@]}"
fi
}
ensure_command() {
local cmd="$1"
local pkg="$2"
if ! command -v "$cmd" >/dev/null 2>&1; then
apt_install_if_missing "$pkg"
fi
command -v "$cmd" >/dev/null 2>&1 || die "Tool '$cmd' still not available after installing '$pkg'."
}
TEMP_DIR=""
cleanup() {
if [[ -n "${TEMP_DIR}" && -d "${TEMP_DIR}" ]]; then
rm -rf "${TEMP_DIR}"
fi
}
trap cleanup EXIT SIGINT SIGTERM
usage() {
cat <<'EOF'
Usage: ./install-procfd-rk3588-debian11.sh [options]
Options:
--src-dir PATH Use an existing procfd source directory (default: ~/deshaw-procfd)
--no-clone Do not clone if source dir is missing (offline-friendly; will error instead)
--skip-verify Skip post-install verification commands
-h, --help Show this help
EOF
}
while [[ "${#}" -gt 0 ]]; do
case "$1" in
--src-dir)
shift
[[ "${1:-}" ]] || die "--src-dir requires a path"
SRC_DIR="$1"
;;
--no-clone)
DO_CLONE_IF_MISSING="no"
;;
--skip-verify)
DO_VERIFY="no"
;;
-h|--help)
usage
exit 0
;;
*)
die "Unknown option: $1"
;;
esac
shift
done
# ----------------------------
# Rust toolchain helpers
# ----------------------------
parse_rustc_version() {
if ! command -v rustc >/dev/null 2>&1; then
return 1
fi
local v
v="$(rustc --version 2>/dev/null | awk '{print $2}')"
[[ -n "$v" ]] || return 1
local major minor patch
major="$(echo "$v" | cut -d. -f1)"
minor="$(echo "$v" | cut -d. -f2)"
patch="$(echo "$v" | cut -d. -f3 | sed 's/[^0-9].*$//')"
[[ -n "$major" && -n "$minor" && -n "$patch" ]] || return 1
echo "$major $minor $patch"
}
rustc_is_new_enough_for_2021() {
local major minor patch
if ! read -r major minor patch < <(parse_rustc_version); then
return 1
fi
if (( major > RUST_2021_MIN_MAJOR )); then
return 0
fi
if (( major < RUST_2021_MIN_MAJOR )); then
return 1
fi
(( minor >= RUST_2021_MIN_MINOR ))
}
source_cargo_env_if_present() {
if [[ -f "$HOME/.cargo/env" ]]; then
# shellcheck disable=SC1090
source "$HOME/.cargo/env"
fi
export PATH="$HOME/.cargo/bin:/usr/local/bin:$PATH"
}
install_rust_toolchain_if_needed() {
source_cargo_env_if_present
if rustc_is_new_enough_for_2021; then
log "Found rustc new enough for Rust 2021: $(rustc --version)"
return 0
fi
if command -v rustc >/dev/null 2>&1; then
warn "Found rustc, but it may be too old for Rust 2021: $(rustc --version)"
warn "Will install/update Rust via rustup to ensure compatibility."
else
log "rustc not found; will install Rust via rustup."
fi
ensure_command curl curl
local rustup_init_bin="$TEMP_DIR/rustup-init"
run_or_die "Downloading rustup-init (aarch64) to $rustup_init_bin" curl -Lfo "$rustup_init_bin" "$RUSTUP_INIT_URL_AARCH64"
[[ -s "$rustup_init_bin" ]] || die "Downloaded rustup-init is empty or missing."
run_or_die "Marking rustup-init executable" chmod +x "$rustup_init_bin"
confirm_yes "WARNING: This will execute the downloaded Rust installer '$rustup_init_bin' and install/update Rust in your home directory (~/.cargo, ~/.rustup). Continue?"
run_or_die "Running rustup-init installer" "$rustup_init_bin" -y --no-modify-path
source_cargo_env_if_present
command -v rustup >/dev/null 2>&1 || die "rustup not found after installation."
run_or_die "Installing stable Rust toolchain" rustup toolchain install stable
run_or_die "Setting stable as default toolchain" rustup default stable
run_or_die "Printing rustc version" rustc --version
rustc_is_new_enough_for_2021 || die "rustc is still not new enough for Rust 2021 after rustup install."
}
# ----------------------------
# Source tree
# ----------------------------
ensure_source_tree() {
if [[ -d "$SRC_DIR" && -f "$SRC_DIR/Cargo.toml" ]]; then
log "Using existing source directory: $SRC_DIR"
return 0
fi
if [[ "$DO_CLONE_IF_MISSING" != "yes" ]]; then
die "Source directory not found at '$SRC_DIR' and cloning is disabled (--no-clone)."
fi
ensure_command git git
log "Source directory not found. Cloning procfd from upstream into: $SRC_DIR"
if [[ -e "$SRC_DIR" && ! -d "$SRC_DIR" ]]; then
die "Path exists but is not a directory: $SRC_DIR"
fi
run_or_die "Cloning repository" git clone "$SRC_REPO_URL" "$SRC_DIR"
[[ -f "$SRC_DIR/Cargo.toml" ]] || die "Clone completed but Cargo.toml not found in $SRC_DIR"
}
# ----------------------------
# Patch: add --no-unix-peers flag and skip peer lookup
# ----------------------------
patch_add_no_unix_peers_flag_in_place() {
log "Patching source in-place to add --no-unix-peers"
[[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]] || die "Internal error: TEMP_DIR not initialized."
ensure_command python3 python3
# Idempotency checks
if grep -q "pub no_unix_peers: bool" "$SRC_DIR/src/opts.rs" 2>/dev/null && grep -q "args.no_unix_peers" "$SRC_DIR/src/lib.rs" 2>/dev/null; then
log "Patch already present (opts.rs + lib.rs)."
return 0
fi
# Backups
local ts
ts="$(date +%Y%m%d-%H%M%S)"
run_or_die "Backing up src/opts.rs" cp -a "$SRC_DIR/src/opts.rs" "$SRC_DIR/src/opts.rs.bak.$ts"
run_or_die "Backing up src/lib.rs" cp -a "$SRC_DIR/src/lib.rs" "$SRC_DIR/src/lib.rs.bak.$ts"
local py="$TEMP_DIR/patch_no_unix_peers.py"
cat > "$py" <<'PY'
from pathlib import Path
import sys
root = Path(sys.argv[1])
opts_path = root / "src" / "opts.rs"
lib_path = root / "src" / "lib.rs"
if not opts_path.exists():
raise SystemExit(f"Missing {opts_path}")
if not lib_path.exists():
raise SystemExit(f"Missing {lib_path}")
opts = opts_path.read_text(encoding="utf-8")
lib = lib_path.read_text(encoding="utf-8")
# ---- Patch opts.rs: add no_unix_peers flag ----
if "pub no_unix_peers: bool" not in opts:
anchor = "pub pid_only: bool,"
idx = opts.find(anchor)
if idx != -1:
line_end = opts.find("\n", idx)
if line_end == -1:
raise SystemExit("Unexpected opts.rs format (no newline after pid_only).")
insert = (
"\n\n #[clap(\n"
" long = \"no-unix-peers\",\n"
" display_order = 17,\n"
" help = \"Skip UNIX socket peer endpoint lookup via sock_diag (useful on kernels without unix_diag)\"\n"
" )]\n"
" pub no_unix_peers: bool,\n"
)
opts = opts[:line_end+1] + insert + opts[line_end+1:]
else:
# Fallback: insert before the final closing brace of the struct.
# This is less precise but avoids failing hard if formatting changes.
struct_start = opts.find("pub struct Args")
if struct_start == -1:
raise SystemExit("Could not find 'pub struct Args' in src/opts.rs")
# Find the last occurrence of a standalone '}' after struct start.
closing = opts.rfind("}\n")
if closing == -1 or closing < struct_start:
raise SystemExit("Could not find struct closing brace in src/opts.rs")
insert = (
"\n #[clap(\n"
" long = \"no-unix-peers\",\n"
" display_order = 17,\n"
" help = \"Skip UNIX socket peer endpoint lookup via sock_diag (useful on kernels without unix_diag)\"\n"
" )]\n"
" pub no_unix_peers: bool,\n"
)
opts = opts[:closing] + insert + opts[closing:]
# ---- Patch lib.rs: guard update_unix_map_with_peer with !args.no_unix_peers ----
needle = "if fd_filter.query_fds() && fd_filter.query_unix_entry(None) {"
replacement = "if !args.no_unix_peers && fd_filter.query_fds() && fd_filter.query_unix_entry(None) {"
if "args.no_unix_peers" not in lib:
if needle not in lib:
raise SystemExit("Could not find expected unix peer lookup 'if' line in src/lib.rs to patch.")
lib = lib.replace(needle, replacement, 1)
opts_path.write_text(opts, encoding="utf-8")
lib_path.write_text(lib, encoding="utf-8")
PY
run_or_die "Applying in-place patch via python3" python3 "$py" "$SRC_DIR"
grep -q "pub no_unix_peers: bool" "$SRC_DIR/src/opts.rs" || die "Patch failed: no_unix_peers not found in src/opts.rs"
grep -q "args.no_unix_peers" "$SRC_DIR/src/lib.rs" || die "Patch failed: args.no_unix_peers not found in src/lib.rs"
log "Patch applied successfully. Backups: src/opts.rs.bak.$ts, src/lib.rs.bak.$ts"
}
# ----------------------------
# Build + install + verify
# ----------------------------
build_procfd() {
log "Building procfd (release)"
source_cargo_env_if_present
command -v cargo >/dev/null 2>&1 || die "cargo not found; Rust toolchain not available."
(cd "$SRC_DIR" && run_or_die "Running cargo build --release" cargo build --release)
[[ -x "$SRC_DIR/target/release/procfd" ]] || die "Build did not produce expected binary at target/release/procfd"
}
install_procfd_binary() {
local built_bin="$SRC_DIR/target/release/procfd"
if [[ -e "$INSTALL_PATH" ]]; then
log "Existing install detected: $INSTALL_PATH"
run_or_warn "Existing procfd version (best-effort)" "$INSTALL_PATH" --version
confirm_yes "WARNING: '$INSTALL_PATH' already exists and will be overwritten. Continue?"
fi
run_or_die "Installing procfd to $INSTALL_PATH" sudo install -m 0755 "$built_bin" "$INSTALL_PATH"
run_or_die "Verifying installed procfd is runnable" "$INSTALL_PATH" --help >/dev/null
}
detect_base_flags_for_kernel() {
# Returns either "" or "--no-unix-peers" based on a real probe run.
local errf="$TEMP_DIR/procfd_probe_stderr.txt"
: > "$errf"
set +e
"$INSTALL_PATH" --pid "$$" >/dev/null 2>"$errf"
local rc=$?
set -e
if [[ $rc -eq 0 ]]; then
echo ""
return 0
fi
# If the failure smells like the known unix peer lookup issue, fall back.
if grep -qiE "Error collecting socket peer data|diagnostic socket|sock_diag|No such file or directory\s*\(os error 2\)" "$errf"; then
warn "Kernel appears to lack UNIX sock_diag support; will use --no-unix-peers by default."
echo "--no-unix-peers"
return 0
fi
# Unknown failure: print stderr and fail.
echo "--- procfd probe failed (stderr) ---" >&2
cat "$errf" >&2
echo "-----------------------------------" >&2
die "procfd failed during probe for an unexpected reason."
}
verify_procfd() {
if [[ "$DO_VERIFY" != "yes" ]]; then
warn "Skipping verification as requested (--skip-verify)."
return 0
fi
log "Running procfd verification commands..."
local base_flags
base_flags="$(detect_base_flags_for_kernel)"
local errf="$TEMP_DIR/procfd_verify_stderr.txt"
# Basic run
: > "$errf"
set +e
"$INSTALL_PATH" $base_flags --pid "$$" >/dev/null 2>"$errf"
local rc=$?
set -e
if [[ $rc -ne 0 ]]; then
echo "--- Basic verification failed (stderr) ---" >&2
cat "$errf" >&2
echo "----------------------------------------" >&2
die "Basic verification failed."
fi
# JSON output
: > "$errf"
set +e
"$INSTALL_PATH" $base_flags --pid "$$" --json | head -c 200 >/dev/null 2>"$errf"
rc=$?
set -e
if [[ $rc -ne 0 ]]; then
echo "--- JSON verification failed (stderr) ---" >&2
cat "$errf" >&2
echo "----------------------------------------" >&2
die "JSON verification failed."
fi
# PID-only
: > "$errf"
set +e
"$INSTALL_PATH" $base_flags --cmd '/bash/' --pid-only >/dev/null 2>"$errf"
rc=$?
set -e
if [[ $rc -ne 0 ]]; then
echo "--- PID-only verification failed (stderr) ---" >&2
cat "$errf" >&2
echo "--------------------------------------------" >&2
die "PID-only verification failed."
fi
# Best-effort filters
run_or_warn "socket-type tcp (no DNS) (best-effort)" bash -c "\"$INSTALL_PATH\" $base_flags --no-dns --socket-type tcp >/dev/null"
run_or_warn "socket-domain unix (no DNS) (best-effort)" bash -c "\"$INSTALL_PATH\" $base_flags --no-dns --socket-domain unix >/dev/null"
log "Verification complete."
log "Recommended run on this device: $INSTALL_PATH $base_flags --pid $$"
}
# ----------------------------
# Main
# ----------------------------
main() {
log "Starting procfd install + patch (--no-unix-peers) for Debian 11 ARM64"
ensure_not_root
ensure_in_home_tree
ensure_debian11_arm64
ensure_sudo_works
TEMP_DIR="$(mktemp -d)"
apt_install_if_missing ca-certificates curl build-essential pkg-config git python3
install_rust_toolchain_if_needed
ensure_source_tree
patch_add_no_unix_peers_flag_in_place
build_procfd
install_procfd_binary
verify_procfd
log "Done."
}
main "$@"
URL: https://ib.bsb.br/procfd