#!/usr/bin/env php
<?php
/**
 * onoxd — Onoxsoft License Watchdog Daemon
 *
 * Long-running process (systemd service). Her 30 saniyede bir:
 *   1. LicenseManager'dan mevcut tier features'ini al
 *   2. Premium servisleri listele (mail_server -> postfix/dovecot, vs.)
 *   3. Lisansta OLMAYAN feature'in servisi calisiyor mu kontrol et
 *   4. Calisiyorsa:
 *      - ADVISORY mode: log + crack-report (servisi DURDURMA)
 *      - ENFORCE mode: systemctl stop + systemctl mask + crack-report
 *   5. Lisans heartbeat OK kontrol — JWT expired/invalid ise yine de calismaya devam
 *      (panel kendi grace periodu uygular, watchdog karismaz)
 *
 * Mode degisikligi: /opt/onoxsoft/.onox-enforce dosyasi varlik kontrolu ile.
 * Operator panelden veya elle "touch /opt/onoxsoft/.onox-enforce" yapinca
 * sonraki dongude enforce mode'a gecer.
 *
 * Install: /usr/local/sbin/onoxd + /etc/systemd/system/onoxd.service
 */

declare(strict_types=1);

const ONOX_ROOT = '/opt/onoxsoft';
const CHECK_INTERVAL_SEC = 30;
const LOG_FILE = '/var/log/onoxsoft/onoxd.log';
const VIOLATION_LOG = '/var/log/onoxsoft/license-violations.log';
const ENFORCE_FLAG_FILE = ONOX_ROOT . '/.onox-enforce';

/**
 * Premium feature -> systemd service map.
 *
 * Yeni premium feature ekleyince bu map'i guncelle.
 * Servis adlari Linux distro'ya gore degisebilir (postfix vs. postfix.service).
 * Watchdog `systemctl is-active` ile generic kontrol yapar.
 */
/**
 * v83.14: MTA driver dinamik — Postfix VEYA Exim aktif olabilir. Hardcoded
 * 'postfix' eklenirse, Exim aktifken onoxd Postfix'i auto-start ediyordu →
 * Postfix.service'in `Conflicts=exim.service` directive'i tetiklenip systemd
 * önce Exim'i stop ediyor → Postfix license-check fail → no MTA active.
 *
 * Fix: FEATURE_SERVICES static yerine dinamik (getFeatureServices() function).
 * mail_server için DB'den aktif MTA okunup sadece onu listele.
 */
function getFeatureServices(): array {
    static $cache = null;
    static $cachedAt = 0;
    // 30 sn cache (CHECK_INTERVAL_SEC ile uyumlu — her döngüde fresh)
    if ($cache !== null && (time() - $cachedAt) < 30) {
        return $cache;
    }

    $activeMta = getActiveMtaDriver();
    // 'none'/'relay' local servis yok; postfix/exim ise sadece o servisi listele
    $mtaServices = ($activeMta && in_array($activeMta, ['postfix', 'exim'], true))
        ? [$activeMta]
        : [];

    $cache = [
        'mail_server'     => array_merge($mtaServices, ['dovecot', 'opendkim', 'opendmarc', 'saslauthd', 'rspamd']),
        'whmcs_plugin'    => ['onox-whmcs-bridge'],
        'ai_assistant'    => ['onox-ai-worker'],
        'cluster'         => ['onox-cluster-agent'],
        'reseller'        => ['onox-reseller-api'],
        'cpanel_migration'=> [],
    ];
    $cachedAt = time();
    return $cache;
}

/**
 * v83.14: Aktif MTA driver'ı panel DB'sinden oku.
 * PDO direct connect — Laravel bootstrap onoxd boot süresi için ağır.
 * .env'den DB credentials parse + tek satır SELECT.
 */
