feat(wg): WireGuard help topic (registration + DE/EN content + test)

feat/v1-foundation
boban 2026-06-20 22:46:04 +02:00
parent 0342d4c6e0
commit c9556bffbf
6 changed files with 109 additions and 1 deletions

View File

@ -17,7 +17,7 @@ class Index extends Component
/** Help topics in display order. */ /** Help topics in display order. */
private const TOPICS = [ private const TOPICS = [
'overview', 'domain-tls', 'security', 'updates', 'commands', 'overview', 'domain-tls', 'security', 'updates', 'commands',
'servers', 'sessions', 'email', 'audit', 'recovery', 'servers', 'sessions', 'email', 'audit', 'wireguard', 'recovery',
]; ];
#[Url] #[Url]
@ -51,6 +51,7 @@ class Index extends Component
'sessions' => __('help.topic_sessions'), 'sessions' => __('help.topic_sessions'),
'email' => __('help.topic_email'), 'email' => __('help.topic_email'),
'audit' => __('help.topic_audit'), 'audit' => __('help.topic_audit'),
'wireguard' => __('help.topic_wireguard'),
'recovery' => __('help.topic_recovery'), 'recovery' => __('help.topic_recovery'),
]; ];

View File

@ -15,5 +15,6 @@ return [
'topic_sessions' => 'Sitzungen & Benutzer', 'topic_sessions' => 'Sitzungen & Benutzer',
'topic_email' => 'E-Mail (SMTP)', 'topic_email' => 'E-Mail (SMTP)',
'topic_audit' => 'Audit-Log', 'topic_audit' => 'Audit-Log',
'topic_wireguard' => 'WireGuard-Zugang',
'topic_recovery' => 'Konto-Wiederherstellung', 'topic_recovery' => 'Konto-Wiederherstellung',
]; ];

View File

@ -15,5 +15,6 @@ return [
'topic_sessions' => 'Sessions & users', 'topic_sessions' => 'Sessions & users',
'topic_email' => 'Email (SMTP)', 'topic_email' => 'Email (SMTP)',
'topic_audit' => 'Audit log', 'topic_audit' => 'Audit log',
'topic_wireguard' => 'WireGuard access',
'topic_recovery' => 'Account recovery', 'topic_recovery' => 'Account recovery',
]; ];

View File

