@deinossrl/dgp-agent 1.4.53 → 1.4.55
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/CHANGELOG.md +18 -0
- package/index.mjs +219 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog - DGP Agent
|
|
2
2
|
|
|
3
|
+
## [1.4.55] - 2026-01-12
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Ahora se copia `pg_restore.exe` junto con `pg_dump.exe` al instalar PostgreSQL
|
|
7
|
+
- Resuelve error ENOENT al ejecutar comando de restore
|
|
8
|
+
|
|
9
|
+
## [1.4.54] - 2026-01-12
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Implementado comando `pg_restore` para restaurar bases de datos desde backups
|
|
13
|
+
- Descarga automática de backups desde Supabase Storage
|
|
14
|
+
- Soporte para restauración completa, solo esquema, o solo datos
|
|
15
|
+
- Actualización automática de estado en tabla `db_restores`
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- `pg_restore` viene incluido con pg_dump (mismo binario de PostgreSQL)
|
|
19
|
+
- Limpieza automática de archivos temporales después de restore
|
|
20
|
+
|
|
3
21
|
## [1.4.53] - 2026-01-12
|
|
4
22
|
|
|
5
23
|
### Fixed
|
package/index.mjs
CHANGED
|
@@ -1519,12 +1519,19 @@ async function findOrInstallPgDump() {
|
|
|
1519
1519
|
const extractedPgDump = join(BIN_DIR, 'pgsql', 'bin', 'pg_dump.exe');
|
|
1520
1520
|
|
|
1521
1521
|
if (existsSync(extractedPgDump)) {
|
|
1522
|
-
// Copiar pg_dump.exe y TODAS las DLLs al BIN_DIR raíz
|
|
1522
|
+
// Copiar pg_dump.exe, pg_restore.exe y TODAS las DLLs al BIN_DIR raíz
|
|
1523
1523
|
const binSrcDir = join(BIN_DIR, 'pgsql', 'bin');
|
|
1524
1524
|
|
|
1525
1525
|
// Copiar pg_dump.exe
|
|
1526
1526
|
execSync(`copy "${extractedPgDump}" "${localPgDump}"`, { shell: 'cmd.exe' });
|
|
1527
1527
|
|
|
1528
|
+
// Copiar pg_restore.exe
|
|
1529
|
+
const extractedPgRestore = join(BIN_DIR, 'pgsql', 'bin', 'pg_restore.exe');
|
|
1530
|
+
const localPgRestore = join(BIN_DIR, 'pg_restore.exe');
|
|
1531
|
+
if (existsSync(extractedPgRestore)) {
|
|
1532
|
+
execSync(`copy "${extractedPgRestore}" "${localPgRestore}"`, { shell: 'cmd.exe' });
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1528
1535
|
// Copiar TODAS las DLLs (no solo algunas específicas)
|
|
1529
1536
|
execSync(`copy "${binSrcDir}\\*.dll" "${BIN_DIR}\\"`, { shell: 'cmd.exe' });
|
|
1530
1537
|
|
|
@@ -1568,6 +1575,13 @@ async function findOrInstallPgDump() {
|
|
|
1568
1575
|
// Copiar pg_dump.exe
|
|
1569
1576
|
execSync(`copy "${extractedPgDump}" "${localPgDump}"`, { shell: 'cmd.exe' });
|
|
1570
1577
|
|
|
1578
|
+
// Copiar pg_restore.exe
|
|
1579
|
+
const extractedPgRestore = join(BIN_DIR, 'pgsql', 'bin', 'pg_restore.exe');
|
|
1580
|
+
const localPgRestore = join(BIN_DIR, 'pg_restore.exe');
|
|
1581
|
+
if (existsSync(extractedPgRestore)) {
|
|
1582
|
+
execSync(`copy "${extractedPgRestore}" "${localPgRestore}"`, { shell: 'cmd.exe' });
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1571
1585
|
// Copiar TODAS las DLLs
|
|
1572
1586
|
execSync(`copy "${binSrcDir}\\*.dll" "${BIN_DIR}\\"`, { shell: 'cmd.exe' });
|
|
1573
1587
|
|
|
@@ -1872,6 +1886,207 @@ async function executePgDump(command) {
|
|
|
1872
1886
|
}
|
|
1873
1887
|
}
|
|
1874
1888
|
|
|
1889
|
+
/**
|
|
1890
|
+
* Ejecuta pg_restore para restaurar una base de datos desde un backup
|
|
1891
|
+
* @param {Object} command - El comando con params de restore
|
|
1892
|
+
*/
|
|
1893
|
+
async function executePgRestore(command) {
|
|
1894
|
+
const { id, params } = command;
|
|
1895
|
+
const { restore_id, backup_storage_path, target_environment, restore_type, clean_before_restore } = params;
|
|
1896
|
+
|
|
1897
|
+
let localFilePath = null;
|
|
1898
|
+
|
|
1899
|
+
try {
|
|
1900
|
+
console.log('');
|
|
1901
|
+
logInfo(`=== Ejecutando pg_restore (Restauración) ===`);
|
|
1902
|
+
await addCommandLog('info', 'Iniciando restore...');
|
|
1903
|
+
|
|
1904
|
+
// Verificar que pg_restore esté disponible
|
|
1905
|
+
const pgRestoreCmd = await findOrInstallPgDump(); // Usa la misma función, pg_restore viene con pg_dump
|
|
1906
|
+
if (!pgRestoreCmd) {
|
|
1907
|
+
throw new Error('pg_restore no está disponible');
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
const pgRestorePath = pgRestoreCmd.replace('pg_dump', 'pg_restore');
|
|
1911
|
+
logInfo(`Usando pg_restore: ${pgRestorePath}`);
|
|
1912
|
+
|
|
1913
|
+
// Descargar backup desde Storage
|
|
1914
|
+
logInfo(`Descargando backup desde Storage: ${backup_storage_path}`);
|
|
1915
|
+
await addCommandLog('info', `Descargando backup: ${backup_storage_path}`);
|
|
1916
|
+
|
|
1917
|
+
const supabase = createClient(
|
|
1918
|
+
CONFIG.apiUrl.replace('/functions/v1/dgp-agent-status', ''),
|
|
1919
|
+
CONFIG.supabaseKey
|
|
1920
|
+
);
|
|
1921
|
+
|
|
1922
|
+
const { data: fileData, error: downloadError } = await supabase.storage
|
|
1923
|
+
.from('database-backups')
|
|
1924
|
+
.download(backup_storage_path);
|
|
1925
|
+
|
|
1926
|
+
if (downloadError) {
|
|
1927
|
+
throw new Error(`Error descargando backup: ${downloadError.message}`);
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
// Guardar temporalmente
|
|
1931
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
|
|
1932
|
+
const tempFileName = `restore_${timestamp}.dump`;
|
|
1933
|
+
localFilePath = join(BIN_DIR, tempFileName);
|
|
1934
|
+
|
|
1935
|
+
// Convertir Blob a Buffer y guardar
|
|
1936
|
+
const arrayBuffer = await fileData.arrayBuffer();
|
|
1937
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
1938
|
+
writeFileSync(localFilePath, buffer);
|
|
1939
|
+
|
|
1940
|
+
const fileSize = statSync(localFilePath).size;
|
|
1941
|
+
logSuccess(`Backup descargado (${(fileSize / 1024 / 1024).toFixed(2)} MB)`);
|
|
1942
|
+
await addCommandLog('success', `Backup descargado: ${(fileSize / 1024 / 1024).toFixed(2)} MB`);
|
|
1943
|
+
|
|
1944
|
+
// Marcar restore como running
|
|
1945
|
+
await supabase
|
|
1946
|
+
.schema('dgp')
|
|
1947
|
+
.from('db_restores')
|
|
1948
|
+
.update({ status: 'running', started_at: new Date().toISOString() })
|
|
1949
|
+
.eq('id', restore_id);
|
|
1950
|
+
|
|
1951
|
+
// Construir argumentos de pg_restore
|
|
1952
|
+
const pgRestoreArgs = [];
|
|
1953
|
+
|
|
1954
|
+
// Conexión
|
|
1955
|
+
if (target_environment.connection_type === 'supabase') {
|
|
1956
|
+
const projectRef = target_environment.supabase_project_ref;
|
|
1957
|
+
pgRestoreArgs.push(`--host=db.${projectRef}.supabase.co`);
|
|
1958
|
+
pgRestoreArgs.push(`--port=5432`);
|
|
1959
|
+
pgRestoreArgs.push(`--username=postgres`);
|
|
1960
|
+
pgRestoreArgs.push(`--dbname=postgres`);
|
|
1961
|
+
|
|
1962
|
+
// Service role key como password
|
|
1963
|
+
if (target_environment.metadata?.service_role_key) {
|
|
1964
|
+
process.env.PGPASSWORD = target_environment.metadata.service_role_key;
|
|
1965
|
+
}
|
|
1966
|
+
} else if (target_environment.connection_type === 'postgresql') {
|
|
1967
|
+
pgRestoreArgs.push(`--host=${target_environment.db_host}`);
|
|
1968
|
+
pgRestoreArgs.push(`--port=${target_environment.db_port || 5432}`);
|
|
1969
|
+
pgRestoreArgs.push(`--username=${target_environment.db_user}`);
|
|
1970
|
+
pgRestoreArgs.push(`--dbname=${target_environment.db_name}`);
|
|
1971
|
+
|
|
1972
|
+
if (target_environment.db_password_encrypted || target_environment.metadata?.password) {
|
|
1973
|
+
process.env.PGPASSWORD = target_environment.db_password_encrypted || target_environment.metadata.password;
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
// Opciones de restore
|
|
1978
|
+
if (clean_before_restore) {
|
|
1979
|
+
pgRestoreArgs.push('--clean');
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
if (restore_type === 'schema_only') {
|
|
1983
|
+
pgRestoreArgs.push('--schema-only');
|
|
1984
|
+
} else if (restore_type === 'data_only') {
|
|
1985
|
+
pgRestoreArgs.push('--data-only');
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
// No detener en errores (útil para restauraciones parciales)
|
|
1989
|
+
pgRestoreArgs.push('--exit-on-error');
|
|
1990
|
+
pgRestoreArgs.push('--no-owner');
|
|
1991
|
+
pgRestoreArgs.push('--no-privileges');
|
|
1992
|
+
|
|
1993
|
+
// Archivo de entrada
|
|
1994
|
+
pgRestoreArgs.push(localFilePath);
|
|
1995
|
+
|
|
1996
|
+
// Ejecutar pg_restore
|
|
1997
|
+
logCommand(`Ejecutando: pg_restore [conexión oculta]`);
|
|
1998
|
+
await addCommandLog('command', 'Ejecutando pg_restore...');
|
|
1999
|
+
|
|
2000
|
+
const result = spawnSync(pgRestorePath, pgRestoreArgs, {
|
|
2001
|
+
env: process.env,
|
|
2002
|
+
encoding: 'utf-8',
|
|
2003
|
+
stdio: 'pipe',
|
|
2004
|
+
});
|
|
2005
|
+
|
|
2006
|
+
if (result.error) {
|
|
2007
|
+
throw new Error(`Error ejecutando pg_restore: ${result.error.message}`);
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
if (result.status !== 0) {
|
|
2011
|
+
const errorMsg = result.stderr || result.stdout || 'Unknown error';
|
|
2012
|
+
throw new Error(`pg_restore failed with code ${result.status}: ${errorMsg}`);
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
logSuccess('Restauración completada');
|
|
2016
|
+
await addCommandLog('success', 'Base de datos restaurada');
|
|
2017
|
+
|
|
2018
|
+
// Actualizar estado en DB
|
|
2019
|
+
await supabase
|
|
2020
|
+
.schema('dgp')
|
|
2021
|
+
.from('db_restores')
|
|
2022
|
+
.update({
|
|
2023
|
+
status: 'completed',
|
|
2024
|
+
completed_at: new Date().toISOString()
|
|
2025
|
+
})
|
|
2026
|
+
.eq('id', restore_id);
|
|
2027
|
+
|
|
2028
|
+
logSuccess('Registro de restore actualizado');
|
|
2029
|
+
|
|
2030
|
+
// Limpiar archivo local
|
|
2031
|
+
try {
|
|
2032
|
+
unlinkSync(localFilePath);
|
|
2033
|
+
logInfo('Archivo temporal eliminado');
|
|
2034
|
+
} catch (e) {
|
|
2035
|
+
// Ignorar
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
// Marcar comando como completado
|
|
2039
|
+
await updateCommandStatus(id, 'completed', {
|
|
2040
|
+
restore_id: restore_id,
|
|
2041
|
+
restored_from: backup_storage_path
|
|
2042
|
+
});
|
|
2043
|
+
|
|
2044
|
+
console.log('');
|
|
2045
|
+
logSuccess(`=== Restauración completada exitosamente ===`);
|
|
2046
|
+
console.log('');
|
|
2047
|
+
|
|
2048
|
+
return { success: true };
|
|
2049
|
+
|
|
2050
|
+
} catch (error) {
|
|
2051
|
+
logError(`Restore failed: ${error.message}`);
|
|
2052
|
+
await addCommandLog('error', error.message);
|
|
2053
|
+
|
|
2054
|
+
// Limpiar archivo local si existe
|
|
2055
|
+
if (localFilePath && existsSync(localFilePath)) {
|
|
2056
|
+
try {
|
|
2057
|
+
unlinkSync(localFilePath);
|
|
2058
|
+
} catch (e) {
|
|
2059
|
+
// Ignorar
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
// Actualizar estado del restore en DB
|
|
2064
|
+
try {
|
|
2065
|
+
const supabase = createClient(
|
|
2066
|
+
CONFIG.apiUrl.replace('/functions/v1/dgp-agent-status', ''),
|
|
2067
|
+
CONFIG.supabaseKey
|
|
2068
|
+
);
|
|
2069
|
+
|
|
2070
|
+
await supabase
|
|
2071
|
+
.schema('dgp')
|
|
2072
|
+
.from('db_restores')
|
|
2073
|
+
.update({
|
|
2074
|
+
status: 'failed',
|
|
2075
|
+
error_message: error.message
|
|
2076
|
+
})
|
|
2077
|
+
.eq('id', restore_id);
|
|
2078
|
+
} catch (e) {
|
|
2079
|
+
// Ignorar errores al actualizar
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
await updateCommandStatus(id, 'failed', {}, error.message);
|
|
2083
|
+
return { success: false, error: error.message };
|
|
2084
|
+
} finally {
|
|
2085
|
+
// Limpiar variables de entorno
|
|
2086
|
+
delete process.env.PGPASSWORD;
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
|
|
1875
2090
|
/**
|
|
1876
2091
|
* Ejecuta un comando recibido
|
|
1877
2092
|
* @param {Object} command - El comando a ejecutar
|
|
@@ -1898,6 +2113,9 @@ async function executeCommand(command, useAI = false) {
|
|
|
1898
2113
|
case 'pg_dump':
|
|
1899
2114
|
return await executePgDump(command);
|
|
1900
2115
|
|
|
2116
|
+
case 'pg_restore':
|
|
2117
|
+
return await executePgRestore(command);
|
|
2118
|
+
|
|
1901
2119
|
case 'rollback':
|
|
1902
2120
|
logError('Rollback not implemented yet');
|
|
1903
2121
|
await updateCommandStatus(command.id, 'failed', {}, 'Rollback not implemented');
|