<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://ckardaris.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://ckardaris.com/" rel="alternate" type="text/html" /><updated>2026-09-17T16:53:11+00:00</updated><id>https://ckardaris.com/feed.xml</id><title type="html">Charalampos Kardaris’ blog</title><author><name>Charalampos Kardaris</name></author><entry><title type="html">Icons</title><link href="https://ckardaris.com/blog/2026/09/08/icons.html" rel="alternate" type="text/html" title="Icons" /><published>2026-09-08T00:00:00+00:00</published><updated>2026-09-08T00:00:00+00:00</updated><id>https://ckardaris.com/blog/2026/09/08/icons</id><content type="html" xml:base="https://ckardaris.com/blog/2026/09/08/icons.html"><![CDATA[<p>Free time application development is a curious activity.
While programming professionally <em>usually</em> means that one is responsible for only a part —
smaller or bigger — of the final product, programming outside working hours is different.</p>

<p>Publishing a side project requires completing a lot of different tasks.
The creator needs to wear many different hats.
Apart from architecting and programming the application, they have to think about the user interface
and the user experience, write the documentation, promote the project in the various public channels
and more.</p>

<p>All the roles are bound to one person.
Every role is important.</p>

<p>It’s not all doom and gloom though.
The extra responsibilities bring extra opportunities to learn and to grow — new information to read,
new material to study, more experimentation in unknown areas.</p>

<p>One of my favorite “side quests” in my free time programming is icon design.
I am not particularly good — although I think I am getting better — but thinking about a concept,
iterating over the different ideas in <a href="https://inkscape.org/">Inkscape</a> and producing the final result
is a process that I find quite enjoyable.</p>

<p>Last month, I released my first ever GUI application, Klisi.
I wrote some thoughts about that initial release in a <a href="/blog/2026/08/15/klisi.html">previous post</a>.</p>

<p>Of course, Klisi came with its own application icon — one that I designed after I read the relevant
section in the <a href="https://developer.gnome.org/hig/guidelines/app-icons.html">GNOME Human Interface
Guidelines</a>.</p>

<p>After many iterations changing the colors, the number of elements, the characters to print and the
shapes, this is what I was able to come up with.</p>
<div> <svg height="128" viewBox="0 0 128 128" width="128" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientTransform="matrix(.196428 0 0 .342105 2.59729 273.740051)" gradientUnits="userSpaceOnUse" x1="88.595886" x2="536.595886" y1="-449.394012" y2="-449.394012"><stop offset="0" stop-color="#5e5c64" /><stop offset=".0500001" stop-color="#77767b" /><stop offset=".099807" stop-color="#5e5c64" /><stop offset=".900001" stop-color="#5e5c64" /><stop offset=".950001" stop-color="#77767b" /><stop offset="1" stop-color="#5e5c64" /></linearGradient><path d="m28 16h72c4.417969 0 8 3.582031 8 8v88c0 4.417969-3.582031 8-8 8h-72c-4.417969 0-8-3.582031-8-8v-88c0-4.417969 3.582031-8 8-8zm0 0" fill="url(#a)" /><path d="m28 8h72c4.417969 0 8 3.1875 8 7.121094v81.914062c0 3.933594-3.582031 7.121094-8 7.121094h-72c-4.417969 0-8-3.1875-8-7.121094v-81.914062c0-3.933594 3.582031-7.121094 8-7.121094zm0 0" fill="#fff" /><path d="m21.070312 25.070312h85.859376v29.859376h-85.859376zm0 0" fill="#99c1f1" stroke="#99c1f1" stroke-width="2.13968" /><path d="m35.988281 32.847656h8.023438c1.734375 0 3.140625 1.421875 3.140625 3.171875v7.960938c0 1.75-1.40625 3.171875-3.140625 3.171875h-8.023438c-1.734375 0-3.140625-1.421875-3.140625-3.171875v-7.960938c0-1.75 1.40625-3.171875 3.140625-3.171875zm0 0" fill="#f66151" stroke="#f66151" stroke-width="1.69393" /><path d="m35.988281 72.847656h8.023438c1.734375 0 3.140625 1.421875 3.140625 3.171875v7.960938c0 1.75-1.40625 3.171875-3.140625 3.171875h-8.023438c-1.734375 0-3.140625-1.421875-3.140625-3.171875v-7.960938c0-1.75 1.40625-3.171875 3.140625-3.171875zm0 0" fill="#f66151" stroke="#f66151" stroke-width="1.69393" /><g fill="#5e5c64" stroke="#5e5c64" stroke-width=".75"><path d="m78.980468 36.847656-.8125-2.1875c-.242187-.664063-.527343-1.117188-.859375-1.359375-.324218-.25-.890625-.375-1.703125-.375h-1.453125v-3.0625h2.6875c1.582032 0 2.800782.335937 3.65625 1 .851563.667969 1.578125 1.792969 2.171875 3.375l5.953125 15.890625h-4.65625l-2.90625-7.65625-3.0625 7.65625h-4.65625zm-6.4375-11.472656" /><path d="m87.542969 81.515625c-.554687 0-.980469.242188-1.28125.71875-.304687.484375-.453125 1.171875-.453125 2.0625 0 .898438.144531 1.585938.4375 2.0625.300781.484375.734375.71875 1.296875.71875s.988281-.234375 1.28125-.71875c.300781-.476562.453125-1.164062.453125-2.0625 0-.890625-.152344-1.578125-.453125-2.0625-.292969-.476562-.71875-.71875-1.28125-.71875zm0-1.96875c1.457031 0 2.601563.421875 3.4375 1.265625.832031.84375 1.25 2.007813 1.25 3.484375 0 1.46875-.417969 2.640625-1.25 3.5-.835937.859375-1.980469 1.28125-3.4375 1.28125-1.449219 0-2.59375-.421875-3.4375-1.28125-.835937-.859375-1.25-2.03125-1.25-3.5s.414063-2.625 1.25-3.46875c.84375-.851562 1.988281-1.28125 3.4375-1.28125zm-10.734375 9.53125h-2.59375l10.9375-18.15625h2.59375zm-2.40625-18.15625c1.445313 0 2.585938.429688 3.421875 1.28125.832031.84375 1.25 2 1.25 3.46875 0 1.484375-.417969 2.648438-1.25 3.5-.835937.859375-1.976562 1.28125-3.421875 1.28125-1.460937 0-2.605469-.421875-3.4375-1.28125-.824219-.851562-1.234375-2.015625-1.234375-3.5 0-1.46875.410156-2.625 1.234375-3.46875.832031-.851562 1.976563-1.28125 3.4375-1.28125zm0 1.96875c-.5625 0-1 .25-1.3125.734375-.304687.484375-.453125 1.164063-.453125 2.046875 0 .90625.148438 1.609375.453125 2.09375.3125.484375.75.71875 1.3125.71875s.992188-.234375 1.296875-.71875c.300781-.484375.453125-1.1875.453125-2.09375 0-.882812-.15625-1.5625-.46875-2.046875-.304687-.484375-.730469-.734375-1.28125-.734375zm-5.453125-6.429687" /></g></svg> </div>

<p>This seemed adequate for a while, but I was not completely satisfied.
I wanted the icon to be a little more lively, a little more playful with the words.
Klisi means “call” in Greek.
I thought the name was fitting for a Callgrind profile viewer and generator.</p>

<p>The icon was not yet up to the same standard.
The first attempt was good, but the output felt a little dry.
It could lean a little more into the name.
I wanted to find an item that would elegantly accompany the name.
The obvious relation would be some kind of phone.
Many “call-related” applications use a telephone handset.
But this is quite common and not very playful.
I instead opted for something a little more retro.</p>

<p>A rotary dial.</p>

<p>I wanted to keep the <em>λ</em> (lambda) and <em>%</em> characters from the original icon — to maintain the spirit
of function call profiling.
I thought it would be fun to use the lambda as the rotary phone finger stop.
The <em>%</em> character would then be placed in the center.
Additionally, the numbers inside the finger holes would provide an additional indication of the
number crunching nature of the application.</p>

<p>Et voilà!</p>

