@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.
Files changed (3) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/index.mjs +205 -1
  3. 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.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFzaXZheWhicnFlbm53aXd0dGRzIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTcyMjU1Mzc3MCwiZXhwIjoyMDM4MTI5NzcwfQ.r7RM4wXOQN4b7QMeHEw5U0y_l3dPZc8b0a7vp7_PBpQ',
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');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deinossrl/dgp-agent",
3
- "version": "1.4.52",
3
+ "version": "1.4.54",
4
4
  "description": "Agente local para Despliegue-GPT - Reporta el estado del repositorio Git a la plataforma TenMinute IA",
5
5
  "main": "index.mjs",
6
6
  "bin": {