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 (simplified - assumes OpenZiti is pre-installed) */ public function enrollRouter($hashKey, $apiEndpoint = null) { try { if ($apiEndpoint) { $this->apiClient = new ApiClient($apiEndpoint); } $this->reportProgress('INIT', 'Starting router enrollment process...', 0); // Step 1: Verify OpenZiti is installed $this->reportProgress('REQUIREMENTS', 'Verifying OpenZiti installation...', 10); if (!$this->verifyZitiInstallation()) { throw new Exception('OpenZiti CLI not found. Please run install.sh first to install required packages.'); } // Step 2: Create directories $this->reportProgress('DIRECTORIES', 'Creating necessary directories...', 20); if (!$this->createDirectories()) { throw new Exception('Failed to create directories'); } // Step 3: Register router with API $this->reportProgress('REGISTER', 'Registering router with ZitiNexus Portal...', 30); $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'], 40); // Step 4: Save configuration files $this->reportProgress('CONFIG', 'Saving configuration files...', 50); if (!$this->saveConfiguration()) { throw new Exception('Failed to save configuration files'); } // Step 5: Enroll router with OpenZiti $this->reportProgress('ENROLL', 'Enrolling router with OpenZiti controller...', 60); if (!$this->enrollWithZiti()) { throw new Exception('Router enrollment with OpenZiti failed'); } // Step 6: Create systemd service $this->reportProgress('SERVICE', 'Creating systemd service...', 75); if (!$this->createSystemdService()) { throw new Exception('Failed to create systemd service'); } // Step 7: Start router service $this->reportProgress('START', 'Starting router service...', 85); if (!$this->startRouter()) { throw new Exception('Failed to start router service'); } // Step 8: 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 ]; } } /** * Verify OpenZiti installation (assumes pre-installed by install.sh) */ private function verifyZitiInstallation() { // Check if running as root if (!isRunningAsRoot()) { throw new Exception('This script must be run as root (use sudo)'); } // Check if ziti command exists if (!$this->checkCommand('ziti')) { throw new Exception('OpenZiti CLI not found. Please run install.sh first to install required packages.'); } // Get and report ziti version $output = ''; executeCommand('ziti version 2>/dev/null | head -n1', $output); $zitiVersion = trim($output); $this->reportProgress('REQUIREMENTS', 'OpenZiti CLI found: ' . ($zitiVersion ?: 'unknown version')); // Verify ziti router command is available if (!executeCommand('ziti router --help >/dev/null 2>&1')) { throw new Exception('OpenZiti router commands not available. Please run install.sh to install the complete OpenZiti package.'); } // Check if systemctl is available if (!$this->checkCommand('systemctl')) { throw new Exception('systemctl is required but not available'); } // Verify basic system commands are available (should be installed by install.sh) $requiredCommands = ['curl', 'hostname', 'uname']; foreach ($requiredCommands as $cmd) { if (!$this->checkCommand($cmd)) { throw new Exception("Required command '$cmd' not found. Please run install.sh to install system dependencies."); } } $this->reportProgress('REQUIREMENTS', 'All required components verified successfully'); 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 = <<&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 = <<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 ); } /** * 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; } } ?>