Feature: ClamAV-Verwaltung in Sicherheit-Sidebar
- Neue Seite /security/clamav: Status, Aktivieren/Deaktivieren, Signatur-Update, RAM-Hinweis, Info-Box - Optionale Dienste (ClamAV) im Dashboard nur sichtbar wenn aktiv - mailwolt-clamav sbin-Wrapper + sudoers-Regel in ensure_system Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>main v1.1.311
parent
a7f6d8e242
commit
b107e9a580
|
|
@ -90,6 +90,8 @@ class Dashboard extends Component
|
|||
foreach ($card['sources'] as $src) {
|
||||
if ($this->probeSource($src)) { $isOk = true; break; }
|
||||
}
|
||||
// Optionale Dienste (z.B. ClamAV) nur anzeigen wenn aktiv
|
||||
if (!$isOk && ($card['optional'] ?? false)) continue;
|
||||
$rows[] = ['label' => $card['label'], 'hint' => $card['hint'], 'ok' => $isOk];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
namespace App\Livewire\Ui\Security;
|
||||
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\Title;
|
||||
use Livewire\Component;
|
||||
|
||||
#[Layout('layouts.dvx')]
|
||||
#[Title('Virenschutz · Mailwolt')]
|
||||
class ClamavManager extends Component
|
||||
{
|
||||
public bool $installed = false;
|
||||
public bool $running = false;
|
||||
public bool $enabled = false;
|
||||
public string $dbDate = '—';
|
||||
public string $dbVersion = '—';
|
||||
public ?int $ramMb = null;
|
||||
public string $lastError = '';
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->refresh();
|
||||
}
|
||||
|
||||
public function refresh(): void
|
||||
{
|
||||
$this->lastError = '';
|
||||
$this->installed = $this->isInstalled();
|
||||
$this->running = $this->installed && $this->serviceActive();
|
||||
$this->enabled = $this->installed && $this->serviceEnabled();
|
||||
$this->ramMb = $this->running ? $this->readRamMb() : null;
|
||||
[$this->dbDate, $this->dbVersion] = $this->readDbInfo();
|
||||
}
|
||||
|
||||
public function enable(): void
|
||||
{
|
||||
if (!$this->installed) {
|
||||
$this->lastError = 'ClamAV ist nicht installiert.';
|
||||
return;
|
||||
}
|
||||
$out = [];
|
||||
@exec('sudo -n /usr/local/sbin/mailwolt-clamav enable 2>&1', $out, $rc);
|
||||
if ($rc !== 0) {
|
||||
$this->lastError = implode(' ', $out) ?: 'Fehler beim Aktivieren.';
|
||||
} else {
|
||||
$this->dispatch('toast', type: 'success', badge: 'ClamAV',
|
||||
title: 'Aktiviert', text: 'ClamAV läuft jetzt und startet automatisch beim Booten.');
|
||||
}
|
||||
$this->refresh();
|
||||
}
|
||||
|
||||
public function disable(): void
|
||||
{
|
||||
$out = [];
|
||||
@exec('sudo -n /usr/local/sbin/mailwolt-clamav disable 2>&1', $out, $rc);
|
||||
if ($rc !== 0) {
|
||||
$this->lastError = implode(' ', $out) ?: 'Fehler beim Deaktivieren.';
|
||||
} else {
|
||||
$this->dispatch('toast', type: 'success', badge: 'ClamAV',
|
||||
title: 'Deaktiviert', text: 'ClamAV wurde gestoppt und aus dem Autostart entfernt.');
|
||||
}
|
||||
$this->refresh();
|
||||
}
|
||||
|
||||
public function updateDb(): void
|
||||
{
|
||||
$out = [];
|
||||
@exec('sudo -n /usr/local/sbin/mailwolt-clamav freshclam 2>&1', $out, $rc);
|
||||
if ($rc !== 0) {
|
||||
$this->lastError = implode(' ', $out) ?: 'Fehler beim Aktualisieren.';
|
||||
} else {
|
||||
$this->dispatch('toast', type: 'success', badge: 'ClamAV',
|
||||
title: 'Datenbank aktualisiert', text: 'Virensignaturen wurden aktualisiert.');
|
||||
}
|
||||
$this->refresh();
|
||||
}
|
||||
|
||||
private function isInstalled(): bool
|
||||
{
|
||||
return is_executable('/usr/bin/clamscan') || is_executable('/usr/sbin/clamd')
|
||||
|| @file_exists('/usr/sbin/clamd') || @file_exists('/usr/bin/clamdscan');
|
||||
}
|
||||
|
||||
private function serviceActive(): bool
|
||||
{
|
||||
$exit = null;
|
||||
@exec('systemctl is-active --quiet clamav-daemon 2>/dev/null', $_, $exit);
|
||||
return $exit === 0;
|
||||
}
|
||||
|
||||
private function serviceEnabled(): bool
|
||||
{
|
||||
$exit = null;
|
||||
@exec('systemctl is-enabled --quiet clamav-daemon 2>/dev/null', $_, $exit);
|
||||
return $exit === 0;
|
||||
}
|
||||
|
||||
private function readRamMb(): ?int
|
||||
{
|
||||
$out = [];
|
||||
@exec("ps -C clamd -o rss= 2>/dev/null", $out);
|
||||
$kb = array_sum(array_map('intval', array_filter($out)));
|
||||
return $kb > 0 ? (int) round($kb / 1024) : null;
|
||||
}
|
||||
|
||||
private function readDbInfo(): array
|
||||
{
|
||||
$paths = [
|
||||
'/var/lib/clamav/main.cvd',
|
||||
'/var/lib/clamav/main.cld',
|
||||
'/var/lib/clamav/daily.cvd',
|
||||
'/var/lib/clamav/daily.cld',
|
||||
];
|
||||
$latest = 0;
|
||||
foreach ($paths as $p) {
|
||||
if (@file_exists($p)) {
|
||||
$mtime = @filemtime($p);
|
||||
if ($mtime > $latest) $latest = $mtime;
|
||||
}
|
||||
}
|
||||
if ($latest === 0) return ['—', '—'];
|
||||
$date = date('d.m.Y H:i', $latest);
|
||||
|
||||
$ver = '—';
|
||||
$out = [];
|
||||
@exec('sigtool --info /var/lib/clamav/daily.cld 2>/dev/null | grep "^Version:" | head -1', $out);
|
||||
if (empty($out)) {
|
||||
@exec('sigtool --info /var/lib/clamav/daily.cvd 2>/dev/null | grep "^Version:" | head -1', $out);
|
||||
}
|
||||
if (!empty($out[0])) {
|
||||
$ver = trim(str_replace('Version:', '', $out[0]));
|
||||
}
|
||||
|
||||
return [$date, $ver];
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.ui.security.clamav-manager');
|
||||
}
|
||||
}
|
||||
|
|
@ -40,6 +40,7 @@ return [
|
|||
'label' => 'Sicherheit', 'icon' => 'ph-shield', 'items' => [
|
||||
['label' => 'TLS & Ciphers', 'route' => 'ui.security.tls'],
|
||||
['label' => 'Ratelimits', 'route' => 'ui.security.abuse'],
|
||||
['label' => 'Virenschutz', 'route' => 'ui.security.clamav'],
|
||||
['label' => 'Audit-Logs', 'route' => 'ui.security.audit'],
|
||||
],
|
||||
],
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ return [
|
|||
],
|
||||
'clamav' => [
|
||||
'label' => 'ClamAV', 'hint' => 'Virenscanner',
|
||||
'optional' => true,
|
||||
'sources' => [
|
||||
'systemd:clamav-daemon', 'systemd:clamav-daemon@scan', 'systemd:clamd',
|
||||
'socket:/run/clamav/clamd.ctl', 'pid:/run/clamav/clamd.pid', 'tcp:127.0.0.1:3310',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,178 @@
|
|||
<x-slot:breadcrumbParent>Sicherheit</x-slot:breadcrumbParent>
|
||||
<x-slot:breadcrumb>Virenschutz</x-slot:breadcrumb>
|
||||
|
||||
<div>
|
||||
|
||||
<div class="mbx-page-header">
|
||||
<div class="mbx-page-title">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M8 1.5C8 1.5 3 3.5 3 7.5v3.5l5 2.5 5-2.5V7.5C13 3.5 8 1.5 8 1.5Z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/>
|
||||
<path d="M5.5 8l1.5 1.5L10 6" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
Virenschutz (ClamAV)
|
||||
</div>
|
||||
<div class="mbx-page-actions">
|
||||
<button wire:click="refresh" class="mbx-act-btn">
|
||||
<svg width="12" height="12" viewBox="0 0 14 14" fill="none" wire:loading.class="animate-spin" wire:target="refresh,enable,disable,updateDb"><path d="M12 7A5 5 0 1 1 7 2" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><path d="M7 2l2-2v4H5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
Aktualisieren
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Fehler-Banner --}}
|
||||
@if($lastError)
|
||||
<div style="margin-bottom:16px;padding:10px 14px;background:rgba(239,68,68,.08);border:1px solid rgba(239,68,68,.25);border-radius:8px;display:flex;align-items:center;gap:8px">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" style="color:#ef4444;flex-shrink:0"><circle cx="7" cy="7" r="6" stroke="currentColor" stroke-width="1.3"/><path d="M7 4v3.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><circle cx="7" cy="10" r=".6" fill="currentColor"/></svg>
|
||||
<span style="font-size:12.5px;color:#fca5a5">{{ $lastError }}</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="sec-layout">
|
||||
<div style="min-width:0;display:flex;flex-direction:column;gap:16px">
|
||||
|
||||
{{-- RAM-Hinweis --}}
|
||||
<div style="padding:12px 16px;background:rgba(245,158,11,.07);border:1px solid rgba(245,158,11,.25);border-radius:10px;display:flex;gap:12px;align-items:flex-start">
|
||||
<svg width="18" height="18" viewBox="0 0 16 16" fill="none" style="color:#f59e0b;flex-shrink:0;margin-top:1px"><path d="M8 1.5L13.5 12H2.5L8 1.5Z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/><path d="M8 6v3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><circle cx="8" cy="10.5" r=".6" fill="currentColor"/></svg>
|
||||
<div>
|
||||
<div style="font-size:12.5px;font-weight:600;color:#fcd34d;margin-bottom:3px">Hoher RAM-Verbrauch</div>
|
||||
<div style="font-size:12px;color:rgba(253,230,138,.7);line-height:1.5">
|
||||
ClamAV lädt die komplette Viren-Datenbank (~500 MB) in den Arbeitsspeicher.
|
||||
Auf Servern mit weniger als 2 GB RAM kann das die Performance beeinträchtigen.
|
||||
Empfohlen für Produktiv-Mailserver mit ausreichend RAM.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Status-Karte --}}
|
||||
<div class="mbx-section">
|
||||
<div class="mbx-domain-head">
|
||||
<div class="mbx-domain-info">
|
||||
<span class="mbx-badge-mute">Status</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding:16px 18px">
|
||||
|
||||
@if(!$installed)
|
||||
{{-- Nicht installiert --}}
|
||||
<div style="display:flex;flex-direction:column;gap:14px">
|
||||
<div style="display:flex;align-items:center;gap:10px;padding:12px 14px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:8px">
|
||||
<div style="width:8px;height:8px;border-radius:50%;background:#6b7280;flex-shrink:0"></div>
|
||||
<div>
|
||||
<div style="font-size:13px;font-weight:500;color:var(--mw-t2)">ClamAV nicht installiert</div>
|
||||
<div style="font-size:11.5px;color:var(--mw-t4);margin-top:2px">Das Paket <code style="font-size:11px;background:var(--mw-bg3);padding:1px 5px;border-radius:4px">clamav clamav-daemon</code> ist nicht verfügbar.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding:12px 14px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:8px">
|
||||
<div style="font-size:12px;font-weight:500;color:var(--mw-t3);margin-bottom:6px">Installation</div>
|
||||
<code style="font-size:11.5px;color:var(--mw-t2);background:var(--mw-bg3);padding:6px 10px;border-radius:6px;display:block">apt install clamav clamav-daemon -y</code>
|
||||
<div style="font-size:11.5px;color:var(--mw-t4);margin-top:6px">Nach der Installation diese Seite neu laden.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@else
|
||||
{{-- Installiert --}}
|
||||
<div style="display:flex;flex-direction:column;gap:16px">
|
||||
|
||||
{{-- Status-Zeile --}}
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:12px 14px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:8px">
|
||||
<div style="display:flex;align-items:center;gap:10px">
|
||||
@if($running)
|
||||
<div style="width:8px;height:8px;border-radius:50%;background:#34d399;box-shadow:0 0 6px rgba(52,211,153,.4);flex-shrink:0"></div>
|
||||
<div>
|
||||
<div style="font-size:13px;font-weight:500;color:var(--mw-t1)">ClamAV läuft</div>
|
||||
<div style="font-size:11.5px;color:var(--mw-t4);margin-top:1px">
|
||||
Aktiv{{ $enabled ? ', Autostart ein' : '' }}
|
||||
@if($ramMb) · {{ $ramMb }} MB RAM @endif
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div style="width:8px;height:8px;border-radius:50%;background:#6b7280;flex-shrink:0"></div>
|
||||
<div>
|
||||
<div style="font-size:13px;font-weight:500;color:var(--mw-t2)">ClamAV inaktiv</div>
|
||||
<div style="font-size:11.5px;color:var(--mw-t4);margin-top:1px">Installiert, aber nicht aktiv{{ $enabled ? '' : ' · Autostart aus' }}</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div style="display:flex;gap:8px">
|
||||
@if($running)
|
||||
<button wire:click="disable" wire:loading.attr="disabled" wire:target="disable"
|
||||
class="mbx-act-danger"
|
||||
onclick="return confirm('ClamAV wirklich stoppen und deaktivieren?')">
|
||||
<svg width="12" height="12" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="2" stroke="currentColor" stroke-width="1.3"/></svg>
|
||||
<span wire:loading.remove wire:target="disable">Deaktivieren</span>
|
||||
<span wire:loading wire:target="disable">Bitte warten…</span>
|
||||
</button>
|
||||
@else
|
||||
<button wire:click="enable" wire:loading.attr="disabled" wire:target="enable"
|
||||
class="mbx-btn-primary">
|
||||
<svg width="12" height="12" viewBox="0 0 14 14" fill="none"><path d="M7 2v10M2 7h10" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
|
||||
<span wire:loading.remove wire:target="enable">Aktivieren & starten</span>
|
||||
<span wire:loading wire:target="enable">Wird gestartet…</span>
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Signaturen --}}
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||||
<div style="padding:10px 14px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:8px">
|
||||
<div style="font-size:11px;color:var(--mw-t4);margin-bottom:3px">Signatur-Datenbank</div>
|
||||
<div style="font-size:13px;font-weight:500;color:var(--mw-t1)">{{ $dbDate }}</div>
|
||||
@if($dbVersion !== '—')<div style="font-size:11.5px;color:var(--mw-t4);margin-top:1px">Version {{ $dbVersion }}</div>@endif
|
||||
</div>
|
||||
<div style="padding:10px 14px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:8px;display:flex;align-items:center;justify-content:space-between">
|
||||
<div>
|
||||
<div style="font-size:11px;color:var(--mw-t4);margin-bottom:3px">Signaturen aktualisieren</div>
|
||||
<div style="font-size:12px;color:var(--mw-t3)">Läuft automatisch täglich</div>
|
||||
</div>
|
||||
<button wire:click="updateDb" wire:loading.attr="disabled" wire:target="updateDb" class="mbx-act-btn" style="flex-shrink:0">
|
||||
<svg width="11" height="11" viewBox="0 0 14 14" fill="none" wire:loading.class="animate-spin" wire:target="updateDb"><path d="M12 7A5 5 0 1 1 7 2" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><path d="M7 2l2-2v4H5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
<span wire:loading.remove wire:target="updateDb">Jetzt</span>
|
||||
<span wire:loading wire:target="updateDb">…</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Info-Box --}}
|
||||
<div class="mbx-section">
|
||||
<div class="mbx-domain-head">
|
||||
<div class="mbx-domain-info">
|
||||
<span class="mbx-badge-mute">Info</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding:16px 18px;display:flex;flex-direction:column;gap:10px">
|
||||
<div style="font-size:12.5px;color:var(--mw-t3);line-height:1.6">
|
||||
ClamAV ist ein Open-Source-Virenscanner der in Kombination mit Rspamd eingehende E-Mails auf Schadsoftware prüft.
|
||||
Infizierte Anhänge werden automatisch abgewiesen oder in Quarantäne verschoben.
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:4px">
|
||||
<div style="padding:8px 12px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:7px">
|
||||
<div style="font-size:11px;color:var(--mw-t4);margin-bottom:2px">Empfohlen bei</div>
|
||||
<div style="font-size:12px;color:var(--mw-t2)">≥ 2 GB RAM</div>
|
||||
</div>
|
||||
<div style="padding:8px 12px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:7px">
|
||||
<div style="font-size:11px;color:var(--mw-t4);margin-bottom:2px">RAM-Verbrauch</div>
|
||||
<div style="font-size:12px;color:var(--mw-t2)">~500–700 MB</div>
|
||||
</div>
|
||||
<div style="padding:8px 12px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:7px">
|
||||
<div style="font-size:11px;color:var(--mw-t4);margin-bottom:2px">Integration</div>
|
||||
<div style="font-size:12px;color:var(--mw-t2)">Rspamd → Milter</div>
|
||||
</div>
|
||||
<div style="padding:8px 12px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:7px">
|
||||
<div style="font-size:11px;color:var(--mw-t4);margin-bottom:2px">Signaturen</div>
|
||||
<div style="font-size:12px;color:var(--mw-t2)">Täglich automatisch</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -60,6 +60,7 @@ Route::middleware(['auth.user', 'require2fa'])->name('ui.')->group(function () {
|
|||
Route::get('/rspamd', \App\Livewire\Ui\Security\RspamdForm::class)->name('rspamd');
|
||||
Route::get('/tls-ciphers', \App\Livewire\Ui\Security\TlsCiphersForm::class)->name('tls');
|
||||
Route::get('/audit-logs', \App\Livewire\Ui\Security\AuditLogsTable::class)->name('audit');
|
||||
Route::get('/clamav', \App\Livewire\Ui\Security\ClamavManager::class)->name('clamav');
|
||||
});
|
||||
|
||||
#DOMAIN ROUTES
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/env bash
|
||||
# Wrapper für ClamAV-Verwaltung via MailWolt UI (läuft als root via sudo)
|
||||
set -euo pipefail
|
||||
|
||||
CMD="${1:-status}"
|
||||
|
||||
case "$CMD" in
|
||||
enable)
|
||||
systemctl enable clamav-daemon
|
||||
systemctl start clamav-daemon
|
||||
echo "ClamAV aktiviert und gestartet."
|
||||
;;
|
||||
disable)
|
||||
systemctl stop clamav-daemon || true
|
||||
systemctl disable clamav-daemon
|
||||
echo "ClamAV gestoppt und deaktiviert."
|
||||
;;
|
||||
freshclam)
|
||||
systemctl stop clamav-freshclam 2>/dev/null || true
|
||||
freshclam --quiet
|
||||
systemctl start clamav-freshclam 2>/dev/null || true
|
||||
echo "Signaturen aktualisiert."
|
||||
;;
|
||||
status)
|
||||
systemctl is-active clamav-daemon 2>/dev/null && echo "active" || echo "inactive"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: mailwolt-clamav {enable|disable|freshclam|status}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
|
@ -183,10 +183,12 @@ ensure_system(){
|
|||
_sbin_install "${APP_DIR}/scripts/mailwolt-apply-domains" /usr/local/sbin/mailwolt-apply-domains
|
||||
_sbin_install "${APP_DIR}/scripts/mailwolt-fetch-tags" /usr/local/sbin/mailwolt-fetch-tags
|
||||
_sbin_install "${APP_DIR}/scripts/update.sh" /usr/local/sbin/mailwolt-update
|
||||
_sbin_install "${APP_DIR}/scripts/mailwolt-clamav" /usr/local/sbin/mailwolt-clamav
|
||||
# Sudoers-Regeln nachrüsten
|
||||
for f in /etc/sudoers.d/mailwolt-certbot /etc/sudoers.d/mailwolt-dkim; do
|
||||
_sudoers_add "$f" 'www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-fetch-tags'
|
||||
_sudoers_add "$f" 'www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-update --check-only'
|
||||
_sudoers_add "$f" 'www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-clamav'
|
||||
_sudoers_add "$f" 'www-data ALL=(root) NOPASSWD: /usr/bin/openssl x509 -enddate -noout -in /etc/letsencrypt/live/*/fullchain.pem'
|
||||
done
|
||||
# Laravel Scheduler cron (schedule:run muss jede Minute laufen)
|
||||
|
|
|
|||
Loading…
Reference in New Issue