148 lines
4.7 KiB
PHP
148 lines
4.7 KiB
PHP
<?php
|
||
|
||
namespace App\Console\Commands;
|
||
|
||
use Illuminate\Console\Command;
|
||
use Symfony\Component\Process\Process;
|
||
|
||
class ProvisionCert extends Command
|
||
{
|
||
protected $signature = 'mailwolt:provision-cert
|
||
{domain : z.B. mail.example.com}
|
||
{--email= : E-Mail für Let\'s Encrypt}
|
||
{--self-signed : Statt LE ein self-signed Zertifikat erzeugen}';
|
||
|
||
protected $description = 'Beschafft ein Zertifikat (LE oder self-signed) und setzt Nginx darauf.';
|
||
|
||
private string $sslDir = '/etc/mailwolt/ssl';
|
||
private string $certPath;
|
||
private string $keyPath;
|
||
|
||
public function handle(): int
|
||
{
|
||
$domain = $this->argument('domain');
|
||
$email = (string)$this->option('email');
|
||
$self = (bool)$this->option('self-signed');
|
||
|
||
$this->certPath = "{$this->sslDir}/cert.pem";
|
||
$this->keyPath = "{$this->sslDir}/key.pem";
|
||
|
||
if (!is_dir($this->sslDir)) {
|
||
@mkdir($this->sslDir, 0750, true);
|
||
@chgrp($this->sslDir, 'www-data');
|
||
}
|
||
|
||
if ($self) {
|
||
return $this->issueSelfSigned($domain);
|
||
}
|
||
|
||
// Versuche Let's Encrypt – bei Fehler fallback self-signed
|
||
$rc = $this->issueLetsEncrypt($domain, $email);
|
||
if ($rc !== 0) {
|
||
$this->warn('Let’s Encrypt fehlgeschlagen – erstelle Self-Signed als Fallback…');
|
||
return $this->issueSelfSigned($domain);
|
||
}
|
||
return $rc;
|
||
}
|
||
|
||
private function issueLetsEncrypt(string $domain, string $email): int
|
||
{
|
||
if (empty($email)) {
|
||
$this->error('Für Let’s Encrypt ist --email erforderlich.');
|
||
return 2;
|
||
}
|
||
|
||
// Webroot sicherstellen (Nginx-Standort ist im Installer bereits konfiguriert)
|
||
@mkdir('/var/www/letsencrypt', 0755, true);
|
||
|
||
$cmd = [
|
||
'bash','-lc',
|
||
// non-interactive, webroot challenge
|
||
"certbot certonly --webroot -w /var/www/letsencrypt -d {$domain} ".
|
||
"--email ".escapeshellarg($email)." --agree-tos --no-eff-email --non-interactive --rsa-key-size 2048"
|
||
];
|
||
$p = new Process($cmd, null, ['PATH' => getenv('PATH') ?: '/usr/bin:/bin']);
|
||
$p->setTimeout(600);
|
||
$p->run(function($type,$buff){ $this->output->write($buff); });
|
||
|
||
if (!$p->isSuccessful()) {
|
||
$this->error('Certbot-Fehler.');
|
||
return 1;
|
||
}
|
||
|
||
// Pfade vom Certbot-Store
|
||
$leBase = "/etc/letsencrypt/live/{$domain}";
|
||
$fullchain = "{$leBase}/fullchain.pem";
|
||
$privkey = "{$leBase}/privkey.pem";
|
||
|
||
if (!is_file($fullchain) || !is_file($privkey)) {
|
||
$this->error("LE-Dateien fehlen unter {$leBase}");
|
||
return 1;
|
||
}
|
||
|
||
// In unsere Standard-Pfade kopieren (Nginx zeigt bereits darauf)
|
||
if (!@copy($fullchain, $this->certPath) || !@copy($privkey, $this->keyPath)) {
|
||
$this->error('Konnte Zertifikate nicht in /etc/mailwolt/ssl kopieren.');
|
||
return 1;
|
||
}
|
||
|
||
@chown($this->certPath, 'root'); @chgrp($this->certPath, 'www-data'); @chmod($this->certPath, 0640);
|
||
@chown($this->keyPath, 'root'); @chgrp($this->keyPath, 'www-data'); @chmod($this->keyPath, 0640);
|
||
|
||
// Nginx reload
|
||
$reload = new Process(['bash','-lc','systemctl reload nginx']);
|
||
$reload->run();
|
||
|
||
$this->info('Let’s Encrypt Zertifikat gesetzt und Nginx neu geladen.');
|
||
return 0;
|
||
}
|
||
|
||
private function issueSelfSigned(string $domain): int
|
||
{
|
||
$cfgPath = "{$this->sslDir}/openssl.cnf";
|
||
$cfg = <<<CFG
|
||
[req]
|
||
default_bits = 2048
|
||
prompt = no
|
||
default_md = sha256
|
||
req_extensions = req_ext
|
||
distinguished_name = dn
|
||
|
||
[dn]
|
||
CN = {$domain}
|
||
O = MailWolt
|
||
C = DE
|
||
|
||
[req_ext]
|
||
subjectAltName = @alt_names
|
||
|
||
[alt_names]
|
||
DNS.1 = {$domain}
|
||
CFG;
|
||
@file_put_contents($cfgPath, $cfg);
|
||
|
||
$cmd = [
|
||
'bash','-lc',
|
||
"openssl req -x509 -newkey rsa:2048 -days 825 -nodes -keyout {$this->keyPath} -out {$this->certPath} -config {$cfgPath}"
|
||
];
|
||
$p = new Process($cmd);
|
||
$p->setTimeout(60);
|
||
$p->run(function($t,$b){ $this->output->write($b); });
|
||
|
||
if (!$p->isSuccessful()) {
|
||
$this->error('Self-Signed Erstellung fehlgeschlagen.');
|
||
return 1;
|
||
}
|
||
|
||
@chown($this->certPath, 'root'); @chgrp($this->certPath, 'www-data'); @chmod($this->certPath, 0640);
|
||
@chown($this->keyPath, 'root'); @chgrp($this->keyPath, 'www-data'); @chmod($this->keyPath, 0640);
|
||
@chmod($this->sslDir, 0750);
|
||
|
||
$reload = new Process(['bash','-lc','systemctl reload nginx']);
|
||
$reload->run();
|
||
|
||
$this->info('Self-Signed Zertifikat erstellt und Nginx neu geladen.');
|
||
return 0;
|
||
}
|
||
}
|