Files
api/services/nginx/app/classes/edge_gateway_schema_bootstrap.php
T
Jeppe Bundgaard d30a006457 Add delivery metadata support, preferred channels, and enhanced agent validation
This commit introduces delivery metadata tracking for gateway commands, updates, and shells. Adds preferred delivery channel handling, refined validation for edge agents, improved relay management logic, and broker presence reporting. Includes schema changes, enhanced shell handling, and test coverage.
2026-04-16 13:44:28 +02:00

279 lines
13 KiB
PHP

<?php
namespace classes;
/**
* Ensures additive department edge gateway tables exist.
*
* The legacy PHP stack has no centralized migration runner, so this bootstrap
* must be safe to call from runtime flows and tests.
*/
class edge_gateway_schema_bootstrap
{
private static bool $initialized = false;
public static function ensureTables(): void
{
if (self::$initialized) {
return;
}
global $db;
$queries = [
"CREATE TABLE IF NOT EXISTS edge_gateways (
id INT AUTO_INCREMENT PRIMARY KEY,
department_id INT NOT NULL,
label VARCHAR(255) NOT NULL,
hostname VARCHAR(255) NULL,
agent_token_hash VARCHAR(255) NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
transport_mode VARCHAR(32) NOT NULL DEFAULT 'gateway',
release_channel VARCHAR(32) NOT NULL DEFAULT 'stable',
installed_version VARCHAR(64) NULL,
target_version VARCHAR(64) NULL,
last_heartbeat_at DATETIME NULL,
last_seen_ip VARCHAR(64) NULL,
discovery_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
is_primary TINYINT(1) NOT NULL DEFAULT 1,
metadata_json JSON NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
INDEX idx_edge_gateways_department_status (department_id, status),
INDEX idx_edge_gateways_department_primary (department_id, is_primary),
INDEX idx_edge_gateways_last_heartbeat (last_heartbeat_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS edge_gateway_claim_tokens (
id INT AUTO_INCREMENT PRIMARY KEY,
department_id INT NOT NULL,
label VARCHAR(255) NULL,
token_hash VARCHAR(255) NOT NULL,
created_by INT NULL,
expires_at DATETIME NOT NULL,
used_at DATETIME NULL,
metadata_json JSON NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
INDEX idx_edge_gateway_claim_tokens_department (department_id),
INDEX idx_edge_gateway_claim_tokens_expires (expires_at),
INDEX idx_edge_gateway_claim_tokens_used (used_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS edge_gateway_device_inventory (
id INT AUTO_INCREMENT PRIMARY KEY,
gateway_id INT NOT NULL,
device_id VARCHAR(255) NOT NULL,
local_ip VARCHAR(64) NULL,
model VARCHAR(255) NULL,
channel_count INT NOT NULL DEFAULT 1,
capabilities_json JSON NULL,
online TINYINT(1) NOT NULL DEFAULT 0,
last_seen_at DATETIME NULL,
metadata_json JSON NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
UNIQUE KEY uniq_edge_gateway_inventory_device (gateway_id, device_id),
INDEX idx_edge_gateway_inventory_gateway (gateway_id),
INDEX idx_edge_gateway_inventory_ip (local_ip)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS edge_gateway_relay_bindings (
id INT AUTO_INCREMENT PRIMARY KEY,
gateway_id INT NOT NULL,
department_id INT NOT NULL,
relay_id VARCHAR(255) NOT NULL,
device_id VARCHAR(255) NOT NULL,
local_ip VARCHAR(64) NULL,
channel INT NOT NULL DEFAULT 0,
binding_source VARCHAR(64) NOT NULL DEFAULT 'MANUAL',
approved_by INT NULL,
approved_at DATETIME NULL,
metadata_json JSON NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
UNIQUE KEY uniq_edge_gateway_binding (gateway_id, relay_id),
INDEX idx_edge_gateway_binding_department (department_id),
INDEX idx_edge_gateway_binding_device (gateway_id, device_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS edge_gateway_command_jobs (
id INT AUTO_INCREMENT PRIMARY KEY,
gateway_id INT NOT NULL,
command_type VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
request_json JSON NULL,
response_json JSON NULL,
delivery_json JSON NULL,
correlation_id VARCHAR(128) NOT NULL,
requested_by INT NULL,
requested_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
completed_at DATETIME NULL,
error_message TEXT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
UNIQUE KEY uniq_edge_gateway_command_correlation (correlation_id),
INDEX idx_edge_gateway_command_gateway (gateway_id),
INDEX idx_edge_gateway_command_status (status),
INDEX idx_edge_gateway_command_type (command_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS edge_gateway_update_jobs (
id INT AUTO_INCREMENT PRIMARY KEY,
gateway_id INT NOT NULL,
command_job_id INT NULL,
target_version VARCHAR(64) NOT NULL,
release_channel VARCHAR(32) NOT NULL DEFAULT 'stable',
status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
requested_by INT NULL,
requested_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
started_at DATETIME NULL,
completed_at DATETIME NULL,
delivery_json JSON NULL,
result_json JSON NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
INDEX idx_edge_gateway_update_gateway (gateway_id),
INDEX idx_edge_gateway_update_command (command_job_id),
INDEX idx_edge_gateway_update_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS edge_gateway_shell_sessions (
id INT AUTO_INCREMENT PRIMARY KEY,
gateway_id INT NOT NULL,
reason TEXT NOT NULL,
approval_status VARCHAR(32) NOT NULL DEFAULT 'APPROVED',
session_token_hash VARCHAR(255) NOT NULL,
requested_by INT NULL,
approved_by INT NULL,
approved_at DATETIME NULL,
expires_at DATETIME NOT NULL,
opened_at DATETIME NULL,
closed_at DATETIME NULL,
transcript_text LONGTEXT NULL,
metadata_json JSON NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
INDEX idx_edge_gateway_shell_gateway (gateway_id),
INDEX idx_edge_gateway_shell_expires (expires_at),
INDEX idx_edge_gateway_shell_status (approval_status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS edge_gateway_shell_action_jobs (
id INT AUTO_INCREMENT PRIMARY KEY,
gateway_id INT NOT NULL,
session_id INT NOT NULL,
action_type VARCHAR(32) NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
payload_json JSON NULL,
delivery_json JSON NULL,
requested_by INT NULL,
requested_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
completed_at DATETIME NULL,
error_message TEXT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
INDEX idx_edge_gateway_shell_action_gateway (gateway_id),
INDEX idx_edge_gateway_shell_action_session (session_id),
INDEX idx_edge_gateway_shell_action_status (status),
INDEX idx_edge_gateway_shell_action_type (action_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS edge_gateway_shell_events (
id INT AUTO_INCREMENT PRIMARY KEY,
gateway_id INT NOT NULL,
session_id INT NOT NULL,
event_type VARCHAR(32) NOT NULL,
payload_json JSON NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
INDEX idx_edge_gateway_shell_event_gateway (gateway_id),
INDEX idx_edge_gateway_shell_event_session (session_id),
INDEX idx_edge_gateway_shell_event_type (event_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS edge_gateway_audit_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
gateway_id INT NULL,
department_id INT NULL,
action VARCHAR(128) NOT NULL,
actor_user_id INT NULL,
actor_type VARCHAR(32) NOT NULL DEFAULT 'USER',
severity VARCHAR(16) NOT NULL DEFAULT 'INFO',
context_json JSON NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_edge_gateway_audit_gateway (gateway_id),
INDEX idx_edge_gateway_audit_department (department_id),
INDEX idx_edge_gateway_audit_action (action)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
];
foreach ($queries as $sql) {
$db->query($sql);
}
if (!self::tableHasColumn('edge_gateway_update_jobs', 'command_job_id')) {
$db->query(
"ALTER TABLE edge_gateway_update_jobs
ADD COLUMN command_job_id INT NULL AFTER gateway_id,
ADD INDEX idx_edge_gateway_update_command (command_job_id)"
);
}
if (!self::tableHasColumn('edge_gateway_command_jobs', 'delivery_json')) {
$db->query(
"ALTER TABLE edge_gateway_command_jobs
ADD COLUMN delivery_json JSON NULL AFTER response_json"
);
}
if (!self::tableHasColumn('edge_gateway_shell_action_jobs', 'delivery_json')) {
$db->query(
"ALTER TABLE edge_gateway_shell_action_jobs
ADD COLUMN delivery_json JSON NULL AFTER payload_json"
);
}
if (!self::tableHasColumn('edge_gateway_update_jobs', 'delivery_json')) {
$db->query(
"ALTER TABLE edge_gateway_update_jobs
ADD COLUMN delivery_json JSON NULL AFTER completed_at"
);
}
self::$initialized = true;
}
private static function tableHasColumn(string $table, string $column): bool
{
global $db;
$table = $db->escape_string($table);
$column = $db->escape_string($column);
$database = $db->escape_string($db->getDatabase());
$result = $db->query(
"SELECT COUNT(*) AS c
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = '$database'
AND TABLE_NAME = '$table'
AND COLUMN_NAME = '$column'"
);
if (!$result) {
return false;
}
$row = $result->fetch_assoc();
return ((int)($row['c'] ?? 0)) > 0;
}
}