A few stability improvements. Added Fenrir ssh announcement script.
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
AGENTS.md
|
||||||
|
CLAUDE.md
|
||||||
|
*.qcow2
|
||||||
|
*.img
|
||||||
|
*.sha1sum
|
||||||
|
*.xz
|
||||||
|
*.zst
|
||||||
@@ -25,16 +25,24 @@ set -e # Don't want to destroy stuff if this goes majorly wrong.
|
|||||||
trap cleanup EXIT # make sure the script cleans up after itself before closing.
|
trap cleanup EXIT # make sure the script cleans up after itself before closing.
|
||||||
|
|
||||||
|
|
||||||
|
# shellcheck disable=SC2329 # cleanup is invoked via trap EXIT
|
||||||
cleanup() {
|
cleanup() {
|
||||||
|
status=$? # capture original exit status so failures propagate
|
||||||
|
|
||||||
if [[ $mounted -eq 0 ]]; then
|
if [[ $mounted -eq 0 ]]; then
|
||||||
umount -R /mnt
|
umount -R /mnt || true
|
||||||
partx -d "${loopdev}"
|
|
||||||
losetup --detach "${loopdev}"
|
|
||||||
fi
|
fi
|
||||||
if [[ -n "${imageFileName}" ]]; then
|
|
||||||
|
if [[ -n "${loopdev:-}" ]]; then
|
||||||
|
partx -d "${loopdev}" || true
|
||||||
|
losetup --detach "${loopdev}" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${imageFileName:-}" ]]; then
|
||||||
rm "${imageFileName}"
|
rm "${imageFileName}"
|
||||||
fi
|
fi
|
||||||
exit 0
|
|
||||||
|
exit "$status"
|
||||||
}
|
}
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
@@ -122,7 +130,7 @@ imageUrl="http://os.archlinuxarm.org/os/ArchLinuxARM-rpi-aarch64-latest.tar.gz"
|
|||||||
|
|
||||||
fallocate -l "$imageSize" "$imageName"
|
fallocate -l "$imageSize" "$imageName"
|
||||||
loopdev="$(losetup --find --show "${imageName}")"
|
loopdev="$(losetup --find --show "${imageName}")"
|
||||||
parted --script "${loopdev}" mklabel msdos mkpart primary fat32 0% 200M mkpart primary ext4 200M 100%
|
parted --script "${loopdev}" mklabel msdos mkpart primary fat32 0% 512M mkpart primary ext4 512M 100%
|
||||||
mkfs.vfat -F32 -n STRMX_BOOT "${loopdev}p1"
|
mkfs.vfat -F32 -n STRMX_BOOT "${loopdev}p1"
|
||||||
mkfs.ext4 -F -L STRMX_ROOT "${loopdev}p2"
|
mkfs.ext4 -F -L STRMX_ROOT "${loopdev}p2"
|
||||||
mount "${loopdev}p2" /mnt
|
mount "${loopdev}p2" /mnt
|
||||||
@@ -130,7 +138,7 @@ mkdir /mnt/boot
|
|||||||
mount "${loopdev}p1" /mnt/boot
|
mount "${loopdev}p1" /mnt/boot
|
||||||
# Things are mounted now, so set mounted to 0 (bash true)
|
# Things are mounted now, so set mounted to 0 (bash true)
|
||||||
mounted=0
|
mounted=0
|
||||||
imageFileName=$(mktemp)
|
imageFileName=$(mktemp -p . ArchLinuxARM-rpi-aarch64-XXXXXX.tar.gz)
|
||||||
wget "${imageUrl}" -O "${imageFileName}"
|
wget "${imageUrl}" -O "${imageFileName}"
|
||||||
bsdtar -xpf "${imageFileName}" -C /mnt
|
bsdtar -xpf "${imageFileName}" -C /mnt
|
||||||
|
|
||||||
@@ -143,6 +151,7 @@ find ../files/etc -mindepth 1 -maxdepth 1 ! -name skel -exec cp -rv {} /mnt/etc/
|
|||||||
|
|
||||||
PS1="(Chroot) [\u@\h \W] \$" arch-chroot /mnt << EOF
|
PS1="(Chroot) [\u@\h \W] \$" arch-chroot /mnt << EOF
|
||||||
echo "Chroot started."
|
echo "Chroot started."
|
||||||
|
set -euo pipefail
|
||||||
# set up pacman
|
# set up pacman
|
||||||
pacman-key --init
|
pacman-key --init
|
||||||
pacman-key --populate archlinuxarm
|
pacman-key --populate archlinuxarm
|
||||||
@@ -301,6 +310,7 @@ services=(
|
|||||||
log-to-ram-sync.timer
|
log-to-ram-sync.timer
|
||||||
log-to-ram-shutdown.service
|
log-to-ram-shutdown.service
|
||||||
NetworkManager.service
|
NetworkManager.service
|
||||||
|
ssh-login-monitor.service
|
||||||
)
|
)
|
||||||
|
|
||||||
for service in "\${services[@]}"; do
|
for service in "\${services[@]}"; do
|
||||||
@@ -311,8 +321,7 @@ for service in "\${services[@]}"; do
|
|||||||
echo " \$service: FAILED"
|
echo " \$service: FAILED"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
# Cleanup packages
|
|
||||||
pacman -Runcds libx11 --noconfirm
|
|
||||||
pacman -Sc --noconfirm
|
pacman -Sc --noconfirm
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
@@ -320,5 +329,8 @@ EOF
|
|||||||
# Copy skel files to stormux user home (after user rename in chroot)
|
# Copy skel files to stormux user home (after user rename in chroot)
|
||||||
find ../files/etc/skel/ -mindepth 1 -exec cp -rv "{}" /mnt/home/stormux/ \;
|
find ../files/etc/skel/ -mindepth 1 -exec cp -rv "{}" /mnt/home/stormux/ \;
|
||||||
|
|
||||||
|
# Copy boot files again to ensure custom config overrides any package changes
|
||||||
|
cp -rv ../files/boot/* /mnt/boot
|
||||||
|
|
||||||
# Exiting calls the cleanup function to unmount.
|
# Exiting calls the cleanup function to unmount.
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
17
pi4/files/etc/systemd/system/ssh-login-monitor.service
Normal file
17
pi4/files/etc/systemd/system/ssh-login-monitor.service
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Fenrir SSH Login Monitor
|
||||||
|
After=sshd.service
|
||||||
|
Wants=sshd.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/share/fenrirscreenreader/scripts/ssh-login-monitor.sh
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
# Security settings
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=false
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
185
pi4/files/usr/share/fenrirscreenreader/scripts/ssh-login-monitor.sh
Executable file
185
pi4/files/usr/share/fenrirscreenreader/scripts/ssh-login-monitor.sh
Executable file
@@ -0,0 +1,185 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# SSH Login Monitor for Fenrir Screen Reader
|
||||||
|
# Monitors SSH logins and announces them via Fenrir's speech system
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
fenrirSocket="/tmp/fenrirscreenreader-deamon.sock"
|
||||||
|
logFile="/var/log/auth.log"
|
||||||
|
stateFile="/tmp/fenrir-ssh-monitor.state"
|
||||||
|
checkInterval=2 # seconds between checks
|
||||||
|
|
||||||
|
# Voice settings
|
||||||
|
announceUser=true
|
||||||
|
announceIp=true
|
||||||
|
announceHostname=true
|
||||||
|
announceLogout=false # Announce SSH disconnections (disabled by default - may not work reliably on all systems)
|
||||||
|
|
||||||
|
# Function to send message to Fenrir
|
||||||
|
fenrirSay() {
|
||||||
|
local message="$1"
|
||||||
|
# Only announce if Fenrir socket exists (silently skip if not)
|
||||||
|
if [[ -S "$fenrirSocket" ]]; then
|
||||||
|
echo "command say ${message}" | socat - UNIX-CLIENT:"${fenrirSocket}" 2>/dev/null
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to get last processed line number
|
||||||
|
getLastLine() {
|
||||||
|
if [[ -f "$stateFile" ]]; then
|
||||||
|
cat "$stateFile"
|
||||||
|
else
|
||||||
|
echo "0"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to save last processed line number
|
||||||
|
saveLastLine() {
|
||||||
|
echo "$1" > "$stateFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to parse SSH login and announce
|
||||||
|
processLogin() {
|
||||||
|
local logLine="$1"
|
||||||
|
local user=""
|
||||||
|
local ip=""
|
||||||
|
local hostname=""
|
||||||
|
|
||||||
|
# Parse different SSH login patterns
|
||||||
|
# Pattern 1: "Accepted publickey for USER from IP"
|
||||||
|
# Pattern 2: "Accepted password for USER from IP"
|
||||||
|
if [[ "$logLine" =~ Accepted\ (publickey|password|keyboard-interactive/pam)\ for\ ([^[:space:]]+)\ from\ ([^[:space:]]+) ]]; then
|
||||||
|
user="${BASH_REMATCH[2]}"
|
||||||
|
ip="${BASH_REMATCH[3]}"
|
||||||
|
|
||||||
|
# Try to resolve hostname
|
||||||
|
if command -v host &> /dev/null && [[ "$announceHostname" == "true" ]]; then
|
||||||
|
hostname="$(host "$ip" 2>/dev/null | grep -oP 'domain name pointer \K[^.]+' | head -1)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build announcement message (concise format)
|
||||||
|
local message=""
|
||||||
|
|
||||||
|
if [[ "$announceUser" == "true" ]]; then
|
||||||
|
message+="${user} "
|
||||||
|
fi
|
||||||
|
|
||||||
|
message+="S S H login"
|
||||||
|
|
||||||
|
if [[ "$announceIp" == "true" ]]; then
|
||||||
|
message+=" from ${ip}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$hostname" ]] && [[ "$announceHostname" == "true" ]]; then
|
||||||
|
message+=" ${hostname}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fenrirSay "$message"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to parse SSH logout and announce
|
||||||
|
processLogout() {
|
||||||
|
local logLine="$1"
|
||||||
|
local user=""
|
||||||
|
|
||||||
|
# Parse SSH disconnect patterns
|
||||||
|
# Pattern: "pam_unix(sshd:session): session closed for user USER"
|
||||||
|
if [[ "$logLine" =~ session\ closed\ for\ user\ ([^[:space:]]+) ]]; then
|
||||||
|
user="${BASH_REMATCH[1]}"
|
||||||
|
|
||||||
|
if [[ "$announceLogout" == "true" ]]; then
|
||||||
|
local message=""
|
||||||
|
|
||||||
|
if [[ "$announceUser" == "true" ]]; then
|
||||||
|
message+="${user} "
|
||||||
|
fi
|
||||||
|
|
||||||
|
message+="disconnected from S S H"
|
||||||
|
|
||||||
|
fenrirSay "$message"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to monitor auth.log
|
||||||
|
monitorAuthLog() {
|
||||||
|
local lastLine
|
||||||
|
lastLine=$(getLastLine)
|
||||||
|
|
||||||
|
# Get total lines in log
|
||||||
|
local totalLines
|
||||||
|
totalLines=$(wc -l < "$logFile" 2>/dev/null || echo "0")
|
||||||
|
|
||||||
|
# If log was rotated, reset
|
||||||
|
if [[ $totalLines -lt $lastLine ]]; then
|
||||||
|
lastLine=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Process new lines
|
||||||
|
if [[ $totalLines -gt $lastLine ]]; then
|
||||||
|
local newLines=$((totalLines - lastLine))
|
||||||
|
|
||||||
|
# Read only new lines
|
||||||
|
tail -n "$newLines" "$logFile" 2>/dev/null | while IFS= read -r line; do
|
||||||
|
if [[ "$line" =~ sshd.*Accepted ]]; then
|
||||||
|
processLogin "$line"
|
||||||
|
elif [[ "$line" =~ sshd.*session\ closed ]]; then
|
||||||
|
processLogout "$line"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
saveLastLine "$totalLines"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to monitor journalctl (alternative for systemd systems)
|
||||||
|
monitorJournalctl() {
|
||||||
|
# Follow journalctl for SSH logins and logouts
|
||||||
|
journalctl -u sshd -u ssh -f -n 0 --no-pager 2>/dev/null | while IFS= read -r line; do
|
||||||
|
if [[ "$line" =~ Accepted ]]; then
|
||||||
|
processLogin "$line"
|
||||||
|
elif [[ "$line" =~ session\ closed ]]; then
|
||||||
|
processLogout "$line"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [[ "$(id -u)" -ne 0 ]]; then
|
||||||
|
echo "Error: This script must be run with sudo privileges to access system logs."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Note: We don't require Fenrir to be running at startup
|
||||||
|
# The script will silently skip announcements when Fenrir socket doesn't exist
|
||||||
|
|
||||||
|
# Determine monitoring method
|
||||||
|
if command -v journalctl &> /dev/null && systemctl is-active --quiet sshd 2>/dev/null; then
|
||||||
|
echo "Starting SSH login monitor (using journalctl)..."
|
||||||
|
fenrirSay "SSH login monitor started."
|
||||||
|
|
||||||
|
# Use journalctl for real-time monitoring
|
||||||
|
trap 'fenrirSay "SSH login monitor stopped."; exit 0' INT TERM
|
||||||
|
monitorJournalctl
|
||||||
|
elif [[ -f "$logFile" ]]; then
|
||||||
|
echo "Starting SSH login monitor (using auth.log)..."
|
||||||
|
fenrirSay "SSH login monitor started."
|
||||||
|
|
||||||
|
# Use auth.log polling
|
||||||
|
trap 'fenrirSay "SSH login monitor stopped."; rm -f "$stateFile"; exit 0' INT TERM
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
monitorAuthLog
|
||||||
|
sleep "$checkInterval"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "Error: Cannot find SSH logs. Neither journalctl nor ${logFile} is available."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user