Modernize tmux handling

This commit is contained in:
DariusIII
2025-11-01 16:26:59 +01:00
parent 96ef05f812
commit 189338aab6
21 changed files with 2109 additions and 10823 deletions
+30
View File
@@ -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
+52
View File
@@ -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;
}
}
+194
View File
@@ -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);
}
}
+91
View File
@@ -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;
}
}
}
+8 -14
View File
@@ -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');
}
}
+6 -30
View File
@@ -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());
+8 -18
View File
@@ -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'),
]);
}
}
+128
View File
@@ -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);
}
+25
View File
@@ -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";
+227
View File
@@ -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);
}
}
+419
View File
@@ -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;
}
}
+146
View File
@@ -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() : '';
}
}
+145
View File
@@ -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';
}
}
+521
View File
@@ -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);
}
}
+45
View File
@@ -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 '%%'"
+64
View File
@@ -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),
],
];
-95
View File
@@ -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
View File
@@ -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