94 lines
5.5 KiB
PHP
94 lines
5.5 KiB
PHP
<?php
|
|
namespace App\Services\Nas;
|
|
|
|
use App\Models\NasMikrotik;
|
|
use App\Models\NasPackageProfile;
|
|
use App\Models\NasPackageProfileTarget;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Throwable;
|
|
|
|
class PackageProfileDeploymentService
|
|
{
|
|
public function __construct(protected RouterOsApiService $routerOs) {}
|
|
|
|
public function targets(NasPackageProfile $profile)
|
|
{
|
|
return $profile->targets()->with('mikrotik:id,tenant_id,name,connection_type,host,connection_status')->orderBy('id')->get();
|
|
}
|
|
|
|
public function saveTarget(NasPackageProfile $profile, array $data): NasPackageProfileTarget
|
|
{
|
|
$nas = NasMikrotik::with('tenant.nasSetting')->whereKey($data['nas_mikrotik_id'])->where('tenant_id', $profile->tenant_id)->firstOrFail();
|
|
$backendMode = $nas->effectiveBackendMode();
|
|
return NasPackageProfileTarget::updateOrCreate(
|
|
['nas_package_profile_id' => $profile->id, 'nas_mikrotik_id' => $nas->id],
|
|
['tenant_id' => $profile->tenant_id, 'backend_type' => in_array($backendMode, ['platform_radius','external_radius','hybrid'], true) ? 'radius' : 'routeros_api',
|
|
'external_profile_name' => $data['external_profile_name'], 'sync_status' => 'not_deployed']
|
|
);
|
|
}
|
|
|
|
public function sync(NasPackageProfileTarget $target, string $mode): NasPackageProfileTarget
|
|
{
|
|
$target->load(['packageProfile.scripts.versions', 'mikrotik']);
|
|
$desired = $this->desiredSnapshot($target->packageProfile);
|
|
try {
|
|
$remote = $target->backend_type === 'routeros_api'
|
|
? (in_array($target->packageProfile->service_type, ['static_ip', 'dynamic_ip'], true)
|
|
? ['managed_per_customer' => true, 'network' => $desired['network']]
|
|
: $this->routerOs->syncProfile($target->mikrotik, $target->packageProfile->service_type, $target->external_profile_name, $desired['routeros'], $mode))
|
|
: ['radius_attributes' => $desired['radius']];
|
|
$checksum = hash('sha256', json_encode($desired));
|
|
$target->update(['sync_status' => 'synced', 'desired_snapshot' => $desired, 'remote_snapshot' => $remote,
|
|
'remote_id' => $remote['.id'] ?? null, 'deployed_checksum' => $checksum, 'last_checked_at' => now(), 'last_synced_at' => now(), 'last_error' => null]);
|
|
} catch (Throwable $e) {
|
|
$target->update(['sync_status' => 'error', 'desired_snapshot' => $desired, 'last_checked_at' => now(), 'last_error' => $e instanceof ValidationException ? collect($e->errors())->flatten()->first() : $e->getMessage()]);
|
|
throw $e;
|
|
}
|
|
return $target->fresh('mikrotik');
|
|
}
|
|
|
|
public function assertSynced(NasPackageProfile $profile, NasMikrotik $nas): NasPackageProfileTarget
|
|
{
|
|
$target = $profile->targets()->where('nas_mikrotik_id', $nas->id)->where('sync_status', 'synced')->first();
|
|
if (! $target) throw ValidationException::withMessages(['package_profile_id' => 'Profile paket belum disinkronkan ke NAS yang dipilih.']);
|
|
return $target;
|
|
}
|
|
|
|
private function desiredSnapshot(NasPackageProfile $profile): array
|
|
{
|
|
$scripts = $profile->scripts->keyBy(fn ($policy) => $policy->pivot->event);
|
|
$script = fn (string $event) => $this->scriptTemplate($scripts->get($event), $profile);
|
|
$rate = ($profile->upload_kbps ? ($profile->upload_kbps.'k') : '0').'/'.($profile->download_kbps ? ($profile->download_kbps.'k') : '0');
|
|
$common = ['rate-limit' => $rate, 'session-timeout' => $profile->session_timeout ? $profile->session_timeout.'s' : null,
|
|
'idle-timeout' => $profile->idle_timeout ? $profile->idle_timeout.'s' : null];
|
|
$router = $profile->service_type === 'hotspot'
|
|
? [...$common, 'shared-users' => (string) $profile->shared_users, 'on-login' => $script('on_login'), 'on-logout' => $script('on_logout')]
|
|
: ($profile->service_type === 'ppp'
|
|
? [...$common, 'local-address' => $profile->local_address, 'remote-address' => $profile->remote_address_pool, 'on-up' => $script('on_up'), 'on-down' => $script('on_down')]
|
|
: []);
|
|
return ['routeros' => $router, 'radius' => ['Mikrotik-Rate-Limit' => $rate, 'Session-Timeout' => $profile->session_timeout, 'Idle-Timeout' => $profile->idle_timeout, 'Simultaneous-Use' => $profile->shared_users],
|
|
'network' => $profile->only(['mac_lock_required','ip_pool','subnet_cidr','gateway','dns_servers','vlan_id']),
|
|
'profile' => $profile->only(['profile_code','service_type','usage_mode','validity_start','validity_value','validity_unit','uptime_limit_seconds','data_limit_bytes'])];
|
|
}
|
|
|
|
private function scriptTemplate($policy, NasPackageProfile $profile): ?string
|
|
{
|
|
if (! $policy || ! $policy->pivot->enabled) return null;
|
|
|
|
$versionId = $policy->pivot->nas_script_policy_version_id;
|
|
$version = $versionId
|
|
? $policy->versions->firstWhere('id', $versionId)
|
|
: $policy->versions->sortByDesc('version')->first();
|
|
if (! $version) return null;
|
|
|
|
return strtr($version->script_template, [
|
|
'{{ username }}' => '$user',
|
|
'{{username}}' => '$user',
|
|
'{{ package_code }}' => (string) $profile->profile_code,
|
|
'{{package_code}}' => (string) $profile->profile_code,
|
|
'{{ tenant_code }}' => (string) optional($profile->tenant)->tenant_code,
|
|
'{{tenant_code}}' => (string) optional($profile->tenant)->tenant_code,
|
|
]);
|
|
}
|
|
}
|