<div> <svg height="128" viewBox="0 0 128 128" width="128" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="1" gradientTransform="matrix(.232143 0 0 .355263 -8.56691 283.653107)" gradientUnits="userSpaceOnUse" x1="88.595886" x2="536.595886" y1="-449.394012" y2="-449.394012"><stop offset="0" stop-color="#cb8b3d" /><stop offset=".0384615" stop-color="#e6b26c" /><stop offset=".0768555" stop-color="#cb8b3d" /><stop offset=".923077" stop-color="#cb8b3d" /><stop offset=".961538" stop-color="#e6b26c" /><stop offset="1" stop-color="#cb8b3d" /></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="28.000002" x2="100" y1="232" y2="232"><stop offset="1" stop-color="#e5c5a0" /><stop offset="1" stop-color="#e5a55b" stop-opacity="0" /></linearGradient><path d="m20 16h88c4.417969 0 8 3.867188 8 8.640625v90.71875c0 4.773437-3.582031 8.640625-8 8.640625h-88c-4.417969 0-8-3.867188-8-8.640625v-90.71875c0-4.773437 3.582031-8.640625 8-8.640625zm0 0" fill="url(#1)" /><path d="m20 12h88c4.417969 0 8 3.726562 8 8.320312v87.359376c0 4.59375-3.582031 8.320312-8 8.320312h-88c-4.417969 0-8-3.726562-8-8.320312v-87.359376c0-4.59375 3.582031-8.320312 8-8.320312zm0 0" fill="#ffbe6f" /><path d="m107.667969 64c0 24.117188-19.550781 43.667969-43.667969 43.667969s-43.667969-19.550781-43.667969-43.667969 19.550781-43.667969 43.667969-43.667969 43.667969 19.550781 43.667969 43.667969zm0 0" fill="#e5a55b" stroke="#e5a55b" stroke-width=".662835" /><path d="m99.445312 232c0 19.578125-15.867187 35.445312-35.445312 35.445312s-35.445312-15.867187-35.445312-35.445312 15.867187-35.445312 35.445312-35.445312 35.445312 15.867187 35.445312 35.445312zm0 0" fill="#e5c5a0" stroke="url(#b)" stroke-width="1.107" transform="translate(0 -168)" /><path d="m79.753906 64c0 8.699219-7.054687 15.753906-15.753906 15.753906s-15.753906-7.054687-15.753906-15.753906 7.054687-15.753906 15.753906-15.753906 15.753906 7.054687 15.753906 15.753906zm0 0" fill="#fff" stroke="#fff" stroke-width=".492" /><path d="m-90.826954-125.264704-.767556-2.095818c-.24199-.632233-.514933-1.066006-.827524-1.296737-.30614-.237462-.844964-.357897-1.625166-.356726l-1.38905-.001294-.000489-2.923091 2.560474.000612c1.509922.000927 2.672821.323506 3.486546.96998.820083.635351 1.515435 1.710714 2.077084 3.21749l5.6865 15.156384-4.437407-.000344-2.77937-7.312512-2.923972 7.312658-4.437407-.000345zm-6.142463-10.957662" fill="#f66151" stroke="#f15e4e" stroke-width=".75" transform="matrix(.907522 -.869807 .869807 .907522 275.156982 111.858246)" /><g fill="#fff" stroke="#fff" stroke-width=".917483"><path d="m42.542969 64c0 2.507812-2.035157 4.542969-4.542969 4.542969s-4.542969-2.035157-4.542969-4.542969 2.035157-4.542969 4.542969-4.542969 4.542969 2.035157 4.542969 4.542969zm0 0" /><path d="m68.542969 90c0 2.507812-2.035157 4.542969-4.542969 4.542969s-4.542969-2.035157-4.542969-4.542969 2.035157-4.542969 4.542969-4.542969 4.542969 2.035157 4.542969 4.542969zm0 0" /><path d="m68.542969 38c0 2.507812-2.035157 4.542969-4.542969 4.542969s-4.542969-2.035157-4.542969-4.542969 2.035157-4.542969 4.542969-4.542969 4.542969 2.035157 4.542969 4.542969zm0 0" /><path d="m-164.376018 145.425197c.000023 2.507853-2.032181 4.542661-4.540033 4.542683-2.507852.000023-4.544613-2.035563-4.544636-4.543415s2.034134-4.539277 4.541986-4.5393 4.54266 2.03218 4.542683 4.540032zm0 0" transform="matrix(.5 -.866025 .866025 .5 0 -168)" /><path d="m-56.032499 258.918322c-.000023 2.507852-2.034831 4.540055-4.542683 4.540033-2.507852-.000023-4.542008-2.031448-4.541985-4.5393s2.036784-4.543439 4.544636-4.543416 4.540055 2.034831 4.540032 4.542683zm0 0" transform="matrix(.866025 -.5 .5 .866025 0 -168)" /><path d="m-56.031581 206.916733c-.000023 2.507852-2.036784 4.543438-4.544636 4.543415s-4.540055-2.034831-4.540033-4.542683c.000023-2.507852 2.034831-4.540055 4.542683-4.540032s4.542009 2.031448 4.541986 4.5393zm0 0" transform="matrix(.866025 -.5 .5 .866025 0 -168)" /><path d="m175.968419 194.919055c.000023 2.507852-2.034134 4.539277-4.541986 4.5393-2.507852.000022-4.54266-2.032181-4.542683-4.540033-.000022-2.507852 2.032181-4.54266 4.540033-4.542683s4.544613 2.035564 4.544636 4.543416zm0 0" transform="matrix(.866025 .5 -.5 .866025 0 -168)" /><path d="m175.967501 142.917465c.000023 2.507852-2.03218 4.54266-4.540032 4.542683s-4.544613-2.035563-4.544636-4.543415 2.034133-4.539277 4.541985-4.5393 4.54266 2.03218 4.542683 4.540032zm0 0" transform="matrix(.866025 .5 -.5 .866025 0 -168)" /><g transform="matrix(.5 .866025 -.866025 .5 0 -168)"><path d="m237.459769 86.574803c-.000023 2.507852-2.034831 4.540055-4.542683 4.540032s-4.542008-2.031448-4.541985-4.5393c.000022-2.507852 2.036784-4.543438 4.544636-4.543415 2.507852.000022 4.540055 2.03483 4.540032 4.542683zm0 0" /><path d="m237.460687 34.573213c-.000023 2.507852-2.036784 4.543438-4.544636 4.543416-2.507852-.000023-4.540056-2.034831-4.540033-4.542683s2.034831-4.540056 4.542683-4.540033 4.542009 2.031448 4.541986 4.5393zm0 0" /></g></g><path d="m68.03126395 64.67579643c-.45703083 0-.81640634.19921838-1.07031251.59374887-.26171872.37890686-.3867194.92187566-.3867194 1.61328131 0 .67187575.12500068 1.20312574.3867194 1.59375141.25390618.38281168.61328168.5703127 1.07031251.5703127.43749947 0 .78515616-.18750102 1.04687489-.5703127.25390618-.39062567.3867194-.92187566.3867194-1.59375141 0-.67968684-.13281322-1.2148431-.3867194-1.61328131-.26171872-.39453049-.60937541-.59374887-1.04687489-.59374887zm0-.97656346c.82031261 0 1.46875073.28906262 1.95703173.86328159.48437473.56250016.72656209 1.33593751.72656209 2.32031205 0 .97656346-.24218736 1.75000081-.72656209 2.32421978-.488281.56250016-1.13671913.83984396-1.95703173.83984396-.83593769 0-1.49609463-.27734381-1.98046936-.83984396-.488281-.57421897-.73046837-1.34765632-.73046837-2.32421978 0-.98437455.24218736-1.7578119.73046837-2.32031205.49999982-.57421897 1.16015675-.86328159 1.98046936-.86328159zm-8.05859548-4.75781224c-.45312456 0-.81250006.19921838-1.06640624.59374887-.26171872.37890686-.38671795.91015685-.38671795 1.58984514 0 .68359311.12499923 1.22656191.38671795 1.61718759.25390618.38281168.61328168.5703127 1.06640624.5703127.45703229 0 .80859379-.18750102 1.07031251-.5703127.25781245-.39062567.3867194-.93359447.3867194-1.61718759 0-.6796883-.13671804-1.21093828-.41015558-1.58984514-.26172018-.39453049-.60546914-.59374887-1.04687634-.59374887zm7.05468958-1.00000109h1.25390581l-7.30468803 12.10547072h-1.25390727zm-7.05468958 0c.82031261 0 1.46875073.28906262 1.95703173.86328159.50390609.56250016.75390599 1.33984378.75390599 2.32031351 0 .976562-.24999991 1.74999935-.75390599 2.32421832-.488281.56250016-1.13671913.83984396-1.95703173.83984396-.83593769 0-1.49609317-.27734381-1.97656163-.83984396-.47265737-.57421897-.70703219-1.34765632-.70703219-2.32421832 0-.96484465.23828109-1.738282.72656209-2.32031351.48437619-.57421897 1.1406254-.86328159 1.95703173-.86328159zm-3.57031305-2.96875018" fill="#f66151" stroke="#f15e4e" stroke-width="1.092358" /><g fill="#e5a55b" stroke="#e5a55b" stroke-width=".75"><path d="m84.902344 53.242188h1.28125v-4.4375l-1.40625.28125v-.71875l1.40625-.28125h.78125v5.15625h1.296875v.671875h-3.359375zm-1-6.75" /><path d="m64.71875 37.773437c.375.070313.664062.242188.875.5.21875.25.328125.5625.328125.9375 0 .585938-.195313 1.03125-.59375 1.34375-.398438.3125-.960938.46875-1.6875.46875-.25 0-.507813-.03125-.765625-.078125-.25-.039062-.515625-.109375-.796875-.203125v-.765625c.21875.125.460937.226563.71875.296875.257812.0625.53125.09375.8125.09375.5 0 .882812-.101562 1.140625-.296875.257812-.195312.390625-.484375.390625-.859375 0-.34375-.125-.609375-.375-.796875-.242188-.195312-.570313-.296875-1-.296875h-.671875v-.640625h.703125c.382812 0 .679687-.078125.890625-.234375.210937-.15625.3125-.382812.3125-.671875 0-.304687-.109375-.53125-.328125-.6875-.210938-.164062-.507813-.25-.90625-.25-.21875 0-.453125.023438-.703125.078125-.242188.039063-.507813.117188-.8125.21875v-.703125c.304687-.085937.585937-.148437.84375-.1875.257812-.039062.507812-.0625.75-.0625.59375 0 1.0625.132813 1.40625.40625.351562.273438.53125.640625.53125 1.109375 0 .320313-.09375.59375-.28125.8125-.179688.21875-.4375.375-.78125.46875zm-3.25-4.289062" /><path d="m51.515625 39.257812-2 3.109375h2zm-.21875-.6875h1v3.796875h.828125v.65625h-.828125v1.375h-.78125v-1.375h-2.640625v-.765625zm-2.8125-1.597656" /><path d="m39.839844 48.03125h3.109375v.65625h-2.375v1.4375c.113281-.039062.226562-.070312.34375-.09375.113281-.019531.226562-.03125.34375-.03125.644531 0 1.15625.183594 1.53125.546875.382812.355469.578125.835937.578125 1.4375 0 .625-.199219 1.117187-.59375 1.46875-.386719.34375-.933594.515625-1.640625.515625-.25 0-.5-.023438-.75-.0625-.25-.042969-.511719-.101562-.78125-.1875v-.796875c.226562.136719.46875.234375.71875.296875s.515625.09375.796875.09375c.445312 0 .800781-.117188 1.0625-.359375.269531-.238281.40625-.5625.40625-.96875s-.136719-.722656-.40625-.953125c-.261719-.238281-.617188-.359375-1.0625-.359375-.210938 0-.421875.027344-.640625.078125-.210938.042969-.421875.109375-.640625.203125zm-.859375-1.597656" /><path d="m38.070312 63.679687c-.359375 0-.640625.125-.84375.375-.210937.242188-.3125.570313-.3125.984375 0 .414063.101563.75.3125 1 .203125.242188.484375.359375.84375.359375.351563 0 .632813-.117187.84375-.359375.203125-.25.3125-.585937.3125-1 0-.414062-.109375-.742187-.3125-.984375-.210937-.25-.492187-.375-.84375-.375zm1.5625-2.46875v.71875c-.203125-.09375-.398437-.164062-.59375-.21875-.203125-.054687-.398437-.078125-.59375-.078125-.523437 0-.921875.179688-1.203125.53125-.273437.351563-.429687.882813-.46875 1.59375.15625-.226562.34375-.398437.578125-.515625.226563-.125.484375-.1875.765625-.1875.578125 0 1.046875.179688 1.390625.53125.34375.351563.515625.835938.515625 1.453125 0 .601563-.179687 1.085938-.53125 1.453125-.359375.351563-.828125.53125-1.421875.53125-.679687 0-1.195312-.257812-1.546875-.765625-.359375-.523437-.53125-1.273437-.53125-2.25 0-.929687.21875-1.664062.65625-2.203125.4375-.554687 1.023438-.828125 1.765625-.828125.195313 0 .390625.023438.59375.0625.195313.03125.40625.085938.625.171875zm-4.203125-1.726562" /><path d="m39.609375 74.085938h3.75v.328125l-2.109375 5.5h-.828125l1.984375-5.171875h-2.796875zm-.65625-1.59375" /><path d="m51.003906 86.664063c-.375 0-.671875.101562-.890625.296875-.210938.199218-.3125.476562-.3125.828125 0 .355468.101562.632812.3125.828125.21875.199218.515625.296875.890625.296875s.664063-.097657.875-.296875c.21875-.207032.328125-.484375.328125-.828125 0-.351563-.109375-.628907-.328125-.828125-.210937-.195313-.5-.296875-.875-.296875zm-.796875-.34375c-.335938-.082032-.59375-.238282-.78125-.46875-.1875-.226563-.28125-.507813-.28125-.84375 0-.46875.160156-.835938.484375-1.109375.332031-.269532.789063-.40625 1.375-.40625.582031 0 1.035156.136718 1.359375.40625.332031.273437.5.640625.5 1.109375 0 .335937-.09375.617187-.28125.84375-.1875.230468-.449219.386718-.78125.46875.375.09375.664062.273437.875.53125.21875.25.328125.5625.328125.9375 0 .5625-.171875.996093-.515625 1.296875-.34375.304687-.839844.453125-1.484375.453125-.648437 0-1.140625-.148438-1.484375-.453125-.34375-.300782-.515625-.734375-.515625-1.296875 0-.375.101563-.6875.3125-.9375.21875-.257813.515625-.4375.890625-.53125zm-.28125-1.25c0 .304687.09375.542968.28125.71875.1875.167968.453125.25.796875.25.332031 0 .59375-.082032.78125-.25.195313-.175782.296875-.414063.296875-.71875 0-.300782-.101562-.535157-.296875-.703125-.1875-.164063-.449219-.25-.78125-.25-.34375 0-.609375.085937-.796875.25-.1875.167968-.28125.402343-.28125.703125zm-1.46875-3.066407" /><path d="m62.359375 92.789062v-.71875c.195312.09375.394531.164063.59375.21875.207031.054688.410156.078125.609375.078125.519531 0 .914063-.179687 1.1875-.53125.28125-.351562.441406-.882812.484375-1.59375-.15625.21875-.351562.390625-.578125.515625-.230469.117188-.484375.171875-.765625.171875-.585938 0-1.046875-.171875-1.390625-.515625-.34375-.351562-.515625-.835937-.515625-1.453125 0-.601562.175781-1.085937.53125-1.4375.351562-.367187.828125-.546875 1.421875-.546875.675781 0 1.191406.257813 1.546875.78125.351562.507813.53125 1.257813.53125 2.25 0 .914063-.21875 1.648438-.65625 2.203125-.4375.539063-1.027344.8125-1.765625.8125-.199219 0-.402344-.023437-.609375-.0625-.199219-.03125-.40625-.085937-.625-.171875zm1.578125-2.46875c.351563 0 .632813-.117187.84375-.359375.207031-.25.3125-.585937.3125-1 0-.414062-.105469-.742187-.3125-.984375-.210937-.25-.492187-.375-.84375-.375-.355469 0-.636719.125-.84375.375-.210937.242188-.3125.570313-.3125.984375 0 .414063.101563.75.3125 1 .207031.242188.488281.359375.84375.359375zm-2.453125-4.835937" /><path d="m77.003906 84.117187c-.40625 0-.714843.203125-.921875.609375-.199218.398438-.296875.996094-.296875 1.796875 0 .792969.097657 1.390625.296875 1.796875.207032.398438.515625.59375.921875.59375s.707032-.195312.90625-.59375c.207032-.40625.3125-1.003906.3125-1.796875 0-.800781-.105468-1.398437-.3125-1.796875-.199218-.40625-.5-.609375-.90625-.609375zm0-.625c.65625 0 1.15625.261719 1.5.78125.34375.511719.515625 1.261719.515625 2.25 0 .980469-.171875 1.730469-.515625 2.25-.34375.511719-.84375.765625-1.5.765625s-1.15625-.253906-1.5-.765625c-.34375-.519531-.515625-1.269531-.515625-2.25 0-.988281.171875-1.738281.515625-2.25.34375-.519531.84375-.78125 1.5-.78125zm-2.546875-1.488281" /><path d="m76.09375 43.78125h2.765625v.671875h-3.703125v-.671875c.289062-.300782.695312-.710938 1.21875-1.234375.519531-.53125.84375-.867188.96875-1.015625.257812-.28125.4375-.519532.53125-.71875.101562-.195313.15625-.390625.15625-.578125 0-.3125-.109375-.566407-.328125-.765625-.21875-.195313-.5-.296875-.84375-.296875-.25 0-.515625.046875-.796875.140625-.273438.085937-.5625.214843-.875.390625v-.796875c.320312-.132813.617188-.234375.890625-.296875.28125-.0625.535156-.09375.765625-.09375.601562 0 1.082031.152343 1.4375.453125.363281.304687.546875.703125.546875 1.203125 0 .242187-.046875.46875-.140625.6875s-.257812.476562-.484375.765625c-.074219.074218-.289063.292968-.640625.65625-.34375.355468-.835938.855468-1.46875 1.5zm-1.53125-6.753907" /></g></svg>
 </div>