function getActiveMtaDriver(): ?string {
    static $envCache = null;
    if ($envCache === null) {
        $envFile = '/opt/onoxsoft/.env';
        if (! is_readable($envFile)) return null;
        $envCache = [];
        foreach (file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
            if (preg_match('/^\s*([A-Z_]+)\s*=\s*"?([^"]*)"?\s*$/', $line, $m)) {
                $envCache[$m[1]] = $m[2];
            }
        }
    }
    $host = $envCache['DB_HOST'] ?? '127.0.0.1';
    $port = $envCache['DB_PORT'] ?? '3306';
    $name = $envCache['DB_DATABASE'] ?? 'onoxsoft_panel';
    $user = $envCache['DB_USERNAME'] ?? null;
    $pass = $envCache['DB_PASSWORD'] ?? null;
    if (! $user || ! $pass) return null;

    try {
        $pdo = new PDO("mysql:host={$host};port={$port};dbname={$name};charset=utf8mb4", $user, $pass, [
            PDO::ATTR_TIMEOUT => 2,
            PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT,
        ]);
        $stmt = $pdo->query("SELECT `value` FROM server_settings WHERE `key`='mailserver_driver' LIMIT 1");
        $val = $stmt ? $stmt->fetchColumn() : null;
        return is_string($val) && $val !== '' ? $val : null;
    } catch (\Throwable) {
        return null; // DB unreachable → tolerant (boş list dön, hiçbir MTA monitor edilmez)
    }
}

// Backward-compat: bazı yerlerde hala FEATURE_SERVICES referansı varsa
// const olarak en sade default'u tutuyoruz (sadece Dovecot vs).
const FEATURE_SERVICES_FALLBACK = [
    'mail_server'     => ['dovecot', 'opendkim', 'opendmarc', 'saslauthd', 'rspamd'],
    'whmcs_plugin'    => ['onox-whmcs-bridge'],
    'ai_assistant'    => ['onox-ai-worker'],
    'cluster'         => ['onox-cluster-agent'],
    'reseller'        => ['onox-reseller-api'],
    'cpanel_migration'=> [],

    // NOT: firewall + fail2ban ASLA listede degil!
    // Bu altyapi servisleri (httpd, mariadb, firewalld, fail2ban, crond, sshd)
    // sistem buyunde olmazsa olmaz — onoxd ASLA dokunmamalı.
];

/**
 * KRITIK altyapi servisleri — onoxd ASLA dokunmaz.
 * Bu liste defansif sigorta: yanlislikla FEATURE_SERVICES'a eklense bile
 * stopBlockedService() bu listedeki unit'leri atlar.
 */
const NEVER_TOUCH_SERVICES = [
    'httpd', 'apache2', 'nginx',         // web sunucu
    'mariadb', 'mysql', 'postgresql',    // DB
    'redis', 'memcached',                // cache
    'firewalld', 'iptables', 'nftables', // firewall
    'fail2ban',                          // brute force koruma
    'sshd', 'ssh',                       // SSH
    'crond', 'cronie', 'cron',           // cron
    'systemd-journald',                  // log
    'php82-php-fpm', 'php83-php-fpm', 'php81-php-fpm', 'php74-php-fpm',
    'onoxsoft-horizon', 'onoxsoft-queue', // panel queue worker
    'onoxd',                             // KENDISI! suicide korumasi
    'pdns', 'powerdns',                  // DNS
    'chronyd', 'chrony', 'ntpd',         // NTP
];

// ─── Logging ───────────────────────────────────────────────────────
function ensureLogDir(): void {
    $dir = dirname(LOG_FILE);
    if (! is_dir($dir)) @mkdir($dir, 0755, true);
}

function logLine(string $level, string $msg, array $ctx = []): void {
    $line = sprintf(
        "[%s] %s %s%s\n",
        date('Y-m-d H:i:s'),
        $level,
        $msg,
        $ctx ? ' ' . json_encode($ctx, JSON_UNESCAPED_UNICODE) : ''
    );
    @file_put_contents(LOG_FILE, $line, FILE_APPEND | LOCK_EX);
}

function logViolation(string $service, string $feature, string $action): void {
    $line = sprintf(
        "[%s] WATCHDOG service=%s feature=%s action=%s host=%s\n",
        date('Y-m-d H:i:s'),
        $service,
        $feature,
        $action,
        gethostname() ?: 'unknown'
    );
    @file_put_contents(VIOLATION_LOG, $line, FILE_APPEND | LOCK_EX);
}

// ─── Mode detection ────────────────────────────────────────────────
function isEnforceMode(): bool {
    return file_exists(ENFORCE_FLAG_FILE);
}

// ─── systemctl helpers ─────────────────────────────────────────────
function svcExists(string $svc): bool {
    exec("systemctl cat -- " . escapeshellarg($svc) . " >/dev/null 2>&1", $out, $rc);
    return $rc === 0;
}

