Files
website/website/database/seeders/ConfigOptionSeeder.php
Andrew bb04a5e3b9 chore(dedicated): refresh OS catalog to April 2026 currents + fix Proxmox color
OS list updated to reflect each project's actual current and supported
versions as of 2026-04 (verified per project release calendars):

Added:
- AlmaLinux 10 (RHEL 10 rebuild, default) — was missing
- Rocky Linux 10 — was missing
- Ubuntu 26.04 LTS (Resolute Raccoon, released Apr 2026) — was missing
- Debian 13 Trixie (current stable since Aug 2025) — was missing;
  bumped from oldstable Debian 12
- openSUSE Leap 16.0 (Oct 2025) — replaces 15.6 (EOLs Apr 30 2026)
- FreeBSD 15.0 (Dec 2025) and 14.4 (Mar 2026) — was a generic "14"
- Proxmox VE 9.1 (Nov 2025) — added alongside 8.4 (security maint
  through Aug 2026)

Removed:
- Debian 11 (LTS ends Aug 2026, dropped to avoid offering near-EOL)
- openSUSE Leap 15.6 (EOLs in 4 days)

Default OS flipped from AlmaLinux 9 → AlmaLinux 10 (current major).

Proxmox logo color: the Wikimedia CoreUI Proxmox SVG was monochrome
(inheriting black). Added fill="#E57000" so the brand orange is
correct.

22 OS options total across 9 distro families. metaFor() in
OsGroupSelector already handles all families via slug prefix —
no component changes needed for the new versions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 21:56:26 -04:00

