mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-01 05:56:16 +00:00
Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
2318e1b1df | |||
65898b0ee6 | |||
3124f8c795 |
@ -7,4 +7,6 @@ final class Webserver
|
||||
const NONE = 'none';
|
||||
|
||||
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: '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)]
|
||||
|
@ -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.');
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
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\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
|
||||
*/
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
42
composer.lock
generated
42
composer.lock
generated
@ -2808,16 +2808,16 @@
|
||||
},
|
||||
{
|
||||
"name": "league/commonmark",
|
||||
"version": "2.6.1",
|
||||
"version": "2.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/commonmark.git",
|
||||
"reference": "d990688c91cedfb69753ffc2512727ec646df2ad"
|
||||
"reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d990688c91cedfb69753ffc2512727ec646df2ad",
|
||||
"reference": "d990688c91cedfb69753ffc2512727ec646df2ad",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405",
|
||||
"reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2854,7 +2854,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "2.7-dev"
|
||||
"dev-main": "2.8-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@ -2911,7 +2911,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-12-29T14:10:59+00:00"
|
||||
"time": "2025-05-05T12:20:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/config",
|
||||
@ -3995,16 +3995,16 @@
|
||||
},
|
||||
{
|
||||
"name": "nette/utils",
|
||||
"version": "v4.0.5",
|
||||
"version": "v4.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nette/utils.git",
|
||||
"reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96"
|
||||
"reference": "ce708655043c7050eb050df361c5e313cf708309"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
|
||||
"reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
|
||||
"url": "https://api.github.com/repos/nette/utils/zipball/ce708655043c7050eb050df361c5e313cf708309",
|
||||
"reference": "ce708655043c7050eb050df361c5e313cf708309",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -4075,9 +4075,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nette/utils/issues",
|
||||
"source": "https://github.com/nette/utils/tree/v4.0.5"
|
||||
"source": "https://github.com/nette/utils/tree/v4.0.6"
|
||||
},
|
||||
"time": "2024-08-07T15:39:19+00:00"
|
||||
"time": "2025-03-30T21:06:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
@ -7172,16 +7172,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.31.0",
|
||||
"version": "v1.32.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
|
||||
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
|
||||
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
|
||||
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -7232,7 +7232,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0"
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -7248,7 +7248,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
"time": "2025-01-02T08:10:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php83",
|
||||
@ -11717,7 +11717,7 @@
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {},
|
||||
"stability-flags": [],
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
@ -11725,6 +11725,6 @@
|
||||
"ext-ftp": "*",
|
||||
"ext-intl": "*"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
||||
|
@ -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',
|
||||
|
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 |
@ -1,4 +1,4 @@
|
||||
if ! sudo DEBIAN_FRONTEND=noninteractive mysqldump -u root \`{{ $database }}\` > {{ $file }}.sql; then
|
||||
if ! sudo DEBIAN_FRONTEND=noninteractive mysqldump -u root {{ $database }} > {{ $file }}.sql; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
if ! sudo mariadb -e "CREATE DATABASE IF NOT EXISTS \`{{ $name }}\` CHARACTER SET '{{ $charset }}' COLLATE '{{ $collation }}'"; then
|
||||
if ! sudo mariadb -e "CREATE DATABASE IF NOT EXISTS {{ $name }} CHARACTER SET '{{ $charset }}' COLLATE '{{ $collation }}'"; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
if ! sudo mariadb -e "DROP DATABASE IF EXISTS \`{{ $name }}\`"; then
|
||||
if ! sudo mariadb -e "DROP DATABASE IF EXISTS {{ $name }}"; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
if ! sudo mariadb -e "GRANT ALL PRIVILEGES ON \`{{ $database }}\`.* TO '{{ $username }}'@'{{ $host }}'"; then
|
||||
if ! sudo mariadb -e "GRANT ALL PRIVILEGES ON {{ $database }}.* TO '{{ $username }}'@'{{ $host }}'"; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo DEBIAN_FRONTEND=noninteractive mariadb -u root \`{{ $database }}\` < {{ $file }}.sql; then
|
||||
if ! sudo DEBIAN_FRONTEND=noninteractive mariadb -u root {{ $database }} < {{ $file }}.sql; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
if ! sudo DEBIAN_FRONTEND=noninteractive mysqldump -u root \`{{ $database }}\` > {{ $file }}.sql; then
|
||||
if ! sudo DEBIAN_FRONTEND=noninteractive mysqldump -u root {{ $database }} > {{ $file }}.sql; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
if ! sudo mysql -e "CREATE DATABASE IF NOT EXISTS \`{{ $name }}\` CHARACTER SET '{{ $charset }}' COLLATE '{{ $collation }}'"; then
|
||||
if ! sudo mysql -e "CREATE DATABASE IF NOT EXISTS {{ $name }} CHARACTER SET '{{ $charset }}' COLLATE '{{ $collation }}'"; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
if ! sudo mysql -e "DROP DATABASE IF EXISTS \`{{ $name }}\`"; then
|
||||
if ! sudo mysql -e "DROP DATABASE IF EXISTS {{ $name }}"; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
if ! sudo mysql -e "GRANT ALL PRIVILEGES ON \`{{ $database }}\`.* TO '{{ $username }}'@'{{ $host }}'"; then
|
||||
if ! sudo mysql -e "GRANT ALL PRIVILEGES ON {{ $database }}.* TO '{{ $username }}'@'{{ $host }}'"; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! sudo DEBIAN_FRONTEND=noninteractive mysql -u root \`{{ $database }}\` < {{ $file }}.sql; then
|
||||
if ! sudo DEBIAN_FRONTEND=noninteractive mysql -u root {{ $database }} < {{ $file }}.sql; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
|
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
|
||||
{
|
||||
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
|
||||
{
|
||||
$this->actingAs($this->user);
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
*/
|
||||
|
Reference in New Issue
Block a user