serverId = $serverId; $server = Server::find($serverId); if (! $server) { $this->error = __('common.server_not_found'); return; } $this->serverName = $server->name; try { $current = $fail2ban->readConfig($server); $this->bantime = $current['bantime']; $this->maxretry = $current['maxretry']; $this->findtime = $current['findtime']; $this->loaded = true; } catch (Throwable $e) { $this->error = $e->getMessage(); } } public static function modalMaxWidth(): string { return 'lg'; } public function save(Fail2banService $fail2ban): void { // Never overwrite the remote policy with defaults the operator never saw — // saving is only allowed once the current values were read successfully. if (! $this->loaded) { $this->error = __('modals.fail2ban_config.error_read_locked'); return; } $this->error = null; // fail2ban duration grammar: a number (seconds) optionally with a unit // (s/m/h/d/w/mo/y), space-separated composites, or -1 for a permanent ban. $duration = ['required', 'string', 'max:40', 'regex:/^(?:-1|\d+(?:\.\d+)?\s*(?:s|m|h|d|w|mo|y)?(?:\s+\d+(?:\.\d+)?\s*(?:s|m|h|d|w|mo|y)?)*)$/i']; $validated = $this->validate([ 'bantime' => $duration, 'maxretry' => ['required', 'integer', 'min:1', 'max:100'], 'findtime' => $duration, ], [ 'bantime.regex' => __('modals.fail2ban_config.error_bantime_invalid'), 'findtime.regex' => __('modals.fail2ban_config.error_findtime_invalid'), ], [ 'bantime' => __('modals.fail2ban_config.attr_bantime'), 'maxretry' => __('modals.fail2ban_config.attr_maxretry'), 'findtime' => __('modals.fail2ban_config.attr_findtime'), ]); $server = Server::find($this->serverId); if (! $server) { $this->error = __('common.server_not_found'); return; } try { // Tuning lives in its OWN drop-in (separate from the whitelist), so this can // never touch ignoreip. Values were validated/clamped above. $result = $fail2ban->writeTuning( $server, (string) $validated['bantime'], (int) $validated['maxretry'], (string) $validated['findtime'], ); } catch (Throwable $e) { $this->error = $e->getMessage(); return; } if (! $result['ok']) { $this->error = __('modals.fail2ban_config.error_save_failed', ['output' => $result['output']]); return; } AuditEvent::create([ 'user_id' => Auth::id(), 'server_id' => $server->id, 'actor' => Auth::user()?->name ?? 'system', 'action' => 'fail2ban.configure', 'target' => $server->name.' · bantime '.$validated['bantime'].', maxretry '.$validated['maxretry'], 'ip' => request()->ip(), ]); $this->dispatch('hardeningApplied'); $this->dispatch('notify', message: __('modals.fail2ban_config.notify_saved')); $this->closeModal(); } public function render() { return view('livewire.modals.fail2ban-config'); } }