warn('Password rotation: no passwords in wordlist — skipped.'); return self::SUCCESS; } if (! $this->option('force') && ! $this->isDue()) { return self::SUCCESS; } $password = $passwords[array_rand($passwords)]; $rotated = 0; foreach ($wlanIds as $wlanId) { try { $unifi->updateWlan($wlanId, ['x_passphrase' => $password]); $rotated++; } catch (\Throwable $e) { $this->error("Failed to rotate wlan {$wlanId}: {$e->getMessage()}"); } } if ($rotated > 0) { Setting::set('unifi.password_rotation.last_rotated_at', now()->toIso8601String()); $this->info("Rotated password for {$rotated} SSID(s)."); } // ── Rotate PPSK passwords ──────────────────────────────────────────── $rotatedPpsks = 0; foreach (UnifiPpsk::where('rotate_password', true)->where('state', 'active')->whereNotNull('unifi_id')->get() as $ppsk) { // Each PPSK gets its own independently-chosen password from the wordlist $newPass = $passwords[array_rand($passwords)]; try { $unifi->updatePpsk($ppsk->unifi_id, ['x_passphrase' => $newPass]); $ppsk->update(['x_passphrase' => $newPass]); $rotatedPpsks++; } catch (\Throwable $e) { $this->error("Failed to rotate PPSK \"{$ppsk->name}\": {$e->getMessage()}"); } } if ($rotatedPpsks > 0) { $this->info("Rotated password for {$rotatedPpsks} PPSK(s)."); } return self::SUCCESS; } private function isDue(): bool { $frequency = Setting::get('unifi.password_rotation.frequency', 'weekly'); $hour = (int) Setting::get('unifi.password_rotation.hour', 2); $minute = (int) Setting::get('unifi.password_rotation.minute', 0); $dow = (int) Setting::get('unifi.password_rotation.day_of_week', 0); $tz = \App\Support\Timezone::current(); $now = now($tz); if ($now->hour !== $hour || $now->minute !== $minute) { return false; } if ($frequency === 'weekly' && $now->dayOfWeek !== $dow) { return false; } return true; } }