ejiek’s - BlogZola2023-11-12T00:00:00+00:00https://ejiek.id/blog/atom.xmlChallenges of defining a variable for a packaged systemd unit2023-11-12T00:00:00+00:002023-11-12T00:00:00+00:00
Unknown
https://ejiek.id/blog/2023/systemd-env-var/<p>I manage an Arch Linux VPS I'd like to keep an eye on.
<a href="https://grafana.com/products/cloud/">Grafana Cloud</a> combined with <a href="https://github.com/prometheus/node_exporter">node_exporter</a> look like a good start.
Grafana Cloud uses Prometheus to collect metrics.
Prometheus works by pulling data from exporters.
Therefore, my node_exporter needs to be accessible to Grafana Cloud's Prometheus service.</p>
<p>I don't want to expose <strong>node_exporter</strong> to the World Wide Web.
In a more controlled environment, it's possible to get Prometheus and node_exporter onto one network.
Through tailscale, nebula, a plain VPN, or a tunnel.
Plenty of options.
This isn't possible with Grafana Cloud since it's beyond my control.
Grafana Labs documentation has a solution - <a href="https://grafana.com/docs/grafana-cloud/send-data/metrics/metrics-prometheus/prometheus-config-examples/noagent_linuxnode/#install-prometheus-on-the-node">install Prometheus on the node</a>.
To run Prometheus locally, scrape local node_exporter and push resulting data to Prometheus in Grafana Cloud.
This is the intended way to use Prometheus in Grafana Cloud - to ship them data you pull yourself with Prometheus, Grafana agent, or any other compatible tool.</p>
<p>I installed node_exporter on the Arch Linux VPS: <code>pacman -S prometheus-node-exporter</code>.
Got it running: <code>systemctl enable --now prometheus-node-exporter.service</code>.
Now its web interface is accessible on port <code>:9100</code>.
The problem is, it's publicly accessible
Not what I intended.
Stopping the service for now.</p>
<p>Although blocking access to a port with a firewall is a valid option, I see it only as an additional measure.
This measure is fine for protection against misconfiguration, but ideally, the port should not be exposed at all.
Alternatively, we can ditch web communication altogether in favor of a Unix socket.
A wonderful option to have but it involves a file system with paths and permissions - extra hoops to jump through.
The solution I've chosen is to bind the port to a local-only addressThe solution I've chosen is to bind the port to a local-only address.</p>
<p>By default, node_exporter binds to <code>*:9100</code> which is configurable with <code>--web.listen-address=</code> CLI argument.
Setting it so <code>127.0.0.1:9100</code> limits connections to the ones originating from the machine itself.</p>
<p><code>systemctl show prometheus-node-exporter.service</code> to check what's in store for us.
It looks like a well-thought-out systemd unit file</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">...</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">ExecStart</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-meta z-group z-expansion z-brace z-shell"><span class="z-punctuation z-section z-expansion z-brace z-begin z-shell">{</span> path=/usr/bin/prometheus-node-exporter ; argv<span class="z-keyword z-control z-regexp z-set z-begin z-shell">[</span><span class="z-keyword z-control z-regexp z-set z-end z-shell">]</span>=/usr/bin/prometheus-node-exporter <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">NODE_EXPORTER_ARGS</span></span> ; ignore_errors=no ; start_time=<span class="z-keyword z-control z-regexp z-set z-begin z-shell">[</span>Sun 2023<span class="z-keyword z-operator z-word z-shell">-</span>11<span class="z-keyword z-operator z-word z-shell">-</span>05 20:49:05 UTC<span class="z-keyword z-control z-regexp z-set z-end z-shell">]</span> ; stop_time=<span class="z-keyword z-control z-regexp z-set z-begin z-shell">[</span>n/a<span class="z-keyword z-control z-regexp z-set z-end z-shell">]</span> ; pid=27419 ; code=(null) ; status=0/0 <span class="z-punctuation z-section z-expansion z-brace z-end z-shell">}</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">...</span></span>
</span></code></pre>
<p>It uses <code>$NODE_EXPORTER_ARGS</code> to save us from overriding the whole <code>ExecStart</code>.
That's plain awesome!
When <code>ExecStart</code> changes with future updates, it will be updated by the package manager since we're not overriding it.</p>
<p>All that is left to do is to set a variable.
<code>systemctl edit prometheus-node-exporter.service</code> to define an override.
<code>cat /etc/systemd/system/prometheus-node-exporter.service.d/override.conf</code> to check that it's saved.</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">[Service]</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">Environment</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>NODE_EXPORTER_ARGS=--web.listen-address=127.0.0.1:9100<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span></code></pre>
<p>Looks good.
<code>systemctl daemon-reload</code> to get new config into systemd.
<code>systemctl start prometheus-node-exporter.service</code>.</p>
<p><img src="https://ejiek.id/blog/2023/systemd-env-var/./still-public.png" alt="Aaaaaaaand it's still publicly accessible o.O" /></p>
<h2 id="configs-are-not-configuring">Configs are not configuring</h2>
<p><code>systemctl show prometheus-node-exporter.service</code> shows that the configuration line is present:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">...</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">Environment</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell">NODE_EXPORTER_ARGS=--web.listen-address=127.0.0.1:9100</span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">EnvironmentFiles</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell">/etc/conf.d/prometheus-node-exporter</span> <span class="z-punctuation z-definition z-compound z-begin z-shell">(</span><span class="z-variable z-other z-readwrite z-assignment z-shell">ignore_errors</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell">yes</span><span class="z-punctuation z-definition z-compound z-end z-shell">)</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">...</span></span>
</span></code></pre>
<p>Yet it starts with the default parameters.
I've tried a different port just to make sure that it's a configuration issue, not an exporter ignoring the IP address part.
Still the defaults.</p>
<p>Only after 10 more minutes of playing around with <code>ps</code> and <code>ss</code>, I've noticed that this unit file comes with <code>EnvironmentFiles</code> defined.
Well, it's definitively a good place to define a variable.
Let's try it then.</p>
<p>Huh, it's not empty <code>cat /etc/conf.d/prometheus-node-exporter</code>:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">NODE_EXPORTER_ARGS</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span></code></pre>
<p>The variable I'm struggling with is conveniently defined here as an empty one.
It overrode my attempts to define it in systemd unit override.</p>
<p>So, I'm setting it in an environment file:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">NODE_EXPORTER_ARGS</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>--web.listen-address=127.0.0.1:9100<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span></code></pre>
<p>Units reload, service restart, and node_exporter is no longer exposed.
Mission accomplished 🎉</p>
<h2 id="concluding-thoughts">Concluding Thoughts</h2>
<p>Crisis averted but I'm left with a feeling that a thing that is made for convenience is causing me trouble.
I'm not aware of any consensus on clearing or resetting a variable for a systemd unit file externally.
On the other hand, I can see how it's useful not to have your service broken because of a variable set elsewhere.
Fortunately, systemd doesn't source many places to get environmental variables for a service.
<code>NODE_EXPORTER_ARGS</code> is a self-explanatory and unique name.
The chance of interfering with a variable defined elsewhere is extremely low.
In my humble opinion it's better to leave this one commented out.
It's easy to uncomment and use while the user still has the freedom to define a variable in a unit file.
Moreover, the Env file is managed by the package manager.
The service override file isn't touched by a package manager, so the chance of getting a <a href="https://wiki.archlinux.org/title/Pacman/Pacnew_and_Pacsave#.pacnew">.pacnew</a> conflict is lower.</p>
<p>I want to be able to use a systemd unit override.
I want to define variables with it.
Please, don't block it with an external (env) file.</p>
<h2 id="broader-perspective">Broader Perspective</h2>
<p>Why stop at my opinion?
What's there in other Arch packages?
Community and AUR packages are less affected by the official Arch Linux way of doing things.
I'm going through the <a href="https://gitlab.archlinux.org/archlinux/packaging/packages"><em>main</em> packages</a>.</p>
<p>I've managed to find 58 files with variables in <code>ExecStart</code> using GitLab search:</p>
<details>
<summary>Gently click 🫵🏼 to see 58 links to Arch GitLab instance </summary>
<ul>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/minio/-/blob/main/minio.service">minio - minio.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/wpa_supplicant/-/blob/main/wpa_supplicant_dbus_service_syslog.patch">wpa_supplicant - wpa_supplicant_dbus_service_syslog.patch</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/elasticsearch/-/blob/main/elasticsearch@.service">elasticsearch - elasticsearch@.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/opensearch/-/blob/main/opensearch@.service">opensearch - opensearch@.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/osquery/-/blob/main/fixes.patch">osquery - fixes.patch</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/opensearch/-/blob/main/opensearch.service">opensearch - opensearch.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/dictd/-/blob/main/dictd.service">dictd - dictd.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/erlang/-/blob/main/epmd.service">erlang - epmd.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/kubernetes/-/blob/main/kube-scheduler.service">kubernetes - kube-scheduler.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/kubernetes/-/blob/main/kube-proxy.service">kubernetes - kube-proxy.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/dhcp-helper/-/blob/main/dhcp-helper.service">dhcp-helper - dhcp-helper.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/kubernetes/-/blob/main/kube-controller-manager.service">kubernetes - kube-controller-manager.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/kubernetes/-/blob/main/kube-apiserver.service">kubernetes - kube-apiserver.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/iodine/-/blob/main/iodined.service">iodine - iodined.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/dkfilter/-/blob/main/dkfilter-in.service">dkfilter - dkfilter-in.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/dkfilter/-/blob/main/dkfilter-out.service">dkfilter - dkfilter-out.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/kubernetes/-/blob/main/kubelet.service">kubernetes - kubelet.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/rng-tools/-/blob/main/rngd.service">rng-tools - rngd.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/prometheus-bird-exporter/-/blob/main/prometheus-bird-exporter.service">prometheus-bird-exporter - prometheus-bird-exporter.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/prometheus-mysqld-exporter/-/blob/main/prometheus-mysqld-exporter.service">prometheus-mysqld-exporter - prometheus-mysqld-exporter.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/postgresql/-/blob/main/postgresql.service">postgresql - postgresql.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/distcc/-/blob/main/distccd.service">distcc - distccd.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/consul/-/blob/main/consul.service">consul - consul.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/smbnetfs/-/blob/main/smbnetfs.service">smbnetfs - smbnetfs.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/prometheus-snmp-exporter/-/blob/main/systemd.service">prometheus-snmp-exporter - systemd.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/prometheus-systemd-exporter/-/blob/main/prometheus-systemd-exporter.service">prometheus-systemd-exporter - prometheus-systemd-exporter.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/gortr/-/blob/main/gortr.service">gortr - gortr.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/openpgp-ca/-/blob/main/openpgp-ca-restd.service">openpgp-ca - openpgp-ca-restd.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/prometheus-memcached-exporter/-/blob/main/prometheus-memcached-exporter.service">prometheus-memcached-exporter - prometheus-memcached-exporter.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/php7/-/blob/main/php-fpm.patch">php7 - php-fpm.patch</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/subversion/-/blob/main/svnserve.service">subversion - svnserve.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/cyrus-sasl/-/blob/main/saslauthd.service">cyrus-sasl - saslauthd.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/opendkim/-/blob/main/opendkim.service">opendkim - opendkim.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/openfire/-/blob/main/openfire.service">openfire - openfire.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/jenkins/-/blob/main/jenkins.service">jenkins - jenkins.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/syncplay/-/blob/main/syncplay.service">syncplay - syncplay.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/php7/-/blob/main/generate_patches">php7 - generate_patches</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/openssh/-/blob/main/ssh-agent.service">openssh - ssh-agent.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/beats/-/blob/main/metricbeat.service">beats - metricbeat.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/prometheus-postgres-exporter/-/blob/main/prometheus-postgres-exporter.service">prometheus-postgres-exporter - prometheus-postgres-exporter.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/nginx-prometheus-exporter/-/blob/main/nginx-prometheus-exporter.service">nginx-prometheus-exporter - nginx-prometheus-exporter.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/beats/-/blob/main/auditbeat.service">beats - auditbeat.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/beats/-/blob/main/journalbeat.service">beats - journalbeat.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/beats/-/blob/main/filebeat.service">beats - filebeat.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/beats/-/blob/main/heartbeat.service">beats - heartbeat.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/logstash/-/blob/main/logstash.service">logstash - logstash.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/prometheus-blackbox-exporter/-/blob/main/prometheus-blackbox-exporter.service">prometheus-blackbox-exporter - prometheus-blackbox-exporter.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/badvpn/-/blob/main/badvpn-ncd.service">badvpn - badvpn-ncd.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/choria-io/-/blob/main/choria-server.service">choria-io - choria-server.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/atftp/-/blob/main/atftpd.service">atftp - atftpd.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/prometheus-node-exporter/-/blob/main/prometheus-node-exporter.service">prometheus-node-exporter - prometheus-node-exporter.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/prometheus-ssl-exporter/-/blob/main/systemd.service">prometheus-ssl-exporter - systemd.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/prometheus-ssl-exporter/-/blob/main/systemd.service">prometheus-ssl-exporter - systemd.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/prometheus-wireguard-exporter/-/blob/main/prometheus-wireguard-exporter.service">prometheus-wireguard-exporter - prometheus-wireguard-exporter.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/prometheus/-/blob/main/prometheus.service">prometheus - prometheus.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/tomcat9/-/blob/main/tomcat9.service">tomcat9 - tomcat9.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/irker/-/blob/main/irkerd.service">irker - irkerd.service</a></li>
<li><a href="https://gitlab.archlinux.org/archlinux/packaging/packages/tigervnc/-/blob/main/remove-selinux.patch">tigervnc - remove-selinux.patch</a></li>
</ul>
</details>
<p>To make the following research a bit easier, I've downloaded repositories with these files locally.
Since some files belong to the same package, there are only 46 repositories to look through.</p>
<p>Almost all Prometheus exporters use this pattern:</p>
<details>
<summary>Slightly push to see RigGreping through exporters' config files</summary>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">rg</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> --</span>files</span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">rg</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>exporter*\.conf<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">xargs</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>l1</span> bat<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> --</span>style</span> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>header,grid<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span>
</span></code></pre>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">File:</span></span><span class="z-meta z-function-call z-arguments z-shell"> nginx-prometheus-exporter/nginx-prometheus-exporter.conf</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">NGINX_EXPORTER_ARGS</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">File:</span></span><span class="z-meta z-function-call z-arguments z-shell"> prometheus-postgres-exporter/prometheus-postgres-exporter.conf</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">DATA_SOURCE_NAME</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">POSTGRESQL_EXPORTER_ARGS</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">File:</span></span><span class="z-meta z-function-call z-arguments z-shell"> prometheus-blackbox-exporter/prometheus-blackbox-exporter.conf</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">BLACKBOX_EXPORTER_ARGS</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>--config.file='/etc/prometheus/blackbox.yml'<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">File:</span></span><span class="z-meta z-function-call z-arguments z-shell"> prometheus-systemd-exporter/prometheus-systemd-exporter.conf</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">SYSTEMD_EXPORTER_ARGS</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">File:</span></span><span class="z-meta z-function-call z-arguments z-shell"> prometheus-node-exporter/prometheus-node-exporter.conf</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">NODE_EXPORTER_ARGS</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">File:</span></span><span class="z-meta z-function-call z-arguments z-shell"> prometheus-bird-exporter/prometheus-bird-exporter.conf</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">BIRD_EXPORTER_ARGS</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">File:</span></span><span class="z-meta z-function-call z-arguments z-shell"> prometheus-mysqld-exporter/prometheus-mysqld-exporter.conf</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">DATA_SOURCE_NAME</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">MYSQLD_EXPORTER_ARGS</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">File:</span></span><span class="z-meta z-function-call z-arguments z-shell"> prometheus-wireguard-exporter/prometheus-wireguard-exporter.conf</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">WIREGUARD_EXPORTER_ARGS</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>--prepend_sudo=true<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">File:</span></span><span class="z-meta z-function-call z-arguments z-shell"> prometheus-memcached-exporter/prometheus-memcached-exporter.conf</span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">MEMCACHED_EXPORTER_ARGS</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">──────────────────────────────────────────────────────────────────────────────────</span></span>
</span></code></pre>
</details>
<p>It's not a good sample since most of them are packaged by the same person (<a href="https://vdwaa.nl/">Jelle van der Waa</a>).
Setting instead of resetting was introduced by others.</p>
<p>By now there is setting an <code>*ARGS</code> variable in Env file and resetting it.</p>
<p>I went through the rest of the packages and files.
There are two most common names for variables used in <code>ExecStart</code> - <code>*ARGS</code> and <code>*OPTS</code>.
So there is no agreement on that which is fine.</p>
<p>There are some other packages that only reset a variable in a Env file: <strong>rngd</strong>, <strong>subversion</strong>, <strong>prometheus</strong>, <strong>kubelet</strong>.</p>
<p><strong>badvpn</strong> sets one and resets args variable:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">NCD_CONFIG</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>/etc/ncd.conf<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">NCD_ARGS</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span></code></pre>
<p>and uses both in a unit file:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">ExecStart</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell">/usr/bin/badvpn-ncd</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell"><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">NCD_ARGS</span></span></span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> --</span>config-file</span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">NCD_CONFIG</span></span></span>
</span></code></pre>
<p>Some use Env file only to set variables: <strong>dkfilter</strong>, <strong>cyrus-sasl</strong>, <strong>dhcp</strong>, <strong>opendkim</strong></p>
<p>Others set variables in a systemd unit file: <strong>openssh agent</strong>, <strong>logstash</strong>, <strong>logstash</strong>:</p>
<pre data-lang="ini" class="language-ini z-code"><code class="language-ini" data-lang="ini"><span class="z-source z-genconfig"><span class="z-storage z-type z-genconfig">[Service]
</span></span><span class="z-source z-genconfig"><span class="z-keyword z-operator z-genconfig">.</span><span class="z-keyword z-operator z-genconfig">.</span><span class="z-keyword z-operator z-genconfig">.</span>
</span><span class="z-source z-genconfig"><span class="z-meta z-param z-genconfig"><span class="z-variable z-parameter z-genconfig">Environment</span><span class="z-keyword z-operator z-genconfig">=</span></span><span class="z-support z-constant z-genconfig">LS_HOME</span><span class="z-keyword z-operator z-genconfig">=</span><span class="z-keyword z-operator z-genconfig">/</span>var<span class="z-keyword z-operator z-genconfig">/</span>lib<span class="z-keyword z-operator z-genconfig">/</span>logstash
</span><span class="z-source z-genconfig"><span class="z-meta z-param z-genconfig"><span class="z-variable z-parameter z-genconfig">Environment</span><span class="z-keyword z-operator z-genconfig">=</span></span><span class="z-support z-constant z-genconfig">LS_HEAP_SIZE</span><span class="z-keyword z-operator z-genconfig">=</span><span class="z-string z-quoted z-double z-genconfig">"500m"</span>
</span><span class="z-source z-genconfig"><span class="z-meta z-param z-genconfig"><span class="z-variable z-parameter z-genconfig">Environment</span><span class="z-keyword z-operator z-genconfig">=</span></span><span class="z-support z-constant z-genconfig">LS_CONF_DIR</span><span class="z-keyword z-operator z-genconfig">=</span><span class="z-keyword z-operator z-genconfig">/</span>etc<span class="z-keyword z-operator z-genconfig">/</span>logstash<span class="z-keyword z-operator z-genconfig">/</span>conf<span class="z-keyword z-operator z-genconfig">.</span>d
</span><span class="z-source z-genconfig"><span class="z-meta z-param z-genconfig"><span class="z-variable z-parameter z-genconfig">Environment</span><span class="z-keyword z-operator z-genconfig">=</span></span><span class="z-support z-constant z-genconfig">LS_LOG_DIR</span><span class="z-keyword z-operator z-genconfig">=</span><span class="z-keyword z-operator z-genconfig">/</span>var<span class="z-keyword z-operator z-genconfig">/</span>log<span class="z-keyword z-operator z-genconfig">/</span>logstash
</span><span class="z-source z-genconfig"><span class="z-meta z-param z-genconfig"><span class="z-variable z-parameter z-genconfig">Environment</span><span class="z-keyword z-operator z-genconfig">=</span></span><span class="z-support z-constant z-genconfig">LS_SETTINGS_DIR</span><span class="z-keyword z-operator z-genconfig">=</span><span class="z-keyword z-operator z-genconfig">/</span>etc<span class="z-keyword z-operator z-genconfig">/</span>logstash
</span><span class="z-source z-genconfig"><span class="z-keyword z-operator z-genconfig">.</span><span class="z-keyword z-operator z-genconfig">.</span><span class="z-keyword z-operator z-genconfig">.</span>
</span><span class="z-source z-genconfig"><span class="z-meta z-param z-genconfig"><span class="z-variable z-parameter z-genconfig">ExecStart</span><span class="z-keyword z-operator z-genconfig">=</span></span><span class="z-keyword z-operator z-genconfig">/</span>usr<span class="z-keyword z-operator z-genconfig">/</span>share<span class="z-keyword z-operator z-genconfig">/</span>logstash<span class="z-keyword z-operator z-genconfig">/</span>bin<span class="z-keyword z-operator z-genconfig">/</span>logstash <span class="z-keyword z-operator z-genconfig">-</span>f <span class="z-storage z-source z-genconfig">$LS_CONF_DIR</span> <span class="z-keyword z-operator z-genconfig">-</span><span class="z-keyword z-operator z-genconfig">-</span>path<span class="z-keyword z-operator z-genconfig">.</span>logs <span class="z-storage z-source z-genconfig">$LS_LOG_DIR</span> <span class="z-keyword z-operator z-genconfig">-</span><span class="z-keyword z-operator z-genconfig">-</span>path<span class="z-keyword z-operator z-genconfig">.</span>data <span class="z-storage z-source z-genconfig">$LS_HOME</span> <span class="z-keyword z-operator z-genconfig">-</span><span class="z-keyword z-operator z-genconfig">-</span>path<span class="z-keyword z-operator z-genconfig">.</span>settings <span class="z-storage z-source z-genconfig">$LS_SETTINGS_DIR</span>
</span><span class="z-source z-genconfig"><span class="z-keyword z-operator z-genconfig">.</span><span class="z-keyword z-operator z-genconfig">.</span><span class="z-keyword z-operator z-genconfig">.</span>
</span></code></pre>
<p>Back to the Env file.
<strong>iodine</strong> & <strong>Jenkins</strong> define a bunch of variables but there are two that get purged <code>JAVA_OPTS</code>,
<code>JENKINS_OPTS</code></p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">JAVA</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell">/usr/lib/jvm/java-17-openjdk/bin/java</span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">JAVA_ARGS</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell">-Xmx512m</span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">JAVA_OPTS</span><span class="z-keyword z-operator z-assignment z-shell">=</span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">JENKINS_USER</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell">jenkins</span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">JENKINS_HOME</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell">/var/lib/jenkins</span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">JENKINS_WAR</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell">/usr/share/java/jenkins/jenkins.war</span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">JENKINS_WEBROOT</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell">--webroot=/var/cache/jenkins</span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">JENKINS_PORT</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell">--httpPort=8090</span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">JENKINS_OPTS</span><span class="z-keyword z-operator z-assignment z-shell">=</span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">JENKINS_COMMAND_LINE</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">JAVA</span></span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">JAVA_ARGS</span></span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">JAVA_OPTS</span></span> -jar <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">JENKINS_WAR</span></span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">JENKINS_WEBROOT</span></span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">JENKINS_PORT</span></span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">JENKINS_OPTS</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span></code></pre>
<p><strong>opensearch</strong> & <strong>elasticsearch</strong> set variables in a unit file.
In an Env file, JAVA_HOME is defined and other JVM/runtime-specific variables are commented out.</p>
<p>With <strong>distcc</strong> we finally move to my suggested turf - comments:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">DISTCC_ARGS</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>--allow 127.0.0.1<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">DISTCC_ARGS="--allow 192.168.0.0/24 --log-level error --log-file /tmp/distccd.log"</span><span class="z-comment z-line z-number-sign z-shell">
</span></span></code></pre>
<p>A default value for arguments and commented out a suggestion on how to use it.
My idea of commenting suggestions in getting somewhere.
The next two take it to the state I was thinking about after figuring out the node_exporter trick.</p>
<p><strong>openfire</strong> env file:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> If you wish to set any specific options to pass to the JVM, you can</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> set them with the following variable.</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">OPENFIRE_OPTS="-Xmx1024m"%</span><span class="z-comment z-line z-number-sign z-shell">
</span></span></code></pre>
<p><strong>syncplay</strong> env file:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> This is the file that syncplay@.service loads settings from, it does not affect the binary itself</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> See https://syncplay.pl/guide/server/ for a list of available flags and description</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">port="--port=8999"</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">isolate="--isolate-room"</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">password="--password yourpassword"</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">salt="--salt RANDOMSALT"</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">motd="--motd-file /etc/syncplay/motd"</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">ready="--disable-ready"</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">chat="--disable-chat"</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">maxChars="--max-chat-message-length 500"</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">usernameLength="--max-username-length 20"</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">statsFile="--stats-db-file /etc/syncplay/stats.db"</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">tls="--tls /etc/letsencrypt/live/syncplay.example.com/"</span><span class="z-comment z-line z-number-sign z-shell">
</span></span></code></pre>
<p>A bit of documenting comments and commented-out suggestions that leave me with the freedom to define variables in whatever way I please.</p>
<p>I'm afraid that there is no silver bullet, no proper way to define environmental variables in any situation.
It depends, as always.
Whether there are default arguments you have to provide and many probably many other factors I'm missing without a deeper dive.
Resetting a variable outside of the systemd unit file where it's used does feel like a wrong one though.</p>
Effortless Markdown Presentations in No Time2023-10-24T00:00:00+00:002023-10-24T00:00:00+00:00
Unknown
https://ejiek.id/blog/2023/effortless-markdown-presentation/<p>Have you ever found yourself in a pinch, needing to create a presentation but short on time?
I definitely have been there, and it's precisely where the simplicity of this method comes into play.
With a few keystrokes and the right tools, you can turn your Markdown notes into a stylish presentation.
No <a href="https://en.wikipedia.org/wiki/WYSIWYG">WYSIWYG</a> editors required, no clicking through various visual objects.
In this blog post, I’ll go over a method for creating visually appealing presentations in just 5 minutes.</p>
<p>It's a journey from the comfort of your terminal to the applause at the end of your well-delivered presentation.
I have to warn you though, you have to buckle up because initial setup might require some effort.</p>
<h2 id="technologies">Technologies</h2>
<p>This way of creating a presentation involves using several tools together.</p>
<p><a href="https://www.markdownguide.org/">Markdown</a> - to create the presentation/write its source code.
It is an easy markup language that is very much human-readable and easily stored as a plain text file.</p>
<p><a href="https://pandoc.org/">Pandoc</a> - to convert Markdown text into an actual presentation.
It is a universal document converter.
Just take a look at <a href="https://pandoc.org/diagram.svgz">this terrifyingly big diagram</a>.
Looks like it can convert anything to anything else. But most importantly Markdown to reveal.js.</p>
<p><a href="https://revealjs.com/">reveal.js</a> - to render the presentation.
It is a presentation framework built with web technologies.</p>
<p>You can use any text editor editor to author a Markdown file.
Pandoc is the only thing you need to install.
It's a CLI tool so you'll need a bit of terminal sweetness.
In our case, pandoc produces an HTML file with presentation and reveal.js is already included so the only thing left to do is to open this HTML file in a browser.</p>
<p>In this blog post you wont see any of these technologies used to its max.
If you want more, you can get more, just follow the links ^.~</p>
<h2 id="demo">Demo</h2>
<p>Here's what a final presentation may look like <em>(click it to activate keyboard navigation)</em>:</p>
<figure id="presentation-demo">
<iframe
src="./presentation.html"
title="Presentation Demo">
</iframe>
</figure>
<p>Now let's take a look at its source code:</p>
<p><code>index.md</code></p>
<pre data-lang="markdown" class="language-markdown z-code"><code class="language-markdown" data-lang="markdown"><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">% Easy Markdown presentation
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">% 10 october 2023 by ejiek
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown"><span class="z-markup z-heading z-1 z-markdown"><span class="z-punctuation z-definition z-heading z-begin z-markdown">#</span> </span><span class="z-markup z-heading z-1 z-markdown"><span class="z-entity z-name z-section z-markdown">Navigation</span><span class="z-meta z-whitespace z-newline z-markdown">
</span></span></span></span><span class="z-text z-html z-markdown">
</span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">This presentation has vertival and horizonal navigation
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown"><span class="z-markup z-heading z-2 z-markdown"><span class="z-punctuation z-definition z-heading z-begin z-markdown">##</span> </span><span class="z-markup z-heading z-2 z-markdown"><span class="z-entity z-name z-section z-markdown">Vertical navigation</span><span class="z-meta z-whitespace z-newline z-markdown">
</span></span></span></span><span class="z-text z-html z-markdown">
</span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">Uses second level of headings
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown"><span class="z-markup z-heading z-1 z-markdown"><span class="z-punctuation z-definition z-heading z-begin z-markdown">#</span> </span><span class="z-markup z-heading z-1 z-markdown"><span class="z-entity z-name z-section z-markdown">Horizontal navigation</span><span class="z-meta z-whitespace z-newline z-markdown">
</span></span></span></span><span class="z-text z-html z-markdown">
</span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">Is based on first level of headings
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown">#</span>
</span><span class="z-text z-html z-markdown">
</span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">You don't have to have any title at all
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown"><span class="z-markup z-heading z-1 z-markdown"><span class="z-punctuation z-definition z-heading z-begin z-markdown">#</span> </span><span class="z-markup z-heading z-1 z-markdown"><span class="z-entity z-name z-section z-markdown">Partial reveal</span><span class="z-meta z-whitespace z-newline z-markdown">
</span></span></span></span><span class="z-text z-html z-markdown">
</span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">One other way to show content
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">. . .
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">is delaying it
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown"><span class="z-markup z-heading z-1 z-markdown"><span class="z-punctuation z-definition z-heading z-begin z-markdown">#</span> </span><span class="z-markup z-heading z-1 z-markdown"><span class="z-entity z-name z-section z-markdown">Pictures</span><span class="z-meta z-whitespace z-newline z-markdown">
</span></span></span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"><span class="z-meta z-image z-inline z-markdown"><span class="z-punctuation z-definition z-image z-begin z-markdown">![</span></span><span class="z-meta z-image z-inline z-markdown"><span class="z-punctuation z-definition z-image z-end z-markdown">]</span></span><span class="z-meta z-image z-inline z-markdown"><span class="z-punctuation z-definition z-metadata z-begin z-markdown">(</span></span><span class="z-meta z-image z-inline z-markdown"><span class="z-markup z-underline z-link z-image z-markdown">/android-chrome-512x512.png</span><span class="z-punctuation z-definition z-metadata z-end z-markdown">)</span></span>
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown"><span class="z-markup z-heading z-1 z-markdown"><span class="z-punctuation z-definition z-heading z-begin z-markdown">#</span> </span><span class="z-markup z-heading z-1 z-markdown"><span class="z-entity z-name z-section z-markdown">Full screen pictures {data-background-image="/android-chrome-512x512.png"}</span><span class="z-meta z-whitespace z-newline z-markdown">
</span></span></span></span><span class="z-text z-html z-markdown">
</span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">are just backgrounds
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown"><span class="z-markup z-heading z-1 z-markdown"><span class="z-punctuation z-definition z-heading z-begin z-markdown">#</span> </span><span class="z-markup z-heading z-1 z-markdown"><span class="z-entity z-name z-section z-markdown">Full screen videos {data-background-video="/video_file.webm"}</span><span class="z-meta z-whitespace z-newline z-markdown">
</span></span></span></span><span class="z-text z-html z-markdown">
</span><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown"><span class="z-markup z-heading z-1 z-markdown"><span class="z-punctuation z-definition z-heading z-begin z-markdown">#</span> </span><span class="z-markup z-heading z-1 z-markdown"><span class="z-entity z-name z-section z-markdown">Tables</span><span class="z-meta z-whitespace z-newline z-markdown">
</span></span></span></span><span class="z-text z-html z-markdown">
</span><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown"><span class="z-meta z-table z-header z-markdown-gfm"><span class="z-punctuation z-separator z-table-cell z-markdown">|</span> Regular <span class="z-punctuation z-separator z-table-cell z-markdown">|</span> Markdown <span class="z-punctuation z-separator z-table-cell z-markdown">|</span>
</span></span></span><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown"><span class="z-meta z-table z-header-separator z-markdown-gfm"><span class="z-punctuation z-separator z-table-cell z-markdown">|</span><span class="z-punctuation z-section z-table-header z-markdown">---------</span><span class="z-punctuation z-separator z-table-cell z-markdown">|</span><span class="z-punctuation z-section z-table-header z-markdown">----------</span><span class="z-punctuation z-separator z-table-cell z-markdown">|</span>
</span><span class="z-meta z-table z-markdown-gfm"></span></span></span><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown"><span class="z-meta z-table z-markdown-gfm"><span class="z-punctuation z-separator z-table-cell z-markdown">|</span> Tables <span class="z-punctuation z-separator z-table-cell z-markdown">|</span> Work <span class="z-punctuation z-separator z-table-cell z-markdown">|</span>
</span></span></span><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown"><span class="z-meta z-table z-markdown-gfm"></span></span>
</span><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown"><span class="z-markup z-heading z-1 z-markdown"><span class="z-punctuation z-definition z-heading z-begin z-markdown">#</span> </span><span class="z-markup z-heading z-1 z-markdown"><span class="z-entity z-name z-section z-markdown">Notes</span><span class="z-meta z-whitespace z-newline z-markdown">
</span></span></span></span><span class="z-text z-html z-markdown">
</span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">Are only visible in the "Speaker view"
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">::: notes
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">And not rendered in the presentation
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">:::
</span></span></code></pre>
<p>The only thing that is missing right now is the video file.</p>
<h2 id="tools-let-s-build-it">🛠 Let's build it</h2>
<p>In a terminal, in the same directory with the demo file <code>index.md</code>:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">pandoc</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>t</span> revealjs<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> --</span>slide-level</span> 2<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>s</span><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>o</span> index.html index.md</span>
</span></code></pre>
<ul>
<li><code>-t FORMAT</code> is short for <code>--to FORMAT</code> which sets a destination format to convert to</li>
<li><code>--slide-level NUMBER</code> enables vertical navigation for level 2 headings</li>
<li><code>-s</code> is short for <code>--standalone[=true|false]</code> and it helps to produce a valid HTML with <code><head></code> and <code><body></code> instead of a document fragment</li>
<li><code>-o FILE</code> specifies a file path to put the result of conversion to</li>
<li><code>index.md</code> is a positional argument and specifies a document we want to convert</li>
</ul>
<p>Now open <code>index.html</code> and enjoy your very own presentation 🎉</p>
<h2 id="keyboard-hot-keys">⌨️ Hot keys</h2>
<p>Click the Demo above and try the following:</p>
<ul>
<li><code>B</code> fades the presentation to black. Useful when you need to talk</li>
<li><code>S</code> opens the speaker view. An external window with the next slide preview, notes, and a timer</li>
<li><code>F</code> full screen</li>
<li><code>Escape</code> opens a navigation view</li>
<li><code>Alt+click</code> to Zoom in (<code>Ctrl+click</code> <em>for linux</em>).</li>
</ul>
<p>These keys should work out of the gate when you open a presentation but the Demo is embedded in another page, so it needs to be focused.
You're doing exactly that by clicking it - focusing the presentation.</p>
<h2 id="repeat-live-preview">🔁 Live preview</h2>
<p>The command we've used before runs once, produces a result and stops.
That cool when you know exactly what your presentation should look like from very beginning.
Otherwise, it's tedious.</p>
<p>Don't you worry there is a way to update your presentation live just as you edit it.
To achieve this I present you two new dependencies you have to install:</p>
<ul>
<li><a href="https://github.com/inotify-tools/inotify-tools/wiki">inotifywait</a> to run pandoc on updates to the source file (when the change is saved)</li>
<li><a href="http://tapiov.net/live-server/">live-server</a> to update presentation in a browser when a new presentation file is generated</li>
</ul>
<p>Here is a very simple script to run it all:</p>
<p><code>serve.sh</code></p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">!/usr/bin/env bash</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function z-shell"><span class="z-entity z-name z-function z-shell">build_presentation</span><span class="z-punctuation z-section z-parens z-begin z-shell">(</span><span class="z-punctuation z-section z-parens z-end z-shell">)</span> <span class="z-punctuation z-section z-braces z-begin z-shell">{</span>
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function z-shell"> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">pandoc</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>t</span> revealjs<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> --</span>slide-level</span> 2<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>s</span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">FILE_TO_WATCH</span></span><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>o</span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">OUTPUT_HTML</span></span></span>
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function z-shell"><span class="z-punctuation z-section z-braces z-end z-shell">}</span></span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">FILE_TO_WATCH</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>./index.md<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">OUTPUT_HTML</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>index.html<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">build_presentation</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">live-server</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> --</span>watch</span><span class="z-keyword z-operator z-assignment z-option z-shell">=</span><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-punctuation z-section z-expansion z-parameter z-begin z-shell">{</span></span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-variable z-other z-readwrite z-shell">OUTPUT_HTML</span></span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-section z-expansion z-parameter z-end z-shell">}</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span> <span class="z-keyword z-operator z-logical z-job z-shell">&</span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">LIVE_SERVER_PID</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-language z-shell">!</span></span></span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function z-shell"><span class="z-storage z-type z-function z-shell">function</span> <span class="z-entity z-name z-function z-shell">stop_live_server</span> <span class="z-punctuation z-section z-braces z-begin z-shell">{</span>
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function z-shell"> <span class="z-meta z-function-call z-shell"><span class="z-support z-function z-echo z-shell">echo</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>Stopping live-server<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function z-shell"> <span class="z-meta z-function-call z-shell"><span class="z-support z-function z-kill z-shell">kill</span></span><span class="z-meta z-function-call z-arguments z-shell"> -9 <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">LIVE_SERVER_PID</span></span></span>
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function z-shell"><span class="z-punctuation z-section z-braces z-end z-shell">}</span></span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-support z-function z-trap z-shell">trap</span></span><span class="z-meta z-function-call z-arguments z-shell"> stop_live_server EXIT</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-keyword z-control z-loop z-while z-shell">while</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">true</span></span><span class="z-keyword z-operator z-logical z-continue z-shell">;</span> <span class="z-keyword z-control z-loop z-do z-shell">do</span>
</span><span class="z-source z-shell z-bash"> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">inotifywait</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>e</span> modify <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">FILE_TO_WATCH</span></span></span>
</span><span class="z-source z-shell z-bash"> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">build_presentation</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-keyword z-control z-loop z-end z-shell">done</span>
</span></code></pre>
<p>Don't forget to make it executable and run <code>./serve.sh</code></p>
<p><code>trap</code> is used to stop <code>live-server</code> when the script is stopped.</p>
<h2 id="package-shipping-it">📦 Shipping it</h2>
<p>After performing a great presentation it's common to share the slides.
There are two ways.</p>
<p>By default, HTML produced by pandoc doesn't include local assets it links to.
To make things a bit easier, there is an option to include all the assets into a single HTML file.
Add <code>--embed-resources</code> to the pandoc build command:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">pandoc</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>t</span> revealjs<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> --</span>slide-level</span> 2<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>s</span><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> --</span>embed-resources</span><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>o</span> index.html index.md</span>
</span></code></pre>
<p>The second way is to export a presentation to a PDF file in a browser.
reveal.js supports converting slides into printable pages.
The result doesn't usually look good, so I'd recommend sticking with the first option.</p>
<h2 id="flight-departure-offline-builds">🛫 Offline builds</h2>
<p>HTML file produced by pandoc uses an on-line version of reveal.js.
Which can be a problem when you're planning to go offline.
It's possible to override reveal.js location and even point it to a local directory.
The only two things left to do it to get (<code>git clone</code>) <a href="https://github.com/hakimel/reveal.js">reveal.js</a>.
Then point pandoc to it by adding <code>-M revealjs-url="path/to/reveal.js"</code> to the build command.</p>
<h2 id="link-showing-off-external-sites">🔗 Showing off external sites</h2>
<p>reveal.js allows to use <code><iframe></code> which make embedding sites as slides possible.
That's really neat!
Unfortunately, not all sites like to be embedded this way into someone's presentation.
What if you're going to say something bad about it ...</p>
<p>Well, we're going to say something anyway!
Here's an Atlassian page that talks dirty about gitflow being a legacy workflow - <a href="https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow">https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow</a>.</p>
<p>Straight forward way to embed it would be:</p>
<pre data-lang="markdown" class="language-markdown z-code"><code class="language-markdown" data-lang="markdown"><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown"><span class="z-markup z-heading z-2 z-markdown"><span class="z-punctuation z-definition z-heading z-begin z-markdown">##</span> </span><span class="z-markup z-heading z-2 z-markdown"><span class="z-entity z-name z-section z-markdown">{data-background-iframe="<span class="z-markup z-underline z-link z-markdown-gfm">https://www.atlassian.com</span><span class="z-markup z-underline z-link z-markdown-gfm">/git/tutorials/comparing-workflows/gitflow-workflow"}</span></span><span class="z-meta z-whitespace z-newline z-markdown">
</span></span></span></span></code></pre>
<p>It looks like this</p>
<figure id="presentation-demo">
<iframe
src="https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow"
title="Atlassian site refusing to be embedded">
</iframe>
</figure>
<p>and has the following error in the browser console <code>Refused to frame 'https://www.atlassian.com/' because an ancestor violates the following Content Security Policy directive: "frame-ancestors 'none'".</code></p>
<p>Well, that's quite unfortunate. We can tell our browser to ignore but it's kind of magic that I don't possess.
We can use a reverse proxy to strip all the restrictions from the original page.
One more dependency - <a href="https://caddyserver.com/">Caddy</a>.
It supports 1-liners to do awesome stuff straight in a command line without a configuration file.
I was not able to accomplish our goal in one line so here goes a <code>Caddyfile</code>:</p>
<pre data-lang="caddyfile" class="language-caddyfile z-code"><code class="language-caddyfile" data-lang="caddyfile"><span class="z-text z-plain">{
</span><span class="z-text z-plain"> http_port 2080
</span><span class="z-text z-plain">}
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">localhost:2080 {
</span><span class="z-text z-plain"> route {
</span><span class="z-text z-plain"> header -Content-Security-Policy
</span><span class="z-text z-plain"> header -X-Frame-Options
</span><span class="z-text z-plain"> reverse_proxy https://www.atlassian.com {
</span><span class="z-text z-plain"> header_up Host {http.reverse_proxy.upstream.hostport}
</span><span class="z-text z-plain"> }
</span><span class="z-text z-plain"> }
</span><span class="z-text z-plain">}
</span></code></pre>
<p>Back to the presentation.
Now we need to change <code>https://www.atlassian.com</code> to <code>http://localhost:2080</code> and it should be a part of our presentation (with an annoying cookie overlay).</p>
<pre data-lang="markdown" class="language-markdown z-code"><code class="language-markdown" data-lang="markdown"><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown"><span class="z-markup z-heading z-2 z-markdown"><span class="z-punctuation z-definition z-heading z-begin z-markdown">##</span> </span><span class="z-markup z-heading z-2 z-markdown"><span class="z-entity z-name z-section z-markdown">{data-background-iframe="http://localhost:2080/git/tutorials/comparing-workflows/gitflow-workflow"}</span><span class="z-meta z-whitespace z-newline z-markdown">
</span></span></span></span></code></pre>
<h2 id="nix">❄️ Nix</h2>
<p>For fellow <a href="https://nixos.org/">Nix</a> users, there is a small <a href="https://nixos.wiki/wiki/Flakes">flake</a> file to install dependencies <code>flake.nix</code>:</p>
<pre data-lang="nix" class="language-nix z-code"><code class="language-nix" data-lang="nix"><span class="z-source z-nix"><span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">description</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>A minimal flake for presentation made with markdown, pandoc and reveal.js<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">inputs</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">nixpkgs</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">url</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-double z-nix"><span class="z-punctuation z-definition z-string z-double z-start z-nix">"</span>github:NixOS/nixpkgs/nixos-unstable<span class="z-punctuation z-definition z-string z-double z-end z-nix">"</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">outputs</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-entity z-function z-2 z-nix">{</span> <span class="z-variable z-parameter z-function z-1 z-nix">self</span><span class="z-keyword z-operator z-nix">,</span> <span class="z-variable z-parameter z-function z-1 z-nix">nixpkgs</span><span class="z-keyword z-operator z-nix">,</span> <span class="z-keyword z-operator z-nix">... </span><span class="z-punctuation z-definition z-entity z-function z-nix">}</span><span class="z-punctuation z-definition z-function z-nix">:</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">devShell</span>.<span class="z-entity z-other z-attribute-name z-multipart z-nix">x86_64-linux</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-keyword z-other z-nix">with</span> <span class="z-variable z-parameter z-name z-nix">nixpkgs</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">legacyPackages</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">x86_64-linux</span>; <span class="z-variable z-parameter z-name z-nix">mkShell</span> <span class="z-punctuation z-definition z-attrset-or-function z-nix">{</span>
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">buildInputs</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-punctuation z-definition z-list z-nix">[</span>
</span><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">pandoc</span>
</span><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">inotify-tools</span>
</span><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">nodejs</span>
</span><span class="z-source z-nix"> <span class="z-variable z-parameter z-name z-nix">nodePackages</span><span class="z-keyword z-operator z-nix">.</span><span class="z-variable z-parameter z-name z-nix">live-server</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-list z-nix">]</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix">
</span><span class="z-source z-nix"> <span class="z-entity z-other z-attribute-name z-multipart z-nix">shellHook</span> <span class="z-keyword z-operator z-bind z-nix">=</span> <span class="z-string z-quoted z-other z-nix"><span class="z-punctuation z-definition z-string z-other z-start z-nix">''</span>
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> cat << EOF
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> Update a FILE_TO_WATCH variable in serve.sh
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> Run ./serve.sh
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> Enjoy auto-updating presentation
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> EOF
</span></span><span class="z-source z-nix"><span class="z-string z-quoted z-other z-nix"> <span class="z-punctuation z-definition z-string z-other z-end z-nix">''</span></span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"> <span class="z-punctuation z-definition z-attrset z-nix">}</span><span class="z-punctuation z-terminator z-bind z-nix">;</span>
</span><span class="z-source z-nix"><span class="z-punctuation z-definition z-attrset z-nix">}</span>
</span></code></pre>
<p>The following command gets you into the environment with all the dependencies:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">nix</span></span><span class="z-meta z-function-call z-arguments z-shell"> develop</span>
</span></code></pre>
<p>It should greet you with the following message:</p>
<pre class="z-code"><code><span class="z-text z-plain">Update a FILE_TO_WATCH variable in serve.sh
</span><span class="z-text z-plain">Run ./serve.sh
</span><span class="z-text z-plain">Enjoy auto-updating presentation
</span></code></pre>
<p>To start a live editing setup:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">./serve</span></span>
</span></code></pre>
<p>Even better - just one command:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">nix</span></span><span class="z-meta z-function-call z-arguments z-shell"> develop<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>c</span> ./serve.sh</span>
</span></code></pre>
NixOS hidden packages2023-10-18T00:00:00+00:002023-10-18T00:00:00+00:00
Unknown
https://ejiek.id/blog/2023/nixos-hidden-packages/<p>Recently I've decided to try out <a href="https://astro.build/">Astro</a> as a superb static site generator with support for new and shiny features like <a href="https://astro.build/blog/astro-3/#astro-view-transitions">View Transitions</a>.
Astro has its own file format <code>.astro</code> and Astro's team is so cool that they <a href="https://github.com/withastro/language-tools/tree/main/packages/language-server">provide</a> a <a href="https://en.wikipedia.org/wiki/Language_Server_Protocol">Language Server</a> for it.</p>
<h2 id="search-party">Search party</h2>
<p>As someone knee-deep in the NixOS ecosystem, the first thing I did was search for the Astro Language Server on <a href="https://search.nixos.org/packages?channel=unstable&from=0&size=50&sort=relevance&type=packages&query=astro">NixOS Search</a>.
However, to my surprise, it was nowhere to be found! I tried my luck with terminal commands <code>nix search astro</code> and <code>nix-env -qaP astro</code>, but alas, nothing popped up.</p>
<p>It's probably not packaged!
Not a big deal.
So, off I went to the <a href="https://github.com/NixOS/nixpkgs">nixpkgs repo</a> to package it up myself.
And guess what?
It was there all along!</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">@astrojs/language-server</span></span>
</span></code></pre>
<p>I found it chilling in <code>pkgs/development/node-packages/node-packages.nix</code>, ready for some action.
But why on earth did it take me so long to find it?
Why was it so elusive in the first place?
Was the search index taking a stroll?
Nope, the language server had been packaged for months, so that couldn’t be it.</p>
<h2 id="why-is-it-missing">Why is it missing?!</h2>
<p>There are <code>@</code> and <code>/</code> in the name.
Well, that's unusual!
It reminded me of a similar hiccup I had with the tailwind language server on a previous NixOS adventure.
<a href="https://github.com/NixOS/nixpkgs/issues/248413">nixpkgs issue: Package request: @tailwindcss/language-server</a>.
It's a package request for something that was already packaged.
I'm not the only one to miss a Node package in this search conundrum.</p>
<p>The search seems to be broken.
There are no packages that have <code>@</code> in their names.
I went through the Astro Language Server file of origin:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">cat</span></span><span class="z-meta z-function-call z-arguments z-shell"> pkgs/development/node-packages/node-packages.json</span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">grep</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>@<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span>
</span><span class="z-source z-shell z-bash"> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@angular/cli<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@antfu/ni<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@astrojs/language-server<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@babel/cli<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@commitlint/cli<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@commitlint/config-conventional<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@microsoft/rush<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@shopify/cli<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@tailwindcss/aspect-ratio<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@tailwindcss/forms<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@tailwindcss/language-server<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@tailwindcss/line-clamp<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@tailwindcss/typography<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@uppy/companion<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@volar/vue-language-server<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@vue/cli<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-meta z-group z-expansion z-brace z-shell"><span class="z-punctuation z-section z-expansion z-brace z-begin z-shell">{</span><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@webassemblyjs/cli<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>: <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>1.11.1<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span><span class="z-punctuation z-section z-expansion z-brace z-end z-shell">}</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-meta z-group z-expansion z-brace z-shell"><span class="z-punctuation z-section z-expansion z-brace z-begin z-shell">{</span><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@webassemblyjs/repl<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>: <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>1.11.1<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span><span class="z-punctuation z-section z-expansion z-brace z-end z-shell">}</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@webassemblyjs/wasm-strip<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-meta z-group z-expansion z-brace z-shell"><span class="z-punctuation z-section z-expansion z-brace z-begin z-shell">{</span><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@webassemblyjs/wasm-text-gen<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>: <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>1.11.1<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span><span class="z-punctuation z-section z-expansion z-brace z-end z-shell">}</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-meta z-group z-expansion z-brace z-shell"><span class="z-punctuation z-section z-expansion z-brace z-begin z-shell">{</span><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@webassemblyjs/wast-refmt<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>: <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>1.11.1<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span><span class="z-punctuation z-section z-expansion z-brace z-end z-shell">}</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@electron-forge/cli<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@gitbeaker/cli<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@prisma/language-server<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@withgraphite/graphite-cli<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@yaegassy/coc-nginx<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">,</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>@zwave-js/server<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span></code></pre>
<p>At least 27 packages are not shown in the search.
Doesn't look too user-friendly, right?</p>
<h2 id="rescue-party">Rescue party</h2>
<p>I would like to say that the NixOS community is here to save the day.
There is a <a href="https://github.com/NixOS/nixos-search/issues/293">nixos-search issue</a> discussing this exact problem.
Unfortunately, it's from March 2021 and stale for more than two years.
A little crawling from this issue brings me to a "good package name" regex <code>[A-Za-z_][A-Za-z0-9-_+]*</code>.
So there might be even more non-searchable packages.
I've successfully searched for packages that don't fit this regex, though 🤷🏻♂</p>
<h2 id="nix-is-blue-heart">Nix is 💙</h2>
<p>I have very warm feelings for NixOS, and I want to see it soar to new heights.
I'm passionate about Nix, NixOS and what's possible with them.
It is my daily driver on two laptops, a desktop and even SteamDeck.
There are more than 80 000 searchable packages which put NixOS in front of many other if not all Linux distributions.</p>
<h2 id="road-ahead">Road ahead</h2>
<p>I've been using NixOS for over a year now and I still feel like an idiot almost every time I need to configure any of my systems.
It feels unnecessarily complicated.
Broken search, lacking documentation, and being left with the freedom to implement things however you like make it not a welcoming environment.
I want NixOS to be more welcoming.
I want it to be as accessible as possible.</p>
<p>It's just the beginning of my NixOS chronicles.
My best bud is hopping on this bandwagon too, and together, we aim to dive deep into the NixOS waters.
We're already getting into Pull Requests, Issues, and documentation.</p>
<p>While I'm still not competent enough to fix NixOS issues myself, I'm going to be vocal about any roadblocks I stumble upon.
I hope to become a useful and helpful member of the Nix community one day.
I encourage you to join and make NixOS an even better place together!
Participating in opened issues from this blog post is a good start.</p>
<p>Stay tuned, more NixOS adventures incoming =]</p>
I was not ready to become a consultant2023-09-04T00:00:00+00:002023-09-04T00:00:00+00:00
Unknown
https://ejiek.id/blog/2023/failedconsultant/<p>Throughout my IT career, I've been through many roles, the majority of which were leadership positions such as Lead Developer, Team Leader, Architect, Platform Owner, and Chief Technical Officer.
None of them were my first choice.
I'm passionate about developing software myself.
I prefer to code. And just a tiny bit of hardware tinkering.</p>
<p>When I was first offered a CTO role, I refused.
I was more excited by a Pencil (a soldering iron by Pine64 with support for Power Delivery, RISC-V Processor, and open-source firmware) than by the idea of leading an IT department.</p>
<blockquote>
<p>I refused</p>
</blockquote>
<p>This was my first “No” in taking more responsibility!
I should be proud of myself!</p>
<p>At that time, I was leading a Platform Team and held the title of a Platform Owner.
It meant that I was in charge of infrastructure and developer experience.
I was not communicating much with the current CTO which was fine since I was busy solving “my” problems.
There were many moving pieces reaching out far from the Platform Team to the IT department and the company in general.
My team was mastering Pulumi for Infrastructure as actual Code and Kubernetes management while making it easy for developers.
Developers were getting familiar with new responsibilities like managing their infrastructure, pipelines, and documentation right in their repository.
Observability was getting to a point where it was actually usable by every developer.
The software development life cycle was changing to accommodate trunk-based development.
I was not just in the middle of it.
I was actively steering the whole project in those directions.
Bringing practices and tools to support them, organizing processes, and advocating change.
Sitting down with every team to figure out ways to thrive together.</p>
<p>Doesn't sound like a lot of coding, right? Yeah, you're right! Accepting the CTO position, then, shouldn't have been a big leap.
The only thing I had to sacrifice was just a little bit of coding I had left.
Two days and one difficult conversation later, I accepted the new role.</p>
<p>But why?</p>
<p>I have to accept that I love solving problems.
Software-related ones in particular.
However, software challenges don't exist in isolation.
So I would like to go back to the beginning of my IT career.</p>
<p>I began as a developer. First working with C++ for a sophisticated machine that detects problems in huuuuuuge transformers via oil quality monitoring.
Then it was signal processing, also with C++.
After that, I got involved in creating a social network with Liferay, so Java it was.
These were my first experiences as a developer and in each of these roles, I did more than just code.
I was trying to bring git to the first place to replace sharing files and archives.
Acted a as scrum master and was responsible for bringing Doxygen, automation (mostly makefiles), and Jenkins at the second one.
At the third one, I became responsible for our infrastructure.
All of it while still being a developer and not being asked to do any of it.</p>
<p>These were issues that bothered me as a developer, so I went on an adventure to solve them before returning to pure coding—or so I thought.</p>
<p>Turns out I have little tolerance for unnecessary inconveniences.
Norman doors are a wonderful example.
While it's difficult to change doors in a building you don't own, it's possible to change things in a company you're working at or at least in a project.
This is what I've been doing for the past 10 years.
Changing things.
Not just code or even architecture.
Things that may be way out of my original scope.
The product itself, processes, and communications internal or external.</p>
<p>For me, it boils down to key points:</p>
<ul>
<li>I care</li>
<li>I strive to see the bigger picture</li>
</ul>
<p>So, what's wrong with being a consultant?</p>
<p>I thought I had enough experience in "seeing the whole picture" to get into a company, get familiar with how it works, and after taking a deep look give useful pieces of advice.
Not just a summary of problems but also instructions on how to solve them in this particular case.
That part turned out to be relatively easy.
I've been there before.</p>
<p>The "I care" part got me in trouble.
In all of my experience, I had at least some authority to enforce changes.
I always was there with the rest of the team to lead the change and support them.
But as a consultant, I'm just an outsider that has something to say.
There is no way to act on my own.
No way to lead the way.
All I can do is to show the way.
It might be enough in case change is awaited and welcomed.
I had wonderful experiences consulting individuals.
Not so much with a company.
Individuals had particular problems while the company was not even sure they had one.
Maybe consulting a company requires better persuasive skills.
For the first three months, I was stuck in a situation where many improvements could be made but there was no movement towards them.
I felt helpless.</p>
<p>I came to a company to help, not to get some money for talking.
The hard truth is that a consultant just offers a solution it's on the company to act on it.
My problem-solving skills are stopped right before jumping into action.
The spring is already loaded, ready to catapult me into making changes.
Instead, I'm left with my hands tied to watch.
It hurts when nothing happens.
I care.</p>
<p>I consider my first experience as a consultant a failure.
After a lot of difficult discussions with CTO & CEO, I agreed to become CTO myself.
Which I still am at the time of writing this post.
Actively changing things.</p>
Abusing CSS :before pseudo-element2023-03-18T00:00:00+00:002023-03-18T00:00:00+00:00
Unknown
https://ejiek.id/blog/2023/abusingcssbefore/<h2 id="tl-dr">TL;DR</h2>
<p>I've encountered a <code>:before</code> element in CSS for a switch/slider that made no sense.
There was nothing "before" the switch.
Now I understand what it was used for, but I don't like the particular use case.
As a bonus - I got a dark/light theme toggle for this site.</p>
<h2 id="the-story">The story</h2>
<p>Having not been hands-on with web development since 2019, I recently decided to implement a dark/light theme switch for my website (this one, <a href="https://ejiek.id">ejiek.id</a>).
I wanted the switch to look aesthetically pleasing, meaning a simple checkbox wouldn't cut it.
A quick search for a CSS switch led me to a <a href="https://www.w3schools.com/howto/howto_css_switch.asp">w3schools example</a>:</p>
<figure id="w3schools-example">
<iframe
src="./w3schools.html"
style="width: 140px;
height: 88px;">
</iframe>
<figcaption>Toggle them ☝️</figcaption>
</figure>
<p>Looks good, works well.
However, there are some questions to the implementation.
In this post, I go over the problems, possible solutions, and improvements.</p>
<p>Let's start analyzing the HTML part:</p>
<pre data-lang="html" class="language-html z-code"><code class="language-html" data-lang="html"><span class="z-text z-html z-basic"><span class="z-meta z-tag z-other z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span></span><span class="z-meta z-tag z-other z-html"><span class="z-entity z-name z-tag z-other z-html">label class="switch"</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-other z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span></span><span class="z-meta z-tag z-other z-html"><span class="z-entity z-name z-tag z-other z-html">input type="checkbox"</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-other z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span></span><span class="z-meta z-tag z-other z-html"><span class="z-entity z-name z-tag z-other z-html">span class="slider"</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span><span class="z-meta z-tag z-inline z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"></</span><span class="z-entity z-name z-tag z-inline z-any z-html">span</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"><span class="z-meta z-tag z-inline z-form z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"></</span><span class="z-entity z-name z-tag z-inline z-form z-html">label</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span></code></pre>
<p>Each slider has only two visual elements: a switch body and a knob.
However, there are three HTML elements describing it:</p>
<ul>
<li><code>label</code> holds everything inside and provides click handling for the input</li>
<li><code>checkbox</code> stores the on/off state and is hidden</li>
<li><code>span</code> creates the visual representation of ⚠️ <strong>both</strong> the switch and its knob.</li>
</ul>
<p>We need to stylize and dynamically alter both the slider body and the knob.
While <code>span</code> works well for the slider body, there is nothing left for the knob.
<em>Aha!</em>
This is where the :before pseudo-element comes into play.</p>
<p><code>:before</code> is a pseudo-element intended to define a visual element that appears before its parent element.
Its counterpart, <code>:after</code>, works similarly.
The MDN documentation provides <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::before">a good example</a> of how <code>:before</code> is meant to be used - decorating a link.</p>
<p>The following CSS makes all your links go from <a href="https://ejiek.id">ejiek.id</a> to <a href="https://ejiek.id" class="link-with-chain-symbol-infront">ejiek.id</a></p>
<style>
.link-with-chain-symbol-infront::before {
content: '🔗';
}
</style>
<pre data-lang="css" class="language-css z-code"><code class="language-css" data-lang="css"><span class="z-source z-css"><span class="z-meta z-selector z-css"><span class="z-entity z-name z-tag z-css">a</span><span class="z-entity z-other z-pseudo-element z-css"><span class="z-punctuation z-definition z-entity z-css">::</span>before</span> </span><span class="z-meta z-property-list z-css"><span class="z-punctuation z-section z-property-list z-css">{</span>
</span></span><span class="z-source z-css"><span class="z-meta z-property-list z-css"> <span class="z-meta z-property-name z-css"><span class="z-support z-type z-property-name z-css">content</span></span><span class="z-punctuation z-separator z-key-value z-css">:</span><span class="z-meta z-property-value z-css"> </span><span class="z-meta z-property-value z-css"><span class="z-string z-quoted z-single z-css"><span class="z-punctuation z-definition z-string z-begin z-css">'</span>🔗<span class="z-punctuation z-definition z-string z-end z-css">'</span></span></span><span class="z-punctuation z-terminator z-rule z-css">;</span>
</span></span><span class="z-source z-css"><span class="z-meta z-property-list z-css"></span><span class="z-punctuation z-section z-property-list z-css">}</span>
</span></code></pre>
<p>I can understand this <code>:before</code> use case.
It's easy to reason about - <code>:before</code> is actually before.
This visual enhancement doesn't require any modification to the source (HTML).
Easy!
The slider solution, on the other hand, requires extra HTML anyway, and <code>:before</code> is semantically broken.</p>
<p>Going back to the slider, a straightforward approach would be to create two meaningful HTML elements and manipulate them:</p>
<pre data-lang="html" class="language-html z-code"><code class="language-html" data-lang="html"><span class="z-text z-html z-basic"><span class="z-meta z-tag z-other z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span></span><span class="z-meta z-tag z-other z-html"><span class="z-entity z-name z-tag z-other z-html">span class="slider"</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-inline z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span><span class="z-entity z-name z-tag z-inline z-any z-html">span</span> <span class="z-meta z-attribute-with-value z-class z-html"><span class="z-entity z-other z-attribute-name z-class z-html">class</span><span class="z-punctuation z-separator z-key-value z-html">=</span><span class="z-string z-quoted z-double z-html"><span class="z-punctuation z-definition z-string z-begin z-html">"</span></span><span class="z-string z-quoted z-double z-html"><span class="z-meta z-class-name z-html">knob</span><span class="z-punctuation z-definition z-string z-end z-html">"</span></span></span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span><span class="z-meta z-tag z-inline z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"></</span><span class="z-entity z-name z-tag z-inline z-any z-html">span</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"><span class="z-meta z-tag z-inline z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"></</span><span class="z-entity z-name z-tag z-inline z-any z-html">span</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span></code></pre>
<p>I've seen a knob positioned next to the slider, but I find the nesting variant more representative of the final result.
Hence, easier to understand and maintain.
This restructure eliminates one of the perks of using <code>:before</code>.
In the original version, the knob in an "on" position is defined like this:</p>
<pre data-lang="css" class="language-css z-code"><code class="language-css" data-lang="css"><span class="z-source z-css"><span class="z-meta z-selector z-css"><span class="z-entity z-name z-tag z-css">input</span><span class="z-entity z-other z-pseudo-class z-css"><span class="z-punctuation z-definition z-entity z-css">:</span>checked</span> <span class="z-punctuation z-separator z-combinator z-css">+</span> <span class="z-entity z-other z-attribute-name z-class z-css"><span class="z-punctuation z-definition z-entity z-css">.</span>slider</span><span class="z-entity z-other z-pseudo-element z-css"><span class="z-punctuation z-definition z-entity z-css">:</span>before</span> </span><span class="z-meta z-property-list z-css"><span class="z-punctuation z-section z-property-list z-css">{</span>
</span></span><span class="z-source z-css"><span class="z-meta z-property-list z-css"> <span class="z-comment z-block z-css"><span class="z-punctuation z-definition z-comment z-css">/*</span> some knob manipulation <span class="z-punctuation z-definition z-comment z-css">*/</span></span>
</span></span><span class="z-source z-css"><span class="z-meta z-property-list z-css"></span><span class="z-punctuation z-section z-property-list z-css">}</span>
</span></code></pre>
<p>It uses <code>+</code> (<a href="https://www.w3.org/TR/selectors-3/#adjacent-sibling-combinators">adjacent sibling combinator</a>) which works for two elements that are right next to each other.
Both <code>slider</code> and <code>slider:before</code> are tied to the same HTML element, so they have the same neighbors!
Our new knob with a separate HTML element isn't next to a checkbox because it's hidden in a slider span.
So the following doesn't work:</p>
<div class="content local-code-block-with-stop-sign">
<pre data-lang="css" class="language-css z-code"><code class="language-css" data-lang="css"><span class="z-source z-css"><span class="z-meta z-selector z-css"><span class="z-entity z-name z-tag z-css">input</span><span class="z-entity z-other z-pseudo-class z-css"><span class="z-punctuation z-definition z-entity z-css">:</span>checked</span> <span class="z-punctuation z-separator z-combinator z-css">+</span> <span class="z-entity z-other z-attribute-name z-class z-css"><span class="z-punctuation z-definition z-entity z-css">.</span>knob</span> </span><span class="z-meta z-property-list z-css"><span class="z-punctuation z-section z-property-list z-css">{</span>
</span></span><span class="z-source z-css"><span class="z-meta z-property-list z-css"> <span class="z-comment z-block z-css"><span class="z-punctuation z-definition z-comment z-css">/*</span> some knob manipulation <span class="z-punctuation z-definition z-comment z-css">*/</span></span>
</span></span><span class="z-source z-css"><span class="z-meta z-property-list z-css"></span><span class="z-punctuation z-section z-property-list z-css">}</span>
</span></code></pre>
</div>
<style>
.local-code-block-with-stop-sign {
position: relative;
}
.local-code-block-with-stop-sign > pre {
position: relative;
z-index: 1;
}
.local-code-block-with-stop-sign::after {
content: "⛔";
position: absolute;
z-index: 2;
top: 0.3em;
right: 0.3em;
font-size: 1em;
}
</style>
<p>Fortunately, it's easy to fix because the parent of the knob is still an <code>input</code> neighbor.
We just need to specify it:</p>
<pre data-lang="css" class="language-css z-code"><code class="language-css" data-lang="css"><span class="z-source z-css"><span class="z-meta z-selector z-css"><span class="z-entity z-name z-tag z-css">input</span><span class="z-entity z-other z-pseudo-class z-css"><span class="z-punctuation z-definition z-entity z-css">:</span>checked</span> <span class="z-punctuation z-separator z-combinator z-css">+</span> <span class="z-entity z-other z-attribute-name z-class z-css"><span class="z-punctuation z-definition z-entity z-css">.</span>slide</span> <span class="z-entity z-other z-attribute-name z-class z-css"><span class="z-punctuation z-definition z-entity z-css">.</span>knob</span> </span><span class="z-meta z-property-list z-css"><span class="z-punctuation z-section z-property-list z-css">{</span>
</span></span><span class="z-source z-css"><span class="z-meta z-property-list z-css"> <span class="z-comment z-block z-css"><span class="z-punctuation z-definition z-comment z-css">/*</span> some knob manipulation <span class="z-punctuation z-definition z-comment z-css">*/</span></span>
</span></span><span class="z-source z-css"><span class="z-meta z-property-list z-css"></span><span class="z-punctuation z-section z-property-list z-css">}</span>
</span></code></pre>
<p>We can be less specific about a parent:</p>
<pre data-lang="css" class="language-css z-code"><code class="language-css" data-lang="css"><span class="z-source z-css"><span class="z-meta z-selector z-css"><span class="z-entity z-name z-tag z-css">input</span><span class="z-entity z-other z-pseudo-class z-css"><span class="z-punctuation z-definition z-entity z-css">:</span>checked</span> <span class="z-punctuation z-separator z-combinator z-css">+</span> <span class="z-entity z-name z-tag z-wildcard z-css">*</span> <span class="z-entity z-other z-attribute-name z-class z-css"><span class="z-punctuation z-definition z-entity z-css">.</span>knob</span> </span><span class="z-meta z-property-list z-css"><span class="z-punctuation z-section z-property-list z-css">{</span>
</span></span><span class="z-source z-css"><span class="z-meta z-property-list z-css"> <span class="z-comment z-block z-css"><span class="z-punctuation z-definition z-comment z-css">/*</span> some knob manipulation <span class="z-punctuation z-definition z-comment z-css">*/</span></span>
</span></span><span class="z-source z-css"><span class="z-meta z-property-list z-css"></span><span class="z-punctuation z-section z-property-list z-css">}</span>
</span></code></pre>
<p>Version with a separate knob element requires more HTML and just a tiny bit more complex CSS but to my eye, it's much more readable and maintainable because it's easier to reason about.
The knob is a knob, the slider body is a slider body.
No mysterious <code>:before</code> for an element that's not actually before.</p>
<h2 id="wait-there-is-more">Wait, there is more!</h2>
<p>A modern dark/light theme switch is more complex than a binary decision.
How so?
There is a system-wide preference.
I want to have an option to respect that.
That means that a checkbox no longer suits our needs because our choice is no longer binary.
Light | System | Dark.
Three options.
Now we're talking!
There are several ways to tackle this problem.
We can style an existing HTML slider to look and behave like a switch or draw the switch like before but with another way to store the value.</p>
<h3 id="html-slider">HTML slider</h3>
<p>Set a min value to 0, a max value to 2, style it, aaaaaaand it's working just like a slider...</p>
<figure>
<iframe
src="./slider.html"
style="width: 226px;
height: 40px;">
</iframe>
<figcaption>Slide them ↔️</figcaption>
</figure>
<p>There are a lot of problems with a slider though:</p>
<ul>
<li>non-standard pseudo-elements for the knob/thumb (<code>::-webkit-slider-thumb</code>, <code>::-moz-range-thumb</code>, ...)</li>
<li>it's made with an idea that a knob is bigger than the track, so it's hard to style</li>
<li>CSS knows nothing about a range value / requires JS</li>
<li>even though many binary switches look like sliders they often behave like a button & user expects to press/click, not to drag</li>
</ul>
<p>It's a fan solution but far from practical, so I'm scratching it.</p>
<h3 id="radio-buttons">Radio buttons</h3>
<p>I don't know any reasonable way to make them look and behave like a 3 state switch.</p>
<figure>
<input type="radio" name="switch" class="local-switch-0">
<input type="radio" name="switch" class="local-switch-1" checked>
<input type="radio" name="switch" class="local-switch-2">
<figcaption class="figcaption">Tick <span>☝️</span> it</figcaption>
</figure>
<style>
.local-switch-0:checked ~ figcaption::before {
content: '☝️';
}
.local-switch-2:checked ~ figcaption::after {
content: '☝️';
}
.local-switch-0:checked ~ figcaption span,
.local-switch-2:checked ~ figcaption span {
display: none;
}
</style>
<p>I do know how to make them store values in HTML in a way accessible by css.
Demo above works exactly that way.
Plus, I know how to hide them & draw my own switch.
Let's start by defining 3 radio buttons:</p>
<pre data-lang="html" class="language-html z-code"><code class="language-html" data-lang="html"><span class="z-text z-html z-basic"><span class="z-meta z-tag z-other z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span></span><span class="z-meta z-tag z-other z-html"><span class="z-entity z-name z-tag z-other z-html">div class="switch"</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-other z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span></span><span class="z-meta z-tag z-other z-html"><span class="z-entity z-name z-tag z-other z-html">input type="radio"</span> <span class="z-meta z-attribute-with-value z-html"><span class="z-entity z-other z-attribute-name z-html">name</span><span class="z-punctuation z-separator z-key-value z-html">=</span><span class="z-string z-quoted z-double z-html"><span class="z-punctuation z-definition z-string z-begin z-html">"</span>switch<span class="z-punctuation z-definition z-string z-end z-html">"</span></span></span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-other z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span></span><span class="z-meta z-tag z-other z-html"><span class="z-entity z-name z-tag z-other z-html">input type="radio"</span> <span class="z-meta z-attribute-with-value z-html"><span class="z-entity z-other z-attribute-name z-html">name</span><span class="z-punctuation z-separator z-key-value z-html">=</span><span class="z-string z-quoted z-double z-html"><span class="z-punctuation z-definition z-string z-begin z-html">"</span>switch<span class="z-punctuation z-definition z-string z-end z-html">"</span></span></span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-other z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span></span><span class="z-meta z-tag z-other z-html"><span class="z-entity z-name z-tag z-other z-html">input type="radio"</span> <span class="z-meta z-attribute-with-value z-html"><span class="z-entity z-other z-attribute-name z-html">name</span><span class="z-punctuation z-separator z-key-value z-html">=</span><span class="z-string z-quoted z-double z-html"><span class="z-punctuation z-definition z-string z-begin z-html">"</span>switch<span class="z-punctuation z-definition z-string z-end z-html">"</span></span></span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-other z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span></span><span class="z-meta z-tag z-other z-html"><span class="z-entity z-name z-tag z-other z-html">span class="slider"</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-inline z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span><span class="z-entity z-name z-tag z-inline z-any z-html">span</span> <span class="z-meta z-attribute-with-value z-class z-html"><span class="z-entity z-other z-attribute-name z-class z-html">class</span><span class="z-punctuation z-separator z-key-value z-html">=</span><span class="z-string z-quoted z-double z-html"><span class="z-punctuation z-definition z-string z-begin z-html">"</span></span><span class="z-string z-quoted z-double z-html"><span class="z-meta z-class-name z-html">knob</span><span class="z-punctuation z-definition z-string z-end z-html">"</span></span></span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span><span class="z-meta z-tag z-inline z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"></</span><span class="z-entity z-name z-tag z-inline z-any z-html">span</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-inline z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"></</span><span class="z-entity z-name z-tag z-inline z-any z-html">span</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"><span class="z-meta z-tag z-block z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"></</span><span class="z-entity z-name z-tag z-block z-any z-html">div</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span></code></pre>
<p>The way radio buttons are interacting with CSS is a bit different from the way a checkbox does it.
We need to know which input is checked right now.
CSS has <code>nth-child</code> pseudo-class that can help us identify a radio button:</p>
<pre data-lang="css" class="language-css z-code"><code class="language-css" data-lang="css"><span class="z-source z-css"><span class="z-meta z-selector z-css"><span class="z-entity z-name z-tag z-css">input</span><span class="z-meta z-function-call z-css"><span class="z-entity z-other z-pseudo-class z-css"><span class="z-punctuation z-definition z-entity z-css">:</span>nth-child</span><span class="z-meta z-group z-css"><span class="z-punctuation z-definition z-group z-begin z-css">(</span><span class="z-constant z-numeric z-integer z-decimal z-css">3</span></span><span class="z-meta z-group z-css"><span class="z-punctuation z-definition z-group z-end z-css">)</span></span></span><span class="z-entity z-other z-pseudo-class z-css"><span class="z-punctuation z-definition z-entity z-css">:</span>checked</span> <span class="z-punctuation z-separator z-combinator z-css">+</span> <span class="z-entity z-other z-attribute-name z-class z-css"><span class="z-punctuation z-definition z-entity z-css">.</span>slider</span> </span><span class="z-meta z-property-list z-css"><span class="z-punctuation z-section z-property-list z-css">{</span>
</span></span><span class="z-source z-css"><span class="z-meta z-property-list z-css"> <span class="z-comment z-block z-css"><span class="z-punctuation z-definition z-comment z-css">/*</span> some slider manipulation <span class="z-punctuation z-definition z-comment z-css">*/</span></span>
</span></span><span class="z-source z-css"><span class="z-meta z-property-list z-css"></span><span class="z-punctuation z-section z-property-list z-css">}</span>
</span></code></pre>
<p>This option works and is quite universal but is a bit hard to maintain.
We are tied to a particular order of elements in HTML.
Another option would be to give each radio button an id or a unique class.
I prefer the unique class way over id because it allows having multiple switches.
But then again if you have multiple switches isn't it easier to use <code>nth-child()</code>.
I suggest choosing one method and trying to stick with it.</p>
<p>Another problem comes from the fact that we have multiple inputs.
It means that the first and second inputs are no longer neighbors of the slider span.
Not a big deal.
Let's change <code>+</code> combinator to a <code>~</code> (<a href="https://www.w3.org/TR/selectors-3/#general-sibling-combinators">general sibling combinator</a>).
This one looks for a neighbor under the roof of the same parent in a document tree and allows to have other elements in between.</p>
<pre data-lang="css" class="language-css z-code"><code class="language-css" data-lang="css"><span class="z-source z-css"><span class="z-meta z-selector z-css"><span class="z-entity z-other z-attribute-name z-class z-css"><span class="z-punctuation z-definition z-entity z-css">.</span>radio-dark</span><span class="z-entity z-other z-pseudo-class z-css"><span class="z-punctuation z-definition z-entity z-css">:</span>checked</span> <span class="z-punctuation z-separator z-combinator z-css">~</span> <span class="z-entity z-other z-attribute-name z-class z-css"><span class="z-punctuation z-definition z-entity z-css">.</span>slider</span> </span><span class="z-meta z-property-list z-css"><span class="z-punctuation z-section z-property-list z-css">{</span>
</span></span><span class="z-source z-css"><span class="z-meta z-property-list z-css"> <span class="z-comment z-block z-css"><span class="z-punctuation z-definition z-comment z-css">/*</span> some slider manipulation <span class="z-punctuation z-definition z-comment z-css">*/</span></span>
</span></span><span class="z-source z-css"><span class="z-meta z-property-list z-css"></span><span class="z-punctuation z-section z-property-list z-css">}</span>
</span></code></pre>
<p><code>+</code> still works for the last radio button, but I prefer consistency.
So I'm using <code>~</code> for all the inputs.</p>
<figure>
<iframe
src="./radio-based.html"
style="width: 220px;
height: 40px;">
</iframe>
<figcaption>Toggle them ☝️</figcaption>
</figure>
<p>You may have noticed, that when I was defining radio buttons earlier, I switched <code><label></code> for a <code><div></code>.
In <a href="https://ejiek.id/blog/2023/abusingcssbefore/#the-story">w3schools example</a> <code><label></code> was useful, it handled clicks by changing the state of a checkbox.
With 3 radio buttons, it always chooses the first one.
I want the click to choose the next option so that a user can easily cycle through available options.
There is no way to do it in pure HTML and CSS.
Let's use JS to add a click handler to each slider and use it to cycle through radio buttons:</p>
<pre data-lang="js" class="language-js z-code"><code class="language-js" data-lang="js"><span class="z-source z-ts"> <span class="z-meta z-var z-expr z-ts"><span class="z-storage z-type z-ts">const</span> <span class="z-meta z-var-single-variable z-expr z-ts"><span class="z-meta z-definition z-variable z-ts"><span class="z-variable z-other z-constant z-ts">switches</span></span> </span><span class="z-keyword z-operator z-assignment z-ts">=</span> <span class="z-meta z-function-call z-ts"><span class="z-support z-variable z-dom z-ts">document</span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-support z-function z-dom z-ts">querySelectorAll</span></span><span class="z-meta z-brace z-round z-ts">(</span><span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>.switch<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span><span class="z-meta z-brace z-round z-ts">)</span></span><span class="z-punctuation z-terminator z-statement z-ts">;</span>
</span><span class="z-source z-ts"> <span class="z-meta z-function-call z-ts"><span class="z-variable z-other z-object z-ts">switches</span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-support z-function z-dom z-ts">forEach</span></span><span class="z-meta z-brace z-round z-ts">(</span> <span class="z-meta z-arrow z-ts"><span class="z-variable z-parameter z-ts">switchEl</span> </span><span class="z-meta z-arrow z-ts"><span class="z-storage z-type z-function z-arrow z-ts">=></span> <span class="z-meta z-block z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span>
</span></span></span><span class="z-source z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"> <span class="z-meta z-function-call z-ts"><span class="z-variable z-other z-object z-ts">switchEl</span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-support z-function z-dom z-ts">querySelector</span></span><span class="z-meta z-brace z-round z-ts">(</span><span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>.slider<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span><span class="z-meta z-brace z-round z-ts">)</span><span class="z-meta z-function-call z-ts"><span class="z-punctuation z-accessor z-ts">.</span><span class="z-support z-function z-dom z-ts">addEventListener</span></span><span class="z-meta z-brace z-round z-ts">(</span><span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>click<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span><span class="z-meta z-arrow z-ts"> <span class="z-meta z-parameters z-ts"><span class="z-punctuation z-definition z-parameters z-begin z-ts">(</span><span class="z-variable z-parameter z-ts">e</span><span class="z-punctuation z-definition z-parameters z-end z-ts">)</span></span> </span><span class="z-meta z-arrow z-ts"><span class="z-storage z-type z-function z-arrow z-ts">=></span> <span class="z-meta z-block z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"> <span class="z-meta z-var z-expr z-ts"><span class="z-storage z-type z-ts">const</span> <span class="z-meta z-var-single-variable z-expr z-ts"><span class="z-meta z-definition z-variable z-ts"><span class="z-variable z-other z-constant z-ts">radios</span></span> </span><span class="z-keyword z-operator z-assignment z-ts">=</span> <span class="z-meta z-function-call z-ts"><span class="z-variable z-other z-object z-ts">switchEl</span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-support z-function z-dom z-ts">querySelectorAll</span></span><span class="z-meta z-brace z-round z-ts">(</span><span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>input[type="radio"]<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span><span class="z-meta z-brace z-round z-ts">)</span></span><span class="z-punctuation z-terminator z-statement z-ts">;</span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"> <span class="z-keyword z-control z-conditional z-ts">if</span> <span class="z-meta z-brace z-round z-ts">(</span><span class="z-variable z-other z-readwrite z-ts">radios</span><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-brace z-square z-ts">[</span><span class="z-constant z-numeric z-decimal z-ts">0</span><span class="z-meta z-brace z-square z-ts">]</span></span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-support z-variable z-property z-dom z-ts">checked</span><span class="z-meta z-brace z-round z-ts">)</span> <span class="z-meta z-block z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-block z-ts"> <span class="z-variable z-other z-readwrite z-ts">radios</span><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-brace z-square z-ts">[</span><span class="z-constant z-numeric z-decimal z-ts">1</span><span class="z-meta z-brace z-square z-ts">]</span></span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-support z-variable z-property z-dom z-ts">checked</span> <span class="z-keyword z-operator z-assignment z-ts">=</span> <span class="z-constant z-language z-boolean z-true z-ts">true</span><span class="z-punctuation z-terminator z-statement z-ts">;</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-block z-ts"> <span class="z-punctuation z-definition z-block z-ts">}</span></span> <span class="z-keyword z-control z-conditional z-ts">else</span> <span class="z-keyword z-control z-conditional z-ts">if</span> <span class="z-meta z-brace z-round z-ts">(</span><span class="z-variable z-other z-readwrite z-ts">radios</span><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-brace z-square z-ts">[</span><span class="z-constant z-numeric z-decimal z-ts">1</span><span class="z-meta z-brace z-square z-ts">]</span></span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-support z-variable z-property z-dom z-ts">checked</span><span class="z-meta z-brace z-round z-ts">)</span> <span class="z-meta z-block z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-block z-ts"> <span class="z-variable z-other z-readwrite z-ts">radios</span><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-brace z-square z-ts">[</span><span class="z-constant z-numeric z-decimal z-ts">2</span><span class="z-meta z-brace z-square z-ts">]</span></span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-support z-variable z-property z-dom z-ts">checked</span> <span class="z-keyword z-operator z-assignment z-ts">=</span> <span class="z-constant z-language z-boolean z-true z-ts">true</span><span class="z-punctuation z-terminator z-statement z-ts">;</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-block z-ts"> <span class="z-punctuation z-definition z-block z-ts">}</span></span> <span class="z-keyword z-control z-conditional z-ts">else</span> <span class="z-keyword z-control z-conditional z-ts">if</span> <span class="z-meta z-brace z-round z-ts">(</span><span class="z-variable z-other z-readwrite z-ts">radios</span><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-brace z-square z-ts">[</span><span class="z-constant z-numeric z-decimal z-ts">2</span><span class="z-meta z-brace z-square z-ts">]</span></span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-support z-variable z-property z-dom z-ts">checked</span><span class="z-meta z-brace z-round z-ts">)</span> <span class="z-meta z-block z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-block z-ts"> <span class="z-variable z-other z-readwrite z-ts">radios</span><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-brace z-square z-ts">[</span><span class="z-constant z-numeric z-decimal z-ts">0</span><span class="z-meta z-brace z-square z-ts">]</span></span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-support z-variable z-property z-dom z-ts">checked</span> <span class="z-keyword z-operator z-assignment z-ts">=</span> <span class="z-constant z-language z-boolean z-true z-ts">true</span><span class="z-punctuation z-terminator z-statement z-ts">;</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-block z-ts"> <span class="z-punctuation z-definition z-block z-ts">}</span></span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"> <span class="z-punctuation z-definition z-block z-ts">}</span></span></span><span class="z-meta z-brace z-round z-ts">)</span><span class="z-punctuation z-terminator z-statement z-ts">;</span>
</span></span></span><span class="z-source z-ts"><span class="z-meta z-arrow z-ts"><span class="z-meta z-block z-ts"> <span class="z-punctuation z-definition z-block z-ts">}</span></span></span><span class="z-meta z-brace z-round z-ts">)</span><span class="z-punctuation z-terminator z-statement z-ts">;</span>
</span></code></pre>
<p>It works fine for a demo of a switch.
There is one more problem.
I've built all of my theme-switching logic on the state of the radio buttons.
To be more precise, I've added a <code>change</code> listener to radio buttons.
Changing <code>checked</code> status of a radio button from JS doesn't trigger a change event.
To get around this we can create an event manually.
Here's an example for one button:</p>
<pre data-lang="js" class="language-js z-code"><code class="language-js" data-lang="js"><span class="z-source z-ts"> <span class="z-variable z-other z-readwrite z-ts">radios</span><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-brace z-square z-ts">[</span><span class="z-constant z-numeric z-decimal z-ts">1</span><span class="z-meta z-brace z-square z-ts">]</span></span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-support z-variable z-property z-dom z-ts">checked</span> <span class="z-keyword z-operator z-assignment z-ts">=</span> <span class="z-constant z-language z-boolean z-true z-ts">true</span><span class="z-punctuation z-terminator z-statement z-ts">;</span>
</span><span class="z-source z-ts"> <span class="z-variable z-other z-readwrite z-ts">radios</span><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-brace z-square z-ts">[</span><span class="z-constant z-numeric z-decimal z-ts">1</span><span class="z-meta z-brace z-square z-ts">]</span></span><span class="z-meta z-function-call z-ts"><span class="z-punctuation z-accessor z-ts">.</span><span class="z-support z-function z-dom z-ts">dispatchEvent</span></span><span class="z-meta z-brace z-round z-ts">(</span><span class="z-new z-expr z-ts"><span class="z-keyword z-operator z-new z-ts">new</span> <span class="z-entity z-name z-type z-ts">Event</span><span class="z-meta z-brace z-round z-ts">(</span><span class="z-string z-quoted z-double z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">"</span>change<span class="z-punctuation z-definition z-string z-end z-ts">"</span></span><span class="z-meta z-brace z-round z-ts">)</span></span><span class="z-meta z-brace z-round z-ts">)</span><span class="z-punctuation z-terminator z-statement z-ts">;</span>
</span></code></pre>
<p>This is very close to the variant I've decided to stick with for some time.
The main difference is the middle state that represents the system preference.
I use a media query <code>prefers-color-scheme</code> to style the middle state accordingly to the system preference.
There is a chance you can see it in action in the <a href="#">header</a> or a sandwich menu there.
A significant downside of the current solution is the lack of keyboard support, but it's a problem for the future =]</p>
<h2 id="summary">Summary</h2>
<p>While <code>::before</code> and <code>::after</code> can be useful even when not used as intended, it's essential to consider more readable and maintainable alternatives.
They are two extra elements you can do whatever you want to.
They share neighbors with the parent element which can be very useful to simplify CSS.
The slider example already required changes to HTML, so there was no reason to stick with the pseudo-element.</p>
<hr />
<h2 id="ps-additional-discoveries"><em>PS. Additional discoveries</em></h2>
<h3 id="difference-between-before-before">Difference between :before ::before</h3>
<p>I learned from <a href="https://www.kevinpowell.co/">Kevin Powell</a> to use:</p>
<pre data-lang="css" class="language-css z-code"><code class="language-css" data-lang="css"><span class="z-source z-css"><span class="z-meta z-selector z-css"><span class="z-entity z-name z-tag z-wildcard z-css">*</span><span class="z-punctuation z-separator z-sequence z-css">,</span> <span class="z-entity z-name z-tag z-wildcard z-css">*</span><span class="z-entity z-other z-pseudo-element z-css"><span class="z-punctuation z-definition z-entity z-css">::</span>before</span><span class="z-punctuation z-separator z-sequence z-css">,</span> <span class="z-entity z-name z-tag z-wildcard z-css">*</span><span class="z-entity z-other z-pseudo-element z-css"><span class="z-punctuation z-definition z-entity z-css">::</span>after</span> </span><span class="z-meta z-property-list z-css"><span class="z-punctuation z-section z-property-list z-css">{</span>
</span></span><span class="z-source z-css"><span class="z-meta z-property-list z-css"> <span class="z-comment z-block z-css"><span class="z-punctuation z-definition z-comment z-css">/*</span> unimportant to the case <span class="z-punctuation z-definition z-comment z-css">*/</span></span>
</span></span><span class="z-source z-css"><span class="z-meta z-property-list z-css"></span><span class="z-punctuation z-section z-property-list z-css">}</span>
</span></code></pre>
<p>This is how I got the notion that pseudo-elements should be preceded by <code>::</code> which is css3 syntax for pseudo-elements.
While <code>:</code> should be used for pseudo-classes (like <code>:checked</code>).</p>
<p>However, using <code>::</code> is merely a suggestion and browsers still support <code>:</code> for pseudo-elements.
Plus there are old browsers that aren't aware of <code>::</code> but support <code>:</code> from css2 age.
Fortunately, we're pretty safe.
Most modern browsers have supported the <code>::</code> syntax for over ten years, so compatibility concerns are minimal.</p>
<h3 id="you-can-use-in-urls">You can use <code>:</code> in URLs</h3>
<p>One of the links in this post is <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::before">https://developer.mozilla.org/en-US/docs/Web/CSS/::before</a>.
It has <code>:</code> not as a protocol separator, but as a part of the path.
It's the first time I've noticed such a thing.</p>
<p>This reminds me of an article by Jan Schaumann - <a href="https://www.netmeister.org/blog/urls.html">URLs: It's complicated...</a></p>
Managing SSH config2021-11-04T00:00:00+00:002021-11-04T00:00:00+00:00
Unknown
https://ejiek.id/blog/2021/ssh-config/<p>OpenSSH is an awesome tool.
However, using it may seem tedious.
So many parameters to remember and type out.
Three jump hosts in one line?
I'm not sure it's even possible.</p>
<p>Not to worry!
We are going easy our interaction with the tool and tidy up its config.</p>
<h2 id="no-config-at-all">No config at all</h2>
<p>Let's say we have a wonderful server with an address <code>111.222.333.444</code> and a user <code>remote-user</code>.</p>
<p>To access we have to type a bulky command:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">ssh</span></span><span class="z-meta z-function-call z-arguments z-shell"> remote-user@111.222.333.444</span>
</span></code></pre>
<p>We can save some time by skipping <code>remote-user@</code> if our local user has exactly the same name.
We still have to remember an IP address which is somewhat doable with IPv4 but IPv6 is much more questionable.
It can be solved by a domain name but they are not always there.</p>
<p>There are other popular options like a private key, a non-standard port.</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">ssh</span></span><span class="z-meta z-function-call z-arguments z-shell"> remote-user@111.222.333.444<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>i</span> <span class="z-meta z-group z-expansion z-tilde"><span class="z-variable z-language z-tilde z-shell">~</span></span>/.ssh/wonderful_server<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>p</span> 54321</span>
</span></code></pre>
<p>Well, that's definitely a lot to remember.</p>
<h2 id="non-native-ssh-config">Non-native ssh config</h2>
<p>A power user of a shell may have a nice trick up their sleeve if it's the only server we have or one of a few.
Shell alias.</p>
<p>For zsh we can add the following alias to <code>~/.zshrc</code>:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-support z-function z-alias z-shell">alias</span> <span class="z-entity z-name z-function z-alias z-shell">ws</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>ssh remote-user@111.222.333.444 -i ~/.ssh/wonderful_server -p 54321<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span></span>
</span></code></pre>
<p>where <code>ws</code> stands for <strong>Wonderful Server</strong>.
It's probably the easiest way to offload it from the memory.
The result is the shortest one.</p>
<p>Unfortunately, it doesn't scale really well.</p>
<h2 id="native-ssh-config-mvp">Native ssh config MVP</h2>
<p>By default ssh looks for its config in <code>~/.ssh/config</code>.
Let's map a familiar command to a config file.</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">ssh</span></span><span class="z-meta z-function-call z-arguments z-shell"> remote-user@111.222.333.444<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>i</span> <span class="z-meta z-group z-expansion z-tilde"><span class="z-variable z-language z-tilde z-shell">~</span></span>/.ssh/wonderful_server<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>p</span> 54321</span>
</span></code></pre>
<p>should become</p>
<pre class="z-code"><code><span class="z-text z-plain">Host ws wonderful_server
</span><span class="z-text z-plain"> User remote-user
</span><span class="z-text z-plain"> HostName 111.222.333.444
</span><span class="z-text z-plain"> IdentityFile ~/.ssh/wonderful_server
</span><span class="z-text z-plain"> Port 54321
</span></code></pre>
<p>Now we can access it either by typing:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">ssh</span></span><span class="z-meta z-function-call z-arguments z-shell"> ws</span>
</span></code></pre>
<p>or</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">ssh</span></span><span class="z-meta z-function-call z-arguments z-shell"> wonderful_server</span>
</span></code></pre>
<p>Bot <code>ws</code> and <code>wonderful_server</code> are just local names (aliases) for us to call this server.
You can use as many aliases as you like (as I'm aware).</p>
<p>It's super useful with services like GitLab and GitHub.
I recommend having three aliases: <code>gh</code>, <code>github</code>, <code>github.com</code></p>
<p>The first one is for you to be able to make a quick <code>git clone</code>:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">git</span></span><span class="z-meta z-function-call z-arguments z-shell"> clone gh:ejiek/detox</span>
</span></code></pre>
<p>The second one is handy when you have multiple upstreams.</p>
<p>The third one is for tools that supports ssh git interaction but have no way to override a host.</p>
<h2 id="proxyjumps">ProxyJumps</h2>
<p>This is the first option that takes us beyond plain ssh commands in a console.
For quite some time OpenSSH natively support jumping through the hosts with forwarding traffic bach to the original client.
It's awesome because you can limit your keys and ssh agent just to a local machine.</p>
<p>The ability to jump through multiple hosts is limited to a config file.
It's not exactly true.
There is a flag <code>-J</code>.
Unfortunately, I had a lot of struggle specifying multiple jumphosts that are not in a config file when they have a lot of parameters.</p>
<p>In this example, we have 3 servers: <code>ws0</code>, <code>ws1</code>, <code>ws2</code>.
For some reason, maybe related to security, we can't access <code>ws1</code> and <code>ws2</code> directly.
<code>ws0</code> knows how to get to <code>ws1</code>.
<code>ws1</code> knows how to get to <code>ws2</code>.</p>
<p>Let's jump:</p>
<pre class="z-code"><code><span class="z-text z-plain">Host ws0 wonderful_server
</span><span class="z-text z-plain"> User remote-user
</span><span class="z-text z-plain"> HostName 111.222.333.444
</span><span class="z-text z-plain"> IdentityFile ~/.ssh/wonderful_server
</span><span class="z-text z-plain"> Port 54321
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">Host ws1 wonderful_server1
</span><span class="z-text z-plain"> User anothe-remote-user
</span><span class="z-text z-plain"> HostName 222.333.444.555
</span><span class="z-text z-plain"> IdentityFile ~/.ssh/wonderful_server
</span><span class="z-text z-plain"> Port 54321
</span><span class="z-text z-plain"> ProxyJump ws0
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">Host ws2 wonderful_server2
</span><span class="z-text z-plain"> User anothe-remote-user
</span><span class="z-text z-plain"> HostName 333.444.555.666
</span><span class="z-text z-plain"> IdentityFile ~/.ssh/wonderful_server
</span><span class="z-text z-plain"> Port 54321
</span><span class="z-text z-plain"> ProxyJump ws1
</span></code></pre>
<p>Connection to <code>ws2</code> would actually establish a connection to <code>ws0</code>, from there to <code>ws1</code> forwarding traffic back to you.
Only then it's going to connect to ws2 from ws1.</p>
<p>If you have a passphrase for your key and it's not in an ssh agent, you'll have to enter the passphrase three times.
One time for each server in a chain.</p>
<h2 id="includes">Includes</h2>
<p>There could be a lot of wonderful servers.
At some point, they are going to eat up a lot of space in the main file.
Fortunately, OpenSSH supports includes.</p>
<p>In my config I have the following <strong>before any actual host definitions</strong>:</p>
<pre class="z-code"><code><span class="z-text z-plain">Include config.d/*
</span></code></pre>
<p>Since the provided path is relative, it's going to look for <code>config.d</code> in <code>~/.ssh</code>.
Let's move all wonderful servers into a separate file <code>~/.ssh/conig.d/wonderful_servers</code></p>
<p>Be careful with placing the include directive.
It supports using inside <strong>Match</strong> and <strong>Host</strong>.</p>
<h3 id="vim-configuration-non-default-files">vim configuration non default files</h3>
<p>I had my config highlighting broken for all included files.
To fix it in Vim, it was enough to add one line:</p>
<pre data-lang="vim" class="language-vim z-code"><code class="language-vim" data-lang="vim"><span class="z-source z-viml"><span class="z-comment z-line z-quotes z-viml">" ssh config.d/* syntax highlighting</span>
</span><span class="z-source z-viml"><span class="z-support z-function z-viml">autocmd</span> <span class="z-support z-variable z-viml">BufRead</span>,<span class="z-support z-variable z-viml">BufNewFile</span> ~<span class="z-string z-regexp z-viml">/.ssh/</span>config<span class="z-storage z-function z-viml">.</span>d/<span class="z-storage z-function z-viml">*</span> <span class="z-support z-function z-viml">set</span> syntax=sshconfig
</span></code></pre>
<h2 id="wildcard">Wildcard</h2>
<p>All the wonderful servers look pretty much the same.
It's as bad with just a few of them.
With dozens - it's a disaster.</p>
<p>Let's shave things off wonderful servers configs:</p>
<pre class="z-code"><code><span class="z-text z-plain">Host ws0 wonderful_server
</span><span class="z-text z-plain"> User remote-user
</span><span class="z-text z-plain"> HostName 111.222.333.444
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">Host ws1 wonderful_server1
</span><span class="z-text z-plain"> HostName 222.333.444.555
</span><span class="z-text z-plain"> ProxyJump ws0
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">Host ws2 wonderful_server2
</span><span class="z-text z-plain"> HostName 333.444.555.666
</span><span class="z-text z-plain"> ProxyJump ws1
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">Host ws* wonderful_server*
</span><span class="z-text z-plain"> User anothe-remote-user
</span><span class="z-text z-plain"> IdentityFile ~/.ssh/wonderful_server
</span><span class="z-text z-plain"> Port 54321
</span></code></pre>
<p>A wildcard must go after hosts that use it.
OpenSSH keeps the first value for a given parameters.
Having a wildcard in front would block us from overriding values in a Host.
In this example we are overriding user for ws0.</p>
<p>Sometimes it's necessary to override ProxyJump not to jump at all.
It's possible with <code>ProxyJump none</code>.</p>
<h3 id="my-global-wildcards">My global wildcards</h3>
<p>There are two of them right in the beginning of my <code>~/.ssh/config</code>:</p>
<pre class="z-code"><code><span class="z-text z-plain">Host *
</span><span class="z-text z-plain"> ServerAliveInterval 240
</span><span class="z-text z-plain"> IdentitiesOnly yes
</span></code></pre>
<p>The first one keeps alive connections with NAT somewhere in the way.
When you idle for too long, a TCP session for your ssh connect isn't closed.
However, some firewalls can decide that enough is enough and just forget about it.
This option sends some packets once it 4 minutes just to keep NATs happy.
You may need to tune the time.</p>
<p><code>IdentityOnly</code> stop your OpenSSH client from going through all the keys you have, when the provided one isn't working.
Aside from not wasting time and effort, it helps not to reach the server's attempts limit (if there is one) and be sure that you're using a correct key.
It can be fun to remove a key you think you don't use.
Not that I know of =]</p>
<h2 id="to-the-infinity-and">To the infinity and ...</h2>
<p>OpenSSH knows many more cool tricks.
Port forwarding in both directions, socks5 proxy, and much more.
To find out more use <a href="https://linux.die.net/man/5/ssh_config">man 5 ssh_config</a>.</p>
macOS first impressions2021-07-27T00:00:00+00:002021-07-27T00:00:00+00:00
Unknown
https://ejiek.id/blog/2021/macos/<p>I've joined a brand new company and was provided with a laptop.
A MacBook Pro from 2017.
Nice 13" machine without a touch bar.
It means I have an escape button to switch keyboard layouts and CapsLock is free to be an Escape replacement.</p>
<p>Unfortunately, the keyboard layout is not as good as I'm used to.
There is no TrackPoint and a huge space bar instead of several buttons like on a Japanese keyboard for Lenovo x220.
I can deal with that.
Stacking window management is something I can't deal with.
After years and years of tiling, it just hurts.
Cuts deep every time I have to look for a window hiding somewhere under a pile of other windows.</p>
<p>MacOS is not that bad when you navigate it with a touchpad.
Many useful gestures to change workspaces and applications.
Since it looks live navigation seems to be OK, I've tried to fix the lack of tiling with <a href="https://ianyh.com/amethyst/">Amethyst</a>.
Boom! Tiling all over the place!
But complicated default modifiers have scared me off.</p>
<p>Turns out tiling is just a small part of my habits.
I need to spawn new terminals and browser instances with ease. </p>
<p>This is the time for <del>DOOM music</del> UNIX way to kick in.
There is a simple hotkey daemon <a href="https://github.com/koekeishiya/skhd">skhd</a>.
It doesn't know a thing about window management but is awesome at listening for hotkeys and calling external commands. </p>
<p>Then there is <a href="https://github.com/koekeishiya/yabai">yabai</a> a window manager that doesn't know a thing about intercepting keystrokes.
What is it good at?
Managing windows!
And listening for commands you can call straight from your terminal.
Or from <a href="https://github.com/koekeishiya/skhd">skhd</a>.</p>
<p>Are these two working good together?
Yeap, but...
You need to provide the glue o.O</p>
<p>There is a good <a href="https://github.com/koekeishiya/skhd">skhd</a> config example in <a href="https://github.com/koekeishiya/yabai">yabai</a> installation under:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">/usr/local/opt/yabai/share/yabai/examples/skhdrc</span></span>
</span></code></pre>
<p>Unfortunately, it's not ready for use, so it's just a good reference.
Good thing is - <a href="https://github.com/koekeishiya/yabai/wiki">yabai's wiki</a> is awesome.
<a href="https://github.com/koekeishiya/yabai/wiki/Commands">Commands section</a> provides enough examples to build yourself a powerful WM.
Feel free to check out my <a href="https://gitlab.com/ejiek/slashTheDot">dotfiles</a> (<a href="https://github.com/ejiek/dotfiles">mirror</a>) for an example.</p>
<p>Is the fight over with the window manager kicking with all the beloved hotkeys.
There are a lot of things figure out.
<a href="https://www.qutebrowser.org/">Qutebrowser</a> is <a href="https://github.com/qutebrowser/qutebrowser/issues/4067">a bit broken</a>.
I don't have a replacement for <a href="https://github.com/carnager/rofi-pass">rofi-pass</a>.
A lot of hotkeys should be disabled or reassigned by hand in macOS Preferences or on application basis.</p>
<p><a href="https://github.com/alacritty/alacritty">Alacritty</a> is a fine example of broken hotkeys.
<code>alt+.</code> to call previous last argument isn't working.
It works in iTerm because hack to make <code>alt</code> metakey work are embedded.
It's <a href="https://github.com/alacritty/alacritty">fixable</a> but definitely feels like one more unnecessary step to the comfort zone.</p>
<p>Nevertheless, it feels much more like home now.
There is even a free bonus - rounded corners!</p>
<p>One last thing for this post.
<a href="https://brew.sh/">Brew</a>.
It's pretty good.
Not Nix or ArchLinux with AUR good.
But pretty good.
Package availability is better than what I remember with Ubuntu-based distros.
macOS package management is not though.
I had to enable a whole bunch of packages in Security Settings.
Installations of <a href="https://github.com/koekeishiya/yabai">yabai</a> is a whole another topic.</p>
Pipewire is ready for me2021-05-16T00:00:00+00:002021-05-16T00:00:00+00:00
Unknown
https://ejiek.id/blog/2021/pipewire/<p>PulseAudio is a good audio solution for a common Linux user.
It's the default for many distros and there is a good reason why - it works.
Alsa works too, but PulseAudio is quite more flexible.
JACK is probably too complicated.
One other benefit of PulseAudio - it's well supported, I mean some programs use PulseAudio interface directly & often exclusively.</p>
<p>With this said, why bother with PipeWire?
Just for fun is my typical answer but not this time.
This time we have a headset problem.</p>
<p>It starts with a fact that bidirectional audio over Bluetooth sucks.
To participate in a conversation you have to have a minimal lag.
It's achieved through compromising audio quality.
There is a different problem.
Who in a right mind would buy an expensive wireless headset that sounds way worse than the cheapest wired ones.
To fix this more than one codec is used.
One for great quality and noticeable delay and another with awful quality and close to absent delay.
Problem is, PulseAudio can work well only with the first type.
There is no internal support for the second one and Bluetooth microphone at all.
So we have to improvise something with <code>ophono</code> or some other big project which covers much more than just audio.</p>
<p>Let's take a look at two popular PulseAudio configuration programs with plain PulseAudio.</p>
<p><strong>Pavucontrol GUI:</strong></p>
<p><img src="https://ejiek.id/blog/2021/pipewire/pulse-pavucontrol.png" alt="pulse pavucontrol screenshot" /></p>
<p><strong>PulseMixer TUI:</strong></p>
<p><img src="https://ejiek.id/blog/2021/pipewire/pulse-pulsemixer.png" alt="pulse pulsemixer screenshot" /></p>
<p>Here comes PipeWire!
Not only it's awesome on its own (it's almost a must-have if you want to share your Wayland screen), it has a build-in support for Bluetooth headsets.
A cherry on top is PipeWire being able to be a drop-in replacement for PulseAudio.</p>
<p>As I'm using ArchLinux here's a link to <a href="https://wiki.archlinux.org/title/PipeWire">ArchLinux Wiki PipeWire article</a>.
I'm going to provide some steps from my installation process to show the ease of the procedure but be aware, it's not a guide.</p>
<p>First, we want to get PipeWire and a part responsible for PulseAudio computability.</p>
<pre class="z-code"><code><span class="z-text z-plain">$ sudo pacman -S pipewire pipewire-pulse
</span></code></pre>
<p>There are conflicts with the current PulseAudio installation.
That's fine, we are ready to remove them:</p>
<pre class="z-code"><code><span class="z-text z-plain">:: pipewire-pulse and pulseaudio are in conflict. Remove pulseaudio? [y/N] y
</span><span class="z-text z-plain">:: pipewire-pulse and pulseaudio-bluetooth are in conflict. Remove pulseaudio-bluetooth? [y/N] y
</span></code></pre>
<p>After the installation is done, we do still have pulse server running because it's already loaded in the memory.
Easy, we just have to kill it.</p>
<pre class="z-code"><code><span class="z-text z-plain">killall pulseaudio
</span></code></pre>
<p>*Sad headset noises (disconnected) *</p>
<p>Now we just have to bring PipeWire up.
I'm running it a user, so a bit of systemd unit enabling in necessary:</p>
<pre class="z-code"><code><span class="z-text z-plain">systemctl --user enable --now pipewire.service pipewire-pulse.socket pipewire-pulse.service pipewire-media-session.service
</span></code></pre>
<p>* Happy headset noises (connected back again)*</p>
<p>It may be necessary to restart some applications to get familiar with new PulseAudio server imposter.
I'd recommend to relogin or just restart your system to be sure.</p>
<p>Now let's take a look at familiar PulseAudio configuration software.</p>
<p><strong>Pavucontrol GUI with PipeWire:</strong></p>
<p><img src="https://ejiek.id/blog/2021/pipewire/pipe-pavucontrol.png" alt="piprwire pavucontrol screenshot" /></p>
<p><strong>PulseMixer TUI with PipeWire:</strong></p>
<p><img src="https://ejiek.id/blog/2021/pipewire/pipe-pulsemixer.png" alt="pipewire pulsemixer screenshot" /></p>
<p>We have <code>A2DP</code> and <code>HSP/HFP</code> available now.
The latter one in new.
We can choose it and suddenly a new microphone is available and the audio with poor quality is blazing fast.</p>
<p>Awesome conversations to you!
Good luck & have fun!</p>
Protect the LAN2021-04-07T00:00:00+00:002021-04-07T00:00:00+00:00
Unknown
https://ejiek.id/blog/2021/protect-the-lan/<p>This post is a brainchild of two big topics: forced digital transformation and an increase of "smart" things in an average household.
Unfortunately, both of these topics have significant security-related issues.</p>
<p>Isolation has played the main role in moving a huge part of the population to work from home, a lot of PCs moved out of a controlled company network to a home network.
Are home networks any good?
They are usually much safer than public networks.
It means working from home is more secure than working from, let's say, Starbucks, right?
It's definitely harder for a bad actor to get to your home network, but it's not impossible.
And usually, it's much easier to get and stay undetected than in a corporate network.</p>
<p>Here comes the second topic, "smart" devices.
Nowadays it's almost obligatory for tech to be "smart" and I don't have a problem with it.
I'm a huge fan of <a href="https://www.home-assistant.io/">Home Assistant</a>, an open-source home automation system.
Opening curtains, heating the floor in a bathroom, vacuuming are all good tasks to be automated.
It's not as impressive on its own, but when there is a central system which can perform complex scenarios with a variety of automated task, it feels like living in the future.
Sounds good!
Not at all!
Many of these "smart" devices are not just incompatible with Home Assistant but incapable of being "smart" without an internet connection.
They just absolutely have to call home and dump a whole lot of data.</p>
<p>There are some benefits for communicating with a server somewhere there, on the internet.
Easy over-the-air updates.
It also allows you to access this device from anywhere as long as you have internet.
This is a cool feature, but not every device needs it and it's totally should not be the only way to programmatically access a device.
Besides those devices becoming suddenly "dumb" with servers going down (and they eventually will due to the end of a support cycle or just a company ceasing to exist) there are poor implementations.
We should not expect a company known for producing good washing machines to become an all-knowing IT corporation.
Somehow plenty of companies from small startups to ancient giants decide to DIY some unnecessary server-based solution.
Usually, it's bad and opens doors to bad actors who managed to get access to a "smart" device server or intercepted communications in-between.
Regular firewall and NAT are barely anything for these attack vectors.</p>
<p>What can we do?</p>
<p>The market of smart home appliances is rapidly growing so it's a perfect time to vote with your wallet.
Do your research and choose products that don't invade your privacy and demand internet access.
I have to give Apple some credit here because HomeKit looks decent in terms of security requirements you have to meet to become part of this ecosystem.
This is while they <a href="https://kiding.medium.com/macos-ocsp-telemetry-explainer-and-mitigation-9bc243928f4c">send back</a> every application start event you generate on MacOS.
The last thing I have to mention about HomeKit topic is <a href="https://homebridge.io/">Homebridge</a> - a way to connect many devices that don't support HomeKit.</p>
<p>Ok, choosing a product is fine, but it's about the future, what can we do with ones we own already?
<strong>Divide and conquer</strong>!
It's going to be the answer till the end of this article.</p>
<p><strong>Untrusted IoT devices?</strong> throw them into a separate VLAN.
Our goal here is to separate your precious workstation from compromised cattle.
You should be able to open a connection to an IoT network, but devices in it should not be able to initiate connections to you.
If there is no VLAN and firewall configuration in your router there is still a chance your router support guest WiFi.
It's also a viable option for this type of devices.</p>
<p>A separate WiFi network is also good when any of your smart devices use the SmartConfig protocol by Texas Instruments to connect to your WiFi.
It broadcasts your SSID and Pass Phrase on your exact channel through a side-channel leak.
And it's in plain text or with questionable encryption practices.
If someone is listening nearby, your network is compromised just by the act of connecting your brand new smart device.</p>
<p><strong>Users?</strong>
You might not be living alone, so there are other machines on the network.
Probably other users are not security experts, so there is a chance their machines are going to be compromised.
It's possible to give them a separate VLAN, but might be inconvenient.
So local <strong>firewall</strong> on your workstation is a must-have.</p>
<p>Now when we are protected from other users, time to protect ourselves from the most dangerous user - ourselves.
Separate work and personal machines are the most obvious way to protect your workplace.
Dualboot with full disk encryption is another good option (encryption stops one system from stealing from another, but does nothing to protect from corruption).
<a href="https://www.qubes-os.org/">Qubes OS</a> is an awesome way to use one PC and avoid all the reboot into another OS hustle.</p>
<p>It's more of a rant than a structured article and I'm sorry for that.
The point is <strong>please try not to be an entry point for an attacker into your company and your life</strong>.
Thanks!</p>
Cyberpunk 20772021-03-28T00:00:00+00:002021-03-28T00:00:00+00:00
Unknown
https://ejiek.id/blog/2021/cyperpunk/<p><img src="https://ejiek.id/blog/2021/cyperpunk/screenshot.webp" alt="Cyberpunk 2077 on Arch Linux (an image of V leaving Night City)" /></p>
<p>The 28th of March 2021 is a good day for Cyberpunk 2077 and Arch Linux.
It's the day Mesa 21 got from <code>testing</code> Arch Linux repositories into regular <code>extra</code>.
It means that everything we need to play is finally available without any extra hustle, if you are rocking an AMD GPU.
Mesa is something like the userspace part of a video card driver and it was the last piece of the puzzle.</p>
<p>Cyberpunk 2077 itself is a controversial topic.
I know some people who dislike and it's alright, tastes differ.
Especially with the game came out with a plethora of bugs and some mechanics unfinished or broken.
A lot of hyped expectations were broken.
It's not a GTA 5 in cyberpunk future, not a good shooter (especially at the beginning of the game).
We can go through many genres to figure out that Cyberpunk 2077 is not the best in there but it's not meant to be.
What I can't understand is the overall amount of hatred towards the game.
So I'd like to devote the next part of this post to some love towards this awesome adventure.</p>
<p>It's a game I've enjoyed since the release and up to the completion of the game(for two times).
Furthermore, I plead guilty to preordering it and excited to get DLCs.
Let's take a quick look at the reasons I like it so much.
There is a technique of creating an illusion of well thought out world:
you go into extreme details from the get-go, then you're free to go below average.
This works because first encounter matters and you can nail it.
Players, readers or viewers get so impressed with the amount of effort and love that you've put into the first part of your creation, that they assume that it's true for the whole thing.
It sometimes works but occasionally we can figure out the unfortunate truth.
<a href="https://en.wikipedia.org/wiki/Superliminal">Superliminal</a> is a good example.
The first part of the game keeps you entertained by regularly breaking expectations, sneaking jokes (smile cursor, mini soda) and puzzles.
Closer to the end it changes.
Partly it's motivated by the main character getting into a darker place but, as I see it, the game just got considerably less interesting.
I've struggled to finish it.
With all of this said, Superliminal is an interesting game that's definitely worth a try.
I just regret that it's not awesome all the way through.</p>
<p>Back to the main hero of this post - Cyberpunk 2077.
It's an awesome blend of many different things but the reason why this game is soooo good - all of those things are made with love and care.
The amount of details is beyond anything I've expected.
A great deal of them is easy to miss because they are not the "in you face" type.
Some are just there in case you've decided to turn your head away from the main action.
Some hidden deep underwater.
Some you just accidentally eavesdrop.
And the list can go on and on.</p>
<p>The whole playable world is tightly connected with materials you can read.
Cyberpunk 2077 is full of this stuff.
Messages on your phone, articles and notes on shards, messages on other NPC computers.
Drama, humor and just Night City, all can be found there.</p>
<p>Interactions with NPCs aren't meaningless.
It's questionable, how much they impact the story in general, but they definitely feel like genuine human relationships.
For example, if you mention the death of a close friend to Panam, she'll raise a toast for this person much later in the game, when an opportunity comes.
It hits home hard because by that point both of them are in your heart.</p>
<p>All of this sums up into a believable story, characters and the world.
Isn't it the most important part of a Role Play game?</p>
<p>I have no regrets of playing two weeks straight and more later.
It's not perfect, but it's most definitely a masterpiece.</p>
<p>Thanks to youtuber <strong>CROWNED</strong> we can check out how beautiful it can be:
</div>
</div>
</section>
<figure class="image is-215by9">
<iframe class="has-ratio" src="https://www.youtube.com/embed/Vv110C3olIk" frameborder="0" allowfullscreen></iframe>
</figure>
<section class="section">
<div class="container">
<div class="content is-max-desktop">
</p>
<h2 id="relationship-with-linux">Relationship with Linux</h2>
<p>Cyberpunk is available on PC and Stadia.
Looks somewhat great!
Unfortunately, PC means Windows in this case and while Stadia is running Linux under the hood, there is no native Linux build publicly available.
There are reasons for it.
Linux is not only an ever-moving target, there is no well knows target for developers to follow.
Every distribution is different and it's beautiful!
Stadia on the other hand provides a very particular set of libraries and the rest of the underlying stuff.
So here we are with a Linux build only up there, in the clouds.</p>
<p>How bad is it?
It's rather good, actually!
Turns out Proton is a good target for developers.
More so if developers are aware of it.</p>
<p>Cyberpunk 2077 was not possible with Proton and Mesa we had.
Usually, it means that after release community has to step up and figure out, what's wrong and fix it on our side.
This time things were different!
On release day we had a <a href="https://github.com/ValveSoftware/Proton/releases/tag/proton-5.13-4">new version of Proton</a> (thanks to Andrew Eikum from <a href="https://www.codeweavers.com/">CodeWeavers</a>) and all necessary changes in the development branch of Mesa.
It means release day Linux support for such a huge title!
Not the easiest one, but it was there.
For the platform CDPR doesn't support.
It's a contribution well outside their responsibility even if it's not code directly.</p>
<p>The fact of going this far and doing so before release shows how much the team cares about this project.
Now we can easily enjoy it in Steam on Arch Linux.
As a cherry on the top, Cyberpunk 2077 recognises my DualShock 4 and renders relevant UI.</p>
<h2 id="photo-mode">Photo mode</h2>
<p>Photo mode in this game is awesome, but it's a bit tricky to find your screenshots in Linux version.
Since we are running the Windows version of the game, it thinks in Windows terms.
By default, it saves screenshots in <code>C:/Users/%user%/Pictures/Cyberpunk 2077/</code>.
All we have to do is find where it is in our setup.
Unfortunately, it's not <code>~/Pictures/Cyberpunk 2077</code>, because there is no mapping of disk <code>C</code> used by proton to your home directory.
Let's find this <code>C</code> disk.
I'm using a version of Proton provided by Steam and there is a special place for proton files inside Steam file tree.
For me it's:</p>
<pre class="z-code"><code><span class="z-text z-plain">~/.local/share/Steam/steamapps/compatdata/1091500/pfx/drive_c/users/steamuser/My\ Pictures/Cyberpunk\ 2077/`
</span></code></pre>
<p>The game ID, a number between <code>comatdata</code> and <code>pfx</code>, may be different for you.
To be sure that it's the correct directory create some screenshots prior to the search.</p>
<p>We can make working with screenshots a bit by creating a symlink, that leads the game to our desired location for screenshots.
First, we need to save existing screenshots in some other location, then create a destination directory & delete the one used by the game.
I've achieved this by a simple move:
<code>mv ~/.local/share/Steam/steamapps/compatdata/1091500/pfx/drive_c/users/steamuser/My\ Pictures/Cyberpunk\ 2077 ~/Pictures/ </code></p>
<p>The only thing left is to create a symlink:
<code>ln -s ~/Pictures/Cyberpunk\ 2077 ~/.local/share/Steam/steamapps/compatdata/1091500/pfx/drive_c/users/steamuser/My\ Pictures/Cyberpunk\ 2077</code></p>
<p>From now on your in-game masterpieces are going to be conveniently stored in a comfort of a home directory.</p>
Pi4 SSD failure2021-03-23T00:00:00+00:002021-03-23T00:00:00+00:00
Unknown
https://ejiek.id/blog/2021/pi4-ssd-failure/<p>Several Raspberry Pi's occupy my place and I have this urge to use them in a proper way.
Problem is, I'm not sure what this "proper" means.</p>
<p>There is a Pi3 B+ with an encrypted root file system and dropbear in the initial ramdisk to remotely unlock it.
Is it any good?
It runs Arch Linux which is nice.
Somewhat secure.
Runs of an SD card.
Somethings around 120 Gigs, has "High Endurance" in its name.
Still an SD card though.</p>
<p>I was so satisfied when this setup came out working.
It could have been much easier with DNS working in <code>arch-chroot</code> on my laptop.
Without it, I had to create an image from Arch Linux running on Pi itself.
Quite interesting exercise!
First, you create a small regular Arch Linux Arm installation on rather big boot partition & small root one.
From there you create and install encrypted root to the rest of an SD card.
The boot partition is reused for the new system and the old root becomes swap.</p>
<p>Small, silent and somewhat secure server.
Somewhere around 200 Mbit/s which is more than a typical household connection.
<strong>Neat!</strong></p>
<p>Then Pi4 B got into my hands.
With cool metal case:</p>
<p><img src="https://ejiek.id/blog/2021/pi4-ssd-failure/pi4-case.webp" alt="Pi4 metal case" /></p>
<p>Several times I've failed miserably with Arch Linux ARM setup.
At first, I've tried to port Pi3 setup by just plugging this exact SD card.
But there is a catch.
AArch64 version names SD card differently: <code>mmcblk1</code> instead of <code>mmcblk0</code>.
It's easy to change in fstab, but why just change when you can make it universal?!
UUID is the way.</p>
<p>With proper fstab it was at least booting but had no USB support.
No keyboard, no networks.
Ouch!
Fresh <code>.dtb</code> helped a little.
No luck getting into the system though.
Fresh installation of 64bit Arch Linux ARM rendered itself useless after first update.</p>
<p>Plain Ubuntu setup was working well though.
Unfortunately, it's not the setup I was going for.</p>
<p>Yesterday I decided to go back to this issue with an external SATA 2.5" case with an SSD in it.
Raspberry OS is doing something but not booting.
Ubuntu resizes its partition, no fully loaded OS yet.
Arch is stuck in a boot loop.
In a blink of an error screen I've managed to read about failure in voltage communication.
At least some sort of clue!</p>
<p>I'll buy official power supply because Power Delivery one doesn't know Pis special sauce.
Which is quite sad!
In case the power supply wouldn't be enough there is a chance that Pi's picky to SATA-USB adapter.
This journey has thought me that the ARM ecosystem is still quite far from x86.
But it's definitely a pleasure to see so much progress in computing power with good power efficiency.</p>
Blog Crusade2021-03-21T00:00:00+00:002021-03-21T00:00:00+00:00
Unknown
https://ejiek.id/blog/2021/blog-crusade/<p>Up to this point, I've hesitated a lot before writing anything here.
I've tried to treat every post as something close to absolute truth.
The motivation was pure:
I've wanted to make it as useful and comprehensible to a reader as possible.
Results are devastating!
Mere three posts in three years.</p>
<p>Two previous posts about PinePhone were an attempt to share a state of things in an ongoing project.
I felt uneasy posting them due to that exact reason.
Projects were ongoing, which means it's not finished.
AAaaaa!</p>
<p>At work, it's perfectly normal to share progress.
We almost have to do it to be productive.
It's natural.
Here it feels different.
No team.
No senior management.
No one to sync up with.
So I thought at least.
Boy, oh boy, I was wrong.</p>
<p>You, my dear reader, are the one!</p>
<p>From now on I plan to post regularly.
Hopes are quantity will eventually improve quality.
I don't intend to keep posts up to date though.
It's not a wiki, it's just a small blog.</p>
<p>Right now blog section of this site leaves a lot to be desired!
We have to work on the look and feel of it.
Definitely add RSS or some other type of feed.
Nevertheless, all of it is useless without actual content, so a whole load is planned, and I sincerely hope that it's coming.
Some SSH related fun, ZFS setup example/story, all the interesting guts of this site in its current form.
Maybe even some travel stories!</p>
Archlinux on PinePhone [Part 1]2020-04-13T00:00:00+00:002020-04-13T00:00:00+00:00
Unknown
https://ejiek.id/blog/2020/archpp1/<p>In this part I'm trying to make a phone out of <a href="https://www.pine64.org/pinephone/">PinePhone</a> BraveHeart Edition running <a href="https://archlinuxarm.org/">Arch Linux ARM</a>.</p>
<p>In the <a href="/blog/2020/archpp">previous part</a> I've got a bare-bones system running with SSH access.</p>
<p>A phone with a terminal is cool but I don't even have a keyboard to interact with it.
There are plenty of desktop environments and window managers to solve this problem but just a few are built with a phone in mind.
Gnome is a good example.
Starting from version 3 it's much more touch-friendly but I'd say it's more tabled oriented.
It's not my daily driver but I've grown to love this DE and there is a mobile-first shell for it - <a href="https://source.puri.sm/Librem5/phosh">phosh</a> by <a href="https://puri.sm/">Purism</a>.</p>
<p>Phosh is developed for Purism's Debian based <a href="https://pureos.net/">PureOS</a>.
It's also <a href="https://wiki.postmarketos.org/wiki/Phosh">well supported</a> by <a href="https://postmarketos.org/">PostmarketOS</a> based on <a href="https://alpinelinux.org/">Alpine Linux</a> which is much closer to Arch Linux than Debian due to <code>APKBUILD</code> being similar to <code>PKGBUILD</code>.
I'm going to use the postmarketOS implementation/packaging of phosh as a reference.</p>
<p>The disappointing thing is the lack of Phosh and its dependencies in the main repos.
Let's take a quick look at what is available at <a href="https://p64.arikawa-hi.me/aarch64/">Dreemurrs Embedded Labs repo</a>:</p>
<pre class="z-code"><code><span class="z-text z-plain">$ pacman -Sl pine64
</span><span class="z-text z-plain">pine64 danctnix-usb-tethering 0.1-0 [installed]
</span><span class="z-text z-plain">pine64 linux-pine64 5.5.0-2 [installed]
</span><span class="z-text z-plain">pine64 linux-pine64-headers 5.5.0-2 [installed]
</span><span class="z-text z-plain">pine64 mesa-git 20.1.0_devel.120770.bc5724faf40-1 [installed]
</span><span class="z-text z-plain">pine64 qt5-es2-base 5.14.1-1
</span><span class="z-text z-plain">pine64 qt5-es2-declarative 5.14.1-1
</span><span class="z-text z-plain">pine64 qt5-es2-multimedia 5.14.1-1
</span><span class="z-text z-plain">pine64 qt5-es2-wayland 5.14.1-1
</span><span class="z-text z-plain">pine64 qt5-es2-xcb-private-headers 5.14.1-1
</span><span class="z-text z-plain">pine64 rtl8723bt-firmware 20190223.28ad358-1 [installed]
</span><span class="z-text z-plain">pine64 tinymembench-git 0.4.r13.ga2cf6d7-1
</span><span class="z-text z-plain">pine64 uboot-pinephone v2020.01_megous-1 [installed]
</span></code></pre>
<p>No phosh but we have git version of mesa which is what postmarketOS is using with its phosh PinePhone build.
<strong>Danct12</strong> <a href="https://github.com/dreemurrs-embedded/Pine64-Arch/issues/2#issuecomment-612454277">wrote</a> that Phosh UI is coming to this repo.
I think there is nothing wrong with trying to get Phosh running myself.</p>
<p>It means I'm going to <a href="https://aur.archlinux.org/">AUR</a>.
One thing about it: I try not to use <a href="https://wiki.archlinux.org/index.php/AUR_helpers">AUR helpers</a> because I have more than one machine and compiling stuff on every one of them seems like a waste of time and resources.
The solution is running my <a href="/repository">small repos</a>.
The cool thing is it also helps some of my friends and close ones to save some time.
Right now for PinePhone users, there are two interesting ones:</p>
<ul>
<li><code>PinePhone</code> - PinePhone/mobile-specific stuff goes to</li>
<li><code>General</code> - used to be AUR builds and my own projects for <code>x86_64</code> but now <code>aarch64</code> is slowly getting to the same pace</li>
</ul>
<p>It's subject to change because I hope that PinePhone stuff is going to appear in Dreemurrs Embedded Labs repo and Arch main repo.
Or my repos can just get merged.</p>
<p>AUR search for phosh gave a positive result.
I see <a href="https://aur.archlinux.org/packages/phosh/">phosh</a> with up-to-date version!
Checking <code>PKGBUILD</code> reveals that there is no <code>aarch64</code> architecture but other ARMs are present.
Arch Linux ARM build system adds all necessary ARM architectures to upstream's (regular Arch Linux) <code>PKGBUILD</code>s automatically.
But since other ARM variants are already there why not to ask to add one more =]
As for now I'm passing the <code>-A</code> flag to the <code>makepkg</code> to ignore a missing architecture (more info in <a href="https://www.archlinux.org/pacman/makepkg.8.html">man</a>).
<a href="https://aur.archlinux.org/packages/phosh/#comment-739208">Done</a>!</p>
<p>Now cloning it to the PinePhone and trying to build.
First things first - missing dependencies: <code>feedbackd</code> and <code>phoc</code> are available only in AUR.
Moving to build them first.</p>
<p>Right about now I would like to mention that Arch Linux <a href="https://aur.archlinux.org/packages/phosh/">doesn't support partial upgrades</a>.
This is the thing I'm well aware of and still, it was able to bite me.
While building the <code>feedbackd</code> I've encountered an issue of missing <code>libffi</code> even though I was building it with <code>makepkg -s</code> which installs all available dependencies (<code>libffi</code> is not one of this package dependencies but it sure should be installed because it's at least required by vim).
An easy thing to fix: search for it <code>pacman -Ss libffi</code>, install it <code>pacman -S libffi</code>.
I'm not sure what it was exactly, probably an update for <code>glibc</code> and <code>libffi</code> or some other important underlying bits.
Versions of underlying components got mismatched.
I, being unaware of this, was still trying to build the <code>feedbackd</code> and fighting build errors. I decide to reboot.
<strong>BOOM!</strong> a kernel panic at boot!</p>
<p>The previous version of <code>libffi</code> is not available in the pacman cache.
I'm not aware of <a href="https://archive.archlinux.org/">archive</a> for Arch Linux ARM.
My little panic.
After all, it's always possible to nuke and pave this system but it means losing some part of a challenge spirit.</p>
<p>Luckily in pacman cache of one of my RaspberryPis was a suitable version of <code>libffi</code> (<code>6.so</code> instead of <code>7.so</code>).
Copy to sdcard.
Downgrad in a qemu powered arch-chroot.
Boot on PP.
It's working!</p>
<p>At this point, I'm starting to guess that my <code>feedbackd</code> dependency play got in the middle of an upgrade.
Upgrading everything and rebooting got <code>feedbackd</code> to build just fine.</p>
<p>No problems with <code>phoc</code> apart from a failing test.
It has to be started and requires EGL to be tested which is not a state I can get during build right now or at all.
Passing <code>--nocheck</code> to <code>makepkg</code> solves the problem.</p>
<p>Good time to add some phone related apps to repo:</p>
<ul>
<li><a href="https://aur.archlinux.org/packages/calls/">calls</a></li>
<li><a href="https://aur.archlinux.org/packages/kgx/">kgx</a> - phone friendly terminal (had to add to AUR release based version)</li>
<li><a href="https://aur.archlinux.org/packages/squeekboard/">squeekboard</a></li>
<li><a href="https://aur.archlinux.org/packages/purism-chatty/">purism-chattay</a></li>
</ul>
<p>It's time to start all of it!
PostmarketOS uses lightdm for this.
Installing and configuring it by creating <code>/usr/share/lightdm/lightdm.conf.d/20-autologin.conf</code>:</p>
<pre class="z-code"><code><span class="z-text z-plain">[Seat:*]
</span><span class="z-text z-plain">autologin-user=alarm
</span><span class="z-text z-plain">autologin-session=phosh
</span></code></pre>
<p>Configuring groups like in <a href="https://wiki.archlinux.org/index.php/LightDM#Enabling_autologin">this guide</a></p>
<p><em><code>alarm</code> user used in this example is the default one for Arch Linux ARM installation.
It's highly recommended to change it.
I use another one.
Do it.</em></p>
<p>Firing it with <code>systemctl start lightdm</code>.
I've got couple service failures due to misconfiguration but <code>/var/log/lightdm/lightdm.log</code> pointed them out.
Clean service start!
The terminal blinker stopped!</p>
<p>Wait!
There is no video.
journalctl says there is a core dump by phoc:</p>
<pre class="z-code"><code><span class="z-text z-plain">Stack trace of thread 36003:
</span><span class="z-text z-plain">#0 0x0000ffff86d3bab8 raise (libc.so.6 + 0x36ab8)
</span><span class="z-text z-plain">#1 0x0000ffff871877e8 g_log_default_handler (libglib-2.0.so.0 + 0x667e8)
</span><span class="z-text z-plain">#2 0x0000ffff87187a6c g_logv (libglib-2.0.so.0 + 0x66a6c)
</span><span class="z-text z-plain">#3 0x0000ffff87187cb8 g_log (libglib-2.0.so.0 + 0x66cb8)
</span><span class="z-text z-plain">#4 0x0000ffff8741a5dc n/a (libgio-2.0.so.0 + 0x1295dc)
</span><span class="z-text z-plain">#5 0x0000ffff87295278 n/a (libgobject-2.0.so.0 + 0x1d278)
</span><span class="z-text z-plain">#6 0x0000ffff8729736c g_object_new_valist (libgobject-2.0.so.0 + 0x1f36c)
</span><span class="z-text z-plain">#7 0x0000ffff87297634 g_object_new (libgobject-2.0.so.0 + 0x1f634)
</span><span class="z-text z-plain">#8 0x0000aaaac996dbb8 n/a (phoc + 0xfbb8)
</span><span class="z-text z-plain">#9 0x0000ffff87294ed4 n/a (libgobject-2.0.so.0 + 0x1ced4)
</span><span class="z-text z-plain">#10 0x0000ffff87296c78 g_object_new_with_properties (libgobject-2.0.so.0 + 0x1ec78)
</span><span class="z-text z-plain">#11 0x0000ffff87297668 g_object_new (libgobject-2.0.so.0 + 0x1f668)
</span><span class="z-text z-plain">#12 0x0000aaaac9969b88 n/a (phoc + 0xbb88)
</span><span class="z-text z-plain">#13 0x0000aaaac99686d0 n/a (phoc + 0xa6d0)
</span><span class="z-text z-plain">#14 0x0000aaaac9967e80 n/a (phoc + 0x9e80)
</span><span class="z-text z-plain">#15 0x0000ffff86d2912c __libc_start_main (libc.so.6 + 0x2412c)
</span><span class="z-text z-plain">#16 0x0000aaaac9967fc8 n/a (phoc + 0x9fc8)
</span><span class="z-text z-plain">#17 0x0000aaaac9967fc8 n/a (phoc + 0x9fc8)
</span></code></pre>
<p>Well, that's all I got energy for today.
Going to be back soon!</p>
<hr />
<p>P.S. Resources I find useful to monitor:</p>
<ul>
<li><a href="https://gitlab.com/pine64-org/linux/">Pine fork of linux</a> - there is 5.6 already</li>
<li><a href="https://images.postmarketos.org/pinephone/">Demo image of pOS</a> - logs are quite interesting</li>
<li><a href="https://gitlab.com/postmarketOS/pmaports/">Postmakert ports</a> in particular <a href="https://gitlab.com/postmarketOS/pmaports/-/blob/master/device/testing/device-pine64-pinephone/APKBUILD">pinephone APKBUILD</a></li>
<li><a href="https://xnux.eu/news.html">High-performance Linux based software for mobile devices</a></li>
</ul>
Archlinux on PinePhone [intro]2020-04-11T00:00:00+00:002020-04-11T00:00:00+00:00
Unknown
https://ejiek.id/blog/2020/archpp/<p>This is a short story of me getting my <a href="https://www.pine64.org/pinephone/">PinePhone</a> BraveHeart Edition to run ArchLinux.
<strong>Not a tutorial</strong>.</p>
<p>Running Arch on a somewhat modern phone was a long-forgotten dream.
So I'm extremely excited to get to it!</p>
<p>For the image, I went to <a href="https://github.com/dreemurrs-embedded/Pine64-Arch/releases">Dreemurrs Embedded Labs repo</a> because there is no official image available.
It was <a href="https://github.com/dreemurrs-embedded/Pine64-Arch/releases/tag/20200318">20200318</a> to be precise.</p>
<p>There are two installation targets:</p>
<ul>
<li>internal eMMC</li>
<li>MicroSD card</li>
</ul>
<p>I went with the second one because my sd card is much bigger than integrated eMMC and I'm planning on playing with a variety of software to figure out usable configuration.</p>
<p>Dreemurrs Emvedded Labs image supports "DHCP over USB" which means all I need to do is to plug PP into my computer.
A new network interface with an IP address appeared.
Neat!
No need for external USB to Ethernet adapters.
Just ssh into <code>alarm@10.15.19.82</code>.</p>
<p>I'm greeted by this useful message of the day:</p>
<pre class="z-code"><code><span class="z-text z-plain">This is unofficial ARM build maintained by Dreemurrs Embedded Labs.
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">To make a issue/pull request to our repo:
</span><span class="z-text z-plain"> * Issues: https://github.com/dreemurrs-embedded/Pine64-Arch/issues
</span><span class="z-text z-plain"> * Pull Requests: https://github.com/dreemurrs-embedded/Pine64-Arch/pulls
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">Support can be provided from our chat rooms:
</span><span class="z-text z-plain"> * Discord: https://discord.gg/AvtdRJ3
</span><span class="z-text z-plain"> * Matrix: #danctnix-portingv2:matrix.org
</span></code></pre>
<p>The first recommended thing to do is to resize partition with provided <code>resize_rootfs.sh</code>.
And it just works.
<code>df -h</code> shows more than 100G available.
Connecting to the WiFi with <code>nmcli</code>/<code>nmtui</code> and off I go.</p>
<p>Time to practice Arch user tic: <code>pacman -Syu</code>.
And here we go!
Meet the first problem:</p>
<pre class="z-code"><code><span class="z-text z-plain">error: could not open file /var/lib/pacman/sync/pine64.db: Unrecognized archive format
</span><span class="z-text z-plain">error: failed to prepare transaction (invalid or corrupted database)
</span></code></pre>
<p>There is an extra repository with PinePhone specific stuff and it's blocking my updates?
Can't be!
Firing <code>file</code> shows that this file is an HTTP file o.O
Not good!
It should be some compressed data.</p>
<p>Checking <code>/etc/pacman.conf</code>:</p>
<pre class="z-code"><code><span class="z-text z-plain">[pine64]
</span><span class="z-text z-plain">SigLevel = Never
</span><span class="z-text z-plain">Server = http://p64.arikawa-hi.me/aarch64/
</span></code></pre>
<p>A-ha! An <code>http</code> connection and there is no signature verification.
An Internet provider can easily become a man in the middle and ruin the update process just by showing some "very important" notifications (for example, about traffic limitations).
The actual bad thing is there is no way for pacman to verify this.
I'm also not sure how pacman handles 301/302 http codes (are used to switch <code>http</code> to <code>https</code>).</p>
<p>Checking upstream with <code>curl -v http://p64.arikawa-hi.me/aarch64/pine64.db</code> showed that there is a 301 code redirect to <code>https</code>.
And <code>https</code> actually works.</p>
<p>Good!
Removing wrong <code>.db</code> file, changing to <code>https</code> in <code>/etc/pacman.conf</code> and <a href="https://github.com/dreemurrs-embedded/Pine64-Arch/issues/2">filing an issue</a>.</p>
<p>Back to Arch user tic.
The update is going just fine!</p>
<p>Now I have an <code>aarch64</code> system in phone form factor.
Next step is to make a phone out of it =]</p>
<p>Be right back with a report on getting <a href="https://source.puri.sm/Librem5/phosh">phosh</a> running.</p>
<p><strong><a href="/blog/2020/archpp1">[Already available]</a></strong></p>
<hr />
<p>PS. I have an unlimited data plan for my phone and my provider does everything it can come up with and reason to block any 'non-phone' activities like tethering or plugging a sim card in non-phone devices.
PinePhone was recognized as a tablet that led to speed limitation from provider.
It took me 5 days of waiting, photos of Ubuntu Touch "About" section and of it's terminal output for <code>cat /etc/os-release</code> to get PP approved as a phone.</p>
<hr />
<p>PPS. Farther you can find things I've tried without real success =\</p>
<p>To avoid connecting your PP to PC for initial configuration I've copied my wifi config file to the sd card right after flashing it.
I'm using NetworkManager on both your PC and PinePhone.
Bits that I've changed:</p>
<ul>
<li>uuid (just a bit to differ)</li>
<li>interface-name to <code>wlan0</code> (PP interface name)</li>
</ul>
<p>This attempt failed because PP was not connecting to my WiFi.
Never investigated the reason or the correct way to do it because it was so easy to just plug it into a computer.</p>
<p>The second failed idea was to prepare freshly flashed sd card before plugging it into the phone!
I'm quite used to <code>arch-chroot</code> for configuring another Arch systems but my workstation is rocking x86 family CPU while PP is ARM based.</p>
<p>This process became much easier in the past several years not without systemd.
Here comes <code>qemu</code> and <code>qemu-arm-static</code> specifically (there is an <a href="https://aur.archlinux.org/packages/qemu-arm-static/">AUR package</a> and it's present in my <a href="/repository/#general">'General' repo</a>).
To use it copy just one file:</p>
<pre class="z-code"><code><span class="z-text z-plain">sudo cp /usr/bin/qemu-arm-static /mnt/usr/bin/
</span></code></pre>
<p><em>in this example target sdcard is mounted at <code>/mnt</code></em></p>
<p>At this point chrooting is just one command ahead:</p>
<pre class="z-code"><code><span class="z-text z-plain">sudo arch-chroot /mnt
</span></code></pre>
<p>Nice! I'm in!
The problem is name resolution is broken o.O</p>
<p><code>drill</code> works just fine but <code>pacman</code> is unable to connect to the mirrors.
<code>/etc/resolv.conf</code> is fine so I'm not sure what's the issue.</p>
<p>It's an interesting one to solve but I can't wait to use the phone so postponing it for a while.</p>
Git for work and for you2020-03-28T00:00:00+00:002020-03-28T00:00:00+00:00
Unknown
https://ejiek.id/blog/2020/git-for-work-and-for-you/<p>Do you have your work email provided by your employer?
If so it's probably a good thing to use it in your git configuration while you are committing something for your company.</p>
<p>What about commit templates? hooks? It's also a good thing to have configured the way a company requires!</p>
<p>But what about <strong>you</strong>? Your own commitment to open source software or anything else?
I would like to have my personal email for that matter!
Bet you too =]
<em>(Especially if it's done in your own/free time)</em></p>
<p>There is a variety of solutions to have both on one machine!
<a href="https://direnv.net/">DirEnv</a> is an awesome project but we can use git native solutions!</p>
<p>The lowest entry point is to do it on per project basis.
A project config is stored in <code>.git/config</code> of a given project and is modified with the <code>git config</code> command when you do <strong>not</strong> pass <code>--global</code> or <code>--system</code> flags.
But it's a hell to maintain once you have more than a couple of personal and work related projects.</p>
<p>Here comes the hero, <code>includeIf</code>!</p>
<p>A bit of context.
There is an <code>include</code> section in git config file that allows you to move some mess out.</p>
<p>Lets say you have an awesome <code>[alias]</code> section but it's huge which makes your <code>~/.gitconfig</code> harder to read and manage.</p>
<p>Example of a neat alias:</p>
<pre class="z-code"><code><span class="z-text z-plain">[alias]
</span><span class="z-text z-plain"> lg = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n'' %C(white)%s%C(reset) %C(dim green)- %an%C(reset)' --all
</span></code></pre>
<p><code>include</code> allows you to put it into separate file (let it be <code>~/.config/git/alias</code> here) and yeah include it in the main one like this</p>
<pre class="z-code"><code><span class="z-text z-plain">[include]
</span><span class="z-text z-plain"> path = ~/.config/git/alias
</span></code></pre>
<p>Since it is not specific to aliases you can put almost any part of config in there! </p>
<p><code>includeIf</code> takes it much further than just debloating.
Now there is a condition!
And we going to use one (path by <code>gitdit</code>).
Config you put there overrides the main one.</p>
<p>My preference is to have my personal info in <code>~/.gitconfig</code> and work related stuff in <code>~/.config/git/companyname.conf</code> included by:</p>
<pre class="z-code"><code><span class="z-text z-plain">[includeIf "gitdir:~/work/"]
</span><span class="z-text z-plain"> path = ~/.config/git/companyname
</span></code></pre>
<p>In <code>~/.config/git/companyname</code> I override <code>[user]</code> section and add <code>[commit]</code> section with commit templates.</p>
<p>This way work config is applied whenever project is under <code>~/work/</code> and regular one works everywhere else.</p>
<p>Sure it's a simple example and you are welcome to go deeper and cooler!</p>
<p>For more info use <a href="https://git-scm.com/docs/git-config#_includes">man git-config</a></p>
<p>Good luck tinkering with stuff!</p>