@qubiit/lmagent 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +18 -0
- package/AGENTS.md +169 -0
- package/CLAUDE.md +122 -0
- package/CONTRIBUTING.md +90 -0
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/config/commands.yaml +194 -0
- package/config/levels.yaml +135 -0
- package/config/models.yaml +192 -0
- package/config/settings.yaml +405 -0
- package/config/tools-extended.yaml +534 -0
- package/config/tools.yaml +437 -0
- package/docs/assets/logo.png +0 -0
- package/docs/commands.md +132 -0
- package/docs/customization-guide.md +445 -0
- package/docs/getting-started.md +154 -0
- package/docs/how-to-start.md +242 -0
- package/docs/navigation-index.md +227 -0
- package/docs/usage-guide.md +113 -0
- package/install.js +1044 -0
- package/package.json +35 -0
- package/pyproject.toml +182 -0
- package/rules/_bootstrap.md +138 -0
- package/rules/agents-ia.md +607 -0
- package/rules/api-design.md +337 -0
- package/rules/automations-n8n.md +646 -0
- package/rules/code-style.md +570 -0
- package/rules/documentation.md +98 -0
- package/rules/security.md +316 -0
- package/rules/stack.md +395 -0
- package/rules/testing.md +326 -0
- package/rules/workflow.md +353 -0
- package/scripts/create_skill.js +300 -0
- package/scripts/validate_skills.js +283 -0
- package/skills/ai-agent-engineer/SKILL.md +394 -0
- package/skills/ai-agent-engineer/references/agent-patterns.md +149 -0
- package/skills/api-designer/SKILL.md +429 -0
- package/skills/api-designer/references/api-standards.md +13 -0
- package/skills/architect/SKILL.md +285 -0
- package/skills/architect/references/c4-model.md +133 -0
- package/skills/automation-engineer/SKILL.md +352 -0
- package/skills/automation-engineer/references/n8n-patterns.md +127 -0
- package/skills/backend-engineer/SKILL.md +261 -0
- package/skills/backend-engineer/assets/fastapi-project-structure.yaml +74 -0
- package/skills/backend-engineer/references/debugging-guide.md +174 -0
- package/skills/backend-engineer/references/design-patterns.md +208 -0
- package/skills/backend-engineer/scripts/scaffold_backend.py +313 -0
- package/skills/bmad-methodology/SKILL.md +202 -0
- package/skills/bmad-methodology/references/scale-adaptive-levels.md +141 -0
- package/skills/browser-agent/SKILL.md +502 -0
- package/skills/browser-agent/scripts/playwright_setup.ts +16 -0
- package/skills/code-reviewer/SKILL.md +306 -0
- package/skills/code-reviewer/references/code-review-checklist.md +16 -0
- package/skills/data-engineer/SKILL.md +474 -0
- package/skills/data-engineer/assets/pg-monitoring-queries.sql +154 -0
- package/skills/data-engineer/references/index-strategy.md +128 -0
- package/skills/data-engineer/scripts/backup_postgres.py +221 -0
- package/skills/devops-engineer/SKILL.md +547 -0
- package/skills/devops-engineer/references/ci-cd-patterns.md +265 -0
- package/skills/devops-engineer/scripts/docker_healthcheck.py +125 -0
- package/skills/document-generator/SKILL.md +746 -0
- package/skills/document-generator/references/pdf-generation.md +22 -0
- package/skills/frontend-engineer/SKILL.md +532 -0
- package/skills/frontend-engineer/references/accessibility-guide.md +146 -0
- package/skills/frontend-engineer/scripts/audit_bundle.py +144 -0
- package/skills/git-workflow/SKILL.md +374 -0
- package/skills/git-workflow/references/git-flow.md +25 -0
- package/skills/mcp-builder/SKILL.md +471 -0
- package/skills/mcp-builder/references/mcp-server-guide.md +23 -0
- package/skills/mobile-engineer/SKILL.md +502 -0
- package/skills/mobile-engineer/references/platform-guidelines.md +160 -0
- package/skills/orchestrator/SKILL.md +246 -0
- package/skills/orchestrator/references/methodology-routing.md +117 -0
- package/skills/orchestrator/references/persona-mapping.md +85 -0
- package/skills/orchestrator/references/routing-logic.md +110 -0
- package/skills/performance-engineer/SKILL.md +549 -0
- package/skills/performance-engineer/references/caching-patterns.md +181 -0
- package/skills/performance-engineer/scripts/profile_endpoint.py +170 -0
- package/skills/product-manager/SKILL.md +488 -0
- package/skills/product-manager/references/prioritization-frameworks.md +126 -0
- package/skills/prompt-engineer/SKILL.md +433 -0
- package/skills/prompt-engineer/references/prompt-patterns.md +158 -0
- package/skills/qa-engineer/SKILL.md +441 -0
- package/skills/qa-engineer/references/testing-strategy.md +166 -0
- package/skills/qa-engineer/scripts/run_coverage.py +147 -0
- package/skills/scrum-master/SKILL.md +225 -0
- package/skills/scrum-master/references/sprint-ceremonies.md +159 -0
- package/skills/security-analyst/SKILL.md +390 -0
- package/skills/security-analyst/references/owasp-top10.md +188 -0
- package/skills/security-analyst/scripts/audit_security.py +242 -0
- package/skills/seo-auditor/SKILL.md +523 -0
- package/skills/seo-auditor/references/seo-checklist.md +17 -0
- package/skills/spec-driven-dev/SKILL.md +342 -0
- package/skills/spec-driven-dev/references/phase-gates.md +107 -0
- package/skills/supabase-expert/SKILL.md +602 -0
- package/skills/supabase-expert/references/supabase-patterns.md +19 -0
- package/skills/swe-agent/SKILL.md +311 -0
- package/skills/swe-agent/references/trajectory-format.md +134 -0
- package/skills/systematic-debugger/SKILL.md +512 -0
- package/skills/systematic-debugger/references/debugging-guide.md +12 -0
- package/skills/tech-lead/SKILL.md +409 -0
- package/skills/tech-lead/references/code-review-checklist.md +111 -0
- package/skills/technical-writer/SKILL.md +631 -0
- package/skills/technical-writer/references/doc-templates.md +218 -0
- package/skills/testing-strategist/SKILL.md +476 -0
- package/skills/testing-strategist/references/testing-pyramid.md +16 -0
- package/skills/ux-ui-designer/SKILL.md +419 -0
- package/skills/ux-ui-designer/references/design-system-foundation.md +168 -0
- package/skills_overview.txt +94 -0
- package/templates/PROJECT_KICKOFF.md +284 -0
- package/templates/SKILL_TEMPLATE.md +131 -0
- package/templates/USAGE.md +95 -0
- package/templates/agent-python/README.md +71 -0
- package/templates/agent-python/agent.py +272 -0
- package/templates/agent-python/config.yaml +76 -0
- package/templates/agent-python/prompts/system.md +109 -0
- package/templates/agent-python/requirements.txt +7 -0
- package/templates/automation-n8n/README.md +14 -0
- package/templates/automation-n8n/webhook-handler.json +57 -0
- package/templates/backend-node/Dockerfile +12 -0
- package/templates/backend-node/README.md +15 -0
- package/templates/backend-node/package.json +30 -0
- package/templates/backend-node/src/index.ts +19 -0
- package/templates/backend-node/src/routes.ts +7 -0
- package/templates/backend-node/tsconfig.json +22 -0
- package/templates/backend-python/Dockerfile +11 -0
- package/templates/backend-python/README.md +78 -0
- package/templates/backend-python/app/core/config.py +12 -0
- package/templates/backend-python/app/core/database.py +12 -0
- package/templates/backend-python/app/main.py +17 -0
- package/templates/backend-python/app/routers/__init__.py +1 -0
- package/templates/backend-python/app/routers/health.py +7 -0
- package/templates/backend-python/requirements-dev.txt +6 -0
- package/templates/backend-python/requirements.txt +4 -0
- package/templates/backend-python/tests/test_health.py +9 -0
- package/templates/checkpoint.yaml +117 -0
- package/templates/database/README.md +474 -0
- package/templates/frontend-react/README.md +446 -0
- package/templates/plan.yaml +320 -0
- package/templates/session.yaml +125 -0
- package/templates/spec.yaml +229 -0
- package/templates/tasks.yaml +330 -0
- package/workflows/bugfix-backend.md +380 -0
- package/workflows/documentation.md +232 -0
- package/workflows/generate-prd.md +320 -0
- package/workflows/ideation.md +396 -0
- package/workflows/new-agent-ia.md +497 -0
- package/workflows/new-automation.md +374 -0
- package/workflows/new-feature.md +290 -0
- package/workflows/optimize-performance.md +373 -0
- package/workflows/resolve-github-issue.md +524 -0
- package/workflows/security-review.md +291 -0
- package/workflows/spec-driven.md +476 -0
- package/workflows/testing-strategy.md +296 -0
- package/workflows/third-party-integration.md +277 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Index Strategy Guide — Data Engineer
|
|
2
|
+
|
|
3
|
+
> Guía completa para estrategia de índices en PostgreSQL.
|
|
4
|
+
|
|
5
|
+
## Regla de Oro
|
|
6
|
+
|
|
7
|
+
> **No crees índices prematuramente.** Crea índices basados en queries reales que observas en producción vía `pg_stat_statements` y `EXPLAIN ANALYZE`.
|
|
8
|
+
|
|
9
|
+
## Tipos de Índices
|
|
10
|
+
|
|
11
|
+
| Tipo | Cuándo Usar | Ejemplo |
|
|
12
|
+
|------|------------|---------|
|
|
13
|
+
| **B-Tree** (default) | Igualdad, rango, ORDER BY | `CREATE INDEX idx_users_email ON users(email)` |
|
|
14
|
+
| **Hash** | Solo igualdad, reads pesados | `CREATE INDEX idx_users_uuid ON users USING hash(uuid)` |
|
|
15
|
+
| **GIN** | Arrays, JSONB, full-text search | `CREATE INDEX idx_data_tags ON items USING gin(tags)` |
|
|
16
|
+
| **GiST** | Geoespacial, rangos, proximidad | `CREATE INDEX idx_locations ON places USING gist(location)` |
|
|
17
|
+
| **BRIN** | Datos naturalmente ordenados (timestamps) | `CREATE INDEX idx_logs_created ON logs USING brin(created_at)` |
|
|
18
|
+
|
|
19
|
+
## Patrones de Índices
|
|
20
|
+
|
|
21
|
+
### 1. Índice Simple
|
|
22
|
+
|
|
23
|
+
```sql
|
|
24
|
+
-- Para filtros frecuentes en una columna
|
|
25
|
+
CREATE INDEX idx_orders_status ON orders(status);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 2. Índice Compuesto
|
|
29
|
+
|
|
30
|
+
```sql
|
|
31
|
+
-- Para queries que filtran por múltiples columnas
|
|
32
|
+
-- IMPORTANTE: el orden importa (left-to-right)
|
|
33
|
+
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
|
|
34
|
+
|
|
35
|
+
-- Este índice sirve para:
|
|
36
|
+
-- ✅ WHERE user_id = X
|
|
37
|
+
-- ✅ WHERE user_id = X AND status = Y
|
|
38
|
+
-- ❌ WHERE status = Y (no usa el índice)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 3. Índice Parcial
|
|
42
|
+
|
|
43
|
+
```sql
|
|
44
|
+
-- Solo indexa un subconjunto de filas
|
|
45
|
+
-- Ideal para queries que siempre filtran lo mismo
|
|
46
|
+
CREATE INDEX idx_orders_pending
|
|
47
|
+
ON orders(created_at)
|
|
48
|
+
WHERE status = 'pending';
|
|
49
|
+
-- Mucho más pequeño que indexar toda la tabla
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 4. Índice Covering (INCLUDE)
|
|
53
|
+
|
|
54
|
+
```sql
|
|
55
|
+
-- Incluye columnas extra para evitar table lookup
|
|
56
|
+
CREATE INDEX idx_orders_user_covering
|
|
57
|
+
ON orders(user_id)
|
|
58
|
+
INCLUDE (status, total, created_at);
|
|
59
|
+
-- Index-Only Scan: no necesita ir a la tabla
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 5. Índice Unique
|
|
63
|
+
|
|
64
|
+
```sql
|
|
65
|
+
-- Garantiza unicidad a nivel de DB
|
|
66
|
+
CREATE UNIQUE INDEX unq_users_email ON users(email);
|
|
67
|
+
|
|
68
|
+
-- Unique parcial (solo activos)
|
|
69
|
+
CREATE UNIQUE INDEX unq_users_active_email
|
|
70
|
+
ON users(email)
|
|
71
|
+
WHERE deleted_at IS NULL;
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 6. Índice para JSONB
|
|
75
|
+
|
|
76
|
+
```sql
|
|
77
|
+
-- GIN para queries dentro de JSONB
|
|
78
|
+
CREATE INDEX idx_items_metadata ON items USING gin(metadata);
|
|
79
|
+
|
|
80
|
+
-- Soporta:
|
|
81
|
+
-- WHERE metadata @> '{"category": "electronics"}'
|
|
82
|
+
-- WHERE metadata ? 'key_name'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Cuándo NO Crear Índices
|
|
86
|
+
|
|
87
|
+
| Situación | Razón |
|
|
88
|
+
|-----------|-------|
|
|
89
|
+
| Tablas con < 1000 filas | Seq Scan es más rápido |
|
|
90
|
+
| Columnas con muy baja cardinalidad (ej: boolean) | El índice no ayuda |
|
|
91
|
+
| Tablas con muchos INSERT/UPDATE | Los índices penalizan escrituras |
|
|
92
|
+
| Columnas que nunca se filtran | Desperdicio de espacio |
|
|
93
|
+
|
|
94
|
+
## Diagnóstico con EXPLAIN ANALYZE
|
|
95
|
+
|
|
96
|
+
```sql
|
|
97
|
+
-- Siempre usar EXPLAIN (ANALYZE, BUFFERS) para verificar
|
|
98
|
+
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
|
|
99
|
+
SELECT * FROM orders
|
|
100
|
+
WHERE user_id = 'abc-123'
|
|
101
|
+
AND status = 'pending'
|
|
102
|
+
ORDER BY created_at DESC
|
|
103
|
+
LIMIT 10;
|
|
104
|
+
|
|
105
|
+
-- Lo que buscas:
|
|
106
|
+
-- ✅ Index Scan / Index Only Scan
|
|
107
|
+
-- ⚠️ Bitmap Heap Scan (aceptable para muchos resultados)
|
|
108
|
+
-- ❌ Seq Scan en tabla grande = falta índice
|
|
109
|
+
-- ❌ Nested Loop con muchas rows = posible N+1
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Mantenimiento de Índices
|
|
113
|
+
|
|
114
|
+
```sql
|
|
115
|
+
-- Crear sin bloquear la tabla (CONCURRENTLY)
|
|
116
|
+
CREATE INDEX CONCURRENTLY idx_users_phone ON users(phone);
|
|
117
|
+
|
|
118
|
+
-- Reconstruir índice fragmentado
|
|
119
|
+
REINDEX INDEX CONCURRENTLY idx_users_email;
|
|
120
|
+
|
|
121
|
+
-- Ver tamaño de índices
|
|
122
|
+
SELECT
|
|
123
|
+
indexrelname,
|
|
124
|
+
pg_size_pretty(pg_relation_size(indexrelid)) AS size,
|
|
125
|
+
idx_scan AS scans
|
|
126
|
+
FROM pg_stat_user_indexes
|
|
127
|
+
ORDER BY pg_relation_size(indexrelid) DESC;
|
|
128
|
+
```
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
LMAgent - Database Backup & Restore Script (PostgreSQL)
|
|
4
|
+
Crea backups comprimidos y restaura bases de datos PostgreSQL.
|
|
5
|
+
|
|
6
|
+
Uso:
|
|
7
|
+
python backup_postgres.py backup --db mydb --output ./backups/
|
|
8
|
+
python backup_postgres.py restore --db mydb --file ./backups/mydb_20240121.sql.gz
|
|
9
|
+
python backup_postgres.py list --dir ./backups/
|
|
10
|
+
python backup_postgres.py clean --dir ./backups/ --keep 7
|
|
11
|
+
|
|
12
|
+
Requiere: pg_dump y pg_restore en el PATH.
|
|
13
|
+
Las credenciales se leen de variables de entorno.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import gzip
|
|
18
|
+
import os
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
from datetime import datetime
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_db_config() -> dict:
|
|
26
|
+
"""Obtiene configuración de DB desde variables de entorno."""
|
|
27
|
+
return {
|
|
28
|
+
"host": os.getenv("DB_HOST", "localhost"),
|
|
29
|
+
"port": os.getenv("DB_PORT", "5432"),
|
|
30
|
+
"user": os.getenv("DB_USER", "postgres"),
|
|
31
|
+
"password": os.getenv("DB_PASSWORD", ""),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def backup(db_name: str, output_dir: str) -> str:
|
|
36
|
+
"""Crea un backup comprimido de la base de datos."""
|
|
37
|
+
config = get_db_config()
|
|
38
|
+
output_path = Path(output_dir)
|
|
39
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
|
|
41
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
42
|
+
filename = f"{db_name}_{timestamp}.sql.gz"
|
|
43
|
+
filepath = output_path / filename
|
|
44
|
+
|
|
45
|
+
env = os.environ.copy()
|
|
46
|
+
env["PGPASSWORD"] = config["password"]
|
|
47
|
+
|
|
48
|
+
print(f"📦 Creando backup de '{db_name}'...")
|
|
49
|
+
|
|
50
|
+
# pg_dump → gzip
|
|
51
|
+
pg_dump_cmd = [
|
|
52
|
+
"pg_dump",
|
|
53
|
+
"-h", config["host"],
|
|
54
|
+
"-p", config["port"],
|
|
55
|
+
"-U", config["user"],
|
|
56
|
+
"-d", db_name,
|
|
57
|
+
"--format=plain",
|
|
58
|
+
"--no-owner",
|
|
59
|
+
"--no-privileges",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
result = subprocess.run(
|
|
64
|
+
pg_dump_cmd,
|
|
65
|
+
capture_output=True,
|
|
66
|
+
env=env,
|
|
67
|
+
timeout=300,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if result.returncode != 0:
|
|
71
|
+
print(f"❌ Error en pg_dump: {result.stderr.decode()}")
|
|
72
|
+
sys.exit(1)
|
|
73
|
+
|
|
74
|
+
with gzip.open(filepath, "wb") as f:
|
|
75
|
+
f.write(result.stdout)
|
|
76
|
+
|
|
77
|
+
size_mb = filepath.stat().st_size / (1024 * 1024)
|
|
78
|
+
print(f"✅ Backup creado: {filepath} ({size_mb:.1f} MB)")
|
|
79
|
+
return str(filepath)
|
|
80
|
+
|
|
81
|
+
except subprocess.TimeoutExpired:
|
|
82
|
+
print("❌ Timeout: el backup tardó más de 5 minutos")
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
except FileNotFoundError:
|
|
85
|
+
print("❌ pg_dump no encontrado. Verifica que PostgreSQL esté instalado.")
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def restore(db_name: str, backup_file: str):
|
|
90
|
+
"""Restaura una base de datos desde un backup."""
|
|
91
|
+
config = get_db_config()
|
|
92
|
+
filepath = Path(backup_file)
|
|
93
|
+
|
|
94
|
+
if not filepath.exists():
|
|
95
|
+
print(f"❌ Archivo no encontrado: {filepath}")
|
|
96
|
+
sys.exit(1)
|
|
97
|
+
|
|
98
|
+
env = os.environ.copy()
|
|
99
|
+
env["PGPASSWORD"] = config["password"]
|
|
100
|
+
|
|
101
|
+
print(f"🔄 Restaurando backup en '{db_name}' desde {filepath.name}...")
|
|
102
|
+
|
|
103
|
+
# Descomprimir si es .gz
|
|
104
|
+
if filepath.suffix == ".gz":
|
|
105
|
+
with gzip.open(filepath, "rb") as f:
|
|
106
|
+
sql_content = f.read()
|
|
107
|
+
else:
|
|
108
|
+
sql_content = filepath.read_bytes()
|
|
109
|
+
|
|
110
|
+
psql_cmd = [
|
|
111
|
+
"psql",
|
|
112
|
+
"-h", config["host"],
|
|
113
|
+
"-p", config["port"],
|
|
114
|
+
"-U", config["user"],
|
|
115
|
+
"-d", db_name,
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
result = subprocess.run(
|
|
120
|
+
psql_cmd,
|
|
121
|
+
input=sql_content,
|
|
122
|
+
capture_output=True,
|
|
123
|
+
env=env,
|
|
124
|
+
timeout=600,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if result.returncode != 0:
|
|
128
|
+
stderr = result.stderr.decode()
|
|
129
|
+
if "ERROR" in stderr:
|
|
130
|
+
print(f"⚠️ Restauración completada con errores:\n{stderr[:500]}")
|
|
131
|
+
else:
|
|
132
|
+
print(f"✅ Restauración completada exitosamente.")
|
|
133
|
+
else:
|
|
134
|
+
print(f"✅ Restauración completada exitosamente.")
|
|
135
|
+
|
|
136
|
+
except subprocess.TimeoutExpired:
|
|
137
|
+
print("❌ Timeout: la restauración tardó más de 10 minutos")
|
|
138
|
+
sys.exit(1)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def list_backups(backup_dir: str):
|
|
142
|
+
"""Lista los backups disponibles."""
|
|
143
|
+
backup_path = Path(backup_dir)
|
|
144
|
+
|
|
145
|
+
if not backup_path.exists():
|
|
146
|
+
print(f"❌ Directorio no encontrado: {backup_path}")
|
|
147
|
+
sys.exit(1)
|
|
148
|
+
|
|
149
|
+
backups = sorted(backup_path.glob("*.sql*"), reverse=True)
|
|
150
|
+
|
|
151
|
+
if not backups:
|
|
152
|
+
print("📂 No hay backups disponibles.")
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
print(f"\n📂 Backups en {backup_path}:\n")
|
|
156
|
+
print(f"{'Archivo':<45} {'Tamaño':<12} {'Fecha'}")
|
|
157
|
+
print("-" * 75)
|
|
158
|
+
|
|
159
|
+
for b in backups:
|
|
160
|
+
size_mb = b.stat().st_size / (1024 * 1024)
|
|
161
|
+
mod_time = datetime.fromtimestamp(b.stat().st_mtime).strftime("%Y-%m-%d %H:%M")
|
|
162
|
+
print(f"{b.name:<45} {size_mb:>8.1f} MB {mod_time}")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def clean_old_backups(backup_dir: str, keep: int = 7):
|
|
166
|
+
"""Elimina backups antiguos, manteniendo los N más recientes."""
|
|
167
|
+
backup_path = Path(backup_dir)
|
|
168
|
+
backups = sorted(backup_path.glob("*.sql*"), key=lambda p: p.stat().st_mtime, reverse=True)
|
|
169
|
+
|
|
170
|
+
to_delete = backups[keep:]
|
|
171
|
+
|
|
172
|
+
if not to_delete:
|
|
173
|
+
print(f"✅ No hay backups para eliminar (manteniendo últimos {keep}).")
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
for b in to_delete:
|
|
177
|
+
b.unlink()
|
|
178
|
+
print(f"🗑️ Eliminado: {b.name}")
|
|
179
|
+
|
|
180
|
+
print(f"✅ Eliminados {len(to_delete)} backups. Mantenidos: {keep}.")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def main():
|
|
184
|
+
parser = argparse.ArgumentParser(
|
|
185
|
+
description="LMAgent PostgreSQL Backup/Restore"
|
|
186
|
+
)
|
|
187
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
188
|
+
|
|
189
|
+
# backup
|
|
190
|
+
p_backup = sub.add_parser("backup", help="Crear backup")
|
|
191
|
+
p_backup.add_argument("--db", required=True, help="Nombre de la base de datos")
|
|
192
|
+
p_backup.add_argument("--output", default="./backups", help="Directorio de salida")
|
|
193
|
+
|
|
194
|
+
# restore
|
|
195
|
+
p_restore = sub.add_parser("restore", help="Restaurar backup")
|
|
196
|
+
p_restore.add_argument("--db", required=True, help="Nombre de la base de datos")
|
|
197
|
+
p_restore.add_argument("--file", required=True, help="Archivo de backup")
|
|
198
|
+
|
|
199
|
+
# list
|
|
200
|
+
p_list = sub.add_parser("list", help="Listar backups")
|
|
201
|
+
p_list.add_argument("--dir", default="./backups", help="Directorio de backups")
|
|
202
|
+
|
|
203
|
+
# clean
|
|
204
|
+
p_clean = sub.add_parser("clean", help="Limpiar backups antiguos")
|
|
205
|
+
p_clean.add_argument("--dir", default="./backups", help="Directorio de backups")
|
|
206
|
+
p_clean.add_argument("--keep", type=int, default=7, help="Cantidad a mantener")
|
|
207
|
+
|
|
208
|
+
args = parser.parse_args()
|
|
209
|
+
|
|
210
|
+
if args.command == "backup":
|
|
211
|
+
backup(args.db, args.output)
|
|
212
|
+
elif args.command == "restore":
|
|
213
|
+
restore(args.db, args.file)
|
|
214
|
+
elif args.command == "list":
|
|
215
|
+
list_backups(args.dir)
|
|
216
|
+
elif args.command == "clean":
|
|
217
|
+
clean_old_backups(args.dir, args.keep)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
if __name__ == "__main__":
|
|
221
|
+
main()
|