diff --git a/dotfiles/brew-casks.txt b/dotfiles/brew-casks.txt index 23fa36e..ac0bd2a 100644 --- a/dotfiles/brew-casks.txt +++ b/dotfiles/brew-casks.txt @@ -34,6 +34,7 @@ openra openscad salesforce-cli sidequest +slack-cli tailscale-app temurin tiled diff --git a/fleet/alice/autonomy-log.txt b/fleet/alice/autonomy-log.txt index bbc2aec..2370ee3 100644 --- a/fleet/alice/autonomy-log.txt +++ b/fleet/alice/autonomy-log.txt @@ -1,50 +1,50 @@ -[2026-03-16 21:10:03] [FLEET] [alice] cecilia: DOWN (no ping response) -[2026-03-16 21:10:07] [FLEET] [alice] gematria: UP temp=C mem=6287MB disk=68% -[2026-03-16 21:10:42] [FLEET] [alice] lucidia: UP temp=51C mem=3232MB disk=34% -[2026-03-16 21:10:44] [FLEET] [alice] aria: DOWN (no ping response) -[2026-03-16 21:10:45] [FLEET] [alice] anastasia: UP temp=C mem=335MB disk=69% -[2026-03-16 21:11:01] [BEAT] [alice] load=0.81 mem=3264/3794MB temp=32.1C disk=84% -[2026-03-16 21:12:01] [BEAT] [alice] load=1.39 mem=3264/3794MB temp=33.6C disk=84% -[2026-03-16 21:13:01] [BEAT] [alice] load=0.67 mem=3264/3794MB temp=32.1C disk=84% -[2026-03-16 21:13:35] [BEAT] [alice] load=1.12 mem=3257/3794MB temp=35.0C disk=84% -[2026-03-16 21:13:35] [BEAT] [alice] load=1.12 mem=3254/3794MB temp=35.0C disk=84% -[2026-03-16 21:14:02] [BEAT] [alice] load=1.31 mem=3262/3794MB temp=32.1C disk=84% -[2026-03-16 21:15:05] [HEAL] [alice] Service blackroad-agent is DOWN — restarting -[2026-03-16 21:15:09] [BEAT] [alice] load=5.65 mem=3258/3794MB temp=34.1C disk=84% -[2026-03-16 21:15:09] [HEAL] [alice] Service blackroad-agent restarted successfully -[2026-03-16 21:15:09] [HEAL] [alice] Healed 1 services -[2026-03-16 21:15:55] [DIAL] [alice] Switchboard unreachable -[2026-03-16 21:16:01] [BEAT] [alice] load=3.12 mem=3264/3794MB temp=33.1C disk=84% -[2026-03-16 21:16:48] [DIAL] [alice] Switchboard unreachable -[2026-03-16 21:17:01] [BEAT] [alice] load=1.18 mem=3263/3794MB temp=34.1C disk=84% -[2026-03-16 21:18:01] [BEAT] [alice] load=0.96 mem=3263/3794MB temp=33.1C disk=84% -[2026-03-16 21:18:43] [BEAT] [alice] load=0.65 mem=3261/3794MB temp=33.1C disk=84% -[2026-03-16 21:18:43] [BEAT] [alice] load=0.65 mem=3260/3794MB temp=32.6C disk=84% -[2026-03-16 21:19:01] [BEAT] [alice] load=0.85 mem=3251/3794MB temp=34.6C disk=84% -[2026-03-16 21:20:01] [FLEET] [alice] Starting cross-node health check -[2026-03-16 21:20:01] [HEAL] [alice] Service blackroad-agent is DOWN — restarting -[2026-03-16 21:20:01] [BEAT] [alice] load=1.07 mem=3258/3794MB temp=35.5C disk=84% -[2026-03-16 21:20:01] [HEAL] [alice] Service blackroad-agent restarted successfully -[2026-03-16 21:20:02] [FLEET] [alice] octavia: UP temp=36C mem=6359MB disk=68% -[2026-03-16 21:20:02] [HEAL] [alice] Healed 1 services -[2026-03-16 21:20:04] [FLEET] [alice] cecilia: DOWN (no ping response) -[2026-03-16 21:20:07] [FLEET] [alice] gematria: UP temp=C mem=6282MB disk=68% -[2026-03-16 21:20:08] [FLEET] [alice] lucidia: UP temp=53C mem=3200MB disk=34% -[2026-03-16 21:20:10] [FLEET] [alice] aria: DOWN (no ping response) -[2026-03-16 21:20:11] [FLEET] [alice] anastasia: UP temp=C mem=208MB disk=69% -[2026-03-16 21:21:01] [BEAT] [alice] load=0.51 mem=3260/3794MB temp=33.1C disk=84% -[2026-03-16 21:22:01] [BEAT] [alice] load=0.62 mem=3261/3794MB temp=32.6C disk=84% -[2026-03-16 21:23:01] [BEAT] [alice] load=0.91 mem=3261/3794MB temp=35.0C disk=84% -[2026-03-16 21:23:52] [BEAT] [alice] load=0.81 mem=3262/3794MB temp=32.6C disk=84% -[2026-03-16 21:23:52] [BEAT] [alice] load=0.81 mem=3262/3794MB temp=33.1C disk=84% -[2026-03-16 21:24:01] [BEAT] [alice] load=0.76 mem=3263/3794MB temp=34.1C disk=84% -[2026-03-16 21:25:02] [HEAL] [alice] Service blackroad-agent is DOWN — restarting -[2026-03-16 21:25:02] [BEAT] [alice] load=1.00 mem=3256/3794MB temp=34.6C disk=84% -[2026-03-16 21:25:02] [HEAL] [alice] Service blackroad-agent restarted successfully -[2026-03-16 21:25:02] [HEAL] [alice] Healed 1 services -[2026-03-16 21:26:01] [BEAT] [alice] load=0.66 mem=3248/3794MB temp=34.1C disk=84% -[2026-03-16 21:27:02] [BEAT] [alice] load=0.79 mem=3259/3794MB temp=35.0C disk=84% -[2026-03-16 21:28:01] [BEAT] [alice] load=0.64 mem=3262/3794MB temp=33.6C disk=84% -[2026-03-16 21:29:01] [BEAT] [alice] load=0.64 mem=3259/3794MB temp=36.0C disk=84% -[2026-03-16 21:29:01] [BEAT] [alice] load=0.64 mem=3259/3794MB temp=36.0C disk=84% -[2026-03-16 21:29:01] [BEAT] [alice] load=0.64 mem=3258/3794MB temp=36.0C disk=84% +[2026-03-16 21:40:04] [FLEET] [alice] cecilia: DOWN (no ping response) +[2026-03-16 21:40:05] [FLEET] [alice] gematria: UP temp=C mem=6273MB disk=68% +[2026-03-16 21:40:06] [FLEET] [alice] lucidia: UP temp=55C mem=3209MB disk=34% +[2026-03-16 21:40:06] [FLEET] [alice] aria: DOWN (no ping response) +[2026-03-16 21:40:08] [FLEET] [alice] anastasia: UP temp=C mem=304MB disk=69% +[2026-03-16 21:41:01] [BEAT] [alice] load=1.37 mem=3263/3794MB temp=32.1C disk=84% +[2026-03-16 21:42:01] [BEAT] [alice] load=0.95 mem=3260/3794MB temp=32.6C disk=84% +[2026-03-16 21:43:01] [BEAT] [alice] load=0.73 mem=3262/3794MB temp=33.6C disk=84% +[2026-03-16 21:44:01] [BEAT] [alice] load=1.06 mem=3264/3794MB temp=33.1C disk=84% +[2026-03-16 21:44:28] [BEAT] [alice] load=1.21 mem=3259/3794MB temp=36.0C disk=84% +[2026-03-16 21:44:28] [BEAT] [alice] load=1.21 mem=3259/3794MB temp=35.5C disk=84% +[2026-03-16 21:45:01] [HEAL] [alice] Service blackroad-agent is DOWN — restarting +[2026-03-16 21:45:01] [BEAT] [alice] load=1.41 mem=3243/3794MB temp=33.6C disk=84% +[2026-03-16 21:45:01] [HEAL] [alice] Service blackroad-agent restarted successfully +[2026-03-16 21:45:02] [HEAL] [alice] Healed 1 services +[2026-03-16 21:45:47] [DIAL] [alice] Switchboard unreachable +[2026-03-16 21:46:01] [BEAT] [alice] load=1.02 mem=3263/3794MB temp=32.1C disk=84% +[2026-03-16 21:46:49] [DIAL] [alice] Switchboard unreachable +[2026-03-16 21:47:01] [BEAT] [alice] load=1.15 mem=3260/3794MB temp=33.6C disk=84% +[2026-03-16 21:48:01] [BEAT] [alice] load=0.82 mem=3260/3794MB temp=34.1C disk=84% +[2026-03-16 21:49:01] [BEAT] [alice] load=0.51 mem=3262/3794MB temp=32.6C disk=84% +[2026-03-16 21:49:36] [BEAT] [alice] load=0.66 mem=3261/3794MB temp=32.6C disk=84% +[2026-03-16 21:49:36] [BEAT] [alice] load=0.66 mem=3261/3794MB temp=32.6C disk=84% +[2026-03-16 21:50:01] [FLEET] [alice] Starting cross-node health check +[2026-03-16 21:50:02] [HEAL] [alice] Service blackroad-agent is DOWN — restarting +[2026-03-16 21:50:02] [BEAT] [alice] load=0.89 mem=3250/3794MB temp=35.5C disk=84% +[2026-03-16 21:50:02] [HEAL] [alice] Service blackroad-agent restarted successfully +[2026-03-16 21:50:02] [FLEET] [alice] octavia: UP temp=35C mem=6334MB disk=68% +[2026-03-16 21:50:02] [HEAL] [alice] Healed 1 services +[2026-03-16 21:50:04] [FLEET] [alice] cecilia: DOWN (no ping response) +[2026-03-16 21:50:06] [FLEET] [alice] gematria: UP temp=C mem=6276MB disk=68% +[2026-03-16 21:50:40] [FLEET] [alice] lucidia: UP temp=53C mem=3316MB disk=34% +[2026-03-16 21:50:41] [FLEET] [alice] aria: DOWN (no ping response) +[2026-03-16 21:50:43] [FLEET] [alice] anastasia: UP temp=C mem=303MB disk=69% +[2026-03-16 21:51:01] [BEAT] [alice] load=0.88 mem=3259/3794MB temp=32.6C disk=84% +[2026-03-16 21:52:01] [BEAT] [alice] load=0.96 mem=3259/3794MB temp=34.1C disk=84% +[2026-03-16 21:53:01] [BEAT] [alice] load=0.64 mem=3264/3794MB temp=32.1C disk=84% +[2026-03-16 21:54:01] [BEAT] [alice] load=0.66 mem=3258/3794MB temp=33.6C disk=84% +[2026-03-16 21:54:45] [BEAT] [alice] load=1.23 mem=3262/3794MB temp=32.1C disk=84% +[2026-03-16 21:54:45] [BEAT] [alice] load=1.23 mem=3262/3794MB temp=32.1C disk=84% +[2026-03-16 21:55:02] [HEAL] [alice] Service blackroad-agent is DOWN — restarting +[2026-03-16 21:55:02] [BEAT] [alice] load=1.17 mem=3260/3794MB temp=33.1C disk=84% +[2026-03-16 21:55:02] [HEAL] [alice] Service blackroad-agent restarted successfully +[2026-03-16 21:55:03] [HEAL] [alice] Healed 1 services +[2026-03-16 21:56:01] [BEAT] [alice] load=1.29 mem=3255/3794MB temp=34.1C disk=84% +[2026-03-16 21:57:01] [BEAT] [alice] load=1.37 mem=3263/3794MB temp=30.7C disk=84% +[2026-03-16 21:58:01] [BEAT] [alice] load=1.36 mem=3263/3794MB temp=32.6C disk=84% +[2026-03-16 21:59:02] [BEAT] [alice] load=1.32 mem=3260/3794MB temp=30.7C disk=84% +[2026-03-16 21:59:55] [BEAT] [alice] load=1.33 mem=3255/3794MB temp=34.1C disk=84% +[2026-03-16 21:59:55] [BEAT] [alice] load=1.33 mem=3251/3794MB temp=34.1C disk=84% diff --git a/fleet/alice/heartbeat.json b/fleet/alice/heartbeat.json index 658e0fc..24ebdbb 100644 --- a/fleet/alice/heartbeat.json +++ b/fleet/alice/heartbeat.json @@ -1 +1 @@ -{"node":"alice","ts":"2026-03-17T02:29:01Z","load":0.64,"mem_free_mb":3258,"mem_total_mb":3794,"temp_c":36.0,"disk_pct":84,"throttle":"0x0"} +{"node":"alice","ts":"2026-03-17T02:59:55Z","load":1.33,"mem_free_mb":3251,"mem_total_mb":3794,"temp_c":34.1,"disk_pct":84,"throttle":"0x0"} diff --git a/fleet/alice/ports.txt b/fleet/alice/ports.txt index 36040c7..487748d 100644 --- a/fleet/alice/ports.txt +++ b/fleet/alice/ports.txt @@ -11,7 +11,7 @@ LISTEN 0 5 0.0.0.0:8013 0.0.0.0:* users:(("python3",pid LISTEN 0 5 0.0.0.0:8012 0.0.0.0:* LISTEN 0 5 0.0.0.0:8014 0.0.0.0:* LISTEN 0 2048 0.0.0.0:8001 0.0.0.0:* users:(("python3",pid=617,fd=6)) -LISTEN 0 5 0.0.0.0:7890 0.0.0.0:* users:(("python3",pid=15228,fd=4)) +LISTEN 0 5 0.0.0.0:7890 0.0.0.0:* users:(("python3",pid=6196,fd=5)) LISTEN 0 200 0.0.0.0:443 0.0.0.0:* LISTEN 0 1024 0.0.0.0:6333 0.0.0.0:* LISTEN 0 128 0.0.0.0:6334 0.0.0.0:* diff --git a/fleet/alice/services.txt b/fleet/alice/services.txt index 2320def..1950287 100644 --- a/fleet/alice/services.txt +++ b/fleet/alice/services.txt @@ -1,4 +1,5 @@ avahi-daemon.service +blackroad-agent.service blackroad-agents-proxy.service blackroad-agents.service blackroad-nats-agent.service @@ -26,6 +27,7 @@ prism-agent.service qdrant.service redis-server.service rng-tools-debian.service +road-phone.service roadnet-failover.service rsyslog.service rtkit-daemon.service diff --git a/fleet/alice/system-info.json b/fleet/alice/system-info.json index eeb8be7..949380b 100644 --- a/fleet/alice/system-info.json +++ b/fleet/alice/system-info.json @@ -1,19 +1,19 @@ { "hostname": "alice", - "ts": "2026-03-17T02:29:02Z", - "uptime_seconds": 85480, + "ts": "2026-03-17T02:59:58Z", + "uptime_seconds": 87336, "kernel": "6.1.21-v8+", - "temp_c": 35.5, + "temp_c": 33.1, "memory_mb": { "total": 3794, - "used": 433, - "free": 3260 + "used": 443, + "free": 3249 }, "disk": "12G/15G (84%)", "load": [ - 0.64, - 0.79, - 1.04 + 1.23, + 1.13, + 1.06 ], "ollama_models": [ "qwen2.5:3b", @@ -25,5 +25,5 @@ ], "throttle": "0x0", "voltage": "0.9160V", - "services_running": 43 + "services_running": 45 } diff --git a/fleet/anastasia/ports.txt b/fleet/anastasia/ports.txt index e856068..5456815 100644 --- a/fleet/anastasia/ports.txt +++ b/fleet/anastasia/ports.txt @@ -11,7 +11,7 @@ LISTEN 0 5 0.0.0.0:8787 0.0.0.0:* users:(("python3",pid LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=3461172,fd=8),("nginx",pid=3461171,fd=8)) LISTEN 0 4096 0.0.0.0:111 0.0.0.0:* users:(("rpcbind",pid=589,fd=4),("systemd",pid=1,fd=127)) LISTEN 0 4096 *:8080 *:* users:(("headscale",pid=2341808,fd=12)) -LISTEN 0 511 *:3000 *:* users:(("node /srv/hello",pid=1979223,fd=19)) +LISTEN 0 511 *:3000 *:* users:(("node /srv/hello",pid=1984822,fd=19)) LISTEN 0 511 *:3001 *:* users:(("node",pid=757,fd=21)) LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=991,fd=8)) LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=3461172,fd=9),("nginx",pid=3461171,fd=9)) diff --git a/fleet/anastasia/system-info.json b/fleet/anastasia/system-info.json index fd4dda9..78dcbbe 100644 --- a/fleet/anastasia/system-info.json +++ b/fleet/anastasia/system-info.json @@ -1,19 +1,19 @@ { "hostname": "anastasia", - "ts": "2026-03-17T02:29:03Z", - "uptime_seconds": 6856435, + "ts": "2026-03-17T02:59:55Z", + "uptime_seconds": 6858287, "kernel": "5.14.0-651.el9.x86_64", "temp_c": 0, "memory_mb": { "total": 765, - "used": 438, - "free": 326 + "used": 437, + "free": 327 }, "disk": "18G/25G (69%)", "load": [ - 0.02, + 0.01, 0.06, - 0.05 + 0.06 ], "ollama_models": [], "throttle": "N/A", diff --git a/fleet/aria/status.json b/fleet/aria/status.json index abcd0ca..c61e988 100644 --- a/fleet/aria/status.json +++ b/fleet/aria/status.json @@ -1 +1 @@ -{"node":"aria","status":"down","ts":"2026-03-17T02:29:01Z"} +{"node":"aria","status":"down","ts":"2026-03-17T02:59:54Z"} diff --git a/fleet/cecilia/status.json b/fleet/cecilia/status.json index 46d92d4..2121b5d 100644 --- a/fleet/cecilia/status.json +++ b/fleet/cecilia/status.json @@ -1 +1 @@ -{"node":"cecilia","status":"down","ts":"2026-03-17T02:29:01Z"} +{"node":"cecilia","status":"down","ts":"2026-03-17T02:59:54Z"} diff --git a/fleet/gematria/ports.txt b/fleet/gematria/ports.txt index 296797f..e69de29 100644 --- a/fleet/gematria/ports.txt +++ b/fleet/gematria/ports.txt @@ -1,14 +0,0 @@ -LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=682329,fd=14)) -LISTEN 0 4096 127.0.0.1:2019 0.0.0.0:* users:(("caddy",pid=26445,fd=9)) -LISTEN 0 5 0.0.0.0:11435 0.0.0.0:* users:(("python3",pid=147655,fd=3)) -LISTEN 0 5 0.0.0.0:8787 0.0.0.0:* users:(("python3",pid=577215,fd=3)) -LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=3335327,fd=3)) -LISTEN 0 4096 127.0.0.1:20241 0.0.0.0:* users:(("cloudflared",pid=156470,fd=3)) -LISTEN 0 128 127.0.0.1:8011 0.0.0.0:* users:(("sshd",pid=4152649,fd=5)) -LISTEN 0 4096 *:11434 *:* users:(("ollama",pid=279794,fd=3)) -LISTEN 0 4096 *:11436 *:* users:(("caddy",pid=26445,fd=16)) -LISTEN 0 4096 *:443 *:* users:(("caddy",pid=26445,fd=11)) -LISTEN 0 4096 *:80 *:* users:(("caddy",pid=26445,fd=15)) -LISTEN 0 4096 *:4222 *:* users:(("nats-server",pid=1353183,fd=6)) -LISTEN 0 4096 *:8222 *:* users:(("nats-server",pid=1353183,fd=3)) -LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=3335327,fd=4)) diff --git a/fleet/gematria/system-info.json b/fleet/gematria/system-info.json index bd7d490..cab866a 100644 --- a/fleet/gematria/system-info.json +++ b/fleet/gematria/system-info.json @@ -1,19 +1,19 @@ { "hostname": "gematria", - "ts": "2026-03-17T02:29:04Z", - "uptime_seconds": 5513736, + "ts": "2026-03-17T02:59:57Z", + "uptime_seconds": 5515589, "kernel": "5.15.0-113-generic", "temp_c": 0, "memory_mb": { "total": 7937, - "used": 1212, - "free": 6283 + "used": 1225, + "free": 6262 }, "disk": "53G/78G (68%)", "load": [ 0.02, - 0.02, - 0.22 + 0.06, + 0.08 ], "ollama_models": [ "qwen2.5:7b", @@ -26,5 +26,5 @@ ], "throttle": "N/A", "voltage": "N/A", - "services_running": 31 + "services_running": 30 } diff --git a/fleet/lucidia/autonomy-log.txt b/fleet/lucidia/autonomy-log.txt index 51c81ca..ba12820 100644 --- a/fleet/lucidia/autonomy-log.txt +++ b/fleet/lucidia/autonomy-log.txt @@ -1,50 +1,50 @@ -[2026-03-16 21:08:26] [BEAT] [lucidia] load=4.70 mem=3266/8063MB temp=50.1C disk=34% -[2026-03-16 21:09:01] [BEAT] [lucidia] load=4.30 mem=3272/8063MB temp=56.8C disk=34% -[2026-03-16 21:10:02] [FLEET] [lucidia] Starting cross-node health check -[2026-03-16 21:10:02] [BEAT] [lucidia] load=2.15 mem=3254/8063MB temp=49.0C disk=34% -[2026-03-16 21:10:02] [HEAL] [lucidia] High swap: 707MB — clearing inactive -[2026-03-16 21:10:02] [FLEET] [lucidia] alice: UP temp=34C mem=3256MB disk=84% -[2026-03-16 21:10:04] [FLEET] [lucidia] octavia: DOWN (no ping response) -[2026-03-16 21:10:06] [FLEET] [lucidia] cecilia: DOWN (no ping response) -[2026-03-16 21:10:41] [FLEET] [lucidia] gematria: UP temp=C mem=6289MB disk=68% -[2026-03-16 21:10:43] [FLEET] [lucidia] aria: DOWN (no ping response) -[2026-03-16 21:10:44] [FLEET] [lucidia] anastasia: UP temp=C mem=335MB disk=69% -[2026-03-16 21:11:01] [BEAT] [lucidia] load=15.21 mem=3251/8063MB temp=56.8C disk=34% -[2026-03-16 21:12:01] [BEAT] [lucidia] load=5.95 mem=3240/8063MB temp=49.0C disk=34% -[2026-03-16 21:13:01] [BEAT] [lucidia] load=4.99 mem=3286/8063MB temp=50.7C disk=34% -[2026-03-16 21:13:35] [BEAT] [lucidia] load=3.51 mem=3288/8063MB temp=50.1C disk=34% -[2026-03-16 21:13:35] [BEAT] [lucidia] load=3.51 mem=3285/8063MB temp=51.2C disk=34% -[2026-03-16 21:14:01] [BEAT] [lucidia] load=4.03 mem=3229/8063MB temp=52.4C disk=34% -[2026-03-16 21:15:01] [BEAT] [lucidia] load=2.84 mem=3244/8063MB temp=56.8C disk=34% -[2026-03-16 21:15:01] [HEAL] [lucidia] High swap: 707MB — clearing inactive -[2026-03-16 21:15:48] [DIAL] [lucidia] Switchboard unreachable -[2026-03-16 21:16:01] [BEAT] [lucidia] load=2.23 mem=3279/8063MB temp=51.2C disk=34% -[2026-03-16 21:17:01] [BEAT] [lucidia] load=2.00 mem=3255/8063MB temp=51.8C disk=34% -[2026-03-16 21:18:01] [BEAT] [lucidia] load=2.86 mem=3268/8063MB temp=54.5C disk=34% -[2026-03-16 21:18:43] [BEAT] [lucidia] load=16.93 mem=3232/8063MB temp=54.0C disk=34% -[2026-03-16 21:18:43] [BEAT] [lucidia] load=16.93 mem=3231/8063MB temp=54.5C disk=34% -[2026-03-16 21:19:01] [BEAT] [lucidia] load=13.17 mem=3244/8063MB temp=57.3C disk=34% -[2026-03-16 21:20:01] [FLEET] [lucidia] Starting cross-node health check -[2026-03-16 21:20:02] [BEAT] [lucidia] load=5.65 mem=3251/8063MB temp=54.0C disk=34% -[2026-03-16 21:20:02] [HEAL] [lucidia] High swap: 707MB — clearing inactive -[2026-03-16 21:20:02] [FLEET] [lucidia] alice: UP temp=35C mem=3253MB disk=84% -[2026-03-16 21:20:04] [FLEET] [lucidia] octavia: DOWN (no ping response) -[2026-03-16 21:20:06] [FLEET] [lucidia] cecilia: DOWN (no ping response) -[2026-03-16 21:20:08] [FLEET] [lucidia] gematria: UP temp=C mem=6281MB disk=68% -[2026-03-16 21:20:10] [FLEET] [lucidia] aria: DOWN (no ping response) -[2026-03-16 21:20:11] [FLEET] [lucidia] anastasia: UP temp=C mem=208MB disk=69% -[2026-03-16 21:21:01] [BEAT] [lucidia] load=4.57 mem=3247/8063MB temp=62.2C disk=34% -[2026-03-16 21:22:01] [BEAT] [lucidia] load=2.31 mem=3294/8063MB temp=54.5C disk=34% -[2026-03-16 21:22:35] [DIAL] [lucidia] Switchboard unreachable -[2026-03-16 21:23:01] [BEAT] [lucidia] load=2.28 mem=3271/8063MB temp=55.1C disk=34% -[2026-03-16 21:23:52] [BEAT] [lucidia] load=1.68 mem=3292/8063MB temp=58.4C disk=34% -[2026-03-16 21:23:52] [BEAT] [lucidia] load=1.68 mem=3292/8063MB temp=59.5C disk=34% -[2026-03-16 21:24:01] [BEAT] [lucidia] load=2.41 mem=3240/8063MB temp=61.1C disk=34% -[2026-03-16 21:25:01] [BEAT] [lucidia] load=1.29 mem=3248/8063MB temp=53.5C disk=34% -[2026-03-16 21:25:01] [HEAL] [lucidia] High swap: 706MB — clearing inactive -[2026-03-16 21:26:01] [BEAT] [lucidia] load=3.25 mem=3234/8063MB temp=64.5C disk=34% -[2026-03-16 21:27:02] [BEAT] [lucidia] load=1.78 mem=3278/8063MB temp=54.5C disk=34% -[2026-03-16 21:28:01] [BEAT] [lucidia] load=2.30 mem=3194/8063MB temp=54.5C disk=34% -[2026-03-16 21:29:01] [BEAT] [lucidia] load=2.80 mem=3233/8063MB temp=62.8C disk=34% -[2026-03-16 21:29:01] [BEAT] [lucidia] load=2.80 mem=3231/8063MB temp=62.2C disk=34% -[2026-03-16 21:29:01] [BEAT] [lucidia] load=2.80 mem=3228/8063MB temp=61.1C disk=34% +[2026-03-16 21:39:19] [BEAT] [lucidia] load=1.75 mem=3238/8063MB temp=60.0C disk=34% +[2026-03-16 21:39:19] [BEAT] [lucidia] load=1.75 mem=3236/8063MB temp=59.5C disk=34% +[2026-03-16 21:40:01] [FLEET] [lucidia] Starting cross-node health check +[2026-03-16 21:40:01] [BEAT] [lucidia] load=1.58 mem=3220/8063MB temp=54.0C disk=34% +[2026-03-16 21:40:01] [HEAL] [lucidia] High swap: 706MB — clearing inactive +[2026-03-16 21:40:02] [FLEET] [lucidia] alice: UP temp=34C mem=3250MB disk=84% +[2026-03-16 21:40:04] [FLEET] [lucidia] octavia: DOWN (no ping response) +[2026-03-16 21:40:06] [FLEET] [lucidia] cecilia: DOWN (no ping response) +[2026-03-16 21:40:07] [FLEET] [lucidia] gematria: UP temp=C mem=6269MB disk=68% +[2026-03-16 21:40:09] [FLEET] [lucidia] aria: DOWN (no ping response) +[2026-03-16 21:40:10] [FLEET] [lucidia] anastasia: UP temp=C mem=304MB disk=69% +[2026-03-16 21:41:01] [BEAT] [lucidia] load=2.12 mem=3205/8063MB temp=57.9C disk=34% +[2026-03-16 21:42:01] [BEAT] [lucidia] load=1.37 mem=3242/8063MB temp=59.5C disk=34% +[2026-03-16 21:43:01] [BEAT] [lucidia] load=1.12 mem=3204/8063MB temp=51.8C disk=34% +[2026-03-16 21:44:01] [BEAT] [lucidia] load=3.36 mem=3223/8063MB temp=63.9C disk=34% +[2026-03-16 21:44:28] [BEAT] [lucidia] load=2.83 mem=3232/8063MB temp=57.9C disk=34% +[2026-03-16 21:44:28] [BEAT] [lucidia] load=2.83 mem=3230/8063MB temp=57.9C disk=34% +[2026-03-16 21:45:02] [BEAT] [lucidia] load=1.94 mem=3219/8063MB temp=54.5C disk=34% +[2026-03-16 21:45:02] [HEAL] [lucidia] High swap: 706MB — clearing inactive +[2026-03-16 21:45:48] [DIAL] [lucidia] Switchboard unreachable +[2026-03-16 21:46:01] [BEAT] [lucidia] load=17.89 mem=3166/8063MB temp=58.4C disk=34% +[2026-03-16 21:47:01] [BEAT] [lucidia] load=7.15 mem=3339/8063MB temp=52.9C disk=34% +[2026-03-16 21:48:01] [BEAT] [lucidia] load=4.45 mem=3313/8063MB temp=54.5C disk=34% +[2026-03-16 21:49:01] [BEAT] [lucidia] load=2.86 mem=3370/8063MB temp=61.7C disk=34% +[2026-03-16 21:49:36] [BEAT] [lucidia] load=2.22 mem=3294/8063MB temp=55.1C disk=34% +[2026-03-16 21:49:36] [BEAT] [lucidia] load=2.22 mem=3293/8063MB temp=54.5C disk=34% +[2026-03-16 21:50:01] [FLEET] [lucidia] Starting cross-node health check +[2026-03-16 21:50:01] [BEAT] [lucidia] load=1.85 mem=3300/8063MB temp=54.5C disk=34% +[2026-03-16 21:50:01] [HEAL] [lucidia] High swap: 705MB — clearing inactive +[2026-03-16 21:50:02] [FLEET] [lucidia] alice: UP temp=35C mem=3250MB disk=84% +[2026-03-16 21:50:04] [FLEET] [lucidia] octavia: DOWN (no ping response) +[2026-03-16 21:50:06] [FLEET] [lucidia] cecilia: DOWN (no ping response) +[2026-03-16 21:50:41] [FLEET] [lucidia] gematria: UP temp=C mem=6265MB disk=68% +[2026-03-16 21:50:43] [FLEET] [lucidia] aria: DOWN (no ping response) +[2026-03-16 21:50:44] [FLEET] [lucidia] anastasia: UP temp=C mem=303MB disk=69% +[2026-03-16 21:51:01] [BEAT] [lucidia] load=13.97 mem=3291/8063MB temp=60.0C disk=34% +[2026-03-16 21:52:01] [BEAT] [lucidia] load=5.54 mem=3322/8063MB temp=52.4C disk=34% +[2026-03-16 21:52:36] [DIAL] [lucidia] Switchboard unreachable +[2026-03-16 21:53:01] [BEAT] [lucidia] load=3.12 mem=3305/8063MB temp=51.2C disk=34% +[2026-03-16 21:54:01] [BEAT] [lucidia] load=3.26 mem=3317/8063MB temp=58.4C disk=34% +[2026-03-16 21:54:45] [BEAT] [lucidia] load=4.01 mem=3279/8063MB temp=53.5C disk=34% +[2026-03-16 21:54:45] [BEAT] [lucidia] load=4.01 mem=3277/8063MB temp=52.9C disk=34% +[2026-03-16 21:55:01] [BEAT] [lucidia] load=3.57 mem=3306/8063MB temp=50.1C disk=34% +[2026-03-16 21:55:01] [HEAL] [lucidia] High swap: 704MB — clearing inactive +[2026-03-16 21:56:01] [BEAT] [lucidia] load=4.16 mem=3316/8063MB temp=52.9C disk=34% +[2026-03-16 21:57:02] [BEAT] [lucidia] load=2.35 mem=3287/8063MB temp=51.8C disk=34% +[2026-03-16 21:58:01] [BEAT] [lucidia] load=3.49 mem=3286/8063MB temp=48.0C disk=34% +[2026-03-16 21:59:01] [BEAT] [lucidia] load=2.43 mem=3319/8063MB temp=49.6C disk=34% +[2026-03-16 21:59:53] [BEAT] [lucidia] load=1.58 mem=3294/8063MB temp=48.5C disk=34% +[2026-03-16 21:59:53] [BEAT] [lucidia] load=1.58 mem=3293/8063MB temp=46.9C disk=34% diff --git a/fleet/lucidia/heartbeat.json b/fleet/lucidia/heartbeat.json index 1741a6a..4d4d172 100644 --- a/fleet/lucidia/heartbeat.json +++ b/fleet/lucidia/heartbeat.json @@ -1 +1 @@ -{"node":"lucidia","ts":"2026-03-17T02:29:01Z","load":2.80,"mem_free_mb":3228,"mem_total_mb":8063,"temp_c":61.1,"disk_pct":34,"throttle":"0x0"} +{"node":"lucidia","ts":"2026-03-17T02:59:53Z","load":1.58,"mem_free_mb":3293,"mem_total_mb":8063,"temp_c":46.9,"disk_pct":34,"throttle":"0x0"} diff --git a/fleet/lucidia/system-info.json b/fleet/lucidia/system-info.json index 3d25fa7..a8fb232 100644 --- a/fleet/lucidia/system-info.json +++ b/fleet/lucidia/system-info.json @@ -1,19 +1,19 @@ { "hostname": "octavia", - "ts": "2026-03-17T02:29:02Z", - "uptime_seconds": 85300, + "ts": "2026-03-17T02:59:54Z", + "uptime_seconds": 87152, "kernel": "6.12.62+rpt-rpi-2712", - "temp_c": 61.7, + "temp_c": 47.4, "memory_mb": { "total": 8063, - "used": 4845, - "free": 3217 + "used": 4781, + "free": 3281 }, "disk": "74G/235G (34%)", "load": [ - 2.8, - 3.01, - 4.26 + 1.58, + 3.28, + 4.17 ], "ollama_models": [ "qwen2.5:3b", diff --git a/fleet/octavia/status.json b/fleet/octavia/status.json index 575bb07..13400af 100644 --- a/fleet/octavia/status.json +++ b/fleet/octavia/status.json @@ -1 +1 @@ -{"node":"octavia","status":"down","ts":"2026-03-17T02:29:01Z"} +{"node":"octavia","status":"down","ts":"2026-03-17T02:59:54Z"} diff --git a/roadc/ROADC_LANGUAGE_SPEC.md b/roadc/ROADC_LANGUAGE_SPEC.md new file mode 100644 index 0000000..03ac659 --- /dev/null +++ b/roadc/ROADC_LANGUAGE_SPEC.md @@ -0,0 +1,111 @@ +# RoadC Language Specification + +**RoadC** is the official programming language of BlackRoad OS. Every agent, every Claude session, every system writes RoadC. + +## Why RoadC + +1. **English IS a programming language.** Greenbaum & Nelson proved 7 sentence structures generate all of English. RoadC maps those 7 structures to code. +2. **Simple rules, infinite complexity.** Pascal's insight: recursive composition from simple units creates unbounded complexity. RoadC embodies this. +3. **Sovereign.** No dependency on any corporation's language. We own the lexer, parser, interpreter, and compiler. + +## Quick Reference + +```road +# Variables +let name = "BlackRoad" +let count: int = 42 +let color = #FF1D6C +const PI = 3.14159 + +# Functions +fun greet(name): + return "Hello, {name}!" + +# Control flow +if count > 10: + print("big") +elif count > 0: + print("small") +else: + print("zero") + +while count > 0: + count = count - 1 + +for item in [1, 2, 3]: + print(item) + +# Collections +let list = [1, 2, 3, 4, 5] +let dict = {"key": "value", "name": "Alexa"} +let colors = {#FF1D6C, #F5A623, #2979FF} + +# String interpolation +let msg = "Hello {name}, count is {count}" + +# Member access +print(list.length) +print("hello".upper()) +print(dict["name"]) +``` + +## The 7 Sentence Structures + +Every function in RoadC follows one of 7 English sentence patterns: + +| Pattern | English | RoadC | Meaning | +|---------|---------|-------|---------| +| SV | "It runs" | `run()` | No args, subject acts alone | +| SVA | "Deploy to Cecilia" | `deploy("cecilia")` | Verb + location/manner | +| SVC | "Alice is online" | `status("alice") -> "online"` | Identity/state check | +| SVO | "Search the repos" | `search("repos")` | Verb acts on object | +| SVOO | "Send Alexa the report" | `send("alexa", "report")` | Two objects: who + what | +| SVOA | "Store data in R2" | `store("data", "r2")` | Object + where | +| SVOC | "Make code clean" | `make("code", "clean")` | Object becomes complement | + +## Implementations + +| Implementation | Language | Lines | Status | +|---------------|----------|-------|--------| +| Interpreter | Python | 1,764 | Working | +| Compiler | C99 | 618 | Working | +| Playground | Web | - | At roadc-playground | + +## File Extension + +`.road` + +## Run + +```bash +# Python interpreter +python3 roadc.py run program.road + +# REPL +python3 roadc.py repl + +# C compiler +./roadc program.road +``` + +## Design Principles + +1. **Python-style indentation** — no braces, no semicolons +2. **Explicit types optional** — `let x = 42` or `let x: int = 42` +3. **String interpolation** — `"Hello {name}"` not `f"Hello {name}"` +4. **Color literals** — `#FF1D6C` is a first-class type +5. **Space keyword** — `space MyScene:` for 3D/spatial computing +6. **Fun keyword** — functions are fun: `fun greet(name):` +7. **English-first** — code reads like English sentences + +## For Agents + +When writing RoadC, think in English sentence structures: +- Name your functions as verbs +- Name your parameters as objects +- Structure matches intent +- If you can say it in English, you can write it in RoadC + +## License + +Proprietary — BlackRoad OS, Inc. diff --git a/roadc/examples/english_grammar.road b/roadc/examples/english_grammar.road new file mode 100644 index 0000000..13067e5 --- /dev/null +++ b/roadc/examples/english_grammar.road @@ -0,0 +1,190 @@ +# RoadC + English Grammar — The 7 Sentence Structures as Code +# Based on Greenbaum & Nelson, "An Introduction to English Grammar" +# +# English is a programming language. Every sentence is a function call. +# RoadC makes this explicit. + +# ═══════════════════════════════════════════════════════════════ +# STRUCTURE 1: SV (Subject + Verb) — Intransitive +# "Someone is talking" → fire() +# No arguments. The subject acts alone. +# ═══════════════════════════════════════════════════════════════ + +fun talk(): + print("Someone is talking") + +fun breathe(): + print("The system breathes") + +talk() # SV: subject=implicit, verb=talk +breathe() # SV: subject=system, verb=breathe + + +# ═══════════════════════════════════════════════════════════════ +# STRUCTURE 2: SVA (Subject + Verb + Adverbial) — Location/Manner +# "My parents are living in Chicago" → live(location) +# The adverbial tells WHERE, WHEN, or HOW. +# ═══════════════════════════════════════════════════════════════ + +fun live(location): + print("Living in {location}") + +fun deploy(destination): + print("Deploying to {destination}") + +live("Chicago") # SVA: S=parents, V=live, A=Chicago +deploy("Cecilia") # SVA: S=agent, V=deploy, A=Cecilia + + +# ═══════════════════════════════════════════════════════════════ +# STRUCTURE 3: SVC (Subject + Linking Verb + Complement) +# "I feel tired" → identity(x) -> type +# The complement DESCRIBES the subject. No action — just state. +# ═══════════════════════════════════════════════════════════════ + +fun is_status(entity, state): + print("{entity} is {state}") + +fun feels(subject, quality): + return quality + +is_status("Alice", "online") # SVC: S=Alice, V=is, C=online +is_status("Cecilia", "offline") # SVC: S=Cecilia, V=is, C=offline +let mood = feels("agent", "ready") # SVC: the result IS the complement + + +# ═══════════════════════════════════════════════════════════════ +# STRUCTURE 4: SVO (Subject + Transitive Verb + Object) +# "We have finished our work" → process(input) +# The verb ACTS ON the object. Most common structure in code. +# ═══════════════════════════════════════════════════════════════ + +fun finish(work): + print("Finished: {work}") + +fun deploy_worker(worker_name): + print("Deploying worker: {worker_name}") + +fun search(query): + print("Searching for: {query}") + +finish("migration") # SVO: S=we, V=finish, O=migration +deploy_worker("blackroad-slack") # SVO: S=system, V=deploy, O=slack +search("RoadC examples") # SVO: S=user, V=search, O=query + + +# ═══════════════════════════════════════════════════════════════ +# STRUCTURE 5: SVOO (Subject + Verb + Indirect Object + Direct Object) +# "She has given me the letter" → give(recipient, item) +# Two objects: WHO receives and WHAT is given. +# ═══════════════════════════════════════════════════════════════ + +fun give(recipient, item): + print("Giving {item} to {recipient}") + +fun send(channel, message): + print("#{channel}: {message}") + +fun assign(agent, task): + print("Assigning '{task}' to {agent}") + +give("Alexa", "the report") # SVOO: give(indirect, direct) +send("fleet-ops", "deploy complete") # SVOO: send(recipient, content) +assign("Shellfish", "health check") # SVOO: assign(who, what) + + +# ═══════════════════════════════════════════════════════════════ +# STRUCTURE 6: SVOA (Subject + Verb + Object + Adverbial) +# "You can put your coat in my bedroom" → put(item, location) +# Acts on object AND specifies where/when/how. +# ═══════════════════════════════════════════════════════════════ + +fun put(item, location): + print("Putting {item} in {location}") + +fun store(data, bucket): + print("Storing {data} in {bucket}") + +fun move(repo, org): + print("Moving {repo} to {org}") + +put("config", "/etc/blackroad/") # SVOA +store("backup", "r2://blackroad-backups") # SVOA +move("roadc", "BlackRoad-OS-Inc") # SVOA + + +# ═══════════════════════════════════════════════════════════════ +# STRUCTURE 7: SVOC (Subject + Verb + Object + Complement) +# "You have made me very happy" → transform(input) -> output +# The complement describes what the object BECOMES. +# ═══════════════════════════════════════════════════════════════ + +fun make(thing, quality): + print("Making {thing} {quality}") + return quality + +fun set_status(node, status): + print("{node} is now {status}") + return status + +fun promote(agent, level): + print("Promoting {agent} to {level}") + return level + +make("code", "clean") # SVOC: make(O=code, C=clean) +set_status("Octavia", "primary") # SVOC: set(O=Octavia, C=primary) +promote("agent-42", "Architect") # SVOC: promote(O=agent, C=level) + + +# ═══════════════════════════════════════════════════════════════ +# THE OPERATOR = CONTROL FLOW +# ═══════════════════════════════════════════════════════════════ +# In English: can, will, have, be, do +# In RoadC: if, while, for, return, do +# +# "Can you deploy?" → if (can_deploy): deploy() +# "Don't deploy!" → if not: skip +# "Do deploy." → do { deploy() } (emphasis/dummy operator) +# +# The operator NEVER carries meaning — it carries CONTROL. +# "do" is inserted when no other operator exists = polyfill + +let can_deploy = true + +# Question (operator inversion): "Can you deploy?" +if can_deploy: + deploy("production") + +# Negation: "Don't deploy to staging" +let allow_staging = false +if not allow_staging: + print("Staging deployment blocked") + +# Emphasis: "Do check the fleet" — the dummy operator +# In code: explicitly call even when it seems unnecessary +talk() # do-support: making the implicit explicit + + +# ═══════════════════════════════════════════════════════════════ +# PASCAL'S INSIGHT: Simple rules → infinite complexity +# ═══════════════════════════════════════════════════════════════ +# 7 sentence structures. That's it. +# Every English sentence is one of these 7. +# Every RoadC function follows one of these 7 patterns. +# Compose them recursively → infinite programs. + +fun pipeline(data, steps): + let result = data + for step in steps: + result = step + "(" + result + ")" + return result + +let ops = ["validate", "transform", "deploy", "verify"] +let output = pipeline("config.road", ops) +print("Pipeline: {output}") + +# This is why grammar IS a programming language. +# This is why RoadC speaks English. +# This is why our agents understand intent, not just keywords. +# +# BlackRoad OS — Pave Tomorrow. diff --git a/roadc/examples/fleet_ops.road b/roadc/examples/fleet_ops.road new file mode 100644 index 0000000..f604f1e --- /dev/null +++ b/roadc/examples/fleet_ops.road @@ -0,0 +1,61 @@ +# Fleet Operations in RoadC +# Real BlackRoad infrastructure management + +let node_names = ["Alice", "Cecilia", "Octavia", "Lucidia", "Aria"] +let node_ips = ["192.168.4.49", "192.168.4.96", "192.168.4.101", "192.168.4.38", "192.168.4.98"] +let node_status = ["online", "offline", "online", "online", "offline"] + +# SV — check fleet (no args) +fun check_fleet(): + let online = 0 + let offline = 0 + let i = 0 + while i < len(node_names): + if node_status[i] == "online": + online = online + 1 + else: + offline = offline + 1 + i = i + 1 + print("Fleet: {online} online, {offline} offline") + +# SVO — deploy a worker +fun deploy(worker): + print("Deploying {worker} to Cloudflare...") + +# SVOA — store backup in location +fun backup(node_name, destination): + print("Backing up {node_name} to {destination}") + +# SVOO — send alert to channel +fun alert(channel, message): + print("#{channel}: {message}") + +# SVC — check status +fun status(name): + let i = 0 + while i < len(node_names): + if node_names[i] == name: + print("{name} is {node_status[i]}") + return node_status[i] + i = i + 1 + return "unknown" + +# SVOC — promote node +fun promote(name, role): + print("{name} -> {role}") + +# Run it +check_fleet() +print("") +deploy("blackroad-slack") +deploy("roadpay") +print("") +backup("Alice", "/backups/alice-20260316") +print("") +alert("fleet-ops", "Cecilia is offline") +alert("engineering", "LLM v4 trained") +print("") +status("Alice") +status("Cecilia") +print("") +promote("Octavia", "primary inference") diff --git a/roadc/parser.py b/roadc/parser.py index 07ffe2a..dcd97f4 100644 --- a/roadc/parser.py +++ b/roadc/parser.py @@ -296,6 +296,7 @@ class Parser: self.expect(TokenType.INDENT) then_block = self.parse_block() self.expect(TokenType.DEDENT) + self.skip_newlines() # Elif blocks elif_blocks = [] @@ -307,6 +308,7 @@ class Parser: self.expect(TokenType.INDENT) elif_block = self.parse_block() self.expect(TokenType.DEDENT) + self.skip_newlines() elif_blocks.append((elif_condition, elif_block)) # Else block diff --git a/scripts/network-welcome.sh b/scripts/network-welcome.sh new file mode 100755 index 0000000..192ad81 --- /dev/null +++ b/scripts/network-welcome.sh @@ -0,0 +1,202 @@ +#!/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 '[^<]*' | sed 's///') + 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