@ -0,0 +1,36 @@
@php
$h = 'font-display text-base font-semibold text-ink';
$p = 'text-sm leading-relaxed text-ink-2';
$li = 'text-sm leading-relaxed text-ink-2';
$code = 'rounded bg-inset px-1.5 py-0.5 font-mono text-[12px] text-accent-text';
@endphp
<div class="space-y-3">
<h3 class="{{ $h }}">Was der WireGuard-Zugang macht</h3>
<p class="{{ $p }}">Stellt das <span class="text-ink">gesamte Panel hinter einen WireGuard-Tunnel</span>: Der Server wird zum WG-Server, deine Geräte verbinden sich als Peers, und das Panel (HTTP/HTTPS, Ports 80/443) ist <span class="text-ink">nur noch über den Tunnel</span> erreichbar. Eine Netzwerk-Sperre <span class="text-ink">zusätzlich</span> zu 2FA und Anmeldeschutz: Die schützen das <span class="text-ink">Login</span>, dies verbirgt das <span class="text-ink">ganze Panel</span> vor dem öffentlichen Internet.</p>
<p class="{{ $p }}">Alles läuft über die Host-CLI <code class="{{ $code }}">clusev wg </code> (per SSH auf dem Server). Standardmäßig ist nichts aktiv du entscheidest, wann der Tunnel und die Sperre eingeschaltet werden.</p>
</div>
<div class="space-y-3">
<h3 class="{{ $h }}">Einrichten <code class="{{ $code }}">clusev wg setup</code></h3>
<p class="{{ $p }}">Interaktiv, jeder Wert mit sinnvoller Vorgabe und Erklärung:</p>
<ul class="ml-4 list-disc space-y-1.5">
<li class="{{ $li }}"><span class="text-ink">WG-Subnetz</span> (Vorgabe <code class="{{ $code }}">10.99.0.0/24</code>) ein privates Netz, das <span class="text-ink">nicht</span> mit deinem LAN/VPN kollidieren darf. Eine Kollision wird erkannt und abgewiesen.</li>
<li class="{{ $li }}"><span class="text-ink">Öffentlicher Endpoint</span> die automatisch erkannte öffentliche IP. <span class="text-ink">Hinter NAT oder einem Cloud-Load-Balancer ist das evtl. nicht die Adresse, die Clients wählen müssen</span> vor dem Aktivieren der Sperre prüfen.</li>
<li class="{{ $li }}"><span class="text-ink">Erster Peer</span> wird gleich angelegt; Konfiguration und QR-Code werden ausgegeben.</li>
</ul>
<p class="{{ $p }}">Setup startet den Tunnel (übersteht Neustarts), aktiviert aber <span class="text-ink">nicht</span> die Sperre das Panel bleibt zunächst öffentlich.</p>
</div>
<div class="space-y-3">
<h3 class="{{ $h }}">Client verbinden &amp; testen</h3>
<p class="{{ $p }}">Den QR-Code aus <code class="{{ $code }}">setup</code> (oder <code class="{{ $code }}">clusev wg add-peer &lt;name&gt;</code>) in der WireGuard-App scannen. Standard ist <span class="text-ink">Split-Tunnel</span>: nur Panel-Verkehr läuft über WG, dein normales Internet nicht. Voll-Tunnel (<code class="{{ $code }}">0.0.0.0/0</code>) ist möglich, aber <span class="text-ink">nicht empfohlen</span>.</p>
<p class="{{ $p }}">Verbinden, dann <code class="{{ $code }}">http://&lt;Server-Tunnel-IP&gt;</code> öffnen erreicht das Panel? Erst wenn der Tunnel sicher funktioniert, die Sperre aktivieren.</p>
</div>
<div class="space-y-3">
<h3 class="{{ $h }}">Sperre ein/aus <code class="{{ $code }}">clusev wg up</code> / <code class="{{ $code }}">down</code></h3>
<p class="{{ $p }}"><code class="{{ $code }}">clusev wg up</code> sperrt 80/443 auf das WG-Subnetz von außen ist das Panel danach nicht mehr erreichbar. <code class="{{ $code }}">clusev wg status</code> zeigt Peers, Handshakes und den Sperr-Status.</p>
<p class="{{ $p }}"><span class="text-ink">Notausgang:</span> <code class="{{ $code }}">clusev wg down</code> (per SSH) entfernt die Sperre sofort das Panel ist wieder öffentlich. <span class="text-ink">SSH (Port 22) und der WireGuard-Port sind von der Sperre nie betroffen</span>, du kommst also immer per SSH auf den Server.</p>
<p class="{{ $p }}">Schlägt <code class="{{ $code }}">wg0</code> nach einem Neustart fehl, wird die Sperre <span class="text-ink">nicht</span> angewendet das Panel bleibt öffentlich erreichbar statt dich auszusperren.</p>
</div>

View File

