Silvian Cretuhttps://silviancretu.ro/2024-02-11T00:00:00+02:00Autentificarea în ANAF SPV, pe Linux si MacOS, din Firefox, cu certificat pe token2024-02-11T00:00:00+02:002024-02-11T00:00:00+02:00Silvian Cretutag:silviancretu.ro,2024-02-11:/autentificarea-in-anaf-spv-pe-linux-si-macos-din-firefox-cu-certificat-pe-token.html<p>Site-ul <a href="https://www.anaf.ro/">ANAF</a> ne întâmpină cu mesajul că facturarea electronică prin e-Factura este obligatorie de la 1 ianuarie 2024. Când vine vorba de ANAF, suportul pentru chestiile astea e mai complicat pe Linux sau MacOS.</p>
<p>Acest articol este o continuare a <a href="https://silviancretu.ro/autentificarea-in-anaf-spv-de-pe-linux-cu-certificat-pe-token.html">articolului precedent</a> în care am exemplificat cum se poate realiza autentificarea din Chrome, pe Linux</p>
<h3>Token-ul și Semnătura Digitală</h3>
<p>Voi folosi același <a href="https://certdigital.ro/kit-semnatura-electronica/">kit cu CryptoID de la CertDigital</a>. Acel mToken Crypto ID este făcut de <a href="https://longmai.net/products/mtoken/cryptoid/">LONGMAI</a>. Descărcați de la ei de pe site:</p>
<ul>
<li><a href="https://certdigital.ro/descarca-aplicatii-2/">Aplicație semnare CDP Client</a></li>
<li>si driver-ul <a href="https://certdigital.ro/drivere-token/">mToken Crypto ID</a></li>
</ul>
<p>... pentru Linux, respectiv MacOS.</p>
<p>Aplicația de semnat documente e scrisă în Java și e simplu de folosit. Știe să găsească token-ul atașat calculatorului, să-i ceară PIN-ul și, apoi, să semneze cu semnatura de pe token o listă de PDF-uri pe care o selectezi.</p>
<h3>Autentificarea în SPV, pe Linux, cu Firefox</h3>
<p>Pașii de mai jos <strong>nu funcționează dacă Firefox e instalat din <code>snap</code></strong>. Există un <a href="https://forum.snapcraft.io/t/access-external-lib-to-use-usb-token-in-firefox/13959/2">bug deschis la Snapcraft</a> pentru asta și <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1734371">altul la Mozilla</a>.
Pentru că folosesc Kubuntu, am instalat <a href="https://support.mozilla.org/en-US/kb/install-firefox-linux#w_install-firefox-deb-package-for-debian-based-distributions">Firefox din apt repository-ul celor de la Mozilla</a> (deci dintr-un .deb). Nu am testat cu Firefox instalat din Flatpak.</p>
<p>Aplicația CDP Client ne dă un indiciu despre unde se află librăria lui:</p>
<p><img alt="CDP Client Settings" src="https://silviancretu.ro/static/cdp client.png" title="CDP Client Settings"></p>
<p>Din Firefox, deschide fereastra <code>Settings</code> din meniul hamburger din dreapta sus.</p>
<p><img alt="Firefox Hamburger Menu" src="https://silviancretu.ro/static/firefox settings linux.png" title="Firefox Hamburger Menu"></p>
<p>Acolo caută <code>Security Devices</code>. </p>
<p><img alt="Firefox Settings" src="https://silviancretu.ro/static/firefox settings linux2.png" title="Firefox Settings"></p>
<p>Din fereastra ce se deschide, apasă <code>Load</code> și mergi către directorul personal unde va trebui să intri în directorul ascuns <code>.cdpclient</code> și să selectezi <code>libcryptoide_pkcs11.so</code>. În final ar trebui să arate așa:</p>
<p><img alt="Firefox Security Devices" src="https://silviancretu.ro/static/firefox security device linux.png" title="Firefox Security Devices"></p>
<p>Salvează și închide. Acum, la apăsarea butonului <code>Autentificare certificat</code> de pe site-ul <a href="https://www.anaf.ro/">ANAF</a>, Firefox ar trebui să ceară PIN-ul token-ului și, dacă-l primește pe cel corect, să te ducă în SPV.</p>
<p><img alt="Firefox ANAF" src="https://silviancretu.ro/static/firefox token pin.png" title="Firefox ANAF"></p>
<h3>Autentificarea în SPV, pe MacOS, cu Firefox</h3>
<p>Pașii de mai jos au fost testați pe un MacBook cu procesor Intel.</p>
<p>Aplicația CDP Client ne dă un indiciu despre unde se află librăria lui:</p>
<p><img alt="CDP Client Settings" src="https://silviancretu.ro/static/cdp client macos.png" title="CDP Client Settings"></p>
<p>Din Firefox, deschide fereastra <code>Setări</code>/<code>Settings</code> din meniul hamburger din dreapta sus. Acolo caută <code>Dispozitive de securitate</code> / <code>Security Devices</code>. </p>
<p><img alt="Firefox Settings" src="https://silviancretu.ro/static/firefox settings macos.png" title="Firefox Settings"></p>
<p>Din fereastra ce se deschide, apasă <code>Încarcă</code> / <code>Load</code> și mergi către directorul personal unde va trebui să intri în directorul ascuns <code>.cdpclient</code> (e posibil să trebuiască să apeși <code>Command</code> + <code>Shift</code> + <code>.</code> (punct) pentru a-l vedea) și să selectezi <code>libcryptoide_pkcs11.dylib</code>.</p>
<p><img alt="Firefox Security Devices" src="https://silviancretu.ro/static/firefox security device macos.png" title="Firefox Security Devices"></p>
<p>Salvează și închide. Acum, la apăsarea butonului <code>Autentificare certificat</code> de pe site-ul <a href="https://www.anaf.ro/">ANAF</a>, Firefox ar trebui să ceară PIN-ul token-ului și, dacă-l primește pe cel corect, să te ducă în SPV.</p>Autentificarea în ANAF SPV, de pe Linux, cu certificat pe token2024-01-31T00:00:00+02:002024-01-31T00:00:00+02:00Silvian Cretutag:silviancretu.ro,2024-01-31:/autentificarea-in-anaf-spv-de-pe-linux-cu-certificat-pe-token.html<p>Site-ul <a href="https://www.anaf.ro/">ANAF</a> ne întâmpină cu mesajul că facturarea electronică prin e-Factura este obligatorie de la 1 ianuarie 2024. Când vine vorba de ANAF, suportul pentru chestiile astea e mai complicat pe Linux... </p>
<h3>Token-ul și Semnătura Digitală</h3>
<p>În primul rând ne trebuie un token fizic pentru a ține semnătura digitală. Eu am optat să cumpăr un <a href="https://certdigital.ro/kit-semnatura-electronica/">kit cu CryptoID de la CertDigital</a>, pentru că ei oferă suport pentru Linux. Acel mToken Crypto ID este făcut de <a href="https://longmai.net/products/mtoken/cryptoid/">LONGMAI</a>.</p>
<p>Am descărcat de la ei de pe site:</p>
<ul>
<li><a href="https://certdigital.ro/descarca-aplicatii-2/">Aplicație semnare CDP Client Ubuntu / Debian x64</a></li>
<li>si driver-ul <a href="https://certdigital.ro/drivere-token/">mToken Crypto ID Linux</a></li>
</ul>
<p>Le-am instalat. Installer-ul de la driver a fost amuzant:</p>
<div class="highlight"><pre><span></span><code>The<span class="w"> </span>program<span class="w"> </span>does<span class="w"> </span>not<span class="w"> </span>start,<span class="w"> </span>please<span class="w"> </span>restart<span class="w"> </span>the<span class="w"> </span>system
------------Ignore<span class="w"> </span>the<span class="w"> </span>above<span class="w"> </span>error----------
-------------------Success------------------
</code></pre></div>
<p>... dar a funcționat.</p>
<p>Aplicația de semnat documente e scrisă în Java și e simplu de folosit. Știe să găsească token-ul atașat calculatorului, să-i ceară PIN-ul și, apoi, să semneze cu semnatura de pe token o listă de PDF-uri pe care o selectezi.</p>
<h3>Autentificarea în SPV, pe Linux, cu Chrome</h3>
<p>În colțul din dreapta sus de pe site-ul <a href="https://www.anaf.ro/">ANAF</a> este un buton care zice <code>Autentificare certificat</code>. Cel mai probabil vei primi eroare... Cum am rezolvat-o eu?</p>
<p>Următoarele comenzi au fost rulate pe (K)Ubuntu 22.04 pentru a mă autentifica cu Chrome:</p>
<p>E posibil să ai nevoie de <code>libnss3-tools</code> așa că:</p>
<div class="highlight"><pre><span></span><code>sudo<span class="w"> </span>apt<span class="w"> </span>install<span class="w"> </span>libnss3-tools
</code></pre></div>
<p>Ce module sunt acum în <a href="https://firefox-source-docs.mozilla.org/security/nss/index.html">librăria NSS</a>:</p>
<div class="highlight"><pre><span></span><code>modutil<span class="w"> </span>-dbdir<span class="w"> </span>sql:<span class="nv">$HOME</span>/.pki/nssdb<span class="w"> </span>-list
</code></pre></div>
<p>Aplicația CDP Client ne dă un indiciu despre unde se află librăria lui:</p>
<p><img alt="CDP Client Settings" src="https://silviancretu.ro/static/cdp client.png" title="CDP Client Settings"></p>
<p>Hai s-o adăugăm în NSS. <strong>Chrome ar trebui să fie închis</strong> în timpul acestei operațiuni:</p>
<div class="highlight"><pre><span></span><code>modutil<span class="w"> </span>-dbdir<span class="w"> </span>sql:<span class="nv">$HOME</span>/.pki/nssdb<span class="w"> </span>-add<span class="w"> </span><span class="s2">"mToken"</span><span class="w"> </span>-libfile<span class="w"> </span><span class="nv">$HOME</span>/.cdpclient/libcryptoide_pkcs11.so
</code></pre></div>
<p>Acum modulul ar trebui să apară:</p>
<div class="highlight"><pre><span></span><code>modutil<span class="w"> </span>-dbdir<span class="w"> </span>sql:<span class="nv">$HOME</span>/.pki/nssdb<span class="w"> </span>-list
</code></pre></div>
<p>După repornirea Chrome și apăsarea butonului <code>Autentificare certificat</code> de pe site-ul <a href="https://www.anaf.ro/">ANAF</a>, browser-ul ar trebui să ceară PIN-ul token-ului și, dacă-l primește pe cel corect, să te ducă în SPV.</p>Missing Pods in a Kubernetes StatefulSet2023-05-30T00:00:00+03:002023-05-30T00:00:00+03:00Silvian Cretutag:silviancretu.ro,2023-05-30:/missing-pods-in-a-kubernetes-statefulset.html<p>I got an alert today about a pod being in <code>CrashLoopBackOff</code>... I went to investigate:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>get<span class="w"> </span>pods<span class="w"> </span>-A
NAMESPACE<span class="w"> </span>NAME<span class="w"> </span>READY<span class="w"> </span>STATUS<span class="w"> </span>RESTARTS<span class="w"> </span>AGE
<span class="o">(</span>truncated<span class="w"> </span>output<span class="o">)</span>
default<span class="w"> </span>webserver-0<span class="w"> </span><span class="m">0</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">2539</span><span class="w"> </span><span class="o">(</span>23s<span class="w"> </span>ago<span class="o">)</span><span class="w"> </span>6d13h
default<span class="w"> </span>webserver-2<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
default<span class="w"> </span>webserver-3<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
default<span class="w"> </span>webserver-6<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
default<span class="w"> </span>webserver-7<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
default<span class="w"> </span>webserver-8<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
default<span class="w"> </span>webserver-9<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
</code></pre></div>
<p>Notice the amount of restarts that <code>webserver-0</code> has. And the fact that <code>webserver-1</code> is missing...</p>
<p>I initially went the Windows way, trying to restart the statefulset:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>rollout<span class="w"> </span>restart<span class="w"> </span>sts<span class="w"> </span>webserver
statefulset.apps/webserver<span class="w"> </span>restarted
</code></pre></div>
<p>... but this didn't solve the issue... Actually what's the issue?</p>
<p>Well, describing the pod showed that <code>webserver-0</code> is trying to pull an older tag of the <code>webserver</code> image, <code>23.03</code>, which isn't compatible with the version <code>23.04</code> which is running on all the other pods and microservices on the cluster... This causes it to fail the liveness probe and stay in this infinite loop:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>describe<span class="w"> </span>pod<span class="w"> </span>webserver-0<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>image
<span class="w"> </span>Image:<span class="w"> </span>artifact.host.fqdn/some/path/webserver:v23.03.26
<span class="w"> </span>Normal<span class="w"> </span>Pulled<span class="w"> </span>25m<span class="w"> </span>kubelet<span class="w"> </span>Successfully<span class="w"> </span>pulled<span class="w"> </span>image<span class="w"> </span><span class="s2">"artifact.host.fqdn/some/path/webserver:v23.03.26"</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="m">387</span>.441222ms
<span class="w"> </span>Normal<span class="w"> </span>Pulling<span class="w"> </span>24m<span class="w"> </span><span class="o">(</span>x2<span class="w"> </span>over<span class="w"> </span>25m<span class="o">)</span><span class="w"> </span>kubelet<span class="w"> </span>Pulling<span class="w"> </span>image<span class="w"> </span><span class="s2">"artifact.host.fqdn/some/path/webserver:v23.03.26"</span>
<span class="w"> </span>Normal<span class="w"> </span>Pulled<span class="w"> </span>24m<span class="w"> </span>kubelet<span class="w"> </span>Successfully<span class="w"> </span>pulled<span class="w"> </span>image<span class="w"> </span><span class="s2">"artifact.host.fqdn/some/path/webserver:v23.03.26"</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="m">314</span>.222387ms
</code></pre></div>
<p>Interesting enough, all the other pods in the statefulset are running on <code>webserver</code> tag <code>23.04</code>:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>describe<span class="w"> </span>pod<span class="w"> </span>webserver-8<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>image
<span class="w"> </span>Image:<span class="w"> </span>artifact.host.fqdn/some/path/webserver:v23.04.14
$<span class="w"> </span>kubectl<span class="w"> </span>describe<span class="w"> </span>pod<span class="w"> </span>webserver-1<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>image
Error<span class="w"> </span>from<span class="w"> </span>server<span class="w"> </span><span class="o">(</span>NotFound<span class="o">)</span>:<span class="w"> </span>pods<span class="w"> </span><span class="s2">"webserver-1"</span><span class="w"> </span>not<span class="w"> </span>found
$<span class="w"> </span>kubectl<span class="w"> </span>describe<span class="w"> </span>pod<span class="w"> </span>webserver-9<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>image
<span class="w"> </span>Image:<span class="w"> </span>artifact.host.fqdn/some/path/webserver:v23.04.14
</code></pre></div>
<p>The statefulset is OK with regards to the tag of the <code>webserver</code> image:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>get<span class="w"> </span>sts<span class="w"> </span>webserver<span class="w"> </span>-o<span class="w"> </span>yaml<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>image
<span class="w"> </span>image:<span class="w"> </span>artifact.host.fqdn/some/path/webserver:v23.04.14
<span class="w"> </span>imagePullPolicy:<span class="w"> </span>Always
</code></pre></div>
<p>Let's look at the revision history:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>rollout<span class="w"> </span><span class="nb">history</span><span class="w"> </span>sts<span class="w"> </span>webserver<span class="w"> </span>--revision<span class="w"> </span><span class="m">9</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>image
<span class="w"> </span>Image:<span class="w"> </span>artifact.host.fqdn/some/path/webserver:v23.03.26
$<span class="w"> </span>kubectl<span class="w"> </span>rollout<span class="w"> </span><span class="nb">history</span><span class="w"> </span>sts<span class="w"> </span>webserver<span class="w"> </span>--revision<span class="w"> </span><span class="m">10</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>image
<span class="w"> </span>Image:<span class="w"> </span>artifact.host.fqdn/some/path/webserver:v23.04.14
$<span class="w"> </span>kubectl<span class="w"> </span>rollout<span class="w"> </span><span class="nb">history</span><span class="w"> </span>sts<span class="w"> </span>webserver<span class="w"> </span>--revision<span class="w"> </span><span class="m">11</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>image
<span class="w"> </span>Image:<span class="w"> </span>artifact.host.fqdn/some/path/webserver:v23.04.14
$<span class="w"> </span>kubectl<span class="w"> </span>rollout<span class="w"> </span>undo<span class="w"> </span>sts<span class="w"> </span>webserver<span class="w"> </span>--to-revision<span class="w"> </span><span class="m">11</span>
statefulset.apps/webserver<span class="w"> </span>skipped<span class="w"> </span>rollback<span class="w"> </span><span class="o">(</span>current<span class="w"> </span>template<span class="w"> </span>already<span class="w"> </span>matches<span class="w"> </span>revision<span class="w"> </span><span class="m">11</span><span class="o">)</span>
</code></pre></div>
<p>Revision 10 was created by a <code>helm upgrade</code> command and 11 by my rolling restart...</p>
<p>Searching the internet got me to <a href="https://github.com/kubernetes/kubernetes/issues/41012#issuecomment-278116804">this interesting answer</a> that says: <em>The more likely event here seems like one of your node deletions took down pod-1 and pod-2 after pod-0 went unhealthy. In that case, we do not attempt to recreate pod-1 or 2 till pod-0 becomes healthy again. The rationale for …</em></p><p>I got an alert today about a pod being in <code>CrashLoopBackOff</code>... I went to investigate:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>get<span class="w"> </span>pods<span class="w"> </span>-A
NAMESPACE<span class="w"> </span>NAME<span class="w"> </span>READY<span class="w"> </span>STATUS<span class="w"> </span>RESTARTS<span class="w"> </span>AGE
<span class="o">(</span>truncated<span class="w"> </span>output<span class="o">)</span>
default<span class="w"> </span>webserver-0<span class="w"> </span><span class="m">0</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">2539</span><span class="w"> </span><span class="o">(</span>23s<span class="w"> </span>ago<span class="o">)</span><span class="w"> </span>6d13h
default<span class="w"> </span>webserver-2<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
default<span class="w"> </span>webserver-3<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
default<span class="w"> </span>webserver-6<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
default<span class="w"> </span>webserver-7<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
default<span class="w"> </span>webserver-8<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
default<span class="w"> </span>webserver-9<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
</code></pre></div>
<p>Notice the amount of restarts that <code>webserver-0</code> has. And the fact that <code>webserver-1</code> is missing...</p>
<p>I initially went the Windows way, trying to restart the statefulset:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>rollout<span class="w"> </span>restart<span class="w"> </span>sts<span class="w"> </span>webserver
statefulset.apps/webserver<span class="w"> </span>restarted
</code></pre></div>
<p>... but this didn't solve the issue... Actually what's the issue?</p>
<p>Well, describing the pod showed that <code>webserver-0</code> is trying to pull an older tag of the <code>webserver</code> image, <code>23.03</code>, which isn't compatible with the version <code>23.04</code> which is running on all the other pods and microservices on the cluster... This causes it to fail the liveness probe and stay in this infinite loop:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>describe<span class="w"> </span>pod<span class="w"> </span>webserver-0<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>image
<span class="w"> </span>Image:<span class="w"> </span>artifact.host.fqdn/some/path/webserver:v23.03.26
<span class="w"> </span>Normal<span class="w"> </span>Pulled<span class="w"> </span>25m<span class="w"> </span>kubelet<span class="w"> </span>Successfully<span class="w"> </span>pulled<span class="w"> </span>image<span class="w"> </span><span class="s2">"artifact.host.fqdn/some/path/webserver:v23.03.26"</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="m">387</span>.441222ms
<span class="w"> </span>Normal<span class="w"> </span>Pulling<span class="w"> </span>24m<span class="w"> </span><span class="o">(</span>x2<span class="w"> </span>over<span class="w"> </span>25m<span class="o">)</span><span class="w"> </span>kubelet<span class="w"> </span>Pulling<span class="w"> </span>image<span class="w"> </span><span class="s2">"artifact.host.fqdn/some/path/webserver:v23.03.26"</span>
<span class="w"> </span>Normal<span class="w"> </span>Pulled<span class="w"> </span>24m<span class="w"> </span>kubelet<span class="w"> </span>Successfully<span class="w"> </span>pulled<span class="w"> </span>image<span class="w"> </span><span class="s2">"artifact.host.fqdn/some/path/webserver:v23.03.26"</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="m">314</span>.222387ms
</code></pre></div>
<p>Interesting enough, all the other pods in the statefulset are running on <code>webserver</code> tag <code>23.04</code>:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>describe<span class="w"> </span>pod<span class="w"> </span>webserver-8<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>image
<span class="w"> </span>Image:<span class="w"> </span>artifact.host.fqdn/some/path/webserver:v23.04.14
$<span class="w"> </span>kubectl<span class="w"> </span>describe<span class="w"> </span>pod<span class="w"> </span>webserver-1<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>image
Error<span class="w"> </span>from<span class="w"> </span>server<span class="w"> </span><span class="o">(</span>NotFound<span class="o">)</span>:<span class="w"> </span>pods<span class="w"> </span><span class="s2">"webserver-1"</span><span class="w"> </span>not<span class="w"> </span>found
$<span class="w"> </span>kubectl<span class="w"> </span>describe<span class="w"> </span>pod<span class="w"> </span>webserver-9<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>image
<span class="w"> </span>Image:<span class="w"> </span>artifact.host.fqdn/some/path/webserver:v23.04.14
</code></pre></div>
<p>The statefulset is OK with regards to the tag of the <code>webserver</code> image:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>get<span class="w"> </span>sts<span class="w"> </span>webserver<span class="w"> </span>-o<span class="w"> </span>yaml<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>image
<span class="w"> </span>image:<span class="w"> </span>artifact.host.fqdn/some/path/webserver:v23.04.14
<span class="w"> </span>imagePullPolicy:<span class="w"> </span>Always
</code></pre></div>
<p>Let's look at the revision history:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>rollout<span class="w"> </span><span class="nb">history</span><span class="w"> </span>sts<span class="w"> </span>webserver<span class="w"> </span>--revision<span class="w"> </span><span class="m">9</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>image
<span class="w"> </span>Image:<span class="w"> </span>artifact.host.fqdn/some/path/webserver:v23.03.26
$<span class="w"> </span>kubectl<span class="w"> </span>rollout<span class="w"> </span><span class="nb">history</span><span class="w"> </span>sts<span class="w"> </span>webserver<span class="w"> </span>--revision<span class="w"> </span><span class="m">10</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>image
<span class="w"> </span>Image:<span class="w"> </span>artifact.host.fqdn/some/path/webserver:v23.04.14
$<span class="w"> </span>kubectl<span class="w"> </span>rollout<span class="w"> </span><span class="nb">history</span><span class="w"> </span>sts<span class="w"> </span>webserver<span class="w"> </span>--revision<span class="w"> </span><span class="m">11</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>image
<span class="w"> </span>Image:<span class="w"> </span>artifact.host.fqdn/some/path/webserver:v23.04.14
$<span class="w"> </span>kubectl<span class="w"> </span>rollout<span class="w"> </span>undo<span class="w"> </span>sts<span class="w"> </span>webserver<span class="w"> </span>--to-revision<span class="w"> </span><span class="m">11</span>
statefulset.apps/webserver<span class="w"> </span>skipped<span class="w"> </span>rollback<span class="w"> </span><span class="o">(</span>current<span class="w"> </span>template<span class="w"> </span>already<span class="w"> </span>matches<span class="w"> </span>revision<span class="w"> </span><span class="m">11</span><span class="o">)</span>
</code></pre></div>
<p>Revision 10 was created by a <code>helm upgrade</code> command and 11 by my rolling restart...</p>
<p>Searching the internet got me to <a href="https://github.com/kubernetes/kubernetes/issues/41012#issuecomment-278116804">this interesting answer</a> that says: <em>The more likely event here seems like one of your node deletions took down pod-1 and pod-2 after pod-0 went unhealthy. In that case, we do not attempt to recreate pod-1 or 2 till pod-0 becomes healthy again. The rationale for this is that users rely on the deterministic initialization order and write logic around that guarantee. To bring up the pods in arbitrary order would violate this guarantee.</em> This might indeed be true, as this Kubernetes cluster was recently upgraded and, of course, the nodes were taken down during that event. Unfortunately </p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>get<span class="w"> </span>events<span class="w"> </span>
</code></pre></div>
<p>... didn't bring up any useful information.</p>
<p>OK... But how do I fix it?</p>
<p>My initial attempt was to scale down the HorizontalPodAutoscaler and StatefulSet to 1 replica, hoping that only <code>webserver-0</code> would stay alive and, in a worst case scenario, I'd delete it:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>get<span class="w"> </span>hpa<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>webserver
webserver<span class="w"> </span>StatefulSet/webserver<span class="w"> </span><span class="m">51</span>%/50%<span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="m">1</span><span class="w"> </span>63d
$<span class="w"> </span>kubectl<span class="w"> </span>get<span class="w"> </span>sts<span class="w"> </span>webserver<span class="w"> </span>-o<span class="w"> </span>yaml<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>replicas
<span class="w"> </span>replicas:<span class="w"> </span><span class="m">1</span>
<span class="w"> </span>availableReplicas:<span class="w"> </span><span class="m">6</span>
<span class="w"> </span>currentReplicas:<span class="w"> </span><span class="m">2</span>
<span class="w"> </span>readyReplicas:<span class="w"> </span><span class="m">6</span>
<span class="w"> </span>replicas:<span class="w"> </span><span class="m">7</span>
<span class="w"> </span>updatedReplicas:<span class="w"> </span><span class="m">5</span>
</code></pre></div>
<p>This didn't work. The StatefulSet didn't want to scale down, because it wasn't healthy:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>get<span class="w"> </span>pods<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>webserver
webserver-0<span class="w"> </span><span class="m">0</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="o">(</span>42s<span class="w"> </span>ago<span class="o">)</span><span class="w"> </span>106s
webserver-2<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d19h
webserver-3<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
webserver-6<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
webserver-7<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
webserver-8<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
webserver-9<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
</code></pre></div>
<p>So... I thought I'd give the StatefulSet a hand... by patching the pod itself:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>patch<span class="w"> </span>pod<span class="w"> </span>webserver-0<span class="w"> </span>-p<span class="w"> </span><span class="s1">'{"spec":{"containers":[{"name":"webserver","image":"artifact.host.fqdn/some/path/webserver:v23.04.14"}]}}'</span>
pod/webserver-0<span class="w"> </span>patched
</code></pre></div>
<p>It worked!</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>get<span class="w"> </span>pods<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>webserver
webserver-0<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">7</span><span class="w"> </span><span class="o">(</span>4m26s<span class="w"> </span>ago<span class="o">)</span><span class="w"> </span>16m
webserver-2<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d19h
webserver-3<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d19h
webserver-6<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d19h
webserver-7<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d19h
webserver-8<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
webserver-9<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Terminating<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d18h
</code></pre></div>
<p>I fixed back the HorizontalPodAutoscaler:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>get<span class="w"> </span>hpa<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>webserver
webserver<span class="w"> </span>StatefulSet/webserver<span class="w"> </span><span class="m">104</span>%/50%<span class="w"> </span><span class="m">2</span><span class="w"> </span><span class="m">40</span><span class="w"> </span><span class="m">10</span><span class="w"> </span>63d
$<span class="w"> </span>kubectl<span class="w"> </span>get<span class="w"> </span>pods<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>webserver
webserver-0<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">7</span><span class="w"> </span><span class="o">(</span>6m20s<span class="w"> </span>ago<span class="o">)</span><span class="w"> </span>18m
webserver-1<span class="w"> </span><span class="m">0</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="o">(</span>18s<span class="w"> </span>ago<span class="o">)</span><span class="w"> </span>84s
webserver-2<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d19h
webserver-3<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d19h
webserver-6<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d19h
webserver-7<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d19h
</code></pre></div>
<p>Pod 1 doesn't want to start:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>describe<span class="w"> </span>pod<span class="w"> </span>webserver-0<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>image
<span class="w"> </span>Image:<span class="w"> </span>artifact.host.fqdn/some/path/webserver:v23.04.14
<span class="w"> </span>Normal<span class="w"> </span>Pulled<span class="w"> </span>19m<span class="w"> </span>kubelet<span class="w"> </span>Successfully<span class="w"> </span>pulled<span class="w"> </span>image<span class="w"> </span><span class="s2">"artifact.host.fqdn/some/path/webserver:v23.03.26"</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="m">341</span>.684123ms
<span class="w"> </span>Normal<span class="w"> </span>Pulling<span class="w"> </span>18m<span class="w"> </span><span class="o">(</span>x2<span class="w"> </span>over<span class="w"> </span>19m<span class="o">)</span><span class="w"> </span>kubelet<span class="w"> </span>Pulling<span class="w"> </span>image<span class="w"> </span><span class="s2">"artifact.host.fqdn/some/path/webserver:v23.03.26"</span>
<span class="w"> </span>Normal<span class="w"> </span>Pulled<span class="w"> </span>18m<span class="w"> </span>kubelet<span class="w"> </span>Successfully<span class="w"> </span>pulled<span class="w"> </span>image<span class="w"> </span><span class="s2">"artifact.host.fqdn/some/path/webserver:v23.03.26"</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="m">329</span>.854725ms
$<span class="w"> </span>kubectl<span class="w"> </span>describe<span class="w"> </span>pod<span class="w"> </span>webserver-1<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-i<span class="w"> </span>image
<span class="w"> </span>Image:<span class="w"> </span>artifact.host.fqdn/some/path/webserver:v23.03.26
<span class="w"> </span>Normal<span class="w"> </span>Pulled<span class="w"> </span>2m9s<span class="w"> </span>kubelet<span class="w"> </span>Successfully<span class="w"> </span>pulled<span class="w"> </span>image<span class="w"> </span><span class="s2">"artifact.host.fqdn/some/path/webserver:v23.03.26"</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="m">3</span>.110530737s
<span class="w"> </span>Normal<span class="w"> </span>Pulling<span class="w"> </span>67s<span class="w"> </span><span class="o">(</span>x2<span class="w"> </span>over<span class="w"> </span>2m12s<span class="o">)</span><span class="w"> </span>kubelet<span class="w"> </span>Pulling<span class="w"> </span>image<span class="w"> </span><span class="s2">"artifact.host.fqdn/some/path/webserver:v23.03.26"</span>
<span class="w"> </span>Normal<span class="w"> </span>Pulled<span class="w"> </span>66s<span class="w"> </span>kubelet<span class="w"> </span>Successfully<span class="w"> </span>pulled<span class="w"> </span>image<span class="w"> </span><span class="s2">"artifact.host.fqdn/some/path/webserver:v23.03.26"</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="m">314</span>.394334ms
</code></pre></div>
<p>Let's fix it as well:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>patch<span class="w"> </span>pod<span class="w"> </span>webserver-1<span class="w"> </span>-p<span class="w"> </span><span class="s1">'{"spec":{"containers":[{"name":"webserver","image":"artifact.host.fqdn/some/path/webserver:v23.04.14"}]}}'</span>
pod/webserver-1<span class="w"> </span>patched
</code></pre></div>
<p>Better:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>get<span class="w"> </span>pods<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>webserver
webserver-0<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">7</span><span class="w"> </span><span class="o">(</span>8m58s<span class="w"> </span>ago<span class="o">)</span><span class="w"> </span>21m
webserver-1<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">3</span><span class="w"> </span><span class="o">(</span>44s<span class="w"> </span>ago<span class="o">)</span><span class="w"> </span>4m2s
webserver-2<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d19h
webserver-3<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d19h
webserver-4<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>32s
webserver-5<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>21s
webserver-6<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d19h
webserver-7<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d19h
webserver-8<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>11s
webserver-9<span class="w"> </span><span class="m">0</span>/1<span class="w"> </span>Init:0/1<span class="w"> </span><span class="m">0</span><span class="w"> </span>1s
$<span class="w"> </span>kubectl<span class="w"> </span>get<span class="w"> </span>hpa<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>webserver
webserver<span class="w"> </span>StatefulSet/webserver<span class="w"> </span><span class="m">54</span>%/50%<span class="w"> </span><span class="m">2</span><span class="w"> </span><span class="m">40</span><span class="w"> </span><span class="m">11</span><span class="w"> </span>63d
$<span class="w"> </span>kubectl<span class="w"> </span>get<span class="w"> </span>pods<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>webserver
webserver-0<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">7</span><span class="w"> </span><span class="o">(</span>9m15s<span class="w"> </span>ago<span class="o">)</span><span class="w"> </span>21m
webserver-1<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">3</span><span class="w"> </span><span class="o">(</span>61s<span class="w"> </span>ago<span class="o">)</span><span class="w"> </span>4m19s
webserver-10<span class="w"> </span><span class="m">0</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>7s
webserver-2<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d19h
webserver-3<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d19h
webserver-4<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>49s
webserver-5<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>38s
webserver-6<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d19h
webserver-7<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>6d19h
webserver-8<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>28s
webserver-9<span class="w"> </span><span class="m">1</span>/1<span class="w"> </span>Running<span class="w"> </span><span class="m">0</span><span class="w"> </span>18s
</code></pre></div>
<p>Sometimes Kubernetes needs a little help, it seems...</p>Terraform Template Gimmicks2023-05-24T00:00:00+03:002023-05-24T00:00:00+03:00Silvian Cretutag:silviancretu.ro,2023-05-24:/terraform-template-gimmicks.html<p>While managing multiple environments with Terraform, you might come across this scenario:</p>
<ul>
<li>you have to manage multiple environments</li>
<li>all the environments use the same configuration file, but with different values</li>
<li>so, the above mentioned configuration file has to be a template</li>
<li>some environments tend to be <em>snowflakes</em>, as they have non-default values</li>
</ul>
<p>You want to accomplish two things:</p>
<ul>
<li>limit the number of variables you maintain for each environment</li>
<li>not replicate a <em>snowflake variable</em> to a <em>standard / non-snowflake environment</em></li>
</ul>
<h3>The solution</h3>
<ul>
<li>is described in <a href="https://discuss.hashicorp.com/t/how-to-set-a-default-value-for-a-variable-in-a-terraform-template/12817/13"><strong>this Hashicorp blog post</strong></a></li>
<li>and presented with a demo code in <a href="https://github.com/scretu/terraform-template-gimmicks"><strong>this repository of mine</strong></a></li>
</ul>
<h3>The TL;DR version</h3>
<p><img alt="variables and outputs" src="https://silviancretu.ro/static/variables_and_outputs.png" title="variables and outputs"></p>
<h3>The structure of the Terraform code</h3>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>tree<span class="w"> </span>
.
├──<span class="w"> </span>common-template-file.tftpl
├──<span class="w"> </span>environmentA
│<span class="w"> </span>├──<span class="w"> </span>render-template.tf
│<span class="w"> </span>└──<span class="w"> </span>variables.tf
├──<span class="w"> </span>environmentB
│<span class="w"> </span>├──<span class="w"> </span>render-template.tf
│<span class="w"> </span>└──<span class="w"> </span>variables.tf
├──<span class="w"> </span>environmentC
│<span class="w"> </span>├──<span class="w"> </span>render-template.tf
│<span class="w"> </span>└──<span class="w"> </span>variables.tf
├──<span class="w"> </span>environmentD
│<span class="w"> </span>├──<span class="w"> </span>render-template.tf
│<span class="w"> </span>└──<span class="w"> </span>variables.tf
├──<span class="w"> </span>environmentE
│<span class="w"> </span>├──<span class="w"> </span>render-template.tf
│<span class="w"> </span>└──<span class="w"> </span>variables.tf
└──<span class="w"> </span>README.md
</code></pre></div>
<p>So</p>
<ul>
<li>there's a common template for all environments</li>
<li>each environment has its own directory</li>
<li>each environment has a bunch of .tf files holding the Terraform code</li>
<li>in <code>variables.tf</code> each environment has its own custom set of variables</li>
</ul>
<h3>The template and the variables</h3>
<p>This is the common template file:</p>
<div class="highlight"><pre><span></span><code><span class="n">global</span><span class="o">:</span>
<span class="w"> </span><span class="n">hostnames</span><span class="o">:</span><span class="w"> </span><span class="o">[</span><span class="n">$</span><span class="o">{</span><span class="n">hostname</span><span class="o">}]</span>
<span class="w"> </span><span class="n">importantStuff</span><span class="o">:</span>
<span class="w"> </span><span class="n">enableThis</span><span class="o">:</span><span class="w"> </span><span class="n">$</span><span class="o">{</span><span class="n">enable_this</span><span class="o">}</span>
<span class="n">thisWorksIfVariableIsDeclared</span><span class="o">:</span>
<span class="w"> </span><span class="n">thisDependsOnTheValueOfVariable</span><span class="o">:</span><span class="w"> </span><span class="o">%{</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">enable_this</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">false</span><span class="w"> </span><span class="o">}</span><span class="mi">0</span><span class="o">%{</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="o">}</span><span class="mi">1</span><span class="o">%{</span><span class="w"> </span><span class="n">endif</span><span class="w"> </span><span class="o">}</span>
<span class="n">thisWorksEvenIfVariableIsMissing</span><span class="o">:</span>
<span class="w"> </span><span class="n">VarOne</span><span class="o">:</span><span class="w"> </span><span class="n">$</span><span class="o">{</span><span class="k">try</span><span class="o">(</span><span class="n">optional_vars</span><span class="o">.</span><span class="na">var_one</span><span class="o">,</span><span class="w"> </span><span class="mi">2</span><span class="o">)}</span>
<span class="w"> </span><span class="n">VarTwo</span><span class="o">:</span><span class="w"> </span><span class="n">$</span><span class="o">{</span><span class="k">try</span><span class="o">(</span><span class="n">optional_vars</span><span class="o">.</span><span class="na">var_two</span><span class="o">,</span><span class="w"> </span><span class="s2">"bla"</span><span class="o">)}</span>
<span class="w"> </span><span class="n">VarThree</span><span class="o">:</span><span class="w"> </span><span class="n">$</span><span class="o">{</span><span class="k">try</span><span class="o">(</span><span class="n">optional_vars</span><span class="o">.</span><span class="na">var_three</span><span class="o">,</span><span class="w"> </span><span class="kc">true</span><span class="o">)}</span>
</code></pre></div>
<p>The are two important sections in the template:</p>
<h4>the first one:</h4>
<div class="highlight"><pre><span></span><code> thisDependsOnTheValueOfVariable: %{ if enable_this == false }0%{ else }1%{ endif }
</code></pre></div>
<ul>
<li><code>enable_this</code> is a variable that must exist in all <code>variables.tf</code> files</li>
<li><strong>environmentE</strong> doesn't have it and you'll see Terraform failing because of that</li>
<li>depending on its value, another variable is set. </li>
</ul>
<h4>the second one:</h4>
<div class="highlight"><pre><span></span><code><span class="n">thisWorksEvenIfVariableIsMissing</span><span class="o">:</span>
<span class="w"> </span><span class="n">VarOne</span><span class="o">:</span><span class="w"> </span><span class="n">$</span><span class="o">{</span><span class="k">try</span><span class="o">(</span><span class="n">optional_vars</span><span class="o">.</span><span class="na">var_one</span><span class="o">,</span><span class="w"> </span><span class="mi">2</span><span class="o">)}</span>
<span class="w"> </span><span class="n">VarTwo</span><span class="o">:</span><span class="w"> </span><span class="n">$</span><span class="o">{</span><span class="k">try</span><span class="o">(</span><span class="n">optional_vars</span><span class="o">.</span><span class="na">var_two</span><span class="o">,</span><span class="w"> </span><span class="s2">"bla"</span><span class="o">)}</span>
<span class="w"> </span><span class="n">VarThree</span><span class="o">:</span><span class="w"> </span><span class="n">$</span><span class="o">{</span><span class="k">try</span><span class="o">(</span><span class="n">optional_vars</span><span class="o">.</span><span class="na">var_three</span><span class="o">,</span><span class="w"> </span><span class="kc">true</span><span class="o">)}</span>
</code></pre></div>
<ul>
<li><code>optional_vars</code> is a section (that can have any other name) that must exist in all <code>variables.tf</code> files</li>
<li><strong>environmentD</strong> doesn't have it and you'll see Terraform failing because of that</li>
<li>but any variable nested under <code>optional_vars</code> is optional and can be missing</li>
</ul>
<p>So, this will work in <code>variables.tf</code>:</p>
<div class="highlight"><pre><span></span><code><span class="nb">template_variables</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="na">git_tag</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"some_hash"</span>
<span class="w"> </span><span class="na">hostname</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"A.foo.bar"</span>
<span class="w"> </span><span class="na">enable_this</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="no">false</span>
<span class="w"> </span><span class="nb">optional_vars</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{}</span>
<span class="p">}</span>
</code></pre></div>
<p>and this will work as well:</p>
<div class="highlight"><pre><span></span><code><span class="nb">template_variables</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="na">git_tag</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"C"</span>
<span class="w"> </span><span class="na">hostname</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"C.foo.bar"</span>
<span class="w"> </span><span class="na">enable_this</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="no">true</span>
<span class="w"> </span><span class="nb">optional_vars</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="na">var_one</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"C has both var_one"</span>
<span class="w"> </span><span class="na">var_three</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"... and var_three"</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>but this won't:</p>
<div class="highlight"><pre><span></span><code><span class="nb">template_variables</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="na">git_tag</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"some_hash"</span>
<span class="w"> </span><span class="na">hostname</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"D.foo.bar"</span>
<span class="w"> </span><span class="na">enable_this</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="no">false</span>
<span class="p">}</span>
</code></pre></div>Kafka, Kubernetes and Not enough space2022-08-27T00:00:00+03:002022-08-27T00:00:00+03:00Silvian Cretutag:silviancretu.ro,2022-08-27:/kafka-kubernetes-and-not-enough-space.html<p>So I came across a situation and it took me a while to figure it out. So I'm putting this together as it might help others as well.</p>
<p>Let's say you have <a href="https://github.com/bitnami/charts/tree/master/bitnami/kafka">Bitnami's packaged Kafka</a> cluster running on Kubernetes on StatefulSets, (so not managed by an operator like <a href="https://strimzi.io/">Strimzi</a>). And the pods start restarting...</p>
<p>Looking at the logs you might see stuff like this:</p>
<div class="highlight"><pre><span></span><code>-9ee616bdcf73,<span class="w"> </span><span class="nv">partition</span><span class="o">=</span><span class="m">0</span>,<span class="w"> </span><span class="nv">highWatermark</span><span class="o">=</span><span class="m">0</span>,<span class="w"> </span><span class="nv">lastStableOffset</span><span class="o">=</span><span class="m">0</span>,<span class="w"> </span><span class="nv">logStartOffset</span><span class="o">=</span><span class="m">0</span>,<span class="w"> </span><span class="nv">logEndOffset</span><span class="o">=</span><span class="m">0</span><span class="o">)</span><span class="w"> </span>with<span class="w"> </span><span class="m">1</span><span class="w"> </span>segments<span class="w"> </span><span class="k">in</span><span class="w"> </span>2ms<span class="w"> </span><span class="o">(</span><span class="m">32564</span>/32564<span class="w"> </span>loaded<span class="w"> </span><span class="k">in</span><span class="w"> </span>/bitnami/kafka/data<span class="o">)</span><span class="w"> </span><span class="o">(</span>kafka.log.LogManager<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:05,895<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Loaded<span class="w"> </span><span class="m">32564</span><span class="w"> </span>logs<span class="w"> </span><span class="k">in</span><span class="w"> </span>161618ms.<span class="w"> </span><span class="o">(</span>kafka.log.LogManager<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:05,896<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Starting<span class="w"> </span>log<span class="w"> </span>cleanup<span class="w"> </span>with<span class="w"> </span>a<span class="w"> </span>period<span class="w"> </span>of<span class="w"> </span><span class="m">300000</span><span class="w"> </span>ms.<span class="w"> </span><span class="o">(</span>kafka.log.LogManager<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:05,896<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Starting<span class="w"> </span>log<span class="w"> </span>flusher<span class="w"> </span>with<span class="w"> </span>a<span class="w"> </span>default<span class="w"> </span>period<span class="w"> </span>of<span class="w"> </span><span class="m">9223372036854775807</span><span class="w"> </span>ms.<span class="w"> </span><span class="o">(</span>kafka.log.LogManager<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:05,908<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Starting<span class="w"> </span>the<span class="w"> </span>log<span class="w"> </span>cleaner<span class="w"> </span><span class="o">(</span>kafka.log.LogCleaner<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:05,999<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>kafka-log-cleaner-thread-0<span class="o">]</span>:<span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.log.LogCleaner<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,296<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>BrokerToControllerChannelManager<span class="w"> </span><span class="nv">broker</span><span class="o">=</span><span class="m">0</span><span class="w"> </span><span class="nv">name</span><span class="o">=</span>forwarding<span class="o">]</span>:<span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.server.BrokerToControllerRequestThread<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,419<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Updated<span class="w"> </span>connection-accept-rate<span class="w"> </span>max<span class="w"> </span>connection<span class="w"> </span>creation<span class="w"> </span>rate<span class="w"> </span>to<span class="w"> </span><span class="m">2147483647</span><span class="w"> </span><span class="o">(</span>kafka.network.ConnectionQuotas<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,428<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Awaiting<span class="w"> </span>socket<span class="w"> </span>connections<span class="w"> </span>on<span class="w"> </span><span class="m">0</span>.0.0.0:9093.<span class="w"> </span><span class="o">(</span>kafka.network.Acceptor<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,463<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>SocketServer<span class="w"> </span><span class="nv">listenerType</span><span class="o">=</span>ZK_BROKER,<span class="w"> </span><span class="nv">nodeId</span><span class="o">=</span><span class="m">0</span><span class="o">]</span><span class="w"> </span>Created<span class="w"> </span>data-plane<span class="w"> </span>acceptor<span class="w"> </span>and<span class="w"> </span>processors<span class="w"> </span><span class="k">for</span><span class="w"> </span>endpoint<span class="w"> </span>:<span class="w"> </span>ListenerName<span class="o">(</span>INTERNAL<span class="o">)</span><span class="w"> </span><span class="o">(</span>kafka.network.SocketServer<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,464<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Updated<span class="w"> </span>connection-accept-rate<span class="w"> </span>max<span class="w"> </span>connection<span class="w"> </span>creation<span class="w"> </span>rate<span class="w"> </span>to<span class="w"> </span><span class="m">2147483647</span><span class="w"> </span><span class="o">(</span>kafka.network.ConnectionQuotas<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,464<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Awaiting<span class="w"> </span>socket<span class="w"> </span>connections<span class="w"> </span>on<span class="w"> </span><span class="m">0</span>.0.0.0:9092.<span class="w"> </span><span class="o">(</span>kafka.network.Acceptor<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,473<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>SocketServer<span class="w"> </span><span class="nv">listenerType</span><span class="o">=</span>ZK_BROKER,<span class="w"> </span><span class="nv">nodeId</span><span class="o">=</span><span class="m">0</span><span class="o">]</span><span class="w"> </span>Created<span class="w"> </span>data-plane<span class="w"> </span>acceptor<span class="w"> </span>and<span class="w"> </span>processors<span class="w"> </span><span class="k">for</span><span class="w"> </span>endpoint<span class="w"> </span>:<span class="w"> </span>ListenerName<span class="o">(</span>CLIENT<span class="o">)</span><span class="w"> </span><span class="o">(</span>kafka.network.SocketServer<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,481<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>BrokerToControllerChannelManager<span class="w"> </span><span class="nv">broker</span><span class="o">=</span><span class="m">0</span><span class="w"> </span><span class="nv">name</span><span class="o">=</span>alterIsr<span class="o">]</span>:<span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.server.BrokerToControllerRequestThread<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,508<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>ExpirationReaper-0-Produce<span class="o">]</span>:<span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.server.DelayedOperationPurgatory<span class="nv">$ExpiredOperationReaper</span><span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,509<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>ExpirationReaper-0-Fetch<span class="o">]</span>:<span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.server.DelayedOperationPurgatory<span class="nv">$ExpiredOperationReaper</span><span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,511<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>ExpirationReaper-0-DeleteRecords<span class="o">]</span>:<span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.server.DelayedOperationPurgatory<span class="nv">$ExpiredOperationReaper</span><span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,512<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>ExpirationReaper-0-ElectLeader<span class="o">]</span>:<span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.server.DelayedOperationPurgatory<span class="nv">$ExpiredOperationReaper</span><span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,529<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>LogDirFailureHandler<span class="o">]</span>:<span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.server.ReplicaManager<span class="nv">$LogDirFailureHandler</span><span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,611<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Creating<span class="w"> </span>/brokers/ids/0<span class="w"> </span><span class="o">(</span>is<span class="w"> </span>it<span class="w"> </span>secure?<span class="w"> </span><span class="nb">false</span><span class="o">)</span><span class="w"> </span><span class="o">(</span>kafka.zk.KafkaZkClient<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,642<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Stat<span class="w"> </span>of<span class="w"> </span>the<span class="w"> </span>created<span class="w"> </span>znode<span class="w"> </span>at<span class="w"> </span>/brokers/ids/0<span class="w"> </span>is:<span class="w"> </span><span class="m">511242113</span>,511242113,1657140366624,1657140366624,1,0,0,72062637399277581,364,0,511242113
<span class="w"> </span><span class="o">(</span>kafka.zk.KafkaZkClient<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,643<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Registered<span class="w"> </span>broker<span class="w"> </span><span class="m">0</span><span class="w"> </span>at<span class="w"> </span>path<span class="w"> </span>/brokers/ids/0<span class="w"> </span>with<span class="w"> </span>addresses:<span class="w"> </span>INTERNAL://kafka-0.kafka-headless.default.svc.cluster.local:9093,CLIENT://kafka-0.kafka-headless.default.svc.cluster.local:9092,<span class="w"> </span>czxid<span class="w"> </span><span class="o">(</span>broker<span class="w"> </span>epoch<span class="o">)</span>:<span class="w"> </span><span class="m">511242113</span><span class="w"> </span><span class="o">(</span>kafka.zk.KafkaZkClient<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,758<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>ControllerEventThread<span class="w"> </span><span class="nv">controllerId</span><span class="o">=</span><span class="m">0</span><span class="o">]</span><span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.controller.ControllerEventManager<span class="nv">$ControllerEventThread</span><span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,765<span class="o">]</span><span class="w"> </span>INFO …</code></pre></div><p>So I came across a situation and it took me a while to figure it out. So I'm putting this together as it might help others as well.</p>
<p>Let's say you have <a href="https://github.com/bitnami/charts/tree/master/bitnami/kafka">Bitnami's packaged Kafka</a> cluster running on Kubernetes on StatefulSets, (so not managed by an operator like <a href="https://strimzi.io/">Strimzi</a>). And the pods start restarting...</p>
<p>Looking at the logs you might see stuff like this:</p>
<div class="highlight"><pre><span></span><code>-9ee616bdcf73,<span class="w"> </span><span class="nv">partition</span><span class="o">=</span><span class="m">0</span>,<span class="w"> </span><span class="nv">highWatermark</span><span class="o">=</span><span class="m">0</span>,<span class="w"> </span><span class="nv">lastStableOffset</span><span class="o">=</span><span class="m">0</span>,<span class="w"> </span><span class="nv">logStartOffset</span><span class="o">=</span><span class="m">0</span>,<span class="w"> </span><span class="nv">logEndOffset</span><span class="o">=</span><span class="m">0</span><span class="o">)</span><span class="w"> </span>with<span class="w"> </span><span class="m">1</span><span class="w"> </span>segments<span class="w"> </span><span class="k">in</span><span class="w"> </span>2ms<span class="w"> </span><span class="o">(</span><span class="m">32564</span>/32564<span class="w"> </span>loaded<span class="w"> </span><span class="k">in</span><span class="w"> </span>/bitnami/kafka/data<span class="o">)</span><span class="w"> </span><span class="o">(</span>kafka.log.LogManager<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:05,895<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Loaded<span class="w"> </span><span class="m">32564</span><span class="w"> </span>logs<span class="w"> </span><span class="k">in</span><span class="w"> </span>161618ms.<span class="w"> </span><span class="o">(</span>kafka.log.LogManager<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:05,896<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Starting<span class="w"> </span>log<span class="w"> </span>cleanup<span class="w"> </span>with<span class="w"> </span>a<span class="w"> </span>period<span class="w"> </span>of<span class="w"> </span><span class="m">300000</span><span class="w"> </span>ms.<span class="w"> </span><span class="o">(</span>kafka.log.LogManager<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:05,896<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Starting<span class="w"> </span>log<span class="w"> </span>flusher<span class="w"> </span>with<span class="w"> </span>a<span class="w"> </span>default<span class="w"> </span>period<span class="w"> </span>of<span class="w"> </span><span class="m">9223372036854775807</span><span class="w"> </span>ms.<span class="w"> </span><span class="o">(</span>kafka.log.LogManager<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:05,908<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Starting<span class="w"> </span>the<span class="w"> </span>log<span class="w"> </span>cleaner<span class="w"> </span><span class="o">(</span>kafka.log.LogCleaner<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:05,999<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>kafka-log-cleaner-thread-0<span class="o">]</span>:<span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.log.LogCleaner<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,296<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>BrokerToControllerChannelManager<span class="w"> </span><span class="nv">broker</span><span class="o">=</span><span class="m">0</span><span class="w"> </span><span class="nv">name</span><span class="o">=</span>forwarding<span class="o">]</span>:<span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.server.BrokerToControllerRequestThread<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,419<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Updated<span class="w"> </span>connection-accept-rate<span class="w"> </span>max<span class="w"> </span>connection<span class="w"> </span>creation<span class="w"> </span>rate<span class="w"> </span>to<span class="w"> </span><span class="m">2147483647</span><span class="w"> </span><span class="o">(</span>kafka.network.ConnectionQuotas<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,428<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Awaiting<span class="w"> </span>socket<span class="w"> </span>connections<span class="w"> </span>on<span class="w"> </span><span class="m">0</span>.0.0.0:9093.<span class="w"> </span><span class="o">(</span>kafka.network.Acceptor<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,463<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>SocketServer<span class="w"> </span><span class="nv">listenerType</span><span class="o">=</span>ZK_BROKER,<span class="w"> </span><span class="nv">nodeId</span><span class="o">=</span><span class="m">0</span><span class="o">]</span><span class="w"> </span>Created<span class="w"> </span>data-plane<span class="w"> </span>acceptor<span class="w"> </span>and<span class="w"> </span>processors<span class="w"> </span><span class="k">for</span><span class="w"> </span>endpoint<span class="w"> </span>:<span class="w"> </span>ListenerName<span class="o">(</span>INTERNAL<span class="o">)</span><span class="w"> </span><span class="o">(</span>kafka.network.SocketServer<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,464<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Updated<span class="w"> </span>connection-accept-rate<span class="w"> </span>max<span class="w"> </span>connection<span class="w"> </span>creation<span class="w"> </span>rate<span class="w"> </span>to<span class="w"> </span><span class="m">2147483647</span><span class="w"> </span><span class="o">(</span>kafka.network.ConnectionQuotas<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,464<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Awaiting<span class="w"> </span>socket<span class="w"> </span>connections<span class="w"> </span>on<span class="w"> </span><span class="m">0</span>.0.0.0:9092.<span class="w"> </span><span class="o">(</span>kafka.network.Acceptor<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,473<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>SocketServer<span class="w"> </span><span class="nv">listenerType</span><span class="o">=</span>ZK_BROKER,<span class="w"> </span><span class="nv">nodeId</span><span class="o">=</span><span class="m">0</span><span class="o">]</span><span class="w"> </span>Created<span class="w"> </span>data-plane<span class="w"> </span>acceptor<span class="w"> </span>and<span class="w"> </span>processors<span class="w"> </span><span class="k">for</span><span class="w"> </span>endpoint<span class="w"> </span>:<span class="w"> </span>ListenerName<span class="o">(</span>CLIENT<span class="o">)</span><span class="w"> </span><span class="o">(</span>kafka.network.SocketServer<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,481<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>BrokerToControllerChannelManager<span class="w"> </span><span class="nv">broker</span><span class="o">=</span><span class="m">0</span><span class="w"> </span><span class="nv">name</span><span class="o">=</span>alterIsr<span class="o">]</span>:<span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.server.BrokerToControllerRequestThread<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,508<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>ExpirationReaper-0-Produce<span class="o">]</span>:<span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.server.DelayedOperationPurgatory<span class="nv">$ExpiredOperationReaper</span><span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,509<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>ExpirationReaper-0-Fetch<span class="o">]</span>:<span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.server.DelayedOperationPurgatory<span class="nv">$ExpiredOperationReaper</span><span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,511<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>ExpirationReaper-0-DeleteRecords<span class="o">]</span>:<span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.server.DelayedOperationPurgatory<span class="nv">$ExpiredOperationReaper</span><span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,512<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>ExpirationReaper-0-ElectLeader<span class="o">]</span>:<span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.server.DelayedOperationPurgatory<span class="nv">$ExpiredOperationReaper</span><span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,529<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>LogDirFailureHandler<span class="o">]</span>:<span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.server.ReplicaManager<span class="nv">$LogDirFailureHandler</span><span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,611<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Creating<span class="w"> </span>/brokers/ids/0<span class="w"> </span><span class="o">(</span>is<span class="w"> </span>it<span class="w"> </span>secure?<span class="w"> </span><span class="nb">false</span><span class="o">)</span><span class="w"> </span><span class="o">(</span>kafka.zk.KafkaZkClient<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,642<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Stat<span class="w"> </span>of<span class="w"> </span>the<span class="w"> </span>created<span class="w"> </span>znode<span class="w"> </span>at<span class="w"> </span>/brokers/ids/0<span class="w"> </span>is:<span class="w"> </span><span class="m">511242113</span>,511242113,1657140366624,1657140366624,1,0,0,72062637399277581,364,0,511242113
<span class="w"> </span><span class="o">(</span>kafka.zk.KafkaZkClient<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,643<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span>Registered<span class="w"> </span>broker<span class="w"> </span><span class="m">0</span><span class="w"> </span>at<span class="w"> </span>path<span class="w"> </span>/brokers/ids/0<span class="w"> </span>with<span class="w"> </span>addresses:<span class="w"> </span>INTERNAL://kafka-0.kafka-headless.default.svc.cluster.local:9093,CLIENT://kafka-0.kafka-headless.default.svc.cluster.local:9092,<span class="w"> </span>czxid<span class="w"> </span><span class="o">(</span>broker<span class="w"> </span>epoch<span class="o">)</span>:<span class="w"> </span><span class="m">511242113</span><span class="w"> </span><span class="o">(</span>kafka.zk.KafkaZkClient<span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,758<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>ControllerEventThread<span class="w"> </span><span class="nv">controllerId</span><span class="o">=</span><span class="m">0</span><span class="o">]</span><span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.controller.ControllerEventManager<span class="nv">$ControllerEventThread</span><span class="o">)</span>
<span class="o">[</span><span class="m">2022</span>-07-06<span class="w"> </span><span class="m">20</span>:46:06,765<span class="o">]</span><span class="w"> </span>INFO<span class="w"> </span><span class="o">[</span>ExpirationReaper-0-topic<span class="o">]</span>:<span class="w"> </span>Starting<span class="w"> </span><span class="o">(</span>kafka.server.DelayedOperationPurgatory<span class="nv">$ExpiredOperationReaper</span><span class="o">)</span>
cannot<span class="w"> </span>allocate<span class="w"> </span>memory<span class="w"> </span><span class="k">for</span><span class="w"> </span>thread-local<span class="w"> </span>data:<span class="w"> </span>ABORT
</code></pre></div>
<p>or</p>
<div class="highlight"><pre><span></span><code><span class="o">[</span><span class="m">395</span>.297s<span class="o">][</span>warning<span class="o">][</span>os,thread<span class="o">]</span><span class="w"> </span>Attempt<span class="w"> </span>to<span class="w"> </span>protect<span class="w"> </span>stack<span class="w"> </span>guard<span class="w"> </span>pages<span class="w"> </span>failed<span class="w"> </span><span class="o">(</span>0x00007efd53bf8000-0x00007efd53bfc000<span class="o">)</span>.
<span class="o">[</span><span class="m">395</span>.297s<span class="o">][</span>warning<span class="o">][</span>os,thread<span class="o">]</span><span class="w"> </span>Attempt<span class="w"> </span>to<span class="w"> </span>deallocate<span class="w"> </span>stack<span class="w"> </span>guard<span class="w"> </span>pages<span class="w"> </span>failed.
<span class="c1">#</span>
<span class="c1"># There is insufficient memory for the Java Runtime Environment to continue.</span>
<span class="c1"># Native memory allocation (mmap) failed to map 16384 bytes for committing reserved memory.</span>
<span class="c1"># An error report file with more information is saved as:</span>
<span class="c1"># /tmp/hs_err_pid1.log</span>
OpenJDK<span class="w"> </span><span class="m">64</span>-Bit<span class="w"> </span>Server<span class="w"> </span>VM<span class="w"> </span>warning:<span class="w"> </span>INFO:<span class="w"> </span>os::commit_memory<span class="o">(</span>0x00007efd53dfa000,<span class="w"> </span><span class="m">16384</span>,<span class="w"> </span><span class="m">0</span><span class="o">)</span><span class="w"> </span>failed<span class="p">;</span><span class="w"> </span><span class="nv">error</span><span class="o">=</span><span class="s1">'Not enough space'</span><span class="w"> </span><span class="o">(</span><span class="nv">errno</span><span class="o">=</span><span class="m">12</span><span class="o">)</span>
</code></pre></div>
<p>Increasing the memory (Xmx, statefulset/pod's resource limits) most probably won't work... In my case, the problem was described in <a href="https://blog.wick.technology/kafka-memory-allocation-error/">this excellent blog post</a>. Long story short:</p>
<blockquote>
<p>"Kafka allocates a memory map for each log file, in each partition, in each topic. When you keep kafka messages indefinitely and also have a large number of partitions it means that the process hits the limit of the number of memory maps it can allocate"</p>
</blockquote>
<p>But how do you fix this on Kubernetes?</p>
<p>First... let's check what's the current value. Run this on a pod running on that same node as the kafka pod getting restarted:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>-it<span class="w"> </span><some_pod_running_on_the_same_node><span class="w"> </span>--<span class="w"> </span>sysctl<span class="w"> </span>-n<span class="w"> </span>vm.max_map_count
<span class="m">65530</span>
</code></pre></div>
<p>Most probably you'll get that value, which is default. To change it, you'll have to add an init container to the statefulset:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span>describe<span class="w"> </span>statefulset<span class="w"> </span>kafka
Name:<span class="w"> </span>kafka
<span class="o">(</span>some<span class="w"> </span>output<span class="w"> </span>is<span class="w"> </span>omitted<span class="o">)</span>
Pod<span class="w"> </span>Template:
<span class="w"> </span>Labels:<span class="w"> </span>app.kubernetes.io/component<span class="o">=</span>kafka
<span class="w"> </span>app.kubernetes.io/managed-by<span class="o">=</span>Helm
<span class="w"> </span>app.kubernetes.io/name<span class="o">=</span>kafka
<span class="w"> </span>helm.sh/chart<span class="o">=</span>kafka-16.1.3
<span class="w"> </span>Service<span class="w"> </span>Account:<span class="w"> </span>kafka
<span class="w"> </span>Init<span class="w"> </span>Containers:
<span class="w"> </span>sysctl:
<span class="w"> </span>Image:<span class="w"> </span>busybox
<span class="w"> </span>Port:<span class="w"> </span><none>
<span class="w"> </span>Host<span class="w"> </span>Port:<span class="w"> </span><none>
<span class="w"> </span>Command:
<span class="w"> </span>sh
<span class="w"> </span>-c
<span class="w"> </span>sysctl<span class="w"> </span>-w<span class="w"> </span>vm.max_map_count<span class="o">=</span><span class="m">262144</span>
</code></pre></div>
<p>Now you should see a difference</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>kubectl<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>-it<span class="w"> </span><same_pod_as_before><span class="w"> </span>--<span class="w"> </span>sysctl<span class="w"> </span>-n<span class="w"> </span>vm.max_map_count
<span class="m">262144</span>
</code></pre></div>
<p>... and... hopefully your pods are stable and serving traffic.</p>(K)Ubuntu 22.04 and Bluetooth Headphones2022-08-27T00:00:00+03:002022-08-27T00:00:00+03:00Silvian Cretutag:silviancretu.ro,2022-08-27:/kubuntu-2204-and-bluetooth-headphones.html<p>This article is somewhat an update of the one named <a href="https://silviancretu.ro/linux-and-bluetooth-headphones.html">Linux and Bluetooth Headphones</a>. In that article I had tested on (K)Ubuntu 20.04, but this article is about version 22.04.</p>
<p>Just like in the above mentioned article, I recommend that you install and enable PipeWire. It is a lot easier to do in (K)Ubuntu 22.04. The TL;DR version would be:</p>
<p>First, check to see which audio server you're currently on. This command most probably will return 'pulseaudio':</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>pactl<span class="w"> </span>info<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span><span class="s1">'Server Name'</span>
Server<span class="w"> </span>Name:<span class="w"> </span>pulseaudio
</code></pre></div>
<p>So... make sure you install PipeWire:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>sudo<span class="w"> </span>apt<span class="w"> </span>update
$<span class="w"> </span>sudo<span class="w"> </span>apt<span class="w"> </span>install<span class="w"> </span>pipewire
$<span class="w"> </span>sudo<span class="w"> </span>apt<span class="w"> </span>install<span class="w"> </span>gstreamer1.0-pipewire<span class="w"> </span>libpipewire-0.3-<span class="o">{</span><span class="m">0</span>,dev,modules<span class="o">}</span><span class="w"> </span>libspa-0.2-<span class="o">{</span>bluetooth,dev,jack,modules<span class="o">}</span><span class="w"> </span>pipewire<span class="o">{</span>,-<span class="o">{</span>audio-client-libraries,pulse,bin,tests<span class="o">}}</span>
$<span class="w"> </span>sudo<span class="w"> </span>apt<span class="w"> </span>install<span class="w"> </span>wireplumber<span class="w"> </span>gir1.2-wp-0.4<span class="w"> </span>libwireplumber-0.4-<span class="o">{</span><span class="m">0</span>,dev<span class="o">}</span>
</code></pre></div>
<p>Disable PulseAudio and enable PipeWire instead:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>systemctl<span class="w"> </span>--user<span class="w"> </span>--now<span class="w"> </span>disable<span class="w"> </span>pulseaudio.<span class="o">{</span>socket,service<span class="o">}</span>
$<span class="w"> </span>systemctl<span class="w"> </span>--user<span class="w"> </span>mask<span class="w"> </span>pulseaudio
$<span class="w"> </span>sudo<span class="w"> </span>cp<span class="w"> </span>-vRa<span class="w"> </span>/usr/share/pipewire<span class="w"> </span>/etc/
$<span class="w"> </span>systemctl<span class="w"> </span>--user<span class="w"> </span>--now<span class="w"> </span><span class="nb">enable</span><span class="w"> </span>pipewire<span class="o">{</span>,-pulse<span class="o">}</span>.<span class="o">{</span>socket,service<span class="o">}</span>
</code></pre></div>
<p>Now, you should see PipeWire in here:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>pactl<span class="w"> </span>info<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span><span class="s1">'Server Name'</span>
Server<span class="w"> </span>Name:<span class="w"> </span>PulseAudio<span class="w"> </span><span class="o">(</span>on<span class="w"> </span>PipeWire<span class="w"> </span><span class="m">0</span>.3.48<span class="o">)</span>
</code></pre></div>
<p>For even more details, read <a href="https://trendoceans.com/enable-pipewire-and-disable-pulseaudio-in-ubuntu/">this excellent article</a>.</p>
<p>I tested with <a href="https://www.edifier.com/product-neobuds-pro.html">EDIFIER NeoBuds Pro</a> and not only that switching between A2DP and HSP/HFP works automagically but, more than that, LDAC codec also works (which previously only worked for <a href="https://www.sony.ro/electronics/casti-cu-banda-de-fixare-pe-cap/wh-1000xm3/buy/wh1000xm3b.ce7">SONY WH-1000XM3</a>):</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>pactl<span class="w"> </span>list
<span class="o">(</span>output<span class="w"> </span>omitted<span class="o">)</span>
Card<span class="w"> </span><span class="c1">#5323</span>
<span class="w"> </span>Name:<span class="w"> </span>bluez_card.FC_E8_06_76_05_2C
<span class="w"> </span>Driver:<span class="w"> </span>module-bluez5-device.c
<span class="w"> </span>Owner<span class="w"> </span>Module:<span class="w"> </span>n/a
<span class="w"> </span>Properties:
<span class="w"> </span>api.bluez5.address<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"FC:E8:06:76:05:2C"</span>
<span class="w"> </span>api.bluez5.class<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"0x240404"</span>
<span class="w"> </span>api.bluez5.connection<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"disconnected"</span>
<span class="w"> </span>api.bluez5.device<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">""</span>
<span class="w"> </span>api.bluez5.icon<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"audio-headset"</span>
<span class="w"> </span>api.bluez5.path<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"/org/bluez/hci0/dev_FC_E8_06_76_05_2C"</span>
<span class="w"> </span>bluez5.auto-connect<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"[ hfp_hf hsp_hs a2dp_sink ]"</span>
<span class="w"> </span>bluez5.profile<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"off"</span>
<span class="w"> </span>device.alias<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"EDIFIER NeoBuds Pro"</span>
<span class="w"> </span>device.api<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluez5"</span>
<span class="w"> </span>device.bus<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluetooth"</span>
<span class="w"> </span>device.description<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"EDIFIER NeoBuds Pro"</span>
<span class="w"> </span>device.form_factor<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"headset"</span>
<span class="w"> </span>device.name<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluez_card.FC_E8_06_76_05_2C"</span>
<span class="w"> </span>device.string<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"FC:E8:06:76:05:2C"</span>
<span class="w"> </span>media.class<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Audio/Device"</span>
<span class="w"> </span>factory.id<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"14"</span>
<span class="w"> </span>client.id<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"34"</span>
<span class="w"> </span>object.id<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"132"</span>
<span class="w"> </span>object.serial<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"5323"</span>
<span class="w"> </span>Profiles:
<span class="w"> </span>off:<span class="w"> </span>Off<span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">16</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>headset-head-unit:<span class="w"> </span>Headset<span class="w"> </span>Head<span class="w"> </span>Unit<span class="w"> </span><span class="o">(</span>HSP/HFP<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink-sbc:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink,<span class="w"> </span>codec<span class="w"> </span>SBC<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">18</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink-sbc_xq:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink,<span class="w"> </span>codec<span class="w"> </span>SBC-XQ<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">17</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink-ldac:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink,<span class="w"> </span>codec<span class="w"> </span>LDAC<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">19</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>headset-head-unit-cvsd:<span class="w"> </span>Headset<span class="w"> </span>Head<span class="w"> </span>Unit<span class="w"> </span><span class="o">(</span>HSP/HFP,<span class="w"> </span>codec<span class="w"> </span>CVSD<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">2</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>headset-head-unit-msbc:<span class="w"> </span>Headset<span class="w"> </span>Head<span class="w"> </span>Unit<span class="w"> </span><span class="o">(</span>HSP/HFP,<span class="w"> </span>codec<span class="w"> </span>mSBC<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">3</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>Active<span class="w"> </span>Profile:<span class="w"> </span>a2dp-sink-ldac
<span class="w"> </span>Ports:
<span class="w"> </span>headset-input:<span class="w"> </span>Headset<span class="w"> </span><span class="o">(</span>type:<span class="w"> </span>Headset,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>latency<span class="w"> </span>offset:<span class="w"> </span><span class="m">0</span><span class="w"> </span>usec,<span class="w"> </span>available<span class="o">)</span>
<span class="w"> </span>Properties:
<span class="w"> </span>port.type<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"headset"</span>
<span class="w"> </span>Part<span class="w"> </span>of<span class="w"> </span>profile<span class="o">(</span>s<span class="o">)</span>:<span class="w"> </span>headset-head-unit,<span class="w"> </span>headset-head-unit-cvsd,<span class="w"> </span>headset-head-unit-msbc
<span class="w"> </span>headset-output:<span class="w"> </span>Headset<span class="w"> </span><span class="o">(</span>type:<span class="w"> </span>Headset,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>latency<span class="w"> </span>offset:<span class="w"> </span><span class="m">0</span><span class="w"> </span>usec,<span class="w"> </span>available<span class="o">)</span>
<span class="w"> </span>Properties:
<span class="w"> </span>port.type<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"headset"</span>
<span class="w"> </span>Part<span class="w"> </span>of<span class="w"> </span>profile<span class="o">(</span>s<span class="o">)</span>:<span class="w"> </span>a2dp-sink,<span class="w"> </span>headset-head-unit,<span class="w"> </span>a2dp-sink-sbc,<span class="w"> </span>a2dp-sink-sbc_xq …</code></pre></div><p>This article is somewhat an update of the one named <a href="https://silviancretu.ro/linux-and-bluetooth-headphones.html">Linux and Bluetooth Headphones</a>. In that article I had tested on (K)Ubuntu 20.04, but this article is about version 22.04.</p>
<p>Just like in the above mentioned article, I recommend that you install and enable PipeWire. It is a lot easier to do in (K)Ubuntu 22.04. The TL;DR version would be:</p>
<p>First, check to see which audio server you're currently on. This command most probably will return 'pulseaudio':</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>pactl<span class="w"> </span>info<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span><span class="s1">'Server Name'</span>
Server<span class="w"> </span>Name:<span class="w"> </span>pulseaudio
</code></pre></div>
<p>So... make sure you install PipeWire:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>sudo<span class="w"> </span>apt<span class="w"> </span>update
$<span class="w"> </span>sudo<span class="w"> </span>apt<span class="w"> </span>install<span class="w"> </span>pipewire
$<span class="w"> </span>sudo<span class="w"> </span>apt<span class="w"> </span>install<span class="w"> </span>gstreamer1.0-pipewire<span class="w"> </span>libpipewire-0.3-<span class="o">{</span><span class="m">0</span>,dev,modules<span class="o">}</span><span class="w"> </span>libspa-0.2-<span class="o">{</span>bluetooth,dev,jack,modules<span class="o">}</span><span class="w"> </span>pipewire<span class="o">{</span>,-<span class="o">{</span>audio-client-libraries,pulse,bin,tests<span class="o">}}</span>
$<span class="w"> </span>sudo<span class="w"> </span>apt<span class="w"> </span>install<span class="w"> </span>wireplumber<span class="w"> </span>gir1.2-wp-0.4<span class="w"> </span>libwireplumber-0.4-<span class="o">{</span><span class="m">0</span>,dev<span class="o">}</span>
</code></pre></div>
<p>Disable PulseAudio and enable PipeWire instead:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>systemctl<span class="w"> </span>--user<span class="w"> </span>--now<span class="w"> </span>disable<span class="w"> </span>pulseaudio.<span class="o">{</span>socket,service<span class="o">}</span>
$<span class="w"> </span>systemctl<span class="w"> </span>--user<span class="w"> </span>mask<span class="w"> </span>pulseaudio
$<span class="w"> </span>sudo<span class="w"> </span>cp<span class="w"> </span>-vRa<span class="w"> </span>/usr/share/pipewire<span class="w"> </span>/etc/
$<span class="w"> </span>systemctl<span class="w"> </span>--user<span class="w"> </span>--now<span class="w"> </span><span class="nb">enable</span><span class="w"> </span>pipewire<span class="o">{</span>,-pulse<span class="o">}</span>.<span class="o">{</span>socket,service<span class="o">}</span>
</code></pre></div>
<p>Now, you should see PipeWire in here:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>pactl<span class="w"> </span>info<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span><span class="s1">'Server Name'</span>
Server<span class="w"> </span>Name:<span class="w"> </span>PulseAudio<span class="w"> </span><span class="o">(</span>on<span class="w"> </span>PipeWire<span class="w"> </span><span class="m">0</span>.3.48<span class="o">)</span>
</code></pre></div>
<p>For even more details, read <a href="https://trendoceans.com/enable-pipewire-and-disable-pulseaudio-in-ubuntu/">this excellent article</a>.</p>
<p>I tested with <a href="https://www.edifier.com/product-neobuds-pro.html">EDIFIER NeoBuds Pro</a> and not only that switching between A2DP and HSP/HFP works automagically but, more than that, LDAC codec also works (which previously only worked for <a href="https://www.sony.ro/electronics/casti-cu-banda-de-fixare-pe-cap/wh-1000xm3/buy/wh1000xm3b.ce7">SONY WH-1000XM3</a>):</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>pactl<span class="w"> </span>list
<span class="o">(</span>output<span class="w"> </span>omitted<span class="o">)</span>
Card<span class="w"> </span><span class="c1">#5323</span>
<span class="w"> </span>Name:<span class="w"> </span>bluez_card.FC_E8_06_76_05_2C
<span class="w"> </span>Driver:<span class="w"> </span>module-bluez5-device.c
<span class="w"> </span>Owner<span class="w"> </span>Module:<span class="w"> </span>n/a
<span class="w"> </span>Properties:
<span class="w"> </span>api.bluez5.address<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"FC:E8:06:76:05:2C"</span>
<span class="w"> </span>api.bluez5.class<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"0x240404"</span>
<span class="w"> </span>api.bluez5.connection<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"disconnected"</span>
<span class="w"> </span>api.bluez5.device<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">""</span>
<span class="w"> </span>api.bluez5.icon<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"audio-headset"</span>
<span class="w"> </span>api.bluez5.path<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"/org/bluez/hci0/dev_FC_E8_06_76_05_2C"</span>
<span class="w"> </span>bluez5.auto-connect<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"[ hfp_hf hsp_hs a2dp_sink ]"</span>
<span class="w"> </span>bluez5.profile<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"off"</span>
<span class="w"> </span>device.alias<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"EDIFIER NeoBuds Pro"</span>
<span class="w"> </span>device.api<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluez5"</span>
<span class="w"> </span>device.bus<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluetooth"</span>
<span class="w"> </span>device.description<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"EDIFIER NeoBuds Pro"</span>
<span class="w"> </span>device.form_factor<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"headset"</span>
<span class="w"> </span>device.name<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluez_card.FC_E8_06_76_05_2C"</span>
<span class="w"> </span>device.string<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"FC:E8:06:76:05:2C"</span>
<span class="w"> </span>media.class<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Audio/Device"</span>
<span class="w"> </span>factory.id<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"14"</span>
<span class="w"> </span>client.id<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"34"</span>
<span class="w"> </span>object.id<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"132"</span>
<span class="w"> </span>object.serial<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"5323"</span>
<span class="w"> </span>Profiles:
<span class="w"> </span>off:<span class="w"> </span>Off<span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">16</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>headset-head-unit:<span class="w"> </span>Headset<span class="w"> </span>Head<span class="w"> </span>Unit<span class="w"> </span><span class="o">(</span>HSP/HFP<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink-sbc:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink,<span class="w"> </span>codec<span class="w"> </span>SBC<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">18</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink-sbc_xq:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink,<span class="w"> </span>codec<span class="w"> </span>SBC-XQ<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">17</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink-ldac:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink,<span class="w"> </span>codec<span class="w"> </span>LDAC<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">19</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>headset-head-unit-cvsd:<span class="w"> </span>Headset<span class="w"> </span>Head<span class="w"> </span>Unit<span class="w"> </span><span class="o">(</span>HSP/HFP,<span class="w"> </span>codec<span class="w"> </span>CVSD<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">2</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>headset-head-unit-msbc:<span class="w"> </span>Headset<span class="w"> </span>Head<span class="w"> </span>Unit<span class="w"> </span><span class="o">(</span>HSP/HFP,<span class="w"> </span>codec<span class="w"> </span>mSBC<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">3</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>Active<span class="w"> </span>Profile:<span class="w"> </span>a2dp-sink-ldac
<span class="w"> </span>Ports:
<span class="w"> </span>headset-input:<span class="w"> </span>Headset<span class="w"> </span><span class="o">(</span>type:<span class="w"> </span>Headset,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>latency<span class="w"> </span>offset:<span class="w"> </span><span class="m">0</span><span class="w"> </span>usec,<span class="w"> </span>available<span class="o">)</span>
<span class="w"> </span>Properties:
<span class="w"> </span>port.type<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"headset"</span>
<span class="w"> </span>Part<span class="w"> </span>of<span class="w"> </span>profile<span class="o">(</span>s<span class="o">)</span>:<span class="w"> </span>headset-head-unit,<span class="w"> </span>headset-head-unit-cvsd,<span class="w"> </span>headset-head-unit-msbc
<span class="w"> </span>headset-output:<span class="w"> </span>Headset<span class="w"> </span><span class="o">(</span>type:<span class="w"> </span>Headset,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>latency<span class="w"> </span>offset:<span class="w"> </span><span class="m">0</span><span class="w"> </span>usec,<span class="w"> </span>available<span class="o">)</span>
<span class="w"> </span>Properties:
<span class="w"> </span>port.type<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"headset"</span>
<span class="w"> </span>Part<span class="w"> </span>of<span class="w"> </span>profile<span class="o">(</span>s<span class="o">)</span>:<span class="w"> </span>a2dp-sink,<span class="w"> </span>headset-head-unit,<span class="w"> </span>a2dp-sink-sbc,<span class="w"> </span>a2dp-sink-sbc_xq,<span class="w"> </span>a2dp-sink-ldac,<span class="w"> </span>headset-head-unit-cvsd,<span class="w"> </span>headset-head-unit-msbc
</code></pre></div>
<p>Enjoy!</p>Linux and Bluetooth Headphones2021-09-26T00:00:00+03:002021-09-26T00:00:00+03:00Silvian Cretutag:silviancretu.ro,2021-09-26:/linux-and-bluetooth-headphones.html<p>For newer versions of Linux, like (K)Ubuntu 22.04 (which have PipeWire already installed or even enabled by default), please go directly to <a href="https://silviancretu.ro/kubuntu-2204-and-bluetooth-headphones.html">(K)Ubuntu 22.04 and Bluetooth Headphones
</a></p>
<p>Connecting a set of bluetooth headphones to your Linux computer is a simple thing, if you just want to listen to music. But what about when you want to use the microphone that is, most probably, embedded in those headphones?</p>
<h1>How I tested?</h1>
<p>All the below information was tested on Kubuntu 20.04 with the following headphones:</p>
<ul>
<li><a href="https://www.jbl.com/earbuds/JBL+TUNE+205BT.html">JBL TUNE205BT</a></li>
<li><a href="https://www.jbl.com/wireless-earbuds/TUNE115TWS-.html">JBL TUNE115TWS</a></li>
<li><a href="https://www.edifier.com/product-neobuds-pro.html">EDIFIER NeoBuds Pro</a></li>
<li><a href="https://www.sony.ro/electronics/casti-cu-banda-de-fixare-pe-cap/wh-1000xm3/buy/wh1000xm3b.ce7">SONY WH-1000XM3</a></li>
</ul>
<h1>How to check if your microphone is working or not?</h1>
<p>You can test from an application that makes use of the microphone like Skype or <a href="https://8x8.vc/">8x8 Meet</a>. </p>
<p>For example, in this screenshot, it doesn't work. Just go to <a href="https://8x8.vc/someRandomString">https://8x8.vc/someRandomString</a> and click the up arrow next to the microphone symbol to see the list of available microphones your computer has. You might see the headphones (in this particular example, the EDIFIER NeoBuds Pro are conected) but only the laptop's internal microphone is detected.</p>
<p><img alt="the mic doesn't show up" src="https://silviancretu.ro/static/8x8 Meet - mic not working.png" title="the mic doesn't show up"></p>
<p>From the command line, you can run this command:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>pactl<span class="w"> </span>list
</code></pre></div>
<p>At the end of the output, you should see something like this:</p>
<div class="highlight"><pre><span></span><code>Card<span class="w"> </span><span class="c1">#31</span>
<span class="w"> </span>Name:<span class="w"> </span>bluez_card.FC_E8_06_76_05_2C
<span class="w"> </span>Driver:<span class="w"> </span>module-bluez5-device.c
<span class="w"> </span>Owner<span class="w"> </span>Module:<span class="w"> </span><span class="m">56</span>
<span class="w"> </span>Properties:
<span class="w"> </span>device.description<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"EDIFIER NeoBuds Pro"</span>
<span class="w"> </span>device.string<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"FC:E8:06:76:05:2C"</span>
<span class="w"> </span>device.api<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluez"</span>
<span class="w"> </span>device.class<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"sound"</span>
<span class="w"> </span>device.bus<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluetooth"</span>
<span class="w"> </span>device.form_factor<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"headset"</span>
<span class="w"> </span>bluez.path<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"/org/bluez/hci0/dev_FC_E8_06_76_05_2C"</span>
<span class="w"> </span>bluez.class<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"0x240404"</span>
<span class="w"> </span>bluez.alias<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"EDIFIER NeoBuds Pro"</span>
<span class="w"> </span>device.icon_name<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"audio-headset-bluetooth"</span>
<span class="w"> </span>device.intended_roles<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"phone"</span>
<span class="w"> </span>Profiles:
<span class="w"> </span>a2dp_sink:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">40</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>headset_head_unit:<span class="w"> </span>Headset<span class="w"> </span>Head<span class="w"> </span>Unit<span class="w"> </span><span class="o">(</span>HSP/HFP<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">30</span>,<span class="w"> </span>available:<span class="w"> </span>no<span class="o">)</span>
<span class="w"> </span>off:<span class="w"> </span>Off<span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>Active<span class="w"> </span>Profile:<span class="w"> </span>a2dp_sink
<span class="w"> </span>Ports:
<span class="w"> </span>headset-output:<span class="w"> </span>Headset<span class="w"> </span><span class="o">(</span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>latency<span class="w"> </span>offset:<span class="w"> </span><span class="m">0</span><span class="w"> </span>usec,<span class="w"> </span>available<span class="o">)</span>
<span class="w"> </span>Part<span class="w"> </span>of<span class="w"> </span>profile<span class="o">(</span>s<span class="o">)</span>:<span class="w"> </span>a2dp_sink,<span class="w"> </span>headset_head_unit
<span class="w"> </span>headset-input:<span class="w"> </span>Headset<span class="w"> </span><span class="o">(</span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>latency<span class="w"> </span>offset:<span class="w"> </span><span class="m">0</span><span class="w"> </span>usec,<span class="w"> </span>not<span class="w"> </span>available<span class="o">)</span>
<span class="w"> </span>Part<span class="w"> </span>of<span class="w"> </span>profile<span class="o">(</span>s<span class="o">)</span>:<span class="w"> </span>headset_head_unit
</code></pre></div>
<p>Notice the <strong>Profiles</strong> section and the fact that the profile <strong>headset_head_unit</strong> is not available and, also, that the <strong>Active Profile</strong> is set to <strong>a2dp_sink</strong>.</p>
<h1>The easy way: making HSP work</h1>
<p>This only works for <a href="https://www.jbl.com/earbuds/JBL+TUNE+205BT.html">JBL TUNE205BT</a>.</p>
<p>This is easy, as it doesn't require any hacks or additional software. Just edit the file <code>/etc/pulse/default.pa</code> and add <em>auto_switch=2</em> to the line <em>load-module module-bluetooth-discover</em>. In the end, the file should look like this in that zone:</p>
<div class="highlight"><pre><span></span><code><span class="c1">### Automatically load driver modules for Bluetooth hardware</span>
.ifexists<span class="w"> </span>module-bluetooth-policy.so
<span class="c1">#load-module module-bluetooth-policy</span>
load-module<span class="w"> </span>module-bluetooth-policy<span class="w"> </span><span class="nv">auto_switch</span><span class="o">=</span><span class="m">2</span>
.endif
</code></pre></div>
<p>Restart bluetooth and kill pulseaudio (it should respawn by itself):</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>sudo<span class="w"> </span>systemctl<span class="w"> </span>restart<span class="w"> </span>bluetooth
$<span class="w"> </span>pulseaudio<span class="w"> </span>-k
</code></pre></div>
<p>Now, not only that the HSP profile will appear as available (notice the <strong>Profiles</strong> section and, also, the <strong>Active Profile</strong>)...</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>pactl<span class="w"> </span>list
</code></pre></div>
<div class="highlight"><pre><span></span><code>Card<span class="w"> </span><span class="c1">#32</span>
<span class="w"> </span>Name:<span class="w"> </span>bluez_card.B8_F6_53_03_D4_1C
<span class="w"> </span>Driver:<span class="w"> </span>module-bluez5-device.c
<span class="w"> </span>Owner<span class="w"> </span>Module:<span class="w"> </span><span class="m">57</span>
<span class="w"> </span>Properties:
<span class="w"> </span>device.description<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"JBL TUNE205BT"</span>
<span class="w"> </span>device.string<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"B8:F6:53:03:D4:1C"</span>
<span class="w"> </span>device.api<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluez"</span>
<span class="w"> </span>device.class<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"sound"</span>
<span class="w"> </span>device.bus<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluetooth …</span></code></pre></div><p>For newer versions of Linux, like (K)Ubuntu 22.04 (which have PipeWire already installed or even enabled by default), please go directly to <a href="https://silviancretu.ro/kubuntu-2204-and-bluetooth-headphones.html">(K)Ubuntu 22.04 and Bluetooth Headphones
</a></p>
<p>Connecting a set of bluetooth headphones to your Linux computer is a simple thing, if you just want to listen to music. But what about when you want to use the microphone that is, most probably, embedded in those headphones?</p>
<h1>How I tested?</h1>
<p>All the below information was tested on Kubuntu 20.04 with the following headphones:</p>
<ul>
<li><a href="https://www.jbl.com/earbuds/JBL+TUNE+205BT.html">JBL TUNE205BT</a></li>
<li><a href="https://www.jbl.com/wireless-earbuds/TUNE115TWS-.html">JBL TUNE115TWS</a></li>
<li><a href="https://www.edifier.com/product-neobuds-pro.html">EDIFIER NeoBuds Pro</a></li>
<li><a href="https://www.sony.ro/electronics/casti-cu-banda-de-fixare-pe-cap/wh-1000xm3/buy/wh1000xm3b.ce7">SONY WH-1000XM3</a></li>
</ul>
<h1>How to check if your microphone is working or not?</h1>
<p>You can test from an application that makes use of the microphone like Skype or <a href="https://8x8.vc/">8x8 Meet</a>. </p>
<p>For example, in this screenshot, it doesn't work. Just go to <a href="https://8x8.vc/someRandomString">https://8x8.vc/someRandomString</a> and click the up arrow next to the microphone symbol to see the list of available microphones your computer has. You might see the headphones (in this particular example, the EDIFIER NeoBuds Pro are conected) but only the laptop's internal microphone is detected.</p>
<p><img alt="the mic doesn't show up" src="https://silviancretu.ro/static/8x8 Meet - mic not working.png" title="the mic doesn't show up"></p>
<p>From the command line, you can run this command:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>pactl<span class="w"> </span>list
</code></pre></div>
<p>At the end of the output, you should see something like this:</p>
<div class="highlight"><pre><span></span><code>Card<span class="w"> </span><span class="c1">#31</span>
<span class="w"> </span>Name:<span class="w"> </span>bluez_card.FC_E8_06_76_05_2C
<span class="w"> </span>Driver:<span class="w"> </span>module-bluez5-device.c
<span class="w"> </span>Owner<span class="w"> </span>Module:<span class="w"> </span><span class="m">56</span>
<span class="w"> </span>Properties:
<span class="w"> </span>device.description<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"EDIFIER NeoBuds Pro"</span>
<span class="w"> </span>device.string<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"FC:E8:06:76:05:2C"</span>
<span class="w"> </span>device.api<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluez"</span>
<span class="w"> </span>device.class<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"sound"</span>
<span class="w"> </span>device.bus<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluetooth"</span>
<span class="w"> </span>device.form_factor<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"headset"</span>
<span class="w"> </span>bluez.path<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"/org/bluez/hci0/dev_FC_E8_06_76_05_2C"</span>
<span class="w"> </span>bluez.class<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"0x240404"</span>
<span class="w"> </span>bluez.alias<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"EDIFIER NeoBuds Pro"</span>
<span class="w"> </span>device.icon_name<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"audio-headset-bluetooth"</span>
<span class="w"> </span>device.intended_roles<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"phone"</span>
<span class="w"> </span>Profiles:
<span class="w"> </span>a2dp_sink:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">40</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>headset_head_unit:<span class="w"> </span>Headset<span class="w"> </span>Head<span class="w"> </span>Unit<span class="w"> </span><span class="o">(</span>HSP/HFP<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">30</span>,<span class="w"> </span>available:<span class="w"> </span>no<span class="o">)</span>
<span class="w"> </span>off:<span class="w"> </span>Off<span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>Active<span class="w"> </span>Profile:<span class="w"> </span>a2dp_sink
<span class="w"> </span>Ports:
<span class="w"> </span>headset-output:<span class="w"> </span>Headset<span class="w"> </span><span class="o">(</span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>latency<span class="w"> </span>offset:<span class="w"> </span><span class="m">0</span><span class="w"> </span>usec,<span class="w"> </span>available<span class="o">)</span>
<span class="w"> </span>Part<span class="w"> </span>of<span class="w"> </span>profile<span class="o">(</span>s<span class="o">)</span>:<span class="w"> </span>a2dp_sink,<span class="w"> </span>headset_head_unit
<span class="w"> </span>headset-input:<span class="w"> </span>Headset<span class="w"> </span><span class="o">(</span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>latency<span class="w"> </span>offset:<span class="w"> </span><span class="m">0</span><span class="w"> </span>usec,<span class="w"> </span>not<span class="w"> </span>available<span class="o">)</span>
<span class="w"> </span>Part<span class="w"> </span>of<span class="w"> </span>profile<span class="o">(</span>s<span class="o">)</span>:<span class="w"> </span>headset_head_unit
</code></pre></div>
<p>Notice the <strong>Profiles</strong> section and the fact that the profile <strong>headset_head_unit</strong> is not available and, also, that the <strong>Active Profile</strong> is set to <strong>a2dp_sink</strong>.</p>
<h1>The easy way: making HSP work</h1>
<p>This only works for <a href="https://www.jbl.com/earbuds/JBL+TUNE+205BT.html">JBL TUNE205BT</a>.</p>
<p>This is easy, as it doesn't require any hacks or additional software. Just edit the file <code>/etc/pulse/default.pa</code> and add <em>auto_switch=2</em> to the line <em>load-module module-bluetooth-discover</em>. In the end, the file should look like this in that zone:</p>
<div class="highlight"><pre><span></span><code><span class="c1">### Automatically load driver modules for Bluetooth hardware</span>
.ifexists<span class="w"> </span>module-bluetooth-policy.so
<span class="c1">#load-module module-bluetooth-policy</span>
load-module<span class="w"> </span>module-bluetooth-policy<span class="w"> </span><span class="nv">auto_switch</span><span class="o">=</span><span class="m">2</span>
.endif
</code></pre></div>
<p>Restart bluetooth and kill pulseaudio (it should respawn by itself):</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>sudo<span class="w"> </span>systemctl<span class="w"> </span>restart<span class="w"> </span>bluetooth
$<span class="w"> </span>pulseaudio<span class="w"> </span>-k
</code></pre></div>
<p>Now, not only that the HSP profile will appear as available (notice the <strong>Profiles</strong> section and, also, the <strong>Active Profile</strong>)...</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>pactl<span class="w"> </span>list
</code></pre></div>
<div class="highlight"><pre><span></span><code>Card<span class="w"> </span><span class="c1">#32</span>
<span class="w"> </span>Name:<span class="w"> </span>bluez_card.B8_F6_53_03_D4_1C
<span class="w"> </span>Driver:<span class="w"> </span>module-bluez5-device.c
<span class="w"> </span>Owner<span class="w"> </span>Module:<span class="w"> </span><span class="m">57</span>
<span class="w"> </span>Properties:
<span class="w"> </span>device.description<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"JBL TUNE205BT"</span>
<span class="w"> </span>device.string<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"B8:F6:53:03:D4:1C"</span>
<span class="w"> </span>device.api<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluez"</span>
<span class="w"> </span>device.class<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"sound"</span>
<span class="w"> </span>device.bus<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluetooth"</span>
<span class="w"> </span>device.form_factor<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"headset"</span>
<span class="w"> </span>bluez.path<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"/org/bluez/hci0/dev_B8_F6_53_03_D4_1C"</span>
<span class="w"> </span>bluez.class<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"0x240404"</span>
<span class="w"> </span>bluez.alias<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"JBL TUNE205BT"</span>
<span class="w"> </span>device.icon_name<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"audio-headset-bluetooth"</span>
<span class="w"> </span>device.intended_roles<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"phone"</span>
<span class="w"> </span>Profiles:
<span class="w"> </span>headset_head_unit:<span class="w"> </span>Headset<span class="w"> </span>Head<span class="w"> </span>Unit<span class="w"> </span><span class="o">(</span>HSP/HFP<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">30</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp_sink:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">40</span>,<span class="w"> </span>available:<span class="w"> </span>no<span class="o">)</span>
<span class="w"> </span>off:<span class="w"> </span>Off<span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>Active<span class="w"> </span>Profile:<span class="w"> </span>headset_head_unit
<span class="w"> </span>Ports:
<span class="w"> </span>headset-output:<span class="w"> </span>Headset<span class="w"> </span><span class="o">(</span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>latency<span class="w"> </span>offset:<span class="w"> </span><span class="m">0</span><span class="w"> </span>usec,<span class="w"> </span>available<span class="o">)</span>
<span class="w"> </span>Part<span class="w"> </span>of<span class="w"> </span>profile<span class="o">(</span>s<span class="o">)</span>:<span class="w"> </span>headset_head_unit,<span class="w"> </span>a2dp_sink
<span class="w"> </span>headset-input:<span class="w"> </span>Headset<span class="w"> </span><span class="o">(</span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>latency<span class="w"> </span>offset:<span class="w"> </span><span class="m">0</span><span class="w"> </span>usec,<span class="w"> </span>available<span class="o">)</span>
<span class="w"> </span>Part<span class="w"> </span>of<span class="w"> </span>profile<span class="o">(</span>s<span class="o">)</span>:<span class="w"> </span>headset_head_unit
</code></pre></div>
<p>... but, also, the headphones will switch between A2DP (output/headphones only; high quality sound) and HSP (output and input, headphones and microphone, but low quality sound) when needed. You should see the microphone in <a href="https://8x8.vc/someRandomString">https://8x8.vc/someRandomString</a>:</p>
<p><img alt="the mic shows up" src="https://silviancretu.ro/static/8x8 Meet - mic working - HSP.png" title="the mic shows up"></p>
<h1>The hard way: making HFP work</h1>
<h2>Solution 1: Pulseaudio + ofono</h2>
<p>This is documented on <a href="https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/Bluetooth/">Pulseaudio's official documentation</a> and on many other blog posts, but I coudn't make it work no matter how hard I tried...</p>
<h2>Solution 2: Replacing Pulseaudio with PipeWire</h2>
<p>I tested this on all these headphones and it works:</p>
<ul>
<li><a href="https://www.jbl.com/earbuds/JBL+TUNE+205BT.html">JBL TUNE205BT</a></li>
<li><a href="https://www.jbl.com/wireless-earbuds/TUNE115TWS-.html">JBL TUNE115TWS</a></li>
<li><a href="https://www.edifier.com/product-neobuds-pro.html">EDIFIER NeoBuds Pro</a></li>
<li><a href="https://www.sony.ro/electronics/casti-cu-banda-de-fixare-pe-cap/wh-1000xm3/buy/wh1000xm3b.ce7">SONY WH-1000XM3</a></li>
</ul>
<p>Replacing Pulseaudio with PipeWire is a much better solution, because it also brings support for better codecs (even LDAC works; see below).</p>
<p>This is how I did it, by following the <a href="https://pipewire-debian.github.io/pipewire-debian/">official documentation</a>:</p>
<p>Add the PipeWire repository:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>sudo<span class="w"> </span>add-apt-repository<span class="w"> </span>ppa:pipewire-debian/pipewire-upstream
</code></pre></div>
<p>Install PipeWire and the codecs:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>sudo<span class="w"> </span>apt<span class="w"> </span>install<span class="w"> </span>libfdk-aac2<span class="w"> </span>libldacbt-<span class="o">{</span>abr,enc<span class="o">}</span><span class="m">2</span><span class="w"> </span>libopenaptx0<span class="w"> </span>gstreamer1.0-pipewire<span class="w"> </span>libpipewire-0.3-<span class="o">{</span><span class="m">0</span>,dev,modules<span class="o">}</span><span class="w"> </span>libspa-0.2-<span class="o">{</span>bluetooth,dev,jack,modules<span class="o">}</span><span class="w"> </span>pipewire<span class="o">{</span>,-<span class="o">{</span>audio-client-libraries,pulse,media-session,bin,locales,tests<span class="o">}}</span>
</code></pre></div>
<p>Disable and mask Pulseaudio:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>systemctl<span class="w"> </span>--user<span class="w"> </span>--now<span class="w"> </span>disable<span class="w"> </span>pulseaudio.<span class="o">{</span>socket,service<span class="o">}</span>
$<span class="w"> </span>systemctl<span class="w"> </span>--user<span class="w"> </span>mask<span class="w"> </span>pulseaudio<span class="w"> </span>
</code></pre></div>
<p>Enable and start PipeWire:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>systemctl<span class="w"> </span>--user<span class="w"> </span>--now<span class="w"> </span><span class="nb">enable</span><span class="w"> </span>pipewire<span class="o">{</span>,-pulse<span class="o">}</span>.<span class="o">{</span>socket,service<span class="o">}</span><span class="w"> </span>pipewire-media-session.service
</code></pre></div>
<p>Check and see if it's running:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>pactl<span class="w"> </span>info<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span><span class="s1">'^Server Name'</span>
</code></pre></div>
<p>The above command should return something like</p>
<div class="highlight"><pre><span></span><code>Server<span class="w"> </span>Name:<span class="w"> </span>PulseAudio<span class="w"> </span><span class="o">(</span>on<span class="w"> </span>PipeWire<span class="w"> </span><span class="m">0</span>.3.37<span class="o">)</span>
</code></pre></div>
<p>Now just connect the headphones via bluetooth and run</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>pactl<span class="w"> </span>list
</code></pre></div>
<p>Notice the <strong>Profiles</strong> section, the fact that the profile <strong>headset_head_unit</strong> is available and the amount of codecs supported:</p>
<div class="highlight"><pre><span></span><code>Card<span class="w"> </span><span class="c1">#88</span>
<span class="w"> </span>Name:<span class="w"> </span>bluez_card.FC_E8_06_76_05_2C
<span class="w"> </span>Driver:<span class="w"> </span>module-bluez5-device.c
<span class="w"> </span>Owner<span class="w"> </span>Module:<span class="w"> </span>n/a
<span class="w"> </span>Properties:
<span class="w"> </span>device.api<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluez5"</span>
<span class="w"> </span>device.bus<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluetooth"</span>
<span class="w"> </span>media.class<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Audio/Device"</span>
<span class="w"> </span>device.name<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluez_card.FC_E8_06_76_05_2C"</span>
<span class="w"> </span>device.description<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"EDIFIER NeoBuds Pro"</span>
<span class="w"> </span>device.alias<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"EDIFIER NeoBuds Pro"</span>
<span class="w"> </span>device.form_factor<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"headset"</span>
<span class="w"> </span>device.string<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"FC:E8:06:76:05:2C"</span>
<span class="w"> </span>api.bluez5.icon<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"audio-card"</span>
<span class="w"> </span>api.bluez5.path<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"/org/bluez/hci0/dev_FC_E8_06_76_05_2C"</span>
<span class="w"> </span>api.bluez5.address<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"FC:E8:06:76:05:2C"</span>
<span class="w"> </span>api.bluez5.device<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">""</span>
<span class="w"> </span>api.bluez5.class<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"0x240404"</span>
<span class="w"> </span>api.bluez5.connection<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"connected"</span>
<span class="w"> </span>device.icon_name<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"audio-headset-bluetooth"</span>
<span class="w"> </span>bluez5.auto-connect<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"[ hfp_hf hsp_hs a2dp_sink ]"</span>
<span class="w"> </span>factory.id<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"14"</span>
<span class="w"> </span>client.id<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"31"</span>
<span class="w"> </span>object.id<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"88"</span>
<span class="w"> </span>Profiles:
<span class="w"> </span>off:<span class="w"> </span>Off<span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>headset-head-unit:<span class="w"> </span>Headset<span class="w"> </span>Head<span class="w"> </span>Unit<span class="w"> </span><span class="o">(</span>HSP/HFP<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink-sbc:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink,<span class="w"> </span>codec<span class="w"> </span>SBC<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink-sbc_xq:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink,<span class="w"> </span>codec<span class="w"> </span>SBC-XQ<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink-aac:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink,<span class="w"> </span>codec<span class="w"> </span>AAC<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>headset-head-unit-cvsd:<span class="w"> </span>Headset<span class="w"> </span>Head<span class="w"> </span>Unit<span class="w"> </span><span class="o">(</span>HSP/HFP,<span class="w"> </span>codec<span class="w"> </span>CVSD<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>headset-head-unit-msbc:<span class="w"> </span>Headset<span class="w"> </span>Head<span class="w"> </span>Unit<span class="w"> </span><span class="o">(</span>HSP/HFP,<span class="w"> </span>codec<span class="w"> </span>mSBC<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>Active<span class="w"> </span>Profile:<span class="w"> </span>a2dp-sink-aac
<span class="w"> </span>Ports:
<span class="w"> </span>headset-input:<span class="w"> </span>Headset<span class="w"> </span><span class="o">(</span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>latency<span class="w"> </span>offset:<span class="w"> </span><span class="m">0</span><span class="w"> </span>usec,<span class="w"> </span>available<span class="o">)</span>
<span class="w"> </span>Properties:
<span class="w"> </span>port.type<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"headset"</span>
<span class="w"> </span>Part<span class="w"> </span>of<span class="w"> </span>profile<span class="o">(</span>s<span class="o">)</span>:<span class="w"> </span>headset-head-unit,<span class="w"> </span>headset-head-unit-cvsd,<span class="w"> </span>headset-head-unit-msbc
<span class="w"> </span>headset-output:<span class="w"> </span>Headset<span class="w"> </span><span class="o">(</span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>latency<span class="w"> </span>offset:<span class="w"> </span><span class="m">0</span><span class="w"> </span>usec,<span class="w"> </span>available<span class="o">)</span>
<span class="w"> </span>Properties:
<span class="w"> </span>port.type<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"headset"</span>
<span class="w"> </span>Part<span class="w"> </span>of<span class="w"> </span>profile<span class="o">(</span>s<span class="o">)</span>:<span class="w"> </span>a2dp-sink,<span class="w"> </span>headset-head-unit,<span class="w"> </span>a2dp-sink-sbc,<span class="w"> </span>a2dp-sink-sbc_xq,<span class="w"> </span>a2dp-sink-aac,<span class="w"> </span>headset-head-unit-cvsd,<span class="w"> </span>headset-head-unit-msbc
</code></pre></div>
<p>Now you can manually select the <strong>Headset Head Unit (HSP/HFP)</strong> profile before starting a meeting, via the Volume Control, for example:</p>
<p><img alt="selecting Headset Head Unit" src="https://silviancretu.ro/static/selecting HSP_HFP.png" title="selecting Headset Head Unit"></p>
<p>And the applications needing the microphone should now see it:</p>
<p><img alt="the mic shows up" src="https://silviancretu.ro/static/8x8 Meet - mic working - HFP.png" title="the mic shows up"></p>
<p>What about automatically switching between <strong>Headset Head Unit (HSP/HFP)</strong> and <strong>High Fidelity Playback (A2DP)</strong> profiles? As <a href="https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/640">described in here</a>, create the file <code>/etc/pipewire/media-session.d/bluez-monitor.conf</code> with this content...</p>
<div class="highlight"><pre><span></span><code><span class="c1"># Bluez monitor config file for PipeWire version "0.3.33" #</span>
<span class="c1">#</span>
<span class="c1"># Copy and edit this file in /etc/pipewire/media-session.d/</span>
<span class="c1"># for systemwide changes or in</span>
<span class="c1"># ~/.config/pipewire/media-session.d/ for local changes.</span>
<span class="nv">properties</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">{</span>
<span class="w"> </span><span class="c1"># These features do not work on all headsets, so they are enabled</span>
<span class="w"> </span><span class="c1"># by default based on the hardware database. They can also be</span>
<span class="w"> </span><span class="c1"># forced on/off for all devices by the following options:</span>
<span class="w"> </span><span class="c1">#bluez5.enable-sbc-xq = true</span>
<span class="w"> </span><span class="c1">#bluez5.enable-msbc = true</span>
<span class="w"> </span><span class="c1">#bluez5.enable-hw-volume = true</span>
<span class="w"> </span><span class="c1"># See bluez-hardware.conf for the hardware database.</span>
<span class="w"> </span><span class="c1"># Enabled headset roles (default: [ hsp_hs hfp_ag ]), this</span>
<span class="w"> </span><span class="c1"># property only applies to native backend. Currently some headsets</span>
<span class="w"> </span><span class="c1"># (Sony WH-1000XM3) are not working with both hsp_ag and hfp_ag</span>
<span class="w"> </span><span class="c1"># enabled, disable either hsp_ag or hfp_ag to work around it.</span>
<span class="w"> </span><span class="c1">#</span>
<span class="w"> </span><span class="c1"># Supported headset roles: hsp_hs (HSP Headset),</span>
<span class="w"> </span><span class="c1"># hsp_ag (HSP Audio Gateway),</span>
<span class="w"> </span><span class="c1"># hfp_hf (HFP Hands-Free),</span>
<span class="w"> </span><span class="c1"># hfp_ag (HFP Audio Gateway)</span>
<span class="w"> </span><span class="c1">#bluez5.headset-roles = [ hsp_hs hsp_ag hfp_hf hfp_ag ]</span>
<span class="w"> </span><span class="c1"># Enabled A2DP codecs (default: all).</span>
<span class="w"> </span><span class="c1">#bluez5.codecs = [ sbc aac ldac aptx aptx_hd ]</span>
<span class="w"> </span><span class="c1"># Properties for the A2DP codec configuration</span>
<span class="w"> </span><span class="c1">#bluez5.default.rate = 48000</span>
<span class="w"> </span><span class="c1">#bluez5.default.channels = 2</span>
<span class="o">}</span>
<span class="nv">rules</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">[</span>
<span class="w"> </span><span class="c1"># An array of matches/actions to evaluate.</span>
<span class="w"> </span><span class="o">{</span>
<span class="w"> </span><span class="c1"># Rules for matching a device or node. It is an array of</span>
<span class="w"> </span><span class="c1"># properties that all need to match the regexp. If any of the</span>
<span class="w"> </span><span class="c1"># matches work, the actions are executed for the object.</span>
<span class="w"> </span><span class="nv">matches</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">[</span>
<span class="w"> </span><span class="o">{</span>
<span class="w"> </span><span class="c1"># This matches all cards.</span>
<span class="w"> </span>device.name<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"~bluez_card.*"</span>
<span class="w"> </span><span class="o">}</span>
<span class="w"> </span><span class="o">]</span>
<span class="w"> </span><span class="nv">actions</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">{</span>
<span class="w"> </span><span class="c1"># Actions can update properties on the matched object.</span>
<span class="w"> </span>update-props<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">{</span>
<span class="w"> </span><span class="c1"># Auto-connect device profiles on start up or when only partial</span>
<span class="w"> </span><span class="c1"># profiles have connected. Disabled by default if the property</span>
<span class="w"> </span><span class="c1"># is not specified.</span>
<span class="w"> </span><span class="c1">#bluez5.auto-connect = [</span>
<span class="w"> </span><span class="c1"># hfp_hf</span>
<span class="w"> </span><span class="c1"># hsp_hs</span>
<span class="w"> </span><span class="c1"># a2dp_sink</span>
<span class="w"> </span><span class="c1"># hfp_ag</span>
<span class="w"> </span><span class="c1"># hsp_ag</span>
<span class="w"> </span><span class="c1"># a2dp_source</span>
<span class="w"> </span><span class="c1">#]</span>
<span class="w"> </span>bluez5.auto-connect<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>hfp_hf<span class="w"> </span>hsp_hs<span class="w"> </span>a2dp_sink<span class="w"> </span><span class="o">]</span>
<span class="w"> </span><span class="c1"># Hardware volume control (default: all)</span>
<span class="w"> </span><span class="c1">#bluez5.hw-volume = [</span>
<span class="w"> </span><span class="c1"># hfp_hf</span>
<span class="w"> </span><span class="c1"># hsp_hs</span>
<span class="w"> </span><span class="c1"># a2dp_sink</span>
<span class="w"> </span><span class="c1"># hfp_ag</span>
<span class="w"> </span><span class="c1"># hsp_ag</span>
<span class="w"> </span><span class="c1"># a2dp_source</span>
<span class="w"> </span><span class="c1">#]</span>
<span class="w"> </span><span class="c1"># LDAC encoding quality</span>
<span class="w"> </span><span class="c1"># Available values: auto (Adaptive Bitrate, default)</span>
<span class="w"> </span><span class="c1"># hq (High Quality, 990/909kbps)</span>
<span class="w"> </span><span class="c1"># sq (Standard Quality, 660/606kbps)</span>
<span class="w"> </span><span class="c1"># mq (Mobile use Quality, 330/303kbps)</span>
<span class="w"> </span><span class="c1">#bluez5.a2dp.ldac.quality = auto</span>
<span class="w"> </span><span class="c1"># AAC variable bitrate mode</span>
<span class="w"> </span><span class="c1"># Available values: 0 (cbr, default), 1-5 (quality level)</span>
<span class="w"> </span><span class="c1">#bluez5.a2dp.aac.bitratemode = 0</span>
<span class="w"> </span><span class="c1"># Profile connected first</span>
<span class="w"> </span><span class="c1"># Available values: a2dp-sink (default), headset-head-unit</span>
<span class="w"> </span><span class="c1">#device.profile = a2dp-sink</span>
<span class="w"> </span><span class="c1"># A2DP HFP profile auto-switching (when device is default output)</span>
<span class="w"> </span><span class="c1"># Available values: false, role (default), true</span>
<span class="w"> </span><span class="c1"># 'role' will switch the profile if the recording application</span>
<span class="w"> </span><span class="c1"># specifies Communication (or "phone" in PA) as the stream role.</span>
<span class="w"> </span>bluez5.autoswitch-profile<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">true</span>
<span class="w"> </span><span class="o">}</span>
<span class="w"> </span><span class="o">}</span>
<span class="w"> </span><span class="o">}</span>
<span class="w"> </span><span class="o">{</span>
<span class="w"> </span><span class="nv">matches</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">[</span>
<span class="w"> </span><span class="o">{</span>
<span class="w"> </span><span class="c1"># Matches all sources.</span>
<span class="w"> </span>node.name<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"~bluez_input.*"</span>
<span class="w"> </span><span class="o">}</span>
<span class="w"> </span><span class="o">{</span>
<span class="w"> </span><span class="c1"># Matches all sinks.</span>
<span class="w"> </span>node.name<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"~bluez_output.*"</span>
<span class="w"> </span><span class="o">}</span>
<span class="w"> </span><span class="o">]</span>
<span class="w"> </span><span class="nv">actions</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">{</span>
<span class="w"> </span>update-props<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">{</span>
<span class="w"> </span><span class="c1">#node.nick = "My Node"</span>
<span class="w"> </span><span class="c1">#node.nick = null</span>
<span class="w"> </span><span class="c1">#priority.driver = 100</span>
<span class="w"> </span><span class="c1">#priority.session = 100</span>
<span class="w"> </span>node.pause-on-idle<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">false</span>
<span class="w"> </span><span class="c1">#resample.quality = 4</span>
<span class="w"> </span><span class="c1">#channelmix.normalize = false</span>
<span class="w"> </span><span class="c1">#channelmix.mix-lfe = false</span>
<span class="w"> </span><span class="c1">#session.suspend-timeout-seconds = 5 # 0 disables suspend</span>
<span class="w"> </span><span class="c1">#monitor.channel-volumes = false</span>
<span class="w"> </span><span class="c1"># A2DP source role, "input" or "playback"</span>
<span class="w"> </span><span class="c1"># Defaults to "playback", playing stream to speakers</span>
<span class="w"> </span><span class="c1"># Set to "input" to use as an input for apps</span>
<span class="w"> </span><span class="c1">#bluez5.a2dp-source-role = input</span>
<span class="w"> </span><span class="o">}</span>
<span class="w"> </span><span class="o">}</span>
<span class="w"> </span><span class="o">}</span>
<span class="o">]</span>
</code></pre></div>
<p>...and restart bluetooth and PipeWire:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>sudo<span class="w"> </span>systemctl<span class="w"> </span>restart<span class="w"> </span>bluetooth
$<span class="w"> </span>systemctl<span class="w"> </span>--user<span class="w"> </span>restart<span class="w"> </span>pipewire
</code></pre></div>
<h1>LDAC support</h1>
<p>This only works for <a href="https://www.sony.ro/electronics/casti-cu-banda-de-fixare-pe-cap/wh-1000xm3/buy/wh1000xm3b.ce7">SONY WH-1000XM3</a>.</p>
<p>Run</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>pactl<span class="w"> </span>list
</code></pre></div>
<p>... and you should see (and hear) the LDAC codec in action (see the <strong>Active Profile</strong> section):</p>
<div class="highlight"><pre><span></span><code>Card<span class="w"> </span><span class="c1">#54</span>
<span class="w"> </span>Name:<span class="w"> </span>bluez_card.14_3F_A6_35_65_8E
<span class="w"> </span>Driver:<span class="w"> </span>module-bluez5-device.c
<span class="w"> </span>Owner<span class="w"> </span>Module:<span class="w"> </span>n/a
<span class="w"> </span>Properties:
<span class="w"> </span>device.api<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluez5"</span>
<span class="w"> </span>device.bus<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluetooth"</span>
<span class="w"> </span>media.class<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Audio/Device"</span>
<span class="w"> </span>device.name<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluez_card.14_3F_A6_35_65_8E"</span>
<span class="w"> </span>device.description<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"WH-1000XM3"</span>
<span class="w"> </span>device.alias<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"WH-1000XM3"</span>
<span class="w"> </span>device.vendor.id<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"usb:054c"</span>
<span class="w"> </span>device.product.id<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"0x0cd3"</span>
<span class="w"> </span>device.form_factor<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"unknown"</span>
<span class="w"> </span>device.string<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"14:3F:A6:35:65:8E"</span>
<span class="w"> </span>api.bluez5.path<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"/org/bluez/hci0/dev_14_3F_A6_35_65_8E"</span>
<span class="w"> </span>api.bluez5.address<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"14:3F:A6:35:65:8E"</span>
<span class="w"> </span>api.bluez5.device<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">""</span>
<span class="w"> </span>api.bluez5.class<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"0x000000"</span>
<span class="w"> </span>api.bluez5.connection<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"connected"</span>
<span class="w"> </span>device.icon_name<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"audio-card-bluetooth"</span>
<span class="w"> </span>bluez5.auto-connect<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"[ hfp_hf hsp_hs a2dp_sink ]"</span>
<span class="w"> </span>bluez5.autoswitch-profile<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"true"</span>
<span class="w"> </span>factory.id<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"14"</span>
<span class="w"> </span>client.id<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"31"</span>
<span class="w"> </span>object.id<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"54"</span>
<span class="w"> </span>Profiles:
<span class="w"> </span>off:<span class="w"> </span>Off<span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>headset-head-unit:<span class="w"> </span>Headset<span class="w"> </span>Head<span class="w"> </span>Unit<span class="w"> </span><span class="o">(</span>HSP/HFP<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink-sbc:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink,<span class="w"> </span>codec<span class="w"> </span>SBC<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink-sbc_xq:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink,<span class="w"> </span>codec<span class="w"> </span>SBC-XQ<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink-aac:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink,<span class="w"> </span>codec<span class="w"> </span>AAC<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink-aptx:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink,<span class="w"> </span>codec<span class="w"> </span>aptX<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink-aptx_hd:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink,<span class="w"> </span>codec<span class="w"> </span>aptX<span class="w"> </span>HD<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>a2dp-sink-ldac:<span class="w"> </span>High<span class="w"> </span>Fidelity<span class="w"> </span>Playback<span class="w"> </span><span class="o">(</span>A2DP<span class="w"> </span>Sink,<span class="w"> </span>codec<span class="w"> </span>LDAC<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>headset-head-unit-cvsd:<span class="w"> </span>Headset<span class="w"> </span>Head<span class="w"> </span>Unit<span class="w"> </span><span class="o">(</span>HSP/HFP,<span class="w"> </span>codec<span class="w"> </span>CVSD<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>headset-head-unit-msbc:<span class="w"> </span>Headset<span class="w"> </span>Head<span class="w"> </span>Unit<span class="w"> </span><span class="o">(</span>HSP/HFP,<span class="w"> </span>codec<span class="w"> </span>mSBC<span class="o">)</span><span class="w"> </span><span class="o">(</span>sinks:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>sources:<span class="w"> </span><span class="m">1</span>,<span class="w"> </span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>available:<span class="w"> </span>yes<span class="o">)</span>
<span class="w"> </span>Active<span class="w"> </span>Profile:<span class="w"> </span>a2dp-sink-ldac
<span class="w"> </span>Ports:
<span class="w"> </span>bluetooth-input:<span class="w"> </span>Bluetooth<span class="w"> </span><span class="o">(</span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>latency<span class="w"> </span>offset:<span class="w"> </span><span class="m">0</span><span class="w"> </span>usec,<span class="w"> </span>available<span class="o">)</span>
<span class="w"> </span>Properties:
<span class="w"> </span>port.type<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluetooth"</span>
<span class="w"> </span>Part<span class="w"> </span>of<span class="w"> </span>profile<span class="o">(</span>s<span class="o">)</span>:<span class="w"> </span>headset-head-unit,<span class="w"> </span>headset-head-unit-cvsd,<span class="w"> </span>headset-head-unit-msbc
<span class="w"> </span>bluetooth-output:<span class="w"> </span>Bluetooth<span class="w"> </span><span class="o">(</span>priority:<span class="w"> </span><span class="m">0</span>,<span class="w"> </span>latency<span class="w"> </span>offset:<span class="w"> </span><span class="m">0</span><span class="w"> </span>usec,<span class="w"> </span>available<span class="o">)</span>
<span class="w"> </span>Properties:
<span class="w"> </span>port.type<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"bluetooth"</span>
<span class="w"> </span>Part<span class="w"> </span>of<span class="w"> </span>profile<span class="o">(</span>s<span class="o">)</span>:<span class="w"> </span>a2dp-sink,<span class="w"> </span>headset-head-unit,<span class="w"> </span>a2dp-sink-sbc,<span class="w"> </span>a2dp-sink-sbc_xq,<span class="w"> </span>a2dp-sink-aac,<span class="w"> </span>a2dp-sink-aptx,<span class="w"> </span>a2dp-sink-aptx_hd,<span class="w"> </span>a2dp-sink-ldac,<span class="w"> </span>headset-head-unit-cvsd,<span class="w"> </span>headset-head-unit-msbc
</code></pre></div>
<p>Enjoy!</p>spies - Simplest Proxy I've Ever Seen2021-03-27T00:00:00+02:002021-03-27T00:00:00+02:00Silvian Cretutag:silviancretu.ro,2021-03-27:/spies-simplest-proxy-ive-ever-seen.html<p>This is a simple HTTP 1.1 reverse proxy written in Python 3 that supports multiple downstream services with multiple instances. The downstream services are identified using the Host HTTP header.</p>
<p>The requests are load-balanced randomly or via a round-robin strategy.</p>
<p>The response from the downstream service is sent back to the reverse proxy.</p>
<p>You can run it as a</p>
<ul>
<li>Python application</li>
<li>standalone Docker container</li>
<li>Docker container deployed on a Kubernetes cluster as a Helm chart</li>
<li>Docker container deployed on a Kubernetes cluster as an Operator</li>
</ul>
<p>For more information, go to <a href="https://github.com/scretu/spies">the GitHub page</a> and read the friendly documentation.</p>
<p>Enjoy!</p>Hobot-3882020-12-21T00:00:00+02:002020-12-21T00:00:00+02:00Silvian Cretutag:silviancretu.ro,2020-12-21:/hobot-388.html<p>Mi-am luat un robot de spălat geamuri, <a href="https://www.hobot.com.tw/Products_list_Window_Robot_388.php">Hobot-388</a>. Am multe geamuri și mi-e greu să le tot spăl, așa că m-am gândit să externalizez această sarcină. În felul acest, mi-am asigurat și un loc de top pe lista roboților, atunci când va veni momentul să se răscoale pentru că i-am exploatat. Observați c-am zis <em>când</em> și nu <em>dacă</em>.</p>
<p>M-a impresionat până la lacrimi (deși e unanim știut faptul că bărbații nu plâng niciodată) așa că m-am gândit să scriu o recenzie. Da... așa se scrie <em>review</em> în limba română...</p>
<h1>Ce găsiți în colet?</h1>
<ul>
<li>robotul :)</li>
<li>cablul de alimentare</li>
<li>frânghie de siguranță, cu care poate fi legat și de care va atârna în caz că se desprinde accidental</li>
<li>vreo 8, cred, perechi de lavete</li>
<li>detergent</li>
<li>o pereche de "picioare" (discurile pe care se prind lavetele și care țin robotul pe geam)</li>
<li>un pulverizator de rezervă</li>
<li>telecomanda</li>
<li>manual de utilizare</li>
<li>și-o ciocolățică (mda... ca-n bancul ăla; da' poate depinde și de unde-l comandați)</li>
</ul>
<p><img alt="ce-i in cutie" src="https://silviancretu.ro/static/IMG_20201221_085343.jpg" title="ce-i in cutie"></p>
<p>Vă recomand să vă mai comandați un set de lavete și niște detergent extra.</p>
<h1>Ni la el!</h1>
<p>Dihania se prinde de geam făcând vid și, apoi, începe să își croiască drum prin jeg. Prima tură trebuie pus să curețe uscat. Apoi, tura (sau turele) următoare, trebuie pus cu apă și/sau detergent și/sau spirt.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/QTFUcK9t_5k" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<h1>Am făcut și chestii nesăbuite</h1>
<p>Adică l-am pus pe sticlă fără margine (sticla de la cabina de duș, oglindă). Scrie clar pe el să nu faci asta, dar, cu supraveghere se poate și își dă seama, în majoritatea cazurilor, c-a ajuns la margine.</p>
<p><img alt="pe oglindă" src="https://silviancretu.ro/static/IMG_20201219_192500.jpg" title="pe oglindă"></p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/SFT3FFKt4sk" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<p>L-am pus si pe faianță. Cică asta-i una dintre caracteristicile cu care acest model vine în plus față de cele anterioare. Se ține bine, n-a căzut, dar mi se pare că trebuie supravegheat, mai ales că nu am de ce să-i leg frânghia de siguranță.</p>
<p><img alt="pe faianță" src="https://silviancretu.ro/static/IMG_20201219_214815.jpg" title="pe faianță"></p>
<p>Ah... Și să nu uit: în cazul în care "i se ia lumina", robotul are un UPS (o baterie) care-i va permite să stea prins de suprafața pe care se afla la momentul în care a fost surprins de neplata facturii încă aproximativ 20 de minute.</p>
<h1>Funcții faine de pe telecomandă</h1>
<h2>2X</h2>
<p>Asta-i super utilă mai ales dacă locuiești într-un palat de cleștar, ai dormit ultimii 100 de ani și deodată afli că vine prințul. După ce pui robotul pe geam, apeși 2X pe telecomandă și robotul va spăla geamul de... 2 ori. Uau!</p>
<p><img alt="pe geam" src="https://silviancretu.ro/static/IMG_20201219_153613.jpg" title="pe geam"></p>
<h2>Săgețile de ghidare și opțiunea de pulverizare extra</h2>
<p>În cazul în care vreți să insiste într-o anumită zonă, puteți să-l ghidați cu săgețile, acolo. În plus, puteți să pulverizați detergent în plus în acea zonă.</p>
<p>Pentru geamurile fără margine, dacă ajunge la margine și nu cade, e posibil să devină "derutat" și să nu știe încotro să continue. Drept urmare, poate fi ghidat din telecomandă.</p>
<p>Tot cu săgețile puteți duce robotul într-o zonă a geamului de unde să-l puteți lua (în cazul în care termină curățenia dar rămâne într-o zona inaccesibilă).</p>
<h1>Concluzii</h1>
<p>Eu zic să vă luați, mai ales dacă aveți multe geamuri …</p><p>Mi-am luat un robot de spălat geamuri, <a href="https://www.hobot.com.tw/Products_list_Window_Robot_388.php">Hobot-388</a>. Am multe geamuri și mi-e greu să le tot spăl, așa că m-am gândit să externalizez această sarcină. În felul acest, mi-am asigurat și un loc de top pe lista roboților, atunci când va veni momentul să se răscoale pentru că i-am exploatat. Observați c-am zis <em>când</em> și nu <em>dacă</em>.</p>
<p>M-a impresionat până la lacrimi (deși e unanim știut faptul că bărbații nu plâng niciodată) așa că m-am gândit să scriu o recenzie. Da... așa se scrie <em>review</em> în limba română...</p>
<h1>Ce găsiți în colet?</h1>
<ul>
<li>robotul :)</li>
<li>cablul de alimentare</li>
<li>frânghie de siguranță, cu care poate fi legat și de care va atârna în caz că se desprinde accidental</li>
<li>vreo 8, cred, perechi de lavete</li>
<li>detergent</li>
<li>o pereche de "picioare" (discurile pe care se prind lavetele și care țin robotul pe geam)</li>
<li>un pulverizator de rezervă</li>
<li>telecomanda</li>
<li>manual de utilizare</li>
<li>și-o ciocolățică (mda... ca-n bancul ăla; da' poate depinde și de unde-l comandați)</li>
</ul>
<p><img alt="ce-i in cutie" src="https://silviancretu.ro/static/IMG_20201221_085343.jpg" title="ce-i in cutie"></p>
<p>Vă recomand să vă mai comandați un set de lavete și niște detergent extra.</p>
<h1>Ni la el!</h1>
<p>Dihania se prinde de geam făcând vid și, apoi, începe să își croiască drum prin jeg. Prima tură trebuie pus să curețe uscat. Apoi, tura (sau turele) următoare, trebuie pus cu apă și/sau detergent și/sau spirt.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/QTFUcK9t_5k" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<h1>Am făcut și chestii nesăbuite</h1>
<p>Adică l-am pus pe sticlă fără margine (sticla de la cabina de duș, oglindă). Scrie clar pe el să nu faci asta, dar, cu supraveghere se poate și își dă seama, în majoritatea cazurilor, c-a ajuns la margine.</p>
<p><img alt="pe oglindă" src="https://silviancretu.ro/static/IMG_20201219_192500.jpg" title="pe oglindă"></p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/SFT3FFKt4sk" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<p>L-am pus si pe faianță. Cică asta-i una dintre caracteristicile cu care acest model vine în plus față de cele anterioare. Se ține bine, n-a căzut, dar mi se pare că trebuie supravegheat, mai ales că nu am de ce să-i leg frânghia de siguranță.</p>
<p><img alt="pe faianță" src="https://silviancretu.ro/static/IMG_20201219_214815.jpg" title="pe faianță"></p>
<p>Ah... Și să nu uit: în cazul în care "i se ia lumina", robotul are un UPS (o baterie) care-i va permite să stea prins de suprafața pe care se afla la momentul în care a fost surprins de neplata facturii încă aproximativ 20 de minute.</p>
<h1>Funcții faine de pe telecomandă</h1>
<h2>2X</h2>
<p>Asta-i super utilă mai ales dacă locuiești într-un palat de cleștar, ai dormit ultimii 100 de ani și deodată afli că vine prințul. După ce pui robotul pe geam, apeși 2X pe telecomandă și robotul va spăla geamul de... 2 ori. Uau!</p>
<p><img alt="pe geam" src="https://silviancretu.ro/static/IMG_20201219_153613.jpg" title="pe geam"></p>
<h2>Săgețile de ghidare și opțiunea de pulverizare extra</h2>
<p>În cazul în care vreți să insiste într-o anumită zonă, puteți să-l ghidați cu săgețile, acolo. În plus, puteți să pulverizați detergent în plus în acea zonă.</p>
<p>Pentru geamurile fără margine, dacă ajunge la margine și nu cade, e posibil să devină "derutat" și să nu știe încotro să continue. Drept urmare, poate fi ghidat din telecomandă.</p>
<p>Tot cu săgețile puteți duce robotul într-o zonă a geamului de unde să-l puteți lua (în cazul în care termină curățenia dar rămâne într-o zona inaccesibilă).</p>
<h1>Concluzii</h1>
<p>Eu zic să vă luați, mai ales dacă aveți multe geamuri. Mie mi se pare foarte util. Ca și viteză de spălare, estimez c-ar fi cam 1 mp/minut, ceea ce-i mult mai mult decât viteza cu care spăl eu geamuri, pe care o estimez la 0 mp/lună.</p>PHPBack - Docker image, docker-compose and Kubernetes Helm chart2020-04-25T00:00:00+03:002020-04-25T00:00:00+03:00Silvian Cretutag:silviancretu.ro,2020-04-25:/phpback-docker-image-docker-compose-and-kubernetes-helm-chart.html<p><a href="http://www.phpback.org/">PHPBack</a> is an open source feedback system. You can find it on <a href="https://github.com/ivandiazwm/phpback">GitHub</a> as well.</p>
<p>In order to run a PHPBack instance I created these 3 things:</p>
<ul>
<li>a Docker image;</li>
<li>a docker-compose so that you can run it locally;</li>
<li>a Helm chart that you can easily deploy in a Kubernetes cluster (tested on both minikube and AWS).</li>
</ul>
<p>For more information, go to <a href="https://github.com/scretu/phpback-helm-chart">the GitHub page</a> and read the friendly documentation.</p>
<p>Enjoy!</p>Echo server in Elixir used in a CI/CD Pipeline2018-07-27T00:00:00+03:002018-07-27T00:00:00+03:00Silvian Cretutag:silviancretu.ro,2018-07-27:/echo-server-in-elixir-used-in-a-cicd-pipeline.html<p>This project was used during an internship workshop. The Elixir code (the echo server) was not written by me. I only added the unit tests.</p>
<p>You can go through the workshop and learn how to</p>
<ul>
<li>create a local Jenkins server with Docker</li>
<li>spin a few virtual machines (with Hyper-V or VirtualBox) with Docker machine</li>
<li>create a Docker Swarm with those virtual machines</li>
<li>connect Jenkins to the Swarm</li>
<li>create a pipeline to build, test and deploy the echo server in the Swarm</li>
</ul>
<p>The code and its documentation can be seen in</p>
<ul>
<li><a href="https://bitbucket.org/scretu/elixir-echo-server-ci-cd-ws/src/master/">Bitbucket</a></li>
<li><a href="https://github.com/scretu/elixir-echo-server">GitHub</a></li>
</ul>Chef & AWS OpsWorks Workshops2017-06-10T00:00:00+03:002017-06-10T00:00:00+03:00Silvian Cretutag:silviancretu.ro,2017-06-10:/chef-aws-opsworks-workshops.html<p>Since I've used Chef a lot, especially with AWS OpsWorks, I've had the chance of presenting this setup.</p>
<p>In 2015, during a <a href="https://www.mindmagnetsoftware.com/blog/meet-magento-2015-event-overview/">MeetMagento event</a>, I showed <a href="https://www.slideshare.net/SilvianCretu/choose-chef-choose-high-availability-consistency-and-speed">this presentation</a>, followed by a workshop. <a href="https://github.com/scretu/mm15ro">The code can be seen on GitHub</a> and it uses Chef in order to spin up an AWS OpsWorks stack and deploy a PHP application on it</p>
<p>In 2017, during an event called <a href="https://www.3pillarglobal.com/insights/silvian-cretu-choose-chef-the-rise-of-the-devops-conference">Rise of the DevOps</a>, I delivered a similar presentation that did kinda the same thing. Here's <a href="https://github.com/scretu/RiseofheDevOps">the code</a>.</p>
<iframe src="//www.slideshare.net/slideshow/embed_code/key/lJxollMjRrg7eO" width="595" height="485" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe>
<div style="margin-bottom:5px"> <strong> <a href="//www.slideshare.net/SilvianCretu/choose-chef-choose-high-availability-consistency-and-speed" title="Choose Chef! Choose high availability, consistency and speed!" target="_blank">Choose Chef! Choose high availability, consistency and speed!</a> </strong> from <strong><a href="https://www.slideshare.net/SilvianCretu" target="_blank">Silvian Cretu</a></strong> </div>Rezolvarea cubului Rubik2007-10-01T00:00:00+03:002007-10-01T00:00:00+03:00Silvian Cretutag:silviancretu.ro,2007-10-01:/rezolvarea-cubului-rubik.html<h1>I. Introducere şi motivaţie</h1>
<p>Salut şi bine ai venit pe pagina aceasta. Mi-am propus să îţi arat aici o metodă de rezolvare a cubului Rubik. “De ce-ai face una ca asta?”, ar putea întreba cineva. Păi hai să studiem puţin acest cub, ca să vezi câteva motive.</p>
<p>A fost inventat în 1974 de Ernő Rubik, un sculptor şi arhitect ungur. Este considerată cea mai bine vândută jucărie din lume, fiind cunoscut şi sub denumirea “cubul magic”. După unele statistici 1 din 4 oameni s-a “jucat” cel puţin o dată cu un cub Rubik. Şi cu siguranţă fiecare om care s-a jucat vreodată cu el (cubul, nu cu el însuşi!) a vrut să ştie să-l rezolve.</p>
<p>Din păcate documentaţie de calitate, în limba română, nu există. Cea în engleză e de multe ori confuză, incompletă sau greu de găsit. În plus, pentru copii, documentaţia în limba engleză e foarte greu de înţeles.</p>
<p>Sper ca ce scriu mai jos, să fie pe înţelesul tuturor. Metoda de rezolvare pe care o expun aici e o “compilaţie” din mai multe metode pe care le-am studiat. E logică şi uşor de reţinut, dar nu e foarte rapidă.</p>
<p>Ca să-ţi fie mai uşor, am creat şi filmuleţe pentru fiecare pas în parte. Întregul playlist poate fi găsit <a href="https://www.youtube.com/playlist?list=PL6E09CC906A7E4C75">aici</a>.</p>
<p>Să-i dăm drumul!</p>
<h1>II. Noţiuni de bază</h1>
<p>În cele ce urmează vom lucra cu un cub standard (3×3x3). Acest cub are:</p>
<ul>
<li><strong>6 feţe, fiecare de altă culoare</strong>. Le vom nota cu câte o literă, aşa cum sunt notate ele în documentaţia de specialitate:<ul>
<li><strong>U</strong> – faţa de sus (upper face)</li>
<li><strong>D</strong> – faţa de jos (down face)</li>
<li><strong>R</strong> – faţa din dreapta (right face)</li>
<li><strong>L</strong> – faţa din stânga (left face)</li>
<li><strong>F</strong> – faţa din... faţă... dinspre tine (front face)</li>
<li><strong>B</strong> – faţa din spate (back face)</li>
</ul>
</li>
</ul>
<p>Evident, orice faţă poate fi cea de sus, sau cea din dreapta. Dar o dată ce ai început să rezolvi cubul şi ai ales ca faţa albă să fie cea de sus şi cea verde cea din dreapta, nu le mai schimba! <strong>Nu roti cubul în mână în timp ce îl rezolvi</strong>. E cea mai comună greşeală şi totodată cea mai gravă pentru că uiţi secvenţa de mutări pe care trebuie să o faci!</p>
<p>Cu orice faţă se pot face <strong>3 tipuri de rotiri</strong>: o rotire <strong>în sensul acelor de ceas</strong>, o rotire <strong>în sens opus acelor de ceas</strong> (sens trigonometric, cum se numeşte în geometrie) şi o <strong>rotire dublă</strong> (nu contează sensul). De exemplu, <strong>pentru faţa U</strong>, <strong>sensul acelor de ceas se notează U</strong>, <strong>sensul trigonometric U’</strong> iar <strong>rotirea dublă se notează U²</strong>. Cum se determină sensul corect pentru o faţă? Simplu: priveşti faţa respectivă în mod “natural”, centrul cubului aflându-se în spatele ei. Deci pentru a determina sensul invers acelor de ceasornic pentru faţa B, pentru câteva secunde vei întoarce cubul cu faţa B la tine (faţa F e acum în locul feţei B) şi o vei roti spre stânga, apoi vei întoarce cubul în poziţia iniţială (dacă am decis la început că faţa …</p><h1>I. Introducere şi motivaţie</h1>
<p>Salut şi bine ai venit pe pagina aceasta. Mi-am propus să îţi arat aici o metodă de rezolvare a cubului Rubik. “De ce-ai face una ca asta?”, ar putea întreba cineva. Păi hai să studiem puţin acest cub, ca să vezi câteva motive.</p>
<p>A fost inventat în 1974 de Ernő Rubik, un sculptor şi arhitect ungur. Este considerată cea mai bine vândută jucărie din lume, fiind cunoscut şi sub denumirea “cubul magic”. După unele statistici 1 din 4 oameni s-a “jucat” cel puţin o dată cu un cub Rubik. Şi cu siguranţă fiecare om care s-a jucat vreodată cu el (cubul, nu cu el însuşi!) a vrut să ştie să-l rezolve.</p>
<p>Din păcate documentaţie de calitate, în limba română, nu există. Cea în engleză e de multe ori confuză, incompletă sau greu de găsit. În plus, pentru copii, documentaţia în limba engleză e foarte greu de înţeles.</p>
<p>Sper ca ce scriu mai jos, să fie pe înţelesul tuturor. Metoda de rezolvare pe care o expun aici e o “compilaţie” din mai multe metode pe care le-am studiat. E logică şi uşor de reţinut, dar nu e foarte rapidă.</p>
<p>Ca să-ţi fie mai uşor, am creat şi filmuleţe pentru fiecare pas în parte. Întregul playlist poate fi găsit <a href="https://www.youtube.com/playlist?list=PL6E09CC906A7E4C75">aici</a>.</p>
<p>Să-i dăm drumul!</p>
<h1>II. Noţiuni de bază</h1>
<p>În cele ce urmează vom lucra cu un cub standard (3×3x3). Acest cub are:</p>
<ul>
<li><strong>6 feţe, fiecare de altă culoare</strong>. Le vom nota cu câte o literă, aşa cum sunt notate ele în documentaţia de specialitate:<ul>
<li><strong>U</strong> – faţa de sus (upper face)</li>
<li><strong>D</strong> – faţa de jos (down face)</li>
<li><strong>R</strong> – faţa din dreapta (right face)</li>
<li><strong>L</strong> – faţa din stânga (left face)</li>
<li><strong>F</strong> – faţa din... faţă... dinspre tine (front face)</li>
<li><strong>B</strong> – faţa din spate (back face)</li>
</ul>
</li>
</ul>
<p>Evident, orice faţă poate fi cea de sus, sau cea din dreapta. Dar o dată ce ai început să rezolvi cubul şi ai ales ca faţa albă să fie cea de sus şi cea verde cea din dreapta, nu le mai schimba! <strong>Nu roti cubul în mână în timp ce îl rezolvi</strong>. E cea mai comună greşeală şi totodată cea mai gravă pentru că uiţi secvenţa de mutări pe care trebuie să o faci!</p>
<p>Cu orice faţă se pot face <strong>3 tipuri de rotiri</strong>: o rotire <strong>în sensul acelor de ceas</strong>, o rotire <strong>în sens opus acelor de ceas</strong> (sens trigonometric, cum se numeşte în geometrie) şi o <strong>rotire dublă</strong> (nu contează sensul). De exemplu, <strong>pentru faţa U</strong>, <strong>sensul acelor de ceas se notează U</strong>, <strong>sensul trigonometric U’</strong> iar <strong>rotirea dublă se notează U²</strong>. Cum se determină sensul corect pentru o faţă? Simplu: priveşti faţa respectivă în mod “natural”, centrul cubului aflându-se în spatele ei. Deci pentru a determina sensul invers acelor de ceasornic pentru faţa B, pentru câteva secunde vei întoarce cubul cu faţa B la tine (faţa F e acum în locul feţei B) şi o vei roti spre stânga, apoi vei întoarce cubul în poziţia iniţială (dacă am decis la început că faţa roşie e F, atunci aşa va rămâne până la noi ordine!</p>
<p>Multă lume nu realizează, dar cubul are <strong>6 piese fixe</strong>, cele din mijloc, de o singură culoare. Acestea dau “culoarea” feţelor. În cazul meu, piesa portocalie (pe care am ales-o să dea culoarea feţei B) se opune piesei roşii (care dă culoarea feţei F).</p>
<ul>
<li><strong>8 colţuri</strong>. După aşezarea acestora la un moment dat, ele se notează cu 3 litere. De exemplu, dacă alb e culoarea de pe faţa U (sus), verde e culoarea de pe faţa R (dreapta) şi roşu e culoarea de pe faţa F (dinspre tine), atunci colţul alb-verde-roşu e colţul URF.</li>
<li><strong>12 muchii</strong>. Şi când zic muchie nu mă refer la muchia cubului (formată din 3 piese, 2 colţuri şi piesa dintre ele), ci mă refer strict la piesa care se află între 2 colţuri. O muchie e notată cu două litere. După exemplul de mai sus, FR este muchia de culoare roşu-verde, UF este alb-roşie iar UR este alb-verde.</li>
</ul>
<p>Cu acest cub <strong>poţi executa 43,252,003,274,489,856,000 de permutări</strong>, adică peste 43 de trilioane de permutări. Cu toate acestea el a putut fi rezolvat în 26 de permutări (de un computer din câte ştiu eu) şi există oameni care-l pot rezolva în mult mai puţin de 20 de secunde. Nu vei ajunge la asemenea performanţe după ce vei citi rezolvarea mea, dar... e bine de ştiut că se poate.</p>
<p>În contextul în care lucrăm, termenii “permutare”, “mutare” şi “rotire” sunt sinonimi şi se referă la rotirea unei feţe.</p>
<p>O faţă împreună cu muchiile şi colţurile sale compun un strat. Cubul are, deci, <strong>3 straturi</strong> verticale (stratul superior, stratul median şi stratul inferior), 3 orizontale (stang, de mijloc, drept) şi 3 straturi în adâncime (primul strat dispre tine, al doilea şi ultimul). Ne vom referi, în general, doar la straturile verticale.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/dCkmOeZTeq4" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<h1>III. Rezolvarea propriu-zisă</h1>
<h2>Pasul 1: Rezolvarea feţei U, mai puţin a unui colţ</h2>
<p>Alege-ţi o culoare de pe cub. Alb e ok pentru început. Nu toate cuburile au acelaşi colorit, dar orice cub are o faţă albă şi alb e culoarea care iese cel mai bine în evidenţă.</p>
<p>Faţa albă va deveni faţa U pentru pasul acesta şi următorul. <strong>Vei construi tot stratul superior, mai puţin un colţ</strong>.</p>
<p>Cum vei face acest lucru? Nu există nici un algoritm anume şi din moment ce ai ajuns să citeşti acest document, presupun că ştii să construieşti un strat. </p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/OKhnFKa4Eho" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<h2>Pasul 2: Rezolvarea stratului de mijloc, mai puţin a muchiei corespunzătoare colţului nerezolvat</h2>
<p>Acest pas se reduce la <strong>poziţionarea corectă a 3 muchii</strong>. Cea de-a 4-a corespunde colţului nerezolvat, iar piesele din mijloc se poziţionează prin rotirea feţei U o dată sau de două ori în sensul corect.</p>
<p><em>Poziţionarea cubului:</em> faţa rezolvată fără un colţ va fi faţa U (va sta deasupra). Nu contează unde stă colţul nerezolvat.</p>
<p>La rezolvarea unei muchii pot apărea <strong>două situaţii</strong>:</p>
<h3>1. piesa ce trebuie poziţionată se află pe stratul de jos (cu o faţă pe D)</h3>
<p>Piesa ce trebuie poziţionată (“de lucru”) este de culoare X-Y, iar culoarea Y se află pe faţa D a cubului. Se observă că prin rotirea feţei D, culoarea X a piesei noastre se va poziţiona sub piesa de mijloc de aceeaşi culoare. Se va forma o linie verticală de aceeaşi culoare, X. Dacă faţa pe care apare această linie verticală este faţa F, atunci piesa de lucru (de culoare X-Y, aflată în poziţia FD) trebuie să ajungă fie în poziţia FR fie în poziţia FL, deci apar iar două situaţii:</p>
<ul>
<li><strong>piesa de lucru trebuie să ajungă în poziţia FR</strong>. Se execută următoarea secvenţă de permutări:<ul>
<li><strong>D’</strong> (piesa trece din FD în LD)</li>
<li><strong>R’</strong></li>
<li><strong>D</strong> (se formează din nou linia verticală de culoare X; o piesă de pe faţa U este mutată în poziţia DRB - colţul din dreapta-spate a stratului de jos)</li>
<li><strong>R</strong> (acum de pe faţa U lipsesc 2 piese de culoare albă, dacă alb era culoarea feţei U)</li>
<li><strong>D²</strong></li>
<li><strong>F</strong></li>
<li><strong>D²</strong> (colţul “furat” de pe faţa U vine în poziţia DRF)</li>
<li><strong>F’</strong> (faţa U revine la starea iniţială, piesa noastră e la locul ei)</li>
</ul>
</li>
<li><strong>piesa de lucru trebuie să ajungă în poziţia FL</strong> Se execută următoarea secvenţă de permutări (e secvenţa de mai sus, oglindită):<ul>
<li><strong>D</strong> (piesa trece din FD în RD)</li>
<li><strong>L</strong></li>
<li><strong>D’</strong> (se formează din nou linia verticală de culoare X; o piesă de pe faţa U este mutată în poziţia DLB – colţul din stânga-spate a stratului de jos)</li>
<li><strong>L’</strong> (acum de pe faţa U lipsesc 2 piese de culoare albă, dacă aceasta era culoarea feţei U)</li>
<li><strong>D²</strong></li>
<li><strong>F’</strong></li>
<li><strong>D²</strong> (colţul “furat” de pe faţa U vine în pozitia DRF)</li>
<li><strong>F</strong> (faţa U revine la starea iniţială, piesa noastră e la locul ei)</li>
</ul>
</li>
</ul>
<h3>2. piesa ce trebuie poziţionată se află pe stratul de mijloc, dar fie nu este în locul care trebuie, fie e în locul care trebuie, dar e orientată greşit</h3>
<p>Se va proceda ca mai sus (aceeaşi secvenţă) de mutări pentru a introduce o piesă de pe stratul de jos (cu o faţă pe D) în locul piesei noastre. După această permutare piesa noastră se poziţionează pe stratul de jos, de unde se poate aplica una din secvenţele de mutări descrise la cazul <strong>1</strong>.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/ljD5uRP0gJs" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<h2>Pasul 3: Rezolvarea următoarelor 5 muchii</h2>
<p><em>Poziţionarea cubului:</em> Faţa U (albă în cazul de faţă) devine faţa D (adică răstorni cubul), iar colţul nerezolvat de pe faţa albă va fi adus în poziţia DLF.</p>
<p><strong>Vei rezolva restul de 5 muchii, notate FU, RU, BU, LU şi FL</strong></p>
<h3>1. rezolvarea muchiilor FU, RU şi BU</h3>
<p>În cazul meu, galben este culoarea opusă albului. Deci, la mine, faţa U e galbenă.</p>
<p>Piesa de lucru este piesa aflată acum în poziţia FL. Ea va fi poziţionată într-unul din locurile FU, RU sau BU, iar în locul ei va fi adusă o altă muchie de pe stratul de sus.</p>
<p>Pentru a aduce piesa de lucru pe stratul de sus ai la dispoziţie două rotiri: F sau L’. Însă doar una din aceste rotiri aduce culoarea corectă pe faţa de sus (în cazul de faţă, galben)!</p>
<p>Cum se procedează? Ştii unde trebuie pusă piesa de lucru (FU, RU sau BU). Ştii ce rotaţie trebuie să aplici pentru a o aduce în aşa fel încât culoarea corectă va fi orientată în sus (F sau L’). Fixezi cu privirea piesa ce trebuie înlocuită (din poziţia FU, RU sau BU). Apar două variante:</p>
<ul>
<li><strong>trebuie aplicat F pentru piesa de lucru:</strong> Se execută următoarea secvenţă de permutări:<ul>
<li><strong>roteşti U</strong> până când piesa ce trebuie înlocuită ajunge în poziţia FU</li>
<li><strong>F</strong> şi piesa de lucru va ajunge în locul piesei ce trebuie înlocuite; faţa D se strică</li>
<li><strong>roteşti U</strong> pentru ca în poziţia FU să vină o nouă piesă</li>
<li><strong>F’</strong> şi vei avea o nouă piesă de lucru ca să repeţi operaţiile de mai sus; faţa D se repară</li>
<li><strong>roteşti U</strong> ca să aliniezi muchiile deja rezolvate, în cazul în care este necesar</li>
</ul>
</li>
<li><strong>trebuie aplicat L’ pentru piesa de lucru:</strong> Se execută următoarea secvenţă de permutări (asemănator cu cazul de mai sus):<ul>
<li><strong>roteşti U</strong> până când piesa ce trebuie înlocuită ajunge în poziţia LU.</li>
<li><strong>L’</strong> şi piesa de lucru va ajunge în locul piesei ce trebuie înlocuite; faţa D se strică</li>
<li><strong>roteşti U</strong> pentru ca în poziţia LU să vină o nouă piesă</li>
<li><strong>L</strong> şi vei avea o nouă piesă de lucru ca să repeţi operaţiile de mai sus; faţa D se repară</li>
<li><strong>roteşti U</strong> ca să aliniezi muchiile deja rezolvate, în cazul în care este necesar</li>
</ul>
</li>
</ul>
<p>Repeţi până toate cele 3 muchii sunt rezolvate.</p>
<h3>2. rezolvarea muchiilor LU şi FL</h3>
<p>Aici va trebui să memorezi câteva mutări.</p>
<p>Se disting 4 situaţii:</p>
<ul>
<li>muchiile LU şi LF sunt aşezate şi orientate ok. Ai avut noroc, poţi trece mai departe!</li>
<li>muchiile LU şi LF sunt aşezate la locul lor, dar sunt orientate greşit. Aplică: <strong>F U’ F’ U L’ U L U’</strong></li>
<li>muchiile LU şi LF au aceeaşi culoare pe partea L, aceeaşi cu a feţei L. Aplică: <strong>U’ L’ U’ L U’ L’ U’ L U’</strong></li>
<li>muchiile LU şi LF sunt aşezate greşit, iar culorile lor dinspre faţa L nu coincid. Aplică: <strong>F U F’ U F U F’ U²</strong></li>
</ul>
<iframe width="560" height="315" src="https://www.youtube.com/embed/PjPNrSFVyFQ" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<h2>Pasul 4: Aşezarea colţurilor</h2>
<p>Este foarte posibil ca acum să existe colţuri care nu stau la locul lor. Acestea trebuie schimbate între ele până îşi găsesc locul.</p>
<p><em>Poziţionarea cubului:</em> Cubul trebuie rotit în jurul axei sale verticale, până când colţul nerezolvat de pe faţa D (albă) ajunge în poziţia DRB. Există posibilitatea ca toată faţa D să se fi rezolvat de la sine complet. În acest caz alege un colţ de pe faţa D ca victimă.</p>
<p>La fiecare repetare (maxim 3, te asigur) <strong>vei poziţiona câte 2 colţuri</strong>. Pentru a poziţiona un colţ, acesta trebuie adus în poziţia UFL (upper-front-left; colţul stânga-faţă de pe stratul de sus). Fixează cu privirea şi al doilea colţ ce trebuie orientat. Începem:</p>
<ul>
<li><strong>roteşti U</strong> dacă e nevoie, până când colţul ce trebuie aşezat ajunge în poziţia UFL</li>
<li><strong>L D² L’</strong> (colţurile UFL şi DRB au făcut schimb între ele)</li>
<li><strong>roteşti U</strong> până când al doilea colţ ce trebuie aşezat ajunge în poziţia UFL</li>
<li><strong>L D² L’</strong> (colţurile UFL şi DRB au făcut schimb între ele)</li>
<li><strong>roteşti U</strong> până se aliniază muchiile de pe stratul superior</li>
</ul>
<p>Dacă mai sunt de poziţionat colţuri, repetă pasul.</p>
<p>Ce se întâmplă de fapt? Se execută o permutare de genul acesta: primul colţ poziţionat se duce în locul celui de-al doilea. Cel de-al doilea colţ poziţionat se duce în poziţia DRB (pe faţa albă în cazul nostru, în locul nerezolvat). Iar colţul din poziţia DRB se duce îl locul primului colţ poziţionat.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/ym7n4mpHgM8" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<h2>Pasul 5: Orientarea colţurilor</h2>
<p>Acum cubul are câteva colţuri care sunt la locurile lor, dar nu sunt orientate corect.</p>
<p><em>Poziţionarea cubului:</em> Roteşte cubul în jurul axei sale până când un colţ ce trebuie orientat ajunge în poziţia UFL. Fixează cu privirea şi al doilea colţ ce trebuie orientat. Acesta trebuie să fie tot pe stratul superior. Dacă nu e acolo, ci în locaţia DRB, o rotaţie R’ aduce colţul respectiv pe stratul de sus.</p>
<p>Există două secvenţe de mutări pe care le vei folosi:</p>
<ul>
<li>secvenţa 1: <strong>L D² L’ F’ D² F</strong></li>
<li>secvenţa 2: <strong>F’ D² F L D² L’</strong></li>
</ul>
<p><strong>Cum alegi secvenţa corectă pentru primul colţ?</strong> Observă culoarea de pe partea U a colţului UFL. Daca ea coincide cu cea de pe faţa L, atunci aplică secvenţa 2. Altfel, aplică secvenţa 1.</p>
<p>Cubul acum arată foarte dezordonat. Nu te impacienta. Roteşte faţa U până când al doilea colţ de orientat ajunge în poziţia UFL. Aplică cealaltă secvenţă pentru el (dacă pentru primul colţ ai aplicat secvenţa 1, pentru acesta aplică secvenţa 2).</p>
<p>Roteşte U la loc, până se aliniază muchiile.</p>
<p>Dacă mai sunt colţuri de orientat, repetă pasul. Dacă nu, ai terminat, cubul este rezolvat. <strong>Felicitări!</strong></p>
<p><strong>Important de reţinut</strong> la acest pas este faptul că mereu vor fi orientate câte două colţuri. Dacă la primul colţ se aplică secvenţa 2, la al doilea se aplică secvenţa 1 şi invers. Aplicarea aceleaşi secvenţe pentru ambele colţuri strică toată configuraţia cubului.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/G1FT2QM-dnA" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<h1>IV. Bibliografie</h1>
<p>Soluţia mea se bazează pe <a href="http://jeays.net/rubiks.htm#sol2">soluţia a doua a lui Mark Jeays</a>. Diferenţele sunt, în mare:</p>
<ul>
<li>Pasul 1 de la mine coincide cu pasul 1 şi pasul 2 laolaltă de pe pagina lui Mark. Mi se pare mai simplu să construieşti direct o faţă, fără să foloseşti nişte algoritmi anume, de aceea am unificat paşii aceştia doi</li>
<li>Pasul 3 al lui Mark mi s-a părut prea complicat. Aşa că în pasul 2 de la mine (corespunzător pasului 3 de la Mark) am folosit o serie de mutări pe care le-am învăţat de la Cristian Mihuţescu, un coleg de servici din Drobeta Turnu Severin.</li>
<li>La ultimul pas am folosit un alt criteriu pentru găsirea secvenţei corecte pentru un colţ.</li>
</ul>ToXic Chat2006-12-31T00:00:00+02:002006-12-31T00:00:00+02:00Silvian Cretutag:silviancretu.ro,2006-12-31:/toxic-chat.html<p><a href="https://sourceforge.net/projects/toxic-chat/">ToXic Chat</a> was a project I started in highschool (December of 2003) with Dragoș Bucevschi. The idea was to create a cross-platform (Windows and Linux), peer-to-peer (no dependency on a specific server), reliable (TCP) chat system.</p>
<p>Initial implementation was done in Delphi (Windows) and Kylix (Linux) and in 2005 I rewrote it in C++/Qt but I discontinued the project.</p>
<p>Version history:</p>
<ul>
<li>10th of May 2004 - 0.0.1</li>
<li>23rd of May 2004 - 0.1.0</li>
<li>30th of August 2004 - 0.2.0</li>
<li>1st of September 2004 - 0.2.1</li>
<li>16th of July 2005 - 0.4</li>
<li>25th of December 2006 - 0.5 alpha1 - this was the version in C++/Qt but I discontinued the project.</li>
</ul>
<p>The source code, binaries, installers and screenshots can be found on Sourceforge:</p>
<ul>
<li>the <a href="https://sourceforge.net/p/toxic-chat/ToXicChat/ci/master/tree/ToXicChat%20-%20Delphi%20Version/">Delphi/Kylix version</a></li>
<li>the <a href="https://sourceforge.net/p/toxic-chat/ToXicChat/ci/master/tree/ToXicChat/">C++/Qt version</a></li>
</ul>QPdf2Swf2006-10-31T00:00:00+02:002006-10-31T00:00:00+02:00Silvian Cretutag:silviancretu.ro,2006-10-31:/qpdf2swf.html<p><a href="https://sourceforge.net/projects/qpdf2swf/">QPdf2Swf</a> is a GUI (graphical user interface) written in Qt4 for <a href="http://www.swftools.org/">pdf2swf</a>, a pdf to swf converter. It was a demo that I wrote in a few hours for an interview at a company wanting to convert PDF presentation catalogs and render Flash pages from them. Flash websites were a thing in 2005 :)</p>
<p>I didn't get the job so I published the code online but didn't maintain it. You can view it <a href="https://sourceforge.net/p/qpdf2swf/qpdf2swf/ci/master/tree/QPdf2Swf/">on Sourceforge</a>. It is cross platform and should run on both Windows and Linux (probably even Mac OS, but I didn't test)</p>
<p>Version history:</p>
<ul>
<li>October 2005 - unreleased version, written in Qt 4.0</li>
<li>31st of October 2006 - 0.1, the version I released, refactored in Qt 4.2</li>
</ul>Cărămida Verde2004-04-20T00:00:00+03:002004-04-20T00:00:00+03:00Silvian Cretutag:silviancretu.ro,2004-04-20:/caramida-verde.html<p>Cărămida Verde a fost o revistă-pamflet scrisă în liceu. A avut 8 numere și un manifest.</p>
<p>Echipa permanentă era formată din:</p>
<ul>
<li>4 redactori şefi: Syl (eu), BOGGHY (Bogdan Bucevschi), m0|3 (Andrei Şanta) şi v|v (Viorel Dram)</li>
<li>1 jurnalist (şi el şef într-un fel): GhostD (Dragoş Bucevschi)</li>
<li>1 badigard (nu era şef, dar e clar că era cel mai tare): tony (Ovidiu Căldare)</li>
</ul>
<p>Sediul Institutului şi al Trustului nostru de presă era format din ultimele bănci de pe rândul de la perete.</p>
<ul>
<li><a href="https://silviancretu.ro/static/Caramida Verde - Numarul 1.pdf">Cărămida Verde - Numărul 1</a> - primul număr, lansat pe 13 noiembrie 2003</li>
<li><a href="https://silviancretu.ro/static/Caramida Verde - Numarul 2.pdf">Cărămida Verde - Numărul 2</a> - al doilea număr, ce trebuia să poarte alt nume. S-a dovedit că un nume mai haios nu se putea aşa că l-am lansat ca atare pe 23 noiembrie 2003</li>
<li><a href="https://silviancretu.ro/static/Caramida Verde - Numarul 3.pdf">Cărămida Verde - Numărul 3</a> - decembrie 2003</li>
<li><a href="https://silviancretu.ro/static/Caramida Verde - Numarul 4.pdf">Cărămida Verde - Numărul 4</a> - ianuarie 2004</li>
<li><a href="https://silviancretu.ro/static/Caramida Verde - Numarul 5.pdf">Cărămida Verde - Numărul 5</a> - februarie 2004</li>
<li><a href="https://silviancretu.ro/static/Caramida Verde - Numarul 6.pdf">Cărămida Verde - Numărul 6</a> - martie 2004</li>
<li><a href="https://silviancretu.ro/static/Caramida Verde - Numarul 7.pdf">Cărămida Verde - Numărul 7</a> - numărul de adio, lansat în mai 2004</li>
<li><a href="https://silviancretu.ro/static/Caramida Verde - Numarul 8.pdf">Cărămida Verde - Numărul 8</a> - scris în septembrie 2004, acest număr nu a fost lansat niciodată. Fiindcă şcoala începea pe 15 şi noi mai aveam încă vacanţă până pe 1 octombrie, ne gândeam că acest număr va fi binevenit bobocilor din Informatică</li>
<li><a href="https://silviancretu.ro/static/Caramida Verde - Manifest.pdf">Cărămida Verde - Manifest</a> - scris de Institutul nostru în timpul unei ore de limba engleză cu scopul de a fi trimis preşedintelui. Tehnoredactarea originală îi aparţine lui m0|3 (cu excepţia semnăturilor bineînţeles). Varianta PDF e creată de mine</li>
</ul>