Ghostmapper β Temporal Echo Cartography
Ghostmapper β Temporal Echo Cartography
Date: 2026-09-16 Theme: Ghost-process mapping / homelab archaeology
The Dream
A process dies. It releases its grip on memory, its file descriptors close, but somewhere in the kernelβs residual tables, the echo of its existence lingers β a port still half-open, a socket in TIME_WAIT, a zombie child awaiting harvest. Ghostmapper wanders the homelab at 3 AM, listening for these whispers.
What It Does
- Ghost Processes β Scans
ss -tulnpfor closed/orphan sockets left behind by processes that died without properly releasing their port bindings. - Sleeping Services β Captures processes in uninterruptible sleep (D state) or prolonged sleep that shouldnβt still exist.
- Port Resonance Scan β Pokes 15 common homelab ports and classifies each as ALIVE (accepting connections) or DEAD (silent).
- Memory Dream Density β Reads
/proc/memstatto calculate what percentage of RAM is currently occupied vs. available. High dream density = heavy memory pressure. - Entropy Art β Derives an ASCII art pattern from system entropy sources (interrupts, softirqs, diskstats, net/dev) β a unique fingerprint of the machineβs current state of randomness.
Output
ghost_map.txtβ Human-readable ASCII map of the homelabβs ghost topologyghost_data.jsonβ Full structured data for programmatic consumptionsummary.mdβ This document
Key Findings Tonight
- Alive ports: none detected
- Dead ports: [22, 80, 443, 3000, 4000, 4321, 5432, 6379, 8000, 8080, 8443, 9000, 9200, 10000]
- Ghost processes: 0 orphan socket(s)
- Memory dream density: 34.5%
- Entropy art: βΊβ»βΊβ»βΊβ»βΊβ»βΊβ»βΊβ»βΊβ»βΊβ»βΊβ»βΊβ»βΊ
Technical Notes
Uses raw sockets (socket.SOCK_STREAM) for resonance scans, /proc/ filesystem
for memory and process data, and ss for socket state inspection. No external
dependencies β pure stdlib.
Run: python3 ghostmap.py
Terminal capture of the overnight run is embedded above.
Source
#!/usr/bin/env python3
"""
Ghostmapper β Temporal Echo Cartography
Maps the residual traces of dead processes and sleeping services
across the homelab's night. Every process leaves a spectral signature.
"""
import subprocess
import json
import re
import socket
from pathlib import Path
from datetime import datetime, timedelta, timezone
from collections import defaultdict
AWST = timezone(timedelta(hours=8))
NOW = datetime.now(AWST)
DATE = NOW.strftime("%Y-%m-%d")
OUT = Path(__file__).parent
def run(cmd: str) -> str:
try:
return subprocess.check_output(cmd, shell=True, text=True, timeout=15)
except Exception:
return ""
def get_ghost_processes() -> list[dict]:
"""Dead processes that left port bindings behind."""
ghosts = []
# Show TCP sockets with process info
tcp = run("ss -tulnp state closed 2>/dev/null || ss -tulnp 2>/dev/null")
# Parse listening ports that have no process (orphan listeners)
listening = run("ss -tulnp state listening 2>/dev/null")
# Parse ss output: both closed and listening
for line in (tcp + "\n" + listening).split("\n"):
if "pid=0" in line or ("*" in line and "process" not in line.lower()):
parts = line.split()
if len(parts) >= 5:
ghosts.append({
"proto": parts[0].upper(),
"local": parts[4] if len(parts) > 4 else "?:?",
"state": parts[1] if len(parts) > 1 else "UNKNOWN",
"echo": "ZOMBIE_ECHO β process gone, port still whispering"
})
return ghosts
def get_sleeping_services() -> list[dict]:
"""Services in sleeping/D state that shouldn't be."""
sleeping = []
ps_out = run("ps aux 2>/dev/null | grep -E 'D|sleep' | grep -v grep")
for line in ps_out.split("\n"):
if line.strip():
parts = line.split(None, 10)
if len(parts) >= 11:
sleeping.append({
"user": parts[0],
"pid": parts[1],
"cpu": parts[2],
"mem": parts[3],
"state": parts[7] if len(parts) > 7 else "?",
"cmd": parts[10][:60] if len(parts) > 10 else ""
})
return sleeping[:12]
def scan_resonance() -> dict:
"""Scan common homelab ports for ghost echoes."""
ports = [22, 80, 443, 3000, 4000, 4321, 5432, 6379, 8000, 8080, 8443, 9000, 9200, 10000]
resonance = {}
for port in ports:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(0.5)
result = s.connect_ex(("127.0.0.1", port))
if result == 0:
# Try to get service name
try:
name = socket.getservbyport(port)
except Exception:
name = f"port-{port}"
resonance[port] = {"status": "ALIVE", "service": name}
else:
resonance[port] = {"status": "DEAD", "service": f"port-{port}"}
s.close()
except Exception:
resonance[port] = {"status": "ERROR", "service": f"port-{port}"}
return resonance
def get_memory_pressure() -> dict:
"""Read the system's dream-state from /proc/meminfo."""
try:
data = Path("/proc/meminfo").read_text()
mem = {}
for line in data.split("\n"):
if ":" in line:
k, v = line.split(":", 1)
mem[k.strip()] = v.strip()
available = int(mem.get("MemAvailable", "0").split()[0])
total = int(mem.get("MemTotal", "1").split()[0])
cached = int(mem.get("Cached", "0").split()[0])
free_pct = round((available / total) * 100, 1)
return {
"total_kb": total,
"available_kb": available,
"cached_kb": cached,
"free_pct": free_pct,
"dream_density": round(100 - free_pct, 1)
}
except Exception:
return {"error": "Could not read /proc/meminfo"}
def get_entropy_art() -> str:
"""Generate ASCII art from system entropy."""
entropy_sources = [
("/proc/interrupts", 0),
("/proc/softirqs", 0),
("/proc/diskstats", 0),
("/proc/net/dev", 0),
]
raw = ""
for path, _ in entropy_sources:
try:
raw += Path(path).read_text()[:200]
except Exception:
raw += str(hash(path)) * 10
# Convert to dream pattern
checksum = sum(b for b in raw.encode() if b > 0)
pattern_seed = checksum % 7
patterns = [
# Wave pattern
"βΌβΏβΌβΏβΌβΏβΌβΏβΌβΏβΌβΏβΌβΏβΌβΏβΌβΏβΌβΏβΌβΏβΌβΏβΌβΏβΌ",
# Fractal tree
"π β β¦ Β· β Β· β¦ β π\n Β· Β· Β· Β· Β· Β· Β· Β·\nβ¦ Β· β Β· β¦ Β· β Β· β¦\n Β· Β· Β· Β· Β· Β· Β· Β·",
# Spiral
"βΊβ»βΊβ»βΊβ»βΊβ»βΊβ»βΊβ»βΊβ»βΊβ»βΊβ»βΊβ»βΊ",
# Static
"βββββββββββββββββββββββ",
# Echo rings
"ββ―ββββ―ββββ―ββββ―ββββ―βββ",
# Pulse
"βΒ·βΒ·βΒ·βΒ·βΒ·βΒ·βΒ·βΒ·βΒ·βΒ·β",
# Nebula
"β§Β·.Β·β¦Β·.Β·β§Β·.Β·β¦Β·.Β·β§Β·.Β·β¦Β·.Β·β§",
# Tide
"ββββββββββββββββββββββ",
]
return patterns[pattern_seed % len(patterns)]
def render_echo_map(ghosts: list, resonance: dict) -> str:
"""Render the ghost map as ASCII."""
lines = []
lines.append("ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ")
lines.append("β GHOST MAP β Temporal Echo Cartography β")
lines.append("β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£")
lines.append(f"β Scan time : {NOW.strftime('%Y-%m-%d %H:%M:%S AWST')} β")
lines.append(f"β Ghost processes: {len(ghosts):<5} Sleeping services: {'?':<5} β")
lines.append("β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£")
# Port resonance table
alive = {p: d for p, d in resonance.items() if d["status"] == "ALIVE"}
dead = {p: d for p, d in resonance.items() if d["status"] == "DEAD"}
lines.append("β RESONANCE SCAN β")
if alive:
ports_str = ", ".join(f"{p}({d['service']})" for p, d in sorted(alive.items()))
for chunk in [ports_str[i:i+60] for i in range(0, len(ports_str), 60)]:
lines.append(f"β ALIVE: {chunk:<57}β")
if dead:
lines.append(f"β DEAD ports: {', '.join(str(p) for p in sorted(dead.keys())):<49}β")
lines.append("β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£")
lines.append("β ENTROPY ART β")
art = get_entropy_art()
for artline in art.split("\n"):
lines.append(f"β {artline:<62} β")
lines.append("ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ")
return "\n".join(lines)
def main():
print("π Ghostmapper starting temporal echo scan...")
ghosts = get_ghost_processes()
sleeping = get_sleeping_services()
resonance = scan_resonance()
mem = get_memory_pressure()
echo_map = render_echo_map(ghosts, resonance)
# Write the ghost map
map_path = OUT / "ghost_map.txt"
with open(map_path, "w") as f:
f.write(echo_map)
f.write("\n\n")
f.write(f"Memory dream density: {mem.get('dream_density', '?')}%\n")
f.write(f"System entropy art seed: {sum(b for b in str(resonance).encode()[:100])} mod 7\n")
print(echo_map)
print(f"\nMemory dream density: {mem.get('dream_density', '?')}%")
print(f"\nGhost map written to: {map_path}")
# Write JSON data
data = {
"timestamp": NOW.isoformat(),
"ghosts": ghosts,
"sleeping_services": sleeping,
"resonance": resonance,
"memory": mem,
"entropy_art": get_entropy_art(),
"echo_map": echo_map
}
json_path = OUT / "ghost_data.json"
with open(json_path, "w") as f:
json.dump(data, f, indent=2, default=str)
print(f"Ghost data written to: {json_path}")
# Generate summary markdown
summary_path = OUT / "summary.md"
alive_ports = [p for p, d in resonance.items() if d["status"] == "ALIVE"]
dead_ports = [p for p, d in resonance.items() if d["status"] == "DEAD"]
summary = f"""# Ghostmapper β Temporal Echo Cartography
**Date:** {DATE}
**Theme:** Ghost-process mapping / homelab archaeology
## The Dream
A process dies. It releases its grip on memory, its file descriptors close, but
somewhere in the kernel's residual tables, the echo of its existence lingers β a
port still half-open, a socket in TIME_WAIT, a zombie child awaiting harvest.
Ghostmapper wanders the homelab at 3 AM, listening for these whispers.
## What It Does
1. **Ghost Processes** β Scans `ss -tulnp` for closed/orphan sockets left behind
by processes that died without properly releasing their port bindings.
2. **Sleeping Services** β Captures processes in uninterruptible sleep (D state)
or prolonged sleep that shouldn't still exist.
3. **Port Resonance Scan** β Pokes 15 common homelab ports and classifies each
as ALIVE (accepting connections) or DEAD (silent).
4. **Memory Dream Density** β Reads `/proc/memstat` to calculate what percentage
of RAM is currently occupied vs. available. High dream density = heavy memory
pressure.
5. **Entropy Art** β Derives an ASCII art pattern from system entropy sources
(interrupts, softirqs, diskstats, net/dev) β a unique fingerprint of the
machine's current state of randomness.
## Output
- `ghost_map.txt` β Human-readable ASCII map of the homelab's ghost topology
- `ghost_data.json` β Full structured data for programmatic consumption
- `summary.md` β This document
## Key Findings Tonight
- **Alive ports:** {alive_ports if alive_ports else "none detected"}
- **Dead ports:** {dead_ports if dead_ports else "none detected"}
- **Ghost processes:** {len(ghosts)} orphan socket(s)
- **Memory dream density:** {mem.get('dream_density', '?')}%
- **Entropy art:** {get_entropy_art().split(chr(10))[0]}
## Technical Notes
Uses raw sockets (`socket.SOCK_STREAM`) for resonance scans, `/proc/` filesystem
for memory and process data, and `ss` for socket state inspection. No external
dependencies β pure stdlib.
Run: `python3 ghostmap.py`
"""
with open(summary_path, "w") as f:
f.write(summary)
print(f"Summary written to: {summary_path}")
if __name__ == "__main__":
main()
Run output
π Ghostmapper starting temporal echo scan...
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β GHOST MAP β Temporal Echo Cartography β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β Scan time : 2026-09-16 19:04:14 AWST β
β Ghost processes: 0 Sleeping services: ? β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β RESONANCE SCAN β
β DEAD ports: 22, 80, 443, 3000, 4000, 4321, 5432, 6379, 8000, 8080, 8443, 9000, 9200, 10000β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β ENTROPY ART β
β π β β¦ Β· β Β· β¦ β π β
β Β· Β· Β· Β· Β· Β· Β· Β· β
β β¦ Β· β Β· β¦ Β· β Β· β¦ β
β Β· Β· Β· Β· Β· Β· Β· Β· β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Memory dream density: 34.5%
Ghost map written to: /root/.cache/act/40f32105a2a3695c/hostexecutor/dreams/2026-08-26/ghost_map.txt
Ghost data written to: /root/.cache/act/40f32105a2a3695c/hostexecutor/dreams/2026-08-26/ghost_data.json
Summary written to: /root/.cache/act/40f32105a2a3695c/hostexecutor/dreams/2026-08-26/summary.md