mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 17:01:16 +00:00
Modernize tmux handling
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# =============================================================================
|
||||
# Tmux Configuration
|
||||
# =============================================================================
|
||||
# These settings configure the tmux session management system.
|
||||
# Most settings have sensible defaults and don't need to be changed.
|
||||
|
||||
# Tmux session name (default: from database setting or 'nntmux')
|
||||
# TMUX_SESSION_NAME=nntmux
|
||||
|
||||
# Process niceness level (0-19, lower = higher priority, default: 2)
|
||||
# Higher values use less CPU priority
|
||||
# TMUX_NICENESS=2
|
||||
|
||||
# Monitor refresh delay in seconds (default: 10)
|
||||
# How often the monitor updates statistics
|
||||
# TMUX_MONITOR_DELAY=10
|
||||
|
||||
# Monitor statistics refresh interval in seconds (default: 60)
|
||||
# How often to re-query the database for statistics
|
||||
# TMUX_REFRESH_INTERVAL=60
|
||||
|
||||
# Timeout for tmux operations in seconds (default: 300)
|
||||
# TMUX_TIMEOUT=300
|
||||
|
||||
# Custom tmux config file path (default: config/tmux.conf)
|
||||
# TMUX_CONFIG_FILE=/path/to/custom/tmux.conf
|
||||
|
||||
# Terminal type (default: xterm-256color)
|
||||
# TMUX_TERMINAL=xterm-256color
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Settings;
|
||||
use App\Services\Tmux\TmuxSessionManager;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class TmuxAttach extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'tmux:attach
|
||||
{--session= : Tmux session name}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Attach to the tmux processing session';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$sessionName = $this->option('session')
|
||||
?? Settings::settingValue('tmux_session')
|
||||
?? config('tmux.session.default_name', 'nntmux');
|
||||
|
||||
$sessionManager = new TmuxSessionManager($sessionName);
|
||||
|
||||
if (! $sessionManager->sessionExists()) {
|
||||
$this->error("❌ Session '{$sessionName}' does not exist");
|
||||
$this->info("💡 Run 'php artisan tmux:start' to create it");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$this->info("📎 Attaching to session '{$sessionName}'...");
|
||||
$this->info('💡 Press Ctrl+A then D to detach');
|
||||
|
||||
// Execute tmux attach
|
||||
passthru("tmux attach -t {$sessionName}");
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Collection;
|
||||
use App\Models\Settings;
|
||||
use App\Services\Tmux\TmuxLayoutBuilder;
|
||||
use App\Services\Tmux\TmuxSessionManager;
|
||||
use Blacklight\ColorCLI;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class TmuxStart extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'tmux:start
|
||||
{--session= : Tmux session name}
|
||||
{--force : Force start even if session exists}
|
||||
{--attach : Attach to session after starting}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Start tmux processing session (modernized)';
|
||||
|
||||
private TmuxSessionManager $sessionManager;
|
||||
|
||||
private TmuxLayoutBuilder $layoutBuilder;
|
||||
|
||||
private ColorCLI $colorCli;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$this->colorCli = new ColorCLI;
|
||||
|
||||
try {
|
||||
$this->colorCli->header('Starting Tmux Processing');
|
||||
|
||||
// Get session name
|
||||
$sessionName = $this->option('session')
|
||||
?? Settings::settingValue('tmux_session')
|
||||
?? config('tmux.session.default_name', 'nntmux');
|
||||
|
||||
// Initialize services
|
||||
$this->sessionManager = new TmuxSessionManager($sessionName);
|
||||
$this->layoutBuilder = new TmuxLayoutBuilder($this->sessionManager);
|
||||
|
||||
// Check if tmux is installed
|
||||
if (! $this->checkTmuxInstalled()) {
|
||||
$this->error('❌ tmux is not installed');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
// Check if session already exists
|
||||
if ($this->sessionManager->sessionExists()) {
|
||||
if (! $this->option('force')) {
|
||||
$this->error("❌ Session '{$sessionName}' already exists");
|
||||
if ($this->confirm('Would you like to restart it?', false)) {
|
||||
$this->call('tmux:stop', ['--session' => $sessionName]);
|
||||
sleep(2);
|
||||
} else {
|
||||
return Command::FAILURE;
|
||||
}
|
||||
} else {
|
||||
$this->call('tmux:stop', ['--session' => $sessionName]);
|
||||
sleep(2);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset old collections
|
||||
$this->info('🔄 Resetting old collections...');
|
||||
$this->resetOldCollections();
|
||||
|
||||
// Get sequential mode
|
||||
$sequential = (int) (Settings::settingValue('sequential') ?? 0);
|
||||
$this->info("📐 Building layout (mode: {$sequential})");
|
||||
|
||||
// Build the tmux layout
|
||||
if (! $this->layoutBuilder->buildLayout($sequential)) {
|
||||
$this->error('❌ Failed to build tmux layout');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$this->info('✅ Tmux layout created');
|
||||
|
||||
// Set running flag
|
||||
Settings::query()->where('name', 'running')->update(['value' => 1]);
|
||||
$this->info('✅ Running flag set');
|
||||
|
||||
// Start monitor in background
|
||||
$this->info('🚀 Starting monitor...');
|
||||
$this->startMonitor($sessionName);
|
||||
|
||||
$this->info("✅ Tmux session '{$sessionName}' started successfully");
|
||||
|
||||
// Attach if requested
|
||||
if ($this->option('attach')) {
|
||||
$this->info('📎 Attaching to session...');
|
||||
$this->sessionManager->attachSession();
|
||||
} else {
|
||||
$this->info("💡 To attach to the session, run: tmux attach -t {$sessionName}");
|
||||
$this->info('💡 Or use: php artisan tmux:attach');
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->error('❌ Failed to start tmux: '.$e->getMessage());
|
||||
logger()->error('Tmux start error', [
|
||||
'message' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if tmux is installed
|
||||
*/
|
||||
private function checkTmuxInstalled(): bool
|
||||
{
|
||||
$result = \Illuminate\Support\Facades\Process::timeout(5)
|
||||
->run('which tmux 2>/dev/null');
|
||||
|
||||
return $result->successful() && str_contains($result->output(), 'tmux');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset old collections
|
||||
*/
|
||||
private function resetOldCollections(): void
|
||||
{
|
||||
$delayTime = (int) (Settings::settingValue('delaytime') ?? 2);
|
||||
|
||||
try {
|
||||
DB::transaction(function () use ($delayTime) {
|
||||
$count = Collection::query()
|
||||
->where('dateadded', '<', now()->subHours($delayTime))
|
||||
->update(['dateadded' => now()]);
|
||||
|
||||
if ($count > 0) {
|
||||
$this->info(" ✓ Reset {$count} expired collections");
|
||||
} else {
|
||||
$this->info(' ✓ No collections needed resetting');
|
||||
}
|
||||
}, 10);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->warn(' ⚠ Failed to reset collections: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the monitor process
|
||||
*/
|
||||
private function startMonitor(string $sessionName): void
|
||||
{
|
||||
$paneManager = new \App\Services\Tmux\TmuxPaneManager($sessionName);
|
||||
|
||||
// Priority: new monitor > old monitor > artisan command
|
||||
$newMonitor = base_path('app/Services/Tmux/Scripts/monitor.php');
|
||||
$oldMonitor = base_path('misc/update/tmux/monitor.php');
|
||||
|
||||
if (file_exists($newMonitor)) {
|
||||
// Use the new modernized monitor script
|
||||
$command = "php {$newMonitor}";
|
||||
$this->info(' ✓ Using modernized monitor script');
|
||||
} elseif (file_exists($oldMonitor)) {
|
||||
// Fall back to original monitor script
|
||||
$command = "php {$oldMonitor}";
|
||||
$this->warn(' ⚠ Using legacy monitor.php (consider updating)');
|
||||
} else {
|
||||
// Last resort: use artisan command (not ideal for pane)
|
||||
$artisan = base_path('artisan');
|
||||
$command = "php {$artisan} tmux:monitor --session={$sessionName}";
|
||||
$this->warn(' ⚠ Using artisan command (not recommended for pane)');
|
||||
}
|
||||
|
||||
// Spawn monitor in pane 0.0
|
||||
$paneManager->respawnPane('0.0', $command);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Settings;
|
||||
use App\Services\Tmux\TmuxSessionManager;
|
||||
use Blacklight\ColorCLI;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class TmuxStop extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'tmux:stop
|
||||
{--session= : Tmux session name}
|
||||
{--force : Force stop without confirmation}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Stop tmux processing session (modernized)';
|
||||
|
||||
private TmuxSessionManager $sessionManager;
|
||||
|
||||
private ColorCLI $colorCli;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$this->colorCli = new ColorCLI;
|
||||
|
||||
try {
|
||||
// Get session name
|
||||
$sessionName = $this->option('session')
|
||||
?? Settings::settingValue('tmux_session')
|
||||
?? config('tmux.session.default_name', 'nntmux');
|
||||
|
||||
$this->sessionManager = new TmuxSessionManager($sessionName);
|
||||
|
||||
// Check if session exists
|
||||
if (! $this->sessionManager->sessionExists()) {
|
||||
$this->warn("⚠️ Session '{$sessionName}' is not running");
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
// Confirm unless forced
|
||||
if (! $this->option('force')) {
|
||||
if (! $this->confirm("Stop tmux session '{$sessionName}'?", true)) {
|
||||
$this->info('Cancelled');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
$this->colorCli->header('Stopping Tmux Session');
|
||||
|
||||
// Set running flag to 0
|
||||
Settings::query()->where('name', 'running')->update(['value' => 0]);
|
||||
$this->info('✅ Running flag cleared');
|
||||
|
||||
// Wait for panes to shut down gracefully
|
||||
$delay = (int) (Settings::settingValue('monitor_delay') ?? 10);
|
||||
$this->info("⏳ Waiting {$delay} seconds for panes to shut down gracefully...");
|
||||
sleep($delay);
|
||||
|
||||
// Kill the session
|
||||
if ($this->sessionManager->killSession()) {
|
||||
$this->info("✅ Session '{$sessionName}' stopped successfully");
|
||||
|
||||
return Command::SUCCESS;
|
||||
} else {
|
||||
$this->error("❌ Failed to stop session '{$sessionName}'");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->error('❌ Failed to stop tmux: '.$e->getMessage());
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,24 +18,18 @@ class TmuxUIRestart extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Restart processing of tmux scripts completely';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
protected $description = 'Restart processing of tmux scripts (deprecated - use tmux:stop && tmux:start)';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): void
|
||||
public function handle(): int
|
||||
{
|
||||
$this->call('tmux-ui:stop', ['--kill' => true]);
|
||||
$this->call('tmux-ui:start');
|
||||
$this->warn('⚠️ This command is deprecated. Use "php artisan tmux:stop && php artisan tmux:start" instead.');
|
||||
|
||||
$this->call('tmux:stop', ['--force' => true]);
|
||||
sleep(2);
|
||||
|
||||
return $this->call('tmux:start');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Settings;
|
||||
use App\Support\UpdatePerformanceHelper;
|
||||
use Blacklight\Tmux;
|
||||
use Illuminate\Console\Command;
|
||||
@@ -34,36 +33,13 @@ class TmuxUIStart extends Command
|
||||
{
|
||||
try {
|
||||
$this->info('🚀 Starting Tmux UI...');
|
||||
$this->warn('⚠️ This command is deprecated. Use "php artisan tmux:start" instead.');
|
||||
|
||||
$tmux = new Tmux;
|
||||
$tmuxSession = Settings::settingValue('tmux_session') ?? 0;
|
||||
$timeout = (int) $this->option('timeout');
|
||||
|
||||
// Check system resources before starting
|
||||
$this->checkSystemResources();
|
||||
|
||||
// Check if session already exists
|
||||
if (! $this->option('force') && $this->isSessionRunning($tmuxSession)) {
|
||||
$this->error("❌ Tmux session '$tmuxSession' is already running");
|
||||
|
||||
if (! $this->confirm('Would you like to restart the session?')) {
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$this->call('tmux-ui:stop', ['--kill' => true]);
|
||||
sleep(2); // Brief pause to ensure clean shutdown
|
||||
}
|
||||
|
||||
// Set running state
|
||||
$tmux->startRunning();
|
||||
$this->line(' ✓ Tmux running state activated');
|
||||
|
||||
// Start tmux session
|
||||
$this->startTmuxSession($tmuxSession, $timeout);
|
||||
|
||||
$this->info('✅ Tmux UI started successfully');
|
||||
|
||||
return Command::SUCCESS;
|
||||
// Delegate to new command
|
||||
return $this->call('tmux:start', [
|
||||
'--force' => $this->option('force'),
|
||||
'--attach' => $this->option('monitor'),
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->error('❌ Failed to start Tmux UI: '.$e->getMessage());
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Settings;
|
||||
use Blacklight\Tmux;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
|
||||
class TmuxUIStop extends Command
|
||||
{
|
||||
@@ -21,24 +18,17 @@ class TmuxUIStop extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Stop the processing of tmux scripts.';
|
||||
protected $description = 'Stop the processing of tmux scripts (deprecated - use tmux:stop)';
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
* Execute the console command
|
||||
*/
|
||||
public function handle(): void
|
||||
public function handle(): int
|
||||
{
|
||||
$tmux = new Tmux;
|
||||
$tmux->stopIfRunning();
|
||||
if ($this->option('kill') === true) {
|
||||
$sessionName = Settings::settingValue('tmux_session');
|
||||
$tmuxSession = Process::run('tmux kill-session -t '.$sessionName);
|
||||
$this->info('Killing active tmux session: '.$sessionName);
|
||||
if ($tmuxSession->successful()) {
|
||||
$this->info('Tmux session killed successfully');
|
||||
} else {
|
||||
$this->info('No valid tmux sessions found');
|
||||
}
|
||||
}
|
||||
$this->warn('⚠️ This command is deprecated. Use "php artisan tmux:stop" instead.');
|
||||
|
||||
return $this->call('tmux:stop', [
|
||||
'--force' => $this->option('kill'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Tmux Monitor Script
|
||||
*
|
||||
* This script runs in tmux pane 0.0 and continuously monitors the system,
|
||||
* collecting statistics and spawning tasks in other panes.
|
||||
*
|
||||
* This is the modernized version that uses the new service architecture.
|
||||
*/
|
||||
|
||||
// Bootstrap Laravel
|
||||
// From: app/Services/Tmux/Scripts/monitor.php
|
||||
// To: bootstrap/autoload.php (4 levels up)
|
||||
require_once __DIR__.'/../../../../bootstrap/autoload.php';
|
||||
|
||||
use App\Models\Settings;
|
||||
use App\Services\Tmux\TmuxMonitorService;
|
||||
use App\Services\Tmux\TmuxTaskRunner;
|
||||
use Blacklight\ColorCLI;
|
||||
use Blacklight\TmuxOutput;
|
||||
use Blacklight\TmuxRun;
|
||||
|
||||
$colorCli = new ColorCLI;
|
||||
|
||||
try {
|
||||
// Get session name
|
||||
$sessionName = Settings::settingValue('tmux_session') ?? 'nntmux';
|
||||
|
||||
// Initialize services
|
||||
$monitor = new TmuxMonitorService;
|
||||
$taskRunner = new TmuxTaskRunner($sessionName);
|
||||
$output = new TmuxOutput;
|
||||
|
||||
// For backwards compatibility, also initialize old classes
|
||||
$tRun = new TmuxRun;
|
||||
|
||||
// Initialize monitor
|
||||
$runVar = $monitor->initializeMonitor();
|
||||
|
||||
// Add paths that TmuxRun expects
|
||||
$runVar['paths']['misc'] = base_path().'/misc/';
|
||||
$runVar['paths']['cli'] = base_path().'/cli/';
|
||||
|
||||
// Also initialize old-style commands for compatibility
|
||||
$tmux_niceness = Settings::settingValue('niceness') ?? 2;
|
||||
$runVar['commands']['_php'] = " nice -n{$tmux_niceness} php";
|
||||
$runVar['commands']['_phpn'] = "nice -n{$tmux_niceness} php";
|
||||
$runVar['commands']['_sleep'] = "{$runVar['commands']['_phpn']} ".base_path('app/Services/Tmux/Scripts/showsleep.php');
|
||||
|
||||
// Add scripts paths for TmuxRun - Using multiprocessing scripts WITH ARGUMENTS
|
||||
$runVar['scripts']['binaries'] = "nice -n{$tmux_niceness} php ".base_path('misc/update/multiprocessing/binaries.php').' 0';
|
||||
$runVar['scripts']['backfill'] = "nice -n{$tmux_niceness} php ".base_path('misc/update/multiprocessing/backfill.php').' 0';
|
||||
$runVar['scripts']['releases'] = "nice -n{$tmux_niceness} php ".base_path('misc/update/multiprocessing/releases.php');
|
||||
|
||||
// Spawn IRC scraper immediately
|
||||
try {
|
||||
$taskRunner->runIRCScraper(['constants' => $runVar['constants']]);
|
||||
} catch (Exception $e) {
|
||||
logger()->error('Failed to spawn IRC scraper: '.$e->getMessage());
|
||||
}
|
||||
|
||||
// Get list of panes for compatibility
|
||||
$runVar['panes'] = $tRun->getListOfPanes($runVar['constants']);
|
||||
|
||||
$colorCli->header('Tmux Monitor Started');
|
||||
echo "Session: {$sessionName}\n";
|
||||
echo "Press Ctrl+C to stop (or set exit flag in settings)\n\n";
|
||||
|
||||
// Main monitoring loop
|
||||
while ($monitor->shouldContinue()) {
|
||||
// Collect statistics (but preserve our custom settings)
|
||||
$scriptsBackup = $runVar['scripts'] ?? [];
|
||||
$commandsBackup = $runVar['commands'] ?? [];
|
||||
$pathsBackup = $runVar['paths'] ?? [];
|
||||
|
||||
$runVar = $monitor->collectStatistics();
|
||||
|
||||
// Restore our custom settings that collectStatistics doesn't know about
|
||||
$runVar['scripts'] = $scriptsBackup;
|
||||
$runVar['commands'] = $commandsBackup;
|
||||
$runVar['paths'] = array_merge($runVar['paths'] ?? [], $pathsBackup);
|
||||
|
||||
// Refresh panes list periodically
|
||||
$runVar['panes'] = $tRun->getListOfPanes($runVar['constants']);
|
||||
|
||||
// Update display
|
||||
$output->updateMonitorPane($runVar);
|
||||
|
||||
// Run pane tasks if tmux is running
|
||||
if ((int) ($runVar['settings']['is_running'] ?? 0) === 1) {
|
||||
$sequential = (int) ($runVar['constants']['sequential'] ?? 0);
|
||||
|
||||
// Determine which panes to run based on sequential mode
|
||||
$panesToRun = match ($sequential) {
|
||||
0 => ['main', 'fixnames', 'removecrap', 'ppadditional', 'nonamazon', 'amazon'],
|
||||
1 => ['main', 'fixnames', 'removecrap', 'ppadditional', 'nonamazon', 'amazon'],
|
||||
2 => ['main', 'fixnames', 'amazon'],
|
||||
default => [],
|
||||
};
|
||||
|
||||
// Run each pane task
|
||||
foreach ($panesToRun as $paneName) {
|
||||
try {
|
||||
// Use old TmuxRun for compatibility during transition
|
||||
$tRun->runPane($paneName, $runVar);
|
||||
} catch (Exception $e) {
|
||||
logger()->error("Failed to run pane {$paneName}: ".$e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Increment iteration and sleep
|
||||
$monitor->incrementIteration();
|
||||
sleep(10);
|
||||
}
|
||||
|
||||
$colorCli->info('Monitor stopped by exit flag');
|
||||
|
||||
} catch (Exception $e) {
|
||||
$colorCli->error('Monitor failed: '.$e->getMessage());
|
||||
logger()->error('Tmux monitor error', [
|
||||
'message' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
exit(1);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Sleep display script for tmux panes
|
||||
* Shows a countdown timer in the pane
|
||||
*/
|
||||
$sleepTime = (int) ($argv[1] ?? 60);
|
||||
|
||||
if ($sleepTime <= 0) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$endTime = time() + $sleepTime;
|
||||
|
||||
while (time() < $endTime) {
|
||||
$remaining = $endTime - time();
|
||||
$hours = floor($remaining / 3600);
|
||||
$minutes = floor(($remaining % 3600) / 60);
|
||||
$seconds = $remaining % 60;
|
||||
|
||||
printf("\r⏳ Sleeping: %02d:%02d:%02d remaining", $hours, $minutes, $seconds);
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
echo "\r✅ Sleep complete".str_repeat(' ', 30)."\n";
|
||||
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Tmux;
|
||||
|
||||
use App\Models\Settings;
|
||||
|
||||
/**
|
||||
* Service for building tmux window layouts based on sequential mode
|
||||
*/
|
||||
class TmuxLayoutBuilder
|
||||
{
|
||||
protected TmuxSessionManager $sessionManager;
|
||||
|
||||
protected TmuxPaneManager $paneManager;
|
||||
|
||||
protected string $sessionName;
|
||||
|
||||
public function __construct(TmuxSessionManager $sessionManager)
|
||||
{
|
||||
$this->sessionManager = $sessionManager;
|
||||
$this->sessionName = $sessionManager->getSessionName();
|
||||
$this->paneManager = new TmuxPaneManager($this->sessionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the appropriate layout based on sequential mode
|
||||
*/
|
||||
public function buildLayout(int $sequentialMode): bool
|
||||
{
|
||||
return match ($sequentialMode) {
|
||||
1 => $this->buildBasicLayout(),
|
||||
2 => $this->buildStrippedLayout(),
|
||||
default => $this->buildFullLayout(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build full non-sequential layout (mode 0)
|
||||
*/
|
||||
protected function buildFullLayout(): bool
|
||||
{
|
||||
// Window 0: Monitor + Binaries + Backfill + Releases
|
||||
if (! $this->sessionManager->createSession('Monitor')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Select pane 0.0, then split for binaries (right side, 67%)
|
||||
$this->paneManager->selectPane('0.0');
|
||||
$this->paneManager->splitHorizontal('0', '67%', 'update_binaries');
|
||||
|
||||
// Select pane 0.1 (binaries), then split for backfill (bottom, 67%)
|
||||
$this->paneManager->selectPane('0.1');
|
||||
$this->paneManager->splitVertical('0', '67%', 'backfill');
|
||||
|
||||
// Split again for releases (bottom, 50%)
|
||||
$this->paneManager->splitVertical('0', '50%', 'update_releases');
|
||||
|
||||
// Window 1: Fix names + Remove crap
|
||||
$this->paneManager->createWindow(1, 'utils');
|
||||
$this->paneManager->setPaneTitle('1.0', 'fixReleaseNames');
|
||||
$this->paneManager->selectPane('1.0');
|
||||
$this->paneManager->splitHorizontal('1', '50%', 'removeCrapReleases');
|
||||
|
||||
// Window 2: Postprocessing (Additional + Non-Amazon + Amazon)
|
||||
$this->paneManager->createWindow(2, 'post');
|
||||
$this->paneManager->setPaneTitle('2.0', 'postprocessing_additional');
|
||||
$this->paneManager->splitVertical('2', '67%', 'postprocessing_non_amazon');
|
||||
$this->paneManager->splitVertical('2', '50%', 'postprocessing_amazon');
|
||||
|
||||
// Window 3: IRC Scraper
|
||||
$this->createIRCScraperWindow();
|
||||
|
||||
// Additional windows (optional monitoring tools)
|
||||
$this->createOptionalWindows();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build basic sequential layout (mode 1)
|
||||
*/
|
||||
protected function buildBasicLayout(): bool
|
||||
{
|
||||
// Window 0: Monitor + Releases
|
||||
if (! $this->sessionManager->createSession('Monitor')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Select pane 0.0, then split for releases
|
||||
$this->paneManager->selectPane('0.0');
|
||||
$this->paneManager->splitHorizontal('0', '67%', 'update_releases');
|
||||
|
||||
// Window 1: Utils (Fix names + Remove crap)
|
||||
$this->paneManager->createWindow(1, 'utils');
|
||||
$this->paneManager->setPaneTitle('1.0', 'fixReleaseNames');
|
||||
$this->paneManager->selectPane('1.0');
|
||||
$this->paneManager->splitHorizontal('1', '50%', 'removeCrapReleases');
|
||||
|
||||
// Window 2: Postprocessing
|
||||
$this->paneManager->createWindow(2, 'post');
|
||||
$this->paneManager->setPaneTitle('2.0', 'postprocessing_additional');
|
||||
$this->paneManager->splitVertical('2', '67%', 'postprocessing_non_amazon');
|
||||
$this->paneManager->splitVertical('2', '50%', 'postprocessing_amazon');
|
||||
|
||||
// Window 3: IRC Scraper
|
||||
$this->createIRCScraperWindow();
|
||||
|
||||
$this->createOptionalWindows();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build stripped sequential layout (mode 2)
|
||||
*/
|
||||
protected function buildStrippedLayout(): bool
|
||||
{
|
||||
// Window 0: Monitor + Sequential
|
||||
if (! $this->sessionManager->createSession('Monitor')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Select pane 0.0, then split for sequential
|
||||
$this->paneManager->selectPane('0.0');
|
||||
$this->paneManager->splitHorizontal('0', '67%', 'sequential');
|
||||
|
||||
// Window 1: Amazon postprocessing
|
||||
$this->paneManager->createWindow(1, 'utils');
|
||||
$this->paneManager->setPaneTitle('1.0', 'fixReleaseNames');
|
||||
$this->paneManager->selectPane('1.0');
|
||||
$this->paneManager->splitHorizontal('1', '50%', 'postprocessing_amazon');
|
||||
|
||||
// Window 2: IRC Scraper
|
||||
$this->createIRCScraperWindow();
|
||||
|
||||
$this->createOptionalWindows();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create IRC scraper window
|
||||
*/
|
||||
protected function createIRCScraperWindow(): bool
|
||||
{
|
||||
$this->paneManager->createWindow(3, 'IRCScraper');
|
||||
$this->paneManager->setPaneTitle('3.0', 'scrapeIRC');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create optional monitoring windows based on settings
|
||||
*/
|
||||
protected function createOptionalWindows(): void
|
||||
{
|
||||
$windowIndex = 4;
|
||||
|
||||
// htop
|
||||
if ((int) Settings::settingValue('htop') === 1 && $this->commandExists('htop')) {
|
||||
$this->paneManager->createWindow($windowIndex, 'htop');
|
||||
$this->paneManager->respawnPane("{$windowIndex}.0", 'htop');
|
||||
$windowIndex++;
|
||||
}
|
||||
|
||||
// nmon
|
||||
if ((int) Settings::settingValue('nmon') === 1 && $this->commandExists('nmon')) {
|
||||
$this->paneManager->createWindow($windowIndex, 'nmon');
|
||||
$this->paneManager->respawnPane("{$windowIndex}.0", 'nmon -t');
|
||||
$windowIndex++;
|
||||
}
|
||||
|
||||
// vnstat
|
||||
if ((int) Settings::settingValue('vnstat') === 1 && $this->commandExists('vnstat')) {
|
||||
$vnstatArgs = Settings::settingValue('vnstat_args') ?? '';
|
||||
$this->paneManager->createWindow($windowIndex, 'vnstat');
|
||||
$this->paneManager->respawnPane("{$windowIndex}.0", "watch -n10 'vnstat {$vnstatArgs}'");
|
||||
$windowIndex++;
|
||||
}
|
||||
|
||||
// tcptrack
|
||||
if ((int) Settings::settingValue('tcptrack') === 1 && $this->commandExists('tcptrack')) {
|
||||
$tcptrackArgs = Settings::settingValue('tcptrack_args') ?? '';
|
||||
$this->paneManager->createWindow($windowIndex, 'tcptrack');
|
||||
$this->paneManager->respawnPane("{$windowIndex}.0", "tcptrack {$tcptrackArgs}");
|
||||
$windowIndex++;
|
||||
}
|
||||
|
||||
// bwm-ng
|
||||
if ((int) Settings::settingValue('bwmng') === 1 && $this->commandExists('bwm-ng')) {
|
||||
$this->paneManager->createWindow($windowIndex, 'bwm-ng');
|
||||
$this->paneManager->respawnPane("{$windowIndex}.0", 'bwm-ng');
|
||||
$windowIndex++;
|
||||
}
|
||||
|
||||
// mytop
|
||||
if ((int) Settings::settingValue('mytop') === 1 && $this->commandExists('mytop')) {
|
||||
$this->paneManager->createWindow($windowIndex, 'mytop');
|
||||
$this->paneManager->respawnPane("{$windowIndex}.0", 'mytop -u');
|
||||
$windowIndex++;
|
||||
}
|
||||
|
||||
// redis-stat
|
||||
if ((int) Settings::settingValue('redis') === 1 && $this->commandExists('redis-cli')) {
|
||||
$this->paneManager->createWindow($windowIndex, 'redis-stat');
|
||||
$this->paneManager->respawnPane("{$windowIndex}.0", 'redis-stat --verbose --server=63790');
|
||||
$windowIndex++;
|
||||
}
|
||||
|
||||
// bash console
|
||||
if ((int) Settings::settingValue('console') === 1) {
|
||||
$this->paneManager->createWindow($windowIndex, 'bash');
|
||||
$this->paneManager->respawnPane("{$windowIndex}.0", 'bash -i');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a command exists
|
||||
*/
|
||||
protected function commandExists(string $command): bool
|
||||
{
|
||||
$result = \Illuminate\Support\Facades\Process::timeout(5)
|
||||
->run("which {$command} 2>/dev/null");
|
||||
|
||||
return $result->successful() && str_contains($result->output(), $command);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Tmux;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Collection;
|
||||
use App\Models\Release;
|
||||
use App\Models\Settings;
|
||||
use Blacklight\Tmux;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Service for monitoring tmux operations and collecting statistics
|
||||
*/
|
||||
class TmuxMonitorService
|
||||
{
|
||||
protected Tmux $tmux;
|
||||
|
||||
protected array $runVar = [];
|
||||
|
||||
protected int $iterations = 1;
|
||||
|
||||
protected bool $shouldContinue = true;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->tmux = new Tmux;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize monitor with default values
|
||||
*/
|
||||
public function initializeMonitor(): array
|
||||
{
|
||||
$this->runVar['paths']['misc'] = base_path().'/misc/';
|
||||
$this->runVar['paths']['cli'] = base_path().'/cli/';
|
||||
$this->runVar['paths']['scraper'] = base_path().'/misc/IRCScraper/scrape.php';
|
||||
|
||||
$this->runVar['constants'] = $this->tmux->getConstantSettings();
|
||||
$this->runVar['settings'] = $this->tmux->getMonitorSettings();
|
||||
$this->runVar['connections'] = $this->tmux->getConnectionsInfo($this->runVar['constants']);
|
||||
|
||||
// Initialize timers
|
||||
$this->runVar['timers'] = $this->initializeTimers();
|
||||
|
||||
// Initialize counts
|
||||
$this->runVar['counts'] = [
|
||||
'iterations' => 1,
|
||||
'now' => [],
|
||||
'start' => [],
|
||||
'diff' => [],
|
||||
'percent' => [],
|
||||
];
|
||||
|
||||
return $this->runVar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all timers
|
||||
*/
|
||||
protected function initializeTimers(): array
|
||||
{
|
||||
$now = time();
|
||||
|
||||
return [
|
||||
'timer1' => $now,
|
||||
'timer2' => $now,
|
||||
'timer3' => $now,
|
||||
'timer4' => $now,
|
||||
'timer5' => $now,
|
||||
'query' => [
|
||||
'tmux_time' => 0,
|
||||
'split_time' => 0,
|
||||
'init_time' => 0,
|
||||
'proc1_time' => 0,
|
||||
'proc2_time' => 0,
|
||||
'proc3_time' => 0,
|
||||
'split1_time' => 0,
|
||||
'init1_time' => 0,
|
||||
'proc11_time' => 0,
|
||||
'proc21_time' => 0,
|
||||
'proc31_time' => 0,
|
||||
'tpg_time' => 0,
|
||||
'tpg1_time' => 0,
|
||||
],
|
||||
'newOld' => [
|
||||
'newestrelname' => '',
|
||||
'oldestcollection' => 0,
|
||||
'newestpre' => 0,
|
||||
'newestrelease' => 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect current statistics
|
||||
*/
|
||||
public function collectStatistics(): array
|
||||
{
|
||||
// Refresh settings periodically
|
||||
$monitorDelay = (int) ($this->runVar['settings']['monitor'] ?? 60);
|
||||
$timeSinceLastRefresh = time() - ($this->runVar['timers']['timer2'] ?? 0);
|
||||
|
||||
if ($this->iterations === 1 || $timeSinceLastRefresh >= $monitorDelay) {
|
||||
$this->refreshStatistics();
|
||||
$this->runVar['timers']['timer2'] = time();
|
||||
}
|
||||
|
||||
// Update connection counts
|
||||
$this->updateConnectionCounts();
|
||||
|
||||
// Set killswitches
|
||||
$this->setKillswitches();
|
||||
|
||||
return $this->runVar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh all statistics from database
|
||||
*/
|
||||
protected function refreshStatistics(): void
|
||||
{
|
||||
$timer = time();
|
||||
|
||||
// Refresh settings
|
||||
$this->runVar['settings'] = $this->tmux->getMonitorSettings();
|
||||
$this->runVar['timers']['query']['tmux_time'] = time() - $timer;
|
||||
|
||||
// Get category counts
|
||||
$this->getCategoryCounts();
|
||||
|
||||
// Get process counts
|
||||
$this->getProcessCounts();
|
||||
|
||||
// Get table counts
|
||||
$this->getTableCounts();
|
||||
|
||||
// Calculate diffs and percentages
|
||||
$this->calculateStatistics();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get counts by category
|
||||
*/
|
||||
protected function getCategoryCounts(): void
|
||||
{
|
||||
$timer = time();
|
||||
|
||||
$this->runVar['counts']['now']['tv'] = Release::query()
|
||||
->whereBetween('categories_id', [Category::TV_ROOT, Category::TV_OTHER])
|
||||
->count('id');
|
||||
|
||||
$this->runVar['counts']['now']['movies'] = Release::query()
|
||||
->whereBetween('categories_id', [Category::MOVIE_ROOT, Category::MOVIE_OTHER])
|
||||
->count('id');
|
||||
|
||||
$this->runVar['counts']['now']['audio'] = Release::query()
|
||||
->whereBetween('categories_id', [Category::MUSIC_ROOT, Category::MUSIC_OTHER])
|
||||
->count('id');
|
||||
|
||||
$this->runVar['counts']['now']['books'] = Release::query()
|
||||
->whereBetween('categories_id', [Category::BOOKS_ROOT, Category::BOOKS_UNKNOWN])
|
||||
->count('id');
|
||||
|
||||
$this->runVar['counts']['now']['console'] = Release::query()
|
||||
->whereBetween('categories_id', [Category::GAME_ROOT, Category::GAME_OTHER])
|
||||
->count('id');
|
||||
|
||||
$this->runVar['counts']['now']['pc'] = Release::query()
|
||||
->whereBetween('categories_id', [Category::PC_ROOT, Category::PC_PHONE_ANDROID])
|
||||
->count('id');
|
||||
|
||||
$this->runVar['counts']['now']['xxx'] = Release::query()
|
||||
->whereBetween('categories_id', [Category::XXX_ROOT, Category::XXX_OTHER])
|
||||
->count('id');
|
||||
|
||||
$this->runVar['counts']['now']['misc'] = Release::query()
|
||||
->whereBetween('categories_id', [Category::OTHER_ROOT, Category::OTHER_HASHED])
|
||||
->count('id');
|
||||
|
||||
$this->runVar['timers']['query']['init_time'] = time() - $timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get process-related counts
|
||||
*/
|
||||
protected function getProcessCounts(): void
|
||||
{
|
||||
$timer = time();
|
||||
|
||||
try {
|
||||
$bookReqIds = $this->runVar['settings']['book_reqids'] ?? Category::BOOKS_ROOT;
|
||||
$dbName = config('nntmux.db_name');
|
||||
|
||||
$proc1Query = $this->tmux->proc_query(1, $bookReqIds, $dbName);
|
||||
$proc1Result = DB::selectOne($proc1Query);
|
||||
|
||||
if ($proc1Result) {
|
||||
foreach ((array) $proc1Result as $key => $value) {
|
||||
$this->runVar['counts']['now'][$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$this->runVar['timers']['query']['proc1_time'] = time() - $timer;
|
||||
|
||||
// Process 2
|
||||
$timer2 = time();
|
||||
$maxSize = $this->runVar['settings']['maxsize_pp'] ?? '';
|
||||
$minSize = $this->runVar['settings']['minsize_pp'] ?? '';
|
||||
|
||||
$proc2Query = $this->tmux->proc_query(2, $bookReqIds, $dbName, $maxSize, $minSize);
|
||||
$proc2Result = DB::selectOne($proc2Query);
|
||||
|
||||
if ($proc2Result) {
|
||||
foreach ((array) $proc2Result as $key => $value) {
|
||||
$this->runVar['counts']['now'][$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$this->runVar['timers']['query']['proc2_time'] = time() - $timer2;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
logger()->error('Error collecting process counts: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table row counts
|
||||
*/
|
||||
protected function getTableCounts(): void
|
||||
{
|
||||
$timer = time();
|
||||
|
||||
try {
|
||||
$this->runVar['counts']['now']['collections_table'] = Collection::query()->count();
|
||||
|
||||
// Get binaries/parts counts from information_schema or use approximation
|
||||
$dbName = config('nntmux.db_name');
|
||||
$tables = $this->tmux->cbpmTableQuery();
|
||||
|
||||
$this->runVar['counts']['now']['binaries_table'] = 0;
|
||||
$this->runVar['counts']['now']['parts_table'] = 0;
|
||||
$this->runVar['counts']['now']['missed_parts_table'] = 0;
|
||||
|
||||
foreach ($tables as $table) {
|
||||
$tableName = $table->name;
|
||||
$count = $this->getTableRowCount($tableName);
|
||||
|
||||
if (str_contains($tableName, 'binaries')) {
|
||||
$this->runVar['counts']['now']['binaries_table'] += $count;
|
||||
} elseif (str_contains($tableName, 'missed_parts')) {
|
||||
$this->runVar['counts']['now']['missed_parts_table'] += $count;
|
||||
} elseif (str_contains($tableName, 'parts')) {
|
||||
$this->runVar['counts']['now']['parts_table'] += $count;
|
||||
}
|
||||
}
|
||||
|
||||
$this->runVar['timers']['query']['tpg_time'] = time() - $timer;
|
||||
|
||||
// Get additional table counts (query 4)
|
||||
$timer4 = time();
|
||||
$bookReqIds = $this->runVar['settings']['book_reqids'] ?? Category::BOOKS_ROOT;
|
||||
$proc4Query = $this->tmux->proc_query(4, $bookReqIds, $dbName);
|
||||
$proc4Result = DB::selectOne($proc4Query);
|
||||
|
||||
if ($proc4Result) {
|
||||
foreach ((array) $proc4Result as $key => $value) {
|
||||
$this->runVar['counts']['now'][$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Get newest/oldest data (query 6)
|
||||
$timer6 = time();
|
||||
$proc6Query = $this->tmux->proc_query(6, $bookReqIds, $dbName);
|
||||
$proc6Result = DB::selectOne($proc6Query);
|
||||
|
||||
if ($proc6Result) {
|
||||
foreach ((array) $proc6Result as $key => $value) {
|
||||
$this->runVar['timers']['newOld'][$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
logger()->error('Error collecting table counts: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get row count for a table
|
||||
*/
|
||||
protected function getTableRowCount(string $tableName): int
|
||||
{
|
||||
try {
|
||||
$result = DB::selectOne(
|
||||
'SELECT TABLE_ROWS AS count FROM information_schema.TABLES
|
||||
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = DATABASE()',
|
||||
[$tableName]
|
||||
);
|
||||
|
||||
return (int) ($result->count ?? 0);
|
||||
} catch (\Exception $e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate statistics (diffs, percentages, totals)
|
||||
*/
|
||||
protected function calculateStatistics(): void
|
||||
{
|
||||
// Calculate total work
|
||||
$this->runVar['counts']['now']['total_work'] = 0;
|
||||
|
||||
foreach ($this->runVar['counts']['now'] as $key => $value) {
|
||||
if (str_starts_with($key, 'process')) {
|
||||
$this->runVar['counts']['now']['total_work'] += $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Set initial start values on first iteration
|
||||
if ($this->iterations === 1) {
|
||||
$this->runVar['counts']['start'] = $this->runVar['counts']['now'];
|
||||
}
|
||||
|
||||
// Calculate diffs and percentages
|
||||
$totalReleases = $this->runVar['counts']['now']['releases'] ?? 1;
|
||||
|
||||
foreach ($this->runVar['counts']['now'] as $key => $value) {
|
||||
$startValue = $this->runVar['counts']['start'][$key] ?? 0;
|
||||
$this->runVar['counts']['diff'][$key] = number_format($value - $startValue);
|
||||
$this->runVar['counts']['percent'][$key] = $totalReleases > 0
|
||||
? sprintf('%02d', floor(($value / $totalReleases) * 100))
|
||||
: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update connection counts
|
||||
*/
|
||||
protected function updateConnectionCounts(): void
|
||||
{
|
||||
$this->runVar['conncounts'] = $this->tmux->getUSPConnections(
|
||||
'primary',
|
||||
$this->runVar['connections']
|
||||
);
|
||||
|
||||
if ((int) ($this->runVar['constants']['alternate_nntp'] ?? 0) === 1) {
|
||||
$alternateConns = $this->tmux->getUSPConnections(
|
||||
'alternate',
|
||||
$this->runVar['connections']
|
||||
);
|
||||
$this->runVar['conncounts'] = array_merge($this->runVar['conncounts'], $alternateConns);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set killswitches based on limits
|
||||
*/
|
||||
protected function setKillswitches(): void
|
||||
{
|
||||
$ppKillLimit = (int) ($this->runVar['settings']['postprocess_kill'] ?? 0);
|
||||
$collKillLimit = (int) ($this->runVar['settings']['collections_kill'] ?? 0);
|
||||
|
||||
$totalWork = (int) ($this->runVar['counts']['now']['total_work'] ?? 0);
|
||||
$collections = (int) ($this->runVar['counts']['now']['collections_table'] ?? 0);
|
||||
|
||||
$this->runVar['killswitch']['pp'] = ($ppKillLimit > 0 && $ppKillLimit < $totalWork);
|
||||
$this->runVar['killswitch']['coll'] = ($collKillLimit > 0 && $collKillLimit < $collections);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the monitor display
|
||||
*/
|
||||
public function updateDisplay(): void
|
||||
{
|
||||
// This would output to terminal - implementation depends on TmuxOutput
|
||||
// For now, we'll just log basic info
|
||||
if ($this->iterations % 10 === 0) {
|
||||
logger()->info('Tmux Monitor', [
|
||||
'iteration' => $this->iterations,
|
||||
'total_work' => $this->runVar['counts']['now']['total_work'] ?? 0,
|
||||
'collections' => $this->runVar['counts']['now']['collections_table'] ?? 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment iteration counter
|
||||
*/
|
||||
public function incrementIteration(): void
|
||||
{
|
||||
$this->iterations++;
|
||||
$this->runVar['counts']['iterations'] = $this->iterations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if monitoring should continue
|
||||
*/
|
||||
public function shouldContinue(): bool
|
||||
{
|
||||
$exitFlag = (int) Settings::settingValue('exit');
|
||||
|
||||
if ($exitFlag === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->shouldContinue = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current run variables
|
||||
*/
|
||||
public function getRunVar(): array
|
||||
{
|
||||
return $this->runVar;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Tmux;
|
||||
|
||||
use Illuminate\Support\Facades\Process;
|
||||
|
||||
/**
|
||||
* Service for managing individual tmux panes
|
||||
*/
|
||||
class TmuxPaneManager
|
||||
{
|
||||
protected string $sessionName;
|
||||
|
||||
public function __construct(string $sessionName)
|
||||
{
|
||||
$this->sessionName = $sessionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new window
|
||||
*/
|
||||
public function createWindow(int $index, string $name): bool
|
||||
{
|
||||
$result = Process::timeout(10)->run(
|
||||
"tmux new-window -t {$this->sessionName}:{$index} -n {$name} 'printf \"\\033]2;{$name}\\033\"'"
|
||||
);
|
||||
|
||||
return $result->successful();
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a pane horizontally
|
||||
*/
|
||||
public function splitHorizontal(string $target, string $percentage, string $title = ''): bool
|
||||
{
|
||||
$titleCmd = $title ? "printf \"\\033]2;{$title}\\033\"" : '';
|
||||
|
||||
$result = Process::timeout(10)->run(
|
||||
"tmux splitw -t {$this->sessionName}:{$target} -h -l {$percentage} '{$titleCmd}'"
|
||||
);
|
||||
|
||||
return $result->successful();
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a pane vertically
|
||||
*/
|
||||
public function splitVertical(string $target, string $percentage, string $title = ''): bool
|
||||
{
|
||||
$titleCmd = $title ? "printf \"\\033]2;{$title}\\033\"" : '';
|
||||
|
||||
$result = Process::timeout(10)->run(
|
||||
"tmux splitw -t {$this->sessionName}:{$target} -v -l {$percentage} '{$titleCmd}'"
|
||||
);
|
||||
|
||||
return $result->successful();
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a specific pane
|
||||
*/
|
||||
public function selectPane(string $target): bool
|
||||
{
|
||||
$result = Process::timeout(5)->run(
|
||||
"tmux selectp -t {$this->sessionName}:{$target}"
|
||||
);
|
||||
|
||||
return $result->successful();
|
||||
}
|
||||
|
||||
/**
|
||||
* Respawn a pane with a new command
|
||||
*/
|
||||
public function respawnPane(string $target, string $command, bool $kill = false): bool
|
||||
{
|
||||
$killFlag = $kill ? '-k' : '';
|
||||
|
||||
$result = Process::timeout(10)->run(
|
||||
"tmux respawnp {$killFlag} -t {$this->sessionName}:{$target} '{$command}'"
|
||||
);
|
||||
|
||||
return $result->successful();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send keys to a pane
|
||||
*/
|
||||
public function sendKeys(string $target, string $keys, bool $enter = true): bool
|
||||
{
|
||||
$enterKey = $enter ? ' Enter' : '';
|
||||
|
||||
$result = Process::timeout(5)->run(
|
||||
"tmux send-keys -t {$this->sessionName}:{$target} '{$keys}'{$enterKey}"
|
||||
);
|
||||
|
||||
return $result->successful();
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill a specific pane
|
||||
*/
|
||||
public function killPane(string $target): bool
|
||||
{
|
||||
$result = Process::timeout(5)->run(
|
||||
"tmux kill-pane -t {$this->sessionName}:{$target}"
|
||||
);
|
||||
|
||||
return $result->successful();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set pane title
|
||||
*/
|
||||
public function setPaneTitle(string $target, string $title): bool
|
||||
{
|
||||
$result = Process::timeout(5)->run(
|
||||
"tmux select-pane -t {$this->sessionName}:{$target} -T '{$title}'"
|
||||
);
|
||||
|
||||
return $result->successful();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pane title
|
||||
*/
|
||||
public function getPaneTitle(string $target): ?string
|
||||
{
|
||||
$result = Process::timeout(5)->run(
|
||||
"tmux display-message -p -t {$this->sessionName}:{$target} '#{pane_title}'"
|
||||
);
|
||||
|
||||
return $result->successful() ? trim($result->output()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture pane content
|
||||
*/
|
||||
public function capturePane(string $target, int $lines = 100): string
|
||||
{
|
||||
$result = Process::timeout(10)->run(
|
||||
"tmux capture-pane -p -t {$this->sessionName}:{$target} -S -{$lines}"
|
||||
);
|
||||
|
||||
return $result->successful() ? $result->output() : '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Tmux;
|
||||
|
||||
use App\Models\Settings;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
|
||||
/**
|
||||
* Service for managing tmux sessions and panes
|
||||
*/
|
||||
class TmuxSessionManager
|
||||
{
|
||||
protected string $sessionName;
|
||||
|
||||
protected string $configFile;
|
||||
|
||||
public function __construct(?string $sessionName = null)
|
||||
{
|
||||
$this->sessionName = $sessionName ?? $this->getSessionName();
|
||||
$this->configFile = config('tmux.config_file');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tmux session name from settings or config
|
||||
*/
|
||||
public function getSessionName(): string
|
||||
{
|
||||
return Settings::settingValue('tmux_session')
|
||||
?? config('tmux.session.name')
|
||||
?? config('tmux.session.default_name', 'nntmux');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if tmux session exists
|
||||
*/
|
||||
public function sessionExists(): bool
|
||||
{
|
||||
$result = Process::timeout(10)
|
||||
->run("tmux list-sessions 2>/dev/null | grep -q '^{$this->sessionName}:'");
|
||||
|
||||
return $result->successful();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new tmux session
|
||||
*/
|
||||
public function createSession(string $windowName = 'Monitor'): bool
|
||||
{
|
||||
if ($this->sessionExists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$configOption = file_exists($this->configFile) ? "-f {$this->configFile}" : '';
|
||||
|
||||
$result = Process::timeout(30)->run(
|
||||
"tmux {$configOption} new-session -d -s {$this->sessionName} -n {$windowName} 'printf \"\\033]2;{$windowName}\\033\"'"
|
||||
);
|
||||
|
||||
return $result->successful();
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill the tmux session
|
||||
*/
|
||||
public function killSession(): bool
|
||||
{
|
||||
if (! $this->sessionExists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$result = Process::timeout(30)->run("tmux kill-session -t {$this->sessionName}");
|
||||
|
||||
return $result->successful();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach to the tmux session
|
||||
*/
|
||||
public function attachSession(): bool
|
||||
{
|
||||
if (! $this->sessionExists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = Process::timeout(5)->run(
|
||||
"tmux select-window -t {$this->sessionName}:0; tmux attach-session -d -t {$this->sessionName}"
|
||||
);
|
||||
|
||||
return $result->successful();
|
||||
}
|
||||
|
||||
/**
|
||||
* List all panes in the session
|
||||
*/
|
||||
public function listPanes(): array
|
||||
{
|
||||
if (! $this->sessionExists()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = Process::timeout(10)->run(
|
||||
"tmux list-panes -s -t {$this->sessionName} -F '#{window_index}:#{pane_index} #{pane_title}'"
|
||||
);
|
||||
|
||||
if (! $result->successful()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$panes = [];
|
||||
$lines = explode("\n", trim($result->output()));
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (empty($line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$position, $title] = explode(' ', $line, 2);
|
||||
$panes[$position] = $title;
|
||||
}
|
||||
|
||||
return $panes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pane status
|
||||
*/
|
||||
public function getPaneStatus(string $window, string $pane): ?string
|
||||
{
|
||||
$result = Process::timeout(5)->run(
|
||||
"tmux display-message -p -t {$this->sessionName}:{$window}.{$pane} '#{pane_current_command}'"
|
||||
);
|
||||
|
||||
return $result->successful() ? trim($result->output()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a pane is running a command
|
||||
*/
|
||||
public function isPaneActive(string $window, string $pane): bool
|
||||
{
|
||||
$status = $this->getPaneStatus($window, $pane);
|
||||
|
||||
return $status !== null && $status !== 'bash' && $status !== 'sh';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,521 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Tmux;
|
||||
|
||||
use App\Models\Settings;
|
||||
use Blacklight\ColorCLI;
|
||||
|
||||
/**
|
||||
* Service for running tasks in tmux panes
|
||||
*/
|
||||
class TmuxTaskRunner
|
||||
{
|
||||
protected TmuxPaneManager $paneManager;
|
||||
|
||||
protected ColorCLI $colorCli;
|
||||
|
||||
protected string $sessionName;
|
||||
|
||||
public function __construct(string $sessionName)
|
||||
{
|
||||
$this->sessionName = $sessionName;
|
||||
$this->paneManager = new TmuxPaneManager($sessionName);
|
||||
$this->colorCli = new ColorCLI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a task in a specific pane
|
||||
*/
|
||||
public function runTask(string $taskName, array $config): bool
|
||||
{
|
||||
$pane = $config['pane'] ?? null;
|
||||
$command = $config['command'] ?? null;
|
||||
$enabled = $config['enabled'] ?? true;
|
||||
$workAvailable = $config['work_available'] ?? true;
|
||||
|
||||
if (! $pane || ! $command) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if task is enabled and has work
|
||||
if (! $enabled) {
|
||||
return $this->disablePane($pane, $taskName, 'disabled in settings');
|
||||
}
|
||||
|
||||
if (! $workAvailable) {
|
||||
return $this->disablePane($pane, $taskName, 'no work available');
|
||||
}
|
||||
|
||||
// Respawn the pane with the command
|
||||
return $this->paneManager->respawnPane($pane, $command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable a pane with a message
|
||||
*/
|
||||
protected function disablePane(string $pane, string $taskName, string $reason): bool
|
||||
{
|
||||
$color = $this->getRandomColor();
|
||||
$message = "echo \"\\033[38;5;{$color}m\\n{$taskName} has been disabled: {$reason}\"";
|
||||
|
||||
return $this->paneManager->respawnPane($pane, $message, kill: true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a command with logging
|
||||
*/
|
||||
public function buildCommand(string $baseCommand, array $options = []): string
|
||||
{
|
||||
$parts = [$baseCommand];
|
||||
|
||||
// Add sleep timer at the end if specified
|
||||
if (isset($options['sleep'])) {
|
||||
$sleepCommand = $this->buildSleepCommand($options['sleep']);
|
||||
$parts[] = "date +'%Y-%m-%d %T'";
|
||||
$parts[] = $sleepCommand;
|
||||
}
|
||||
|
||||
// Add logging if enabled
|
||||
if (isset($options['log_pane'])) {
|
||||
$logFile = $this->getLogFile($options['log_pane']);
|
||||
$command = implode('; ', $parts);
|
||||
|
||||
return "{$command} 2>&1 | tee -a {$logFile}";
|
||||
}
|
||||
|
||||
return implode('; ', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build sleep command
|
||||
*/
|
||||
protected function buildSleepCommand(int $seconds): string
|
||||
{
|
||||
$niceness = Settings::settingValue('niceness') ?? 2;
|
||||
$sleepScript = base_path('app/Services/Tmux/Scripts/showsleep.php');
|
||||
|
||||
if (file_exists($sleepScript)) {
|
||||
return "nice -n{$niceness} php {$sleepScript} {$seconds}";
|
||||
}
|
||||
|
||||
return "sleep {$seconds}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get log file path for a pane
|
||||
*/
|
||||
protected function getLogFile(string $paneName): string
|
||||
{
|
||||
$logsEnabled = (int) Settings::settingValue('write_logs') === 1;
|
||||
|
||||
if (! $logsEnabled) {
|
||||
return '/dev/null';
|
||||
}
|
||||
|
||||
$logDir = config('tmux.paths.logs', storage_path('logs/tmux'));
|
||||
|
||||
if (! is_dir($logDir)) {
|
||||
mkdir($logDir, 0755, true);
|
||||
}
|
||||
|
||||
$date = now()->format('Y_m_d');
|
||||
|
||||
return "{$logDir}/{$paneName}-{$date}.log";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a random color for terminal output
|
||||
*/
|
||||
protected function getRandomColor(): int
|
||||
{
|
||||
$start = (int) Settings::settingValue('colors_start') ?? 0;
|
||||
$end = (int) Settings::settingValue('colors_end') ?? 255;
|
||||
$exclude = Settings::settingValue('colors_exc') ?? '';
|
||||
|
||||
if (empty($exclude)) {
|
||||
return random_int($start, $end);
|
||||
}
|
||||
|
||||
$exceptions = array_map('intval', explode(',', $exclude));
|
||||
sort($exceptions);
|
||||
|
||||
$number = random_int($start, $end - count($exceptions));
|
||||
|
||||
foreach ($exceptions as $exception) {
|
||||
if ($number >= $exception) {
|
||||
$number++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the IRC scraper
|
||||
*/
|
||||
public function runIRCScraper(array $config): bool
|
||||
{
|
||||
$runScraper = (int) ($config['constants']['run_ircscraper'] ?? 0);
|
||||
$pane = '3.0';
|
||||
|
||||
if ($runScraper !== 1) {
|
||||
return $this->disablePane($pane, 'IRC Scraper', 'disabled in settings');
|
||||
}
|
||||
|
||||
$scraperScript = base_path('misc/IRCScraper/scrape.php');
|
||||
|
||||
if (! file_exists($scraperScript)) {
|
||||
return $this->disablePane($pane, 'IRC Scraper', 'script not found');
|
||||
}
|
||||
|
||||
$niceness = Settings::settingValue('niceness') ?? 2;
|
||||
$command = "nice -n{$niceness} php {$scraperScript}";
|
||||
$command = $this->buildCommand($command, ['log_pane' => 'scraper']);
|
||||
|
||||
return $this->paneManager->respawnPane($pane, $command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run binaries update
|
||||
*/
|
||||
public function runBinariesUpdate(array $config): bool
|
||||
{
|
||||
$enabled = (int) ($config['settings']['binaries_run'] ?? 0);
|
||||
$killswitch = $config['killswitch']['pp'] ?? false;
|
||||
$pane = '0.1';
|
||||
|
||||
if (! $enabled) {
|
||||
return $this->disablePane($pane, 'Update Binaries', 'disabled in settings');
|
||||
}
|
||||
|
||||
if ($killswitch) {
|
||||
return $this->disablePane($pane, 'Update Binaries', 'postprocess kill limit exceeded');
|
||||
}
|
||||
|
||||
$script = match ((int) $enabled) {
|
||||
1 => base_path('misc/update/multiprocessing/safe.php binaries'),
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (! $script) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$niceness = Settings::settingValue('niceness') ?? 2;
|
||||
$command = "nice -n{$niceness} php {$script}";
|
||||
$sleep = (int) ($config['settings']['bins_timer'] ?? 60);
|
||||
$command = $this->buildCommand($command, ['log_pane' => 'binaries', 'sleep' => $sleep]);
|
||||
|
||||
return $this->paneManager->respawnPane($pane, $command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run backfill
|
||||
*/
|
||||
public function runBackfill(array $config): bool
|
||||
{
|
||||
$enabled = (int) ($config['settings']['backfill'] ?? 0);
|
||||
$collKillswitch = $config['killswitch']['coll'] ?? false;
|
||||
$ppKillswitch = $config['killswitch']['pp'] ?? false;
|
||||
$pane = '0.2';
|
||||
|
||||
if (! $enabled) {
|
||||
return $this->disablePane($pane, 'Backfill', 'disabled in settings');
|
||||
}
|
||||
|
||||
if ($collKillswitch || $ppKillswitch) {
|
||||
return $this->disablePane($pane, 'Backfill', 'kill limit exceeded');
|
||||
}
|
||||
|
||||
$script = match ((int) $enabled) {
|
||||
1 => base_path('misc/update/multiprocessing/backfill.php'),
|
||||
4 => base_path('misc/update/multiprocessing/safe.php backfill'),
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (! $script) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate sleep time (progressive if enabled)
|
||||
$baseSleep = (int) ($config['settings']['back_timer'] ?? 600);
|
||||
$collections = (int) ($config['counts']['now']['collections_table'] ?? 0);
|
||||
$progressive = (int) ($config['settings']['progressive'] ?? 0);
|
||||
|
||||
$sleep = ($progressive === 1 && floor($collections / 500) > $baseSleep)
|
||||
? floor($collections / 500)
|
||||
: $baseSleep;
|
||||
|
||||
$niceness = Settings::settingValue('niceness') ?? 2;
|
||||
$command = "nice -n{$niceness} php {$script}";
|
||||
$command = $this->buildCommand($command, ['log_pane' => 'backfill', 'sleep' => $sleep]);
|
||||
|
||||
return $this->paneManager->respawnPane($pane, $command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run releases update
|
||||
*/
|
||||
public function runReleasesUpdate(array $config): bool
|
||||
{
|
||||
$enabled = (int) ($config['settings']['releases_run'] ?? 0);
|
||||
$pane = $config['pane'] ?? '0.3';
|
||||
|
||||
if (! $enabled) {
|
||||
return $this->disablePane($pane, 'Update Releases', 'disabled in settings');
|
||||
}
|
||||
|
||||
$script = base_path('misc/update/multiprocessing/releases.php');
|
||||
$niceness = Settings::settingValue('niceness') ?? 2;
|
||||
$command = "nice -n{$niceness} php {$script}";
|
||||
$sleep = (int) ($config['settings']['rel_timer'] ?? 60);
|
||||
$command = $this->buildCommand($command, ['log_pane' => 'releases', 'sleep' => $sleep]);
|
||||
|
||||
return $this->paneManager->respawnPane($pane, $command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a specific pane task based on task name
|
||||
*
|
||||
* @param string $taskName The name of the task to run
|
||||
* @param array $config Configuration for the task (target pane, etc.)
|
||||
* @param array $runVar Runtime variables and settings
|
||||
* @return bool Success status
|
||||
*/
|
||||
public function runPaneTask(string $taskName, array $config, array $runVar): bool
|
||||
{
|
||||
$sequential = (int) ($runVar['constants']['sequential'] ?? 0);
|
||||
|
||||
return match ($taskName) {
|
||||
'main' => $this->runMainTask($sequential, $runVar),
|
||||
'fixnames' => $this->runFixNamesTask($runVar),
|
||||
'removecrap' => $this->runRemoveCrapTask($runVar),
|
||||
'ppadditional' => $this->runPostProcessAdditional($runVar),
|
||||
'nonamazon' => $this->runNonAmazonTask($runVar),
|
||||
'amazon' => $this->runAmazonTask($runVar),
|
||||
'scraper' => $this->runIRCScraper($runVar),
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Run main task (varies by sequential mode)
|
||||
*/
|
||||
protected function runMainTask(int $sequential, array $runVar): bool
|
||||
{
|
||||
return match ($sequential) {
|
||||
0 => $this->runMainNonSequential($runVar),
|
||||
1 => $this->runMainBasic($runVar),
|
||||
2 => $this->runMainSequential($runVar),
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Run main non-sequential task (binaries, backfill, releases)
|
||||
*/
|
||||
protected function runMainNonSequential(array $runVar): bool
|
||||
{
|
||||
// This runs in pane 0.1, 0.2, 0.3
|
||||
// For now, delegate to existing methods
|
||||
$this->runBinariesUpdate($runVar);
|
||||
$this->runBackfill($runVar);
|
||||
$this->runReleasesUpdate(array_merge($runVar, ['pane' => '0.3']));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run main basic sequential task (just releases)
|
||||
*/
|
||||
protected function runMainBasic(array $runVar): bool
|
||||
{
|
||||
return $this->runReleasesUpdate(array_merge($runVar, ['pane' => '0.1']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Run main full sequential task
|
||||
*/
|
||||
protected function runMainSequential(array $runVar): bool
|
||||
{
|
||||
// Full sequential mode - runs sequential script
|
||||
$pane = '0.1';
|
||||
$script = base_path('misc/update/nix/tmux/bin/sequential.php');
|
||||
|
||||
if (! file_exists($script)) {
|
||||
return $this->disablePane($pane, 'Sequential', 'script not found');
|
||||
}
|
||||
|
||||
$niceness = Settings::settingValue('niceness') ?? 2;
|
||||
$command = "nice -n{$niceness} php {$script}";
|
||||
$command = $this->buildCommand($command, ['log_pane' => 'sequential']);
|
||||
|
||||
return $this->paneManager->respawnPane($pane, $command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run fix release names task
|
||||
*/
|
||||
protected function runFixNamesTask(array $runVar): bool
|
||||
{
|
||||
$enabled = (int) ($runVar['settings']['fix_names'] ?? 0);
|
||||
$work = (int) ($runVar['counts']['now']['processrenames'] ?? 0);
|
||||
$pane = '1.0';
|
||||
|
||||
if ($enabled !== 1) {
|
||||
return $this->disablePane($pane, 'Fix Release Names', 'disabled in settings');
|
||||
}
|
||||
|
||||
if ($work === 0) {
|
||||
return $this->disablePane($pane, 'Fix Release Names', 'no releases to process');
|
||||
}
|
||||
|
||||
$artisan = base_path('artisan');
|
||||
$log = $this->getLogFile('fixnames');
|
||||
|
||||
// Run multiple fix-names passes
|
||||
$commands = [];
|
||||
foreach ([3, 5, 7, 9, 11, 13, 15, 17, 19] as $level) {
|
||||
$commands[] = "php {$artisan} releases:fix-names {$level} --update --category=other --set-status --show 2>&1 | tee -a {$log}";
|
||||
}
|
||||
|
||||
$sleep = (int) ($runVar['settings']['fix_timer'] ?? 300);
|
||||
$allCommands = implode('; ', $commands);
|
||||
$fullCommand = "{$allCommands}; date +'%Y-%m-%d %T'; sleep {$sleep}";
|
||||
|
||||
return $this->paneManager->respawnPane($pane, $fullCommand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run remove crap releases task
|
||||
*/
|
||||
protected function runRemoveCrapTask(array $runVar): bool
|
||||
{
|
||||
$enabled = (int) ($runVar['settings']['fix_crap'] ?? 0);
|
||||
$pane = '1.1';
|
||||
|
||||
if ($enabled !== 1) {
|
||||
return $this->disablePane($pane, 'Remove Crap', 'disabled in settings');
|
||||
}
|
||||
|
||||
$option = (int) ($runVar['settings']['fix_crap_opt'] ?? 1);
|
||||
$script = base_path('misc/update/nix/tmux/bin/removecrap.php');
|
||||
|
||||
if (! file_exists($script)) {
|
||||
return $this->disablePane($pane, 'Remove Crap', 'script not found');
|
||||
}
|
||||
|
||||
$niceness = Settings::settingValue('niceness') ?? 2;
|
||||
$command = "nice -n{$niceness} php {$script} {$option}";
|
||||
$sleep = (int) ($runVar['settings']['crap_timer'] ?? 300);
|
||||
$command = $this->buildCommand($command, ['log_pane' => 'removecrap', 'sleep' => $sleep]);
|
||||
|
||||
return $this->paneManager->respawnPane($pane, $command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run post-process additional task
|
||||
*/
|
||||
protected function runPostProcessAdditional(array $runVar): bool
|
||||
{
|
||||
$enabled = (int) ($runVar['settings']['post'] ?? 0);
|
||||
$pane = '2.0';
|
||||
|
||||
if ($enabled !== 1) {
|
||||
return $this->disablePane($pane, 'Post-process Additional', 'disabled in settings');
|
||||
}
|
||||
|
||||
$script = base_path('misc/update/postprocess.php');
|
||||
|
||||
if (! file_exists($script)) {
|
||||
return $this->disablePane($pane, 'Post-process Additional', 'script not found');
|
||||
}
|
||||
|
||||
$niceness = Settings::settingValue('niceness') ?? 2;
|
||||
$command = "nice -n{$niceness} php {$script} additional true";
|
||||
$sleep = (int) ($runVar['settings']['post_timer'] ?? 300);
|
||||
$command = $this->buildCommand($command, ['log_pane' => 'post_additional', 'sleep' => $sleep]);
|
||||
|
||||
return $this->paneManager->respawnPane($pane, $command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run non-Amazon post-processing (TV, Movies, Anime)
|
||||
*/
|
||||
protected function runNonAmazonTask(array $runVar): bool
|
||||
{
|
||||
$enabled = (int) ($runVar['settings']['post_non'] ?? 0);
|
||||
$pane = '2.1';
|
||||
|
||||
if ($enabled !== 1) {
|
||||
return $this->disablePane($pane, 'Post-process Non-Amazon', 'disabled in settings');
|
||||
}
|
||||
|
||||
$hasWork = (int) ($runVar['counts']['now']['processmovies'] ?? 0) > 0
|
||||
|| (int) ($runVar['counts']['now']['processtv'] ?? 0) > 0
|
||||
|| (int) ($runVar['counts']['now']['processanime'] ?? 0) > 0;
|
||||
|
||||
if (! $hasWork) {
|
||||
return $this->disablePane($pane, 'Post-process Non-Amazon', 'no movies/tv/anime to process');
|
||||
}
|
||||
|
||||
$script = base_path('misc/update/postprocess.php');
|
||||
|
||||
if (! file_exists($script)) {
|
||||
return $this->disablePane($pane, 'Post-process Non-Amazon', 'script not found');
|
||||
}
|
||||
|
||||
$niceness = Settings::settingValue('niceness') ?? 2;
|
||||
$log = $this->getLogFile('post_non');
|
||||
|
||||
$commands = [
|
||||
"php {$script} tv true 2>&1 | tee -a {$log}",
|
||||
"php {$script} movie true 2>&1 | tee -a {$log}",
|
||||
"php {$script} anime true 2>&1 | tee -a {$log}",
|
||||
];
|
||||
|
||||
$sleep = (int) ($runVar['settings']['post_timer_non'] ?? 300);
|
||||
$allCommands = "nice -n{$niceness} ".implode('; nice -n{$niceness} php ', $commands);
|
||||
$fullCommand = "{$allCommands}; date +'%Y-%m-%d %T'; sleep {$sleep}";
|
||||
|
||||
return $this->paneManager->respawnPane($pane, $fullCommand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Amazon post-processing (Books, Music, Games, Console, XXX)
|
||||
*/
|
||||
protected function runAmazonTask(array $runVar): bool
|
||||
{
|
||||
$enabled = (int) ($runVar['settings']['post_amazon'] ?? 0);
|
||||
$pane = '2.2';
|
||||
|
||||
if ($enabled !== 1) {
|
||||
return $this->disablePane($pane, 'Post-process Amazon', 'disabled in settings');
|
||||
}
|
||||
|
||||
$hasWork = (int) ($runVar['counts']['now']['processmusic'] ?? 0) > 0
|
||||
|| (int) ($runVar['counts']['now']['processbooks'] ?? 0) > 0
|
||||
|| (int) ($runVar['counts']['now']['processconsole'] ?? 0) > 0
|
||||
|| (int) ($runVar['counts']['now']['processgames'] ?? 0) > 0
|
||||
|| (int) ($runVar['counts']['now']['processxxx'] ?? 0) > 0;
|
||||
|
||||
if (! $hasWork) {
|
||||
return $this->disablePane($pane, 'Post-process Amazon', 'no music/books/games to process');
|
||||
}
|
||||
|
||||
$script = base_path('misc/update/postprocess.php');
|
||||
|
||||
if (! file_exists($script)) {
|
||||
return $this->disablePane($pane, 'Post-process Amazon', 'script not found');
|
||||
}
|
||||
|
||||
$niceness = Settings::settingValue('niceness') ?? 2;
|
||||
$command = "nice -n{$niceness} php {$script} amazon true";
|
||||
$sleep = (int) ($runVar['settings']['post_timer_amazon'] ?? 300);
|
||||
$command = $this->buildCommand($command, ['log_pane' => 'post_amazon', 'sleep' => $sleep]);
|
||||
|
||||
return $this->paneManager->respawnPane($pane, $command);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
# NNTmux Tmux Configuration
|
||||
# This file is now managed in the config/ directory
|
||||
|
||||
# GNU-Screen compatible prefix
|
||||
set -g prefix2 C-a
|
||||
bind C-a send-prefix -2
|
||||
|
||||
# Set copy mode to use VI keys - Activates PgUp/PgDn buttons
|
||||
setw -g mode-keys vi
|
||||
|
||||
# Allows for faster key repetition
|
||||
set -s escape-time 0
|
||||
|
||||
# Set 256 color display
|
||||
set -g default-terminal "xterm-256color"
|
||||
|
||||
# Set status bar
|
||||
set -g status-bg black
|
||||
set -g status-fg white
|
||||
set -g status-left ""
|
||||
|
||||
# Constrain window size to the maximum size of any client connected to that window
|
||||
setw -g aggressive-resize on
|
||||
|
||||
# Activity monitoring
|
||||
setw -g monitor-activity on
|
||||
|
||||
# Refresh the status bar every 5 seconds
|
||||
set -g status-interval 5
|
||||
|
||||
# Right status bar with system information
|
||||
set -g status-right "#[fg=yellow]#(free -h | grep 'Mem' | awk '{ print \"RAM Used: \"$3\", Cached: \"$6\", \";}')#(free -m | grep 'Swap' | awk '{ print \"Swapped: \"$3;}')M #[fg=cyan,bold] #(uptime | cut -d ',' -f 4-)"
|
||||
|
||||
# Scrollback line buffer per pane
|
||||
set -g history-limit 6000
|
||||
|
||||
# Keep pane open after process ends - required for monitor.php re-spawns
|
||||
set -g remain-on-exit on
|
||||
|
||||
# Enable mouse support
|
||||
set -g mouse on
|
||||
|
||||
# Rename pane
|
||||
bind t command-prompt -p "(rename-pane)" -I "#T" "select-pane -T '%%'"
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Tmux Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This file contains configuration options for tmux session management.
|
||||
| Previously located in misc/update/tmux/tmux.conf, this has been
|
||||
| modernized and integrated into Laravel's config system.
|
||||
|
|
||||
*/
|
||||
|
||||
'config_file' => env('TMUX_CONFIG_FILE', config_path('tmux.conf')),
|
||||
|
||||
'session' => [
|
||||
'name' => env('TMUX_SESSION_NAME'),
|
||||
'default_name' => 'nntmux',
|
||||
],
|
||||
|
||||
'terminal' => [
|
||||
'type' => env('TMUX_TERMINAL', 'xterm-256color'),
|
||||
'escape_time' => 0,
|
||||
],
|
||||
|
||||
'status_bar' => [
|
||||
'interval' => 5,
|
||||
'bg_color' => 'black',
|
||||
'fg_color' => 'white',
|
||||
'show_system_info' => true,
|
||||
],
|
||||
|
||||
'panes' => [
|
||||
'history_limit' => 6000,
|
||||
'remain_on_exit' => true,
|
||||
'aggressive_resize' => true,
|
||||
'monitor_activity' => true,
|
||||
],
|
||||
|
||||
'keys' => [
|
||||
'mode' => 'vi',
|
||||
'prefix' => 'C-a',
|
||||
],
|
||||
|
||||
'mouse' => [
|
||||
'enabled' => true,
|
||||
],
|
||||
|
||||
'paths' => [
|
||||
'scripts' => base_path('app/Services/Tmux/Scripts'),
|
||||
'logs' => storage_path('logs/tmux'),
|
||||
],
|
||||
|
||||
'monitor' => [
|
||||
'delay' => env('TMUX_MONITOR_DELAY', 10),
|
||||
'refresh_interval' => env('TMUX_REFRESH_INTERVAL', 60),
|
||||
],
|
||||
|
||||
'performance' => [
|
||||
'niceness' => env('TMUX_NICENESS', 2),
|
||||
'timeout' => env('TMUX_TIMEOUT', 300),
|
||||
],
|
||||
];
|
||||
@@ -1,95 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Smarty to Blade Migration Helper Script
|
||||
*
|
||||
* This script helps convert Smarty template files to Blade templates
|
||||
* Usage: php migrate-templates.php [template-file.tpl]
|
||||
*/
|
||||
function convertSmartyToBlade($smartyContent)
|
||||
{
|
||||
$bladeContent = $smartyContent;
|
||||
|
||||
// Convert comments
|
||||
$bladeContent = preg_replace('/\{\*(.+?)\*\}/s', '{{-- $1 --}}', $bladeContent);
|
||||
|
||||
// Convert variables: {$var} to {{ $var }}
|
||||
$bladeContent = preg_replace('/\{\$([a-zA-Z0-9_\.\[\]\'\"]+)\}/', '{{ $$$1 }}', $bladeContent);
|
||||
|
||||
// Convert if statements
|
||||
$bladeContent = preg_replace('/\{if\s+(.+?)\}/', '@if($1)', $bladeContent);
|
||||
$bladeContent = preg_replace('/\{elseif\s+(.+?)\}/', '@elseif($1)', $bladeContent);
|
||||
$bladeContent = preg_replace('/\{else\}/', '@else', $bladeContent);
|
||||
$bladeContent = preg_replace('/\{\/if\}/', '@endif', $bladeContent);
|
||||
|
||||
// Convert foreach loops
|
||||
$bladeContent = preg_replace('/\{foreach\s+\$([a-zA-Z0-9_]+)\s+as\s+\$([a-zA-Z0-9_]+)\}/', '@foreach($$1 as $$2)', $bladeContent);
|
||||
$bladeContent = preg_replace('/\{foreach\s+\$([a-zA-Z0-9_]+)\s+as\s+\$([a-zA-Z0-9_]+)\s*=>\s*\$([a-zA-Z0-9_]+)\}/', '@foreach($$1 as $$2 => $$3)', $bladeContent);
|
||||
$bladeContent = preg_replace('/\{\/foreach\}/', '@endforeach', $bladeContent);
|
||||
|
||||
// Convert includes
|
||||
$bladeContent = preg_replace('/\{include\s+file=[\'"](.+?)[\'"]\}/', '@include(\'$1\')', $bladeContent);
|
||||
|
||||
// Convert URLs - keep as is since they're already using Laravel syntax
|
||||
// {{url()}} and {{route()}} work in both Smarty and Blade
|
||||
|
||||
// Convert Auth checks
|
||||
$bladeContent = preg_replace('/\{if\s+Auth::check\(\)\}/', '@auth', $bladeContent);
|
||||
|
||||
// Fix object property access: $obj.prop to $obj->prop
|
||||
$bladeContent = preg_replace('/\$([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)/', '$$$1->$2', $bladeContent);
|
||||
|
||||
return $bladeContent;
|
||||
}
|
||||
|
||||
if ($argc < 2) {
|
||||
echo "Usage: php migrate-templates.php [template-file.tpl]\n";
|
||||
echo "Example: php migrate-templates.php resources/views/themes/Gentele/browse.tpl\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$inputFile = $argv[1];
|
||||
|
||||
if (! file_exists($inputFile)) {
|
||||
echo "Error: File not found: $inputFile\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$smartyContent = file_get_contents($inputFile);
|
||||
$bladeContent = convertSmartyToBlade($smartyContent);
|
||||
|
||||
// Determine output filename
|
||||
$outputFile = preg_replace('/\.tpl$/', '.blade.php', $inputFile);
|
||||
$outputFile = str_replace('/themes/Gentele/', '/', $outputFile);
|
||||
$outputFile = str_replace('/themes/admin/', '/admin/', $outputFile);
|
||||
$outputFile = str_replace('/themes/shared/', '/shared/', $outputFile);
|
||||
|
||||
echo "Converting: $inputFile\n";
|
||||
echo "To: $outputFile\n";
|
||||
echo "\nPreview of converted content:\n";
|
||||
echo str_repeat('-', 80)."\n";
|
||||
echo substr($bladeContent, 0, 500)."...\n";
|
||||
echo str_repeat('-', 80)."\n";
|
||||
|
||||
echo "\nDo you want to save this conversion? (y/n): ";
|
||||
$handle = fopen('php://stdin', 'r');
|
||||
$line = fgets($handle);
|
||||
if (trim($line) != 'y') {
|
||||
echo "Cancelled.\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
$outputDir = dirname($outputFile);
|
||||
if (! is_dir($outputDir)) {
|
||||
mkdir($outputDir, 0755, true);
|
||||
}
|
||||
|
||||
file_put_contents($outputFile, $bladeContent);
|
||||
echo "Saved to: $outputFile\n";
|
||||
echo "\nNote: Please review the converted file and make manual adjustments as needed.\n";
|
||||
echo "Common manual adjustments:\n";
|
||||
echo " - Array access: \$arr['key'] instead of \$arr.key\n";
|
||||
echo " - Complex expressions may need refinement\n";
|
||||
echo " - Bootstrap classes should be converted to TailwindCSS\n";
|
||||
@@ -1 +0,0 @@
|
||||
*.log
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user