Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
354e7ee
Build version 3.20.2
saeedvaziry Mar 3, 2026
0fdcfe5
Fix missing authorization in workflow site creation (#1036)
saeedvaziry Mar 3, 2026
f9fe28f
npm audit
saeedvaziry Mar 3, 2026
ad661c1
Build version 3.20.3
saeedvaziry Mar 3, 2026
05686b0
Better error handling and exception cases for Domains (#1049)
RichardAnderson Mar 16, 2026
9ec070b
DNS Management Improvements (#1050)
RichardAnderson Mar 16, 2026
4a3dfa6
[Domains] Edit DNS Provider (#1051)
RichardAnderson Mar 17, 2026
226aca5
Build version 3.21.0
saeedvaziry Mar 17, 2026
9d266f4
[Fix] Resolve SSH Directory Issue (#1054)
RichardAnderson Mar 17, 2026
f364009
[Fix] Resolves Domain Dialog Issues (#1055)
RichardAnderson Mar 18, 2026
0b644f1
[FIX] Improve .env update validation (#1061)
RichardAnderson Mar 22, 2026
341b31d
npm audit
saeedvaziry Mar 23, 2026
9bcaba8
Build version 3.21.1
saeedvaziry Mar 23, 2026
e7134a9
update packages
saeedvaziry Mar 23, 2026
07d265e
Build version 3.21.2
saeedvaziry Mar 23, 2026
a20f5b9
[FIX] Match synced MySQL users by (username, host) to avoid collapsin…
erhanurgun May 2, 2026
7282560
Audit third-party packages (#1077)
saeedvaziry May 2, 2026
c5045cb
Build version 3.21.3
saeedvaziry May 2, 2026
64937c7
fix: re-sync SSH deploy keys when updating site source control
urufudev May 11, 2026
7d9b0b9
fix: unsetRelation to avoid cached sourceControl after update
urufudev May 13, 2026
b2d80a9
test: add test coverage for deploy key re-sync on source control update
urufudev May 13, 2026
a9ffcee
fix: validate new source control before deleting old deploy key
urufudev May 15, 2026
cf4246b
[FIX] Use server SSH port for default Firewall Rules (#1086)
RichardAnderson May 17, 2026
0553687
[Fix] Resolve Dropbox backup issue (#1099)
RichardAnderson May 17, 2026
4e629ff
Build version 3.22.0
saeedvaziry May 18, 2026
80206af
Merge branch '3.x' into fix/ssh-key-resync-on-source-control-update
saeedvaziry May 20, 2026
aa7abdc
style
saeedvaziry May 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
28 changes: 23 additions & 5 deletions app/Actions/DNSProvider/EditDNSProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,32 @@ class EditDNSProvider
*/
public function edit(DNSProvider $dnsProvider, array $input): DNSProvider
{
Validator::make($input, [
'name' => [
'required',
],
])->validate();
$provider = $dnsProvider->provider();

$rules = array_merge(
['name' => ['required']],
$provider->editValidationRules($input),
);

Validator::make($input, $rules)->validate();

$dnsProvider->name = $input['name'];
$dnsProvider->project_id = isset($input['global']) && $input['global'] ? null : $dnsProvider->user->currentProject?->id;

[$newCredentials, $needsReconnect] = $provider->mergeEditData($input);

if ($needsReconnect) {
if (! $provider->connect($newCredentials)) {
throw ValidationException::withMessages([
'provider' => [sprintf("Couldn't connect to %s. Please check your credentials.", $dnsProvider->provider)],
]);
}
}

if ($newCredentials !== $dnsProvider->credentials) {
$dnsProvider->credentials = $newCredentials;
}

$dnsProvider->connected = true;
$dnsProvider->save();

Expand Down
13 changes: 10 additions & 3 deletions app/Actions/Database/SyncDatabaseUsers.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,17 @@ private function updateUsers(Server $server, Database $handler): void
foreach ($users as $user) {
$databases = $user[2] != 'NULL' ? explode(',', $user[2]) : [];

$query = $server->databaseUsers()->where('username', $user[0]);

// MySQL/MariaDB distinguish users by (username, host); match both to avoid
// collapsing distinct accounts onto a single row. PostgreSQL's get-users-list
// returns an empty host (roles are host-agnostic), so only filter when present.
if ($user[1] !== '') {
$query->where('host', $user[1]);
}

/** @var ?DatabaseUser $databaseUser */
$databaseUser = $server->databaseUsers()
->where('username', $user[0])
->first();
$databaseUser = $query->first();

if ($databaseUser === null) {
$server->databaseUsers()->create([
Expand Down
31 changes: 21 additions & 10 deletions app/Actions/Domain/AddDomain.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use App\Models\Domain;
use App\Models\Project;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
Expand Down Expand Up @@ -34,18 +35,28 @@ public function add(User $user, Project $project, array $input): Domain
]);
}

$domain = new Domain;
$domain->dns_provider_id = $dnsProvider->id;
$domain->user_id = $user->id;
$domain->project_id = $project->id;
$domain->domain = $domainData['name'];
$domain->provider_domain_id = $domainData['id'];
$domain->metadata = $domainData;
$domain->save();
try {
return DB::transaction(function () use ($user, $project, $dnsProvider, $domainData) {
$domain = new Domain;
$domain->dns_provider_id = $dnsProvider->id;
$domain->user_id = $user->id;
$domain->project_id = $project->id;
$domain->domain = $domainData['name'];
$domain->provider_domain_id = $domainData['id'];
$domain->metadata = $domainData;
$domain->save();

$domain->syncDnsRecords();
$domain->syncDnsRecords();

return $domain;
return $domain;
});
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
throw ValidationException::withMessages([
'domain' => [$e->getMessage()],
]);
}
}

private function validate(array $input): void
Expand Down
39 changes: 2 additions & 37 deletions app/Actions/Domain/CreateDNSRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use App\Models\DNSRecord;
use App\Models\Domain;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Throwable;

Expand Down Expand Up @@ -37,6 +36,7 @@ public function create(Domain $domain, array $input): DNSRecord
$dnsRecord->content = $input['content'];
$dnsRecord->ttl = $input['ttl'] ?? 1;
$dnsRecord->proxied = $input['proxied'] ?? false;
$dnsRecord->priority = $input['priority'] ?? null;
$dnsRecord->provider_record_id = $recordData['id'];
$dnsRecord->metadata = $recordData;
$dnsRecord->save();
Expand All @@ -46,41 +46,6 @@ public function create(Domain $domain, array $input): DNSRecord

private function validate(array $input): void
{
$rules = [
'type' => [
'required',
Rule::in([
'A',
'AAAA',
'CNAME',
'TXT',
'MX',
'SRV',
'NS',
'CAA',
'PTR',
'SOA',
]),
],
'name' => [
'required',
'string',
'max:255',
],
'content' => [
'required',
'string',
],
'ttl' => [
'integer',
'min:1',
'max:86400',
],
'proxied' => [
'boolean',
],
];

Validator::make($input, $rules)->validate();
Validator::make($input, DNSRecordRules::rules())->validate();
}
}
45 changes: 45 additions & 0 deletions app/Actions/Domain/DNSRecordRules.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace App\Actions\Domain;

use Illuminate\Validation\Rule;

class DNSRecordRules
{
/**
* @return array<string, mixed>
*/
public static function rules(): array
{
return [
'type' => [
'required',
Rule::in(['A', 'AAAA', 'CNAME', 'TXT', 'MX', 'SRV', 'NS', 'CAA', 'PTR', 'SOA']),
],
'name' => [
'required',
'string',
'max:255',
],
'content' => [
'required',
'string',
],
'ttl' => [
'integer',
'min:1',
'max:86400',
],
'proxied' => [
'boolean',
],
'priority' => [
'nullable',
'prohibited_unless:type,MX',
'integer',
'min:0',
'max:65535',
],
];
}
}
33 changes: 33 additions & 0 deletions app/Actions/Domain/GetAvailableDomains.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace App\Actions\Domain;

use App\Models\DNSProvider;
use App\Models\Domain;
use Illuminate\Support\Facades\Cache;

class GetAvailableDomains
{
/**
* Get available domains from the DNS provider that haven't been added yet.
*
* @return array<int, array<string, mixed>>
*/
public function execute(DNSProvider $dnsProvider, bool $useCache = true): array
{
$existing = Domain::where('dns_provider_id', $dnsProvider->id)
->pluck('provider_domain_id')
->toArray();

$cacheKey = "dns_provider_{$dnsProvider->id}_domains";

$domains = $useCache ? Cache::get($cacheKey) : null;

if ($domains === null) {
$domains = $dnsProvider->provider()->getDomains();
Cache::put($cacheKey, $domains, 3600);
}

return array_values(array_filter($domains, fn (array $domain) => ! in_array($domain['id'], $existing)));
}
}
28 changes: 2 additions & 26 deletions app/Actions/Domain/UpdateDNSRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use App\Models\DNSRecord;
use Exception;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;

class UpdateDNSRecord
Expand Down Expand Up @@ -38,6 +37,7 @@ public function update(DNSRecord $dnsRecord, array $input): DNSRecord
$dnsRecord->content = $input['content'];
$dnsRecord->ttl = $input['ttl'] ?? 1;
$dnsRecord->proxied = $input['proxied'] ?? false;
$dnsRecord->priority = $input['priority'] ?? null;
$dnsRecord->metadata = $recordData;
$dnsRecord->save();

Expand All @@ -46,30 +46,6 @@ public function update(DNSRecord $dnsRecord, array $input): DNSRecord

private function validate(array $input): void
{
$rules = [
'type' => [
'required',
Rule::in(['A', 'AAAA', 'CNAME', 'TXT', 'MX', 'SRV', 'NS', 'CAA', 'PTR', 'SOA']),
],
'name' => [
'required',
'string',
'max:255',
],
'content' => [
'required',
'string',
],
'ttl' => [
'integer',
'min:1',
'max:86400',
],
'proxied' => [
'boolean',
],
];

Validator::make($input, $rules)->validate();
Validator::make($input, DNSRecordRules::rules())->validate();
}
}
13 changes: 12 additions & 1 deletion app/Actions/Site/UpdateEnv.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Exceptions\SSHError;
use App\Models\Site;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;

class UpdateEnv
{
Expand All @@ -17,12 +18,22 @@ public function update(Site $site, array $input): void
{
Validator::make($input, [
'env' => ['required', 'string'],
'path' => ['nullable', 'string'],
'path' => ['nullable', 'string', 'regex:/^[a-zA-Z0-9\/_.\-]+$/'],
])->validate();

$typeData = $site->type_data ?? [];
$path = $input['path'] ?? data_get($typeData, 'env_path', $site->path.'/.env');

$storedEnvPath = data_get($typeData, 'env_path');
$withinSitePath = str_starts_with($path, $site->path.'/');
$matchesStoredPath = $storedEnvPath !== null && $path === $storedEnvPath;

if (str_contains($path, '..') || (! $withinSitePath && ! $matchesStoredPath)) {
throw ValidationException::withMessages([
'path' => __('The path must be within the site directory.'),
]);
}

$site->server->os()->write(
$path,
trim((string) $input['env']),
Expand Down
40 changes: 40 additions & 0 deletions app/Actions/Site/UpdateSourceControl.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
use App\Exceptions\RepositoryPermissionDenied;
use App\Exceptions\SourceControlIsNotConnected;
use App\Models\Site;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Throwable;

class UpdateSourceControl
{
Expand All @@ -27,6 +29,8 @@ public function update(Site $site, array $input): void
])->validate();

$site->source_control_id = $input['source_control'];
$site->unsetRelation('sourceControl');

try {
if ($site->sourceControl) {
$site->sourceControl->getRepo($site->repository);
Expand All @@ -44,6 +48,42 @@ public function update(Site $site, array $input): void
'repository' => 'Repository not found',
]);
}

try {
if ($site->sourceControl && isset($site->type_data['deploy_key_id'])) {
$site->unsetRelation('sourceControl');
$site->source_control_id = $site->getOriginal('source_control_id');
$site->unsetRelation('sourceControl');
$site->sourceControl->provider()->deleteDeployKey(
$site->type_data['deploy_key_id'],
$site->repository,
);
}
} catch (Throwable $e) {
Log::warning('Failed to delete previous deploy key during source control update', [
'site' => $site->id,
'error' => $e->getMessage(),
]);
}

$site->source_control_id = $input['source_control'];
$site->unsetRelation('sourceControl');
$site->save();

if ($site->ssh_key && $site->repository) {
try {
$keyId = $site->sourceControl->provider()->deployKey(
$site->getDeployKeyName(),
$site->repository,
$site->ssh_key
);
$site->jsonUpdate('type_data', 'deploy_key_id', $keyId);
} catch (Throwable $e) {
Log::warning('Failed to re-deploy SSH key after source control update', [
'site' => $site->id,
'error' => $e->getMessage(),
]);
}
}
}
}
Loading
Loading