Compare commits

...

68 Commits

Author SHA1 Message Date
Andras Bacsai
69ebff1a7a Merge pull request #1347 from coollabsio/next
v4.0.0-beta.97
2023-10-20 15:25:50 +02:00
Andras Bacsai
5d9cfc393e add s3 to magicbar 2023-10-20 15:02:40 +02:00
Andras Bacsai
e2a256b31c add api tokens to magic bar 2023-10-20 15:00:57 +02:00
Andras Bacsai
4855af7e57 feat: start all kinds of things 2023-10-20 14:58:00 +02:00
Andras Bacsai
a664174c02 feat: api tokens + deploy webhook 2023-10-20 14:51:01 +02:00
Andras Bacsai
c19c13b4e2 feat: cloning project 2023-10-20 12:34:53 +02:00
Andras Bacsai
266b99bc25 fix: port exposes change, shoud regenerate label 2023-10-20 12:34:25 +02:00
Andras Bacsai
51ef24e1fb fix 2023-10-20 09:38:21 +02:00
Andras Bacsai
33d38ccf40 fix: preselect s3 storage if available 2023-10-20 09:38:13 +02:00
Andras Bacsai
f470ebbbe0 ui: updates 2023-10-20 09:29:09 +02:00
Andras Bacsai
11bd46b200 wip: mongodb backup 2023-10-19 17:17:38 +02:00
Andras Bacsai
53f5674771 wip: mongodb backup 2023-10-19 13:46:15 +02:00
Andras Bacsai
c53d88902c feat: standalone mongodb 2023-10-19 13:32:03 +02:00
Andras Bacsai
e342c4fd65 fix: add PGUSER to prevent HC warning 2023-10-19 11:58:12 +02:00
Andras Bacsai
aab7bd5e28 Merge pull request #1345 from altinselimi/patch-1
Fix spelling error in README.md
2023-10-19 11:49:42 +02:00
Andras Bacsai
3adefb9e49 command: generate services 2023-10-19 11:28:25 +02:00
Altin Selimi
c904441787 Update README.md
Fix image -> imagine spelling error
2023-10-19 11:02:56 +02:00
Andras Bacsai
b7f79ae034 Merge pull request #1333 from theh2so4/main
[+] Templates: BabyBuddy, Code-Server, Dokuwiki, Heimdall, MeTube, SnapDrop and PairDrop
2023-10-19 10:51:24 +02:00
Andras Bacsai
2d63fcdc7f implement new service templates 2023-10-19 10:51:03 +02:00
Andras Bacsai
c1d0cabcfb fix: service docs links 2023-10-19 10:50:52 +02:00
Andras Bacsai
166419b13a update contribution guide 2023-10-19 10:50:47 +02:00
Andras Bacsai
cfc4d3acc7 Merge branch 'main' into next 2023-10-19 09:23:30 +02:00
Andras Bacsai
13a0c2cf43 Merge pull request #1344 from liweiyi88/github-trending-badge
Add github trending badge in readme recognitions
2023-10-19 09:23:01 +02:00
liweiyi88
6ef6975432 add github trending badge in readme recognitions 2023-10-19 09:41:54 +11:00
Andras Bacsai
2c40e93d3b wip: PAT by team 2023-10-18 18:02:09 +02:00
Andras Bacsai
a30ae4fb38 version++ 2023-10-18 15:49:58 +02:00
Andras Bacsai
5b8785d1a9 update prod compose 2023-10-18 15:49:50 +02:00
Andras Bacsai
f6f3364269 Merge pull request #1343 from coollabsio/next
v4.0.0-beta.96
2023-10-18 15:43:26 +02:00
Andras Bacsai
2f93f4450f fix: containerStatus job 2023-10-18 15:43:14 +02:00
Andras Bacsai
2ad7c2b1ce fix: remove custom port from git repo url 2023-10-18 15:33:07 +02:00
Andras Bacsai
6c848199ed fix: add custom port as ssh option to deploy_key based commands 2023-10-18 15:23:43 +02:00
Andras Bacsai
76aab722b8 fix: limit horizon processes to 2 by default 2023-10-18 15:07:04 +02:00
Andras Bacsai
12290304c4 Merge pull request #1342 from coollabsio/next
v4.0.0-beta.95
2023-10-18 14:47:05 +02:00
Andras Bacsai
3a27d13c3e fix 2023-10-18 14:46:26 +02:00
Andras Bacsai
4f588ced96 call handle not matter what 2023-10-18 14:43:48 +02:00
Andras Bacsai
e266c7cdec fix: email channel no recepients 2023-10-18 14:22:09 +02:00
Andras Bacsai
eedc3faba3 fix: labels 2023-10-18 14:14:40 +02:00
Andras Bacsai
2e2c932f07 Merge pull request #1341 from coollabsio/next
v4.0.0-beta.94
2023-10-18 12:49:11 +02:00
Andras Bacsai
e4aed185a2 fix: label generation 2023-10-18 12:48:29 +02:00
Andras Bacsai
dddbe40bbe fix dashboard ui on small screens 2023-10-18 11:35:36 +02:00
Andras Bacsai
59d6818f70 Merge pull request #1339 from coollabsio/next
v4.0.0-beta.93
2023-10-18 11:30:40 +02:00
Andras Bacsai
7678cd47df fix: add config_hash if its null (old deployments) 2023-10-18 11:26:01 +02:00
Andras Bacsai
b101fbacd4 fix: do not show configuration changed if config_hash is null 2023-10-18 11:22:56 +02:00
Andras Bacsai
a61a86dc3b feat: show if config is not applied 2023-10-18 11:20:40 +02:00
Andras Bacsai
0b3cde44c3 feat: able to customize docker labels on applications 2023-10-18 10:32:08 +02:00
TheH2SO4
618d5d837c [+] Template: Dokuwiki
🆕 **New Templates**:

-> ℹ️ **Dokuwiki**: A lightweight and easy-to-use wiki platform for creating and managing documentation and knowledge bases with simplicity and flexibility.
2023-10-18 10:01:37 +02:00
TheH2SO4
d234e8969d [+] Template: MeTube
🆕 **New Template**:

-> ℹ️ **MeTube**: A web GUI for youtube-dl with playlist support. It enables you to effortlessly download videos from YouTube and dozens of other sites.
2023-10-18 09:22:47 +02:00
TheH2SO4
1be77b3fea [+] Template: BabyBuddy
🆕 **New Template**:

-> ℹ️ **Heimdall**: Baby Buddy is an open-source web application that helps parents track their baby's daily activities, growth, and health with ease. It's a handy tool for new parents to keep a close eye on their little one's development.
2023-10-18 09:11:21 +02:00
Andras Bacsai
6b302ab786 Add restarting indicator to resources 2023-10-18 09:03:14 +02:00
TheH2SO4
5831dd6196 Merge pull request #1 from coollabsio/main
4.0.0-beta.92
2023-10-18 08:28:56 +02:00
Andras Bacsai
3c623f13e2 revert version 2023-10-17 20:54:54 +02:00
Andras Bacsai
da54c24e8d fix: setup:dev script & contribution guide 2023-10-17 20:54:26 +02:00
Andras Bacsai
1e39c3d5ab Merge pull request #1338 from coollabsio/next
v4.0.0-beta.92
2023-10-17 19:03:04 +02:00
Andras Bacsai
6071412986 fix: proxy start process 2023-10-17 19:00:23 +02:00
Andras Bacsai
ba7148206a Merge pull request #1336 from coollabsio/next
v4.0.0-beta.91
2023-10-17 15:41:30 +02:00
Andras Bacsai
59c5b22e6c fix: always start proxy if not NONE is selected 2023-10-17 15:40:47 +02:00
Andras Bacsai
be7f2ad9c4 ui: add helper to service domains 2023-10-17 15:34:20 +02:00
Andras Bacsai
62295ef573 Merge pull request #1335 from coollabsio/next
v4.0.0-beta.90
2023-10-17 14:45:26 +02:00
Andras Bacsai
ceb9fcf3b6 service: wordpress 2023-10-17 14:44:25 +02:00
Andras Bacsai
60282f7b6c fix: only include config.json if its exists and a file 2023-10-17 14:23:07 +02:00
Andras Bacsai
f14b0a3411 Merge pull request #1334 from coollabsio/next
v4.0.0-beta.89
2023-10-17 14:06:12 +02:00
Andras Bacsai
30af317bd9 fix: show docker build logs 2023-10-17 14:04:21 +02:00
Andras Bacsai
95faa1c3ad fix: noindex meta tag 2023-10-17 13:28:33 +02:00
TheH2SO4
423d31f227 Merge branch 'main' into main 2023-10-17 13:24:22 +02:00
TheH2SO4
fbb063030d [+] Templates: Heimdall, PairDrop and SnapDrop
🆕 **New Template**:

-> ℹ️ **Heimdall**: Heimdall is a self-hosted dashboard for managing and organizing your server applications, providing a centralized and efficient interface.
-> ℹ️ **PairDrop**: Pairdrop is a self-hosted file sharing and collaboration platform, offering secure file sharing and collaboration capabilities for efficient teamwork.
-> ℹ️ **SnapDrop**: A self-hosted file-sharing service for secure and convenient file transfers, whether on a local network or the internet.
2023-10-17 13:22:37 +02:00
TheH2SO4
1968726cfe [+] Template: Code-Server
🆕 **New Template**:

-> ℹ️ **Code-Server**: Code-Server is a self-hosted, web-based code editor that enables remote coding and collaboration from any device, anywhere.
2023-10-17 12:53:44 +02:00
Andras Bacsai
fb280afe41 Merge pull request #1332 from coollabsio/next
v4.0.0-beta.88
2023-10-17 12:41:45 +02:00
Andras Bacsai
fd488a561a feat: use docker login credentials from server 2023-10-17 12:35:04 +02:00
106 changed files with 2132 additions and 371 deletions

View File

@@ -6,13 +6,14 @@
You can ask for guidance anytime on our
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
## Code Contribution
## 1) Setup your development environment
### 1) Setup your development environment
- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system.
- For better DX, install [Spin](https://serversideup.net/open-source/spin/).
## 2) Set your environment variables
### 2) Set your environment variables
- Copy [.env.development.example](./.env.development.example) to .env.
@@ -23,7 +24,14 @@ You can ask for guidance anytime on our
- Run `./scripts/run setup:dev` - This will generate a secret key for you, delete any existing database layouts, migrate database to the new layout, and seed your database.
## 4) Start development
### 4) Start development
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
Mails are caught by Mailpit: `localhost:8025`
## New Service Contribution
Check out the docs [here](https://coolify.io/docs/how-to-add-a-service).

View File

@@ -4,7 +4,7 @@ Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Verc
It helps you to manage your servers, applications, databases on your own hardware, all you need is SSH connection. You can manage VPS, Bare Metal, Raspberry PI's anything.
Image if you could have the ease of a cloud but with your own servers. That is **Coolify**.
Imagine if you could have the ease of a cloud but with your own servers. That is **Coolify**.
No vendor lock-in, which means that all the configuration for your applications/databases/etc are saved to your server. So if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You just lose the automations and all the magic. 🪄️
@@ -40,6 +40,7 @@ Contact us [here](https://coolify.io/docs/contact).
## Recognitions
<p>
<a href="https://news.ycombinator.com/item?id=26624341">
<img
style="width: 250px; height: 54px;" width="250" height="54"
@@ -47,9 +48,12 @@ Contact us [here](https://coolify.io/docs/contact).
src="https://hackernews-badge.vercel.app/api?id=26624341"
/>
</a>
</p>
<a href="https://www.producthunt.com/posts/coolify?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An&#0032;open&#0045;source&#0032;&#0038;&#0032;self&#0045;hostable&#0032;Heroku&#0044;&#0032;Netlify&#0032;alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
## 💰 Financial Contributors
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)]

View File

@@ -2,6 +2,7 @@
namespace App\Actions\Database;
use App\Models\StandaloneMongodb;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -11,13 +12,15 @@ class StartDatabaseProxy
{
use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql $database)
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb $database)
{
$internalPort = null;
if ($database->getMorphClass()=== 'App\Models\StandaloneRedis') {
if ($database->getMorphClass() === 'App\Models\StandaloneRedis') {
$internalPort = 6379;
} else if ($database->getMorphClass()=== 'App\Models\StandalonePostgresql') {
} else if ($database->getMorphClass() === 'App\Models\StandalonePostgresql') {
$internalPort = 5432;
} else if ($database->getMorphClass() === 'App\Models\StandaloneMongodb') {
$internalPort = 27017;
}
$containerName = "{$database->uuid}-proxy";
$configuration_dir = database_proxy_dir($database->uuid);
@@ -87,7 +90,7 @@ class StartDatabaseProxy
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
"docker compose --project-directory {$configuration_dir} up --build -d >/dev/null",
"docker compose --project-directory {$configuration_dir} up --build -d",
], $database->destination->server);
}
}

View File

@@ -0,0 +1,163 @@
<?php
namespace App\Actions\Database;
use App\Models\StandaloneMongodb;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
class StartMongodb
{
use AsAction;
public StandaloneMongodb $database;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneMongodb $database)
{
$this->database = $database;
$startCommand = "mongod";
$container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [
"echo '####### Starting {$database->name}.'",
"mkdir -p $this->configuration_dir",
];
$persistent_storages = $this->generate_local_persistent_volumes();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->add_custom_mongo_conf();
$docker_compose = [
'version' => '3.8',
'services' => [
$container_name => [
'image' => $this->database->image,
'command' => $startCommand,
'container_name' => $container_name,
'environment' => $environment_variables,
'restart' => RESTART_MODE,
'networks' => [
$this->database->destination->network,
],
'labels' => [
'coolify.managed' => 'true',
],
'healthcheck' => [
'test' => [
'CMD-SHELL',
'mongo --eval "printjson(db.serverStatus())" | grep uptime | grep -v grep'
],
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s'
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
'networks' => [
$this->database->destination->network => [
'external' => true,
'name' => $this->database->destination->network,
'attachable' => true,
]
]
];
if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
}
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
if (!is_null($this->database->mongo_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind',
'source' => $this->configuration_dir . '/mongod.conf',
'target' => '/etc/mongo/mongod.conf',
'read_only' => true,
];
$docker_compose['services'][$container_name]['command'] = $startCommand . ' --config /etc/mongo/mongod.conf';
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '####### {$database->name} started.'";
return remote_process($this->commands, $database->destination->server);
}
private function generate_local_persistent_volumes()
{
$local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
}
return $local_persistent_volumes;
}
private function generate_local_persistent_volumes_only_volume_names()
{
$local_persistent_volumes_names = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path) {
continue;
}
$name = $persistentStorage->name;
$local_persistent_volumes_names[$name] = [
'name' => $name,
'external' => false,
];
}
return $local_persistent_volumes_names;
}
private function generate_environment_variables()
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
$environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
$environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
}
return $environment_variables->all();
}
private function add_custom_mongo_conf()
{
if (is_null($this->database->mongo_conf)) {
return;
}
$filename = 'mongod.conf';
$content = $this->database->mongo_conf;
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
}
}

View File

