mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-16 22:14:34 +00:00
Merge branch 'next' into shadow/improve-service-postgresus
This commit is contained in:
37 files changed
+1456
-162
No files matched your search
@@ -75,6 +75,10 @@ class StartProxy
|
||||
' done',
|
||||
" echo 'Successfully stopped and removed existing coolify-proxy.'",
|
||||
'fi',
|
||||
]);
|
||||
// Ensure required networks exist BEFORE docker compose up (networks are declared as external)
|
||||
$commands = $commands->merge(ensureProxyNetworksExist($server));
|
||||
$commands = $commands->merge([
|
||||
"echo 'Starting coolify-proxy.'",
|
||||
'docker compose up -d --wait --remove-orphans',
|
||||
"echo 'Successfully started coolify-proxy.'",
|
||||
|
||||
@@ -202,7 +202,8 @@ class CleanupNames extends Command
|
||||
|
||||
exec($command, $output, $returnCode);
|
||||
} catch (\Exception $e) {
|
||||
// Silently continue
|
||||
// Log failure but continue - backup is optional safeguard
|
||||
Log::warning('Name cleanup backup failed', ['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ class SyncBunny extends Command
|
||||
|
||||
// Clone the repository
|
||||
$this->info('Cloning coolify-cdn repository...');
|
||||
$output = [];
|
||||
exec('gh repo clone coollabsio/coolify-cdn '.escapeshellarg($tmpDir).' 2>&1', $output, $returnCode);
|
||||
if ($returnCode !== 0) {
|
||||
$this->error('Failed to clone repository: '.implode("\n", $output));
|
||||
@@ -59,6 +60,7 @@ class SyncBunny extends Command
|
||||
|
||||
// Create feature branch
|
||||
$this->info('Creating feature branch...');
|
||||
$output = [];
|
||||
exec('cd '.escapeshellarg($tmpDir).' && git checkout -b '.escapeshellarg($branchName).' 2>&1', $output, $returnCode);
|
||||
if ($returnCode !== 0) {
|
||||
$this->error('Failed to create branch: '.implode("\n", $output));
|
||||
@@ -96,6 +98,7 @@ class SyncBunny extends Command
|
||||
|
||||
// Stage and commit
|
||||
$this->info('Committing changes...');
|
||||
$output = [];
|
||||
exec('cd '.escapeshellarg($tmpDir).' && git add json/releases.json 2>&1', $output, $returnCode);
|
||||
if ($returnCode !== 0) {
|
||||
$this->error('Failed to stage changes: '.implode("\n", $output));
|
||||
@@ -133,6 +136,7 @@ class SyncBunny extends Command
|
||||
|
||||
// Push to remote
|
||||
$this->info('Pushing branch to remote...');
|
||||
$output = [];
|
||||
exec('cd '.escapeshellarg($tmpDir).' && git push origin '.escapeshellarg($branchName).' 2>&1', $output, $returnCode);
|
||||
if ($returnCode !== 0) {
|
||||
$this->error('Failed to push branch: '.implode("\n", $output));
|
||||
@@ -146,6 +150,7 @@ class SyncBunny extends Command
|
||||
$prTitle = 'Update releases.json - '.date('Y-m-d H:i:s');
|
||||
$prBody = 'Automated update of releases.json with latest '.count($releases).' releases from GitHub API';
|
||||
$prCommand = 'gh pr create --repo coollabsio/coolify-cdn --title '.escapeshellarg($prTitle).' --body '.escapeshellarg($prBody).' --base main --head '.escapeshellarg($branchName).' 2>&1';
|
||||
$output = [];
|
||||
exec($prCommand, $output, $returnCode);
|
||||
|
||||
// Clean up
|
||||
@@ -211,6 +216,7 @@ class SyncBunny extends Command
|
||||
|
||||
// 3. Clone the repository
|
||||
$this->info('Cloning coolify-cdn repository...');
|
||||
$output = [];
|
||||
exec('gh repo clone coollabsio/coolify-cdn '.escapeshellarg($tmpDir).' 2>&1', $output, $returnCode);
|
||||
if ($returnCode !== 0) {
|
||||
$this->error('Failed to clone repository: '.implode("\n", $output));
|
||||
@@ -220,6 +226,7 @@ class SyncBunny extends Command
|
||||
|
||||
// 4. Create feature branch
|
||||
$this->info('Creating feature branch...');
|
||||
$output = [];
|
||||
exec('cd '.escapeshellarg($tmpDir).' && git checkout -b '.escapeshellarg($branchName).' 2>&1', $output, $returnCode);
|
||||
if ($returnCode !== 0) {
|
||||
$this->error('Failed to create branch: '.implode("\n", $output));
|
||||
@@ -274,6 +281,7 @@ class SyncBunny extends Command
|
||||
|
||||
// 7. Stage both files
|
||||
$this->info('Staging changes...');
|
||||
$output = [];
|
||||
exec('cd '.escapeshellarg($tmpDir).' && git add json/releases.json '.escapeshellarg($versionsTargetPath).' 2>&1', $output, $returnCode);
|
||||
if ($returnCode !== 0) {
|
||||
$this->error('Failed to stage changes: '.implode("\n", $output));
|
||||
@@ -303,6 +311,7 @@ class SyncBunny extends Command
|
||||
// 9. Commit changes
|
||||
$envLabel = $nightly ? 'NIGHTLY' : 'PRODUCTION';
|
||||
$commitMessage = "Update releases.json and $envLabel versions.json to $actualVersion - ".date('Y-m-d H:i:s');
|
||||
$output = [];
|
||||
exec('cd '.escapeshellarg($tmpDir).' && git commit -m '.escapeshellarg($commitMessage).' 2>&1', $output, $returnCode);
|
||||
if ($returnCode !== 0) {
|
||||
$this->error('Failed to commit changes: '.implode("\n", $output));
|
||||
@@ -313,6 +322,7 @@ class SyncBunny extends Command
|
||||
|
||||
// 10. Push to remote
|
||||
$this->info('Pushing branch to remote...');
|
||||
$output = [];
|
||||
exec('cd '.escapeshellarg($tmpDir).' && git push origin '.escapeshellarg($branchName).' 2>&1', $output, $returnCode);
|
||||
if ($returnCode !== 0) {
|
||||
$this->error('Failed to push branch: '.implode("\n", $output));
|
||||
@@ -326,6 +336,7 @@ class SyncBunny extends Command
|
||||
$prTitle = "Update releases.json and $envLabel versions.json to $actualVersion - ".date('Y-m-d H:i:s');
|
||||
$prBody = "Automated update:\n- releases.json with latest ".count($releases)." releases from GitHub API\n- $envLabel versions.json to version $actualVersion";
|
||||
$prCommand = 'gh pr create --repo coollabsio/coolify-cdn --title '.escapeshellarg($prTitle).' --body '.escapeshellarg($prBody).' --base main --head '.escapeshellarg($branchName).' 2>&1';
|
||||
$output = [];
|
||||
exec($prCommand, $output, $returnCode);
|
||||
|
||||
// 12. Clean up
|
||||
|
||||
@@ -351,7 +351,7 @@ class ServicesController extends Controller
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
];
|
||||
if ($oneClickServiceName === 'cloudflared') {
|
||||
if ($oneClickServiceName === 'pgadmin') {
|
||||
data_set($servicePayload, 'connect_to_docker_network', true);
|
||||
}
|
||||
$service = Service::create($servicePayload);
|
||||
|
||||
@@ -489,17 +489,22 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$collectionsToExclude = collect();
|
||||
}
|
||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||
|
||||
// Validate and escape database name to prevent command injection
|
||||
validateShellSafePath($databaseName, 'database name');
|
||||
$escapedDatabaseName = escapeshellarg($databaseName);
|
||||
|
||||
if ($collectionsToExclude->count() === 0) {
|
||||
if (str($this->database->image)->startsWith('mongo:4')) {
|
||||
$commands[] = "docker exec $this->container_name mongodump --uri=\"$url\" --gzip --archive > $this->backup_location";
|
||||
} else {
|
||||
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=\"$url\" --db $databaseName --gzip --archive > $this->backup_location";
|
||||
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=\"$url\" --db $escapedDatabaseName --gzip --archive > $this->backup_location";
|
||||
}
|
||||
} else {
|
||||
if (str($this->database->image)->startsWith('mongo:4')) {
|
||||
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
|
||||
} else {
|
||||
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=\"$url\" --db $databaseName --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
|
||||
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=\"$url\" --db $escapedDatabaseName --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -525,7 +530,10 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if ($this->backup->dump_all) {
|
||||
$backupCommand .= " $this->container_name pg_dumpall --username {$this->database->postgres_user} | gzip > $this->backup_location";
|
||||
} else {
|
||||
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
|
||||
// Validate and escape database name to prevent command injection
|
||||
validateShellSafePath($database, 'database name');
|
||||
$escapedDatabase = escapeshellarg($database);
|
||||
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $escapedDatabase > $this->backup_location";
|
||||
}
|
||||
|
||||
$commands[] = $backupCommand;
|
||||
@@ -547,7 +555,10 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if ($this->backup->dump_all) {
|
||||
$commands[] = "docker exec $this->container_name mysqldump -u root -p\"{$this->database->mysql_root_password}\" --all-databases --single-transaction --quick --lock-tables=false --compress | gzip > $this->backup_location";
|
||||
} else {
|
||||
$commands[] = "docker exec $this->container_name mysqldump -u root -p\"{$this->database->mysql_root_password}\" $database > $this->backup_location";
|
||||
// Validate and escape database name to prevent command injection
|
||||
validateShellSafePath($database, 'database name');
|
||||
$escapedDatabase = escapeshellarg($database);
|
||||
$commands[] = "docker exec $this->container_name mysqldump -u root -p\"{$this->database->mysql_root_password}\" $escapedDatabase > $this->backup_location";
|
||||
}
|
||||
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||
$this->backup_output = trim($this->backup_output);
|
||||
@@ -567,7 +578,10 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if ($this->backup->dump_all) {
|
||||
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p\"{$this->database->mariadb_root_password}\" --all-databases --single-transaction --quick --lock-tables=false --compress > $this->backup_location";
|
||||
} else {
|
||||
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p\"{$this->database->mariadb_root_password}\" $database > $this->backup_location";
|
||||
// Validate and escape database name to prevent command injection
|
||||
validateShellSafePath($database, 'database name');
|
||||
$escapedDatabase = escapeshellarg($database);
|
||||
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p\"{$this->database->mariadb_root_password}\" $escapedDatabase > $this->backup_location";
|
||||
}
|
||||
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||
$this->backup_output = trim($this->backup_output);
|
||||
|
||||
@@ -168,6 +168,9 @@ class ValidateAndInstallServerJob implements ShouldQueue
|
||||
if (! $this->server->isBuildServer()) {
|
||||
$proxyShouldRun = CheckProxy::run($this->server, true);
|
||||
if ($proxyShouldRun) {
|
||||
// Ensure networks exist BEFORE dispatching async proxy startup
|
||||
// This prevents race condition where proxy tries to start before networks are created
|
||||
instant_remote_process(ensureProxyNetworksExist($this->server)->toArray(), $this->server, false);
|
||||
StartProxy::dispatch($this->server);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,25 @@ class BackupEdit extends Component
|
||||
$this->backup->save_s3 = $this->saveS3;
|
||||
$this->backup->disable_local_backup = $this->disableLocalBackup;
|
||||
$this->backup->s3_storage_id = $this->s3StorageId;
|
||||
|
||||
// Validate databases_to_backup to prevent command injection
|
||||
if (filled($this->databasesToBackup)) {
|
||||
$databases = str($this->databasesToBackup)->explode(',');
|
||||
foreach ($databases as $index => $db) {
|
||||
$dbName = trim($db);
|
||||
try {
|
||||
validateShellSafePath($dbName, 'database name');
|
||||
} catch (\Exception $e) {
|
||||
// Provide specific error message indicating which database failed validation
|
||||
$position = $index + 1;
|
||||
throw new \Exception(
|
||||
"Database #{$position} ('{$dbName}') validation failed: ".
|
||||
$e->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->backup->databases_to_backup = $this->databasesToBackup;
|
||||
$this->backup->dump_all = $this->dumpAll;
|
||||
$this->backup->timeout = $this->timeout;
|
||||
|
||||
@@ -328,12 +328,15 @@ class General extends Component
|
||||
$configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
|
||||
if ($oldScript && $oldScript['filename'] !== $script['filename']) {
|
||||
$old_file_path = "$configuration_dir/docker-entrypoint-initdb.d/{$oldScript['filename']}";
|
||||
$delete_command = "rm -f $old_file_path";
|
||||
try {
|
||||
// Validate and escape filename to prevent command injection
|
||||
validateShellSafePath($oldScript['filename'], 'init script filename');
|
||||
$old_file_path = "$configuration_dir/docker-entrypoint-initdb.d/{$oldScript['filename']}";
|
||||
$escapedOldPath = escapeshellarg($old_file_path);
|
||||
$delete_command = "rm -f {$escapedOldPath}";
|
||||
instant_remote_process([$delete_command], $this->server);
|
||||
} catch (Exception $e) {
|
||||
$this->dispatch('error', 'Failed to remove old init script from server: '.$e->getMessage());
|
||||
$this->dispatch('error', $e->getMessage());
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -370,13 +373,17 @@ class General extends Component
|
||||
if ($found) {
|
||||
$container_name = $this->database->uuid;
|
||||
$configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
$file_path = "$configuration_dir/docker-entrypoint-initdb.d/{$script['filename']}";
|
||||
|
||||
$command = "rm -f $file_path";
|
||||
try {
|
||||
// Validate and escape filename to prevent command injection
|
||||
validateShellSafePath($script['filename'], 'init script filename');
|
||||
$file_path = "$configuration_dir/docker-entrypoint-initdb.d/{$script['filename']}";
|
||||
$escapedPath = escapeshellarg($file_path);
|
||||
|
||||
$command = "rm -f {$escapedPath}";
|
||||
instant_remote_process([$command], $this->server);
|
||||
} catch (Exception $e) {
|
||||
$this->dispatch('error', 'Failed to remove init script from server: '.$e->getMessage());
|
||||
$this->dispatch('error', $e->getMessage());
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -405,6 +412,16 @@ class General extends Component
|
||||
'new_filename' => 'required|string',
|
||||
'new_content' => 'required|string',
|
||||
]);
|
||||
|
||||
try {
|
||||
// Validate filename to prevent command injection
|
||||
validateShellSafePath($this->new_filename, 'init script filename');
|
||||
} catch (Exception $e) {
|
||||
$this->dispatch('error', $e->getMessage());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$found = collect($this->initScripts)->firstWhere('filename', $this->new_filename);
|
||||
if ($found) {
|
||||
$this->dispatch('error', 'Filename already exists.');
|
||||
|
||||
@@ -81,7 +81,7 @@ class Create extends Component
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
];
|
||||
if ($oneClickServiceName === 'cloudflared' || $oneClickServiceName === 'pgadmin' || $oneClickServiceName === 'postgresus') {
|
||||
if ($oneClickServiceName === 'pgadmin' || $oneClickServiceName === 'postgresus') {
|
||||
data_set($service_payload, 'connect_to_docker_network', true);
|
||||
}
|
||||
$service = Service::create($service_payload);
|
||||
|
||||
@@ -179,6 +179,10 @@ class Storage extends Component
|
||||
$this->file_storage_directory_destination = trim($this->file_storage_directory_destination);
|
||||
$this->file_storage_directory_destination = str($this->file_storage_directory_destination)->start('/')->value();
|
||||
|
||||
// Validate paths to prevent command injection
|
||||
validateShellSafePath($this->file_storage_directory_source, 'storage source path');
|
||||
validateShellSafePath($this->file_storage_directory_destination, 'storage destination path');
|
||||
|
||||
\App\Models\LocalFileVolume::create([
|
||||
'fs_path' => $this->file_storage_directory_source,
|
||||
'mount_path' => $this->file_storage_directory_destination,
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
|
||||
namespace App\Livewire\Project\Shared\EnvironmentVariable;
|
||||
|
||||
use App\Models\Environment;
|
||||
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
|
||||
use App\Models\Project;
|
||||
use App\Models\SharedEnvironmentVariable;
|
||||
use App\Traits\EnvironmentVariableAnalyzer;
|
||||
use App\Traits\EnvironmentVariableProtection;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Component;
|
||||
|
||||
class Show extends Component
|
||||
@@ -184,6 +187,7 @@ class Show extends Component
|
||||
|
||||
$this->serialize();
|
||||
$this->syncData(true);
|
||||
$this->syncData(false);
|
||||
$this->dispatch('success', 'Environment variable updated.');
|
||||
$this->dispatch('envsUpdated');
|
||||
$this->dispatch('configurationChanged');
|
||||
@@ -192,6 +196,72 @@ class Show extends Component
|
||||
}
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function availableSharedVariables(): array
|
||||
{
|
||||
$team = currentTeam();
|
||||
$result = [
|
||||
'team' => [],
|
||||
'project' => [],
|
||||
'environment' => [],
|
||||
];
|
||||
|
||||
// Early return if no team
|
||||
if (! $team) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Check if user can view team variables
|
||||
try {
|
||||
$this->authorize('view', $team);
|
||||
$result['team'] = $team->environment_variables()
|
||||
->pluck('key')
|
||||
->toArray();
|
||||
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
|
||||
// User not authorized to view team variables
|
||||
}
|
||||
|
||||
// Get project variables if we have a project_uuid in route
|
||||
$projectUuid = data_get($this->parameters, 'project_uuid');
|
||||
if ($projectUuid) {
|
||||
$project = Project::where('team_id', $team->id)
|
||||
->where('uuid', $projectUuid)
|
||||
->first();
|
||||
|
||||
if ($project) {
|
||||
try {
|
||||
$this->authorize('view', $project);
|
||||
$result['project'] = $project->environment_variables()
|
||||
->pluck('key')
|
||||
->toArray();
|
||||
|
||||
// Get environment variables if we have an environment_uuid in route
|
||||
$environmentUuid = data_get($this->parameters, 'environment_uuid');
|
||||
if ($environmentUuid) {
|
||||
$environment = $project->environments()
|
||||
->where('uuid', $environmentUuid)
|
||||
->first();
|
||||
|
||||
if ($environment) {
|
||||
try {
|
||||
$this->authorize('view', $environment);
|
||||
$result['environment'] = $environment->environment_variables()
|
||||
->pluck('key')
|
||||
->toArray();
|
||||
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
|
||||
// User not authorized to view environment variables
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
|
||||
// User not authorized to view project variables
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -25,13 +25,25 @@ class DynamicConfigurationNavbar extends Component
|
||||
$this->authorize('update', $this->server);
|
||||
$proxy_path = $this->server->proxyPath();
|
||||
$proxy_type = $this->server->proxyType();
|
||||
|
||||
// Decode filename: pipes are used to encode dots for Livewire property binding
|
||||
// (e.g., 'my|service.yaml' -> 'my.service.yaml')
|
||||
// This must happen BEFORE validation because validateShellSafePath() correctly
|
||||
// rejects pipe characters as dangerous shell metacharacters
|
||||
$file = str_replace('|', '.', $fileName);
|
||||
|
||||
// Validate filename to prevent command injection
|
||||
validateShellSafePath($file, 'proxy configuration filename');
|
||||
|
||||
if ($proxy_type === 'CADDY' && $file === 'Caddyfile') {
|
||||
$this->dispatch('error', 'Cannot delete Caddyfile.');
|
||||
|
||||
return;
|
||||
}
|
||||
instant_remote_process(["rm -f {$proxy_path}/dynamic/{$file}"], $this->server);
|
||||
|
||||
$fullPath = "{$proxy_path}/dynamic/{$file}";
|
||||
$escapedPath = escapeshellarg($fullPath);
|
||||
instant_remote_process(["rm -f {$escapedPath}"], $this->server);
|
||||
if ($proxy_type === 'CADDY') {
|
||||
$this->server->reloadCaddy();
|
||||
}
|
||||
|
||||
@@ -41,6 +41,10 @@ class NewDynamicConfiguration extends Component
|
||||
'fileName' => 'required',
|
||||
'value' => 'required',
|
||||
]);
|
||||
|
||||
// Validate filename to prevent command injection
|
||||
validateShellSafePath($this->fileName, 'proxy configuration filename');
|
||||
|
||||
if (data_get($this->parameters, 'server_uuid')) {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(data_get($this->parameters, 'server_uuid'))->first();
|
||||
}
|
||||
@@ -65,8 +69,10 @@ class NewDynamicConfiguration extends Component
|
||||
}
|
||||
$proxy_path = $this->server->proxyPath();
|
||||
$file = "{$proxy_path}/dynamic/{$this->fileName}";
|
||||
$escapedFile = escapeshellarg($file);
|
||||
|
||||
if ($this->newFile) {
|
||||
$exists = instant_remote_process(["test -f $file && echo 1 || echo 0"], $this->server);
|
||||
$exists = instant_remote_process(["test -f {$escapedFile} && echo 1 || echo 0"], $this->server);
|
||||
if ($exists == 1) {
|
||||
$this->dispatch('error', 'File already exists');
|
||||
|
||||
@@ -80,7 +86,7 @@ class NewDynamicConfiguration extends Component
|
||||
}
|
||||
$base64_value = base64_encode($this->value);
|
||||
instant_remote_process([
|
||||
"echo '{$base64_value}' | base64 -d | tee {$file} > /dev/null",
|
||||
"echo '{$base64_value}' | base64 -d | tee {$escapedFile} > /dev/null",
|
||||
], $this->server);
|
||||
if ($proxy_type === 'CADDY') {
|
||||
$this->server->reloadCaddy();
|
||||
|
||||
@@ -206,6 +206,9 @@ class ValidateAndInstall extends Component
|
||||
if (! $proxyShouldRun) {
|
||||
return;
|
||||
}
|
||||
// Ensure networks exist BEFORE dispatching async proxy startup
|
||||
// This prevents race condition where proxy tries to start before networks are created
|
||||
instant_remote_process(ensureProxyNetworksExist($this->server)->toArray(), $this->server, false);
|
||||
StartProxy::dispatch($this->server);
|
||||
} else {
|
||||
$requiredDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.');
|
||||
|
||||
@@ -1035,7 +1035,7 @@ class Application extends BaseModel
|
||||
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = base64_encode($this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->custom_network_aliases.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect.$this->custom_nginx_configuration.$this->settings->use_build_secrets);
|
||||
$newConfigHash = base64_encode($this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->custom_network_aliases.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect.$this->custom_nginx_configuration.$this->settings->use_build_secrets.$this->settings->inject_build_args_to_dockerfile.$this->settings->include_source_commit_in_build);
|
||||
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get(['value', 'is_multiline', 'is_literal', 'is_buildtime', 'is_runtime'])->sort());
|
||||
} else {
|
||||
|
||||
@@ -190,11 +190,11 @@ class EnvironmentVariable extends BaseModel
|
||||
return $environment_variable;
|
||||
}
|
||||
foreach ($sharedEnvsFound as $sharedEnv) {
|
||||
$type = str($sharedEnv)->match('/(.*?)\./');
|
||||
$type = str($sharedEnv)->trim()->match('/(.*?)\./');
|
||||
if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||
continue;
|
||||
}
|
||||
$variable = str($sharedEnv)->match('/\.(.*)/');
|
||||
$variable = str($sharedEnv)->trim()->match('/\.(.*)/');
|
||||
if ($type->value() === 'environment') {
|
||||
$id = $resource->environment->id;
|
||||
} elseif ($type->value() === 'project') {
|
||||
@@ -231,7 +231,7 @@ class EnvironmentVariable extends BaseModel
|
||||
$environment_variable = trim($environment_variable);
|
||||
$type = str($environment_variable)->after('{{')->before('.')->value;
|
||||
if (str($environment_variable)->startsWith('{{'.$type) && str($environment_variable)->endsWith('}}')) {
|
||||
return encrypt((string) str($environment_variable)->replace(' ', ''));
|
||||
return encrypt($environment_variable);
|
||||
}
|
||||
|
||||
return encrypt($environment_variable);
|
||||
|
||||
@@ -61,9 +61,14 @@ class LocalFileVolume extends BaseModel
|
||||
$path = $path->after('.');
|
||||
$path = $workdir.$path;
|
||||
}
|
||||
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
||||
|
||||
// Validate and escape path to prevent command injection
|
||||
validateShellSafePath($path, 'storage path');
|
||||
$escapedPath = escapeshellarg($path);
|
||||
|
||||
$isFile = instant_remote_process(["test -f {$escapedPath} && echo OK || echo NOK"], $server);
|
||||
if ($isFile === 'OK') {
|
||||
$content = instant_remote_process(["cat $path"], $server, false);
|
||||
$content = instant_remote_process(["cat {$escapedPath}"], $server, false);
|
||||
// Check if content contains binary data by looking for null bytes or non-printable characters
|
||||
if (str_contains($content, "\0") || preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', $content)) {
|
||||
$content = '[binary file]';
|
||||
@@ -91,14 +96,19 @@ class LocalFileVolume extends BaseModel
|
||||
$path = $path->after('.');
|
||||
$path = $workdir.$path;
|
||||
}
|
||||
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
||||
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
|
||||
|
||||
// Validate and escape path to prevent command injection
|
||||
validateShellSafePath($path, 'storage path');
|
||||
$escapedPath = escapeshellarg($path);
|
||||
|
||||
$isFile = instant_remote_process(["test -f {$escapedPath} && echo OK || echo NOK"], $server);
|
||||
$isDir = instant_remote_process(["test -d {$escapedPath} && echo OK || echo NOK"], $server);
|
||||
if ($path && $path != '/' && $path != '.' && $path != '..') {
|
||||
if ($isFile === 'OK') {
|
||||
$commands->push("rm -rf $path > /dev/null 2>&1 || true");
|
||||
$commands->push("rm -rf {$escapedPath} > /dev/null 2>&1 || true");
|
||||
} elseif ($isDir === 'OK') {
|
||||
$commands->push("rm -rf $path > /dev/null 2>&1 || true");
|
||||
$commands->push("rmdir $path > /dev/null 2>&1 || true");
|
||||
$commands->push("rm -rf {$escapedPath} > /dev/null 2>&1 || true");
|
||||
$commands->push("rmdir {$escapedPath} > /dev/null 2>&1 || true");
|
||||
}
|
||||
}
|
||||
if ($commands->count() > 0) {
|
||||
@@ -135,10 +145,15 @@ class LocalFileVolume extends BaseModel
|
||||
$path = $path->after('.');
|
||||
$path = $workdir.$path;
|
||||
}
|
||||
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
||||
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
|
||||
|
||||
// Validate and escape path to prevent command injection
|
||||
validateShellSafePath($path, 'storage path');
|
||||
$escapedPath = escapeshellarg($path);
|
||||
|
||||
$isFile = instant_remote_process(["test -f {$escapedPath} && echo OK || echo NOK"], $server);
|
||||
$isDir = instant_remote_process(["test -d {$escapedPath} && echo OK || echo NOK"], $server);
|
||||
if ($isFile === 'OK' && $this->is_directory) {
|
||||
$content = instant_remote_process(["cat $path"], $server, false);
|
||||
$content = instant_remote_process(["cat {$escapedPath}"], $server, false);
|
||||
$this->is_directory = false;
|
||||
$this->content = $content;
|
||||
$this->save();
|
||||
@@ -151,8 +166,8 @@ class LocalFileVolume extends BaseModel
|
||||
throw new \Exception('The following file is a directory on the server, but you are trying to mark it as a file. <br><br>Please delete the directory on the server or mark it as directory.');
|
||||
}
|
||||
instant_remote_process([
|
||||
"rm -fr $path",
|
||||
"touch $path",
|
||||
"rm -fr {$escapedPath}",
|
||||
"touch {$escapedPath}",
|
||||
], $server, false);
|
||||
FileStorageChanged::dispatch(data_get($server, 'team_id'));
|
||||
}
|
||||
@@ -161,19 +176,19 @@ class LocalFileVolume extends BaseModel
|
||||
$chown = data_get($this, 'chown');
|
||||
if ($content) {
|
||||
$content = base64_encode($content);
|
||||
$commands->push("echo '$content' | base64 -d | tee $path > /dev/null");
|
||||
$commands->push("echo '$content' | base64 -d | tee {$escapedPath} > /dev/null");
|
||||
} else {
|
||||
$commands->push("touch $path");
|
||||
$commands->push("touch {$escapedPath}");
|
||||
}
|
||||
$commands->push("chmod +x $path");
|
||||
$commands->push("chmod +x {$escapedPath}");
|
||||
if ($chown) {
|
||||
$commands->push("chown $chown $path");
|
||||
$commands->push("chown $chown {$escapedPath}");
|
||||
}
|
||||
if ($chmod) {
|
||||
$commands->push("chmod $chmod $path");
|
||||
$commands->push("chmod $chmod {$escapedPath}");
|
||||
}
|
||||
} elseif ($isDir === 'NOK' && $this->is_directory) {
|
||||
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
|
||||
$commands->push("mkdir -p {$escapedPath} > /dev/null 2>&1 || true");
|
||||
}
|
||||
|
||||
return instant_remote_process($commands, $server);
|
||||
|
||||
@@ -26,6 +26,7 @@ class EnvVarInput extends Component
|
||||
public bool $disabled = false,
|
||||
public bool $readonly = false,
|
||||
public ?string $helper = null,
|
||||
public bool $allowToPeak = true,
|
||||
public string $defaultClass = 'input',
|
||||
public string $autocomplete = 'off',
|
||||
public ?int $minlength = null,
|
||||
@@ -72,6 +73,10 @@ class EnvVarInput extends Component
|
||||
$this->name = $this->modelBinding !== 'null' ? $this->modelBinding : (string) $this->id;
|
||||
}
|
||||
|
||||
if ($this->type === 'password') {
|
||||
$this->defaultClass = $this->defaultClass.' pr-[2.8rem]';
|
||||
}
|
||||
|
||||
$this->scopeUrls = [
|
||||
'team' => route('shared-variables.team.index'),
|
||||
'project' => route('shared-variables.project.index'),
|
||||
|
||||
Reference in new issue
Block a user