diff --git a/app/Enums/Webserver.php b/app/Enums/Webserver.php index 886f85f7..aa2ed7df 100644 --- a/app/Enums/Webserver.php +++ b/app/Enums/Webserver.php @@ -7,4 +7,6 @@ final class Webserver const NONE = 'none'; const NGINX = 'nginx'; + + const CADDY = 'caddy'; } diff --git a/app/Http/Controllers/API/ServerController.php b/app/Http/Controllers/API/ServerController.php index 0fb42252..4dfcd0f1 100644 --- a/app/Http/Controllers/API/ServerController.php +++ b/app/Http/Controllers/API/ServerController.php @@ -52,7 +52,7 @@ public function index(Project $project): ResourceCollection #[BodyParam(name: 'port', description: 'SSH Port if the provider is custom')] #[BodyParam(name: 'name', description: 'The name of the server.', required: true)] #[BodyParam(name: 'os', description: 'The os of the server', required: true)] - #[BodyParam(name: 'webserver', description: 'Web server', required: true, enum: [Webserver::NONE, Webserver::NGINX])] + #[BodyParam(name: 'webserver', description: 'Web server', required: true, enum: [Webserver::NONE, Webserver::NGINX, Webserver::CADDY])] #[BodyParam(name: 'database', description: 'Database', required: true, enum: [Database::NONE, Database::MYSQL57, Database::MYSQL80, Database::MARIADB103, Database::MARIADB104, Database::MARIADB103, Database::POSTGRESQL12, Database::POSTGRESQL13, Database::POSTGRESQL14, Database::POSTGRESQL15, Database::POSTGRESQL16], )] #[BodyParam(name: 'php', description: 'PHP version', required: true, enum: [PHP::V70, PHP::V71, PHP::V72, PHP::V73, PHP::V74, PHP::V80, PHP::V81, PHP::V82, PHP::V83])] #[ResponseFromApiResource(ServerResource::class, Server::class)] diff --git a/app/SSH/Services/Webserver/AbstractWebserver.php b/app/SSH/Services/Webserver/AbstractWebserver.php index deb79fbd..dea520ec 100755 --- a/app/SSH/Services/Webserver/AbstractWebserver.php +++ b/app/SSH/Services/Webserver/AbstractWebserver.php @@ -3,5 +3,37 @@ namespace App\SSH\Services\Webserver; use App\SSH\Services\AbstractService; +use Closure; -abstract class AbstractWebserver extends AbstractService implements Webserver {} +abstract class AbstractWebserver extends AbstractService implements Webserver +{ + public function creationRules(array $input): array + { + return [ + 'type' => [ + 'required', + function (string $attribute, mixed $value, Closure $fail): void { + $webserverExists = $this->service->server->webserver(); + if ($webserverExists) { + $fail('You already have a webserver service on the server.'); + } + }, + ], + ]; + } + + public function deletionRules(): array + { + return [ + 'service' => [ + function (string $attribute, mixed $value, Closure $fail): void { + $hasSite = $this->service->server->sites() + ->exists(); + if ($hasSite) { + $fail('Cannot uninstall webserver while you have websites using it.'); + } + }, + ], + ]; + } +} diff --git a/app/SSH/Services/Webserver/Caddy.php b/app/SSH/Services/Webserver/Caddy.php new file mode 100755 index 00000000..e8ea0285 --- /dev/null +++ b/app/SSH/Services/Webserver/Caddy.php @@ -0,0 +1,195 @@ +service->server->ssh()->exec( + view('ssh.services.webserver.caddy.install-caddy'), + 'install-caddy' + ); + + $this->service->server->ssh()->write( + '/etc/caddy/Caddyfile', + view('ssh.services.webserver.caddy.caddy'), + 'root' + ); + + $this->service->server->ssh()->write( + '/etc/systemd/system/caddy.service', + view('ssh.services.webserver.caddy.caddy-systemd'), + 'root' + ); + + $this->service->server->systemd()->reload(); + + $this->service->server->systemd()->restart('caddy'); + + $this->service->server->os()->cleanup(); + } + + /** + * @throws SSHError + */ + public function uninstall(): void + { + $this->service->server->ssh()->exec( + view('ssh.services.webserver.caddy.uninstall-caddy'), + 'uninstall-caddy' + ); + $this->service->server->os()->cleanup(); + } + + /** + * @throws SSHError + */ + public function createVHost(Site $site): void + { + // We need to get the isolated user first, if the site is isolated + // otherwise, use the default ssh user + $ssh = $this->service->server->ssh($site->user); + + $ssh->exec( + view('ssh.services.webserver.caddy.create-path', [ + 'path' => $site->path, + ]), + 'create-path', + $site->id + ); + + $this->service->server->ssh()->write( + '/etc/caddy/sites-available/'.$site->domain, + $this->generateVhost($site), + 'root' + ); + + $this->service->server->ssh()->exec( + view('ssh.services.webserver.caddy.create-vhost', [ + 'domain' => $site->domain, + ]), + 'create-vhost', + $site->id + ); + } + + /** + * @throws SSHError + */ + public function updateVHost(Site $site, ?string $vhost = null): void + { + $this->service->server->ssh()->write( + '/etc/caddy/sites-available/'.$site->domain, + $vhost ?? $this->generateVhost($site), + 'root' + ); + + $this->service->server->systemd()->restart('caddy'); + } + + /** + * @throws SSHError + */ + public function getVHost(Site $site): string + { + return $this->service->server->ssh()->exec( + view('ssh.services.webserver.caddy.get-vhost', [ + 'domain' => $site->domain, + ]), + ); + } + + /** + * @throws SSHError + */ + public function deleteSite(Site $site): void + { + $this->service->server->ssh()->exec( + view('ssh.services.webserver.caddy.delete-site', [ + 'domain' => $site->domain, + 'path' => $site->path, + ]), + 'delete-vhost', + $site->id + ); + $this->service->restart(); + } + + /** + * @throws SSHError + */ + public function changePHPVersion(Site $site, string $version): void + { + $this->service->server->ssh()->exec( + view('ssh.services.webserver.caddy.change-php-version', [ + 'domain' => $site->domain, + 'oldVersion' => $site->php_version, + 'newVersion' => $version, + ]), + 'change-php-version', + $site->id + ); + } + + /** + * @throws SSHError + */ + public function setupSSL(Ssl $ssl): void + { + if ($ssl->type == 'custom') { + $ssl->certificate_path = '/etc/ssl/'.$ssl->id.'/cert.pem'; + $ssl->pk_path = '/etc/ssl/'.$ssl->id.'/privkey.pem'; + $ssl->save(); + $command = view('ssh.services.webserver.caddy.create-custom-ssl', [ + 'path' => dirname($ssl->certificate_path), + 'certificate' => $ssl->certificate, + 'pk' => $ssl->pk, + 'certificatePath' => $ssl->certificate_path, + 'pkPath' => $ssl->pk_path, + ]); + $result = $this->service->server->ssh()->setLog($ssl->log)->exec( + $command, + 'create-ssl', + $ssl->site_id + ); + if (! $ssl->validateSetup($result)) { + throw new SSLCreationException; + } + } + } + + /** + * @throws Throwable + */ + public function removeSSL(Ssl $ssl): void + { + if ($ssl->certificate_path) { + $this->service->server->ssh()->exec( + 'sudo rm -rf '.dirname($ssl->certificate_path), + 'remove-ssl', + $ssl->site_id + ); + } + + $this->updateVHost($ssl->site); + } + + private function generateVhost(Site $site): string + { + $vhost = view('ssh.services.webserver.caddy.vhost', [ + 'site' => $site, + ]); + + return format_nginx_config($vhost); + } +} diff --git a/app/SSH/Services/Webserver/Nginx.php b/app/SSH/Services/Webserver/Nginx.php index 845249a7..4904924e 100755 --- a/app/SSH/Services/Webserver/Nginx.php +++ b/app/SSH/Services/Webserver/Nginx.php @@ -6,7 +6,6 @@ use App\Exceptions\SSLCreationException; use App\Models\Site; use App\Models\Ssl; -use Closure; use Throwable; class Nginx extends AbstractWebserver @@ -34,21 +33,6 @@ public function install(): void $this->service->server->os()->cleanup(); } - public function deletionRules(): array - { - return [ - 'service' => [ - function (string $attribute, mixed $value, Closure $fail): void { - $hasSite = $this->service->server->sites() - ->exists(); - if ($hasSite) { - $fail('Cannot uninstall webserver while you have websites using it.'); - } - }, - ], - ]; - } - /** * @throws SSHError */ diff --git a/app/SSH/Systemd/Systemd.php b/app/SSH/Systemd/Systemd.php index fcab26e7..484a9157 100644 --- a/app/SSH/Systemd/Systemd.php +++ b/app/SSH/Systemd/Systemd.php @@ -87,4 +87,16 @@ public function disable(string $unit): string return $this->server->ssh()->exec($command, sprintf('disable-%s', $unit)); } + + /** + * @throws SSHError + */ + public function reload(): string + { + $command = <<<'EOD' + sudo systemctl daemon-reload + EOD; + + return $this->server->ssh()->exec($command, 'reload-systemctl'); + } } diff --git a/config/core.php b/config/core.php index 1d72e830..49dc3c3d 100755 --- a/config/core.php +++ b/config/core.php @@ -26,6 +26,7 @@ 'webservers' => [ \App\Enums\Webserver::NONE, \App\Enums\Webserver::NGINX, + \App\Enums\Webserver::CADDY, ], 'php_versions' => [ \App\Enums\PHP::NONE, @@ -173,6 +174,7 @@ */ 'service_types' => [ 'nginx' => 'webserver', + 'caddy' => 'webserver', 'mysql' => 'database', 'mariadb' => 'database', 'postgresql' => 'database', @@ -186,6 +188,7 @@ ], 'service_handlers' => [ 'nginx' => \App\SSH\Services\Webserver\Nginx::class, + 'caddy' => \App\SSH\Services\Webserver\Caddy::class, 'mysql' => \App\SSH\Services\Database\Mysql::class, 'mariadb' => \App\SSH\Services\Database\Mariadb::class, 'postgresql' => \App\SSH\Services\Database\Postgresql::class, @@ -201,6 +204,9 @@ 'nginx' => [ 'latest', ], + 'caddy' => [ + 'latest', + ], 'mysql' => [ '5.7', '8.0', @@ -273,6 +279,17 @@ 'latest' => 'nginx', ], ], + 'caddy' => [ + \App\Enums\OperatingSystem::UBUNTU20 => [ + 'latest' => 'caddy', + ], + \App\Enums\OperatingSystem::UBUNTU22 => [ + 'latest' => 'caddy', + ], + \App\Enums\OperatingSystem::UBUNTU24 => [ + 'latest' => 'caddy', + ], + ], 'mysql' => [ \App\Enums\OperatingSystem::UBUNTU20 => [ '5.7' => 'mysql', diff --git a/resources/svg/caddy.svg b/resources/svg/caddy.svg new file mode 100644 index 00000000..54e7ba57 --- /dev/null +++ b/resources/svg/caddy.svg @@ -0,0 +1,9 @@ + + New Project + + + + + + \ No newline at end of file diff --git a/resources/views/ssh/services/webserver/caddy/caddy-systemd.blade.php b/resources/views/ssh/services/webserver/caddy/caddy-systemd.blade.php new file mode 100755 index 00000000..35c34238 --- /dev/null +++ b/resources/views/ssh/services/webserver/caddy/caddy-systemd.blade.php @@ -0,0 +1,19 @@ +[Unit] +Description=Caddy web server +Documentation=https://caddyserver.com/docs/ +After=network.target network-online.target +Requires=network-online.target + +[Service] +Type=notify +ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile +ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile +TimeoutStopSec=5s +LimitNOFILE=1048576 +LimitNPROC=512 +PrivateTmp=true +ProtectSystem=full +AmbientCapabilities=CAP_NET_BIND_SERVICE + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/resources/views/ssh/services/webserver/caddy/caddy.blade.php b/resources/views/ssh/services/webserver/caddy/caddy.blade.php new file mode 100755 index 00000000..3f926a9c --- /dev/null +++ b/resources/views/ssh/services/webserver/caddy/caddy.blade.php @@ -0,0 +1,59 @@ +{ + # Global Errors Log + log { + output file /var/log/caddy/errors.log { + roll_size 100MB + roll_keep 10 + roll_keep_for 720h # 30 days + } + format json { + time_format iso8601 + } + level ERROR + exclude http.log.access + } +} + +# Common snippets +(access_log) { + log { + output file /var/log/caddy/{args[0]}-access.log { + roll_size 100MB + roll_keep 10 + roll_keep_for 720h # 30 days + } + format json { + time_format iso8601 + } + } +} + +(security_headers) { + header { + # Remove server and software information + -Server + -X-Powered-By + -Via + + # Security headers + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + X-Content-Type-Options "nosniff" + X-Frame-Options "SAMEORIGIN" + Referrer-Policy "strict-origin-when-cross-origin" + X-XSS-Protection "1; mode=block" + Content-Security-Policy "upgrade-insecure-requests" + + # Enable compression + defer + } +} + +(compression) { + encode { + gzip 6 + zstd + minimum_length 1024 + } +} + +import sites-enabled/* \ No newline at end of file diff --git a/resources/views/ssh/services/webserver/caddy/change-php-version.blade.php b/resources/views/ssh/services/webserver/caddy/change-php-version.blade.php new file mode 100755 index 00000000..32976736 --- /dev/null +++ b/resources/views/ssh/services/webserver/caddy/change-php-version.blade.php @@ -0,0 +1,9 @@ +if ! sudo sed -i 's/php{{ $oldVersion }}/php{{ $newVersion }}/g' /etc/caddy/sites-available/{{ $domain }}; then + echo 'VITO_SSH_ERROR' && exit 1 +fi + +if ! sudo service caddy restart; then + echo 'VITO_SSH_ERROR' && exit 1 +fi + +echo "PHP Version Changed to {{ $newVersion }}" diff --git a/resources/views/ssh/services/webserver/caddy/create-custom-ssl.blade.php b/resources/views/ssh/services/webserver/caddy/create-custom-ssl.blade.php new file mode 100644 index 00000000..e5d55568 --- /dev/null +++ b/resources/views/ssh/services/webserver/caddy/create-custom-ssl.blade.php @@ -0,0 +1,13 @@ +if ! sudo mkdir -p {{ $path }}; then + echo 'VITO_SSH_ERROR' && exit 1 +fi + +if ! echo "{{ $certificate }}" | sudo tee {{ $certificatePath }}; then + echo 'VITO_SSH_ERROR' && exit 1 +fi + +if ! echo "{{ $pk }}" | sudo tee {{ $pkPath }}; then + echo 'VITO_SSH_ERROR' && exit 1 +fi + +echo "Successfully received certificate." diff --git a/resources/views/ssh/services/webserver/caddy/create-path.blade.php b/resources/views/ssh/services/webserver/caddy/create-path.blade.php new file mode 100644 index 00000000..6fd38a96 --- /dev/null +++ b/resources/views/ssh/services/webserver/caddy/create-path.blade.php @@ -0,0 +1,7 @@ +export DEBIAN_FRONTEND=noninteractive + +rm -rf {{ $path }} + +mkdir {{ $path }} + +chmod -R 755 {{ $path }} diff --git a/resources/views/ssh/services/webserver/caddy/create-vhost.blade.php b/resources/views/ssh/services/webserver/caddy/create-vhost.blade.php new file mode 100755 index 00000000..97b8eb72 --- /dev/null +++ b/resources/views/ssh/services/webserver/caddy/create-vhost.blade.php @@ -0,0 +1,7 @@ +if ! sudo ln -s /etc/caddy/sites-available/{{ $domain }} /etc/caddy/sites-enabled/; then + echo 'VITO_SSH_ERROR' && exit 1 +fi + +if ! sudo service caddy restart; then + echo 'VITO_SSH_ERROR' && exit 1 +fi diff --git a/resources/views/ssh/services/webserver/caddy/delete-site.blade.php b/resources/views/ssh/services/webserver/caddy/delete-site.blade.php new file mode 100755 index 00000000..2c01be6f --- /dev/null +++ b/resources/views/ssh/services/webserver/caddy/delete-site.blade.php @@ -0,0 +1,7 @@ +rm -rf {{ $path }} + +sudo rm /etc/caddy/sites-available/{{ $domain }} + +sudo rm /etc/caddy/sites-enabled/{{ $domain }} + +echo "Site deleted" diff --git a/resources/views/ssh/services/webserver/caddy/get-vhost.blade.php b/resources/views/ssh/services/webserver/caddy/get-vhost.blade.php new file mode 100755 index 00000000..7d4266b4 --- /dev/null +++ b/resources/views/ssh/services/webserver/caddy/get-vhost.blade.php @@ -0,0 +1 @@ +cat /etc/caddy/sites-available/{{ $domain }} \ No newline at end of file diff --git a/resources/views/ssh/services/webserver/caddy/install-caddy.blade.php b/resources/views/ssh/services/webserver/caddy/install-caddy.blade.php new file mode 100755 index 00000000..1bf17b52 --- /dev/null +++ b/resources/views/ssh/services/webserver/caddy/install-caddy.blade.php @@ -0,0 +1,20 @@ +# Add Caddy's GPG key and repository +curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg + +curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \ + +sudo tee /etc/apt/sources.list.d/caddy-stable.list + +# Install required packages +sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \ + debian-keyring debian-archive-keyring apt-transport-https curl + +# Update package list +sudo DEBIAN_FRONTEND=noninteractive apt-get update -y + +# Install Caddy +sudo DEBIAN_FRONTEND=noninteractive apt-get install caddy -y + +sudo mkdir /etc/caddy/sites-available + +sudo mkdir /etc/caddy/sites-enabled diff --git a/resources/views/ssh/services/webserver/caddy/redirects.blade.php b/resources/views/ssh/services/webserver/caddy/redirects.blade.php new file mode 100644 index 00000000..d07111d0 --- /dev/null +++ b/resources/views/ssh/services/webserver/caddy/redirects.blade.php @@ -0,0 +1,3 @@ +@foreach($site->activeRedirects as $redirect) + redir {{ $redirect->from }} {{ $redirect->to }} {{ $redirect->mode }} +@endforeach diff --git a/resources/views/ssh/services/webserver/caddy/uninstall-caddy.blade.php b/resources/views/ssh/services/webserver/caddy/uninstall-caddy.blade.php new file mode 100755 index 00000000..4bf7c994 --- /dev/null +++ b/resources/views/ssh/services/webserver/caddy/uninstall-caddy.blade.php @@ -0,0 +1,12 @@ +sudo service caddy stop + +sudo DEBIAN_FRONTEND=noninteractive sudo apt remove caddy -y + +sudo rm -rf /etc/caddy +sudo rm -rf /var/log/caddy +sudo rm -rf /var/lib/caddy +sudo rm -rf /var/cache/caddy +sudo rm -rf /usr/share/caddy +sudo rm -rf /etc/systemd/system/caddy.service + +sudo systemctl daemon-reload diff --git a/resources/views/ssh/services/webserver/caddy/vhost.blade.php b/resources/views/ssh/services/webserver/caddy/vhost.blade.php new file mode 100755 index 00000000..665ea30e --- /dev/null +++ b/resources/views/ssh/services/webserver/caddy/vhost.blade.php @@ -0,0 +1,47 @@ +{{ $site->domain }} {{ $site->getAliasesString() }} { + @if ($site->activeSsl) + tls {{ $site->activeSsl->certificate_path }} {{ $site->activeSsl->pk_path }} + @endif + @if ($site->activeSsl && $site->force_ssl) + redir @http https://{host}{uri} permanent + @endif + import access_log {{ $site->domain }} + import compression + import security_headers + @if ($site->type()->language() === 'php') + root * {{ $site->getWebDirectoryPath() }} + @php + $phpSocket = "unix//var/run/php/php{$site->php_version}-fpm.sock"; + if ($site->isIsolated()) { + $phpSocket = "unix//run/php/php{$site->php_version}-fpm-{$site->user}.sock"; + } + @endphp + try_files {path} {path}/ /index.php?{query} + php_fastcgi {{ $phpSocket }} + file_server + @endif + @if ($site->type === \App\Enums\SiteType::LOAD_BALANCER) + reverse_proxy { + @if ($site->loadBalancerServers()->count() > 0) + @foreach($site->loadBalancerServers as $server) + to {{ $server->ip }}:{{ $server->port }} + @endforeach + @else + to 127.0.0.1 + @endif + @switch($site->type_data['method'] ?? \App\Enums\LoadBalancerMethod::ROUND_ROBIN) + @case(\App\Enums\LoadBalancerMethod::LEAST_CONNECTIONS) + lb_policy least_conn + @break + @case(\App\Enums\LoadBalancerMethod::IP_HASH) + lb_policy ip_hash + @break + @default + lb_policy round_robin + @endswitch + header_up Host {host} + header_up X-Real-IP {remote} + } + @endif + @include('ssh.services.webserver.caddy.redirects', ['site' => $site]) +} \ No newline at end of file diff --git a/tests/Feature/API/ServerTest.php b/tests/Feature/API/ServerTest.php index f5f02025..73d46194 100644 --- a/tests/Feature/API/ServerTest.php +++ b/tests/Feature/API/ServerTest.php @@ -68,6 +68,31 @@ public function test_create_server(): void ]); } + public function test_create_server_with_caddy(): void + { + Sanctum::actingAs($this->user, ['read', 'write']); + + SSH::fake('Active: active'); // fake output for service installations + + $this->json('POST', route('api.projects.servers.create', [ + 'project' => $this->user->current_project_id, + ]), [ + 'provider' => ServerProvider::CUSTOM, + 'name' => 'test', + 'ip' => '1.1.1.1', + 'port' => '22', + 'os' => OperatingSystem::UBUNTU22, + 'webserver' => Webserver::CADDY, + 'database' => Database::MYSQL80, + 'php' => '8.2', + ]) + ->assertSuccessful() + ->assertJsonFragment([ + 'name' => 'test', + 'type' => ServerType::REGULAR, + ]); + } + public function test_delete_server(): void { Sanctum::actingAs($this->user, ['read', 'write']); diff --git a/tests/Feature/ServerTest.php b/tests/Feature/ServerTest.php index f7b2ebfd..b8784cb5 100644 --- a/tests/Feature/ServerTest.php +++ b/tests/Feature/ServerTest.php @@ -83,6 +83,63 @@ public function test_create_regular_server(): void ]); } + public function test_create_regular_server_with_caddy(): void + { + $this->actingAs($this->user); + + SSH::fake('Active: active'); // fake output for service installations + + Livewire::test(Index::class) + ->callAction('create', [ + 'provider' => ServerProvider::CUSTOM, + 'name' => 'caddy-test', + 'ip' => '2.2.2.2', + 'port' => '22', + 'os' => OperatingSystem::UBUNTU22, + 'webserver' => Webserver::CADDY, + 'database' => Database::MYSQL80, + 'php' => '8.2', + ]) + ->assertSuccessful(); + + $this->assertDatabaseHas('servers', [ + 'name' => 'caddy-test', + 'ip' => '2.2.2.2', + 'status' => ServerStatus::READY, + ]); + + $this->assertDatabaseHas('services', [ + 'server_id' => 2, + 'type' => 'php', + 'version' => '8.2', + 'status' => ServiceStatus::READY, + ]); + + $this->assertDatabaseHas('services', [ + 'server_id' => 2, + 'type' => 'webserver', + 'name' => 'caddy', + 'version' => 'latest', + 'status' => ServiceStatus::READY, + ]); + + $this->assertDatabaseHas('services', [ + 'server_id' => 2, + 'type' => 'database', + 'name' => 'mysql', + 'version' => '8.0', + 'status' => ServiceStatus::READY, + ]); + + $this->assertDatabaseHas('services', [ + 'server_id' => 2, + 'type' => 'firewall', + 'name' => 'ufw', + 'version' => 'latest', + 'status' => ServiceStatus::READY, + ]); + } + public function test_delete_server(): void { $this->actingAs($this->user); diff --git a/tests/Unit/Actions/Service/InstallTest.php b/tests/Unit/Actions/Service/InstallTest.php index a1efeda3..497f9922 100644 --- a/tests/Unit/Actions/Service/InstallTest.php +++ b/tests/Unit/Actions/Service/InstallTest.php @@ -75,6 +75,29 @@ public function test_install_nginx(): void $this->assertNotNull($service->type_data); } + public function test_install_caddy(): void + { + $this->server->webserver()->delete(); + + SSH::fake('Active: active'); + + $service = app(Install::class)->install($this->server, [ + 'type' => 'webserver', + 'name' => 'caddy', + 'version' => 'latest', + ]); + + $this->assertDatabaseHas('services', [ + 'server_id' => $this->server->id, + 'name' => 'caddy', + 'type' => 'webserver', + 'version' => 'latest', + 'status' => ServiceStatus::READY, + ]); + + $this->assertNotNull($service->type_data); + } + public function test_install_mysql(): void { $this->server->database()->delete(); diff --git a/tests/Unit/Actions/Service/UninstallTest.php b/tests/Unit/Actions/Service/UninstallTest.php index c8a6ae6c..ba59868c 100644 --- a/tests/Unit/Actions/Service/UninstallTest.php +++ b/tests/Unit/Actions/Service/UninstallTest.php @@ -51,6 +51,18 @@ public function test_cannot_uninstall_nginx(): void app(Uninstall::class)->uninstall($this->server->webserver()); } + /** + * Cannot uninstall caddy because some sites using it + */ + public function test_cannot_uninstall_caddy(): void + { + SSH::fake(); + + $this->expectException(ValidationException::class); + + app(Uninstall::class)->uninstall($this->server->webserver()); + } + /** * Cannot uninstall mysql because some databases exist */