- package updates
- feat: service fixes - ui: help menu
This commit is contained in:
@@ -10,19 +10,10 @@ class StartService
|
||||
use AsAction;
|
||||
public function handle(Service $service)
|
||||
{
|
||||
$workdir = service_configuration_dir() . "/{$service->uuid}";
|
||||
$service->saveComposeConfigs();
|
||||
$commands[] = "cd " . $service->workdir();
|
||||
$commands[] = "echo '####### Starting service {$service->name} on {$service->server->name}.'";
|
||||
$commands[] = "echo '####### Pulling images.'";
|
||||
$commands[] = "mkdir -p $workdir";
|
||||
$commands[] = "cd $workdir";
|
||||
|
||||
$docker_compose_base64 = base64_encode($service->docker_compose);
|
||||
$commands[] = "echo $docker_compose_base64 | base64 -d > docker-compose.yml";
|
||||
$envs = $service->environment_variables()->get();
|
||||
$commands[] = "rm -f .env || true";
|
||||
foreach ($envs as $env) {
|
||||
$commands[] = "echo '{$env->key}={$env->value}' >> .env";
|
||||
}
|
||||
$commands[] = "docker compose pull --quiet";
|
||||
$commands[] = "echo '####### Starting containers.'";
|
||||
$commands[] = "docker compose up -d >/dev/null 2>&1";
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Models\Project;
|
||||
use App\Models\Service;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class DockerCompose extends Component
|
||||
{
|
||||
@@ -18,57 +19,50 @@ class DockerCompose extends Component
|
||||
$this->query = request()->query();
|
||||
if (isDev()) {
|
||||
$this->dockercompose = 'services:
|
||||
ghost:
|
||||
documentation: https://ghost.org/docs/config
|
||||
image: ghost:5
|
||||
plausible_events_db:
|
||||
image: clickhouse/clickhouse-server:23.3.7.5-alpine
|
||||
restart: always
|
||||
volumes:
|
||||
- ghost-content-data:/var/lib/ghost/content
|
||||
environment:
|
||||
- url=$SERVICE_FQDN_GHOST
|
||||
- database__client=mysql
|
||||
- database__connection__host=mysql
|
||||
- database__connection__user=$SERVICE_USER_MYSQL
|
||||
- database__connection__password=$SERVICE_PASSWORD_MYSQL
|
||||
- database__connection__database=${MYSQL_DATABASE-ghost}
|
||||
depends_on:
|
||||
- mysql
|
||||
mysql:
|
||||
documentation: https://hub.docker.com/_/mysql
|
||||
image: mysql:8.0
|
||||
volumes:
|
||||
- ghost-mysql-data:/var/lib/mysql
|
||||
environment:
|
||||
- MYSQL_USER=${SERVICE_USER_MYSQL}
|
||||
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
|
||||
- MYSQL_DATABASE=${MYSQL_DATABASE}
|
||||
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQL_ROOT}
|
||||
- event-data:/var/lib/clickhouse
|
||||
- ./clickhouse/clickhouse-config.xml:/etc/clickhouse-server/config.d/logging.xml:ro
|
||||
- ./clickhouse/clickhouse-user-config.xml:/etc/clickhouse-server/users.d/logging.xml:ro
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 262144
|
||||
hard: 262144
|
||||
';
|
||||
}
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$this->validate([
|
||||
'dockercompose' => 'required'
|
||||
]);
|
||||
$server_id = $this->query['server_id'];
|
||||
try {
|
||||
$this->validate([
|
||||
'dockercompose' => 'required'
|
||||
]);
|
||||
$this->dockercompose = Yaml::dump(Yaml::parse($this->dockercompose), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
|
||||
$server_id = $this->query['server_id'];
|
||||
|
||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
||||
|
||||
$service = Service::create([
|
||||
'name' => 'service' . Str::random(10),
|
||||
'docker_compose_raw' => $this->dockercompose,
|
||||
'environment_id' => $environment->id,
|
||||
'server_id' => (int) $server_id,
|
||||
]);
|
||||
$service->name = "service-$service->uuid";
|
||||
$service = Service::create([
|
||||
'name' => 'service' . Str::random(10),
|
||||
'docker_compose_raw' => $this->dockercompose,
|
||||
'environment_id' => $environment->id,
|
||||
'server_id' => (int) $server_id,
|
||||
]);
|
||||
$service->name = "service-$service->uuid";
|
||||
|
||||
$service->parse(isNew: true);
|
||||
$service->parse(isNew: true);
|
||||
|
||||
return redirect()->route('project.service', [
|
||||
'service_uuid' => $service->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
return redirect()->route('project.service', [
|
||||
'service_uuid' => $service->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ use Livewire\Component;
|
||||
class Application extends Component
|
||||
{
|
||||
public ServiceApplication $application;
|
||||
public $fileStorages = null;
|
||||
protected $listeners = ["refreshFileStorages"];
|
||||
protected $rules = [
|
||||
'application.human_name' => 'nullable',
|
||||
'application.description' => 'nullable',
|
||||
@@ -15,14 +17,22 @@ class Application extends Component
|
||||
];
|
||||
public function render()
|
||||
{
|
||||
ray($this->application->fileStorages()->get());
|
||||
return view('livewire.project.service.application');
|
||||
}
|
||||
public function refreshFileStorages()
|
||||
{
|
||||
$this->fileStorages = $this->application->fileStorages()->get();
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
$this->refreshFileStorages();
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->application->save();
|
||||
$this->emit('success', 'Application saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
} finally {
|
||||
|
||||
@@ -3,17 +3,42 @@
|
||||
namespace App\Http\Livewire\Project\Service;
|
||||
|
||||
use App\Models\LocalFileVolume;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class FileStorage extends Component
|
||||
{
|
||||
public LocalFileVolume $fileStorage;
|
||||
public ServiceApplication|ServiceDatabase $service;
|
||||
public string $fs_path;
|
||||
|
||||
protected $rules = [
|
||||
'fileStorage.fs_path' => 'required',
|
||||
'fileStorage.mount_path' => 'required',
|
||||
'fileStorage.content' => 'nullable',
|
||||
];
|
||||
public function mount() {
|
||||
$this->service = $this->fileStorage->service;
|
||||
$this->fs_path = Str::of($this->fileStorage->fs_path)->beforeLast('/');
|
||||
$file = Str::of($this->fileStorage->fs_path)->afterLast('/');
|
||||
if (Str::of($this->fs_path)->startsWith('.')) {
|
||||
$this->fs_path = Str::of($this->fs_path)->after('.');
|
||||
$this->fs_path = $this->service->service->workdir() . $this->fs_path . "/" .$file;
|
||||
}
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->fileStorage->save();
|
||||
$this->service->saveFileVolumes();
|
||||
$this->emit('success', 'File updated successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.file-storage');
|
||||
|
||||
@@ -15,7 +15,7 @@ class Index extends Component
|
||||
'service.docker_compose_raw' => 'required',
|
||||
'service.docker_compose' => 'required',
|
||||
'service.name' => 'required',
|
||||
'service.description' => 'required',
|
||||
'service.description' => 'nullable',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@@ -28,19 +28,23 @@ class Index extends Component
|
||||
{
|
||||
return view('livewire.project.service.index');
|
||||
}
|
||||
public function save() {
|
||||
public function save()
|
||||
{
|
||||
$this->service->save();
|
||||
$this->service->parse();
|
||||
$this->service->refresh();
|
||||
$this->emit('refreshEnvs');
|
||||
$this->emit('success', 'Service saved successfully.');
|
||||
$this->service->saveComposeConfigs();
|
||||
}
|
||||
public function submit() {
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->service->save();
|
||||
$this->emit('success', 'Service saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@ class LocalFileVolume extends BaseModel
|
||||
|
||||
public function service()
|
||||
{
|
||||
return $this->morphTo();
|
||||
return $this->morphTo('resource');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\ProxyTypes;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -80,6 +79,24 @@ class Service extends BaseModel
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class)->orderBy('key', 'asc');
|
||||
}
|
||||
public function workdir() {
|
||||
return service_configuration_dir() . "/{$this->uuid}";
|
||||
}
|
||||
public function saveComposeConfigs()
|
||||
{
|
||||
$workdir = $this->workdir();
|
||||
$commands[] = "mkdir -p $workdir";
|
||||
$commands[] = "cd $workdir";
|
||||
|
||||
$docker_compose_base64 = base64_encode($this->docker_compose);
|
||||
$commands[] = "echo $docker_compose_base64 | base64 -d > docker-compose.yml";
|
||||
$envs = $this->environment_variables()->get();
|
||||
$commands[] = "rm -f .env || true";
|
||||
foreach ($envs as $env) {
|
||||
$commands[] = "echo '{$env->key}={$env->value}' >> .env";
|
||||
}
|
||||
instant_remote_process($commands, $this->server);
|
||||
}
|
||||
public function parse(bool $isNew = false): Collection
|
||||
{
|
||||
ray('parsing');
|
||||
@@ -147,7 +164,7 @@ class Service extends BaseModel
|
||||
}
|
||||
}
|
||||
}
|
||||
$savedService->fqdn = $defaultUsableFqdn;
|
||||
$savedService->fqdn = $defaultUsableFqdn ?? null;
|
||||
$savedService->save();
|
||||
}
|
||||
}
|
||||
@@ -178,33 +195,58 @@ class Service extends BaseModel
|
||||
if ($serviceVolumes->count() > 0) {
|
||||
LocalPersistentVolume::whereResourceId($savedService->id)->whereResourceType(get_class($savedService))->delete();
|
||||
foreach ($serviceVolumes as $volume) {
|
||||
if (is_string($volume) && Str::startsWith($volume, './')) {
|
||||
// Local file
|
||||
$fsPath = Str::before($volume, ':');
|
||||
$volumePath = Str::of($volume)->after(':')->beforeLast(':');
|
||||
ray($fsPath, $volumePath);
|
||||
LocalFileVolume::updateOrCreate(
|
||||
[
|
||||
'mount_path' => $volumePath,
|
||||
'resource_id' => $savedService->id,
|
||||
'resource_type' => get_class($savedService)
|
||||
],
|
||||
[
|
||||
'fs_path' => $fsPath,
|
||||
'mount_path' => $volumePath,
|
||||
'resource_id' => $savedService->id,
|
||||
'resource_type' => get_class($savedService)
|
||||
]
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (is_string($volume)) {
|
||||
if (Str::startsWith($volume, './')) {
|
||||
$fsPath = Str::before($volume, ':');
|
||||
$volumePath = Str::of($volume)->after(':')->beforeLast(':');
|
||||
LocalFileVolume::updateOrCreate(
|
||||
[
|
||||
'mount_path' => $volumePath,
|
||||
'resource_id' => $savedService->id,
|
||||
'resource_type' => get_class($savedService)
|
||||
],
|
||||
[
|
||||
'fs_path' => $fsPath,
|
||||
'mount_path' => $volumePath,
|
||||
'resource_id' => $savedService->id,
|
||||
'resource_type' => get_class($savedService)
|
||||
]
|
||||
);
|
||||
$savedService->saveFileVolumes();
|
||||
continue;
|
||||
}
|
||||
$volumeName = Str::before($volume, ':');
|
||||
$volumePath = Str::after($volume, ':');
|
||||
}
|
||||
if (is_array($volume)) {
|
||||
$volumeName = data_get($volume, 'source');
|
||||
$volumePath = data_get($volume, 'target');
|
||||
$volumeContent = data_get($volume, 'content');
|
||||
if (Str::startsWith($volumeName, './')) {
|
||||
$payload = [
|
||||
'fs_path' => $volumeName,
|
||||
'mount_path' => $volumePath,
|
||||
|
||||
'resource_id' => $savedService->id,
|
||||
'resource_type' => get_class($savedService)
|
||||
];
|
||||
if ($volumeContent) {
|
||||
$payload['content'] = $volumeContent;
|
||||
}
|
||||
LocalFileVolume::updateOrCreate(
|
||||
[
|
||||
'mount_path' => $volumePath,
|
||||
'resource_id' => $savedService->id,
|
||||
'resource_type' => get_class($savedService)
|
||||
],
|
||||
$payload
|
||||
);
|
||||
if ($volumeContent) {
|
||||
$volume = data_forget($volume, 'content');
|
||||
}
|
||||
$savedService->saveFileVolumes();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$volumeExists = $serviceVolumes->contains(function ($_, $key) use ($volumeName) {
|
||||
@@ -369,6 +411,31 @@ class Service extends BaseModel
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
} else if ($variableName->startsWith('SERVICE_BASE64')) {
|
||||
$variableDefined = EnvironmentVariable::whereServiceId($this->id)->where('key', $variableName->value())->first();
|
||||
$length = Str::of($variableName)->after('SERVICE_BASE64_')->beforeLast('_')->value();
|
||||
if (is_numeric($length)) {
|
||||
$length = (int) $length;
|
||||
} else {
|
||||
$length = 1;
|
||||
}
|
||||
if (!$variableDefined) {
|
||||
$generatedValue = base64_encode(Str::password(length: $length, symbols: false));
|
||||
} else {
|
||||
$generatedValue = $variableDefined->value;
|
||||
}
|
||||
if (!$envs->has($variableName->value())) {
|
||||
$envs->put($variableName->value(), $generatedValue);
|
||||
EnvironmentVariable::updateOrCreate([
|
||||
'key' => $variableName->value(),
|
||||
'service_id' => $this->id,
|
||||
], [
|
||||
'value' => $generatedValue,
|
||||
'is_build_time' => false,
|
||||
'service_id' => $this->id,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
} else if ($variableName->startsWith('SERVICE_FQDN')) {
|
||||
if ($fqdns) {
|
||||
$number = Str::of($variableName)->after('SERVICE_FQDN')->afterLast('_')->value();
|
||||
@@ -419,6 +486,7 @@ class Service extends BaseModel
|
||||
data_set($service, 'restart', RESTART_MODE);
|
||||
data_set($service, 'container_name', $container_name);
|
||||
data_forget($service, 'documentation');
|
||||
data_forget($service, 'volumes.*.content');
|
||||
return $service;
|
||||
});
|
||||
$finalServices = [
|
||||
@@ -427,8 +495,11 @@ class Service extends BaseModel
|
||||
'volumes' => $composeVolumes->toArray(),
|
||||
'networks' => $composeNetworks->toArray(),
|
||||
];
|
||||
data_forget($yaml, 'services.*.volumes.*.content');
|
||||
$this->docker_compose_raw = Yaml::dump($yaml, 10, 2);
|
||||
$this->docker_compose = Yaml::dump($finalServices, 10, 2);
|
||||
$this->save();
|
||||
$this->saveComposeConfigs();
|
||||
$shouldBeDefined = collect([
|
||||
'envs' => $envs,
|
||||
'volumes' => $volumes,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
@@ -31,4 +30,8 @@ class ServiceApplication extends BaseModel
|
||||
{
|
||||
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||
}
|
||||
public function saveFileVolumes()
|
||||
{
|
||||
saveFileVolumesHelper($this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,4 +26,12 @@ class ServiceDatabase extends BaseModel
|
||||
{
|
||||
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||
}
|
||||
public function fileStorages()
|
||||
{
|
||||
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||
}
|
||||
public function saveFileVolumes()
|
||||
{
|
||||
saveFileVolumesHelper($this);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user