feature/integracion-cloudrestore-targets

This commit is contained in:
2026-06-05 10:50:27 -06:00
parent 4642086e8c
commit 969acdbdf4
32 changed files with 2484 additions and 62 deletions

View File

@@ -0,0 +1,8 @@
-- Migración 001 (DOWN): revierte la integración CloudRestoreAS.
-- Se quita primero la columna de asignación y la tabla dependiente.
ALTER TABLE a24c.database_nodes DROP COLUMN IF EXISTS restore_target_id;
DROP TABLE IF EXISTS a24c.restore_job_logs;
DROP TABLE IF EXISTS a24c.restore_targets;

View File

@@ -0,0 +1,49 @@
-- Migración 001 (UP): integración CloudRestoreAS.
-- Servidores de restauración (Alfa/Omega/Gamma) + asignación por base de datos
-- (database_nodes.restore_target_id) + bitácora de jobs. Idempotente.
-- Rollback en 001_restore_targets.down.sql.
CREATE SCHEMA IF NOT EXISTS a24c;
CREATE TABLE IF NOT EXISTS a24c.restore_targets (
id SERIAL PRIMARY KEY,
name VARCHAR(120) NOT NULL UNIQUE,
server_ip VARCHAR(255),
sql_username VARCHAR(128),
sql_password_encrypted TEXT,
data_folder VARCHAR(500),
ssh_host VARCHAR(255),
ssh_port INTEGER DEFAULT 22,
ssh_username VARCHAR(128),
ssh_password_encrypted TEXT,
remote_inbox_path VARCHAR(500),
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
INSERT INTO a24c.restore_targets (name) VALUES ('Alfa'), ('Omega'), ('Gamma')
ON CONFLICT (name) DO NOTHING;
-- Columna de asignación en database_nodes (tabla aprovisionada por el backend a24c).
ALTER TABLE a24c.database_nodes
ADD COLUMN IF NOT EXISTS restore_target_id INTEGER
REFERENCES a24c.restore_targets (id) ON DELETE SET NULL;
CREATE TABLE IF NOT EXISTS a24c.restore_job_logs (
id SERIAL PRIMARY KEY,
filename VARCHAR(500) NOT NULL,
restore_target_id INTEGER REFERENCES a24c.restore_targets (id) ON DELETE SET NULL,
db_name VARCHAR(255),
status VARCHAR(20) NOT NULL CHECK (status IN ('completed', 'failed', 'forwarded')),
duration_ms INTEGER,
error_message TEXT,
restored_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_a24c_restore_job_logs_target
ON a24c.restore_job_logs (restore_target_id);
CREATE INDEX IF NOT EXISTS idx_a24c_restore_job_logs_restored_at
ON a24c.restore_job_logs (restored_at);
CREATE INDEX IF NOT EXISTS idx_a24c_restore_job_logs_status
ON a24c.restore_job_logs (status);

View File

@@ -0,0 +1,3 @@
SET search_path TO a24c, public;
DROP TABLE IF EXISTS a24c.cloudrestore_status;

View File

@@ -0,0 +1,17 @@
-- Estado reportado por CloudRestoreAS (carpeta de entrada, host, versión).
-- Solo CloudRestoreAS escribe vía POST /api/restore/instance-config; el panel solo consulta.
-- Rollback en 002_cloudrestore_status.down.sql.
SET search_path TO a24c, public;
CREATE TABLE IF NOT EXISTS a24c.cloudrestore_status (
id SERIAL PRIMARY KEY,
instance_key VARCHAR(120) NOT NULL UNIQUE DEFAULT 'default',
input_folder VARCHAR(500) NOT NULL,
host_name VARCHAR(255),
app_version VARCHAR(50),
reported_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
COMMENT ON TABLE a24c.cloudrestore_status IS
'Carpeta de entrada vigente reportada por CloudRestoreAS. Solo lectura en el panel.';

View File

@@ -62,3 +62,75 @@ VALUES (
true
)
ON CONFLICT (username) DO NOTHING;
-- ============================================================================
-- Integración CloudRestoreAS: servidores de restauración y bitácora de jobs.
-- Equivalente versionado en database/migrations/001_restore_targets.{up,down}.sql
-- ============================================================================
-- Servidores SQL Server destino de restauración (Alfa, Omega, Gamma). Son máquinas
-- EXTERNAS independientes: el .bak se transfiere por SFTP/SSH al servidor y el SQL Server
-- restaura desde su disco local. Cada base (a24c.database_nodes.restore_target_id) se asigna
-- a uno de ellos. Las contraseñas (SQL y SSH) se guardan cifradas con AES-256-GCM, nunca en plano.
CREATE TABLE IF NOT EXISTS a24c.restore_targets (
id SERIAL PRIMARY KEY,
name VARCHAR(120) NOT NULL UNIQUE, -- Alfa | Omega | Gamma
server_ip VARCHAR(255), -- SQL Server: IP/hostname; acepta "ip,puerto"
sql_username VARCHAR(128),
sql_password_encrypted TEXT, -- sobre gcm:iv:tag:ciphertext
data_folder VARCHAR(500), -- ruta .mdf/.ldf EN el server remoto (C:\SQLData)
ssh_host VARCHAR(255), -- host SSH del servidor (suele ser el mismo equipo)
ssh_port INTEGER DEFAULT 22,
ssh_username VARCHAR(128),
ssh_password_encrypted TEXT, -- credencial SSH cifrada (AES-256-GCM)
remote_inbox_path VARCHAR(500), -- ruta en el server donde se sube el .bak y se restaura (C:\RestoreInbox)
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Tres servidores fijos de partida (editables). Datos de conexión vacíos hasta configurarse.
INSERT INTO a24c.restore_targets (name) VALUES ('Alfa'), ('Omega'), ('Gamma')
ON CONFLICT (name) DO NOTHING;
-- Asignación servidor de restauración ↔ base de datos (la tabla database_nodes la aprovisiona
-- el backend a24c; aquí solo se agrega la columna de asignación).
ALTER TABLE a24c.database_nodes
ADD COLUMN IF NOT EXISTS restore_target_id INTEGER
REFERENCES a24c.restore_targets (id) ON DELETE SET NULL;
-- Bitácora de restauraciones reportadas por CloudRestoreAS.
CREATE TABLE IF NOT EXISTS a24c.restore_job_logs (
id SERIAL PRIMARY KEY,
filename VARCHAR(500) NOT NULL,
restore_target_id INTEGER REFERENCES a24c.restore_targets (id) ON DELETE SET NULL,
db_name VARCHAR(255),
status VARCHAR(20) NOT NULL CHECK (status IN ('completed', 'failed', 'forwarded')),
duration_ms INTEGER,
error_message TEXT,
restored_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_a24c_restore_job_logs_target
ON a24c.restore_job_logs (restore_target_id);
CREATE INDEX IF NOT EXISTS idx_a24c_restore_job_logs_restored_at
ON a24c.restore_job_logs (restored_at);
CREATE INDEX IF NOT EXISTS idx_a24c_restore_job_logs_status
ON a24c.restore_job_logs (status);
COMMENT ON TABLE a24c.restore_targets IS 'Servidores SQL Server destino de restauración (CloudRestoreAS). Contraseña cifrada AES-256-GCM.';
COMMENT ON TABLE a24c.restore_job_logs IS 'Bitácora de restauraciones reportadas por CloudRestoreAS.';
-- Equivalente versionado en database/migrations/002_cloudrestore_status.{up,down}.sql
-- Carpeta de entrada reportada por CloudRestoreAS (solo lectura en el panel).
CREATE TABLE IF NOT EXISTS a24c.cloudrestore_status (
id SERIAL PRIMARY KEY,
instance_key VARCHAR(120) NOT NULL UNIQUE DEFAULT 'default',
input_folder VARCHAR(500) NOT NULL,
host_name VARCHAR(255),
app_version VARCHAR(50),
reported_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
COMMENT ON TABLE a24c.cloudrestore_status IS
'Carpeta de entrada vigente reportada por CloudRestoreAS. Solo lectura en el panel.';

View File

@@ -0,0 +1,47 @@
-- Seed dev: CRA local en esta máquina (instancia Alfa).
-- Aplica sobre a24c en Postgres. Idempotente donde es posible.
-- Contraseñas cifradas con ENCRYPTION_KEY del .env del panel (Soluciones01).
SET search_path TO a24c, public;
-- Bitácora: permitir status forwarded (reenvío SFTP).
ALTER TABLE a24c.restore_job_logs DROP CONSTRAINT IF EXISTS restore_job_logs_status_check;
ALTER TABLE a24c.restore_job_logs ADD CONSTRAINT restore_job_logs_status_check
CHECK (status IN ('completed', 'failed', 'forwarded'));
-- Servidor Alfa: SQL local + SSH local (misma máquina Windows).
UPDATE a24c.restore_targets
SET
server_ip = 'localhost',
sql_username = 'sa',
sql_password_encrypted = 'gcm:hp9C+T5Zvdxqc/CS:S+56zEHeJFszt3MptzjS9A==:1UTe60xRrMSOW0/+',
data_folder = 'C:\Program Files\Microsoft SQL Server\MSSQL16.MSSQLSERVER\MSSQL\DATA',
ssh_host = 'localhost',
ssh_port = 22,
ssh_username = 'hugo_reyes',
ssh_password_encrypted = 'gcm:iJgYS7AJ1osrrwHj:8D4TiN9xOEr207+HBIxt8w==:/wt/4ZwPY4Xg4CLA',
remote_inbox_path = 'C:\CloudRestore\Entrada',
notes = 'CRA dev local — esta máquina',
updated_at = now()
WHERE name = 'Alfa';
-- Carpeta de entrada reportada (hasta que el CRA guarde config real).
INSERT INTO a24c.cloudrestore_status (instance_key, input_folder, host_name, app_version, reported_at)
VALUES ('Alfa', 'C:\CloudRestore\Entrada', 'DEV-LOCAL', '1.0.0-dev', now())
ON CONFLICT (instance_key) DO UPDATE SET
input_folder = EXCLUDED.input_folder,
host_name = EXCLUDED.host_name,
app_version = EXCLUDED.app_version,
reported_at = now();
-- Nodos de prueba asignados a Alfa con SQL en localhost.
UPDATE a24c.database_nodes
SET
server_name = 'localhost',
is_active = 1
WHERE restore_target_id = (SELECT id FROM a24c.restore_targets WHERE name = 'Alfa');
-- Asegurar nodo de prueba principal para restore_local.
UPDATE a24c.database_nodes
SET restore_target_id = (SELECT id FROM a24c.restore_targets WHERE name = 'Alfa')
WHERE node_subnode_key = 'GENERICA-TEST';