Files
blackroad/scripts/network-welcome.sh
Alexa Amundson 558634c0e6
Some checks failed
Lint & Format / detect (push) Has been cancelled
Lint & Format / js-lint (push) Has been cancelled
Lint & Format / py-lint (push) Has been cancelled
Lint & Format / sh-lint (push) Has been cancelled
Lint & Format / go-lint (push) Has been cancelled
Monorepo Lint / lint-shell (push) Has been cancelled
Monorepo Lint / lint-js (push) Has been cancelled
sync: 2026-03-16 22:00 — 21 files from Alexandria
RoadChain-SHA2048: f31122b68d27a309
RoadChain-Identity: alexa@sovereign
RoadChain-Full: f31122b68d27a30949e6be04538b248fc34fc9a056bbb0cce1a6d2bcd333a83956b6c6bf6c4771ca9bb6fb6a284c367ebf6eee3d2c1d97ab6d2d5913fa4ae58c85045eabcef75c88329792905fa71b79e4f7b0d79616f32e99a806df1b0d1ad1e4abc1fb3ae950e91f79f029e0f17ed3463e5e5f05a7c81585955c3c8b8b50f8d10007d33237e1e87a601333aa33f6b48e14a6d1f78c40e178e7e3050b609668d2e323ee30df27dd63f3267dc46b08df2348aa4e8b64de024ff350c5191b04a15f588a43e0f1b6d97ef309ea6dc68e8e138a7060faff35fd3f1b38bcb702e49bea951f4e792cb4d2b7dd2a314b5eb72c4d350ceb9b29a2c9436e34192aee0e43
2026-03-16 22:00:02 -05:00

203 lines
7.0 KiB
Bash
Executable File

