Update: nueva version Horux Despachos
This commit is contained in:
71
scripts/backup.sh
Normal file
71
scripts/backup.sh
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/bin/bash
|
||||
# Horux360 Database Backup Script
|
||||
# Backs up all databases (central + tenant) with daily/weekly rotation
|
||||
# Requires: .pgpass file at /root/.pgpass with format: localhost:5432:*:postgres:<password>
|
||||
#
|
||||
# Usage: Add to crontab:
|
||||
# 0 1 * * * /root/Horux/scripts/backup.sh >> /var/log/horux-backup.log 2>&1
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BACKUP_DIR="/var/horux/backups"
|
||||
DAILY_DIR="$BACKUP_DIR/daily"
|
||||
WEEKLY_DIR="$BACKUP_DIR/weekly"
|
||||
PG_USER="postgres"
|
||||
PG_HOST="localhost"
|
||||
DATE=$(date +%Y-%m-%d)
|
||||
DAY_OF_WEEK=$(date +%u)
|
||||
|
||||
# Retention
|
||||
DAILY_KEEP=7
|
||||
WEEKLY_KEEP=4
|
||||
|
||||
echo "=== Horux360 Backup Started: $(date) ==="
|
||||
|
||||
# Create directories
|
||||
mkdir -p "$DAILY_DIR" "$WEEKLY_DIR"
|
||||
|
||||
# Get list of all horux databases (central + tenant)
|
||||
DATABASES=$(psql -h "$PG_HOST" -U "$PG_USER" -t -c \
|
||||
"SELECT datname FROM pg_database WHERE datname = 'horux360' OR datname LIKE 'horux_%' AND datname NOT LIKE '%_deleted_%'" \
|
||||
| tr -d ' ')
|
||||
|
||||
TOTAL=0
|
||||
ERRORS=0
|
||||
|
||||
for DB in $DATABASES; do
|
||||
echo "Backing up: $DB"
|
||||
DUMP_FILE="$DAILY_DIR/${DB}_${DATE}.sql.gz"
|
||||
|
||||
if pg_dump -h "$PG_HOST" -U "$PG_USER" "$DB" | gzip > "$DUMP_FILE"; then
|
||||
# Verify file is not empty
|
||||
if [ -s "$DUMP_FILE" ]; then
|
||||
TOTAL=$((TOTAL + 1))
|
||||
echo " OK: $(du -h "$DUMP_FILE" | cut -f1)"
|
||||
else
|
||||
echo " WARNING: Empty backup file for $DB"
|
||||
rm -f "$DUMP_FILE"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
else
|
||||
echo " ERROR: Failed to backup $DB"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# Weekly backup on Sundays (day 7)
|
||||
if [ "$DAY_OF_WEEK" -eq 7 ]; then
|
||||
echo "Creating weekly backup..."
|
||||
cp "$DAILY_DIR"/*_"${DATE}".sql.gz "$WEEKLY_DIR/" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Clean old daily backups
|
||||
echo "Cleaning daily backups older than $DAILY_KEEP days..."
|
||||
find "$DAILY_DIR" -name "*.sql.gz" -mtime "+$DAILY_KEEP" -delete
|
||||
|
||||
# Clean old weekly backups
|
||||
echo "Cleaning weekly backups older than $WEEKLY_KEEP weeks..."
|
||||
find "$WEEKLY_DIR" -name "*.sql.gz" -mtime "+$((WEEKLY_KEEP * 7))" -delete
|
||||
|
||||
echo "=== Backup Complete: $TOTAL databases backed up, $ERRORS errors ==="
|
||||
echo "=== Finished: $(date) ==="
|
||||
27
scripts/tune-postgres.sh
Normal file
27
scripts/tune-postgres.sh
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
# PostgreSQL Production Tuning for Horux360
|
||||
# Server: 32GB RAM, 8 cores
|
||||
# Target: 50 tenants, PM2 cluster ×2
|
||||
#
|
||||
# Run once: sudo bash scripts/tune-postgres.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "=== PostgreSQL Production Tuning ==="
|
||||
|
||||
sudo -u postgres psql -c "ALTER SYSTEM SET max_connections = 300;"
|
||||
sudo -u postgres psql -c "ALTER SYSTEM SET shared_buffers = '4GB';"
|
||||
sudo -u postgres psql -c "ALTER SYSTEM SET work_mem = '16MB';"
|
||||
sudo -u postgres psql -c "ALTER SYSTEM SET effective_cache_size = '16GB';"
|
||||
sudo -u postgres psql -c "ALTER SYSTEM SET maintenance_work_mem = '512MB';"
|
||||
sudo -u postgres psql -c "ALTER SYSTEM SET checkpoint_completion_target = 0.9;"
|
||||
sudo -u postgres psql -c "ALTER SYSTEM SET wal_buffers = '64MB';"
|
||||
sudo -u postgres psql -c "ALTER SYSTEM SET random_page_cost = 1.1;"
|
||||
|
||||
echo "Settings applied. Restarting PostgreSQL..."
|
||||
sudo systemctl restart postgresql
|
||||
|
||||
echo "Verifying settings..."
|
||||
sudo -u postgres psql -c "SHOW max_connections; SHOW shared_buffers; SHOW work_mem;"
|
||||
|
||||
echo "=== Done ==="
|
||||
135
scripts/update-cfdi-xml.js
Normal file
135
scripts/update-cfdi-xml.js
Normal file
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Script para actualizar CFDIs existentes con su XML original
|
||||
* Uso: node scripts/update-cfdi-xml.js <directorio> <schema>
|
||||
* Ejemplo: node scripts/update-cfdi-xml.js /root/xmls tenant_roem691011ez4
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { Pool } = require('pg');
|
||||
|
||||
// Configuración de la base de datos
|
||||
const pool = new Pool({
|
||||
host: 'localhost',
|
||||
port: 5432,
|
||||
database: 'horux360',
|
||||
user: 'postgres',
|
||||
password: 'postgres',
|
||||
});
|
||||
|
||||
// Extraer UUID del XML
|
||||
function extractUuidFromXml(xmlContent) {
|
||||
// Buscar UUID en TimbreFiscalDigital
|
||||
const uuidMatch = xmlContent.match(/UUID=["']([A-Fa-f0-9-]{36})["']/i);
|
||||
return uuidMatch ? uuidMatch[1].toUpperCase() : null;
|
||||
}
|
||||
|
||||
// Procesar archivos en lotes
|
||||
async function processFiles(directory, schema, batchSize = 500) {
|
||||
const files = fs.readdirSync(directory).filter(f => f.toLowerCase().endsWith('.xml'));
|
||||
|
||||
console.log(`\nEncontrados ${files.length} archivos XML en ${directory}`);
|
||||
console.log(`Schema: ${schema}`);
|
||||
console.log(`Tamaño de lote: ${batchSize}\n`);
|
||||
|
||||
let updated = 0;
|
||||
let notFound = 0;
|
||||
let errors = 0;
|
||||
let processed = 0;
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
// Procesar en lotes
|
||||
for (let i = 0; i < files.length; i += batchSize) {
|
||||
const batch = files.slice(i, i + batchSize);
|
||||
const updates = [];
|
||||
|
||||
// Leer y parsear XMLs del lote
|
||||
for (const file of batch) {
|
||||
try {
|
||||
const filePath = path.join(directory, file);
|
||||
const xmlContent = fs.readFileSync(filePath, 'utf8');
|
||||
const uuid = extractUuidFromXml(xmlContent);
|
||||
|
||||
if (uuid) {
|
||||
updates.push({ uuid, xmlContent });
|
||||
} else {
|
||||
errors++;
|
||||
if (errors <= 5) {
|
||||
console.log(` ⚠ No se encontró UUID en: ${file}`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
errors++;
|
||||
if (errors <= 5) {
|
||||
console.log(` ✗ Error leyendo ${file}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar base de datos en una transacción
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
||||
for (const { uuid, xmlContent } of updates) {
|
||||
const result = await client.query(
|
||||
`UPDATE "${schema}".cfdis SET xml_original = $1 WHERE UPPER(uuid_fiscal) = $2 AND xml_original IS NULL`,
|
||||
[xmlContent, uuid]
|
||||
);
|
||||
|
||||
if (result.rowCount > 0) {
|
||||
updated++;
|
||||
} else {
|
||||
notFound++;
|
||||
}
|
||||
}
|
||||
|
||||
await client.query('COMMIT');
|
||||
} catch (err) {
|
||||
await client.query('ROLLBACK');
|
||||
console.error(`Error en lote: ${err.message}`);
|
||||
errors += batch.length;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
|
||||
processed += batch.length;
|
||||
|
||||
// Progreso
|
||||
const percent = ((processed / files.length) * 100).toFixed(1);
|
||||
const elapsed = ((Date.now() - startTime) / 1000).toFixed(0);
|
||||
const rate = (processed / elapsed).toFixed(0);
|
||||
process.stdout.write(`\r Procesando: ${processed}/${files.length} (${percent}%) - ${updated} actualizados - ${rate} archivos/seg `);
|
||||
}
|
||||
|
||||
const totalTime = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||
|
||||
console.log(`\n\n✓ Completado en ${totalTime} segundos`);
|
||||
console.log(` - Actualizados: ${updated}`);
|
||||
console.log(` - No encontrados (UUID no existe en BD): ${notFound}`);
|
||||
console.log(` - Errores: ${errors}`);
|
||||
|
||||
await pool.end();
|
||||
}
|
||||
|
||||
// Main
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length < 2) {
|
||||
console.log('Uso: node scripts/update-cfdi-xml.js <directorio> <schema>');
|
||||
console.log('Ejemplo: node scripts/update-cfdi-xml.js /root/xmls tenant_roem691011ez4');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const [directory, schema] = args;
|
||||
|
||||
if (!fs.existsSync(directory)) {
|
||||
console.error(`Error: El directorio ${directory} no existe`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
processFiles(directory, schema).catch(err => {
|
||||
console.error('Error fatal:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user