#!/usr/bin/env php * * This file is part of a Moko Consulting project. * * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION * DEFGROUP: moko-platform.CLI * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/client_inventory.php * VERSION: 01.00.00 * BRIEF: Discover and list all client-waas repos with their server configuration status */ declare(strict_types=1); final class ClientInventory { private string $giteaUrl = 'https://git.mokoconsulting.tech'; private string $token = ''; private bool $jsonOutput = false; public function run(): int { $this->parseArgs(); if ($this->token === '') { $this->log('ERROR: --token is required.'); $this->printUsage(); return 1; } $this->log("Scanning Gitea instance: {$this->giteaUrl}"); // Step 1: List all orgs $orgs = $this->fetchOrgs(); if ($orgs === null) { $this->log('ERROR: Failed to fetch organizations.'); return 1; } $this->log('Found ' . count($orgs) . ' organization(s).'); // Step 2 & 3: For each org, find client-waas repos $inventory = []; foreach ($orgs as $org) { $orgName = $org['username'] ?? $org['name'] ?? ''; if ($orgName === '') { continue; } $repos = $this->fetchOrgRepos($orgName); if ($repos === null) { $this->log("WARNING: Could not fetch repos for org: {$orgName}"); continue; } foreach ($repos as $repo) { $repoName = $repo['name'] ?? ''; if (strpos($repoName, 'client-waas') === false) { continue; } $hasDevConfig = $this->checkVariables($orgName, $repoName, ['DEV_SYNC_HOST', 'DEV_SYNC_PATH']); $hasLiveConfig = $this->checkVariables($orgName, $repoName, ['LIVE_SSH_HOST', 'LIVE_SYNC_PATH']); $lastPush = $repo['updated_at'] ?? 'unknown'; if ($lastPush !== 'unknown') { $lastPush = substr($lastPush, 0, 19); } $status = 'OK'; if (!$hasDevConfig && !$hasLiveConfig) { $status = 'UNCONFIGURED'; } elseif (!$hasDevConfig) { $status = 'NO DEV'; } elseif (!$hasLiveConfig) { $status = 'NO LIVE'; } $inventory[] = [ 'org' => $orgName, 'repo' => $repoName, 'has_dev_config' => $hasDevConfig, 'has_live_config' => $hasLiveConfig, 'last_push' => $lastPush, 'status' => $status, ]; } } // Output results if ($this->jsonOutput) { fwrite(STDOUT, json_encode($inventory, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL); return 0; } if (count($inventory) === 0) { $this->log('No client-waas repos found.'); return 0; } // Print table $this->log(''); $this->log(sprintf( '%-20s | %-35s | %-10s | %-11s | %-19s | %s', 'Org', 'Repo', 'Dev Config', 'Live Config', 'Last Push', 'Status' )); $this->log(str_repeat('-', 120)); foreach ($inventory as $entry) { $this->log(sprintf( '%-20s | %-35s | %-10s | %-11s | %-19s | %s', $entry['org'], $entry['repo'], $entry['has_dev_config'] ? 'Yes' : 'No', $entry['has_live_config'] ? 'Yes' : 'No', $entry['last_push'], $entry['status'] )); } $this->log(''); $this->log('Total: ' . count($inventory) . ' client-waas repo(s).'); return 0; } private function parseArgs(): void { $args = $_SERVER['argv'] ?? []; $count = count($args); for ($i = 1; $i < $count; $i++) { switch ($args[$i]) { case '--gitea-url': $this->giteaUrl = rtrim($args[++$i] ?? '', '/'); break; case '--token': $this->token = $args[++$i] ?? ''; break; case '--json': $this->jsonOutput = true; break; case '--help': case '-h': $this->printUsage(); exit(0); default: $this->log("WARNING: Unknown argument: {$args[$i]}"); break; } } } private function printUsage(): void { $this->log('Usage: client_inventory.php --token [options]'); $this->log(''); $this->log('Options:'); $this->log(' --gitea-url Gitea URL (default: https://git.mokoconsulting.tech)'); $this->log(' --token Gitea API token'); $this->log(' --json Output results as JSON'); $this->log(' --help, -h Show this help'); } private function fetchOrgs(): ?array { // Try admin endpoint first, fall back to user-visible orgs $response = $this->apiRequest('GET', '/api/v1/admin/orgs?limit=50'); if ($response['code'] >= 200 && $response['code'] < 300) { $data = json_decode($response['body'], true); if (is_array($data)) { return $data; } } $this->log('Admin orgs endpoint unavailable, falling back to user orgs...'); $response = $this->apiRequest('GET', '/api/v1/user/orgs?limit=50'); if ($response['code'] >= 200 && $response['code'] < 300) { $data = json_decode($response['body'], true); if (is_array($data)) { return $data; } } return null; } private function fetchOrgRepos(string $org): ?array { $page = 1; $allRepos = []; while (true) { $response = $this->apiRequest('GET', "/api/v1/orgs/{$org}/repos?limit=50&page={$page}"); if ($response['code'] < 200 || $response['code'] >= 300) { return $page === 1 ? null : $allRepos; } $data = json_decode($response['body'], true); if (!is_array($data) || count($data) === 0) { break; } $allRepos = array_merge($allRepos, $data); $page++; } return $allRepos; } private function checkVariables(string $org, string $repo, array $requiredVars): bool { $response = $this->apiRequest('GET', "/api/v1/repos/{$org}/{$repo}/actions/variables"); if ($response['code'] < 200 || $response['code'] >= 300) { return false; } $data = json_decode($response['body'], true); if (!is_array($data)) { return false; } $existingVars = []; foreach ($data as $variable) { if (isset($variable['name'])) { $existingVars[] = $variable['name']; } } foreach ($requiredVars as $var) { if (!in_array($var, $existingVars, true)) { return false; } } return true; } private function apiRequest(string $method, string $endpoint, ?string $body = null): array { $url = $this->giteaUrl . $endpoint; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Accept: application/json', "Authorization: token {$this->token}", ]); if ($body !== null) { curl_setopt($ch, CURLOPT_POSTFIELDS, $body); } $responseBody = curl_exec($ch); $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); if (curl_errno($ch)) { $error = curl_error($ch); curl_close($ch); return ['code' => 0, 'body' => "cURL error: {$error}"]; } curl_close($ch); return ['code' => $httpCode, 'body' => $responseBody]; } private function log(string $message): void { fwrite(STDERR, $message . PHP_EOL); } } $app = new ClientInventory(); exit($app->run());