#!/bin/bash
# BlackRoad Network Welcome Center
# Watches for new devices joining the network and welcomes them to Slack
# Cron: */5 * * * * /Users/alexa/network-welcome.sh
set -e
KNOWN_DEVICES_FILE="$HOME/.blackroad/network/known-devices.json"
SLACK_WEBHOOK_URL="https://blackroad-slack.amundsonalexa.workers.dev/post"
SLACK_ASK_URL="https://blackroad-slack.amundsonalexa.workers.dev/ask"
LOG_FILE="$HOME/.blackroad/network/welcome.log"
mkdir -p "$(dirname "$KNOWN_DEVICES_FILE")"
# Initialize known devices file if missing
if [ ! -f "$KNOWN_DEVICES_FILE" ]; then
cat > "$KNOWN_DEVICES_FILE" << 'EOF'
{
"devices": {
"192.168.4.1": { "name": "Eero", "emoji": "📡", "type": "router", "welcomed": true },
"192.168.4.22": { "name": "Spark", "emoji": "⚡", "type": "iot", "welcomed": true },
"192.168.4.26": { "name": "BigScreen", "emoji": "📺", "type": "roku", "welcomed": true },
"192.168.4.27": { "name": "AppleTV", "emoji": "🍎", "type": "appletv", "welcomed": true },
"192.168.4.28": { "name": "Alexandria", "emoji": "📚", "type": "mac", "welcomed": true },
"192.168.4.33": { "name": "Streamer", "emoji": "🎬", "type": "roku", "welcomed": true },
"192.168.4.38": { "name": "Lucidia", "emoji": "💡", "type": "pi", "welcomed": true },
"192.168.4.44": { "name": "Pixel", "emoji": "🟢", "type": "iot", "welcomed": true },
"192.168.4.45": { "name": "Morse", "emoji": "📟", "type": "iot", "welcomed": true },
"192.168.4.49": { "name": "Alice", "emoji": "🌐", "type": "pi", "welcomed": true },
"192.168.4.96": { "name": "Cecilia", "emoji": "🧠", "type": "pi", "welcomed": true },
"192.168.4.98": { "name": "Aria", "emoji": "🎵", "type": "pi", "welcomed": true },
"192.168.4.101": { "name": "Octavia", "emoji": "🐙", "type": "pi", "welcomed": true }
},
"last_scan": ""
}
EOF
fi
# Scan the network
ALIVE_IPS=$(nmap -sn 192.168.4.0/24 2>/dev/null | grep "Nmap scan report" | awk '{print $5}' | sort)
NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
for ip in $ALIVE_IPS; do
# Check if this IP is known
IS_KNOWN=$(python3 -c "
import json,sys
with open('$KNOWN_DEVICES_FILE') as f:
d = json.load(f)
print('yes' if '$ip' in d.get('devices',{}) else 'no')
" 2>/dev/null)
if [ "$IS_KNOWN" = "no" ]; then
# NEW DEVICE DETECTED!
MAC=$(arp -a | grep "($ip)" | awk '{print $4}' | head -1)
MAC=${MAC:-"unknown"}
# Try to identify vendor
MAC_PREFIX=$(echo "$MAC" | cut -d: -f1-3)
VENDOR=$(curl -s "https://api.macvendors.com/$MAC_PREFIX" 2>/dev/null | head -1)
# Clean up vendor response
echo "$VENDOR" | grep -q "errors" && VENDOR="Unknown"
[ -z "$VENDOR" ] && VENDOR="Unknown"
# Check for open ports (quick)
PORTS=""
for port in 80 443 8060 22 5000 7000; do
(echo >/dev/tcp/$ip/$port) 2>/dev/null && PORTS="$PORTS $port"
done
# Detect device type
DEVICE_TYPE="unknown"
EMOJI="🆕"
SUGGESTED_NAME=""
# Roku?
ROKU_INFO=$(curl -s "http://$ip:8060/query/device-info" -m 2 2>/dev/null)
if echo "$ROKU_INFO" | grep -q "Roku"; then
DEVICE_TYPE="roku"
EMOJI="📺"
ROKU_MODEL=$(echo "$ROKU_INFO" | grep -o '<model-name>[^<]*' | sed 's/<model-name>//')
SUGGESTED_NAME="Roku-${ip##*.}"
fi
# Apple device?
if echo "$VENDOR" | grep -qi "apple"; then
DEVICE_TYPE="apple"
EMOJI="🍎"
SUGGESTED_NAME="Apple-${ip##*.}"
fi
# Raspberry Pi?
if echo "$VENDOR" | grep -qi "raspberry"; then
DEVICE_TYPE="pi"
EMOJI="🍓"
SUGGESTED_NAME="Pi-${ip##*.}"
fi
# Random MAC = phone/tablet
FIRST_BYTE=$(echo "$MAC" | cut -d: -f1)
IS_RANDOM=$(python3 -c "print('yes' if int('$FIRST_BYTE', 16) & 0x02 else 'no')" 2>/dev/null)
if [ "$IS_RANDOM" = "yes" ]; then
DEVICE_TYPE="mobile"
EMOJI="📱"
SUGGESTED_NAME="Mobile-${ip##*.}"
fi
# IoT (no ports, private MAC)
if [ -z "$PORTS" ] && [ "$DEVICE_TYPE" = "unknown" ]; then
DEVICE_TYPE="iot"
EMOJI="🔌"
SUGGESTED_NAME="IoT-${ip##*.}"
fi
[ -z "$SUGGESTED_NAME" ] && SUGGESTED_NAME="Device-${ip##*.}"
# Welcome message to Slack!
WELCOME_MSG="🎉 *NEW DEVICE DETECTED!*
${EMOJI} *${SUGGESTED_NAME}* just joined the network!
IP: \`$ip\`
MAC: \`$MAC\`
Vendor: $VENDOR
Type: $DEVICE_TYPE
Open ports:${PORTS:-" none"}
First seen: $NOW
_Welcome to the BlackRoad, friend. You're home now._"
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{\"text\": $(echo "$WELCOME_MSG" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')}" \
>/dev/null 2>&1
# Have Hestia (hearth keeper) welcome them
curl -s -X POST "$SLACK_ASK_URL" \
-H "Content-Type: application/json" \
-d "{\"agent\": \"hestia\", \"message\": \"A new device just joined our home network: ${SUGGESTED_NAME} at ${ip} (${VENDOR}, type: ${DEVICE_TYPE}). Welcome them warmly in one sentence.\", \"slack\": true}" \
>/dev/null 2>&1
# Add to known devices
python3 -c "
import json
with open('$KNOWN_DEVICES_FILE', 'r') as f:
data = json.load(f)
data['devices']['$ip'] = {
'name': '$SUGGESTED_NAME',
'emoji': '$EMOJI',
'type': '$DEVICE_TYPE',
'mac': '$MAC',
'vendor': '$VENDOR',
'ports': '${PORTS}'.strip(),
'first_seen': '$NOW',
'welcomed': True
}
data['last_scan'] = '$NOW'
with open('$KNOWN_DEVICES_FILE', 'w') as f:
json.dump(data, f, indent=2)
" 2>/dev/null
echo "[$NOW] WELCOMED: $ip ($SUGGESTED_NAME, $VENDOR, $DEVICE_TYPE)" >> "$LOG_FILE"
fi
done
# Check for devices that went away (were known, now offline)
python3 << PYEOF
import json, subprocess, sys
with open("$KNOWN_DEVICES_FILE") as f:
data = json.load(f)
alive_set = set("$ALIVE_IPS".split())
gone = []
back = []
for ip, info in data["devices"].items():
was_online = info.get("online", True)
is_online = ip in alive_set
if was_online and not is_online and info.get("type") not in ("mobile",):
gone.append(f"{info.get('emoji','?')} *{info['name']}* ({ip})")
data["devices"][ip]["online"] = False
elif not was_online and is_online:
back.append(f"{info.get('emoji','?')} *{info['name']}* ({ip})")
data["devices"][ip]["online"] = True
with open("$KNOWN_DEVICES_FILE", "w") as f:
json.dump(data, f, indent=2)
# Report comings and goings (only if something changed)
if gone:
msg = "👋 *Devices left the network:*\\n" + "\\n".join(f" {g}" for g in gone)
subprocess.run(["curl", "-s", "-X", "POST", "$SLACK_WEBHOOK_URL",
"-H", "Content-Type: application/json",
"-d", json.dumps({"text": msg})], capture_output=True)
if back:
msg = "🏠 *Welcome back!*\\n" + "\\n".join(f" {b}" for b in back)
subprocess.run(["curl", "-s", "-X", "POST", "$SLACK_WEBHOOK_URL",
"-H", "Content-Type: application/json",
"-d", json.dumps({"text": msg})], capture_output=True)
PYEOF