<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="cs">
  <title>ruza · writing</title>
  <subtitle>Writing, links, and signals by ruza.</subtitle>
  <link href="https://ruza.eu/" rel="alternate" type="text/html"/>
  <link href="https://ruza.eu/feed.xml" rel="self" type="application/atom+xml"/>
  <id>https://ruza.eu/feed.xml</id>
  <updated>2026-04-18T00:00:00Z</updated>
  <author>
    <name>ruza</name>
    <uri>https://ruza.eu/</uri>
  </author>
  <generator uri="https://ruza.eu/">build.py</generator>
  <entry>
    <title>QubesOS - DisposableVM s VPN per zákazník</title>
    <link href="https://ruza.eu/articles/qubesos-named-disposables.html" rel="alternate" type="text/html"/>
    <id>https://ruza.eu/articles/qubesos-named-disposables.html</id>
    <published>2026-04-18T00:00:00Z</published>
    <updated>2026-04-18T00:00:00Z</updated>
    <summary>Návod na zprovoznění DisposableVM s VPN ke konrétním zákazníkům</summary>
    <content type="html"><![CDATA[
<h1>Zadání</h1>
<p>Cílem je z menu mít relativně rychle dostupné jednotné prostředí s předpřipraveným VPN přístupem k různým zákazníkům.</p>
<ul>
<li>Jedna AppVM <code>work</code>, která drží persistentní konfiguraci v /home/ adresáři.</li>
<li>pro každého zákazníka<ul>
<li><code>work-zákazníkX</code> disposable pro práci u konkrétního zákazníka</li>
<li><code>vpn-zákazníkX</code> s VPN klientem k zákazníkX</li>
</ul>
</li>
</ul>
<h2>Architektura</h2>
<pre><code>[work-zákazník1] ──► [vpn-zákazník1] ──► [sys-firewall] ──► [sys-net]
[work-zákazník2] ──► [vpn-zákazník2] ──► [sys-firewall] ──► [sys-net]
</code></pre>
<p>Každý Named DispVM se při startu vytvoří čistý z šablony, má vlastní VPN tunel a po zavření se smaže.</p>
<hr>
<h2>1. Připrav base AppVM jako DispVM šablonu</h2>
<p>Máš AppVM (řekněme <code>work</code>), kde máš nainstalované SSH klienty, nástroje atd. Tu nastavíš jako šablonu pro disposable:</p>
<pre><code class="language-bash"># v dom0
qvm-prefs work template_for_dispvms True
</code></pre>
<p>AppVM  <code>work</code> můžeš normálně používat a pokud budeš potřebovat nové work prostředí s VPN ke konkrétnímu zákazníkovi spustíš odpovídající work disposable.</p>
<p>Networking u <code>work</code> samotné můžeš nastavit na <code>none</code>, pokud budeš používat jen disposable. Síť se řeší až na úrovni Named DispVMs:</p>
<pre><code class="language-bash">qvm-prefs work netvm none
</code></pre>
<p>Veškerý software (SSH klient, wireshark, nmap…) instaluješ do TemplateVM, ze které <code>work</code> vychází (např. <code>debian-13</code>). Samotná <code>work</code> AppVM pak slouží jako DispVM šablona — přizpůsobení (dotfiles, SSH config) dáváš do <code>/home/user/</code> v <code>work</code>.</p>
<p>Nezapomeň, že v disposable se nic persistentně na disk neukládá.</p>
<hr>
<h2>2. Vytvoř VPN ProxyVM pro každého zákazníka</h2>
<p>Pro každého zákazníka potřebuješ samostatnou VM, která poběží jako VPN gateway.</p>
<pre><code class="language-bash"># v dom0
qvm-create vpn-zákazník1 --class AppVM --template debian-13 --label orange
qvm-prefs vpn-zákazník1 netvm sys-firewall
qvm-prefs vpn-zákazník1 provides_network True

qvm-create vpn-zákazník2 --class AppVM --template debian-13 --label orange
qvm-prefs vpn-zákazník2 netvm sys-firewall
qvm-prefs vpn-zákazník2 provides_network True
</code></pre>
<p><code>provides_network True</code> je klíčové — díky tomu se VM chová jako ProxyVM a ostatní VM ji můžou použít jako svůj <code>netvm</code>.</p>
<h3>Konfigurace VPN uvnitř každé proxy VM</h3>
<p>Spusť <code>vpn-zákazník1</code> a nastav VPN. Příklad s WireGuard:</p>
<pre><code class="language-bash"># uvnitř vpn-zákazník1
sudo nano /rw/config/vpn/wg0.conf
# vlož WireGuard config zákazníka 1
</code></pre>
<p>Aby se VPN spustila automaticky při startu, přidej do <code>/rw/config/rc.local</code>:</p>
<pre><code class="language-bash">#!/bin/bash
cp /rw/config/vpn/wg0.conf /etc/wireguard/wg0.conf
systemctl start wg-quick@wg0

# Povolit forwarding (nutné pro ProxyVM funkci)
echo 1 &gt; /proc/sys/net/ipv4/ip_forward
iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
iptables -A FORWARD -i eth0 -o wg0 -j ACCEPT
iptables -A FORWARD -i wg0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT

# Volitelně: blokuj traffic mimo tunel (kill switch)
iptables -A OUTPUT -o wg0 -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -d 10.137.0.0/16 -j ACCEPT  # Qubes interní síť
iptables -A OUTPUT -p udp --dport 51820 -j ACCEPT  # WG endpoint
iptables -A OUTPUT -j DROP
</code></pre>
<p>Pro OpenVPN je postup analogický — místo WireGuard spustíš <code>openvpn --config /rw/config/vpn/client.ovpn --daemon</code>.</p>
<p>Totéž provedeš pro <code>vpn-zákazník2</code> s konfigurací druhého zákazníka.</p>
<hr>
<h2>3. Vytvoř Named Disposables</h2>
<pre><code class="language-bash"># v dom0
qvm-create work-zákazník1 --class DispVM --template work --label green
qvm-prefs work-zákazník1 netvm vpn-zákazník1

qvm-create work-zákazník2 --class DispVM --template work --label blue
qvm-prefs work-zákazník2 netvm vpn-zákazník2
</code></pre>
<p>Tím vzniknou pojmenované DispVM, které se zobrazí v App menu a můžeš je spouštět opakovaně, pokaždé čisté, ale vždy s přiřazenou VPN.</p>
<h2>Vytvoření skriptem</h2>
<pre><code class="language-bash">#!/bin/bash                                                                                                                           
ZAKAZNIK=&quot;${1}&quot;

# Kontrola, zda je promenna ZAKAZNIK nastavena                                                                                        
if [ -z &quot;${ZAKAZNIK}&quot; ]; then
    # Pokud neni nastavena, zkontroluj prvni parametr                                                                                 
    if [ $# -eq 0 ] || [ -z &quot;$1&quot; ]; then
        echo &quot;Chyba: Promenna ZAKAZNIK neni nastavena a nebyl poskytnut prvni parametr.&quot;
        echo &quot;Zadejte hodnotu pro ZAKAZNIK:&quot;
        read -r ZAKAZNIK
        if [ -z &quot;${ZAKAZNIK}&quot; ]; then
            echo &quot;Chyba: ZAKAZNIK neni zadan. Skript konci.&quot;
            exit 1
        fi
    else
        ZAKAZNIK=&quot;$1&quot;
    fi
fi

echo &quot;Vytvorim: work-${ZAKAZNIK} jako NamedDispVM..&quot;

qvm-create work-${ZAKAZNIK} --class DispVM --template work --label blue
qvm-prefs work-${ZAKAZNIK} netvm vpn-${ZAKAZNIK}
</code></pre>
<h2>4. Ověření</h2>
<pre><code class="language-bash"># Spusť Named DispVM
qvm-run work-zákazník1 --auto gnome-terminal

# Uvnitř DispVM ověř IP
curl ifconfig.me
# Měla by se zobrazit VPN IP zákazníka 1
</code></pre>
<hr>
<h2>Shrnutí struktury</h2>
<table>
<thead>
<tr>
<th>VM</th>
<th>Třída</th>
<th>NetVM</th>
<th>Účel</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>work</code></td>
<td>AppVM a DispVM šablona</td>
<td>none / sys-firewall</td>
<td>Šablona s nástroji a dotfiles</td>
</tr>
<tr>
<td><code>vpn-zákazník1</code></td>
<td>AppVM (provides_network)</td>
<td>sys-firewall</td>
<td>VPN tunel zákazníka 1</td>
</tr>
<tr>
<td><code>vpn-zákazník2</code></td>
<td>AppVM (provides_network)</td>
<td>sys-firewall</td>
<td>VPN tunel zákazníka 2</td>
</tr>
<tr>
<td><code>work-zákazník1</code></td>
<td>DispVM</td>
<td>vpn-zákazník1</td>
<td>Pracovní session zákazník 1</td>
</tr>
<tr>
<td><code>work-zákazník2</code></td>
<td>DispVM</td>
<td>vpn-zákazník2</td>
<td>Pracovní session zákazník 2</td>
</tr>
</tbody>
</table>
<h2>Tipy</h2>
<p><strong>Přidání dalšího zákazníka</strong> je vždy jen 3 příkazy v dom0 — vytvoř VPN proxy, nakonfiguruj VPN uvnitř, vytvoř Named DispVM s příslušným netvm.</p>
<p><strong>SSH klíče</strong> — pokud používáš split-GPG/split-SSH, klíče zůstávají ve <code>vault-net</code> a DispVM si je vyžádá přes Qubes RPC. Nemusíš je kopírovat do každé disposable.</p>
<p><strong>Persistentní SSH config</strong> — <code>/home/user/.ssh/config</code> v <code>work</code> AppVM se propaguje do každého DispVM, takže aliasy, proxy jumpy atd. stačí nastavit jednou.</p>
    ]]></content>
    <author>
      <name>ruza</name>
    </author>
  </entry>
  <entry>
    <title>Windows - restart Voicemeeter po připojení Bluetooth zařízení</title>
    <link href="https://ruza.eu/articles/windows-restart-audio-on-bt-event.html" rel="alternate" type="text/html"/>
    <id>https://ruza.eu/articles/windows-restart-audio-on-bt-event.html</id>
    <published>2026-04-18T00:00:00Z</published>
    <updated>2026-04-18T00:00:00Z</updated>
    <summary>Automatický restart Voicemeeter po připojení Bluetooth zařízení</summary>
    <content type="html"><![CDATA[
<p>Tato stránka popisuje, jak na Windows 11 automaticky spustit nebo restartovat <strong>Voicemeeter Potato</strong> (s parametrem <code>-r</code> pro restart audio engine) po připojení konkrétního Bluetooth zařízení, s 2sekundovým zpožděním.</p>
<h2>Předpoklady</h2>
<ul>
<li>Windows 11 s povoleným Bluetooth</li>
<li>Voicemeeter Potato nainstalovaný v <code>C:\Program Files (x86)\VB\Voicemeeter\</code></li>
<li>Povolený log <code>Microsoft-Windows-Bluetooth-Policy/Operational</code> v Event Vieweru</li>
</ul>
<h2>1. Povolení Bluetooth Event Logu</h2>
<p>Ve výchozím stavu je log vypnutý. Spusť <strong>PowerShell jako Administrator</strong>:</p>
<pre><code class="language-powershell">wevtutil sl Microsoft-Windows-Bluetooth-Policy/Operational /e:true
</code></pre>
<p>Ověření:</p>
<pre><code class="language-powershell">wevtutil gl &quot;Microsoft-Windows-Bluetooth-Policy/Operational&quot;
</code></pre>
<p>V výstupu hledej <code>enabled: true</code>.</p>
<h2>2. Zjištění adresy Bluetooth zařízení</h2>
<p>Připoj cílové BT zařízení a spusť:</p>
<pre><code class="language-powershell">Get-WinEvent -LogName &quot;Microsoft-Windows-Bluetooth-Policy/Operational&quot; -MaxEvents 10 |
  Format-Table TimeCreated, Id, Message -Wrap
</code></pre>
<p>Hledej řádek s <strong>Event ID 9</strong> (úspěšné připojení) a poznamenej si adresu zařízení (např. <code>A00CE238FE34</code>).</p>
<h2>3. Vytvoření spouštěcího skriptu</h2>
<p>Vytvoř soubor <code>C:\Scripts\voicemeeter-bt.bat</code>:</p>
<pre><code class="language-batch">@echo off
timeout /t 2 /nobreak &gt;nul
start &quot;&quot; &quot;C:\Program Files (x86)\VB\Voicemeeter\voicemeeter8x64.exe&quot; -r
</code></pre>
<p><code>timeout /t 2</code> zajistí 2sekundové zpoždění, než se Voicemeeter spustí.</p>
<h2>4. Nastavení úlohy v Task Scheduleru</h2>
<h3>4.1 Otevření Task Scheduleru</h3>
<ul>
<li><code>Win+R</code> → <code>taskschd.msc</code> → Enter</li>
</ul>
<h3>4.2 Vytvoření úlohy</h3>
<ul>
<li>V pravém panelu klikni na <strong>Create Task</strong> (ne &ldquo;Create Basic Task&rdquo;)</li>
</ul>
<h3>4.3 Záložka General</h3>
<ul>
<li><strong>Name:</strong> <code>Voicemeeter BT Auto-Start</code></li>
<li><strong>Description:</strong> <code>Spustí Voicemeeter Potato po připojení BT zařízení</code></li>
<li><strong>Security options:</strong> zaškrtni <strong>Run only when user is logged on</strong></li>
</ul>
<h3>4.4 Záložka Triggers</h3>
<ol>
<li>Klikni <strong>New…</strong></li>
<li><strong>Begin the task:</strong> <code>On an event</code></li>
<li>Přepni na <strong>Custom</strong></li>
<li>Klikni <strong>New Event Filter…</strong> → záložka <strong>XML</strong> → zaškrtni <strong>Edit query manually</strong></li>
<li>Vlož následující XML (uprav <code>A00CE238FE34</code> na adresu svého zařízení):</li>
</ol>
<pre><code class="language-xml">&lt;QueryList&gt;
  &lt;Query Id=&quot;0&quot; Path=&quot;Microsoft-Windows-Bluetooth-Policy/Operational&quot;&gt;
    &lt;Select Path=&quot;Microsoft-Windows-Bluetooth-Policy/Operational&quot;&gt;
      *[System[EventID=9] and EventData[Data and contains(.,'A00CE238FE34')]]
    &lt;/Select&gt;
  &lt;/Query&gt;
&lt;/QueryList&gt;
</code></pre>
<ol start="6">
<li>Potvrď <strong>OK</strong></li>
</ol>
<h3>4.5 Záložka Actions</h3>
<ol>
<li>Klikni <strong>New…</strong></li>
<li><strong>Action:</strong> <code>Start a program</code></li>
<li><strong>Program/script:</strong> <code>C:\Scripts\voicemeeter-bt.bat</code></li>
<li>Potvrď <strong>OK</strong></li>
</ol>
<h3>4.6 Záložka Conditions</h3>
<ul>
<li><strong>Odškrtni</strong> &ldquo;Start the task only if the computer is on AC power&rdquo; (pokud chceš spouštět i na baterii)</li>
</ul>
<h3>4.7 Záložka Settings</h3>
<ul>
<li>Zaškrtni <strong>Allow task to be run on demand</strong> (pro ruční testování)</li>
<li>Zaškrtni <strong>If the task is already running, then do not start a new instance</strong></li>
<li>Potvrď <strong>OK</strong></li>
</ul>
<h2>5. Testování</h2>
<ol>
<li>Odpoj BT zařízení</li>
<li>Znovu ho připoj</li>
<li>Po 2 sekundách by se měl spustit Voicemeeter Potato</li>
</ol>
<p>Pro diagnostiku zkontroluj:</p>
<pre><code class="language-powershell">Get-WinEvent -LogName &quot;Microsoft-Windows-Bluetooth-Policy/Operational&quot; -MaxEvents 5 |
  Format-Table TimeCreated, Id, Message -Wrap
</code></pre>
<p>Historii spouštění úlohy najdeš v Task Scheduleru pod <strong>Task Scheduler Library</strong> → pravý klik na úlohu → <strong>Properties</strong> → záložka <strong>History</strong>.</p>
<h2>Řešení problémů</h2>
<table>
<thead>
<tr>
<th>Problém</th>
<th>Řešení</th>
</tr>
</thead>
<tbody>
<tr>
<td>Log je prázdný</td>
<td>Ověř, že je log povolený (krok 1)</td>
</tr>
<tr>
<td>Event ID 9 se neobjevuje</td>
<td>Některé BT adaptéry logují jinak – zkontroluj i logy <code>Bluetooth-BthMini</code> a <code>System</code></td>
</tr>
<tr>
<td>Úloha se nespouští</td>
<td>Zkontroluj, že XML query odpovídá přesné adrese zařízení; otestuj úlohu ručně přes pravý klik → Run</td>
</tr>
<tr>
<td>Voicemeeter se spustí vícekrát</td>
<td>V Settings úlohy nastav &ldquo;Do not start a new instance&rdquo;</td>
</tr>
</tbody>
</table>
<h2>Poznámky</h2>
<ul>
<li>Parametr <code>-r</code> zajistí restart audio engine Voicemeeter, což je užitečné pro reinicializaci BT audio zařízení.</li>
<li>Pokud chceš reagovat na <strong>jakékoli</strong> BT zařízení (ne jen jedno), odstraň z XML podmínku <code>and EventData[...]</code> a ponechej pouze <code>*[System[EventID=9]]</code>.</li>
<li>Po aktualizaci Windows může dojít ke změně struktury Event Logu – v takovém případě ověř event ID znovu.</li>
</ul>
    ]]></content>
    <author>
      <name>ruza</name>
    </author>
  </entry>
  <entry>
    <title>Nový design ruza.eu</title>
    <link href="https://ruza.eu/articles/novy-design.html" rel="alternate" type="text/html"/>
    <id>https://ruza.eu/articles/novy-design.html</id>
    <published>2026-04-17T00:00:00Z</published>
    <updated>2026-04-17T00:00:00Z</updated>
    <summary>Přepis starého osobního rozcestníku na nový terminálový design s Markdown workflow</summary>
    <content type="html"><![CDATA[
<p>Stará verze stránky byla prostá HTML tabulka — funkční, ale vizuálně z roku 2003. Po pár iteracích se zrodil nový design: černé pozadí, žluté odkazy, monospace typografie, Mastodon feed a jednotné <code>style.css</code>.</p>
<h2>Co se změnilo</h2>
<ul>
<li>dark mode s volitelným light mode přes <code>prefers-color-scheme</code></li>
<li>socialní odkazy v karetním gridu s pořádnými SVG ikonkami</li>
<li>GPG fingerprint s copy-to-clipboard funkcí</li>
<li>sekce <em>writing</em> s odkazy na články</li>
<li>tři nejnovější Mastodon posty načítané přes veřejné API</li>
<li>všechny stránky sdílí jediný <code>/style.css</code></li>
</ul>
<h2>Markdown pipeline</h2>
<p>Místo psaní HTML ručně teď mám build script <code>build.py</code>, který bere Markdown soubor s YAML frontmatter a generuje finální HTML. Proces:</p>
<ol>
<li>Napíšu článek v Obsidianu jako <code>.md</code></li>
<li>Spustím <code>python3 build.py articles/src/</code></li>
<li>Hotovo — vygeneruje se jak jednotlivý článek, tak aktualizovaný seznam</li>
</ol>
<h2>Citace</h2>
<blockquote>
<p>Dobré rozhraní je jako vtip. Když ho musíte vysvětlovat, není moc dobré.
— Martin LeBlanc</p>
</blockquote>
    ]]></content>
    <author>
      <name>ruza</name>
    </author>
  </entry>
</feed>