@@ -2,7 +2,6 @@
namespace App\Actions\Database;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
@@ -17,7 +16,7 @@ class StartPostgresql
public array $init_scripts = [];
public string $configuration_dir;
public function handle(Server $server, StandalonePostgresql $database)
public function handle(StandalonePostgresql $database)
{
$this->database = $database;
$container_name = $this->database->uuid;
@@ -104,7 +103,7 @@ class StartPostgresql
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '####### {$database->name} started.'";
return remote_process($this->commands, $server);
return remote_process($this->commands, $database->destination->server);
}
private function generate_local_persistent_volumes()
@@ -145,6 +144,9 @@ class StartPostgresql
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PGUSER'))->isEmpty()) {
$environment_variables->push("PGUSER={$this->database->postgres_user}");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");

View File

@@ -2,7 +2,6 @@
namespace App\Actions\Database;
use App\Models\Server;
use App\Models\StandaloneRedis;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
@@ -17,7 +16,7 @@ class StartRedis
public string $configuration_dir;
public function handle(Server $server, StandaloneRedis $database)
public function handle(StandaloneRedis $database)
{
$this->database = $database;
@@ -104,7 +103,7 @@ class StartRedis
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '####### {$database->name} started.'";
return remote_process($this->commands, $server);
return remote_process($this->commands, $database->destination->server);
}
private function generate_local_persistent_volumes()

View File

