Merge remote-tracking branch 'origin/next' into audit-policies

This commit is contained in:
Andras Bacsai
2026-06-04 10:18:54 +02:00
148 changed files with 5987 additions and 1101 deletions
+13 -11
View File
@@ -86,7 +86,7 @@ function format_docker_command_output_to_json($rawOutput): Collection
return $outputLines
->reject(fn ($line) => empty($line))
->map(fn ($outputLine) => json_decode($outputLine, true, flags: JSON_THROW_ON_ERROR));
} catch (\Throwable) {
} catch (Throwable) {
return collect([]);
}
}
@@ -123,7 +123,7 @@ function format_docker_envs_to_json($rawOutput)
return [$env[0] => $env[1]];
});
} catch (\Throwable) {
} catch (Throwable) {
return collect([]);
}
}
@@ -255,12 +255,12 @@ function defaultLabels($id, $name, string $projectName, string $resourceName, st
function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
{
if ($resource->getMorphClass() === \App\Models\ServiceApplication::class) {
if ($resource->getMorphClass() === ServiceApplication::class) {
$uuid = data_get($resource, 'uuid');
$server = data_get($resource, 'service.server');
$environment_variables = data_get($resource, 'service.environment_variables');
$type = $resource->serviceType();
} elseif ($resource->getMorphClass() === \App\Models\Application::class) {
} elseif ($resource->getMorphClass() === Application::class) {
$uuid = data_get($resource, 'uuid');
$server = data_get($resource, 'destination.server');
$environment_variables = data_get($resource, 'environment_variables');
@@ -641,7 +641,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
}
}
}
} catch (\Throwable) {
} catch (Throwable) {
continue;
}
}
@@ -1000,6 +1000,7 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null)
'--ulimit',
'--device',
'--shm-size',
'--dns',
]);
$mapping = collect([
'--cap-add' => 'cap_add',
@@ -1013,6 +1014,7 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null)
'--ip' => 'ip',
'--ip6' => 'ip6',
'--shm-size' => 'shm_size',
'--dns' => 'dns',
'--gpus' => 'gpus',
'--hostname' => 'hostname',
'--entrypoint' => 'entrypoint',
@@ -1219,7 +1221,7 @@ function validateComposeFile(string $compose, int $server_id): string|Throwable
$server = Server::ownedByCurrentTeam()->find($server_id);
try {
if (! $server) {
throw new \Exception('Server not found');
throw new Exception('Server not found');
}
$yaml_compose = Yaml::parse($compose);
@@ -1235,7 +1237,7 @@ function validateComposeFile(string $compose, int $server_id): string|Throwable
], $server);
return 'OK';
} catch (\Throwable $e) {
} catch (Throwable $e) {
return $e->getMessage();
} finally {
if (filled($server)) {
@@ -1351,10 +1353,10 @@ function escapeBashDoubleQuoted(?string $value): string
* Generate Docker build arguments from environment variables collection
* Returns only keys (no values) since values are sourced from environment via export
*
* @param \Illuminate\Support\Collection|array $variables Collection of variables with 'key', 'value', and optionally 'is_multiline'
* @return \Illuminate\Support\Collection Collection of formatted --build-arg strings (keys only)
* @param Collection|array $variables Collection of variables with 'key', 'value', and optionally 'is_multiline'
* @return Collection Collection of formatted --build-arg strings (keys only)
*/
function generateDockerBuildArgs($variables): \Illuminate\Support\Collection
function generateDockerBuildArgs($variables): Collection
{
$variables = collect($variables);
@@ -1369,7 +1371,7 @@ function generateDockerBuildArgs($variables): \Illuminate\Support\Collection
/**
* Generate Docker environment flags from environment variables collection
*
* @param \Illuminate\Support\Collection|array $variables Collection of variables with 'key', 'value', and optionally 'is_multiline'
* @param Collection|array $variables Collection of variables with 'key', 'value', and optionally 'is_multiline'
* @return string Space-separated environment flags
*/
function generateDockerEnvFlags($variables): string
+17 -10
View File
@@ -4,6 +4,7 @@ use App\Models\GithubApp;
use App\Models\GitlabApp;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Lcobucci\JWT\Encoding\ChainedFormatter;
@@ -20,7 +21,7 @@ function generateGithubToken(GithubApp $source, string $type)
$timeDiff = abs($serverTime->diffInSeconds($githubTime));
if ($timeDiff > 50) {
throw new \Exception(
throw new Exception(
'System time is out of sync with GitHub API time:<br>'.
'- System time: '.$serverTime->format('Y-m-d H:i:s').' UTC<br>'.
'- GitHub time: '.$githubTime->format('Y-m-d H:i:s').' UTC<br>'.
@@ -60,7 +61,7 @@ function generateGithubToken(GithubApp $source, string $type)
return $response->json()['token'];
})(),
default => throw new \InvalidArgumentException("Unsupported token type: {$type}")
default => throw new InvalidArgumentException("Unsupported token type: {$type}")
};
}
@@ -77,11 +78,11 @@ function generateGithubJwt(GithubApp $source)
function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $method = 'get', ?array $data = null, bool $throwError = true)
{
if (is_null($source)) {
throw new \Exception('Source is required for API calls');
throw new Exception('Source is required for API calls');
}
if ($source->getMorphClass() !== GithubApp::class) {
throw new \InvalidArgumentException("Unsupported source type: {$source->getMorphClass()}");
throw new InvalidArgumentException("Unsupported source type: {$source->getMorphClass()}");
}
if ($source->is_public) {
@@ -100,7 +101,7 @@ function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $m
$errorMessage = data_get($response->json(), 'message', 'no error message found');
$remainingCalls = $response->header('X-RateLimit-Remaining', '0');
throw new \Exception(
throw new Exception(
'GitHub API call failed:<br>'.
"Error: {$errorMessage}<br>".
'Rate Limit Status:<br>'.
@@ -116,13 +117,19 @@ function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $m
];
}
function getInstallationPath(GithubApp $source)
function getInstallationPath(GithubApp $source): string
{
$github = GithubApp::where('uuid', $source->uuid)->first();
$name = str(Str::kebab($github->name));
$installation_path = $github->html_url === 'https://github.com' ? 'apps' : 'github-apps';
$name = str(Str::kebab($source->name));
$installation_path = $source->html_url === 'https://github.com' ? 'apps' : 'github-apps';
$state = Str::random(64);
return "$github->html_url/$installation_path/$name/installations/new";
Cache::put('github-app-setup-state:'.hash('sha256', $state), [
'action' => 'install',
'github_app_id' => $source->id,
'team_id' => $source->team_id,
], now()->addMinutes(60));
return "$source->html_url/$installation_path/$name/installations/new?".http_build_query(['state' => $state]);
}
function getPermissionsPath(GithubApp $source)
+5 -4
View File
@@ -4,6 +4,7 @@ use App\Actions\Proxy\SaveProxyConfiguration;
use App\Enums\ProxyTypes;
use App\Models\Application;
use App\Models\Server;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Yaml\Yaml;
@@ -137,7 +138,7 @@ function connectProxyToNetworks(Server $server)
* This must be called BEFORE docker compose up since the compose file declares networks as external.
*
* @param Server $server The server to ensure networks on
* @return \Illuminate\Support\Collection Commands to create networks if they don't exist
* @return Collection Commands to create networks if they don't exist
*/
function ensureProxyNetworksExist(Server $server)
{
@@ -215,7 +216,7 @@ function extractCustomProxyCommands(Server $server, string $existing_config): ar
$custom_commands[] = $command;
}
}
} catch (\Exception $e) {
} catch (Exception $e) {
// If we can't parse the config, return empty array
// Silently fail to avoid breaking the proxy regeneration
}
@@ -436,7 +437,7 @@ function getExactTraefikVersionFromContainer(Server $server): ?string
Log::debug("getExactTraefikVersionFromContainer: Server '{$server->name}' (ID: {$server->id}) - Could not detect exact version");
return null;
} catch (\Exception $e) {
} catch (Exception $e) {
Log::error("getExactTraefikVersionFromContainer: Server '{$server->name}' (ID: {$server->id}) - Error: ".$e->getMessage());
return null;
@@ -483,7 +484,7 @@ function getTraefikVersionFromDockerCompose(Server $server): ?string
Log::debug("getTraefikVersionFromDockerCompose: Server '{$server->name}' (ID: {$server->id}) - Image format doesn't match expected pattern: {$image}");
return null;
} catch (\Exception $e) {
} catch (Exception $e) {
Log::error("getTraefikVersionFromDockerCompose: Server '{$server->name}' (ID: {$server->id}) - Error: ".$e->getMessage());
return null;
+9 -2
View File
@@ -200,6 +200,7 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
}
$application = Application::find(data_get($application_deployment_queue, 'application_id'));
$is_debug_enabled = data_get($application, 'settings.is_debug_enabled');
$serverTimezone = getServerTimezone(data_get($application, 'destination.server'));
// Members should never see debug logs, even if an admin enabled debug mode
if ($is_debug_enabled && auth()->check() && auth()->user()->isMember()) {
@@ -245,8 +246,14 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
return $formatted
->sortBy(fn ($i) => data_get($i, 'order'))
->map(function ($i) {
data_set($i, 'timestamp', Carbon::parse(data_get($i, 'timestamp'))->format('Y-M-d H:i:s.u'));
->map(function ($i) use ($serverTimezone) {
$timestamp = Carbon::parse(data_get($i, 'timestamp'));
try {
$timestamp->setTimezone($serverTimezone);
} catch (Exception) {
$timestamp->setTimezone('UTC');
}
data_set($i, 'timestamp', $timestamp->format('Y-M-d H:i:s.u'));
return $i;
})
+3 -3
View File
@@ -1865,15 +1865,15 @@ function isBase64Encoded($strValue)
{
return base64_encode(base64_decode($strValue, true)) === $strValue;
}
function customApiValidator(Collection|array $item, array $rules)
function customApiValidator(Collection|array $item, array $rules, array $messages = [])
{
if (is_array($item)) {
$item = collect($item);
}
return Validator::make($item->toArray(), $rules, [
return Validator::make($item->toArray(), $rules, array_merge([
'required' => 'This field is required.',
]);
], $messages));
}
function parseDockerComposeFile(Service|Application $resource, bool $isNew = false, int $pull_request_id = 0, ?int $preview_id = null)
{