Merge branch 'next' into main
This commit is contained in:
10
README.md
10
README.md
@@ -40,7 +40,7 @@ Special thanks to our biggest sponsors!
|
||||
|
||||
### Special Sponsors
|
||||
|
||||

|
||||

|
||||
|
||||
* [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry.
|
||||
* [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions.
|
||||
@@ -50,6 +50,7 @@ Special thanks to our biggest sponsors!
|
||||
* [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) optimizing website performance through global content distribution.
|
||||
* [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform providing real-time protection against API abuse and bot attacks.
|
||||
* [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase.
|
||||
* [GoldenVM](https://billing.goldenvm.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
|
||||
* [Tigris](https://tigrisdata.com/?ref=coolify.io) - A fully managed serverless object storage service compatible with Amazon S3 API. Offers high performance, scalability, and built-in search capabilities for efficient data management.
|
||||
* [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising and data-driven marketing strategies.
|
||||
* [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets.
|
||||
@@ -90,7 +91,6 @@ Special thanks to our biggest sponsors!
|
||||
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
|
||||
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
|
||||
<a href="https://formbricks.com/?utm_source=coolify.io"><img src="https://github.com/formbricks.png" width="60px" alt="Formbricks" /></a>
|
||||
<a href="https://x.com/adithsuhas17?utm_source=coolify.io"><img src="https://github.com/adith-suhas-sv.png" width="60px" alt="Adith Suhas" /></a>
|
||||
<a href="https://startupfa.me?utm_source=coolify.io"><img src="https://github.com/startupfame.png" width="60px" alt="StartupFame" /></a>
|
||||
<a href="https://jonasjaeger.com?utm_source=coolify.io"><img src="https://github.com/toxin20.png" width="60px" alt="Jonas Jaeger" /></a>
|
||||
<a href="https://github.com/therealjp?utm_source=coolify.io"><img src="https://github.com/therealjp.png" width="60px" alt="JP" /></a>
|
||||
@@ -147,10 +147,10 @@ By subscribing to the cloud version, you get the Coolify server for the same pri
|
||||
|
||||
# Core Maintainers
|
||||
|
||||
| Andras Bacsai | Peak |
|
||||
| Andras Bacsai | 🏔️ Peak |
|
||||
|------------|------------|
|
||||
| <img src="https://github.com/andrasbacsai.png" width="200px" alt="Andras Bacsai" /> | <img src="https://github.com/peaklabs-dev.png" width="200px" alt="Peak Labs" /> |
|
||||
| <a href="https://x.com/heyandras"><img src="https://raw.githubusercontent.com/gauravghongde/social-icons/master/SVG/Color/Twitter.svg" width="25px"></a> <a href="https://github.com/andrasbacsai"><img src="https://raw.githubusercontent.com/gauravghongde/social-icons/master/SVG/Color/Github.svg" width="25px"></a> | <a href="https://x.com/peaklabs_dev"><img src="https://raw.githubusercontent.com/gauravghongde/social-icons/master/SVG/Color/Twitter.svg" width="25px"></a> <a href="https://github.com/peaklabs-dev"><img src="https://raw.githubusercontent.com/gauravghongde/social-icons/master/SVG/Color/Github.svg" width="25px"></a> |
|
||||
| <img src="https://github.com/andrasbacsai.png" width="200px" alt="Andras Bacsai" /> | <img src="https://github.com/peaklabs-dev.png" width="200px" alt="peaklabs-dev" /> |
|
||||
| <a href="https://github.com/andrasbacsai"><img src="https://api.iconify.design/devicon:github.svg" width="25px"></a> <a href="https://x.com/heyandras"><img src="https://api.iconify.design/devicon:twitter.svg" width="25px"></a> <a href="https://bsky.app/profile/heyandras.dev"><img src="https://api.iconify.design/simple-icons:bluesky.svg" width="25px"></a> | <a href="https://github.com/peaklabs-dev"><img src="https://api.iconify.design/devicon:github.svg" width="25px"></a> <a href="https://x.com/peaklabs_dev"><img src="https://api.iconify.design/devicon:twitter.svg" width="25px"></a> <a href="https://bsky.app/profile/peaklabs.dev"><img src="https://api.iconify.design/simple-icons:bluesky.svg" width="25px"></a> |
|
||||
|
||||
# Repo Activity
|
||||
|
||||
|
@@ -24,7 +24,7 @@ class StartClickhouse
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"echo 'Starting database.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
|
@@ -26,7 +26,7 @@ class StartDragonfly
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"echo 'Starting database.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
|
@@ -27,7 +27,7 @@ class StartKeydb
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"echo 'Starting database.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
|
@@ -24,7 +24,7 @@ class StartMariadb
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"echo 'Starting database.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
|
@@ -30,7 +30,7 @@ class StartMongodb
|
||||
}
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"echo 'Starting database.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
|
@@ -24,7 +24,7 @@ class StartMysql
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"echo 'Starting database.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
|
@@ -25,7 +25,7 @@ class StartPostgresql
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"echo 'Starting database.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/",
|
||||
];
|
||||
|
@@ -25,7 +25,7 @@ class StartRedis
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"echo 'Starting database.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
|
@@ -179,7 +179,7 @@ class GetContainersStatus
|
||||
})->first();
|
||||
if (! $foundTcpProxy) {
|
||||
StartDatabaseProxy::run($database);
|
||||
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for database", $this->server));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@@ -51,7 +51,6 @@ class ServerCheck
|
||||
|
||||
$containerReplicates = null;
|
||||
$this->isSentinel = true;
|
||||
|
||||
} else {
|
||||
['containers' => $this->containers, 'containerReplicates' => $containerReplicates] = $this->server->getContainers();
|
||||
// ServerStorageCheckJob::dispatch($this->server);
|
||||
@@ -148,7 +147,6 @@ class ServerCheck
|
||||
} else {
|
||||
$labels = Arr::undot(data_get($container, 'Config.Labels'));
|
||||
}
|
||||
|
||||
}
|
||||
$managed = data_get($labels, 'coolify.managed');
|
||||
if (! $managed) {
|
||||
@@ -259,7 +257,7 @@ class ServerCheck
|
||||
})->first();
|
||||
if (! $foundTcpProxy) {
|
||||
StartDatabaseProxy::run($database);
|
||||
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for database", $this->server));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@ class CloudCleanupSubscriptions extends Command
|
||||
}
|
||||
// If the team has no subscription id and the invoice is paid, we need to reset the invoice paid status
|
||||
if (! (data_get($team, 'subscription.stripe_subscription_id'))) {
|
||||
$this->info("Resetting invoice paid status for team {$team->id} {$team->name}");
|
||||
$this->info("Resetting invoice paid status for team {$team->id}");
|
||||
|
||||
$team->subscription->update([
|
||||
'stripe_invoice_paid' => false,
|
||||
@@ -61,9 +61,9 @@ class CloudCleanupSubscriptions extends Command
|
||||
$this->info('Subscription id: '.data_get($team, 'subscription.stripe_subscription_id'));
|
||||
$confirm = $this->confirm('Do you want to cancel the subscription?', true);
|
||||
if (! $confirm) {
|
||||
$this->info("Skipping team {$team->id} {$team->name}");
|
||||
$this->info("Skipping team {$team->id}");
|
||||
} else {
|
||||
$this->info("Cancelling subscription for team {$team->id} {$team->name}");
|
||||
$this->info("Cancelling subscription for team {$team->id}");
|
||||
$team->subscription->update([
|
||||
'stripe_invoice_paid' => false,
|
||||
'stripe_trial_already_ended' => false,
|
||||
|
@@ -1591,16 +1591,32 @@ class ApplicationsController extends Controller
|
||||
}
|
||||
$domains = $request->domains;
|
||||
if ($request->has('domains') && $server->isProxyShouldRun()) {
|
||||
$errors = [];
|
||||
$uuid = $request->uuid;
|
||||
$fqdn = $request->domains;
|
||||
$fqdn = str($fqdn)->replaceEnd(',', '')->trim();
|
||||
$fqdn = str($fqdn)->replaceStart(',', '')->trim();
|
||||
$application->fqdn = $fqdn;
|
||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
||||
$customLabels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||
$application->custom_labels = base64_encode($customLabels);
|
||||
$errors = [];
|
||||
$fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) {
|
||||
$domain = trim($domain);
|
||||
if (filter_var($domain, FILTER_VALIDATE_URL) === false || !preg_match('/^https?:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}/', $domain)) {
|
||||
$errors[] = 'Invalid domain: '.$domain;
|
||||
}
|
||||
return $domain;
|
||||
});
|
||||
if (count($errors) > 0) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => $errors,
|
||||
], 422);
|
||||
}
|
||||
if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'domains' => 'One of the domain is already used.',
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
$request->offsetUnset('domains');
|
||||
}
|
||||
|
||||
$dockerComposeDomainsJson = collect();
|
||||
@@ -2811,3 +2827,30 @@ class ApplicationsController extends Controller
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fqdn = str($fqdn)->replaceStart(',', '')->trim();
|
||||
$errors = [];
|
||||
$fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) {
|
||||
if (filter_var($domain, FILTER_VALIDATE_URL) === false) {
|
||||
$errors[] = 'Invalid domain: ' . $domain;
|
||||
}
|
||||
|
||||
return str($domain)->trim()->lower();
|
||||
});
|
||||
if (count($errors) > 0) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => $errors,
|
||||
], 422);
|
||||
}
|
||||
if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'domains' => 'One of the domain is already used.',
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -211,8 +211,9 @@ class DatabasesController extends Controller
|
||||
'mongo_conf' => ['type' => 'string', 'description' => 'Mongo conf'],
|
||||
'mongo_initdb_root_username' => ['type' => 'string', 'description' => 'Mongo initdb root username'],
|
||||
'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_password' => ['type' => 'string', 'description' => 'MySQL password'],
|
||||
'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'],
|
||||
'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'],
|
||||
'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'],
|
||||
@@ -241,7 +242,7 @@ class DatabasesController extends Controller
|
||||
)]
|
||||
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();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
@@ -413,12 +414,12 @@ class DatabasesController extends Controller
|
||||
}
|
||||
break;
|
||||
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(), [
|
||||
'mongo_conf' => 'string',
|
||||
'mongo_initdb_root_username' => 'string',
|
||||
'mongo_initdb_root_password' => 'string',
|
||||
'mongo_initdb_init_database' => 'string',
|
||||
'mongo_initdb_database' => 'string',
|
||||
]);
|
||||
if ($request->has('mongo_conf')) {
|
||||
if (! isBase64Encoded($request->mongo_conf)) {
|
||||
@@ -443,9 +444,10 @@ class DatabasesController extends Controller
|
||||
|
||||
break;
|
||||
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(), [
|
||||
'mysql_root_password' => 'string',
|
||||
'mysql_password' => 'string',
|
||||
'mysql_user' => 'string',
|
||||
'mysql_database' => 'string',
|
||||
'mysql_conf' => 'string',
|
||||
@@ -909,6 +911,7 @@ class DatabasesController extends Controller
|
||||
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
|
||||
'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_password' => ['type' => 'string', 'description' => 'MySQL password'],
|
||||
'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'],
|
||||
'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'],
|
||||
'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'],
|
||||
@@ -1013,7 +1016,7 @@ class DatabasesController extends Controller
|
||||
|
||||
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();
|
||||
if (is_null($teamId)) {
|
||||
@@ -1220,9 +1223,10 @@ class DatabasesController extends Controller
|
||||
|
||||
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
||||
} 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(), [
|
||||
'mysql_root_password' => 'string',
|
||||
'mysql_password' => 'string',
|
||||
'mysql_user' => 'string',
|
||||
'mysql_database' => 'string',
|
||||
'mysql_conf' => 'string',
|
||||
@@ -1456,12 +1460,12 @@ class DatabasesController extends Controller
|
||||
|
||||
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
||||
} 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(), [
|
||||
'mongo_conf' => 'string',
|
||||
'mongo_initdb_root_username' => 'string',
|
||||
'mongo_initdb_root_password' => 'string',
|
||||
'mongo_initdb_init_database' => 'string',
|
||||
'mongo_initdb_database' => 'string',
|
||||
]);
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
if ($validator->fails() || ! empty($extraFields)) {
|
||||
@@ -1557,7 +1561,8 @@ class DatabasesController extends Controller
|
||||
]
|
||||
)
|
||||
),
|
||||
]),
|
||||
]
|
||||
),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
@@ -1632,9 +1637,11 @@ class DatabasesController extends Controller
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Database starting request queued.'],
|
||||
])
|
||||
]
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
@@ -1708,9 +1715,11 @@ class DatabasesController extends Controller
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Database stopping request queued.'],
|
||||
])
|
||||
]
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
@@ -1784,9 +1793,11 @@ class DatabasesController extends Controller
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Database restaring request queued.'],
|
||||
])
|
||||
]
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
|
@@ -463,7 +463,7 @@ class Github extends Controller
|
||||
$private_key = data_get($data, 'pem');
|
||||
$webhook_secret = data_get($data, 'webhook_secret');
|
||||
$private_key = PrivateKey::create([
|
||||
'name' => $slug,
|
||||
'name' => "github-app-{$slug}",
|
||||
'private_key' => $private_key,
|
||||
'team_id' => $github_app->team_id,
|
||||
'is_git_related' => true,
|
||||
|
@@ -140,6 +140,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
private ?string $buildTarget = null;
|
||||
|
||||
private bool $disableBuildCache = false;
|
||||
|
||||
private Collection $saved_outputs;
|
||||
|
||||
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->commit = $this->application_deployment_queue->commit;
|
||||
$this->rollback = $this->application_deployment_queue->rollback;
|
||||
$this->disableBuildCache = $this->application->settings->disable_build_cache;
|
||||
$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->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile';
|
||||
$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->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') {
|
||||
$this->application_deployment_queue->addLogEntry('Static deployment. Copying static assets to the image.');
|
||||
} else {
|
||||
@@ -2400,7 +2409,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
if (! $this->only_this_server) {
|
||||
$this->deploy_to_additional_destinations();
|
||||
}
|
||||
$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
|
||||
//$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -306,7 +306,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if ($this->backup->save_s3) {
|
||||
$this->upload_to_s3();
|
||||
}
|
||||
$this->team?->notify(new BackupSuccess($this->backup, $this->database, $database));
|
||||
//$this->team?->notify(new BackupSuccess($this->backup, $this->database, $database));
|
||||
$this->backup_log->update([
|
||||
'status' => 'success',
|
||||
'message' => $this->backup_output,
|
||||
|
@@ -172,13 +172,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
|
||||
public function getProxyType()
|
||||
{
|
||||
// Set Default Proxy Type
|
||||
$this->selectProxy(ProxyTypes::TRAEFIK->value);
|
||||
// $proxyTypeSet = $this->createdServer->proxy->type;
|
||||
// if (!$proxyTypeSet) {
|
||||
// $this->currentState = 'select-proxy';
|
||||
// return;
|
||||
// }
|
||||
$this->getProjects();
|
||||
}
|
||||
|
||||
@@ -189,7 +183,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
|
||||
return;
|
||||
}
|
||||
$this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey);
|
||||
$this->createdPrivateKey = PrivateKey::where('team_id', currentTeam()->id)->where('id', $this->selectedExistingPrivateKey)->first();
|
||||
$this->privateKey = $this->createdPrivateKey->private_key;
|
||||
$this->currentState = 'create-server';
|
||||
}
|
||||
|
@@ -37,7 +37,7 @@ class Email extends Component
|
||||
#[Validate(['nullable', 'numeric'])]
|
||||
public ?int $smtpPort = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
#[Validate(['nullable', 'string', 'in:tls,ssl,none'])]
|
||||
public ?string $smtpEncryption = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
@@ -73,8 +73,8 @@ class Email extends Component
|
||||
#[Validate(['nullable', 'string'])]
|
||||
public ?string $resendApiKey = null;
|
||||
|
||||
#[Validate(['required', 'email'])]
|
||||
public string $testEmailAddress = '';
|
||||
#[Validate(['nullable', 'email'])]
|
||||
public ?string $testEmailAddress = null;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
|
@@ -25,6 +25,9 @@ class Advanced extends Component
|
||||
#[Validate(['boolean'])]
|
||||
public bool $isAutoDeployEnabled = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $disableBuildCache = false;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $isLogDrainEnabled = false;
|
||||
|
||||
@@ -95,6 +98,7 @@ class Advanced extends Component
|
||||
$this->application->settings->is_stripprefix_enabled = $this->isStripprefixEnabled;
|
||||
$this->application->settings->is_raw_compose_deployment_enabled = $this->isRawComposeDeploymentEnabled;
|
||||
$this->application->settings->connect_to_docker_network = $this->isConnectToDockerNetworkEnabled;
|
||||
$this->application->settings->disable_build_cache = $this->disableBuildCache;
|
||||
$this->application->settings->save();
|
||||
} else {
|
||||
$this->isForceHttpsEnabled = $this->application->isForceHttpsEnabled();
|
||||
@@ -116,6 +120,7 @@ class Advanced extends Component
|
||||
$this->customInternalName = $this->application->settings->custom_internal_name;
|
||||
$this->isRawComposeDeploymentEnabled = $this->application->settings->is_raw_compose_deployment_enabled;
|
||||
$this->isConnectToDockerNetworkEnabled = $this->application->settings->connect_to_docker_network;
|
||||
$this->disableBuildCache = $this->application->settings->disable_build_cache;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -21,19 +21,16 @@ class Configuration extends Component
|
||||
->select('id', 'uuid', 'team_id')
|
||||
->where('uuid', request()->route('project_uuid'))
|
||||
->firstOrFail();
|
||||
|
||||
$environment = $project->environments()
|
||||
->select('id', 'name', 'project_id')
|
||||
->where('name', request()->route('environment_name'))
|
||||
->firstOrFail();
|
||||
|
||||
$application = $environment->applications()
|
||||
->with(['destination'])
|
||||
->where('uuid', request()->route('application_uuid'))
|
||||
->firstOrFail();
|
||||
|
||||
$this->application = $application;
|
||||
|
||||
if ($application->destination && $application->destination->server) {
|
||||
$mainServer = $application->destination->server;
|
||||
$this->servers = Server::ownedByCurrentTeam()
|
||||
|
@@ -168,18 +168,42 @@ class ExecuteContainerCommand extends Component
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Validate container name format
|
||||
if (! preg_match('/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/', $this->selected_container)) {
|
||||
throw new \InvalidArgumentException('Invalid container name format');
|
||||
}
|
||||
|
||||
// Verify container exists in our allowed list
|
||||
$container = collect($this->containers)->firstWhere('container.Names', $this->selected_container);
|
||||
if (is_null($container)) {
|
||||
throw new \RuntimeException('Container not found.');
|
||||
}
|
||||
$server = data_get($this->container, 'server');
|
||||
|
||||
// Verify server ownership and status
|
||||
$server = data_get($container, 'server');
|
||||
if (! $server || ! $server instanceof Server) {
|
||||
throw new \RuntimeException('Invalid server configuration.');
|
||||
}
|
||||
|
||||
if ($server->isForceDisabled()) {
|
||||
throw new \RuntimeException('Server is disabled.');
|
||||
}
|
||||
|
||||
// Additional ownership verification based on resource type
|
||||
$resourceServer = match ($this->type) {
|
||||
'application' => $this->resource->destination->server,
|
||||
'database' => $this->resource->destination->server,
|
||||
'service' => $this->resource->server,
|
||||
default => throw new \RuntimeException('Invalid resource type.')
|
||||
};
|
||||
|
||||
if ($server->id !== $resourceServer->id && ! $this->resource->additional_servers->contains('id', $server->id)) {
|
||||
throw new \RuntimeException('Server ownership verification failed.');
|
||||
}
|
||||
|
||||
$this->dispatch(
|
||||
'send-terminal-command',
|
||||
isset($container),
|
||||
true,
|
||||
data_get($container, 'container.Names'),
|
||||
data_get($container, 'server.uuid')
|
||||
);
|
||||
|
@@ -29,11 +29,20 @@ class Terminal extends Component
|
||||
$server = Server::ownedByCurrentTeam()->whereUuid($serverUuid)->firstOrFail();
|
||||
|
||||
if ($isContainer) {
|
||||
// Validate container identifier format (alphanumeric, dashes, and underscores only)
|
||||
if (! preg_match('/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/', $identifier)) {
|
||||
throw new \InvalidArgumentException('Invalid container identifier format');
|
||||
}
|
||||
|
||||
// Verify container exists and belongs to the user's team
|
||||
$status = getContainerStatus($server, $identifier);
|
||||
if ($status !== 'running') {
|
||||
return;
|
||||
}
|
||||
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$identifier} sh -c 'PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
|
||||
|
||||
// Escape the identifier for shell usage
|
||||
$escapedIdentifier = escapeshellarg($identifier);
|
||||
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$escapedIdentifier} sh -c 'PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
|
||||
} else {
|
||||
$command = SshMultiplexingHelper::generateSshCommand($server, 'PATH=$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n "$SHELL" ]; then exec $SHELL; else sh; fi');
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ namespace App\Livewire\Server;
|
||||
use App\Actions\Server\StartSentinel;
|
||||
use App\Actions\Server\StopSentinel;
|
||||
use App\Models\Server;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -79,9 +79,6 @@ class Show extends Component
|
||||
#[Validate(['required'])]
|
||||
public string $serverTimezone;
|
||||
|
||||
#[Locked]
|
||||
public array $timezones;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$teamId = auth()->user()->currentTeam()->id;
|
||||
@@ -96,13 +93,21 @@ class Show extends Component
|
||||
{
|
||||
try {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
|
||||
$this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
|
||||
$this->syncData();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function timezones(): array
|
||||
{
|
||||
return collect(timezone_identifiers_list())
|
||||
->sort()
|
||||
->values()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function syncData(bool $toModel = false)
|
||||
{
|
||||
if ($toModel) {
|
||||
|
@@ -7,7 +7,7 @@ use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -17,9 +17,6 @@ class Index extends Component
|
||||
|
||||
protected Server $server;
|
||||
|
||||
#[Locked]
|
||||
public $timezones;
|
||||
|
||||
#[Validate('boolean')]
|
||||
public bool $is_auto_update_enabled;
|
||||
|
||||
@@ -101,12 +98,20 @@ class Index extends Component
|
||||
$this->is_api_enabled = $this->settings->is_api_enabled;
|
||||
$this->auto_update_frequency = $this->settings->auto_update_frequency;
|
||||
$this->update_check_frequency = $this->settings->update_check_frequency;
|
||||
$this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
|
||||
$this->instance_timezone = $this->settings->instance_timezone;
|
||||
$this->disable_two_step_confirmation = $this->settings->disable_two_step_confirmation;
|
||||
}
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function timezones(): array
|
||||
{
|
||||
return collect(timezone_identifiers_list())
|
||||
->sort()
|
||||
->values()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function instantSave($isSave = true)
|
||||
{
|
||||
$this->validate();
|
||||
|
@@ -19,7 +19,7 @@ class SettingsEmail extends Component
|
||||
#[Validate(['nullable', 'numeric', 'min:1', 'max:65535'])]
|
||||
public ?int $smtpPort = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
#[Validate(['nullable', 'string', 'in:tls,ssl,none'])]
|
||||
public ?string $smtpEncryption = null;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
|
@@ -4,6 +4,11 @@ namespace App\Livewire\Source\Github;
|
||||
|
||||
use App\Jobs\GithubAppPermissionJob;
|
||||
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;
|
||||
|
||||
class Change extends Component
|
||||
@@ -51,12 +56,20 @@ class Change extends Component
|
||||
'github_app.administration' => 'nullable|string',
|
||||
];
|
||||
|
||||
public function boot()
|
||||
{
|
||||
if ($this->github_app) {
|
||||
$this->github_app->makeVisible(['client_secret', 'webhook_secret']);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkPermissions()
|
||||
{
|
||||
GithubAppPermissionJob::dispatchSync($this->github_app);
|
||||
$this->github_app->refresh()->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||
$this->dispatch('success', 'Github App permissions updated.');
|
||||
}
|
||||
|
||||
// public function check()
|
||||
// {
|
||||
|
||||
@@ -90,15 +103,16 @@ class Change extends Component
|
||||
|
||||
// ray($runners_by_repository);
|
||||
// }
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
$github_app_uuid = request()->github_app_uuid;
|
||||
$this->github_app = GithubApp::ownedByCurrentTeam()->whereUuid($github_app_uuid)->firstOrFail();
|
||||
$this->github_app->makeVisible(['client_secret', 'webhook_secret']);
|
||||
|
||||
$this->applications = $this->github_app->applications;
|
||||
$settings = instanceSettings();
|
||||
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||
|
||||
$this->name = str($this->github_app->name)->kebab();
|
||||
$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()
|
||||
{
|
||||
try {
|
||||
|
@@ -4,6 +4,7 @@ namespace App\Models;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Process\InvokedProcess;
|
||||
@@ -104,7 +105,7 @@ use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Application extends BaseModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
private static $parserVersion = '4';
|
||||
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@@ -18,4 +19,18 @@ abstract class BaseModel extends Model
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function name(): Attribute
|
||||
{
|
||||
return new Attribute(
|
||||
get: fn () => sanitize_string($this->getRawOriginal('name')),
|
||||
);
|
||||
}
|
||||
|
||||
public function image(): Attribute
|
||||
{
|
||||
return new Attribute(
|
||||
get: fn () => sanitize_string($this->getRawOriginal('image')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ use App\Notifications\Server\Reachable;
|
||||
use App\Notifications\Server\Unreachable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -48,7 +49,7 @@ use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class Server extends BaseModel
|
||||
{
|
||||
use SchemalessAttributesTrait, SoftDeletes;
|
||||
use HasFactory, SchemalessAttributesTrait, SoftDeletes;
|
||||
|
||||
public static $batch_counter = 0;
|
||||
|
||||
@@ -610,7 +611,8 @@ $schema://$host {
|
||||
}
|
||||
$memory = json_decode($memory, true);
|
||||
$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();
|
||||
@@ -1039,7 +1041,7 @@ $schema://$host {
|
||||
$this->unreachable_notification_sent = false;
|
||||
$this->save();
|
||||
$this->refresh();
|
||||
$this->team->notify(new Reachable($this));
|
||||
// $this->team->notify(new Reachable($this));
|
||||
}
|
||||
|
||||
public function sendUnreachableNotification()
|
||||
@@ -1047,7 +1049,7 @@ $schema://$host {
|
||||
$this->unreachable_notification_sent = true;
|
||||
$this->save();
|
||||
$this->refresh();
|
||||
$this->team->notify(new Unreachable($this));
|
||||
// $this->team->notify(new Unreachable($this));
|
||||
}
|
||||
|
||||
public function validateConnection(bool $justCheckingNewKey = false)
|
||||
|
@@ -66,11 +66,12 @@ class EmailChannel
|
||||
'transport' => 'smtp',
|
||||
'host' => data_get($notifiable, 'smtp_host'),
|
||||
'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'),
|
||||
'password' => data_get($notifiable, 'smtp_password'),
|
||||
'timeout' => data_get($notifiable, 'smtp_timeout'),
|
||||
'local_domain' => null,
|
||||
'auto_tls' => data_get($notifiable, 'smtp_encryption') === 'none' ? '0' : '',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@@ -46,7 +46,7 @@ const SPECIFIC_SERVICES = [
|
||||
|
||||
// Based on /etc/os-release
|
||||
const SUPPORTED_OS = [
|
||||
'ubuntu debian raspbian',
|
||||
'ubuntu debian raspbian pop',
|
||||
'centos fedora rhel ol rocky amzn almalinux',
|
||||
'sles opensuse-leap opensuse-tumbleweed',
|
||||
'arch',
|
||||
|
@@ -288,9 +288,9 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
|
||||
$host_without_www = str($host)->replace('www.', '');
|
||||
$schema = $url->getScheme();
|
||||
$port = $url->getPort();
|
||||
$handle = "handle_path";
|
||||
$handle = 'handle_path';
|
||||
if (! $is_stripprefix_enabled) {
|
||||
$handle = "handle";
|
||||
$handle = 'handle';
|
||||
}
|
||||
if (is_null($port) && ! is_null($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}.try_files={path} /index.html /index.php");
|
||||
|
||||
|
||||
if ($port) {
|
||||
$labels->push("caddy_{$loop}.{$handle}.{$loop}_reverse_proxy={{upstreams $port}}");
|
||||
} else {
|
||||
|
@@ -91,8 +91,31 @@ function metrics_dir(): string
|
||||
return base_configuration_dir() . '/metrics';
|
||||
}
|
||||
|
||||
function sanitize_string(?string $input = null): ?string
|
||||
{
|
||||
if (is_null($input)) {
|
||||
return null;
|
||||
}
|
||||
// Remove any HTML/PHP tags
|
||||
$sanitized = strip_tags($input);
|
||||
|
||||
// Convert special characters to HTML entities
|
||||
$sanitized = htmlspecialchars($sanitized, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
|
||||
// Remove any control characters
|
||||
$sanitized = preg_replace('/[\x00-\x1F\x7F]/u', '', $sanitized);
|
||||
|
||||
// Trim whitespace
|
||||
$sanitized = trim($sanitized);
|
||||
|
||||
return $sanitized;
|
||||
}
|
||||
|
||||
function generate_readme_file(string $name, string $updated_at): string
|
||||
{
|
||||
$name = sanitize_string($name);
|
||||
$updated_at = sanitize_string($updated_at);
|
||||
|
||||
return "Resource name: $name\nLatest Deployment Date: $updated_at";
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
return [
|
||||
'coolify' => [
|
||||
'version' => '4.0.0-beta.374',
|
||||
'version' => '4.0.0-beta.376',
|
||||
'self_hosted' => env('SELF_HOSTED', true),
|
||||
'autoupdate' => env('AUTOUPDATE'),
|
||||
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
||||
|
@@ -49,6 +49,22 @@ return [
|
||||
'search_path' => 'public',
|
||||
'sslmode' => 'prefer',
|
||||
],
|
||||
|
||||
'testing' => [
|
||||
'driver' => 'pgsql',
|
||||
'url' => env('DATABASE_TEST_URL'),
|
||||
'host' => env('DB_TEST_HOST', 'postgres'),
|
||||
'port' => env('DB_TEST_PORT', '5432'),
|
||||
'database' => env('DB_TEST_DATABASE', 'coolify_test'),
|
||||
'username' => env('DB_TEST_USERNAME', 'coolify'),
|
||||
'password' => env('DB_TEST_PASSWORD', 'password'),
|
||||
'charset' => 'utf8',
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'search_path' => 'public',
|
||||
'sslmode' => 'prefer',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|
22
database/factories/ApplicationFactory.php
Normal file
22
database/factories/ApplicationFactory.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class ApplicationFactory extends Factory
|
||||
{
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->unique()->name(),
|
||||
'destination_id' => 1,
|
||||
'git_repository' => fake()->url(),
|
||||
'git_branch' => fake()->word(),
|
||||
'build_pack' => 'nixpacks',
|
||||
'ports_exposes' => '3000',
|
||||
'environment_id' => 1,
|
||||
'destination_id' => 1,
|
||||
];
|
||||
}
|
||||
}
|
17
database/factories/ServerFactory.php
Normal file
17
database/factories/ServerFactory.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class ServerFactory extends Factory
|
||||
{
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->unique()->name(),
|
||||
'ip' => fake()->unique()->ipv4(),
|
||||
'private_key_id' => 1,
|
||||
];
|
||||
}
|
||||
}
|
@@ -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([
|
||||
'name' => 'coolify-laravel-development-public',
|
||||
'uuid' => '69420',
|
||||
'organization' => 'coollabsio',
|
||||
'api_url' => 'https://api.github.com',
|
||||
'html_url' => 'https://github.com',
|
||||
'is_public' => false,
|
||||
|
10
openapi.json
10
openapi.json
@@ -3011,7 +3011,7 @@
|
||||
"type": "string",
|
||||
"description": "Mongo initdb root password"
|
||||
},
|
||||
"mongo_initdb_init_database": {
|
||||
"mongo_initdb_database": {
|
||||
"type": "string",
|
||||
"description": "Mongo initdb init database"
|
||||
},
|
||||
@@ -3019,6 +3019,10 @@
|
||||
"type": "string",
|
||||
"description": "MySQL root password"
|
||||
},
|
||||
"mysql_password": {
|
||||
"type": "string",
|
||||
"description": "MySQL password"
|
||||
},
|
||||
"mysql_user": {
|
||||
"type": "string",
|
||||
"description": "MySQL user"
|
||||
@@ -3842,6 +3846,10 @@
|
||||
"type": "string",
|
||||
"description": "MySQL root password"
|
||||
},
|
||||
"mysql_password": {
|
||||
"type": "string",
|
||||
"description": "MySQL password"
|
||||
},
|
||||
"mysql_user": {
|
||||
"type": "string",
|
||||
"description": "MySQL user"
|
||||
|
@@ -2089,12 +2089,15 @@ paths:
|
||||
mongo_initdb_root_password:
|
||||
type: string
|
||||
description: 'Mongo initdb root password'
|
||||
mongo_initdb_init_database:
|
||||
mongo_initdb_database:
|
||||
type: string
|
||||
description: 'Mongo initdb init database'
|
||||
mysql_root_password:
|
||||
type: string
|
||||
description: 'MySQL root password'
|
||||
mysql_password:
|
||||
type: string
|
||||
description: 'MySQL password'
|
||||
mysql_user:
|
||||
type: string
|
||||
description: 'MySQL user'
|
||||
@@ -2684,6 +2687,9 @@ paths:
|
||||
mysql_root_password:
|
||||
type: string
|
||||
description: 'MySQL root password'
|
||||
mysql_password:
|
||||
type: string
|
||||
description: 'MySQL password'
|
||||
mysql_user:
|
||||
type: string
|
||||
description: 'MySQL user'
|
||||
|
@@ -13,8 +13,8 @@
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||
<env name="CACHE_DRIVER" value="array"/>
|
||||
<!-- <env name="DB_CONNECTION" value="sqlite"/> -->
|
||||
<!-- <env name="DB_DATABASE" value=":memory:"/> -->
|
||||
<env name="DB_CONNECTION" value="testing"/>
|
||||
<env name="DB_TEST_DATABASE" value="coolify_test"/>
|
||||
<env name="MAIL_MAILER" value="array"/>
|
||||
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||
<env name="SESSION_DRIVER" value="array"/>
|
||||
|
6
public/svgs/overseerr.svg
Normal file
6
public/svgs/overseerr.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="512" height="512">
|
||||
<path d="M0 0 C0.77839233 0.70552002 0.77839233 0.70552002 1.57250977 1.42529297 C20.51724698 18.95857955 35.3183542 42.25039645 45.25 65.9375 C45.75917969 67.13761719 46.26835937 68.33773437 46.79296875 69.57421875 C53.13904333 85.15591751 57.42335993 102.21306817 59.25 118.9375 C59.35957031 119.79472656 59.46914063 120.65195313 59.58203125 121.53515625 C65.30647708 174.58168758 49.01780615 227.1014046 15.875 268.5 C12.4098987 272.65198864 8.83161111 276.6927508 5.1875 280.6875 C4.48197998 281.46589233 4.48197998 281.46589233 3.76220703 282.26000977 C-13.77107955 301.20474698 -37.06289645 316.0058542 -60.75 325.9375 C-61.95011719 326.44667969 -63.15023438 326.95585937 -64.38671875 327.48046875 C-79.96841751 333.82654333 -97.02556817 338.11085993 -113.75 339.9375 C-114.60722656 340.04707031 -115.46445312 340.15664063 -116.34765625 340.26953125 C-169.39418758 345.99397708 -221.9139046 329.70530615 -263.3125 296.5625 C-267.46448864 293.0973987 -271.5052508 289.51911111 -275.5 285.875 C-276.27839233 285.16947998 -276.27839233 285.16947998 -277.07250977 284.44970703 C-296.01724698 266.91642045 -310.8183542 243.62460355 -320.75 219.9375 C-321.25917969 218.73738281 -321.76835937 217.53726562 -322.29296875 216.30078125 C-328.63904333 200.71908249 -332.92335993 183.66193183 -334.75 166.9375 C-334.85957031 166.08027344 -334.96914063 165.22304688 -335.08203125 164.33984375 C-340.80647708 111.29331242 -324.51780615 58.7735954 -291.375 17.375 C-287.9098987 13.22301136 -284.33161111 9.1822492 -280.6875 5.1875 C-280.21715332 4.66857178 -279.74680664 4.14964355 -279.26220703 3.61499023 C-261.72892045 -15.32974698 -238.43710355 -30.1308542 -214.75 -40.0625 C-213.54988281 -40.57167969 -212.34976563 -41.08085938 -211.11328125 -41.60546875 C-195.53158249 -47.95154333 -178.47443183 -52.23585993 -161.75 -54.0625 C-160.89277344 -54.17207031 -160.03554687 -54.28164063 -159.15234375 -54.39453125 C-99.82500101 -60.79676248 -43.54726444 -39.72491441 0 0 Z " fill="#A289F9" transform="translate(393.75,113.0625)"/>
|
||||
<path d="M0 0 C2.63622485 2.22708275 5.19150738 4.52921782 7.7409668 6.85546875 C8.46670898 7.49871094 9.19245117 8.14195312 9.94018555 8.8046875 C28.36748008 26.03588989 41.75155651 53.35960955 42.86425781 78.65869141 C43.70797362 112.3608808 36.58885956 142.14006064 13.7409668 167.85546875 C12.54020508 169.20705078 12.54020508 169.20705078 11.31518555 170.5859375 C-8.53153214 191.53705519 -37.09680019 204.15705607 -65.8137207 205.140625 C-77.94100422 205.37709014 -89.5422865 205.25443065 -101.2590332 201.85546875 C-103.0224707 201.37980469 -103.0224707 201.37980469 -104.8215332 200.89453125 C-121.40302939 195.94116057 -136.3527812 187.31831389 -149.2590332 175.85546875 C-150.14848633 175.0665625 -151.03793945 174.27765625 -151.9543457 173.46484375 C-167.40909083 158.83370573 -177.61511815 140.25052542 -183.2590332 119.85546875 C-183.5787207 118.70820313 -183.8984082 117.5609375 -184.2277832 116.37890625 C-188.26683057 98.91376625 -188.98196274 77.02425735 -183.2590332 59.85546875 C-182.5990332 58.86546875 -181.9390332 57.87546875 -181.2590332 56.85546875 C-180.93032227 57.46132812 -180.60161133 58.0671875 -180.26293945 58.69140625 C-172.21889547 72.86113142 -160.79177658 83.491992 -144.8840332 88.23046875 C-127.13242009 91.2910917 -110.45150547 88.72225434 -95.2590332 78.85546875 C-83.60677615 70.49132656 -75.75104634 56.9235307 -73.2590332 42.85546875 C-71.45757952 24.03946126 -74.73970981 8.78864654 -86.8762207 -6.1328125 C-90.96217608 -10.5413433 -95.77705169 -13.57401274 -100.90356445 -16.6640625 C-103.2590332 -18.14453125 -103.2590332 -18.14453125 -105.2590332 -20.14453125 C-103.57312478 -20.62926803 -101.88520775 -21.10702051 -100.1965332 -21.58203125 C-98.78694336 -21.98228516 -98.78694336 -21.98228516 -97.34887695 -22.390625 C-64.24168178 -30.4686132 -26.24452842 -21.45755623 0 0 Z " fill="#131928" transform="translate(344.259033203125,182.14453125)"/>
|
||||
<path d="M0 0 C27.51668329 -1.54459553 56.68622642 12.36261605 76.97143555 30.24731445 C100.05828616 51.22761351 116.14269665 79.83089922 118.22119141 111.35473633 C119.4842267 149.41509562 109.54536352 181.25369492 83.42504883 209.56640625 C61.20694161 232.76768274 30.18258643 245.68447023 -1.72436523 246.47045898 C-37.47694444 246.75019142 -67.91707948 234.63666274 -93.52539062 209.50195312 C-112.03605933 190.64804024 -127.24579555 162.86302866 -128.09765625 135.91015625 C-128.08605469 134.97042969 -128.07445312 134.03070313 -128.0625 133.0625 C-128.05347656 132.10472656 -128.04445312 131.14695312 -128.03515625 130.16015625 C-128.01775391 129.09087891 -128.01775391 129.09087891 -128 128 C-127.67 128 -127.34 128 -127 128 C-126.79246094 129.23492187 -126.58492187 130.46984375 -126.37109375 131.7421875 C-122.03641631 156.12747696 -112.65378734 175.70645513 -96 194 C-95.43023437 194.65226563 -94.86046875 195.30453125 -94.2734375 195.9765625 C-77.53338041 214.09016274 -49.82674725 227.04001852 -25.19677734 228.12329102 C8.55585455 228.96826963 38.21751895 221.8135893 64 199 C64.82757813 198.27296875 65.65515625 197.5459375 66.5078125 196.796875 C87.15165343 177.37583998 99.32367565 148.51732778 100.28515625 120.4453125 C100.52162139 108.31802898 100.3989619 96.7167467 97 85 C96.68289063 83.824375 96.36578125 82.64875 96.0390625 81.4375 C91.07002873 64.80357134 82.4108957 49.99996858 71 37 C70.28457031 36.18402344 69.56914062 35.36804688 68.83203125 34.52734375 C50.47303107 14.99722536 25.98974766 5.20070823 0 1 C0 0.67 0 0.34 0 0 Z " fill="#6865CD" transform="translate(286,158)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 5.5 KiB |
1
public/svgs/plex.svg
Normal file
1
public/svgs/plex.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><rect width="512" height="512" rx="15%" fill="#282a2d"/><path d="M256 70H148l108 186-108 186h108l108-186z" fill="#e5a00d"/></svg>
|
After Width: | Height: | Size: 191 B |
1
public/svgs/prowlarr.svg
Normal file
1
public/svgs/prowlarr.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 13 KiB |
BIN
public/svgs/pterodactyl.png
Normal file
BIN
public/svgs/pterodactyl.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
1
public/svgs/radarr.svg
Normal file
1
public/svgs/radarr.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="1024" viewBox="0 0 1024 1024" width="1024" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="m0 0h1024v1024h-1024z"/><clipPath id="b"><use clip-rule="evenodd" xlink:href="#a"/></clipPath></defs><g clip-path="url(#b)"><use fill="none" xlink:href="#a"/><g transform="translate(70 21.00012)"><path d="m105.302 154.943 7.522 714.549c-60.173 7.522-105.30242-22.565-105.30242-82.737l-7.52158-594.205c0-188.03894 172.996-233.1684 278.298-157.9526l534.032 308.3846c75.216 52.651 90.259 150.431 52.651 218.125-7.521-52.651-30.086-82.737-75.216-112.823l-601.726-338.471c-45.129-30.0862-82.737-22.5646-82.737 45.13z" fill="#24292e"/><path d="m0 376.079c45.1295 15.043 90.259 7.521 127.867-15.043l616.769-361.036c37.608 52.651 30.087 105.302-15.043 135.388l-518.989 300.863c-75.216 37.608-172.9961 0-210.604-60.172z" fill="#24292e" transform="translate(60.17249 531.0214)"/><path d="m0 413.687 368.557-210.604-361.03543-203.083z" fill="#ffc230" transform="translate(240.6902 282.8092)"/></g></g></svg>
|
After Width: | Height: | Size: 1.0 KiB |
1
public/svgs/sonarr.svg
Normal file
1
public/svgs/sonarr.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="216.9" viewBox="0 0 216.7 216.9" width="216.7" xmlns="http://www.w3.org/2000/svg"><g clip-rule="evenodd"><path d="m216.7 108.45c0 29.833-10.533 55.4-31.6 76.7-.7.833-1.483 1.6-2.35 2.3-3.466 3.4-7.133 6.484-11 9.25-18.267 13.467-39.367 20.2-63.3 20.2-23.967 0-45.033-6.733-63.2-20.2-4.8-3.4-9.3-7.25-13.5-11.55-16.367-16.266-26.417-35.167-30.15-56.7-.733-4.2-1.217-8.467-1.45-12.8-.1-2.4-.15-4.8-.15-7.2 0-2.533.05-4.95.15-7.25 0-.233.066-.467.2-.7 1.567-26.6 12.033-49.583 31.4-68.95 21.3-21.033 46.867-31.55 76.7-31.55 29.933 0 55.484 10.517 76.65 31.55 21.067 21.433 31.6 47.067 31.6 76.9z" fill="#eee" fill-rule="evenodd"/><path d="m194.65 42.5-22.4 22.4c-13.098 13.098-14.25 24.5-14.25 44.6 0 17.934 2.852 34.352 16.2 47.7 9.746 9.746 19 18.95 19 18.95-2.5 3.067-5.2 6.067-8.1 9-.7.833-1.483 1.6-2.35 2.3-2.533 2.5-5.167 4.817-7.9 6.95l-17.55-17.55c-15.598-15.6-27.996-17.1-48.6-17.1-19.77 0-33.223 1.822-47.7 16.3-8.647 8.647-18.55 18.6-18.55 18.6-3.767-2.867-7.333-6.034-10.7-9.5-2.8-2.8-5.417-5.667-7.85-8.6 0 0 9.798-9.848 19.15-19.2 13.852-13.853 16.1-29.916 16.1-47.85 0-17.5-2.874-33.823-15.6-46.55-8.835-8.836-21.05-21-21.05-21 2.833-3.6 5.917-7.067 9.25-10.4 2.934-2.867 5.934-5.55 9-8.05l20.35 20.35c13.002 13.002 29.667 16.35 47.6 16.35 18.467 0 35.077-3.577 48.6-17.1 8.32-8.32 19.3-19.25 19.3-19.25 2.9 2.367 5.733 4.933 8.5 7.7 3.467 3.533 6.65 7.183 9.55 10.95z" fill="#3a3f51" fill-rule="evenodd"/><g><path d="m78.7 114c-.2-1.167-.332-2.35-.4-3.55-.032-.667-.05-1.333-.05-2 0-.7.018-1.367.05-2 0-.067.018-.133.05-.2.435-7.367 3.334-13.733 8.7-19.1 5.9-5.833 12.984-8.75 21.25-8.75 8.3 0 15.384 2.917 21.25 8.75 5.834 5.934 8.75 13.033 8.75 21.3s-2.916 15.35-8.75 21.25c-.2.233-.416.45-.65.65-.966.933-1.982 1.783-3.05 2.55-5.065 3.733-10.916 5.6-17.55 5.6s-12.466-1.866-17.5-5.6c-1.332-.934-2.582-2-3.75-3.2-4.532-4.5-7.316-9.734-8.35-15.7z" fill="#0cf" fill-rule="evenodd"/><g fill="none" stroke="#0cf" stroke-miterlimit="1"><path d="m157.8 59.75-15 14.65m-112.015-41.874 40.865 40.724m84.6 84.25 27.808 28.78m1.855-153.894-28.113 27.364m-125.45 126 27.35-27.4" stroke-width="2"/><path d="m157.8 59.75-16.95 17.2m-81.88-16.346 17.2 17.15m-16.547 80.676 16.75-17.4m61.928-1.396 18.028 17.945" stroke-width="7"/></g></g></g></svg>
|
After Width: | Height: | Size: 2.2 KiB |
@@ -6,7 +6,7 @@
|
||||
|
||||
html,
|
||||
body {
|
||||
@apply h-full bg-neutral-50 text-neutral-800 dark:bg-base dark:text-neutral-400;
|
||||
@apply h-full bg-neutral-50 text-neutral-800 dark:bg-base dark:text-neutral-400 w-full;
|
||||
}
|
||||
|
||||
body {
|
||||
|
@@ -7,6 +7,7 @@
|
||||
'action' => 'delete',
|
||||
'content' => null,
|
||||
'closeOutside' => true,
|
||||
'minWidth' => '36rem',
|
||||
])
|
||||
<div x-data="{ modalOpen: false }" :class="{ 'z-40': modalOpen }" @keydown.window.escape="modalOpen=false"
|
||||
class="relative w-auto h-auto" wire:ignore>
|
||||
@@ -40,7 +41,7 @@
|
||||
x-transition:leave="ease-in duration-100"
|
||||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
||||
class="relative w-full py-6 border rounded drop-shadow min-w-full lg:min-w-[36rem] max-w-fit bg-white border-neutral-200 dark:bg-base px-6 dark:border-coolgray-300">
|
||||
class="relative w-full py-6 border rounded drop-shadow min-w-full lg:min-w-[{{ $minWidth }}] max-w-fit bg-white border-neutral-200 dark:bg-base px-6 dark:border-coolgray-300">
|
||||
<div class="flex items-center justify-between pb-3">
|
||||
<h3 class="text-2xl font-bold">{{ $title }}</h3>
|
||||
<button @click="modalOpen=false"
|
||||
|
@@ -7,8 +7,13 @@
|
||||
}
|
||||
window.location.reload();
|
||||
},
|
||||
setZoom(zoom) {
|
||||
localStorage.setItem('zoom', zoom);
|
||||
window.location.reload();
|
||||
},
|
||||
init() {
|
||||
this.full = localStorage.getItem('pageWidth');
|
||||
this.zoom = localStorage.getItem('zoom');
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
||||
const userSettings = localStorage.getItem('theme');
|
||||
if (userSettings !== 'system') {
|
||||
@@ -21,6 +26,7 @@
|
||||
}
|
||||
});
|
||||
this.queryTheme();
|
||||
this.checkZoom();
|
||||
},
|
||||
setTheme(type) {
|
||||
this.theme = type;
|
||||
@@ -44,6 +50,30 @@
|
||||
this.theme = 'system';
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
},
|
||||
checkZoom() {
|
||||
if (this.zoom === null) {
|
||||
this.setZoom(100);
|
||||
}
|
||||
if (this.zoom === '90') {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
html {
|
||||
font-size: 93.75%;
|
||||
}
|
||||
|
||||
:root {
|
||||
--vh: 1vh;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
html {
|
||||
font-size: 87.5%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
}
|
||||
}">
|
||||
<div class="flex pt-6 pb-4 pl-2">
|
||||
@@ -69,7 +99,7 @@
|
||||
</div>
|
||||
</x-slot:title>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="mb-1 font-bold border-b dark:border-coolgray-500 dark:text-white text-md">Color</div>
|
||||
<div class="font-bold border-b dark:border-coolgray-500 dark:text-white text-md">Color</div>
|
||||
<button @click="setTheme('dark')" class="px-1 dropdown-item-no-padding">Dark</button>
|
||||
<button @click="setTheme('light')" class="px-1 dropdown-item-no-padding">Light</button>
|
||||
<button @click="setTheme('system')" class="px-1 dropdown-item-no-padding">System</button>
|
||||
@@ -78,6 +108,9 @@
|
||||
x-show="full === 'full'">Center</button>
|
||||
<button @click="switchWidth()" class="px-1 dropdown-item-no-padding"
|
||||
x-show="full === 'center'">Full</button>
|
||||
<div class="my-1 font-bold border-b dark:border-coolgray-500 dark:text-white text-md">Zoom</div>
|
||||
<button @click="setZoom(100)" class="px-1 dropdown-item-no-padding">100%</button>
|
||||
<button @click="setZoom(90)" class="px-1 dropdown-item-no-padding">90%</button>
|
||||
</div>
|
||||
</x-dropdown>
|
||||
</div>
|
||||
@@ -163,8 +196,8 @@
|
||||
class="{{ request()->is('storages*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
||||
href="{{ route('storage.index') }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round"
|
||||
stroke-linejoin="round" stroke-width="2">
|
||||
<path d="M4 6a8 3 0 1 0 16 0A8 3 0 1 0 4 6" />
|
||||
<path d="M4 6v6a8 3 0 0 0 16 0V6" />
|
||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
|
||||
|
@@ -17,7 +17,8 @@
|
||||
@if (isEmailEnabled($team) && auth()->user()->isAdminFromSession() && isTestEmailEnabled($team))
|
||||
<x-modal-input buttonTitle="Send Test Email" title="Send Test Email">
|
||||
<form wire:submit.prevent="sendTestEmail" class="flex flex-col w-full gap-2">
|
||||
<x-forms.input wire:model="testEmailAddress" placeholder="test@example.com" id="testEmailAddress" label="Recipients" required />
|
||||
<x-forms.input wire:model="testEmailAddress" placeholder="test@example.com" id="testEmailAddress"
|
||||
label="Recipients" required />
|
||||
<x-forms.button type="submit" @click="modalOpen=false">
|
||||
Send Email
|
||||
</x-forms.button>
|
||||
@@ -62,8 +63,11 @@
|
||||
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
||||
<x-forms.input required id="smtpHost" placeholder="smtp.mailgun.org" label="Host" />
|
||||
<x-forms.input required id="smtpPort" placeholder="587" label="Port" />
|
||||
<x-forms.input id="smtpEncryption" helper="If SMTP uses SSL, set it to 'tls'."
|
||||
placeholder="tls" label="Encryption" />
|
||||
<x-forms.select id="smtpEncryption" label="Encryption">
|
||||
<option value="tls">TLS</option>
|
||||
<option value="ssl">SSL</option>
|
||||
<option value="none">None</option>
|
||||
</x-forms.select>
|
||||
</div>
|
||||
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
||||
<x-forms.input id="smtpUsername" label="SMTP Username" />
|
||||
|
@@ -13,6 +13,8 @@
|
||||
helper="Allow to automatically deploy Preview Deployments for all opened PR's.<br><br>Closing a PR will delete Preview Deployments."
|
||||
instantSave id="isPreviewDeploymentsEnabled" label="Preview Deployments" />
|
||||
@endif
|
||||
<x-forms.checkbox helper="Disable Docker build cache on every deployment." instantSave id="disableBuildCache"
|
||||
label="Disable Build Cache" />
|
||||
<x-forms.checkbox
|
||||
helper="Your application will be available only on https if your domain starts with https://..."
|
||||
instantSave id="isForceHttpsEnabled" label="Force Https" />
|
||||
|
@@ -32,7 +32,7 @@
|
||||
@forelse ($deployments as $deployment)
|
||||
<div @class([
|
||||
'dark:bg-coolgray-100 p-2 border-l-2 transition-colors hover:no-underline box-without-bg-without-border bg-white flex-col cursor-pointer dark:hover:text-neutral-400 dark:hover:bg-coolgray-200',
|
||||
'border-warning border-dashed ' =>
|
||||
'border-white border-dashed ' =>
|
||||
data_get($deployment, 'status') === 'in_progress' ||
|
||||
data_get($deployment, 'status') === 'cancelled-by-user',
|
||||
'border-error border-dashed ' =>
|
||||
|
@@ -58,30 +58,34 @@
|
||||
<div @if ($isKeepAliveOn) wire:poll.2000ms="polling" @endif
|
||||
class="flex flex-col-reverse w-full p-2 px-4 mt-4 overflow-y-auto bg-white dark:text-white dark:bg-coolgray-100 scrollbar dark:border-coolgray-300"
|
||||
:class="fullscreen ? '' : 'min-h-14 max-h-[40rem] border border-dotted rounded'">
|
||||
<div :class="fullscreen ? 'fixed' : 'absolute'" class="top-4 right-6">
|
||||
<div :class="fullscreen ? 'fixed' : 'absolute'" class="top-2 right-3">
|
||||
<div class="flex justify-end gap-4 fixed -translate-x-full">
|
||||
<button title="Toggle timestamps" x-on:click="showTimestamps = !showTimestamps">
|
||||
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
stroke="currentColor" stroke-width="2">
|
||||
<svg class="w-5 h-5 opacity-30 hover:opacity-100" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor"
|
||||
stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button title="Go Top" x-show="fullscreen" x-on:click="goTop">
|
||||
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg class="w-5 h-5 opacity-30 hover:opacity-100" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round"
|
||||
stroke-linejoin="round" stroke-width="2" d="M12 5v14m4-10l-4-4M8 9l4-4" />
|
||||
</svg>
|
||||
</button>
|
||||
<button title="Follow Logs" x-show="fullscreen" :class="alwaysScroll ? 'dark:text-warning' : ''"
|
||||
x-on:click="toggleScroll">
|
||||
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg class="w-5 h-5 opacity-30 hover:opacity-100" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round"
|
||||
stroke-linejoin="round" stroke-width="2" d="M12 5v14m4-4l-4 4m-4-4l4 4" />
|
||||
</svg>
|
||||
</button>
|
||||
<button title="Fullscreen" x-show="!fullscreen" x-on:click="makeFullscreen">
|
||||
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg class="w-5 h-5 opacity-30 hover:opacity-100" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none">
|
||||
<path
|
||||
d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" />
|
||||
@@ -91,7 +95,8 @@
|
||||
</svg>
|
||||
</button>
|
||||
<button title="Minimize" x-show="fullscreen" x-on:click="makeFullscreen">
|
||||
<svg class="icon" viewBox="0 0 24 24"xmlns="http://www.w3.org/2000/svg">
|
||||
<svg class="w-5 h-5 opacity-30 hover:opacity-100"
|
||||
viewBox="0 0 24 24"xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round"
|
||||
stroke-linejoin="round" stroke-width="2"
|
||||
d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" />
|
||||
|
@@ -43,7 +43,7 @@
|
||||
<h4 class="py-2 ">Select another Private Key</h4>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@foreach ($privateKeys as $key)
|
||||
<x-forms.button wire:click.defer="setPrivateKey('{{ $key->id }}')">{{ $key->name }}
|
||||
<x-forms.button wire:click="setPrivateKey('{{ $key->id }}')">{{ $key->name }}
|
||||
</x-forms.button>
|
||||
@endforeach
|
||||
</div>
|
||||
|
@@ -169,7 +169,8 @@
|
||||
}
|
||||
const filtered = Object.values(items).filter(item => {
|
||||
return (item.name?.toLowerCase().includes(searchLower) ||
|
||||
item.description?.toLowerCase().includes(searchLower))
|
||||
item.description?.toLowerCase().includes(searchLower) ||
|
||||
item.slogan?.toLowerCase().includes(searchLower))
|
||||
})
|
||||
return isSort ? filtered.sort(sortFn) : filtered;
|
||||
},
|
||||
|
@@ -15,7 +15,7 @@
|
||||
volume
|
||||
name, example: <span class='text-helper'>-pr-1</span>" />
|
||||
@if ($resource?->build_pack !== 'dockercompose')
|
||||
<x-modal-input :closeOutside="false" buttonTitle="+ Add" title="New Persistent Storage">
|
||||
<x-modal-input :closeOutside="false" buttonTitle="+ Add" title="New Persistent Storage" minWidth="64rem">
|
||||
<livewire:project.shared.storages.add :resource="$resource" />
|
||||
</x-modal-input>
|
||||
@endif
|
||||
|
@@ -1,13 +1,15 @@
|
||||
<div class="flex flex-col w-full gap-2 rounded max-h-[80vh] overflow-y-auto scrollbar">
|
||||
<div class="p-4">
|
||||
You can add Volumes, Files and Directories to your resources here.
|
||||
<div class="flex flex-col w-full gap-2 max-h-[80vh] overflow-y-auto scrollbar">
|
||||
<form class="flex flex-col w-full gap-2 rounded " wire:submit='submitPersistentVolume'>
|
||||
<div class="flex flex-col">
|
||||
<h3>Volume Mount</h3>
|
||||
<div>Docker Volumes mounted to the container.</div>
|
||||
</div>
|
||||
@if ($isSwarm)
|
||||
<h5>Swarm Mode detected: You need to set a shared volume (EFS/NFS/etc) on all the worker nodes if you
|
||||
would
|
||||
like to use a persistent volumes.</h5>
|
||||
@endif
|
||||
<div class="flex flex-col gap-2 px-2">
|
||||
<x-forms.input placeholder="pv-name" id="name" label="Name" required helper="Volume name." />
|
||||
@if ($isSwarm)
|
||||
<x-forms.input placeholder="/root" id="host_path" label="Source Path" required
|
||||
@@ -19,29 +21,39 @@
|
||||
<x-forms.input placeholder="/tmp/root" id="mount_path" label="Destination Path" required
|
||||
helper="Directory inside the container." />
|
||||
<x-forms.button type="submit" @click="modalOpen=false">
|
||||
Save
|
||||
Add
|
||||
</x-forms.button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submitFileStorage'>
|
||||
<form class="flex flex-col w-full gap-2 rounded py-4" wire:submit='submitFileStorage'>
|
||||
<div class="flex flex-col">
|
||||
<h3>File Mount</h3>
|
||||
<div>Actual file mounted from the host system to the container.</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 px-2">
|
||||
<x-forms.input placeholder="/etc/nginx/nginx.conf" id="file_storage_path" label="Destination Path" required
|
||||
helper="File inside the container" />
|
||||
helper="File location inside the container" />
|
||||
<x-forms.textarea label="Content" id="file_storage_content"></x-forms.textarea>
|
||||
<x-forms.button type="submit" @click="modalOpen=false">
|
||||
Save
|
||||
Add
|
||||
</x-forms.button>
|
||||
</div>
|
||||
</form>
|
||||
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submitFileStorageDirectory'>
|
||||
<div class="flex flex-col">
|
||||
<h3>Directory Mount</h3>
|
||||
<div>Directory mounted from the host system to the container.</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 px-2">
|
||||
<x-forms.input placeholder="{{ application_configuration_dir() }}/{{ $resource->uuid }}/etc/nginx"
|
||||
id="file_storage_directory_source" label="Source Directory" required
|
||||
helper="Directory on the host system." />
|
||||
<x-forms.input placeholder="/etc/nginx" id="file_storage_directory_destination"
|
||||
label="Destination Directory" required helper="Directory inside the container." />
|
||||
<x-forms.button type="submit" @click="modalOpen=false">
|
||||
Save
|
||||
Add
|
||||
</x-forms.button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -88,7 +88,7 @@
|
||||
<div class="w-full" x-data="{
|
||||
open: false,
|
||||
search: '{{ $serverTimezone ?: '' }}',
|
||||
timezones: @js($timezones),
|
||||
timezones: @js($this->timezones),
|
||||
placeholder: '{{ $serverTimezone ? 'Search timezone...' : 'Select Server Timezone' }}',
|
||||
init() {
|
||||
this.$watch('search', value => {
|
||||
|
@@ -33,8 +33,11 @@
|
||||
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
||||
<x-forms.input required id="smtpHost" placeholder="smtp.mailgun.org" label="Host" />
|
||||
<x-forms.input required id="smtpPort" placeholder="587" label="Port" />
|
||||
<x-forms.input id="smtpEncryption" helper="If SMTP uses SSL, set it to 'tls'." placeholder="tls"
|
||||
label="Encryption" />
|
||||
<x-forms.select id="smtpEncryption" label="Encryption">
|
||||
<option value="tls">TLS</option>
|
||||
<option value="ssl">SSL</option>
|
||||
<option value="none">None</option>
|
||||
</x-forms.select>
|
||||
</div>
|
||||
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
||||
<x-forms.input id="smtpUsername" label="SMTP Username" />
|
||||
|
@@ -23,7 +23,7 @@
|
||||
<div class="w-full" x-data="{
|
||||
open: false,
|
||||
search: '{{ $settings->instance_timezone ?: '' }}',
|
||||
timezones: @js($timezones),
|
||||
timezones: @js($this->timezones),
|
||||
placeholder: '{{ $settings->instance_timezone ? 'Search timezone...' : 'Select Server Timezone' }}',
|
||||
init() {
|
||||
this.$watch('search', value => {
|
||||
|
@@ -58,7 +58,18 @@
|
||||
@else
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex gap-2">
|
||||
<div class="flex items-end gap-2 w-full">
|
||||
<x-forms.input id="github_app.name" label="App Name" disabled />
|
||||
<x-forms.button wire:click.prevent="updateGithubAppName" class="bg-coollabs">
|
||||
Sync Name
|
||||
</x-forms.button>
|
||||
<a href="{{ $this->getGithubAppNameUpdatePath() }}">
|
||||
<x-forms.button>
|
||||
Rename
|
||||
<x-external-link />
|
||||
</x-forms.button>
|
||||
</a>
|
||||
</div>
|
||||
<x-forms.input id="github_app.organization" label="Organization" disabled
|
||||
placeholder="If empty, personal user will be used" />
|
||||
</div>
|
||||
|
@@ -83,6 +83,11 @@ if [ "$OS_TYPE" = "manjaro" ] || [ "$OS_TYPE" = "manjaro-arm" ]; then
|
||||
OS_TYPE="arch"
|
||||
fi
|
||||
|
||||
# Check if the OS is Endeavour OS, if so, change it to arch
|
||||
if [ "$OS_TYPE" = "endeavouros" ]; then
|
||||
OS_TYPE="arch"
|
||||
fi
|
||||
|
||||
# Check if the OS is Asahi Linux, if so, change it to fedora
|
||||
if [ "$OS_TYPE" = "fedora-asahi-remix" ]; then
|
||||
OS_TYPE="fedora"
|
||||
|
@@ -9,11 +9,11 @@ services:
|
||||
image: budibase.docker.scarf.sh/budibase/apps
|
||||
environment:
|
||||
- SELF_HOSTED=1
|
||||
- COUCH_DB_URL=http://$SERVICE_USER_BUDIBASE_COUCHDB:$SERVICE_PASSWORD_BUDIBASE_COUCHDB@couchdb-service:5984
|
||||
- COUCH_DB_URL=http://$SERVICE_USER_COUCHDB:$SERVICE_PASSWORD_COUCHDB@couchdb-service:5984
|
||||
- WORKER_URL=http://worker-service:4003
|
||||
- MINIO_URL=http://minio-service:9000
|
||||
- MINIO_ACCESS_KEY=$SERVICE_USER_BUDIBASE_MINIO
|
||||
- MINIO_SECRET_KEY=$SERVICE_PASSWORD_BUDIBASE_MINIO
|
||||
- MINIO_ACCESS_KEY=$SERVICE_USER_MINIO
|
||||
- MINIO_SECRET_KEY=$SERVICE_PASSWORD_MINIO
|
||||
- INTERNAL_API_KEY=$SERVICE_BASE64_128_BUDIBASE
|
||||
- BUDIBASE_ENVIRONMENT=${BUDIBASE_ENVIRONMENT:-PRODUCTION}
|
||||
- PORT=4002
|
||||
@@ -22,14 +22,14 @@ services:
|
||||
- LOG_LEVEL=info
|
||||
- ENABLE_ANALYTICS=${ENABLE_ANALYTICS:-true}
|
||||
- REDIS_URL=redis-service:6379
|
||||
- REDIS_PASSWORD=$SERVICE_PASSWORD_BUDIBASE_REDIS
|
||||
- REDIS_PASSWORD=$SERVICE_PASSWORD_REDIS
|
||||
- BB_ADMIN_USER_EMAIL=
|
||||
- BB_ADMIN_USER_PASSWORD=
|
||||
depends_on:
|
||||
- worker-service
|
||||
- redis-service
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://app-service:4002"]
|
||||
test: ["CMD", "wget", "--spider", "-qO-", "http://localhost:4002/health"]
|
||||
interval: 15s
|
||||
timeout: 15s
|
||||
retries: 5
|
||||
@@ -43,21 +43,21 @@ services:
|
||||
- CLUSTER_PORT=10000
|
||||
- API_ENCRYPTION_KEY=$SERVICE_BASE64_64_BUDIBASE
|
||||
- JWT_SECRET=$SERVICE_BASE64_64_BUDIBASE
|
||||
- MINIO_ACCESS_KEY=$SERVICE_USER_BUDIBASE_MINIO
|
||||
- MINIO_SECRET_KEY=$SERVICE_PASSWORD_BUDIBASE_MINIO
|
||||
- MINIO_ACCESS_KEY=$SERVICE_USER_MINIO
|
||||
- MINIO_SECRET_KEY=$SERVICE_PASSWORD_MINIO
|
||||
- MINIO_URL=http://minio-service:9000
|
||||
- APPS_URL=http://app-service:4002
|
||||
- COUCH_DB_USERNAME=$SERVICE_USER_BUDIBASE_COUCHDB
|
||||
- COUCH_DB_PASSWORD=$SERVICE_PASSWORD_BUDIBASE_COUCHDB
|
||||
- COUCH_DB_URL=http://$SERVICE_USER_BUDIBASE_COUCHDB:$SERVICE_PASSWORD_BUDIBASE_COUCHDB@couchdb-service:5984
|
||||
- COUCH_DB_USERNAME=$SERVICE_USER_COUCHDB
|
||||
- COUCH_DB_PASSWORD=$SERVICE_PASSWORD_COUCHDB
|
||||
- COUCH_DB_URL=http://$SERVICE_USER_COUCHDB:$SERVICE_PASSWORD_COUCHDB@couchdb-service:5984
|
||||
- INTERNAL_API_KEY=$SERVICE_BASE64_128_BUDIBASE
|
||||
- REDIS_URL=redis-service:6379
|
||||
- REDIS_PASSWORD=$SERVICE_PASSWORD_BUDIBASE_REDIS
|
||||
- REDIS_PASSWORD=$SERVICE_PASSWORD_REDIS
|
||||
depends_on:
|
||||
- redis-service
|
||||
- minio-service
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://worker-service:4003"]
|
||||
test: ["CMD", "wget", "--spider", "-qO-", "http://localhost:4003/health"]
|
||||
interval: 15s
|
||||
timeout: 15s
|
||||
retries: 5
|
||||
@@ -68,8 +68,8 @@ services:
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
environment:
|
||||
- MINIO_ACCESS_KEY=$SERVICE_USER_BUDIBASE_MINIO
|
||||
- MINIO_SECRET_KEY=$SERVICE_PASSWORD_BUDIBASE_MINIO
|
||||
- MINIO_ROOT_USER=$SERVICE_USER_MINIO
|
||||
- MINIO_ROOT_PASSWORD=$SERVICE_PASSWORD_MINIO
|
||||
- MINIO_BROWSER=off
|
||||
command: server /data --console-address ":9001"
|
||||
healthcheck:
|
||||
@@ -105,8 +105,8 @@ services:
|
||||
couchdb-service:
|
||||
image: budibase/couchdb
|
||||
environment:
|
||||
- COUCHDB_PASSWORD=$SERVICE_PASSWORD_BUDIBASE_COUCHDB
|
||||
- COUCHDB_USER=$SERVICE_USER_BUDIBASE_COUCHDB
|
||||
- COUCHDB_PASSWORD=$SERVICE_PASSWORD_COUCHDB
|
||||
- COUCHDB_USER=$SERVICE_USER_COUCHDB
|
||||
- TARGETBUILD=docker-compose
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:5984/"]
|
||||
@@ -119,12 +119,12 @@ services:
|
||||
|
||||
redis-service:
|
||||
image: redis
|
||||
command: redis-server --requirepass "$SERVICE_PASSWORD_BUDIBASE_REDIS"
|
||||
command: redis-server --requirepass "$SERVICE_PASSWORD_REDIS"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test:
|
||||
["CMD", "redis-cli", "-a", "$SERVICE_PASSWORD_BUDIBASE_REDIS", "ping"]
|
||||
["CMD", "redis-cli", "-a", "$SERVICE_PASSWORD_REDIS", "ping"]
|
||||
interval: 15s
|
||||
timeout: 15s
|
||||
retries: 5
|
||||
@@ -138,13 +138,8 @@ services:
|
||||
command: --debug --http-api-update bbapps bbworker bbproxy
|
||||
environment:
|
||||
- WATCHTOWER_HTTP_API=true
|
||||
- WATCHTOWER_HTTP_API_TOKEN=$SERVICE_PASSWORD_BUDIBASE_WATCHTOWER
|
||||
- WATCHTOWER_HTTP_API_TOKEN=$SERVICE_PASSWORD_WATCHTOWER
|
||||
- WATCHTOWER_CLEANUP=true
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.enable=false"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://watchtower-service:8080"]
|
||||
interval: 15s
|
||||
timeout: 15s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
exclude_from_hc: true
|
||||
|
@@ -1,3 +1,4 @@
|
||||
# ignore: true
|
||||
# documentation: https://coder.com/docs
|
||||
# slogan: Coder is an open-source platform for creating and managing cloud development environments on your infrastructure, with the tools and IDEs your developers already love.
|
||||
# tags: coder,development,environment,self-hosted,postgres
|
||||
|
@@ -12,10 +12,10 @@ services:
|
||||
- WEBAPP_URL=$SERVICE_FQDN_FORMBRICKS
|
||||
- DATABASE_URL=postgres://$SERVICE_USER_POSTGRESQL:$SERVICE_PASSWORD_POSTGRESQL@postgresql:5432/${POSTGRESQL_DATABASE:-formbricks}
|
||||
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL}
|
||||
- NEXTAUTH_SECRET=$SERVICE_BASE64_64_NEXTAUTH
|
||||
- NEXTAUTH_SECRET=$SERVICE_BASE64_NEXTAUTH
|
||||
- NEXTAUTH_URL=$SERVICE_FQDN_FORMBRICKS
|
||||
- ENCRYPTION_KEY=$SERVICE_BASE64_64_ENCRYPTION
|
||||
- CRON_SECRET=$SERVICE_BASE64_64_CRON
|
||||
- ENCRYPTION_KEY=$SERVICE_BASE64_ENCRYPTION
|
||||
- CRON_SECRET=$SERVICE_BASE64_CRON
|
||||
- ENTERPRISE_LICENSE_KEY=${ENTERPRISE_LICENSE_KEY}
|
||||
- MAIL_FROM=${MAIL_FROM:-test@example.com}
|
||||
- SMTP_HOST=${SMTP_HOST:-test.example.com}
|
||||
@@ -59,7 +59,7 @@ services:
|
||||
- REDIS_URL=${REDIS_URL}
|
||||
- REDIS_HTTP_URL=${REDIS_HTTP_URL}
|
||||
- DEFAULT_ORGANIZATION_ID=${DEFAULT_ORGANIZATION_ID}
|
||||
- DEFAULT_ORGANIZATION_ROLE=${DEFAULT_ORGANIZATION_ROLE:-admin}
|
||||
- DEFAULT_ORGANIZATION_ROLE=${DEFAULT_ORGANIZATION_ROLE:-owner}
|
||||
volumes:
|
||||
- formbricks-uploads:/apps/web/uploads/
|
||||
depends_on:
|
||||
@@ -72,7 +72,7 @@ services:
|
||||
retries: 15
|
||||
|
||||
postgresql:
|
||||
image: postgres:16-alpine
|
||||
image: pgvector/pgvector:pg16
|
||||
volumes:
|
||||
- formbricks-postgresql-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
|
@@ -1,3 +1,4 @@
|
||||
# ignore: true
|
||||
# documentation: https://github.com/dockur/macos
|
||||
# slogan: Run macOS in a containerized environment.
|
||||
# tags: macos, virtualization, container, os
|
||||
@@ -12,6 +13,8 @@ services:
|
||||
environment:
|
||||
- SERVICE_FQDN_MACOS_8006
|
||||
- VERSION=15
|
||||
devices:
|
||||
- /dev/kvm:/dev/kvm
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
stop_grace_period: 2m
|
||||
|
30
templates/compose/overseerr.yaml
Normal file
30
templates/compose/overseerr.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
# documentation: https://docs.overseerr.dev/getting-started/installation#docker
|
||||
# slogan: Overseerr is a request management and media discovery tool built to work with your existing Plex ecosystem.
|
||||
# tags: media,request,plex,sonarr,radarr
|
||||
# logo: svgs/overseerr.svg
|
||||
# port: 5055
|
||||
|
||||
services:
|
||||
overseerr:
|
||||
image: sctx/overseerr:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_OVERSEERR_5055
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=${TZ:-America/Toronto}
|
||||
volumes:
|
||||
- overseerr-config:/app/config
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"wget",
|
||||
"--tries",
|
||||
"1",
|
||||
"--no-verbose",
|
||||
"--spider",
|
||||
"http://localhost:5055/api/v1/status",
|
||||
]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 15
|
27
templates/compose/plex.yaml
Normal file
27
templates/compose/plex.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
# documentation: https://docs.linuxserver.io/images/docker-plex/
|
||||
# slogan: Plex organizes video, music and photos from personal media libraries and streams them to smart TVs, streaming boxes and mobile devices.
|
||||
# tags: media, server, movies, tv, music
|
||||
# logo: svgs/plex.svg
|
||||
# port: 32400
|
||||
|
||||
services:
|
||||
plex:
|
||||
image: lscr.io/linuxserver/plex:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_PLEX_32400
|
||||
- _APP_URL=$SERVICE_FQDN_PLEX
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=${TZ:-America/Toronto}
|
||||
- PLEX_CLAIM=${PLEX_CLAIM}
|
||||
#devices:
|
||||
# - "/dev/dri:/dev/dri"
|
||||
volumes:
|
||||
- plex-config:/config
|
||||
- plex-tv:/tv
|
||||
- plex-movies:/movies
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:32400/identity"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 15
|
@@ -13,9 +13,8 @@ services:
|
||||
- FRONTEND_URL=${SERVICE_FQDN_POSTIZ}
|
||||
- NEXT_PUBLIC_BACKEND_URL=${SERVICE_FQDN_POSTIZ}/api
|
||||
- JWT_SECRET=${SERVICE_PASSWORD_JWTSECRET}
|
||||
- DATABASE_URL=postgresql://${SERVICE_USER_POSTGRESQL}:${SERVICE_PASSWORD_POSTGRESQL}@postgres:5432/${POSTGRESQL_DATABASE:-postiz-db}
|
||||
# Changed Redis URL to use default username
|
||||
- REDIS_URL=redis://${SERVICE_USER_REDIS}:${SERVICE_PASSWORD_REDIS}@redis:6379
|
||||
- DATABASE_URL=postgresql://postgres:${SERVICE_PASSWORD_POSTGRESQL}@postgres:5432/${POSTGRESQL_DATABASE:-postiz-db}
|
||||
- REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@redis:6379
|
||||
- BACKEND_INTERNAL_URL=http://localhost:3000
|
||||
|
||||
# Cloudflare R2 Settings
|
||||
@@ -36,6 +35,7 @@ services:
|
||||
- RESEND_API_KEY=${RESEND_API_KEY}
|
||||
- EMAIL_FROM_ADDRESS=${EMAIL_FROM_ADDRESS}
|
||||
- EMAIL_FROM_NAME=${EMAIL_FROM_NAME}
|
||||
- EMAIL_PROVIDER=${EMAIL_PROVIDER}
|
||||
|
||||
# Social Media API Settings
|
||||
- X_API_KEY=${SERVICE_X_API}
|
||||
@@ -75,7 +75,7 @@ services:
|
||||
# Misc Settings
|
||||
- NEXT_PUBLIC_DISCORD_SUPPORT=${NEXT_PUBLIC_DISCORD_SUPPORT}
|
||||
- NEXT_PUBLIC_POLOTNO=${NEXT_PUBLIC_POLOTNO}
|
||||
- IS_GENERAL=${IS_GENERAL:-true}
|
||||
- IS_GENERAL=true
|
||||
- NX_ADD_PLUGINS=${NX_ADD_PLUGINS:-false}
|
||||
|
||||
# Payment Settings
|
||||
@@ -106,13 +106,11 @@ services:
|
||||
volumes:
|
||||
- postiz_postgresql_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_USER=${SERVICE_USER_POSTGRESQL}
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL}
|
||||
- POSTGRES_DB=${POSTGRESQL_DATABASE:-postiz-db}
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD-SHELL
|
||||
- pg_isready -U ${SERVICE_USER_POSTGRESQL} -d ${POSTGRESQL_DATABASE:-postiz-db}
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB:-postiz-db}"]
|
||||
interval: 5s
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
@@ -121,7 +119,6 @@ services:
|
||||
image: redis:7.2
|
||||
environment:
|
||||
- REDIS_PASSWORD=${SERVICE_PASSWORD_REDIS}
|
||||
- REDIS_USER=${SERVICE_USER_REDIS}
|
||||
command: redis-server --requirepass ${SERVICE_PASSWORD_REDIS}
|
||||
volumes:
|
||||
- postiz_redis_data:/data
|
||||
|
22
templates/compose/prowlarr.yaml
Normal file
22
templates/compose/prowlarr.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
# documentation: https://hub.docker.com/r/linuxserver/prowlarr
|
||||
# slogan: Prowlarr is a indexer manager/proxy built on the popular arr .net/reactjs base stack to integrate with your various PVR apps. Prowlarr supports both Torrent Trackers and Usenet Indexers. It integrates seamlessly with Sonarr, Radarr, Lidarr, and Readarr offering complete management of your indexers with no per app Indexer setup required (we do it all).
|
||||
# tags: media, server, movies, tv, indexer, torrent, nzb, usenet
|
||||
# logo: svgs/prowlarr.svg
|
||||
# port: 9696
|
||||
|
||||
services:
|
||||
prowlarr:
|
||||
image: lscr.io/linuxserver/prowlarr:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_PROWLARR_9696
|
||||
- _APP_URL=$SERVICE_FQDN_PROWLARR
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=${TZ:-America/Toronto}
|
||||
volumes:
|
||||
- prowlarr-config:/config
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9696/ping"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 15
|
147
templates/compose/pterodactyl.yaml
Normal file
147
templates/compose/pterodactyl.yaml
Normal file
@@ -0,0 +1,147 @@
|
||||
# ignore: true
|
||||
# documentation: https://pterodactyl.io/
|
||||
# slogan: Pterodactyl is a free, open-source game server management panel
|
||||
# tags: game, game server, management, panel, minecraft
|
||||
# logo: svgs/pterodactyl.png
|
||||
# port: 80
|
||||
|
||||
services:
|
||||
mariadb:
|
||||
image: mariadb:10.5
|
||||
restart: unless-stopped
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
healthcheck:
|
||||
test:
|
||||
["CMD-SHELL", "healthcheck.sh --connect --innodb_initialized || exit 1"]
|
||||
start_period: 10s
|
||||
interval: 10s
|
||||
timeout: 1s
|
||||
retries: 3
|
||||
environment:
|
||||
- SERVICE_PASSWORD_MYSQL
|
||||
- MYSQL_ROOT_PASSWORD=$SERVICE_PASSWORD_MYSQLROOT
|
||||
- MYSQL_DATABASE=panel
|
||||
- MYSQL_USER=pterodactyl
|
||||
- MYSQL_PASSWORD=$SERVICE_PASSWORD_MYSQL
|
||||
volumes:
|
||||
- pterodactyl-db:/var/lib/mysql
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "redis-cli ping || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 1s
|
||||
retries: 3
|
||||
|
||||
pterodactyl:
|
||||
image: ghcr.io/pterodactyl/panel:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- "panel-var:/app/var/"
|
||||
- "panel-nginx:/etc/nginx/http.d/"
|
||||
- "panel-certs:/etc/letsencrypt/"
|
||||
- type: bind
|
||||
source: ./etc/entrypoint.sh
|
||||
target: /entrypoint.sh
|
||||
mode: "0755"
|
||||
content: |
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
echo "Waiting for services to be ready..."
|
||||
sleep 30
|
||||
|
||||
echo "Setting logs permissions..."
|
||||
chown -R nginx: /app/storage/logs/
|
||||
|
||||
if ! php artisan p:user:list | grep -q "$ADMIN_EMAIL"; then
|
||||
echo "Creating admin user..."
|
||||
php artisan p:user:make --no-interaction \
|
||||
--admin=1 \
|
||||
--email="$ADMIN_EMAIL" \
|
||||
--username="$ADMIN_USERNAME" \
|
||||
--name-first="$ADMIN_FIRSTNAME" \
|
||||
--name-last="$ADMIN_LASTNAME" \
|
||||
--password="$ADMIN_PASSWORD"
|
||||
echo "Admin user created"
|
||||
else
|
||||
echo "Admin user already exists, skipping creation"
|
||||
fi
|
||||
|
||||
exec supervisord -c --nodaemon
|
||||
|
||||
command: ["/entrypoint.sh"]
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://localhost:80 || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 1s
|
||||
retries: 3
|
||||
environment:
|
||||
- SERVICE_FQDN_PTERODACTYL
|
||||
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@example.com}
|
||||
- ADMIN_USERNAME=${SERVICE_USER_ADMIN}
|
||||
- ADMIN_FIRSTNAME=${ADMIN_FIRSTNAME:-Admin}
|
||||
- ADMIN_LASTNAME=${ADMIN_LASTNAME:-User}
|
||||
- ADMIN_PASSWORD=${SERVICE_PASSWORD_ADMIN}
|
||||
- PTERODACTYL_HTTPS=${PTERODACTYL_HTTPS:-false}
|
||||
- APP_ENV=production
|
||||
- APP_ENVIRONMENT_ONLY=false
|
||||
- APP_URL=${PTERODACTYL_PUBLIC_FQDN:-$SERVICE_FQDN_PTERODACTYL}
|
||||
- APP_TIMEZONE=${TIMEZONE:-UTC}
|
||||
- APP_SERVICE_AUTHOR=${APP_SERVICE_AUTHOR:-author@example.com}
|
||||
- LOG_LEVEL=${LOG_LEVEL:-debug}
|
||||
- CACHE_DRIVER=redis
|
||||
- SESSION_DRIVER=redis
|
||||
- QUEUE_DRIVER=redis
|
||||
- REDIS_HOST=redis
|
||||
- DB_HOST=mariadb
|
||||
- DB_PORT=3306
|
||||
- DB_PASSWORD=$SERVICE_PASSWORD_MYSQL
|
||||
- MAIL_FROM=$MAIL_FROM
|
||||
- MAIL_DRIVER=$MAIL_DRIVER
|
||||
- MAIL_HOST=$MAIL_HOST
|
||||
- MAIL_PORT=$MAIL_PORT
|
||||
- MAIL_USERNAME=$MAIL_USERNAME
|
||||
- MAIL_PASSWORD=$MAIL_PASSWORD
|
||||
- MAIL_ENCRYPTION=$MAIL_ENCRYPTION
|
||||
|
||||
wings:
|
||||
image: ghcr.io/pterodactyl/wings:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- SERVICE_FQDN_WINGS_8080
|
||||
- TZ=${TIMEZONE:-UTC}
|
||||
- WINGS_USERNAME=pterodactyl
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||
- "/var/lib/docker/containers/:/var/lib/docker/containers/"
|
||||
- "/var/lib/pterodactyl/:/var/lib/pterodactyl/" # See https://discord.com/channels/122900397965705216/493443725012500490/1272195151309045902
|
||||
- "/tmp/pterodactyl/:/tmp/pterodactyl/" # See https://discord.com/channels/122900397965705216/493443725012500490/1272195151309045902
|
||||
- "wings-logs:/var/log/pterodactyl/"
|
||||
|
||||
- type: bind
|
||||
source: ./etc/config.yml
|
||||
target: /etc/pterodactyl/config.yml
|
||||
content: |
|
||||
docker:
|
||||
network:
|
||||
interface: 172.28.0.1
|
||||
dns:
|
||||
- 1.1.1.1
|
||||
- 1.0.0.1
|
||||
name: pterodactyl_nw
|
||||
ispn: false
|
||||
driver: ""
|
||||
network_mode: pterodactyl_nw
|
||||
is_internal: false
|
||||
enable_icc: true
|
||||
network_mtu: 1500
|
||||
interfaces:
|
||||
v4:
|
||||
subnet: 172.28.0.0/16
|
||||
gateway: 172.28.0.1
|
||||
v6:
|
||||
subnet: fdba:17c8:6c94::/64
|
||||
gateway: fdba:17c8:6c94::1011
|
24
templates/compose/radarr.yaml
Normal file
24
templates/compose/radarr.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
# documentation: https://hub.docker.com/r/linuxserver/radarr
|
||||
# slogan: Radarr - A fork of Sonarr to work with movies à la Couchpotato.
|
||||
# tags: media, server, movies
|
||||
# logo: svgs/radarr.svg
|
||||
# port: 7878
|
||||
|
||||
services:
|
||||
radarr:
|
||||
image: lscr.io/linuxserver/radarr:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_RADARR_7878
|
||||
- _APP_URL=$SERVICE_FQDN_RADARR
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=${TZ:-America/Toronto}
|
||||
volumes:
|
||||
- radarr-config:/config
|
||||
# - radarr-movies:/movies #optional
|
||||
# - downloads:/downloads #optional
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:7878/ping"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 15
|
24
templates/compose/sonarr.yaml
Normal file
24
templates/compose/sonarr.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
# documentation: https://hub.docker.com/r/linuxserver/sonarr
|
||||
# slogan: Sonarr (formerly NZBdrone) is a PVR for usenet and bittorrent users. It can monitor multiple RSS feeds for new episodes of your favorite shows and will grab, sort and rename them. It can also be configured to automatically upgrade the quality of files already downloaded when a better quality format becomes available.
|
||||
# tags: media, server, tv
|
||||
# logo: svgs/sonarr.svg
|
||||
# port: 8989
|
||||
|
||||
services:
|
||||
sonarr:
|
||||
image: lscr.io/linuxserver/sonarr:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_SONARR_8989
|
||||
- _APP_URL=$SERVICE_FQDN_SONARR
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=${TZ:-America/Toronto}
|
||||
volumes:
|
||||
- sonarr-config:/config
|
||||
# - sonarr-tv:/tv #optional
|
||||
# - downloads:/downloads #optional
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8989/ping"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 15
|
@@ -1,3 +1,4 @@
|
||||
# ignore: true
|
||||
# documentation: https://github.com/dockur/windows
|
||||
# slogan: Run Windows in a containerized environment.
|
||||
# tags: windows, virtualization, container, os
|
||||
@@ -12,6 +13,8 @@ services:
|
||||
environment:
|
||||
- SERVICE_FQDN_WINDOWS_8006
|
||||
- VERSION=11
|
||||
devices:
|
||||
- /dev/kvm:/dev/kvm
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
stop_grace_period: 2m
|
||||
|
File diff suppressed because one or more lines are too long
57
tests/Feature/ExecuteContainerCommandTest.php
Normal file
57
tests/Feature/ExecuteContainerCommandTest.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
use Tests\Traits\HandlesTestDatabase;
|
||||
|
||||
class ExecuteContainerCommandTest extends TestCase
|
||||
{
|
||||
use HandlesTestDatabase;
|
||||
|
||||
private $user;
|
||||
|
||||
private $team;
|
||||
|
||||
private $server;
|
||||
|
||||
private $application;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Only set up database for tests that need it
|
||||
if ($this->shouldSetUpDatabase()) {
|
||||
$this->setUpTestDatabase();
|
||||
}
|
||||
// Create test data
|
||||
$this->user = User::factory()->create();
|
||||
$this->team = $this->user->teams()->first();
|
||||
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
|
||||
$this->application = Application::factory()->create();
|
||||
|
||||
// Login the user
|
||||
$this->actingAs($this->user);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
if ($this->shouldSetUpDatabase()) {
|
||||
$this->tearDownTestDatabase();
|
||||
}
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
private function shouldSetUpDatabase(): bool
|
||||
{
|
||||
return in_array($this->name(), [
|
||||
'it_allows_valid_container_access',
|
||||
'it_prevents_cross_server_container_access',
|
||||
]);
|
||||
}
|
||||
}
|
78
tests/Traits/HandlesTestDatabase.php
Normal file
78
tests/Traits/HandlesTestDatabase.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Traits;
|
||||
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
trait HandlesTestDatabase
|
||||
{
|
||||
protected function setUpTestDatabase(): void
|
||||
{
|
||||
try {
|
||||
// Create test database if it doesn't exist
|
||||
$database = config('database.connections.testing.database');
|
||||
$this->createTestDatabase($database);
|
||||
|
||||
// Run migrations
|
||||
Artisan::call('migrate:fresh', [
|
||||
'--database' => 'testing',
|
||||
'--seed' => false,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->tearDownTestDatabase();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
protected function tearDownTestDatabase(): void
|
||||
{
|
||||
try {
|
||||
// Drop test database
|
||||
$database = config('database.connections.testing.database');
|
||||
$this->dropTestDatabase($database);
|
||||
} catch (\Exception $e) {
|
||||
// Log error but don't throw
|
||||
error_log('Failed to tear down test database: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected function createTestDatabase($database)
|
||||
{
|
||||
try {
|
||||
// Connect to postgres database to create/drop test database
|
||||
config(['database.connections.pgsql.database' => 'postgres']);
|
||||
DB::purge('pgsql');
|
||||
DB::reconnect('pgsql');
|
||||
|
||||
// Drop if exists and create new database
|
||||
DB::connection('pgsql')->statement("DROP DATABASE IF EXISTS $database WITH (FORCE);");
|
||||
DB::connection('pgsql')->statement("CREATE DATABASE $database;");
|
||||
|
||||
// Switch back to testing connection
|
||||
DB::disconnect('pgsql');
|
||||
DB::reconnect('testing');
|
||||
} catch (\Exception $e) {
|
||||
$this->tearDownTestDatabase();
|
||||
throw new \Exception('Could not create test database: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected function dropTestDatabase($database)
|
||||
{
|
||||
try {
|
||||
// Connect to postgres database to drop test database
|
||||
config(['database.connections.pgsql.database' => 'postgres']);
|
||||
DB::purge('pgsql');
|
||||
DB::reconnect('pgsql');
|
||||
|
||||
// Drop the test database
|
||||
DB::connection('pgsql')->statement("DROP DATABASE IF EXISTS $database WITH (FORCE);");
|
||||
|
||||
DB::disconnect('pgsql');
|
||||
} catch (\Exception $e) {
|
||||
// Log error but don't throw
|
||||
error_log('Failed to drop test database: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"coolify": {
|
||||
"v4": {
|
||||
"version": "4.0.0-beta.374"
|
||||
"version": "4.0.0-beta.376"
|
||||
},
|
||||
"nightly": {
|
||||
"version": "4.0.0-beta.375"
|
||||
"version": "4.0.0-beta.377"
|
||||
},
|
||||
"helper": {
|
||||
"version": "1.0.4"
|
||||
|
Reference in New Issue
Block a user