option('yes'); if (File::exists(base_path().'/_install/install.lock')) { if ($yesMode) { $this->info('Install is locked. The file "install.lock" is present. Use interactive mode to remove it.'); exit; } else { if ($this->confirm('Install is locked. Do you want to remove the "install.lock" file to continue?')) { $this->info('Removing install.lock file so we can continue with install process...'); $remove = Process::timeout(600)->run('rm _install/install.lock'); echo $remove->output(); echo $remove->errorOutput(); } else { $this->info('Installation aborted. The file "install.lock" was not removed.'); exit; } } } if (! $yesMode) { if (! $this->confirm('Are you sure you want to install NNTmux? This will wipe your database!!')) { $this->info('Installation aborted by user.'); exit; } } // Ask for database type and configure accordingly $this->configureDatabaseDriver($yesMode); $this->info('Migrating tables and seeding them with initial data'); if (config('app.env') !== 'production') { $this->call('migrate:fresh', ['--seed' => true]); } else { $this->call('migrate:fresh', ['--force' => true, '--seed' => true]); } $paths = $this->updatePaths(); if ($paths !== false) { $this->info('Paths checked successfully'); } $this->setupManticoreConfig(); if ($this->addAdminUser()) { File::put(base_path().'/_install/install.lock', 'application install locked on '.now()); $this->info('Generating application key'); $this->call('key:generate', ['--force' => true]); $this->info('NNTmux installation completed successfully'); exit(); } $this->error('NNTmux installation failed. Fix reported problems and try again'); } /** * Ask user for database type and configure the driver and schema accordingly. */ private function configureDatabaseDriver(bool $yesMode): void { $currentDriver = config('database.default', 'mysql'); if ($yesMode) { $this->info("Using current database driver: {$currentDriver}"); $dbType = $currentDriver; } else { $dbType = $this->choice( 'Which database are you using?', ['mariadb', 'mysql'], $currentDriver === 'mariadb' ? 0 : 1 ); } $this->info("Configuring database driver for: {$dbType}"); // Update .env file with the selected database driver $this->updateEnvFile('DB_CONNECTION', $dbType); // Handle schema file - ensure the correct schema file exists for the selected driver $this->ensureSchemaFileExists($dbType); // Clear config cache to ensure the new driver is used $this->call('config:clear'); // Reload the configuration config(['database.default' => $dbType]); } /** * Update a value in the .env file. */ private function updateEnvFile(string $key, string $value): void { $envPath = base_path('.env'); if (! File::exists($envPath)) { $this->error('.env file not found!'); return; } $envContent = File::get($envPath); // Check if the key exists in the .env file if (preg_match("/^{$key}=.*/m", $envContent)) { // Replace the existing value $envContent = preg_replace("/^{$key}=.*/m", "{$key}={$value}", $envContent); } else { // Add the key if it doesn't exist $envContent .= "\n{$key}={$value}"; } File::put($envPath, $envContent); $this->info("Updated .env: {$key}={$value}"); } /** * Ensure the schema file exists for the selected database driver. * Laravel looks for {connection}-schema.sql in database/schema/ */ private function ensureSchemaFileExists(string $dbType): void { $schemaDir = database_path('schema'); $targetSchema = "{$schemaDir}/{$dbType}-schema.sql"; $mariadbSchema = "{$schemaDir}/mariadb-schema.sql"; // If the target schema already exists, we're good if (File::exists($targetSchema)) { $this->info("Schema file exists: {$dbType}-schema.sql"); return; } // For MySQL, we can use the MariaDB schema as they are compatible if ($dbType === 'mysql' && File::exists($mariadbSchema)) { $this->info('Creating MySQL schema file from MariaDB schema (they are compatible)...'); File::copy($mariadbSchema, $targetSchema); $this->info("Created: {$dbType}-schema.sql"); return; } $this->warn("Schema file not found: {$dbType}-schema.sql"); $this->warn('The migration will create tables from scratch.'); } /** * @return bool * * @throws \Exception * @throws \RuntimeException */ private function updatePaths() { $covers_path = config('nntmux_settings.covers_path'); $nzb_path = config('nntmux_settings.path_to_nzbs'); $zip_path = config('nntmux_settings.tmp_unzip_path'); $unrar_path = config('nntmux.tmp_unrar_path'); // Validate that all required paths are configured if (empty($nzb_path)) { $this->warn('NZB path (nntmux_settings.path_to_nzbs) is not configured. Please check your .env or config files.'); return false; } if (empty($unrar_path)) { $this->warn('Unrar path (nntmux.tmp_unrar_path) is not configured. Please check your .env or config files.'); return false; } if (empty($covers_path)) { $this->warn('Covers path (nntmux_settings.covers_path) is not configured. Please check your .env or config files.'); return false; } if (empty($zip_path)) { $this->warn('Unzip path (nntmux_settings.tmp_unzip_path) is not configured. Please check your .env or config files.'); return false; } if (! File::isWritable($nzb_path)) { $this->warn($nzb_path.' is not writable. Please fix folder permissions'); return false; } if (! file_exists($unrar_path)) { $this->info('Creating missing '.$unrar_path.' folder'); if (! @File::makeDirectory($unrar_path) && ! File::isDirectory($unrar_path)) { throw new \RuntimeException('Unable to create '.$unrar_path.' folder'); } $this->info('Folder '.$unrar_path.' successfully created'); } if (! is_writable($unrar_path)) { $this->warn($unrar_path.' is not writable. Please fix folder permissions'); return false; } if (! File::isWritable($covers_path)) { $this->warn($covers_path.' is not writable. Please fix folder permissions'); return false; } if (! File::isWritable($zip_path)) { $this->warn($zip_path.' is not writable. Please fix folder permissions'); return false; } return true; } private function addAdminUser(): bool { if (config('nntmux.admin_username') === '' || config('nntmux.admin_password') === '' || config('nntmux.admin_email') === '') { $this->error('Admin user data cannot be empty! Please edit .env file and fill in admin user details and run this script again!'); exit(); } $this->info('Adding admin user to database'); try { User::add(config('nntmux.admin_username'), config('nntmux.admin_password'), config('nntmux.admin_email'), 2); User::where('username', config('nntmux.admin_username'))->first()?->markEmailAsVerified(); } catch (\Throwable $e) { echo $e->getMessage(); $this->error('Unable to add admin user!'); return false; } return true; } /** * Setup Manticore configuration by creating a symlink from the system config location * to the project's config file (for native installations, not Docker). */ private function setupManticoreConfig(): void { $sourceConfig = base_path('config/manticore.conf'); $targetConfig = '/etc/manticoresearch/manticore.conf'; // Skip if running in Docker (config is mounted via docker-compose) if (app()->runningInConsole() && getenv('LARAVEL_SAIL')) { return; } if (! File::exists($sourceConfig)) { return; } // Check if target directory exists if (! File::isDirectory('/etc/manticoresearch')) { $this->warn('Manticore config directory /etc/manticoresearch does not exist. Is Manticore installed?'); return; } // Check if symlink or file already exists if (File::exists($targetConfig) || is_link($targetConfig)) { if (is_link($targetConfig) && readlink($targetConfig) === $sourceConfig) { $this->info('Manticore config symlink already exists and points to correct location.'); return; } $this->warn("Manticore config already exists at {$targetConfig}."); $this->warn("To use the project's config, manually run: sudo ln -sf {$sourceConfig} {$targetConfig}"); return; } // Check if we can write to the target directory without sudo $needsSudo = ! is_writable('/etc/manticoresearch'); if ($needsSudo) { $this->info('Creating symlink for Manticore configuration requires sudo privileges.'); if (! $this->confirm('Do you want to create the symlink now? You will be prompted for your password.')) { $this->warn("Skipping symlink creation. To create it manually, run: sudo ln -s {$sourceConfig} {$targetConfig}"); return; } // Use TTY mode to allow interactive sudo password prompt $result = Process::forever()->tty()->run("sudo ln -s {$sourceConfig} {$targetConfig}"); } else { $this->info('Creating symlink for Manticore configuration...'); $result = Process::run("ln -s {$sourceConfig} {$targetConfig}"); } if ($result->successful()) { $this->info("Manticore config symlink created: {$targetConfig} -> {$sourceConfig}"); } else { $this->warn('Could not create Manticore config symlink.'); $this->warn($result->errorOutput()); $this->warn("Please run manually: sudo ln -s {$sourceConfig} {$targetConfig}"); } } }