mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-01 14:06:15 +00:00
Add Caddy Server Support Alongside Nginx (#600)
* added enum * add config for caddy * add svg icon * add caddy service class * wip * install caddy * create base Caddyfile with common snippets * Create a systemd service to run Caddy in the background. * create uninstall file * wip * create path * create vhost * get vhost * delete site * add php version change file * add custom ssl * create redirect file * add vhost for caddy site & load balancer * update svg * fix caddy icon * fix style * add systemctl reload method * Reload systemd after modifying the Caddy service file. * add caddy * added tests * format with pint * prevent multiple web server installations * added error log & access log
This commit is contained in:
@ -7,4 +7,6 @@ final class Webserver
|
|||||||
const NONE = 'none';
|
const NONE = 'none';
|
||||||
|
|
||||||
const NGINX = 'nginx';
|
const NGINX = 'nginx';
|
||||||
|
|
||||||
|
const CADDY = 'caddy';
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ public function index(Project $project): ResourceCollection
|
|||||||
#[BodyParam(name: 'port', description: 'SSH Port if the provider is custom')]
|
#[BodyParam(name: 'port', description: 'SSH Port if the provider is custom')]
|
||||||
#[BodyParam(name: 'name', description: 'The name of the server.', required: true)]
|
#[BodyParam(name: 'name', description: 'The name of the server.', required: true)]
|
||||||
#[BodyParam(name: 'os', description: 'The os 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: '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])]
|
#[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)]
|
#[ResponseFromApiResource(ServerResource::class, Server::class)]
|
||||||
|
@ -3,5 +3,37 @@
|
|||||||
namespace App\SSH\Services\Webserver;
|
namespace App\SSH\Services\Webserver;
|
||||||
|
|
||||||
use App\SSH\Services\AbstractService;
|
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.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
195
app/SSH/Services/Webserver/Caddy.php
Executable file
195
app/SSH/Services/Webserver/Caddy.php
Executable file
@ -0,0 +1,195 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\SSH\Services\Webserver;
|
||||||
|
|
||||||
|
use App\Exceptions\SSHError;
|
||||||
|
use App\Exceptions\SSLCreationException;
|
||||||
|
use App\Models\Site;
|
||||||
|
use App\Models\Ssl;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class Caddy extends AbstractWebserver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws SSHError
|
||||||
|
*/
|
||||||
|
public function install(): void
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,6 @@
|
|||||||
use App\Exceptions\SSLCreationException;
|
use App\Exceptions\SSLCreationException;
|
||||||
use App\Models\Site;
|
use App\Models\Site;
|
||||||
use App\Models\Ssl;
|
use App\Models\Ssl;
|
||||||
use Closure;
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
class Nginx extends AbstractWebserver
|
class Nginx extends AbstractWebserver
|
||||||
@ -34,21 +33,6 @@ public function install(): void
|
|||||||
$this->service->server->os()->cleanup();
|
$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
|
* @throws SSHError
|
||||||
*/
|
*/
|
||||||
|
@ -87,4 +87,16 @@ public function disable(string $unit): string
|
|||||||
|
|
||||||
return $this->server->ssh()->exec($command, sprintf('disable-%s', $unit));
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
'webservers' => [
|
'webservers' => [
|
||||||
\App\Enums\Webserver::NONE,
|
\App\Enums\Webserver::NONE,
|
||||||
\App\Enums\Webserver::NGINX,
|
\App\Enums\Webserver::NGINX,
|
||||||
|
\App\Enums\Webserver::CADDY,
|
||||||
],
|
],
|
||||||
'php_versions' => [
|
'php_versions' => [
|
||||||
\App\Enums\PHP::NONE,
|
\App\Enums\PHP::NONE,
|
||||||
@ -173,6 +174,7 @@
|
|||||||
*/
|
*/
|
||||||
'service_types' => [
|
'service_types' => [
|
||||||
'nginx' => 'webserver',
|
'nginx' => 'webserver',
|
||||||
|
'caddy' => 'webserver',
|
||||||
'mysql' => 'database',
|
'mysql' => 'database',
|
||||||
'mariadb' => 'database',
|
'mariadb' => 'database',
|
||||||
'postgresql' => 'database',
|
'postgresql' => 'database',
|
||||||
@ -186,6 +188,7 @@
|
|||||||
],
|
],
|
||||||
'service_handlers' => [
|
'service_handlers' => [
|
||||||
'nginx' => \App\SSH\Services\Webserver\Nginx::class,
|
'nginx' => \App\SSH\Services\Webserver\Nginx::class,
|
||||||
|
'caddy' => \App\SSH\Services\Webserver\Caddy::class,
|
||||||
'mysql' => \App\SSH\Services\Database\Mysql::class,
|
'mysql' => \App\SSH\Services\Database\Mysql::class,
|
||||||
'mariadb' => \App\SSH\Services\Database\Mariadb::class,
|
'mariadb' => \App\SSH\Services\Database\Mariadb::class,
|
||||||
'postgresql' => \App\SSH\Services\Database\Postgresql::class,
|
'postgresql' => \App\SSH\Services\Database\Postgresql::class,
|
||||||
@ -201,6 +204,9 @@
|
|||||||
'nginx' => [
|
'nginx' => [
|
||||||
'latest',
|
'latest',
|
||||||
],
|
],
|
||||||
|
'caddy' => [
|
||||||
|
'latest',
|
||||||
|
],
|
||||||
'mysql' => [
|
'mysql' => [
|
||||||
'5.7',
|
'5.7',
|
||||||
'8.0',
|
'8.0',
|
||||||
@ -273,6 +279,17 @@
|
|||||||
'latest' => 'nginx',
|
'latest' => 'nginx',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
'caddy' => [
|
||||||
|
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||||
|
'latest' => 'caddy',
|
||||||
|
],
|
||||||
|
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||||
|
'latest' => 'caddy',
|
||||||
|
],
|
||||||
|
\App\Enums\OperatingSystem::UBUNTU24 => [
|
||||||
|
'latest' => 'caddy',
|
||||||
|
],
|
||||||
|
],
|
||||||
'mysql' => [
|
'mysql' => [
|
||||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||||
'5.7' => 'mysql',
|
'5.7' => 'mysql',
|
||||||
|
9
resources/svg/caddy.svg
Normal file
9
resources/svg/caddy.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 17 KiB |
19
resources/views/ssh/services/webserver/caddy/caddy-systemd.blade.php
Executable file
19
resources/views/ssh/services/webserver/caddy/caddy-systemd.blade.php
Executable file
@ -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
|
59
resources/views/ssh/services/webserver/caddy/caddy.blade.php
Executable file
59
resources/views/ssh/services/webserver/caddy/caddy.blade.php
Executable file
@ -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/*
|
@ -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 }}"
|
@ -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."
|
@ -0,0 +1,7 @@
|
|||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
rm -rf {{ $path }}
|
||||||
|
|
||||||
|
mkdir {{ $path }}
|
||||||
|
|
||||||
|
chmod -R 755 {{ $path }}
|
7
resources/views/ssh/services/webserver/caddy/create-vhost.blade.php
Executable file
7
resources/views/ssh/services/webserver/caddy/create-vhost.blade.php
Executable file
@ -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
|
7
resources/views/ssh/services/webserver/caddy/delete-site.blade.php
Executable file
7
resources/views/ssh/services/webserver/caddy/delete-site.blade.php
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
rm -rf {{ $path }}
|
||||||
|
|
||||||
|
sudo rm /etc/caddy/sites-available/{{ $domain }}
|
||||||
|
|
||||||
|
sudo rm /etc/caddy/sites-enabled/{{ $domain }}
|
||||||
|
|
||||||
|
echo "Site deleted"
|
1
resources/views/ssh/services/webserver/caddy/get-vhost.blade.php
Executable file
1
resources/views/ssh/services/webserver/caddy/get-vhost.blade.php
Executable file
@ -0,0 +1 @@
|
|||||||
|
cat /etc/caddy/sites-available/{{ $domain }}
|
20
resources/views/ssh/services/webserver/caddy/install-caddy.blade.php
Executable file
20
resources/views/ssh/services/webserver/caddy/install-caddy.blade.php
Executable file
@ -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
|
@ -0,0 +1,3 @@
|
|||||||
|
@foreach($site->activeRedirects as $redirect)
|
||||||
|
redir {{ $redirect->from }} {{ $redirect->to }} {{ $redirect->mode }}
|
||||||
|
@endforeach
|
12
resources/views/ssh/services/webserver/caddy/uninstall-caddy.blade.php
Executable file
12
resources/views/ssh/services/webserver/caddy/uninstall-caddy.blade.php
Executable file
@ -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
|
47
resources/views/ssh/services/webserver/caddy/vhost.blade.php
Executable file
47
resources/views/ssh/services/webserver/caddy/vhost.blade.php
Executable file
@ -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])
|
||||||
|
}
|
@ -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
|
public function test_delete_server(): void
|
||||||
{
|
{
|
||||||
Sanctum::actingAs($this->user, ['read', 'write']);
|
Sanctum::actingAs($this->user, ['read', 'write']);
|
||||||
|
@ -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
|
public function test_delete_server(): void
|
||||||
{
|
{
|
||||||
$this->actingAs($this->user);
|
$this->actingAs($this->user);
|
||||||
|
@ -75,6 +75,29 @@ public function test_install_nginx(): void
|
|||||||
$this->assertNotNull($service->type_data);
|
$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
|
public function test_install_mysql(): void
|
||||||
{
|
{
|
||||||
$this->server->database()->delete();
|
$this->server->database()->delete();
|
||||||
|
@ -51,6 +51,18 @@ public function test_cannot_uninstall_nginx(): void
|
|||||||
app(Uninstall::class)->uninstall($this->server->webserver());
|
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
|
* Cannot uninstall mysql because some databases exist
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user