NA sees every campus; SSH from the admin laptop to every machine
Campuses set up from NA's install line tell NA where they are and learn nothing back (no campus ever shows another). NA alone reads each campus's key states every two seconds and keeps two pages in its Companion (added at the end, 98 and 99, since every other page is in use): CAMPUSES, a key per campus (its ALL key, purple when it stops answering), and CAMPUS, the tapped campus's status page drawn live in the same places, view only. Tried on NA with a test campus pointing at NA itself, then removed. tools/fleet.py: an Apple Remote Desktop command that turns on Remote Login, adds the laptop's key and reports each Mac to NA, and the laptop SSH shortcuts made from those reports (tried on NA Translation). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
"""SSH from the admin laptop to every machine on every campus.
|
||||
|
||||
python3 tools/fleet.py ard the command to paste into Apple Remote Desktop (Send UNIX Command, as root)
|
||||
python3 tools/fleet.py config write ~/.ssh/config.d/waters from the machines that reported back
|
||||
|
||||
The ARD command, on each Mac: turns on Remote Login, lets the logged-in user's account take this
|
||||
laptop's key (~/.ssh/id_ed25519_waters), and reports name, user and addresses to North Attleboro's
|
||||
settings page (/api/fleet/hello). `config` turns that list into shortcuts: ssh na-tracks-mac.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
HUB = os.environ.get("FLEET_HUB", "http://10.1.1.1:8020")
|
||||
KEY = os.path.expanduser("~/.ssh/id_ed25519_waters")
|
||||
OUT = os.path.expanduser("~/.ssh/config.d/waters")
|
||||
|
||||
|
||||
def pubkey():
|
||||
return open(KEY + ".pub").read().strip()
|
||||
|
||||
|
||||
def ard():
|
||||
key = pubkey()
|
||||
return r"""U=$(stat -f%Su /dev/console); if [ "$U" = root ] || [ -z "$U" ]; then echo "SKIPPED $(scutil --get ComputerName): nobody is logged in"; exit 0; fi
|
||||
H=$(dscl . -read "/Users/$U" NFSHomeDirectory | awk '{print $2}'); KEY='""" + key + r"""'
|
||||
mkdir -p "$H/.ssh" && touch "$H/.ssh/authorized_keys" && (grep -qF "$KEY" "$H/.ssh/authorized_keys" || echo "$KEY" >> "$H/.ssh/authorized_keys") && chown -R "$U:staff" "$H/.ssh" && chmod 700 "$H/.ssh" && chmod 600 "$H/.ssh/authorized_keys"
|
||||
nc -z -G 2 127.0.0.1 22 >/dev/null 2>&1 || systemsetup -setremotelogin on >/dev/null 2>&1 || launchctl load -w /System/Library/LaunchDaemons/ssh.plist >/dev/null 2>&1
|
||||
dseditgroup -o read com.apple.access_ssh >/dev/null 2>&1 && dseditgroup -o edit -a "$U" -t user com.apple.access_ssh >/dev/null 2>&1
|
||||
IPS=$(ifconfig | awk '/inet /{print $2}' | grep -v '^127\.' | sed 's/.*/"&"/' | paste -sd, -); N=$(scutil --get ComputerName | tr -d '"\\')
|
||||
curl -s -m 5 -X POST -H 'Content-Type: application/json' -d "{\"user\":\"$U\",\"name\":\"$N\",\"hostname\":\"$(scutil --get LocalHostName).local\",\"os\":\"mac\",\"ips\":[$IPS]}" """ + HUB + r"""/api/fleet/hello >/dev/null
|
||||
echo "OK $N user=$U ssh=$(nc -z -G 2 127.0.0.1 22 >/dev/null 2>&1 && echo on || echo OFF)"
|
||||
"""
|
||||
|
||||
|
||||
def slug(s):
|
||||
s = re.sub(r"\s*\(\d+\)$", "", s) # macOS's "Name (2)" after a rename clash
|
||||
return re.sub(r"[^a-z0-9]+", "-", s.lower()).strip("-") or "mac"
|
||||
|
||||
|
||||
def config():
|
||||
with urllib.request.urlopen(HUB + "/api/fleet", timeout=10) as r:
|
||||
machines = json.load(r)["machines"]
|
||||
wired = lambda ip: not ip.startswith(("10.6.", "169.254.", "127."))
|
||||
lines, used = ["# Written by tools/fleet.py from %s/api/fleet - every machine that took this laptop's key." % HUB], set()
|
||||
for m in sorted(machines, key=lambda m: m["name"].lower()):
|
||||
ip = next((i for i in m["ips"] if wired(i)), m["ips"][0] if m["ips"] else None)
|
||||
if not ip:
|
||||
continue
|
||||
alias = slug(m["name"])
|
||||
while alias in used:
|
||||
alias += "-2"
|
||||
used.add(alias)
|
||||
lines += ["", "Host %s" % alias, " HostName %s" % ip, " User %s" % m["user"],
|
||||
" IdentityFile %s" % KEY, " IdentitiesOnly yes", " ConnectTimeout 8"]
|
||||
os.makedirs(os.path.dirname(OUT), exist_ok=True)
|
||||
open(OUT, "w").write("\n".join(lines) + "\n")
|
||||
cfg = os.path.expanduser("~/.ssh/config")
|
||||
text = open(cfg).read() if os.path.exists(cfg) else ""
|
||||
if "Include ~/.ssh/config.d/*" not in text:
|
||||
open(cfg, "w").write("Include ~/.ssh/config.d/*\n\n" + text)
|
||||
print("%d machines -> %s: %s" % (len(used), OUT, ", ".join(sorted(used))))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
what = (sys.argv[1:] or ["ard"])[0]
|
||||
print(ard()) if what == "ard" else config()
|
||||
Reference in New Issue
Block a user