function svcIsActive(string $svc): bool {
    exec("systemctl is-active -- " . escapeshellarg($svc) . " 2>/dev/null", $out, $rc);
    return $rc === 0 && ($out[0] ?? '') === 'active';
}

function svcIsMasked(string $svc): bool {
    exec("systemctl is-enabled -- " . escapeshellarg($svc) . " 2>/dev/null", $out, $rc);
    return ($out[0] ?? '') === 'masked';
}

function svcStop(string $svc): bool {
    exec("systemctl stop -- " . escapeshellarg($svc) . " 2>&1", $out, $rc);
    return $rc === 0;
}

function svcStart(string $svc): bool {
    exec("systemctl start -- " . escapeshellarg($svc) . " 2>&1", $out, $rc);
    return $rc === 0;
}

function svcMask(string $svc): bool {
    // Mask = sembolic link to /dev/null — manuel start dahi engellenir.
    // Sadece enforce mode'da, ve customer 'systemctl unmask' ile geri alabilir.
    exec("systemctl mask -- " . escapeshellarg($svc) . " 2>&1", $out, $rc);
    return $rc === 0;
}

function svcUnmask(string $svc): bool {
    exec("systemctl unmask -- " . escapeshellarg($svc) . " 2>&1", $out, $rc);
    return $rc === 0;
}

function svcEnable(string $svc): bool {
    exec("systemctl enable -- " . escapeshellarg($svc) . " 2>&1", $out, $rc);
    return $rc === 0;
}

/**
 * Admin "geçici kapat" override kontrolü.
 * license_feature_overrides tablosunda feature için enabled=false varsa
 * onoxd auto-start YAPMAZ (admin/operator manuel kapatmıştır).
 *
 * Mantik: admin "Mail Server'i bakim icin kapat" derse onoxd 30sn sonra
 * tekrar acmamali. Override silinince (resetFeature) auto-start tekrar
 * devreye girer.
 */
function adminOverrideExplicitlyOff(string $feature): bool {
    try {
        if (! \Illuminate\Support\Facades\Schema::hasTable('license_feature_overrides')) {
            return false;
        }
        $row = \Illuminate\Support\Facades\DB::table('license_feature_overrides')
            ->where('feature', $feature)->first();
        return $row && ((int) $row->enabled) === 0;
    } catch (\Throwable) {
        return false;
    }
}

// ─── License manager bridge ────────────────────────────────────────
function loadLicenseFeatures(): ?array {
    // Laravel bootstrap her dongude — minimal overhead, fresh JWT okunur
    require_once ONOX_ROOT . '/vendor/autoload.php';
    $app = require ONOX_ROOT . '/bootstrap/app.php';
    $app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();

    try {
        $mgr = app(\App\Domain\License\Services\LicenseManager::class);
        if (! $mgr->isValid()) {
            // JWT yok / expired — bekle (grace period devrede, panel kendisi yonetir)
            return null;
        }
        return [
            'tier' => $mgr->tier(),
            'features' => $mgr->features(),
            'tier_allowed' => $mgr->tierAllowedFeatures(),
        ];
    } catch (\Throwable $e) {
        logLine('ERROR', 'license_load_exception', ['msg' => $e->getMessage()]);
        return null;
    }
}

function isFeatureLicensed(string $feature, array $licenseState): bool {
    $jwt = $licenseState['features'] ?? [];
    $tier = $licenseState['tier_allowed'] ?? [];
    $jwtAllows = in_array('*', $jwt, true) || in_array($feature, $jwt, true);
    $tierAllows = in_array('*', $tier, true) || in_array($feature, $tier, true);
    return $jwtAllows && $tierAllows;
}

function reportToLicenseServer(string $feature, string $service, string $action): void {
    try {
        require_once ONOX_ROOT . '/vendor/autoload.php';
        $app = require ONOX_ROOT . '/bootstrap/app.php';
        $app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();

        $mgr = app(\App\Domain\License\Services\LicenseManager::class);
        $claims = $mgr->getValidClaims();
        $uuid = $claims['sub'] ?? null;
        if (! $uuid) return;

        $client = app(\App\Domain\License\Services\OnoxsoftLicenseClient::class);
        $client->crackReport($uuid, 'service_unauthorized_running', [
            'service' => $service,
            'feature' => $feature,
            'action'  => $action,
            'host'    => gethostname(),
            'tier'    => $mgr->tier(),
        ]);
    } catch (\Throwable) {
        // Sessiz — watchdog yine de devam etmeli
    }
}

