feat(pi): rustscan instead of nmap
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Waiting to run
Python tests / test (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / lib-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Krzysztof Rudnicki 2026-11-10 14:32:12 +01:00
parent 2a788b741d
commit 7581a904cd
13 changed files with 349 additions and 224 deletions

View File

@ -0,0 +1,13 @@
{
"title": "Pi flashing: discover SSH hosts with rustscan, not nmap",
"objective": "The Pi flash/discovery scripts find SSH hosts on the LAN with `rustscan -a <net> -p 22 -g`, install/check for rustscan instead of nmap, and rustscan is configured (scripts = \"None\") so it never pipes into nmap, which the pacman wrapper blocks.",
"acceptance_criteria": [
"No nmap references remain in the Pi flashing scripts and rpi_nc_deps checks for rustscan (test_pi_discovery_rustscan.sh)",
"fresh-install/main.sh installs fresh-install/rustscan.toml to ~/.rustscan.toml"
],
"out_of_scope": [
"The pacman nmap block and /usr/local/bin/nmap shim (digital-wellbeing repo)",
"Live scan of a real LAN"
],
"verifier": "linux_configuration/tests/test_pi_discovery_rustscan.sh; pre-commit run --files <changed-files>"
}

View File

@ -1,44 +0,0 @@
{
"intent": "DNS-over-TLS (stunnel-dot) comes up at every boot, dnsmasq has no ordering cycle, and the service checker stops expecting the removed pc_startup feature and finds the extracted repos under ~/src.",
"scope": [
"linux_configuration/features/lib/dns_units.sh, dot_resolver_install.sh and their tests",
"linux_configuration/periodic_background check_and_enable_services.sh, services_units.sh, services_report.sh and their tests",
"Non-goal: the firewall, the blocklist feed, the guard warnings (compulsive-block, LeechBlock, workout-locker) this checker reports"
],
"changes": [
"dnsmasq: bind-dynamic instead of bind-interfaces, so starting before enp6s0 exists no longer exits with 'unknown interface'",
"dnsmasq drop-in: drop After/Wants=network-online.target, which cycled with the stock Before=network-online.target",
"stunnel-dot.service: Wants=dnsmasq.service instead of Requires=, so one failed dnsmasq start no longer kills DoT until a manual restart",
"check_and_enable_services: remove check_startup_monitor (pc_startup was removed in 765fa925) and default HOSTS_BLOCKER/DW/WORKOUT_LOCKER repos to ~/src/<repo>"
],
"verification": [
{
"command": "pre-commit run --files <the 11 files> (shell-coverage-ratchet runs the suites in the jail)",
"result": "pass",
"evidence": "all hooks passed, ratchet 100%"
},
{
"command": "vmbox: dnsmasq with interface=<absent>, bind-interfaces vs bind-dynamic; Requires= vs Wants= on a failing dependency",
"result": "pass",
"evidence": "bind-interfaces: DIED 'unknown interface lanx0'; bind-dynamic: survived, bound 10.199.0.1:5353 after the interface appeared; Requires=: inactive; Wants=: active"
},
{
"command": "host: setup_dot_resolver.sh + dns_units functions + validate_and_enable",
"result": "pass",
"evidence": "dnsmasq --test OK; stunnel-dot active on 10.8.0.1:853, TLSv1.3 Verification OK; DNS answers on 127.0.0.1 and 192.168.1.43; no network-online in dnsmasq After="
},
{
"command": "sudo check_and_enable_services.sh --status (host) and vmbox diff against HEAD",
"result": "pass",
"evidence": "full run, no 'not found'; only diff vs HEAD is the removed PC Startup Monitor section"
}
],
"risks": [
"bind-dynamic also listens on addresses that appear later on enp6s0 (intended)",
"stunnel now starts even if dnsmasq is down; queries fail until dnsmasq is back, which Restart=always plus the watchdog already handle"
],
"rollback": [
"git revert this commit, then rerun setup_dns_blocker.sh setup and setup_dot_resolver.sh",
"Validate with: systemctl is-active dnsmasq stunnel-dot; setup_dot_resolver.sh --check"
]
}

View File

@ -0,0 +1,35 @@
{
"intent": "Pi flashing scripts discover SSH hosts with rustscan instead of nmap; rustscan runs with scripts = \"None\" so it never calls nmap.",
"scope": [
"linux_configuration/features/lib/{nc_discover,nc_flash,nc_image,nc_remote,pi_flash_remote,rpi_nc_deps}.sh",
"linux_configuration/features/raspberry_pi_flash_sd.sh",
"linux_configuration/fresh-install/main.sh, rustscan.toml",
"linux_configuration/tests/test_pi_discovery_rustscan.sh",
"Not touched: pacman wrapper (digital-wellbeing), live LAN scanning"
],
"changes": [
"nmap ping sweep + port scan replaced by `rustscan -a <net> -p 22 -g | awk '{print $1}'` (greppable output, one IP per line)",
"Dependency checks and hint messages name rustscan instead of nmap",
"fresh-install/rustscan.toml (scripts = \"None\") copied to ~/.rustscan.toml by main.sh"
],
"verification": [
{
"command": "linux_configuration/tests/test_pi_discovery_rustscan.sh",
"result": "pass",
"evidence": "ok per discovery script, ok rpi_nc_deps dependencies, ok no nmap references"
},
{
"command": "pre-commit run --files <changed-files>",
"result": "pass",
"evidence": "shellcheck, codespell and secrets hooks passed on the shell files"
}
],
"risks": [
"rustscan -g output format change would break the awk IP extraction",
"Hosts that drop rustscan's fast SYN probing may be missed versus nmap"
],
"rollback": [
"git revert the commit",
"Re-run a Pi discovery and confirm hosts are listed"
]
}

View File

@ -39,13 +39,11 @@ discover_raspberry_pi() {
return
fi
# Ping sweep to wake up hosts
log_info "Hostname resolution failed, scanning network..."
nmap -sn -T4 "$network" &>/dev/null || true
# Scan for SSH-enabled devices (excluding our IP and known laptop)
local ssh_hosts
ssh_hosts=$(nmap -p 22 --open -sT -T4 "$network" 2>/dev/null | grep "Nmap scan report" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -vw "$my_ip" | grep -vw "$REMOTE_LAPTOP_IP" 2>/dev/null | sort -u) || true
ssh_hosts=$(rustscan -a "$network" -p 22 -g 2>/dev/null | awk '{print $1}' | grep -vw "$my_ip" | grep -vw "$REMOTE_LAPTOP_IP" 2>/dev/null | sort -u) || true
if [[ -z $ssh_hosts ]]; then
die "No new SSH-enabled devices found. Is the Pi connected and booted?"

View File

@ -67,9 +67,9 @@ ensure_dependencies() {
local missing_packages=()
# Check for nmap (fast network scanning)
if ! command -v nmap &>/dev/null; then
missing_packages+=("nmap")
# Check for rustscan (fast network scanning)
if ! command -v rustscan &>/dev/null; then
missing_packages+=("rustscan")
fi
# Check for sshpass (for initial SSH key setup)

View File

@ -176,5 +176,5 @@ EOF
log_success "SD card configured for headless boot!"
log_info "Insert the SD card into your Raspberry Pi and power it on."
log_info "Find the Pi's IP address from your router or use: nmap -sn 192.168.1.0/24"
log_info "Find the Pi's IP address from your router or use: rustscan -a 192.168.1.0/24 -p 22 -g"
}

View File

@ -22,13 +22,11 @@ discover_remote_laptop() {
log_info "Local IP: $my_ip, Gateway: $gateway, Network: $network"
# Use nmap for fast parallel SSH port scanning
log_info "Scanning network for SSH-enabled devices (using nmap)..."
# Use rustscan for fast parallel SSH port scanning
log_info "Scanning network for SSH-enabled devices (using rustscan)..."
local ssh_hosts
# First do a ping sweep to wake up hosts, then scan SSH port
nmap -sn -T4 "$network" &>/dev/null || true
# Extract IPs from nmap output - grep for report lines then extract IP
ssh_hosts=$(nmap -p 22 --open -sT -T4 "$network" 2>/dev/null | grep "Nmap scan report" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -vw "$my_ip" | sort -u)
# Greppable output: one "IP -> [22]" line per host with the port open
ssh_hosts=$(rustscan -a "$network" -p 22 -g 2>/dev/null | awk '{print $1}' | grep -vw "$my_ip" | sort -u) || true
if [[ -z $ssh_hosts ]]; then
die "No SSH-enabled devices found on network"

View File

@ -6,136 +6,136 @@
# and the variables defined above the source line.
discover_remote_laptop() {
log_info "Auto-discovering remote laptop on local network..."
log_info "Auto-discovering remote laptop on local network..."
ensure_dependencies
ensure_dependencies
local my_ip
my_ip=$(ip -4 addr show | grep -oP '(?<=inet\s)(?!127\.)\d+(\.\d+){3}' | head -1)
local my_ip
my_ip=$(ip -4 addr show | grep -oP '(?<=inet\s)(?!127\.)\d+(\.\d+){3}' | head -1)
local gateway
gateway=$(ip route | grep default | awk '{print $3}' | head -1)
local network="${gateway%.*}.0/24"
local gateway
gateway=$(ip route | grep default | awk '{print $3}' | head -1)
local network="${gateway%.*}.0/24"
log_info "Local IP: $my_ip, Gateway: $gateway, Network: $network"
log_info "Scanning network for SSH-enabled devices (using nmap)..."
log_info "Local IP: $my_ip, Gateway: $gateway, Network: $network"
log_info "Scanning network for SSH-enabled devices (using rustscan)..."
local ssh_hosts
nmap -sn -T4 "$network" &> /dev/null || true
ssh_hosts=$(nmap -p 22 --open -sT -T4 "$network" 2> /dev/null | grep "Nmap scan report" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -vw "$my_ip" | sort -u)
local ssh_hosts
# Greppable output: one "IP -> [22]" line per host with the port open
ssh_hosts=$(rustscan -a "$network" -p 22 -g 2>/dev/null | awk '{print $1}' | grep -vw "$my_ip" | sort -u) || true
if [[ -z $ssh_hosts ]]; then
die "No SSH-enabled devices found on network"
fi
if [[ -z $ssh_hosts ]]; then
die "No SSH-enabled devices found on network"
fi
local host_count
host_count=$(echo "$ssh_hosts" | wc -l)
log_info "Found $host_count SSH-enabled device(s): $(echo "$ssh_hosts" | tr '\n' ' ')"
local host_count
host_count=$(echo "$ssh_hosts" | wc -l)
log_info "Found $host_count SSH-enabled device(s): $(echo "$ssh_hosts" | tr '\n' ' ')"
local common_users=("$REMOTE_LAPTOP_USER" "kuchy" "kuhy" "$(whoami)" "pi" "user" "admin")
local users=()
for u in "${common_users[@]}"; do
local is_dup=0
for existing in "${users[@]}"; do
if [[ $u == "$existing" ]]; then
is_dup=1
break
fi
done
if [[ $is_dup -eq 0 ]]; then
users+=("$u")
fi
done
local common_users=("$REMOTE_LAPTOP_USER" "kuchy" "kuhy" "$(whoami)" "pi" "user" "admin")
local users=()
for u in "${common_users[@]}"; do
local is_dup=0
for existing in "${users[@]}"; do
if [[ $u == "$existing" ]]; then
is_dup=1
break
fi
done
if [[ $is_dup -eq 0 ]]; then
users+=("$u")
fi
done
log_info "Will try usernames: ${users[*]}"
log_info "Will try usernames: ${users[*]}"
local found_laptop=""
local found_user=""
local idx=0
local found_laptop=""
local found_user=""
local idx=0
for ip in $ssh_hosts; do
idx=$((idx + 1))
for ip in $ssh_hosts; do
idx=$((idx + 1))
if [[ $ip == "$gateway" ]]; then
log_info "[$idx/$host_count] Skipping $ip (gateway)"
continue
fi
if [[ $ip == "$gateway" ]]; then
log_info "[$idx/$host_count] Skipping $ip (gateway)"
continue
fi
log_info "[$idx/$host_count] $ip - Trying SSH key access with common usernames..."
log_info "[$idx/$host_count] $ip - Trying SSH key access with common usernames..."
for try_user in "${users[@]}"; do
if ssh -o BatchMode=yes -o ConnectTimeout=2 -o StrictHostKeyChecking=accept-new "${try_user}@${ip}" "echo ok" 2> /dev/null | grep -q "ok"; then
log_success "[$idx/$host_count] $ip - SSH key access confirmed with user '$try_user'!"
found_user="$try_user"
for try_user in "${users[@]}"; do
if ssh -o BatchMode=yes -o ConnectTimeout=2 -o StrictHostKeyChecking=accept-new "${try_user}@${ip}" "echo ok" 2>/dev/null | grep -q "ok"; then
log_success "[$idx/$host_count] $ip - SSH key access confirmed with user '$try_user'!"
found_user="$try_user"
log_info "[$idx/$host_count] $ip - Checking for SD card..."
local has_sd
has_sd=$(ssh -o BatchMode=yes -o ConnectTimeout=2 "${try_user}@${ip}" "lsblk -d -o NAME,RM,TRAN 2>/dev/null | grep -E '1.*(usb|mmc)' | head -1" 2> /dev/null || true)
log_info "[$idx/$host_count] $ip - Checking for SD card..."
local has_sd
has_sd=$(ssh -o BatchMode=yes -o ConnectTimeout=2 "${try_user}@${ip}" "lsblk -d -o NAME,RM,TRAN 2>/dev/null | grep -E '1.*(usb|mmc)' | head -1" 2>/dev/null || true)
if [[ -n $has_sd ]]; then
log_success "[$idx/$host_count] $ip - Found SD card: $has_sd"
found_laptop="$ip"
break 2
else
log_warning "[$idx/$host_count] $ip - No SD card detected, saving as fallback..."
if [[ -z $found_laptop ]]; then
found_laptop="$ip"
fi
fi
break
fi
done
done
if [[ -n $has_sd ]]; then
log_success "[$idx/$host_count] $ip - Found SD card: $has_sd"
found_laptop="$ip"
break 2
else
log_warning "[$idx/$host_count] $ip - No SD card detected, saving as fallback..."
if [[ -z $found_laptop ]]; then
found_laptop="$ip"
fi
fi
break
fi
done
done
if [[ -z $found_laptop ]] || [[ -z $found_user ]]; then
log_warning "No device with passwordless SSH found using common usernames."
if [[ -z $found_laptop ]] || [[ -z $found_user ]]; then
log_warning "No device with passwordless SSH found using common usernames."
found_laptop=$(echo "$ssh_hosts" | grep -vw "$gateway" | head -1)
found_laptop=$(echo "$ssh_hosts" | grep -vw "$gateway" | head -1)
if [[ -z $found_laptop ]]; then
die "Could not find any suitable SSH-enabled device"
fi
if [[ -z $found_laptop ]]; then
die "Could not find any suitable SSH-enabled device"
fi
log_info "Found SSH host at $found_laptop but need credentials."
read -r -p "Enter username for $found_laptop: " found_user
log_info "Found SSH host at $found_laptop but need credentials."
read -r -p "Enter username for $found_laptop: " found_user
if [[ -z $found_user ]]; then
die "No username provided"
fi
fi
if [[ -z $found_user ]]; then
die "No username provided"
fi
fi
REMOTE_LAPTOP_IP="$found_laptop"
REMOTE_LAPTOP_USER="$found_user"
log_success "Selected remote laptop: ${REMOTE_LAPTOP_USER}@${REMOTE_LAPTOP_IP}"
REMOTE_LAPTOP_IP="$found_laptop"
REMOTE_LAPTOP_USER="$found_user"
log_success "Selected remote laptop: ${REMOTE_LAPTOP_USER}@${REMOTE_LAPTOP_IP}"
save_config
save_config
}
setup_ssh_key_to_remote() {
local remote_host="$1"
local remote_user="$2"
local remote_host="$1"
local remote_user="$2"
if ssh -o BatchMode=yes -o ConnectTimeout=5 "${remote_user}@${remote_host}" "echo 'SSH key works'" 2> /dev/null; then
log_success "SSH key authentication to ${remote_user}@${remote_host} already configured"
return 0
fi
if ssh -o BatchMode=yes -o ConnectTimeout=5 "${remote_user}@${remote_host}" "echo 'SSH key works'" 2>/dev/null; then
log_success "SSH key authentication to ${remote_user}@${remote_host} already configured"
return 0
fi
log_info "Setting up SSH key authentication to ${remote_user}@${remote_host}..."
log_info "Setting up SSH key authentication to ${remote_user}@${remote_host}..."
if [[ ! -f ~/.ssh/id_rsa.pub ]] && [[ ! -f ~/.ssh/id_ed25519.pub ]]; then
log_info "Generating SSH key..."
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N "" -q
fi
if [[ ! -f ~/.ssh/id_rsa.pub ]] && [[ ! -f ~/.ssh/id_ed25519.pub ]]; then
log_info "Generating SSH key..."
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N "" -q
fi
log_info "Copying SSH key to remote host (you may be prompted for password)..."
log_info "Copying SSH key to remote host (you may be prompted for password)..."
if command -v ssh-copy-id &> /dev/null; then
ssh-copy-id -o StrictHostKeyChecking=no "${remote_user}@${remote_host}"
else
local pub_key
pub_key=$(cat ~/.ssh/id_ed25519.pub 2> /dev/null || cat ~/.ssh/id_rsa.pub)
ssh -o StrictHostKeyChecking=no "${remote_user}@${remote_host}" "mkdir -p ~/.ssh && echo '$pub_key' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
fi
if command -v ssh-copy-id &>/dev/null; then
ssh-copy-id -o StrictHostKeyChecking=no "${remote_user}@${remote_host}"
else
local pub_key
pub_key=$(cat ~/.ssh/id_ed25519.pub 2>/dev/null || cat ~/.ssh/id_rsa.pub)
ssh -o StrictHostKeyChecking=no "${remote_user}@${remote_host}" "mkdir -p ~/.ssh && echo '$pub_key' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
fi
log_success "SSH key authentication configured"
log_success "SSH key authentication configured"
}

View File

@ -36,8 +36,8 @@ wait_for_apt_lock() {
ensure_dependencies() {
local missing_packages=()
if ! command -v nmap &>/dev/null; then
missing_packages+=("nmap")
if ! command -v rustscan &>/dev/null; then
missing_packages+=("rustscan")
fi
if ! command -v sshpass &>/dev/null; then
@ -88,10 +88,9 @@ discover_raspberry_pi() {
fi
log_info "Hostname resolution failed, scanning network..."
nmap -sn -T4 "$network" &>/dev/null || true
local ssh_hosts
ssh_hosts=$(nmap -p 22 --open -sT -T4 "$network" 2>/dev/null | grep "Nmap scan report" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -vw "$my_ip" | sort -u) || true
ssh_hosts=$(rustscan -a "$network" -p 22 -g 2>/dev/null | awk '{print $1}' | grep -vw "$my_ip" | sort -u) || true
if [[ -z $ssh_hosts ]]; then
die "No SSH-enabled devices found. Is the Pi connected and booted?"

View File

@ -14,8 +14,8 @@ CONFIG_FILE="${SCRIPT_DIR}/.raspberry_pi.conf"
# Load configuration from gitignored config file if it exists
if [[ -f $CONFIG_FILE ]]; then
# shellcheck source=/dev/null
source "$CONFIG_FILE"
# shellcheck source=/dev/null
source "$CONFIG_FILE"
fi
# Configuration - Customize these values (or set in config file)
@ -38,34 +38,34 @@ NC='\033[0m'
# All log functions output to stderr so they don't interfere with function return values
log_info() {
echo -e "${BLUE}[INFO]${NC} $1" >&2
echo -e "${BLUE}[INFO]${NC} $1" >&2
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1" >&2
echo -e "${GREEN}[SUCCESS]${NC} $1" >&2
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1" >&2
echo -e "${YELLOW}[WARNING]${NC} $1" >&2
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
echo -e "${RED}[ERROR]${NC} $1" >&2
}
die() {
log_error "$1"
exit 1
log_error "$1"
exit 1
}
check_root() {
if [[ $EUID -ne 0 ]]; then
die "This script must be run as root. Use: sudo $0"
fi
if [[ $EUID -ne 0 ]]; then
die "This script must be run as root. Use: sudo $0"
fi
}
save_config() {
cat > "$CONFIG_FILE" << EOF
cat >"$CONFIG_FILE" <<EOF
# Raspberry Pi Setup - Auto-generated config
# This file is gitignored and stores discovered settings
@ -81,22 +81,22 @@ PI_TIMEZONE="${PI_TIMEZONE}"
# Generated passwords (KEEP THIS FILE SECURE!)
PI_PASSWORD="${PI_PASSWORD}"
EOF
chmod 600 "$CONFIG_FILE"
log_info "Configuration saved to $CONFIG_FILE"
chmod 600 "$CONFIG_FILE"
log_info "Configuration saved to $CONFIG_FILE"
}
generate_password() {
local length="${1:-16}"
local chars
chars=$(dd if=/dev/urandom bs=256 count=1 2> /dev/null | tr -dc 'A-Za-z0-9!@#$%&*' | cut -c1-"$length")
echo "$chars"
local length="${1:-16}"
local chars
chars=$(dd if=/dev/urandom bs=256 count=1 2>/dev/null | tr -dc 'A-Za-z0-9!@#$%&*' | cut -c1-"$length")
echo "$chars"
}
auto_generate_pi_password() {
if [[ -z $PI_PASSWORD ]]; then
PI_PASSWORD=$(generate_password 16)
log_info "Auto-generated Pi password (will be saved to config file)"
fi
if [[ -z $PI_PASSWORD ]]; then
PI_PASSWORD=$(generate_password 16)
log_info "Auto-generated Pi password (will be saved to config file)"
fi
}
# =============================================================================
@ -104,31 +104,31 @@ auto_generate_pi_password() {
# =============================================================================
ensure_dependencies() {
local missing_packages=()
local missing_packages=()
if ! command -v nmap &> /dev/null; then
missing_packages+=("nmap")
fi
if ! command -v rustscan &>/dev/null; then
missing_packages+=("rustscan")
fi
if ! command -v sshpass &> /dev/null; then
missing_packages+=("sshpass")
fi
if ! command -v sshpass &>/dev/null; then
missing_packages+=("sshpass")
fi
if [[ ${#missing_packages[@]} -gt 0 ]]; then
log_info "Installing missing packages: ${missing_packages[*]}"
if [[ ${#missing_packages[@]} -gt 0 ]]; then
log_info "Installing missing packages: ${missing_packages[*]}"
if command -v pacman &> /dev/null; then
sudo pacman -S --noconfirm "${missing_packages[@]}"
elif command -v apt-get &> /dev/null; then
sudo apt-get update && sudo apt-get install -y "${missing_packages[@]}"
elif command -v dnf &> /dev/null; then
sudo dnf install -y "${missing_packages[@]}"
else
die "Could not detect package manager. Please install manually: ${missing_packages[*]}"
fi
if command -v pacman &>/dev/null; then
sudo pacman -S --noconfirm "${missing_packages[@]}"
elif command -v apt-get &>/dev/null; then
sudo apt-get update && sudo apt-get install -y "${missing_packages[@]}"
elif command -v dnf &>/dev/null; then
sudo dnf install -y "${missing_packages[@]}"
else
die "Could not detect package manager. Please install manually: ${missing_packages[*]}"
fi
log_success "Dependencies installed"
fi
log_success "Dependencies installed"
fi
}
# shellcheck source=lib/pi_flash_remote.sh
@ -138,29 +138,28 @@ source "$SCRIPT_DIR/lib/pi_flash_image.sh"
# shellcheck source=lib/pi_flash_exec.sh
source "$SCRIPT_DIR/lib/pi_flash_exec.sh"
main() {
local command="${1:-help}"
local command="${1:-help}"
case "$command" in
local)
phase_flash_local
;;
remote)
phase_flash_remote
;;
execute-remote)
phase_execute_remote "${2:-}"
;;
help | --help | -h)
show_help
;;
*)
log_error "Unknown command: $command"
show_help
exit 1
;;
esac
case "$command" in
local)
phase_flash_local
;;
remote)
phase_flash_remote
;;
execute-remote)
phase_execute_remote "${2:-}"
;;
help | --help | -h)
show_help
;;
*)
log_error "Unknown command: $command"
show_help
exit 1
;;
esac
}
main "$@"

View File

@ -169,6 +169,8 @@ if [ ! -d "$HOME/.config/mpv" ]; then
mkdir -p "$HOME/.config/mpv"
fi
cp mpv.conf "$HOME/.config/mpv/mpv.conf"
# rustscan must never call nmap (nonfree, blocked by the pacman wrapper)
cp rustscan.toml "$HOME/.rustscan.toml"
if [ ! -d "$HOME/.oh-my-zsh" ]; then
yes | sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

View File

@ -0,0 +1,4 @@
# RustScan config (copied to ~/.rustscan.toml by main.sh).
# "None" keeps rustscan from piping open ports into nmap (nonfree, blocked by
# the pacman wrapper; the /usr/local/bin/nmap shim would only exit 1).
scripts = "None"

View File

@ -0,0 +1,121 @@
#!/bin/bash
# Regression test: Raspberry Pi / laptop SSH discovery uses rustscan, never
# nmap (nonfree NPSL licence; the pacman wrapper refuses to install it).
#
# Runs the real discover/ensure_dependencies functions with stubbed
# ip/getent/sshpass/rustscan/nmap/pacman on PATH and asserts:
# - rustscan is called as `-a <net> -p 22 -g` and nmap is never executed
# - our own IP is excluded and the Pi (answering the hostname probe) is found
# - ensure_dependencies asks for rustscan, not nmap
# - no script in the set still invokes nmap
set -euo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
FEATURES="$(cd -- "$SCRIPT_DIR/.." && pwd)/features"
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT
STUBS="$TMP_DIR/stubs"
mkdir -p "$STUBS"
export STUB_LOG="$TMP_DIR/calls.log"
: >"$STUB_LOG"
stub() { # name; body on stdin
{
printf '#!/bin/bash\n'
cat
} >"$STUBS/$1"
chmod +x "$STUBS/$1"
}
stub ip <<'STUB'
if [[ $1 == -4 ]]; then
echo " inet 192.168.1.5/24 brd 192.168.1.255 scope global eth0"
else
echo "default via 192.168.1.1 dev eth0"
fi
STUB
stub getent <<'STUB'
exit 2
STUB
stub nmap <<'STUB'
echo "nmap $*" >>"$STUB_LOG"
exit 1
STUB
stub rustscan <<'STUB'
echo "rustscan $*" >>"$STUB_LOG"
printf '192.168.1.5 -> [22]\n192.168.1.43 -> [22]\n192.168.1.26 -> [22]\n'
STUB
stub pacman <<'STUB'
echo "pacman $*" >>"$STUB_LOG"
STUB
stub sudo <<'STUB'
"$@"
STUB
# sshpass -p PW ssh ... user@IP CMD : only .43 is the Pi.
stub sshpass <<'STUB'
for a in "$@"; do [[ $a == *@* ]] && ip=${a#*@}; done
echo "ssh $ip" >>"$STUB_LOG"
if [[ $ip == 192.168.1.43 ]]; then
case "${*: -1}" in hostname) echo nextcloud-pi ;; *) echo ok ;; esac
fi
STUB
log_info() { :; }
log_success() { :; }
die() {
printf 'die: %s\n' "$*" >&2
exit 1
}
export PI_HOSTNAME=nextcloud-pi PI_USER=pi PI_PASSWORD=pw REMOTE_LAPTOP_IP=192.168.1.17
# Runs discover_raspberry_pi from the given lib with every external stubbed.
check_discover() { # lib file
: >"$STUB_LOG"
(
PATH="$STUBS:$PATH"
# Real ensure_dependencies (no-op: rustscan/sshpass stubs are on PATH).
# shellcheck source=../features/lib/rpi_nc_deps.sh
source "$FEATURES/lib/rpi_nc_deps.sh"
# shellcheck source=/dev/null
source "$FEATURES/lib/$1"
found=$(discover_raspberry_pi)
[[ $found == 192.168.1.43 ]] || fail "$1 found '$found', want 192.168.1.43"
)
grep -qx 'rustscan -a 192.168.1.0/24 -p 22 -g' "$STUB_LOG" ||
fail "$1: rustscan not called with '-a 192.168.1.0/24 -p 22 -g': $(tr '\n' '|' <"$STUB_LOG")"
if grep -q '^nmap' "$STUB_LOG"; then fail "$1 ran nmap"; fi
if grep -qx 'ssh 192.168.1.5' "$STUB_LOG"; then fail "$1 tried our own IP"; fi
printf 'ok discover %s\n' "$1"
}
check_discover nc_discover.sh
check_discover rpi_nc_deps.sh
# --- ensure_dependencies asks for rustscan, not nmap ------------------------
: >"$STUB_LOG"
rm -f "$STUBS/rustscan" "$STUBS/sshpass" # hide them so the install branch runs
(
PATH="$STUBS"
hash -r
# shellcheck source=../features/lib/rpi_nc_deps.sh
source "$FEATURES/lib/rpi_nc_deps.sh"
ensure_dependencies >/dev/null 2>&1 || true
)
grep -q '^pacman -S --noconfirm rustscan' "$STUB_LOG" ||
fail "ensure_dependencies did not request rustscan: $(tr '\n' '|' <"$STUB_LOG")"
grep -E '^pacman .*nmap' "$STUB_LOG" && fail "ensure_dependencies still requests nmap"
printf 'ok rpi_nc_deps dependencies\n'
# --- no remaining nmap invocation in the set --------------------------------
for f in lib/nc_discover.sh lib/nc_remote.sh lib/rpi_nc_deps.sh lib/nc_flash.sh \
lib/nc_image.sh lib/pi_flash_remote.sh raspberry_pi_flash_sd.sh; do
if grep -nE '(^|[^[:alnum:]_-])nmap([^[:alnum:]_-]|$)' "$FEATURES/$f"; then
fail "$f still mentions nmap"
fi
done
printf 'ok no nmap references\n'