-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathserver-report
More file actions
executable file
·288 lines (244 loc) · 10.5 KB
/
Copy pathserver-report
File metadata and controls
executable file
·288 lines (244 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
#!/usr/bin/env bash
# server-report — Compact VPS health reports for humans and bots
# https://github.com/ranjith-src/vps-harden
# Usage: sudo server-report <summary|auth|audit|full>
set -euo pipefail
readonly VERSION="1.0.0"
usage() {
cat <<'EOF'
Usage: sudo server-report <command>
Commands:
summary Uptime, load, memory, disk, SSH attempts, services, updates
auth Failed/successful logins (48h), sessions, banned IPs
audit Audit events by key (ssh_config, user_db, sudoers, etc.)
full Full logwatch report (today, detail low)
version Print version and exit
All output is plain text (no colors, no control codes).
EOF
exit 0
}
# ── Helpers ──────────────────────────────────────────────────────────────────
cmd_exists() { command -v "$1" &>/dev/null; }
safe_run() {
# Run a command, return output or fallback string
local fallback="${1:-(unavailable)}"
shift
"$@" 2>/dev/null || echo "$fallback"
}
section() {
echo ""
echo "--- $1 ---"
}
# ── summary ──────────────────────────────────────────────────────────────────
cmd_summary() {
echo "=== Server Health Summary ==="
echo "Generated: $(date -Iseconds)"
section "System"
echo "Hostname: $(hostname)"
echo "Uptime: $(uptime -p 2>/dev/null || uptime | sed 's/.*up /up /' | sed 's/,.*load.*//')"
echo "Load avg: $(cut -d' ' -f1-3 /proc/loadavg 2>/dev/null || echo '(unavailable)')"
echo "Kernel: $(uname -r)"
section "Memory"
if cmd_exists free; then
free -h | awk '/^Mem:/ {printf "RAM: %s used / %s total (%s free)\n", $3, $2, $4}'
free -h | awk '/^Swap:/ {printf "Swap: %s used / %s total\n", $3, $2}'
else
echo "(free not available)"
fi
section "Disk"
df -h / | awk 'NR==2 {printf "Root (/): %s used / %s total (%s)\n", $3, $2, $5}'
section "SSH (last 48h)"
local failed=0 accepted=0
local cutoff
cutoff=$(date -d '48 hours ago' '+%b %e' 2>/dev/null || date -d '2 days ago' '+%b %e' 2>/dev/null || echo "")
if [[ -n "$cutoff" && -f /var/log/auth.log ]]; then
# Filter auth.log to roughly the last 48h by date prefix
local recent
recent=$(awk -v cutoff="$cutoff" 'BEGIN{found=0} $0 ~ cutoff {found=1} found{print}' /var/log/auth.log 2>/dev/null || true)
failed=$(echo "$recent" | grep -c "Failed password\|Failed publickey" || echo 0)
accepted=$(echo "$recent" | grep -c "Accepted" || echo 0)
elif cmd_exists journalctl; then
failed=$(journalctl -u ssh -u sshd --since "48h ago" --no-pager 2>/dev/null | grep -c "Failed password\|Failed publickey" || echo 0)
accepted=$(journalctl -u ssh -u sshd --since "48h ago" --no-pager 2>/dev/null | grep -c "Accepted" || echo 0)
elif [[ -f /var/log/auth.log ]]; then
# No date filtering available, scan whole file
failed=$(grep -c "Failed password\|Failed publickey" /var/log/auth.log 2>/dev/null || echo 0)
accepted=$(grep -c "Accepted" /var/log/auth.log 2>/dev/null || echo 0)
fi
echo "Failed: $failed attempts"
echo "Accepted: $accepted logins"
section "Fail2ban"
if cmd_exists fail2ban-client; then
local jails banned
jails=$(fail2ban-client status 2>/dev/null | grep "Jail list" | sed 's/.*:\s*//' | tr -d '\t' || echo "(none)")
echo "Jails: $jails"
if fail2ban-client status sshd &>/dev/null; then
banned=$(fail2ban-client status sshd 2>/dev/null | grep "Currently banned" | awk '{print $NF}')
echo "SSH bans: $banned currently banned"
fi
else
echo "(fail2ban not installed)"
fi
section "Sudo usage (last 48h)"
local sudo_count=0
if [[ -n "${cutoff:-}" && -f /var/log/auth.log ]]; then
sudo_count=$(awk -v cutoff="$cutoff" 'BEGIN{found=0} $0 ~ cutoff {found=1} found{print}' /var/log/auth.log 2>/dev/null | grep -c "sudo:" || echo 0)
elif cmd_exists journalctl; then
sudo_count=$(journalctl --since "48h ago" --no-pager 2>/dev/null | grep -c "sudo:" || echo 0)
elif [[ -f /var/log/auth.log ]]; then
sudo_count=$(grep -c "sudo:" /var/log/auth.log 2>/dev/null || echo 0)
fi
echo "Sudo events: $sudo_count"
section "Audit"
if cmd_exists auditctl; then
local rule_count
rule_count=$(auditctl -l 2>/dev/null | grep -cv "^No rules" || echo 0)
echo "Rules loaded: $rule_count"
if cmd_exists aureport; then
local auth_events
auth_events=$(aureport --auth --summary 2>/dev/null | tail -1 | awk '{print $1}' || echo "0")
echo "Auth events: $auth_events (total)"
fi
else
echo "(auditd not installed)"
fi
section "Services"
local services=(ssh sshd fail2ban ufw netbird auditd)
for svc in "${services[@]}"; do
if systemctl list-unit-files "${svc}.service" &>/dev/null 2>&1; then
local state
state=$(systemctl is-active "${svc}.service" 2>/dev/null || echo "unknown")
echo " ${svc}: ${state}"
fi
done
# Check openclaw gateway (user service)
local oc_state
oc_state=$(systemctl --user is-active openclaw-gateway.service 2>/dev/null || echo "not found")
echo " openclaw-gateway: ${oc_state}"
section "Updates"
if cmd_exists apt-get; then
local updates
updates=$(apt-get -s upgrade 2>/dev/null | grep -c "^Inst " || echo 0)
echo "Pending: $updates package(s)"
local security
security=$(apt-get -s upgrade 2>/dev/null | grep "^Inst " | grep -c "-security" || echo 0)
echo "Security: $security package(s)"
else
echo "(apt not available)"
fi
echo ""
echo "=== End Summary ==="
}
# ── auth ─────────────────────────────────────────────────────────────────────
cmd_auth() {
echo "=== Auth Report (last 48h) ==="
echo "Generated: $(date -Iseconds)"
section "Failed logins"
if [[ -f /var/log/auth.log ]]; then
grep "Failed password\|Failed publickey" /var/log/auth.log 2>/dev/null \
| awk '{print $1, $2, $3, $9, $11}' \
| sort | uniq -c | sort -rn \
| head -20 || echo "(none)"
elif cmd_exists journalctl; then
journalctl -u ssh -u sshd --since "48h ago" --no-pager 2>/dev/null \
| grep "Failed password\|Failed publickey" \
| awk '{print $1, $2, $3, $9, $11}' \
| sort | uniq -c | sort -rn \
| head -20 || echo "(none)"
else
echo "(no log source available)"
fi
section "Successful logins"
if [[ -f /var/log/auth.log ]]; then
grep "Accepted" /var/log/auth.log 2>/dev/null \
| awk '{print $1, $2, $3, $9, $11}' \
| tail -20 || echo "(none)"
elif cmd_exists journalctl; then
journalctl -u ssh -u sshd --since "48h ago" --no-pager 2>/dev/null \
| grep "Accepted" \
| awk '{print $1, $2, $3, $9, $11}' \
| tail -20 || echo "(none)"
else
echo "(no log source available)"
fi
section "Current sessions"
who 2>/dev/null || echo "(none)"
section "Banned IPs (fail2ban)"
if cmd_exists fail2ban-client; then
if fail2ban-client status sshd &>/dev/null; then
local banned_list
banned_list=$(fail2ban-client status sshd 2>/dev/null | grep "Banned IP" | sed 's/.*:\s*//')
if [[ -n "$banned_list" ]]; then
echo "$banned_list"
else
echo "(none currently banned)"
fi
else
echo "(sshd jail not active)"
fi
else
echo "(fail2ban not installed)"
fi
echo ""
echo "=== End Auth Report ==="
}
# ── audit ────────────────────────────────────────────────────────────────────
cmd_audit() {
echo "=== Audit Report ==="
echo "Generated: $(date -Iseconds)"
if ! cmd_exists aureport; then
echo "(auditd/aureport not installed — skipping)"
echo "=== End Audit Report ==="
return 0
fi
section "Events by key (last 24h)"
local keys=("ssh_config" "user_db" "sudoers_changes" "firewall_config" "cron_changes" "priv_esc")
for key in "${keys[@]}"; do
local count
count=$(ausearch -k "$key" --start today 2>/dev/null | grep -c "^type=" || echo 0)
printf " %-20s %s event(s)\n" "$key" "$count"
done
section "Authentication summary"
aureport --auth --summary 2>/dev/null | tail -5 || echo "(no data)"
section "Failed syscalls (last 24h)"
aureport --failed --summary 2>/dev/null | tail -10 || echo "(no data)"
section "Anomaly events"
aureport --anomaly --summary 2>/dev/null | tail -5 || echo "(no data)"
echo ""
echo "=== End Audit Report ==="
}
# ── full ─────────────────────────────────────────────────────────────────────
cmd_full() {
echo "=== Full Logwatch Report ==="
echo "Generated: $(date -Iseconds)"
if ! cmd_exists logwatch; then
echo "(logwatch not installed — falling back to summary)"
cmd_summary
return 0
fi
logwatch --detail low --range today --output stdout 2>/dev/null \
|| echo "(logwatch failed — check configuration)"
echo ""
echo "=== End Logwatch Report ==="
}
# ── Main ─────────────────────────────────────────────────────────────────────
main() {
if [[ $# -eq 0 ]]; then
usage
fi
local cmd="$1"
case "$cmd" in
summary) cmd_summary ;;
auth) cmd_auth ;;
audit) cmd_audit ;;
full) cmd_full ;;
version) echo "server-report v${VERSION}"; exit 0 ;;
-h|--help|help) usage ;;
*)
echo "Error: unknown command '$cmd'"
echo "Run 'server-report --help' for usage."
exit 1
;;
esac
}
main "$@"