道&c.https://blog.taoetc.org/2023-07-14T05:05:43ZSynthstrom Deluge sound designtag:blog.taoetc.org,2023-07-14:synthstrom_deluge_sound_design/index.html2023-07-14T05:05:43Z
Amazing video showing how powerful the Synthstrom Deluge is for sound design.
<p><a href="https://blog.taoetc.org/synthstrom_deluge_sound_design/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p><a href="https://blog.taoetc.org/synthstrom_deluge_sound_design/index.html">Permalink</p>
Having fun with NixOStag:blog.taoetc.org,2023-07-11:having_fun_with_nixos/index.html2023-07-11T05:05:41Z
First impressions of NixOS.
<p><a href="https://blog.taoetc.org/having_fun_with_nixos/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>I decided to run <a href="https://nixos.org/">NixOS</a> on my personal laptop, replacing an <a href="https://archlinux.org/">Arch Linux</a> installation that I've had for years. The idea of a declarative OS has a strong appeal to me, since I'm a big fan of declarative languages — SQL probably being the main one.</p>
<p>The installation was easy, and most of the software I use is open-source and is already in the <a href="https://search.nixos.org/packages">more than 80,000 packages available for NixOS</a>. I use my laptop mostly for music production and software development with Python, and it was easy to install Ardour, the plugins that I use, as well as <a href="https://neovim.io/">the Neovim editor</a>.</p>
<p>There were a few surprises along the way, some happy, some not as much. Installing the <a href="https://www.overtonedsp.co.uk/">OvertoneDSP plugins</a>, for example, required me to learn how to <a href="https://nixos.wiki/wiki/Packaging/Binaries">package binaries</a>, and I did the same for <a href="https://github.com/mourednik/argotlunar">Argotlunar, a delay line granulator</a> that I love.</p>
<p>On the other hand, I realized I don't have to install <a href="https://github.com/pyenv/pyenv">pyenv</a> in order to create isolated Python environments for development. I can just use a Nix shell to spawn a new Python version where I do my development.</p>
<p><a href="https://blog.taoetc.org/having_fun_with_nixos/index.html">Permalink</p>
Storing Markdown image description as EXIFtag:blog.taoetc.org,2023-07-07:storing_markdown_image_description_as_exif/index.html2023-07-07T02:02:54Z
I wrote some code to add image descriptions from Markdown to the files as EXIF.
<p><a href="https://blog.taoetc.org/storing_markdown_image_description_as_exif/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p><img src="img/florian-schmetz-Rks6FTfX5OU-unsplash.jpg" alt="Photo of a Sony Walkman model WM-2011 and headphones on a orange surface" /></p>
<p>I wrote <a href="https://github.com/betodealmeida/nefelibata/blob/main/src/nefelibata/assistants/exif_description.py">a simple assistant for my blog</a> that extracts the description of images from the Markdown source of a post, and embeds it as EXIF in the file. This way, when the blog post gets <a href="https://github.com/betodealmeida/nefelibata/blob/main/src/nefelibata/announcers/mastodon.py">announced to Mastodon</a> they should have <code>ALT</code> texts automatically populated.</p>
<p><a href="https://blog.taoetc.org/storing_markdown_image_description_as_exif/index.html">Permalink</p>
Re: Fire Retardants are not a legal requirementtag:blog.taoetc.org,2023-07-06:re_fire_retardants_are_not_a_legal_requirement/index.html2023-07-06T05:05:48Z
I think they are...
<p><a href="https://blog.taoetc.org/re_fire_retardants_are_not_a_legal_requirement/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>A reply to the post <a href="gemini://gemini.ctrl-c.club/~stack/gemlog/2023-07-05.mattress.gmi">Fire Retardants are not a legal requirement</a>, which comments on a post I wrote about making my own mattress.</p>
<p>The source linked in the gemlog post above lists only legislation for Delaware regarding mattresses, and even then the legislation only prohibits "harmful flame retardant chemicals", but doesn't say anything about not requirering a flame retardant. On the other hand:</p>
<blockquote>
<p>According to the safety regulations from the U.S. Consumer Product Safety Commission (CPSC) first issued in 2007, it is a requirement for all mattresses to contain flame retardants so that each mattress can withstand exposure to an open flame for a sustained period of time. (<a href="https://www.mattressclarity.com/blog/flame-retardant/">source</a>)</p>
</blockquote>
<p><a href="https://blog.taoetc.org/re_fire_retardants_are_not_a_legal_requirement/index.html">Permalink</p>
Why I ended up making my own mattresstag:blog.taoetc.org,2023-07-05:why_i_ended_up_making_my_own_mattress/index.html2023-07-05T05:05:27Z
Turns out it's not trivial to get a mattress that is both organic and vegan. I hade to make my own.
<p><a href="https://blog.taoetc.org/why_i_ended_up_making_my_own_mattress/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>I wanted a new mattress that met two conditions:</p>
<ol>
<li>Used organic materials; and</li>
<li>Was vegan.</li>
</ol>
<p>Turns out this is not trivial, at least in the US. In the US mattresses are required to have a fire-retardant material, that is either not-organic or wool (<a href="https://theminimalistvegan.com/is-wool-vegan/">wool is not vegan</a>). My partner called a company and asked if they could make us a mattress without wool and they told her they would require a doctor's note (!) for that.</p>
<p>We ended up building our own mattress. We bought a base made of coconut fiber, and 3 mattress toppers of increasing densities made of pure organic latex. Each mattress topper was 3" tall, for a total height of ~9", which is the typical height for a mattress.</p>
<p><img src="img/PXL_20230702_154852228.jpg" alt="Photo showing the a coconut fiber layer used as the base of the mattress." /></p>
<p><img src="img/PXL_20230702_154855302.jpg" alt="Photo showing the bottom latex topping, of high density." /></p>
<p><img src="img/PXL_20230702_154857244.jpg" alt="Photo showing the middle latex topping, of medium density." /></p>
<p><img src="img/PXL_20230702_154916312.jpg" alt="Photo showing the stacked toppings, with a low density one on top." /></p>
<p><a href="https://blog.taoetc.org/why_i_ended_up_making_my_own_mattress/index.html">Permalink</p>
Interleaved 2D Notation for Concatenative Programmingtag:blog.taoetc.org,2023-06-27:interleaved_2d_notation_for_concatenative_programming/index.html2023-06-27T04:04:31Z
An interesting 2D programming language
<p><a href="https://blog.taoetc.org/interleaved_2d_notation_for_concatenative_programming/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>A really cool 2D concatenative programming language. There's even an <a href="https://homepages.ecs.vuw.ac.nz/~mwh/demos/p22-2d-concat/">online IDE</a> for it.</p>
<p><a href="https://blog.taoetc.org/interleaved_2d_notation_for_concatenative_programming/index.html">Permalink</p>
Nowtag:blog.taoetc.org,2023-04-18:now/index.html2023-04-18T15:03:15Z
What I'm currently working on
<p><a href="https://blog.taoetc.org/now/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<ul>
<li>Preparing an RV so I can move in and travel the US for a few months, while working and making music</li>
<li>Playing Horizon Zero Dawn on my Steamdeck</li>
<li>Working on Señor Octopus, my open-source lightweight alternative to IFTTT</li>
</ul>
<p><a href="https://blog.taoetc.org/now/index.html">Permalink</p>
Identity theft is not a joke, Jim!tag:blog.taoetc.org,2023-04-18:identity_theft_is_not_a_joke_jim/index.html2023-04-18T03:03:29Z
Someone tried to open a credit card in my name today
<p><a href="https://blog.taoetc.org/identity_theft_is_not_a_joke_jim/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>After 10+ years in the US, it finally happened: someone tried to open a credit card in my name today. I received an email from my bank congratulating me and, fortunately, telling me that the application was "pending".</p>
<p>I half-panicked. The email said:</p>
<blockquote>
<p>If you did not submit this credit application, please call us at 1-844-750-7939, Monday through Friday: 6 a.m. – 5 p.m., Saturday: 7 a.m. – 3:30 p.m. Pacific Time.</p>
</blockquote>
<p>I called the number. A woman named Ashley picked up, and I told her that someone applied for a credit card in my name. She asked for the last 4 digits of my SSN.</p>
<p>"No!", I said.</p>
<p>"No?"</p>
<p>"How do I know you're not a scammer? I don't trust this number!"</p>
<p>"OK, sir... then you should go to a branch and ask them to call us."</p>
<p>"That's what I'm going to do!", I said, and hung up.</p>
<p>I went to my bank's website and searched for fraud numbers. I tried calling them, but they were all useless. I was being transferred from department to department, and I had to explain what happened from the start every time. Eventually, I got angry and decided to go to the nearest branch, a 30-minute drive away.</p>
<p>Upon arriving there, the person who helped me was also not super useful. All they did was call numbers and hand me the phone, so I had to explain what happened for the 5th, 6th, and 7th time while being transferred around. Some departments told me that since the application was still pending, they couldn't do anything!</p>
<p>Eventually, I got sent to the right place. I heard a familiar voice: "Hello, this is Ashley, how can I help?"</p>
<p>"Hmmm, hi, Ashley... sorry for being rude and not believing you earlier today. I'm at the branch now."</p>
<p>She laughed and said she totally understood, then quickly helped me cancel the application and oriented me to freeze my credit.</p>
<p><a href="https://blog.taoetc.org/identity_theft_is_not_a_joke_jim/index.html">Permalink</p>
Configuring a Micron Bolt Mini-2 GPS trackertag:blog.taoetc.org,2023-04-17:configuring_a_micron_bolt_mini_2_gps_tracker/index.html2023-04-17T00:12:40Z
How I found a GPS tracker in my car and reconfigured it to send data to my server
<p><a href="https://blog.taoetc.org/configuring_a_micron_bolt_mini_2_gps_tracker/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>Back in 2020 I left San Francisco and rented a ranch house in Sonoma county together with my partner, Kim. We wanted to move to a remote place because of the COVID pandemic, and the place she found for us was perfect: 40 acres of land between Sonoma and Marin counties, with a renovated house that was mostly surrounded by trees — the exception being the view of the Estero Americano estuary.</p>
<p>I didn't have a car in San Francisco; I remember hearing about a research from Lyft saying that 95% of the rides inside of the city were faster on an electric bike than by car, and that's what I did. But moving into a rural area I knew I needed a car, so I bought a 2010 RAV4.</p>
<p>The car had 3 owners before me, and one day I discovered one of them left behind a hidden GPS tracker:</p>
<p><img src="img/tracker-front.jpg" alt="Photo of the front of a GPS tracker, with the Micron wordmark" /></p>
<p><img src="img/tracker-back.jpg" alt="Photo of the back of a GPS tracker, with model name and IMEI number" /></p>
<p>As soon as I found it I was excited to put it back to work. I searched for documentation online, but couldn't find anything because this tracker is not a consumer product. I also emailed <a href="https://www.micronwireless.net/">the tracker manufacturer</a> asking for documentation on the unit, but they never got back to me. As a last resource I opened the device and tried plugging it into my computer via USB, but no device was identified, so I stored in the basement and forgot about it.</p>
<hr />
<p>Now that I bought an RV, a 36' long class-A, I decided to give it another try in getting the tracker working. It would be much cheaper and easier for me to just buy a consumer tracker, but I would hate to waste a perfectly good product that I already had at home. I also wanted to control my data, and it would be easier to do that with the Bolt Mini-2 instead of with a consumer product which usually requires a subscription and requires me to install yet another app in my phone.</p>
<p>I did some more research, and discovered <a href="https://www.traccar.org/">Traccar, an open-source GPS tracking platform</a>. On their website I learned that I could interact with my tracker via SMS (good), that there were many different protocols on the market (bad), and that my tracker was not supported (ugly). It would also very likely require a password to configure the device, which I didn't have.</p>
<p>Doing some more research I discovered <a href="https://www.navixy.com/">a commercial GPS tracking platform</a> that supported the Bolt Mini-2. I signed up for a 14 day trial, and tried to add my tracker: they asked for model, which was in their supported list, the IMEI, and the phone number of the SIM card in the device.</p>
<p>I went through the process of adding the tracker, but instead of putting the number of the SIM card in the GPS I used my own phone number. And as soon as I activated it I got a message:</p>
<div class="highlight"><pre><span></span>AT+GTQSS=AIR11,h2g2,,,4,,1,tracker.us.navixy.com,47764,13.52.37.2,47764,+12162780905,10,1,,,0001$
</pre></div>
<p>A-ha! This gave me a <strong>lot</strong> of information. I searched online for the <code>AT+GTQSS</code> command, and found an explanation of what it means. The protocol used by the Bolt Mini-2 seemed to be a variant of the <code>gl200</code> protocol. In the command above, <code>AIR11</code> is the (default?) password of my device, and <code>h2g2</code> is the APN that I had provided when adding the tracker. More importantly, the command tells the tracker to send UDP (<code>4</code>) packages to the server <code>tracker.us.navixy.com:47764</code> (falling back to <code>13.52.37.2:47764</code>), and send a heartbeat message every 10 minutes.</p>
<p>I ordered <a href="https://speedtalkmobile.com/gps-tracker-sim-card-plans/">a cheap SIM card with a $5/month plan</a> and sent a slightly modified command to my tracker, configuring it to send the messages to my server. I then created a very simply UDP server that simply printed all the messages it received:</p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">socket</span>
<span class="n">UDP_IP</span> <span class="o">=</span> <span class="s2">"0.0.0.0"</span>
<span class="n">UDP_PORT</span> <span class="o">=</span> <span class="mi">5000</span>
<span class="n">sock</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_DGRAM</span><span class="p">)</span>
<span class="n">sock</span><span class="o">.</span><span class="n">bind</span><span class="p">((</span><span class="n">UDP_IP</span><span class="p">,</span> <span class="n">UDP_PORT</span><span class="p">))</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="n">data</span><span class="p">,</span> <span class="n">addr</span> <span class="o">=</span> <span class="n">sock</span><span class="o">.</span><span class="n">recvfrom</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"received message: </span><span class="si">{</span><span class="n">message</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="n">sock</span><span class="o">.</span><span class="n">sendto</span><span class="p">(</span><span class="sa">b</span><span class="s2">"Hello!"</span><span class="p">,</span> <span class="n">addr</span><span class="p">)</span>
</pre></div>
<p>After a while, I started receiving messages from the tracker! Unfortunately the messages didn't make sense to me. They all looked exactly like this:</p>
<div class="highlight"><pre><span></span>+BUFF:GTINF,423136,352009117419957,,42,8901240204119593571,20,4,0,,,,0,1,1,0,0,20230413182059,84,,,,,20230413182742,0122$
</pre></div>
<p>All the messages were identical, the only difference is that every 10 minutes I'd get a heartbeat ping:</p>
<div class="highlight"><pre><span></span>+ACK:GTHBD,423136,352009117419957,,20230413183607,0109$
</pre></div>
<p>The heartbeat messages were slightly different from each other: the number at the end would increase every time: <code>0108</code>, <code>0109</code>, <code>010A</code>, etc. But the <code>BUFF:GTINF</code> messages all had the same ending of <code>0122</code>. They also didn't contain any information about location, so they were useless to me. I spent a whole day sending different commands to the tracker, trying to make it send me a report of its position, but nothing worked.</p>
<p>I re-read all the docs I had found about GPS trackers, and I eventually figured it out! When the tracker is in UDP mode it's recommended to enable the heartbeat (which was happening every 10 minutes) and to also have the tracker expect an acknowledgment from the server! The end of the configuration command (<code>AT+GTQSS</code>) has:</p>
<div class="highlight"><pre><span></span>+12162780905,10,1,,,0001$
</pre></div>
<p>The first element is the number of the SMS gateway, the second one is the number of minutes between heartbeats (10), and third one tells the tracker to wait for confirmation from the backend. So I modified my script a little bit:</p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">socket</span>
<span class="n">UDP_IP</span> <span class="o">=</span> <span class="s2">"0.0.0.0"</span>
<span class="n">UDP_PORT</span> <span class="o">=</span> <span class="mi">5000</span>
<span class="n">sock</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_DGRAM</span><span class="p">)</span>
<span class="n">sock</span><span class="o">.</span><span class="n">bind</span><span class="p">((</span><span class="n">UDP_IP</span><span class="p">,</span> <span class="n">UDP_PORT</span><span class="p">))</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="n">data</span><span class="p">,</span> <span class="n">addr</span> <span class="o">=</span> <span class="n">sock</span><span class="o">.</span><span class="n">recvfrom</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span> <span class="c1"># buffer size is 1024 bytes</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span>
<span class="k">if</span> <span class="n">message</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"+ACK:GTHBD"</span><span class="p">):</span>
<span class="c1"># heartbeat response</span>
<span class="n">parts</span> <span class="o">=</span> <span class="n">message</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">','</span><span class="p">)</span>
<span class="n">protocol</span> <span class="o">=</span> <span class="n">parts</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">count</span> <span class="o">=</span> <span class="n">parts</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span><span class="o">.</span><span class="n">rstrip</span><span class="p">(</span><span class="s1">'$'</span><span class="p">)</span>
<span class="n">reply</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'+SACK:GTHBD,</span><span class="si">{</span><span class="n">protocol</span><span class="si">}</span><span class="s1">,</span><span class="si">{</span><span class="n">count</span><span class="si">}</span><span class="s1">$</span><span class="se">\r\n</span><span class="s1">'</span>
<span class="k">else</span><span class="p">:</span>
<span class="c1"># acknowledge packets</span>
<span class="n">parts</span> <span class="o">=</span> <span class="n">message</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">','</span><span class="p">)</span>
<span class="n">count</span> <span class="o">=</span> <span class="n">parts</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">rstrip</span><span class="p">(</span><span class="s1">'$'</span><span class="p">)</span>
<span class="n">reply</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'+SACK:</span><span class="si">{</span><span class="n">count</span><span class="si">}</span><span class="s1">$</span><span class="se">\r\n</span><span class="s1">'</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"received message: </span><span class="si">{</span><span class="n">message</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="n">sock</span><span class="o">.</span><span class="n">sendto</span><span class="p">(</span><span class="n">reply</span><span class="o">.</span><span class="n">encode</span><span class="p">(),</span> <span class="n">addr</span><span class="p">)</span>
</pre></div>
<p>With this, the tracker started sending new messages, each one with an increasing identifier. These were all messages that were buffered in the tracker. With the correct protocol, I started getting messages that were more interesting... yet, they didn't have anything that looked like coordinates. The new messages looked like this:</p>
<div class="highlight"><pre><span></span>+BUFF:GTWIF,423136,352009117419957,,2,cc40d07349b2,-73,,,,ecc302e1eb84,-67,,,,,,,,79,20230414161214,0248$
</pre></div>
<p>Looking at the source code for Traccar I realized these were wifi networks! Here the tracker is advertising that it can see 2 wifi networks: <code>cc40d07349b2</code> (<code>CC:40:D0:73:49:B2</code>), with a signal strength of -73 dB, and <code>ecc302e1eb84</code>, with -67 dB. Using the <a href="https://developers.google.com/maps/documentation/geolocation/overview">Google geolocation API</a> I was able to convert these messages into approximate coordinates, which is really cool. But I really wanted some old school latitude/longitude pairs.</p>
<p>Eventually, going back to the documentation for the <code>gl200</code> protocol, I figured out the command to get periodic reports:</p>
<div class="highlight"><pre><span></span>AT+GTFRI=AIR11,1,1,,,0000,2359,86400,1,86400,1,001F,1000,1000,,,,,,,0450$
</pre></div>
<p>With this, I started getting new messages that had latitude, longitude, altitude, and more. In theory this should make the tracker reports its position once per day (86400 seconds), but for some reason that I haven't figured out yet the messages arrive every 15 minutes or so.</p>
<hr />
<p>My end goal was to have the UDP server posting the positions to my message queue, so that I could see the position of the tracker on my phone using the <a href="https://owntracks.org/">Owntracks open-source mobile tracker</a> app. In order to do that, I used an application that I wrote for <a href="https://github.com/betodealmeida/senor-octopus">Señor Octopus, my own home automation system</a>. Señor Octopus is a lightweight (an open source!) alternative to <a href="https://ifttt.com/">IFTT</a>. This is how my configuration looks like:</p>
<div class="highlight"><pre><span></span><span class="nt">tracker</span><span class="p">:</span>
<span class="nt">plugin</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">source.udp</span>
<span class="nt">flow</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">-> owntracks</span>
<span class="nt">host</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">0.0.0.0</span>
<span class="nt">port</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">5000</span>
<span class="nt">protocol</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">micron_bolt_mini_2</span>
<span class="nt">api_key</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">GOOGLE_API_KEY</span>
<span class="nt">owntracks</span><span class="p">:</span>
<span class="nt">flow</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">tracker -> owntracks-deserializer</span>
<span class="nt">plugin</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">filter.jinja</span>
<span class="nt">template</span><span class="p">:</span> <span class="p p-Indicator">|</span>
<span class="no">_type: location</span>
<span class="no">{% if event.value.accuracy %}</span>
<span class="no">acc: {{ int(event.value.accuracy) }}</span>
<span class="no">{% endif %}</span>
<span class="no">{% if event.value.altitude %}</span>
<span class="no">alt: {{ int(event.value.altitude) }}</span>
<span class="no">{% endif %}</span>
<span class="no">batt: {{ int(event.value.battery) }}</span>
<span class="no">bs: 1</span>
<span class="no">conn: m</span>
<span class="no">created_at: {{ int(event.value.send_time.timestamp()) }}</span>
<span class="no">lat: {{ event.value.latitude }}</span>
<span class="no">lon: {{ event.value.longitude }}</span>
<span class="no">m: 1</span>
<span class="no">t: t</span>
<span class="no">tid: MT</span>
<span class="no">{% if event.value.fix_time %}</span>
<span class="no">tst: {{ int(event.value.fix_time.timestamp()) }}</span>
<span class="no">{% endif %}</span>
<span class="no">vac: 0</span>
<span class="no">{% if event.value.speed %}</span>
<span class="no">vel: {{ int(event.value.speed) }}</span>
<span class="no">{% endif %}</span>
<span class="nt">owntracks-deserializer</span><span class="p">:</span>
<span class="nt">flow</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">owntracks -> mqtt-serializer</span>
<span class="nt">plugin</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">filter.deserialize</span>
<span class="nt">format</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">YAML</span>
<span class="nt">mqtt-serializer</span><span class="p">:</span>
<span class="nt">flow</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">owntracks-deserializer -> mqtt-sink</span>
<span class="nt">plugin</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">filter.serialize</span>
<span class="nt">format</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">JSON</span>
<span class="nt">mqtt-sink</span><span class="p">:</span>
<span class="nt">flow</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">mqtt-serializer -></span>
<span class="nt">plugin</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">sink.mqtt</span>
<span class="nt">topic</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">owntracks/rv/tracker1</span>
<span class="nt">host</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">localhost</span>
<span class="nt">port</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">1883</span>
<span class="nt">username</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">rv</span>
<span class="nt">password</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">MQTT_SECRET</span>
</pre></div>
<p>The configuration above defines a simple pipeline:</p>
<div class="highlight"><pre><span></span><span class="n">tracker</span> <span class="o">-></span> <span class="n">owntracks</span> <span class="o">-></span> <span class="n">owntracks</span><span class="o">-</span><span class="n">deserializer</span> <span class="o">-></span> <span class="n">mqtt</span><span class="o">-</span><span class="n">serializer</span> <span class="o">-></span> <span class="n">mqtt</span><span class="o">-</span><span class="n">sink</span>
</pre></div>
<p>The <strong>source</strong>, <code>tracker</code>, runs a UDP server on port <code>0.0.0.0:5000</code>, using <a href="https://github.com/betodealmeida/senor-octopus/blob/main/src/senor_octopus/sources/udp/protocols/micron_bolt_mini_2.py">a custom protocol that I wrote for the tracker</a>. It emits events that look like this:</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="nt">"accuracy"</span><span class="p">:</span> <span class="mi">123</span><span class="p">,</span>
<span class="nt">"speed"</span><span class="p">:</span> <span class="mi">456</span><span class="p">,</span>
<span class="nt">"azimuth"</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span>
<span class="nt">"altitude"</span><span class="p">:</span> <span class="mi">20</span><span class="p">,</span>
<span class="nt">"longitude"</span><span class="p">:</span> <span class="mi">-120</span><span class="p">,</span>
<span class="nt">"latitude"</span><span class="p">:</span> <span class="mi">38</span><span class="p">,</span>
<span class="nt">"fix_time"</span><span class="p">:</span> <span class="s2">"2023-04-01T12:00:00"</span><span class="p">,</span>
<span class="nt">"battery"</span><span class="p">:</span> <span class="mi">85</span><span class="p">,</span>
<span class="nt">"send_time"</span><span class="p">:</span> <span class="s2">"2023-04-01T12:00:05"</span><span class="p">,</span>
<span class="nt">"source"</span><span class="p">:</span> <span class="s2">"gps"</span><span class="p">,</span>
<span class="p">}</span>
</pre></div>
<p>These events are then converted to <a href="https://owntracks.org/booklet/tech/json/">the jSON schema expected by Owntracks</a>, through a somewhat convoluted process: generating a YAML file using Jinja2, deserializing it and serializing to JSON. The final payload is then sent to the message queue, eventually showing up in Owntracks:</p>
<p><img src="img/owntracks.png" alt="Screenshot of the Owntracks app, showing the location of 3 subjects" /></p>
<p><a href="https://blog.taoetc.org/configuring_a_micron_bolt_mini_2_gps_tracker/index.html">Permalink</p>
New internet setuptag:blog.taoetc.org,2023-04-09:new_internet_setup/index.html2023-04-09T22:10:58Z
Moving my internet services around a little bit
<p><a href="https://blog.taoetc.org/new_internet_setup/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>For the last 2 years I've been running some of my internet infrastructure from my closet; I had a cluster of 3 Raspberry Pis that were running my personal Mastodon instance and my home automation, in addition to a Postgres database and a message queue. Since I'm planning to move fulltime into an RV in the next couple months I had to move the services to the cloud.</p>
<p>This was a nice opportunity to consolidate some of the other services I run on Digital Ocean, and cut my cloud bill in almost half. I went from 12 servers (!) to only 4:</p>
<ul>
<li>An "infra" server, running Postgres, Redis, MQTT, and Señor Octopus (my own home automation software).</li>
<li>A mail server, running Mail-in-a-Box and NextCloud (for photo backups).</li>
<li>A personal server, running my Mastodon instance, my Gemini capsule, and a custom made IndieAuth server.</li>
<li>An artist server, hosting my website and a few Fediverse instances (Pleroma, FunkWhale, PixelFed).</li>
</ul>
<p>It took me a couple days to get everything working. The hardest part was probably migrating my personal Mastodon instance, since it was running an older version (3.5.4). But I was able to migrate to 4.1.2 running on Ubuntu 22.10, storing the files in S3.</p>
<p><a href="https://blog.taoetc.org/new_internet_setup/index.html">Permalink</p>
Publishing songs to Mastodontag:blog.taoetc.org,2022-11-09:publishing_songs_to_mastodon/index.html2022-11-09T17:05:08Z
A quick tutorial on how to publish songs to Mastodon
<p><a href="https://blog.taoetc.org/publishing_songs_to_mastodon/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>This is a quick guide for musicians who want to publish their songs on Mastodon. Mastodon supports uploading media, and music files will be embedded so that followers can listen to it from their timeline. But there's a small trick that greatly improves the experience: adding covert art!</p>
<p>You can the <code>lame</code> MP3 encoder to convert WAV files to MP3 and add a cover image. All you need to do is:</p>
<div class="highlight"><pre><span></span>lame --ti cover.jpg song.wav song.mp3
</pre></div>
<p>Now when you attach the <code>song.mp3</code> file to a Mastodon post the embedded player will show the cover image, and it will also have a nice background color based on the cover art:</p>
<p><a href="https://2c.taoetc.org/web/@beto/106909368178764281"><img src="img/screenshot.png" alt="Screenshot of a Mastodon post showing the music player using the cover art embedded in the MP3 file." /></a></p>
<p>Edit: turns out you can now do this from the UI! Just upload an MP3 and then click the edit button. You'll be able to choose an image file to use as the cover. So cool!</p>
<p><a href="https://blog.taoetc.org/publishing_songs_to_mastodon/index.html">Permalink</p>
Point d’écoutetag:blog.taoetc.org,2022-04-26:point_decoute/index.html2022-04-26T17:05:35Z
A compilation of songs that I really like
<p><a href="https://blog.taoetc.org/point_decoute/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>At the end of 2004 I was finishing a 1-year stay in the Netherlands for my PhD. It was winter, and I would bike 30 minutes every day to the meteorological institute where I was doing my research, in De Bilt. I had a small brandless MP3 player that could hold a few albums, and I discovered a really nice compilation of free songs on a french blog called <a href="https://www.blogotheque.net/">La Blogothèque</a>. I loaded the 23 MP3s into my player and I would listen to it every day, going back and forth on my commute.</p>
<p>The blog page is still up, but the compilation page is long gone. <a href="https://web.archive.org/web/20041217220144/https://www.blogotheque.net/mp3_art.php3?id_article=342">It's available in archive.org</a>, though, and I've had the MP3s since 2004, copied from hard disk to hard disk as I upgraded computers in those almost 20 years. A couple months ago I set up a <a href="https://funkwhale.audio/">Funkwhale</a> instance for myself, and I started uploading all my songs. This week I finally upload the Point d’écoute compilation, and listening to it again after so many years made me very nostalgic and a little sad. I followed the links to the bands from the original post on the archive.org page, and I couldn't find almost any information about the artists: albums last published in 2008, domains taken over by cybersquatters, blogs last updated in 2011. The music I once loved, indie songs that I once considered great... all mostly gone, ephemeral.</p>
<p>The whole process made me think of "cyberarchaeology", and everything we have to explore in archive.org that is no longer available in the regular internet. And how much we lost and are going to lose in this transition from physical to digital media.</p>
<p><a href="https://blog.taoetc.org/point_decoute/index.html">Permalink</p>
Happy 77, dadtag:blog.taoetc.org,2021-12-11:happy_77_dad/index.html2021-12-11T17:05:27Z
My dad would turn 77 today
<p><a href="https://blog.taoetc.org/happy_77_dad/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>Today would be my dad's 77th birthday. He passed away almost 10 years ago.</p>
<p>When I was a teenager we were very close. We'd go for long walks, and talk about a lot of things -- usually technology. When I was 15 we traveled together to Europe for 21 days. We visited France, Italy, and Spain, and I was impressed by how he could speak all the 3 languages fluently, in addition to English and our native Portuguese.</p>
<p>After I went to college he started getting depressed, and for the next 15 years he turned into a shadow of himself. Most of the time the only things he would talk about were his pain, his sickness, and how the world was becoming a worse place. Over time, this became his only subject.</p>
<p>His funeral had a positive impact on me. I met with his friends and ex-coworkers, and they all knew him from before his depression. They reminded me of the kind, honest, fun, and smart person my dad used to be, replacing the dark image I had of his last years.</p>
<p>There are many things that I would love to talk about with my dad. For some of them I have a good idea of what my dad would say. But for others, I have no guess. For example, I only started caring about politics after my dad passed away, and I don't have a good sense of his political opinions. Many times I wonder were he would stand in these very polarized days.</p>
<p>He was a software engineer, and I only became one after he died. It would be nice to be able to talk with him about my work, my projects, and the current technological challenges.</p>
<p>I started making music a couple years after he died. I wonder what he would think of my music. Would he like it, would he listen to it?</p>
<p>I try to imagine what he would like, thinking of him as the person untouched by a mental disorder. But that's not how it works, you can't really dissociate the person from the disease. He was what he was in his whole -- kind, honest, fun, smart, but also sad, lonely, lost. Like a circle, without start nor end.</p>
<p>Happy birthday, dad. I wish you were here. I wish you could visit me, and we'd have a bottle of wine on my porch, watching the moon rise, sharing stories and laughter.</p>
<p><a href="https://blog.taoetc.org/happy_77_dad/index.html">Permalink</p>
Vegan stew recipetag:blog.taoetc.org,2021-12-09:vegan_stew_recipe/index.html2021-12-09T17:05:53Z
I recently made an amazing vegan version of a classic Polish stew
<p><a href="https://blog.taoetc.org/vegan_stew_recipe/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>One of my closest friends is Polish, and he mentioned a classic dish on our latest weekly chat: the Bigos, or "hunter's stew". I decided to try making a vegan version, which I called the "bad hunter's stew". My friend said it looks nor taste (based on my description) nothing like the original, but it was definitely one of the best things I've ever cooked. I'm definitely adding the recipe to the list of things I usually cook, so I decided to share it here.</p>
<p>I used <a href="https://www.connoisseurusveg.com/vegan-bigos/" title="Vegan Bigos recipe">a recipe I found online</a>, with very little modifications. The only thing I changed is that I threw all the ingredients at the same time into a slow cooker, cooking on high for 5 hours. I was also missing marjoram and caraway seed, so I made it without. Next time I cook the stew I'm planning to double the sauerkraut and cabbage, since my friend said it's supposed to be the main ingredient in the stew.</p>
<h2>Ingredients</h2>
<ul>
<li>2 tablespoons canola oil</li>
<li>2 tablespoons tamari or soy sauce</li>
<li>1 tablespoon maple syrup</li>
<li>1 tablespoon red vinegar</li>
<li>1 teaspoon liquid smoke</li>
<li>1 teaspoon smoked paprika</li>
<li>1 teaspoon black pepper</li>
<li>1 ounce extra-firm tofu, pressed and cut into 1/2" cubes</li>
<li>1 medium onion, diced</li>
<li>2 medium carrots, diced</li>
<li>4 garlic cloves, minced</li>
<li>1/2 cup dry red wine</li>
<li>3 cups vegetable broth (I just used water)</li>
<li>1 medium russet potato, scrubbed and cut into 1" cubes</li>
<li>2 cups chopped cabbage (I'm planning to double next time)</li>
<li>2 tablespoons sweet paprika</li>
<li>1 teaspoon dried marjoram</li>
<li>1/2 teaspoon caraway seed</li>
<li>2 tablespoons vegan Worcestershire sauce</li>
<li>2 cups sauerkraut, including juice (I'm planning to double next time)</li>
<li>1/3 cup finely diced dried prunes</li>
<li>1/4 cup tomato paste</li>
<li>salt to taste</li>
</ul>
<h2>Instructions</h2>
<p>Cook for 5 hours on a slow cooker on the "high" setting. Serve with chopped fresh dill and chopped fresh chives.</p>
<p><a href="https://blog.taoetc.org/vegan_stew_recipe/index.html">Permalink</p>
How to publish a static site over NNCPtag:blog.taoetc.org,2021-11-16:how_to_publish_a_static_site_over_nncp/index.html2021-11-16T02:02:52Z
A tutorial on how to use NNCP to publish a static site to a server over an unreliable connection
<p><a href="https://blog.taoetc.org/how_to_publish_a_static_site_over_nncp/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>Let's say you're running a static site generator on your laptop, and you want to push the built website to a machine running a web server. A good tool to do that would be <code>rsync</code>, a command-line utility that synchronizes files and directories efficiently, sending only files that were modified. You can use <code>rsync</code> to synchronize a directory between two computers, sending the updates through an encrypted channel via <code>ssh</code>.</p>
<p>But what if your connection to the web server is unreliable? Maybe the server is down from time to time, maybe the network link is unstable. You could just run <code>rsync</code>, and keep retrying until you succeed, but there are better ways. One better solution for this problem is <a href="http://www.nncpgo.org/">Node to Node copy</a>, a small collection of utilities written in Go as an evolution of <a href="https://en.wikipedia.org/wiki/UUCP">UUCP</a>.</p>
<p>Last week I set up NNCP as a way of publishing my blog (https://blog.taoetc.org/) to a Raspberry Pi that will run under solar power and a 4G connection. Since the documentation for NNCP is very sparse I decided to write down the process, hoping it can be useful for other people.</p>
<h2>Set up</h2>
<p>Let's assume you have two machines, <code>client</code> and <code>server</code>. The client machine has a directory that is updated frequently, and you want to send those files to the server using NNCP. Let's also assume that your static site generator is smart enough to know which files were modified since the last build.</p>
<h2>Installation</h2>
<p>The first step is to install NNCP in both machines. I couldn't find packages for Debian stable, so I installed Go and compiled NNCP. If you're going to compile NNCP make sure you have Go version 1.13 or higher; you can verify which version you have by running <code>go version</code>. The compilation requires a command called <code>redo</code>, which I didn't have installed. In that case, you can use <code>contrib/do</code> from the package:</p>
<div class="highlight"><pre><span></span><span class="nv">contrib</span><span class="o">/</span><span class="k">do</span> <span class="nv">all</span>
</pre></div>
<p>On the client I installed NNCP in the <code>~/.local</code> directory, since I wanted to run the commands as my user:</p>
<div class="highlight"><pre><span></span><span class="nv">PREFIX</span><span class="o">=~/</span>.<span class="nv">local</span> <span class="nv">contrib</span><span class="o">/</span><span class="k">do</span> <span class="nv">install</span>
</pre></div>
<p>On the server I created an <code>nncp</code> system account, and installed NNCP to <code>/usr/local</code>:</p>
<div class="highlight"><pre><span></span><span class="nv">PREFIX</span><span class="o">=/</span><span class="nv">usr</span><span class="o">/</span><span class="nv">local</span> <span class="nv">contrib</span><span class="o">/</span><span class="k">do</span> <span class="nv">install</span>
</pre></div>
<h2>Configuration</h2>
<p>The next step is to create configuration file, using the command <code>nncp-cfgnew</code>. For the client:</p>
<div class="highlight"><pre><span></span>nncp-cfgnew > ~/.local/etc/nncp.hjson
</pre></div>
<p>And for the server:</p>
<div class="highlight"><pre><span></span>nncp-cfgnew > /usr/local/etc/nncp.hjson
</pre></div>
<p>These configuration files contain public and private keys, so make sure that no one else can read them:</p>
<div class="highlight"><pre><span></span>chmod 600 ~/.local/etc/nncp.hjson
</pre></div>
<div class="highlight"><pre><span></span>chown nncp:nncp /usr/local/etc/nncp.hjson
chmod 600 /usr/local/etc/nncp.hjson
</pre></div>
<p>By default the configuration file will use a spool directory and store a log file in <code>/var/spool/nncp</code>. On the server I made sure to create the directory and give ownership to the system account:</p>
<div class="highlight"><pre><span></span><span class="n">mkdir</span> <span class="o">-</span><span class="n">p</span> <span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">spool</span><span class="o">/</span><span class="n">nncp</span>
<span class="n">chown</span> <span class="n">nncp</span><span class="p">:</span><span class="n">nncp</span> <span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">spool</span><span class="o">/</span><span class="n">nncp</span>
</pre></div>
<p>On the client I changed the configuration to have the spool and the log file pointing to <code>~/.local</code>:</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="c1"># Path to encrypted packets spool directory</span>
<span class="n">spool</span><span class="p">:</span> <span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">user</span><span class="o">/.</span><span class="n">local</span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">spool</span><span class="o">/</span><span class="n">nncp</span>
<span class="c1"># Path to log file</span>
<span class="nb">log</span><span class="p">:</span> <span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">user</span><span class="o">/.</span><span class="n">local</span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">spool</span><span class="o">/</span><span class="n">nncp</span><span class="o">/</span><span class="nb">log</span>
</pre></div>
<p>And then created the directory:</p>
<div class="highlight"><pre><span></span><span class="n">mkdir</span> <span class="o">-</span><span class="n">p</span> <span class="o">~/.</span><span class="n">local</span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">spool</span><span class="o">/</span><span class="n">nncp</span>
</pre></div>
<p>The next step is modifying the configuration files so that the nodes (computers) are aware of each other.</p>
<h2>Creating a friend-to-friend network</h2>
<p>NNCP allows us to build a small network of trusted nodes; unlike UUCP there are no anonymous peers in this network, which is why it's called friend-to-friend.</p>
<p>To create the network you need to add the public keys of all neighboring nodes to the configuration file you created in the previous step, under the <code>neigh</code> section. Initially this section could contain only an entry for a node called <code>self</code>, representing the node itself. All you need to do is copy these keys to the other nodes. For example, for the client:</p>
<div class="highlight"><pre><span></span>neigh: {
# client
self: {
id: WWW
exchpub: XXX
signpub: YYY
noisepub: ZZZ
}
# "self" from server
server: {
id: AAA
exchpub: BBB
signpub: CCC
noisepub: DDD
}
}
</pre></div>
<p>And for the server:</p>
<div class="highlight"><pre><span></span>neigh: {
# server
self: {
id: AAA
exchpub: BBB
signpub: CCC
noisepub: DDD
}
# "self" from client
client: {
id: WWW
exchpub: XXX
signpub: YYY
noisepub: ZZZ
}
}
</pre></div>
<p>With this configuration the nodes can identify each other. But how do they communicate? In addition to the public keys you can also put the address where the node can be found. In this case the client will push files to the server, so you only need to configure the address of the server on the client configuration:</p>
<div class="highlight"><pre><span></span>neigh: {
# client
self: {
id: WWW
exchpub: XXX
signpub: YYY
noisepub: ZZZ
}
# "self" from server
server: {
id: AAA
exchpub: BBB
signpub: CCC
noisepub: DDD
addrs: {
internet: server.example.com:5400
}
}
}
</pre></div>
<p>Now, <code>client</code> knows who <code>server</code> is, and how to talk to it. And <code>server</code> knows who <code>client</code> is when receiving a message from it.</p>
<h2>Sending a file</h2>
<p>OK, now that you have a network, how do you send a file? From the <code>client</code> machine you simply run:</p>
<div class="highlight"><pre><span></span>nncp-file -cfg ~/.local/etc/nncp.hjson file.txt server:
</pre></div>
<p>If you run the command above you'll notice that it completes very quickly! The file was not actually sent. Instead, it was queued in the spool at <code>~/.local/var/spool/nncp</code>. Inside the spool there should be a directory with the ID of <code>server</code> ("AAA", in this example), and a symlink called "server" pointing to it. Inside this directory there should be a directory called "tx", and inside an encrypted and compressed version of our file.</p>
<p>To actually send the file you need to call the server:</p>
<div class="highlight"><pre><span></span>nncp-call -cfg ~/.local/etc/nncp.hjson server
</pre></div>
<p>This command will try to connect to server and send all the packets that are in the spool. It will fail, because the server is not listening to any incoming connections yet.</p>
<h2>Configuring the server</h2>
<p>On the server you need to run a daemon listening for incoming connections:</p>
<div class="highlight"><pre><span></span>nncp-daemon -autotoss -bind server.example.com:5400
</pre></div>
<p>You also need to change the server configuration, so that the it will allow incoming file requests from the client:</p>
<div class="highlight"><pre><span></span>neigh: {
# server
self: {
id: AAA
exchpub: BBB
signpub: CCC
noisepub: DDD
}
# "self" from client
client: {
id: WWW
exchpub: XXX
signpub: YYY
noisepub: ZZZ
incoming: "/path/to/files/from/client"
}
}
</pre></div>
<p>Now the client can send file packets to the server. The <code>autotoss</code> option in the daemon means that these packets will be automatically processed, and moved to the incoming directory.</p>
<p>If you go back to the client and run:</p>
<div class="highlight"><pre><span></span>nncp-file -cfg ~/.local/etc/nncp.hjson file.txt server:
nncp-call -cfg ~/.local/etc/nncp.hjson server
</pre></div>
<p>The file <code>file.txt</code> will show up in <code>/path/to/files/from/client/file.txt</code>... success!</p>
<h2>Synchronizing files</h2>
<p>With this setup, how do you periodically sync the files from a static site generator to the server? You could simply have the generator call the command on files that were modified, pushing them to a directory that the server would then serve. For example:</p>
<div class="highlight"><pre><span></span>nncp-file -cfg ~/.local/etc/nncp.hjson index.html server:my_blog/index.html
nncp-file -cfg ~/.local/etc/nncp.hjson posts/hello_world/index.html server:my_blog/hello_world/index.html
nncp-file -cfg ~/.local/etc/nncp.hjson css/theme.css server:my_blog/css/theme.css
nncp-call -cfg ~/.local/etc/nncp.hjson server
</pre></div>
<p>This works, but it has a problem: whenever a file changes and you run the command again, the file will not be overwritten. For example, if <code>index.html</code> changes, the next time you send it to the server it will be called <code>index.html.0</code>, to prevent the original one from being overwritten. And there's no configuration flag to allow overwriting files.</p>
<p>Fortunately NNCP also allows you to execute remote commands! On the server we can define a command called "sync", that will use <code>rsync</code> to copy the pushed files to the website directory:</p>
<div class="highlight"><pre><span></span><span class="n">neigh</span><span class="p">:</span> <span class="p">{</span>
<span class="c1"># server</span>
<span class="bp">self</span><span class="p">:</span> <span class="p">{</span>
<span class="n">id</span><span class="p">:</span> <span class="n">AAA</span>
<span class="n">exchpub</span><span class="p">:</span> <span class="n">BBB</span>
<span class="n">signpub</span><span class="p">:</span> <span class="n">CCC</span>
<span class="n">noisepub</span><span class="p">:</span> <span class="n">DDD</span>
<span class="p">}</span>
<span class="c1"># "self" from client</span>
<span class="n">client</span><span class="p">:</span> <span class="p">{</span>
<span class="n">id</span><span class="p">:</span> <span class="n">WWW</span>
<span class="n">exchpub</span><span class="p">:</span> <span class="n">XXX</span>
<span class="n">signpub</span><span class="p">:</span> <span class="n">YYY</span>
<span class="n">noisepub</span><span class="p">:</span> <span class="n">ZZZ</span>
<span class="n">incoming</span><span class="p">:</span> <span class="s2">"/path/to/files/from/client"</span>
<span class="n">exec</span><span class="p">:</span> <span class="p">{</span>
<span class="n">sync</span><span class="p">:</span> <span class="p">[</span>
<span class="s2">"/usr/bin/rsync"</span><span class="p">,</span>
<span class="s2">"-av"</span><span class="p">,</span>
<span class="s2">"--remove-source-files"</span><span class="p">,</span>
<span class="s2">"/path/to/files/from/client/"</span><span class="p">,</span>
<span class="s2">"/var/www/example.com"</span>
<span class="p">]</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>The rsync command will move any new files that show up in the incoming directory to the root of the website. To execute the command the client needs to run:</p>
<div class="highlight"><pre><span></span><span class="nv">echo</span> <span class="o">|</span> <span class="nv">nncp</span><span class="o">-</span><span class="k">exec</span> <span class="o">-</span><span class="nv">cfg</span> <span class="o">~/</span>.<span class="nv">local</span><span class="o">/</span><span class="nv">etc</span><span class="o">/</span><span class="nv">nncp</span>.<span class="nv">hjson</span> <span class="nv">server</span> <span class="nv">sync</span>
</pre></div>
<p>The <code>nncp-exec</code> command reads from stdin, which is why you need to have the <code>echo</code> command piping into it. With this, your static site generator can now run:</p>
<div class="highlight"><pre><span></span><span class="nv">nncp</span><span class="o">-</span><span class="nv">file</span> <span class="o">-</span><span class="nv">cfg</span> <span class="o">~/</span>.<span class="nv">local</span><span class="o">/</span><span class="nv">etc</span><span class="o">/</span><span class="nv">nncp</span>.<span class="nv">hjson</span> <span class="nv">index</span>.<span class="nv">html</span> <span class="nv">server</span>:<span class="nv">my_blog</span><span class="o">/</span><span class="nv">index</span>.<span class="nv">html</span>
<span class="nv">nncp</span><span class="o">-</span><span class="nv">file</span> <span class="o">-</span><span class="nv">cfg</span> <span class="o">~/</span>.<span class="nv">local</span><span class="o">/</span><span class="nv">etc</span><span class="o">/</span><span class="nv">nncp</span>.<span class="nv">hjson</span> <span class="nv">posts</span><span class="o">/</span><span class="nv">hello_world</span><span class="o">/</span><span class="nv">index</span>.<span class="nv">html</span> <span class="nv">server</span>:<span class="nv">my_blog</span><span class="o">/</span><span class="nv">hello_world</span><span class="o">/</span><span class="nv">index</span>.<span class="nv">html</span>
<span class="nv">nncp</span><span class="o">-</span><span class="nv">file</span> <span class="o">-</span><span class="nv">cfg</span> <span class="o">~/</span>.<span class="nv">local</span><span class="o">/</span><span class="nv">etc</span><span class="o">/</span><span class="nv">nncp</span>.<span class="nv">hjson</span> <span class="nv">css</span><span class="o">/</span><span class="nv">theme</span>.<span class="nv">css</span> <span class="nv">server</span>:<span class="nv">my_blog</span><span class="o">/</span><span class="nv">css</span><span class="o">/</span><span class="nv">theme</span>.<span class="nv">css</span>
<span class="nv">echo</span> <span class="o">|</span> <span class="nv">nncp</span><span class="o">-</span><span class="k">exec</span> <span class="o">-</span><span class="nv">cfg</span> <span class="o">~/</span>.<span class="nv">local</span><span class="o">/</span><span class="nv">etc</span><span class="o">/</span><span class="nv">nncp</span>.<span class="nv">hjson</span> <span class="nv">server</span> <span class="nv">sync</span>
<span class="nv">nncp</span><span class="o">-</span><span class="nv">call</span> <span class="o">-</span><span class="nv">cfg</span> <span class="o">~/</span>.<span class="nv">local</span><span class="o">/</span><span class="nv">etc</span><span class="o">/</span><span class="nv">nncp</span>.<span class="nv">hjson</span> <span class="nv">server</span>
</pre></div>
<p>The commands above will push the files to the server, and invoke <code>rsync</code> remotely to move the files to the website directory.</p>
<h2>Order of execution</h2>
<p>There's just one problem with the solution above. NNCP does not guarantee that packets are processed in order. When I tested it on my blog the <code>rsync</code> call was running before all the files were transferred. There's a clever way to solve the problem, though: NNCP packets can have different priorities. You can send the file packets with a higher priority than the execution (sync) packet, and call for the higher packets to be processed first:</p>
<div class="highlight"><pre><span></span><span class="nv">nncp</span><span class="o">-</span><span class="nv">file</span> <span class="o">-</span><span class="nv">nice</span> <span class="nv">PRIORITY</span> <span class="o">-</span><span class="nv">cfg</span> <span class="o">~/</span>.<span class="nv">local</span><span class="o">/</span><span class="nv">etc</span><span class="o">/</span><span class="nv">nncp</span>.<span class="nv">hjson</span> <span class="nv">index</span>.<span class="nv">html</span> <span class="nv">server</span>:<span class="nv">my_blog</span><span class="o">/</span><span class="nv">index</span>.<span class="nv">html</span>
<span class="nv">nncp</span><span class="o">-</span><span class="nv">file</span> <span class="o">-</span><span class="nv">nice</span> <span class="nv">PRIORITY</span> <span class="o">-</span><span class="nv">cfg</span> <span class="o">~/</span>.<span class="nv">local</span><span class="o">/</span><span class="nv">etc</span><span class="o">/</span><span class="nv">nncp</span>.<span class="nv">hjson</span> <span class="nv">posts</span><span class="o">/</span><span class="nv">hello_world</span><span class="o">/</span><span class="nv">index</span>.<span class="nv">html</span> <span class="nv">server</span>:<span class="nv">my_blog</span><span class="o">/</span><span class="nv">hello_world</span><span class="o">/</span><span class="nv">index</span>.<span class="nv">html</span>
<span class="nv">nncp</span><span class="o">-</span><span class="nv">file</span> <span class="o">-</span><span class="nv">nice</span> <span class="nv">PRIORITY</span> <span class="o">-</span><span class="nv">cfg</span> <span class="o">~/</span>.<span class="nv">local</span><span class="o">/</span><span class="nv">etc</span><span class="o">/</span><span class="nv">nncp</span>.<span class="nv">hjson</span> <span class="nv">css</span><span class="o">/</span><span class="nv">theme</span>.<span class="nv">css</span> <span class="nv">server</span>:<span class="nv">my_blog</span><span class="o">/</span><span class="nv">css</span><span class="o">/</span><span class="nv">theme</span>.<span class="nv">css</span>
<span class="nv">echo</span> <span class="o">|</span> <span class="nv">nncp</span><span class="o">-</span><span class="k">exec</span> <span class="o">-</span><span class="nv">nice</span> <span class="nv">NORMAL</span> <span class="o">-</span><span class="nv">cfg</span> <span class="o">~/</span>.<span class="nv">local</span><span class="o">/</span><span class="nv">etc</span><span class="o">/</span><span class="nv">nncp</span>.<span class="nv">hjson</span> <span class="nv">server</span> <span class="nv">sync</span>
<span class="nv">nncp</span><span class="o">-</span><span class="nv">call</span> <span class="o">-</span><span class="nv">nice</span> <span class="nv">PRIORITY</span> <span class="o">-</span><span class="nv">cfg</span> <span class="o">~/</span>.<span class="nv">local</span><span class="o">/</span><span class="nv">etc</span><span class="o">/</span><span class="nv">nncp</span>.<span class="nv">hjson</span> <span class="nv">server</span>
<span class="nv">nncp</span><span class="o">-</span><span class="nv">call</span> <span class="o">-</span><span class="nv">nice</span> <span class="nv">NORMAL</span> <span class="o">-</span><span class="nv">cfg</span> <span class="o">~/</span>.<span class="nv">local</span><span class="o">/</span><span class="nv">etc</span><span class="o">/</span><span class="nv">nncp</span>.<span class="nv">hjson</span> <span class="nv">server</span>
</pre></div>
<p>The first <code>nncp-call</code> will process only the file packets, while the second one will process the sync call.</p>
<h2>Calling in the background</h2>
<p>The nice thing about <code>nncp-file</code> and <code>nncp-exec</code> is that they queue commands and terminate quickly. Ideally the static site generator will call these commands to queue the operations, and the client will run <code>nncp-call</code> in the background periodically, to transfer the files and run the sync whenever the server is up. To do this, you can specify in the client configuration that the server should be called periodically:</p>
<div class="highlight"><pre><span></span>neigh: {
# client
self: {
id: WWW
exchpub: XXX
signpub: YYY
noisepub: ZZZ
}
# "self" from server
server: {
id: AAA
exchpub: BBB
signpub: CCC
noisepub: DDD
addrs: {
internet: server.example.com:5400
}
calls: [
{
cron: "0 * * * *"
nice: PRIORITY
}
{
cron: "10 * * * *"
nice: NORMAL
}
]
}
}
</pre></div>
<p>The configuration above will make the client call the server every hour to process priority packets, and 10 minutes later call it again to process normal packets. This way there's no need to manually call <code>nncp-call</code>.</p>
<p><a href="https://blog.taoetc.org/how_to_publish_a_static_site_over_nncp/index.html">Permalink</p>
Re: How you were using the Internet in the 1991-1995 and 1995-2005?tag:blog.taoetc.org,2021-11-15:re_how_you_were_using_the_internet_in_the_1991-1995_and_1995-2005/index.html2021-11-15T03:03:51Z
Looking back at my early days on the internet
<p><a href="https://blog.taoetc.org/re_how_you_were_using_the_internet_in_the_1991-1995_and_1995-2005/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>A reply to <a href="gemini://szczezuja.flounder.online/gemlog/2021-11-13-How-you-were-using-the-Internet.gmi">How you were using the Internet in the 1991-1995 and 1995-2005?</a>.</p>
<h2>1993-1995</h2>
<p>My earliest memory of the internet is from 1993, when I was 15. I was living in Brazil, and my dad brought back 2 modems from a trip to the US. The modems were 14.4 kbps, and I don't remember them having a manual or a brand. I also had no idea how to use them.</p>
<p>I had an older friend who was in college studying computer science, and he had a special phone number to access the internet. The number was different from any other phone number I've ever seen, with 20+ digits. He gave me the number, the login ("alunos", meaning "students") and the password: "alunos93".</p>
<p>He shared the login with me so we could play a multi-user dungeon (MUD) together, connecting via <code>telnet</code>. That's all I remember doing on the internet back then. I was hooked, and spent hours connected. I remember I was worried about the phone bill, so I called the phone company and asked how much it cost to call the number.</p>
<p>"Sir, this number does not exist", was the response.</p>
<p>A bill never came, so I kept dialing every day, for hours, and playing the MUD.</p>
<p>In January 1994 I was no longer able to log in. I tried the password "alunos94" and it worked.</p>
<p>In 1995 my dad got a new job and we moved to a new city. My friend's connection no longer worked, even after trying the obvious password "alunos95". I was desperate for internet, but there were no commercial providers in Brazil back then, and I was still not in college.</p>
<p>It was at that time that I discovered bulletin board systems (BBS). I signed up for a few BBSes in my new city. It was fun an exciting: there was software to be downloaded, games to be played, and I could exchange messages. I was surprised to discover that messages could even be sent between different BBSes, though it could take days.</p>
<p>I even ran my own BBS back in 1995, with a friend who was younger but had more experience than me.</p>
<p>But what I really wanted was to use the internet again. I could use it at my dad's company, and I remember reading books on how to connect to the internet. I would try configuring my computer using internet settings that were made for the US, hoping it would somehow work.</p>
<p>Eventually I was able to get internet access via my dad's company. I would visit websites by guessing their URL. I remember visiting the Louvre and looking at art, as the images downloaded slowly over my dial-up connection. I also remember visiting the Brazilian space agency, mesmerized as I downloaded satellite images showing the large scale weather patterns!</p>
<p>I applied for college at the end of 1995. I don't remember using Gopher a lot in this period, but I used it to access the university and check the results from the entrance examination. That's how I discovered I was going to college: via Gopher.</p>
<h2>1996-2000</h2>
<p>In 1996 I moved to a new city for college. The city had no commercial internet providers, but a few months later one opened. I was one of their first clients. It was nice being able to keep in touch with my dad via email. I remember daydreaming about the day when everyone would have an email address... "you'd meet someone and exchange email addresses!". It felt like it would never happen.</p>
<p>In college we used mostly dumb-terminals for email, library, and other student activities. At home I was using Netscape Navigator. I remember friends making fun of me because I would check my email after waking up, or because I would read the news online before going to college. "Such a nerd!", they would say.</p>
<p>Little did they know.</p>
<p>During this time I made my first website in Geocities, talking about climate change. I learned Java so I could write applets. One of my first applets was a simulation of James Lovelock's <a href="https://en.wikipedia.org/wiki/Daisyworld">Daisyworld</a>.</p>
<p>In 1998 I started using Linux at home, and eventually set up a server at my university for the students. We had an FTP server so we could share studying materials, a forum, and an email server. By 2000, my last year in college, I had set up a LAMP (Linux, Apache, MySQL, PHP) server with a friend, teaching about oceanography (our major) and hosting mailing lists for oceanographic projects.</p>
<h2>2001-2005</h2>
<p>In 2001 I went to grad school, and continued building websites on a LAMP stack. I also started a blog, the first of dozens. My first blog used an engine called <code>b2</code>, and over the years I ran WordPress, PyBlosxom, Blogger, Blogspot, and a dozen of inhouse engines. I was jealous of the people using Movable Type, a commercial blog engine written in Perl.</p>
<p>There was a small community of bloggers in Brazil back then, and I met a good number of them. I'm still friends with a few. It was a nice community, something that I've only recently rediscovered with Gemini.</p>
<p>In 2003 I moved to the Netherlands for 1 year, for my PhD. I signed up for an amazing provider called "XS4ALL", a bore-bones ISP that would provide a connection, a real unblocked IP address, and take your money. It was a hacker's dream.</p>
<p>The most amazing thing from this period was Skype. I remember using Skype to call my family back in Brazil, and it was mind-blowing to be able to use the internet to talk with someone on the other side of the world like that. The quality was incredible and you couldn't beat the price: free.</p>
<p>In 2005 I participated in the first Google Summer of Code. I was already programming in Python back then, so I submitted a proposal to the Python Software Foundation (PSF) to work on a scientific data server that I had developed. The proposal was accepted, and I was paid to work for 3 months on an open-source project, being mentored by Paul DuBois, the creator of Numeric (a predecessor of Numpy).</p>
<p>Participating in the Google Summer of Code was one of the critical events that changed my life. I had learned about free software on the internet, learned how to program, and was using it to develop my own projects. A decade later I would become a software engineer, and for the last 4 years I've been working almost full time on free software.</p>
<p><a href="https://blog.taoetc.org/re_how_you_were_using_the_internet_in_the_1991-1995_and_1995-2005/index.html">Permalink</p>
Running my blog on solar powertag:blog.taoetc.org,2021-11-14:running_my_blog_on_solar_power/index.html2021-11-14T23:11:26Z
Notes on a work-in-progress project
<p><a href="https://blog.taoetc.org/running_my_blog_on_solar_power/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>I'm planning to run my blog on solar power. Initially I'm migrating only my blog (https://blog.taoetc.org/) to a solar-powered server, but the goal is to eventually migrate my Gemini capsule (gemini://taoetc.org/) as well. These are some notes on the project, currently in progress.</p>
<h2>Hardware</h2>
<p>I'm using an old Raspberry Pi Zero W that I had at home. I added a new SD card with 128 GB, and installed Debian on it. I've been using Debian since their 2.0 release, in 1998, and I always admired the project philosophy and integrity. It's my go-to distribution for servers.</p>
<p>The Pi is currently running connected to a <a href="https://poniie.com/products/17">Poniie PN1500 Watt Meter</a> so I can measure how much power it needs, in order to dimension the solar panel and battery that I'm going to use. So far I've seen the Pi run consuming around 0.8W, but I want to run my website for a week to get a better estimate.</p>
<p>Once I have the data on power consumption I want to look at weather data to figure out the number of consecutive cloudy days I should expect where I live. With information I can choose a battery based on my desired uptime, and a solar panel that can keep it charged.</p>
<p>I recently learned about <a href="https://www.bbc.com/future/article/20210223-the-battery-invented-120-years-too-soon">Nickel-iron batteries</a>. They can last for decades, have a smaller environmental impact, and are more resilient to discharges. Unfortunately they are very expensive (though they should pay off on the long run): the cheapest battery I could find costs $1000, too much for a small project like this. Because of this, I'll probably use a standard lead battery.</p>
<h2>Networking</h2>
<p>Currently the Pi is connecting to the internet via WiFi, but I'm planning to have it connected via a 4G connection. I have a Google Fi plan on my phone, and I have a couple SIM cards that are data-only associated with my account. I'm not expecting data usage to be high, but if that happens I might consider using the WiFi as the main connection and 4G as a backup.</p>
<p>To connect to 4G I'm using a <a href="https://kuwfi.shop/products/kuwfi-4g-wifi-router-dongle-unlocked-3g-4g-usb-modem-external-antenna-mobile-wireless-wifi-hotspot-with-sim-card-slot">KuWFi 4G WiFi Router Dongle</a>. It works great with Linux: it plugs via USB, creating both a hotspot as well as an ethernet interface. I tested an older model and it works fine, but the reception was poor. I ordered the linked model since it has a connector for an external antenna, and I'm hoping that I'll get a better signal with it.</p>
<p>In order to give the Pi a fixed, unfiltered, IP address I'm using a <a href="https://hoppy.network/">WireGuard commercial provider</a> called hoppy. I've been using them for a few months and I really like the product: for $8 a month you get a real IPV4 address that's trivial to set up in Linux.</p>
<p>I'm using <a href="http://www.nncpgo.org/">NNCP</a> to transfer files to the Pi. I learned about NNCP reading the article <a href="gemini://jb55.com/log/2021-11-06-airgapped-bitcoin-node-nncp.gmi">Building an airgapped bitcoin node with NNCP</a>, and loved the concept. It's also perfect for the unreliable communication link with my solar Pi.</p>
<p>Now, when I publish my site the engine will call <code>nncp-file</code> for each modified file, queuing it to transfer to the Pi. It also calls <code>nncp-exec</code> at the end, queuing a command that copies the modified files to the web directory. Every hour my laptop then runs <code>nncp-call</code>, trying to send the files and command to the Pi.</p>
<h2>Software</h2>
<p>The Pi is running <code>nginx</code> to serve the static files from my blog. Once I have the blog fully migrated I want to install a lightweight Gemini server, and also host the contents of my capsule there.</p>
<p>I found a project called <a href="https://github.com/collinturney/solarshed">solarshed</a> that has a Python script to monitor a cheap Renogy solar panel controller. I'm planning to buy the same controller, and have the Pi collect data on the battery and the solar panel.</p>
<p>One of the things I want to do is to display the current data on my blog, similar to how <a href="https://solar.lowtechmagazine.com/">LOW←TECH MAGAZINE</a> does. Since my blog is static I'm planning to run a script periodically, generating a CSS file that I can include on my site to show the battery level.</p>
<p><a href="https://blog.taoetc.org/running_my_blog_on_solar_power/index.html">Permalink</p>
Monopoly was originally satiretag:blog.taoetc.org,2021-11-10:monopoly_was_originally_satire/index.html2021-11-10T18:06:15Z
<p><a href="https://blog.taoetc.org/monopoly_was_originally_satire/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>Really interesting post by Tracy Durnell, on how the Monopoly game had a different set of collaborative rules called "Prosperity". Everyone would win when the person that started with the least amount of money had doubled it.</p>
<p>The quote that ressonated with me was this one, though:</p>
<blockquote>
<p>The equal right of all men to use the land is as clear as their equal right to breathe the air – it is a right proclaimed by the fact of their existence.</p>
</blockquote>
<p><a href="https://blog.taoetc.org/monopoly_was_originally_satire/index.html">Permalink</p>
Thoughts on the command line interfacetag:blog.taoetc.org,2021-11-10:thoughts_on_the_command_line_interface/index.html2021-11-10T04:04:09Z
My story with the command line interface, from 1998 to today
<p><a href="https://blog.taoetc.org/thoughts_on_the_command_line_interface/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>My first experience using Linux was in 1998.</p>
<p>I had learned about Linux a few years before from my father, and in 1996 I volunteered at a lab in my University that had a Linux computer, mostly used for email. I didn't understand that machine. While it booted up I would see an endless scrolling of enigmatic lines, nothing that made any sense to me. And that was fascinating!</p>
<p>I was familiar with a command line, having learned programming on a Commodore 64, so the black terminal didn't intimidate me. But I wanted to use it, understand it. It was a glimpse of a different world, one that I wanted to conquer.</p>
<p>After an unsuccessful experience trying to install Red Hat on my personal computer in 1997, I discovered Slackware. I started reading the Slackware manuals, and some things started to make sense to me. I vaguely remember installing Ghostscript on my Windows 95 machine, so I could read the Postscript manuals. Printer ink was expensive for an undergrad student in Brazil, so I printed just the initial chapter on installation, so I could follow along while I installed a new operating system on the only computer of the house.</p>
<p>It took me days to download all the Slackware disks.</p>
<p>Back then I was lucky to even have dial-up at home, and I didn't have access to any computers at the University that I could use to download the disks -- all I had access to were the <a href="https://www.ibm.com/docs/en/zos-basic-skills?topic=enhanced-introduction-3270-terminal">IBM 3270 terminals</a> that students used to read their emails. Dial up was expensive because phone calls were expensive. To download Slackware I had to connect between midnight and 6am, a period when the calls had a fixed cost. For days I would connect immediately after midnight, go to sleep, wake up at 5:50am, and disconnect.</p>
<p>After a couple weeks, I had all the disks downloaded. I booted into the first one, and proceeded to install Slackware. "Do you want to install yacc? Yacc is similar to bison." [Yes]. I powered through the installation as best as I could, with the little knowledge I had -- I was an oceanography major. After going through all the disks the computed rebooted into a login prompt. I typed my username, password, and there was I, in command line.</p>
<blockquote>
<p><code>startx</code></p>
</blockquote>
<p>That was the command that should take me to a graphical interface. But my graphics card was not supported by X. For me, Linux meant using the command line to write emails, browse the internet, and building parsers.</p>
<p>Eventually I was able to save some money and buy a graphics card that was supported. This was a few years later, when I discovered Conectiva Linux, a Brazilian distribution that later merged with Mandrake to become Mandriva. I remember trying the first version of GNOME, and also how impressed I was by KDE 1.0... "they accomplished what Windows 98 dreamed of", I remember saying.</p>
<p>But even then, I was still using the command line. I wrote my PhD thesis using <code>vim</code>, I've used <code>grep</code>, <code>sed</code>, and <code>awk</code> thousands of times in the last 20 years. For many years I used the <code>mutt</code> email client, and I'm back to it today. When I first learned about <code>vim</code> in the 90s I remember thinking "why would anyone use this editor?", and today I'm using it to write this blog post. I use it to write emails, and I use it to do the core of my job: programming.</p>
<p>Lately I started using <code>newsboat</code>, a command line interface news reader, for my RSS feeds. I use <code>ncmpc</code> to listen to music. And <code>amfora</code> for navigating Gemini. It's nice. I can use a 10-year old laptop to do everything. It might be nostalgia, but I like to think that new people who have never been exposed to the command line before would also see a glimpse of that world I saw back in the 90s, fascinating and mysterious.</p>
<p><a href="https://blog.taoetc.org/thoughts_on_the_command_line_interface/index.html">Permalink</p>
Old softwaretag:blog.taoetc.org,2021-11-05:old_software/index.html2021-11-05T02:02:33Z
Thoughts on all the "old" software I've been using lately
<p><a href="https://blog.taoetc.org/old_software/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>A couple weeks ago I started playing <a href="http://nwn.beamdog.com/">Neverwinter Nights</a> (NWN). The original game is from 2002, and I remember playing it back in... 2007? It's a Dungeons & Dragons (D&D) game, and you can build and run your own campaigns, so for a while I was excited to try it with my group of childhood friends, since we used to play D&D together when we were teenagers. Unfortunately I was never able to get enough people excited for an online campaign back then, and I stopped playing it.</p>
<p>I don't remember why, but I decided to give it another try, and I've been having a lot of fun playing the original campaign! This is a game that's almost 20 years old, but the graphics are still pretty good! Granted, this is an enhanced version running on modern hardware, but when my girlfriend first saw me playing (on a 34" ultrawide monitor) she said "this is gorgeous!" The game has a cool effect that your character is never occluded as you move the camera, and many of the in-game locations are beautiful.</p>
<p>And one thing hasn't aged that much: the game play. And that's the great thing about old games: the ones that we still remember are still great, because they had great stories.</p>
<p>Something about playing NWN makes me happy. I don't need a PlayStation 5 to have fun with games. In fact, I never played on a modern console, so the graphics from NWN are one of the best I've seen in a game. I like the idea that there's is enough content already available for me for free or cheap, enough for a lifetime: books and movies from my local library, music from independent artists on Bandcamp, games that cost a few dollars on <a href="http://nwn.beamdog.com/">Good Old Games</a>.</p>
<p>I'm writing this blog post on a 10-year old laptop. It works great! It runs a reasonably secure OS, and I can do everything I need: write blog posts, interact with other people's content, develop software. There's no need for upgrading my computer every 2 years. Why should I? <a href="https://solar.lowtechmagazine.com/2020/12/how-and-why-i-stopped-buying-new-laptops.html">Laptops don't change</a>.</p>
<p>Recently I started using <a href="https://mopidy.com/">Mopidy</a> to manage my music collection. It runs on my other laptop, which I use for music production and is connected to nice speakers. I use a command-line application to interact with Mopidy, searching for songs, adding them to the queue, and playing/pausing the music. I can do that from any other computer, usually my work laptop during the day.</p>
<p>The thing I love about Mopidy is that it aggregates everything. My local MP3/FLAC files are there. But also my 160+ albums on Bandcamp. And if I want to listen to something that's not on my collection it will search on Bandcamp (first) and YouTube Music (second) for it. I feel that for the first time in 5+ years I have some control over what I'm listening -- I'm listening to <strong>my</strong> music collection.</p>
<p><a href="https://blog.taoetc.org/old_software/index.html">Permalink</p>
Backing up photos automaticallytag:blog.taoetc.org,2021-10-30:backing_up_photos_automatically/index.html2021-10-30T05:05:12Z
Setting up NextCloud for a friend (and myself)
<p><a href="https://blog.taoetc.org/backing_up_photos_automatically/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>I've been organizing a virtual happy hour with my childhood friends every Friday since last year, when COVID-19 started. We meet over Zoom and chat about politics, food, and games, mostly. It's been one of the few positive aspects of the pandemic -- reconnecting with old friends and even making new friends, since the original circle of people attending has shifted considerably.</p>
<p>Today my best friend was telling us how he accidentally broke the screen on his new phone. On the phone he had ~2 months of photos of his newborn, which unfortunately he hadn't backed up yet. We looked into ways he could try to recover the photos, without any successful ideas. Since he has an Android phone, eventually someone asked why wasn't he backing up the photos automatically to Google Photos. He answered that he didn't want to give Google access to his photos, which I understand.</p>
<p>While we where talking I remembered that <a href="https://nextcloud.com/">NextCloud</a> offers a way of backing up photos automatically. I quickly set up a VPS and installed NextCloud on it -- it took me literally less than 30 minutes! Before the happy hour was over I told my friend that I had created an account for him, that he could use to automatically back up his photos. I also used the opportunity to set up an account for me, since my phone storage is almost full and I know that Google Photos doesn't store photos at the original resolution.</p>
<p>Now we just need to find a way to recover the photos from his OnePlus phone...</p>
<p><a href="https://blog.taoetc.org/backing_up_photos_automatically/index.html">Permalink</p>
Exploring Qubes OStag:blog.taoetc.org,2021-10-27:exploring_qubes_os/index.html2021-10-27T01:01:44Z
Running a secure/private OS on an old laptop
<p><a href="https://blog.taoetc.org/exploring_qubes_os/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>I have an old Thinkpad laptop that I love. It's a T420, originally released in 2011, but I bought mine used probably around 2015. It's heavy, the keyboard feels good, and the battery still holds a charge. My favorite feature is the white LED at the top of the screen, pointing down to illuminate the keys when working in the dark. Recently I upgraded the memory to 16 GB (even though officially only 8 GB are supported), and I replaced a keyboard that could only type the numbers 5-6 (letters worked fine!). The disk is an old HDD with 500 GB that I'll probably replace with an SSD soon.</p>
<p>Last week I learned about <a href="https://www.qubes-os.org/">Qubes OS</a>. It's an operating system that relies heavily on virtualization (using Xen). The main UI runs a virtualized Linux, from which you can spawn more virtual machines. Each machine has different levels of trust. For example, you have an untrusted machine that is responsible to connecting to the network. A firewall machine then connects to it, giving filtered and protected network access to the other machines.</p>
<p>The idea is that you create separate machines for separate parts of your digital life. I have a "personal" machine, which currently I'm using to write my blog and work on its static site generator. By default these machines have no access to USB devices, and the installed software resets when they get restarted (data persists, though). You can easily create disposable machines, and even machines with Tor installed. This provides security and privacy.</p>
<p>I installed Qubes OS on my T420, and I've been enjoying it so far! I'm running <code>i3</code> as the window manager on my main ("dom0") machine. A cool thing about the OS, regardless of the window manager, is that when you spawn an application on a virtual machine only the window pops up, and the color of the window decoration indicates the trust level. So I might have a red terminal, from an untrusted machine, running next to a yellow terminal, somewhat trusted. And the title of each window has as a prefix the machine name.</p>
<p>The system is a bit slow, requiring a lot of RAM if you're running multiple machines at the same time. Luckily you can stop machines that you're not using; the only downside is that it will be slower to run an application, since it has to start the machine first. My plan is to use the laptop mostly for simple applications, so it's not that a big of a problem. But I'm still considering upgrading the HDD to an SSD to see if I can get a little more performance from the laptop.</p>
<p>Edit: I found two 128 GB SSDs lying around; one of them was actually <strong>inside</strong> the T420! I re-installed Qubes OS on the fastest one, and I'm going to use the other one for backups.</p>
<p><a href="https://blog.taoetc.org/exploring_qubes_os/index.html">Permalink</p>
Hosting a website for 500 yearstag:blog.taoetc.org,2021-10-25:hosting_a_website_for_500_years/index.html2021-10-25T22:10:48Z
Saving my blog to archive.org every time there's a new post
<p><a href="https://blog.taoetc.org/hosting_a_website_for_500_years/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>I saw this post on Hacker News (the posts on HN are usually good, just don't read the comments) asking for the <a href="https://news.ycombinator.com/item?id=28957573">Best way to host a website for 500 years?</a></p>
<p>One of the design goals of my static site generator, Nefelibata, is persistence. Everything is saved as text: posts are written as Markdown and stored inside RFC 822 files; metadata is stored in YAML files; external images are mirrored locally, and external links are saved to the Wayback Machine in Archive.org.</p>
<p>One thing I added today that I had forgotten was to also save my blog every time there's a new post! I now modified it to call Archive.org to save the new post, as well as the main landing page. That's my bet on the best way to keep my blog around for the next 500 years.</p>
<p>And if you're reading this from the future... I guess it worked?</p>
<p><a href="https://blog.taoetc.org/hosting_a_website_for_500_years/index.html">Permalink</p>
The other side of the newspaper clippingtag:blog.taoetc.org,2021-10-21:the_other_side_of_the_newspaper_clipping/index.html2021-10-21T03:03:36Z
Showing a random news headline with each of my blog posts
<p><a href="https://blog.taoetc.org/the_other_side_of_the_newspaper_clipping/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>One day, while looking at old newspaper clippings with my dad, he told me:</p>
<blockquote>
<p>The other side of the clipping is always more interesting than the clipping itself!</p>
</blockquote>
<p>And while "always" was a bit of stretch, it was often true. It was fun turning the clipping around and reading the half-news that were captured by accident.</p>
<p>I decided to do something similar with my blog. My static generator has an "assistant" that fetches the current weather when I create a new post (even though it's currently not displayed anywhere). I used it as a template to write another "assistant" that fetches a random headline of the day, and stores it in a file called <code>news.yaml</code>.</p>
<p>Later, I can then incorporate this in my templates. The static generator loads all YAML files that live alongside a post (like <code>news.yaml</code>, or <code>current_weather.yaml</code>), and exposes them to the templates, eg:</p>
<div class="highlight"><pre><span></span><span class="x">Current weather: </span><span class="cp">{{</span> <span class="nv">post.metadata.current_weather.current_condition.weatherDesc.value</span> <span class="cp">}}</span><span class="x"></span>
<span class="x">In this day: "</span><span class="cp">{{</span> <span class="nv">post.metadata.news.title</span> <span class="cp">}}</span><span class="x">"</span>
</pre></div>
<p>For now, I'm just happy in keeping these headlines saved.</p>
<p>Edit: I've decided to only show the headlines when the post is older than 1 year.</p>
<p><a href="https://blog.taoetc.org/the_other_side_of_the_newspaper_clipping/index.html">Permalink</p>
Building an IndieAuth endpointtag:blog.taoetc.org,2021-10-20:building_an_indieauth_endpoint/index.html2021-10-20T20:08:08Z
<p><a href="https://blog.taoetc.org/building_an_indieauth_endpoint/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>James writes a clear and informative post on his IndieAuth endpoint, explaining IndieAuth and RelMeAauth authentications. I wrote <a href="https://github.com/betodealmeida/este-sou-eu" title="My IndieAuth endpoint">my own endpoint</a> last year, also using Flask, but when I tested it recently it wasn't working for some reason. I'll have to take another look when I have the chance.</p>
<p>Edit: <a href="https://github.com/betodealmeida/este-sou-eu/commit/869afd5a25ab66d7d8bf9000f3be3fa7139bc5bf" title="Link to the fix">it's fixed!</a></p>
<p><a href="https://blog.taoetc.org/building_an_indieauth_endpoint/index.html">Permalink</p>
Using emojis on the Linux terminaltag:blog.taoetc.org,2021-10-20:using_emojis_on_the_linux_terminal/index.html2021-10-20T15:03:23Z
<p><a href="https://blog.taoetc.org/using_emojis_on_the_linux_terminal/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>For my last post I had to configure emojis to show up on my Linux terminal (Alacritty). This link was helpful!</p>
<p><a href="https://blog.taoetc.org/using_emojis_on_the_linux_terminal/index.html">Permalink</p>
Re: Questions for October 🤔tag:blog.taoetc.org,2021-10-20:re_questions_for_october/index.html2021-10-20T15:03:39Z
Answering 4 questions posed by JBanana
<p><a href="https://blog.taoetc.org/re_questions_for_october/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>Answering <a href="gemini://freeshell.de/gemlog/2021-10-19.gmi" title="Link to the four questions">these four questions</a> from JBanana:</p>
<blockquote>
<p>What's the stupidest way you ever hurt yourself?</p>
</blockquote>
<p>It was New Year's Eve 2012-2013. I was at the beach, playing ball with some kids, and had quite a few beers already. I decided to do a flip and throw the ball while I was mid-air. It worked, but a wave came in just before, lowering the water level as I performing my stunt.</p>
<p>Without enough water for my fall I went face first on the sand. I scratched my forehead and nose bridge, and came out of the water with blood dripping all over my face. The kids were so scared that they ran away! My partner at the time covered my face with a t-shirt and took me to the ER.</p>
<p>I still have the scar on my forehead, the shape of a kiss.</p>
<blockquote>
<p>You have a coupon for one free thing from a place that sells garden stuff, so what do you take?</p>
</blockquote>
<p>Can I get a succulent? I would get a succulent. They're easy to care for, and the first flower I ever got was a succulent, so they're dear to me.</p>
<blockquote>
<p>When you last used a needle and thread, what were you sewing?</p>
</blockquote>
<p>I was in Israel. I usually pack very light when I travel: I wear a pair of jeans, comfortable shoes and a warm jacket, and I carry a backpack with 4-5 t-shirts and 4-5 pairs of socks and underwear. If I'm staying more than 5 days I'll find a place to do wash the shirts, socks, and underwear.</p>
<p>This time, the pair of jeans I had was pretty used, and it ripped in the crotch. I used the sewing kit from the hotel to fix it on the first day.</p>
<p>Unfortunately, when I went out on the first night, I discovered that smoking is allowed in bars in Israel. When I woke up the next day not only my pants were ripping again, but they also smelled horribly of cigarettes. I took a long shower, cleaned myself, and then wore the pants to the closest clothing store, where I bought a new pair and threw the old one away.</p>
<blockquote>
<p>What song do you want played at your funeral, and why?</p>
</blockquote>
<p>I think I'd want something calm and contemplative. I'd go with Brian Eno's "Thursday Afternoon".</p>
<p><a href="https://blog.taoetc.org/re_questions_for_october/index.html">Permalink</p>
Adding RSVP'd events to my calendartag:blog.taoetc.org,2021-10-16:adding_rsvpd_events_to_my_calendar/index.html2021-10-16T19:07:06Z
How my blog automatically adds RSVP'd events to my calendar
<p><a href="https://blog.taoetc.org/adding_rsvpd_events_to_my_calendar/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>I recently added support for <a href="https://indieweb.org/rsvp">RSVP posts</a> to my static site generator, allowing me to confirm my participation in IndieWeb events.</p>
<p>To create a new RSVP I run:</p>
<div class="highlight"><pre><span></span>nb new -t rsvp "RSVPing to an event"
</pre></div>
<p>This will create a new directory and open my editor with this template:</p>
<div class="highlight"><pre><span></span><span class="n">subject</span><span class="o">:</span> <span class="n">RSVPing</span> <span class="n">to</span> <span class="n">an</span> <span class="n">event</span>
<span class="n">summary</span><span class="o">:</span>
<span class="n">keywords</span><span class="o">:</span>
<span class="n">rsvp</span><span class="o">-</span><span class="n">url</span><span class="o">:</span>
<span class="n">rsvp</span><span class="o">-</span><span class="n">name</span><span class="o">:</span>
<span class="n">rsvp</span><span class="o">-</span><span class="n">answer</span><span class="o">:</span>
</pre></div>
<p>After populating the fields and optionally adding more content I can build the Gemini and HTML versions of my blog:</p>
<div class="highlight"><pre><span></span>nb build
</pre></div>
<p>This command will fetch the page of the event (<code>rsvp-url</code>) and try to parse the information from an <code>h-event</code> microformat. If the information is found, the event is automatically added to my calendar.</p>
<p>The last step is publishing the post:</p>
<div class="highlight"><pre><span></span>nb publish
</pre></div>
<p>This will push the HTML pages to an S3 bucket and the Gemini pages to an FTP server. More importantly, it will send a webmention to the event page, confirming my participation.</p>
<p><a href="https://blog.taoetc.org/adding_rsvpd_events_to_my_calendar/index.html">Permalink</p>
IndieWeb meetup 2021-10-20tag:blog.taoetc.org,2021-10-16:indieweb_meetup_2021-10-20/index.html2021-10-16T17:05:42Z
<p><a href="https://blog.taoetc.org/indieweb_meetup_2021-10-20/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>Last year I attended an IndieWebCamp event and I really enjoyed the experience. It was a weekend long event, and I built an <a href="https://github.com/betodealmeida/cassette-tape-player/">SVG-based music player</a> for my blog. The player has the shape of a cassette tape, and the spools animate as the song plays.</p>
<p>Even though the community was great I never participated in other events. I moved, switched jobs, and my static site generator was put aside for almost a year. Now that I've been working on it again I'd love to reconnect with the IndieWeb community and participate in more events.</p>
<p><a href="https://blog.taoetc.org/indieweb_meetup_2021-10-20/index.html">Permalink</p>
The biggest change in my lifetimetag:blog.taoetc.org,2021-10-16:the_biggest_change_in_my_lifetime/index.html2021-10-16T02:02:19Z
Reflecting about the biggest change I've seen in my lifetime
<p><a href="https://blog.taoetc.org/the_biggest_change_in_my_lifetime/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>I'm 43 years old.</p>
<p>When I was 19, as an oceanography student, I participated in a <a href="https://campagnes.flotteoceanographique.fr/campagnes/97080060/" title="The SAMBA3 research cruise">research cruise</a> that collected 605 KB of data. Back then, it was common for researchers to go out to the ocean once a year, collect a few megabytes of data, and spend the rest of the year analyzing the data. Graduate students would write their whole theses on datasets like this.</p>
<p>At the age of 35 I got my first big job in tech, working as a data engineer at Facebook. My manager left, and I became the owner of a growth accounting framework. One of the first things I did was to clean up a table that had historical data no one ever accessed. I deleted 2 petabytes of data. It would take 9 thousand years of daily cruises like the one I did in 1997 to collect that amount of data.</p>
<p>I grew up bored. As a kid, I read all the books we had at home. Books that I normally wouldn't read, that normally wouldn't catch my attention. I read them because I was bored. Being bored forced me to be creative, to go out and explore, to try to build things that would never work, to read book I would never read.</p>
<p>At the age of 43, I'm never bored. I have a never-ending stream of books, movies, facts, photos, shows, songs... all in my pocket. There's always an interesting video to watch, an amazing open-source project to study, a new inspiring jam. There's always some news begging for my opinion, there's always a urgent matter, always a tragedy of the day.</p>
<p>I miss being bored. In my lifetime we went from too little to too much. Too much information. Too much data. Too many songs.</p>
<p>We receive the news and we build our outrage, we laugh at how stupid our opponents are. That's our daily battle. We consume content, accumulating the knowledge and the ideas that someday we'll transform into creation — but that day never arrives. We consume passively, we respond passively.</p>
<p>What can we do about it?</p>
<p>A few months ago I read the article <a href="https://www.econlib.org/archives/2013/04/make_your_own_b.html">Make Your Own Bubble in 10 Easy Steps</a>, and I found some good lessons there. The most important one to me was "Stop paying attention to things that aggravate you unless (a) they concretely affect your life AND (b) you can realistically do something about them.". Another one is to consume curated news: "If you need to know about world politics, read history books, not newspaper articles."</p>
<p>I've disconnected from Facebook and Twitter. I follow people on Mastodon that I like, and I interact with people that think like me. I try to act consciously, and not just as a reaction to the indignation of the week. And I try to allow myself to be bored. Because I miss being bored.</p>
<p><a href="https://blog.taoetc.org/the_biggest_change_in_my_lifetime/index.html">Permalink</p>
Converting Markdown to Gemtexttag:blog.taoetc.org,2021-10-12:converting_markdown_to_gemtext/index.html2021-10-12T01:01:24Z
How I ended up implementing my own Markdown to Gemtext converter in Python
<p><a href="https://blog.taoetc.org/converting_markdown_to_gemtext/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>I received an email from <a href="gemini://idiomdrottning.org/">Sandra Snan</a> today, recommending two different programs to convert Markdown to Gemtext, since I've been having problems with multi-line blockquotes with <code>md2gemini</code>. Sandra suggested <code>gemgen</code> and <code>7off</code>, mentioning the latter was slower but better with links.</p>
<p>Since I'm not really worried about performance I cloned the <code>7off</code> repo, installed <a href="https://www.call-cc.org/">CHICKEN scheme</a>, and gave it a try. It was my first time compiling a scheme program, so it took me a couple minutes to figure out that I had to run <code>chicken-install</code>. Once I did that <code>7off</code> worked great with my blog posts, except that it doesn't support triple backticks for code blocks, only 4-space indentation.</p>
<p>While scheme looks interesting, I was really looking for a Python solution, since it's easier to integrate with my static site generator. Since I'm already using <a href="https://pypi.org/project/marko/">marko</a> to parse Markdown in order to extract links I decided to write my own <code>GeminiRenderer</code>, and it turned out to be much simpler than I expected. <a href="https://github.com/betodealmeida/nefelibata/commit/0b027665720095b4a9e424f3d4b4368b4891ae49#diff-ab5faf1b552d0b5bd47367ed090c088c3228b20cf6338c94d991ad9dcdbc4222R31-R117">My Gemini renderer</a> is <100 LOC, and most of that is comments and whitespace.</p>
<p><a href="https://blog.taoetc.org/converting_markdown_to_gemtext/index.html">Permalink</p>
Testing webmentiontag:blog.taoetc.org,2021-10-11:testing_webmention/index.html2021-10-11T15:03:56Z
Testing my webmention implementation
<p><a href="https://blog.taoetc.org/testing_webmention/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>I've been working on my static site generator, <a href="https://github.com/betodealmeida/nefelibata">Nefelibata</a>, since I learned about Gemini. I decided to rewrite it from scratch, since even though the initial design was very modular it didn't account for multiple protocols. The assumption that the user would generate HTML for the web was present from the start.</p>
<p>Nefelibata uses Markdown for posts, so it was relatively easy to build both Gemtext and HTML from the source. I'm still having some problems with multi-line blockquotes, and I might end up implementing my own Markdown -> Gemtext convertor. But I'm happy with the current functionality.</p>
<p>On the web-side, one of the things that I always enjoyed was implementing and using webmentions. This is a test of the new plugin that I wrote. If you're reading this in Gemini please ignore these links and forgive the noise.</p>
<p><a href="https://webmention.rocks/test/1">Test 1</a>
<a href="https://webmention.rocks/test/2">Test 2</a>
<a href="https://webmention.rocks/test/3">Test 3</a>
<a href="https://webmention.rocks/test/4">Test 4</a>
<a href="https://webmention.rocks/test/5">Test 5</a>
<a href="https://webmention.rocks/test/6">Test 6</a>
<a href="https://webmention.rocks/test/7">Test 7</a>
<a href="https://webmention.rocks/test/8">Test 8</a>
<a href="https://webmention.rocks/test/9">Test 9</a>
<a href="https://webmention.rocks/test/10">Test 10</a>
<a href="https://webmention.rocks/test/11">Test 11</a>
<a href="https://webmention.rocks/test/12">Test 12</a>
<a href="https://webmention.rocks/test/13">Test 13</a>
<a href="https://webmention.rocks/test/14">Test 14</a>
<a href="https://webmention.rocks/test/15">Test 15</a>
<a href="https://webmention.rocks/test/16">Test 16</a>
<a href="https://webmention.rocks/test/17">Test 17</a>
<a href="https://webmention.rocks/test/18">Test 18</a>
<a href="https://webmention.rocks/test/19">Test 19</a>
<a href="https://webmention.rocks/test/20">Test 20</a>
<a href="https://webmention.rocks/test/21">Test 21</a>
<a href="https://webmention.rocks/test/22">Test 22</a>
<a href="https://webmention.rocks/test/23/page">Test 23</a></p>
<p><a href="https://blog.taoetc.org/testing_webmention/index.html">Permalink</p>
Re: Dithering: an idea whose time has finally come?tag:blog.taoetc.org,2021-10-11:re_dithering_an_idea_whose_time_has_finally_come/index.html2021-10-11T02:02:22Z
Dithering and tinting images
<p><a href="https://blog.taoetc.org/re_dithering_an_idea_whose_time_has_finally_come/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p><a href="gemini://mntn.xyz/posts/2021-10-09-dithering-an-idea-whose-time-has-finally-come/">mntn.xyz talks about dithering</a>:</p>
<blockquote>
<p>It seems that dithering is undergoing a renaissance on Gemini. Not only does it introduce a pleasant retro aesthetic, but for images with smaller dimensions, dithering with a reduced palette can both save bandwidth and make content more accessible to old devices.</p>
</blockquote>
<p>I decided to give it a try, with a couple twists.</p>
<p><img src="sunrise.png" alt="Sunrise in the Estero Americano, Bodega Bay, CA" /></p>
<h2>Python dithering</h2>
<p>I'm using Python to dither the images:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">PIL</span> <span class="kn">import</span> <span class="n">Image</span>
<span class="n">input_</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s1">'image.jpg'</span><span class="p">)</span>
<span class="n">output</span> <span class="o">=</span> <span class="n">input_</span><span class="o">.</span><span class="n">quantize</span><span class="p">(</span>
<span class="n">colors</span><span class="o">=</span><span class="mi">8</span><span class="p">,</span>
<span class="n">dither</span><span class="o">=</span><span class="n">Image</span><span class="o">.</span><span class="n">FLOYDSTEINBERG</span><span class="p">,</span>
<span class="n">kmeans</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
<span class="p">)</span>
</pre></div>
<h2>CSS blending</h2>
<p>On my blog I'm blending the dithered images using CSS, depending on the choice of a light or dark theme:</p>
<div class="highlight"><pre><span></span><span class="nv">@media</span><span class="w"> </span><span class="p">(</span><span class="n">prefers</span><span class="o">-</span><span class="n">color</span><span class="o">-</span><span class="nl">scheme</span><span class="p">:</span><span class="w"> </span><span class="n">dark</span><span class="p">)</span><span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span><span class="n">img</span><span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span><span class="n">mix</span><span class="o">-</span><span class="n">blend</span><span class="o">-</span><span class="nl">mode</span><span class="p">:</span><span class="w"> </span><span class="n">screen</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="err">}</span><span class="w"></span>
<span class="err">}</span><span class="w"></span>
<span class="nv">@media</span><span class="w"> </span><span class="p">(</span><span class="n">prefers</span><span class="o">-</span><span class="n">color</span><span class="o">-</span><span class="nl">scheme</span><span class="p">:</span><span class="w"> </span><span class="n">light</span><span class="p">)</span><span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span><span class="n">img</span><span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span><span class="n">mix</span><span class="o">-</span><span class="n">blend</span><span class="o">-</span><span class="nl">mode</span><span class="p">:</span><span class="w"> </span><span class="n">multiply</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="err">}</span><span class="w"></span>
<span class="err">}</span><span class="w"></span>
</pre></div>
<p><a href="https://blog.taoetc.org/re_dithering_an_idea_whose_time_has_finally_come/index.html">Permalink</p>
Special cases aren't special enough to break the rulestag:blog.taoetc.org,2021-10-05:special_cases_arent_special_enough_to_break_the_rules/index.html2021-10-05T20:08:49Z
My thoughts on special cases in programming
<p><a href="https://blog.taoetc.org/special_cases_arent_special_enough_to_break_the_rules/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>If you're not familiar with the Zen of Python, by Tim Peters, type <code>import this</code> on an interpreter:</p>
<blockquote>
<p>Beautiful is better than ugly.<br />
Explicit is better than implicit.<br />
Simple is better than complex.<br />
Complex is better than complicated.<br />
Flat is better than nested.<br />
Sparse is better than dense.<br />
Readability counts.<br />
Special cases aren't special enough to break the rules.<br />
Although practicality beats purity.<br />
Errors should never pass silently.<br />
Unless explicitly silenced.<br />
In the face of ambiguity, refuse the temptation to guess.<br />
There should be one-- and preferably only one --obvious way to do it.<br />
Although that way may not be obvious at first unless you're Dutch.<br />
Now is better than never.<br />
Although never is often better than <em>right</em> now.<br />
If the implementation is hard to explain, it's a bad idea.<br />
If the implementation is easy to explain, it may be a good idea.<br />
Namespaces are one honking great idea -- let's do more of those! </p>
</blockquote>
<p>I have a corollary to rule #8:</p>
<blockquote>
<p>Every time you fix a bug with an <code>if</code> condition, you're hiding an underlying architectural problem.</p>
</blockquote>
<p>When we're working on a problem (or a ticket) it's easy to get tunnel vision, and only see the conditions that make the bug appear. There's an old joke where a person went to the doctor and said:</p>
<blockquote>
<p>— Doctor, every time I press my stomach it hurts.<br />
— Then stop doing it! </p>
</blockquote>
<p>It's a similar thought process when working on a bug: "when these conditions occur, this will break". That thought is easily translated directly into a solution, going from:</p>
<div class="highlight"><pre><span></span><span class="n">do_something</span><span class="p">()</span> <span class="c1"># breaks if `these_conditions`</span>
</pre></div>
<p>To:</p>
<div class="highlight"><pre><span></span><span class="k">if</span> <span class="nv">not</span> <span class="nv">these_conditions</span>:
<span class="nv">do_something</span><span class="ss">()</span> # <span class="nv">no</span> <span class="nv">longer</span> <span class="nv">breaks</span>
</pre></div>
<p>While this might fix the problem at hand (and close the ticket!), there are a few problems with this:</p>
<ul>
<li>It might not fix other related problems. What if the conditions are slightly different, will the code still break?</li>
<li>It might break expectations, if this part of the code is one of many components of a bigger system. We now have one component that behaves slightly different depending on the input conditions.</li>
<li>It might hide underlying architectural problems. A good design has to deal with as few special cases as possible. Can the system be redesigned so that the <code>if</code> condition is no longer needed?</li>
</ul>
<p>Conditions sure have their place in code, but my suggestion is to ask yourself these questions every time you want to add an <code>if</code> to solve a bug. And remember that special cases usually aren't that special.</p>
<p><a href="https://blog.taoetc.org/special_cases_arent_special_enough_to_break_the_rules/index.html">Permalink</p>
How APIs dietag:blog.taoetc.org,2021-10-04:how_apis_die/index.html2021-10-04T03:03:50Z
Ramblings on the Twitter and Facebook APIs
<p><a href="https://blog.taoetc.org/how_apis_die/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>Back in 2013 I wrote my own static site generator, which I still use, to run a blog. At the time I had already realized that blogs were no longer mainstream, so I wrote a blog engine that published my posts to the cloud. Each post would be announced in Facebook and Twitter, a small summary with a link to the blog post. People could comment on Twitter and Facebook, and periodically I would pull their replies and display them on the static post.</p>
<p>The Twitter API was already bad back then. I discovered there's no way to get replies to a given tweet. The workaround is to request all the tweets that mention you after the given tweet was posted, and scan each one to see if it is a reply to it. 8 years after, as far as I can tell, this is still the only way to do it.</p>
<p>The Facebook API used to be better. Initially I could post to Facebook from my blog engine, and collect replies from the platform. Eventually they made it more and more difficult. These days the only way to get it working is to create an application — but when you publish a post only people who have added that application can see your posts and reply to it. So it's practically useless.</p>
<p>Of course these platforms are not interested in people interacting with them from outside. If I could post programmatically to Facebook and fetch the comments and likes from my friends I would never visit the site (I already don't), and it would encourage them to also leave the platform. Once an API becomes popular enough, it dies.</p>
<p>Compare these two with the Mastodon API. Authentication is easy. Posting is easy. Fetching replies is easy. I miss the days when APIs were simple and everything had an RSS feed.</p>
<p><a href="https://blog.taoetc.org/how_apis_die/index.html">Permalink</p>
Sunrise in the Estero Americanotag:blog.taoetc.org,2021-10-03:sunrise_in_the_estero_americano/index.html2021-10-03T18:06:32Z
A photo of the sunrise
<p><a href="https://blog.taoetc.org/sunrise_in_the_estero_americano/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p><img src="img/4e8971803c5c04a96388ef9570c9a051.jpg" alt="Sunrise in the Estero Americano, Bodega Bay, CA" /></p>
<p><a href="https://blog.taoetc.org/sunrise_in_the_estero_americano/index.html">Permalink</p>
A conversation with Aubrey Blanchetag:blog.taoetc.org,2021-10-03:a_conversation_with_aubrey_blanche/index.html2021-10-03T02:02:30Z
A simple tip to increase diversity and inclusion in your company
<p><a href="https://blog.taoetc.org/a_conversation_with_aubrey_blanche/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>Yesterday I hosted a fireside chat with <a href="https://aubreyblanche.com/">Aubrey Blanche</a> at my company. She gave an amazing presentation on how equity is the foundation to transform diversity into inclusion, and what small actions everyone at the company can take to improve inclusion. After her talk I asked a few follow up questions, specially on measuring those variables — we are a data company after all!</p>
<p>One of her suggestions that struck me as interesting was to limit the requirements on job ads to ~5 items. Keep the list minimal, and eliminate requirements that are not requirements per se, but simply responsibilities of the role. Finally, add a small note at the end saying "please do apply even if you don't meet all the requirements".</p>
<p>The reason this helps is because people from under-represented groups tend to be risk-adverse, and in general avoid applying for jobs when they don't meet all of the requirements. This doesn't mean they are a bad fit for the company, or even for the role, and these small changes increase the number of people from under-represented minorities that apply.</p>
<p><a href="https://blog.taoetc.org/a_conversation_with_aubrey_blanche/index.html">Permalink</p>
50 songs in 90 daystag:blog.taoetc.org,2021-09-29:50_songs_in_90_days/index.html2021-09-29T14:02:58Z
Reflections on the 50/90 challenge
<p><a href="https://blog.taoetc.org/50_songs_in_90_days/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>Since 2016 I've been participating in an online musical challenge called 50/90. The goal is to write 50 songs during the 90 days of (northern hemisphere) summer. There are no prizes, just an amazingly supportive community. People give feedback, suggest prompts, and host skirmishes (you have one hour to write a song on a given topic).</p>
<p>50 songs sound like a lot, but as most things in life it gets easier the more you practice. This year I had a really slow start, writing only 2 songs in the first 2 months. When I had 24 days left I decided to write 2 songs on each remaining day, and I was actually able to do that.</p>
<p>Most days I would wake up early, go to my studio, and compose 2 songs before starting work. I wrote most songs using the Teenage Engineering OP-Z, pairing it with different gear every day to help with the creativity. But I also wrote a couple songs on my acoustic guitar, including a bossa-nova.</p>
<p>One my favorite things in the challenge is that we have a group of people who collaborate on songs using cassette 4-track recorders. Someone will start a song by recording something on track 1, and the tapes gets mailed to the next person, who adds something to track 2, and so on. The 4th person is responsible for mixing, mastering, and uploading the song.</p>
<p>You can listen to my songs on my <a href="https://thefishermenandthepriestess.com/album/50-90-2021">Bandcamp page</a>, and I've also included in this post one of the 4-track collaborations I did, "Neverending Sweetheart".</p>
<p><a href="https://blog.taoetc.org/50_songs_in_90_days/index.html">Permalink</p>
A Raspberry Pi clustertag:blog.taoetc.org,2021-09-23:a_raspi_cluster/index.html2021-09-23T22:10:47Z
Some notes about my self-hosting setup
<p><a href="https://blog.taoetc.org/a_raspi_cluster/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>I've been using a Raspberry Pi for my self-hosting needs, but last week it ran out of disk space because of Mastodon. The Pi was running on an 8GB SD card, so I order one with 128GB so I don't have to worry about it for a while. Since I have a few Pis at home I decided to split the services across a few of them.</p>
<p>I ordered a small 3D-printed case that holds 4 Pis and a hard disk, and this is what I'm planning to do:</p>
<h2>Gateway (Pi 3)</h2>
<p>This Pi will have an external IPV4 address, and forward ports to the other Pis. I'll probably create a new wifi SSID for the Pis, since it's simpler than wiring them through a hub, and I don't care about latency. This Pi would be an access point for the other Pis, keeping them isolated from my main wifi SSID.</p>
<h2>Application (Pi 4 8GB)</h2>
<p>I want to have a Pi dedicated to running applications. This includes:</p>
<ul>
<li>My personal Mastodon instance.</li>
<li>Mosquitto, a message queue that I use for home automation.</li>
<li>Señor Octopus, a custom home automation/data archival tool that I built.</li>
<li>Shoutout bot, a Slack bot that I use or people to send shout-outs at work.</li>
<li>Jetforce, a Gemini server. I'm currently running my capsule on a VPS.</li>
<li>A custom IndieAuth provider that I wrote.</li>
<li>Apache Superset, a data visualization web application that I work on.</li>
</ul>
<h2>Storage (Pi 3)</h2>
<p>The third Pi will be used for storage, running my database and a large disk for remote backs via duplicity:</p>
<ul>
<li>Postgres</li>
<li>3 TB hard drive</li>
</ul>
<h2>Mail (Pi 4 4GB)</h2>
<p>Finally, I've been considering moving my mail server home. I currently run Mail-in-a-box on a VPS, and it would be nice to have all my hosting from my closet. I'm somewhat worried about my network connectivity (I have a line-of-sight provider), and with being without email when the internet is down. But I can always have Pi #1 (the router) connect to my mobile hotspot in cases like that.</p>
<p><a href="https://blog.taoetc.org/a_raspi_cluster/index.html">Permalink</p>
Usestag:blog.taoetc.org,2021-09-23:uses/index.html2021-09-23T02:02:28Z
What things I currently use to do what I do
<p><a href="https://blog.taoetc.org/uses/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<h2>Music production</h2>
<p>The <a href="https://teenage.engineering/products/op-z">Teenage Engineering OP-Z</a> is my main instrument these days. It's a super portable drum machine, sequencer, and sampler. I can make full songs using only the OP-Z, but I also use it to sequence external gear via MIDI, or with some effects units.</p>
<p>I use <a href="https://ardour.org/">Ardour</a> as my DAW. I learned music production on Ableton Live, which was great due to the number of tutorials available for it. Later I switched to Bitwig, which I still love for its modular capabilities. But I really wanted to make music with OSS, so I switched to Ardour a few years ago. Ardour made me less productive, but it forced me to learn more about music production and to use my ears.</p>
<p>I love small portable units that are versatile — I'm trying to build a studio that can travel with me, since I'm planning to move into an RV in the next couple months. The <a href="https://empresseffects.com/products/zoia">Empress ZOIA</a>, the <a href="https://www.polyeffects.com/polyeffects/p/beebo">Poly Effects Beebo</a>, the <a href="https://mod.audio/dwarf/">MOD Dwarf</a>, the <a href="https://monome.org/docs/norns/">Norns</a>, and the <a href="https://www.critterandguitari.com/organelle">Organelle</a> all fit into that category.</p>
<h2>Music listening</h2>
<p>I downloaded all the albums I own on Bandcamp (360+) plus a few other digital albums that I've downloaded over the years, and moved them to a <a href="https://kodi.tv/">Kodi enterntainment center</a> running in my RV. Musicians are paid nothing for streams, so I highly recommend supporting your favorite artists on Bandcamp.</p>
<h2>Programming</h2>
<p>I used <code>vim</code> as my main editor for more than 23 years now (I wrote my PhD thesis with vim!), and I recently switched to <a href="https://neovim.io/">the Neovim editor</a>. Python is my go-to language, and I learned to love type hinting by working on open-source projects with other people. On the frontend I use Typescript and React, and I've been a fan of Javascript since I can remember.</p>
<h2>Hosting</h2>
<p>I run <a href="https://mailinabox.email/guide.html">my own mail server</a> on Digital Ocean, and have been managing my own email since 2000; I do not recommend it, though. Until recently I ran a few sites and services from a Raspberry Pi cluster in my closet, but with the move to the RV everything is now in Digital Ocean.</p>
<p><a href="https://blog.taoetc.org/">My personal blog</a> is built by <a href="https://github.com/betodealmeida/nefelibata">my custom static site generator called Nefelibata</a> and hosted on S3, as well as <a href="gemini://taoetc.org/">my Gemini capsule</a>, running on the <a href="https://github.com/michael-lazar/jetforce">Jetforce Gemini server</a>. They have identical content.</p>
<p><a href="https://blog.taoetc.org/uses/index.html">Permalink</p>
What's in a nametag:blog.taoetc.org,2021-09-17:whats_in_a_name/index.html2021-09-17T01:01:14Z
Where the name 道&c. comes from
<p><a href="https://blog.taoetc.org/whats_in_a_name/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>We have a common expression in Brazil: "etc. e tal". It's used when describing the end of a list of things, and it literally means "etc. and such". I always enjoyed it, and since "tal" is pronounced similarly to "tao" I came up with this domain name: "tao etc.", a play on the word "tao" — path — and the original expression.</p>
<p>You might also not know that "&" is a ligature of "et", which means "and" in Latin. In some fonts it's really clear that "&" is just a representation of "et", but those are the exception. Because of that, some people write "etc" as "&c", though it's not common.</p>
<p>So this is my blog. 道&c. The path, and more. Tao, etc. I love the combination of 道&c, because it spans ages and continents.</p>
<p><a href="https://blog.taoetc.org/whats_in_a_name/index.html">Permalink</p>
Changing old code is riskytag:blog.taoetc.org,2021-09-14:changing_old_code_is_risky/index.html2021-09-14T23:11:00Z
A reflection on old code
<p><a href="https://blog.taoetc.org/changing_old_code_is_risky/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<p>I was talking to a colleague today who's a junior engineer. He was refactoring some code that my team wrote in the last few weeks, trying to improve type conversion when passing objects between the frontend and the backend. On the frontend, an object is being serialized to a string via <code>JSON.stringify</code>. The resulting string is then appended to another object, which also gets serialized to a JSON string in the request payload.</p>
<p>My recommendation was to remove the call to <code>JSON.stringy</code> in the frontend, remove the probable call to <code>json.loads</code> on the backend, reducing the amount of code and simplifying the mental load. He started doing that, but then realized that we are doing the same double-serialization before our changes, in parts of the API that already existed. I said:</p>
<blockquote>
<p>Don't change it, then. The older the code is the more risky it is to change it.</p>
</blockquote>
<p>I was surprised by what I said. Almost every engineer loves refactoring code, rewriting projects from scratch and getting rid of old code. I know I do. Old code doesn't follow the latest best practices. It doesn't use the new testing libraries. It doesn't leverage new features from the programming language — no async, no <code>TypedDict</code>. It's easy to dismiss it as inelegant and stale.</p>
<p>But old code has a history. It survived bugs, fixed them, worked around them. It's also harder to understand. When you revisit old code it's harder to understand the context, the subtleties, the reasoning — even with good comments left behind. Dismissing the old code is both disrespectful and unwise.</p>
<p><a href="https://blog.taoetc.org/changing_old_code_is_risky/index.html">Permalink</p>
New beginningstag:blog.taoetc.org,2021-09-14:hello_gemini/index.html2021-09-14T04:04:47Z
About joining Mastodon and Gemini
<p><a href="https://blog.taoetc.org/hello_gemini/index.html">Permalink</p>
Beto Dealmeidaroberto@dealmeida.net
<h2>Mastodon</h2>
<p>A week ago I joined Mastodon looking for a social network where I could have more meaningful conversations, away from ads and algorithms. I've used Mastodon in the past, creating accounts on a few different instances based on my interests (music and OSS) and using them regularly for a while. I've also set up a small instance for my group of childhood friends, at a time when the group was concerned with online privacy, but unfortunately it never got any usage and I eventually turned it off.</p>
<p>I never really liked using Mastodon accounts on other people's instances. It feels unwise to invest in an account that I don't fully control, even though I know that Mastodon supports migrating accounts. If I'm going to use a decentralized and federated social network I'd rather run my own instance, under a domain that I control and a server that I have access to.</p>
<p>At home I run a Raspberry Pi server from my closet. My home internet is offered by a small local provider via a line-of-sight connection, and unfortunately the router doesn't get a real IP address that can be reached from the internet. To have the Pi working as a server I use <a href="https://hoppy.network/">hoppy.network</a>, which gives the Pi a real unfiltered IPV4/6 IP address for $8 via WireGuard.</p>
<p>I'm already running a few services on the Pi. It runs a custom-made home automation service that I wrote called <a href="https://github.com/betodealmeida/senor-octopus">Señor Octopus</a>, which I use for turning lights on & off, monitoring air quality, keeping track of my and my dog's locations, and more. It needs to run a message queue (MQTT) and a database (Postgres) for some of the automations. Additionally, it also runs a Slack bot that allows people to send each other shout-outs, which I developed for my company's Slack server.</p>
<p>The only problem with the Pi server is my internet connectivity. The bandwidth is somewhat limited (20↓/10↑ Mbps), and it can be down sometimes for longer than a day. But since the home automation won't work if the internet is down anyway, and the Slack bot is not a critical service, I've been really happy with the Pi server, and I'm still amazed by how much we can do with a $35 machine these days.</p>
<p>For Mastodon, I realized I also didn't care if my instance was occasionally down. So I installed <a href="https://2c.taoetc.org/">my own instance</a> on the Pi. I've been following a few people on topics of politics, programming, and music, and it's been a refreshing experience. Interesting posts, nice conversations, no ads, and I'm not overwhelmed with content — I can read my feed in a couple minutes and close the app.</p>
<h2>Gemini</h2>
<p>One of the things I learned about on Mastodon in the past week was <a href="https://gemini.circumlunar.space/">Gemini</a>:</p>
<blockquote>
<p>Gemini is a new application-level internet protocol for the distribution of arbitrary files, with some special consideration for serving a lightweight hypertext format which facilitates linking between files.</p>
</blockquote>
<p>I loved the idea of having a "web, stripped right back to its essence"! </p>
<p>The internet has changed a lot since I started using it back in 1993, going from a collection of well-tended personal gardens of content to a handful of sterile websites controlled by huge corporations. I was already a fan of the <a href="https://indieweb.org/">IndieWeb</a>, I was already running my own blog (and blog engine), but one of my concerns was the increasing complexity of web browsers: it's practically impossible for a developer to write their own browser these days.</p>
<p>I remember thinking that someone should write a browser that only reads Markdown, and Gemini is very close to that.</p>
<p>This week I started working on my blog engine, <a href="https://nefelibata.readthedocs.io/en/latest/">Nefelibata</a>. Nefelibata is a static site generator that "announces" new posts on social media, linking back to the original blog post. When the website is rebuilt it will then collect any replies and store them locally, so that the comments are displayed in the blog along the post.</p>
<p>Nefelibata is very modular, but unfortunately one of its core assumptions is that it's going to produce HTML for the web. The assumption was strong enough that I decided to start a new branch from scratch. I implemented a Gemini builder and an FTP publisher (Nefelibata can publish to many places, including S3, NeoCities and IPFS), the minimum I needed to get a capsule running.</p>
<p>Instead of hosting it on my Raspberry Pi, for now I'm uploading the gemtext files to a VPS, where I have the taoetc.org domain running. I run a custom-built identify server on the VPS, that I use for <a href="https://indieauth.com/">IndieAuth</a>, and I'm not sure if I'd want to have that moved to the Pi, given the connectivity limitations. Something that I need to think about.</p>
<p>On the VPS I installed <a href="https://github.com/michael-lazar/jetforce">Jetforce</a> to serve the Gemini capsule, and — I never thought I'd ever say this again! — I'm excited to start adding a few CGI files.</p>
<p>This is fun.</p>
<p><a href="https://blog.taoetc.org/hello_gemini/index.html">Permalink</p>