@@ -2,16 +2,16 @@
namespace App\Actions\Database;
use App\Models\StandaloneMongodb;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use App\Notifications\Application\StatusChanged;
use Lorisleiva\Actions\Concerns\AsAction;
class StopDatabase
{
use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql $database)
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb $database)
{
$server = $database->destination->server;
instant_remote_process(

View File

@@ -2,6 +2,7 @@
namespace App\Actions\Database;
use App\Models\StandaloneMongodb;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -10,7 +11,7 @@ class StopDatabaseProxy
{
use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql $database)
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb $database)
{
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server);
$database->is_public = false;

View File

@@ -2,41 +2,50 @@
namespace App\Actions\Proxy;
use App\Enums\ProxyTypes;
use App\Models\Server;
use Illuminate\Support\Str;
use Lorisleiva\Actions\Concerns\AsAction;
use Spatie\Activitylog\Models\Activity;
class CheckProxy
{
use AsAction;
public function handle(Server $server)
public function handle(Server $server, $fromUI = false)
{
if (!$server->isProxyShouldRun()) {
throw new \Exception("Proxy should not run");
if ($fromUI) {
throw new \Exception("Proxy should not run. You selected the Custom Proxy.");
} else {
return false;
}
}
$status = getContainerStatus($server, 'coolify-proxy');
if ($status === 'running') {
$server->proxy->set('status', 'running');
$server->save();
return 'OK';
return false;
}
$ip = $server->ip;
if ($server->id === 0) {
$ip = 'host.docker.internal';
}
$connection = @fsockopen($ip, '80');
$connection = @fsockopen($ip, '443');
$port80 = is_resource($connection) && fclose($connection);
$port443 = is_resource($connection) && fclose($connection);
ray($ip);
$connection80 = @fsockopen($ip, '80');
$connection443 = @fsockopen($ip, '443');
$port80 = is_resource($connection80) && fclose($connection80);
$port443 = is_resource($connection443) && fclose($connection443);
if ($port80) {
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
if ($fromUI) {
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
} else {
return false;
}
}
if ($port443) {
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>>");
if ($fromUI) {
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
} else {
return false;
}
}
return true;
}
}

View File

@@ -13,8 +13,6 @@ class StartProxy
public function handle(Server $server, bool $async = true): string|Activity
{
try {
CheckProxy::run($server);
$proxyType = $server->proxyType();
$commands = collect([]);
$proxy_path = get_proxy_path();

View File

@@ -0,0 +1,96 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Yaml\Yaml;
class GenerateServiceTemplates extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'services:generate';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generate service-templates.yaml based on /templates/compose directory';
/**
* Execute the console command.
*/
public function handle()
{
ray()->clearAll();
$files = array_diff(scandir(base_path('templates/compose')), ['.', '..']);
$files = array_filter($files, function ($file) {
return strpos($file, '.yaml') !== false;
});
$serviceTemplatesJson = [];
foreach ($files as $file) {
$parsed = $this->process_file($file);
if ($parsed) {
$name = data_get($parsed, 'name');
$parsed = data_forget($parsed, 'name');
$serviceTemplatesJson[$name] = $parsed;
}
}
$serviceTemplatesJson = json_encode($serviceTemplatesJson, JSON_PRETTY_PRINT);
file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson);
}
private function process_file($file)
{
$serviceName = str($file)->before('.yaml')->value();
$content = file_get_contents(base_path("templates/compose/$file"));
// $this->info($content);
$ignore = collect(preg_grep('/^# ignore:/', explode("\n", $content)))->values();
if ($ignore->count() > 0) {
$ignore = (bool)str($ignore[0])->after('# ignore:')->trim()->value();
} else {
$ignore = false;
}
if ($ignore) {
$this->info("Ignoring $file");
return;
}
$this->info("Processing $file");
$documentation = collect(preg_grep('/^# documentation:/', explode("\n", $content)))->values();
if ($documentation->count() > 0) {
$documentation = str($documentation[0])->after('# documentation:')->trim()->value();
} else {
$documentation = 'https://coolify.io/docs';
}
$slogan = collect(preg_grep('/^# slogan:/', explode("\n", $content)))->values();
if ($slogan->count() > 0) {
$slogan = str($slogan[0])->after('# slogan:')->trim()->value();
} else {
$slogan = str($file)->headline()->value();
}
$env_file = collect(preg_grep('/^# env_file:/', explode("\n", $content)))->values();
if ($env_file->count() > 0) {
$env_file = str($env_file[0])->after('# env_file:')->trim()->value();
} else {
$env_file = null;
}
$json = Yaml::parse($content);
$yaml = base64_encode(Yaml::dump($json, 10, 2));
$payload = [
'name' => $serviceName,
'documentation' => $documentation,
'slogan' => $slogan,
'compose' => $yaml,
];
if ($env_file) {
$payload['envs'] = $env_file;
}
return $payload;
}
}

View File

@@ -19,15 +19,12 @@ class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule): void
{
if (isDev()) {
// $schedule->job(new ContainerStatusJob(Server::find(0)))->everyTenMinutes()->onOneServer();
// $schedule->command('horizon:snapshot')->everyMinute();
$schedule->command('horizon:snapshot')->everyMinute();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly();
// $schedule->job(new DockerCleanupJob)->everyOddHour();
// $this->instance_auto_update($schedule);
// $this->check_scheduled_backups($schedule);
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->cleanup_servers($schedule);
$this->check_scheduled_backups($schedule);
} else {
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
@@ -68,7 +65,6 @@ class Kernel extends ConsoleKernel
}
private function check_scheduled_backups($schedule)
{
ray('check_scheduled_backups');
$scheduled_backups = ScheduledDatabaseBackup::all();
if ($scheduled_backups->isEmpty()) {
ray('no scheduled backups');

View File

@@ -63,6 +63,8 @@ class ProjectController extends Controller
$database = create_standalone_postgresql($environment->id, $destination_uuid);
} else if ($type->value() === 'redis') {
$database = create_standalone_redis($environment->id, $destination_uuid);
} else if ($type->value() === 'mongodb') {
$database = create_standalone_mongodb($environment->id, $destination_uuid);
}
return redirect()->route('project.database.configuration', [
'project_uuid' => $project->uuid,

View File

@@ -3,12 +3,9 @@
namespace App\Http\Livewire\Project\Application;
use App\Models\Application;
use App\Models\InstanceSettings;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Livewire\Component;
use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
class General extends Component
{
@@ -22,6 +19,11 @@ class General extends Component
public string $git_branch;
public ?string $git_commit_sha = null;
public string $build_pack;
public ?string $ports_exposes = null;
public $customLabels;
public bool $labelsChanged = false;
public bool $isConfigurationChanged = false;
public bool $is_static;
public bool $is_git_submodules_enabled;
@@ -52,6 +54,7 @@ class General extends Component
'application.docker_registry_image_name' => 'nullable',
'application.docker_registry_image_tag' => 'nullable',
'application.dockerfile_location' => 'nullable',
'application.custom_labels' => 'nullable',
];
protected $validationAttributes = [
'application.name' => 'name',
@@ -73,19 +76,52 @@ class General extends Component
'application.docker_registry_image_name' => 'Docker registry image name',
'application.docker_registry_image_tag' => 'Docker registry image tag',
'application.dockerfile_location' => 'Dockerfile location',
'application.custom_labels' => 'Custom labels',
];
public function updatedApplicationBuildPack(){
public function mount()
{
$this->ports_exposes = $this->application->ports_exposes;
if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) {
$this->application->isConfigurationChanged(true);
}
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
if (is_null(data_get($this->application, 'custom_labels'))) {
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
} else {
$this->customLabels = str($this->application->custom_labels)->replace(',', "\n");
}
if (data_get($this->application, 'settings')) {
$this->is_static = $this->application->settings->is_static;
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
}
$this->checkLabelUpdates();
}
public function updatedApplicationBuildPack()
{
if ($this->application->build_pack !== 'nixpacks') {
$this->application->settings->is_static = $this->is_static = false;
$this->application->settings->save();
}
$this->submit();
}
public function checkLabelUpdates()
{
if (md5($this->application->custom_labels) !== md5(implode(",", generateLabelsApplication($this->application)))) {
$this->labelsChanged = true;
} else {
$this->labelsChanged = false;
}
}
public function instantSave()
{
// @TODO: find another way - if possible
$force_https = $this->application->settings->is_force_https_enabled;
$this->application->settings->is_static = $this->is_static;
if ($this->is_static) {
$this->application->ports_exposes = 80;
@@ -102,37 +138,43 @@ class General extends Component
$this->application->save();
$this->application->refresh();
$this->emit('success', 'Application settings updated!');
$this->checkLabelUpdates();
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
if ($force_https !== $this->is_force_https_enabled) {
$this->resetDefaultLabels(false);
}
}
public function getWildcardDomain() {
public function getWildcardDomain()
{
$server = data_get($this->application, 'destination.server');
if ($server) {
$fqdn = generateFqdn($server, $this->application->uuid);
ray($fqdn);
$this->application->fqdn = $fqdn;
$this->application->save();
$this->emit('success', 'Application settings updated!');
}
}
public function mount()
public function resetDefaultLabels($showToaster = true)
{
if (data_get($this->application,'settings')) {
$this->is_static = $this->application->settings->is_static;
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
}
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
$this->ports_exposes = $this->application->ports_exposes;
$this->submit($showToaster);
}
public function submit()
public function updatedApplicationFqdn()
{
$this->resetDefaultLabels(false);
$this->emit('success', 'Labels reseted to default!');
}
public function submit($showToaster = true)
{
try {
$this->validate();
if (data_get($this->application,'build_pack') === 'dockerimage') {
if ($this->ports_exposes !== $this->application->ports_exposes) {
$this->resetDefaultLabels(false);
}
if (data_get($this->application, 'build_pack') === 'dockerimage') {
$this->validate([
'application.docker_registry_image_name' => 'required',
'application.docker_registry_image_tag' => 'required',
@@ -156,10 +198,17 @@ class General extends Component
if ($this->application->publish_directory && $this->application->publish_directory !== '/') {
$this->application->publish_directory = rtrim($this->application->publish_directory, '/');
}
if (gettype($this->customLabels) === 'string') {
$this->customLabels = str($this->customLabels)->replace(',', "\n");
}
$this->application->custom_labels = $this->customLabels->explode("\n")->implode(',');
$this->application->save();
$this->emit('success', 'Application settings updated!');
$showToaster && $this->emit('success', 'Application settings updated!');
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->checkLabelUpdates();
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
}
}
}

View File

@@ -0,0 +1,143 @@
<?php
namespace App\Http\Livewire\Project;
use App\Models\Environment;
use App\Models\Project;
use App\Models\Server;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class CloneProject extends Component
{
public string $project_uuid;
public string $environment_name;
public int $project_id;
public Project $project;
public $environments;
public $servers;
public ?Environment $environment = null;
public ?int $selectedServer = null;
public ?Server $server = null;
public $resources = [];
public string $newProjectName = '';
protected $messages = [
'selectedServer' => 'Please select a server.',
'newProjectName' => 'Please enter a name for the new project.',
];
public function mount($project_uuid)
{
$this->project_uuid = $project_uuid;
$this->project = Project::where('uuid', $project_uuid)->firstOrFail();
$this->environment = $this->project->environments->where('name', $this->environment_name)->first();
$this->project_id = $this->project->id;
$this->servers = currentTeam()->servers;
$this->newProjectName = $this->project->name . ' (clone)';
}
public function render()
{
return view('livewire.project.clone-project');
}
public function selectServer($server_id)
{
$this->selectedServer = $server_id;
$this->server = $this->servers->where('id', $server_id)->first();
}
public function clone()
{
try {
$this->validate([
'selectedServer' => 'required',
'newProjectName' => 'required',
]);
$newProject = Project::create([
'name' => $this->newProjectName,
'team_id' => currentTeam()->id,
'description' => $this->project->description . ' (clone)',
]);
if ($this->environment->id !== 1) {
$newProject->environments()->create([
'name' => $this->environment->name,
]);
$newProject->environments()->find(1)->delete();
}
$newEnvironment = $newProject->environments->first();
// Clone Applications
$applications = $this->environment->applications;
$databases = $this->environment->databases();
$services = $this->environment->services;
foreach ($applications as $application) {
$uuid = (string)new Cuid2(7);
$newApplication = $application->replicate()->fill([
'uuid' => $uuid,
'fqdn' => generateFqdn($this->server, $uuid),
'status' => 'exited',
'environment_id' => $newEnvironment->id,
'destination_id' => $this->selectedServer,
]);
$newApplication->environment_id = $newProject->environments->first()->id;
$newApplication->save();
$environmentVaribles = $application->environment_variables()->get();
foreach ($environmentVaribles as $environmentVarible) {
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
'application_id' => $newApplication->id,
]);
$newEnvironmentVariable->save();
}
$persistentVolumes = $application->persistentStorages()->get();
foreach ($persistentVolumes as $volume) {
$newPersistentVolume = $volume->replicate()->fill([
'name' => $newApplication->uuid . '-' . str($volume->name)->afterLast('-'),
'resource_id' => $newApplication->id,
]);
$newPersistentVolume->save();
}
}
foreach ($databases as $database) {
$uuid = (string)new Cuid2(7);
$newDatabase = $database->replicate()->fill([
'uuid' => $uuid,
'environment_id' => $newEnvironment->id,
'destination_id' => $this->selectedServer,
]);
$newDatabase->environment_id = $newProject->environments->first()->id;
$newDatabase->save();
$environmentVaribles = $database->environment_variables()->get();
foreach ($environmentVaribles as $environmentVarible) {
$payload = [];
if ($database->type() === 'standalone-postgres') {
$payload['standalone_postgresql_id'] = $newDatabase->id;
} else if ($database->type() === 'standalone_redis') {
$payload['standalone_redis_id'] = $newDatabase->id;
} else if ($database->type() === 'standalone_mongodb') {
$payload['standalone_mongodb_id'] = $newDatabase->id;
}
$newEnvironmentVariable = $environmentVarible->replicate()->fill($payload);
$newEnvironmentVariable->save();
}
}
foreach ($services as $service) {
$uuid = (string)new Cuid2(7);
$newService = $service->replicate()->fill([
'uuid' => $uuid,
'environment_id' => $newEnvironment->id,
'destination_id' => $this->selectedServer,
]);
$newService->environment_id = $newProject->environments->first()->id;
$newService->save();
$newService->parse();
}
return redirect()->route('project.resources', [
'project_uuid' => $newProject->uuid,
'environment_name' => $newEnvironment->name,
]);
} catch (\Exception $e) {
return handleError($e, $this);
}
}
}

View File

@@ -22,6 +22,11 @@ class CreateScheduledBackup extends Component
'frequency' => 'Backup Frequency',
'save_s3' => 'Save to S3',
];
public function mount() {
if ($this->s3s->count() > 0) {
$this->s3_storage_id = $this->s3s->first()->id;
}
}
public function submit(): void
{

View File

@@ -2,6 +2,7 @@
namespace App\Http\Livewire\Project\Database;
use App\Actions\Database\StartMongodb;
use App\Actions\Database\StartPostgresql;
use App\Actions\Database\StartRedis;
use App\Actions\Database\StopDatabase;
@@ -46,11 +47,15 @@ class Heading extends Component
public function start()
{
if ($this->database->type() === 'standalone-postgresql') {
$activity = StartPostgresql::run($this->database->destination->server, $this->database);
$activity = StartPostgresql::run($this->database);
$this->emit('newMonitorActivity', $activity->id);
}
if ($this->database->type() === 'standalone-redis') {
$activity = StartRedis::run($this->database->destination->server, $this->database);
$activity = StartRedis::run($this->database);
$this->emit('newMonitorActivity', $activity->id);
}
if ($this->database->type() === 'standalone-mongodb') {
$activity = StartMongodb::run($this->database);
$this->emit('newMonitorActivity', $activity->id);
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace App\Http\Livewire\Project\Database\Mongodb;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\StandaloneMongodb;
use Exception;
use Livewire\Component;
class General extends Component
{
protected $listeners = ['refresh'];
public StandaloneMongodb $database;
public string $db_url;
protected $rules = [
'database.name' => 'required',
'database.description' => 'nullable',
'database.mongo_conf' => 'nullable',
'database.mongo_initdb_root_username' => 'required',
'database.mongo_initdb_root_password' => 'required',
'database.mongo_initdb_database' => 'required',
'database.image' => 'required',
'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer',
];
protected $validationAttributes = [
'database.name' => 'Name',
'database.description' => 'Description',
'database.mongo_conf' => 'Mongo Configuration',
'database.mongo_initdb_root_username' => 'Root Username',
'database.mongo_initdb_root_password' => 'Root Password',
'database.mongo_initdb_database' => 'Database',
'database.image' => 'Image',
'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port',
];
public function submit() {
try {
$this->validate();
if ($this->database->mongo_conf === "") {
$this->database->mongo_conf = null;
}
$this->database->save();
$this->emit('success', 'Database updated successfully.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function instantSave()
{
try {
if ($this->database->is_public && !$this->database->public_port) {
$this->emit('error', 'Public port is required.');
$this->database->is_public = false;
return;
}
if ($this->database->is_public) {
$this->emit('success', 'Starting TCP proxy...');
StartDatabaseProxy::run($this->database);
$this->emit('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
$this->emit('success', 'Database is no longer publicly accessible.');
}
$this->db_url = $this->database->getDbUrl();
$this->database->save();
} catch(\Throwable $e) {
$this->database->is_public = !$this->database->is_public;
return handleError($e, $this);
}
}
public function refresh(): void
{
$this->database->refresh();
}
public function mount()
{
$this->db_url = $this->database->getDbUrl();
}
public function render()
{
return view('livewire.project.database.mongodb.general');
}
}

View File

@@ -49,15 +49,7 @@ class General extends Component
];
public function mount()
{
$this->getDbUrl();
}
public function getDbUrl() {
if ($this->database->is_public) {
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->destination->server->getIp}:{$this->database->public_port}/{$this->database->postgres_db}";
} else {
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->uuid}:5432/{$this->database->postgres_db}";
}
$this->db_url = $this->database->getDbUrl();
}
public function instantSave()
{
@@ -75,7 +67,7 @@ class General extends Component
StopDatabaseProxy::run($this->database);
$this->emit('success', 'Database is no longer publicly accessible.');
}
$this->getDbUrl();
$this->db_url = $this->database->getDbUrl();
$this->database->save();
} catch(\Throwable $e) {
$this->database->is_public = !$this->database->is_public;

View File

@@ -63,7 +63,7 @@ class General extends Component
StopDatabaseProxy::run($this->database);
$this->emit('success', 'Database is no longer publicly accessible.');
}
$this->getDbUrl();
$this->db_url = $this->database->getDbUrl();
$this->database->save();
} catch(\Throwable $e) {
$this->database->is_public = !$this->database->is_public;
@@ -77,15 +77,7 @@ class General extends Component
public function mount()
{
$this->getDbUrl();
}
public function getDbUrl() {
if ($this->database->is_public) {
$this->db_url = "redis://:{$this->database->redis_password}@{$this->database->destination->server->getIp}:{$this->database->public_port}/0";
} else {
$this->db_url = "redis://:{$this->database->redis_password}@{$this->database->uuid}:6379/0";
}
$this->db_url = $this->database->getDbUrl();
}
public function render()
{

View File

@@ -78,6 +78,9 @@ class All extends Component
case 'standalone-redis':
$environment->standalone_redis_id = $this->resource->id;
break;
case 'standalone-mongodb':
$environment->standalone_mongodb_id = $this->resource->id;
break;
case 'service':
$environment->service_id = $this->resource->id;
break;

View File

@@ -13,6 +13,7 @@ class GetLogs extends Component
public Server $server;
public ?string $container = null;
public ?bool $streamLogs = false;
public ?bool $showTimeStamps = true;
public int $numberOfLines = 100;
public function doSomethingWithThisChunkOfOutput($output)
{
@@ -24,7 +25,11 @@ class GetLogs extends Component
public function getLogs($refresh = false)
{
if ($this->container) {
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");
if ($this->showTimeStamps) {
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");
} else {
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} {$this->container}");
}
if ($refresh) {
$this->outputs = '';
}

View File

@@ -5,6 +5,7 @@ namespace App\Http\Livewire\Project\Shared;
use App\Models\Application;
use App\Models\Server;
use App\Models\Service;
use App\Models\StandaloneMongodb;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Livewire\Component;
@@ -12,7 +13,7 @@ use Livewire\Component;
class Logs extends Component
{
public ?string $type = null;
public Application|StandalonePostgresql|Service|StandaloneRedis $resource;
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb $resource;
public Server $server;
public ?string $container = null;
public $parameters;
@@ -38,9 +39,13 @@ class Logs extends Component
if (is_null($resource)) {
$resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
abort(404);
$resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
abort(404);
}
}
}
$this->resource = $resource;
$this->status = $this->resource->status;
$this->server = $this->resource->destination->server;

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Http\Livewire\Security;
use Livewire\Component;
class ApiTokens extends Component
{
public ?string $description = null;
public $tokens = [];
public function render()
{
return view('livewire.security.api-tokens');
}
public function mount()
{
$this->tokens = auth()->user()->tokens;
}
public function addNewToken()
{
try {
$this->validate([
'description' => 'required|min:3|max:255',
]);
$token = auth()->user()->createToken($this->description);
$this->tokens = auth()->user()->tokens;
session()->flash('token', $token->plainTextToken);
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function revoke(int $id)
{
$token = auth()->user()->tokens()->where('id', $id)->first();
$token->delete();
$this->tokens = auth()->user()->tokens;
}
}

View File

@@ -39,7 +39,7 @@ class Deploy extends Component
public function checkProxy()
{
try {
CheckProxy::run($this->server);
CheckProxy::run($this->server, true);
$this->emit('startProxyPolling');
$this->emit('proxyChecked');
} catch (\Throwable $e) {

View File

@@ -34,7 +34,7 @@ class Status extends Component
}
$this->numberOfPolls++;
}
CheckProxy::run($this->server);
CheckProxy::run($this->server, true);
$this->emit('proxyStatusUpdated');
if ($this->server->proxy->status === 'running') {
$this->polling = false;

View File

@@ -54,7 +54,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private ApplicationPreview|null $preview = null;
private string $container_name;
private string|null $currently_running_container_name = null;
private ?string $currently_running_container_name = null;
private string $basedir;
private string $workdir;
private ?string $build_pack = null;
@@ -71,10 +71,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private $log_model;
private Collection $saved_outputs;
private string $serverUser = 'root';
private string $serverUserHomeDir = '/root';
private string $dockerConfigFileExists = 'NOK';
private int $customPort = 22;
public $tries = 1;
public function __construct(int $application_deployment_queue_id)
{
ray()->clearScreen();
// ray()->clearScreen();
$this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id);
$this->log_model = $this->application_deployment_queue;
$this->application = Application::find($this->application_deployment_queue->application_id);
@@ -92,7 +98,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
$this->destination = $this->application->destination->getMorphClass()::where('id', $this->application->destination->id)->first();
$this->server = $this->destination->server;
$this->serverUser = $this->server->user;
$this->basedir = "/artifacts/{$this->deployment_uuid}";
$this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/');
$this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}";
@@ -160,6 +166,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
return "--add-host $name:$ip";
})->implode(' ');
// Get user home directory
$this->serverUserHomeDir = instant_remote_process(["echo \$HOME"], $this->server);
$this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server);
// Check custom port
preg_match('/(?<=:)\d+(?=\/)/', $this->application->git_repository, $matches);
if (count($matches) === 1) {
$this->customPort = $matches[0];
$gitHost = str($this->application->git_repository)->before(':');
$gitRepo = str($this->application->git_repository)->after('/');
$this->application->git_repository = "$gitHost:$gitRepo";
}
try {
if ($this->application->dockerfile) {
$this->deploy_simple_dockerfile();
@@ -178,6 +196,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
dispatch(new ContainerStatusJob($this->server));
}
$this->next(ApplicationDeploymentStatus::FINISHED->value);
$this->application->isConfigurationChanged(true);
} catch (Exception $e) {
ray($e);
$this->fail($e);
@@ -346,14 +365,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->execute_remote_command([
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
]);
if (Str::of($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
if (Str::of($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
$this->execute_remote_command([
"echo 'Docker Image found locally with the same Git Commit SHA {$this->application->uuid}:{$this->commit}. Build step skipped...'"
"echo 'No configuration changed & Docker Image found locally with the same Git Commit SHA {$this->application->uuid}:{$this->commit}. Build step skipped.'",
]);
$this->generate_compose_file();
$this->rolling_update();
return;
}
if ($this->application->isConfigurationChanged()) {
$this->execute_remote_command([
"echo 'Configuration changed. Rebuilding image.'",
]);
}
}
$this->cleanup_git();
$this->generate_nixpacks_confs();
@@ -450,7 +474,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->stop_running_container();
$this->execute_remote_command(
["echo -n 'Starting preview deployment.'"],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
);
}
@@ -458,7 +482,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
{
$pull = "--pull=always";
$helperImage = config('coolify.helper_image');
$runCommand = "docker run {$pull} -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
if ($this->dockerConfigFileExists === 'OK') {
$runCommand = "docker run {$pull} -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
} else {
$runCommand = "docker run {$pull} -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
}
$this->execute_remote_command(
[
@@ -532,13 +560,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
}
if ($this->application->deploymentType() === 'deploy_key') {
$port = 22;
preg_match('/(?<=:)\d+(?=\/)/', $this->application->git_repository, $matches);
if (count($matches) === 1) {
$port = $matches[0];
}
$private_key = base64_encode($this->application->private_key->private_key);
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -p $port -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_repository} {$this->basedir}";
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_repository} {$this->basedir}";
$git_clone_command = $this->set_git_import_settings($git_clone_command);
$commands = collect([
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"),
@@ -638,6 +661,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables($ports);
$labels = generateLabelsApplication($this->application, $this->preview);
if (data_get($this->application, 'custom_labels')) {
$labels = str($this->application->custom_labels)->explode(',')->toArray();
}
$docker_compose = [
'version' => '3.8',
'services' => [
@@ -646,7 +673,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
'container_name' => $this->container_name,
'restart' => RESTART_MODE,
'environment' => $environment_variables,
'labels' => generateLabelsApplication($this->application, $this->preview, $ports),
'labels' => $labels,
'expose' => $ports,
'networks' => [
$this->destination->network,
@@ -825,7 +852,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
]
);
} else {
ray("docker build $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}");
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
]);
@@ -853,7 +879,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
{
$this->execute_remote_command(
["echo -n 'Starting application (could take a while).'"],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d >/dev/null"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
);
}

View File

@@ -2,6 +2,7 @@
namespace App\Jobs;
use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
use App\Models\ApplicationPreview;
use App\Models\Server;
@@ -11,7 +12,6 @@ use App\Notifications\Server\Revived;
use App\Notifications\Server\Unreachable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
@@ -24,30 +24,23 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 1;
public $timeout = 120;
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
}
public function uniqueId(): string
{
return $this->server->uuid;
}
public function __construct(public Server $server)
{
if (isDev()) {
$this->handle();
}
}
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
}
public function handle()
public function uniqueId(): int
{
return $this->server->id;
}
public function handle(): void
{
ray("checking server status for {$this->server->id}");
try {
// ray("checking server status for {$this->server->id}");
// ray()->clearAll();
$serverUptimeCheckNumber = $this->server->unreachable_count;
$serverUptimeCheckNumberMax = 3;
@@ -117,9 +110,16 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
return data_get($value, 'Name') === '/coolify-proxy';
})->first();
if (!$foundProxyContainer) {
if ($this->server->isProxyShouldRun()) {
StartProxy::run($this->server, false);
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
try {
$shouldStart = CheckProxy::run($this->server);
if ($shouldStart) {
StartProxy::run($this->server, false);
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
} else {
ray('Proxy could not be started.');
}
} catch (\Throwable $e) {
ray($e);
}
} else {
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
@@ -299,7 +299,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
} catch (\Throwable $e) {
send_internal_notification('ContainerStatusJob failed with: ' . $e->getMessage());
ray($e->getMessage());
return handleError($e);
handleError($e);
}
}
}

View File

@@ -6,6 +6,7 @@ use App\Models\S3Storage;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledDatabaseBackupExecution;
use App\Models\Server;
use App\Models\StandaloneMongodb;
use App\Models\StandalonePostgresql;
use App\Models\Team;
use App\Notifications\Database\BackupFailed;
@@ -27,7 +28,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
public ?Team $team = null;
public Server $server;
public ScheduledDatabaseBackup $backup;
public StandalonePostgresql $database;
public StandalonePostgresql|StandaloneMongodb $database;
public ?string $container_name = null;
public ?ScheduledDatabaseBackupExecution $backup_log = null;
@@ -72,12 +73,24 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
if (is_null($databasesToBackup)) {
if ($databaseType === 'standalone-postgresql') {
$databasesToBackup = [$this->database->postgres_db];
} else if ($databaseType === 'standalone-mongodb') {
$databasesToBackup = ['*'];
} else {
return;
}
} else {
$databasesToBackup = explode(',', $databasesToBackup);
$databasesToBackup = array_map('trim', $databasesToBackup);
if ($databaseType === 'standalone-postgresql') {
// Format: db1,db2,db3
$databasesToBackup = explode(',', $databasesToBackup);
$databasesToBackup = array_map('trim', $databasesToBackup);
} else if ($databaseType === 'standalone-mongodb') {
// Format: db1:collection1,collection2|db2:collection3,collection4
$databasesToBackup = explode('|', $databasesToBackup);
$databasesToBackup = array_map('trim', $databasesToBackup);
ray($databasesToBackup);
} else {
return;
}
}
$this->container_name = $this->database->uuid;
$this->backup_dir = backup_dir() . "/databases/" . Str::of($this->team->name)->slug() . '-' . $this->team->id . '/' . $this->container_name;
@@ -92,15 +105,37 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
$size = 0;
ray('Backing up ' . $database);
try {
$this->backup_file = "/pg-dump-$database-" . Carbon::now()->timestamp . ".dmp";
$this->backup_location = $this->backup_dir . $this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
'filename' => $this->backup_location,
'scheduled_database_backup_id' => $this->backup->id,
]);
if ($databaseType === 'standalone-postgresql') {
$this->backup_file = "/pg-dump-$database-" . Carbon::now()->timestamp . ".dmp";
$this->backup_location = $this->backup_dir . $this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
'filename' => $this->backup_location,
'scheduled_database_backup_id' => $this->backup->id,
]);
$this->backup_standalone_postgresql($database);
} else if ($databaseType === 'standalone-mongodb') {
if ($database === '*') {
$database = 'all';
$databaseName = 'all';
} else {
if (str($database)->contains(':')) {
$databaseName = str($database)->before(':');
} else {
$databaseName = $database;
}
ray($databaseName);
}
$this->backup_file = "/mongo-dump-$databaseName-" . Carbon::now()->timestamp . ".tar.gz";
$this->backup_location = $this->backup_dir . $this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $databaseName,
'filename' => $this->backup_location,
'scheduled_database_backup_id' => $this->backup->id,
]);
$this->backup_standalone_mongodb($database);
} else {
throw new \Exception('Unsupported database type');
}
$size = $this->calculate_size();
$this->remove_old_backups();
@@ -114,12 +149,14 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
'size' => $size,
]);
} catch (\Throwable $e) {
$this->backup_log->update([
'status' => 'failed',
'message' => $this->backup_output,
'size' => $size,
'filename' => null
]);
if ($this->backup_log) {
$this->backup_log->update([
'status' => 'failed',
'message' => $this->backup_output,
'size' => $size,
'filename' => null
]);
}
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
throw $e;
@@ -130,11 +167,36 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
throw $e;
}
}
private function backup_standalone_mongodb(string $databaseWithCollections): void
{
try {
$url = $this->database->getDbUrl();
if ($databaseWithCollections === 'all') {
$commands[] = "mkdir -p " . $this->backup_dir;
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";
} else {
$collectionsToExclude = str($databaseWithCollections)->after(':')->explode(',');
$databaseName = str($databaseWithCollections)->before(':');
$commands[] = "mkdir -p " . $this->backup_dir;
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection " . $collectionsToExclude->implode(' --excludeCollection ') . " --archive > $this->backup_location";
}
ray($commands);
$this->backup_output = instant_remote_process($commands, $this->server);
$this->backup_output = trim($this->backup_output);
if ($this->backup_output === '') {
$this->backup_output = null;
}
ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location);
} catch (\Throwable $e) {
$this->add_to_backup_output($e->getMessage());
ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage());
throw $e;
}
}
private function backup_standalone_postgresql(string $database): void
{
try {
ray($this->backup_dir);
$commands[] = "mkdir -p " . $this->backup_dir;
$commands[] = "docker exec $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
$this->backup_output = instant_remote_process($commands, $this->server);
@@ -189,11 +251,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
$bucket = $this->s3->bucket;
$endpoint = $this->s3->endpoint;
$this->s3->testConnection();
if (isDev()) {
$commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v coolify_coolify-data-dev:/data/coolify:ro ghcr.io/coollabsio/coolify-helper >/dev/null 2>&1";
} else {
$commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper >/dev/null 2>&1";
}
$commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper >/dev/null 2>&1";
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";

View File

@@ -7,6 +7,7 @@ use App\Actions\Database\StopDatabase;
use App\Actions\Service\StopService;
use App\Models\Application;
use App\Models\Service;
use App\Models\StandaloneMongodb;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Illuminate\Bus\Queueable;
@@ -20,7 +21,7 @@ class StopResourceJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis $resource)
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb $resource)
{
}
@@ -41,6 +42,9 @@ class StopResourceJob implements ShouldQueue, ShouldBeEncrypted
case 'standalone-redis':
StopDatabase::run($this->resource);
break;
case 'standalone-mongodb':
StopDatabase::run($this->resource);
break;
case 'service':
StopService::run($this->resource);
break;