<p>What do you think?
I believe it can receive some more polishing to provide some more depth to all surfaces, but this is
the extent of my skills for now and I am satisfied with the result.</p>]]></content><author><name>Charalampos Kardaris</name></author><category term="blog" /><category term="technology" /><summary type="html"><![CDATA[I designed a new application icon for Klisi, a GTK4/Adwaita Callgrind profile viewer and generator.]]></summary></entry><entry><title type="html">Front page</title><link href="https://ckardaris.com/blog/2026/08/30/front-page.html" rel="alternate" type="text/html" title="Front page" /><published>2026-08-30T00:00:00+00:00</published><updated>2026-08-30T00:00:00+00:00</updated><id>https://ckardaris.com/blog/2026/08/30/front-page</id><content type="html" xml:base="https://ckardaris.com/blog/2026/08/30/front-page.html"><![CDATA[<p>I have not had a huge success with my writing in this blog so far.
I don’t collect any analytics<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>, so I am not sure exactly how each post fares, but based on the
amount of comments I have received in the various platforms I have shared my posts, the traffic must
not be very high.</p>

<p>This has not discouraged me from writing.
My goal is to share projects that I am developing and to express some of my thoughts when I feel
like it.
I am glad if someone finds the content interesting and I am even happier to engage anyone who sends
a comment my way.
In any case, I think that the mere act of sitting down and writing your thoughts is beneficial by
itself and it is worth doing, even if the audience is not there.</p>

<p>It is true, though, that it feels nice when people accept what you are offering.
And two days ago, I finally got my first breakthrough.
I shared my <a href="/blog/2026/08/28/keyboard-driven-guis.html">last post</a> on Hacker News and it
reached the <a href="https://news.ycombinator.com/front?day=2026-08-28">front page</a>.</p>

<p>I admit it was satisfying to see that a lot of people approved of the message and wanted to share
their own opinions.
I spent the next day or so participating in the
<a href="https://news.ycombinator.com/item?id=49479837">discussion</a> and digesting all the new information
that was available.
One of the main themes of the discussion was user interface accessibility.
It is a part of software development that is of great interest and importance and I hope to more
systematically approach it in my programming and my writing in the near future.</p>

<p>The whole experience was positive.
I do not claim to be a great writer, nor that my post proclaimed some profound truth, but it was
gratifying to see that an extended discussion was started because of something I wrote.
As a result, I now am a little braver to share my thoughts with the world.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>I think they can create bad incentives for content creation. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Charalampos Kardaris</name></author><category term="blog" /><category term="life" /><category term="website" /><summary type="html"><![CDATA[The experience of having a post on the front page of Hacker News for a day.]]></summary></entry><entry><title type="html">GUIs should be fully keyboard-driven</title><link href="https://ckardaris.com/blog/2026/08/28/keyboard-driven-guis.html" rel="alternate" type="text/html" title="GUIs should be fully keyboard-driven" /><published>2026-08-28T00:00:00+00:00</published><updated>2026-08-28T00:00:00+00:00</updated><id>https://ckardaris.com/blog/2026/08/28/keyboard-driven-guis</id><content type="html" xml:base="https://ckardaris.com/blog/2026/08/28/keyboard-driven-guis.html"><![CDATA[<p>This post has made it to the Hacker News front page.
See the <a href="https://news.ycombinator.com/item?id=49479837">discussion</a>.</p>

<hr />

<p>Last week I came across a <a href="https://sockpuppet.org/blog/2026/08/20/stop-making-tuis/">post on <em>Hacker
News</em></a> that encouraged application
developers to stop making terminal user interfaces<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>
(a.k.a. TUIs) and instead focus on graphical user interfaces (a.k.a. GUIs).
The post reached the HN front page and sparked a lively debate in the
<a href="https://news.ycombinator.com/item?id=49384210">comments</a> section.</p>

<p>I think there is merit in both sides of the debate.
On one hand, I understand the GUI-positive arguments of the post author.
In theory, the capabilities of GUI application frameworks are a superset of the capabilities of
their TUI counterparts, so they should be preferred.
On the other hand, as a heavy <em>terminal user</em>, I <em>also</em> greatly appreciate all TUIs that allow me to
“stay” in the terminal and fulfill all my needs.</p>

<p>But I want to oppose a recurring argument in favor of TUIs that in my opinion does not have a solid
foundation<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>.
To paraphrase various commenters:</p>

<blockquote>
  <p>TUIs should be preferred because they are keyboard-driven.</p>
</blockquote>

<p>While it’s true that if you randomly pick a GUI and a TUI application, the latter is more probable
to be fully keyboard-driven, this does not tip the scale in favor of developing TUIs over GUIs<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>.
What it does is highlight the inadequacies of keyboard navigation in many GUI applications.</p>

<p>There is nothing preventing a GUI from being fully keyboard-driven<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup> just like — or even better
than — a TUI.
In fact, many GUI framework application guidelines explicitly encourage GUI application developers
to provide support for keyboard-driven navigation that covers the whole functionality of the
application.</p>

<p>For example, the GNOME Human Interface Guidelines state that <a href="https://developer.gnome.org/hig/guidelines/keyboard.html#:~:text=Just%20as%20it%20should%20be%20possible%20to%20perform%20every%20action%20with%20a%20pointing%20device%2C%20every%20action%20should%20also%20be%20possible%20with%20the%20keyboard"><em>just as it should be possible to perform every
action with a pointing device, every action should also be possible with the
keyboard</em></a>
and that <a href="https://developer.gnome.org/hig/guidelines/keyboard.html#:~:text=It%20should%20be%20possible%20to%20move%20around%20and%20interact%20with%20every%20part%20of%20your%20user%20interface%20using%20the%20keyboard"><em>it should be possible to move around and interact with every part of your user interface
using the
keyboard</em></a>.</p>

<p>This resonates with me as a user.
Being able to intuitively — and predictably — navigate around a GUI application with only my
keyboard gives me more incentive to choose it compared to its alternatives.</p>

<p>Knowing that, and when wearing my developer hat, I have to make sure that my
applications are keyboard-friendly.
For my first ever GUI application, <a href="https://gitlab.com/ckardaris/klisi">Klisi</a>, I invested some time
to implement keyboard shortcuts targeting the whole range of available actions.</p>

<p>Keyboard navigation is not that hard to achieve in most cases and results in an overall better user
experience.
It is not a matter of feasibility, but a matter of will on the application developer’s part.</p>

