Files
newznab-tmux/app/Console/Commands/TmuxStart.php
T
2026-08-04 11:51:35 +02:00

216 lines
7.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Enums\TmuxPaneRole;
use App\Models\Collection;
use App\Models\Settings;
use App\Services\Tmux\TmuxLayoutBuilder;
use App\Services\Tmux\TmuxPaneManager;
use App\Services\Tmux\TmuxSessionManager;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Process;
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 bool $sessionCreated = false;
/**
* Execute the console command.
*/
public function handle(): int
{
try {
cli()->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: '.($this->layoutBuilder->lastError() ?? 'unknown error'));
return Command::FAILURE;
}
$this->info('✅ Tmux layout created');
$this->sessionCreated = true;
// 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);
// Select monitor pane so attach lands there
$paneManager = new TmuxPaneManager($sessionName);
if (! $paneManager->selectWindow(0)
|| ! $paneManager->selectPane($paneManager->paneForRole(TmuxPaneRole::Monitor, '0.0'))) {
throw new \RuntimeException($paneManager->lastError() ?? 'Unable to select the tmux monitor pane.');
}
$this->info("✅ Tmux session '{$sessionName}' started successfully");
// Attach if requested
if ($this->option('attach')) {
$this->info('📎 Attaching to session...');
if (! $this->sessionManager->attachSession()) {
$this->error('❌ Unable to attach to the tmux session.');
return Command::FAILURE;
}
} 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) {
if ($this->sessionCreated && isset($this->sessionManager)) {
$this->sessionManager->killSession();
Settings::query()->where('name', 'running')->update(['value' => 0]);
}
$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 = 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 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)');
}
$monitorPane = $paneManager->paneForRole(TmuxPaneRole::Monitor, '0.0');
if (! $paneManager->respawnPane($monitorPane, $command)) {
throw new \RuntimeException('Unable to start the tmux monitor pane.');
}
}
}