mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-18 14:13:31 +00:00
Merge remote-tracking branch 'origin/next' into pr-7774-fix-upgrade-notification-modal
This commit is contained in:
commit
02558d8672
99 files changed
+1169
-424
No files matched your search
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
@@ -12,6 +13,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue
|
||||
@@ -32,6 +34,7 @@ class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, Sho
|
||||
try {
|
||||
$this->cleanupInvitationLink();
|
||||
$this->cleanupExpiredEmailChangeRequests();
|
||||
$this->enforceBackupRetention();
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('CleanupInstanceStuffsJob failed with error: '.$e->getMessage());
|
||||
}
|
||||
@@ -55,4 +58,25 @@ class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, Sho
|
||||
'email_change_code_expires_at' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
private function enforceBackupRetention(): void
|
||||
{
|
||||
if (! Cache::add('backup-retention-enforcement', true, 1800)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$backups = ScheduledDatabaseBackup::where('enabled', true)->get();
|
||||
foreach ($backups as $backup) {
|
||||
try {
|
||||
removeOldBackups($backup);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Failed to enforce retention for backup '.$backup->id.': '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Failed to enforce backup retention: '.$e->getMessage());
|
||||
Cache::forget('backup-retention-enforcement');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -80,6 +81,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->timeout = $backup->timeout ?? 3600;
|
||||
}
|
||||
|
||||
public function middleware(): array
|
||||
{
|
||||
$expireAfter = ($this->backup->timeout ?? 3600) + 300;
|
||||
|
||||
return [(new WithoutOverlapping('database-backup-'.$this->backup->id))->expireAfter($expireAfter)->dontRelease()];
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
@@ -107,6 +115,8 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
throw new \Exception('Database not found?!');
|
||||
}
|
||||
|
||||
$this->markStaleExecutionsAsFailed();
|
||||
|
||||
BackupCreated::dispatch($this->team->id);
|
||||
|
||||
$status = str(data_get($this->database, 'status'));
|
||||
@@ -727,6 +737,31 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
return "{$helperImage}:{$latestVersion}";
|
||||
}
|
||||
|
||||
private function markStaleExecutionsAsFailed(): void
|
||||
{
|
||||
try {
|
||||
$timeoutSeconds = ($this->backup->timeout ?? 3600) * 2;
|
||||
|
||||
$staleExecutions = $this->backup->executions()
|
||||
->where('status', 'running')
|
||||
->where('created_at', '<', now()->subSeconds($timeoutSeconds))
|
||||
->get();
|
||||
|
||||
foreach ($staleExecutions as $execution) {
|
||||
$execution->update([
|
||||
'status' => 'failed',
|
||||
'message' => 'Marked as failed - backup execution exceeded maximum allowed time',
|
||||
'finished_at' => now(),
|
||||
]);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
Log::channel('scheduled-errors')->warning('Failed to clean up stale backup executions', [
|
||||
'backup_id' => $this->backup->uuid,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function failed(?Throwable $exception): void
|
||||
{
|
||||
Log::channel('scheduled-errors')->error('DatabaseBackup permanently failed', [
|
||||
|
||||
@@ -208,13 +208,8 @@ class PublicGitRepository extends Component
|
||||
|
||||
if ($this->repository_url_parsed->getSegment(3) === 'tree') {
|
||||
$path = str($this->repository_url_parsed->getPath())->trim('/');
|
||||
$this->git_branch = str($path)->after('tree/')->before('/')->value();
|
||||
$this->base_directory = str($path)->after($this->git_branch)->after('/')->value();
|
||||
if (filled($this->base_directory)) {
|
||||
$this->base_directory = '/'.$this->base_directory;
|
||||
} else {
|
||||
$this->base_directory = '/';
|
||||
}
|
||||
$this->git_branch = str($path)->after('tree/')->value();
|
||||
$this->base_directory = '/';
|
||||
} else {
|
||||
$this->git_branch = 'main';
|
||||
}
|
||||
@@ -235,9 +230,32 @@ class PublicGitRepository extends Component
|
||||
return;
|
||||
}
|
||||
if ($this->git_source->getMorphClass() === GithubApp::class) {
|
||||
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = githubApi(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
|
||||
$this->rate_limit_reset = Carbon::parse((int) $this->rate_limit_reset)->format('Y-M-d H:i:s');
|
||||
$this->branchFound = true;
|
||||
$originalBranch = $this->git_branch;
|
||||
$branchToTry = $originalBranch;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
$encodedBranch = urlencode($branchToTry);
|
||||
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = githubApi(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$encodedBranch}");
|
||||
$this->rate_limit_reset = Carbon::parse((int) $this->rate_limit_reset)->format('Y-M-d H:i:s');
|
||||
$this->git_branch = $branchToTry;
|
||||
|
||||
$remaining = str($originalBranch)->after($branchToTry)->trim('/')->value();
|
||||
$this->base_directory = filled($remaining) ? '/'.$remaining : '/';
|
||||
|
||||
$this->branchFound = true;
|
||||
|
||||
return;
|
||||
} catch (\Throwable $e) {
|
||||
if (str_contains($branchToTry, '/')) {
|
||||
$branchToTry = str($branchToTry)->beforeLast('/')->value();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,14 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class ScheduledDatabaseBackup extends BaseModel
|
||||
{
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'database_backup_retention_max_storage_locally' => 'float',
|
||||
'database_backup_retention_max_storage_s3' => 'float',
|
||||
];
|
||||
}
|
||||
|
||||
protected $fillable = [
|
||||
'uuid',
|
||||
'team_id',
|
||||
|
||||
@@ -40,10 +40,11 @@ class ValidationPatterns
|
||||
* Blocks dangerous shell metacharacters: ; | ` $ ( ) > < newlines and carriage returns
|
||||
* Allows & for command chaining (&&) which is common in multi-step build commands
|
||||
* Allows double quotes for build args with spaces (e.g. --build-arg KEY="value")
|
||||
* Blocks backslashes and single quotes to prevent escape-sequence attacks
|
||||
* Blocks backslashes to prevent escape-sequence attacks
|
||||
* Allows single and double quotes for quoted arguments (e.g. --entrypoint "sh -c 'npm start'")
|
||||
* Uses [ \t] instead of \s to explicitly exclude \n and \r (which act as command separators)
|
||||
*/
|
||||
public const SHELL_SAFE_COMMAND_PATTERN = '/^[a-zA-Z0-9 \t._\-\/=:@,+\[\]{}#%^~&"]+$/';
|
||||
public const SHELL_SAFE_COMMAND_PATTERN = '/^[a-zA-Z0-9 \t._\-\/=:@,+\[\]{}#%^~&"\']+$/';
|
||||
|
||||
/**
|
||||
* Pattern for Docker volume names
|
||||
|
||||
Reference in new issue
Block a user