// ─── Ana dongu ─────────────────────────────────────────────────────
ensureLogDir();
logLine('INFO', 'onoxd started', ['pid' => getmypid(), 'interval' => CHECK_INTERVAL_SEC]);

// Graceful shutdown
$running = true;
pcntl_async_signals(true);
pcntl_signal(SIGTERM, function () use (&$running) { $running = false; });
pcntl_signal(SIGINT,  function () use (&$running) { $running = false; });

while ($running) {
    $enforce = isEnforceMode();
    $state = loadLicenseFeatures();

    if ($state === null) {
        // JWT yok — bekle (panel kendi durumunu yonetir, watchdog karismaz)
        logLine('DEBUG', 'no_valid_license_skip', []);
        sleep(CHECK_INTERVAL_SEC);
        continue;
    }

    // v83.14: Dinamik feature/service map — aktif MTA driver fresh oku
    foreach (getFeatureServices() as $feature => $services) {
        $licensed = isFeatureLicensed($feature, $state);

        foreach ($services as $svc) {
            if (! svcExists($svc)) continue;     // servis kurulu degil

            // ─── DURUM 1: LISANSTA VAR ─────────────────────────────
            // Auto-start: önceki enforce'tan kalan masked durumu varsa unmask + start
            // Manuel kapatma korumasi: admin override "kapali" varsa dokunma
            if ($licensed) {
                if (adminOverrideExplicitlyOff($feature)) {
                    // Admin manuel "geçici kapat" demiş — saygılı ol, dokunma
                    continue;
                }

                // Masked mi? Önceki enforce mode'dan kalmis olabilir
                if (svcIsMasked($svc)) {
                    $unmasked = svcUnmask($svc);
                    $enabled  = svcEnable($svc);
                    $started  = svcStart($svc);
                    logLine('AUTO_RECOVERY', 'license_restored_service_unmasked', [
                        'service' => $svc, 'feature' => $feature,
                        'unmask_ok' => $unmasked, 'enable_ok' => $enabled, 'start_ok' => $started,
                    ]);
                    continue;
                }

                // Calismiyor (manuel stop veya boot sirasinda baslamamis)?
                // Auto-start. Manuel kapatma override yoksa start et.
                if (! svcIsActive($svc)) {
                    $started = svcStart($svc);
                    logLine('AUTO_START', 'service_was_down_starting', [
                        'service' => $svc, 'feature' => $feature, 'start_ok' => $started,
                    ]);
                }
                // Else: calisiyor, OK
                continue;
            }

            // ─── DURUM 2: LISANSTA YOK ─────────────────────────────

            // KRITIK altyapi koruma — bu unit listede ise asla dokunma
            // (httpd, mariadb, firewalld, fail2ban, sshd vs.)
            if (in_array($svc, NEVER_TOUCH_SERVICES, true)) {
                logLine('SAFETY', 'critical_service_skip', [
                    'service' => $svc, 'feature' => $feature,
                    'note' => 'NEVER_TOUCH_SERVICES protection — altyapi servisi',
                ]);
                continue;
            }

            if (! svcIsActive($svc) && ! svcIsMasked($svc)) {
                continue; // Zaten kapali ve mask'lanmamış, dokunma
            }

            // VIOLATION — servis calisiyor ama lisansta yok
            $action = $enforce ? 'stopped+masked' : 'logged_only';
            logViolation($svc, $feature, $action);

            if ($enforce) {
                $stopped = svcIsActive($svc) ? svcStop($svc) : true;
                $masked  = ! svcIsMasked($svc) ? svcMask($svc) : true;
                logLine('ENFORCE', 'service_blocked', [
                    'service' => $svc, 'feature' => $feature,
                    'stop_ok' => $stopped, 'mask_ok' => $masked,
                ]);
            } else {
                logLine('ADVISORY', 'service_would_be_blocked', [
                    'service' => $svc, 'feature' => $feature,
                    'note' => 'currently running but not in license (advisory mode — no action)',
                ]);
            }

            // crack-report her durumda (advisory'de bile operator gormeli)
            reportToLicenseServer($feature, $svc, $action);
        }
    }

    sleep(CHECK_INTERVAL_SEC);
}

logLine('INFO', 'onoxd stopped (graceful)', []);
exit(0);
