Previously SyncPpskSchedules returned early when the global setting
was disabled, leaving any PPSK that had been held by a prior sync
stuck in 'held' state. It also only iterated whereNotNull(schedule),
so null-schedule PPSKs ("always on") were never drift-corrected back
to active either.
Now the command always runs and computes a per-PPSK target state:
- global ppsk_scheduling disabled → target = active (always)
- global enabled + null schedule → target = active (always)
- global enabled + has schedule → follow the schedule's slot
PPSKs that drift from the target get enabled/disabled accordingly.
Schedules in unifi_ppsks.schedule are preserved across global toggles
either way — disabling the setting doesn't touch them, so re-enabling
resumes the operator's per-PPSK schedules.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
105 lines
3.9 KiB
PHP
105 lines
3.9 KiB
PHP
<?php
|
||
|
||
namespace Dashboard\Unifi\Console;
|
||
|
||
use App\Models\Setting;
|
||
use Dashboard\Unifi\Models\UnifiPpsk;
|
||
use Dashboard\Unifi\Services\UnifiApiClient;
|
||
use Illuminate\Console\Command;
|
||
|
||
class SyncPpskSchedules extends Command
|
||
{
|
||
protected $signature = 'unifi:sync-ppsk-schedules {--force : Run even if PPSK scheduling is disabled}';
|
||
protected $description = 'Enable or disable PPSKs based on their weekly half-hour schedule, kicking active clients when disabling';
|
||
|
||
public function handle(UnifiApiClient $unifi): int
|
||
{
|
||
// Always run, even when global ppsk_scheduling is disabled — in
|
||
// that case the target state for every PPSK is "active" (always
|
||
// on). That way disabling the global setting actually restores
|
||
// any held PPSKs to active without operators having to do
|
||
// anything else, and null-schedule PPSKs always end up active.
|
||
// Schedules in the DB are preserved regardless of toggle state,
|
||
// so re-enabling resumes the per-PPSK schedule.
|
||
$globalEnabled = (bool) Setting::get('unifi.ppsk_scheduling.enabled');
|
||
|
||
$tz = \App\Support\Timezone::current();
|
||
$now = now($tz);
|
||
$day = $now->dayOfWeek; // 0=Sun … 6=Sat
|
||
$slot = $now->hour * 2 + ($now->minute >= 30 ? 1 : 0); // 0–47
|
||
|
||
$ppsks = UnifiPpsk::all();
|
||
|
||
if ($ppsks->isEmpty()) {
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
// Fetch network confs once so we can resolve vlan → networkconf_id on re-enable
|
||
$networksByVlan = [];
|
||
try {
|
||
foreach ($unifi->getNetworkConfs() as $n) {
|
||
if (isset($n['vlan'])) {
|
||
$networksByVlan[(int) $n['vlan']] = $n;
|
||
}
|
||
}
|
||
} catch (\Throwable $e) {
|
||
$this->warn("Could not fetch network configs: {$e->getMessage()}");
|
||
}
|
||
|
||
foreach ($ppsks as $ppsk) {
|
||
// Default to "always on". Only consult the schedule if
|
||
// global scheduling is enabled AND this PPSK has one.
|
||
$shouldBeOn = true;
|
||
if ($globalEnabled && $ppsk->schedule) {
|
||
$shouldBeOn = (bool) ($ppsk->schedule[$day * 48 + $slot] ?? true);
|
||
}
|
||
|
||
if ($shouldBeOn && $ppsk->state === 'held') {
|
||
$this->enablePpsk($ppsk, $unifi, $networksByVlan);
|
||
} elseif (! $shouldBeOn && $ppsk->state === 'active' && $ppsk->unifi_id) {
|
||
$this->disablePpsk($ppsk, $unifi);
|
||
}
|
||
}
|
||
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
private function enablePpsk(UnifiPpsk $ppsk, UnifiApiClient $unifi, array $networksByVlan): void
|
||
{
|
||
try {
|
||
$data = [
|
||
'name' => $ppsk->name,
|
||
'x_passphrase' => $ppsk->x_passphrase,
|
||
'wlan_id' => $ppsk->wlan_id,
|
||
];
|
||
|
||
if ($ppsk->vlan && isset($networksByVlan[$ppsk->vlan])) {
|
||
$data['networkconf_id'] = $networksByVlan[$ppsk->vlan]['_id'];
|
||
}
|
||
|
||
$result = $unifi->createPpsk($data);
|
||
$raw = $result[0] ?? $result;
|
||
$newId = $raw['_id'] ?? null;
|
||
|
||
$ppsk->update(['state' => 'active', 'unifi_id' => $newId]);
|
||
$this->info("Enabled: {$ppsk->name} (wlan {$ppsk->wlan_id})");
|
||
} catch (\Throwable $e) {
|
||
$this->error("Failed to enable PPSK \"{$ppsk->name}\": {$e->getMessage()}");
|
||
}
|
||
}
|
||
|
||
private function disablePpsk(UnifiPpsk $ppsk, UnifiApiClient $unifi): void
|
||
{
|
||
try {
|
||
$kicked = $unifi->kickClientsForPpsk($ppsk->unifi_id);
|
||
$unifi->deletePpsk($ppsk->unifi_id);
|
||
$ppsk->update(['state' => 'held', 'unifi_id' => null]);
|
||
|
||
$suffix = $kicked > 0 ? " — kicked {$kicked} client(s)" : '';
|
||
$this->info("Disabled: {$ppsk->name}{$suffix}");
|
||
} catch (\Throwable $e) {
|
||
$this->error("Failed to disable PPSK \"{$ppsk->name}\": {$e->getMessage()}");
|
||
}
|
||
}
|
||
}
|