~kris/dots

srice

ref: 3bdfd3e0c26b75a939850355c30bee6fa9cc9abd srice/bin/status -rwxr-xr-x 9.4 KiB
3bdfd3e0 — Kris Yotam sync: lock down plan9 theme, wallpapers, nvim configs 3 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
#!/usr/bin/env python3
"""
Stargate Server Status Dashboard
A beautiful TUI dashboard for monitoring server status.

Usage:
    status          # One-time snapshot
    status -l       # Live mode (updates every 2s)
    status -l -r 5  # Live mode with 5s refresh
"""

import subprocess
import argparse
import sys
import signal
from datetime import datetime

from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.layout import Layout
from rich.live import Live
from rich.text import Text
from rich.align import Align
from rich import box

console = Console()

# ASCII art header
HEADER = """
 ╔═╗╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╔═╗
 ╚═╗ ║ ╠═╣╠╦╝║ ╦╠═╣ ║ ║╣
 ╚═╝ ╩ ╩ ╩╩╚═╚═╝╩ ╩ ╩ ╚═╝
"""

def ssh_command(cmd: str, timeout: int = 5) -> str:
    """Execute command on server via SSH."""
    try:
        result = subprocess.run(
            ["ssh", "-o", "ConnectTimeout=5", "server", cmd],
            capture_output=True,
            text=True,
            timeout=timeout
        )
        return result.stdout.strip() if result.returncode == 0 else ""
    except (subprocess.TimeoutExpired, Exception):
        return ""


def fetch_data() -> dict:
    """Fetch all data from server."""
    return {
        "uptime": ssh_command("uptime -p"),
        "load": ssh_command("cat /proc/loadavg | cut -d' ' -f1-3"),
        "memory": ssh_command("free -h | awk '/^Mem:/ {print $3\"/\"$2}'"),
        "onion_main": ssh_command("sudo cat /var/lib/tor/krisyotam/hostname"),
        "onion_notes": ssh_command("sudo cat /var/lib/tor/notes-krisyotam/hostname"),
        "nginx": ssh_command("systemctl is-active nginx"),
        "tor": ssh_command("systemctl is-active tor"),
        "disks": ssh_command("df -h / /home /mnt/storage 2>/dev/null | tail -n +2"),
        "services": ssh_command("docker service ls --format '{{.Name}}|{{.Replicas}}'"),
        "containers": ssh_command("docker ps --format '{{.Names}}|{{.Status}}' | head -12"),
    }


def make_header() -> Panel:
    """Create header panel."""
    header_text = Text(HEADER, style="bold magenta", justify="center")
    return Panel(
        Align.center(header_text),
        box=box.ROUNDED,
        border_style="magenta",
        padding=(0, 0),
    )


def make_onion_panel(data: dict) -> Panel:
    """Create Tor hidden services panel."""
    table = Table(show_header=False, box=None, padding=(0, 2))
    table.add_column("Site", style="bold white", width=22)
    table.add_column("Onion Address", style="green")

    onion_main = data.get("onion_main") or "[dim]unavailable[/dim]"
    onion_notes = data.get("onion_notes") or "[dim]unavailable[/dim]"

    table.add_row("krisyotam.com", onion_main)
    table.add_row("notes.krisyotam.com", onion_notes)

    return Panel(
        table,
        title="[bold cyan]TOR HIDDEN SERVICES[/bold cyan]",
        box=box.ROUNDED,
        border_style="cyan",
    )


def make_system_panel(data: dict) -> Panel:
    """Create system info panel."""
    table = Table(show_header=False, box=None, padding=(0, 2))
    table.add_column("Metric", style="bold white", width=15)
    table.add_column("Value", style="white")

    table.add_row("Uptime", data.get("uptime") or "unavailable")
    table.add_row("Load", data.get("load") or "unavailable")
    table.add_row("Memory", data.get("memory") or "unavailable")

    nginx_status = data.get("nginx", "")
    tor_status = data.get("tor", "")

    nginx_style = "[green]running[/green]" if nginx_status == "active" else "[red]stopped[/red]"
    tor_style = "[green]running[/green]" if tor_status == "active" else "[red]stopped[/red]"

    table.add_row("nginx", nginx_style)
    table.add_row("tor", tor_style)

    return Panel(
        table,
        title="[bold cyan]SYSTEM[/bold cyan]",
        box=box.ROUNDED,
        border_style="cyan",
    )


