-- ~/.hammerspoon/NetworkCenter.lua local NetworkCenter = {} local Config = require("Config") local menuBar = hs.menubar.new() -- State Management local nasOnline = false local vpnActive = false local ovpnIP = "Offline" local tailscaleIP = "Offline" local tailnetDomain = "Offline" -- NEW: Added state for domain local speedTestResult = "Idle" local lastExternalIP = "Offline" local ispName = "Fetching..." local wifiInfo = { ssid = "N/A", speed = 0, gen = "N/A", band = "N/A", channel = "N/A", protocol = "N/A", rssi = 0, noise = 0 } local gatewayLatency = "---" -- Service Monitoring State local services = { { name = "Plex", host = "192.168.1.105", port = 32400 }, { name = "Komga", host = "192.168.1.101", port = 25600 }, { name = "Sonarr", host = "192.168.1.101", port = 8989 }, { name = "Radarr", host = "192.168.1.101", port = 7878 }, { name = "qBitorrent", host = "192.168.1.101", port = 8080 } } local serviceStatus = {} -- ========================================== -- 1. DATA RETRIEVAL -- ========================================== local function getWifiDetails() local details = hs.wifi.interfaceDetails() if details then wifiInfo.ssid = details.ssid or "Disconnected" wifiInfo.speed = details.transmitRate or 0 wifiInfo.rssi = details.rssi or 0 wifiInfo.noise = details.noise or 0 local phy = details.activePHYMode local phyStr = tostring(phy) if phy == 6 or phyStr:find("6") or phyStr:find("ax") then wifiInfo.gen, wifiInfo.protocol = "6", "AX" elseif phy == 5 or phyStr:find("5") or phyStr:find("ac") then wifiInfo.gen, wifiInfo.protocol = "5", "AC" elseif phy == 4 or phyStr:find("4") or phyStr:find("n") then wifiInfo.gen, wifiInfo.protocol = "4", "N" else wifiInfo.gen, wifiInfo.protocol = "?", "?" end if details.wlanChannel and type(details.wlanChannel) == "table" then wifiInfo.band = tostring(details.wlanChannel.band):gsub("GHz", "") or "N/A" wifiInfo.channel = tostring(details.wlanChannel.number) or "N/A" end else wifiInfo.ssid = hs.wifi.currentNetwork() or "N/A" end end function NetworkCenter.updateStatus() getWifiDetails() -- Async NAS Ping hs.task.new("/sbin/ping", function(code) nasOnline = (code == 0) NetworkCenter.refreshUI() end, {"-c", "1", "-t", "1", Config.nasIP}):start() -- Gateway Latency hs.task.new("/sbin/ping", function(code, stdOut) if code == 0 and stdOut then local ms = stdOut:match("time=(%d+%.?%d*) ms") gatewayLatency = ms and (math.floor(tonumber(ms)) .. "ms") or "Error" else gatewayLatency = "Timeout" end NetworkCenter.refreshUI() end, {"-c", "1", "-t", "1", "192.168.1.1"}):start() -- Service Port Checks for _, svc in ipairs(services) do hs.task.new("/usr/bin/nc", function(code) serviceStatus[svc.name] = (code == 0 and "🟢" or "🔴") NetworkCenter.refreshUI() end, {"-z", "-w", "2", svc.host, tostring(svc.port)}):start() end -- Tailscale Detection & Domain Retrieval local tsPath = "/Applications/Tailscale.app/Contents/MacOS/Tailscale" local tsStatus = hs.execute(tsPath .. " status --json 2>/dev/null") local foundTSIP = "Offline" local foundTSDomain = "Offline" if tsStatus and tsStatus ~= "" then local tsData = hs.json.decode(tsStatus) if tsData and tsData.BackendState == "Running" then -- Get the Tailnet name/domain foundTSDomain = tsData.MagicDNSSuffix or "N/A" if tsData.Self and tsData.Self.TailscaleIPs then for _, ip in ipairs(tsData.Self.TailscaleIPs) do if ip:match("^100%.") then foundTSIP = ip break end end end end end tailscaleIP = foundTSIP tailnetDomain = foundTSDomain -- OpenVPN Detection local ovpnCheck = hs.execute("pgrep -x 'OpenVPN Connect'"):gsub("%s+", "") local ovpnIPQuery = hs.execute("ifconfig | grep -A 1 'utun' | grep 'inet 10.' | awk '{print $2}'"):gsub("%s+", "") if ovpnCheck ~= "" and ovpnIPQuery ~= "" then vpnActive = true ovpnIP = ovpnIPQuery else vpnActive = false ovpnIP = "Offline" end -- External IP & ISP hs.task.new("/usr/bin/curl", function(exitCode, stdOut) if exitCode == 0 and stdOut then local data = hs.json.decode(stdOut) if data then lastExternalIP = data.ip or "Offline" ispName = data.asn_org or data.company_name or "Unknown ISP" end else lastExternalIP = "Offline" ispName = "Offline" end NetworkCenter.refreshUI() end, {"-s", "-m", "5", "https://ifconfig.co/json"}):start() end local function runSpeedTest() speedTestResult = "Testing..." NetworkCenter.refreshUI() hs.task.new("/usr/bin/networkQuality", function(exitCode, stdOut) if exitCode == 0 and stdOut then local data = hs.json.decode(stdOut) if data then local downMbps = math.floor((data.dl_throughput / 1048576) + 0.5) local upMbps = math.floor((data.ul_throughput / 1048576) + 0.5) speedTestResult = string.format("D:%d / U:%d Mbps", downMbps, upMbps) else speedTestResult = "Parse Error" end else speedTestResult = "Failed" end NetworkCenter.refreshUI() end, {"-c"}):start() end -- ========================================== -- 2. ACTIONS & STEALTH LOGIC -- ========================================== local function fastHideOpenVPN() local count = 0 local hideTimer hideTimer = hs.timer.doEvery(0.02, function() local app = hs.application.get("OpenVPN Connect") if app then app:hide() hs.applescript.applescript('tell application "System Events" to set visible of process "OpenVPN Connect" to false') local win = app:mainWindow() if win then win:setFrame(hs.geometry.rect(5000, 5000, 0, 0), 0) end end count = count + 1 if count > 75 then hideTimer:stop() end end) end local function watchForVPN() local attempts = 0 local watchTimer watchTimer = hs.timer.doEvery(1, function() attempts = attempts + 1 local checkIP = hs.execute("ifconfig | grep -A 1 'utun' | grep 'inet 10.'"):gsub("%s+", "") if checkIP ~= "" or attempts > 10 then NetworkCenter.updateStatus() watchTimer:stop() end end) end local function vpnAction(mode) if mode == "connect" then hs.alert.show("Connecting VPN...") fastHideOpenVPN() hs.execute(string.format('"%s" --connect-shortcut=%s', Config.openvpnPath, Config.vpnProfileID)) watchForVPN() else hs.alert.show("Disconnecting VPN...") hs.execute(string.format('"%s" --disconnect-shortcut', Config.openvpnPath)) hs.timer.doAfter(0.5, function() hs.execute("pkill -9 'OpenVPN Connect'") NetworkCenter.updateStatus() end) end end local function tailscaleAction(mode) local tsPath = "/Applications/Tailscale.app/Contents/MacOS/Tailscale" if mode == "up" then hs.alert.show("Starting Tailscale...") hs.execute(tsPath .. " up") else hs.alert.show("Stopping Tailscale...") hs.execute(tsPath .. " down") end hs.timer.doAfter(1, NetworkCenter.updateStatus) hs.timer.doAfter(3, NetworkCenter.updateStatus) end -- ========================================== -- 3. THE MENU UI -- ========================================== function NetworkCenter.refreshUI() if not menuBar then return end local isHome = (wifiInfo.ssid == Config.homeSSID) local locationStr = isHome and "HOME" or "REMOTE" local topIcon = isHome and "🏠" or "🌐" local tsActive = (tailscaleIP ~= "Offline") if vpnActive and tsActive then topIcon = topIcon .. "🛡️" elseif vpnActive then topIcon = topIcon .. "🔒" elseif tsActive then topIcon = topIcon .. "📡" end menuBar:setTitle(topIcon .. " " .. math.floor(wifiInfo.speed) .. "Mb") local shareSubMenu = {} for i, share in ipairs(Config.shares) do local isMounted = hs.fs.attributes("/Volumes/" .. share) ~= nil local sIcon = isMounted and "✅" or "⚪️" local titleText = sIcon .. " " .. share if isMounted then local capCmd = "df -h '/Volumes/" .. share .. "' | tail -1 | awk '{gsub(\"i\", \"b\", $2); print \"(\" $2 \")\"}'" titleText = titleText .. " " .. hs.execute(capCmd):gsub("\n", "") end table.insert(shareSubMenu, { title = titleText, fn = function() if isMounted then hs.execute("diskutil unmount /Volumes/" .. share) else hs.applescript.applescript(string.format('mount volume "smb://%s/%s"', Config.nasIP, share)) end hs.timer.doAfter(3, NetworkCenter.updateStatus) end }) if isMounted then local barCmd = "df -h '/Volumes/" .. share .. "' | tail -1 | awk '{p=int($5); bar=\"\"; for(i=1;i<=10;i++){if(i<=p/10) bar=bar \"■\"; else bar=bar \"□\"} gsub(\"i\", \"b\", $3); print \" [\" bar \"] \" p \"% (\" $3 \" Used)\"}'" table.insert(shareSubMenu, { title = hs.execute(barCmd):gsub("\n", ""), disabled = true }) table.insert(shareSubMenu, { title = " 📂 Browse Folder", fn = function() hs.execute("open /Volumes/" .. share) end }) table.insert(shareSubMenu, { title = "-" }) end end local labSubMenu = {} local labIcons = "" for i, svc in ipairs(services) do local status = serviceStatus[svc.name] or "⚪️" labIcons = labIcons .. status table.insert(labSubMenu, { title = status .. " " .. svc.name, fn = function() hs.execute(string.format("open http://%s:%s", svc.host, svc.port)) end }) end local ovpnHeader = "🔐 OPENVPN CONTROLS" .. (vpnActive and " (🟢 ACTIVE)" or "") local tsHeader = "🚀 TAILSCALE MESH" .. (tsActive and " (🟢 ACTIVE)" or "") local menu = { { title = "📍 LOCATION STATUS" }, { title = " ├─ Profile: " .. locationStr, disabled = true }, { title = " ├─ Gateway: " .. gatewayLatency, disabled = true }, { title = " ├─ Channel: " .. wifiInfo.channel .. " (" .. wifiInfo.band .. " GHz)", disabled = true }, { title = " ├─ Signal: " .. wifiInfo.rssi .. " dBm / Noise: " .. wifiInfo.noise .. " dBm", disabled = true }, { title = " └─ WiFi " .. wifiInfo.gen .. " (" .. wifiInfo.protocol .. ") @ " .. wifiInfo.speed .. " Mbps", disabled = true }, { title = "-" }, { title = "🚀 LAB SERVICES " .. labIcons, menu = labSubMenu }, { title = "-" }, { title = "🛡️ VPN & MESH NET" }, { title = " ├─ OpenVPN: " .. ovpnIP, disabled = true }, { title = " ├─ Tailscale IP: " .. tailscaleIP, disabled = true }, { title = " ├─ Tailnet: " .. tailnetDomain, disabled = true }, -- NEW: Display Domain { title = " ├─ ISP: " .. ispName, disabled = true }, { title = " └─ Public IP: " .. lastExternalIP, disabled = true }, { title = "-" }, { title = "📦 STORAGE & SHARES", menu = shareSubMenu }, { title = " ├─ NAS IP: " .. Config.nasIP, disabled = true }, { title = " └─ NAS Status: " .. (nasOnline and "🟢 Online" or "🔴 Offline"), disabled = true }, { title = "-" }, { title = "⚡️ SPEED TEST: " .. speedTestResult, fn = runSpeedTest }, { title = "-" }, { title = ovpnHeader }, { title = " ├─ 🔒 Connect tunnel", fn = function() vpnAction("connect") end }, { title = " └─ 🔓 Disconnect & Quit", fn = function() vpnAction("disconnect") end }, { title = "-" }, { title = tsHeader }, { title = " ├─ 🟢 Start Service", fn = function() tailscaleAction("up") end }, { title = " └─ 🔴 Stop Service", fn = function() tailscaleAction("down") end }, { title = "-" }, { title = "🔄 Refresh Status", fn = NetworkCenter.updateStatus } } menuBar:setMenu(menu) end NetworkCenter.updateStatus() hs.timer.doEvery(30, NetworkCenter.updateStatus) hs.alert.show("Network Center Platinum Loaded") return NetworkCenter