Merge branch 'next' into fix-migration
This commit is contained in:
@@ -47,7 +47,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/prod/Dockerfile
|
file: docker/production/Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/prod/Dockerfile
|
file: docker/production/Dockerfile
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
|
|||||||
4
.github/workflows/coolify-staging-build.yml
vendored
4
.github/workflows/coolify-staging-build.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/prod/Dockerfile
|
file: docker/production/Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
@@ -75,7 +75,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/prod/Dockerfile
|
file: docker/production/Dockerfile
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
use App\Events\ProxyStarted;
|
use App\Events\ProxyStarted;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
@@ -37,11 +38,16 @@ class StartProxy
|
|||||||
"echo 'Successfully started coolify-proxy.'",
|
"echo 'Successfully started coolify-proxy.'",
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$caddfile = 'import /dynamic/*.caddy';
|
if (isDev()) {
|
||||||
|
if ($proxyType === ProxyTypes::CADDY->value) {
|
||||||
|
$proxy_path = '/data/coolify/proxy/caddy';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$caddyfile = 'import /dynamic/*.caddy';
|
||||||
$commands = $commands->merge([
|
$commands = $commands->merge([
|
||||||
"mkdir -p $proxy_path/dynamic",
|
"mkdir -p $proxy_path/dynamic",
|
||||||
"cd $proxy_path",
|
"cd $proxy_path",
|
||||||
"echo '$caddfile' > $proxy_path/dynamic/Caddyfile",
|
"echo '$caddyfile' > $proxy_path/dynamic/Caddyfile",
|
||||||
"echo 'Creating required Docker Compose file.'",
|
"echo 'Creating required Docker Compose file.'",
|
||||||
"echo 'Pulling docker image.'",
|
"echo 'Pulling docker image.'",
|
||||||
'docker compose pull',
|
'docker compose pull',
|
||||||
|
|||||||
@@ -76,7 +76,5 @@ class Dev extends Command
|
|||||||
} else {
|
} else {
|
||||||
echo "Instance already initialized.\n";
|
echo "Instance already initialized.\n";
|
||||||
}
|
}
|
||||||
// Set permissions
|
|
||||||
Process::run(['chmod', '-R', 'o+rwx', '.']);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class Horizon extends Command
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if (config('constants.horizon.is_horizon_enabled')) {
|
if (config('constants.horizon.is_horizon_enabled')) {
|
||||||
$this->info('[x]: Horizon is enabled. Starting.');
|
$this->info('Horizon is enabled on this server.');
|
||||||
$this->call('horizon');
|
$this->call('horizon');
|
||||||
exit(0);
|
exit(0);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -55,10 +55,8 @@ class Init extends Command
|
|||||||
} else {
|
} else {
|
||||||
$this->cleanup_in_progress_application_deployments();
|
$this->cleanup_in_progress_application_deployments();
|
||||||
}
|
}
|
||||||
echo "[3]: Cleanup Redis keys.\n";
|
|
||||||
$this->call('cleanup:redis');
|
$this->call('cleanup:redis');
|
||||||
|
|
||||||
echo "[4]: Cleanup stucked resources.\n";
|
|
||||||
$this->call('cleanup:stucked-resources');
|
$this->call('cleanup:stucked-resources');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -114,7 +112,6 @@ class Init extends Command
|
|||||||
|
|
||||||
private function optimize()
|
private function optimize()
|
||||||
{
|
{
|
||||||
echo "[1]: Optimizing Laravel (caching config, routes, views).\n";
|
|
||||||
Artisan::call('optimize:clear');
|
Artisan::call('optimize:clear');
|
||||||
Artisan::call('optimize');
|
Artisan::call('optimize');
|
||||||
}
|
}
|
||||||
@@ -189,7 +186,6 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($commands->isNotEmpty()) {
|
if ($commands->isNotEmpty()) {
|
||||||
echo "Cleaning up unused networks from coolify proxy\n";
|
|
||||||
remote_process(command: $commands, type: ActivityTypes::INLINE->value, server: $server, ignore_errors: false);
|
remote_process(command: $commands, type: ActivityTypes::INLINE->value, server: $server, ignore_errors: false);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
@@ -232,15 +228,14 @@ class Init extends Command
|
|||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
$do_not_track = data_get($settings, 'do_not_track');
|
$do_not_track = data_get($settings, 'do_not_track');
|
||||||
if ($do_not_track == true) {
|
if ($do_not_track == true) {
|
||||||
echo "[2]: Skipping sending live signal as do_not_track is enabled\n";
|
echo "Do_not_track is enabled\n";
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version");
|
Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version");
|
||||||
echo "[2]: Sending live signal!\n";
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
echo "[2]: Error in sending live signal: {$e->getMessage()}\n";
|
echo "Error in sending live signal: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +248,6 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
$queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get();
|
$queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get();
|
||||||
foreach ($queued_inprogress_deployments as $deployment) {
|
foreach ($queued_inprogress_deployments as $deployment) {
|
||||||
echo "Cleaning up deployment: {$deployment->id}\n";
|
|
||||||
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
|
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
|
||||||
$deployment->save();
|
$deployment->save();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class Scheduler extends Command
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if (config('constants.horizon.is_scheduler_enabled')) {
|
if (config('constants.horizon.is_scheduler_enabled')) {
|
||||||
$this->info('[x]: Scheduler is enabled. Starting.');
|
$this->info('Scheduler is enabled on this server.');
|
||||||
$this->call('schedule:work');
|
$this->call('schedule:work');
|
||||||
exit(0);
|
exit(0);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -25,26 +25,24 @@ class ApplicationsController extends Controller
|
|||||||
{
|
{
|
||||||
private function removeSensitiveData($application)
|
private function removeSensitiveData($application)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->currentAccessToken();
|
|
||||||
$application->makeHidden([
|
$application->makeHidden([
|
||||||
'id',
|
'id',
|
||||||
]);
|
]);
|
||||||
if ($token->can('view:sensitive')) {
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
return serializeApiResponse($application);
|
$application->makeHidden([
|
||||||
|
'custom_labels',
|
||||||
|
'dockerfile',
|
||||||
|
'docker_compose',
|
||||||
|
'docker_compose_raw',
|
||||||
|
'manual_webhook_secret_bitbucket',
|
||||||
|
'manual_webhook_secret_gitea',
|
||||||
|
'manual_webhook_secret_github',
|
||||||
|
'manual_webhook_secret_gitlab',
|
||||||
|
'private_key_id',
|
||||||
|
'value',
|
||||||
|
'real_value',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
$application->makeHidden([
|
|
||||||
'custom_labels',
|
|
||||||
'dockerfile',
|
|
||||||
'docker_compose',
|
|
||||||
'docker_compose_raw',
|
|
||||||
'manual_webhook_secret_bitbucket',
|
|
||||||
'manual_webhook_secret_gitea',
|
|
||||||
'manual_webhook_secret_github',
|
|
||||||
'manual_webhook_secret_gitlab',
|
|
||||||
'private_key_id',
|
|
||||||
'value',
|
|
||||||
'real_value',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return serializeApiResponse($application);
|
return serializeApiResponse($application);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,26 +19,23 @@ class DatabasesController extends Controller
|
|||||||
{
|
{
|
||||||
private function removeSensitiveData($database)
|
private function removeSensitiveData($database)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->currentAccessToken();
|
|
||||||
$database->makeHidden([
|
$database->makeHidden([
|
||||||
'id',
|
'id',
|
||||||
'laravel_through_key',
|
'laravel_through_key',
|
||||||
]);
|
]);
|
||||||
if ($token->can('view:sensitive')) {
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
return serializeApiResponse($database);
|
$database->makeHidden([
|
||||||
|
'internal_db_url',
|
||||||
|
'external_db_url',
|
||||||
|
'postgres_password',
|
||||||
|
'dragonfly_password',
|
||||||
|
'redis_password',
|
||||||
|
'mongo_initdb_root_password',
|
||||||
|
'keydb_password',
|
||||||
|
'clickhouse_admin_password',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$database->makeHidden([
|
|
||||||
'internal_db_url',
|
|
||||||
'external_db_url',
|
|
||||||
'postgres_password',
|
|
||||||
'dragonfly_password',
|
|
||||||
'redis_password',
|
|
||||||
'mongo_initdb_root_password',
|
|
||||||
'keydb_password',
|
|
||||||
'clickhouse_admin_password',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return serializeApiResponse($database);
|
return serializeApiResponse($database);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,8 +208,9 @@ class DatabasesController extends Controller
|
|||||||
'mongo_conf' => ['type' => 'string', 'description' => 'Mongo conf'],
|
'mongo_conf' => ['type' => 'string', 'description' => 'Mongo conf'],
|
||||||
'mongo_initdb_root_username' => ['type' => 'string', 'description' => 'Mongo initdb root username'],
|
'mongo_initdb_root_username' => ['type' => 'string', 'description' => 'Mongo initdb root username'],
|
||||||
'mongo_initdb_root_password' => ['type' => 'string', 'description' => 'Mongo initdb root password'],
|
'mongo_initdb_root_password' => ['type' => 'string', 'description' => 'Mongo initdb root password'],
|
||||||
'mongo_initdb_init_database' => ['type' => 'string', 'description' => 'Mongo initdb init database'],
|
'mongo_initdb_database' => ['type' => 'string', 'description' => 'Mongo initdb init database'],
|
||||||
'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'],
|
'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'],
|
||||||
|
'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'],
|
||||||
'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'],
|
'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'],
|
||||||
'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'],
|
'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'],
|
||||||
'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'],
|
'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'],
|
||||||
@@ -241,7 +239,7 @@ class DatabasesController extends Controller
|
|||||||
)]
|
)]
|
||||||
public function update_by_uuid(Request $request)
|
public function update_by_uuid(Request $request)
|
||||||
{
|
{
|
||||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalidTokenResponse();
|
return invalidTokenResponse();
|
||||||
@@ -413,12 +411,12 @@ class DatabasesController extends Controller
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'standalone-mongodb':
|
case 'standalone-mongodb':
|
||||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database'];
|
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database'];
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
'mongo_conf' => 'string',
|
'mongo_conf' => 'string',
|
||||||
'mongo_initdb_root_username' => 'string',
|
'mongo_initdb_root_username' => 'string',
|
||||||
'mongo_initdb_root_password' => 'string',
|
'mongo_initdb_root_password' => 'string',
|
||||||
'mongo_initdb_init_database' => 'string',
|
'mongo_initdb_database' => 'string',
|
||||||
]);
|
]);
|
||||||
if ($request->has('mongo_conf')) {
|
if ($request->has('mongo_conf')) {
|
||||||
if (! isBase64Encoded($request->mongo_conf)) {
|
if (! isBase64Encoded($request->mongo_conf)) {
|
||||||
@@ -443,9 +441,10 @@ class DatabasesController extends Controller
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case 'standalone-mysql':
|
case 'standalone-mysql':
|
||||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
'mysql_root_password' => 'string',
|
'mysql_root_password' => 'string',
|
||||||
|
'mysql_password' => 'string',
|
||||||
'mysql_user' => 'string',
|
'mysql_user' => 'string',
|
||||||
'mysql_database' => 'string',
|
'mysql_database' => 'string',
|
||||||
'mysql_conf' => 'string',
|
'mysql_conf' => 'string',
|
||||||
@@ -909,6 +908,7 @@ class DatabasesController extends Controller
|
|||||||
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
|
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
|
||||||
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
||||||
'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'],
|
'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'],
|
||||||
|
'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'],
|
||||||
'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'],
|
'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'],
|
||||||
'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'],
|
'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'],
|
||||||
'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'],
|
'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'],
|
||||||
@@ -1013,7 +1013,7 @@ class DatabasesController extends Controller
|
|||||||
|
|
||||||
public function create_database(Request $request, NewDatabaseTypes $type)
|
public function create_database(Request $request, NewDatabaseTypes $type)
|
||||||
{
|
{
|
||||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||||
|
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
@@ -1220,9 +1220,10 @@ class DatabasesController extends Controller
|
|||||||
|
|
||||||
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
||||||
} elseif ($type === NewDatabaseTypes::MYSQL) {
|
} elseif ($type === NewDatabaseTypes::MYSQL) {
|
||||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
'mysql_root_password' => 'string',
|
'mysql_root_password' => 'string',
|
||||||
|
'mysql_password' => 'string',
|
||||||
'mysql_user' => 'string',
|
'mysql_user' => 'string',
|
||||||
'mysql_database' => 'string',
|
'mysql_database' => 'string',
|
||||||
'mysql_conf' => 'string',
|
'mysql_conf' => 'string',
|
||||||
@@ -1456,12 +1457,12 @@ class DatabasesController extends Controller
|
|||||||
|
|
||||||
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
||||||
} elseif ($type === NewDatabaseTypes::MONGODB) {
|
} elseif ($type === NewDatabaseTypes::MONGODB) {
|
||||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database'];
|
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database'];
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
'mongo_conf' => 'string',
|
'mongo_conf' => 'string',
|
||||||
'mongo_initdb_root_username' => 'string',
|
'mongo_initdb_root_username' => 'string',
|
||||||
'mongo_initdb_root_password' => 'string',
|
'mongo_initdb_root_password' => 'string',
|
||||||
'mongo_initdb_init_database' => 'string',
|
'mongo_initdb_database' => 'string',
|
||||||
]);
|
]);
|
||||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
if ($validator->fails() || ! empty($extraFields)) {
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
|||||||
@@ -16,15 +16,12 @@ class DeployController extends Controller
|
|||||||
{
|
{
|
||||||
private function removeSensitiveData($deployment)
|
private function removeSensitiveData($deployment)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->currentAccessToken();
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
if ($token->can('view:sensitive')) {
|
$deployment->makeHidden([
|
||||||
return serializeApiResponse($deployment);
|
'logs',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$deployment->makeHidden([
|
|
||||||
'logs',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return serializeApiResponse($deployment);
|
return serializeApiResponse($deployment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,13 +11,11 @@ class SecurityController extends Controller
|
|||||||
{
|
{
|
||||||
private function removeSensitiveData($team)
|
private function removeSensitiveData($team)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->currentAccessToken();
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
if ($token->can('view:sensitive')) {
|
$team->makeHidden([
|
||||||
return serializeApiResponse($team);
|
'private_key',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
$team->makeHidden([
|
|
||||||
'private_key',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return serializeApiResponse($team);
|
return serializeApiResponse($team);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,25 +19,22 @@ class ServersController extends Controller
|
|||||||
{
|
{
|
||||||
private function removeSensitiveDataFromSettings($settings)
|
private function removeSensitiveDataFromSettings($settings)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->currentAccessToken();
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
if ($token->can('view:sensitive')) {
|
$settings = $settings->makeHidden([
|
||||||
return serializeApiResponse($settings);
|
'sentinel_token',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
$settings = $settings->makeHidden([
|
|
||||||
'sentinel_token',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return serializeApiResponse($settings);
|
return serializeApiResponse($settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function removeSensitiveData($server)
|
private function removeSensitiveData($server)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->currentAccessToken();
|
|
||||||
$server->makeHidden([
|
$server->makeHidden([
|
||||||
'id',
|
'id',
|
||||||
]);
|
]);
|
||||||
if ($token->can('view:sensitive')) {
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
return serializeApiResponse($server);
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
return serializeApiResponse($server);
|
return serializeApiResponse($server);
|
||||||
|
|||||||
@@ -18,19 +18,16 @@ class ServicesController extends Controller
|
|||||||
{
|
{
|
||||||
private function removeSensitiveData($service)
|
private function removeSensitiveData($service)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->currentAccessToken();
|
|
||||||
$service->makeHidden([
|
$service->makeHidden([
|
||||||
'id',
|
'id',
|
||||||
]);
|
]);
|
||||||
if ($token->can('view:sensitive')) {
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
return serializeApiResponse($service);
|
$service->makeHidden([
|
||||||
|
'docker_compose_raw',
|
||||||
|
'docker_compose',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$service->makeHidden([
|
|
||||||
'docker_compose_raw',
|
|
||||||
'docker_compose',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return serializeApiResponse($service);
|
return serializeApiResponse($service);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,20 +10,18 @@ class TeamController extends Controller
|
|||||||
{
|
{
|
||||||
private function removeSensitiveData($team)
|
private function removeSensitiveData($team)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->currentAccessToken();
|
|
||||||
$team->makeHidden([
|
$team->makeHidden([
|
||||||
'custom_server_limit',
|
'custom_server_limit',
|
||||||
'pivot',
|
'pivot',
|
||||||
]);
|
]);
|
||||||
if ($token->can('view:sensitive')) {
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
return serializeApiResponse($team);
|
$team->makeHidden([
|
||||||
|
'smtp_username',
|
||||||
|
'smtp_password',
|
||||||
|
'resend_api_key',
|
||||||
|
'telegram_token',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
$team->makeHidden([
|
|
||||||
'smtp_username',
|
|
||||||
'smtp_password',
|
|
||||||
'resend_api_key',
|
|
||||||
'telegram_token',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return serializeApiResponse($team);
|
return serializeApiResponse($team);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -463,7 +463,7 @@ class Github extends Controller
|
|||||||
$private_key = data_get($data, 'pem');
|
$private_key = data_get($data, 'pem');
|
||||||
$webhook_secret = data_get($data, 'webhook_secret');
|
$webhook_secret = data_get($data, 'webhook_secret');
|
||||||
$private_key = PrivateKey::create([
|
$private_key = PrivateKey::create([
|
||||||
'name' => $slug,
|
'name' => "github-app-{$slug}",
|
||||||
'private_key' => $private_key,
|
'private_key' => $private_key,
|
||||||
'team_id' => $github_app->team_id,
|
'team_id' => $github_app->team_id,
|
||||||
'is_git_related' => true,
|
'is_git_related' => true,
|
||||||
|
|||||||
@@ -69,5 +69,7 @@ class Kernel extends HttpKernel
|
|||||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||||
'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
|
'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
|
||||||
'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
|
'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
|
||||||
|
'api.ability' => \App\Http\Middleware\ApiAbility::class,
|
||||||
|
'api.sensitive' => \App\Http\Middleware\ApiSensitiveData::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
27
app/Http/Middleware/ApiAbility.php
Normal file
27
app/Http/Middleware/ApiAbility.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;
|
||||||
|
|
||||||
|
class ApiAbility extends CheckForAnyAbility
|
||||||
|
{
|
||||||
|
public function handle($request, $next, ...$abilities)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($request->user()->tokenCan('root')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::handle($request, $next, ...$abilities);
|
||||||
|
} catch (\Illuminate\Auth\AuthenticationException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Unauthenticated.',
|
||||||
|
], 401);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Missing required permissions: '.implode(', ', $abilities),
|
||||||
|
], 403);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
app/Http/Middleware/ApiSensitiveData.php
Normal file
21
app/Http/Middleware/ApiSensitiveData.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ApiSensitiveData
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next)
|
||||||
|
{
|
||||||
|
$token = $request->user()->currentAccessToken();
|
||||||
|
|
||||||
|
// Allow access to sensitive data if token has root or read:sensitive permission
|
||||||
|
$request->attributes->add([
|
||||||
|
'can_read_sensitive' => $token->can('root') || $token->can('read:sensitive'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
|
|
||||||
class IgnoreReadOnlyApiToken
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Handle an incoming request.
|
|
||||||
*
|
|
||||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
|
||||||
*/
|
|
||||||
public function handle(Request $request, Closure $next): Response
|
|
||||||
{
|
|
||||||
$token = auth()->user()->currentAccessToken();
|
|
||||||
if ($token->can('*')) {
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
if ($token->can('read-only')) {
|
|
||||||
return response()->json(['message' => 'You are not allowed to perform this action.'], 403);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
|
|
||||||
class OnlyRootApiToken
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Handle an incoming request.
|
|
||||||
*
|
|
||||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
|
||||||
*/
|
|
||||||
public function handle(Request $request, Closure $next): Response
|
|
||||||
{
|
|
||||||
$token = auth()->user()->currentAccessToken();
|
|
||||||
if ($token->can('*')) {
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(['message' => 'You are not allowed to perform this action.'], 403);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -140,6 +140,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private ?string $buildTarget = null;
|
private ?string $buildTarget = null;
|
||||||
|
|
||||||
|
private bool $disableBuildCache = false;
|
||||||
|
|
||||||
private Collection $saved_outputs;
|
private Collection $saved_outputs;
|
||||||
|
|
||||||
private ?string $full_healthcheck_url = null;
|
private ?string $full_healthcheck_url = null;
|
||||||
@@ -178,7 +180,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->pull_request_id = $this->application_deployment_queue->pull_request_id;
|
$this->pull_request_id = $this->application_deployment_queue->pull_request_id;
|
||||||
$this->commit = $this->application_deployment_queue->commit;
|
$this->commit = $this->application_deployment_queue->commit;
|
||||||
$this->rollback = $this->application_deployment_queue->rollback;
|
$this->rollback = $this->application_deployment_queue->rollback;
|
||||||
|
$this->disableBuildCache = $this->application->settings->disable_build_cache;
|
||||||
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
|
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
|
||||||
|
if ($this->disableBuildCache) {
|
||||||
|
$this->force_rebuild = true;
|
||||||
|
}
|
||||||
$this->restart_only = $this->application_deployment_queue->restart_only;
|
$this->restart_only = $this->application_deployment_queue->restart_only;
|
||||||
$this->restart_only = $this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile';
|
$this->restart_only = $this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile';
|
||||||
$this->only_this_server = $this->application_deployment_queue->only_this_server;
|
$this->only_this_server = $this->application_deployment_queue->only_this_server;
|
||||||
@@ -1976,6 +1982,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->build_args = $this->build_args->implode(' ');
|
$this->build_args = $this->build_args->implode(' ');
|
||||||
|
|
||||||
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
||||||
|
if ($this->disableBuildCache) {
|
||||||
|
$this->application_deployment_queue->addLogEntry('Docker build cache is disabled. It will not be used during the build process.');
|
||||||
|
}
|
||||||
if ($this->application->build_pack === 'static') {
|
if ($this->application->build_pack === 'static') {
|
||||||
$this->application_deployment_queue->addLogEntry('Static deployment. Copying static assets to the image.');
|
$this->application_deployment_queue->addLogEntry('Static deployment. Copying static assets to the image.');
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
59
app/Jobs/SendMessageToSlackJob.php
Normal file
59
app/Jobs/SendMessageToSlackJob.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Notifications\Dto\SlackMessage;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
|
class SendMessageToSlackJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private SlackMessage $message,
|
||||||
|
private string $webhookUrl
|
||||||
|
) {
|
||||||
|
$this->onQueue('high');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
Http::post($this->webhookUrl, [
|
||||||
|
'blocks' => [
|
||||||
|
[
|
||||||
|
'type' => 'section',
|
||||||
|
'text' => [
|
||||||
|
'type' => 'plain_text',
|
||||||
|
'text' => 'Coolify Notification',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'attachments' => [
|
||||||
|
[
|
||||||
|
'color' => $this->message->color,
|
||||||
|
'blocks' => [
|
||||||
|
[
|
||||||
|
'type' => 'header',
|
||||||
|
'text' => [
|
||||||
|
'type' => 'plain_text',
|
||||||
|
'text' => $this->message->title,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'type' => 'section',
|
||||||
|
'text' => [
|
||||||
|
'type' => 'mrkdwn',
|
||||||
|
'text' => $this->message->description,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ class ProxyStartedNotification
|
|||||||
public function handle(ProxyStarted $event): void
|
public function handle(ProxyStarted $event): void
|
||||||
{
|
{
|
||||||
$this->server = data_get($event, 'data');
|
$this->server = data_get($event, 'data');
|
||||||
$this->server->setupDefault404Redirect();
|
$this->server->setupDefaultRedirect();
|
||||||
$this->server->setupDynamicProxyConfiguration();
|
$this->server->setupDynamicProxyConfiguration();
|
||||||
$this->server->proxy->force_stop = false;
|
$this->server->proxy->force_stop = false;
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class Email extends Component
|
|||||||
#[Validate(['nullable', 'numeric'])]
|
#[Validate(['nullable', 'numeric'])]
|
||||||
public ?int $smtpPort = null;
|
public ?int $smtpPort = null;
|
||||||
|
|
||||||
#[Validate(['nullable', 'string'])]
|
#[Validate(['nullable', 'string', 'in:tls,ssl,none'])]
|
||||||
public ?string $smtpEncryption = null;
|
public ?string $smtpEncryption = null;
|
||||||
|
|
||||||
#[Validate(['nullable', 'string'])]
|
#[Validate(['nullable', 'string'])]
|
||||||
|
|||||||
131
app/Livewire/Notifications/Slack.php
Normal file
131
app/Livewire/Notifications/Slack.php
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Notifications;
|
||||||
|
|
||||||
|
use App\Models\Team;
|
||||||
|
use App\Notifications\Test;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Slack extends Component
|
||||||
|
{
|
||||||
|
public Team $team;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $slackEnabled = false;
|
||||||
|
|
||||||
|
#[Validate(['url', 'nullable'])]
|
||||||
|
public ?string $slackWebhookUrl = null;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $slackNotificationsTest = false;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $slackNotificationsDeployments = false;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $slackNotificationsStatusChanges = false;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $slackNotificationsDatabaseBackups = false;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $slackNotificationsScheduledTasks = false;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $slackNotificationsServerDiskUsage = false;
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->team = auth()->user()->currentTeam();
|
||||||
|
$this->syncData();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function syncData(bool $toModel = false)
|
||||||
|
{
|
||||||
|
if ($toModel) {
|
||||||
|
$this->validate();
|
||||||
|
$this->team->slack_enabled = $this->slackEnabled;
|
||||||
|
$this->team->slack_webhook_url = $this->slackWebhookUrl;
|
||||||
|
$this->team->slack_notifications_test = $this->slackNotificationsTest;
|
||||||
|
$this->team->slack_notifications_deployments = $this->slackNotificationsDeployments;
|
||||||
|
$this->team->slack_notifications_status_changes = $this->slackNotificationsStatusChanges;
|
||||||
|
$this->team->slack_notifications_database_backups = $this->slackNotificationsDatabaseBackups;
|
||||||
|
$this->team->slack_notifications_scheduled_tasks = $this->slackNotificationsScheduledTasks;
|
||||||
|
$this->team->slack_notifications_server_disk_usage = $this->slackNotificationsServerDiskUsage;
|
||||||
|
$this->team->save();
|
||||||
|
refreshSession();
|
||||||
|
} else {
|
||||||
|
$this->slackEnabled = $this->team->slack_enabled;
|
||||||
|
$this->slackWebhookUrl = $this->team->slack_webhook_url;
|
||||||
|
$this->slackNotificationsTest = $this->team->slack_notifications_test;
|
||||||
|
$this->slackNotificationsDeployments = $this->team->slack_notifications_deployments;
|
||||||
|
$this->slackNotificationsStatusChanges = $this->team->slack_notifications_status_changes;
|
||||||
|
$this->slackNotificationsDatabaseBackups = $this->team->slack_notifications_database_backups;
|
||||||
|
$this->slackNotificationsScheduledTasks = $this->team->slack_notifications_scheduled_tasks;
|
||||||
|
$this->slackNotificationsServerDiskUsage = $this->team->slack_notifications_server_disk_usage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function instantSaveSlackEnabled()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate([
|
||||||
|
'slackWebhookUrl' => 'required',
|
||||||
|
], [
|
||||||
|
'slackWebhookUrl.required' => 'Slack Webhook URL is required.',
|
||||||
|
]);
|
||||||
|
$this->saveModel();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->slackEnabled = false;
|
||||||
|
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->syncData(true);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->resetErrorBag();
|
||||||
|
$this->syncData(true);
|
||||||
|
$this->saveModel();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveModel()
|
||||||
|
{
|
||||||
|
$this->syncData(true);
|
||||||
|
refreshSession();
|
||||||
|
$this->dispatch('success', 'Settings saved.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendTestNotification()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->team->notify(new Test);
|
||||||
|
$this->dispatch('success', 'Test notification sent.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.notifications.slack');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,9 @@ class Advanced extends Component
|
|||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $isAutoDeployEnabled = true;
|
public bool $isAutoDeployEnabled = true;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $disableBuildCache = false;
|
||||||
|
|
||||||
#[Validate(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $isLogDrainEnabled = false;
|
public bool $isLogDrainEnabled = false;
|
||||||
|
|
||||||
@@ -95,6 +98,7 @@ class Advanced extends Component
|
|||||||
$this->application->settings->is_stripprefix_enabled = $this->isStripprefixEnabled;
|
$this->application->settings->is_stripprefix_enabled = $this->isStripprefixEnabled;
|
||||||
$this->application->settings->is_raw_compose_deployment_enabled = $this->isRawComposeDeploymentEnabled;
|
$this->application->settings->is_raw_compose_deployment_enabled = $this->isRawComposeDeploymentEnabled;
|
||||||
$this->application->settings->connect_to_docker_network = $this->isConnectToDockerNetworkEnabled;
|
$this->application->settings->connect_to_docker_network = $this->isConnectToDockerNetworkEnabled;
|
||||||
|
$this->application->settings->disable_build_cache = $this->disableBuildCache;
|
||||||
$this->application->settings->save();
|
$this->application->settings->save();
|
||||||
} else {
|
} else {
|
||||||
$this->isForceHttpsEnabled = $this->application->isForceHttpsEnabled();
|
$this->isForceHttpsEnabled = $this->application->isForceHttpsEnabled();
|
||||||
@@ -116,6 +120,7 @@ class Advanced extends Component
|
|||||||
$this->customInternalName = $this->application->settings->custom_internal_name;
|
$this->customInternalName = $this->application->settings->custom_internal_name;
|
||||||
$this->isRawComposeDeploymentEnabled = $this->application->settings->is_raw_compose_deployment_enabled;
|
$this->isRawComposeDeploymentEnabled = $this->application->settings->is_raw_compose_deployment_enabled;
|
||||||
$this->isConnectToDockerNetworkEnabled = $this->application->settings->connect_to_docker_network;
|
$this->isConnectToDockerNetworkEnabled = $this->application->settings->connect_to_docker_network;
|
||||||
|
$this->disableBuildCache = $this->application->settings->disable_build_cache;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,14 @@ class Executions extends Component
|
|||||||
#[Locked]
|
#[Locked]
|
||||||
public ?string $serverTimezone = null;
|
public ?string $serverTimezone = null;
|
||||||
|
|
||||||
|
public $currentPage = 1;
|
||||||
|
|
||||||
|
public $logsPerPage = 100;
|
||||||
|
|
||||||
|
public $selectedExecution = null;
|
||||||
|
|
||||||
|
public $isPollingActive = false;
|
||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
$teamId = Auth::user()->currentTeam()->id;
|
$teamId = Auth::user()->currentTeam()->id;
|
||||||
@@ -54,16 +62,84 @@ class Executions extends Component
|
|||||||
public function refreshExecutions(): void
|
public function refreshExecutions(): void
|
||||||
{
|
{
|
||||||
$this->executions = $this->task->executions()->take(20)->get();
|
$this->executions = $this->task->executions()->take(20)->get();
|
||||||
|
if ($this->selectedKey) {
|
||||||
|
$this->selectedExecution = $this->task->executions()->find($this->selectedKey);
|
||||||
|
if ($this->selectedExecution && $this->selectedExecution->status !== 'running') {
|
||||||
|
$this->isPollingActive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function selectTask($key): void
|
public function selectTask($key): void
|
||||||
{
|
{
|
||||||
if ($key == $this->selectedKey) {
|
if ($key == $this->selectedKey) {
|
||||||
$this->selectedKey = null;
|
$this->selectedKey = null;
|
||||||
|
$this->selectedExecution = null;
|
||||||
|
$this->currentPage = 1;
|
||||||
|
$this->isPollingActive = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->selectedKey = $key;
|
$this->selectedKey = $key;
|
||||||
|
$this->selectedExecution = $this->task->executions()->find($key);
|
||||||
|
$this->currentPage = 1;
|
||||||
|
|
||||||
|
// Start polling if task is running
|
||||||
|
if ($this->selectedExecution && $this->selectedExecution->status === 'running') {
|
||||||
|
$this->isPollingActive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function polling()
|
||||||
|
{
|
||||||
|
if ($this->selectedExecution && $this->isPollingActive) {
|
||||||
|
$this->selectedExecution->refresh();
|
||||||
|
if ($this->selectedExecution->status !== 'running') {
|
||||||
|
$this->isPollingActive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadMoreLogs()
|
||||||
|
{
|
||||||
|
$this->currentPage++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLogLinesProperty()
|
||||||
|
{
|
||||||
|
if (! $this->selectedExecution) {
|
||||||
|
return collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->selectedExecution->message) {
|
||||||
|
return collect(['Waiting for task output...']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = collect(explode("\n", $this->selectedExecution->message));
|
||||||
|
|
||||||
|
return $lines->take($this->currentPage * $this->logsPerPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function downloadLogs(int $executionId)
|
||||||
|
{
|
||||||
|
$execution = $this->executions->firstWhere('id', $executionId);
|
||||||
|
if (! $execution) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->streamDownload(function () use ($execution) {
|
||||||
|
echo $execution->message;
|
||||||
|
}, 'task-execution-'.$execution->id.'.log');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasMoreLogs()
|
||||||
|
{
|
||||||
|
if (! $this->selectedExecution || ! $this->selectedExecution->message) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$lines = collect(explode("\n", $this->selectedExecution->message));
|
||||||
|
|
||||||
|
return $lines->count() > ($this->currentPage * $this->logsPerPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function formatDateInServerTimezone($date)
|
public function formatDateInServerTimezone($date)
|
||||||
|
|||||||
@@ -11,13 +11,7 @@ class ApiTokens extends Component
|
|||||||
|
|
||||||
public $tokens = [];
|
public $tokens = [];
|
||||||
|
|
||||||
public bool $viewSensitiveData = false;
|
public array $permissions = ['read'];
|
||||||
|
|
||||||
public bool $readOnly = true;
|
|
||||||
|
|
||||||
public bool $rootAccess = false;
|
|
||||||
|
|
||||||
public array $permissions = ['read-only'];
|
|
||||||
|
|
||||||
public $isApiEnabled;
|
public $isApiEnabled;
|
||||||
|
|
||||||
@@ -29,51 +23,28 @@ class ApiTokens extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->isApiEnabled = InstanceSettings::get()->is_api_enabled;
|
$this->isApiEnabled = InstanceSettings::get()->is_api_enabled;
|
||||||
|
$this->getTokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTokens()
|
||||||
|
{
|
||||||
$this->tokens = auth()->user()->tokens->sortByDesc('created_at');
|
$this->tokens = auth()->user()->tokens->sortByDesc('created_at');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updatedViewSensitiveData()
|
public function updatedPermissions($permissionToUpdate)
|
||||||
{
|
{
|
||||||
if ($this->viewSensitiveData) {
|
if ($permissionToUpdate == 'root') {
|
||||||
$this->permissions[] = 'view:sensitive';
|
$this->permissions = ['root'];
|
||||||
$this->permissions = array_diff($this->permissions, ['*']);
|
} elseif ($permissionToUpdate == 'read:sensitive' && ! in_array('read', $this->permissions)) {
|
||||||
$this->rootAccess = false;
|
$this->permissions[] = 'read';
|
||||||
|
} elseif ($permissionToUpdate == 'deploy') {
|
||||||
|
$this->permissions = ['deploy'];
|
||||||
} else {
|
} else {
|
||||||
$this->permissions = array_diff($this->permissions, ['view:sensitive']);
|
if (count($this->permissions) == 0) {
|
||||||
}
|
$this->permissions = ['read'];
|
||||||
$this->makeSureOneIsSelected();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public function updatedReadOnly()
|
|
||||||
{
|
|
||||||
if ($this->readOnly) {
|
|
||||||
$this->permissions[] = 'read-only';
|
|
||||||
$this->permissions = array_diff($this->permissions, ['*']);
|
|
||||||
$this->rootAccess = false;
|
|
||||||
} else {
|
|
||||||
$this->permissions = array_diff($this->permissions, ['read-only']);
|
|
||||||
}
|
|
||||||
$this->makeSureOneIsSelected();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updatedRootAccess()
|
|
||||||
{
|
|
||||||
if ($this->rootAccess) {
|
|
||||||
$this->permissions = ['*'];
|
|
||||||
$this->readOnly = false;
|
|
||||||
$this->viewSensitiveData = false;
|
|
||||||
} else {
|
|
||||||
$this->readOnly = true;
|
|
||||||
$this->permissions = ['read-only'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function makeSureOneIsSelected()
|
|
||||||
{
|
|
||||||
if (count($this->permissions) == 0) {
|
|
||||||
$this->permissions = ['read-only'];
|
|
||||||
$this->readOnly = true;
|
|
||||||
}
|
}
|
||||||
|
sort($this->permissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addNewToken()
|
public function addNewToken()
|
||||||
@@ -82,8 +53,8 @@ class ApiTokens extends Component
|
|||||||
$this->validate([
|
$this->validate([
|
||||||
'description' => 'required|min:3|max:255',
|
'description' => 'required|min:3|max:255',
|
||||||
]);
|
]);
|
||||||
$token = auth()->user()->createToken($this->description, $this->permissions);
|
$token = auth()->user()->createToken($this->description, array_values($this->permissions));
|
||||||
$this->tokens = auth()->user()->tokens;
|
$this->getTokens();
|
||||||
session()->flash('token', $token->plainTextToken);
|
session()->flash('token', $token->plainTextToken);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
@@ -92,8 +63,12 @@ class ApiTokens extends Component
|
|||||||
|
|
||||||
public function revoke(int $id)
|
public function revoke(int $id)
|
||||||
{
|
{
|
||||||
$token = auth()->user()->tokens()->where('id', $id)->first();
|
try {
|
||||||
$token->delete();
|
$token = auth()->user()->tokens()->where('id', $id)->firstOrFail();
|
||||||
$this->tokens = auth()->user()->tokens;
|
$token->delete();
|
||||||
|
$this->getTokens();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ class Proxy extends Component
|
|||||||
|
|
||||||
public $proxy_settings = null;
|
public $proxy_settings = null;
|
||||||
|
|
||||||
|
public bool $redirect_enabled = true;
|
||||||
|
|
||||||
public ?string $redirect_url = null;
|
public ?string $redirect_url = null;
|
||||||
|
|
||||||
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit'];
|
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit'];
|
||||||
@@ -26,6 +28,7 @@ class Proxy extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->selectedProxy = $this->server->proxyType();
|
$this->selectedProxy = $this->server->proxyType();
|
||||||
|
$this->redirect_enabled = data_get($this->server, 'proxy.redirect_enabled', true);
|
||||||
$this->redirect_url = data_get($this->server, 'proxy.redirect_url');
|
$this->redirect_url = data_get($this->server, 'proxy.redirect_url');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +41,7 @@ class Proxy extends Component
|
|||||||
{
|
{
|
||||||
$this->server->proxy = null;
|
$this->server->proxy = null;
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
$this->dispatch('proxyChanged');
|
$this->dispatch('reloadWindow');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function selectProxy($proxy_type)
|
public function selectProxy($proxy_type)
|
||||||
@@ -46,7 +49,7 @@ class Proxy extends Component
|
|||||||
try {
|
try {
|
||||||
$this->server->changeProxy($proxy_type, async: false);
|
$this->server->changeProxy($proxy_type, async: false);
|
||||||
$this->selectedProxy = $this->server->proxy->type;
|
$this->selectedProxy = $this->server->proxy->type;
|
||||||
$this->dispatch('proxyStatusUpdated');
|
$this->dispatch('reloadWindow');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
@@ -63,13 +66,25 @@ class Proxy extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function instantSaveRedirect()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->server->proxy->redirect_enabled = $this->redirect_enabled;
|
||||||
|
$this->server->save();
|
||||||
|
$this->server->setupDefaultRedirect();
|
||||||
|
$this->dispatch('success', 'Proxy configuration saved.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
SaveConfiguration::run($this->server, $this->proxy_settings);
|
SaveConfiguration::run($this->server, $this->proxy_settings);
|
||||||
$this->server->proxy->redirect_url = $this->redirect_url;
|
$this->server->proxy->redirect_url = $this->redirect_url;
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
$this->server->setupDefault404Redirect();
|
$this->server->setupDefaultRedirect();
|
||||||
$this->dispatch('success', 'Proxy configuration saved.');
|
$this->dispatch('success', 'Proxy configuration saved.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class Deploy extends Component
|
|||||||
public function restart()
|
public function restart()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->stop(forceStop: false);
|
$this->stop();
|
||||||
$this->dispatch('checkProxy');
|
$this->dispatch('checkProxy');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
@@ -105,6 +105,7 @@ class Deploy extends Component
|
|||||||
|
|
||||||
$startTime = Carbon::now()->getTimestamp();
|
$startTime = Carbon::now()->getTimestamp();
|
||||||
while ($process->running()) {
|
while ($process->running()) {
|
||||||
|
ray('running');
|
||||||
if (Carbon::now()->getTimestamp() - $startTime >= $timeout) {
|
if (Carbon::now()->getTimestamp() - $startTime >= $timeout) {
|
||||||
$this->forceStopContainer($containerName);
|
$this->forceStopContainer($containerName);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class SettingsEmail extends Component
|
|||||||
#[Validate(['nullable', 'numeric', 'min:1', 'max:65535'])]
|
#[Validate(['nullable', 'numeric', 'min:1', 'max:65535'])]
|
||||||
public ?int $smtpPort = null;
|
public ?int $smtpPort = null;
|
||||||
|
|
||||||
#[Validate(['nullable', 'string'])]
|
#[Validate(['nullable', 'string', 'in:tls,ssl,none'])]
|
||||||
public ?string $smtpEncryption = null;
|
public ?string $smtpEncryption = null;
|
||||||
|
|
||||||
#[Validate(['nullable', 'string'])]
|
#[Validate(['nullable', 'string'])]
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ namespace App\Livewire\Source\Github;
|
|||||||
|
|
||||||
use App\Jobs\GithubAppPermissionJob;
|
use App\Jobs\GithubAppPermissionJob;
|
||||||
use App\Models\GithubApp;
|
use App\Models\GithubApp;
|
||||||
|
use App\Models\PrivateKey;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Lcobucci\JWT\Configuration;
|
||||||
|
use Lcobucci\JWT\Signer\Key\InMemory;
|
||||||
|
use Lcobucci\JWT\Signer\Rsa\Sha256;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Change extends Component
|
class Change extends Component
|
||||||
@@ -51,12 +56,20 @@ class Change extends Component
|
|||||||
'github_app.administration' => 'nullable|string',
|
'github_app.administration' => 'nullable|string',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
if ($this->github_app) {
|
||||||
|
$this->github_app->makeVisible(['client_secret', 'webhook_secret']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function checkPermissions()
|
public function checkPermissions()
|
||||||
{
|
{
|
||||||
GithubAppPermissionJob::dispatchSync($this->github_app);
|
GithubAppPermissionJob::dispatchSync($this->github_app);
|
||||||
$this->github_app->refresh()->makeVisible('client_secret')->makeVisible('webhook_secret');
|
$this->github_app->refresh()->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||||
$this->dispatch('success', 'Github App permissions updated.');
|
$this->dispatch('success', 'Github App permissions updated.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// public function check()
|
// public function check()
|
||||||
// {
|
// {
|
||||||
|
|
||||||
@@ -90,15 +103,16 @@ class Change extends Component
|
|||||||
|
|
||||||
// ray($runners_by_repository);
|
// ray($runners_by_repository);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$github_app_uuid = request()->github_app_uuid;
|
$github_app_uuid = request()->github_app_uuid;
|
||||||
$this->github_app = GithubApp::ownedByCurrentTeam()->whereUuid($github_app_uuid)->firstOrFail();
|
$this->github_app = GithubApp::ownedByCurrentTeam()->whereUuid($github_app_uuid)->firstOrFail();
|
||||||
|
$this->github_app->makeVisible(['client_secret', 'webhook_secret']);
|
||||||
|
|
||||||
$this->applications = $this->github_app->applications;
|
$this->applications = $this->github_app->applications;
|
||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
|
|
||||||
|
|
||||||
$this->name = str($this->github_app->name)->kebab();
|
$this->name = str($this->github_app->name)->kebab();
|
||||||
$this->fqdn = $settings->fqdn;
|
$this->fqdn = $settings->fqdn;
|
||||||
@@ -142,6 +156,77 @@ class Change extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getGithubAppNameUpdatePath()
|
||||||
|
{
|
||||||
|
if (str($this->github_app->organization)->isNotEmpty()) {
|
||||||
|
return "{$this->github_app->html_url}/organizations/{$this->github_app->organization}/settings/apps/{$this->github_app->name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{$this->github_app->html_url}/settings/apps/{$this->github_app->name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateGithubJwt($private_key, $app_id): string
|
||||||
|
{
|
||||||
|
$configuration = Configuration::forAsymmetricSigner(
|
||||||
|
new Sha256,
|
||||||
|
InMemory::plainText($private_key),
|
||||||
|
InMemory::plainText($private_key)
|
||||||
|
);
|
||||||
|
|
||||||
|
$now = time();
|
||||||
|
|
||||||
|
return $configuration->builder()
|
||||||
|
->issuedBy((string) $app_id)
|
||||||
|
->permittedFor('https://api.github.com')
|
||||||
|
->identifiedBy((string) $now)
|
||||||
|
->issuedAt(new \DateTimeImmutable("@{$now}"))
|
||||||
|
->expiresAt(new \DateTimeImmutable('@'.($now + 600)))
|
||||||
|
->getToken($configuration->signer(), $configuration->signingKey())
|
||||||
|
->toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateGithubAppName()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$privateKey = PrivateKey::ownedByCurrentTeam()->find($this->github_app->private_key_id);
|
||||||
|
|
||||||
|
if (! $privateKey) {
|
||||||
|
$this->dispatch('error', 'No private key found for this GitHub App.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$jwt = $this->generateGithubJwt($privateKey->private_key, $this->github_app->app_id);
|
||||||
|
|
||||||
|
$response = Http::withHeaders([
|
||||||
|
'Accept' => 'application/vnd.github+json',
|
||||||
|
'X-GitHub-Api-Version' => '2022-11-28',
|
||||||
|
'Authorization' => "Bearer {$jwt}",
|
||||||
|
])->get("{$this->github_app->api_url}/app");
|
||||||
|
|
||||||
|
if ($response->successful()) {
|
||||||
|
$app_data = $response->json();
|
||||||
|
$app_slug = $app_data['slug'] ?? null;
|
||||||
|
|
||||||
|
if ($app_slug) {
|
||||||
|
$this->github_app->name = $app_slug;
|
||||||
|
$this->name = str($app_slug)->kebab();
|
||||||
|
$privateKey->name = "github-app-{$app_slug}";
|
||||||
|
$privateKey->save();
|
||||||
|
$this->github_app->save();
|
||||||
|
$this->dispatch('success', 'GitHub App name and SSH key name synchronized successfully.');
|
||||||
|
} else {
|
||||||
|
$this->dispatch('info', 'Could not find App Name (slug) in GitHub response.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error_message = $response->json()['message'] ?? 'Unknown error';
|
||||||
|
$this->dispatch('error', "Failed to fetch GitHub App information: {$error_message}");
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1321,17 +1321,43 @@ class Application extends BaseModel
|
|||||||
if (! $gitRemoteStatus['is_accessible']) {
|
if (! $gitRemoteStatus['is_accessible']) {
|
||||||
throw new \RuntimeException("Failed to read Git source:\n\n{$gitRemoteStatus['error']}");
|
throw new \RuntimeException("Failed to read Git source:\n\n{$gitRemoteStatus['error']}");
|
||||||
}
|
}
|
||||||
|
$getGitVersion = instant_remote_process(['git --version'], $this->destination->server, false);
|
||||||
|
$gitVersion = str($getGitVersion)->explode(' ')->last();
|
||||||
|
|
||||||
$commands = collect([
|
if (version_compare($gitVersion, '2.35.1', '<')) {
|
||||||
"rm -rf /tmp/{$uuid}",
|
$fileList = $fileList->map(function ($file) {
|
||||||
"mkdir -p /tmp/{$uuid}",
|
$parts = explode('/', trim($file, '.'));
|
||||||
"cd /tmp/{$uuid}",
|
$paths = collect();
|
||||||
$cloneCommand,
|
$currentPath = '';
|
||||||
'git sparse-checkout init --cone',
|
foreach ($parts as $part) {
|
||||||
"git sparse-checkout set {$fileList->implode(' ')}",
|
$currentPath .= ($currentPath ? '/' : '').$part;
|
||||||
'git read-tree -mu HEAD',
|
$paths->push($currentPath);
|
||||||
"cat .$workdir$composeFile",
|
}
|
||||||
]);
|
|
||||||
|
return $paths;
|
||||||
|
})->flatten()->unique()->values();
|
||||||
|
$commands = collect([
|
||||||
|
"rm -rf /tmp/{$uuid}",
|
||||||
|
"mkdir -p /tmp/{$uuid}",
|
||||||
|
"cd /tmp/{$uuid}",
|
||||||
|
$cloneCommand,
|
||||||
|
'git sparse-checkout init --cone',
|
||||||
|
"git sparse-checkout set {$fileList->implode(' ')}",
|
||||||
|
'git read-tree -mu HEAD',
|
||||||
|
"cat .$workdir$composeFile",
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$commands = collect([
|
||||||
|
"rm -rf /tmp/{$uuid}",
|
||||||
|
"mkdir -p /tmp/{$uuid}",
|
||||||
|
"cd /tmp/{$uuid}",
|
||||||
|
$cloneCommand,
|
||||||
|
'git sparse-checkout init --cone',
|
||||||
|
"git sparse-checkout set {$fileList->implode(' ')}",
|
||||||
|
'git read-tree -mu HEAD',
|
||||||
|
"cat .$workdir$composeFile",
|
||||||
|
]);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$composeFileContent = instant_remote_process($commands, $this->destination->server);
|
$composeFileContent = instant_remote_process($commands, $this->destination->server);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ abstract class BaseModel extends Model
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function name(): Attribute
|
public function sanitizedName(): Attribute
|
||||||
{
|
{
|
||||||
return new Attribute(
|
return new Attribute(
|
||||||
get: fn () => sanitize_string($this->getRawOriginal('name')),
|
get: fn () => sanitize_string($this->getRawOriginal('name')),
|
||||||
|
|||||||
@@ -105,6 +105,14 @@ class Server extends BaseModel
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (! isset($server->proxy->redirect_enabled)) {
|
||||||
|
$server->proxy->redirect_enabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
static::retrieved(function ($server) {
|
||||||
|
if (! isset($server->proxy->redirect_enabled)) {
|
||||||
|
$server->proxy->redirect_enabled = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
static::forceDeleting(function ($server) {
|
static::forceDeleting(function ($server) {
|
||||||
@@ -184,73 +192,80 @@ class Server extends BaseModel
|
|||||||
return $this->proxyType() && $this->proxyType() !== 'NONE' && $this->isFunctional() && ! $this->isSwarmWorker() && ! $this->settings->is_build_server;
|
return $this->proxyType() && $this->proxyType() !== 'NONE' && $this->isFunctional() && ! $this->isSwarmWorker() && ! $this->settings->is_build_server;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setupDefault404Redirect()
|
public function setupDefaultRedirect()
|
||||||
{
|
{
|
||||||
|
$banner =
|
||||||
|
"# This file is generated by Coolify, do not edit it manually.\n".
|
||||||
|
"# Disable the default redirect to customize (only if you know what are you doing).\n\n";
|
||||||
$dynamic_conf_path = $this->proxyPath().'/dynamic';
|
$dynamic_conf_path = $this->proxyPath().'/dynamic';
|
||||||
$proxy_type = $this->proxyType();
|
$proxy_type = $this->proxyType();
|
||||||
|
$redirect_enabled = $this->proxy->redirect_enabled ?? true;
|
||||||
$redirect_url = $this->proxy->redirect_url;
|
$redirect_url = $this->proxy->redirect_url;
|
||||||
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
if (isDev()) {
|
||||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.yaml";
|
|
||||||
} elseif ($proxy_type === ProxyTypes::CADDY->value) {
|
|
||||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.caddy";
|
|
||||||
}
|
|
||||||
if (empty($redirect_url)) {
|
|
||||||
if ($proxy_type === ProxyTypes::CADDY->value) {
|
if ($proxy_type === ProxyTypes::CADDY->value) {
|
||||||
$conf = ':80, :443 {
|
$dynamic_conf_path = '/data/coolify/proxy/caddy/dynamic';
|
||||||
respond 404
|
|
||||||
}';
|
|
||||||
$conf =
|
|
||||||
"# This file is automatically generated by Coolify.\n".
|
|
||||||
"# Do not edit it manually (only if you know what are you doing).\n\n".
|
|
||||||
$conf;
|
|
||||||
$base64 = base64_encode($conf);
|
|
||||||
instant_remote_process([
|
|
||||||
"mkdir -p $dynamic_conf_path",
|
|
||||||
"echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
|
|
||||||
], $this);
|
|
||||||
$this->reloadCaddy();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
instant_remote_process([
|
|
||||||
"mkdir -p $dynamic_conf_path",
|
|
||||||
"rm -f $default_redirect_file",
|
|
||||||
], $this);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
||||||
$dynamic_conf = [
|
$default_redirect_file = "$dynamic_conf_path/default_redirect_503.yaml";
|
||||||
'http' => [
|
} elseif ($proxy_type === ProxyTypes::CADDY->value) {
|
||||||
'routers' => [
|
$default_redirect_file = "$dynamic_conf_path/default_redirect_503.caddy";
|
||||||
'catchall' => [
|
}
|
||||||
'entryPoints' => [
|
|
||||||
0 => 'http',
|
instant_remote_process([
|
||||||
1 => 'https',
|
"mkdir -p $dynamic_conf_path",
|
||||||
],
|
"rm -f $dynamic_conf_path/default_redirect_404.yaml",
|
||||||
'service' => 'noop',
|
"rm -f $dynamic_conf_path/default_redirect_404.caddy",
|
||||||
'rule' => 'HostRegexp(`.+`)',
|
], $this);
|
||||||
'tls' => [
|
|
||||||
'certResolver' => 'letsencrypt',
|
if ($redirect_enabled === false) {
|
||||||
],
|
instant_remote_process(["rm -f $default_redirect_file"], $this);
|
||||||
'priority' => 1,
|
} else {
|
||||||
'middlewares' => [
|
if ($proxy_type === ProxyTypes::CADDY->value) {
|
||||||
0 => 'redirect-regexp',
|
if (filled($redirect_url)) {
|
||||||
|
$conf = ":80, :443 {
|
||||||
|
redir $redirect_url
|
||||||
|
}";
|
||||||
|
} else {
|
||||||
|
$conf = ':80, :443 {
|
||||||
|
respond 503
|
||||||
|
}';
|
||||||
|
}
|
||||||
|
} elseif ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
||||||
|
$dynamic_conf = [
|
||||||
|
'http' => [
|
||||||
|
'routers' => [
|
||||||
|
'catchall' => [
|
||||||
|
'entryPoints' => [
|
||||||
|
0 => 'http',
|
||||||
|
1 => 'https',
|
||||||
|
],
|
||||||
|
'service' => 'noop',
|
||||||
|
'rule' => 'PathPrefix(`/`)',
|
||||||
|
'tls' => [
|
||||||
|
'certResolver' => 'letsencrypt',
|
||||||
|
],
|
||||||
|
'priority' => -1000,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
'services' => [
|
||||||
'services' => [
|
'noop' => [
|
||||||
'noop' => [
|
'loadBalancer' => [
|
||||||
'loadBalancer' => [
|
'servers' => [],
|
||||||
'servers' => [
|
|
||||||
0 => [
|
|
||||||
'url' => '',
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'middlewares' => [
|
];
|
||||||
|
if (filled($redirect_url)) {
|
||||||
|
$dynamic_conf['http']['routers']['catchall']['middlewares'] = [
|
||||||
|
0 => 'redirect-regexp',
|
||||||
|
];
|
||||||
|
|
||||||
|
$dynamic_conf['http']['services']['noop']['loadBalancer']['servers'][0] = [
|
||||||
|
'url' => '',
|
||||||
|
];
|
||||||
|
$dynamic_conf['http']['middlewares'] = [
|
||||||
'redirect-regexp' => [
|
'redirect-regexp' => [
|
||||||
'redirectRegex' => [
|
'redirectRegex' => [
|
||||||
'regex' => '(.*)',
|
'regex' => '(.*)',
|
||||||
@@ -258,32 +273,17 @@ respond 404
|
|||||||
'permanent' => false,
|
'permanent' => false,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
];
|
||||||
],
|
}
|
||||||
];
|
$conf = Yaml::dump($dynamic_conf, 12, 2);
|
||||||
$conf = Yaml::dump($dynamic_conf, 12, 2);
|
}
|
||||||
$conf =
|
$conf = $banner.$conf;
|
||||||
"# This file is automatically generated by Coolify.\n".
|
|
||||||
"# Do not edit it manually (only if you know what are you doing).\n\n".
|
|
||||||
$conf;
|
|
||||||
|
|
||||||
$base64 = base64_encode($conf);
|
|
||||||
} elseif ($proxy_type === ProxyTypes::CADDY->value) {
|
|
||||||
$conf = ":80, :443 {
|
|
||||||
redir $redirect_url
|
|
||||||
}";
|
|
||||||
$conf =
|
|
||||||
"# This file is automatically generated by Coolify.\n".
|
|
||||||
"# Do not edit it manually (only if you know what are you doing).\n\n".
|
|
||||||
$conf;
|
|
||||||
$base64 = base64_encode($conf);
|
$base64 = base64_encode($conf);
|
||||||
|
instant_remote_process([
|
||||||
|
"echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
|
||||||
|
], $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
instant_remote_process([
|
|
||||||
"mkdir -p $dynamic_conf_path",
|
|
||||||
"echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
|
|
||||||
], $this);
|
|
||||||
|
|
||||||
if ($proxy_type === 'CADDY') {
|
if ($proxy_type === 'CADDY') {
|
||||||
$this->reloadCaddy();
|
$this->reloadCaddy();
|
||||||
}
|
}
|
||||||
@@ -611,7 +611,9 @@ $schema://$host {
|
|||||||
}
|
}
|
||||||
$memory = json_decode($memory, true);
|
$memory = json_decode($memory, true);
|
||||||
$parsedCollection = collect($memory)->map(function ($metric) {
|
$parsedCollection = collect($memory)->map(function ($metric) {
|
||||||
return [(int) $metric['time'], (float) $metric['usedPercent']];
|
$usedPercent = $metric['usedPercent'] ?? 0.0;
|
||||||
|
|
||||||
|
return [(int) $metric['time'], (float) $usedPercent];
|
||||||
});
|
});
|
||||||
|
|
||||||
return $parsedCollection->toArray();
|
return $parsedCollection->toArray();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Models;
|
|||||||
|
|
||||||
use App\Notifications\Channels\SendsDiscord;
|
use App\Notifications\Channels\SendsDiscord;
|
||||||
use App\Notifications\Channels\SendsEmail;
|
use App\Notifications\Channels\SendsEmail;
|
||||||
|
use App\Notifications\Channels\SendsSlack;
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
@@ -70,7 +71,7 @@ use OpenApi\Attributes as OA;
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
class Team extends Model implements SendsDiscord, SendsEmail
|
class Team extends Model implements SendsDiscord, SendsEmail, SendsSlack
|
||||||
{
|
{
|
||||||
use Notifiable;
|
use Notifiable;
|
||||||
|
|
||||||
@@ -127,6 +128,11 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function routeNotificationForSlack()
|
||||||
|
{
|
||||||
|
return data_get($this, 'slack_webhook_url', null);
|
||||||
|
}
|
||||||
|
|
||||||
public function getRecepients($notification)
|
public function getRecepients($notification)
|
||||||
{
|
{
|
||||||
$recipients = data_get($notification, 'emails', null);
|
$recipients = data_get($notification, 'emails', null);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Models\Application;
|
|||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
use App\Notifications\CustomEmailNotification;
|
use App\Notifications\CustomEmailNotification;
|
||||||
use App\Notifications\Dto\DiscordMessage;
|
use App\Notifications\Dto\DiscordMessage;
|
||||||
|
use App\Notifications\Dto\SlackMessage;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
|
||||||
class DeploymentFailed extends CustomEmailNotification
|
class DeploymentFailed extends CustomEmailNotification
|
||||||
@@ -128,4 +129,31 @@ class DeploymentFailed extends CustomEmailNotification
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toSlack(): SlackMessage
|
||||||
|
{
|
||||||
|
if ($this->preview) {
|
||||||
|
$title = "Pull request #{$this->preview->pull_request_id} deployment failed";
|
||||||
|
$description = "Pull request deployment failed for {$this->application_name}";
|
||||||
|
if ($this->preview->fqdn) {
|
||||||
|
$description .= "\nPreview URL: {$this->preview->fqdn}";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$title = 'Deployment failed';
|
||||||
|
$description = "Deployment failed for {$this->application_name}";
|
||||||
|
if ($this->fqdn) {
|
||||||
|
$description .= "\nApplication URL: {$this->fqdn}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$description .= "\n\n**Project:** ".data_get($this->application, 'environment.project.name');
|
||||||
|
$description .= "\n**Environment:** {$this->environment_name}";
|
||||||
|
$description .= "\n**Deployment Logs:** {$this->deployment_url}";
|
||||||
|
|
||||||
|
return new SlackMessage(
|
||||||
|
title: $title,
|
||||||
|
description: $description,
|
||||||
|
color: SlackMessage::errorColor()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Models\Application;
|
|||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
use App\Notifications\CustomEmailNotification;
|
use App\Notifications\CustomEmailNotification;
|
||||||
use App\Notifications\Dto\DiscordMessage;
|
use App\Notifications\Dto\DiscordMessage;
|
||||||
|
use App\Notifications\Dto\SlackMessage;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
|
||||||
class DeploymentSuccess extends CustomEmailNotification
|
class DeploymentSuccess extends CustomEmailNotification
|
||||||
@@ -143,4 +144,31 @@ class DeploymentSuccess extends CustomEmailNotification
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toSlack(): SlackMessage
|
||||||
|
{
|
||||||
|
if ($this->preview) {
|
||||||
|
$title = "Pull request #{$this->preview->pull_request_id} successfully deployed";
|
||||||
|
$description = "New version successfully deployed for {$this->application_name}";
|
||||||
|
if ($this->preview->fqdn) {
|
||||||
|
$description .= "\nPreview URL: {$this->preview->fqdn}";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$title = 'New version successfully deployed';
|
||||||
|
$description = "New version successfully deployed for {$this->application_name}";
|
||||||
|
if ($this->fqdn) {
|
||||||
|
$description .= "\nApplication URL: {$this->fqdn}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$description .= "\n\n**Project:** ".data_get($this->application, 'environment.project.name');
|
||||||
|
$description .= "\n**Environment:** {$this->environment_name}";
|
||||||
|
$description .= "\n**Deployment Logs:** {$this->deployment_url}";
|
||||||
|
|
||||||
|
return new SlackMessage(
|
||||||
|
title: $title,
|
||||||
|
description: $description,
|
||||||
|
color: SlackMessage::successColor()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Notifications\Application;
|
|||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Notifications\CustomEmailNotification;
|
use App\Notifications\CustomEmailNotification;
|
||||||
use App\Notifications\Dto\DiscordMessage;
|
use App\Notifications\Dto\DiscordMessage;
|
||||||
|
use App\Notifications\Dto\SlackMessage;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
|
||||||
class StatusChanged extends CustomEmailNotification
|
class StatusChanged extends CustomEmailNotification
|
||||||
@@ -75,4 +76,20 @@ class StatusChanged extends CustomEmailNotification
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toSlack(): SlackMessage
|
||||||
|
{
|
||||||
|
$title = 'Application stopped';
|
||||||
|
$description = "{$this->resource_name} has been stopped";
|
||||||
|
|
||||||
|
$description .= "\n\n**Project:** ".data_get($this->resource, 'environment.project.name');
|
||||||
|
$description .= "\n**Environment:** {$this->environment_name}";
|
||||||
|
$description .= "\n**Application URL:** {$this->resource_url}";
|
||||||
|
|
||||||
|
return new SlackMessage(
|
||||||
|
title: $title,
|
||||||
|
description: $description,
|
||||||
|
color: SlackMessage::errorColor()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,11 +66,12 @@ class EmailChannel
|
|||||||
'transport' => 'smtp',
|
'transport' => 'smtp',
|
||||||
'host' => data_get($notifiable, 'smtp_host'),
|
'host' => data_get($notifiable, 'smtp_host'),
|
||||||
'port' => data_get($notifiable, 'smtp_port'),
|
'port' => data_get($notifiable, 'smtp_port'),
|
||||||
'encryption' => data_get($notifiable, 'smtp_encryption'),
|
'encryption' => data_get($notifiable, 'smtp_encryption') === 'none' ? null : data_get($notifiable, 'smtp_encryption'),
|
||||||
'username' => data_get($notifiable, 'smtp_username'),
|
'username' => data_get($notifiable, 'smtp_username'),
|
||||||
'password' => data_get($notifiable, 'smtp_password'),
|
'password' => data_get($notifiable, 'smtp_password'),
|
||||||
'timeout' => data_get($notifiable, 'smtp_timeout'),
|
'timeout' => data_get($notifiable, 'smtp_timeout'),
|
||||||
'local_domain' => null,
|
'local_domain' => null,
|
||||||
|
'auto_tls' => data_get($notifiable, 'smtp_encryption') === 'none' ? '0' : '',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
app/Notifications/Channels/SendsSlack.php
Normal file
8
app/Notifications/Channels/SendsSlack.php
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Channels;
|
||||||
|
|
||||||
|
interface SendsSlack
|
||||||
|
{
|
||||||
|
public function routeNotificationForSlack();
|
||||||
|
}
|
||||||
22
app/Notifications/Channels/SlackChannel.php
Normal file
22
app/Notifications/Channels/SlackChannel.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Channels;
|
||||||
|
|
||||||
|
use App\Jobs\SendMessageToSlackJob;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
|
||||||
|
class SlackChannel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Send the given notification.
|
||||||
|
*/
|
||||||
|
public function send(SendsSlack $notifiable, Notification $notification): void
|
||||||
|
{
|
||||||
|
$message = $notification->toSlack();
|
||||||
|
$webhookUrl = $notifiable->routeNotificationForSlack();
|
||||||
|
if (! $webhookUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SendMessageToSlackJob::dispatch($message, $webhookUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ namespace App\Notifications\Container;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Notifications\CustomEmailNotification;
|
use App\Notifications\CustomEmailNotification;
|
||||||
use App\Notifications\Dto\DiscordMessage;
|
use App\Notifications\Dto\DiscordMessage;
|
||||||
|
use App\Notifications\Dto\SlackMessage;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
|
||||||
class ContainerRestarted extends CustomEmailNotification
|
class ContainerRestarted extends CustomEmailNotification
|
||||||
@@ -66,4 +67,20 @@ class ContainerRestarted extends CustomEmailNotification
|
|||||||
|
|
||||||
return $payload;
|
return $payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toSlack(): SlackMessage
|
||||||
|
{
|
||||||
|
$title = 'Resource restarted';
|
||||||
|
$description = "A resource ({$this->name}) has been restarted automatically on {$this->server->name}";
|
||||||
|
|
||||||
|
if ($this->url) {
|
||||||
|
$description .= "\n**Resource URL:** {$this->url}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SlackMessage(
|
||||||
|
title: $title,
|
||||||
|
description: $description,
|
||||||
|
color: SlackMessage::warningColor()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Notifications\Container;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Notifications\CustomEmailNotification;
|
use App\Notifications\CustomEmailNotification;
|
||||||
use App\Notifications\Dto\DiscordMessage;
|
use App\Notifications\Dto\DiscordMessage;
|
||||||
|
use App\Notifications\Dto\SlackMessage;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
|
||||||
class ContainerStopped extends CustomEmailNotification
|
class ContainerStopped extends CustomEmailNotification
|
||||||
@@ -66,4 +67,20 @@ class ContainerStopped extends CustomEmailNotification
|
|||||||
|
|
||||||
return $payload;
|
return $payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toSlack(): SlackMessage
|
||||||
|
{
|
||||||
|
$title = 'Resource stopped';
|
||||||
|
$description = "A resource ({$this->name}) has been stopped unexpectedly on {$this->server->name}";
|
||||||
|
|
||||||
|
if ($this->url) {
|
||||||
|
$description .= "\n**Resource URL:** {$this->url}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SlackMessage(
|
||||||
|
title: $title,
|
||||||
|
description: $description,
|
||||||
|
color: SlackMessage::errorColor()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Notifications\Database;
|
|||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Notifications\CustomEmailNotification;
|
use App\Notifications\CustomEmailNotification;
|
||||||
use App\Notifications\Dto\DiscordMessage;
|
use App\Notifications\Dto\DiscordMessage;
|
||||||
|
use App\Notifications\Dto\SlackMessage;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
|
||||||
class BackupFailed extends CustomEmailNotification
|
class BackupFailed extends CustomEmailNotification
|
||||||
@@ -62,4 +63,19 @@ class BackupFailed extends CustomEmailNotification
|
|||||||
'message' => $message,
|
'message' => $message,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toSlack(): SlackMessage
|
||||||
|
{
|
||||||
|
$title = 'Database backup failed';
|
||||||
|
$description = "Database backup for {$this->name} (db:{$this->database_name}) has FAILED.";
|
||||||
|
|
||||||
|
$description .= "\n\n**Frequency:** {$this->frequency}";
|
||||||
|
$description .= "\n\n**Error Output:**\n{$this->output}";
|
||||||
|
|
||||||
|
return new SlackMessage(
|
||||||
|
title: $title,
|
||||||
|
description: $description,
|
||||||
|
color: SlackMessage::errorColor()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Notifications\Database;
|
|||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Notifications\CustomEmailNotification;
|
use App\Notifications\CustomEmailNotification;
|
||||||
use App\Notifications\Dto\DiscordMessage;
|
use App\Notifications\Dto\DiscordMessage;
|
||||||
|
use App\Notifications\Dto\SlackMessage;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
|
||||||
class BackupSuccess extends CustomEmailNotification
|
class BackupSuccess extends CustomEmailNotification
|
||||||
@@ -60,4 +61,18 @@ class BackupSuccess extends CustomEmailNotification
|
|||||||
'message' => $message,
|
'message' => $message,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toSlack(): SlackMessage
|
||||||
|
{
|
||||||
|
$title = 'Database backup successful';
|
||||||
|
$description = "Database backup for {$this->name} (db:{$this->database_name}) was successful.";
|
||||||
|
|
||||||
|
$description .= "\n\n**Frequency:** {$this->frequency}";
|
||||||
|
|
||||||
|
return new SlackMessage(
|
||||||
|
title: $title,
|
||||||
|
description: $description,
|
||||||
|
color: SlackMessage::successColor()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
app/Notifications/Dto/SlackMessage.php
Normal file
32
app/Notifications/Dto/SlackMessage.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Dto;
|
||||||
|
|
||||||
|
class SlackMessage
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $title,
|
||||||
|
public string $description,
|
||||||
|
public string $color = '#0099ff'
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public static function infoColor(): string
|
||||||
|
{
|
||||||
|
return '#0099ff';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function errorColor(): string
|
||||||
|
{
|
||||||
|
return '#ff0000';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function successColor(): string
|
||||||
|
{
|
||||||
|
return '#00ff00';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function warningColor(): string
|
||||||
|
{
|
||||||
|
return '#ffa500';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,10 @@
|
|||||||
namespace App\Notifications\Internal;
|
namespace App\Notifications\Internal;
|
||||||
|
|
||||||
use App\Notifications\Channels\DiscordChannel;
|
use App\Notifications\Channels\DiscordChannel;
|
||||||
|
use App\Notifications\Channels\SlackChannel;
|
||||||
use App\Notifications\Channels\TelegramChannel;
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
use App\Notifications\Dto\DiscordMessage;
|
use App\Notifications\Dto\DiscordMessage;
|
||||||
|
use App\Notifications\Dto\SlackMessage;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
@@ -25,6 +27,7 @@ class GeneralNotification extends Notification implements ShouldQueue
|
|||||||
$channels = [];
|
$channels = [];
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||||
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||||
|
$isSlackEnabled = data_get($notifiable, 'slack_enabled');
|
||||||
|
|
||||||
if ($isDiscordEnabled) {
|
if ($isDiscordEnabled) {
|
||||||
$channels[] = DiscordChannel::class;
|
$channels[] = DiscordChannel::class;
|
||||||
@@ -32,6 +35,9 @@ class GeneralNotification extends Notification implements ShouldQueue
|
|||||||
if ($isTelegramEnabled) {
|
if ($isTelegramEnabled) {
|
||||||
$channels[] = TelegramChannel::class;
|
$channels[] = TelegramChannel::class;
|
||||||
}
|
}
|
||||||
|
if ($isSlackEnabled) {
|
||||||
|
$channels[] = SlackChannel::class;
|
||||||
|
}
|
||||||
|
|
||||||
return $channels;
|
return $channels;
|
||||||
}
|
}
|
||||||
@@ -51,4 +57,13 @@ class GeneralNotification extends Notification implements ShouldQueue
|
|||||||
'message' => $this->message,
|
'message' => $this->message,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toSlack(): SlackMessage
|
||||||
|
{
|
||||||
|
return new SlackMessage(
|
||||||
|
title: 'Coolify: General Notification',
|
||||||
|
description: $this->message,
|
||||||
|
color: SlackMessage::infoColor(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Notifications\ScheduledTask;
|
|||||||
use App\Models\ScheduledTask;
|
use App\Models\ScheduledTask;
|
||||||
use App\Notifications\CustomEmailNotification;
|
use App\Notifications\CustomEmailNotification;
|
||||||
use App\Notifications\Dto\DiscordMessage;
|
use App\Notifications\Dto\DiscordMessage;
|
||||||
|
use App\Notifications\Dto\SlackMessage;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
|
||||||
class TaskFailed extends CustomEmailNotification
|
class TaskFailed extends CustomEmailNotification
|
||||||
@@ -68,4 +69,24 @@ class TaskFailed extends CustomEmailNotification
|
|||||||
'message' => $message,
|
'message' => $message,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toSlack(): SlackMessage
|
||||||
|
{
|
||||||
|
$title = 'Scheduled task failed';
|
||||||
|
$description = "Scheduled task ({$this->task->name}) failed.";
|
||||||
|
|
||||||
|
if ($this->output) {
|
||||||
|
$description .= "\n\n**Error Output:**\n{$this->output}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->url) {
|
||||||
|
$description .= "\n\n**Task URL:** {$this->url}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SlackMessage(
|
||||||
|
title: $title,
|
||||||
|
description: $description,
|
||||||
|
color: SlackMessage::errorColor()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use App\Notifications\Channels\DiscordChannel;
|
|||||||
use App\Notifications\Channels\TelegramChannel;
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
use App\Notifications\CustomEmailNotification;
|
use App\Notifications\CustomEmailNotification;
|
||||||
use App\Notifications\Dto\DiscordMessage;
|
use App\Notifications\Dto\DiscordMessage;
|
||||||
|
use App\Notifications\Dto\SlackMessage;
|
||||||
|
|
||||||
class DockerCleanup extends CustomEmailNotification
|
class DockerCleanup extends CustomEmailNotification
|
||||||
{
|
{
|
||||||
@@ -21,7 +22,7 @@ class DockerCleanup extends CustomEmailNotification
|
|||||||
// $isEmailEnabled = isEmailEnabled($notifiable);
|
// $isEmailEnabled = isEmailEnabled($notifiable);
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||||
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||||
|
$isSlackEnabled = data_get($notifiable, 'slack_enabled');
|
||||||
if ($isDiscordEnabled) {
|
if ($isDiscordEnabled) {
|
||||||
$channels[] = DiscordChannel::class;
|
$channels[] = DiscordChannel::class;
|
||||||
}
|
}
|
||||||
@@ -31,6 +32,9 @@ class DockerCleanup extends CustomEmailNotification
|
|||||||
if ($isTelegramEnabled) {
|
if ($isTelegramEnabled) {
|
||||||
$channels[] = TelegramChannel::class;
|
$channels[] = TelegramChannel::class;
|
||||||
}
|
}
|
||||||
|
if ($isSlackEnabled) {
|
||||||
|
$channels[] = SlackChannel::class;
|
||||||
|
}
|
||||||
|
|
||||||
return $channels;
|
return $channels;
|
||||||
}
|
}
|
||||||
@@ -62,4 +66,13 @@ class DockerCleanup extends CustomEmailNotification
|
|||||||
'message' => "Coolify: Server '{$this->server->name}' cleanup job done!\n\n{$this->message}",
|
'message' => "Coolify: Server '{$this->server->name}' cleanup job done!\n\n{$this->message}",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toSlack(): SlackMessage
|
||||||
|
{
|
||||||
|
return new SlackMessage(
|
||||||
|
title: 'Server cleanup job done',
|
||||||
|
description: "Server '{$this->server->name}' cleanup job done!\n\n{$this->message}",
|
||||||
|
color: SlackMessage::successColor()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ namespace App\Notifications\Server;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Notifications\Channels\DiscordChannel;
|
use App\Notifications\Channels\DiscordChannel;
|
||||||
use App\Notifications\Channels\EmailChannel;
|
use App\Notifications\Channels\EmailChannel;
|
||||||
|
use App\Notifications\Channels\SlackChannel;
|
||||||
use App\Notifications\Channels\TelegramChannel;
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
use App\Notifications\CustomEmailNotification;
|
use App\Notifications\CustomEmailNotification;
|
||||||
use App\Notifications\Dto\DiscordMessage;
|
use App\Notifications\Dto\DiscordMessage;
|
||||||
|
use App\Notifications\Dto\SlackMessage;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
|
||||||
class ForceDisabled extends CustomEmailNotification
|
class ForceDisabled extends CustomEmailNotification
|
||||||
@@ -23,7 +25,7 @@ class ForceDisabled extends CustomEmailNotification
|
|||||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||||
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||||
|
$isSlackEnabled = data_get($notifiable, 'slack_enabled');
|
||||||
if ($isDiscordEnabled) {
|
if ($isDiscordEnabled) {
|
||||||
$channels[] = DiscordChannel::class;
|
$channels[] = DiscordChannel::class;
|
||||||
}
|
}
|
||||||
@@ -33,6 +35,9 @@ class ForceDisabled extends CustomEmailNotification
|
|||||||
if ($isTelegramEnabled) {
|
if ($isTelegramEnabled) {
|
||||||
$channels[] = TelegramChannel::class;
|
$channels[] = TelegramChannel::class;
|
||||||
}
|
}
|
||||||
|
if ($isSlackEnabled) {
|
||||||
|
$channels[] = SlackChannel::class;
|
||||||
|
}
|
||||||
|
|
||||||
return $channels;
|
return $channels;
|
||||||
}
|
}
|
||||||
@@ -67,4 +72,18 @@ class ForceDisabled extends CustomEmailNotification
|
|||||||
'message' => "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subscriptions).",
|
'message' => "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subscriptions).",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toSlack(): SlackMessage
|
||||||
|
{
|
||||||
|
$title = 'Server disabled';
|
||||||
|
$description = "Server ({$this->server->name}) disabled because it is not paid!\n";
|
||||||
|
$description .= "All automations and integrations are stopped.\n\n";
|
||||||
|
$description .= 'Please update your subscription to enable the server again: https://app.coolify.io/subscriptions';
|
||||||
|
|
||||||
|
return new SlackMessage(
|
||||||
|
title: $title,
|
||||||
|
description: $description,
|
||||||
|
color: SlackMessage::errorColor()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ namespace App\Notifications\Server;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Notifications\Channels\DiscordChannel;
|
use App\Notifications\Channels\DiscordChannel;
|
||||||
use App\Notifications\Channels\EmailChannel;
|
use App\Notifications\Channels\EmailChannel;
|
||||||
|
use App\Notifications\Channels\SlackChannel;
|
||||||
use App\Notifications\Channels\TelegramChannel;
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
use App\Notifications\CustomEmailNotification;
|
use App\Notifications\CustomEmailNotification;
|
||||||
use App\Notifications\Dto\DiscordMessage;
|
use App\Notifications\Dto\DiscordMessage;
|
||||||
|
use App\Notifications\Dto\SlackMessage;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
|
||||||
class ForceEnabled extends CustomEmailNotification
|
class ForceEnabled extends CustomEmailNotification
|
||||||
@@ -23,7 +25,7 @@ class ForceEnabled extends CustomEmailNotification
|
|||||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||||
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||||
|
$isSlackEnabled = data_get($notifiable, 'slack_enabled');
|
||||||
if ($isDiscordEnabled) {
|
if ($isDiscordEnabled) {
|
||||||
$channels[] = DiscordChannel::class;
|
$channels[] = DiscordChannel::class;
|
||||||
}
|
}
|
||||||
@@ -33,6 +35,9 @@ class ForceEnabled extends CustomEmailNotification
|
|||||||
if ($isTelegramEnabled) {
|
if ($isTelegramEnabled) {
|
||||||
$channels[] = TelegramChannel::class;
|
$channels[] = TelegramChannel::class;
|
||||||
}
|
}
|
||||||
|
if ($isSlackEnabled) {
|
||||||
|
$channels[] = SlackChannel::class;
|
||||||
|
}
|
||||||
|
|
||||||
return $channels;
|
return $channels;
|
||||||
}
|
}
|
||||||
@@ -63,4 +68,13 @@ class ForceEnabled extends CustomEmailNotification
|
|||||||
'message' => "Coolify: Server ({$this->server->name}) enabled again!",
|
'message' => "Coolify: Server ({$this->server->name}) enabled again!",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toSlack(): SlackMessage
|
||||||
|
{
|
||||||
|
return new SlackMessage(
|
||||||
|
title: 'Server enabled',
|
||||||
|
description: "Server '{$this->server->name}' enabled again!",
|
||||||
|
color: SlackMessage::successColor()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Notifications\Server;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Notifications\CustomEmailNotification;
|
use App\Notifications\CustomEmailNotification;
|
||||||
use App\Notifications\Dto\DiscordMessage;
|
use App\Notifications\Dto\DiscordMessage;
|
||||||
|
use App\Notifications\Dto\SlackMessage;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
|
||||||
class HighDiskUsage extends CustomEmailNotification
|
class HighDiskUsage extends CustomEmailNotification
|
||||||
@@ -55,4 +56,22 @@ class HighDiskUsage extends CustomEmailNotification
|
|||||||
'message' => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->server_disk_usage_notification_threshold}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.",
|
'message' => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->server_disk_usage_notification_threshold}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toSlack(): SlackMessage
|
||||||
|
{
|
||||||
|
$description = "Server '{$this->server->name}' high disk usage detected!\n";
|
||||||
|
$description .= "Disk usage: {$this->disk_usage}%\n";
|
||||||
|
$description .= "Threshold: {$this->server_disk_usage_notification_threshold}%\n\n";
|
||||||
|
$description .= "Please cleanup your disk to prevent data-loss.\n";
|
||||||
|
$description .= "Tips for cleanup: https://coolify.io/docs/knowledge-base/server/automated-cleanup\n";
|
||||||
|
$description .= "Change settings:\n";
|
||||||
|
$description .= '- Threshold: '.base_url().'/server/'.$this->server->uuid."#advanced\n";
|
||||||
|
$description .= '- Notifications: '.base_url().'/notifications/discord';
|
||||||
|
|
||||||
|
return new SlackMessage(
|
||||||
|
title: 'High disk usage detected',
|
||||||
|
description: $description,
|
||||||
|
color: SlackMessage::errorColor()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ namespace App\Notifications\Server;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Notifications\Channels\DiscordChannel;
|
use App\Notifications\Channels\DiscordChannel;
|
||||||
use App\Notifications\Channels\EmailChannel;
|
use App\Notifications\Channels\EmailChannel;
|
||||||
|
use App\Notifications\Channels\SlackChannel;
|
||||||
use App\Notifications\Channels\TelegramChannel;
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
use App\Notifications\CustomEmailNotification;
|
use App\Notifications\CustomEmailNotification;
|
||||||
use App\Notifications\Dto\DiscordMessage;
|
use App\Notifications\Dto\DiscordMessage;
|
||||||
|
use App\Notifications\Dto\SlackMessage;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
|
||||||
class Reachable extends CustomEmailNotification
|
class Reachable extends CustomEmailNotification
|
||||||
@@ -32,7 +34,7 @@ class Reachable extends CustomEmailNotification
|
|||||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||||
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||||
|
$isSlackEnabled = data_get($notifiable, 'slack_enabled');
|
||||||
if ($isDiscordEnabled) {
|
if ($isDiscordEnabled) {
|
||||||
$channels[] = DiscordChannel::class;
|
$channels[] = DiscordChannel::class;
|
||||||
}
|
}
|
||||||
@@ -42,6 +44,9 @@ class Reachable extends CustomEmailNotification
|
|||||||
if ($isTelegramEnabled) {
|
if ($isTelegramEnabled) {
|
||||||
$channels[] = TelegramChannel::class;
|
$channels[] = TelegramChannel::class;
|
||||||
}
|
}
|
||||||
|
if ($isSlackEnabled) {
|
||||||
|
$channels[] = SlackChannel::class;
|
||||||
|
}
|
||||||
|
|
||||||
return $channels;
|
return $channels;
|
||||||
}
|
}
|
||||||
@@ -72,4 +77,13 @@ class Reachable extends CustomEmailNotification
|
|||||||
'message' => "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!",
|
'message' => "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toSlack(): SlackMessage
|
||||||
|
{
|
||||||
|
return new SlackMessage(
|
||||||
|
title: 'Server revived',
|
||||||
|
description: "Server '{$this->server->name}' revived.\nAll automations & integrations are turned on again!",
|
||||||
|
color: SlackMessage::successColor()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ namespace App\Notifications\Server;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Notifications\Channels\DiscordChannel;
|
use App\Notifications\Channels\DiscordChannel;
|
||||||
use App\Notifications\Channels\EmailChannel;
|
use App\Notifications\Channels\EmailChannel;
|
||||||
|
use App\Notifications\Channels\SlackChannel;
|
||||||
use App\Notifications\Channels\TelegramChannel;
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
use App\Notifications\CustomEmailNotification;
|
use App\Notifications\CustomEmailNotification;
|
||||||
use App\Notifications\Dto\DiscordMessage;
|
use App\Notifications\Dto\DiscordMessage;
|
||||||
|
use App\Notifications\Dto\SlackMessage;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
|
||||||
class Unreachable extends CustomEmailNotification
|
class Unreachable extends CustomEmailNotification
|
||||||
@@ -32,6 +34,7 @@ class Unreachable extends CustomEmailNotification
|
|||||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||||
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||||
|
$isSlackEnabled = data_get($notifiable, 'slack_enabled');
|
||||||
|
|
||||||
if ($isDiscordEnabled) {
|
if ($isDiscordEnabled) {
|
||||||
$channels[] = DiscordChannel::class;
|
$channels[] = DiscordChannel::class;
|
||||||
@@ -42,6 +45,9 @@ class Unreachable extends CustomEmailNotification
|
|||||||
if ($isTelegramEnabled) {
|
if ($isTelegramEnabled) {
|
||||||
$channels[] = TelegramChannel::class;
|
$channels[] = TelegramChannel::class;
|
||||||
}
|
}
|
||||||
|
if ($isSlackEnabled) {
|
||||||
|
$channels[] = SlackChannel::class;
|
||||||
|
}
|
||||||
|
|
||||||
return $channels;
|
return $channels;
|
||||||
}
|
}
|
||||||
@@ -76,4 +82,17 @@ class Unreachable extends CustomEmailNotification
|
|||||||
'message' => "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server and turn on all automations & integrations.",
|
'message' => "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server and turn on all automations & integrations.",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toSlack(): SlackMessage
|
||||||
|
{
|
||||||
|
$description = "Your server '{$this->server->name}' is unreachable.\n";
|
||||||
|
$description .= "All automations & integrations are turned off!\n\n";
|
||||||
|
$description .= '*IMPORTANT:* We automatically try to revive your server and turn on all automations & integrations.';
|
||||||
|
|
||||||
|
return new SlackMessage(
|
||||||
|
title: 'Server unreachable',
|
||||||
|
description: $description,
|
||||||
|
color: SlackMessage::errorColor()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Notifications;
|
namespace App\Notifications;
|
||||||
|
|
||||||
use App\Notifications\Dto\DiscordMessage;
|
use App\Notifications\Dto\DiscordMessage;
|
||||||
|
use App\Notifications\Dto\SlackMessage;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
@@ -67,4 +68,12 @@ class Test extends Notification implements ShouldQueue
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toSlack(): SlackMessage
|
||||||
|
{
|
||||||
|
return new SlackMessage(
|
||||||
|
title: 'Test Slack Notification',
|
||||||
|
description: 'This is a test Slack notification from Coolify.'
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class Checkbox extends Component
|
|||||||
public ?string $id = null,
|
public ?string $id = null,
|
||||||
public ?string $name = null,
|
public ?string $name = null,
|
||||||
public ?string $value = null,
|
public ?string $value = null,
|
||||||
|
public ?string $domValue = null,
|
||||||
public ?string $label = null,
|
public ?string $label = null,
|
||||||
public ?string $helper = null,
|
public ?string $helper = null,
|
||||||
public string|bool|null $checked = false,
|
public string|bool|null $checked = false,
|
||||||
|
|||||||
@@ -288,9 +288,9 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
|
|||||||
$host_without_www = str($host)->replace('www.', '');
|
$host_without_www = str($host)->replace('www.', '');
|
||||||
$schema = $url->getScheme();
|
$schema = $url->getScheme();
|
||||||
$port = $url->getPort();
|
$port = $url->getPort();
|
||||||
$handle = "handle_path";
|
$handle = 'handle_path';
|
||||||
if ( ! $is_stripprefix_enabled){
|
if (! $is_stripprefix_enabled) {
|
||||||
$handle = "handle";
|
$handle = 'handle';
|
||||||
}
|
}
|
||||||
if (is_null($port) && ! is_null($onlyPort)) {
|
if (is_null($port) && ! is_null($onlyPort)) {
|
||||||
$port = $onlyPort;
|
$port = $onlyPort;
|
||||||
@@ -302,7 +302,6 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
|
|||||||
$labels->push("caddy_{$loop}.header=-Server");
|
$labels->push("caddy_{$loop}.header=-Server");
|
||||||
$labels->push("caddy_{$loop}.try_files={path} /index.html /index.php");
|
$labels->push("caddy_{$loop}.try_files={path} /index.html /index.php");
|
||||||
|
|
||||||
|
|
||||||
if ($port) {
|
if ($port) {
|
||||||
$labels->push("caddy_{$loop}.{$handle}.{$loop}_reverse_proxy={{upstreams $port}}");
|
$labels->push("caddy_{$loop}.{$handle}.{$loop}_reverse_proxy={{upstreams $port}}");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -173,13 +173,12 @@ function generate_default_proxy_configuration(Server $server)
|
|||||||
],
|
],
|
||||||
'volumes' => [
|
'volumes' => [
|
||||||
'/var/run/docker.sock:/var/run/docker.sock:ro',
|
'/var/run/docker.sock:/var/run/docker.sock:ro',
|
||||||
"{$proxy_path}:/traefik",
|
|
||||||
],
|
],
|
||||||
'command' => [
|
'command' => [
|
||||||
'--ping=true',
|
'--ping=true',
|
||||||
'--ping.entrypoint=http',
|
'--ping.entrypoint=http',
|
||||||
'--api.dashboard=true',
|
'--api.dashboard=true',
|
||||||
'--api.insecure=false',
|
|
||||||
'--entrypoints.http.address=:80',
|
'--entrypoints.http.address=:80',
|
||||||
'--entrypoints.https.address=:443',
|
'--entrypoints.https.address=:443',
|
||||||
'--entrypoints.http.http.encodequerysemicolons=true',
|
'--entrypoints.http.http.encodequerysemicolons=true',
|
||||||
@@ -187,21 +186,26 @@ function generate_default_proxy_configuration(Server $server)
|
|||||||
'--entrypoints.https.http.encodequerysemicolons=true',
|
'--entrypoints.https.http.encodequerysemicolons=true',
|
||||||
'--entryPoints.https.http2.maxConcurrentStreams=50',
|
'--entryPoints.https.http2.maxConcurrentStreams=50',
|
||||||
'--entrypoints.https.http3',
|
'--entrypoints.https.http3',
|
||||||
'--providers.docker.exposedbydefault=false',
|
|
||||||
'--providers.file.directory=/traefik/dynamic/',
|
'--providers.file.directory=/traefik/dynamic/',
|
||||||
|
'--providers.docker.exposedbydefault=false',
|
||||||
'--providers.file.watch=true',
|
'--providers.file.watch=true',
|
||||||
'--certificatesresolvers.letsencrypt.acme.httpchallenge=true',
|
'--certificatesresolvers.letsencrypt.acme.httpchallenge=true',
|
||||||
'--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json',
|
|
||||||
'--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http',
|
'--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http',
|
||||||
|
'--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json',
|
||||||
],
|
],
|
||||||
'labels' => $labels,
|
'labels' => $labels,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
// $config['services']['traefik']['command'][] = "--log.level=debug";
|
$config['services']['traefik']['command'][] = '--api.insecure=true';
|
||||||
|
$config['services']['traefik']['command'][] = '--log.level=debug';
|
||||||
$config['services']['traefik']['command'][] = '--accesslog.filepath=/traefik/access.log';
|
$config['services']['traefik']['command'][] = '--accesslog.filepath=/traefik/access.log';
|
||||||
$config['services']['traefik']['command'][] = '--accesslog.bufferingsize=100';
|
$config['services']['traefik']['command'][] = '--accesslog.bufferingsize=100';
|
||||||
|
$config['services']['traefik']['volumes'][] = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/:/traefik';
|
||||||
|
} else {
|
||||||
|
$config['services']['traefik']['command'][] = '--api.insecure=false';
|
||||||
|
$config['services']['traefik']['volumes'][] = "{$proxy_path}:/traefik";
|
||||||
}
|
}
|
||||||
if ($server->isSwarm()) {
|
if ($server->isSwarm()) {
|
||||||
data_forget($config, 'services.traefik.container_name');
|
data_forget($config, 'services.traefik.container_name');
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ use App\Models\Team;
|
|||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Notifications\Channels\DiscordChannel;
|
use App\Notifications\Channels\DiscordChannel;
|
||||||
use App\Notifications\Channels\EmailChannel;
|
use App\Notifications\Channels\EmailChannel;
|
||||||
|
use App\Notifications\Channels\SlackChannel;
|
||||||
use App\Notifications\Channels\TelegramChannel;
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
use App\Notifications\Internal\GeneralNotification;
|
use App\Notifications\Internal\GeneralNotification;
|
||||||
use Carbon\CarbonImmutable;
|
use Carbon\CarbonImmutable;
|
||||||
@@ -469,11 +470,13 @@ function setNotificationChannels($notifiable, $event)
|
|||||||
{
|
{
|
||||||
$channels = [];
|
$channels = [];
|
||||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||||
|
$isSlackEnabled = data_get($notifiable, 'slack_enabled');
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||||
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||||
$isSubscribedToEmailEvent = data_get($notifiable, "smtp_notifications_$event");
|
$isSubscribedToEmailEvent = data_get($notifiable, "smtp_notifications_$event");
|
||||||
$isSubscribedToDiscordEvent = data_get($notifiable, "discord_notifications_$event");
|
$isSubscribedToDiscordEvent = data_get($notifiable, "discord_notifications_$event");
|
||||||
$isSubscribedToTelegramEvent = data_get($notifiable, "telegram_notifications_$event");
|
$isSubscribedToTelegramEvent = data_get($notifiable, "telegram_notifications_$event");
|
||||||
|
$isSubscribedToSlackEvent = data_get($notifiable, "slack_notifications_$event");
|
||||||
|
|
||||||
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
|
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
|
||||||
$channels[] = DiscordChannel::class;
|
$channels[] = DiscordChannel::class;
|
||||||
@@ -484,6 +487,9 @@ function setNotificationChannels($notifiable, $event)
|
|||||||
if ($isTelegramEnabled && $isSubscribedToTelegramEvent) {
|
if ($isTelegramEnabled && $isSubscribedToTelegramEvent) {
|
||||||
$channels[] = TelegramChannel::class;
|
$channels[] = TelegramChannel::class;
|
||||||
}
|
}
|
||||||
|
if ($isSlackEnabled && $isSubscribedToSlackEvent) {
|
||||||
|
$channels[] = SlackChannel::class;
|
||||||
|
}
|
||||||
|
|
||||||
return $channels;
|
return $channels;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": "^8.2",
|
"php": "^8.2",
|
||||||
"3sidedcube/laravel-redoc": "^1.0",
|
"3sidedcube/laravel-redoc": "^1.0",
|
||||||
"danharrin/livewire-rate-limiting": "^1.1",
|
"danharrin/livewire-rate-limiting": "2.0.0",
|
||||||
"doctrine/dbal": "^4.2",
|
"doctrine/dbal": "^4.2",
|
||||||
"guzzlehttp/guzzle": "^7.5.0",
|
"guzzlehttp/guzzle": "^7.5.0",
|
||||||
"laravel/fortify": "^1.16.0",
|
"laravel/fortify": "^1.16.0",
|
||||||
|
|||||||
1488
composer.lock
generated
1488
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'coolify' => [
|
'coolify' => [
|
||||||
'version' => '4.0.0-beta.376',
|
'version' => '4.0.0-beta.377',
|
||||||
'self_hosted' => env('SELF_HOSTED', true),
|
'self_hosted' => env('SELF_HOSTED', true),
|
||||||
'autoupdate' => env('AUTOUPDATE'),
|
'autoupdate' => env('AUTOUPDATE'),
|
||||||
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\PersonalAccessToken;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$tokens = PersonalAccessToken::all();
|
||||||
|
foreach ($tokens as $token) {
|
||||||
|
$abilities = collect();
|
||||||
|
if (in_array('*', $token->abilities)) {
|
||||||
|
$abilities->push('root');
|
||||||
|
}
|
||||||
|
if (in_array('read-only', $token->abilities)) {
|
||||||
|
$abilities->push('read');
|
||||||
|
}
|
||||||
|
if (in_array('view:sensitive', $token->abilities)) {
|
||||||
|
$abilities->push('read', 'read:sensitive');
|
||||||
|
}
|
||||||
|
$token->abilities = $abilities->unique()->values()->all();
|
||||||
|
$token->save();
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Error renaming token permissions: '.$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$tokens = PersonalAccessToken::all();
|
||||||
|
foreach ($tokens as $token) {
|
||||||
|
$abilities = collect();
|
||||||
|
if (in_array('write', $token->abilities)) {
|
||||||
|
$abilities->push('*');
|
||||||
|
} else {
|
||||||
|
if (in_array('read', $token->abilities)) {
|
||||||
|
$abilities->push('read-only');
|
||||||
|
}
|
||||||
|
if (in_array('read:sensitive', $token->abilities)) {
|
||||||
|
$abilities->push('view:sensitive');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$token->abilities = $abilities->unique()->values()->all();
|
||||||
|
$token->save();
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Error renaming token permissions: '.$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('teams', function (Blueprint $table) {
|
||||||
|
$table->boolean('slack_enabled')->default(false);
|
||||||
|
$table->string('slack_webhook_url')->nullable();
|
||||||
|
$table->boolean('slack_notifications_test')->default(true);
|
||||||
|
$table->boolean('slack_notifications_deployments')->default(true);
|
||||||
|
$table->boolean('slack_notifications_status_changes')->default(true);
|
||||||
|
$table->boolean('slack_notifications_database_backups')->default(true);
|
||||||
|
$table->boolean('slack_notifications_scheduled_tasks')->default(true);
|
||||||
|
$table->boolean('slack_notifications_server_disk_usage')->default(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('teams', function (Blueprint $table) {
|
||||||
|
$table->dropColumn([
|
||||||
|
'slack_enabled',
|
||||||
|
'slack_webhook_url',
|
||||||
|
'slack_notifications_test',
|
||||||
|
'slack_notifications_deployments',
|
||||||
|
'slack_notifications_status_changes',
|
||||||
|
'slack_notifications_database_backups',
|
||||||
|
'slack_notifications_scheduled_tasks',
|
||||||
|
'slack_notifications_server_disk_usage',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('disable_build_cache')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('disable_build_cache');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -23,6 +23,7 @@ class GithubAppSeeder extends Seeder
|
|||||||
GithubApp::create([
|
GithubApp::create([
|
||||||
'name' => 'coolify-laravel-development-public',
|
'name' => 'coolify-laravel-development-public',
|
||||||
'uuid' => '69420',
|
'uuid' => '69420',
|
||||||
|
'organization' => 'coollabsio',
|
||||||
'api_url' => 'https://api.github.com',
|
'api_url' => 'https://api.github.com',
|
||||||
'html_url' => 'https://github.com',
|
'html_url' => 'https://github.com',
|
||||||
'is_public' => false,
|
'is_public' => false,
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ class ProductionSeeder extends Seeder
|
|||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
echo "[x]: Running in cloud mode.\n";
|
echo " Running in cloud mode.\n";
|
||||||
} else {
|
} else {
|
||||||
echo "[x]: Running in self-hosted mode.\n";
|
echo " Running in self-hosted mode.\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix for 4.0.0-beta.37
|
// Fix for 4.0.0-beta.37
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ services:
|
|||||||
coolify:
|
coolify:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./docker/dev/Dockerfile
|
dockerfile: ./docker/development/Dockerfile
|
||||||
|
args:
|
||||||
|
- USER_ID=${USERID:-1000}
|
||||||
|
- GROUP_ID=${GROUPID:-1000}
|
||||||
ports:
|
ports:
|
||||||
- "${APP_PORT:-8000}:80"
|
- "${APP_PORT:-8000}:80"
|
||||||
environment:
|
environment:
|
||||||
PUID: "${USERID:-1000}"
|
AUTORUN_ENABLED: false
|
||||||
PGID: "${GROUPID:-1000}"
|
|
||||||
SSL_MODE: "off"
|
|
||||||
AUTORUN_LARAVEL_STORAGE_LINK: "false"
|
|
||||||
AUTORUN_LARAVEL_MIGRATION: "false"
|
|
||||||
PUSHER_HOST: "${PUSHER_HOST}"
|
PUSHER_HOST: "${PUSHER_HOST}"
|
||||||
PUSHER_PORT: "${PUSHER_PORT}"
|
PUSHER_PORT: "${PUSHER_PORT}"
|
||||||
PUSHER_SCHEME: "${PUSHER_SCHEME:-http}"
|
PUSHER_SCHEME: "${PUSHER_SCHEME:-http}"
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
# Versions
|
|
||||||
# https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx-alpine
|
|
||||||
ARG SERVERSIDEUP_PHP_VERSION=8.2-fpm-nginx-v2.2.1
|
|
||||||
# https://github.com/minio/mc/releases
|
|
||||||
ARG MINIO_VERSION=RELEASE.2024-11-05T11-29-45Z
|
|
||||||
# https://github.com/cloudflare/cloudflared/releases
|
|
||||||
ARG CLOUDFLARED_VERSION=2024.11.0
|
|
||||||
# https://www.postgresql.org/support/versioning/ - Can not updated automatically so keep it at 15
|
|
||||||
ARG POSTGRES_VERSION=15
|
|
||||||
|
|
||||||
FROM minio/mc:${MINIO_VERSION} AS minio-client
|
|
||||||
|
|
||||||
FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION}
|
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
|
||||||
ARG CLOUDFLARED_VERSION
|
|
||||||
ARG MINIO_VERSION
|
|
||||||
ARG POSTGRES_VERSION
|
|
||||||
|
|
||||||
# Use build arguments for caching
|
|
||||||
ARG BUILDTIME_DEPS="dirmngr ca-certificates software-properties-common gnupg gnupg2 apt-transport-https curl"
|
|
||||||
ARG RUNTIME_DEPS="postgresql-client-$POSTGRES_VERSION php8.2-pgsql openssh-client git git-lfs jq lsof"
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
RUN --mount=type=cache,target=/var/cache/apt \
|
|
||||||
apt-get update && \
|
|
||||||
apt-get install -y $BUILDTIME_DEPS && \
|
|
||||||
curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null && \
|
|
||||||
echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main | tee -a /etc/apt/sources.list.d/postgresql.list && \
|
|
||||||
apt-get update && \
|
|
||||||
apt-get install -y $RUNTIME_DEPS && \
|
|
||||||
apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
|
|
||||||
|
|
||||||
COPY --chmod=755 docker/dev/etc/s6-overlay/ /etc/s6-overlay/
|
|
||||||
|
|
||||||
COPY docker/dev/nginx.conf /etc/nginx/conf.d/custom.conf
|
|
||||||
|
|
||||||
RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc && \
|
|
||||||
echo "alias a='php artisan'" >>/etc/bash.bashrc
|
|
||||||
|
|
||||||
RUN mkdir -p /usr/local/bin
|
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/root/.cache \
|
|
||||||
/bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
|
|
||||||
echo 'amd64' && \
|
|
||||||
curl -sSL https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
|
|
||||||
;fi"
|
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/root/.cache \
|
|
||||||
/bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
|
|
||||||
echo 'arm64' && \
|
|
||||||
curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
|
|
||||||
;fi"
|
|
||||||
|
|
||||||
COPY --from=minio-client /usr/bin/mc /usr/bin/mc
|
|
||||||
RUN chmod +x /usr/bin/mc
|
|
||||||
|
|
||||||
RUN { \
|
|
||||||
echo 'upload_max_filesize=256M'; \
|
|
||||||
echo 'post_max_size=256M'; \
|
|
||||||
} > /etc/php/current_version/cli/conf.d/upload-limits.ini
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/command/execlineb -P
|
|
||||||
foreground {
|
|
||||||
s6-sleep 5
|
|
||||||
su - webuser -c "php /var/www/html/artisan start:horizon"
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/command/execlineb -P
|
|
||||||
foreground { composer -d /var/www/html/ install }
|
|
||||||
foreground { php /var/www/html/artisan migrate --step }
|
|
||||||
foreground { php /var/www/html/artisan dev --init }
|
|
||||||
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/command/execlineb -P
|
|
||||||
foreground {
|
|
||||||
s6-sleep 5
|
|
||||||
su - webuser -c "php /var/www/html/artisan start:scheduler"
|
|
||||||
}
|
|
||||||
81
docker/development/Dockerfile
Normal file
81
docker/development/Dockerfile
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# Versions
|
||||||
|
# https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine
|
||||||
|
ARG SERVERSIDEUP_PHP_VERSION=8.4-fpm-nginx-alpine
|
||||||
|
# https://github.com/minio/mc/releases
|
||||||
|
ARG MINIO_VERSION=RELEASE.2024-11-17T19-35-25Z
|
||||||
|
# https://github.com/cloudflare/cloudflared/releases
|
||||||
|
ARG CLOUDFLARED_VERSION=2024.11.1
|
||||||
|
# https://www.postgresql.org/support/versioning/
|
||||||
|
ARG POSTGRES_VERSION=15
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# Get MinIO client
|
||||||
|
# =================================================================
|
||||||
|
FROM minio/mc:${MINIO_VERSION} AS minio-client
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# Final Stage: Production image
|
||||||
|
# =================================================================
|
||||||
|
FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION}
|
||||||
|
|
||||||
|
ARG USER_ID
|
||||||
|
ARG GROUP_ID
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
ARG POSTGRES_VERSION
|
||||||
|
ARG CLOUDFLARED_VERSION
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \
|
||||||
|
docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx
|
||||||
|
|
||||||
|
# Install PostgreSQL repository and keys
|
||||||
|
RUN apk add --no-cache gnupg && \
|
||||||
|
mkdir -p /usr/share/keyrings && \
|
||||||
|
curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor > /usr/share/keyrings/postgresql.gpg
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
postgresql${POSTGRES_VERSION}-client \
|
||||||
|
openssh-client \
|
||||||
|
git \
|
||||||
|
git-lfs \
|
||||||
|
jq \
|
||||||
|
lsof \
|
||||||
|
vim
|
||||||
|
|
||||||
|
# Configure shell aliases
|
||||||
|
RUN echo "alias ll='ls -al'" >> /etc/profile && \
|
||||||
|
echo "alias a='php artisan'" >> /etc/profile && \
|
||||||
|
echo "alias logs='tail -f storage/logs/laravel.log'" >> /etc/profile
|
||||||
|
|
||||||
|
# Install Cloudflared based on architecture
|
||||||
|
RUN mkdir -p /usr/local/bin && \
|
||||||
|
if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then \
|
||||||
|
curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64" -o /usr/local/bin/cloudflared; \
|
||||||
|
elif [ "${TARGETPLATFORM}" = "linux/arm64" ]; then \
|
||||||
|
curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64" -o /usr/local/bin/cloudflared; \
|
||||||
|
fi && \
|
||||||
|
chmod +x /usr/local/bin/cloudflared
|
||||||
|
|
||||||
|
# Configure PHP
|
||||||
|
COPY docker/development/etc/php/conf.d/zzz-custom-php.ini /usr/local/etc/php/conf.d/zzz-custom-php.ini
|
||||||
|
ENV PHP_OPCACHE_ENABLE=0
|
||||||
|
|
||||||
|
# Configure Nginx and S6 overlay
|
||||||
|
COPY docker/development/etc/nginx/conf.d/custom.conf /etc/nginx/conf.d/custom.conf
|
||||||
|
COPY docker/development/etc/nginx/site-opts.d/http.conf /etc/nginx/site-opts.d/http.conf
|
||||||
|
COPY --chmod=755 docker/development/etc/s6-overlay/ /etc/s6-overlay/
|
||||||
|
|
||||||
|
RUN mkdir -p /etc/nginx/conf.d && \
|
||||||
|
chown -R www-data:www-data /etc/nginx && \
|
||||||
|
chmod -R 755 /etc/nginx
|
||||||
|
|
||||||
|
# Install MinIO client
|
||||||
|
COPY --from=minio-client /usr/bin/mc /usr/bin/mc
|
||||||
|
RUN chmod +x /usr/bin/mc
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER www-data
|
||||||
45
docker/development/etc/nginx/site-opts.d/http.conf
Normal file
45
docker/development/etc/nginx/site-opts.d/http.conf
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
listen 80 default_server;
|
||||||
|
listen [::]:80 default_server;
|
||||||
|
listen 8080 default_server;
|
||||||
|
listen [::]:8080 default_server;
|
||||||
|
|
||||||
|
root /var/www/html/public;
|
||||||
|
|
||||||
|
# Set allowed "index" files
|
||||||
|
index index.html index.htm index.php;
|
||||||
|
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
charset utf-8;
|
||||||
|
|
||||||
|
# Set max upload to 2048M
|
||||||
|
client_max_body_size 2048M;
|
||||||
|
|
||||||
|
# Healthchecks: Set /healthcheck to be the healthcheck URL
|
||||||
|
location /healthcheck {
|
||||||
|
access_log off;
|
||||||
|
|
||||||
|
# set max 5 seconds for healthcheck
|
||||||
|
fastcgi_read_timeout 5s;
|
||||||
|
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_NAME /healthcheck;
|
||||||
|
fastcgi_param SCRIPT_FILENAME /healthcheck;
|
||||||
|
fastcgi_pass 127.0.0.1:9000;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Have NGINX try searching for PHP files as well
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pass "*.php" files to PHP-FPM
|
||||||
|
location ~ \.php$ {
|
||||||
|
fastcgi_pass 127.0.0.1:9000;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_buffers 8 8k;
|
||||||
|
fastcgi_buffer_size 8k;
|
||||||
|
fastcgi_read_timeout 99;
|
||||||
|
}
|
||||||
9
docker/development/etc/php/conf.d/zzz-custom-php.ini
Normal file
9
docker/development/etc/php/conf.d/zzz-custom-php.ini
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
error_reporting = E_ERROR
|
||||||
|
error_log = /dev/stderr
|
||||||
|
log_errors = On
|
||||||
|
log_errors_max_len = 8192
|
||||||
|
ignore_repeated_errors = On
|
||||||
|
ignore_repeated_source = On
|
||||||
|
|
||||||
|
upload_max_filesize = 256M
|
||||||
|
post_max_size = 256M
|
||||||
12
docker/development/etc/s6-overlay/s6-rc.d/horizon/run
Normal file
12
docker/development/etc/s6-overlay/s6-rc.d/horizon/run
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/command/execlineb -P
|
||||||
|
|
||||||
|
# Use with-contenv to ensure environment variables are available
|
||||||
|
with-contenv
|
||||||
|
cd /var/www/html
|
||||||
|
|
||||||
|
foreground {
|
||||||
|
php
|
||||||
|
artisan
|
||||||
|
start:horizon
|
||||||
|
}
|
||||||
|
|
||||||
22
docker/development/etc/s6-overlay/s6-rc.d/init-setup/up
Normal file
22
docker/development/etc/s6-overlay/s6-rc.d/init-setup/up
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/command/execlineb -P
|
||||||
|
|
||||||
|
# Use with-contenv to ensure environment variables are available
|
||||||
|
with-contenv
|
||||||
|
cd /var/www/html
|
||||||
|
foreground {
|
||||||
|
composer
|
||||||
|
install
|
||||||
|
}
|
||||||
|
foreground {
|
||||||
|
php
|
||||||
|
artisan
|
||||||
|
migrate
|
||||||
|
--step
|
||||||
|
}
|
||||||
|
foreground {
|
||||||
|
php
|
||||||
|
artisan
|
||||||
|
dev
|
||||||
|
--init
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#!/command/execlineb -P
|
||||||
|
|
||||||
|
# Use with-contenv to ensure environment variables are available
|
||||||
|
with-contenv
|
||||||
|
cd /var/www/html
|
||||||
|
|
||||||
|
foreground {
|
||||||
|
php
|
||||||
|
artisan
|
||||||
|
start:scheduler
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
# Versions
|
|
||||||
# https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx-alpine
|
|
||||||
ARG SERVERSIDEUP_PHP_VERSION=8.2-fpm-nginx-v2.2.1
|
|
||||||
# https://github.com/minio/mc/releases
|
|
||||||
ARG MINIO_VERSION=RELEASE.2024-11-05T11-29-45Z
|
|
||||||
# https://github.com/cloudflare/cloudflared/releases
|
|
||||||
ARG CLOUDFLARED_VERSION=2024.11.0
|
|
||||||
# https://www.postgresql.org/support/versioning/ - Can not updated automatically so keep it at 15
|
|
||||||
ARG POSTGRES_VERSION=15
|
|
||||||
|
|
||||||
|
|
||||||
FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION} AS base
|
|
||||||
WORKDIR /var/www/html
|
|
||||||
|
|
||||||
COPY composer.json composer.lock ./
|
|
||||||
RUN composer install --no-dev --no-interaction --no-plugins --no-scripts --prefer-dist
|
|
||||||
|
|
||||||
FROM node:20 AS static-assets
|
|
||||||
WORKDIR /app
|
|
||||||
COPY . .
|
|
||||||
COPY --from=base --chown=9999:9999 /var/www/html .
|
|
||||||
RUN npm install
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
FROM minio/mc:${MINIO_VERSION} AS minio-client
|
|
||||||
|
|
||||||
FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION}
|
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
|
||||||
ARG CLOUDFLARED_VERSION
|
|
||||||
ARG POSTGRES_VERSION
|
|
||||||
ARG CI=true
|
|
||||||
|
|
||||||
WORKDIR /var/www/html
|
|
||||||
|
|
||||||
RUN apt-get update
|
|
||||||
# Postgres version requirements
|
|
||||||
RUN apt install dirmngr ca-certificates software-properties-common gnupg gnupg2 apt-transport-https curl -y
|
|
||||||
RUN curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null
|
|
||||||
|
|
||||||
RUN echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main | tee -a /etc/apt/sources.list.d/postgresql.list
|
|
||||||
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get install postgresql-client-${POSTGRES_VERSION} -y
|
|
||||||
|
|
||||||
# Coolify requirements
|
|
||||||
RUN apt-get install -y php8.2-pgsql openssh-client git git-lfs jq lsof vim
|
|
||||||
RUN apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
|
|
||||||
|
|
||||||
COPY docker/prod/nginx.conf /etc/nginx/conf.d/custom.conf
|
|
||||||
|
|
||||||
COPY --from=base --chown=9999:9999 /var/www/html .
|
|
||||||
|
|
||||||
COPY --chown=9999:9999 . .
|
|
||||||
RUN composer dump-autoload
|
|
||||||
|
|
||||||
COPY --from=static-assets --chown=9999:9999 /app/public/build ./public/build
|
|
||||||
COPY --chmod=755 docker/prod/etc/s6-overlay/ /etc/s6-overlay/
|
|
||||||
|
|
||||||
RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc
|
|
||||||
RUN echo "alias a='php artisan'" >>/etc/bash.bashrc
|
|
||||||
RUN echo "alias logs='tail -f storage/logs/laravel.log'" >>/etc/bash.bashrc
|
|
||||||
|
|
||||||
RUN mkdir -p /usr/local/bin
|
|
||||||
|
|
||||||
RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
|
|
||||||
echo 'amd64' && \
|
|
||||||
curl -sSL https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
|
|
||||||
;fi"
|
|
||||||
|
|
||||||
RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
|
|
||||||
echo 'arm64' && \
|
|
||||||
curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
|
|
||||||
;fi"
|
|
||||||
|
|
||||||
RUN { \
|
|
||||||
echo 'upload_max_filesize=256M'; \
|
|
||||||
echo 'post_max_size=256M'; \
|
|
||||||
} > /etc/php/current_version/cli/conf.d/upload-limits.ini
|
|
||||||
|
|
||||||
COPY --from=minio-client /usr/bin/mc /usr/bin/mc
|
|
||||||
RUN chmod +x /usr/bin/mc
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
#!/command/execlineb -P
|
|
||||||
php /var/www/html/artisan migrate --force --isolated
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/command/execlineb -P
|
|
||||||
foreground {
|
|
||||||
s6-sleep 5
|
|
||||||
su - webuser -c "php /var/www/html/artisan start:horizon"
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/command/execlineb -P
|
|
||||||
s6-setuidgid webuser
|
|
||||||
php /var/www/html/artisan app:init
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
#!/command/execlineb -P
|
|
||||||
php /var/www/html/artisan db:seed --class ProductionSeeder --force
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/command/execlineb -P
|
|
||||||
foreground {
|
|
||||||
s6-sleep 5
|
|
||||||
su - webuser -c "php /var/www/html/artisan start:scheduler"
|
|
||||||
}
|
|
||||||
136
docker/production/Dockerfile
Normal file
136
docker/production/Dockerfile
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
# Versions
|
||||||
|
# https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine
|
||||||
|
ARG SERVERSIDEUP_PHP_VERSION=8.4-fpm-nginx-alpine
|
||||||
|
# https://github.com/minio/mc/releases
|
||||||
|
ARG MINIO_VERSION=RELEASE.2024-11-17T19-35-25Z
|
||||||
|
# https://github.com/cloudflare/cloudflared/releases
|
||||||
|
ARG CLOUDFLARED_VERSION=2024.11.1
|
||||||
|
# https://www.postgresql.org/support/versioning/
|
||||||
|
ARG POSTGRES_VERSION=15
|
||||||
|
|
||||||
|
# Add user/group
|
||||||
|
ARG USER_ID=9999
|
||||||
|
ARG GROUP_ID=9999
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# Stage 1: Composer dependencies
|
||||||
|
# =================================================================
|
||||||
|
FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION} AS base
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
ARG USER_ID
|
||||||
|
ARG GROUP_ID
|
||||||
|
|
||||||
|
RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \
|
||||||
|
docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
COPY --chown=www-data:www-data composer.json composer.lock ./
|
||||||
|
RUN composer install --no-dev --no-interaction --no-plugins --no-scripts --prefer-dist
|
||||||
|
|
||||||
|
USER www-data
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# Stage 2: Frontend assets compilation
|
||||||
|
# =================================================================
|
||||||
|
FROM node:20-alpine AS static-assets
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json vite.config.js tailwind.config.js postcss.config.cjs ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# Stage 3: Get MinIO client
|
||||||
|
# =================================================================
|
||||||
|
FROM minio/mc:${MINIO_VERSION} AS minio-client
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# Final Stage: Production image
|
||||||
|
# =================================================================
|
||||||
|
FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION}
|
||||||
|
|
||||||
|
ARG USER_ID
|
||||||
|
ARG GROUP_ID
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
ARG POSTGRES_VERSION
|
||||||
|
ARG CLOUDFLARED_VERSION
|
||||||
|
ARG CI=true
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \
|
||||||
|
docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx
|
||||||
|
|
||||||
|
# Install PostgreSQL repository and keys
|
||||||
|
RUN apk add --no-cache gnupg && \
|
||||||
|
mkdir -p /usr/share/keyrings && \
|
||||||
|
curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor > /usr/share/keyrings/postgresql.gpg
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
postgresql${POSTGRES_VERSION}-client \
|
||||||
|
openssh-client \
|
||||||
|
git \
|
||||||
|
git-lfs \
|
||||||
|
jq \
|
||||||
|
lsof \
|
||||||
|
vim
|
||||||
|
|
||||||
|
# Configure shell aliases
|
||||||
|
RUN echo "alias ll='ls -al'" >> /etc/profile && \
|
||||||
|
echo "alias a='php artisan'" >> /etc/profile && \
|
||||||
|
echo "alias logs='tail -f storage/logs/laravel.log'" >> /etc/profile
|
||||||
|
|
||||||
|
# Install Cloudflared based on architecture
|
||||||
|
RUN mkdir -p /usr/local/bin && \
|
||||||
|
if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then \
|
||||||
|
curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64" -o /usr/local/bin/cloudflared; \
|
||||||
|
elif [ "${TARGETPLATFORM}" = "linux/arm64" ]; then \
|
||||||
|
curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64" -o /usr/local/bin/cloudflared; \
|
||||||
|
fi && \
|
||||||
|
chmod +x /usr/local/bin/cloudflared
|
||||||
|
|
||||||
|
# Configure PHP
|
||||||
|
COPY docker/production/etc/php/conf.d/zzz-custom-php.ini /usr/local/etc/php/conf.d/zzz-custom-php.ini
|
||||||
|
ENV PHP_OPCACHE_ENABLE=1
|
||||||
|
|
||||||
|
# Copy application files from previous stages
|
||||||
|
COPY --from=base --chown=www-data:www-data /var/www/html/vendor ./vendor
|
||||||
|
COPY --from=static-assets --chown=www-data:www-data /app/public/build ./public/build
|
||||||
|
|
||||||
|
# Copy application source code
|
||||||
|
COPY --chown=www-data:www-data composer.json composer.lock ./
|
||||||
|
COPY --chown=www-data:www-data app ./app
|
||||||
|
COPY --chown=www-data:www-data bootstrap ./bootstrap
|
||||||
|
COPY --chown=www-data:www-data config ./config
|
||||||
|
COPY --chown=www-data:www-data database ./database
|
||||||
|
COPY --chown=www-data:www-data lang ./lang
|
||||||
|
COPY --chown=www-data:www-data public ./public
|
||||||
|
COPY --chown=www-data:www-data routes ./routes
|
||||||
|
COPY --chown=www-data:www-data storage ./storage
|
||||||
|
COPY --chown=www-data:www-data templates ./templates
|
||||||
|
COPY --chown=www-data:www-data resources/views ./resources/views
|
||||||
|
COPY --chown=www-data:www-data artisan artisan
|
||||||
|
|
||||||
|
RUN composer dump-autoload
|
||||||
|
|
||||||
|
# Configure Nginx and S6 overlay
|
||||||
|
COPY docker/production/etc/nginx/conf.d/custom.conf /etc/nginx/conf.d/custom.conf
|
||||||
|
COPY docker/production/etc/nginx/site-opts.d/http.conf /etc/nginx/site-opts.d/http.conf
|
||||||
|
COPY --chmod=755 docker/production/etc/s6-overlay/ /etc/s6-overlay/
|
||||||
|
|
||||||
|
RUN mkdir -p /etc/nginx/conf.d && \
|
||||||
|
chown -R www-data:www-data /etc/nginx && \
|
||||||
|
chmod -R 755 /etc/nginx
|
||||||
|
|
||||||
|
# Install MinIO client
|
||||||
|
COPY --from=minio-client /usr/bin/mc /usr/bin/mc
|
||||||
|
RUN chmod +x /usr/bin/mc
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER www-data
|
||||||
45
docker/production/etc/nginx/site-opts.d/http.conf
Normal file
45
docker/production/etc/nginx/site-opts.d/http.conf
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
listen 80 default_server;
|
||||||
|
listen [::]:80 default_server;
|
||||||
|
listen 8080 default_server;
|
||||||
|
listen [::]:8080 default_server;
|
||||||
|
|
||||||
|
root /var/www/html/public;
|
||||||
|
|
||||||
|
# Set allowed "index" files
|
||||||
|
index index.html index.htm index.php;
|
||||||
|
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
charset utf-8;
|
||||||
|
|
||||||
|
# Set max upload to 2048M
|
||||||
|
client_max_body_size 2048M;
|
||||||
|
|
||||||
|
# Healthchecks: Set /healthcheck to be the healthcheck URL
|
||||||
|
location /healthcheck {
|
||||||
|
access_log off;
|
||||||
|
|
||||||
|
# set max 5 seconds for healthcheck
|
||||||
|
fastcgi_read_timeout 5s;
|
||||||
|
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_NAME /healthcheck;
|
||||||
|
fastcgi_param SCRIPT_FILENAME /healthcheck;
|
||||||
|
fastcgi_pass 127.0.0.1:9000;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Have NGINX try searching for PHP files as well
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pass "*.php" files to PHP-FPM
|
||||||
|
location ~ \.php$ {
|
||||||
|
fastcgi_pass 127.0.0.1:9000;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_buffers 8 8k;
|
||||||
|
fastcgi_buffer_size 8k;
|
||||||
|
fastcgi_read_timeout 99;
|
||||||
|
}
|
||||||
9
docker/production/etc/php/conf.d/zzz-custom-php.ini
Normal file
9
docker/production/etc/php/conf.d/zzz-custom-php.ini
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
error_reporting = E_ERROR
|
||||||
|
error_log = /var/www/html/storage/logs/php-error.log
|
||||||
|
log_errors = Off
|
||||||
|
log_errors_max_len = 8192
|
||||||
|
ignore_repeated_errors = On
|
||||||
|
ignore_repeated_source = On
|
||||||
|
|
||||||
|
upload_max_filesize = 256M
|
||||||
|
post_max_size = 256M
|
||||||
13
docker/production/etc/s6-overlay/s6-rc.d/db-migration/up
Normal file
13
docker/production/etc/s6-overlay/s6-rc.d/db-migration/up
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/command/execlineb -P
|
||||||
|
|
||||||
|
# Use with-contenv to ensure environment variables are available
|
||||||
|
with-contenv
|
||||||
|
cd /var/www/html
|
||||||
|
foreground {
|
||||||
|
php
|
||||||
|
artisan
|
||||||
|
migrate
|
||||||
|
--force
|
||||||
|
--isolated
|
||||||
|
}
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user