<p>The takeaway is simple.
Do not compromise on the user experience you provide with your application.
Strive to make it as intuitive as possible.
To that end, enabling full keyboard navigation should not be ignored.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>Or alternatively <a href="https://en.wikipedia.org/wiki/Text-based_user_interface">text-based user
interfaces</a>. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>The original post author mentions this as well. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>There are other more compelling arguments towards tipping that scale, for example ease of
portability. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>Of course, the dexterity achieved via mouse is still preferred — or even required — for some
tasks. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Charalampos Kardaris</name></author><category term="blog" /><category term="programming" /><summary type="html"><![CDATA[GUI applications should be fully driven by the keyboard using shortcuts]]></summary></entry><entry><title type="html">New font</title><link href="https://ckardaris.com/blog/2026/08/27/new-font.html" rel="alternate" type="text/html" title="New font" /><published>2026-08-27T00:00:00+00:00</published><updated>2026-08-27T00:00:00+00:00</updated><id>https://ckardaris.com/blog/2026/08/27/new-font</id><content type="html" xml:base="https://ckardaris.com/blog/2026/08/27/new-font.html"><![CDATA[<p>I saw the <a href="https://github.com/iaolo/iA-Fonts/tree/master/iA%20Writer%20Quattro">iA Writer Quattro</a>
font and I liked it.</p>

<p>So far, I was not explicitly using a font-family for this website and instead relied on the
default <em>sans-serif</em> font provided by the browser.
I thought it is time to change that and present a more unified experience for all readers.</p>

<p>What do you think?</p>]]></content><author><name>Charalampos Kardaris</name></author><category term="blog" /><category term="website" /><summary type="html"><![CDATA[I am trying out a new font for my website, the iA Writer Quattro]]></summary></entry><entry><title type="html">Klisi</title><link href="https://ckardaris.com/blog/2026/08/15/klisi.html" rel="alternate" type="text/html" title="Klisi" /><published>2026-08-15T00:00:00+00:00</published><updated>2026-08-15T00:00:00+00:00</updated><id>https://ckardaris.com/blog/2026/08/15/klisi</id><content type="html" xml:base="https://ckardaris.com/blog/2026/08/15/klisi.html"><![CDATA[<blockquote>
  <p>From Ancient Greek κλῆσις (klêsis), related to καλέω (kaléō, “I call”).</p>
</blockquote>

<p>After around 3 months of effort, I have finally released the first version of
<a href="https://gitlab.com/ckardaris/klisi">Klisi</a>.</p>

<p>Klisi is the result of a desire to fill a small gap in the available
application offering for the <a href="https://www.gnome.org/">GNOME</a> desktop
environment and a recent interest in the <a href="https://clojure.org">Clojure</a>
programming language.</p>

<p>I wanted to develop a <strong>callgrind profile viewer</strong> that better integrates with GNOME and can be used as
a simplified alternative to <a href="https://kcachegrind.github.io">KCacheGrind</a>.</p>

<blockquote>
  <p>Have you heard enough already? <a href="https://gitlab.com/ckardaris/klisi#installation">Install</a> and try
out the application.
You can come back for the rest of the write-up and leave a comment with your thoughts.</p>
</blockquote>

<p>It all started around one year ago.</p>

<p>After more than 7 years of using <a href="https://i3wm.org/">i3</a> as my window manager —
through the end of my university years and the start of my professional career
— I decided that it was time to embrace
<a href="https://wayland.freedesktop.org/">Wayland</a>.
The obvious choice would be to continue using another tiling window manager.
<a href="https://swaywm.org/">sway</a> would be the prime candidate.
But I was also eyeing a more complete solution.
Tiling window managers are great, but they require a lot more manual
configuration to have a fully functioning system.
I am grateful for what I learned in the process, but I was ready to off-load
some of that work to someone else.</p>

<p>So, I switched to GNOME.
I had always liked the aesthetics of its applications, so having them integrate
natively with the whole environment appealed to me.
I had also noticed that I could translate my i3 workflow directly to GNOME
without any significant changes.
I was already using most windows maximized and delegating to
<a href="https://tmux.app/">tmux</a> for my terminal tiling needs.
Scriptability is a little trickier in GNOME than in i3, but GNOME would handle
most of my needs now anyway.</p>

<p>This was last November.</p>

<p>Some time after that — in April — I saw on <a href="https://news.ycombinator.com/">Hacker News</a> the announcement for the <a href="https://clojure.org/about/documentary">Clojure documentary</a>.
People seemed excited about it, so I decided to watch it.
Going in, I had zero knowledge about Clojure.
I did not know anything about <a href="https://en.wikipedia.org/wiki/Rich_Hickey">Rich Hickey</a>.
I did not know anything about the philosophy of the language.
I had only read about it in various post titles, but had never gotten down to study it and use it.</p>

<p>The documentary was engaging enough and it piqued my interest.
I wanted to try to learn another functional programming language after previous
<a href="https://www.haskell.org/">Haskell</a> adventures and this seemed like a nice
opportunity.
Clojure being a Lisp dialect made the whole premise even more appealing.</p>

<p>I started by reading all beginner-friendly documentation on the official
website and then went over <a href="https://www.braveclojure.com/clojure-for-the-brave-and-true">Clojure for the Brave and
True</a> trying to be
as hands-on as possible.</p>

<p>I was ready for the next step.
They say one of the best ways to learn and understand a programming language is
to use it in a project.
And this is exactly what I did.
I set out to combine Clojure and GNOME application development.</p>

<p>On the 2nd of May, I made the first commit of Klisi.
At the time, I had not yet settled on a name and I was only able to launch an
empty <a href="https://www.gtk.org/">GTK</a> window.
Today, the application can do a little more.
After a long development list, consisting of required features and other
ideas that came to my mind while developing, I believe it is good enough to
serve other people.</p>

<p>This brings us to today and the first release.</p>

<p>It was time to share my application with the world.
I hope that someone will find it useful and decide to make it a part of their
development workflow.</p>

<p>In any case, I am very satisfied by the result so far.
I have learned a lot and I will continue to do so, while I further explore
Clojure, GTK and <a href="https://flatpak.org/">Flatpak</a> development.
There is a long way to go and I believe all the lessons I will learn in the
process will be valuable one way or another.</p>

<p>This is it from me for now.
Try out Klisi and let me know what you think.
I will appreciate any well-meaning feedback.</p>]]></content><author><name>Charalampos Kardaris</name></author><category term="blog" /><category term="programming" /><summary type="html"><![CDATA[Klisi is a GTK4/Adwaita callgrind profile viewer and generator]]></summary></entry><entry><title type="html">New address, new adventures</title><link href="https://ckardaris.com/blog/2026/07/27/new-address.html" rel="alternate" type="text/html" title="New address, new adventures" /><published>2026-07-27T00:00:00+00:00</published><updated>2026-07-27T00:00:00+00:00</updated><id>https://ckardaris.com/blog/2026/07/27/new-address</id><content type="html" xml:base="https://ckardaris.com/blog/2026/07/27/new-address.html"><![CDATA[<p>I had been thinking of buying my own domain to host this website for a while.
I was always putting it off, since it was not a priority or a strict necessity.
But, lately, I reached a tipping point.</p>

<p>I have been working on a side project that requires setting an application identifier.
From the beginning I knew that I wanted to go with <code class="language-plaintext highlighter-rouge">com.ckardaris.&lt;Appname&gt;</code> and not share “naming
custody” with GitHub.
You don’t <em>really</em> need to own a domain to use it as part of an application identifier, but it makes
verification with some platforms possible.</p>

<p>So, I bit the bullet and moved forward with my plans.</p>

<p><code class="language-plaintext highlighter-rouge">ckardaris.com</code> is now <strong>live</strong>.</p>

<p>The whole process was quite quick. It took a couple hours reading the required documentation and
setting everything up.</p>

<p>I registered the domain via <a href="https://porkbun.com/">porkbun</a>.</p>

<p>I <a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/verifying-your-custom-domain-for-github-pages">verified the new
domain</a>
for GitHub Pages to avoid any future surprises and added any additional DNS records needed.</p>

<p><del>I created a second static website repository on GitHub and pushed the existing repository to it.
As the first order of business (i.e. first commit) I made sure to fix any “stale” links pointing to
the old <code class="language-plaintext highlighter-rouge">ckardaris.github.io</code> domain to now point to the new one.</del></p>

<p><del>Finally, I edited the old static site to redirect to the new one automatically and that was
all<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>.</del></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nt">&lt;script&gt;</span>
    <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">https://ckardaris.com</span><span class="dl">"</span> <span class="o">+</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">pathname</span> <span class="o">+</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">search</span> <span class="o">+</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">hash</span><span class="p">;</span>
  <span class="nt">&lt;/script&gt;</span>
  <span class="nt">&lt;noscript&gt;</span>
    <span class="nt">&lt;meta</span> <span class="na">http-equiv=</span><span class="s">"refresh"</span> <span class="na">content=</span><span class="s">"0; url=https://ckardaris.com{{ page.url }}"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;/noscript&gt;</span>
</code></pre></div></div>

<p><em>UPDATE</em>: It turns out I did not read the GitHub Pages documentation carefully enough.
Creating the second repository and the manual redirects were not needed.
By setting the custom domain in the original repository, GitHub handles the redirects from the
default domain (i.e. <code class="language-plaintext highlighter-rouge">ckardaris.github.io</code>), while properly returning the <code class="language-plaintext highlighter-rouge">301</code> status code in the
process.</p>

<p>This was my first ever domain purchase and I have to say everything went quite smoothly, so I am
satisfied.</p>

<p>That’s all for now. Watch this space for my side project update.</p>

<p>See you soon.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>Reading online I got the understanding that a proper redirect emitting the <code class="language-plaintext highlighter-rouge">301</code> status code
would be preferable in order to preserve any search engine ranking that I had. But, honestly, I
don’t think that will prove to be much of an issue. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Charalampos Kardaris</name></author><category term="blog" /><category term="website" /><summary type="html"><![CDATA[This website has migrated to ckardaris.com]]></summary></entry><entry><title type="html">The P in PGP isn’t for pain: encrypting emails in the browser</title><link href="https://ckardaris.com/blog/2026/02/07/encrypted-email.html" rel="alternate" type="text/html" title="The P in PGP isn’t for pain: encrypting emails in the browser" /><published>2026-02-07T00:00:00+00:00</published><updated>2026-02-07T00:00:00+00:00</updated><id>https://ckardaris.com/blog/2026/02/07/encrypted-email</id><content type="html" xml:base="https://ckardaris.com/blog/2026/02/07/encrypted-email.html"><![CDATA[<p>In my previous post, I described how I implemented an asynchronous, <a href="/blog/2026/01/22/my-comments-run-on-email.html">email-based comment system</a> for this blog that runs without a true backend.
Every comment is an email sent to a specified address.
After each email is processed, all information is stored publicly in a GitHub repository.</p>

<p>One of the limitations of such a system is user authentication.
Each user needs to send a password alongside each comment that serves to authenticate the user
against their email.
This is needed because <em>email spoofing</em> is a thing and identity theft is not a joke; even if the
stakes are low in the context of blog comments.</p>

<p>In that previous post, I acknowledged that sending a password in plain text via email is not best
practice and I encouraged the usage of encryption.
For this purpose, I provided a <a href="/pgp-public-key.txt">public PGP key</a> that can be used to encrypt any
email and can only be decrypted by me, the owner of the secret key.</p>

<h1 id="problem">Problem</h1>

<p>The problem with this suggestion is that, even though it’s technically sound and legitimate, it is
not straightforward to set up.
Many email clients do not even support PGP encryption and, dare I say, even most technical people do
not have it set up by default.
For non-technical users, configuring everything properly would certainly be a test of computer
literacy.</p>

