@deinossrl/dgp-agent 1.4.52 → 1.4.54
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 +19 -0
- package/index.mjs +205 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog - DGP Agent
|
|
2
2
|
|
|
3
|
+
## [1.4.54] - 2026-01-12
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Implementado comando `pg_restore` para restaurar bases de datos desde backups
|
|
7
|
+
- Descarga automática de backups desde Supabase Storage
|
|
8
|
+
- Soporte para restauración completa, solo esquema, o solo datos
|
|
9
|
+
- Actualización automática de estado en tabla `db_restores`
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- `pg_restore` viene incluido con pg_dump (mismo binario de PostgreSQL)
|
|
13
|
+
- Limpieza automática de archivos temporales después de restore
|
|
14
|
+
|
|
15
|
+
## [1.4.53] - 2026-01-12
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Actualizado service_role key con el correcto desde Supabase Dashboard
|
|
19
|
+
- Resuelve error 401 "Invalid API key" al hacer polling de comandos
|
|
20
|
+
- Ahora puede subir backups a Storage sin errores de RLS
|
|
21
|
+
|
|
3
22
|
## [1.4.52] - 2026-01-12
|
|
4
23
|
|
|
5
24
|
### Fixed
|
package/index.mjs
CHANGED
|
@@ -366,7 +366,7 @@ let AGENT_MODE = 'smart'; // Siempre inteligente
|
|
|
366
366
|
const CONFIG = {
|
|
367
367
|
apiUrl: process.env.DGP_API_URL || fileConfig.apiUrl || 'https://asivayhbrqennwiwttds.supabase.co/functions/v1/dgp-agent-status',
|
|
368
368
|
commandsUrl: process.env.DGP_COMMANDS_URL || fileConfig.commandsUrl || 'https://asivayhbrqennwiwttds.supabase.co/rest/v1/agent_commands',
|
|
369
|
-
supabaseKey: process.env.DGP_SUPABASE_KEY || fileConfig.supabaseKey || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
|
|
369
|
+
supabaseKey: process.env.DGP_SUPABASE_KEY || fileConfig.supabaseKey || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFzaXZheWhicnFlbm53aXd0dGRzIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc2NzMwMDA5NywiZXhwIjoyMDgyODc2MDk3fQ.53h-WsRtVxJ5ZkquHNAYbWJ-keQotmTJwKsYjcZhmIE',
|
|
370
370
|
interval: parseInt(process.env.DGP_INTERVAL || fileConfig.interval || '30', 10),
|
|
371
371
|
commandPollInterval: parseInt(process.env.DGP_COMMAND_POLL_INTERVAL || fileConfig.commandPollInterval || '10', 10),
|
|
372
372
|
machineId: process.env.DGP_MACHINE_ID || fileConfig.machineId || `${hostname()}-${process.env.USERNAME || process.env.USER || 'dev'}`,
|
|
@@ -1872,6 +1872,207 @@ async function executePgDump(command) {
|
|
|
1872
1872
|
}
|
|
1873
1873
|
}
|
|
1874
1874
|
|
|
1875
|
+
/**
|
|
1876
|
+
* Ejecuta pg_restore para restaurar una base de datos desde un backup
|
|
1877
|
+
* @param {Object} command - El comando con params de restore
|
|
1878
|
+
*/
|
|
1879
|
+
async function executePgRestore(command) {
|
|
1880
|
+
const { id, params } = command;
|
|
1881
|
+
const { restore_id, backup_storage_path, target_environment, restore_type, clean_before_restore } = params;
|
|
1882
|
+
|
|
1883
|
+
let localFilePath = null;
|
|
1884
|
+
|
|
1885
|
+
try {
|
|
1886
|
+
console.log('');
|
|
1887
|
+
logInfo(`=== Ejecutando pg_restore (Restauración) ===`);
|
|
1888
|
+
await addCommandLog('info', 'Iniciando restore...');
|
|
1889
|
+
|
|
1890
|
+
// Verificar que pg_restore esté disponible
|
|
1891
|
+
const pgRestoreCmd = await findOrInstallPgDump(); // Usa la misma función, pg_restore viene con pg_dump
|
|
1892
|
+
if (!pgRestoreCmd) {
|
|
1893
|
+
throw new Error('pg_restore no está disponible');
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
const pgRestorePath = pgRestoreCmd.replace('pg_dump', 'pg_restore');
|
|
1897
|
+
logInfo(`Usando pg_restore: ${pgRestorePath}`);
|
|
1898
|
+
|
|
1899
|
+
// Descargar backup desde Storage
|
|
1900
|
+
logInfo(`Descargando backup desde Storage: ${backup_storage_path}`);
|
|
1901
|
+
await addCommandLog('info', `Descargando backup: ${backup_storage_path}`);
|
|
1902
|
+
|
|
1903
|
+
const supabase = createClient(
|
|
1904
|
+
CONFIG.apiUrl.replace('/functions/v1/dgp-agent-status', ''),
|
|
1905
|
+
CONFIG.supabaseKey
|
|
1906
|
+
);
|
|
1907
|
+
|
|
1908
|
+
const { data: fileData, error: downloadError } = await supabase.storage
|
|
1909
|
+
.from('database-backups')
|
|
1910
|
+
.download(backup_storage_path);
|
|
1911
|
+
|
|
1912
|
+
if (downloadError) {
|
|
1913
|
+
throw new Error(`Error descargando backup: ${downloadError.message}`);
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
// Guardar temporalmente
|
|
1917
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
|
|
1918
|
+
const tempFileName = `restore_${timestamp}.dump`;
|
|
1919
|
+
localFilePath = join(BIN_DIR, tempFileName);
|
|
1920
|
+
|
|
1921
|
+
// Convertir Blob a Buffer y guardar
|
|
1922
|
+
const arrayBuffer = await fileData.arrayBuffer();
|
|
1923
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
1924
|
+
writeFileSync(localFilePath, buffer);
|
|
1925
|
+
|
|
1926
|
+
const fileSize = statSync(localFilePath).size;
|
|
1927
|
+
logSuccess(`Backup descargado (${(fileSize / 1024 / 1024).toFixed(2)} MB)`);
|
|
1928
|
+
await addCommandLog('success', `Backup descargado: ${(fileSize / 1024 / 1024).toFixed(2)} MB`);
|
|
1929
|
+
|
|
1930
|
+
// Marcar restore como running
|
|
1931
|
+
await supabase
|
|
1932
|
+
.schema('dgp')
|
|
1933
|
+
.from('db_restores')
|
|
1934
|
+
.update({ status: 'running', started_at: new Date().toISOString() })
|
|
1935
|
+
.eq('id', restore_id);
|
|
1936
|
+
|
|
1937
|
+
// Construir argumentos de pg_restore
|
|
1938
|
+
const pgRestoreArgs = [];
|
|
1939
|
+
|
|
1940
|
+
// Conexión
|
|
1941
|
+
if (target_environment.connection_type === 'supabase') {
|
|
1942
|
+
const projectRef = target_environment.supabase_project_ref;
|
|
1943
|
+
pgRestoreArgs.push(`--host=db.${projectRef}.supabase.co`);
|
|
1944
|
+
pgRestoreArgs.push(`--port=5432`);
|
|
1945
|
+
pgRestoreArgs.push(`--username=postgres`);
|
|
1946
|
+
pgRestoreArgs.push(`--dbname=postgres`);
|
|
1947
|
+
|
|
1948
|
+
// Service role key como password
|
|
1949
|
+
if (target_environment.metadata?.service_role_key) {
|
|
1950
|
+
process.env.PGPASSWORD = target_environment.metadata.service_role_key;
|
|
1951
|
+
}
|
|
1952
|
+
} else if (target_environment.connection_type === 'postgresql') {
|
|
1953
|
+
pgRestoreArgs.push(`--host=${target_environment.db_host}`);
|
|
1954
|
+
pgRestoreArgs.push(`--port=${target_environment.db_port || 5432}`);
|
|
1955
|
+
pgRestoreArgs.push(`--username=${target_environment.db_user}`);
|
|
1956
|
+
pgRestoreArgs.push(`--dbname=${target_environment.db_name}`);
|
|
1957
|
+
|
|
1958
|
+
if (target_environment.db_password_encrypted || target_environment.metadata?.password) {
|
|
1959
|
+
process.env.PGPASSWORD = target_environment.db_password_encrypted || target_environment.metadata.password;
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
// Opciones de restore
|
|
1964
|
+
if (clean_before_restore) {
|
|
1965
|
+
pgRestoreArgs.push('--clean');
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
if (restore_type === 'schema_only') {
|
|
1969
|
+
pgRestoreArgs.push('--schema-only');
|
|
1970
|
+
} else if (restore_type === 'data_only') {
|
|
1971
|
+
pgRestoreArgs.push('--data-only');
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
// No detener en errores (útil para restauraciones parciales)
|
|
1975
|
+
pgRestoreArgs.push('--exit-on-error');
|
|
1976
|
+
pgRestoreArgs.push('--no-owner');
|
|
1977
|
+
pgRestoreArgs.push('--no-privileges');
|
|
1978
|
+
|
|
1979
|
+
// Archivo de entrada
|
|
1980
|
+
pgRestoreArgs.push(localFilePath);
|
|
1981
|
+
|
|
1982
|
+
// Ejecutar pg_restore
|
|
1983
|
+
logCommand(`Ejecutando: pg_restore [conexión oculta]`);
|
|
1984
|
+
await addCommandLog('command', 'Ejecutando pg_restore...');
|
|
1985
|
+
|
|
1986
|
+
const result = spawnSync(pgRestorePath, pgRestoreArgs, {
|
|
1987
|
+
env: process.env,
|
|
1988
|
+
encoding: 'utf-8',
|
|
1989
|
+
stdio: 'pipe',
|
|
1990
|
+
});
|
|
1991
|
+
|
|
1992
|
+
if (result.error) {
|
|
1993
|
+
throw new Error(`Error ejecutando pg_restore: ${result.error.message}`);
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
if (result.status !== 0) {
|
|
1997
|
+
const errorMsg = result.stderr || result.stdout || 'Unknown error';
|
|
1998
|
+
throw new Error(`pg_restore failed with code ${result.status}: ${errorMsg}`);
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
logSuccess('Restauración completada');
|
|
2002
|
+
await addCommandLog('success', 'Base de datos restaurada');
|
|
2003
|
+
|
|
2004
|
+
// Actualizar estado en DB
|
|
2005
|
+
await supabase
|
|
2006
|
+
.schema('dgp')
|
|
2007
|
+
.from('db_restores')
|
|
2008
|
+
.update({
|
|
2009
|
+
status: 'completed',
|
|
2010
|
+
completed_at: new Date().toISOString()
|
|
2011
|
+
})
|
|
2012
|
+
.eq('id', restore_id);
|
|
2013
|
+
|
|
2014
|
+
logSuccess('Registro de restore actualizado');
|
|
2015
|
+
|
|
2016
|
+
// Limpiar archivo local
|
|
2017
|
+
try {
|
|
2018
|
+
unlinkSync(localFilePath);
|
|
2019
|
+
logInfo('Archivo temporal eliminado');
|
|
2020
|
+
} catch (e) {
|
|
2021
|
+
// Ignorar
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// Marcar comando como completado
|
|
2025
|
+
await updateCommandStatus(id, 'completed', {
|
|
2026
|
+
restore_id: restore_id,
|
|
2027
|
+
restored_from: backup_storage_path
|
|
2028
|
+
});
|
|
2029
|
+
|
|
2030
|
+
console.log('');
|
|
2031
|
+
logSuccess(`=== Restauración completada exitosamente ===`);
|
|
2032
|
+
console.log('');
|
|
2033
|
+
|
|
2034
|
+
return { success: true };
|
|
2035
|
+
|
|
2036
|
+
} catch (error) {
|
|
2037
|
+
logError(`Restore failed: ${error.message}`);
|
|
2038
|
+
await addCommandLog('error', error.message);
|
|
2039
|
+
|
|
2040
|
+
// Limpiar archivo local si existe
|
|
2041
|
+
if (localFilePath && existsSync(localFilePath)) {
|
|
2042
|
+
try {
|
|
2043
|
+
unlinkSync(localFilePath);
|
|
2044
|
+
} catch (e) {
|
|
2045
|
+
// Ignorar
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
// Actualizar estado del restore en DB
|
|
2050
|
+
try {
|
|
2051
|
+
const supabase = createClient(
|
|
2052
|
+
CONFIG.apiUrl.replace('/functions/v1/dgp-agent-status', ''),
|
|
2053
|
+
CONFIG.supabaseKey
|
|
2054
|
+
);
|
|
2055
|
+
|
|
2056
|
+
await supabase
|
|
2057
|
+
.schema('dgp')
|
|
2058
|
+
.from('db_restores')
|
|
2059
|
+
.update({
|
|
2060
|
+
status: 'failed',
|
|
2061
|
+
error_message: error.message
|
|
2062
|
+
})
|
|
2063
|
+
.eq('id', restore_id);
|
|
2064
|
+
} catch (e) {
|
|
2065
|
+
// Ignorar errores al actualizar
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
await updateCommandStatus(id, 'failed', {}, error.message);
|
|
2069
|
+
return { success: false, error: error.message };
|
|
2070
|
+
} finally {
|
|
2071
|
+
// Limpiar variables de entorno
|
|
2072
|
+
delete process.env.PGPASSWORD;
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
|
|
1875
2076
|
/**
|
|
1876
2077
|
* Ejecuta un comando recibido
|
|
1877
2078
|
* @param {Object} command - El comando a ejecutar
|
|
@@ -1898,6 +2099,9 @@ async function executeCommand(command, useAI = false) {
|
|
|
1898
2099
|
case 'pg_dump':
|
|
1899
2100
|
return await executePgDump(command);
|
|
1900
2101
|
|
|
2102
|
+
case 'pg_restore':
|
|
2103
|
+
return await executePgRestore(command);
|
|
2104
|
+
|
|
1901
2105
|
case 'rollback':
|
|
1902
2106
|
logError('Rollback not implemented yet');
|
|
1903
2107
|
await updateCommandStatus(command.id, 'failed', {}, 'Rollback not implemented');
|