@ -0,0 +1,36 @@
@php
$h = 'font-display text-base font-semibold text-ink';
$p = 'text-sm leading-relaxed text-ink-2';
$li = 'text-sm leading-relaxed text-ink-2';
$code = 'rounded bg-inset px-1.5 py-0.5 font-mono text-[12px] text-accent-text';
@endphp
<div class="space-y-3">
<h3 class="{{ $h }}">What the WireGuard access does</h3>
<p class="{{ $p }}">Puts the <span class="text-ink">whole panel behind a WireGuard tunnel</span>: the server becomes a WG server, your devices peer in, and the panel (HTTP/HTTPS, ports 80/443) is reachable <span class="text-ink">only through the tunnel</span>. A network-layer gate <span class="text-ink">on top of</span> 2FA and the login protection: those guard the <span class="text-ink">login</span>; this hides the <span class="text-ink">whole panel</span> from the public internet.</p>
<p class="{{ $p }}">Everything runs through the host CLI <code class="{{ $code }}">clusev wg </code> (over SSH on the server). Nothing is active by default you decide when the tunnel and the gate go on.</p>
</div>
<div class="space-y-3">
<h3 class="{{ $h }}">Set up <code class="{{ $code }}">clusev wg setup</code></h3>
<p class="{{ $p }}">Interactive, every value pre-filled with a sensible default and a hint:</p>
<ul class="ml-4 list-disc space-y-1.5">
<li class="{{ $li }}"><span class="text-ink">WG subnet</span> (default <code class="{{ $code }}">10.99.0.0/24</code>) a private network that must <span class="text-ink">not</span> clash with your LAN/VPN. A collision is detected and rejected.</li>
<li class="{{ $li }}"><span class="text-ink">Public endpoint</span> the auto-detected public IP. <span class="text-ink">Behind NAT or a cloud load balancer this may not be the address clients should dial</span> verify it before enabling the gate.</li>
<li class="{{ $li }}"><span class="text-ink">First peer</span> created right away; its config and a QR code are printed.</li>
</ul>
<p class="{{ $p }}">Setup starts the tunnel (survives reboots) but does <span class="text-ink">not</span> enable the gate the panel stays public for now.</p>
</div>
<div class="space-y-3">
<h3 class="{{ $h }}">Connect a client &amp; test</h3>
<p class="{{ $p }}">Scan the QR code from <code class="{{ $code }}">setup</code> (or <code class="{{ $code }}">clusev wg add-peer &lt;name&gt;</code>) in the WireGuard app. The default is <span class="text-ink">split tunnel</span>: only panel traffic goes through WG, your normal internet does not. Full tunnel (<code class="{{ $code }}">0.0.0.0/0</code>) is possible but <span class="text-ink">not recommended</span>.</p>
<p class="{{ $p }}">Connect, then open <code class="{{ $code }}">http://&lt;server-tunnel-ip&gt;</code> does it reach the panel? Only once the tunnel reliably works, enable the gate.</p>
</div>
<div class="space-y-3">
<h3 class="{{ $h }}">Gate on/off <code class="{{ $code }}">clusev wg up</code> / <code class="{{ $code }}">down</code></h3>
<p class="{{ $p }}"><code class="{{ $code }}">clusev wg up</code> restricts 80/443 to the WG subnet from outside the panel is then unreachable. <code class="{{ $code }}">clusev wg status</code> shows peers, handshakes and the gate state.</p>
<p class="{{ $p }}"><span class="text-ink">Escape hatch:</span> <code class="{{ $code }}">clusev wg down</code> (over SSH) removes the gate immediately the panel is public again. <span class="text-ink">SSH (port 22) and the WireGuard port are never affected by the gate</span>, so you can always reach the server over SSH.</p>
<p class="{{ $p }}">If <code class="{{ $code }}">wg0</code> fails to come up after a reboot, the gate is <span class="text-ink">not</span> applied the panel stays publicly reachable rather than locking you out.</p>
</div>

View File

@ -0,0 +1,33 @@
<?php
namespace Tests\Feature;
use App\Livewire\Help\Index;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Tests\TestCase;
class HelpWireguardTopicTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$this->actingAs(User::factory()->create(['must_change_password' => false]));
}
public function test_wireguard_is_a_known_topic_and_renders(): void
{
Livewire::test(Index::class, ['topic' => 'wireguard'])
->assertSet('topic', 'wireguard')
->assertSee('WireGuard');
}
public function test_wireguard_label_exists_in_both_locales(): void
{
$this->assertSame('WireGuard-Zugang', __('help.topic_wireguard', [], 'de'));
$this->assertSame('WireGuard access', __('help.topic_wireguard', [], 'en'));
}
}