777 lines
28 KiB
PHP
777 lines
28 KiB
PHP
<?php
|
|
/**
|
|
* Enrollment Manager for Ziti Router
|
|
* Replicates the functionality of the bash script
|
|
*/
|
|
|
|
require_once 'config.php';
|
|
require_once 'api_client.php';
|
|
|
|
class EnrollmentManager {
|
|
private $apiClient;
|
|
private $routerData;
|
|
private $progressCallback;
|
|
|
|
public function __construct($apiEndpoint = DEFAULT_API_ENDPOINT) {
|
|
$this->apiClient = new ApiClient($apiEndpoint);
|
|
$this->routerData = [];
|
|
}
|
|
|
|
/**
|
|
* Set progress callback function
|
|
*/
|
|
public function setProgressCallback($callback) {
|
|
$this->progressCallback = $callback;
|
|
}
|
|
|
|
/**
|
|
* Report progress
|
|
*/
|
|
private function reportProgress($step, $message, $percentage = null) {
|
|
logMessage('INFO', "[$step] $message");
|
|
|
|
if ($this->progressCallback && is_callable($this->progressCallback)) {
|
|
call_user_func($this->progressCallback, $step, $message, $percentage);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main enrollment process
|
|
*/
|
|
public function enrollRouter($hashKey, $apiEndpoint = null) {
|
|
try {
|
|
if ($apiEndpoint) {
|
|
$this->apiClient = new ApiClient($apiEndpoint);
|
|
}
|
|
|
|
$this->reportProgress('INIT', 'Starting router enrollment process...', 0);
|
|
|
|
// Step 1: Check system requirements
|
|
$this->reportProgress('REQUIREMENTS', 'Checking system requirements...', 10);
|
|
if (!$this->checkSystemRequirements()) {
|
|
throw new Exception('System requirements check failed');
|
|
}
|
|
|
|
// Step 2: Install OpenZiti if needed
|
|
$this->reportProgress('INSTALL', 'Installing OpenZiti CLI...', 20);
|
|
if (!$this->installZiti()) {
|
|
throw new Exception('OpenZiti installation failed');
|
|
}
|
|
|
|
// Step 3: Create directories
|
|
$this->reportProgress('DIRECTORIES', 'Creating necessary directories...', 30);
|
|
if (!$this->createDirectories()) {
|
|
throw new Exception('Failed to create directories');
|
|
}
|
|
|
|
// Step 4: Register router with API
|
|
$this->reportProgress('REGISTER', 'Registering router with ZitiNexus Portal...', 40);
|
|
$result = $this->apiClient->registerRouter($hashKey);
|
|
if (!$result['success']) {
|
|
throw new Exception('Router registration failed: ' . $result['error']);
|
|
}
|
|
|
|
$this->routerData = $result['data'];
|
|
$this->reportProgress('REGISTER', 'Router registered successfully: ' . $this->routerData['routerInfo']['name'], 50);
|
|
|
|
// Step 5: Save configuration files
|
|
$this->reportProgress('CONFIG', 'Saving configuration files...', 60);
|
|
if (!$this->saveConfiguration()) {
|
|
throw new Exception('Failed to save configuration files');
|
|
}
|
|
|
|
// Step 6: Enroll router with OpenZiti
|
|
$this->reportProgress('ENROLL', 'Enrolling router with OpenZiti controller...', 70);
|
|
if (!$this->enrollWithZiti()) {
|
|
throw new Exception('Router enrollment with OpenZiti failed');
|
|
}
|
|
|
|
// Step 7: Create systemd service
|
|
$this->reportProgress('SERVICE', 'Creating systemd service...', 80);
|
|
if (!$this->createSystemdService()) {
|
|
throw new Exception('Failed to create systemd service');
|
|
}
|
|
|
|
// Step 8: Start router service
|
|
$this->reportProgress('START', 'Starting router service...', 90);
|
|
if (!$this->startRouter()) {
|
|
throw new Exception('Failed to start router service');
|
|
}
|
|
|
|
// Step 9: Report success status
|
|
$this->reportProgress('REPORT', 'Reporting enrollment status...', 95);
|
|
$this->reportSuccessStatus($hashKey);
|
|
|
|
$this->reportProgress('COMPLETE', 'Router enrollment completed successfully!', 100);
|
|
|
|
return [
|
|
'success' => true,
|
|
'routerName' => $this->routerData['routerInfo']['name'],
|
|
'routerId' => $this->routerData['routerInfo']['id'],
|
|
'message' => 'Router enrollment completed successfully'
|
|
];
|
|
|
|
} catch (Exception $e) {
|
|
$errorMsg = $e->getMessage();
|
|
logMessage('ERROR', $errorMsg);
|
|
$this->reportProgress('ERROR', $errorMsg, null);
|
|
|
|
// Report failure status
|
|
if (!empty($hashKey) && !empty($this->routerData['callbackUrl'])) {
|
|
$this->apiClient->reportStatus(
|
|
$this->routerData['callbackUrl'],
|
|
$hashKey,
|
|
'failed',
|
|
null,
|
|
$errorMsg
|
|
);
|
|
}
|
|
|
|
return [
|
|
'success' => false,
|
|
'error' => $errorMsg
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check system requirements
|
|
*/
|
|
private function checkSystemRequirements() {
|
|
// Check if running as root
|
|
if (!isRunningAsRoot()) {
|
|
throw new Exception('This script must be run as root (use sudo)');
|
|
}
|
|
|
|
// Check if curl is available
|
|
if (!$this->checkCommand('curl')) {
|
|
$this->reportProgress('REQUIREMENTS', 'Installing curl...');
|
|
if (!$this->installPackage('curl')) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check if jq is available
|
|
if (!$this->checkCommand('jq')) {
|
|
$this->reportProgress('REQUIREMENTS', 'Installing jq...');
|
|
if (!$this->installPackage('jq')) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check if systemctl is available
|
|
if (!$this->checkCommand('systemctl')) {
|
|
throw new Exception('systemctl is required but not available');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Install OpenZiti CLI
|
|
*/
|
|
private function installZiti() {
|
|
// Check if ziti is already installed
|
|
if ($this->checkCommand('ziti')) {
|
|
$output = '';
|
|
executeCommand('ziti version 2>/dev/null | head -n1', $output);
|
|
$this->reportProgress('INSTALL', 'OpenZiti CLI already installed: ' . trim($output));
|
|
return true;
|
|
}
|
|
|
|
$this->reportProgress('INSTALL', 'Setting up OpenZiti package repository...');
|
|
|
|
// Add GPG key using enhanced method to handle PHP execution environment
|
|
if (!$this->addOpenZitiGpgKey()) {
|
|
throw new Exception('Failed to add OpenZiti GPG key');
|
|
}
|
|
|
|
// Set proper permissions
|
|
if (!executeCommand('chmod a+r /usr/share/keyrings/openziti.gpg')) {
|
|
throw new Exception('Failed to set GPG key permissions');
|
|
}
|
|
|
|
// Add repository to sources list
|
|
$repoContent = 'deb [signed-by=/usr/share/keyrings/openziti.gpg] https://packages.openziti.org/zitipax-openziti-deb-stable debian main';
|
|
$tempFile = tempnam(sys_get_temp_dir(), 'openziti-repo');
|
|
file_put_contents($tempFile, $repoContent);
|
|
|
|
if (!executeCommand("cp '$tempFile' /etc/apt/sources.list.d/openziti-release.list")) {
|
|
unlink($tempFile);
|
|
throw new Exception('Failed to add OpenZiti repository');
|
|
}
|
|
unlink($tempFile);
|
|
|
|
// Update package list
|
|
$this->reportProgress('INSTALL', 'Updating package list...');
|
|
if (!executeCommand('apt-get update')) {
|
|
throw new Exception('Failed to update package list');
|
|
}
|
|
|
|
// Install openziti-router package
|
|
$this->reportProgress('INSTALL', 'Installing openziti-router package...');
|
|
if (!executeCommand('apt-get install -y openziti-router')) {
|
|
$this->reportProgress('INSTALL', 'Trying to install ziti CLI only...');
|
|
if (!executeCommand('apt-get install -y ziti')) {
|
|
throw new Exception('Failed to install OpenZiti CLI');
|
|
}
|
|
}
|
|
|
|
// Verify installation
|
|
if (!$this->checkCommand('ziti')) {
|
|
throw new Exception('OpenZiti CLI installation failed - command not found after installation');
|
|
}
|
|
|
|
$output = '';
|
|
executeCommand('ziti version 2>/dev/null | head -n1', $output);
|
|
$this->reportProgress('INSTALL', 'OpenZiti CLI installed successfully: ' . trim($output));
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Create necessary directories
|
|
*/
|
|
private function createDirectories() {
|
|
$directories = [
|
|
CONFIG_DIR => 0755,
|
|
CERTS_DIR => 0700,
|
|
dirname(LOG_FILE) => 0755
|
|
];
|
|
|
|
foreach ($directories as $dir => $permissions) {
|
|
if (!is_dir($dir)) {
|
|
// Use sudo to create system directories
|
|
if (!executeCommand("mkdir -p '$dir'")) {
|
|
throw new Exception("Failed to create directory: $dir");
|
|
}
|
|
if (!executeCommand("chmod " . decoct($permissions) . " '$dir'")) {
|
|
throw new Exception("Failed to set permissions for directory: $dir");
|
|
}
|
|
} else {
|
|
// Ensure permissions are correct even if directory exists
|
|
executeCommand("chmod " . decoct($permissions) . " '$dir'");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Save configuration files
|
|
*/
|
|
private function saveConfiguration() {
|
|
// Save JWT using temp file and sudo
|
|
$tempJwtFile = tempnam(sys_get_temp_dir(), 'ziti-jwt');
|
|
file_put_contents($tempJwtFile, $this->routerData['jwt']);
|
|
|
|
if (!executeCommand("cp '$tempJwtFile' " . JWT_FILE)) {
|
|
unlink($tempJwtFile);
|
|
throw new Exception('Failed to save JWT file');
|
|
}
|
|
unlink($tempJwtFile);
|
|
|
|
if (!executeCommand("chmod 600 " . JWT_FILE)) {
|
|
throw new Exception('Failed to set JWT file permissions');
|
|
}
|
|
|
|
// Save router configuration using temp file and sudo
|
|
$tempConfigFile = tempnam(sys_get_temp_dir(), 'ziti-config');
|
|
file_put_contents($tempConfigFile, $this->routerData['routerConfig']['yaml']);
|
|
|
|
if (!executeCommand("cp '$tempConfigFile' " . ROUTER_CONFIG)) {
|
|
unlink($tempConfigFile);
|
|
throw new Exception('Failed to save router configuration');
|
|
}
|
|
unlink($tempConfigFile);
|
|
|
|
if (!executeCommand("chmod 644 " . ROUTER_CONFIG)) {
|
|
throw new Exception('Failed to set router config permissions');
|
|
}
|
|
|
|
// Fix router configuration for proper enrollment
|
|
$this->fixRouterConfiguration();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Fix router configuration (replicate bash script logic)
|
|
*/
|
|
private function fixRouterConfiguration() {
|
|
// Create backup using sudo
|
|
executeCommand("cp " . ROUTER_CONFIG . " " . ROUTER_CONFIG . ".backup");
|
|
|
|
$routerName = $this->routerData['routerInfo']['name'];
|
|
$routerId = $this->routerData['routerInfo']['id'];
|
|
$tenantId = $this->routerData['metadata']['tenantId'];
|
|
$controllerEndpoint = $this->routerData['metadata']['controllerEndpoint'] ?? 'enroll.zitinexus.com:443';
|
|
|
|
// Add tls: prefix if not present
|
|
if (strpos($controllerEndpoint, 'tls:') !== 0) {
|
|
$controllerEndpoint = 'tls:' . $controllerEndpoint;
|
|
}
|
|
|
|
// Build role attributes
|
|
$roleAttributesSection = '# No role attributes specified';
|
|
if (!empty($this->routerData['routerInfo']['roleAttributes'])) {
|
|
$roleAttributesSection = "roleAttributes:";
|
|
foreach ($this->routerData['routerInfo']['roleAttributes'] as $attr) {
|
|
$roleAttributesSection .= "\n - \"$attr\"";
|
|
}
|
|
}
|
|
|
|
$generatedAt = date('c');
|
|
|
|
$configContent = <<<EOF
|
|
v: 3
|
|
|
|
identity:
|
|
cert: /etc/zitirouter/certs/$routerName.cert
|
|
server_cert: /etc/zitirouter/certs/$routerName.server.chain.cert
|
|
key: /etc/zitirouter/certs/$routerName.key
|
|
ca: /etc/zitirouter/certs/$routerName.cas
|
|
|
|
ctrl:
|
|
endpoint: $controllerEndpoint
|
|
|
|
link:
|
|
dialers:
|
|
- binding: transport
|
|
|
|
listeners:
|
|
# bindings of edge and tunnel requires an "edge" section below
|
|
- binding: edge
|
|
address: tls:0.0.0.0:443
|
|
options:
|
|
advertise: 127.0.0.1:443
|
|
connectTimeoutMs: 5000
|
|
getSessionTimeout: 60
|
|
- binding: tunnel
|
|
options:
|
|
mode: host
|
|
|
|
edge:
|
|
csr:
|
|
country: SG
|
|
province: SG
|
|
locality: Singapore
|
|
organization: Genworx
|
|
organizationalUnit: ZitiNexus
|
|
sans:
|
|
dns:
|
|
- localhost
|
|
- $routerName
|
|
ip:
|
|
- "127.0.0.1"
|
|
- "::1"
|
|
|
|
# Tenant-specific role attributes
|
|
$roleAttributesSection
|
|
|
|
# Router metadata
|
|
metadata:
|
|
tenantId: "$tenantId"
|
|
zitiRouterId: "$routerId"
|
|
routerType: "private-edge"
|
|
generatedAt: "$generatedAt"
|
|
generatedBy: "ZitiNexus"
|
|
EOF;
|
|
|
|
// Write updated config using temp file and sudo
|
|
$tempConfigFile = tempnam(sys_get_temp_dir(), 'ziti-fixed-config');
|
|
file_put_contents($tempConfigFile, $configContent);
|
|
|
|
if (!executeCommand("cp '$tempConfigFile' " . ROUTER_CONFIG)) {
|
|
unlink($tempConfigFile);
|
|
throw new Exception('Failed to save updated router configuration');
|
|
}
|
|
unlink($tempConfigFile);
|
|
|
|
if (!executeCommand("chmod 644 " . ROUTER_CONFIG)) {
|
|
throw new Exception('Failed to set updated router config permissions');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enroll router with OpenZiti
|
|
*/
|
|
private function enrollWithZiti() {
|
|
$command = 'ziti router enroll --jwt ' . JWT_FILE . ' ' . ROUTER_CONFIG . ' 2>&1';
|
|
$output = '';
|
|
|
|
if (!executeCommand($command, $output)) {
|
|
throw new Exception('Router enrollment failed: ' . $output);
|
|
}
|
|
|
|
// Verify certificates were created using sudo (since certs are root-owned with 600 permissions)
|
|
$routerName = $this->routerData['routerInfo']['name'];
|
|
$certFile = CERTS_DIR . '/' . $routerName . '.cert';
|
|
|
|
// Use sudo to check if certificate file exists (www-data can't read root-owned 600 files)
|
|
$checkOutput = '';
|
|
if (!executeCommand("test -f '$certFile'", $checkOutput)) {
|
|
// List what files actually exist for debugging
|
|
$listOutput = '';
|
|
executeCommand("ls -la " . CERTS_DIR . "/", $listOutput);
|
|
throw new Exception("Router certificate not found after enrollment. Expected: $certFile. Files in certs directory: " . $listOutput);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Create systemd service
|
|
*/
|
|
private function createSystemdService() {
|
|
$finalConfig = '/etc/zitirouter/zitirouter.yaml';
|
|
|
|
// Copy router config to final location using sudo
|
|
if (!executeCommand("cp " . ROUTER_CONFIG . " '$finalConfig'")) {
|
|
throw new Exception('Failed to copy router config to final location');
|
|
}
|
|
|
|
if (!executeCommand("chmod 644 '$finalConfig'")) {
|
|
throw new Exception('Failed to set final config permissions');
|
|
}
|
|
|
|
$serviceContent = <<<EOF
|
|
[Unit]
|
|
Description=OpenZiti Router
|
|
After=network.target
|
|
|
|
[Service]
|
|
ExecStart=/usr/bin/ziti router run /etc/zitirouter/zitirouter.yaml
|
|
Restart=on-failure
|
|
User=root
|
|
WorkingDirectory=/etc/zitirouter
|
|
LimitNOFILE=65536
|
|
StandardOutput=append:/var/log/ziti-router.log
|
|
StandardError=append:/var/log/ziti-router.log
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF;
|
|
|
|
// Write service file using sudo
|
|
$tempFile = tempnam(sys_get_temp_dir(), 'ziti-service');
|
|
file_put_contents($tempFile, $serviceContent);
|
|
|
|
if (!executeCommand("cp '$tempFile' " . SYSTEMD_SERVICE_FILE)) {
|
|
unlink($tempFile);
|
|
throw new Exception('Failed to create systemd service file');
|
|
}
|
|
unlink($tempFile);
|
|
|
|
// Reload systemd and enable service
|
|
if (!executeCommand('systemctl daemon-reload')) {
|
|
throw new Exception('Failed to reload systemd');
|
|
}
|
|
|
|
if (!executeCommand('systemctl enable ziti-router.service')) {
|
|
throw new Exception('Failed to enable ziti-router service');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Start router service
|
|
*/
|
|
private function startRouter() {
|
|
if (!executeCommand('systemctl start ziti-router.service')) {
|
|
throw new Exception('Failed to start router service');
|
|
}
|
|
|
|
// Wait and check status
|
|
sleep(3);
|
|
$output = '';
|
|
executeCommand('systemctl is-active ziti-router.service', $output);
|
|
|
|
if (trim($output) !== 'active') {
|
|
logMessage('WARNING', 'Router service may not be running properly');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Report success status
|
|
*/
|
|
private function reportSuccessStatus($hashKey) {
|
|
if (empty($this->routerData['callbackUrl'])) {
|
|
return;
|
|
}
|
|
|
|
$hostname = '';
|
|
$arch = '';
|
|
$os = '';
|
|
$zitiVersion = '';
|
|
|
|
executeCommand('hostname', $hostname);
|
|
executeCommand('uname -m', $arch);
|
|
executeCommand('lsb_release -d 2>/dev/null | cut -f2', $os);
|
|
executeCommand('ziti version 2>/dev/null | head -n1', $zitiVersion);
|
|
|
|
$routerInfo = [
|
|
'version' => trim($zitiVersion) ?: 'unknown',
|
|
'hostname' => trim($hostname),
|
|
'arch' => trim($arch),
|
|
'os' => trim($os) ?: 'Linux'
|
|
];
|
|
|
|
$this->apiClient->reportStatus(
|
|
$this->routerData['callbackUrl'],
|
|
$hashKey,
|
|
'success',
|
|
$routerInfo
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Add OpenZiti GPG key with enhanced error handling for CloudStack environments
|
|
*/
|
|
private function addOpenZitiGpgKey() {
|
|
$gpgKeyUrl = 'https://get.openziti.io/tun/package-repos.gpg';
|
|
$gpgKeyPath = '/usr/share/keyrings/openziti.gpg';
|
|
|
|
// First, check if GPG key already exists and is valid
|
|
if (file_exists($gpgKeyPath) && filesize($gpgKeyPath) > 0) {
|
|
$this->reportProgress('INSTALL', 'OpenZiti GPG key already exists (' . filesize($gpgKeyPath) . ' bytes), skipping installation');
|
|
logMessage('INFO', 'GPG key already exists at: ' . $gpgKeyPath);
|
|
return true;
|
|
}
|
|
|
|
$this->reportProgress('INSTALL', 'Installing OpenZiti GPG key...');
|
|
|
|
// Ensure keyrings directory exists with proper permissions
|
|
if (!$this->ensureKeyringsDirectory()) {
|
|
logMessage('ERROR', 'Failed to create keyrings directory');
|
|
return false;
|
|
}
|
|
|
|
// Method 1: CloudStack-optimized approach - download and process separately
|
|
$this->reportProgress('INSTALL', 'Downloading GPG key...');
|
|
$tempGpgFile = tempnam(sys_get_temp_dir(), 'openziti-gpg');
|
|
|
|
// Step 1: Download GPG key with detailed error reporting
|
|
$downloadCommand = 'curl -sSLf --connect-timeout 30 --max-time 60 ' . $gpgKeyUrl . ' -o ' . $tempGpgFile;
|
|
$output = '';
|
|
|
|
if (!executeCommand($downloadCommand, $output)) {
|
|
@unlink($tempGpgFile);
|
|
$errorMsg = 'Failed to download GPG key from ' . $gpgKeyUrl . '. Error: ' . $output;
|
|
logMessage('ERROR', $errorMsg);
|
|
$this->reportProgress('INSTALL', 'Download failed: ' . $output);
|
|
return false;
|
|
}
|
|
|
|
// Step 2: Verify downloaded file
|
|
if (!file_exists($tempGpgFile) || filesize($tempGpgFile) == 0) {
|
|
@unlink($tempGpgFile);
|
|
$errorMsg = 'Downloaded GPG key file is empty or missing';
|
|
logMessage('ERROR', $errorMsg);
|
|
$this->reportProgress('INSTALL', $errorMsg);
|
|
return false;
|
|
}
|
|
|
|
$fileSize = filesize($tempGpgFile);
|
|
$this->reportProgress('INSTALL', "GPG key downloaded successfully ($fileSize bytes), processing...");
|
|
logMessage('INFO', "Downloaded GPG key: $fileSize bytes to $tempGpgFile");
|
|
|
|
// Step 3: Try multiple processing methods with detailed error reporting
|
|
$methods = [
|
|
'sudo_gpg_dearmor' => [
|
|
'name' => 'Sudo GPG dearmor',
|
|
'command' => "sudo /usr/bin/gpg --dearmor --output '$gpgKeyPath' '$tempGpgFile'"
|
|
],
|
|
'direct_gpg_dearmor' => [
|
|
'name' => 'Direct GPG dearmor',
|
|
'command' => "/usr/bin/gpg --dearmor --output '$gpgKeyPath' '$tempGpgFile'"
|
|
],
|
|
'sudo_cat_pipe' => [
|
|
'name' => 'Sudo cat pipe',
|
|
'command' => "sudo bash -c 'cat $tempGpgFile | /usr/bin/gpg --dearmor > $gpgKeyPath'"
|
|
],
|
|
'sudo_redirect' => [
|
|
'name' => 'Sudo redirect',
|
|
'command' => "sudo bash -c '/usr/bin/gpg --dearmor < $tempGpgFile > $gpgKeyPath'"
|
|
],
|
|
'python_base64' => [
|
|
'name' => 'Python base64 decode',
|
|
'command' => "sudo python3 -c \"import base64; open('$gpgKeyPath', 'wb').write(base64.b64decode(open('$tempGpgFile', 'rb').read()))\""
|
|
]
|
|
];
|
|
|
|
foreach ($methods as $methodKey => $method) {
|
|
$this->reportProgress('INSTALL', "Trying method: {$method['name']}...");
|
|
logMessage('INFO', "Attempting GPG processing method: {$method['name']}");
|
|
|
|
$output = '';
|
|
if (executeCommand($method['command'], $output)) {
|
|
// Verify the processed file exists and has content
|
|
if (file_exists($gpgKeyPath) && filesize($gpgKeyPath) > 0) {
|
|
@unlink($tempGpgFile);
|
|
$processedSize = filesize($gpgKeyPath);
|
|
$this->reportProgress('INSTALL', "GPG key processed successfully using {$method['name']} ($processedSize bytes)");
|
|
logMessage('INFO', "GPG key successfully processed: $processedSize bytes at $gpgKeyPath");
|
|
return true;
|
|
} else {
|
|
logMessage('WARNING', "Method {$method['name']} completed but output file is missing or empty");
|
|
}
|
|
} else {
|
|
logMessage('WARNING', "Method {$method['name']} failed: $output");
|
|
}
|
|
}
|
|
|
|
// Method 4: Last resort - try to use the raw file directly
|
|
$this->reportProgress('INSTALL', 'Trying raw file copy as last resort...');
|
|
logMessage('INFO', 'Attempting raw file copy as final fallback');
|
|
|
|
if (executeCommand("sudo cp '$tempGpgFile' '$gpgKeyPath'", $output)) {
|
|
if (file_exists($gpgKeyPath) && filesize($gpgKeyPath) > 0) {
|
|
@unlink($tempGpgFile);
|
|
$this->reportProgress('INSTALL', 'GPG key copied as raw file - apt will handle format conversion');
|
|
logMessage('INFO', 'GPG key copied as raw file for apt to process');
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Clean up and report final failure
|
|
@unlink($tempGpgFile);
|
|
$errorMsg = 'All GPG key installation methods failed. Check system permissions and network connectivity.';
|
|
logMessage('ERROR', $errorMsg);
|
|
$this->reportProgress('INSTALL', $errorMsg);
|
|
|
|
// Additional diagnostic information
|
|
$this->logDiagnosticInfo();
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Ensure keyrings directory exists with proper permissions
|
|
*/
|
|
private function ensureKeyringsDirectory() {
|
|
$keyringsDir = '/usr/share/keyrings';
|
|
|
|
// Check if directory exists
|
|
if (is_dir($keyringsDir)) {
|
|
logMessage('INFO', 'Keyrings directory already exists');
|
|
return true;
|
|
}
|
|
|
|
// Try to create directory
|
|
$output = '';
|
|
if (executeCommand("sudo mkdir -p '$keyringsDir'", $output)) {
|
|
if (executeCommand("sudo chmod 755 '$keyringsDir'", $output)) {
|
|
logMessage('INFO', 'Keyrings directory created successfully');
|
|
return true;
|
|
} else {
|
|
logMessage('ERROR', 'Failed to set keyrings directory permissions: ' . $output);
|
|
return false;
|
|
}
|
|
} else {
|
|
logMessage('ERROR', 'Failed to create keyrings directory: ' . $output);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Log diagnostic information for troubleshooting
|
|
*/
|
|
private function logDiagnosticInfo() {
|
|
$diagnostics = [];
|
|
|
|
// Check basic commands
|
|
$commands = ['curl', 'gpg', 'python3', 'sudo'];
|
|
foreach ($commands as $cmd) {
|
|
$output = '';
|
|
$available = executeCommand("which $cmd", $output) ? 'available' : 'not found';
|
|
$diagnostics[] = "$cmd: $available";
|
|
}
|
|
|
|
// Check permissions
|
|
$output = '';
|
|
executeCommand('id', $output);
|
|
$diagnostics[] = "Current user: " . trim($output);
|
|
|
|
// Check sudo permissions
|
|
$output = '';
|
|
executeCommand('sudo -l | grep gpg', $output);
|
|
$diagnostics[] = "GPG sudo permissions: " . (empty(trim($output)) ? 'none found' : 'available');
|
|
|
|
// Check network connectivity
|
|
$output = '';
|
|
$networkOk = executeCommand('curl -I --connect-timeout 5 https://get.openziti.io', $output) ? 'working' : 'failed';
|
|
$diagnostics[] = "Network connectivity: $networkOk";
|
|
|
|
// Check disk space
|
|
$output = '';
|
|
executeCommand('df -h /usr/share', $output);
|
|
$diagnostics[] = "Disk space: " . trim($output);
|
|
|
|
logMessage('INFO', 'GPG Installation Diagnostics: ' . implode('; ', $diagnostics));
|
|
}
|
|
|
|
/**
|
|
* Check if command exists
|
|
*/
|
|
private function checkCommand($command) {
|
|
$output = '';
|
|
return executeCommand("which $command", $output);
|
|
}
|
|
|
|
/**
|
|
* Install package using apt
|
|
*/
|
|
private function installPackage($package) {
|
|
return executeCommand("apt-get update && apt-get install -y $package");
|
|
}
|
|
|
|
/**
|
|
* Get system status information
|
|
*/
|
|
public function getSystemStatus() {
|
|
$status = [
|
|
'hostname' => '',
|
|
'ziti_status' => 'unknown',
|
|
'ziti_version' => '',
|
|
'service_active' => false,
|
|
'config_exists' => false,
|
|
'certificates_exist' => false
|
|
];
|
|
|
|
// Get hostname
|
|
executeCommand('hostname', $status['hostname']);
|
|
$status['hostname'] = trim($status['hostname']);
|
|
|
|
// Check if ziti command exists and get version
|
|
if ($this->checkCommand('ziti')) {
|
|
executeCommand('ziti version 2>/dev/null | head -n1', $status['ziti_version']);
|
|
$status['ziti_version'] = trim($status['ziti_version']);
|
|
$status['ziti_status'] = 'installed';
|
|
} else {
|
|
$status['ziti_status'] = 'not_installed';
|
|
}
|
|
|
|
// Check service status
|
|
$output = '';
|
|
if (executeCommand('systemctl is-active ziti-router.service 2>/dev/null', $output)) {
|
|
$status['service_active'] = trim($output) === 'active';
|
|
}
|
|
|
|
// Check configuration files
|
|
$status['config_exists'] = file_exists(ROUTER_CONFIG);
|
|
|
|
// Check certificates
|
|
if (is_dir(CERTS_DIR)) {
|
|
$certFiles = glob(CERTS_DIR . '/*.cert');
|
|
$status['certificates_exist'] = !empty($certFiles);
|
|
}
|
|
|
|
return $status;
|
|
}
|
|
}
|
|
?>
|