<p>This is not good enough, and certainly not welcoming or inclusive.</p>

<h1 id="solution">Solution</h1>

<p>Shortly after I published the first version of the comment system I came up with a question.</p>

<blockquote>
  <p>I already have a public PGP key published and ready to be used.
Is there a way to encrypt the comment payload on the browser, including the password and all other
data?</p>
</blockquote>

<p>I wouldn’t be writing this post if the answer was negative.</p>

<p>After a quick web search, I came across <a href="https://openpgpjs.org/">openPGP.js</a>, a JavaScript library
maintained by the <a href="https://proton.me/">Proton team</a>.
The goal of the library is to <em>“bypass the PGP installation requirement in every machine”</em>.
Just perfect for my use case.</p>

<p>I quickly set everything up.
I downloaded the JavaScript library files at the root of my website and created a couple of helper
functions.
The first to load the library on-demand to avoid unnecessary network transactions and the
second to produce a PGP encrypted message from a plain text string input.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">loadScript</span><span class="p">(</span><span class="nx">src</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nc">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="c1">// Prevent loading the same script twice</span>
        <span class="k">if </span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="s2">`script[src="</span><span class="p">${</span><span class="nx">src</span><span class="p">}</span><span class="s2">"]`</span><span class="p">))</span> <span class="p">{</span>
            <span class="nf">resolve</span><span class="p">();</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="kd">const</span> <span class="nx">script</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">script</span><span class="dl">'</span><span class="p">);</span>
        <span class="nx">script</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="nx">src</span><span class="p">;</span>
        <span class="nx">script</span><span class="p">.</span><span class="k">async</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
        <span class="nx">script</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="nx">resolve</span><span class="p">;</span>
        <span class="nx">script</span><span class="p">.</span><span class="nx">onerror</span> <span class="o">=</span> <span class="nx">reject</span><span class="p">;</span>
        <span class="nb">document</span><span class="p">.</span><span class="nx">head</span><span class="p">.</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">script</span><span class="p">);</span>
    <span class="p">});</span>
<span class="p">}</span>