900 lines
46 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace Database\Seeders;
use App\Models\Plan;
use App\Models\PlanConfigGroup;
use App\Models\PlanConfigOption;
use App\Models\PlanConfigValue;
use Illuminate\Database\Seeder;
class ConfigOptionSeeder extends Seeder
{
public function run(): void
{
$this->seedBuildYourOwnGroups();
$this->seedPresetGroups();
}
private function seedBuildYourOwnGroups(): void
{
// ─── VPS Builder ────────────────────────────────────────────────
$vpsBuilder = PlanConfigGroup::updateOrCreate(
['name' => 'VPS Builder'],
[
'description' => 'Build your own custom VPS with the exact resources you need.',
'mode' => 'build_your_own',
'service_type' => 'vps',
'is_active' => true,
'sort_order' => 1,
],
);
$this->seedSliderOption($vpsBuilder, 'CPU Cores', 'cpu_cores', 1, 16, 1, 'cores', 0.003, 2.00, 1);
$this->seedSliderOption($vpsBuilder, 'RAM', 'ram_gb', 1, 64, 1, 'GB', 0.0015, 1.00, 2);
$this->seedSliderOption($vpsBuilder, 'SSD Storage', 'disk_gb', 25, 1000, 25, 'GB', 0.0001, 0.05, 3);
// ─── MySQL Builder ──────────────────────────────────────────────
$mysqlBuilder = PlanConfigGroup::updateOrCreate(
['name' => 'MySQL Builder'],
[
'description' => 'Build your own managed MySQL instance.',
'mode' => 'build_your_own',
'service_type' => 'mysql',
'is_active' => true,
'sort_order' => 2,
],
);
$this->seedSliderOption($mysqlBuilder, 'Storage', 'storage_gb', 5, 500, 5, 'GB', 0.0003, 0.20, 1);
$this->seedSliderOption($mysqlBuilder, 'Max Connections', 'max_connections', 50, 1000, 50, 'connections', 0.0001, 0.05, 2);
// ─── Game Server Builder ────────────────────────────────────────
$gameBuilder = PlanConfigGroup::updateOrCreate(
['name' => 'Game Server Builder'],
[
'description' => 'Build your own custom game server.',
'mode' => 'build_your_own',
'service_type' => 'game',
'is_active' => true,
'sort_order' => 3,
],
);
$this->seedSliderOption($gameBuilder, 'RAM', 'ram_gb', 1, 16, 1, 'GB', 0.002, 1.50, 1);
$this->seedSliderOption($gameBuilder, 'Storage', 'disk_gb', 10, 200, 10, 'GB', 0.0001, 0.08, 2);
$this->seedSliderOption($gameBuilder, 'Player Slots', 'player_slots', 10, 200, 10, 'slots', 0.0001, 0.05, 3);
}
private function seedPresetGroups(): void
{
// ─── Server Management (dedicated only) ─────────────────────────
$serverMgmt = PlanConfigGroup::updateOrCreate(
['name' => 'Server Management'],
[
'description' => 'Add managed support to your server.',
'mode' => 'preset',
'service_type' => 'dedicated',
'is_active' => true,
'sort_order' => 10,
],
);
$mgmtOption = $this->seedDropdownOption($serverMgmt, 'Server Management', null, false, 1);
$this->seedValues($mgmtOption, [
['label' => 'None', 'value' => 'none', 'monthly' => 0, 'is_default' => true],
['label' => 'Semi-Managed', 'value' => 'semi_managed', 'monthly' => 25.00],
['label' => 'Fully Managed', 'value' => 'fully_managed', 'monthly' => 60.00],
]);
// Attach to dedicated plans only (VPS plans use the dedicated VPS Managed Support group below)
$dedicatedPlanIds = Plan::query()
->where('service_type', 'dedicated')
->whereIn('status', ['active', 'internal'])
->pluck('id');
$serverMgmt->plans()->sync($dedicatedPlanIds);
// ─── VPS Managed Support ────────────────────────────────────────
// 4-tier radio: Self ($0) / Basic ($29) / Pro ($79) / Pilot ($99, VPS-8+).
// Pilot tier gating (min plan tier 8) is enforced in the frontend.
$vpsManaged = PlanConfigGroup::updateOrCreate(
['name' => 'VPS Managed Support'],
[
'description' => 'Choose how hands-on you want our team to be with your VPS.',
'mode' => 'preset',
'service_type' => 'vps',
'is_active' => true,
'sort_order' => 10,
],
);
$vpsManagedOption = $this->seedRadioOption($vpsManaged, 'Managed Support', false, 1);
$this->seedValues($vpsManagedOption, [
['label' => 'Self-Managed', 'value' => 'self', 'monthly' => 0, 'is_default' => true],
['label' => 'Managed Basic', 'value' => 'basic', 'monthly' => 29.00],
['label' => 'Managed Pro', 'value' => 'pro', 'monthly' => 79.00],
['label' => 'Pilot', 'value' => 'pilot', 'monthly' => 99.00],
]);
$vpsPlanIds = Plan::query()
->where('service_type', 'vps')
->whereIn('status', ['active', 'internal'])
->pluck('id');
$vpsManaged->plans()->syncWithoutDetaching($vpsPlanIds);
// ─── Off-site Backup (VPS) ──────────────────────────────────────
// 5-tier radio: None / Lite (7d, 200GB, $5) / Standard (30d, 500GB, $12)
// / Extended (90d, 1TB, $25) / Vault (1yr, 2TB, $59).
// StorJ-backed, encrypted at rest. Restores included (no egress charge).
$vpsBackup = PlanConfigGroup::updateOrCreate(
['name' => 'Off-site Backup'],
[
'description' => 'Encrypted off-site backups stored on StorJ. Daily snapshots, free restores.',
'mode' => 'preset',
'service_type' => 'vps',
'is_active' => true,
'sort_order' => 12,
],
);
$vpsBackupOption = $this->seedRadioOption($vpsBackup, 'Backup Tier', false, 1);
$this->seedValues($vpsBackupOption, [
['label' => 'None', 'value' => 'none', 'monthly' => 0, 'is_default' => true],
['label' => 'Lite (7-day, 200 GB)', 'value' => 'lite', 'monthly' => 5.00],
['label' => 'Standard (30-day, 500 GB)', 'value' => 'standard', 'monthly' => 12.00],
['label' => 'Extended (90-day, 1 TB)', 'value' => 'extended', 'monthly' => 25.00],
['label' => 'Vault (1-year, 2 TB)', 'value' => 'vault', 'monthly' => 59.00],
]);
$vpsBackup->plans()->syncWithoutDetaching($vpsPlanIds);
// ─── VPS Add-ons ────────────────────────────────────────────────
$vpsAddons = PlanConfigGroup::updateOrCreate(
['name' => 'VPS Add-ons'],
[
'description' => 'Additional options for your VPS.',
'mode' => 'preset',
'service_type' => 'vps',
'is_active' => true,
'sort_order' => 11,
],
);
// IPv4 Addresses (quantity)
$ipv4Option = $this->seedQuantityOption($vpsAddons, 'IPv4 Addresses', 1, 8, 'addresses', 8.00, 1);
// Drop the legacy Windows License option if it exists (BYOL is now communicated
// via marketing copy + OS template selection at checkout).
PlanConfigOption::query()
->where('group_id', $vpsAddons->id)
->where('name', 'Windows License')
->delete();
// Attach to all active VPS plans
$vpsSlugs = ['vps-1', 'vps-2', 'vps-3-custom', 'vps-4', 'vps-8', 'vps-16', 'vps-32', 'stor-500', 'stor-1tb'];
$vpsPlans = Plan::query()->whereIn('slug', $vpsSlugs)->pluck('id');
$vpsAddons->plans()->syncWithoutDetaching($vpsPlans);
// ─── Dedicated RAM - DDR4 ──────────────────────────────────────
$ddr4Ram = PlanConfigGroup::updateOrCreate(
['name' => 'Dedicated RAM - DDR4'],
[
'description' => 'Choose the DDR4 ECC RAM configuration for your dedicated server.',
'mode' => 'preset',
'service_type' => 'dedicated',
'is_active' => true,
'sort_order' => 20,
],
);
$ramOption = $this->seedDropdownOption($ddr4Ram, 'DDR4 ECC RAM', null, true, 1);
$this->seedValues($ramOption, [
['label' => '32 GB', 'value' => '32', 'monthly' => 0, 'is_default' => true],
['label' => '64 GB', 'value' => '64', 'monthly' => 15.00],
['label' => '96 GB', 'value' => '96', 'monthly' => 20.00],
['label' => '128 GB', 'value' => '128', 'monthly' => 28.00],
['label' => '192 GB', 'value' => '192', 'monthly' => 40.00],
['label' => '256 GB', 'value' => '256', 'monthly' => 55.00],
['label' => '384 GB', 'value' => '384', 'monthly' => 80.00],
['label' => '512 GB', 'value' => '512', 'monthly' => 120.00],
]);
$ramPlanSlugs = ['dell-r440', 'dell-r640', 'dell-r540', 'dell-r740'];
$ramPlans = Plan::query()->whereIn('slug', $ramPlanSlugs)->pluck('id');
$ddr4Ram->plans()->syncWithoutDetaching($ramPlans);
// ─── Dedicated Network ──────────────────────────────────────────
$dedicatedNetwork = PlanConfigGroup::updateOrCreate(
['name' => 'Dedicated Network'],
[
'description' => 'Network configuration for your dedicated server.',
'mode' => 'preset',
'service_type' => 'dedicated',
'is_active' => true,
'sort_order' => 21,
],
);
// Network Port Speed (radio, required)
$portSpeedOption = $this->seedRadioOption($dedicatedNetwork, 'Network Port Speed', true, 1);
$this->seedValues($portSpeedOption, [
['label' => 'Unmetered 100Mbit', 'value' => '100mbit_unmetered', 'monthly' => 0, 'is_default' => true],
['label' => 'Metered 1Gbit', 'value' => '1gbit_metered', 'monthly' => 0],
['label' => 'Unmetered 2Gbit', 'value' => '2gbit_unmetered', 'monthly' => 150.00],
['label' => 'Unmetered 5Gbit', 'value' => '5gbit_unmetered', 'monthly' => 325.00],
['label' => 'Unmetered 10Gbit', 'value' => '10gbit_unmetered', 'monthly' => 650.00],
]);
// Public Bandwidth (dropdown)
$publicBwOption = $this->seedDropdownOption($dedicatedNetwork, 'Public Bandwidth', null, false, 2);
$this->seedValues($publicBwOption, [
['label' => '20TB', 'value' => '20tb', 'monthly' => 0, 'is_default' => true],
['label' => '50TB', 'value' => '50tb', 'monthly' => 45.00],
['label' => '100TB', 'value' => '100tb', 'monthly' => 100.00],
['label' => 'Unmetered 1Gbps', 'value' => 'unmetered_1gbps', 'monthly' => 150.00],
]);
// Private Bandwidth (radio)
$privateBwOption = $this->seedRadioOption($dedicatedNetwork, 'Private Bandwidth', false, 3);
$this->seedValues($privateBwOption, [
['label' => '1Gbit', 'value' => '1gbit', 'monthly' => 0, 'is_default' => true],
['label' => '10Gbit', 'value' => '10gbit', 'monthly' => 85.00],
]);
$allDedicatedPlans = Plan::query()
->where('service_type', 'dedicated')
->whereIn('status', ['active', 'internal'])
->pluck('id');
$dedicatedNetwork->plans()->syncWithoutDetaching($allDedicatedPlans);
// ─── M.2 NVMe ──────────────────────────────────────────────────
$nvme = PlanConfigGroup::updateOrCreate(
['name' => 'M.2 NVMe'],
[
'description' => 'Add M.2 NVMe storage to your dedicated server.',
'mode' => 'preset',
'service_type' => 'dedicated',
'is_active' => true,
'sort_order' => 23,
],
);
$nvmeOption = $this->seedDropdownOption($nvme, 'M.2 NVMe', null, false, 1);
$this->seedValues($nvmeOption, [
['label' => 'None', 'value' => 'none', 'monthly' => 0, 'is_default' => true],
['label' => '2x 500GB', 'value' => '2x500gb', 'monthly' => 18.00],
['label' => '2x 1TB', 'value' => '2x1tb', 'monthly' => 30.00],
['label' => '2x 2TB', 'value' => '2x2tb', 'monthly' => 75.00],
]);
$nvme->plans()->syncWithoutDetaching($ramPlans);
// ─── Veeam Enterprise Plus Options ─────────────────────────────
$veeamEntPlus = PlanConfigGroup::updateOrCreate(
['name' => 'Veeam Enterprise Plus Options'],
[
'description' => 'Configure your Veeam Enterprise Plus backup solution.',
'mode' => 'preset',
'service_type' => 'backups',
'is_active' => true,
'sort_order' => 30,
],
);
$this->seedQuantityOption($veeamEntPlus, 'Virtual Machines', 0, 100, 'VMs', 15.00, 1);
$this->seedQuantityOption($veeamEntPlus, 'Server Agents', 0, 50, 'agents', 15.00, 2);
$this->seedQuantityOption($veeamEntPlus, 'Workstation Agents', 0, 100, 'agents', 6.00, 3);
$this->seedQuantityOption($veeamEntPlus, 'NAS (per 500GB)', 0, 50, 'units', 14.00, 4);
$this->seedQuantityOption($veeamEntPlus, 'Office 365 Users', 0, 500, 'users', 3.00, 5);
$veeamEntPlusPlan = Plan::query()->where('slug', 'veeam-enterprise-plus')->pluck('id');
$veeamEntPlus->plans()->syncWithoutDetaching($veeamEntPlusPlan);
// ─── Veeam Standard Options ────────────────────────────────────
$veeamStandard = PlanConfigGroup::updateOrCreate(
['name' => 'Veeam Standard Options'],
[
'description' => 'Configure your Veeam Standard backup solution.',
'mode' => 'preset',
'service_type' => 'backups',
'is_active' => true,
'sort_order' => 31,
],
);
$this->seedQuantityOption($veeamStandard, 'Virtual Machines', 0, 100, 'VMs', 7.00, 1);
$this->seedQuantityOption($veeamStandard, 'Server Agents', 0, 50, 'agents', 15.00, 2);
$this->seedQuantityOption($veeamStandard, 'Workstation Agents', 0, 100, 'agents', 6.00, 3);
$this->seedQuantityOption($veeamStandard, 'NAS (per 500GB)', 0, 50, 'units', 14.00, 4);
$this->seedQuantityOption($veeamStandard, 'Office 365 Users', 0, 500, 'users', 3.00, 5);
$veeamStandardPlan = Plan::query()->where('slug', 'veeam-standard')->pluck('id');
$veeamStandard->plans()->syncWithoutDetaching($veeamStandardPlan);
// ─── Veeam Cloud Connect Options ───────────────────────────────
$veeamCloudConnect = PlanConfigGroup::updateOrCreate(
['name' => 'Veeam Cloud Connect Options'],
[
'description' => 'Configure your Veeam Cloud Connect backup and replication solution.',
'mode' => 'preset',
'service_type' => 'backups',
'is_active' => true,
'sort_order' => 32,
],
);
$this->seedQuantityOption($veeamCloudConnect, 'Virtual Machines', 0, 100, 'VMs', 15.00, 1);
$this->seedQuantityOption($veeamCloudConnect, 'Server Agents', 0, 50, 'agents', 15.00, 2);
$this->seedQuantityOption($veeamCloudConnect, 'Workstation Agents', 0, 100, 'agents', 6.00, 3);
$this->seedQuantityOption($veeamCloudConnect, 'NAS (per 500GB)', 0, 50, 'units', 14.00, 4);
$this->seedQuantityOption($veeamCloudConnect, 'Office 365 Users', 0, 500, 'users', 3.00, 5);
$this->seedQuantityOption($veeamCloudConnect, 'Capacity Storage', 1, 100, 'TB', 4.00, 6, required: true);
$this->seedQuantityOption($veeamCloudConnect, 'Replicated VMs', 0, 50, 'VMs', 30.00, 7);
$supportOption = $this->seedDropdownOption($veeamCloudConnect, 'Support Level', null, true, 8);
$this->seedValues($supportOption, [
['label' => 'Basic', 'value' => 'basic', 'monthly' => 30.00, 'is_default' => true],
['label' => 'Enhanced', 'value' => 'enhanced', 'monthly' => 60.00],
['label' => 'Enhanced Plus', 'value' => 'enhanced_plus', 'monthly' => 200.00],
]);
$veeamCloudConnectPlan = Plan::query()->where('slug', 'veeam-cloud-connect')->pluck('id');
$veeamCloudConnect->plans()->syncWithoutDetaching($veeamCloudConnectPlan);
// ─── Dedicated 14th Gen — CPU Upgrade ───────────────────────────
$gen14CpuUpgrade = PlanConfigGroup::updateOrCreate(
['name' => 'Dedicated 14th Gen — CPU Upgrade'],
[
'description' => 'Upgrade from the 2× Xeon Gold 6230 baseline to a higher core count or clock speed.',
'mode' => 'preset',
'service_type' => 'dedicated',
'is_active' => true,
'sort_order' => 40,
],
);
// Platinum 8280 sits in this group but is filtered out at the route
// level for chassis where features.cpu_premium === false (R440/R540).
$gen14CpuOption = $this->seedRadioOption($gen14CpuUpgrade, 'CPU Upgrade', false, 1);
$this->seedValues($gen14CpuOption, [
['label' => 'Baseline: 2× Xeon Gold 6230 (40C / 2.10 GHz)', 'value' => 'gold-6230-baseline', 'monthly' => 0, 'is_default' => true],
// High-clock SKU for per-core licensed workloads (Microsoft SQL,
// Oracle, single-threaded compute). Fewer cores, higher base clock.
['label' => 'High-clock: 2× Xeon Gold 6244 (16C / 3.60 GHz)', 'value' => 'gold-6244', 'monthly' => 25.00],
['label' => 'Upgrade: 2× Xeon Gold 6248 (40C / 2.50 GHz)', 'value' => 'gold-6248', 'monthly' => 35.00],
// Cascade Lake Refresh of the 6230 — same gen, more cores at the
// same clock. Sweet-spot mid-tier between baseline and 6248R.
['label' => 'Mid-tier: 2× Xeon Gold 6230R (52C / 2.10 GHz)', 'value' => 'gold-6230r', 'monthly' => 50.00],
['label' => 'Upgrade: 2× Xeon Gold 6248R (48C / 3.00 GHz)', 'value' => 'gold-6248r', 'monthly' => 75.00],
['label' => 'Upgrade: 2× Xeon Gold 6258R (56C / 2.70 GHz)', 'value' => 'gold-6258r', 'monthly' => 145.00],
['label' => 'Premium: 2× Xeon Platinum 8280 (56C / 2.70 GHz)', 'value' => 'platinum-8280', 'monthly' => 285.00],
]);
$gen14CpuPlanSlugs = ['r440-4lff', 'r540-8lff', 'r640-8sff', 'r740-16sff', 'r740xd-24sff', 'r740xd-12lff', 'r640-10nvme', 'r740xd-24nvme'];
$gen14CpuPlans = Plan::query()->whereIn('slug', $gen14CpuPlanSlugs)->pluck('id');
$gen14CpuUpgrade->plans()->syncWithoutDetaching($gen14CpuPlans);
// ─── Dedicated 14th Gen — CPU Upgrade (R740xd) ──────────────────
$gen14CpuUpgradeXd = PlanConfigGroup::updateOrCreate(
['name' => 'Dedicated 14th Gen — CPU Upgrade (R740xd)'],
[
'description' => 'R740xd-specific CPU options. Higher TDP support than non-xd variants.',
'mode' => 'preset',
'service_type' => 'dedicated',
'is_active' => true,
'sort_order' => 41,
],
);
$gen14CpuXdOption = $this->seedRadioOption($gen14CpuUpgradeXd, 'CPU Upgrade', false, 1);
$this->seedValues($gen14CpuXdOption, [
['label' => 'Baseline: 2× Xeon Gold 6230 (40C / 2.10 GHz)', 'value' => 'gold-6230-baseline', 'monthly' => 0, 'is_default' => true],
['label' => 'Upgrade: 2× Xeon Gold 6248R (48C / 3.00 GHz)', 'value' => 'gold-6248r', 'monthly' => 75.00],
['label' => 'Upgrade: 2× Xeon Gold 6258R (56C / 2.70 GHz)', 'value' => 'gold-6258r', 'monthly' => 145.00],
['label' => 'Upgrade: 2× Xeon Platinum 8280 (56C / 2.70 GHz)', 'value' => 'platinum-8280', 'monthly' => 285.00],
]);
// R740xd-specific group is now obsolete — all chassis use the
// standard CPU group above and the route-level cpu_premium filter
// controls Platinum visibility. Detach all plans; group + values
// remain seeded for backwards-compat with any historical references.
$gen14CpuUpgradeXd->plans()->sync([]);
// ─── Dedicated 14th Gen — RAM Upgrade ───────────────────────────
$gen14Ram = PlanConfigGroup::updateOrCreate(
['name' => 'Dedicated 14th Gen — RAM Upgrade'],
[
'description' => 'DDR4-2400 ECC. RDIMM up to 128 GB; LRDIMM at 256 GB and above.',
'mode' => 'preset',
'service_type' => 'dedicated',
'is_active' => true,
'sort_order' => 42,
],
);
$gen14RamOption = $this->seedRadioOption($gen14Ram, 'RAM Upgrade', false, 1);
$this->seedValues($gen14RamOption, [
// Per-stick math at $65/64GB-equivalent, set to recoup 12-month
// hardware cost on $390/stick DDR4-2400 LRDIMM at 2026 Q2
// shortage prices + ~30% margin. Sits ~17% under Hetzner's
// $78/module pricing — small-provider positioning.
['label' => '32 GB DDR4-2400 ECC RDIMM (baseline)', 'value' => '32', 'monthly' => 0, 'is_default' => true],
['label' => '64 GB DDR4-2400 ECC RDIMM', 'value' => '64', 'monthly' => 65.00],
['label' => '128 GB DDR4-2400 ECC RDIMM', 'value' => '128', 'monthly' => 195.00],
['label' => '256 GB DDR4-2400 ECC LRDIMM', 'value' => '256', 'monthly' => 260.00],
['label' => '512 GB DDR4-2400 ECC LRDIMM', 'value' => '512', 'monthly' => 520.00],
['label' => '1 TB DDR4-2400 ECC LRDIMM', 'value' => '1024', 'monthly' => 1040.00],
// 1.5 TB hides for chassis with features.max_ram_gb < 1536.
['label' => '1.5 TB DDR4-2400 ECC LRDIMM', 'value' => '1536', 'monthly' => 1560.00],
]);
$gen14AllPlanSlugs = ['r440-4lff', 'r540-8lff', 'r640-8sff', 'r740-16sff', 'r740xd-24sff', 'r740xd-12lff', 'r640-10nvme', 'r740xd-24nvme'];
$gen14AllPlans = Plan::query()->whereIn('slug', $gen14AllPlanSlugs)->pluck('id');
$gen14Ram->plans()->syncWithoutDetaching($gen14AllPlans);
// ─── Dedicated 14th Gen — Operating System ──────────────────────
$gen14Os = PlanConfigGroup::updateOrCreate(
['name' => 'Dedicated 14th Gen — Operating System'],
[
'description' => 'Choose the OS image installed at provisioning time. Windows requires your own license (BYOL).',
'mode' => 'preset',
'service_type' => 'dedicated',
'is_active' => true,
'sort_order' => 43,
],
);
$gen14OsOption = $this->seedRadioOption($gen14Os, 'Operating System', false, 1);
// Order: enterprise Linux first (Alma / Rocky), then mainstream LTS
// (Ubuntu / Debian), then leading-edge / specialty (Fedora / openSUSE
// / FreeBSD / Proxmox), then Windows BYOL, with "No OS" last so the
// picker reads as a positive distro choice.
//
// Only versions in active or security maintenance as of 2026-04 are
// listed. Verified against each project's release calendar:
// Alma 10 (current, RHEL 10 rebuild), 9 (active), 8 (security only)
// Rocky 10 (released Jul 2025), 9, 8 (security only)
// Ubuntu 26.04 LTS (released Apr 2026), 24.04 LTS, 22.04 LTS
// Debian 13 trixie (current stable since Aug 2025), 12 (oldstable);
// 11 dropped — LTS ends Aug 2026
// Fedora 44 (released Apr 2026), 43 (released Oct 2025); 41 EOL
// openSUSE Leap 16.0 (Oct 2025); 15.6 dropped — EOL Apr 30 2026
// FreeBSD 15.0 (Dec 2025), 14.4 (Mar 2026)
// Proxmox VE 9.1 (Nov 2025), 8.4 (security through Aug 2026)
// Windows Server 2025 (Nov 2024), 2022, 2019
$this->seedValues($gen14OsOption, [
['label' => 'AlmaLinux 10', 'value' => 'alma10', 'monthly' => 0, 'is_default' => true],
['label' => 'AlmaLinux 9', 'value' => 'alma9', 'monthly' => 0],
['label' => 'AlmaLinux 8', 'value' => 'alma8', 'monthly' => 0],
['label' => 'Rocky Linux 10', 'value' => 'rocky10', 'monthly' => 0],
['label' => 'Rocky Linux 9', 'value' => 'rocky9', 'monthly' => 0],
['label' => 'Rocky Linux 8', 'value' => 'rocky8', 'monthly' => 0],
['label' => 'Ubuntu 26.04 LTS', 'value' => 'ubuntu26', 'monthly' => 0],
['label' => 'Ubuntu 24.04 LTS', 'value' => 'ubuntu24', 'monthly' => 0],
['label' => 'Ubuntu 22.04 LTS', 'value' => 'ubuntu22', 'monthly' => 0],
['label' => 'Debian 13 (Trixie)', 'value' => 'debian13', 'monthly' => 0],
['label' => 'Debian 12 (Bookworm)', 'value' => 'debian12', 'monthly' => 0],
['label' => 'Fedora Server 44', 'value' => 'fedora44', 'monthly' => 0],
['label' => 'Fedora Server 43', 'value' => 'fedora43', 'monthly' => 0],
['label' => 'openSUSE Leap 16.0', 'value' => 'opensuse-leap-160', 'monthly' => 0],
['label' => 'FreeBSD 15.0', 'value' => 'freebsd15', 'monthly' => 0],
['label' => 'FreeBSD 14.4', 'value' => 'freebsd14', 'monthly' => 0],
['label' => 'Proxmox VE 9.1', 'value' => 'proxmox9', 'monthly' => 0],
['label' => 'Proxmox VE 8.4', 'value' => 'proxmox8', 'monthly' => 0],
['label' => 'Windows Server 2025 (BYOL)', 'value' => 'windows-2025-byol', 'monthly' => 0],
['label' => 'Windows Server 2022 (BYOL)', 'value' => 'windows-2022-byol', 'monthly' => 0],
['label' => 'Windows Server 2019 (BYOL)', 'value' => 'windows-2019-byol', 'monthly' => 0],
['label' => 'No OS (BYO image / custom PXE)', 'value' => 'none', 'monthly' => 0],
]);
$gen14Os->plans()->syncWithoutDetaching($gen14AllPlans);
// ─── Dedicated 14th Gen — Bandwidth ─────────────────────────────
$gen14Bandwidth = PlanConfigGroup::updateOrCreate(
['name' => 'Dedicated 14th Gen — Bandwidth'],
[
'description' => 'Network port speed and bandwidth allocation. 10 Gbps tiers include traffic packages with overage protection.',
'mode' => 'preset',
'service_type' => 'dedicated',
'is_active' => true,
'sort_order' => 44,
],
);
$gen14BandwidthOption = $this->seedRadioOption($gen14Bandwidth, 'Bandwidth', false, 1);
$this->seedValues($gen14BandwidthOption, [
['label' => '1 Gbps unmetered (baseline)', 'value' => '1g-unmetered', 'monthly' => 0, 'is_default' => true],
['label' => '10 Gbps + 10 TB included', 'value' => '10g-10tb', 'monthly' => 45.00],
['label' => '10 Gbps + 50 TB included', 'value' => '10g-50tb', 'monthly' => 95.00],
['label' => '10 Gbps + 100 TB included', 'value' => '10g-100tb', 'monthly' => 175.00],
['label' => '10 Gbps + 500 TB included', 'value' => '10g-500tb', 'monthly' => 345.00],
]);
$gen14Bandwidth->plans()->syncWithoutDetaching($gen14AllPlans);
// ─── Dedicated 14th Gen — IPv4 Block ────────────────────────────
$gen14Ipv4 = PlanConfigGroup::updateOrCreate(
['name' => 'Dedicated 14th Gen — IPv4 Block'],
[
'description' => 'Additional IPv4 addresses beyond the 1 included with every server. Blocks of /28 or larger require ARIN justification — we provide the template; you fill in your use case before the IPs are allocated.',
'mode' => 'preset',
'service_type' => 'dedicated',
'is_active' => true,
'sort_order' => 45,
],
);
$gen14Ipv4Option = $this->seedRadioOption($gen14Ipv4, 'IPv4 Block', false, 1);
// ARIN justification policy applies to anything above /29; the group
// description (above) tells customers about it, so we don't repeat
// it on every tier label.
$this->seedValues($gen14Ipv4Option, [
['label' => '1 included (no extra)', 'value' => '1', 'monthly' => 0, 'is_default' => true],
['label' => '/29 (5 usable)', 'value' => '5', 'monthly' => 12.00],
['label' => '/28 (13 usable) — justification required', 'value' => '13', 'monthly' => 36.00],
['label' => '/27 (29 usable) — justification required', 'value' => '29', 'monthly' => 80.00],
['label' => '/26 (61 usable) — justification required', 'value' => '61', 'monthly' => 145.00],
['label' => '/25 (125 usable) — justification required', 'value' => '125', 'monthly' => 275.00],
['label' => '/24 (253 usable) — justification required', 'value' => '253', 'monthly' => 499.00],
]);
$gen14Ipv4->plans()->syncWithoutDetaching($gen14AllPlans);
// ─── Dedicated 14th Gen — Private Networking ────────────────────
// Separate from public bandwidth. Intra-rack private link with no
// bandwidth metering — traffic never traverses the public internet,
// so only the link speed itself is billable. Flat monthly, no setup.
$gen14PrivateNet = PlanConfigGroup::updateOrCreate(
['name' => 'Dedicated 14th Gen — Private Networking'],
[
'description' => 'Optional dedicated private network link for server-to-server traffic. Unmetered (traffic stays on our internal switch fabric).',
'mode' => 'preset',
'service_type' => 'dedicated',
'is_active' => true,
'sort_order' => 46,
],
);
$gen14PrivateNetOption = $this->seedRadioOption($gen14PrivateNet, 'Private Network', false, 1);
$this->seedValues($gen14PrivateNetOption, [
['label' => '1 Gbit private (included)', 'value' => '1g', 'monthly' => 0, 'is_default' => true],
['label' => '10 Gbit private', 'value' => '10g', 'monthly' => 25.00],
['label' => '40 Gbit private', 'value' => '40g', 'monthly' => 75.00],
]);
$gen14PrivateNet->plans()->syncWithoutDetaching($gen14AllPlans);
// ─── Dedicated 14th Gen — PCIe NVMe Add-in ──────────────────────
// Separate category from bay-attached drives — uses empty PCIe slots
// via Sabrent EC-PCIE (single-drive) or EC-P4BF (4-drive bifurcation
// x16) adapter cards. Customers can stack this on top of bay drives
// for fast scratch / cache / metadata storage.
// Pricing: 12-month payback × 1.5x markup on (Sabrent retail drive
// cost + adapter cost). Drive prices scraped from sabrent.com on
// 2026-04-26: Rocket 4 Plus 1TB \$249.99 / 2TB \$289.99 / 4TB \$879.99
// / 8TB \$3,699.99; Rocket 5 Gen5 1TB \$400 / 2TB \$499.99 / 4TB \$1,400.
// Adapters: EC-PCIE \$17.98, EC-P4BF \$99.99.
$gen14PciNvme = PlanConfigGroup::updateOrCreate(
['name' => 'Dedicated 14th Gen — PCIe NVMe Add-in'],
[
'description' => 'Add high-performance M.2 NVMe storage on top of your bay drives. Pick a single drive for fast scratch space or a 4-drive bundle for more capacity.',
'mode' => 'preset',
'service_type' => 'dedicated',
'is_active' => true,
'sort_order' => 47,
],
);
$gen14PciNvmeOption = $this->seedRadioOption($gen14PciNvme, 'PCIe NVMe Add-in', false, 1);
$this->seedValues($gen14PciNvmeOption, [
['label' => 'No PCIe NVMe add-in', 'value' => 'none', 'monthly' => 0, 'is_default' => true],
// Single-drive (Gen4)
['label' => '1× 1 TB M.2 NVMe (Gen4)', 'value' => '1x1tb-r4p-pcie', 'monthly' => 35.00],
['label' => '1× 2 TB M.2 NVMe (Gen4)', 'value' => '1x2tb-r4p-pcie', 'monthly' => 40.00],
['label' => '1× 4 TB M.2 NVMe (Gen4)', 'value' => '1x4tb-r4p-pcie', 'monthly' => 115.00],
['label' => '1× 8 TB M.2 NVMe (Gen4)', 'value' => '1x8tb-r4p-pcie', 'monthly' => 465.00],
// Single-drive (Gen5)
['label' => '1× 2 TB M.2 NVMe (Gen5)', 'value' => '1x2tb-r5-pcie', 'monthly' => 65.00],
['label' => '1× 4 TB M.2 NVMe (Gen5)', 'value' => '1x4tb-r5-pcie', 'monthly' => 180.00],
// 4-drive bundles (Gen4)
['label' => '4× 1 TB M.2 NVMe (Gen4 bundle)', 'value' => '4x1tb-r4p-p4bf', 'monthly' => 140.00],
['label' => '4× 2 TB M.2 NVMe (Gen4 bundle)', 'value' => '4x2tb-r4p-p4bf', 'monthly' => 160.00],
['label' => '4× 4 TB M.2 NVMe (Gen4 bundle)', 'value' => '4x4tb-r4p-p4bf', 'monthly' => 455.00],
['label' => '4× 8 TB M.2 NVMe (Gen4 bundle)', 'value' => '4x8tb-r4p-p4bf', 'monthly' => 1865.00],
]);
// Attach to LFF + SFF chassis (have free PCIe slots). Skip on the
// U.2 NVMe-native chassis since they don't have PCIe slots free
// for add-in cards (lanes are routed to the U.2 backplane).
$gen14PciNvmeSlugs = ['r440-4lff', 'r540-8lff', 'r640-8sff', 'r740-16sff', 'r740xd-24sff', 'r740xd-12lff'];
$gen14PciNvmePlans = Plan::query()->whereIn('slug', $gen14PciNvmeSlugs)->pluck('id');
$gen14PciNvme->plans()->syncWithoutDetaching($gen14PciNvmePlans);
// ─── Dedicated 14th Gen — LFF Drive Bays ────────────────────────
// New shape (Option B): two options per group — Drive Selection (radio,
// PER-DRIVE cost) + Drive Quantity (stepper). Total = drive × quantity,
// computed in the frontend. Per-drive economics keep the catalog short
// and let us add a new size or interface (SAS) without exploding into
// 4-N more flat-radio rows. SAS HDD/SSD variants offer a 12 Gb/s
// dual-port path for customers running clustered storage / HA pairs.
$gen14Lff = PlanConfigGroup::updateOrCreate(
['name' => 'Dedicated 14th Gen — LFF Drive Bays'],
[
'description' => 'Pick a drive type, then choose how many to populate. SATA covers the price-sensitive cases; SAS variants add 12 Gb/s + dual-port for clustered storage workloads.',
'mode' => 'preset',
'service_type' => 'dedicated',
'is_active' => true,
'sort_order' => 50,
],
);
$gen14LffDriveOption = $this->seedRadioOption($gen14Lff, 'Drive Selection', false, 1);
$this->seedValues($gen14LffDriveOption, [
['label' => 'No drives', 'value' => 'none', 'monthly' => 0, 'is_default' => true],
['label' => '4 TB SATA HDD', 'value' => 'sata-hdd-4tb', 'monthly' => 12.00],
['label' => '8 TB SATA HDD', 'value' => 'sata-hdd-8tb', 'monthly' => 20.00],
['label' => '12 TB Enterprise SATA HDD', 'value' => 'sata-hdd-12tb', 'monthly' => 45.00],
['label' => '20 TB Enterprise SATA HDD', 'value' => 'sata-hdd-20tb', 'monthly' => 55.00],
['label' => '24 TB Enterprise SATA HDD', 'value' => 'sata-hdd-24tb', 'monthly' => 75.00],
['label' => '12 TB Enterprise SAS HDD', 'value' => 'sas-hdd-12tb', 'monthly' => 50.00],
['label' => '16 TB Enterprise SAS HDD', 'value' => 'sas-hdd-16tb', 'monthly' => 55.00],
['label' => '480 GB SATA SSD', 'value' => 'sata-ssd-480gb-lff', 'monthly' => 10.00],
['label' => '1.92 TB SATA SSD', 'value' => 'sata-ssd-1920gb-lff', 'monthly' => 18.00],
['label' => '3.84 TB SATA SSD', 'value' => 'sata-ssd-3840gb-lff', 'monthly' => 45.00],
['label' => '7.68 TB SATA SSD', 'value' => 'sata-ssd-7680gb-lff', 'monthly' => 100.00],
['label' => '1.92 TB SAS SSD', 'value' => 'sas-ssd-1920gb-lff', 'monthly' => 40.00],
['label' => '3.84 TB SAS SSD', 'value' => 'sas-ssd-3840gb-lff', 'monthly' => 80.00],
['label' => '7.68 TB SAS SSD', 'value' => 'sas-ssd-7680gb-lff', 'monthly' => 200.00],
]);
// monthly_price = 0 on the stepper itself — actual cost is computed in
// the frontend as drive_selection.monthly_price × quantity. The route
// filter clamps max_qty down to chassis bay_count at request time.
$this->seedQuantityOption($gen14Lff, 'Drive Quantity', 0, 12, 'drives', 0.00, 2);
$gen14LffPlanSlugs = ['r440-4lff', 'r540-8lff', 'r740xd-12lff'];
$gen14LffPlans = Plan::query()->whereIn('slug', $gen14LffPlanSlugs)->pluck('id');
$gen14Lff->plans()->syncWithoutDetaching($gen14LffPlans);
// ─── Dedicated 14th Gen — SFF Drive Bays ────────────────────────
$gen14Sff = PlanConfigGroup::updateOrCreate(
['name' => 'Dedicated 14th Gen — SFF Drive Bays'],
[
'description' => 'Pick a 2.5" drive type, then set quantity. SAS SSD variants add 12 Gb/s + dual-port for HA / clustered storage.',
'mode' => 'preset',
'service_type' => 'dedicated',
'is_active' => true,
'sort_order' => 51,
],
);
$gen14SffDriveOption = $this->seedRadioOption($gen14Sff, 'Drive Selection', false, 1);
$this->seedValues($gen14SffDriveOption, [
['label' => 'No drives', 'value' => 'none', 'monthly' => 0, 'is_default' => true],
['label' => '480 GB SATA SSD', 'value' => 'sata-ssd-480gb', 'monthly' => 10.00],
['label' => '1.92 TB SATA SSD', 'value' => 'sata-ssd-1920gb', 'monthly' => 18.00],
['label' => '3.84 TB SATA SSD', 'value' => 'sata-ssd-3840gb', 'monthly' => 45.00],
['label' => '7.68 TB SATA SSD', 'value' => 'sata-ssd-7680gb', 'monthly' => 100.00],
['label' => '1.92 TB SAS SSD', 'value' => 'sas-ssd-1920gb', 'monthly' => 40.00],
['label' => '3.84 TB SAS SSD', 'value' => 'sas-ssd-3840gb', 'monthly' => 80.00],
['label' => '7.68 TB SAS SSD', 'value' => 'sas-ssd-7680gb', 'monthly' => 200.00],
]);
$this->seedQuantityOption($gen14Sff, 'Drive Quantity', 0, 24, 'drives', 0.00, 2);
$gen14SffPlanSlugs = ['r640-8sff', 'r740-16sff', 'r740xd-24sff'];
$gen14SffPlans = Plan::query()->whereIn('slug', $gen14SffPlanSlugs)->pluck('id');
$gen14Sff->plans()->syncWithoutDetaching($gen14SffPlans);
// ─── Dedicated 14th Gen — NVMe Drive Bays ───────────────────────
$gen14Nvme = PlanConfigGroup::updateOrCreate(
['name' => 'Dedicated 14th Gen — NVMe Drive Bays'],
[
'description' => 'Pick a U.2 NVMe size, then set quantity. Bays direct-attach to CPU PCIe lanes — no RAID controller in the data path; software RAID (ZFS / mdraid / btrfs) only.',
'mode' => 'preset',
'service_type' => 'dedicated',
'is_active' => true,
'sort_order' => 52,
],
);
$gen14NvmeDriveOption = $this->seedRadioOption($gen14Nvme, 'Drive Selection', false, 1);
$this->seedValues($gen14NvmeDriveOption, [
['label' => 'No drives', 'value' => 'none', 'monthly' => 0, 'is_default' => true],
['label' => '1.92 TB U.2 NVMe', 'value' => 'u2-nvme-1920gb', 'monthly' => 30.00],
['label' => '3.84 TB U.2 NVMe', 'value' => 'u2-nvme-3840gb', 'monthly' => 70.00],
['label' => '7.68 TB U.2 NVMe', 'value' => 'u2-nvme-7680gb', 'monthly' => 150.00],
]);
$this->seedQuantityOption($gen14Nvme, 'Drive Quantity', 0, 24, 'drives', 0.00, 2);
$gen14NvmePlanSlugs = ['r640-10nvme', 'r740xd-24nvme'];
$gen14NvmePlans = Plan::query()->whereIn('slug', $gen14NvmePlanSlugs)->pluck('id');
$gen14Nvme->plans()->syncWithoutDetaching($gen14NvmePlans);
}
/**
* Create/update a slider option for BYO groups.
*/
private function seedSliderOption(
PlanConfigGroup $group,
string $name,
string $provisioningKey,
int $min,
int $max,
int $step,
string $unit,
float $hourly,
float $monthly,
int $sortOrder,
): PlanConfigOption {
return PlanConfigOption::updateOrCreate(
['group_id' => $group->id, 'name' => $name],
[
'type' => 'slider',
'provisioning_key' => $provisioningKey,
'required' => true,
'is_active' => true,
'min_qty' => $min,
'max_qty' => $max,
'step' => $step,
'unit_label' => $unit,
'hourly_price' => $hourly,
'monthly_price' => $monthly,
'quarterly_price' => $this->quarterlyPrice($monthly),
'semi_annual_price' => $this->semiAnnualPrice($monthly),
'annual_price' => $this->annualPrice($monthly),
'sort_order' => $sortOrder,
],
);
}
/**
* Create/update a dropdown option.
*/
private function seedDropdownOption(
PlanConfigGroup $group,
string $name,
?string $provisioningKey,
bool $required,
int $sortOrder,
): PlanConfigOption {
return PlanConfigOption::updateOrCreate(
['group_id' => $group->id, 'name' => $name],
[
'type' => 'dropdown',
'provisioning_key' => $provisioningKey,
'required' => $required,
'is_active' => true,
'sort_order' => $sortOrder,
],
);
}
/**
* Create/update a radio option.
*/
private function seedRadioOption(
PlanConfigGroup $group,
string $name,
bool $required,
int $sortOrder,
): PlanConfigOption {
return PlanConfigOption::updateOrCreate(
['group_id' => $group->id, 'name' => $name],
[
'type' => 'radio',
'provisioning_key' => null,
'required' => $required,
'is_active' => true,
'sort_order' => $sortOrder,
],
);
}
/**
* Create/update a checkbox option.
*/
private function seedCheckboxOption(
PlanConfigGroup $group,
string $name,
int $sortOrder,
): PlanConfigOption {
return PlanConfigOption::updateOrCreate(
['group_id' => $group->id, 'name' => $name],
[
'type' => 'checkbox',
'provisioning_key' => null,
'required' => false,
'is_active' => true,
'sort_order' => $sortOrder,
],
);
}
/**
* Create/update a quantity option.
*/
private function seedQuantityOption(
PlanConfigGroup $group,
string $name,
int $min,
int $max,
string $unit,
float $monthly,
int $sortOrder,
bool $required = false,
): PlanConfigOption {
return PlanConfigOption::updateOrCreate(
['group_id' => $group->id, 'name' => $name],
[
'type' => 'quantity',
'provisioning_key' => null,
'required' => $required,
'is_active' => true,
'min_qty' => $min,
'max_qty' => $max,
'step' => 1,
'unit_label' => $unit,
'monthly_price' => $monthly,
'quarterly_price' => $this->quarterlyPrice($monthly),
'semi_annual_price' => $this->semiAnnualPrice($monthly),
'annual_price' => $this->annualPrice($monthly),
'sort_order' => $sortOrder,
],
);
}
/**
* Seed values for a dropdown/radio/checkbox option.
*
* @param array<int, array{label: string, value: string, monthly: float, is_default?: bool}> $values
*/
private function seedValues(PlanConfigOption $option, array $values): void
{
foreach ($values as $index => $data) {
$monthly = $data['monthly'];
PlanConfigValue::updateOrCreate(
['option_id' => $option->id, 'label' => $data['label']],
[
'value' => $data['value'],
'hourly_price' => 0,
'monthly_price' => $monthly,
'quarterly_price' => $this->quarterlyPrice($monthly),
'semi_annual_price' => $this->semiAnnualPrice($monthly),
'annual_price' => $this->annualPrice($monthly),
'is_default' => $data['is_default'] ?? false,
'sort_order' => $index,
],
);
}
}
/**
* quarterly = monthly * 3 * 0.95
*/
private function quarterlyPrice(float $monthly): float
{
return round($monthly * 3 * 0.95, 2);
}
/**
* semi_annual = monthly * 6 * 0.90
*/
private function semiAnnualPrice(float $monthly): float
{
return round($monthly * 6 * 0.90, 2);
}
/**
* annual = monthly * 12 * 0.85
*/
private function annualPrice(float $monthly): float
{
return round($monthly * 12 * 0.85, 2);
}
}