View File

@@ -277,4 +277,31 @@ class Application extends BaseModel
}
return false;
}
public function isConfigurationChanged($save = false)
{
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->port_exposes . $this->port_mappings . $this->base_directory . $this->publish_directory . $this->health_check_path . $this->health_check_port . $this->health_check_host . $this->health_check_method . $this->health_check_return_code . $this->health_check_scheme . $this->health_check_response_text . $this->health_check_interval . $this->health_check_timeout . $this->health_check_retries . $this->health_check_start_period . $this->health_check_enabled . $this->limits_memory . $this->limits_swap . $this->limits_swappiness . $this->limits_reservation . $this->limits_cpus . $this->limits_cpuset . $this->limits_cpu_shares . $this->dockerfile . $this->dockerfile_location . $this->custom_labels;
if ($this->pull_request_id === 0) {
$newConfigHash .= json_encode($this->environment_variables->all());
} else {
$newConfigHash .= json_encode($this->environment_variables_preview->all());
}
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
if ($save) {
$this->config_hash = $newConfigHash;
$this->save();
}
return true;
}
if ($oldConfigHash === $newConfigHash) {
return false;
} else {
if ($save) {
$this->config_hash = $newConfigHash;
$this->save();
}
return true;
}
}
}

View File

@@ -14,7 +14,11 @@ class Environment extends Model
public function can_delete_environment()
{
return $this->applications()->count() == 0 && $this->redis()->count() == 0 && $this->postgresqls()->count() == 0 && $this->services()->count() == 0;
return $this->applications()->count() == 0 &&
$this->redis()->count() == 0 &&
$this->postgresqls()->count() == 0 &&
$this->mongodbs()->count() == 0 &&
$this->services()->count() == 0;
}
public function applications()
@@ -30,12 +34,17 @@ class Environment extends Model
{
return $this->hasMany(StandaloneRedis::class);
}
public function mongodbs()
{
return $this->hasMany(StandaloneMongodb::class);
}
public function databases()
{
$postgresqls = $this->postgresqls;
$redis = $this->redis;
return $postgresqls->concat($redis);
$mongodbs = $this->mongodbs;
return $postgresqls->concat($redis)->concat($mongodbs);
}
public function project()

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;
class PersonalAccessToken extends SanctumPersonalAccessToken
{
protected $fillable = [
'name',
'token',
'abilities',
'expires_at',
'team_id',
];
}

View File

@@ -124,7 +124,8 @@ class Server extends BaseModel
return $this->destinations()->map(function ($standaloneDocker) {
$postgresqls = $standaloneDocker->postgresqls;
$redis = $standaloneDocker->redis;
return $postgresqls->concat($redis);
$mongodbs = $standaloneDocker->mongodbs;
return $postgresqls->concat($redis)->concat($mongodbs);
})->flatten();
}
public function applications()
@@ -193,23 +194,24 @@ class Server extends BaseModel
}
public function isProxyShouldRun()
{
$shouldRun = false;
if ($this->proxyType() === ProxyTypes::NONE->value) {
return false;
}
foreach ($this->applications() as $application) {
if (data_get($application, 'fqdn')) {
$shouldRun = true;
break;
}
}
if ($this->id === 0) {
$settings = InstanceSettings::get();
if (data_get($settings, 'fqdn')) {
$shouldRun = true;
}
}
return $shouldRun;
// foreach ($this->applications() as $application) {
// if (data_get($application, 'fqdn')) {
// $shouldRun = true;
// break;
// }
// }
// ray($this->services()->get());
// if ($this->id === 0) {
// $settings = InstanceSettings::get();
// if (data_get($settings, 'fqdn')) {
// $shouldRun = true;
// }
// }
return true;
}
public function isFunctional()
{

View File

@@ -50,7 +50,7 @@ class Service extends BaseModel
public function documentation()
{
$services = Cache::get('services', []);
$services = getServiceTemplates();
$service = data_get($services, Str::of($this->name)->beforeLast('-')->value, []);
return data_get($service, 'documentation', config('constants.docs.base_url'));
}
@@ -538,7 +538,7 @@ class Service extends BaseModel
$serviceLabels = $serviceLabels->merge($defaultLabels);
if (!$isDatabase && $fqdns->count() > 0) {
if ($fqdns) {
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($fqdns, true));
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($this->uuid, $fqdns, true));
}
}
data_set($service, 'labels', $serviceLabels->toArray());
@@ -568,7 +568,7 @@ class Service extends BaseModel
'networks' => $topLevelNetworks->toArray(),
];
$this->docker_compose_raw = Yaml::dump($yaml, 10, 2);
$this->docker_compose = Yaml::dump($finalServices, 10, 2);
$this->docker_compose = Yaml::dump($finalServices, 10, 2);
$this->save();
$this->saveComposeConfigs();
return collect([]);

View File

@@ -15,10 +15,15 @@ class StandaloneDocker extends BaseModel
{
return $this->morphMany(StandalonePostgresql::class, 'destination');
}
public function redis()
{
return $this->morphMany(StandaloneRedis::class, 'destination');
}
public function mongodbs()
{
return $this->morphMany(StandaloneMongodb::class, 'destination');
}
public function server()
{

View File

@@ -0,0 +1,99 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
class StandaloneMongodb extends BaseModel
{
use HasFactory;
protected $guarded = [];
protected static function booted()
{
static::created(function ($database) {
LocalPersistentVolume::create([
'name' => 'mongodb-data-' . $database->uuid,
'mount_path' => '/data',
'host_path' => null,
'resource_id' => $database->id,
'resource_type' => $database->getMorphClass(),
'is_readonly' => true
]);
});
static::deleting(function ($database) {
$database->scheduledBackups()->delete();
$storages = $database->persistentStorages()->get();
foreach ($storages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $database->destination->server, false);
}
$database->persistentStorages()->delete();
$database->environment_variables()->delete();
});
}
public function portsMappings(): Attribute
{
return Attribute::make(
set: fn ($value) => $value === "" ? null : $value,
);
}
public function portsMappingsArray(): Attribute
{
return Attribute::make(
get: fn () => is_null($this->ports_mappings)
? []
: explode(',', $this->ports_mappings),
);
}
public function type(): string
{
return 'standalone-mongodb';
}
public function getDbUrl() {
if ($this->is_public) {
return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->destination->server->getIp}:{$this->public_port}/?directConnection=true";
} else {
return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->uuid}:27017/?directConnection=true";
}
}
public function environment()
{
return $this->belongsTo(Environment::class);
}
public function fileStorages()
{
return $this->morphMany(LocalFileVolume::class, 'resource');
}
public function destination()
{
return $this->morphTo();
}
public function environment_variables(): HasMany
{
return $this->hasMany(EnvironmentVariable::class);
}
public function runtime_environment_variables(): HasMany
{
return $this->hasMany(EnvironmentVariable::class);
}
public function persistentStorages()
{
return $this->morphMany(LocalPersistentVolume::class, 'resource');
}
public function scheduledBackups()
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
}

View File

@@ -62,6 +62,14 @@ class StandalonePostgresql extends BaseModel
{
return 'standalone-postgresql';
}
public function getDbUrl(): string
{
if ($this->is_public) {
return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}";
} else {
return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}";
}
}
public function environment()
{

View File

@@ -41,8 +41,6 @@ class StandaloneRedis extends BaseModel
);
}
// Normal Deployments
public function portsMappingsArray(): Attribute
{
return Attribute::make(
@@ -57,6 +55,13 @@ class StandaloneRedis extends BaseModel
{
return 'standalone-redis';
}
public function getDbUrl(): string {
if ($this->is_public) {
return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
} else {
return "redis://:{$this->redis_password}@{$this->uuid}:6379/0";
}
}
public function environment()
{

View File

@@ -4,6 +4,7 @@ namespace App\Models;
use App\Notifications\Channels\SendsEmail;
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Messages\MailMessage;
@@ -14,6 +15,8 @@ use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\URL;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Sanctum\HasApiTokens;
use Laravel\Sanctum\NewAccessToken;
use Illuminate\Support\Str;
class User extends Authenticatable implements SendsEmail
{
@@ -47,7 +50,26 @@ class User extends Authenticatable implements SendsEmail
$user->teams()->attach($new_team, ['role' => 'owner']);
});
}
public function createToken(string $name, array $abilities = ['*'], DateTimeInterface $expiresAt = null)
{
ray('asd');
$plainTextToken = sprintf(
'%s%s%s',
config('sanctum.token_prefix', ''),
$tokenEntropy = Str::random(40),
hash('crc32b', $tokenEntropy)
);
$token = $this->tokens()->create([
'name' => $name,
'token' => hash('sha256', $plainTextToken),
'abilities' => $abilities,
'expires_at' => $expiresAt,
'team_id' => session('currentTeam')->id
]);
return new NewAccessToken($token, $token->getKey().'|'.$plainTextToken);
}
public function teams()
{
return $this->belongsToMany(Team::class)->withPivot('role');

View File

@@ -30,7 +30,12 @@ class EmailChannel
);
} catch (Exception $e) {
ray($e->getMessage());
send_internal_notification("EmailChannel error: {$e->getMessage()}. Failed to send email to: " . implode(', ', $recepients) . " with subject: {$mailMessage->subject}");
$message = "EmailChannel error: {$e->getMessage()}. Failed to send email to:";
if (isset($recepients)) {
$message .= implode(', ', $recepients);
}
$message .= " with subject: {$mailMessage->subject}";
send_internal_notification($message);
throw $e;
}
}

View File