<span class="k">async</span> <span class="kd">function</span> <span class="nf">encrypt</span><span class="p">(</span><span class="nx">message</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">publicKeyArmored</span> <span class="o">=</span> <span class="s2">`{% include pgp-public-key.txt %}`</span><span class="p">.</span><span class="nf">trim</span><span class="p">();</span>

    <span class="k">await</span> <span class="nf">loadScript</span><span class="p">(</span><span class="dl">'</span><span class="s1">/openpgp.min.js</span><span class="dl">'</span><span class="p">);</span>

    <span class="k">return</span> <span class="k">await</span> <span class="nb">window</span><span class="p">.</span><span class="nx">openpgp</span><span class="p">.</span><span class="nf">encrypt</span><span class="p">({</span>
        <span class="na">message</span><span class="p">:</span> <span class="k">await</span> <span class="nx">openpgp</span><span class="p">.</span><span class="nf">createMessage</span><span class="p">({</span> <span class="na">text</span><span class="p">:</span> <span class="nx">message</span> <span class="p">}),</span>
        <span class="na">encryptionKeys</span><span class="p">:</span> <span class="k">await</span> <span class="nb">window</span><span class="p">.</span><span class="nx">openpgp</span><span class="p">.</span><span class="nf">readKey</span><span class="p">({</span> <span class="na">armoredKey</span><span class="p">:</span> <span class="nx">publicKeyArmored</span> <span class="p">})</span>
    <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It’s simple.
And it works.</p>

<blockquote>
  <p><strong>Note:</strong> The comment encryption functionality is not available if <em>JavaScript</em> is disabled on the
client.</p>
</blockquote>

<p>For example, if you were to write a comment for this post using the <em>comment form</em> and provided the
following data:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>username: test_user
password: test_password
comment: Test comment.
</code></pre></div></div>
<p>the email to be sent would be pre-filled with the following body:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Your comment has been encrypted and is ready to be submitted.
...

-----BEGIN PGP MESSAGE-----

wV4Dyk92dA2JWC4SAQdAFgNHlb6Q7nAgovzVgUnB5CTOKBWSlXxSoIOzLUQl
Zm4wBdsLdUROxNS0xcTyja4iq9QUsSazv18DYbmRd2Lix3XoL4zDrQVOPVw6
ZGlkeDhi0qEByCityS59xWSuREVqgS4tpPlZg8Lz5Go6D2nSBu+1iTXw2dy6
L9eL5NZMoFhYUqocGukxBlEcHY6j1U+3Lstvc5dZPl+u7+Zh32uTzTo8CC05
doKNwH5AP9ybHk7nr2vVLdrede6vLCIAwGTK9fVYq2V1udXfSPNCESSeunh1
68qEaAA81mjCbSSfcB/RznpL8/nOLLWDXlXxfMPlYV8/+Q==
=k2VX
-----END PGP MESSAGE-----
</code></pre></div></div>
<p>Your comment data (username, password, message, etc.) is automatically encrypted without any
need for manual intervention.</p>

<h1 id="conclusion">Conclusion</h1>

<p>Based on the above proof of concept, I can say that, even without operating a “proper” backend
server, it is still possible to guarantee a reasonable level of security.
And you can do so without requiring any complicated setup on the user side.</p>

<p>To my eyes, this is a win for secure communication when email is used as the medium.
Do you agree?</p>]]></content><author><name>Charalampos Kardaris</name></author><category term="blog" /><category term="programming" /><category term="website" /><summary type="html"><![CDATA[How to receive encrypted messages via emails with openPGP.js]]></summary></entry><entry><title type="html">My comments run on email</title><link href="https://ckardaris.com/blog/2026/01/22/my-comments-run-on-email.html" rel="alternate" type="text/html" title="My comments run on email" /><published>2026-01-22T00:00:00+00:00</published><updated>2026-01-22T00:00:00+00:00</updated><id>https://ckardaris.com/blog/2026/01/22/my-comments-run-on-email</id><content type="html" xml:base="https://ckardaris.com/blog/2026/01/22/my-comments-run-on-email.html"><![CDATA[<p>Not all blogs support commenting.
Initially, I considered doing without them.
There are many places where one may share an article or blog post and start a discussion.
I frequent <a href="https://news.ycombinator.com/news"><em>Hacker News</em></a><sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> myself and I really appreciate the
breadth of opinions in some of the discussions.
Discussion can happen in other <em>spaces</em> as well, like <em>Reddit</em>, <em>LinkedIn</em><sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> and many more.</p>

<p>Despite the abundance of such <em>spaces</em>, where discussions can happen, I think it is convenient and
fitting — especially for blog posts — when the actual post and the readers’ comments live
together.</p>

<p>With that in mind, and feeling the urge to <a href="/blog/2026/01/10/techrastination.html">hack a little more on my website</a>, during the past weekend I set out to add support for comments in
this blog.</p>

<p>Doing so in a static blog poses a big <strong>challenge</strong>.
There is no actual server running that can handle requests and process data.
That means that you do not have the necessary infrastructure for user accounts.
The readers cannot log in and send their comments and there is no straightforward way to take those
comments and automatically post them online.</p>

<p>Another method is therefore needed to receive the comments and provide them as input to the static
site generator, in my case <a href="https://jekyllrb.com/">jekyll</a>.</p>

<p>One of the most frequently used solutions to this problem is <a href="https://giscus.app/">giscus</a>.
<em>giscus</em> is utilizing <em>GitHub</em> discussions to store comments for the different blog posts and can be
configured to automatically update your website when a new comment is created.</p>

<p>This approach is very appealing, but it has one disadvantage in my opinion.
The person who wants to comment needs to have a <em>GitHub</em> account.
This can be a blocker for non-technical people.</p>

<p>I wanted to use a different approach and I also wanted to make commenting accessible to as many
people as possible.
After some thinking I decided to use the most common technology I could think of.</p>

<p><strong>Email.</strong></p>

<p>Almost everyone using the internet has an email address.
By basing my comment system on email, I can make it accessible to a wide range of people.</p>

<p>The fact that every email address is unique allows to implement a kind of primitive authentication
setup.
I am saying primitive, because email is not secure by default, and by extension neither is any
system built on top of it.</p>

<p>It goes without saying that this system could never be used in production in security-sensitive
environments. That said, crazier things have happened.</p>

<h1 id="technical-overview">Technical overview</h1>

<p>At this point, it would be useful to go in a little more detail.
If you are not interested in the technical aspects of the comment system implementation you can skip
this section and go straight to the <a href="#final-thoughts">end</a>. You will miss all the fun, though.</p>

<p>So, how does it all work?</p>

<h2 id="requirements">Requirements</h2>

<p>Before implementing the comment system, I drafted some requirements:</p>

<ol>
  <li>All comments should be displayed with a visible <em>name</em><sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup> selected by the commenter and the
<em>date</em> they were made.</li>
  <li>A commenter should be able to change their visible <em>name</em>.</li>
  <li>A malicious actor should not be able<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup> to assume the identity of a commenter.</li>
  <li>Sending a comment should be as easy as possible and the comment system should be functional with
<em>JavaScript</em> disabled.</li>
  <li>Comment replies should be supported and presented in a nested tree structure, which should
support collapse and expansion of comment sub-trees.</li>
</ol>

<h2 id="data">Data</h2>

<p>The comment system is based on email, so it is logical that you send a comment by sending an email.
Every comment email contains all necessary information, so that the comment can be presented
correctly.
I decided to send the information in the <em>YAML</em> format, since <em>jekyll</em> is <em>YAML</em>-friendly.
Additionally, <em>YAML</em> compared to <em>JSON</em> can be more readable for multi-line data, like comments, so
it made it more fitting for this use case.</p>

<p>Every comment email sends in its body the following information.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">post</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span>
<span class="na">password</span><span class="pi">:</span>
<span class="na">repliesTo</span><span class="pi">:</span>
<span class="na">comment</span><span class="pi">:</span> <span class="pi">|-</span>
</code></pre></div></div>

<p>Sending passwords in plain text is almost always frowned upon.
But I believe this situation deserves some leeway.</p>

<ul>
  <li>The data transferred is not sensitive and proper care is taken to ensure confidentiality.</li>
  <li>This is an educational project with no hard security requirements.</li>
  <li>A password is good to be used as a measure against <a href="https://en.wikipedia.org/wiki/Email_spoofing">sender address
spoofing</a>.</li>
  <li>Email can always be hardened by using <a href="https://en.wikipedia.org/wiki/Pretty_Good_Privacy"><em>PGP
encryption</em></a><sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>:</li>
</ul>

<p>Upon receiving a comment email, some additional information can be obtained: <code class="language-plaintext highlighter-rouge">email_address</code> and <code class="language-plaintext highlighter-rouge">date</code>.</p>

<h2 id="implementation">Implementation</h2>

<p>Let’s see how we can satisfy the system requirements with these data.</p>

<blockquote>
  <p>All comments should be displayed with a visible name selected by the commenter and the
   <em>date</em> they were made.</p>
</blockquote>

<p>For this we use the <code class="language-plaintext highlighter-rouge">name</code> and <code class="language-plaintext highlighter-rouge">date</code> information from the actual comment email.</p>

<blockquote>
  <p>A commenter should be able to change their visible name.<br />
A malicious actor should not be able to easily assume the identity of a commenter.</p>
</blockquote>

<p>All information needed to generate this website is publicly stored in a <em>GitHub</em> repository.
This means that any personal information needs to be properly stored.</p>

<p>For each received comment email a <a href="https://en.wikipedia.org/wiki/SHA-2"><em>SHA256</em></a> message digest
(<em>hash</em>) is computed using the <code class="language-plaintext highlighter-rouge">email_address</code> and a
<a href="https://en.wikipedia.org/wiki/Salt_%28cryptography%29"><em>salt</em></a><sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>.</p>

<p>To check <em>name</em> availability, all <em>email hashes</em> that are <strong>different</strong> from the current one are
gathered and their linked <em>name</em> is compared to the requested one.
If no matches are found the <code class="language-plaintext highlighter-rouge">name</code> can be used.</p>

<p>An additional message digest is computed for the authentication of the commenter using the
<code class="language-plaintext highlighter-rouge">email_address</code>, the <code class="language-plaintext highlighter-rouge">password</code> and the <em>salt</em>.</p>

<p>This <em>authentication hash</em> is compared against the <em>authentication hash</em> of one of previous comment
emails that share the <strong>same</strong> <em>email hash</em><sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>.</p>

<p>In the event of any error, the commenter is informed with a response email.</p>

<blockquote>
  <p>Sending a comment should be as easy as possible and the comment system should be functional with
   <em>JavaScript</em> disabled.</p>
</blockquote>

<p>Commenters with <em>JavaScript</em> enabled can enjoy a streamlined user experience (UX).
Clicking on <u>add comment</u> or <u>reply</u> presents them with an <em>HTML</em> form with three input
fields: <em>name</em>, <em>password</em> and <em>comment</em>.
After filling them and clicking the <em>send</em> button, their preferred email client is launched and the
message is pre-filled for them and ready to be sent.</p>

<p>Commenters with <em>JavaScript</em> disabled still get a pre-filled email message, but they have to fill
the necessary information, while making sure to not break the formatting of the data.</p>

<p>Writing a comment using the <em>HTML</em> form with <em>JavaScript</em> enabled has two benefits:</p>

<ol>
  <li>Browsers are able to save the <em>name</em> and <em>password</em> fields for the commenter and pre-fill them in
the future.</li>
  <li>The password can be hashed in the email body. This does not improve security, but
protects against curious eyes.</li>
</ol>

<blockquote>
  <p>Comment replies should be supported and presented in a nested tree structure, which should
   support collapse and expansion of comment sub-trees.</p>
</blockquote>

<p>Using the <code class="language-plaintext highlighter-rouge">repliesTo</code> field of the comment email<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>, a tree hierarchy of messages is created.
This hierarchy is used to sort the comments properly and to create the necessary logic to collapse
and expand the respective sub-trees of comments.</p>

<h2 id="synchronization">Synchronization</h2>

<p>We are close to the end, but I have so far failed to mention one basic characteristic of the comment
system.
And this concerns its responsiveness, or lack thereof.</p>

<p>After receiving a comment email, there is, as of now, no automation set up to process the email and
post it on the blog.
Posting each comment requires manual intervention.
This makes the discussion asynchronous.
I think that this is not a big issue and does not look out of place considering the static nature of
the website.</p>

<p>Nevertheless, I leave the door open to automation.
Interacting with email is scriptable with programs like <a href="https://marlam.de/msmtp/"><em>msmtp</em></a>
and <a href="https://isync.sourceforge.io/"><em>isync</em></a>.
In the unlikely but welcome scenario that the volume of the received comment emails is too large to
handle manually, another weekend hacking project could automate the whole process.</p>

<h1 id="final-thoughts">Final thoughts</h1>

<p>Creating a comment system based on email has been a worthwhile experience.
I revisited some long-studied concepts and learned new ones.
It was a good exercise in balancing trade-offs which I believe is one of the most important aspects
of designing a system.</p>

<p>The final result leaves me satisfied, while also leaving room for improvements in the future.</p>

<p>And after all this work, I can finally say</p>
<blockquote>
  <p>Let me know what you think in the comments.</p>
</blockquote>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>The user interface (UI) of my comments is <em>heavily</em> inspired by HN comments. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>Even if too <em>corporatey</em> most of the time. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>Or <em>nickname</em>. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>Although, you can never be 100% secure. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5">
      <p>Here is the <a href="/pgp-public-key.txt">PGP public key</a>. <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7">
      <p>Storing message digests of <em>salted</em> data guarantees that, even though all stored data is
publicly available, it is not possible to find out which email addresses are stored and what
<em>name</em> each email is using. <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8">
      <p>If any, the rest of the comments from the same email address have the same <em>authentication
hash</em>. <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:9">
      <p>Root messages, of course, do not set this value. <a href="#fnref:9" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Charalampos Kardaris</name></author><category term="blog" /><category term="programming" /><category term="website" /><summary type="html"><![CDATA[My website comments setup using email as the backend.]]></summary></entry><entry><title type="html">Techrastination</title><link href="https://ckardaris.com/blog/2026/01/10/techrastination.html" rel="alternate" type="text/html" title="Techrastination" /><published>2026-01-10T00:00:00+00:00</published><updated>2026-01-10T00:00:00+00:00</updated><id>https://ckardaris.com/blog/2026/01/10/techrastination</id><content type="html" xml:base="https://ckardaris.com/blog/2026/01/10/techrastination.html"><![CDATA[<p>Have I made up a new word?
How do technology and procrastination go together?</p>

<p>Hacking with <strong><em>technology</em></strong> and <strong><em>procrastination</em></strong> may seem unrelated, but I believe that in
tandem they paint an accurate picture of how lots of people, myself included, interact with
technology in their free time.</p>

<p>When I am referring to <em>hacking</em>, I intend to convey the original meaning.
Wikipedia<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> phrases it quite nicely.</p>

<blockquote>
  <p>The act of engaging in activities (such as programming or other media) in a spirit of playfulness
and exploration is termed hacking. However, the defining characteristic of a hacker is not the
activities performed themselves (e.g. programming), but how it is done and whether it is
exciting and meaningful.</p>
</blockquote>

<p>The key words in the above segment are: <em>playfulness</em>, <em>exploration</em>, <em>excitement</em>,
<em>meaningfulness</em>.
I will come back to them shortly.</p>

<p>I guess most people are at least vaguely familiar with <strong><em>procrastination</em></strong>.
Let’s not mention any definition containing the words <em>laziness</em> or <em>carelessness</em>.
I prefer to keep it nice and neutral<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>.</p>

<blockquote>
  <p>The act of procrastinating; putting off or delaying or deferring an action to a later time.</p>
</blockquote>

<p>And if you are interested in the actual etymology, it can get a little more fun<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>.</p>

<blockquote>
  <p>From Latin prōcrāstinātiō, from prōcrāstinō (“procrastinate”), from prō + crāstinus (“of
tomorrow”), from crās (“tomorrow”).</p>
</blockquote>

<p>What is <strong><em>techrastination</em></strong> then? I will try to give a definition of my own.</p>

<blockquote>
  <p>The act of procrastinating by focusing on the technology associated with an activity rather than
 focusing on the activity itself.</p>
</blockquote>

<p>You may have an idea where I am going with this. I am gonna give some examples anyway.</p>

<p>The first one is quite relevant.</p>

<p>It’s when you spend hours upon hours tinkering with your blog, fixing the theme, checking the fonts,
trying to perfect the alignment of CSS elements and the margins of your titles and subtitles, yet
you are not working towards creating actual blog posts<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>.</p>

<p>It is also when you constantly rework your configuration files and scripts to increase your
productivity, when you are not actually working on something that you need to be productive in.</p>

<p>Or when you are more interested in the technologies you are using – programming language, stack,
tools – and you like to engage in heated arguments with people about them, instead of just focusing
on the actual target at hand, whatever that may be.</p>

<p>It would be safe to assume that we can think of many more similar examples.</p>

<p>I admit I have been <em>guilty</em> of all these to a lesser or greater degree.</p>

<p>But, is guilty the right word?
Should there be a negative tone when we are describing such activity?
Shouldn’t we <em>always</em> try to be as productive as possible?</p>

<p><strong>Not really.</strong></p>

<p>Let’s go back to the key words of the <em>hacking</em> definition above.</p>

<p><strong>Playfulness</strong>.
Play is not just for kids.
Having some amount of play in our lives is <a href="https://www.psych.on.ca/Public/Blog/2025/Playfulness-and-Play-for-Adults">beneficial to our
well-being</a>.
This matches my experience while <em>hacking</em>. When friends or family ask me how I find satisfaction
from my computer projects, I usually respond that it feels like playing.</p>

<p><strong>Exploration</strong>.
It is said that repetition is the mother of learning, but I believe that exploration is an equally
important part of knowledge acquisition.
We don’t have to constantly innovate.
But we must test the limits of our knowledge to expand it and savor its benefits in the form of
<a href="https://psychologyfanatic.com/gaining-knowledge/">personal and professional growth</a>.</p>

<p><strong>Excitement</strong>.
Who involved with programming and technology in general has not felt immense satisfaction after
completing a task?
Or even without completing it.
Completing the task is not even a requirement.
Being <em>in the flow</em> comes with its own set of satisfying emotions.
The actual end product does not hold all value.
It may not even be that useful, if we were to strictly evaluate it.
But this excitement is very important.
It encourages further exploration that leads to more play that leads to more excitement.</p>

<p><strong>Meaningfulness</strong>.
Now we are entering tricky territory.
So far I have argued that engaging in <em>techrastinating</em> activities can be useful without the need
to find an actual meaning.
That statement does not have to be betrayed.
The actual meaning does not derive from the usefulness of an activity. It comes from the effect it
has on the acting individual.
If an activity satisfies us, then it is meaningful.</p>

<p>All this sounds extremely positive. What about the dark side? I would argue that the dark side is
not strictly related to technology activities. Any activity can have a negative impact, when it is
exercised without measure and it begins to affect other parts of our life (e.g. relationships or health).
A general balance should always be maintained.</p>

<p>To sum it all up, hacking with your computer, or actively engaging with technology in general, does
not need to have a purpose.
The experience itself can be immensely rewarding and there are real benefits to be had, even if not
immediately visible to the naked eye.</p>

<p>Whether the word sticks or not, if I had to give one piece of advice, it would be the following.</p>

<blockquote>
  <p>Do not worry about the outcome. <strong><em>Techrastinate</em></strong>.</p>
</blockquote>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>See <a href="https://en.wikipedia.org/wiki/Hacker_culture">hacker culture</a>. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>You can check out some more definitions <a href="https://www.wordnik.com/words/procrastination">here</a>.
The basic theme is the same anyway. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>Who says that about etymology? Well, people like me. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>With the post you are currently reading I am resetting once again the procrastination counter.
<span class="color-magenta" style="font-weight:bold">Success</span>. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Charalampos Kardaris</name></author><category term="blog" /><category term="life" /><category term="programming" /><category term="technology" /><summary type="html"><![CDATA[When hacking meets procrastination]]></summary></entry><entry><title type="html">Template files with Nix Home Manager</title><link href="https://ckardaris.com/blog/2025/12/29/template-files-with-nix-home-manager.html" rel="alternate" type="text/html" title="Template files with Nix Home Manager" /><published>2025-12-29T00:00:00+00:00</published><updated>2025-12-29T00:00:00+00:00</updated><id>https://ckardaris.com/blog/2025/12/29/template-files-with-nix-home-manager</id><content type="html" xml:base="https://ckardaris.com/blog/2025/12/29/template-files-with-nix-home-manager.html"><![CDATA[<h1 id="backstory">Backstory</h1>

<p>It’s been a while since my last post and I did not want to leave 2025 behind without making any.</p>

<p>During the past year I have been gradually migrating my development and configuration file setup to
<a href="https://github.com/nix-community/home-manager">Nix Home Manager</a> (aka <code class="language-plaintext highlighter-rouge">home-manager</code>).</p>

<p>This has not been a quick transition, but I am satisfied with the result and I believe it was worth
it.</p>

<p>I note the following benefits:</p>

<ol>
  <li>
    <p>It enables me to handle all changes (program versions and configuration files) from a single
directory.
A single source of truth that can be easily tracked by <code class="language-plaintext highlighter-rouge">git</code>.</p>
  </li>
  <li>
    <p>I am a fan of Nix reproducible environments and builds and I considered using <code class="language-plaintext highlighter-rouge">home-manager</code> a
nice opportunity to familiarize myself with the language.</p>
  </li>
  <li>
    <p>Nix packages enable me to create a predictable development environment across different machines
(e.g. home, work, etc.), irrespective of the underlying operating system and the packages that it
provides by default.</p>
  </li>
</ol>

<p>I finally finished the migration last month, so I thought it is a good idea to share the interesting
bits of my current setup.
Maybe someone will find it useful.</p>

<p>I don’t know how many posts I will make about it, but let’s get started.</p>

<h1 id="template-files">Template files</h1>
<p>When sharing a <code class="language-plaintext highlighter-rouge">home-manager</code> configuration between multiple machines, a common scenario is to have
a file that needs to be slightly different in each machine.</p>

<p>For example, in my <code class="language-plaintext highlighter-rouge">vim</code> configuration, I want to configure the number of async workers that
<code class="language-plaintext highlighter-rouge">clangd</code> will use when launched by the <code class="language-plaintext highlighter-rouge">LSP</code> server.
In my laptop which has only 4 cores, I want to use 3 of them.
In my work computer that has 20, I want to use more (e.g. 16).</p>

<p>How can we utilize <code class="language-plaintext highlighter-rouge">home-manager</code> to automate this customization?</p>

<h1 id="baseline">Baseline</h1>

<p>This post does not provide an introduction to <code class="language-plaintext highlighter-rouge">home-manager</code>.
Nevertheless, we need to agree on some necessary baseline knowledge.</p>

<p><code class="language-plaintext highlighter-rouge">home-manager</code> requires a configuration file <code class="language-plaintext highlighter-rouge">home.nix</code> that typically lies in the
<code class="language-plaintext highlighter-rouge">$HOME/.config/home-manager</code> directory.
In <code class="language-plaintext highlighter-rouge">home.nix</code> we can define the files that are generated via <code class="language-plaintext highlighter-rouge">home-manager</code> by populating the
<code class="language-plaintext highlighter-rouge">home.file</code><sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> <a href="https://nix.dev/manual/nix/2.24/language/syntax#attrs-literal">attribute set</a>.</p>

<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
  <span class="nv">pkgs</span><span class="p">,</span>
  <span class="nv">lib</span><span class="p">,</span>
  <span class="o">...</span>
<span class="p">}:</span>
<span class="p">{</span>
  <span class="c"># Other home-manager configuration options</span>
  <span class="o">...</span>

  <span class="c"># Files</span>
  <span class="nv">home</span><span class="o">.</span><span class="nv">file</span> <span class="o">=</span> <span class="p">{</span>
    <span class="c"># Key-value file entries</span>
  <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Every “key-value” entry in this attribute set corresponds to a file that we want to generate in our
<code class="language-plaintext highlighter-rouge">$HOME</code> directory.</p>

<p>For example, if we want to generate the <code class="language-plaintext highlighter-rouge">$HOME/.bashrc</code> file, we need to add the
corresponding entry in the <code class="language-plaintext highlighter-rouge">home.file</code> attribute set.</p>

<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="o">...</span>
  <span class="nv">home</span><span class="o">.</span><span class="nv">file</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s2">".bashrc"</span> <span class="o">=</span> <span class="p">{</span>
      <span class="c"># File options</span>
    <span class="p">};</span>
  <span class="p">};</span>
  <span class="o">...</span>
