sysreport.py(檔案已創建)
| @@ -0,0 +1,131 @@ | |||
| 1 | + | #!/usr/bin/env python3 | |
| 2 | + | """ | |
| 3 | + | sysreport.py — Genera un reporte completo del sistema en JSON. | |
| 4 | + | Usa jc para convertir la salida de comandos del sistema a JSON | |
| 5 | + | estructurado. Ideal para diagnosticar, auditar o pasar a una IA. | |
| 6 | + | ||
| 7 | + | Uso: | |
| 8 | + | python3 sysreport.py # Guarda en /tmp/sysreport.json | |
| 9 | + | python3 sysreport.py --stdout # Imprime por stdout | |
| 10 | + | python3 sysreport.py --compact # Sin pretty-print | |
| 11 | + | python3 sysreport.py --only discos # Solo una sección | |
| 12 | + | ||
| 13 | + | Dependencias: | |
| 14 | + | - Python 3.10+ | |
| 15 | + | - jc (pip install jc) | |
| 16 | + | - jq (sudo apt install jq) | |
| 17 | + | - Los comandos del sistema: ps, df, systemctl, ss, free, uptime | |
| 18 | + | """ | |
| 19 | + | ||
| 20 | + | import argparse | |
| 21 | + | import json | |
| 22 | + | import subprocess | |
| 23 | + | import sys | |
| 24 | + | from datetime import datetime | |
| 25 | + | ||
| 26 | + | ||
| 27 | + | # ─── Secciones del reporte ──────────────────────────────────────────────────── | |
| 28 | + | ||
| 29 | + | SECCIONES = { | |
| 30 | + | "procesos": (["ps", "aux"], "ps"), | |
| 31 | + | "discos": (["df", "-h"], "df"), | |
| 32 | + | "servicios": (["systemctl", "list-units", "--no-legend"], "systemctl"), | |
| 33 | + | "puertos": (["ss", "-tlnp"], "ss"), | |
| 34 | + | "memoria": (["free", "-h"], "free"), | |
| 35 | + | "uptime": (["uptime"], "uptime"), | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | ||
| 39 | + | def ejecutar_jc(comando: list, parser: str) -> list | dict: | |
| 40 | + | """Ejecuta un comando y lo parsea con jc.""" | |
| 41 | + | try: | |
| 42 | + | out = subprocess.run(comando, capture_output=True, text=True, timeout=10) | |
| 43 | + | if out.returncode != 0: | |
| 44 | + | return {"error": f"Comando falló: {' '.join(comando)}"} | |
| 45 | + | # Parsear con jc | |
| 46 | + | jc_out = subprocess.run( | |
| 47 | + | ["jc", f"--{parser}"], | |
| 48 | + | input=out.stdout, | |
| 49 | + | capture_output=True, | |
| 50 | + | text=True, | |
| 51 | + | timeout=10, | |
| 52 | + | ) | |
| 53 | + | if jc_out.returncode != 0: | |
| 54 | + | return {"error": f"jc falló para {parser}: {jc_out.stderr[:200]}"} | |
| 55 | + | return json.loads(jc_out.stdout) | |
| 56 | + | except subprocess.TimeoutExpired: | |
| 57 | + | return {"error": f"Timeout ejecutando {' '.join(comando)}"} | |
| 58 | + | except json.JSONDecodeError as e: | |
| 59 | + | return {"error": f"JSON inválido de jc --{parser}: {e}"} | |
| 60 | + | except FileNotFoundError: | |
| 61 | + | return {"error": f"jc no encontrado. Instala: pip install jc"} | |
| 62 | + | ||
| 63 | + | ||
| 64 | + | def generar_reporte(secciones: list[str] | None = None) -> dict: | |
| 65 | + | """Genera el reporte del sistema.""" | |
| 66 | + | incluir = secciones if secciones else list(SECCIONES.keys()) | |
| 67 | + | ||
| 68 | + | reporte = { | |
| 69 | + | "fecha": datetime.now().isoformat(), | |
| 70 | + | "hostname": subprocess.run( | |
| 71 | + | ["hostname"], capture_output=True, text=True, timeout=5 | |
| 72 | + | ).stdout.strip(), | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | for seccion in incluir: | |
| 76 | + | if seccion in SECCIONES: | |
| 77 | + | comando, parser = SECCIONES[seccion] | |
| 78 | + | reporte[seccion] = ejecutar_jc(comando, parser) | |
| 79 | + | ||
| 80 | + | return reporte | |
| 81 | + | ||
| 82 | + | ||
| 83 | + | # ─── Main ───────────────────────────────────────────────────────────────────── | |
| 84 | + | ||
| 85 | + | def main(): | |
| 86 | + | parser = argparse.ArgumentParser( | |
| 87 | + | description="sysreport: reporte del sistema en JSON usando jc", | |
| 88 | + | formatter_class=argparse.RawDescriptionHelpFormatter, | |
| 89 | + | epilog="""\ | |
| 90 | + | Ejemplos: | |
| 91 | + | python3 sysreport.py # Guarda en /tmp/sysreport.json | |
| 92 | + | python3 sysreport.py --stdout # Imprime por stdout | |
| 93 | + | python3 sysreport.py --only procesos discos # Solo procesos y discos | |
| 94 | + | python3 sysreport.py --stdout | jq '.discos' # Filtrar con jq | |
| 95 | + | """, | |
| 96 | + | ) | |
| 97 | + | parser.add_argument("--stdout", action="store_true", help="Imprimir por stdout en vez de guardar") | |
| 98 | + | parser.add_argument("--compact", action="store_true", help="Sin pretty-print (más pequeño)") | |
| 99 | + | parser.add_argument("--only", nargs="+", choices=list(SECCIONES.keys()), | |
| 100 | + | help=f"Solo estas secciones: {', '.join(SECCIONES.keys())}") | |
| 101 | + | ||
| 102 | + | args = parser.parse_args() | |
| 103 | + | ||
| 104 | + | print("📡 Generando reporte del sistema...", file=sys.stderr) | |
| 105 | + | reporte = generar_reporte(args.only) | |
| 106 | + | ||
| 107 | + | indent = None if args.compact else 2 | |
| 108 | + | ||
| 109 | + | if args.stdout: | |
| 110 | + | print(json.dumps(reporte, indent=indent, ensure_ascii=False)) | |
| 111 | + | else: | |
| 112 | + | output_path = "/tmp/sysreport.json" | |
| 113 | + | with open(output_path, "w") as f: | |
| 114 | + | json.dump(reporte, f, indent=indent, ensure_ascii=False) | |
| 115 | + | print(f"✅ Reporte guardado en {output_path}", file=sys.stderr) | |
| 116 | + | print(f"📦 Tamaño: {len(json.dumps(reporte))} caracteres", file=sys.stderr) | |
| 117 | + | print(f" Secciones: {', '.join(reporte.keys())}", file=sys.stderr) | |
| 118 | + | ||
| 119 | + | # Mostrar resumen rápido | |
| 120 | + | errores = [k for k, v in reporte.items() if isinstance(v, dict) and "error" in v] | |
| 121 | + | if errores: | |
| 122 | + | print(f"⚠️ Errores en: {', '.join(errores)}", file=sys.stderr) | |
| 123 | + | ||
| 124 | + | # Contar elementos por sección | |
| 125 | + | for k, v in reporte.items(): | |
| 126 | + | if isinstance(v, list): | |
| 127 | + | print(f" {k}: {len(v)} elementos", file=sys.stderr) | |
| 128 | + | ||
| 129 | + | ||
| 130 | + | if __name__ == "__main__": | |
| 131 | + | main() | |
上一頁
下一頁