前言
参考:
https://community.hetzner.com/tutorials/ssh-notification-with-ntfy
功能
登录通知:当用户通过SSH登录时,向Gotify发送通知,包含
- 登录用户
- IP
- 主机名
- 地理位置
- 时间
排除项:排除来自局域网和服务器自己公网ip的连接
通知示例:
用户 elvish 从 114.51.14.11 登录到 Ubuntu-24.04-server
时间: 2026-01-01 10:00:00 CST
🌍 位置: Shanghai, Shanghai, CN 🇨🇳
说明
- 地理位置服务使用ipinfo.io的API通过ip地址反推,有速率限制
- 脚本会在每次SSH登录时执行,包括非交互式登录(如SCP、SFTP等)
使用说明
1. 创建脚本:
sudo nano /usr/local/bin/ssh-login-notify.sh
粘贴以下内容:
#!/bin/bash
USER_NAME=$PAM_USER
HOST_NAME=$(hostname)
TIME_NOW=$(TZ=Asia/Shanghai date "+%Y-%m-%d %H:%M:%S %Z")
GOTIFY_URL="http://127.0.0.1:8118" # <- 更改此处的地址和token
GOTIFY_TOKEN="<GOTIFY_TOKEN>"
# 获取IP地址 - 修复SFTP连接显示问题
if [ -n "$SSH_CONNECTION" ]; then
IP_ADDRESS=$(echo $SSH_CONNECTION | awk '{print $1}')
else
# 回退到who命令,但过滤掉无效的输出
IP_ADDRESS=$(who | awk '{print $5}' | grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -n 1)
fi
[ -z "$IP_ADDRESS" ] && IP_ADDRESS="unknown"
# 获取服务器的公网IP地址
get_server_public_ip() {
# 尝试通过不同方式获取公网IP
local public_ip=""
# 方法1: 使用dig查询DNS
public_ip=$(dig +short myip.opendns.com @resolver1.opendns.com 2>/dev/null)
# 方法2: 使用curl查询外部服务
if [ -z "$public_ip" ]; then
public_ip=$(curl -s http://ifconfig.me 2>/dev/null)
fi
# 方法3: 使用hostname命令
if [ -z "$public_ip" ]; then
public_ip=$(hostname -I | awk '{print $1}' 2>/dev/null)
fi
echo "$public_ip"
}
# 检查是否为局域网、回环地址或服务器自身公网IP
is_local_or_self_ip() {
local ip=$1
# 回环地址
if [[ $ip == "127.0.0.1" || $ip == "::1" || $ip == "localhost" ]]; then
return 0
fi
# 私有IP地址范围
if [[ $ip =~ ^10\. ]] || \
[[ $ip =~ ^172\.(1[6-9]|2[0-9]|3[0-1])\. ]] || \
[[ $ip =~ ^192\.168\. ]] || \
[[ $ip =~ ^169\.254\. ]]; then
return 0
fi
# 获取服务器公网IP
local server_public_ip=$(get_server_public_ip)
# 检查是否为服务器自身公网IP
if [[ "$ip" == "$server_public_ip" ]]; then
return 0
fi
# 不是本地IP或自身公网IP
return 1
}
# 如果是局域网、回环地址或服务器自身公网IP,直接退出
if is_local_or_self_ip "$IP_ADDRESS"; then
exit 0
fi
# 如果是会话关闭,退出
if [[ $PAM_TYPE == 'close_session' ]]; then
exit 0
fi
# 获取地理位置信息(可选)
GEO_INFO=""
if [[ "$IP_ADDRESS" != "unknown" && "$IP_ADDRESS" != "127.0.0.1" && "$IP_ADDRESS" != ::1 ]]; then
GEO_JSON=$(curl -s "https://ipinfo.io/$IP_ADDRESS/json")
CITY=$(echo "$GEO_JSON" | jq -r '.city')
REGION=$(echo "$GEO_JSON" | jq -r '.region')
COUNTRY=$(echo "$GEO_JSON" | jq -r '.country')
ORG=$(echo "$GEO_JSON" | jq -r '.org')
# 将国家代码转换为国旗表情符号的函数
flag() {
local code="${1^^}"
local first=$(( $(printf "%d" "'${code:0:1}") - 65 + 0x1F1E6 ))
local second=$(( $(printf "%d" "'${code:1:1}") - 65 + 0x1F1E6 ))
printf '%b' "$(printf '\\U%08X\\U%08X' "$first" "$second")"
}
# 使用Unicode编码的表情符号
EARTH_ICON=$(printf '\U1F30D')
GEO_INFO="$EARTH_ICON 位置: $CITY, $REGION, $COUNTRY $(flag $COUNTRY)"
fi
# 构建消息内容
TITLE="SSH登录提醒 - $HOST_NAME"
MESSAGE="用户 $USER_NAME 从 $IP_ADDRESS 登录到 $HOST_NAME
时间: $TIME_NOW
$GEO_INFO"
# 发送到Gotify
if [ -n "$SSH_CONNECTION" ] || [ -n "$SFTP_CONNECTION" ]; then
curl -s -o /dev/null \
-X POST \
-F "title=${TITLE}" \
-F "message=${MESSAGE}" \
-F "priority=5" \
"${GOTIFY_URL}/message?token=${GOTIFY_TOKEN}"
fi
2. 设置执行权限:
CodeBlock Loading...
3. 安装依赖(如果尚未安装):
CodeBlock Loading...
4. 配置PAM:
编辑SSH的PAM配置文件:
CodeBlock Loading...
在文件末尾添加:
session optional pam_exec.so /usr/local/bin/ssh-login-notify.sh
5. 重启SSH服务:
CodeBlock Loading...