</code></pre></div></div>

<p>The file options attribute set fully configures the generation of each file.
This includes the contents, setting the executable bit, any actions to perform when the file changes
and more.</p>

<p>This post only deals with the contents of the generated files.
For this we have two options:</p>

<ul>
  <li>the <code class="language-plaintext highlighter-rouge">source</code> attribute, which is a <a href="https://nix.dev/tutorials/nix-language.html#file-system-paths">Nix
path</a> to a file whose content will
populate the <code class="language-plaintext highlighter-rouge">home-manager</code> generated file
    <div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">home</span><span class="o">.</span><span class="nv">file</span> <span class="o">=</span> <span class="p">{</span>
  <span class="s2">".bashrc"</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nv">source</span> <span class="o">=</span> <span class="sx">/.</span> <span class="o">+</span> <span class="s2">"&lt;absolute-path-to-source-file&gt;"</span><span class="p">;</span>
  <span class="p">};</span>
<span class="p">};</span>
</code></pre></div>    </div>
    <p>or</p>
  </li>
  <li>the <code class="language-plaintext highlighter-rouge">text</code> attribute, which  is a <a href="https://nix.dev/manual/nix/2.19/language/values#type-string">Nix
string</a> defining the content of the
<code class="language-plaintext highlighter-rouge">home-manager</code> generated file
    <div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">home</span><span class="o">.</span><span class="nv">file</span> <span class="o">=</span> <span class="p">{</span>
  <span class="s2">".bashrc"</span> <span class="o">=</span> <span class="p">{</span>
    <span class="nv">text</span> <span class="o">=</span> <span class="s2">''</span><span class="err">
</span><span class="s2">      # This is the content of the $HOME/.bashrc file.</span><span class="err">
</span><span class="s2">    ''</span><span class="p">;</span>
  <span class="p">};</span>
<span class="p">};</span>
</code></pre></div>    </div>
  </li>
</ul>

<h1 id="implementation">Implementation</h1>

<p>We can create a templating layer on top of <code class="language-plaintext highlighter-rouge">home-manager</code> and programmatically
generate files with parametrizable content.</p>

<p>How do we do that?
We can use a template engine like <a href="https://github.com/pallets/jinja">jinja</a>.</p>

<p>For the purposes of my configuration I am using
<a href="https://github.com/mattrobenolt/jinja2-cli">jinja2-cli</a>, because I want to easily invoke <code class="language-plaintext highlighter-rouge">jinja</code>
from the command line.</p>

<p>Let’s take it step by step.</p>

<p>1. <strong>Decouple</strong> templating layer from the actual <code class="language-plaintext highlighter-rouge">home.file</code> attribute set.</p>

<p>We create an attribute set that mirrors the structure of the <code class="language-plaintext highlighter-rouge">home.file</code>. We do not want to deviate
too much from the actual “structure” that <code class="language-plaintext highlighter-rouge">home-manager</code> expects, so that the configuration and
programming logic is simpler.</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nv">files</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s2">".bashrc"</span> <span class="o">=</span> <span class="p">{};</span>
    <span class="s2">".inputrc"</span> <span class="o">=</span> <span class="p">{};</span>
  <span class="p">};</span>
</code></pre></div></div>

<p>2. <strong>Modify</strong> each “key-value” file entry using <code class="language-plaintext highlighter-rouge">builtins.mapAttrs</code><sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>.</p>

<p>Currently the values of the file entries are empty.
We need to populate these attribute sets, so that they are valid <code class="language-plaintext highlighter-rouge">home-manager</code> file entries.</p>

<p>As already mentioned, to set the file content, we need to define either the <code class="language-plaintext highlighter-rouge">source</code> or the <code class="language-plaintext highlighter-rouge">text</code>
attribute.</p>

<p>For <strong>non-template files</strong> the <code class="language-plaintext highlighter-rouge">map</code> function is simple:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nv">makeSimple</span> <span class="o">=</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">mapAttrs</span> <span class="p">(</span>
    <span class="nv">path</span><span class="p">:</span> <span class="nv">options</span><span class="p">:</span> <span class="p">{</span>
      <span class="nv">source</span> <span class="o">=</span> <span class="sx">/.</span> <span class="o">+</span> <span class="s2">"&lt;absolute-path-to-config-files&gt;/</span><span class="si">${</span><span class="nv">path</span><span class="si">}</span><span class="s2">"</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">);</span>
</code></pre></div></div>

<p>You may wonder what this <code class="language-plaintext highlighter-rouge">&lt;absolute-path-to-config-files&gt;</code> is.</p>

<p>In my configuration, I have created a directory inside <code class="language-plaintext highlighter-rouge">$HOME/.config/home-manager</code> that mirrors the
directory structure of the <code class="language-plaintext highlighter-rouge">$HOME</code> directory.
This allows referring to the <code class="language-plaintext highlighter-rouge">home-manager</code> source file and the actual generated location with the
same relative path and permits using <code class="language-plaintext highlighter-rouge">lib.mapAttrs</code> to modify the initial <code class="language-plaintext highlighter-rouge">files</code> attribute set.</p>

<p>For <strong>template files</strong> we need to provide some more information (i.e. the <code class="language-plaintext highlighter-rouge">jinja</code> data).
We do so by adding a <code class="language-plaintext highlighter-rouge">data</code> attribute.</p>

<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nv">files</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s2">".bashrc"</span> <span class="o">=</span> <span class="p">{};</span>
    <span class="s2">".inputrc"</span> <span class="o">=</span> <span class="p">{};</span>
    <span class="s2">".vimrc"</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s2">"data"</span> <span class="o">=</span> <span class="s2">"&lt;absolute-path-to-json-data-file&gt;"</span><span class="p">;</span>
    <span class="p">};</span>
  <span class="p">};</span>
</code></pre></div></div>

<p>Now, we define the <code class="language-plaintext highlighter-rouge">map</code> function.</p>