@@ -4,6 +4,8 @@ namespace App\Providers;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\ServiceProvider;
use Laravel\Sanctum\Sanctum;
use App\Models\PersonalAccessToken;
class AppServiceProvider extends ServiceProvider
{
@@ -13,6 +15,8 @@ class AppServiceProvider extends ServiceProvider
public function boot(): void
{
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
Http::macro('github', function (string $api_url, string|null $github_access_token = null) {
if ($github_access_token) {
return Http::withHeaders([

View File

@@ -46,9 +46,9 @@ class RouteServiceProvider extends ServiceProvider
{
RateLimiter::for('api', function (Request $request) {
if ($request->path() === 'api/health') {
return Limit::perMinute(5000)->by($request->user()?->id ?: $request->ip());
return Limit::perMinute(1000)->by($request->user()?->id ?: $request->ip());
}
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
return Limit::perMinute(30)->by($request->user()?->id ?: $request->ip());
});
RateLimiter::for('5', function (Request $request) {
return Limit::perMinute(5)->by($request->user()?->id ?: $request->ip());

View File

@@ -1,6 +1,6 @@
<?php
const DATABASE_TYPES = ['postgresql','redis'];
const DATABASE_TYPES = ['postgresql','redis', 'mongodb'];
const VALID_CRON_STRINGS = [
'every_minute' => '* * * * *',
'hourly' => '0 * * * *',

View File

@@ -2,6 +2,7 @@
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\StandaloneMongodb;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Visus\Cuid2\Cuid2;
@@ -43,6 +44,21 @@ function create_standalone_redis($environment_id, $destination_uuid): Standalone
]);
}
function create_standalone_mongodb($environment_id, $destination_uuid): StandaloneMongodb
{
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (!$destination) {
throw new Exception('Destination not found');
}
return StandaloneMongodb::create([
'name' => generate_database_name('mongodb'),
'mongo_initdb_root_password' => \Illuminate\Support\Str::password(symbols: false),
'environment_id' => $environment_id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
}
/**
* Delete file locally on the filesystem.
* @param string $filename

View File

@@ -147,12 +147,11 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica
}
return $labels;
}
function fqdnLabelsForTraefik(Collection $domains, bool $is_force_https_enabled, $onlyPort = null)
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled, $onlyPort = null)
{
$labels = collect([]);
$labels->push('traefik.enable=true');
foreach ($domains as $domain) {
$uuid = (string)new Cuid2(7);
foreach ($domains as $loop => $domain) {
$url = Url::fromString($domain);
$host = $url->getHost();
$path = $url->getPath();
@@ -161,8 +160,8 @@ function fqdnLabelsForTraefik(Collection $domains, bool $is_force_https_enabled,
if (is_null($port) && !is_null($onlyPort)) {
$port = $onlyPort;
}
$http_label = "{$uuid}-http";
$https_label = "{$uuid}-https";
$http_label = "{$uuid}-{$loop}-http";
$https_label = "{$uuid}-{$loop}-https";
if ($schema === 'https') {
// Set labels for https
@@ -205,20 +204,21 @@ function fqdnLabelsForTraefik(Collection $domains, bool $is_force_https_enabled,
return $labels;
}
function generateLabelsApplication(Application $application, ?ApplicationPreview $preview = null, $ports): array
function generateLabelsApplication(Application $application, ?ApplicationPreview $preview = null): array
{
$ports = $application->settings->is_static ? [80] : $application->ports_exposes_array;
$onlyPort = null;
if (count($ports) === 1) {
$onlyPort = $ports[0];
}
$pull_request_id = data_get($preview, 'pull_request_id', 0);
$container_name = generateApplicationContainerName($application, $pull_request_id);
// $container_name = generateApplicationContainerName($application, $pull_request_id);
$appId = $application->id;
if ($pull_request_id !== 0 && $pull_request_id !== null) {
$appId = $appId . '-pr-' . $pull_request_id;
}
$labels = collect([]);
$labels = $labels->merge(defaultLabels($appId, $container_name, $pull_request_id));
$labels = $labels->merge(defaultLabels($appId, $application->uuid, $pull_request_id));
if ($application->fqdn) {
if ($pull_request_id !== 0) {
$domains = Str::of(data_get($preview, 'fqdn'))->explode(',');
@@ -226,7 +226,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
}
// Add Traefik labels no matter which proxy is selected
$labels = $labels->merge(fqdnLabelsForTraefik($domains, $application->settings->is_force_https_enabled,$onlyPort));
$labels = $labels->merge(fqdnLabelsForTraefik($application->uuid, $domains, $application->settings->is_force_https_enabled, $onlyPort));
}
return $labels->all();
}

View File

@@ -1,7 +1,12 @@
<?php
use App\Models\Application;
use App\Models\InstanceSettings;
use App\Models\Server;
use App\Models\Service;
use App\Models\StandaloneMongodb;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use App\Models\Team;
use App\Models\User;
use App\Notifications\Channels\DiscordChannel;
@@ -437,9 +442,6 @@ function getServiceTemplates()
if (isDev()) {
$services = File::get(base_path('templates/service-templates.json'));
$services = collect(json_decode($services))->sortKeys();
$deprecated = File::get(base_path('templates/deprecated.json'));
$deprecated = collect(json_decode($deprecated))->sortKeys();
$services = $services->merge($deprecated);
$version = config('version');
$services = $services->map(function ($service) use ($version) {
if (version_compare($version, data_get($service, 'minVersion', '0.0.0'), '<')) {
@@ -456,3 +458,31 @@ function getServiceTemplates()
}
return $services;
}
function getResourceByUuid(string $uuid, ?int $teamId = null)
{
$resource = queryResourcesByUuid($uuid);
if (!is_null($teamId)) {
if (!is_null($resource) && $resource->environment->project->team_id === $teamId) {
return $resource;
}
return null;
} else {
return $resource;
}
}
function queryResourcesByUuid(string $uuid)
{
$resource = null;
$application = Application::whereUuid($uuid)->first();
if ($application) return $application;
$service = Service::whereUuid($uuid)->first();
if ($service) return $service;
$postgresql = StandalonePostgresql::whereUuid($uuid)->first();
if ($postgresql) return $postgresql;
$redis = StandaloneRedis::whereUuid($uuid)->first();
if ($redis) return $redis;
$mongodb = StandaloneMongodb::whereUuid($uuid)->first();
if ($mongodb) return $mongodb;
return $resource;
}

View File

@@ -210,13 +210,13 @@ return [
'production' => [
's6' => [
'autoScalingStrategy' => 'size',
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 10),
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 2),
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
],
'long-running' => [
'autoScalingStrategy' => 'size',
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 10),
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 2),
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
],
@@ -225,13 +225,13 @@ return [
'local' => [
's6' => [
'autoScalingStrategy' => 'size',
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 10),
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 2),
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
],
'long-running' => [
'autoScalingStrategy' => 'size',
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 10),
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 2),
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
],

View File

@@ -7,7 +7,7 @@ return [
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.87',
'release' => '4.0.0-beta.97',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.87';
return '4.0.0-beta.97';

View File

@@ -0,0 +1,23 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\StandaloneMongodb>
*/
class StandaloneMongodbFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
//
];
}
}

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->text('custom_labels')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->dropColumn('custom_labels');
});
}
};

View File

@@ -0,0 +1,56 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('standalone_mongodbs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->string('name');
$table->string('description')->nullable();
$table->text('mongo_conf')->nullable();
$table->text('mongo_initdb_root_username')->default('root');
$table->text('mongo_initdb_root_password');
$table->text('mongo_initdb_database')->default('default');
$table->string('status')->default('exited');
$table->string('image')->default('mongo:7');
$table->boolean('is_public')->default(false);
$table->integer('public_port')->nullable();
$table->text('ports_mappings')->nullable();
$table->string('limits_memory')->default("0");
$table->string('limits_memory_swap')->default("0");
$table->integer('limits_memory_swappiness')->default(60);
$table->string('limits_memory_reservation')->default("0");
$table->string('limits_cpus')->default("0");
$table->string('limits_cpuset')->nullable()->default("0");
$table->integer('limits_cpu_shares')->default(1024);
$table->timestamp('started_at')->nullable();
$table->morphs('destination');
$table->foreignId('environment_id')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('standalone_mongodbs');
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('environment_variables', function (Blueprint $table) {
$table->foreignId('standalone_mongodb_id')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('environment_variables', function (Blueprint $table) {
$table->dropColumn('standalone_mongodb_id');
});
}
};

View File

@@ -37,7 +37,7 @@ class ApplicationSeeder extends Seeder
'git_repository' => 'coollabsio/coolify-examples',
'git_branch' => 'dockerfile',
'build_pack' => 'dockerfile',
'ports_exposes' => '3000',
'ports_exposes' => '80',
'environment_id' => 1,
'destination_id' => 0,
'destination_type' => StandaloneDocker::class,

View File

@@ -34,7 +34,7 @@ services:
POSTGRES_DB: "${DB_DATABASE:-coolify}"
POSTGRES_HOST_AUTH_METHOD: "trust"
volumes:
- ./_data/coolify/_volumes/database/:/var/lib/postgresql/data
- /data/coolify/_volumes/database/:/var/lib/postgresql/data
# - coolify-pg-data-dev:/var/lib/postgresql/data
redis:
ports:
@@ -42,7 +42,7 @@ services:
env_file:
- .env
volumes:
- ./_data/coolify/_volumes/redis/:/data
- /data/coolify/_volumes/redis/:/data
# - coolify-redis-data-dev:/data
vite:
image: node:19
@@ -58,7 +58,7 @@ services:
volumes:
- /:/host
- /var/run/docker.sock:/var/run/docker.sock
- ./_data/coolify/:/data/coolify
- /data/coolify/:/data/coolify
# - coolify-data-dev:/data/coolify
mailpit:
image: "axllent/mailpit:latest"
@@ -79,7 +79,7 @@ services:
MINIO_ACCESS_KEY: "${MINIO_ACCESS_KEY:-minioadmin}"
MINIO_SECRET_KEY: "${MINIO_SECRET_KEY:-minioadmin}"
volumes:
- ./_data/coolify/_volumes/minio/:/data
- /data/coolify/_volumes/minio/:/data
# - coolify-minio-data-dev:/data
networks:
- coolify

View File

@@ -27,6 +27,7 @@ services:
- QUEUE_CONNECTION
- REDIS_HOST
- REDIS_PASSWORD
- HORIZON_MAX_PROCESSES
- SSL_MODE=off
- PHP_PM_CONTROL=dynamic
- PHP_PM_START_SERVERS=1

View File

@@ -1,15 +0,0 @@
services:
postgres:
image: postgres
command: 'postgres -c config_file=/etc/postgresql/postgresql.conf'
volumes:
- type: bind
source: ./postgresql.conf
target: /etc/postgresql/postgresql.conf
- type: bind
source: ./docker-entrypoint-initdb.d
target: /docker-entrypoint-initdb.d/
isDirectory: true
environment:
POSTGRES_USER: $SERVICE_USER_POSTGRES
POSTGRES_PASSWORD: $SERVICE_PASSWORD_POSTGRES

View File

@@ -1,5 +0,0 @@
services:
uptime-kuma:
image: louislam/uptime-kuma:1
volumes:
- uptime-kuma:/app/data

View File

@@ -53,12 +53,14 @@ a {
@apply text-white;
}
.box {
@apply flex items-center p-2 transition-colors cursor-pointer min-h-16 bg-coolgray-200 hover:bg-coollabs-100 hover:text-white hover:no-underline min-w-[24rem];
@apply flex p-2 transition-colors cursor-pointer min-h-16 bg-coolgray-200 hover:bg-coollabs-100 hover:text-white hover:no-underline min-w-[24rem];
}
.box-without-bg {
@apply flex items-center p-2 transition-colors min-h-16 hover:text-white hover:no-underline min-w-[24rem];
@apply flex p-2 transition-colors min-h-16 hover:text-white hover:no-underline min-w-[24rem];
}
.description {
@apply pt-2 text-xs font-bold text-neutral-500 group-hover:text-white;
}
.lds-heart {
animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
}

View File

@@ -1,6 +1,6 @@
<template>
<Transition name="fade">
<div >
<div>
<div class="flex items-center p-1 px-2 overflow-hidden transition-all transform rounded cursor-pointer bg-coolgray-200"
@click="showCommandPalette = true">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 icon" viewBox="0 0 24 24" stroke-width="2"
@@ -54,11 +54,12 @@
sequenceState.sequence[sequenceState.currentActionIndex] }}</span> name
will be:
<span class="inline-block text-warning">{{ search }}</span>
</span>
</span>
<span v-else><span class="capitalize ">{{
sequenceState.sequence[sequenceState.currentActionIndex] }}</span> name
will be:
<span class="inline-block text-warning">randomly generated (type to change)</span>
<span class="inline-block text-warning">randomly generated (type to
change)</span>
</span>
</span>
</li>
@@ -338,82 +339,96 @@ const magicActions = [{
},
{
id: 11,
name: 'Goto: Dashboard',
name: 'Goto: S3 Storage',
tags: 's3,storage',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 12,
name: 'Goto: Servers',
name: 'Goto: Dashboard',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 13,
name: 'Goto: Servers',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 14,
name: 'Goto: Private Keys',
tags: 'destination,docker,network,new,create,ssh,private,key',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 14,
id: 15,
name: 'Goto: Projects',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 15,
id: 16,
name: 'Goto: Sources',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 16,
id: 17,
name: 'Goto: Destinations',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 17,
id: 18,
name: 'Goto: Settings',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 18,
id: 19,
name: 'Goto: Command Center',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 19,
id: 20,
name: 'Goto: Notifications',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 20,
id: 21,
name: 'Goto: Profile',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 21,
id: 22,
name: 'Goto: Teams',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 22,
id: 23,
name: 'Goto: Switch Teams',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 23,
id: 24,
name: 'Goto: Boarding process',
icon: 'goto',
sequence: ['main', 'redirect']
},
{
id: 25,
name: 'Goto: API Tokens',
tags: 'api,tokens,rest',
icon: 'goto',
sequence: ['main', 'redirect']
}
]
const initialState = {
@@ -606,44 +621,50 @@ async function redirect() {
targetUrl.pathname = `/team/storages/new`
break;
case 11:
targetUrl.pathname = `/`
targetUrl.pathname = `/team/storages/`
break;
case 12:
targetUrl.pathname = `/servers`
targetUrl.pathname = `/`
break;
case 13:
targetUrl.pathname = `/security/private-key`
targetUrl.pathname = `/servers`
break;
case 14:
targetUrl.pathname = `/projects`
targetUrl.pathname = `/security/private-key`
break;
case 15:
targetUrl.pathname = `/sources`
targetUrl.pathname = `/projects`
break;
case 16:
targetUrl.pathname = `/destinations`
targetUrl.pathname = `/sources`
break;
case 17:
targetUrl.pathname = `/settings`
targetUrl.pathname = `/destinations`
break;
case 18:
targetUrl.pathname = `/command-center`
targetUrl.pathname = `/settings`
break;
case 19:
targetUrl.pathname = `/team/notifications`
targetUrl.pathname = `/command-center`
break;
case 20:
targetUrl.pathname = `/profile`
targetUrl.pathname = `/team/notifications`
break;
case 21:
targetUrl.pathname = `/team`
targetUrl.pathname = `/profile`
break;
case 22:
targetUrl.pathname = `/team`
break;
case 23:
targetUrl.pathname = `/team`
break;
case 24:
targetUrl.pathname = `/boarding`
break;
case 25:
targetUrl.pathname = `/security/api-tokens`
break;
}
window.location.href = targetUrl;
}

View File

@@ -7,7 +7,7 @@
href="{{ route('project.database.logs', $parameters) }}">
<button>Logs</button>
</a>
@if ($database->getMorphClass() === 'App\Models\StandalonePostgresql')
@if ($database->getMorphClass() === 'App\Models\StandalonePostgresql' || $database->getMorphClass() === 'App\Models\StandaloneMongodb')
<a class="{{ request()->routeIs('project.database.backups.all') ? 'text-white' : '' }}"
href="{{ route('project.database.backups.all', $parameters) }}">
<button>Backups</button>

View File

@@ -10,8 +10,15 @@
</ol>
</nav>
<nav class="navbar-main">
<a class="{{ request()->routeIs('security.private-key.index') ? 'text-white' : '' }}" href="{{ route('security.private-key.index') }}">
<a href="{{ route('security.private-key.index') }}">
<button>Private Keys</button>
</a>
<a href="{{ route('security.api-tokens') }}">
<button>API tokens</button>
</a>
<div class="flex-1"></div>
<div class="-mt-9">
<livewire:switch-team />
</div>
</nav>
</div>

View File

@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://api.fonts.coollabs.io" crossorigin>
<link href="https://api.fonts.coollabs.io/css2?family=Inter&display=swap" rel="stylesheet">
<meta name="robots" content="noindex">
<title>Coolify</title>
@env('local')
<link rel="icon" href="{{ asset('favicon-dev.png') }}" type="image/x-icon" />

View File

@@ -15,7 +15,8 @@
</div>
@endif
@if ($projects->count() === 0 && $servers->count() === 0)
No resources found. Add your first server / private key <a class="text-white underline" href="{{route('server.create')}}">here</a>.
No resources found. Add your first server / private key <a class="text-white underline"
href="{{ route('server.create') }}">here</a>.
@endif
@if ($projects->count() > 0)
<h3 class="pb-4">Projects</h3>
@@ -23,7 +24,7 @@
@if ($projects->count() === 1)
<div class="grid grid-cols-1 gap-2">
@else
<div class="grid grid-cols-3 gap-2">
<div class="grid grid-cols-1 gap-2 xl:grid-cols-2">
@endif
@foreach ($projects as $project)
<div class="gap-2 border border-transparent cursor-pointer box group" x-data
@@ -32,37 +33,39 @@
<a class="flex flex-col flex-1 mx-6 hover:no-underline"
href="{{ route('project.resources', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
<div class="font-bold text-white">{{ $project->name }}</div>
<div class="text-xs group-hover:text-white hover:no-underline">
<div class="description">
{{ $project->description }}</div>
</a>
@else
<a class="flex flex-col flex-1 mx-6 hover:no-underline"
href="{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}">
<div class="font-bold text-white">{{ $project->name }}</div>
<div class="text-xs group-hover:text-white hover:no-underline">
<div class="description">
{{ $project->description }}</div>
</a>
@endif
<a class="mx-4 rounded group-hover:text-white hover:no-underline "
href="{{ route('project.resources.new', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
<span class="font-bold hover:text-warning">+ New Resource</span>
</a>
<a class="mx-4 rounded group-hover:text-white"
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-warning" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
</svg>
</a>
<div class="flex items-center">
<a class="mx-4 rounded group-hover:text-white hover:no-underline"
href="{{ route('project.resources.new', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
<span class="font-bold hover:text-warning">+ New Resource</span>
</a>
<a class="mx-4 rounded group-hover:text-white"
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-warning" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
</svg>
</a>
</div>
</div>
@endforeach
</div>
@if ($projects->count() > 0)
<h3 class="pb-4">Servers</h3>
<h3 class="py-4">Servers</h3>
@endif
@if ($servers->count() === 1)
<div class="grid grid-cols-1 gap-2">
@@ -79,7 +82,7 @@
<div class="font-bold text-white">
{{ $server->name }}
</div>
<div class="text-xs group-hover:text-white">
<div class="description">
{{ $server->description }}</div>
<div class="flex gap-1 text-xs text-error">
@if (!$server->settings->is_reachable)
@@ -97,7 +100,6 @@
</a>
@endforeach
</div>
<script>
function gotoProject(uuid, environment = 'production') {
window.location.href = '/project/' + uuid + '/' + environment;

View File

@@ -5,8 +5,12 @@
<x-forms.button type="submit">
Save
</x-forms.button>
@if ($isConfigurationChanged && !is_null($application->config_hash))
<div class="font-bold text-warning">Configuration not applied to the running application. You need to
redeploy.</div>
@endif
</div>
<div class="">General configuration for your application.</div>
<div>General configuration for your application.</div>
<div class="flex flex-col gap-2 py-4">
<div class="flex flex-col items-end gap-2 xl:flex-row">
<x-forms.input id="application.name" label="Name" required />
@@ -81,7 +85,6 @@
@if ($application->dockerfile)
<x-forms.textarea label="Dockerfile" id="application.dockerfile" rows="6"> </x-forms.textarea>
@endif
<h3>Network</h3>
<div class="flex flex-col gap-2 xl:flex-row">
@if ($application->settings->is_static)
@@ -93,6 +96,12 @@
<x-forms.input placeholder="3000:3000" id="application.ports_mappings" label="Ports Mappings"
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host." />
</div>
@if ($labelsChanged)
<x-forms.textarea label="Custom Labels" rows="15" id="customLabels"></x-forms.textarea>
@else
<x-forms.textarea label="Coolify Generated Labels" rows="15" id="customLabels"></x-forms.textarea>
@endif
<x-forms.button wire:click="resetDefaultLabels">Reset to Coolify Generated Labels</x-forms.button>
</div>
<h3>Advanced</h3>
<div class="flex flex-col">

View File

@@ -0,0 +1,57 @@
<form wire:submit.prevent='clone'>
<div class="flex flex-col">
<div class="flex gap-2">
<h1>Clone</h1>
<x-forms.button type="submit">Clone to a New Project</x-forms.button>
</div>
<div class="subtitle ">Quickly clone a project</div>
</div>
<x-forms.input required id="newProjectName" label="New Project Name" />
<h3 class="pt-4 pb-2">Servers</h3>
<div class="grid gap-2 lg:grid-cols-3">
@foreach ($servers as $srv)
<div wire:click="selectServer('{{ $srv->id }}')"
class="cursor-pointer box-without-bg bg-coolgray-200 group"
:class="'{{ $selectedServer === $srv->id }}' && 'bg-coollabs'">
<div class="flex flex-col mx-6">
<div :class="'{{ $selectedServer === $srv->id }}' && 'text-white'"> {{ $srv->name }}</div>
@isset($selectedServer)
<div :class="'{{ $selectedServer === $srv->id }}' && 'text-white pt-2 text-xs font-bold'">
{{ $srv->description }}</div>
@else
<div class="description">
{{ $srv->description }}</div>
@endisset
</div>
</div>
@endforeach
</div>
<h3 class="pt-4 pb-2">Resources</h3>
<div class="grid grid-cols-1 gap-2">
@foreach ($environment->applications->sortBy('name') as $application)
<div class="p-2 border border-coolgray-200">
<div class="flex flex-col">
<div class="font-bold text-white">{{ $application->name }}</div>
<div class="description">{{ $application->description }}</div>
</div>
</div>
@endforeach
@foreach ($environment->databases()->sortBy('name') as $database)
<div class="p-2 border border-coolgray-200">
<div class="flex flex-col">
<div class="font-bold text-white">{{ $database->name }}</div>
<div class="description">{{ $database->description }}</div>
</div>
</div>
@endforeach
@foreach ($environment->services->sortBy('name') as $service)
<div class="p-2 border border-coolgray-200">
<div class="flex flex-col">
<div class="font-bold text-white">{{ $service->name }}</div>
<div class="description">{{ $service->description }}</div>
</div>
</div>
@endforeach
</div>
</form>

View File

@@ -25,9 +25,21 @@
</x-forms.select>
</div>
@endif
<div class="flex gap-2">
<x-forms.input label="Databases To Backup" helper="Comma separated list of databases to backup. Empty will include the default one." id="backup.databases_to_backup" />
<x-forms.input label="Frequency" id="backup.frequency" />
<x-forms.input label="Number of backups to keep (locally)" id="backup.number_of_backups_locally" />
<div class="flex flex-col gap-2">
<div class="flex gap-2">
@if ($backup->database_type === 'App\Models\StandalonePostgresql')
<x-forms.input label="Databases To Backup"
helper="Comma separated list of databases to backup. Empty will include the default one."
id="backup.databases_to_backup" />
@elseif($backup->database_type === 'App\Models\StandaloneMongodb')
<x-forms.input label="Databases To Include"
helper="A list of databases to backup. You can specify which collection(s) per database to exclude from the backup. Empty will include all databases and collections.<br><br>Example:<br><br>database1:collection1,collection2|database2:collection3,collection4<br><br> database1 will include all collections except collection1 and collection2. <br>database2 will include all collections except collection3 and collection4.<br><br>Another Example:<br><br>database1:collection1|database2<br><br> database1 will include all collections except collection1.<br>database2 will include ALL collections."
id="backup.databases_to_backup" />
@endif
</div>
<div class="flex gap-2">
<x-forms.input label="Frequency" id="backup.frequency" />
<x-forms.input label="Number of backups to keep (locally)" id="backup.number_of_backups_locally" />
</div>
</div>
</form>

View File

@@ -0,0 +1,52 @@
<div>
<form wire:submit.prevent="submit" class="flex flex-col gap-2">
<div class="flex items-center gap-2">
<h2>General</h2>
<x-forms.button type="submit">
Save
</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="database.name" />
<x-forms.input label="Description" id="database.description" />
<x-forms.input label="Image" id="database.image" required
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/mongo'>https://hub.docker.com/_/mongo</a>" />
</div>
@if ($database->started_at)
<div class="flex gap-2">
<x-forms.input label="Initial Username" id="database.mongo_initdb_root_username"
placeholder="If empty: postgres" readonly helper="You can only change this in the database." />
<x-forms.input label="Initial Password" id="database.mongo_initdb_root_password" type="password" required
readonly helper="You can only change this in the database." />
<x-forms.input label="Initial Database" id="database.mongo_initdb_database"
placeholder="If empty, it will be the same as Username." readonly
helper="You can only change this in the database." />
</div>
@else
<div class="pt-8 text-warning">Please verify these values. You can only modify them before the initial
start. After that, you need to modify it in the database.
</div>
<div class="flex gap-2 pb-8">
<x-forms.input required label="Username" id="database.mongo_initdb_root_username"
placeholder="If empty: postgres" />
<x-forms.input label="Password" id="database.mongo_initdb_root_password" type="password" required />
<x-forms.input required label="Database" id="database.mongo_initdb_database"
placeholder="If empty, it will be the same as Username." />
</div>
@endif
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="3000:5432" id="database.ports_mappings" label="Ports Mappings"
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold text-warning'>Example</span>3000:5432,3002:5433" />
<x-forms.input placeholder="5432" disabled="{{ $database->is_public }}" id="database.public_port"
label="Public Port" />
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
</div>
<x-forms.input label="Mongo URL"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="db_url" />
</div>
<x-forms.textarea label="Custom MongoDB Configuration" rows="10" id="database.mongo_conf" />
</form>
</div>

View File

@@ -62,7 +62,7 @@
label="Public Port" />
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
</div>
<x-forms.input label="Postgres URL" type="password" readonly wire:model="db_url" />
<x-forms.input label="Postgres URL" helper="If you change the user/password/port, this could be different. This is with the default values." type="password" readonly wire:model="db_url" />
</div>
</form>
<div class="pb-16">

View File

@@ -21,7 +21,7 @@
label="Public Port" />
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
</div>
<x-forms.input label="Redis URL" type="password" readonly wire:model="db_url" />
<x-forms.input label="Redis URL" helper="If you change the user/password/port, this could be different. This is with the default values." type="password" readonly wire:model="db_url" />
</div>
<x-forms.textarea helper="<a target='_blank' class='text-white underline' href='https://raw.githubusercontent.com/redis/redis/7.2/redis.conf'>Redis Default Configuration</a>" label="Custom Redis Configuration" rows="10" id="database.redis_conf" />
</form>

View File

@@ -24,7 +24,7 @@
<div class="font-bold text-white group-hover:text-white">
Public Repository
</div>
<div class="text-xs group-hover:text-white">
<div class="description">
You can deploy any kind of public repositories from the supported git providers.
</div>
</div>
@@ -34,7 +34,7 @@
<div class="font-bold text-white group-hover:text-white">
Private Repository (with GitHub App)
</div>
<div class="text-xs group-hover:text-white">
<div class="description">
You can deploy public & private repositories through your GitHub Apps.
</div>
</div>
@@ -44,7 +44,7 @@
<div class="font-bold text-white group-hover:text-white">
Private Repository (with deploy key)
</div>
<div class="text-xs group-hover:text-white">
<div class="description">
You can deploy public & private repositories with a simple deploy key (SSH key).
</div>
</div>
@@ -56,7 +56,7 @@
<div class="font-bold text-white group-hover:text-white">
Based on a Dockerfile
</div>
<div class="text-xs group-hover:text-white">
<div class="description">
You can deploy a simple Dockerfile, without Git.
</div>
</div>
@@ -66,7 +66,7 @@
<div class="font-bold text-white group-hover:text-white">
Based on a Docker Compose
</div>
<div class="text-xs group-hover:text-white">
<div class="description">
You can deploy complex application easily with Docker Compose.
</div>
</div>
@@ -76,7 +76,7 @@
<div class="font-bold text-white group-hover:text-white">
Based on an existing Docker Image
</div>
<div class="text-xs group-hover:text-white">
<div class="description">
You can deploy an existing Docker Image form any Registry.
</div>
</div>
@@ -89,7 +89,7 @@
<div class="font-bold text-white group-hover:text-white">
New PostgreSQL
</div>
<div class="text-xs group-hover:text-white">
<div class="description">
The most loved relational database in the world.
</div>
</div>
@@ -99,11 +99,21 @@
<div class="font-bold text-white group-hover:text-white">
New Redis
</div>
<div class="text-xs group-hover:text-white">
<div class="description">
The open source, in-memory data store for cache, streaming engine, and message broker.
</div>
</div>
</div>
<div class="box group" wire:click="setType('mongodb')">
<div class="flex flex-col mx-6">
<div class="font-bold text-white group-hover:text-white">
New MongoDB
</div>
<div class="description">
MongoDB is a source-available cross-platform document-oriented database program.
</div>
</div>
</div>
{{-- <div class="box group" wire:click="setType('existing-postgresql')">
<div class="flex flex-col mx-6">
<div class="group-hover:text-white">
@@ -125,10 +135,9 @@
@else
@foreach ($services as $serviceName => $service)
@if (data_get($service, 'disabled'))
<button class="text-left bg-black cursor-not-allowed bg-coolgray-200/20 box-without-bg"
disabled>
<button class="text-left cursor-not-allowed bg-coolgray-200/20 box-without-bg" disabled>
<div class="flex flex-col mx-6">
<div class="font-bold text-coolgray-500">
<div class="font-bold">
{{ Str::headline($serviceName) }}
</div>
You need to upgrade to {{ data_get($service, 'minVersion') }} to use this service.
@@ -137,12 +146,12 @@
@else
<button class="text-left box group" wire:loading.attr="disabled"
wire:click="setType('one-click-service-{{ $serviceName }}')">
<div class="flex flex-col mx-6">
<div class="flex flex-col mx-2">
<div class="font-bold text-white group-hover:text-white">
{{ Str::headline($serviceName) }}
</div>
@if (data_get($service, 'slogan'))
<div class="text-xs">
<div class="description">
{{ data_get($service, 'slogan') }}
</div>
@endif

View File

@@ -18,10 +18,10 @@
<div class="flex gap-2">
@if ($application->required_fqdn)
<x-forms.input required placeholder="https://app.coolify.io" label="Domains"
id="application.fqdn"></x-forms.input>
id="application.fqdn" helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io, https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "></x-forms.input>
@else
<x-forms.input placeholder="https://app.coolify.io" label="Domains"
id="application.fqdn"></x-forms.input>
id="application.fqdn" helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io, https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "></x-forms.input>
@endif
<x-forms.input required
helper="You can change the image you would like to deploy.<br><br><span class='text-warning'>WARNING. You could corrupt your data. Only do it if you know what you are doing.</span>"

View File

@@ -5,13 +5,15 @@
<span wire:poll.2000ms='getLogs(true)' class="loading loading-xs text-warning loading-spinner"></span>
@endif
</div>
<div class="flex gap-2">
<x-forms.checkbox instantSave label="Stream Logs" id="streamLogs"></x-forms.checkbox>
<x-forms.checkbox instantSave label="Include Timestamps" id="showTimeStamps"></x-forms.checkbox>
</div>
<form wire:submit.prevent='getLogs(true)' class="flex items-end gap-2">
<x-forms.input label="Only Show Number of Lines" placeholder="1000" required id="numberOfLines"></x-forms.input>
<x-forms.button type="submit">Refresh</x-forms.button>
</form>
<div class="w-32">
<x-forms.checkbox instantSave label="Stream Logs" id="streamLogs"></x-forms.checkbox>
</div>
<div class="container w-full pt-4 mx-auto">
<div
class="scrollbar flex flex-col-reverse w-full overflow-y-auto border border-solid rounded border-coolgray-300 max-h-[32rem] p-4 pt-6 text-xs text-white">

View File

@@ -0,0 +1,36 @@
<div>
<x-security.navbar />
<div class="flex gap-2">
<h2 class="pb-4">API Tokens</h2>
<x-helper
helper="Tokens are created with the current team as scope. You will only have access to this team's resources." />
</div>
<h4>Create New Token</h4>
<span>Currently active team: <span class="text-warning">{{ session('currentTeam.name') }}</span></span>
<form class="flex items-end gap-2 pt-4" wire:submit.prevent='addNewToken'>
<x-forms.input required id="description" label="Description" />
<x-forms.button type="submit">Create New Token</x-forms.button>
</form>
@if (session()->has('token'))
<div class="pb-4 font-bold text-warning">Please copy this token now. For your security, it won't be shown again.
</div>
<div class="pb-4 font-bold text-white"> {{ session('token') }}</div>
@endif
<h4 class="py-4">Issued Tokens</h4>
<div class="grid gap-2 lg:grid-cols-1">
@forelse ($tokens as $token)
<div class="flex items-center gap-2">
<div
class="flex items-center gap-2 group-hover:text-white p-2 border border-coolgray-200 hover:text-white hover:no-underline min-w-[24rem] cursor-default">
<div>{{ $token->name }}</div>
</div>
<x-forms.button wire:click="revoke('{{ $token->id }}')">Revoke</x-forms.button>
</div>
@empty
<div>
<div>No API tokens found.</div>
</div>
@endforelse
</div>
</div>

View File

@@ -18,7 +18,7 @@
<div class="font-bold text-white">
{{ $server->name }}
</div>
<div class="text-xs group-hover:text-white">
<div class="description">
{{ $server->description }}</div>
<div class="flex gap-1 text-xs text-error">
@if (!$server->settings->is_reachable)

View File

@@ -40,6 +40,9 @@
@if ($database->type() === 'standalone-redis')
<livewire:project.database.redis.general :database="$database" />
@endif
@if ($database->type() === 'standalone-mongodb')
<livewire:project.database.mongodb.general :database="$database" />
@endif
</div>
<div x-cloak x-show="activeTab === 'environment-variables'">
<livewire:project.shared.environment-variable.all :resource="$database" />

View File

@@ -9,6 +9,10 @@
class="font-normal text-white normal-case border-none rounded hover:no-underline btn btn-primary btn-sm no-animation">+
New</a>
@endif
<a class="font-normal text-white normal-case border-none rounded hover:no-underline btn btn-primary btn-sm no-animation"
href="{{ route('project.clone', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => request()->route('environment_name')]) }}">
Clone
</a>
</div>
<nav class="flex pt-2 pb-10">
<ol class="flex items-center">
@@ -42,13 +46,14 @@
href="{{ route('project.application.configuration', [$project->uuid, $environment->name, $application->uuid]) }}">
<div class="flex flex-col mx-6">
<div class="font-bold text-white">{{ $application->name }}</div>
<div class="text-xs text-gray-400 group-hover:text-white">{{ $application->description }}</div>
<div class="description">{{ $application->description }}</div>
</div>
@if (Str::of(data_get($application, 'status'))->startsWith('running'))
<div class="absolute bg-green-400 -top-1 -left-1 badge badge-xs"></div>
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
@elseif (Str::of(data_get($application, 'status'))->startsWith('exited'))
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
@elseif (Str::of(data_get($application, 'status'))->startsWith('restarting'))
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
@endif
</a>
@endforeach
@@ -57,12 +62,14 @@
href="{{ route('project.database.configuration', [$project->uuid, $environment->name, $database->uuid]) }}">
<div class="flex flex-col mx-6">
<div class="font-bold text-white">{{ $database->name }}</div>
<div class="text-xs text-gray-400 group-hover:text-white">{{ $database->description }}</div>
<div class="description">{{ $database->description }}</div>
</div>
@if (Str::of(data_get($database, 'status'))->startsWith('running'))
<div class="absolute bg-green-400 -top-1 -left-1 badge badge-xs"></div>
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
@elseif (Str::of(data_get($database, 'status'))->startsWith('exited'))
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
@elseif (Str::of(data_get($database, 'status'))->startsWith('restaring'))
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
@endif
</a>
@endforeach
@@ -71,12 +78,12 @@
href="{{ route('project.service', [$project->uuid, $environment->name, $service->uuid]) }}">
<div class="flex flex-col mx-6">
<div class="font-bold text-white">{{ $service->name }}</div>
<div class="text-xs text-gray-400 group-hover:text-white">{{ $service->description }}</div>
<div class="description">{{ $service->description }}</div>
</div>
@if (Str::of(serviceStatus($service))->startsWith('running'))
<div class="absolute bg-green-400 -top-1 -left-1 badge badge-xs"></div>
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
@elseif (Str::of(serviceStatus($service))->startsWith('degraded'))
<div class="absolute bg-yellow-400 -top-1 -left-1 badge badge-xs"></div>
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
@elseif (Str::of(serviceStatus($service))->startsWith('exited'))
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
@endif

View File

@@ -10,7 +10,7 @@
<div class="text-xs truncate subtitle lg:text-sm">{{ $project->name }}</div>
<div class="grid gap-2 lg:grid-cols-2">
@forelse ($project->environments as $environment)
<a class="justify-center box" href="{{ route('project.resources', [$project->uuid, $environment->name]) }}">
<a class="items-center justify-center box description" href="{{ route('project.resources', [$project->uuid, $environment->name]) }}">
{{ $environment->name }}
</a>
@empty

View File

@@ -20,20 +20,22 @@
<a class="flex flex-col flex-1 mx-6 hover:no-underline"
href="{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}">
<div class="font-bold text-white">{{ $project->name }}</div>
<div class="text-xs group-hover:text-white hover:no-underline">
<div class="description ">
{{ $project->description }}</div>
</a>
<a class="mx-4 rounded group-hover:text-white"
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-warning" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
</svg>
</a>
<div class="flex items-center">
<a class="mx-4 rounded group-hover:text-white"
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-warning" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" />
</svg>
</a>
</div>
</div>
@empty
<div>

View File

@@ -1,7 +1,13 @@
<?php
use App\Actions\Database\StartMongodb;
use App\Actions\Database\StartPostgresql;
use App\Actions\Database\StartRedis;
use App\Actions\Service\StartService;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Visus\Cuid2\Cuid2;
/*
|--------------------------------------------------------------------------
@@ -17,9 +23,61 @@ use Illuminate\Support\Facades\Route;
Route::get('/health', function () {
return 'OK';
});
Route::group([
'middleware' => ['auth:sanctum'],
'prefix' => 'v1'
], function () {
Route::get('/deploy', function (Request $request) {
$token = auth()->user()->currentAccessToken();
$teamId = data_get($token, 'team_id');
$uuid = $request->query->get('uuid');
$force = $request->query->get('force') ?? false;
if (is_null($teamId)) {
return response()->json(['error' => 'Invalid token.'], 400);
}
if (!$uuid) {
return response()->json(['error' => 'No UUID provided.'], 400);
}
$resource = getResourceByUuid($uuid, $teamId);
if ($resource) {
$type = $resource->getMorphClass();
if ($type === 'App\Models\Application') {
queue_application_deployment(
application_id: $resource->id,
deployment_uuid: new Cuid2(7),
force_rebuild: $force,
);
return response()->json(['message' => 'Deployment queued.'], 200);
} else if ($type === 'App\Models\StandalonePostgresql') {
StartPostgresql::run($resource);
$resource->update([
'started_at' => now(),
]);
return response()->json(['message' => 'Database started.'], 200);
} else if ($type === 'App\Models\StandaloneRedis') {
StartRedis::run($resource);
$resource->update([
'started_at' => now(),
]);
return response()->json(['message' => 'Database started.'], 200);
} else if ($type === 'App\Models\StandaloneMongodb') {
StartMongodb::run($resource);
$resource->update([
'started_at' => now(),
]);
return response()->json(['message' => 'Database started.'], 200);
}else if ($type === 'App\Models\Service') {
StartService::run($resource);
return response()->json(['message' => 'Service started.'], 200);
}
}
return response()->json(['error' => 'No resource found.'], 404);
});
});
Route::middleware(['throttle:5'])->group(function () {
Route::get('/unsubscribe/{token}', function() {
Route::get('/unsubscribe/{token}', function () {
try {
$token = request()->token;
$email = decrypt($token);
@@ -34,6 +92,5 @@ Route::middleware(['throttle:5'])->group(function () {
} catch (\Throwable $e) {
return 'Something went wrong. Please try again or contact support.';
}
})->name('unsubscribe.marketing.emails');
});

View File

@@ -10,7 +10,9 @@ use App\Http\Livewire\Project\Service\Index as ServiceIndex;
use App\Http\Livewire\Project\Service\Show as ServiceShow;
use App\Http\Livewire\Dev\Compose as Compose;
use App\Http\Livewire\Dashboard;
use App\Http\Livewire\Project\CloneProject;
use App\Http\Livewire\Project\Shared\Logs;
use App\Http\Livewire\Security\ApiTokens;
use App\Http\Livewire\Server\All;
use App\Http\Livewire\Server\Create;
use App\Http\Livewire\Server\Destination\Show as DestinationShow;
@@ -91,8 +93,10 @@ Route::prefix('magic')->middleware(['auth'])->group(function () {
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/projects', [ProjectController::class, 'all'])->name('projects');
Route::get('/project/{project_uuid}/edit', [ProjectController::class, 'edit'])->name('project.edit');
Route::get('/project/{project_uuid}', [ProjectController::class, 'show'])->name('project.show');
Route::get('/project/{project_uuid}/edit', [ProjectController::class, 'edit'])->name('project.edit');
Route::get('/project/{project_uuid}/{environment_name}/clone', CloneProject::class)->name('project.clone');
Route::get('/project/{project_uuid}/{environment_name}/new', [ProjectController::class, 'new'])->name('project.resources.new');
Route::get('/project/{project_uuid}/{environment_name}', [ProjectController::class, 'resources'])->name('project.resources');
@@ -161,6 +165,8 @@ Route::middleware(['auth'])->group(function () {
Route::get('/security/private-key/{private_key_uuid}', fn () => view('security.private-key.show', [
'private_key' => PrivateKey::ownedByCurrentTeam(['name', 'description', 'private_key', 'is_git_related'])->whereUuid(request()->private_key_uuid)->firstOrFail()
]))->name('security.private-key.show');
Route::get('/security/api-tokens', ApiTokens::class)->name('security.api-tokens');
});

View File

@@ -21,9 +21,10 @@ function help {
}
function setup:dev {
docker exec coolify bash -c "php artisan key:generate"
docker exec coolify bash -c "composer install"
docker exec coolify bash -c "php artisan key:generate"
docker exec coolify bash -c "php artisan migrate:fresh --seed"
sudo chmod -R o+rwx .
}
function sync:v3 {
if [ -z "$1" ]; then

View File

@@ -0,0 +1,15 @@
# documentation: https://docs.appsmith.com
# slogan: Appsmith is an open-source, self-hosted application development platform that enables you to build powerful web applications with ease.
services:
appsmith:
image: index.docker.io/appsmith/appsmith-ce:latest
environment:
- SERVICE_FQDN
- APPSMITH_MAIL_ENABLED=false
- APPSMITH_DISABLE_TELEMETRY=true
- APPSMITH_DISABLE_INTERCOM=true
- APPSMITH_SENTRY_DSN=
- APPSMITH_SMART_LOOK_ID=
volumes:
- stacks-data:/appsmith-stacks

View File

@@ -0,0 +1,107 @@
_APP_ENV=production
_APP_LOCALE=en
_APP_OPTIONS_ABUSE=enabled
_APP_OPTIONS_FORCE_HTTPS=disabled
_APP_OPENSSL_KEY_V1=
_APP_DOMAIN_FUNCTIONS=
_APP_CONSOLE_WHITELIST_ROOT=enabled
_APP_CONSOLE_WHITELIST_EMAILS=
_APP_CONSOLE_WHITELIST_IPS=
_APP_SYSTEM_EMAIL_NAME=Appwrite
_APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io
_APP_SYSTEM_RESPONSE_FORMAT=
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=certs@appwrite.io
_APP_USAGE_STATS=enabled
_APP_LOGGING_PROVIDER=
_APP_LOGGING_CONFIG=
_APP_USAGE_AGGREGATION_INTERVAL=30
_APP_USAGE_TIMESERIES_INTERVAL=30
_APP_USAGE_DATABASE_INTERVAL=900
_APP_WORKER_PER_CORE=6
_APP_REDIS_HOST=redis
_APP_REDIS_PORT=6379
_APP_REDIS_USER=
_APP_REDIS_PASS=
_APP_DB_HOST=mariadb
_APP_DB_PORT=3306
_APP_DB_SCHEMA=appwrite
_APP_DB_USER=$SERVICE_USER_MYSQL
_APP_DB_PASS=$SERVICE_PASSWORD_MYSQL
_APP_DB_ROOT_PASS=$SERVICE_PASSWORD_ROOTMYSQL
_APP_INFLUXDB_HOST=influxdb
_APP_INFLUXDB_PORT=8086
_APP_STATSD_HOST=telegraf
_APP_STATSD_PORT=8125
_APP_SMTP_HOST=
_APP_SMTP_PORT=
_APP_SMTP_SECURE=
_APP_SMTP_USERNAME=
_APP_SMTP_PASSWORD=
_APP_SMS_PROVIDER=
_APP_SMS_FROM=
_APP_STORAGE_LIMIT=30000000
_APP_STORAGE_PREVIEW_LIMIT=20000000
_APP_STORAGE_ANTIVIRUS=disabled
_APP_STORAGE_ANTIVIRUS_HOST=clamav
_APP_STORAGE_ANTIVIRUS_PORT=3310
_APP_STORAGE_DEVICE=local
_APP_STORAGE_S3_ACCESS_KEY=
_APP_STORAGE_S3_SECRET=
_APP_STORAGE_S3_REGION=us-east-1
_APP_STORAGE_S3_BUCKET=
_APP_STORAGE_DO_SPACES_ACCESS_KEY=
_APP_STORAGE_DO_SPACES_SECRET=
_APP_STORAGE_DO_SPACES_REGION=us-east-1
_APP_STORAGE_DO_SPACES_BUCKET=
_APP_STORAGE_BACKBLAZE_ACCESS_KEY=
_APP_STORAGE_BACKBLAZE_SECRET=
_APP_STORAGE_BACKBLAZE_REGION=us-west-004
_APP_STORAGE_BACKBLAZE_BUCKET=
_APP_STORAGE_LINODE_ACCESS_KEY=
_APP_STORAGE_LINODE_SECRET=
_APP_STORAGE_LINODE_REGION=eu-central-1
_APP_STORAGE_LINODE_BUCKET=
_APP_STORAGE_WASABI_ACCESS_KEY=
_APP_STORAGE_WASABI_SECRET=
_APP_STORAGE_WASABI_REGION=eu-central-1
_APP_STORAGE_WASABI_BUCKET=
_APP_FUNCTIONS_SIZE_LIMIT=30000000
_APP_FUNCTIONS_TIMEOUT=900
_APP_FUNCTIONS_BUILD_TIMEOUT=900
_APP_FUNCTIONS_CONTAINERS=10
_APP_FUNCTIONS_CPUS=0
_APP_FUNCTIONS_MEMORY=0
_APP_FUNCTIONS_MEMORY_SWAP=0
_APP_FUNCTIONS_RUNTIMES=node-16.0,php-8.0,python-3.9,ruby-3.0
_APP_EXECUTOR_SECRET=your-secret-key
_APP_EXECUTOR_HOST=http://appwrite-executor/v1
_APP_EXECUTOR_RUNTIME_NETWORK=appwrite_runtimes
_APP_FUNCTIONS_ENVS=node-16.0,php-7.4,python-3.9,ruby-3.0
_APP_FUNCTIONS_INACTIVE_THRESHOLD=60
DOCKERHUB_PULL_USERNAME=
DOCKERHUB_PULL_PASSWORD=
DOCKERHUB_PULL_EMAIL=
OPEN_RUNTIMES_NETWORK=appwrite_runtimes
_APP_FUNCTIONS_RUNTIMES_NETWORK=runtimes
_APP_DOCKER_HUB_USERNAME=
_APP_DOCKER_HUB_PASSWORD=
_APP_FUNCTIONS_MAINTENANCE_INTERVAL=3600
_APP_VCS_GITHUB_APP_NAME=
_APP_VCS_GITHUB_PRIVATE_KEY=
_APP_VCS_GITHUB_APP_ID=
_APP_VCS_GITHUB_CLIENT_ID=
_APP_VCS_GITHUB_CLIENT_SECRET=
_APP_VCS_GITHUB_WEBHOOK_SECRET=
_APP_MAINTENANCE_INTERVAL=86400
_APP_MAINTENANCE_RETENTION_CACHE=2592000
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600
_APP_MAINTENANCE_RETENTION_AUDIT=1209600
_APP_MAINTENANCE_RETENTION_ABUSE=86400
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
_APP_GRAPHQL_MAX_BATCH_SIZE=10
_APP_GRAPHQL_MAX_COMPLEXITY=250
_APP_GRAPHQL_MAX_DEPTH=3
_APP_MIGRATIONS_FIREBASE_CLIENT_ID=
_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET=
_APP_ASSISTANT_OPENAI_API_KEY=

View File

@@ -1,20 +1,19 @@
# documentation: https://appwrite.io/docs
# slogan: Appwrite is a self-hosted backend-as-a-service platform that simplifies the development of web and mobile applications by providing a range of features and APIs.
# env_file: appwrite.env
x-logging: &x-logging
logging:
driver: 'json-file'
options:
max-file: '5'
max-size: '10m'
x-image: &x-image
image: appwrite/appwrite:1.4.3
x-image-assistant: &x-image-assistant
image: appwrite/assistant:0.2.1
version: '3'
services:
appwrite:
<<: *x-image
image: appwrite/appwrite:1.4
container_name: appwrite
<<: *x-logging
labels:
@@ -137,7 +136,7 @@ services:
- _APP_ASSISTANT_OPENAI_API_KEY
appwrite-realtime:
<<: *x-image
image: appwrite/appwrite:1.4.3
entrypoint: realtime
container_name: appwrite-realtime
<<: *x-logging
@@ -180,7 +179,7 @@ services:
- _APP_LOGGING_CONFIG
appwrite-worker-audits:
<<: *x-image
image: appwrite/appwrite:1.4.3
entrypoint: worker-audits
<<: *x-logging
container_name: appwrite-worker-audits
@@ -207,7 +206,7 @@ services:
- _APP_LOGGING_CONFIG
appwrite-worker-webhooks:
<<: *x-image
image: appwrite/appwrite:1.4.3
entrypoint: worker-webhooks
<<: *x-logging
container_name: appwrite-worker-webhooks
@@ -230,7 +229,7 @@ services:
- _APP_LOGGING_CONFIG
appwrite-worker-deletes:
<<: *x-image
image: appwrite/appwrite:1.4.3
entrypoint: worker-deletes
<<: *x-logging
container_name: appwrite-worker-deletes
@@ -286,7 +285,7 @@ services:
- _APP_EXECUTOR_HOST
appwrite-worker-databases:
<<: *x-image
image: appwrite/appwrite:1.4.3
entrypoint: worker-databases
<<: *x-logging
container_name: appwrite-worker-databases
@@ -313,7 +312,7 @@ services:
- _APP_LOGGING_CONFIG
appwrite-worker-builds:
<<: *x-image
image: appwrite/appwrite:1.4.3
entrypoint: worker-builds
<<: *x-logging
container_name: appwrite-worker-builds
@@ -375,7 +374,7 @@ services:
- _APP_STORAGE_WASABI_BUCKET
appwrite-worker-certificates:
<<: *x-image
image: appwrite/appwrite:1.4.3
entrypoint: worker-certificates
<<: *x-logging
container_name: appwrite-worker-certificates
@@ -409,7 +408,7 @@ services:
- _APP_LOGGING_CONFIG
appwrite-worker-functions:
<<: *x-image
image: appwrite/appwrite:1.4.3
entrypoint: worker-functions
<<: *x-logging
container_name: appwrite-worker-functions
@@ -446,7 +445,7 @@ services:
- _APP_LOGGING_PROVIDER
appwrite-worker-mails:
<<: *x-image
image: appwrite/appwrite:1.4.3
entrypoint: worker-mails
<<: *x-logging
container_name: appwrite-worker-mails
@@ -474,7 +473,7 @@ services:
- _APP_LOGGING_CONFIG
appwrite-worker-messaging:
<<: *x-image
image: appwrite/appwrite:1.4.3
entrypoint: worker-messaging
<<: *x-logging
container_name: appwrite-worker-messaging
@@ -496,7 +495,7 @@ services:
- _APP_LOGGING_CONFIG
appwrite-worker-migrations:
<<: *x-image
image: appwrite/appwrite:1.4.3
entrypoint: worker-migrations
<<: *x-logging
container_name: appwrite-worker-migrations
@@ -527,7 +526,7 @@ services:
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
appwrite-maintenance:
<<: *x-image
image: appwrite/appwrite:1.4.3
entrypoint: maintenance
<<: *x-logging
container_name: appwrite-maintenance
@@ -561,7 +560,7 @@ services:
- _APP_MAINTENANCE_RETENTION_SCHEDULES
appwrite-usage:
<<: *x-image
image: appwrite/appwrite:1.4.3
entrypoint: usage
container_name: appwrite-usage
<<: *x-logging
@@ -592,7 +591,7 @@ services:
- _APP_LOGGING_CONFIG
appwrite-schedule:
<<: *x-image
image: appwrite/appwrite:1.4.3
entrypoint: schedule
container_name: appwrite-schedule
<<: *x-logging
@@ -617,7 +616,7 @@ services:
- _APP_DB_PASS
appwrite-assistant:
<<: *x-image-assistant
image: appwrite/assistant:0.2.1
container_name: appwrite-assistant
<<: *x-logging
restart: unless-stopped

View File

@@ -0,0 +1,19 @@
# documentation: https://docs.baby-buddy.net
# slogan: Baby Buddy is an open-source web application that helps parents track their baby's daily activities, growth, and health with ease.
services:
babybuddy:
image: lscr.io/linuxserver/babybuddy:latest
environment:
- SERVICE_FQDN_BABYBUDDY
- PUID=1000
- PGID=1000
- TZ=Europe/Madrid
- CSRF_TRUSTED_ORIGINS=$SERVICE_FQDN_BABYBUDDY
volumes:
- babybuddy-config:/config
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000"]
interval: 2s
timeout: 10s
retries: 15

View File

@@ -0,0 +1,21 @@
# documentation: https://coder.com/docs/code-server/latest/guide
# slogan: Code-Server is a self-hosted, web-based code editor that enables remote coding and collaboration from any device, anywhere.
services:
code-server:
image: lscr.io/linuxserver/code-server:latest
environment:
- SERVICE_FQDN_CODESERVER
- PUID=1000
- PGID=1000
- TZ=Europe/Madrid
- PASSWORD=$SERVICE_PASSWORD_64_PASSWORDCODESERVER
- SUDO_PASSWORD=$SERVICE_PASSWORD_SUDOCODESERVER
- DEFAULT_WORKSPACE=/config/workspace
volumes:
- code-server-config:/config
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8443"]
interval: 2s
timeout: 10s
retries: 15

View File

@@ -0,0 +1,18 @@
# documentation: https://www.dokuwiki.org/faq
# slogan: A lightweight and easy-to-use wiki platform for creating and managing documentation and knowledge bases with simplicity and flexibility.
services:
dokuwiki:
image: lscr.io/linuxserver/dokuwiki:latest
environment:
- SERVICE_FQDN_DOKUWIKI
- PUID=1000
- PGID=1000
- TZ=Europe/Madrid
volumes:
- dokuwiki-config:/config
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80"]
interval: 2s
timeout: 10s
retries: 15

View File

@@ -1,10 +1,13 @@
# documentation: https://fider.io/doc
# slogan: Fider is an open-source feedback platform for collecting and managing user feedback, helping you prioritize improvements to your products and services.
services:
fider:
image: getfider/fider:stable
environment:
BASE_URL: $SERVICE_FQDN_FIDER
DATABASE_URL: postgres://$SERVICE_USER_MYSQL:$SERVICE_PASSWORD_MYSQL@database:5432/fider?sslmode=disable
JWT_SECRET: $SERVICE_PASSWORD64_FIDER
JWT_SECRET: $SERVICE_PASSWORD_64_FIDER
EMAIL_NOREPLY: ${EMAIL_NOREPLY:-noreply@example.com}
EMAIL_MAILGUN_API: $EMAIL_MAILGUN_API
EMAIL_MAILGUN_DOMAIN: $EMAIL_MAILGUN_DOMAIN

View File

@@ -1,3 +1,6 @@
# documentation: https://ghost.org/docs
# slogan: Ghost is a popular open-source content management system (CMS) and blogging platform, known for its simplicity and focus on content creation.
services:
ghost:
image: ghost:5

View File

@@ -0,0 +1,18 @@
# documentation: https://github.com/linuxserver/Heimdall
# slogan: Heimdall is a self-hosted dashboard for managing and organizing your server applications, providing a centralized and efficient interface.
services:
heimdall:
image: lscr.io/linuxserver/heimdall:latest
environment:
- SERVICE_FQDN_HEIMDALL
- PUID=1000
- PGID=1000
- TZ=Europe/Madrid
volumes:
- heimdall-config:/config
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80"]
interval: 2s
timeout: 10s
retries: 15

View File

@@ -0,0 +1,17 @@
# documentation: https://github.com/alexta69/metube
# slogan: A web GUI for youtube-dl with playlist support. It enables you to effortlessly download videos from YouTube and dozens of other sites.
services:
metube:
image: ghcr.io/alexta69/metube:latest
environment:
- SERVICE_FQDN_METUBE
- UID=1000
- GID=1000
volumes:
- metube-downloads:/downloads
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8081"]
interval: 2s
timeout: 10s
retries: 15

View File

@@ -1,6 +1,9 @@
# documentation: https://docs.min.io/docs/minio-docker-quickstart-guide.html
# slogan: MinIO is a high performance object storage server compatible with Amazon S3 APIs.
services:
minio:
image: quay.io/minio/minio:RELEASE.2023-09-30T07-02-29Z
image: quay.io/minio/minio:latest
command: server /data --console-address ":9001"
environment:
SERVICE_FQDN_MINIO_9000:

View File

@@ -0,0 +1,17 @@
# documentation: https://github.com/schlagmichdoch/PairDrop/blob/master/docs/faq.md
# slogan: Pairdrop is a self-hosted file sharing and collaboration platform, offering secure file sharing and collaboration capabilities for efficient teamwork.
services:
pairdrop:
image: lscr.io/linuxserver/pairdrop:latest
environment:
- SERVICE_FQDN_PAIRDROP
- PUID=1000
- PGID=1000
- TZ=Europe/Madrid
- DEBUG_MODE=false
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000"]
interval: 2s
timeout: 10s
retries: 15

View File

@@ -1,3 +1,7 @@
# ignore: true
# documentation: https://plausible.io/docs/self-hosting
# slogan: "Plausible Analytics is a simple, open-source, lightweight (< 1 KB) and privacy-friendly web analytics alternative to Google Analytics."
version: "3.3"
services:
plausible:

View File

@@ -0,0 +1,18 @@
# documentation: https://github.com/RobinLinus/snapdrop/blob/master/docs/faq.md
# slogan: A self-hosted file-sharing service for secure and convenient file transfers, whether on a local network or the internet.
services:
snapdrop:
image: lscr.io/linuxserver/snapdrop:latest
environment:
- SERVICE_FQDN_SNAPDROP
- PUID=1000
- PGID=1000
- TZ=Europe/Madrid
volumes:
- snapdrop-config:/config
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80"]
interval: 2s
timeout: 10s
retries: 15

View File

@@ -0,0 +1,27 @@
# documentation: https://umami.is/docs/getting-started
# slogan: Umami is a lightweight, self-hosted web analytics platform designed to provide website owners with insights into visitor behavior without compromising user privacy.
services:
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
environment:
- SERVICE_FQDN_UMAMI
- DATABASE_URL=postgres://$SERVICE_USER_POSTGRES:$SERVICE_PASSWORD_POSTGRES@postgresql:5432/$POSTGRES_DB
- DATABASE_TYPE=postgres
- APP_SECRET=$SERVICE_PASSWORD_64_UMAMI
depends_on:
postgresql:
condition: service_healthy
postgresql:
image: postgres:15-alpine
volumes:
- postgresql-data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=$SERVICE_USER_POSTGRES
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
- POSTGRES_DB=${POSTGRES_DB:-umami}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10

View File

@@ -0,0 +1,15 @@
# documentation: https://github.com/louislam/uptime-kuma/wiki
# slogan: Uptime Kuma is a free, self-hosted monitoring tool for tracking the status and performance of your web services and applications in real-time.
services:
uptime-kuma:
image: louislam/uptime-kuma:1
environment:
- SERVICE_FQDN
volumes:
- uptime-kuma:/app/data
healthcheck:
test: ["CMD-SHELL", "extra/healthcheck"]
interval: 2s
timeout: 10s
retries: 15

View File

@@ -1,3 +1,4 @@
# ignore: true
services:
ghost:
image: ghost:5

Some files were not shown because too many files have changed in this diff Show More