Initial skills documentation — 25 categories, all SKILL.md + references + scripts
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
# Comprehensive server audit script
|
||||
# Usage: ./audit-server.sh <host> [user]
|
||||
# Collects system info, Docker, services, ports, web servers, databases, packages, users, cron
|
||||
|
||||
HOST="$1"
|
||||
USER="${2:-root}"
|
||||
SSH_KEY="/root/.ssh/itpp-infra"
|
||||
SSH_CMD="ssh -i $SSH_KEY -o StrictHostKeyChecking=no -o ConnectTimeout=10 $USER@$HOST"
|
||||
|
||||
echo "=============================================="
|
||||
echo "AUDIT: $HOST (user: $USER)"
|
||||
echo "=============================================="
|
||||
|
||||
# 1. System info
|
||||
echo "--- SYSTEM INFO ---"
|
||||
$SSH_CMD "hostname; cat /etc/os-release 2>/dev/null | head -5; uname -a; uptime; echo 'CPU:'; nproc; lscpu 2>/dev/null | grep 'Model name' | head -1; echo 'RAM:'; free -h | head -2; echo 'DISK:'; df -h / | tail -1"
|
||||
|
||||
# 2. Docker containers
|
||||
echo "--- DOCKER CONTAINERS ---"
|
||||
$SSH_CMD "docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}' 2>/dev/null || echo 'Docker not available'"
|
||||
|
||||
echo "--- DOCKER IMAGES ---"
|
||||
$SSH_CMD "docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}' 2>/dev/null || echo 'Docker not available'"
|
||||
|
||||
echo "--- DOCKER COMPOSE ---"
|
||||
$SSH_CMD "ls /root/docker-compose*.yml /root/*/docker-compose*.yml /root/docker/*.yml /opt/*/docker-compose*.yml 2>/dev/null || echo 'No docker-compose files found in common locations'"
|
||||
|
||||
# 3. Systemd services
|
||||
echo "--- RUNNING SYSTEMD SERVICES ---"
|
||||
$SSH_CMD "systemctl list-units --type=service --state=running --no-pager --no-legend 2>/dev/null | head -40"
|
||||
|
||||
echo "--- CUSTOM SYSTEMD SERVICES ---"
|
||||
$SSH_CMD "ls /etc/systemd/system/*.service 2>/dev/null | grep -v -E '(systemd-|getty|serial-getty|dbus|sshd|cron|networking|ufw|ssh)' | head -30"
|
||||
|
||||
# 4. Listening ports
|
||||
echo "--- TCP LISTENING PORTS ---"
|
||||
$SSH_CMD "ss -tlnp 2>/dev/null | head -30"
|
||||
|
||||
echo "--- UDP LISTENING PORTS ---"
|
||||
$SSH_CMD "ss -ulnp 2>/dev/null | head -20"
|
||||
|
||||
# 5. Web servers
|
||||
echo "--- WEB SERVERS ---"
|
||||
$SSH_CMD "which nginx apache2 caddy 2>/dev/null; echo '---'; ls /etc/nginx/sites-enabled/ 2>/dev/null; ls /etc/apache2/sites-enabled/ 2>/dev/null; cat /etc/caddy/Caddyfile 2>/dev/null | head -30"
|
||||
|
||||
# 6. Databases
|
||||
echo "--- DATABASES ---"
|
||||
$SSH_CMD "which mysql mysqld psql postgres sqlite3 mongod redis-server 2>/dev/null; echo '---'; systemctl is-active mysql mariadb postgresql mongod redis 2>/dev/null"
|
||||
|
||||
# 7. Notable packages
|
||||
echo "--- NOTABLE PACKAGES ---"
|
||||
$SSH_CMD "dpkg -l 2>/dev/null | grep -iE 'docker|nginx|apache|mysql|postgres|redis|mongodb|node|python|php|java|go|rust|ruby|caddy|traccar|unifi' | head -30"
|
||||
|
||||
# 8. Users with home directories
|
||||
echo "--- HOME DIRECTORIES ---"
|
||||
$SSH_CMD "ls /home/ 2>/dev/null; echo '---'; cat /etc/passwd 2>/dev/null | grep -E '/home/|/root' | cut -d: -f1"
|
||||
|
||||
# 9. Custom scripts and cron
|
||||
echo "--- CRONTAB (root) ---"
|
||||
$SSH_CMD "crontab -l 2>/dev/null || echo 'No crontab for root'"
|
||||
|
||||
echo "--- CUSTOM SCRIPTS ---"
|
||||
$SSH_CMD "ls /root/*.sh /root/*.py 2>/dev/null || echo 'No scripts found in /root'"
|
||||
$SSH_CMD "ls /usr/local/bin/*.sh /usr/local/bin/*.py 2>/dev/null || echo 'No scripts found in /usr/local/bin'"
|
||||
|
||||
echo "=============================================="
|
||||
echo "END AUDIT: $HOST"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check IMAP inbox for bounce-back / delivery-failure emails and alert if found.
|
||||
|
||||
Silent (exit 0 with no output) when nothing to report.
|
||||
Outputs summary when bounces are found.
|
||||
"""
|
||||
|
||||
import imaplib
|
||||
import email
|
||||
from email.utils import parsedate_to_datetime
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
HOST = "mail.germainebrown.com"
|
||||
PORT = 993
|
||||
USER = "g@germainebrown.com"
|
||||
PW_FILE = "/root/.config/himalaya/g-germainebrown.pass"
|
||||
CHECK_HOURS = 24
|
||||
|
||||
BOUNCE_KEYWORDS = [
|
||||
"mail delivery failed", "delivery status notification", "undelivered",
|
||||
"returned mail", "mail delivery failure", "non-delivery",
|
||||
"delivery failed", "undeliverable", "delivery report",
|
||||
"returned to sender", "message bounced",
|
||||
]
|
||||
|
||||
BOUNCE_SENDERS = [
|
||||
"mailer-daemon", "postmaster", "mail delivery system",
|
||||
]
|
||||
|
||||
|
||||
def safe_str(val):
|
||||
if val is None:
|
||||
return ""
|
||||
if isinstance(val, str):
|
||||
return val
|
||||
try:
|
||||
return str(val)
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def is_bounce(msg):
|
||||
subject = safe_str(msg.get("Subject", "")).lower()
|
||||
sender = safe_str(msg.get("From", "")).lower()
|
||||
|
||||
for kw in BOUNCE_KEYWORDS:
|
||||
if kw in subject:
|
||||
return True
|
||||
|
||||
for s in BOUNCE_SENDERS:
|
||||
if s in sender:
|
||||
return True
|
||||
|
||||
if msg.get_content_type() == "text/plain":
|
||||
payload = msg.get_payload(decode=True)
|
||||
if payload:
|
||||
body = safe_str(payload).lower()
|
||||
if "permanent error" in body or "could not be delivered" in body:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
pw = open(PW_FILE).read().strip()
|
||||
except FileNotFoundError:
|
||||
print("[SILENT]")
|
||||
return
|
||||
|
||||
since = (datetime.now(timezone.utc) - timedelta(hours=CHECK_HOURS)).strftime("%d-%b-%Y")
|
||||
|
||||
try:
|
||||
conn = imaplib.IMAP4_SSL(HOST, PORT)
|
||||
conn.login(USER, pw)
|
||||
conn.select("INBOX")
|
||||
except Exception as e:
|
||||
print(f"ERROR: IMAP connection failed: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
status, data = conn.search(None, f'(SINCE {since})')
|
||||
if status != "OK":
|
||||
print("[SILENT]")
|
||||
conn.logout()
|
||||
return
|
||||
|
||||
bounces = []
|
||||
for num in data[0].split():
|
||||
status, msg_data = conn.fetch(num, "(RFC822)")
|
||||
if status != "OK":
|
||||
continue
|
||||
raw = email.message_from_bytes(msg_data[0][1])
|
||||
if is_bounce(raw):
|
||||
subject = raw.get("Subject", "(no subject)")
|
||||
sender = raw.get("From", "(unknown)")
|
||||
date = raw.get("Date", "")
|
||||
bounces.append({
|
||||
"subject": subject,
|
||||
"from": sender,
|
||||
"date": date,
|
||||
"uid": num.decode(),
|
||||
})
|
||||
|
||||
conn.logout()
|
||||
|
||||
if not bounces:
|
||||
return
|
||||
|
||||
print(f"📬 {len(bounces)} bounce-back(s) detected in the last {CHECK_HOURS}h:\n")
|
||||
for b in bounces:
|
||||
print(f" • {b['subject']}")
|
||||
print(f" From: {b['from']}")
|
||||
print(f" Date: {b['date']}")
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user