def make_disk_panel(data: dict) -> Panel:
    """Create disk usage panel."""
    table = Table(box=box.SIMPLE, padding=(0, 1))
    table.add_column("Mount", style="bold white", width=16)
    table.add_column("Size", justify="right", width=8)
    table.add_column("Used", justify="right", width=8)
    table.add_column("Avail", justify="right", width=8)
    table.add_column("Use%", justify="right", width=8)

    disks = data.get("disks", "")
    if disks:
        for line in disks.strip().split("\n"):
            if line:
                parts = line.split()
                if len(parts) >= 5:
                    fs = parts[0][:16]
                    size, used, avail = parts[1], parts[2], parts[3]
                    pct = parts[4].rstrip('%')

                    try:
                        pct_int = int(pct)
                        if pct_int >= 90:
                            pct_style = f"[red]{pct}%[/red]"
                        elif pct_int >= 70:
                            pct_style = f"[yellow]{pct}%[/yellow]"
                        else:
                            pct_style = f"[green]{pct}%[/green]"
                    except ValueError:
                        pct_style = f"{pct}%"

                    table.add_row(fs, size, used, avail, pct_style)

    return Panel(
        table,
        title="[bold cyan]DISK USAGE[/bold cyan]",
        box=box.ROUNDED,
        border_style="cyan",
    )


def make_services_panel(data: dict) -> Panel:
    """Create Docker Swarm services panel."""
    table = Table(box=box.SIMPLE, padding=(0, 1))
    table.add_column("Service", style="bold white", width=35)
    table.add_column("Replicas", justify="right", width=12)

    services = data.get("services", "")
    if services:
        for line in services.strip().split("\n"):
            if "|" in line:
                name, replicas = line.split("|", 1)
                if replicas.strip() == "1/1":
                    rep_style = f"[green]{replicas}[/green]"
                else:
                    rep_style = f"[red]{replicas}[/red]"
                table.add_row(name.strip(), rep_style)
    else:
        table.add_row("[dim]No services found[/dim]", "")

    return Panel(
        table,
        title="[bold cyan]DOCKER SWARM[/bold cyan]",
        box=box.ROUNDED,
        border_style="cyan",
    )


def make_containers_panel(data: dict) -> Panel:
    """Create Docker containers panel."""
    table = Table(box=box.SIMPLE, padding=(0, 1))
    table.add_column("Container", style="bold white", width=28)
    table.add_column("Status", width=30)

    containers = data.get("containers", "")
    if containers:
        for line in containers.strip().split("\n"):
            if "|" in line:
                name, status = line.split("|", 1)
                name = name.strip()[:28]
                status = status.strip()[:30]

                if "Up" in status:
                    status_style = f"[green]{status}[/green]"
                else:
                    status_style = f"[red]{status}[/red]"
                table.add_row(name, status_style)
    else:
        table.add_row("[dim]No containers found[/dim]", "")

    return Panel(
        table,
        title="[bold cyan]CONTAINERS[/bold cyan]",
        box=box.ROUNDED,
        border_style="cyan",
    )


def make_footer(live_mode: bool, refresh: int) -> Text:
    """Create footer text."""
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    if live_mode:
        return Text(f" Last updated: {timestamp} │ Refresh: {refresh}s │ Press Ctrl+C to exit ", style="dim")
    else:
        return Text(f" Last updated: {timestamp} │ Run with -l for live mode ", style="dim")


def build_dashboard(data: dict, live_mode: bool = False, refresh: int = 2) -> Layout:
    """Build the complete dashboard layout."""
    layout = Layout()

    layout.split_column(
        Layout(name="header", size=6),
        Layout(name="onion", size=6),
        Layout(name="middle", size=9),
        Layout(name="bottom"),
        Layout(name="footer", size=1),
    )

    layout["middle"].split_row(
        Layout(name="system"),
        Layout(name="disks"),
    )

    layout["bottom"].split_row(
        Layout(name="services"),
        Layout(name="containers"),
    )

    layout["header"].update(make_header())
    layout["onion"].update(make_onion_panel(data))
    layout["system"].update(make_system_panel(data))
    layout["disks"].update(make_disk_panel(data))
    layout["services"].update(make_services_panel(data))
    layout["containers"].update(make_containers_panel(data))
    layout["footer"].update(Align.center(make_footer(live_mode, refresh)))

    return layout


def main():
    parser = argparse.ArgumentParser(description="Stargate Server Status Dashboard")
    parser.add_argument("-l", "--live", action="store_true", help="Enable live updating mode")
    parser.add_argument("-r", "--refresh", type=int, default=2, help="Refresh interval in seconds (default: 2)")
    args = parser.parse_args()

    def signal_handler(sig, frame):
        console.clear()
        sys.exit(0)

    signal.signal(signal.SIGINT, signal_handler)

    if args.live:
        with Live(console=console, refresh_per_second=1, screen=True) as live:
            while True:
                data = fetch_data()
                live.update(build_dashboard(data, live_mode=True, refresh=args.refresh))
                import time
                time.sleep(args.refresh)
    else:
        console.clear()
        data = fetch_data()
        console.print(build_dashboard(data))


if __name__ == "__main__":
    main()