#!/usr/bin/env python3 """ FLEET CANVAS STITCHER & PHYSICAL MONITOR MATRIX Captures screens across the fleet (t520, bazina, boxC, penguin, android), stitches them according to the physical table/monitor canvas, aggregates context from ~/src/dnes, and saves into ~/.agents across fleet. """ import os import sys import json import glob import re import subprocess from datetime import datetime from PIL import Image, ImageDraw, ImageFont, ImageFilter HOME = os.path.expanduser("~") AGENTS_DIR = os.path.join(HOME, ".agents") SRC_AGENTS_DIR = os.path.join(HOME, "src", ".agents") os.makedirs(AGENTS_DIR, exist_ok=True) os.makedirs(SRC_AGENTS_DIR, exist_ok=True) # 1. Capture Screenshots def capture_fleet_screenshots(): print("[*] Capturing screenshots across fleet nodes...") # t520 local t520_path = "/tmp/scrot_t520.png" try: subprocess.run(["scrot", "-z", "-o", t520_path], check=True, env=dict(os.environ, DISPLAY=":0")) print(f" [βœ“] t520: captured ({t520_path})") except Exception as e: print(f" [-] t520 scrot error: {e}") # bazina remote bazina_path = "/tmp/scrot_bazina.png" try: cmd = 'DISPLAY=:0 scrot -z -o /tmp/scrot_bazina.png 2>/dev/null || DISPLAY=:0 XAUTHORITY=/home/kof/.Xauthority scrot -z -o /tmp/scrot_bazina.png' subprocess.run(["ssh", "-o", "ConnectTimeout=3", "bazina", cmd], check=True, timeout=5) subprocess.run(["scp", "-o", "ConnectTimeout=3", "bazina:/tmp/scrot_bazina.png", bazina_path], check=True, timeout=5) print(f" [βœ“] bazina: captured and transferred ({bazina_path})") except Exception as e: print(f" [-] bazina scrot error: {e}") # boxC remote boxc_path = "/tmp/scrot_boxc.png" try: cmd = 'DISPLAY=:0 scrot -z -o /tmp/scrot_boxc.png 2>/dev/null || sudo -u root DISPLAY=:0 scrot -z -o /tmp/scrot_boxc.png' subprocess.run(["ssh", "-o", "ConnectTimeout=3", "boxC", cmd], check=True, timeout=5) subprocess.run(["scp", "-o", "ConnectTimeout=3", "boxC:/tmp/scrot_boxc.png", boxc_path], check=True, timeout=5) print(f" [βœ“] boxC: captured and transferred ({boxc_path})") except Exception as e: print(f" [-] boxC scrot error: {e}") return { "t520": t520_path if os.path.exists(t520_path) else None, "bazina": bazina_path if os.path.exists(bazina_path) else None, "boxC": boxc_path if os.path.exists(boxc_path) else None } # 2. Harvest Context from ~/src/dnes def harvest_dnes_context(): journal_path = os.path.join(HOME, "src", "dnes", "journal.md") milestones = [] if os.path.exists(journal_path): with open(journal_path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if line.startswith("- ["): milestones.append(line) # Moonphase moon_summary = "🌘 UbΓ½vajΓ­cΓ­ srpek (15.4% osvΔ›tlenΓ­)" try: res = subprocess.run(["python3", "-c", "import sys; sys.path.insert(0, os.path.expanduser('~/bin')); import moonphase; print(moonphase.get_moon_metrics().get('summary'))"], capture_output=True, text=True) if res.stdout.strip(): moon_summary = res.stdout.strip() except Exception: pass return { "recent_milestones": milestones[-8:] if milestones else [], "moon_summary": moon_summary, "time_str": datetime.now().strftime("%Y-%m-%d %H:%M:%S CET") } # 3. Create Placeholder Screen Card def create_placeholder_card(title, width, height, ip, details): img = Image.new("RGB", (width, height), (18, 22, 28)) draw = ImageDraw.Draw(img) # Grid lines for x in range(0, width, 40): draw.line([(x, 0), (x, height)], fill=(28, 34, 44), width=1) for y in range(0, height, 40): draw.line([(0, y), (width, y)], fill=(28, 34, 44), width=1) # Frame border draw.rectangle([(2, 2), (width - 3, height - 3)], outline=(60, 80, 110), width=2) # Title bar draw.rectangle([(2, 2), (width - 3, 34)], fill=(32, 45, 62)) draw.text((12, 8), title, fill=(220, 235, 255)) draw.text((width - 140, 8), ip, fill=(120, 190, 255)) # Center terminal box tw, th = int(width * 0.85), int(height * 0.65) tx, ty = (width - tw) // 2, (height - th) // 2 + 10 draw.rectangle([(tx, ty), (tx + tw, ty + th)], fill=(10, 12, 16), outline=(45, 60, 80), width=2) # Text inside terminal y_text = ty + 18 draw.text((tx + 20, y_text), f"$ fleet-node-telemetry --node={title.split()[0].lower()}", fill=(120, 255, 120)) y_text += 26 for line in details: draw.text((tx + 20, y_text), line, fill=(180, 200, 220)) y_text += 22 # Status indicator draw.ellipse([(tx + tw - 24, ty + 12), (tx + tw - 12, ty + 24)], fill=(0, 230, 120)) return img # 4. Render Physical Canvas def render_canvas(screens, ctx): print("[*] Composing physical display canvas matrix...") # Canvas dimensions CANVAS_W = 4920 CANVAS_H = 2280 BG_COLOR = (20, 24, 30) GRID_COLOR = (30, 36, 46) canvas = Image.new("RGB", (CANVAS_W, CANVAS_H), BG_COLOR) draw = ImageDraw.Draw(canvas) # Draw desk blueprint grid for x in range(0, CANVAS_W, 60): draw.line([(x, 0), (x, CANVAS_H)], fill=GRID_COLOR, width=1) for y in range(0, CANVAS_H, 60): draw.line([(0, y), (CANVAS_W, y)], fill=GRID_COLOR, width=1) # Header Bar draw.rectangle([(0, 0), (CANVAS_W, 110)], fill=(12, 15, 20), outline=(40, 50, 65), width=2) draw.text((36, 20), "THE OBSERVATOŘ FLEET // PHYSICAL DISPLAY CANVAS MATRIX", fill=(255, 255, 255)) draw.text((36, 54), f"SYNERGY KVM 5-NODE MULTI-HEAD MESH ⨂ {ctx['time_str']} ⨂ {ctx['moon_summary']}", fill=(121, 172, 228)) # FCZ blue #79ACE4 draw.text((36, 80), "PTP GRANDMASTER (Intel 82579LM Β±6ns) ⨂ 50.000 Hz EU GRID TELEMETRY ⨂ ARCHER VR400 192.168.1.0/24", fill=(160, 175, 195)) # Top right badges draw.rectangle([(CANVAS_W - 540, 20), (CANVAS_W - 36, 90)], fill=(18, 24, 34), outline=(70, 90, 120), width=1) draw.text((CANVAS_W - 520, 32), "FLEET NETWORK HIERARCHY:", fill=(255, 220, 100)) draw.text((CANVAS_W - 520, 52), "L1: LAN 192.168.1.0/24 (Gigabit + WiFi)", fill=(200, 220, 240)) draw.text((CANVAS_W - 520, 70), "L2: WireGuard 10.39.9.0/24 Β· L3: OpenVPN", fill=(140, 180, 220)) # Monitor Bezel & Stand helper def paste_monitor(img, x, y, title, badge, is_server=False): mw, mh = img.size bw = 14 # Bezel thickness # Bezel frame bezel_x1, bezel_y1 = x - bw, y - bw - 28 bezel_x2, bezel_y2 = x + mw + bw, y + mh + bw + 14 # Outer shadow draw.rectangle([(bezel_x1 + 6, bezel_y1 + 6), (bezel_x2 + 6, bezel_y2 + 6)], fill=(10, 12, 16)) # Bezel body bezel_color = (35, 42, 52) if not is_server else (45, 56, 70) draw.rectangle([(bezel_x1, bezel_y1), (bezel_x2, bezel_y2)], fill=bezel_color, outline=(70, 85, 105), width=2) # Title bar on bezel draw.text((bezel_x1 + 14, bezel_y1 + 6), title, fill=(255, 255, 255)) draw.text((bezel_x2 - len(badge)*8 - 14, bezel_y1 + 6), badge, fill=(120, 220, 160) if not is_server else (255, 200, 80)) # Power LED draw.ellipse([(bezel_x2 - 18, bezel_y2 - 12), (bezel_x2 - 8, bezel_y2 - 2)], fill=(0, 255, 130)) # Paste actual screen canvas.paste(img, (x, y)) # Desk stand sw, sh = 120, 30 sx = x + (mw - sw) // 2 draw.rectangle([(sx, bezel_y2), (sx + sw, bezel_y2 + 10)], fill=(50, 60, 75)) draw.rectangle([(sx - 30, bezel_y2 + 10), (sx + sw + 30, bezel_y2 + 18)], fill=(30, 36, 45), outline=(60, 75, 95), width=1) # 1. BAZINA (Left-Top) bazina_img = None if screens.get("bazina") and os.path.exists(screens["bazina"]): bazina_img = Image.open(screens["bazina"]).convert("RGB") else: bazina_img = create_placeholder_card("BAZINA [LEFT-TOP]", 1920, 1080, "192.168.1.104", [ "OS: Debian GNU/Linux 13 (Trixie)", "CPU: AMD Ryzen 5 3600 (6c/12t) @ 4.2 GHz", "GPU: AMD Radeon RX 5600/5700 XT (Navi 10)", "Audio: ASUS VX248 HDMI (WirePlumber p1500)", "NBD Swap Server: 8GB tmpfs ramdisk -> t520", "Print/Scan: Brother DCP-J4120DW (CUPS 600 DPI)", "Storage: /mnt/ssd (1TB NVMe), /mnt/seagate (1TB)" ]) paste_monitor(bazina_img, 60, 160, "BAZINA (LEFT-TOP) // AMD RYZEN + RX 5700 XT", "192.168.1.104 [1920x1080]") # 2. PENGUIN (Left-Bottom) penguin_img = create_placeholder_card("PENGUIN [LEFT-BOTTOM]", 1366, 768, "192.168.1.105", [ "Hardware: Chromebook Field Node", "Identity: crostini [x86_64 Debian container]", "Role: Mobile field terminal & documentation", "Synergy KVM: Client connected (bottom-left)", "NTP: Synchronized to t520 Stratum 1 Grandmaster", "Network: 192.168.1.105 (WiFi) / 10.39.9.105 (WG)", "Physical placement: Left of t520 under bazina" ]) paste_monitor(penguin_img, 60, 1340, "PENGUIN (LEFT-BOTTOM) // CHROMEBOOK FIELD NODE", "192.168.1.105 [1366x768]") # 3. ANDROID (Under table left) android_img = create_placeholder_card("ANDROID [PHONE]", 470, 768, "192.168.1.103", [ "Device: Xiaomi 22101316G", "OS: Android 14 / Termux aarch64", "Port: 8022 (u0_a322)", "Sensors: OSC 9000/8000 -> bazina", "Sync: Syncthing phone-DCIM", "Status: Pod stolem vlevo" ]) paste_monitor(android_img, 60 + 1366 + 84, 1340, "ANDROID // TERMUX aarch64", "192.168.1.103:8022") # 4. T520 (Center KVM Server) t520_img = None if screens.get("t520") and os.path.exists(screens["t520"]): t520_img = Image.open(screens["t520"]).convert("RGB") else: t520_img = create_placeholder_card("T520 [CENTER / SERVER]", 1600, 900, "192.168.1.100", [ "Hardware: ThinkPad t520 (Intel i5-2520M)", "OS: Arch Linux x86_64", "Role: Synergy KVM Server (Port 24800)", "IEEE 1588 PTP Grandmaster: Intel 82579LM (/dev/ptp0)", "SDR Heat Scanner: RTL-SDR 868.95 MHz Wireless M-Bus", "Memory: 8GB zram0 (zstd) + 8GB NBD RAM swap bazina", "Power/ACPI: S3 freeze disabled, lid mute hook active" ]) paste_monitor(t520_img, 2060, 480, "T520 (CENTER SERVER / GRANDMASTER) // THINKPAD T520", "192.168.1.100 [1600x900]", is_server=True) # 5. BOXC (Right Node) boxc_img = None if screens.get("boxC") and os.path.exists(screens["boxC"]): boxc_img = Image.open(screens["boxC"]).convert("RGB") else: boxc_img = create_placeholder_card("BOXC [RIGHT]", 1024, 768, "192.168.1.101", [ "Hardware: Raspberry Pi 4 Model B (4GB RAM)", "OS: Debian GNU/Linux 12 (Bookworm aarch64)", "Display: PiJockey HDMI CRTC scanout (1024x768)", "VPN Host: WireGuard 10.39.9.1 / OpenVPN 10.39.10.1", "Storage: /mnt/kof17 (916GB USB disk NFS/SMB)", "Web: peΕ‘ek.com / Konzdat publishing origin" ]) paste_monitor(boxc_img, 3760, 546, "BOXC (RIGHT) // RASPBERRY PI 4 VPN & STORAGE", "192.168.1.101 [1024x768]") # Traversal Vectors (Synergy KVM Traversal Lines) arrow_color = (255, 190, 40) # Bright Amber # t520 <---> boxC draw.line([(2060 + 1600 + 14, 480 + 450), (3760 - 14, 546 + 384)], fill=arrow_color, width=3) draw.text((3700 - 80, 500), "<=== Synergy KVM ===>", fill=arrow_color) # t520 <---> penguin draw.line([(2060 - 14, 480 + 450), (60 + 1366 + 14, 1340 + 200)], fill=arrow_color, width=3) draw.text((1520, 880), "<=== Synergy KVM ===>", fill=arrow_color) # t520 <---> bazina draw.line([(2060 + 400, 480 - 14), (60 + 1600, 160 + 1080 + 14)], fill=arrow_color, width=3) draw.text((1700, 300), "<=== Synergy KVM ===>", fill=arrow_color) # bazina <---> penguin draw.line([(60 + 600, 160 + 1080 + 14), (60 + 600, 1340 - 14)], fill=arrow_color, width=3) draw.text((60 + 620, 1260), "KVM <^>", fill=arrow_color) # bazina <---> boxC draw.line([(60 + 1920 + 14, 160 + 300), (3760 - 14, 546 + 200)], fill=(100, 180, 255), width=2) draw.text((2900, 220), "<-- NFSv4 Gigabit Link (/mnt/ssd, /mnt/kof17) -->", fill=(100, 180, 255)) # Bottom Context Footer Bar (Aggregated from ~/src/dnes) draw.rectangle([(0, CANVAS_H - 130), (CANVAS_W, CANVAS_H)], fill=(12, 15, 20), outline=(40, 50, 65), width=2) draw.text((36, CANVAS_H - 118), "POSLEDNÍ ZÁPISY A MILNÍKY Z REPOZITÁŘE DNES (JOURNAL.MD):", fill=(255, 220, 80)) col1_x = 36 col2_x = CANVAS_W // 2 for i, m in enumerate(ctx["recent_milestones"][:6]): target_x = col1_x if i < 3 else col2_x target_y = CANVAS_H - 92 + (i % 3) * 26 # clean milestone text clean_m = m if len(m) < 140 else m[:137] + "..." draw.text((target_x, target_y), clean_m, fill=(200, 215, 235)) # Save files out_png = os.path.join(AGENTS_DIR, "fleet_canvas.png") out_jpg = os.path.join(AGENTS_DIR, "fleet_canvas.jpg") out_md = os.path.join(AGENTS_DIR, "fleet_canvas.md") print(f"[*] Saving master canvas to {out_png}...") canvas.save(out_png, "PNG", optimize=True) # Also save 0.5x preview JPEG for small-web viewing print(f"[*] Saving preview JPEG to {out_jpg}...") preview = canvas.resize((CANVAS_W // 2, CANVAS_H // 2), Image.Resampling.LANCZOS) preview.save(out_jpg, "JPEG", quality=88) # Write Markdown documentation with open(out_md, "w", encoding="utf-8") as f: f.write(f"""# The ObservatoΕ™ Fleet // Physical Display Canvas Matrix VygenerovΓ‘no: {ctx['time_str']} LunΓ‘rnΓ­ fΓ‘ze: {ctx['moon_summary']} ![Fleet Physical Canvas]({out_png}) ## Topologie fyzickΓ©ho plΓ‘tna obrazovek a KVM (Synergy) ``` ╔═══════════════════════════════════════════════════════════════════════════════════════════════════════╗ β•‘ [LEVÝ HORNÍ] BAZINA β”‚ [STŘED / KVM SERVER] T520 β”‚ [PRAVÝ] BOXC β•‘ β•‘ 192.168.1.104 Β· 1920x1080 β”‚ 192.168.1.100 Β· 1600x900 β”‚ 192.168.1.101 Β· 1024x768β•‘ β•‘ Debian 13 Β· Ryzen Β· RX 5700 XT β”‚ Arch Linux Β· ThinkPad Β· Intel HD 3000 β”‚ Pi 4 Β· PiJockey HDMI β•‘ ║────────────────────────────────────┴─────────────────────────────────────────┴────────────────────────║ β•‘ [LEVÝ DOLNÍ] PENGUIN β”‚ [POD STOLEM VLEVO] ANDROID β•‘ β•‘ 192.168.1.105 Β· 1366x768 β”‚ 192.168.1.103:8022 Β· Termux aarch64 β•‘ β•‘ Crostini Chromebook x86_64 β”‚ SenzorickΓ‘ telemetrie OSC β•‘ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• ``` ## SouhrnnΓ© parametry zachycenΓ­ - **t520 (Server):** 1600x900, Arch Linux, PTP Grandmaster (Β±6 ns), SDR 868.95 MHz radiΓ‘tory - **bazina:** 1920x1080, Debian 13 Trixie, NBD 8GB swap server, Brother DCP-J4120DW CUPS - **boxC:** 1024x768, Raspberry Pi 4, WireGuard VPN (10.39.9.1), OpenVPN (10.39.10.1), NFS /mnt/kof17 - **penguin:** 1366x768, Chromebook crostini mobilnΓ­ terΓ©nnΓ­ stanice - **android:** 192.168.1.103:8022, Xiaomi Termux aarch64 ## PoslednΓ­ zaznamenanΓ© milnΓ­ky z repozitΓ‘Ε™e dnes: """) for m in ctx["recent_milestones"]: f.write(f"{m}\n") # Synchronize across fleet (.agents directories) print("[*] Synchronizing canvas across fleet nodes...") # Copy to src/.agents subprocess.run(["cp", out_png, out_jpg, out_md, SRC_AGENTS_DIR], check=True) # Copy to bazina:~/.agents/ try: subprocess.run(["ssh", "bazina", "mkdir -p ~/.agents"], timeout=3) subprocess.run(["scp", "-q", out_png, out_jpg, out_md, "bazina:~/.agents/"], timeout=8) print(" [βœ“] bazina: ~/.agents synchronized") except Exception as e: print(f" [-] bazina sync warning: {e}") # Copy to boxC:~/.agents/ and /var/www/html/konzdat/assets/ try: subprocess.run(["ssh", "boxC", "mkdir -p ~/.agents /var/www/html/konzdat/assets"], timeout=3) subprocess.run(["scp", "-q", out_png, out_jpg, out_md, "boxC:~/.agents/"], timeout=8) subprocess.run(["scp", "-q", out_png, out_jpg, "boxC:/var/www/html/konzdat/assets/"], timeout=8) subprocess.run(["ssh", "boxC", "ln -sfn /var/www/html/konzdat/assets/fleet_canvas.png /var/www/html/canvas.png"], timeout=3) print(" [βœ“] boxC: ~/.agents and web http://peΕ‘ek.com/canvas.png synchronized") except Exception as e: print(f" [-] boxC sync warning: {e}") print(f"[βœ“] Fleet canvas complete! Master saved to {out_png}") return out_png def main(): screens = capture_fleet_screenshots() ctx = harvest_dnes_context() render_canvas(screens, ctx) if __name__ == "__main__": main()