From 278207d756726fa462b85a60e3e469ae40813012 Mon Sep 17 00:00:00 2001 From: gavidroselj Date: Tue, 15 Sep 2026 09:30:48 +0000 Subject: [PATCH 1/7] Add option to use allocation alias --- subdomains/README.md | 8 +++ .../010_add_alias_toggle_to_nodes.php | 22 +++++++++ .../SubdomainTargetResource.php | 8 +++ subdomains/src/Models/CloudflareDomain.php | 13 +++-- subdomains/src/Models/Subdomain.php | 49 ++++++++++++++----- 5 files changed, 82 insertions(+), 18 deletions(-) create mode 100644 subdomains/database/migrations/010_add_alias_toggle_to_nodes.php diff --git a/subdomains/README.md b/subdomains/README.md index 59450f80..21bc3999 100644 --- a/subdomains/README.md +++ b/subdomains/README.md @@ -44,6 +44,14 @@ CNAME and SRV Subdomains must point to a specific Subdomain target. These can be Note: According to [RFC2782](https://www.rfc-editor.org/info/rfc2782/), SRV records must always point to either an A or AAAA record. While some applications may handle SRV records pointing to CNAME records correctly, this can lead to undefined behavior. +### Use Allocation Alias + +You can specify whether servers on a specific node should use the allocation IP, or the allocation Alias when creating A and AAAA records. + +This option is recommended if your node is not directly exposed to the internet. + +If your allocation Alias is not a valid IPv4 / IPv6 address, the corresponding record types will not be available. + ### SRV service types SRV Subdomains require an SRV service type. This must be configured in the egg features section. The format is `srv-` and then the service name, e.g. `srv-minecraft` or `srv-rust`. diff --git a/subdomains/database/migrations/010_add_alias_toggle_to_nodes.php b/subdomains/database/migrations/010_add_alias_toggle_to_nodes.php new file mode 100644 index 00000000..ffc86de8 --- /dev/null +++ b/subdomains/database/migrations/010_add_alias_toggle_to_nodes.php @@ -0,0 +1,22 @@ +boolean('subdomain_use_alias')->default(false)->after('subdomain_target'); + }); + } + + public function down(): void + { + Schema::table('nodes', function (Blueprint $table) { + $table->dropColumn('subdomain_use_alias'); + }); + } +}; diff --git a/subdomains/src/Filament/Admin/Resources/SubdomainTargets/SubdomainTargetResource.php b/subdomains/src/Filament/Admin/Resources/SubdomainTargets/SubdomainTargetResource.php index 902a7011..70ffc8b2 100644 --- a/subdomains/src/Filament/Admin/Resources/SubdomainTargets/SubdomainTargetResource.php +++ b/subdomains/src/Filament/Admin/Resources/SubdomainTargets/SubdomainTargetResource.php @@ -8,6 +8,7 @@ use Filament\Resources\Resource; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextInputColumn; +use Filament\Tables\Columns\ToggleColumn; use Filament\Tables\Table; class SubdomainTargetResource extends Resource @@ -45,6 +46,13 @@ public static function table(Table $table): Table 'subdomain_target' => $state, ])->save(); }), + ToggleColumn::make('subdomain_use_alias') + ->label(trans('subdomains::strings.subdomain_use_alias')) + ->updateStateUsing(function (Node $node, $state) { + $node->forceFill([ + 'subdomain_use_alias' => $state, + ])->save(); + }), ]) ->emptyStateIcon('tabler-world-www') ->emptyStateDescription('') diff --git a/subdomains/src/Models/CloudflareDomain.php b/subdomains/src/Models/CloudflareDomain.php index 0f3e3c08..7de742b1 100644 --- a/subdomains/src/Models/CloudflareDomain.php +++ b/subdomains/src/Models/CloudflareDomain.php @@ -66,7 +66,7 @@ public function nameWithPrefix(): string return $this->prefix == '' ? $this->name : "$this->prefix.$this->name"; } - public function prependPrefix(string $subdomain): string + public function appendPrefix(string $subdomain): string { return $this->prefix == '' ? $subdomain : "$subdomain.$this->prefix"; } @@ -102,23 +102,26 @@ public function fetchCloudflareId(): void public function availableRecordTypes(Server $server): Collection { $allocation = $server->allocation; + $targetAddress = $server->node->subdomain_use_alias ? $allocation->ip_alias : $allocation->ip; // @phpstan-ignore property.notFound + $subdomainTarget = $server->node->subdomain_target; // @phpstan-ignore property.notFound + $srvServiceType = SRVServiceType::fromServer($server); + $allowedRecordTypes = $this->allowed_record_types; $allowedRecordsFilterDisabled = $allowedRecordTypes->isEmpty(); - $srvServiceType = SRVServiceType::fromServer($server); $types = new Collection(); // Explicitly forbid ANY record creation when primary allocation is invalid - if ($allocation && in_array($allocation->ip, ['0.0.0.0', '::'])) { + if (in_array($targetAddress, ['0.0.0.0', '::'])) { return $types; } - if (($allowedRecordsFilterDisabled || $allowedRecordTypes->contains(RecordType::A)) && $allocation && is_ipv4($allocation->ip)) { + if (($allowedRecordsFilterDisabled || $allowedRecordTypes->contains(RecordType::A)) && is_ipv4($targetAddress)) { $types->add(RecordType::A); } - if (($allowedRecordsFilterDisabled || $allowedRecordTypes->contains(RecordType::AAAA)) && $allocation && is_ipv6($allocation->ip)) { + if (($allowedRecordsFilterDisabled || $allowedRecordTypes->contains(RecordType::AAAA)) && is_ipv6($targetAddress)) { $types->add(RecordType::AAAA); } diff --git a/subdomains/src/Models/Subdomain.php b/subdomains/src/Models/Subdomain.php index 786064e6..fa0f1929 100644 --- a/subdomains/src/Models/Subdomain.php +++ b/subdomains/src/Models/Subdomain.php @@ -66,14 +66,22 @@ public function getLabel(): string|Htmlable|null /** @throws Exception */ public function upsertOnCloudflare(): void { - // Explicitly forbid ANY record creation when primary allocation is invalid - if ($this->server->allocation && in_array($this->server->allocation->ip, ['0.0.0.0', '::'])) { - throw new Exception('Server has invalid allocation ip (0.0.0.0 or ::)'); - } + + $allocation = $this->server->allocation; + $targetAddress = $this->server->node->subdomain_use_alias ? $allocation->ip_alias : $allocation->ip; // @phpstan-ignore property.notFound $subdomainTarget = $this->server->node->subdomain_target; // @phpstan-ignore property.notFound + $srvServiceType = SRVServiceType::fromServer($this->server); + $node_id = $this->server->node->id; + $searchName = $this->domain->appendPrefix($this->name); + + // Explicitly forbid ANY record creation when primary allocation is invalid + if (in_array($targetAddress, ['0.0.0.0', '::'])) { + throw new Exception('Server has invalid allocation ip (0.0.0.0 or ::)'); + } + if (!($this->domain->nodes->isEmpty() || $this->domain->nodes()->where('nodes.id', $node_id)->exists())) { throw new Exception('Domain ' . $this->domain->nameWithPrefix() . ' is not permitted on node ' . $this->server->node->name); } @@ -84,7 +92,7 @@ public function upsertOnCloudflare(): void switch ($this->record_type) { case RecordType::SRV: - if (!$this->server->allocation) { + if (!$allocation) { throw new Exception('Server has no allocation'); } @@ -92,13 +100,11 @@ public function upsertOnCloudflare(): void throw new Exception('Node has no Subdomain target'); } - $srvServiceType = SRVServiceType::fromServer($this->server); - if (!$srvServiceType) { throw new Exception('Server has no SRV type'); } - $searchName = $this->domain->prependPrefix("$srvServiceType->value.$this->name"); + $searchName = "$srvServiceType->value.$searchName"; $payload = [ 'name' => $searchName, @@ -119,8 +125,6 @@ public function upsertOnCloudflare(): void throw new Exception('Node has no Subdomain target'); } - $searchName = $this->domain->prependPrefix($this->name); - $payload = [ 'name' => $searchName, 'type' => $this->record_type->value, @@ -131,24 +135,43 @@ public function upsertOnCloudflare(): void break; case RecordType::A: + if (!$this->server->allocation) { + throw new Exception('Server has no allocation'); + } + + if (!is_ipv4($targetAddress)) { + throw new Exception('Allocation target address ' . $targetAddress . ' is not a valid IPv4 address'); + } + + $payload = [ + 'name' => $searchName, + 'type' => $this->record_type->value, + 'comment' => 'Created by Pelican Subdomains plugin', + 'content' => $targetAddress, + 'proxied' => false, + ]; + break; + case RecordType::AAAA: if (!$this->server->allocation) { throw new Exception('Server has no allocation'); } - $searchName = $this->domain->prependPrefix($this->name); + if (!is_ipv6($targetAddress)) { + throw new Exception('Allocation target address ' . $targetAddress . ' is not a valid IPv6 address'); + } $payload = [ 'name' => $searchName, 'type' => $this->record_type->value, 'comment' => 'Created by Pelican Subdomains plugin', - 'content' => $this->server->allocation->ip, + 'content' => $targetAddress, 'proxied' => false, ]; break; default: - throw new Exception('Requested subdomain type is unsupported'); + throw new Exception('Requested subdomain type '. $this->record_type . ' is unsupported'); } // @phpstan-ignore staticMethod.notFound From 7859b5e2fbb8c1c06c4716cf193784d6d15ba66e Mon Sep 17 00:00:00 2001 From: gavidroselj Date: Tue, 15 Sep 2026 10:06:01 +0000 Subject: [PATCH 2/7] Unify subdomain validation checks --- subdomains/src/Enums/RecordType.php | 66 ++++++++++++++++++++++ subdomains/src/Models/CloudflareDomain.php | 40 ++----------- subdomains/src/Models/Subdomain.php | 62 ++------------------ 3 files changed, 76 insertions(+), 92 deletions(-) diff --git a/subdomains/src/Enums/RecordType.php b/subdomains/src/Enums/RecordType.php index 118768db..5e6e256c 100644 --- a/subdomains/src/Enums/RecordType.php +++ b/subdomains/src/Enums/RecordType.php @@ -2,7 +2,11 @@ namespace Boy132\Subdomains\Enums; +use App\Models\Allocation; +use App\Models\Server; +use Boy132\Subdomains\Models\CloudflareDomain; use Filament\Support\Contracts\HasLabel; +use Illuminate\Support\Collection; enum RecordType: string implements HasLabel { @@ -15,4 +19,66 @@ public function getLabel(): string { return $this->name; } + + /** + * Returns errors that prevent this record type from being used with the provided server and domain. + * If empty, then this record type is allowed to be used. + * Most important error is always returned first. + * + * @return Collection + */ + public function canBeUsed(Server $server, CloudflareDomain $domain): Collection + { + $allocation = $server->allocation; + $targetAddress = $server->node->subdomain_use_alias ? $allocation->ip_alias : $allocation->ip; // @phpstan-ignore property.notFound + + $subdomainTarget = $server->node->subdomain_target; // @phpstan-ignore property.notFound + $srvServiceType = SRVServiceType::fromServer($server); + + $node_id = $server->node->id; + + $errors = new Collection(); + + // General restrictions checks + + if (!($domain->nodes->isEmpty() || $domain->nodes()->where('nodes.id', $node_id)->exists())) { + $errors->add('Domain ' . $domain->nameWithPrefix() . ' is not permitted on node ' . $server->node->name); + } + + if (!($domain->allowed_record_types->isEmpty() || $domain->allowed_record_types->contains($this))) { + $errors->add('Record type ' . $this->value . ' is not permitted on domain ' . $domain->nameWithPrefix()); + } + + // Allocation checks + + if (in_array($this, [self::A, self::AAAA, self::SRV]) && !$allocation) { + $errors->add('Server has no allocation'); + } + + if (in_array($targetAddress, ['0.0.0.0', '::'])) { + $errors->add('Allocation target address is invalid (0.0.0.0 or ::)'); + } + + if ($this == self::A && !is_ipv4($targetAddress)) { + $errors->add('Allocation target address ' . $targetAddress . ' is not a valid IPv4 address'); + } + + if ($this == self::AAAA && !is_ipv6($targetAddress)) { + $errors->add('Allocation target address ' . $targetAddress . ' is not a valid IPv6 address'); + } + + // Subdomain target checks + + if (in_array($this, [self::CNAME, self::SRV]) && !$subdomainTarget) { + $errors->add('Server has no Subdomain target'); + } + + // Other checks + + if ($this == self::SRV && !$srvServiceType) { + $errors->add('Server has no SRV service type'); + } + + return $errors; + } } diff --git a/subdomains/src/Models/CloudflareDomain.php b/subdomains/src/Models/CloudflareDomain.php index 7de742b1..b0326545 100644 --- a/subdomains/src/Models/CloudflareDomain.php +++ b/subdomains/src/Models/CloudflareDomain.php @@ -5,7 +5,6 @@ use App\Models\Node; use App\Models\Server; use Boy132\Subdomains\Enums\RecordType; -use Boy132\Subdomains\Enums\SRVServiceType; use Exception; use Illuminate\Database\Eloquent\Casts\AsEnumCollection; use Illuminate\Database\Eloquent\Model; @@ -97,43 +96,14 @@ public function fetchCloudflareId(): void } /** - * @return Collection + * @return Collection */ public function availableRecordTypes(Server $server): Collection { - $allocation = $server->allocation; - $targetAddress = $server->node->subdomain_use_alias ? $allocation->ip_alias : $allocation->ip; // @phpstan-ignore property.notFound - - $subdomainTarget = $server->node->subdomain_target; // @phpstan-ignore property.notFound - $srvServiceType = SRVServiceType::fromServer($server); - - $allowedRecordTypes = $this->allowed_record_types; - $allowedRecordsFilterDisabled = $allowedRecordTypes->isEmpty(); - - $types = new Collection(); - - // Explicitly forbid ANY record creation when primary allocation is invalid - if (in_array($targetAddress, ['0.0.0.0', '::'])) { - return $types; - } - - if (($allowedRecordsFilterDisabled || $allowedRecordTypes->contains(RecordType::A)) && is_ipv4($targetAddress)) { - $types->add(RecordType::A); - } - - if (($allowedRecordsFilterDisabled || $allowedRecordTypes->contains(RecordType::AAAA)) && is_ipv6($targetAddress)) { - $types->add(RecordType::AAAA); - } - - if (($allowedRecordsFilterDisabled || $allowedRecordTypes->contains(RecordType::CNAME)) && $subdomainTarget) { - $types->add(RecordType::CNAME); - } - - if (($allowedRecordsFilterDisabled || $allowedRecordTypes->contains(RecordType::SRV)) && $allocation && $subdomainTarget && $srvServiceType) { - $types->add(RecordType::SRV); - } - - return $types; + return collect(RecordType::cases()) + ->filter(fn ($recordType) => $recordType + ->canBeUsed($server, $this) + ->isEmpty()); } /** diff --git a/subdomains/src/Models/Subdomain.php b/subdomains/src/Models/Subdomain.php index fa0f1929..f2b3f431 100644 --- a/subdomains/src/Models/Subdomain.php +++ b/subdomains/src/Models/Subdomain.php @@ -66,6 +66,10 @@ public function getLabel(): string|Htmlable|null /** @throws Exception */ public function upsertOnCloudflare(): void { + $errors = $this->record_type->canBeUsed($this->server, $this->domain); + if ($errors->isNotEmpty()) { + throw new Exception($errors->first()); + } $allocation = $this->server->allocation; $targetAddress = $this->server->node->subdomain_use_alias ? $allocation->ip_alias : $allocation->ip; // @phpstan-ignore property.notFound @@ -73,37 +77,10 @@ public function upsertOnCloudflare(): void $subdomainTarget = $this->server->node->subdomain_target; // @phpstan-ignore property.notFound $srvServiceType = SRVServiceType::fromServer($this->server); - $node_id = $this->server->node->id; - $searchName = $this->domain->appendPrefix($this->name); - // Explicitly forbid ANY record creation when primary allocation is invalid - if (in_array($targetAddress, ['0.0.0.0', '::'])) { - throw new Exception('Server has invalid allocation ip (0.0.0.0 or ::)'); - } - - if (!($this->domain->nodes->isEmpty() || $this->domain->nodes()->where('nodes.id', $node_id)->exists())) { - throw new Exception('Domain ' . $this->domain->nameWithPrefix() . ' is not permitted on node ' . $this->server->node->name); - } - - if (!($this->domain->allowed_record_types->isEmpty() || $this->domain->allowed_record_types->contains($this->record_type))) { - throw new Exception('Record type ' . $this->record_type->value . ' is not permitted on domain ' . $this->domain->nameWithPrefix()); - } - switch ($this->record_type) { case RecordType::SRV: - if (!$allocation) { - throw new Exception('Server has no allocation'); - } - - if (!$subdomainTarget) { - throw new Exception('Node has no Subdomain target'); - } - - if (!$srvServiceType) { - throw new Exception('Server has no SRV type'); - } - $searchName = "$srvServiceType->value.$searchName"; $payload = [ @@ -111,7 +88,7 @@ public function upsertOnCloudflare(): void 'type' => $this->record_type->value, 'comment' => 'Created by Pelican Subdomains plugin', 'data' => [ - 'port' => $this->server->allocation->port, + 'port' => $allocation->port, 'priority' => 0, 'target' => $subdomainTarget, 'weight' => 0, @@ -121,10 +98,6 @@ public function upsertOnCloudflare(): void break; case RecordType::CNAME: - if (!$subdomainTarget) { - throw new Exception('Node has no Subdomain target'); - } - $payload = [ 'name' => $searchName, 'type' => $this->record_type->value, @@ -135,32 +108,7 @@ public function upsertOnCloudflare(): void break; case RecordType::A: - if (!$this->server->allocation) { - throw new Exception('Server has no allocation'); - } - - if (!is_ipv4($targetAddress)) { - throw new Exception('Allocation target address ' . $targetAddress . ' is not a valid IPv4 address'); - } - - $payload = [ - 'name' => $searchName, - 'type' => $this->record_type->value, - 'comment' => 'Created by Pelican Subdomains plugin', - 'content' => $targetAddress, - 'proxied' => false, - ]; - break; - case RecordType::AAAA: - if (!$this->server->allocation) { - throw new Exception('Server has no allocation'); - } - - if (!is_ipv6($targetAddress)) { - throw new Exception('Allocation target address ' . $targetAddress . ' is not a valid IPv6 address'); - } - $payload = [ 'name' => $searchName, 'type' => $this->record_type->value, From ed58112357d12bd6d888e2b7c54a86d4248ad9c0 Mon Sep 17 00:00:00 2001 From: gavidroselj Date: Tue, 15 Sep 2026 10:16:33 +0000 Subject: [PATCH 3/7] Add translations --- subdomains/README.md | 2 +- subdomains/lang/de/strings.php | 1 + subdomains/lang/en/strings.php | 1 + .../Resources/SubdomainTargets/SubdomainTargetResource.php | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/subdomains/README.md b/subdomains/README.md index 21bc3999..dcc7cd46 100644 --- a/subdomains/README.md +++ b/subdomains/README.md @@ -46,7 +46,7 @@ Note: According to [RFC2782](https://www.rfc-editor.org/info/rfc2782/), SRV reco ### Use Allocation Alias -You can specify whether servers on a specific node should use the allocation IP, or the allocation Alias when creating A and AAAA records. +You can specify whether servers on a specific node should use the allocation Alias instead of the allocation IP when creating A and AAAA records. This option is recommended if your node is not directly exposed to the internet. diff --git a/subdomains/lang/de/strings.php b/subdomains/lang/de/strings.php index a4ca74fd..6ff870a2 100644 --- a/subdomains/lang/de/strings.php +++ b/subdomains/lang/de/strings.php @@ -22,6 +22,7 @@ 'is_synced' => 'Ist synchronisiert?', 'subdomain_target' => 'Subdomain Ziel', 'no_subdomain_target' => 'Kein Subdomain Ziel', + 'use_allocation_alias' => 'Allocation Alias verwenden', 'sync' => 'Synchronisieren', diff --git a/subdomains/lang/en/strings.php b/subdomains/lang/en/strings.php index 3f793bd8..d8409c5e 100644 --- a/subdomains/lang/en/strings.php +++ b/subdomains/lang/en/strings.php @@ -22,6 +22,7 @@ 'is_synced' => 'Is Synced?', 'subdomain_target' => 'Subdomain target', 'no_subdomain_target' => 'No Subdomain target', + 'use_allocation_alias' => 'Use allocation alias', 'sync' => 'Sync', diff --git a/subdomains/src/Filament/Admin/Resources/SubdomainTargets/SubdomainTargetResource.php b/subdomains/src/Filament/Admin/Resources/SubdomainTargets/SubdomainTargetResource.php index 70ffc8b2..f27f0297 100644 --- a/subdomains/src/Filament/Admin/Resources/SubdomainTargets/SubdomainTargetResource.php +++ b/subdomains/src/Filament/Admin/Resources/SubdomainTargets/SubdomainTargetResource.php @@ -47,7 +47,7 @@ public static function table(Table $table): Table ])->save(); }), ToggleColumn::make('subdomain_use_alias') - ->label(trans('subdomains::strings.subdomain_use_alias')) + ->label(trans('subdomains::strings.use_allocation_alias')) ->updateStateUsing(function (Node $node, $state) { $node->forceFill([ 'subdomain_use_alias' => $state, From 1d2f672acdebf3b8b9fbd49bc7a87eaa759a9deb Mon Sep 17 00:00:00 2001 From: gavidroselj Date: Tue, 15 Sep 2026 10:21:18 +0000 Subject: [PATCH 4/7] Better function naming --- subdomains/src/Enums/RecordType.php | 2 +- subdomains/src/Models/CloudflareDomain.php | 2 +- subdomains/src/Models/Subdomain.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/subdomains/src/Enums/RecordType.php b/subdomains/src/Enums/RecordType.php index 5e6e256c..a6f9fbb0 100644 --- a/subdomains/src/Enums/RecordType.php +++ b/subdomains/src/Enums/RecordType.php @@ -27,7 +27,7 @@ public function getLabel(): string * * @return Collection */ - public function canBeUsed(Server $server, CloudflareDomain $domain): Collection + public function canBeUsedErrors(Server $server, CloudflareDomain $domain): Collection { $allocation = $server->allocation; $targetAddress = $server->node->subdomain_use_alias ? $allocation->ip_alias : $allocation->ip; // @phpstan-ignore property.notFound diff --git a/subdomains/src/Models/CloudflareDomain.php b/subdomains/src/Models/CloudflareDomain.php index b0326545..71e8e799 100644 --- a/subdomains/src/Models/CloudflareDomain.php +++ b/subdomains/src/Models/CloudflareDomain.php @@ -102,7 +102,7 @@ public function availableRecordTypes(Server $server): Collection { return collect(RecordType::cases()) ->filter(fn ($recordType) => $recordType - ->canBeUsed($server, $this) + ->canBeUsedErrors($server, $this) ->isEmpty()); } diff --git a/subdomains/src/Models/Subdomain.php b/subdomains/src/Models/Subdomain.php index f2b3f431..efd475e8 100644 --- a/subdomains/src/Models/Subdomain.php +++ b/subdomains/src/Models/Subdomain.php @@ -66,7 +66,7 @@ public function getLabel(): string|Htmlable|null /** @throws Exception */ public function upsertOnCloudflare(): void { - $errors = $this->record_type->canBeUsed($this->server, $this->domain); + $errors = $this->record_type->canBeUsedErrors($this->server, $this->domain); if ($errors->isNotEmpty()) { throw new Exception($errors->first()); } From 731fd84bf7e71677b3584dcac081a16b3f7550fd Mon Sep 17 00:00:00 2001 From: gavidroselj Date: Tue, 15 Sep 2026 10:48:54 +0000 Subject: [PATCH 5/7] Do not dereference null allocation --- subdomains/src/Enums/RecordType.php | 6 +++++- subdomains/src/Models/Subdomain.php | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/subdomains/src/Enums/RecordType.php b/subdomains/src/Enums/RecordType.php index a6f9fbb0..c9d33b99 100644 --- a/subdomains/src/Enums/RecordType.php +++ b/subdomains/src/Enums/RecordType.php @@ -30,7 +30,11 @@ public function getLabel(): string public function canBeUsedErrors(Server $server, CloudflareDomain $domain): Collection { $allocation = $server->allocation; - $targetAddress = $server->node->subdomain_use_alias ? $allocation->ip_alias : $allocation->ip; // @phpstan-ignore property.notFound + + $targetAddress = ''; + if ($allocation) { + $targetAddress = $server->node->subdomain_use_alias ? $allocation->ip_alias : $allocation->ip; // @phpstan-ignore property.notFound + } $subdomainTarget = $server->node->subdomain_target; // @phpstan-ignore property.notFound $srvServiceType = SRVServiceType::fromServer($server); diff --git a/subdomains/src/Models/Subdomain.php b/subdomains/src/Models/Subdomain.php index efd475e8..0eab019e 100644 --- a/subdomains/src/Models/Subdomain.php +++ b/subdomains/src/Models/Subdomain.php @@ -72,7 +72,11 @@ public function upsertOnCloudflare(): void } $allocation = $this->server->allocation; - $targetAddress = $this->server->node->subdomain_use_alias ? $allocation->ip_alias : $allocation->ip; // @phpstan-ignore property.notFound + + $targetAddress = ''; + if ($allocation) { + $targetAddress = $this->server->node->subdomain_use_alias ? $allocation->ip_alias : $allocation->ip; // @phpstan-ignore property.notFound + } $subdomainTarget = $this->server->node->subdomain_target; // @phpstan-ignore property.notFound $srvServiceType = SRVServiceType::fromServer($this->server); From 9f7bb1d9db3e6f7586f85e0335c6ffa6db104880 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Wed, 16 Sep 2026 10:52:05 +0200 Subject: [PATCH 6/7] always block invalid allocation ips --- subdomains/src/Enums/RecordType.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/subdomains/src/Enums/RecordType.php b/subdomains/src/Enums/RecordType.php index c9d33b99..bc68387e 100644 --- a/subdomains/src/Enums/RecordType.php +++ b/subdomains/src/Enums/RecordType.php @@ -33,6 +33,10 @@ public function canBeUsedErrors(Server $server, CloudflareDomain $domain): Colle $targetAddress = ''; if ($allocation) { + if (in_array($allocation->ip, ['0.0.0.0', '::'])) { + $errors->add('Allocation ip is invalid (0.0.0.0 or ::)'); + } + $targetAddress = $server->node->subdomain_use_alias ? $allocation->ip_alias : $allocation->ip; // @phpstan-ignore property.notFound } From d0e8d7a8fbe3818493d717456a19a1d39d256430 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Wed, 16 Sep 2026 10:53:35 +0200 Subject: [PATCH 7/7] init errors collection first --- subdomains/src/Enums/RecordType.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/subdomains/src/Enums/RecordType.php b/subdomains/src/Enums/RecordType.php index bc68387e..aa936352 100644 --- a/subdomains/src/Enums/RecordType.php +++ b/subdomains/src/Enums/RecordType.php @@ -29,6 +29,8 @@ public function getLabel(): string */ public function canBeUsedErrors(Server $server, CloudflareDomain $domain): Collection { + $errors = new Collection(); + $allocation = $server->allocation; $targetAddress = ''; @@ -45,8 +47,6 @@ public function canBeUsedErrors(Server $server, CloudflareDomain $domain): Colle $node_id = $server->node->id; - $errors = new Collection(); - // General restrictions checks if (!($domain->nodes->isEmpty() || $domain->nodes()->where('nodes.id', $node_id)->exists())) {