<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nv">makeTemplates</span> <span class="o">=</span> <span class="kr">builtins</span><span class="o">.</span><span class="nv">mapAttrs</span> <span class="p">(</span>
    <span class="nv">path</span><span class="p">:</span> <span class="nv">options</span><span class="p">:</span> <span class="p">{</span>
      <span class="nv">text</span> <span class="o">=</span> <span class="nv">template</span> <span class="p">{</span>
        <span class="nv">src</span> <span class="o">=</span> <span class="sx">/.</span> <span class="o">+</span> <span class="s2">"&lt;absolute-path-to-config-files&gt;/</span><span class="si">${</span><span class="nv">path</span><span class="si">}</span><span class="s2">"</span><span class="p">;</span>
        <span class="nv">data</span> <span class="o">=</span> <span class="sx">/.</span> <span class="o">+</span> <span class="s2">"</span><span class="si">${</span><span class="nv">options</span><span class="o">.</span><span class="nv">data</span><span class="si">}</span><span class="s2">"</span><span class="p">;</span>
      <span class="p">};</span>
    <span class="p">}</span>
  <span class="p">);</span>
</code></pre></div></div>

<p>We can see that we rely on the output of a helper function: <code class="language-plaintext highlighter-rouge">template</code>. This function in its
simplest form<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup> takes two paths as arguments:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">src</code> is the path to the template file.</li>
  <li><code class="language-plaintext highlighter-rouge">data</code> is the path to a file containing data in <code class="language-plaintext highlighter-rouge">JSON</code> format.</li>
</ol>

<p>The return value of this function has to be a Nix string that is then used as the value of the
<code class="language-plaintext highlighter-rouge">text</code> attribute.</p>

<p>We define the <code class="language-plaintext highlighter-rouge">template</code> function as follows:</p>

<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nv">template</span> <span class="o">=</span>
    <span class="p">{</span> <span class="nv">src</span><span class="p">,</span> <span class="nv">data</span> <span class="p">}:</span>
    <span class="kd">let</span>
      <span class="nv">out</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">runCommand</span> <span class="s2">""</span> <span class="p">{</span> <span class="nv">buildInputs</span> <span class="o">=</span> <span class="p">[</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">jinja2-cli</span> <span class="p">];</span> <span class="p">}</span> <span class="s2">''</span><span class="err">
</span><span class="s2">        # If jinja2 fails (e.g. missing keys), just use the input file unchanged.</span><span class="err">
</span><span class="s2">        jinja2 --format=json </span><span class="si">${</span><span class="nv">src</span><span class="si">}</span><span class="s2"> &lt;&lt;&lt; "$(&lt; </span><span class="si">${</span><span class="nv">data</span><span class="si">}</span><span class="s2">)" -o $out 2&gt;/dev/null || cp </span><span class="si">${</span><span class="nv">src</span><span class="si">}</span><span class="s2"> $out</span><span class="err">
</span><span class="s2">      ''</span><span class="p">;</span>
    <span class="kn">in</span>
    <span class="kr">builtins</span><span class="o">.</span><span class="nv">readFile</span> <span class="nv">out</span><span class="p">;</span>
</code></pre></div></div>

<p>How does the <code class="language-plaintext highlighter-rouge">template</code> function work?</p>

<p>The function uses <code class="language-plaintext highlighter-rouge">pkgs.runCommand</code><sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup> to create a <a href="https://nix.dev/manual/nix/2.22/language/derivations">Nix
derivation</a>.
In our use case we only want to create one single file that we can then read from.</p>

<ul>
  <li>We run <code class="language-plaintext highlighter-rouge">jinja2</code> on the <code class="language-plaintext highlighter-rouge">src</code> file using the <code class="language-plaintext highlighter-rouge">data</code> in <code class="language-plaintext highlighter-rouge">json</code> format.</li>
  <li>We write the output to the <code class="language-plaintext highlighter-rouge">$out</code> file (<strong>note:</strong> the <code class="language-plaintext highlighter-rouge">$out</code> variable is used internally by Nix and points
 to the generated derivation file).<br />
 If anything fails during the <code class="language-plaintext highlighter-rouge">jinja2</code> call we copy the original template file content to the
 output file directly.</li>
  <li>Finally, we read the output file and return its content as a Nix string.</li>
</ul>

<p>3. <strong>Merge</strong> simple and template files in one attribute set.</p>

<p>We are almost done.
We need to map <strong>non-template files</strong> with the <code class="language-plaintext highlighter-rouge">makeSimple</code> function and <strong>template files</strong> with the
<code class="language-plaintext highlighter-rouge">makeTemplate</code> function.
The differentiating factor is the presence of the <code class="language-plaintext highlighter-rouge">data</code> attribute and we use that to create a
filter.</p>

<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nv">simpleFile</span> <span class="o">=</span> <span class="p">(</span><span class="nv">lib</span><span class="o">.</span><span class="nv">filterAttrs</span> <span class="p">(</span><span class="nv">path</span><span class="p">:</span> <span class="nv">options</span><span class="p">:</span> <span class="o">!</span><span class="nv">options</span> <span class="o">?</span> <span class="nv">data</span><span class="p">)</span> <span class="nv">files</span><span class="p">);</span>
  <span class="nv">templateFiles</span> <span class="o">=</span> <span class="p">(</span><span class="nv">lib</span><span class="o">.</span><span class="nv">filterAttrs</span> <span class="p">(</span><span class="nv">path</span><span class="p">:</span> <span class="nv">options</span><span class="p">:</span> <span class="nv">options</span> <span class="o">?</span> <span class="nv">data</span><span class="p">)</span> <span class="nv">files</span><span class="p">);</span>

  <span class="nv">result</span> <span class="o">=</span> <span class="nv">makeSimple</span> <span class="nv">simpleFiles</span> <span class="o">//</span> <span class="nv">makeTemplates</span> <span class="nv">templateFiles</span><span class="p">;</span>
</code></pre></div></div>

<p>4. <strong>Assign</strong> the resulting attribute set to <code class="language-plaintext highlighter-rouge">home.file</code>.</p>

<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
  <span class="nv">pkgs</span><span class="p">,</span>
  <span class="nv">lib</span><span class="p">,</span>
  <span class="o">...</span>
<span class="p">}:</span>
<span class="kd">let</span>
  <span class="nv">files</span> <span class="o">=</span> <span class="o">...</span><span class="p">;</span>

  <span class="nv">template</span> <span class="o">=</span> <span class="o">...</span><span class="p">;</span>

  <span class="nv">makeSimple</span> <span class="o">=</span> <span class="o">...</span><span class="p">;</span>
  <span class="nv">makeTemplate</span> <span class="o">=</span> <span class="o">...</span><span class="p">;</span>

  <span class="nv">simpleFiles</span> <span class="o">=</span> <span class="o">...</span><span class="p">;</span>
  <span class="nv">templateFiles</span> <span class="o">=</span> <span class="o">...</span><span class="p">;</span>

  <span class="nv">result</span> <span class="o">=</span> <span class="o">...</span><span class="p">;</span>
<span class="kn">in</span>
<span class="p">{</span>
  <span class="c"># Other home-manager configuration options.</span>
  <span class="o">...</span>

  <span class="c"># Files</span>
  <span class="nv">home</span><span class="o">.</span><span class="nv">file</span> <span class="o">=</span> <span class="nv">result</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>That’s it. We have successfully configured <code class="language-plaintext highlighter-rouge">home-manager</code> to generate files from templates.</p>

<h1 id="in-practice">In practice</h1>

<p>At the <a href="#template-files">beginning</a> of this post I mentioned how one my use cases for template files
is specifying a different number of async workers for <code class="language-plaintext highlighter-rouge">clangd</code> in my <code class="language-plaintext highlighter-rouge">LSP</code> configuration in <code class="language-plaintext highlighter-rouge">vim</code>.</p>

<p>How does this look like based on the described setup?</p>

<p><code class="language-plaintext highlighter-rouge">$HOME/.config/home-manager/home.nix</code>:</p>
<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="o">...</span>
  <span class="nv">files</span> <span class="o">=</span> <span class="p">{</span>
    <span class="o">...</span>
    <span class="s2">".vim/variables.vim"</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nv">data</span> <span class="o">=</span> <span class="s2">"/home/&lt;username&gt;/.local/share/home-mananger/data.json"</span><span class="p">;</span>
    <span class="p">};</span>
    <span class="o">...</span>
  <span class="p">};</span>
  <span class="o">...</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">$HOME/.config/home-manager/files/.vim/variables.vim</code>:</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>autocmd <span class="nb">User</span> LspSetup <span class="k">call</span> LspAddServer<span class="p">([</span>
<span class="se">    \</span>   #<span class="p">{</span>
<span class="se">    \</span>       name<span class="p">:</span> <span class="s1">'clangd'</span><span class="p">,</span>
<span class="se">    \</span>       <span class="k">filetype</span><span class="p">:</span> <span class="p">[</span><span class="s1">'c'</span><span class="p">,</span> <span class="s1">'cpp'</span><span class="p">],</span>
<span class="se">    \</span>       <span class="nb">path</span><span class="p">:</span> <span class="s1">'clangd'</span><span class="p">,</span>
<span class="se">    \</span>       <span class="k">args</span><span class="p">:</span> <span class="p">[</span><span class="s1">'--clang-tidy'</span><span class="p">,</span> <span class="s1">'-j'</span><span class="p">,</span> <span class="p">{{</span> data<span class="p">.</span>nproc * <span class="m">80</span> <span class="sr">//</span> <span class="m">100</span> <span class="p">}}]</span>
<span class="se">    \</span>  <span class="p">},</span>
    <span class="p">...</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">$HOME/.local/share/home-manager/data.json</code>:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"nproc"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>It is that simple<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>.</p>

<h1 id="extensions">Extensions</h1>

<p>Nix enables extensive configuration, so we could certainly improve on the described setup if we
needed to. Here are some ideas:</p>

<ul>
  <li>
    <p>The <code class="language-plaintext highlighter-rouge">template</code> function in its current form requires two file paths as parameters.
We may desire some more flexibility.<br />
For example, we may want to pass the template file as a Nix string.
Or we may want to pass the <code class="language-plaintext highlighter-rouge">data</code> in a different format (e.g. as a Nix attribute set, a
Nix string, etc.).<br />
We should be able to easily achieve that with a few small changes in the <code class="language-plaintext highlighter-rouge">template</code> function in
order to correctly parse and transform parameters of different types.</p>
  </li>
  <li>
    <p>In a previous iteration of my configuration I was generating multiple files from a single template
file by iterating over a <code class="language-plaintext highlighter-rouge">JSON</code> array.<br />
I now have no use for it, so I have not included it in this post, but it is certainly doable with
a few changes.</p>
  </li>
</ul>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>See <a href="https://nix-community.github.io/home-manager/options.xhtml#opt-home.file">home-file</a>. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>See <a href="https://nix.dev/manual/nix/2.28/language/builtins.html#builtins-mapAttrs">builtins.mapAttrs</a>. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>See <a href="#extensions">Extensions</a>. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>See
<a href="https://ryantm.github.io/nixpkgs/builders/trivial-builders/#trivial-builder-runCommand">pkgs.runCommand</a>. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5">
      <p>The file <code class="language-plaintext highlighter-rouge">$HOME/.local/share/home-manager/data.json</code> is automatically generated, but that
deserves a post of its own. <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Charalampos Kardaris</name></author><category term="blog" /><category term="programming" /><summary type="html"><![CDATA[Generate parameterized files with the help of Nix Home Manager]]></summary></entry></feed>