<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Automation on Eliseo Martelli</title><link>https://eliseomartelli.it/tags/automation/</link><description>Recent content in Automation on Eliseo Martelli</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><author>Eliseo Martelli</author><atom:link href="https://eliseomartelli.it/tags/automation/feed.xml" rel="self" type="application/rss+xml"/><item><title>We are a blip.</title><link>https://eliseomartelli.it/blog/2026-07-28-we-are-a-blip/</link><pubDate>Tue, 28 Jul 2026 15:14:54 +0000</pubDate><guid>https://eliseomartelli.it/blog/2026-07-28-we-are-a-blip/</guid><description>&lt;p&gt;Why bother?&lt;/p&gt;
&lt;p&gt;Why go to that event if people are rude, it&amp;rsquo;s a hassle, and in 10 years I
won&amp;rsquo;t remember most of it anyway?&lt;/p&gt;
&lt;p&gt;I know this feeling. It&amp;rsquo;s the cosmic version of nihilism.&lt;br&gt;
You zoom out too far, and your life starts to look like static.&lt;/p&gt;
&lt;p&gt;And yes, objectively, we are small. A blip.&lt;/p&gt;
&lt;p&gt;But here&amp;rsquo;s the part that matters: even if you&amp;rsquo;re insignificant to the universe,
you&amp;rsquo;re not insignificant to yourself.&lt;/p&gt;
&lt;p&gt;Pain still hurts. Joy still feels good. Meaning still lands.&lt;/p&gt;
&lt;p&gt;If someone offers you two lunches, one with your favorite food and one
with something disgusting, you won&amp;rsquo;t be indifferent. You&amp;rsquo;ll pick the better one
immediately.&lt;/p&gt;
&lt;p&gt;That tells you everything: your life has value because it&amp;rsquo;s &lt;em&gt;lived&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Not because other people care. Because you do.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t think life comes with a built-in purpose.&lt;br&gt;
I think purpose is local, and you write it day by day.&lt;/p&gt;
&lt;p&gt;You choose what kind of life you can live with at night.&lt;/p&gt;
&lt;p&gt;You choose whether this brief window of consciousness feels more like heaven or
more like hell.&lt;/p&gt;
&lt;p&gt;Sometimes when I feel small, I remind myself of this:&lt;/p&gt;
&lt;p&gt;I may not change the whole world, but I can still be a spark.&lt;br&gt;
A conversation, a photo, a gesture, a sentence, a decision. One small thing
that shifts reality by one degree.&lt;/p&gt;
&lt;p&gt;Maybe I won&amp;rsquo;t see the effect. That&amp;rsquo;s fine.&lt;/p&gt;
&lt;p&gt;We are a blip. An absurdly privileged one.&lt;/p&gt;
&lt;p&gt;For a short moment we get color, sound, taste, touch, people, ideas, love,
grief, tension, beauty, boredom, and wonder.&lt;/p&gt;
&lt;p&gt;Then it&amp;rsquo;s over.&lt;/p&gt;
&lt;p&gt;So no, don&amp;rsquo;t live for &amp;ldquo;the world&amp;rdquo; in the abstract.&lt;/p&gt;
&lt;p&gt;Live for you.&lt;br&gt;
Build a life you&amp;rsquo;d choose again.&lt;/p&gt;
&lt;p&gt;My objective is still simple: leave a net positive impact.&lt;br&gt;
Some days are negative, some days are positive.&lt;br&gt;
But the direction still matters.&lt;/p&gt;</description><category>Misc</category><category>Short</category></item><item><title>AirPods Pro Case: Revived for €20</title><link>https://eliseomartelli.it/blog/2026-06-24-airpods-pro-case-revived/</link><pubDate>Wed, 24 Jun 2026 13:30:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2026-06-24-airpods-pro-case-revived/</guid><description>&lt;p&gt;Last week, I wrote that my &lt;a href="https://eliseomartelli.it/blog/2026-06-16-airpods-pro-case-dead/"&gt;AirPods Pro case was dead&lt;/a&gt;. The battery wouldn&amp;rsquo;t hold a charge, and Apple&amp;rsquo;s only solution was to charge me €100+ for a replacement case.&lt;/p&gt;
&lt;p&gt;Instead of throwing it away, I decided to try fixing it myself. It cost me €20 and took about 15 minutes.&lt;/p&gt;
&lt;p&gt;Here is how to do it.&lt;/p&gt;
&lt;h3 id="finding-a-battery"&gt;Finding a battery&lt;/h3&gt;
&lt;p&gt;I bought a replacement battery on eBay for €20. You need one specifically for the first-generation AirPods Pro case (model A2190).&lt;/p&gt;
&lt;p&gt;To open and repair the case, I followed this video tutorial by iMeo:&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/nDDdPwQKXLk?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h3 id="the-repair"&gt;The repair&lt;/h3&gt;
&lt;p&gt;The process is straightforward, but it requires steady hands.&lt;/p&gt;
&lt;p&gt;First, you pry the inner assembly out of the outer white shell. A plastic spudger helps here to avoid scratching the plastic. Once the internals slide out, you need to disassemble the case further:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Detach the hall sensor.&lt;/li&gt;
&lt;li&gt;Disconnect the flex cables for the setup button and the Lightning port.&lt;/li&gt;
&lt;li&gt;Remove the motherboard.&lt;/li&gt;
&lt;li&gt;Remove the old battery. It is held down by strong adhesive, so applying some gentle heat (a hair dryer works fine) makes it much easier to pry out.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once the old battery is out, put the new one in place, and do all the steps in reverse.&lt;/p&gt;
&lt;p&gt;The hardest part is reattaching the tiny flex cables to the logic board. They are incredibly small and delicate. I used tweezers and a magnifying glass to line them up. If you tear one of these ribbons, the case is done, so take your time.&lt;/p&gt;
&lt;h3 id="the-result"&gt;The result&lt;/h3&gt;
&lt;p&gt;After clicking the cables back in and putting the case back together, it worked immediately. It now charges normally, holds a charge for days, and charges the earbuds without issues.&lt;/p&gt;
&lt;p&gt;Paying Apple €120 for a replacement is a waste. If your case dies but the earbuds are still good, buy a cheap battery and spend 15 minutes fixing it. Just be careful with the flex cables.&lt;/p&gt;</description><category>Apple</category><category>Hardware</category></item><item><title>My AirPods Pro Case is Dead</title><link>https://eliseomartelli.it/blog/2026-06-16-airpods-pro-case-dead/</link><pubDate>Tue, 16 Jun 2026 14:30:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2026-06-16-airpods-pro-case-dead/</guid><description>&lt;p&gt;I got my AirPods Pro in February 2021. They’ve been my daily drivers for over
five years, which, in the world of tiny lithium-ion batteries, is basically
ancient history.&lt;/p&gt;
&lt;p&gt;Now, the case has finally given up. It doesn&amp;rsquo;t work unless it&amp;rsquo;s plugged into a
charger. The moment I pull the cable, it&amp;rsquo;s a brick.&lt;/p&gt;
&lt;p&gt;It’s the classic symptom of a completely exhausted battery. If you’re in the
same boat, there are a few things you can try before declared it dead, though I
wouldn&amp;rsquo;t hold my breath.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The Factory Reset: Sometimes the firmware just gets stuck. Put the buds in
the case, close the lid for 30 seconds, then open it and hold the setup
button on the back for 15 seconds until the light flashes amber, then
white.&lt;/li&gt;
&lt;li&gt;The Deep Charge: If the battery hit absolute zero, the power management
chip might be struggling. Take the buds out, plug the empty case into a
wall charger, and leave it alone for two hours.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I tried both. Neither worked. The battery is just chemically finished.&lt;/p&gt;
&lt;p&gt;The annoying part? My earbuds themselves were replaced in 2022 because of a
microphone issue. They still hold a decent charge and sound fine. But because
the case is dead, the whole set is essentially unusable.&lt;/p&gt;
&lt;p&gt;Apple doesn&amp;rsquo;t replace these batteries. They just sell you a new case for
€100-€120, or tell you to buy the new model.&lt;/p&gt;
&lt;p&gt;It’s a distinct sting: holding a perfectly functional piece of engineering that
has been rendered a paperweight because of a tiny, non-replaceable pouch of
lithium. It’s the dark side of the wireless audio boom, brilliant convenience
paired with built-in expiration dates.&lt;/p&gt;
&lt;p&gt;I just bought the &lt;a href="https://eliseomartelli.it/blog/2026-04-02-airpods-max-2-review/"&gt;AirPods Max 2 in
April&lt;/a&gt;. Knowing that they also have
non user-replaceable batteries is&amp;hellip; not encouraging.&lt;/p&gt;
&lt;p&gt;However, there’s a small silver lining. The Max houses a much larger,
traditional headphone battery. It undergoes far fewer charge cycles to deliver
the same amount of listening time compared to the tiny cells in the Pro case,
which are subjected to aggressive micro-discharging every single time you put
them away.&lt;/p&gt;
&lt;p&gt;The Max should, theoretically, last much longer. But it doesn&amp;rsquo;t change the
underlying problem. We’re still buying products with a ticking clock inside.&lt;/p&gt;
&lt;p&gt;If you’re stuck with a dead case and functional buds like me, don&amp;rsquo;t pay Apple&amp;rsquo;s
tax for a new one. Check the secondhand market for standalone cases or look at
reputable third-party chargers. It’s better than letting the whole set become
e-waste.&lt;/p&gt;
&lt;p&gt;These AirPods Pro have officially graduated to &amp;ldquo;gym headphones&amp;rdquo; status. I&amp;rsquo;ll
keep the case plugged in at my desk and use them for workouts, where the
battery of the buds will still last for a few hours. It’s not ideal, but it’s
better than tossing them out.&lt;/p&gt;</description><category>Apple</category><category>Hardware</category></item><item><title>Apple's lesson in how NOT to do optimistic updates</title><link>https://eliseomartelli.it/blog/2026-06-03-apple-music-optimistic-updates/</link><pubDate>Wed, 03 Jun 2026 10:20:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2026-06-03-apple-music-optimistic-updates/</guid><description>&lt;p&gt;You&amp;rsquo;re listening to a song you love and want to save it. You click the favorite
star and&amp;hellip; nothing.&lt;/p&gt;
&lt;p&gt;You wonder if you missed the button. Did the app freeze? You&amp;rsquo;re about to click
again when, SECONDS later, the star finally turns red.&lt;/p&gt;
&lt;p&gt;This is the current state of Apple Music (Version 1.6.5.10) on macOS 26.5.1.&lt;/p&gt;
&lt;p&gt;Most apps use &amp;ldquo;optimistic updates.&amp;rdquo; When you like a post or star a song, the UI
changes immediately. The app assumes the server request will work. If it fails,
the UI just reverts later.&lt;/p&gt;
&lt;p&gt;This makes everything feel fast. You shouldn&amp;rsquo;t have to wait for a server
response just to see a star change color.&lt;/p&gt;
&lt;p&gt;Apple Music does the opposite. It waits for the server to confirm the change
before updating the UI.&lt;/p&gt;
&lt;p&gt;In this video, you can see the lag. I click the star, and it stays empty for
five seconds before finally filling in.&lt;/p&gt;
&lt;video controls allowFullScreen src="https://eliseomartelli.it/posts/2026-06-03-apple-music-optimistic-updates/video.mp4" class="w-full rounded-lg shadow-lg" /&gt;
&lt;p&gt;It&amp;rsquo;s not just slow; it&amp;rsquo;s confusing. When the UI doesn&amp;rsquo;t react instantly, you
lose confidence in the app. You end up double-checking your own actions.&lt;/p&gt;
&lt;p&gt;It bums me out. I&amp;rsquo;ve bought so many Apple products and services over the years,
most recently the AirPods Max 2, and it&amp;rsquo;s discouraging to see such a clunky
experience in a core app. For a company that markets &amp;ldquo;fluidity,&amp;rdquo; this is a
basic failure.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s another sign of &lt;a href="https://eliseomartelli.it/blog/2025-03-02-apple-quality/"&gt;declining software quality&lt;/a&gt;.
If a tiny startup can make a button feel instant, Apple can too.&lt;/p&gt;
&lt;p&gt;Tim, we&amp;rsquo;re still not cooking.&lt;/p&gt;
&lt;h3 id="the-song-in-question"&gt;The Song in Question&lt;/h3&gt;
&lt;iframe
allow="autoplay *; encrypted-media *; fullscreen *; clipboard-write"
allowFullScreen
height="150"
class="w-full max-w-2xl overflow-hidden bg-transparent"
sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation"
src="https://embed.music.apple.com/it/album/%C3%BAltima-noche/1836306069?i=1836306073"
/&gt;</description><category>Apple</category></item><item><title>Logbook - May 7, 2026</title><link>https://eliseomartelli.it/blog/2026-05-07-logbook-may-7-2026/</link><pubDate>Thu, 07 May 2026 09:05:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2026-05-07-logbook-may-7-2026/</guid><description>&lt;p&gt;Yesterday morning I watched a puddle drying up, taken by force by the sun&amp;rsquo;s
rays.&lt;br&gt;
A &amp;ldquo;necessary&amp;rdquo; violence to make room for the next puddle.&lt;/p&gt;
&lt;p&gt;Transformation is an act of rupture, and at this moment, I feel I am in a phase
of transformation.&lt;/p&gt;
&lt;p&gt;Today I feel a bit like that puddle: a bit mortified, a bit disappointed, and a
bit of a disappointment.&lt;/p&gt;
&lt;p&gt;Does evaporating mean failing?&lt;/p&gt;</description><category>Misc</category><category>Logbook</category></item><item><title>A Gaussian Splat Portrait on the Homepage</title><link>https://eliseomartelli.it/blog/2026-03-28-gaussian-splat-portrait/</link><pubDate>Sat, 28 Mar 2026 12:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2026-03-28-gaussian-splat-portrait/</guid><description>&lt;p&gt;I recently watched a video by Tsoding where he rendered a full 3D scene using
just a plain canvas 2D element, without any WebGL or Three.js.&lt;/p&gt;
&lt;p&gt;The core of the video was a simple formula to calculate the position of each
pixel:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;given (x, y, z)
x&amp;#39; = y / z
y&amp;#39; = x / z
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It tickled the part of my brain that loves graphics and it was enough to make
me want to try the same thing on my homepage portrait. I wanted to create a 3D
portrait that could be rotated and viewed from different angles, all using just
Canvas 2D and some math.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/qjWkNZ0SXfo?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Some months ago, I was playing around with Apple&amp;rsquo;s
&lt;a href="https://github.com/apple/ml-sharp"&gt;ml-sharp&lt;/a&gt;, which can
reconstruct a 3D point cloud from a single photo.&lt;/p&gt;
&lt;p&gt;At that time, I didn&amp;rsquo;t have a use for it, but it seemed like the perfect tool
for this project.&lt;/p&gt;
&lt;p&gt;This technique is called Gaussian Splatting.
It uses millions of semi-transparent 3D blobs, each with a position, opacity,
color, scale, and rotation. Render them back-to-front and you get surprisingly
good results, especially on faces.&lt;/p&gt;
&lt;p&gt;One photo in. One &lt;code&gt;.ply&lt;/code&gt; file out.&lt;/p&gt;
&lt;p&gt;I ran ml-sharp on a portrait of me and got a &lt;code&gt;.ply&lt;/code&gt; file with ~689,000
gaussians. Most of them were background noise. I loaded it into
&lt;a href="https://superspl.at/editor"&gt;superspl.at/editor&lt;/a&gt; and cropped everything that
wasn&amp;rsquo;t my face.&lt;/p&gt;
&lt;p&gt;Still way too many points. And the raw &lt;code&gt;.ply&lt;/code&gt; format is huge. None of that is
useful for a grayscale web viewer.&lt;/p&gt;
&lt;p&gt;689k gaussians at 60+ bytes each isn&amp;rsquo;t something you ship over the wire. I
wrote a Python script to cut it down.&lt;/p&gt;
&lt;p&gt;The core idea: not all gaussians matter equally. A tiny opaque one is a sharp
detail — an eye, a lip edge. A huge transparent one is blurry fill. Score by
&lt;code&gt;opacity / volume&lt;/code&gt;, importance-sample down to 15k, and you keep what the eye
actually sees:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;volume &lt;span style="color:#666"&gt;=&lt;/span&gt; np&lt;span style="color:#666"&gt;.&lt;/span&gt;exp(s0 &lt;span style="color:#666"&gt;+&lt;/span&gt; s1 &lt;span style="color:#666"&gt;+&lt;/span&gt; s2)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;importance &lt;span style="color:#666"&gt;=&lt;/span&gt; opacity &lt;span style="color:#666"&gt;/&lt;/span&gt; (volume &lt;span style="color:#666"&gt;+&lt;/span&gt; &lt;span style="color:#40a070"&gt;1e-8&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;importance &lt;span style="color:#666"&gt;/=&lt;/span&gt; importance&lt;span style="color:#666"&gt;.&lt;/span&gt;sum()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4&lt;/span&gt;&lt;span&gt;idx &lt;span style="color:#666"&gt;=&lt;/span&gt; rng&lt;span style="color:#666"&gt;.&lt;/span&gt;choice(&lt;span style="color:#007020"&gt;len&lt;/span&gt;(x), size&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#007020"&gt;min&lt;/span&gt;(N, &lt;span style="color:#007020"&gt;len&lt;/span&gt;(x)), replace&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;False&lt;/span&gt;, p&lt;span style="color:#666"&gt;=&lt;/span&gt;importance)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then quantize positions to &lt;code&gt;uint16&lt;/code&gt; and pull grayscale brightness from the DC
term of the spherical harmonics. Pack it all into a tight binary struct:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;def&lt;/span&gt; &lt;span style="color:#06287e"&gt;quant&lt;/span&gt;(a):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt; lo, hi &lt;span style="color:#666"&gt;=&lt;/span&gt; a&lt;span style="color:#666"&gt;.&lt;/span&gt;min(), a&lt;span style="color:#666"&gt;.&lt;/span&gt;max()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;return&lt;/span&gt; lo, hi, np&lt;span style="color:#666"&gt;.&lt;/span&gt;round((a &lt;span style="color:#666"&gt;-&lt;/span&gt; lo) &lt;span style="color:#666"&gt;/&lt;/span&gt; (hi &lt;span style="color:#666"&gt;-&lt;/span&gt; lo) &lt;span style="color:#666"&gt;*&lt;/span&gt; &lt;span style="color:#40a070"&gt;65535&lt;/span&gt;)&lt;span style="color:#666"&gt;.&lt;/span&gt;astype(np&lt;span style="color:#666"&gt;.&lt;/span&gt;uint16)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5&lt;/span&gt;&lt;span&gt;dt &lt;span style="color:#666"&gt;=&lt;/span&gt; np&lt;span style="color:#666"&gt;.&lt;/span&gt;dtype([(&lt;span style="color:#4070a0"&gt;&amp;#34;x&amp;#34;&lt;/span&gt;, &lt;span style="color:#4070a0"&gt;&amp;#34;&amp;lt;u2&amp;#34;&lt;/span&gt;), (&lt;span style="color:#4070a0"&gt;&amp;#34;y&amp;#34;&lt;/span&gt;, &lt;span style="color:#4070a0"&gt;&amp;#34;&amp;lt;u2&amp;#34;&lt;/span&gt;), (&lt;span style="color:#4070a0"&gt;&amp;#34;z&amp;#34;&lt;/span&gt;, &lt;span style="color:#4070a0"&gt;&amp;#34;&amp;lt;u2&amp;#34;&lt;/span&gt;), (&lt;span style="color:#4070a0"&gt;&amp;#34;a&amp;#34;&lt;/span&gt;, &lt;span style="color:#4070a0"&gt;&amp;#34;u1&amp;#34;&lt;/span&gt;), (&lt;span style="color:#4070a0"&gt;&amp;#34;b&amp;#34;&lt;/span&gt;, &lt;span style="color:#4070a0"&gt;&amp;#34;u1&amp;#34;&lt;/span&gt;)])
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Five fields per point, no JSON, no headers.&lt;/p&gt;
&lt;p&gt;To view it, I wrote a single TypeScript component, no dependencies. Each frame
it rotates the points around Y then X, depth-sorts them furthest-first, draws
each one as a 3×3 square into an &lt;code&gt;ImageData&lt;/code&gt; buffer with alpha blending, and
flushes with &lt;code&gt;putImageData&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The projection is just perspective division:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; rx &lt;span style="color:#666"&gt;=&lt;/span&gt; x[i] &lt;span style="color:#666"&gt;*&lt;/span&gt; cosY &lt;span style="color:#666"&gt;+&lt;/span&gt; z[i] &lt;span style="color:#666"&gt;*&lt;/span&gt; sinY;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; rz &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#666"&gt;-&lt;/span&gt;x[i] &lt;span style="color:#666"&gt;*&lt;/span&gt; sinY &lt;span style="color:#666"&gt;+&lt;/span&gt; z[i] &lt;span style="color:#666"&gt;*&lt;/span&gt; cosY;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; ry2 &lt;span style="color:#666"&gt;=&lt;/span&gt; y[i] &lt;span style="color:#666"&gt;*&lt;/span&gt; cosX &lt;span style="color:#666"&gt;-&lt;/span&gt; rz &lt;span style="color:#666"&gt;*&lt;/span&gt; sinX;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; rz2 &lt;span style="color:#666"&gt;=&lt;/span&gt; y[i] &lt;span style="color:#666"&gt;*&lt;/span&gt; sinX &lt;span style="color:#666"&gt;+&lt;/span&gt; rz &lt;span style="color:#666"&gt;*&lt;/span&gt; cosX;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; d &lt;span style="color:#666"&gt;=&lt;/span&gt; rz2 &lt;span style="color:#666"&gt;-&lt;/span&gt; CAM_Z;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; sx &lt;span style="color:#666"&gt;=&lt;/span&gt; (W &lt;span style="color:#666"&gt;*&lt;/span&gt; &lt;span style="color:#40a070"&gt;0.5&lt;/span&gt; &lt;span style="color:#666"&gt;+&lt;/span&gt; (FOCAL &lt;span style="color:#666"&gt;*&lt;/span&gt; rx) &lt;span style="color:#666"&gt;/&lt;/span&gt; d &lt;span style="color:#666"&gt;+&lt;/span&gt; &lt;span style="color:#40a070"&gt;0.5&lt;/span&gt;) &lt;span style="color:#666"&gt;|&lt;/span&gt; &lt;span style="color:#40a070"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; sy &lt;span style="color:#666"&gt;=&lt;/span&gt; (H &lt;span style="color:#666"&gt;*&lt;/span&gt; &lt;span style="color:#40a070"&gt;0.5&lt;/span&gt; &lt;span style="color:#666"&gt;+&lt;/span&gt; (FOCAL &lt;span style="color:#666"&gt;*&lt;/span&gt; ry2) &lt;span style="color:#666"&gt;/&lt;/span&gt; d &lt;span style="color:#666"&gt;+&lt;/span&gt; &lt;span style="color:#40a070"&gt;0.5&lt;/span&gt;) &lt;span style="color:#666"&gt;|&lt;/span&gt; &lt;span style="color:#40a070"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Mouse gives ±4° rotation. Touch drives a sinusoidal pan that coasts a little
after you lift your finger. An &lt;code&gt;IntersectionObserver&lt;/code&gt; kills the animation loop
when the element isn&amp;rsquo;t on screen. The circular crop is &lt;code&gt;rounded-full overflow-hidden&lt;/code&gt; on the wrapper.&lt;/p&gt;
&lt;p&gt;15,000 points is genuinely not that many. I expected it to look sparse. It
doesn&amp;rsquo;t, the face reads clearly, rotation gives real depth, and it moves in a
way a flat photo can&amp;rsquo;t. Surprised at how well it held up.&lt;/p&gt;</description><category>Programming</category></item><item><title>Darkroom: Silver Gelatin Print at Spazio Gnu</title><link>https://eliseomartelli.it/blog/2026-03-09-darkroom-silver-gelatin-print-at-spazio-gnu/</link><pubDate>Mon, 09 Mar 2026 09:54:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2026-03-09-darkroom-silver-gelatin-print-at-spazio-gnu/</guid><description>&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/2026-03-09-darkroom/L1001197.jpeg" alt="A picture of the darkroom"&gt;&lt;/p&gt;
&lt;p&gt;Yesterday I spent my afternoon at &lt;a href="https://fotografiatorino.blogspot.com"&gt;Spazio Gnu&lt;/a&gt;,
a shared photography space in Turin, Italy.&lt;br&gt;
As someone who spends a lot of time working with digital tools (mostly
programming, photo editing, and video editing), stepping into a space
where there are no &amp;ldquo;undo&amp;rdquo; buttons or history states was a refreshing shift.&lt;/p&gt;
&lt;p&gt;For this session, I used a DURST M700 enlarger, along with a box of FOMA
Fomaspeed Variant 311 High Gloss RC paper (8x10 inches), some rolls of
&lt;a href="https://eliseomartelli.it/blog/2026-01-06-developing-kodak-tri-x-400-at-1600-iso-in-bellini-euro-hc/"&gt;home developed
film&lt;/a&gt;,
a pack of Ilford Multigrade filters, and a few trays of chemicals.&lt;/p&gt;
&lt;p&gt;I ended up walking away with a total of 8 prints, which I consider a solid outcome
for a first attempt at silver gelatin printing.&lt;/p&gt;
&lt;p&gt;Spazio Gnu&amp;rsquo;s darkroom is organized into two (and an half) areas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A dry area, where you have the enlargers and the workbench to handle the
negatives and the paper.&lt;/li&gt;
&lt;li&gt;A wet island at the center of the room where you have water and trays to
develop the prints.&lt;/li&gt;
&lt;li&gt;A drying area with a roller dryer to speed up the drying process.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The process of making a print requires a lot of patience and attention to
detail.&lt;/p&gt;
&lt;p&gt;One of the first steps is to mix the chemicals.
Chemicals used in the darkroom are typically divided into three categories:
developer, stop bath, and fixer.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Developer: This chemical makes the latent image appear on the paper. I used
ILFORD MULTIGRADE developer, mixed at a standard ratio of 1+9 with water.&lt;/li&gt;
&lt;li&gt;Stop Bath: This abruptly brings the development to an end and prevents
contamination of the next bath. I used ILFORD ILFOSTOP at a 1+19 dilution.&lt;/li&gt;
&lt;li&gt;Fixer: Makes the image permanent. ILFORD RAPID FIXER, mixed at 1+4.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before committing to a full sheet of paper, I made a test print to find the
correct exposure. By using my hand to cover part of the paper, I exposed the
sheet in 5-second increments (from 5 to 25 seconds), creating a gradient of
exposures on the same strip. Examining this test print allowed me to estimate
the correct exposure for the final image. I ended up choosing 15 seconds as the
optimal base exposure.&lt;/p&gt;
&lt;p&gt;Because FOMA 311 is a variable contrast paper, I also had to select the right
filter to achieve the desired contrast. The ILFORD MULTIGRADE filters range
from 00 (very soft) to 5 (very hard). I predominantly used a grade 2 filter,
which produces a good range of print tones for an average negative.&lt;/p&gt;
&lt;p&gt;On one of the prints, the subject was rendering a bit too dark, so I decided to
experiment with a technique called &amp;ldquo;dodging.&amp;rdquo; Dodging involves using your hands
or pieces of card to hold back light from selected areas of the print during
exposure. By physically blocking the enlarger&amp;rsquo;s light over my subject for a few
seconds, I was able to lighten them and bring out more detail in the shadows.&lt;/p&gt;
&lt;p&gt;After exposing the paper, the real magic happens in the wet area.&lt;/p&gt;
&lt;p&gt;I slid the paper into the developer tray. While the image begins to appear
almost immediately, the print needs a full 50 seconds in the developer to fully
mature. Then, using tongs, I transferred it to the stop bath and rocked the
dish for about 30 seconds. Finally, it went into the fixer to make the image
permanent. (While ILFORD notes fixing takes only 30 seconds in fresh solution,
I left mine in for about a minute to be safe).&lt;/p&gt;
&lt;p&gt;After a quick wash in running water to remove any residual chemicals, I ran the
prints through the roller dryer.&lt;/p&gt;
&lt;p&gt;Holding 8 physical photographs that you crafted entirely by hand is a highly
rewarding experience. Each print has its own unique character and subtle
imperfections, adding to its charm. Seeing an image slowly emerge in the
developer tray is a tactile experience that no digital workflow can replicate.&lt;/p&gt;</description><category>Misc</category><category>Photography</category></item><item><title>The Bloat of Modern Fitness Apps</title><link>https://eliseomartelli.it/blog/2026-02-21-the-bloat-of-modern-fitness-apps/</link><pubDate>Sat, 21 Feb 2026 10:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2026-02-21-the-bloat-of-modern-fitness-apps/</guid><description>&lt;p&gt;Over the past week, I&amp;rsquo;ve been making an effort to strike a balance between my
digital and analog life. Between finally having the
&lt;a href="https://eliseomartelli.it/blog/2026-02-13-automating-my-cv-with-latex-and-github-actions/"&gt;time&lt;/a&gt; to do
some &lt;a href="https://eliseomartelli.it/blog/2026-02-17-less-but-better-a-braun-clock-for-the-r36s/"&gt;personal
projects&lt;/a&gt;, and my
usual photography walks, I started to rely on the gym as my
analog escape, thanks to my &lt;a href="https://nomnp.com"&gt;friends&lt;/a&gt;. I just want to put on
my headphones, listen to some Burial or JPEGMafia to tune out the background
noise, and lift.&lt;/p&gt;
&lt;p&gt;The current state of digital fitness tools makes that surprisingly
difficult. Rather than acting as a simple tools that do one thing well, the
modern fitness app has become a bloated, privacy-invading ecosystem.&lt;/p&gt;
&lt;p&gt;I tried out a few popular fitness apps, and the experience was underwhelming.&lt;/p&gt;
&lt;p&gt;One of the biggest mistake gym apps make is demanding too much cognitive load
before you even log a workout. During onboarding, they want you to set up an
entire multi-day split, enter exact body stats, and sit through tutorials.
When using the app at the gym, resting for 60 seconds between sets, I need
a frictionless interface. Everything else just interrupts the physical flow.&lt;/p&gt;
&lt;p&gt;Human physiology isn&amp;rsquo;t a simple math equation. When an app rigidly demands
that you maintain a streak or push through six consecutive training days
without rest, it ignores the realities of life and fatigue.&lt;/p&gt;
&lt;p&gt;Fitness apps collect incredibly intimate data, from geolocation to biometric
vitals, yet many operate with broadly-worded privacy policies that allow them
to share this information with third-party data brokers and advertisers.&lt;/p&gt;
&lt;p&gt;At the end of the day, a fitness app should be a good appliance:
it should capture the data perfectly, feel great to use, and then get
completely out of your way.&lt;/p&gt;</description><category>Misc</category></item><item><title>A Braun clock for a $30 handheld</title><link>https://eliseomartelli.it/blog/2026-02-17-less-but-better-a-braun-clock-for-the-r36s/</link><pubDate>Tue, 17 Feb 2026 14:45:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2026-02-17-less-but-better-a-braun-clock-for-the-r36s/</guid><description>&lt;p&gt;A week ago, &lt;a href="https://nomnp.com"&gt;Francesco&lt;/a&gt; and I bought a couple of R36S
handhelds. Or at least, that&amp;rsquo;s what the listing said.&lt;/p&gt;
&lt;p&gt;What we got was the &lt;code&gt;Y3506_V05_20251215&lt;/code&gt; revision. It&amp;rsquo;s a clone of a clone. If
you&amp;rsquo;ve played with these cheap Chinese SBCs before, you know the
drill: you flash a standard image, and the screen stays black because the
driver is slightly different. It&amp;rsquo;s a hardware lottery.&lt;/p&gt;
&lt;p&gt;In our case, we had to use DTB files from this
&lt;a href="https://github.com/AeolusUX/R36S-DTB/tree/main/R36S/Panel%205"&gt;repo&lt;/a&gt;, and we
used the &lt;code&gt;ArkOS&lt;/code&gt; image from &lt;a href="https://github.com/AeolusUX/ArkOS-R3XS"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But once the firmware is sorted, the hardware is surprisingly charming. The
transparent shell is pure 90s nostalgia, and the screen is actually good for
the price.&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t want it just for emulating Game Boy and PlayStation games, though. I
wanted it to be useful on my desk.&lt;/p&gt;
&lt;p&gt;Every time I work from Francesco&amp;rsquo;s place, I&amp;rsquo;m distracted by his Braun BC03
clock. It&amp;rsquo;s a Dieter Rams masterpiece. It follows the &amp;ldquo;Less, but better&amp;rdquo;
philosophy that modern tech seems to have forgotten. It does one thing, and it
does it with absolute clarity.&lt;/p&gt;
&lt;p&gt;So, I wrote &lt;a href="https://github.com/eliseomartelli/bc03-r36s"&gt;bc03-r36s&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When I started building this, the obvious path was to use PNGs for the face and
the hands. But that felt like cheating. A Braun-inspired tool should be
defined by its geometry, not by a bitmap.&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;cool&amp;rdquo; part of the code is that there isn&amp;rsquo;t a single image file in the
repository. Everything is drawn directly via &lt;code&gt;SDL_RenderGeometry&lt;/code&gt;. This
allows for drawing arbitrary shapes on the GPU by defining vertices, which is
much cleaner than using &lt;code&gt;SDL_RenderCopyEx&lt;/code&gt; with pre-rendered textures.&lt;/p&gt;
&lt;p&gt;The core of the rendering is the &lt;code&gt;draw_rotated_rect&lt;/code&gt; function. It takes a
center point, dimensions, and an angle, then calculates the rotation matrix to
position the vertices.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#902000"&gt;void&lt;/span&gt; &lt;span style="color:#06287e"&gt;draw_rotated_rect&lt;/span&gt;(SDL_Renderer &lt;span style="color:#666"&gt;*&lt;/span&gt;renderer, &lt;span style="color:#902000"&gt;int&lt;/span&gt; cx, &lt;span style="color:#902000"&gt;int&lt;/span&gt; cy, &lt;span style="color:#902000"&gt;float&lt;/span&gt; length,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt; &lt;span style="color:#902000"&gt;float&lt;/span&gt; width, &lt;span style="color:#902000"&gt;float&lt;/span&gt; angle, &lt;span style="color:#902000"&gt;int&lt;/span&gt; offset_back) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt; &lt;span style="color:#902000"&gt;float&lt;/span&gt; half_w &lt;span style="color:#666"&gt;=&lt;/span&gt; width &lt;span style="color:#666"&gt;/&lt;/span&gt; &lt;span style="color:#40a070"&gt;2.0f&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;// Define corners relative to the rotation point
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt; Pointf corners[&lt;span style="color:#40a070"&gt;4&lt;/span&gt;] &lt;span style="color:#666"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt; {&lt;span style="color:#666"&gt;-&lt;/span&gt;half_w, (&lt;span style="color:#902000"&gt;float&lt;/span&gt;)offset_back},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt; {half_w, (&lt;span style="color:#902000"&gt;float&lt;/span&gt;)offset_back},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt; {half_w, &lt;span style="color:#666"&gt;-&lt;/span&gt;length},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt; {&lt;span style="color:#666"&gt;-&lt;/span&gt;half_w, &lt;span style="color:#666"&gt;-&lt;/span&gt;length}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt; SDL_Vertex vertices[&lt;span style="color:#40a070"&gt;4&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;// ... apply rotation and draw
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt; &lt;span style="color:#06287e"&gt;SDL_RenderGeometry&lt;/span&gt;(renderer, &lt;span style="color:#007020"&gt;NULL&lt;/span&gt;, vertices, &lt;span style="color:#40a070"&gt;4&lt;/span&gt;, indices, &lt;span style="color:#40a070"&gt;6&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;offset_back&lt;/code&gt; parameter is the secret sauce. If you look at a real BC03,
the second hand doesn&amp;rsquo;t start from the center; it has a small counterweight
that extends backwards. By adding a 30px offset, I captured that specific
analog feel.&lt;/p&gt;
&lt;h2 id="fractional-movement"&gt;Fractional Movement&lt;/h2&gt;
&lt;p&gt;Most digital clocks just jump every second. It looks &amp;ldquo;digital&amp;rdquo; and cheap. Real
analog movements have gears. To mimic this, I didn&amp;rsquo;t just use
&lt;code&gt;timeinfo-&amp;gt;tm_sec&lt;/code&gt;. I calculate the angles using the fractional parts of the
time:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#902000"&gt;float&lt;/span&gt; sec &lt;span style="color:#666"&gt;=&lt;/span&gt; timeinfo&lt;span style="color:#666"&gt;-&amp;gt;&lt;/span&gt;tm_sec;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#902000"&gt;float&lt;/span&gt; min &lt;span style="color:#666"&gt;=&lt;/span&gt; timeinfo&lt;span style="color:#666"&gt;-&amp;gt;&lt;/span&gt;tm_min &lt;span style="color:#666"&gt;+&lt;/span&gt; (sec &lt;span style="color:#666"&gt;/&lt;/span&gt; &lt;span style="color:#40a070"&gt;60.0f&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#902000"&gt;float&lt;/span&gt; hr &lt;span style="color:#666"&gt;=&lt;/span&gt; timeinfo&lt;span style="color:#666"&gt;-&amp;gt;&lt;/span&gt;tm_hour &lt;span style="color:#666"&gt;+&lt;/span&gt; (min &lt;span style="color:#666"&gt;/&lt;/span&gt; &lt;span style="color:#40a070"&gt;60.0f&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This means the hour hand moves gradually as the minutes pass, and the minute
hand moves as the seconds pass.&lt;/p&gt;
&lt;p&gt;The project has four themes (Stealth, Classic, Contrast, and Nightstand), all
keeping the iconic yellow second hand.&lt;/p&gt;
&lt;p&gt;Download the latest release from the &lt;a href="https://github.com/eliseomartelli/bc03-r36s/releases"&gt;GitHub repo&lt;/a&gt;.&lt;/p&gt;</description><category>Programming</category></item><item><title>Automating my CV with LaTeX and GitHub Actions</title><link>https://eliseomartelli.it/blog/2026-02-13-automating-my-cv-with-latex-and-github-actions/</link><pubDate>Fri, 13 Feb 2026 10:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2026-02-13-automating-my-cv-with-latex-and-github-actions/</guid><description>&lt;p&gt;After two wonderful, albeit intense years, working with Consortium GARR on
bioinformatics tools, it&amp;rsquo;s time for a new challenge. I&amp;rsquo;m officially back on the
market.&lt;/p&gt;
&lt;p&gt;Being a developer, passionate about automation and reproducibility, with a
strong background in &amp;ldquo;DevOps&amp;rdquo;, I couldn&amp;rsquo;t just &lt;em&gt;write&lt;/em&gt; a CV in a word processor
and call it a day. It has to be version controlled, reproducible, and automated.&lt;/p&gt;
&lt;p&gt;For years, I&amp;rsquo;ve used LaTeX for most of my writing needs, including my resume.
It&amp;rsquo;s stable, looks professional, and most importantly, it&amp;rsquo;s plain text,
which means I can use all the tools I&amp;rsquo;m most confortable with: Git, Vim, and of
course all the good UNIX command line tools.&lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;m easily annoyed by WYSIWYG editors, and I don&amp;rsquo;t want to deal with
formatting issues every time I need to edit something, I created a zero-thought
workflow for my CV. With a custom command to keep the formatting consistent,
a simple Makefile to build the PDF, and a GitHub Action to automate the release
process, I can focus on what really matters: describing my experience and
skills.&lt;/p&gt;
&lt;p&gt;The custom command &lt;code&gt;\resumeItem&lt;/code&gt; is something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-latex" data-lang="latex"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;\newcommand&lt;/span&gt;&lt;span style="color:#007020"&gt;{&lt;/span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;\resumeItem&lt;/span&gt;&lt;span style="color:#007020"&gt;}&lt;/span&gt;[5]&lt;span style="color:#007020"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;\noindent&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;\textbf&lt;/span&gt;&lt;span style="color:#007020"&gt;{&lt;/span&gt;#1&lt;span style="color:#007020"&gt;}&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;\hfill&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;\textbf&lt;/span&gt;&lt;span style="color:#007020"&gt;{&lt;/span&gt;#2&lt;span style="color:#007020"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;\ifboolexpr&lt;/span&gt;&lt;span style="color:#007020"&gt;{&lt;/span&gt;test &lt;span style="color:#007020"&gt;{&lt;/span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;\ifstrempty&lt;/span&gt;&lt;span style="color:#007020"&gt;{&lt;/span&gt;#3&lt;span style="color:#007020"&gt;}}&lt;/span&gt; and test &lt;span style="color:#007020"&gt;{&lt;/span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;\ifstrempty&lt;/span&gt;&lt;span style="color:#007020"&gt;{&lt;/span&gt;#4&lt;span style="color:#007020"&gt;}}}{}{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;\\&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;\textit&lt;/span&gt;&lt;span style="color:#007020"&gt;{&lt;/span&gt;#3&lt;span style="color:#007020"&gt;}&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;\hfill&lt;/span&gt; #4
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;\ifstrempty&lt;/span&gt;&lt;span style="color:#007020"&gt;{&lt;/span&gt;#5&lt;span style="color:#007020"&gt;}{}{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;\begin&lt;/span&gt;&lt;span style="color:#007020"&gt;{&lt;/span&gt;itemize&lt;span style="color:#007020"&gt;}&lt;/span&gt;[noitemsep,topsep=0pt,leftmargin=1.5em]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt; #5
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;\end&lt;/span&gt;&lt;span style="color:#007020"&gt;{&lt;/span&gt;itemize&lt;span style="color:#007020"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;\vspace&lt;/span&gt;&lt;span style="color:#007020"&gt;{&lt;/span&gt;1em&lt;span style="color:#007020"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The GitHub Action is triggered on every push to the repository, and
when I push a tag, the workflow creates a GitHub Release with the PDF attached.&lt;/p&gt;
&lt;p&gt;This setup ensures that I always have a &amp;ldquo;production-ready&amp;rdquo; version of my CV
available at a stable URL. No more &amp;ldquo;CV_final_v2_new.pdf&amp;rdquo; files floating around.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re looking for a developer who treats everything, even his resume, with
the rigor of a production system, &lt;a href="mailto:me@eliseomartelli.it"&gt;let&amp;rsquo;s talk&lt;/a&gt;.&lt;/p&gt;</description><category>Automation</category><category>Programming</category></item><item><title>Finding the needle in the logstack: Reducing LLM context with TF-IDF</title><link>https://eliseomartelli.it/blog/2026-02-05-reducing-the-context-passed-to-llms-with-tf-idf-and-cosine-similarity/</link><pubDate>Thu, 05 Feb 2026 20:51:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2026-02-05-reducing-the-context-passed-to-llms-with-tf-idf-and-cosine-similarity/</guid><description>&lt;p&gt;If you&amp;rsquo;ve ever looked at a build log for a complex project, you know the
feeling. It&amp;rsquo;s a wall of text-thousands of lines of dependency resolutions,
compiler warnings, and progress bars. Somewhere in there, a missing header file
is hiding, but finding it is a chore.&lt;/p&gt;
&lt;p&gt;At work, we&amp;rsquo;ve been building &lt;code&gt;AI-Harness&lt;/code&gt; (part of the broader &lt;code&gt;Rebel&lt;/code&gt;
ecosystem), a tool designed to autonomously fix these build failures. While
&lt;code&gt;Rebel&lt;/code&gt; acts as a reproducible environment manager for bioinformatics, handling
the mess of &lt;code&gt;apt&lt;/code&gt;, &lt;code&gt;conda&lt;/code&gt;, and &lt;code&gt;pip&lt;/code&gt; dependencies—, AI-Harness` is the
&amp;ldquo;self-healing&amp;rdquo; layer.&lt;/p&gt;
&lt;p&gt;Our immediate goal was to seed a database of missing dependencies: essentially
mapping specific compiler or linker error messages to the exact system packages
required to fix them. The idea is simple: feed the log to an LLM, and let it
figure out what’s missing. But there&amp;rsquo;s a catch.&lt;/p&gt;
&lt;h2 id="the-lost-in-the-middle-problem"&gt;The &amp;ldquo;Lost in the Middle&amp;rdquo; Problem&lt;/h2&gt;
&lt;p&gt;Modern LLMs have massive context windows, but they aren&amp;rsquo;t magic. Research like
&lt;a href="https://arxiv.org/abs/2307.03172"&gt;&amp;ldquo;Lost in the Middle&amp;rdquo; (Liu et al., 2023)&lt;/a&gt;
shows that models follow a U-shaped performance curve. They are great at
remembering the beginning and the end of your prompt, but they get &amp;ldquo;lost in the
middle.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;When you dump a 1MB log into a prompt, the actual error message often ends up
buried right in that middle-zone. The model loses its reasoning capabilities,
and instead of a fix, you get a hallucination or a generic &amp;ldquo;I don&amp;rsquo;t see any
errors.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="signal-vs-noise"&gt;Signal vs. Noise&lt;/h2&gt;
&lt;p&gt;To solve this, I implemented a preprocessing pipeline that uses &lt;strong&gt;TF-IDF&lt;/strong&gt; and
&lt;strong&gt;Cosine Similarity&lt;/strong&gt; to find the most relevant chunks of the log before the
LLM even sees it. This is a technique I&amp;rsquo;ve &lt;a href="https://eliseomartelli.it/blog/2025-03-11-recomendation-system/"&gt;previously used for this blog&amp;rsquo;s
recommendation system&lt;/a&gt;, and it turns out
to be just as effective for log analysis.&lt;/p&gt;
&lt;p&gt;The logic is essentially a mini-search engine:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Chunking&lt;/strong&gt;: We break the log into 10-line windows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TF-IDF&lt;/strong&gt;: We rank terms so that common noise (like &lt;code&gt;building&lt;/code&gt;, &lt;code&gt;at&lt;/code&gt;, &lt;code&gt;the&lt;/code&gt;)
is ignored, while rare &amp;ldquo;signal&amp;rdquo; terms (like &lt;code&gt;SegmentationFault&lt;/code&gt;,
&lt;code&gt;AttributeError&lt;/code&gt;, or &lt;code&gt;undefined reference&lt;/code&gt;) are prioritized.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cosine Similarity&lt;/strong&gt;: We compare each chunk against a targeted query:
&lt;code&gt;&amp;quot;error failure exception traceback&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Instead of giving the LLM the entire log, we give it the top K=10 chunks
that mathematically look most like an actual failure. We arrived at this number
after running a series of experiments to find the &amp;ldquo;elbow&amp;rdquo; of the success rate
curve—at 10 chunks, we maximize signal without overwhelming the context window.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/2026-02-05-reducing-the-context-passed-to-llms-with-tf-idf-and-cosine-similarity/experiment_k.png" alt="Success rate vs K"&gt;&lt;/p&gt;
&lt;h2 id="does-it-actually-work"&gt;Does it actually work?&lt;/h2&gt;
&lt;p&gt;We tested this against a dataset of 245 failed dependency installations from
the CRAN (R) ecosystem. The results were pretty stark:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;Success Rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Raw Log&lt;/td&gt;
&lt;td&gt;38.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TF-IDF&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;74.3%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;By being selective about what we show the model, we effectively doubled the
success rate. It turns out that even for &amp;ldquo;intelligent&amp;rdquo; models, less is often
more.&lt;/p&gt;
&lt;p&gt;If you are interested in the math or the implementation, the project lives over
at &lt;a href="https://github.com/Rebel-Project-Core/Rebel"&gt;Rebel&lt;/a&gt; and
&lt;a href="https://github.com/rebel-Project-Core/AI-Harness"&gt;AI-Harness&lt;/a&gt;. It&amp;rsquo;s a reminder
that even in the age of LLMs, classic Information Retrieval techniques still
have a lot to teach us about building the robust, reproducible environments
that open science requires.&lt;/p&gt;</description><category>Ai</category><category>Programming</category></item><item><title>Developing Kodak Tri-X 400 at 1600 ISO in Bellini Euro HC</title><link>https://eliseomartelli.it/blog/2026-01-06-developing-kodak-tri-x-400-at-1600-iso-in-bellini-euro-hc/</link><pubDate>Tue, 06 Jan 2026 09:20:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2026-01-06-developing-kodak-tri-x-400-at-1600-iso-in-bellini-euro-hc/</guid><description>&lt;p&gt;Many of you might know that I&amp;rsquo;ve recently started shooting more film, and I&amp;rsquo;ve
fallen in countless deep rabbit holes, like &lt;a href="https://eliseomartelli.it/blog/2025-11-12-scanning-workflow/"&gt;scanning&lt;/a&gt;.
But one of the most exciting aspect of film photography, for me, is the darkroom
process itself.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve already experimented developing Harman&amp;rsquo;s Kentmere 400 but I have to
perfect my process for it. Today I want to share my approach to developing
Kodak Tri-X 400 pushed to 1600 ISO.&lt;/p&gt;
&lt;p&gt;I develop in a &lt;a href="https://www.ars-imago.com/en/lab-box"&gt;Lab-Box&lt;/a&gt;. It’s partly
similar to the &lt;a href="https://www.gerardobonomo.it/2020/08/03/sviluppiamo-una-pellicola-con-la-tank-daylight-agfa-rondinax-in-pieno-sole/"&gt;Agda Rodinax (link in
italian)&lt;/a&gt;,
that solves the darkroom-less problem, but since I want to save water and
chemicals, I opted to introduce a new variable: &lt;strong&gt;continuous agitation&lt;/strong&gt;, that
changes the way the chemistry interacts with the silver in the film.&lt;/p&gt;
&lt;h3 id="the-calculation"&gt;The Calculation&lt;/h3&gt;
&lt;p&gt;There&amp;rsquo;s no standard time for Tri-X in &lt;a href="https://www.bellinifoto.it/wp-content/uploads/2021/10/EURO-HC.pdf"&gt;Bellini Euro
HC&lt;/a&gt;
(Dilution B). What I did was to go to &lt;a href="https://www.digitaltruth.com/devchart.php?Film=Kodak+Tri-X+400&amp;amp;Developer=HC-110%25&amp;amp;mdc=Search&amp;amp;TempUnits=C&amp;amp;TimeUnits=D"&gt;Massive Dev Chart&lt;/a&gt; for Kodak Tri-X and
find the time for HC-110 at 1600 ISO, wich is &lt;strong&gt;16:00&lt;/strong&gt; at 20°C, dilution B,
with standard agitation. But continuous agitation increases the chemical
activity. The developer is always fresh against the emulsion, so the process
happens faster. Also the push of 2 stops means we need to extend the time.&lt;/p&gt;
&lt;p&gt;To compensate, I used my &lt;a href="https://eliseomartelli.it/tools/filmcalc"&gt;film calculator&lt;/a&gt; to find the right
development time.&lt;/p&gt;
&lt;div class="film-calculator-card" style="border: 1px solid #ccc; border-radius: 8px; margin: 1.5rem 0; overflow: hidden; font-family: inherit; max-width: 500px;"&gt;
&lt;div style="background: var(--card-header-bg, #f5f5f5); padding: 0.75rem 1rem; border-bottom: 1px solid #ccc; display: flex; align-items: center; justify-content: space-between;"&gt;
&lt;span style="font-weight: bold; font-size: 1.05rem; color: var(--text-color, #111);"&gt;Development&lt;/span&gt;
&lt;span style="font-size: 0.9rem; opacity: 0.85; font-style: italic;"&gt;Bellini Euro HC&lt;/span&gt;
&lt;/div&gt;
&lt;div style="padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem; font-size: 0.95rem;"&gt;
&lt;div style="display: flex; justify-content: space-between; border-bottom: 1px dashed #eee; padding-bottom: 0.25rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8;"&gt;Dilution&lt;/span&gt;
&lt;span&gt;1&amp;#43;31 - Dilution B&lt;/span&gt;
&lt;/div&gt;
&lt;div style="display: flex; justify-content: space-between; border-bottom: 1px dashed #eee; padding-bottom: 0.25rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8;"&gt;Time&lt;/span&gt;
&lt;span style="font-weight: bold; color: var(--link-color, #a81d27);"&gt;6:20&lt;/span&gt;
&lt;/div&gt;
&lt;div style="display: flex; justify-content: space-between; border-bottom: 1px dashed #eee; padding-bottom: 0.25rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8;"&gt;Temperature&lt;/span&gt;
&lt;span&gt;20°C&lt;/span&gt;
&lt;/div&gt;
&lt;div style="display: flex; justify-content: space-between; border-bottom: 1px dashed #eee; padding-bottom: 0.25rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8;"&gt;Push&lt;/span&gt;
&lt;span style="font-weight: bold;"&gt;+2 stops&lt;/span&gt;
&lt;/div&gt;
&lt;div style="display: flex; flex-direction: column; gap: 0.15rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8; font-size: 0.9rem;"&gt;Agitation&lt;/span&gt;
&lt;span style="font-size: 0.9rem; opacity: 0.95;"&gt;Continuous rotation&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;The mix: &lt;strong&gt;10ml&lt;/strong&gt; Euro HC + &lt;strong&gt;290ml&lt;/strong&gt; Water. Need help with the ratios? Check the &lt;a href="https://eliseomartelli.it/tools/dilution"&gt;dilution tool&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For the stop bath, I keep it simple. Water. 3 cycles of rotation to ensure the
development is fully halted across the entire roll.&lt;/p&gt;
&lt;div class="film-calculator-card" style="border: 1px solid #ccc; border-radius: 8px; margin: 1.5rem 0; overflow: hidden; font-family: inherit; max-width: 500px;"&gt;
&lt;div style="background: var(--card-header-bg, #f5f5f5); padding: 0.75rem 1rem; border-bottom: 1px solid #ccc; display: flex; align-items: center; justify-content: space-between;"&gt;
&lt;span style="font-weight: bold; font-size: 1.05rem; color: var(--text-color, #111);"&gt;Stop Bath&lt;/span&gt;
&lt;span style="font-size: 0.9rem; opacity: 0.85; font-style: italic;"&gt;Water&lt;/span&gt;
&lt;/div&gt;
&lt;div style="padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem; font-size: 0.95rem;"&gt;
&lt;div style="display: flex; justify-content: space-between; border-bottom: 1px dashed #eee; padding-bottom: 0.25rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8;"&gt;Time&lt;/span&gt;
&lt;span style="font-weight: bold; color: var(--link-color, #a81d27);"&gt;1:00&lt;/span&gt;
&lt;/div&gt;
&lt;div style="display: flex; justify-content: space-between; border-bottom: 1px dashed #eee; padding-bottom: 0.25rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8;"&gt;Temperature&lt;/span&gt;
&lt;span&gt;20°C&lt;/span&gt;
&lt;/div&gt;
&lt;div style="display: flex; flex-direction: column; gap: 0.15rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8; font-size: 0.9rem;"&gt;Agitation&lt;/span&gt;
&lt;span style="font-size: 0.9rem; opacity: 0.95;"&gt;3 cycles of 20 seconds rotation&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then, the fixer. &lt;a href="https://www.bellinifoto.it/wp-content/uploads/2020/03/BWFX-100-2.pdf"&gt;Bellini
FX-100&lt;/a&gt;. It’s
fast and reliable.&lt;/p&gt;
&lt;div class="film-calculator-card" style="border: 1px solid #ccc; border-radius: 8px; margin: 1.5rem 0; overflow: hidden; font-family: inherit; max-width: 500px;"&gt;
&lt;div style="background: var(--card-header-bg, #f5f5f5); padding: 0.75rem 1rem; border-bottom: 1px solid #ccc; display: flex; align-items: center; justify-content: space-between;"&gt;
&lt;span style="font-weight: bold; font-size: 1.05rem; color: var(--text-color, #111);"&gt;Fixer&lt;/span&gt;
&lt;span style="font-size: 0.9rem; opacity: 0.85; font-style: italic;"&gt;Bellini FX-100&lt;/span&gt;
&lt;/div&gt;
&lt;div style="padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem; font-size: 0.95rem;"&gt;
&lt;div style="display: flex; justify-content: space-between; border-bottom: 1px dashed #eee; padding-bottom: 0.25rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8;"&gt;Dilution&lt;/span&gt;
&lt;span&gt;1&amp;#43;4&lt;/span&gt;
&lt;/div&gt;
&lt;div style="display: flex; justify-content: space-between; border-bottom: 1px dashed #eee; padding-bottom: 0.25rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8;"&gt;Time&lt;/span&gt;
&lt;span style="font-weight: bold; color: var(--link-color, #a81d27);"&gt;3:00&lt;/span&gt;
&lt;/div&gt;
&lt;div style="display: flex; justify-content: space-between; border-bottom: 1px dashed #eee; padding-bottom: 0.25rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8;"&gt;Temperature&lt;/span&gt;
&lt;span&gt;20°C&lt;/span&gt;
&lt;/div&gt;
&lt;div style="display: flex; flex-direction: column; gap: 0.15rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8; font-size: 0.9rem;"&gt;Agitation&lt;/span&gt;
&lt;span style="font-size: 0.9rem; opacity: 0.95;"&gt;Continuous rotation&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;The mix: &lt;strong&gt;60ml&lt;/strong&gt; Fixer + &lt;strong&gt;240ml&lt;/strong&gt; Water.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Washing in a Lab-Box can be water-intensive if you aren&amp;rsquo;t careful. I use the
&amp;ldquo;Ilford Method,&amp;rdquo; adapted for rotation. It’s efficient and ensures archival
stability without wasting liters of water.&lt;/p&gt;
&lt;div class="film-calculator-card" style="border: 1px solid #ccc; border-radius: 8px; margin: 1.5rem 0; overflow: hidden; font-family: inherit; max-width: 500px;"&gt;
&lt;div style="background: var(--card-header-bg, #f5f5f5); padding: 0.75rem 1rem; border-bottom: 1px solid #ccc; display: flex; align-items: center; justify-content: space-between;"&gt;
&lt;span style="font-weight: bold; font-size: 1.05rem; color: var(--text-color, #111);"&gt;Wash&lt;/span&gt;
&lt;span style="font-size: 0.9rem; opacity: 0.85; font-style: italic;"&gt;Water&lt;/span&gt;
&lt;/div&gt;
&lt;div style="padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem; font-size: 0.95rem;"&gt;
&lt;div style="display: flex; justify-content: space-between; border-bottom: 1px dashed #eee; padding-bottom: 0.25rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8;"&gt;Time&lt;/span&gt;
&lt;span style="font-weight: bold; color: var(--link-color, #a81d27);"&gt;3:30&lt;/span&gt;
&lt;/div&gt;
&lt;div style="display: flex; justify-content: space-between; border-bottom: 1px dashed #eee; padding-bottom: 0.25rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8;"&gt;Temperature&lt;/span&gt;
&lt;span&gt;20°C&lt;/span&gt;
&lt;/div&gt;
&lt;div style="display: flex; flex-direction: column; gap: 0.15rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8; font-size: 0.9rem;"&gt;Agitation&lt;/span&gt;
&lt;span style="font-size: 0.9rem; opacity: 0.95;"&gt;3 cycles: 30s, 1m, 2m rotation&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Finally, the wetting agent. This is the finishing touch, ensuring the final
negatives are clean and streak-free. I use &lt;a href="https://www.bellinifoto.it/wp-content/uploads/2020/03/BWSTAB1-1.pdf"&gt;Bellini
Stab&lt;/a&gt;.&lt;/p&gt;
&lt;div class="film-calculator-card" style="border: 1px solid #ccc; border-radius: 8px; margin: 1.5rem 0; overflow: hidden; font-family: inherit; max-width: 500px;"&gt;
&lt;div style="background: var(--card-header-bg, #f5f5f5); padding: 0.75rem 1rem; border-bottom: 1px solid #ccc; display: flex; align-items: center; justify-content: space-between;"&gt;
&lt;span style="font-weight: bold; font-size: 1.05rem; color: var(--text-color, #111);"&gt;Wetting Agent&lt;/span&gt;
&lt;span style="font-size: 0.9rem; opacity: 0.85; font-style: italic;"&gt;Bellini Stab&lt;/span&gt;
&lt;/div&gt;
&lt;div style="padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem; font-size: 0.95rem;"&gt;
&lt;div style="display: flex; justify-content: space-between; border-bottom: 1px dashed #eee; padding-bottom: 0.25rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8;"&gt;Dilution&lt;/span&gt;
&lt;span&gt;1&amp;#43;299&lt;/span&gt;
&lt;/div&gt;
&lt;div style="display: flex; justify-content: space-between; border-bottom: 1px dashed #eee; padding-bottom: 0.25rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8;"&gt;Time&lt;/span&gt;
&lt;span style="font-weight: bold; color: var(--link-color, #a81d27);"&gt;2:00&lt;/span&gt;
&lt;/div&gt;
&lt;div style="display: flex; justify-content: space-between; border-bottom: 1px dashed #eee; padding-bottom: 0.25rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8;"&gt;Temperature&lt;/span&gt;
&lt;span&gt;20°C&lt;/span&gt;
&lt;/div&gt;
&lt;div style="display: flex; flex-direction: column; gap: 0.15rem;"&gt;
&lt;span style="font-weight: 600; opacity: 0.8; font-size: 0.9rem;"&gt;Agitation&lt;/span&gt;
&lt;span style="font-size: 0.9rem; opacity: 0.95;"&gt;Gentle rotation&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;The mix: &lt;strong&gt;1ml&lt;/strong&gt; Stab + &lt;strong&gt;300ml&lt;/strong&gt; Water.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="the-results"&gt;The Results&lt;/h3&gt;
&lt;p&gt;Since I’m not patient enough to wait for natural evaporation I hung the film to
dry in a room with a dehumidifier running.&lt;/p&gt;
&lt;p&gt;The negatives look promising. The contrast is punchy, and the highlights are
dense without being blocked up. Most importantly, the edge markings are crisp
and legible. In the darkroom, legibility is the definitive proof of your
development time. If the text is clear, the chemistry worked.&lt;/p&gt;</description><category>Photography</category></item><item><title>Goodbye 2025</title><link>https://eliseomartelli.it/blog/2025-12-31-goodbye-2025/</link><pubDate>Wed, 31 Dec 2025 17:45:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-12-31-goodbye-2025/</guid><description>&lt;p&gt;As 2025 comes to a close, I&amp;rsquo;m taking a moment to commit this year to the
archives (pun intended).&lt;br&gt;
It&amp;rsquo;s been a year defined by contrast. On one hand, I&amp;rsquo;ve seen growth in my
personal and professional life, but on the other hand, it required navigating
through plenty of friction.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve had the opportunity to reconnect with old friends and enjoy company of
my collegues. These genuine connections have been enriching my life in ways
I can&amp;rsquo;t quantify.&lt;/p&gt;
&lt;p&gt;I also faced challenges that tested my resilience, some originating from
people I trusted. While difficult, these experiences have taught me valuable
lessons about the value of boundaries and the importance of surrounding myself
with people who uplift me.&lt;br&gt;
It reinforced my own core principles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create meaningful contributions that benefit others.&lt;/li&gt;
&lt;li&gt;Pursue what feels truly meaningful to me.&lt;/li&gt;
&lt;li&gt;Ensure my actions align with my values and ethics.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This year marked a tremendous change to my hobbies. I reignited my passion
for photography, capturing moments with friends, and the beauty of the mundane.&lt;br&gt;
A passion that I&amp;rsquo;ve been able to share with others, leading to new friendships
and influencing other friends to pick up the camera themselves.&lt;/p&gt;
&lt;p&gt;Thank you to everyone who has been part of the loop this year. Your support
means everything.&lt;/p&gt;
&lt;p&gt;See you in the new year.&lt;/p&gt;</description><category>Misc</category></item><item><title>Reverse Engineering the Cinema: Bypassing Seat Adjacency Rules with DevTools</title><link>https://eliseomartelli.it/blog/2025-12-27-reverse-engineering-the-cinema-bypassing-seat-adjacency-rules-with-devtools/</link><pubDate>Sat, 27 Dec 2025 14:33:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-12-27-reverse-engineering-the-cinema-bypassing-seat-adjacency-rules-with-devtools/</guid><description>&lt;p&gt;The Christmas season is here, and with it the tradition of gathering the family
for a movie.&lt;br&gt;
We have all been there: you navigate to the cinema’s booking
site, find that perfect row in the center of the theater, and click to select
your seats.&lt;/p&gt;
&lt;p&gt;But instead of confirming, the website throws an error: &amp;ldquo;You cannot leave a
single seat empty&amp;rdquo; or &amp;ldquo;Please select adjacent seats.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;It is a case of aggressive business logic interfering with user experience. The
site was enforcing a &amp;ldquo;no orphan seat&amp;rdquo; rule, forcing me to sit next to
strangers or pick a worse row, even though the seats I wanted were available.&lt;/p&gt;
&lt;p&gt;So, instead of changing my seats, I changed the code. Here is how I used the
browser&amp;rsquo;s debugger to get the seats I actually wanted.&lt;/p&gt;
&lt;h2 id="the-problem-client-side-validation"&gt;The Problem: Client-Side Validation&lt;/h2&gt;
&lt;p&gt;Websites often use JavaScript to validate user input before sending it to the
server. In this case, the cinema site was using JavaScript to check the
&amp;ldquo;geometry&amp;rdquo; of my seat selection in real-time.&lt;/p&gt;
&lt;p&gt;The logic was simple: check the seats to the left and right of my selection. If
my choice created an &amp;ldquo;orphan&amp;rdquo; (a single empty gap), the function returned
&lt;code&gt;false&lt;/code&gt;, triggering an error and preventing the seats from being added to the
cart.&lt;/p&gt;
&lt;p&gt;Since this validation was happening entirely in my browser, I had full control
over it. I just needed to find the switch.&lt;/p&gt;
&lt;h2 id="the-hunt-search-trace-locate"&gt;The Hunt: Search, Trace, Locate&lt;/h2&gt;
&lt;p&gt;I searched the frontend source code for the specific Italian localization
string of the error message I was seeing. Once I found the error string, I
looked for the specific conditional statement that triggered it. That trigger
led me backwards to a specific validation function in the JavaScript bundle:
&lt;code&gt;scope.ic.checkJacketSeat&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt;// Original function snippet with added comments.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;scope.ic.checkJacketSeat &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;function&lt;/span&gt;(seatIds) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;// ...omitted code...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;var&lt;/span&gt; prevFree &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;var&lt;/span&gt; fromLeft &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;// Sort the row to scan usually
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt; row.sort(&lt;span style="color:#007020;font-weight:bold"&gt;function&lt;/span&gt; (a, b) { &lt;span style="color:#007020;font-weight:bold"&gt;return&lt;/span&gt; a.colonna &lt;span style="color:#666"&gt;-&lt;/span&gt; b.colonna; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;for&lt;/span&gt; (&lt;span style="color:#007020;font-weight:bold"&gt;var&lt;/span&gt; i &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#40a070"&gt;0&lt;/span&gt;; i &lt;span style="color:#666"&gt;&amp;lt;&lt;/span&gt; row.length; i&lt;span style="color:#666"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;// If we hit a selected seat, and the previous seat was &amp;#34;free&amp;#34; (a gap)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;// Then we are not anchored to the left.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;if&lt;/span&gt; (selectionIds[row[i].idposto] &lt;span style="color:#666"&gt;&amp;amp;&amp;amp;&lt;/span&gt; prevFree) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt; fromLeft &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16&lt;/span&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;// &amp;#34;prevFree&amp;#34; is true if the seat is neither selected nor busy
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17&lt;/span&gt;&lt;span&gt; prevFree &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#666"&gt;!&lt;/span&gt;selectionIds[row[i].idposto] &lt;span style="color:#666"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#666"&gt;!&lt;/span&gt;row[i].busy;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20&lt;/span&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;// ... (The code then repeats this logic for the Right side)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22&lt;/span&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;// If we have gaps on BOTH sides, block the purchase.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23&lt;/span&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;// THIS IS THE KEY CHECK WE WANT TO BYPASS!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;if&lt;/span&gt; (&lt;span style="color:#666"&gt;!&lt;/span&gt;fromRight &lt;span style="color:#666"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#666"&gt;!&lt;/span&gt;fromLeft) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;26&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;27&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;28&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;29&lt;/span&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The logic is surprisingly simple. It scans the row from both directions. If
your selection is preceded by a &amp;ldquo;gap&amp;rdquo; (a seat that is neither sold nor selected
by you) on both the left and the right, it flags you as an invalid selection.&lt;/p&gt;
&lt;h2 id="the-fix-bypass-the-check"&gt;The Fix: Bypass the Check&lt;/h2&gt;
&lt;p&gt;With the function located, I could now override it. Using the browser&amp;rsquo;s DevTools.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I opened the Sources tab in DevTools and located checkJacketSeat.&lt;/li&gt;
&lt;li&gt;I set a breakpoint right inside the function.&lt;/li&gt;
&lt;li&gt;When I clicked my desired seat, the browser paused execution.&lt;/li&gt;
&lt;li&gt;I stepped through the code until I reached the final check.&lt;/li&gt;
&lt;li&gt;Just before the function returned, I modified the return value to &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The UI immediately updated, marking the seats as valid (blue). The server,
trusting the client&amp;rsquo;s validation, accepted the order without complaint.&lt;/p&gt;
&lt;p&gt;If you are a developer, this is a gentle reminder: Client-side validation is a
suggestion, not a &amp;ldquo;law&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;It is excellent for User Experience (giving instant feedback), but it is
useless for security or strict business logic enforcement. If you have strict
rules about seat spacing, they must be enforced on the server. Otherwise, a
user with a debugger is only a breakpoint away from sitting wherever they want.&lt;/p&gt;</description><category>Misc</category></item><item><title>Adjusting the Rangefinder Horizontal Alignment</title><link>https://eliseomartelli.it/blog/2025-11-24-adjusting-the-rangefinder-horizontal-alignment/</link><pubDate>Mon, 24 Nov 2025 00:38:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-11-24-adjusting-the-rangefinder-horizontal-alignment/</guid><description>&lt;p&gt;First things first, let me take this out of the way: I am not a professional
camera technician. However, I have had to adjust the rangefinder horizontal
alignment on several Leica cameras, and I have found a method that works well
for me. Proceed with caution and at your own risk.
Poking tools inside your camera carries significant risk. If you are
uncomfortable with precision adjustments, please send your camera to a
qualified technician.&lt;/p&gt;
&lt;h2 id="the-diagnosis"&gt;The Diagnosis&lt;/h2&gt;
&lt;p&gt;Before even thinking about adjusting the rangefinder, you need to confirm that
it is indeed misaligned. The most straightforward way I have found to do this
is to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Pick a target with high contrast details, such as a newspaper or a brick
wall at a focus distance you feel comfortable working with (e.g., 1-3
meters).&lt;/li&gt;
&lt;li&gt;Focus the lens and check the patch. The image should be sharp and the two
images should align perfectly.&lt;/li&gt;
&lt;li&gt;Take a test shot and review it. If the image is not sharp, it may indicate a
misalignment.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="am-i-backfront-focusing"&gt;Am I back/front focusing?&lt;/h2&gt;
&lt;p&gt;Before adjusting the rangefinder, it&amp;rsquo;s crucial to determine whether your camera
is back-focusing or front-focusing.&lt;/p&gt;
&lt;p&gt;This can be done by taking a series of test shots, wiggle the focus ring
slightly back and forth around the point where you believe the focus is
sharpest. If the sharpest focus occurs when the focus ring is slightly turned
towards infinity, your camera is back-focusing. Conversely, if the sharpest
focus occurs when the focus ring is turned slightly towards the minimum focus
distance, your camera is front-focusing.&lt;/p&gt;
&lt;h2 id="the-adjustment"&gt;The Adjustment&lt;/h2&gt;
&lt;p&gt;What you will need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2mm Hex Key (Allen Wrench): High quality is essential. A cheap one may strip
the screw.&lt;/li&gt;
&lt;li&gt;A high contrast target for focusing.&lt;/li&gt;
&lt;li&gt;Patience and a steady hand.&lt;/li&gt;
&lt;li&gt;A lens that you are comfortable working with.&lt;/li&gt;
&lt;li&gt;Good lighting conditions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pictured: Leica M (typ 240).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/2025-11-24-adjusting-the-rangefinder-horizontal-alignment/IMG_2010.jpg" alt="Leica M (typ 240) with a hex key"&gt;&lt;/p&gt;
&lt;p&gt;Steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start by putting yourself at 2 meters from the target, straight on, and
focus the lens carefully on that target, using the rangefinder patch.&lt;/li&gt;
&lt;li&gt;Take a test shot and review it.&lt;/li&gt;
&lt;li&gt;Remove the lens from the camera body.&lt;/li&gt;
&lt;li&gt;Locate the rangefinder adjustment screw. The screw on the roller is for
horizontal alignment.&lt;/li&gt;
&lt;li&gt;Using the 2mm hex key, make small adjustments to the screw.&lt;/li&gt;
&lt;li&gt;After each adjustment, reattach the lens, focus on the target again, and take
a test shot.&lt;/li&gt;
&lt;li&gt;Review the test shot to see if the focus has improved.&lt;/li&gt;
&lt;li&gt;Repeat the adjustment process until the focus is sharp.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Always apply gentle, steady torque.&lt;/strong&gt;&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/zYf8ZPtbMtU?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Rangefinders are designed to be calibrated at infinity focus.
However, many users find it more practical to calibrate at a closer distance,
such as 2 meters. This is because most of their photography occurs at closer
distances, and calibrating at 2 meters can provide better accuracy for their
typical shooting scenarios.&lt;/p&gt;</description><category>Misc</category><category>Photography</category></item><item><title>A lens on the ground</title><link>https://eliseomartelli.it/blog/2025-11-22-a-lens-on-the-ground/</link><pubDate>Sat, 22 Nov 2025 13:19:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-11-22-a-lens-on-the-ground/</guid><description>&lt;p&gt;I recently decided to add a Leica Elmarit-M 90mm f/2.8 to my kit.&lt;br&gt;
I found a copy at New Old Camera in Milan for €650, a steal, frankly. The
catch? A &amp;ldquo;non-original red dot&amp;rdquo; with a bit of excess glue. A cosmetic
imperfection that shaved €500 off the normal price. Perfect for me.&lt;/p&gt;
&lt;p&gt;It arrived yesterday. I unboxed it, then 10 hours later I dropped it.&lt;/p&gt;
&lt;p&gt;There is a specific kind of silence that fills a room when a piece of precision
German engineering hits the floor. It’s heavy. It’s expensive.&lt;/p&gt;
&lt;p&gt;It fell right out of my hands. The sound was sickening. I didn&amp;rsquo;t even want to
look. When I finally picked it up, the telescopic hood had taken the entire
impact. It’s dented. A distinct, metal scar on a lens I had owned for less than
an afternoon.&lt;/p&gt;
&lt;p&gt;My first reaction was visceral panic. But then my brother reminded me of an
interview I watched recently with Ivo Saglietti (incidentally, also on NOC’s
channel). He talks about his Leica M6 not as a fetish object, but as an
extension of his hand and eye. Battered, brassed, scratched, and infinitely
more beautiful for it.&lt;/p&gt;
&lt;p&gt;I checked the focus ring. Smooth as butter.
I checked the glass. Intact.
I checked the aperture. Clicky and precise.&lt;/p&gt;
&lt;p&gt;The lens took a bullet for me. Before the drop, it was just an asset with a
resale value. Now, it is unequivocally mine. It has a story.&lt;/p&gt;
&lt;p&gt;Cameras and lenses are meant to be meat, not idols. They are tools for
capturing reality, and reality is messy, imperfect, and sometimes hits the
floor.&lt;/p&gt;
&lt;p&gt;I can&amp;rsquo;t mount a filter on it anymore I think. The hood sticks a little when I
retract it in certain positions. But I suspect I will take better photos with
it now than I would have if I were terrified of scratching it.&lt;/p&gt;
&lt;p&gt;It’s not &amp;ldquo;mint&amp;rdquo; anymore. It’s better. It’s used.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/bBuuqUl9h-U?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;</description><category>Misc</category></item><item><title>The API of a Camera and the Unix manifesto</title><link>https://eliseomartelli.it/blog/2025-11-17-the-api-of-a-camera-and-the-unix-manifesto/</link><pubDate>Mon, 17 Nov 2025 12:16:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-11-17-the-api-of-a-camera-and-the-unix-manifesto/</guid><description>&lt;p&gt;As software developers we are keen to discuss APIs, interfaces, and the beauty
of well-crafted systems. We appreciate clarity, intentionality and the power
that comes from a focused set of capabilities. The Unix philosophy, taken as a
cultural norm of minimalist and modular components, brought us the Unix
operating system.&lt;/p&gt;
&lt;p&gt;The unix philosophy is pretty simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make each program do one thing well. To do a new job, build afresh rather
than complicate old programs by adding new &amp;ldquo;features&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;Expect the output of every program to become the input to another, as yet
unknown, program. Don&amp;rsquo;t clutter output with extraneous information. Avoid
stringently columnar or binary input formats. Don&amp;rsquo;t insist on interactive
input.&lt;/li&gt;
&lt;li&gt;Design and build software, even operating systems, to be tried early,
ideally within weeks. Don&amp;rsquo;t hesitate to throw away the clumsy parts and
rebuild them.&lt;/li&gt;
&lt;li&gt;Use tools in preference to unskilled help to lighten a programming task,
even if you have to detour to build the tools and expect to throw some of
them out after you&amp;rsquo;ve finished using them.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But I think this philosophy can be applied to other tools we use in our daily
lives, even outside the &amp;ldquo;digital realm&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;If you know me and you read this blog, you know that I’m also a photographer in
my (always-decreasing) free time. For me, the Unix philosophy finds its
expression in the Leica M line.&lt;/p&gt;
&lt;p&gt;Consider the core controls: shutter speed, ISO, and aperture (on the lens).
That&amp;rsquo;s it. It’s a beautifully constrained &amp;ldquo;API&amp;rdquo; for the act of photography.
There are no distracting modes, no infinite menus to navigate, no array of
buttons whose functions you constantly forget. It’s a deliberately designed
camera that deeply resonates with my appreciation for clean architecture.&lt;/p&gt;
&lt;p&gt;It brings to mind the profound words of Edsger W. Dijkstra:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We are all shaped by the tools we use, in particular: the formalisms we use
shape our thinking habits, for better or for worse, and that means that we
have to be very careful in the choice of what we learn and teach, for
unlearning is not really possible.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This quote encapsulates why the simplicity of a Leica M influences creativity.
You stop thinking about how to operate the camera and start thinking about how
to see the light, compose the frame, and capture the moment. The camera becomes
an extension of intent.&lt;/p&gt;
&lt;p&gt;In contrast, many modern cameras, while technically astounding, present an
overwhelming &amp;ldquo;API surface.&amp;rdquo; With countless buttons, custom modes, and nested
menus, they introduce a cognitive load that can pull the photographer away from
the art itself. It&amp;rsquo;s exactly like the difference between a well-designed
command-line tool that does one thing well, and a GUI mega-application monolith
that tries to do everything, clumsily.&lt;/p&gt;
&lt;p&gt;Another great quote from Dijkstra speaks about emotion:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As my very last remark I should like to stress that the tool as a whole
should have still another quality. It is a much more subtle one; whether we
appreciate it or not depends much more on our personal taste and education
and I shall not even try to define it. The tool should be charming, it should
be elegant, it should be worthy of our love. This is no joke, I am terribly
serious about this. In this respect the programmer does not differ from any
other craftsman: unless he loves his tools it is highly improbable that he
will ever create something of superior quality.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A Leica M, si a tool that is &amp;ldquo;charming&amp;rdquo; and &amp;ldquo;elegant&amp;rdquo;. The tactile satisfaction
of a Leica&amp;rsquo;s mechanical shutter, the precise click of an aperture ring and even
the deliberate act of manual focus, are hand crafted to be loved. The Leica M
does one job: it exposes film or a digital sensor. It&amp;rsquo;s designed to be the
input for the next tool in the chain: the darkroom, the developing tank, the
film scanner, or your digital post-production software.&lt;/p&gt;</description><category>Misc</category><category>Photography</category></item><item><title>Tagging film scans</title><link>https://eliseomartelli.it/blog/2025-11-16-tagging-film-scans/</link><pubDate>Sun, 16 Nov 2025 12:38:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-11-16-tagging-film-scans/</guid><description>&lt;p&gt;If you shoot film, and later you scan it, especially into formats like DNG, you
end up with a file that is poor in metadata. Your scanner software (like
VueScan) knows about the scanner &lt;a href="https://eliseomartelli.it/blog/2025-11-12-scanning-workflow/"&gt;perhaps a Plustek
8100&lt;/a&gt; and the scan settings, but it has no
idea what camera or lens took the original photo.&lt;br&gt;
This leaves a gap in your digital library.&lt;/p&gt;
&lt;p&gt;To fix this, I use &lt;a href="https://exiftool.org/"&gt;ExifTool&lt;/a&gt; to add the relevant EXIF
tags to the scanned files.&lt;/p&gt;
&lt;p&gt;Here is a single command I use to batch-process a directory of DNGs to embed
all the correct metadata for a specific camera and lens combination.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;exiftool -overwrite_original -r -ext dng &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;-Make&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;&amp;#34;Leica Camera AG&amp;#34;&lt;/span&gt; &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;-Model&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;&amp;#34;LEICA M6&amp;#34;&lt;/span&gt; &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt;-SerialNumber&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;&amp;#34;&amp;lt;camera serial number&amp;gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt;-LensMake&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;&amp;#34;Leica Camera AG&amp;#34;&lt;/span&gt; &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt;-LensModel&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;&amp;#34;Summicron-M 1:2/35 ASPH.&amp;#34;&lt;/span&gt; &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;-LensID&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;&amp;#34;Summicron-M 1:2/35 ASPH.&amp;#34;&lt;/span&gt; -LensInfo&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;&amp;#34;35mm f/2&amp;#34;&lt;/span&gt; &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt;-FocalLength&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;&amp;#34;35.0 mm&amp;#34;&lt;/span&gt; &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt;-Artist&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;&amp;#34;GIOELE MARTELLI&amp;#34;&lt;/span&gt; &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt;-ProfileName&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;&amp;#34;LEICA M6&amp;#34;&lt;/span&gt; &lt;span style="color:#4070a0;font-weight:bold"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-overwrite_original&lt;/code&gt; is an important flag. By default, ExifTool creates a
backup copy of your original file (e.g., &lt;code&gt;file.dng_original&lt;/code&gt;) before writing
changes. This flag prevents that, modifying the file in-place.&lt;br&gt;
This is a destructive action. I recommend running the command on &lt;em&gt;copies&lt;/em&gt; of
your files first until you are confident in the results.&lt;/p&gt;
&lt;p&gt;The above command is a &amp;ldquo;master template&amp;rdquo;. I keep a
separate script with variations of this command for my other common
combinations.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a simple, one-time step per batch of scans that makes my digital archive
infinitely more organized and searchable.&lt;/p&gt;</description><category>Short</category><category>Photography</category></item><item><title>My scanning workflow with the Plustek OpticFilm 8100 and VueScan</title><link>https://eliseomartelli.it/blog/2025-11-12-scanning-workflow/</link><pubDate>Wed, 12 Nov 2025 12:54:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-11-12-scanning-workflow/</guid><description>&lt;p&gt;I’ve recently integrated a new piece of hardware into my film photography
workflow: a Plustek OpticFilm 8100. After watching the second-hand market for a
few weeks, I found a unit on subito.it for €200.&lt;/p&gt;
&lt;p&gt;It’s not the best scanner ever, you can find some other scanners from the same
manufacturer with the infrared channel (used for identifying dust on the
negative), but for that price, I think you cannot do any better, and you can
always clean up the dust in post.&lt;/p&gt;
&lt;p&gt;The decision to buy a dedicated scanner was driven almost entirely by cost
effectiveness. Locally, film development alone costs €2,50, which is entirely
reasonable. However, development plus scanning jumps to €12 per roll. The €9,50
difference adds up quickly.  Since I plan to shoot at least 20 rolls a year,
and the scanner was a shared purchase with my
&lt;a href="https://martelligioele.it"&gt;brother&lt;/a&gt;, the scanner pays itself off in about 7
months. It also gives me the flexibility to scan archives and process roll for
friends (looking at you, &lt;a href="https://nomnp.com"&gt;nomnp&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id="software"&gt;Software&lt;/h2&gt;
&lt;p&gt;This scanner comes bundled with SilverFast, but since I’m a macOS user that
likes to keep his setup the most minimal as possible, and I already had VueScan
installed for scanning my Polaroids on a not-so-great all-in-one printer from
Canon, I decided to use it.&lt;br&gt;
Also, the plus is not having a software rooted in late-90s paradigms. I tend to
prefer tools that are straightforward and configurable, and VueScan INI
configuration files are perfect for saving and backing up.&lt;/p&gt;
&lt;h2 id="workflow"&gt;Workflow&lt;/h2&gt;
&lt;p&gt;My core objective is to create a high-quality, archival digital negative while
also getting a usable positive image, all with a single scan operation. To
achieve this, I’ve configured VueScan for a dual-output workflow.&lt;/p&gt;
&lt;p&gt;With every scan, the scanner gives me two files: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Graded Positive (DNG): Inverted, 16 bit grayscale or 48 bit RGB image.&lt;/li&gt;
&lt;li&gt;Raw Negative (DNG): Archival master, 48-bit DNG, unaltered linear data from
the scanner’s sensor.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="scan-settings"&gt;Scan Settings&lt;/h2&gt;
&lt;p&gt;After some nights of tinkering, I’ve settled on a few key settings for the
optimal balance of quality and efficiency. The core settings are: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Resolution: 3600 dpi. The 8100 claims a maximum of &lt;a href="https://plustek.com/us/products/film-photo-scanners/opticfilm-8100/spec.php"&gt;7200
dpi&lt;/a&gt;,
but this is largely coming from oversampling. The effective optical resolution
is closer to 3600-3900 dpi (observed by
&lt;a href="https://tommyraught.medium.com/scanning-35mm-film-with-the-plustek-opticfilm-8100-73efe117a152"&gt;others&lt;/a&gt;).
I’ve settled on 3600 dpi because it provides a good balance of quality and scan
speed. I can always rescan a specific frame at a higher resolution if I truly
need a massive print.&lt;/li&gt;
&lt;li&gt;Bit-depth: 48-bit RGB. This captures the maximum tonal data from the
negative, essential for post processing.&lt;/li&gt;
&lt;li&gt;Multi-Exposure: 2 passes. I’ve enabled 2-pass multi exposure, it doubles the
scan time, but provides a significant reduction in noise, especially in the
shadow areas. VueScan reads the entire scan area multiple times (in your case,
twice) and then averages the data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To get this dual output, I simply checked both &lt;code&gt;Output | Raw file&lt;/code&gt; and &lt;code&gt;Output | TIFF file&lt;/code&gt; in VueScan and enabled the DNG format for both.&lt;/p&gt;
&lt;h2 id="see-it-in-action"&gt;See it in action&lt;/h2&gt;
&lt;p&gt;I ran an Instagram Live session yesterday demonstrating this entire process
I’ve archived the video on YouTube for anyone interested in the specific,
in-depth setup.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/egM_okmvIA0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;This setup achieves the perfect balance for me: it&amp;rsquo;s cost-effective, gives me
full control over the archival process, and provides the flexibility to improve
my conversions in the future without having to rescan my entire archive.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;[VueScan]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;[Input]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;Options=&lt;span style="color:#40a070"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt;Source=OpticFilm &lt;span style="color:#40a070"&gt;8100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt;[Input-OpticFilm8100-Transparency]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt;Media=&lt;span style="color:#40a070"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;AutoMedia=&lt;span style="color:#40a070"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt;PreviewArea=&lt;span style="color:#40a070"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt;BitsPerPixel=&lt;span style="color:#40a070"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt;PreviewResolution=&lt;span style="color:#40a070"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt;ScanResolution=&lt;span style="color:#40a070"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt;NumberOfPasses=&lt;span style="color:#40a070"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt;[Output]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt;TIFFFileType=&lt;span style="color:#40a070"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt;TIFFCompression=&lt;span style="color:#40a070"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16&lt;/span&gt;&lt;span&gt;TIFFDNGFormat=&lt;span style="color:#40a070"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17&lt;/span&gt;&lt;span&gt;RawDNGFormat=&lt;span style="color:#40a070"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18&lt;/span&gt;&lt;span&gt;[Output-OpticFilm8100-Transparency]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19&lt;/span&gt;&lt;span&gt;TIFFFile=&lt;span style="color:#40a070"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20&lt;/span&gt;&lt;span&gt;JPEGFile=&lt;span style="color:#40a070"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21&lt;/span&gt;&lt;span&gt;RawFile=&lt;span style="color:#40a070"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><category>Photography</category><category>Misc</category></item><item><title>Contact and Newsletter temporarily offline</title><link>https://eliseomartelli.it/blog/2025-11-11-contact-and-newsletter-temporarily-offline/</link><pubDate>Tue, 11 Nov 2025 17:06:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-11-11-contact-and-newsletter-temporarily-offline/</guid><description>&lt;p&gt;The spam volume received through the contact function and the newsletter
function has recently crossed the threshold of &amp;ldquo;annoying&amp;rdquo; into &amp;ldquo;actively
degrading my life.&amp;rdquo;
The signal-to-noise ratio in my inbox is now approaching zero, and sifting
through junk to find legitimate messages is no longer a sustainable task.&lt;/p&gt;
&lt;p&gt;Effective immediately, I am temporarily disabling all site-based contact forms
and the newsletter subscription function.&lt;/p&gt;
&lt;p&gt;This is a stop-gap measure. I am currently working on implementing a
low-friction defense for my readers: stateless proof of work.&lt;/p&gt;
&lt;p&gt;Those who know me personally know how to reach me via standard channels.&lt;/p&gt;
&lt;p&gt;Once the new system is solid, the functionality will return.&lt;/p&gt;</description><category>Misc</category></item><item><title>Layers of glass</title><link>https://eliseomartelli.it/blog/2025-10-19-layers-of-glass/</link><pubDate>Sun, 19 Oct 2025 15:04:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-10-19-layers-of-glass/</guid><description>&lt;p&gt;Yesterday I went to see the Lee Miller exhibition at Camera here in Turin (for
the second time). It was a good show, with nice pictures and nice tension, that
Lee Miller carried from the fashion world into war.&lt;/p&gt;
&lt;p&gt;What really struck me wasn’t one of her photographs, but it was watching people
watching her photographs through their phones.&lt;/p&gt;
&lt;p&gt;There was specifically a lady that took a picture of every photograph at the
exhibition. Like xerox copies, as if the goal was to leave with a backup of the
exhibition.&lt;/p&gt;
&lt;p&gt;I don’t get the point in going to an exhibition to experience it through a
phone, you can find the same images online in two minutes.&lt;/p&gt;
&lt;p&gt;Maybe it’s the habit of documentation. Maybe it’s the need to prove that we’ve
been somewhere. We’ve trained ourselves to look at the world mediated by a
screen, even when the original is right in front of us.&lt;/p&gt;</description><category>Misc</category></item><item><title>Apple, how is this legible?</title><link>https://eliseomartelli.it/blog/2025-10-09-apple-how-is-this-legible/</link><pubDate>Thu, 09 Oct 2025 09:39:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-10-09-apple-how-is-this-legible/</guid><description>&lt;p&gt;Today I was using Apple Podcasts on my iPhone, and I noticed something that
made me do a double take. The text in the player, in the section where you ca
change the playback speed, was rendered in a light gray color on a slightly
less light gray background. It was so faint that it was nearly impossible to
read.&lt;/p&gt;
&lt;p&gt;You can&amp;rsquo;t even say if the &amp;ldquo;Enchance Dialogue&amp;rdquo; option was selected or not.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/2025-10-09-apple-how-is-this-legible/IMG_0376.jpeg" alt="Illegible Thing"&gt;&lt;/p&gt;
&lt;p&gt;Happens in both Light and Dark mode. iPhone 16. iOS 26.0.1 (23A355).
FB20588103.&lt;/p&gt;</description><category>Apple</category><category>Short</category></item><item><title>The Simulation of Connection: Spotify</title><link>https://eliseomartelli.it/blog/2025-10-07-the-simulation-of-connection/</link><pubDate>Tue, 07 Oct 2025 15:47:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-10-07-the-simulation-of-connection/</guid><description>&lt;blockquote&gt;
&lt;p&gt;Spotify&amp;rsquo;s vision has always been to be everywhere you are. By bringing
Spotify into ChatGPT, we&amp;rsquo;re creating a powerful new way for fans to connect
with the artists and creators they love conversationally, whenever
inspiration strikes.&lt;/p&gt;
&lt;p&gt;Sten Garmark, Spotify&amp;rsquo;s SVP of Global Head of Consumer Experience.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When I&amp;rsquo;ve read this quote, I found it rhetorically fascinating. It blurs the
line between &amp;ldquo;connection&amp;rdquo; and &amp;ldquo;interaction&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Can we be clear about what&amp;rsquo;s actually happening here? No one is actually
connecting with artists. Why are we continually trading away depth for
immediacy?&lt;/p&gt;
&lt;p&gt;Some examples?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We text instead of calling.&lt;/li&gt;
&lt;li&gt;We eat frozen food instead of cooking.&lt;/li&gt;
&lt;li&gt;We buy fast-fashion instead of clothes that last.&lt;/li&gt;
&lt;li&gt;We scroll through infinite feeds instead of engaging with a single work
deeply.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of these substitutions promises to &lt;em&gt;save time&lt;/em&gt;, as if the goal of life
were to eliminate the experience of living itself. Friction is treated as an
obstacle to be optimized away. But friction is also what gives texture to
experience. The effort, the waiting, these are not bugs in the human condition.
They&amp;rsquo;re &lt;em&gt;THE&lt;/em&gt; features.&lt;/p&gt;
&lt;p&gt;When platforms like Spotify talk about &amp;ldquo;connecting fans with artists,&amp;rdquo; they&amp;rsquo;re
not selling music. They&amp;rsquo;re selling &lt;em&gt;the feeling of intimacy&lt;/em&gt;. The same way
social media sells the &lt;em&gt;feeling of community&lt;/em&gt; or dating apps sell the &lt;em&gt;feeling
of romantic possibility&lt;/em&gt;, or casinos the feeling of winning.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a form of emotional outsourcing. We no longer engage with music as an act
of discovery or empathy; we engage with it as a consumer interface designed to
deliver predictable microdoses of sentiment.&lt;/p&gt;
&lt;p&gt;Art becomes data. Connection becomes simulation. Emotion becomes UX.&lt;/p&gt;
&lt;p&gt;None of this is to condemn technology itself; it&amp;rsquo;s to question the uncritical
way we let convenience dictate meaning.&lt;/p&gt;
&lt;p&gt;At what point do we admit that we&amp;rsquo;ve lost the plot? Or are we clapping like
trained seals, caring only about superficial appearances?&lt;/p&gt;</description><category>Music</category><category>Ai</category><category>Short</category></item><item><title>Slide Over is back!</title><link>https://eliseomartelli.it/blog/2025-10-07-slide-over/</link><pubDate>Tue, 07 Oct 2025 02:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-10-07-slide-over/</guid><description>&lt;p&gt;A few weeks ago, I expressed my concern about the loss of iPadOS 26&amp;rsquo;s essence
as it transformed into a fully windowing environment.&lt;/p&gt;
&lt;p&gt;Today, Apple has partially addressed my call: Slide Over is back, baby!&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/WGrKMVmz3Yw?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;With the release of iPadOS 26.1 Beta 2, Apple has restored Slide Over. This new
version of Slide Over resides within an updated &amp;ldquo;container&amp;rdquo; paradigm, which
differs from the previous version of a floating &amp;ldquo;app&amp;rdquo;. However, it introduces a
new feature: resizable windows.&lt;/p&gt;
&lt;p&gt;Users can resize, move, and manipulate the Slide Over window, maintaining its
temporary nature rather than functioning as a full app window. This compromise
strikes a balance between power and immediacy.&lt;/p&gt;
&lt;p&gt;I genuinely appreciate Apple&amp;rsquo;s decision to bring Slide Over back. It restores
an essential aspect of the iPad&amp;rsquo;s identity and it demonstrates Apple&amp;rsquo;s
commitment to the simplicity that made iPadOS unique in the first place.&lt;/p&gt;
&lt;p&gt;One additional (re issued) feature I would like to see is the return of task
switching within Slide Over. The previous stack of apps tucked on the side
facilitated seamless multitasking. Reintroducing this feature would complete
the experience.&lt;/p&gt;
&lt;p&gt;For now, it&amp;rsquo;s simply gratifying to witness the iPad regaining a sense of its
rhythm.&lt;/p&gt;
&lt;p&gt;Sent from my iPad&lt;/p&gt;</description><category>Apple</category><category>Misc</category></item><item><title>Why I think gear isn't the point</title><link>https://eliseomartelli.it/blog/2025-10-06-why-i-think-gear-isn-t-the-point/</link><pubDate>Mon, 06 Oct 2025 11:49:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-10-06-why-i-think-gear-isn-t-the-point/</guid><description>&lt;p&gt;Over the years I&amp;rsquo;ve been to plenty of photography exhibitions, from small local
artists to the big international names, and something that struck me was that
for every artist who brings genuine artistic weight and perspective, there&amp;rsquo;s a
flood of unauthentic work, that often feels copy-pasted.  
You get the same compositions, same edits, same color gradings, same gear, and
so on. Maybe beautiful, but, using Roland Barthes&amp;rsquo;s terms, that beauty that
only allows the viewer to celebrate the &lt;em&gt;Operator&lt;/em&gt; and a little bit of the
&lt;em&gt;Studium.&lt;/em&gt; But, in my opinion, missing the &lt;em&gt;Punctum.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The main problem is not people that take photos, that&amp;rsquo;s never the problem. The
problem is who curates them, and the aesthetic they decide to elevate.  Too
often I see curators having the &amp;ldquo;Instagram taste&amp;rdquo;: perfect symmetry, beautiful
painted-like skies, reflection so sharp they look AI generated. It&amp;rsquo;s
algorithmic pop. Polished and efficient, but completely empty.&lt;/p&gt;
&lt;p&gt;Last Saturday I went to see World Press Photo exhibition here in Turin, and I&amp;rsquo;m
afraid it didn&amp;rsquo;t hit me as hard as I hoped. Even though in theory it&amp;rsquo;s closer
to the kind of photography I appreciate: documentary, human, narrative-driven.
The exhibit felt a bit disconnected, with too many isolated pieces, and no
single thread tying them together.&lt;/p&gt;
&lt;p&gt;One thing that is true is that I tend to prefer exhibitions where a single
author or a clear concept is presented, I like to follow a line of thought, and
don&amp;rsquo;t jump between traumas and continents every two frames.&lt;/p&gt;
&lt;p&gt;Overall? A solid 6.5 out of 10 from me, and I don&amp;rsquo;t think I will go to see it
again in the coming months.&lt;/p&gt;
&lt;p&gt;My experience was diametrically opposed to the experience of my friend: she
loved the chaos, the abrupt shifts from one tragedy to another.&lt;/p&gt;
&lt;p&gt;She also mentioned something that struck me: she was disappointed there wasn&amp;rsquo;t
more information about the technical side of the photos. No mention of cameras
or lenses.&lt;br&gt;
She wanted to know what tools these photographers prefer and why.&lt;/p&gt;
&lt;p&gt;This is a point that I don&amp;rsquo;t understand, and we had a long discussion about it.&lt;/p&gt;
&lt;p&gt;I think that photography should never be associated to the equipment used
to take the picture; you don&amp;rsquo;t ask Picasso what kind of brush he used, or a
chef what brand of pan he uses. This is because I think that pushing pictures
and gear together passes the message that you need specific gear to create
similar work, and I think that&amp;rsquo;s a big trap.&lt;/p&gt;
&lt;p&gt;Her counter argument is that removing technical context can also strip the
human element, but I think that &lt;em&gt;the human element&lt;/em&gt; is in the result, not the
medium, and not in the relationship the artist has with the camera, the lens,
or any other gear.&lt;/p&gt;
&lt;p&gt;This is one of the reasons I cannot get in tune enough with landscape and
wildlife photography, or at least with the &amp;ldquo;pop&amp;rdquo; part of this genre.&lt;/p&gt;
&lt;p&gt;For me, it feels like that the majority of the work produced in this genre of
photography is 80% gear flex and 20% skill.&lt;br&gt;
Considering that a lot of these photos are made remotely, from pre-set camera
rigs, or with traps. The production quality is insane, sure, but where is the
story? Where&amp;rsquo;s the tension?&lt;/p&gt;
&lt;p&gt;She wasn&amp;rsquo;t having it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Go lie in the snow twelve hours a day for two weeks
to photograph an animal that appears twice a year, then talk to me about
skill.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;She&amp;rsquo;s absolutely right, patience &lt;em&gt;is&lt;/em&gt; a skill. Maybe I undervalue that because
I&amp;rsquo;m drawn more to narrative and emotion than to natural beauty for its own
sake.&lt;/p&gt;
&lt;p&gt;Our disagreement boiled down to something simple: I see photography
as a language, she sees it as a language and a craft intertwined.&lt;/p&gt;
&lt;p&gt;Both approaches are valid. One prioritizes meaning; the other respects the
process too.&lt;/p&gt;
&lt;p&gt;Maybe, if anything, what photography really teaches is this: we all look at the
same frame, but we don&amp;rsquo;t see the same thing.&lt;/p&gt;</description><category>Photography</category></item><item><title>Safari, don't move my fucking content</title><link>https://eliseomartelli.it/blog/2025-09-24-safari-don-t-move-my-fucking-content/</link><pubDate>Wed, 24 Sep 2025 17:15:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-09-24-safari-don-t-move-my-fucking-content/</guid><description>&lt;p&gt;Safari in macOS 26 has broken something fundamental: the stability of the
interface.&lt;/p&gt;
&lt;p&gt;Since 2021, Apple’s browser carried an understated feature IMHO: Compact Tabs.
 Even if they weren’t universally loved, they embodied an important principle
for me: technology staying out of the way.&lt;/p&gt;
&lt;p&gt;Compact Tabs kept Safari’s chrome minimal, they gave you “your content”,
without any of the fuss, and more importantly they followed these three
principles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They avoided layout shifts;&lt;/li&gt;
&lt;li&gt;They preserved spatial memory;&lt;/li&gt;
&lt;li&gt;They reserved space ahead of time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In macOS 26, Safari’s new chrome ignores these values. The interface is
consuming more vertical space than before.&lt;/p&gt;
&lt;p&gt;Opening a new tab with ⌘ + click is a fuck you to the user: the browser asserts
itself over the content, moving what you’re focused on. This is the opposite of
transparent computing.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/MQnAQhCJDCc?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;This matters because stability is trust. Apple walked away from one of the most
important design principles: interfaces should get out of the way.&lt;/p&gt;
&lt;p&gt;Even Firefox now does a better job at getting out of the way.&lt;/p&gt;</description><category>Apple</category><category>Misc</category></item><item><title>iPadOS 26 should've retained Slide Over and Split View</title><link>https://eliseomartelli.it/blog/2025-09-22-slide-over/</link><pubDate>Mon, 22 Sep 2025 11:59:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-09-22-slide-over/</guid><description>&lt;p&gt;With iPadOS 26, Apple has made the iPad more powerful than ever, but the cost of doing that was loosing access to Split View and Slide Over.&lt;/p&gt;
&lt;p&gt;For years, these gestures made it possible to use two apps side-by-side or to pull in a floating panel with virtually zero mental overhead.&lt;/p&gt;
&lt;p&gt;They were a fluid, lightweight and functional para-windowing model that worked really well in a touchscreen first device.&lt;/p&gt;
&lt;p&gt;Today, these features are gone from the latest iteration of iPadOS.&lt;/p&gt;
&lt;p&gt;Apple’s replacement is a full-blown windowing system. Apps now live inside resizable windows that float freely (or can be tiled together), a-la macOS. Apple adopted the iconic Traffic-light iconography from macOS, and last year tiling options. Stage Manager also remained as an option.&lt;/p&gt;
&lt;p&gt;It’s undeniable that today’s system is more flexible. The iPad can now approximate a laptop experience. But this flexibility comes at a cost.&lt;/p&gt;
&lt;p&gt;Slide Over. It was a fundamental experience of many that used the iPad.  It enabled fast, temporary multitasking, apps lived on the edge of the display, allowing us to peek, act and then dismiss.&lt;/p&gt;
&lt;p&gt;That workflow was instant, that simplicity was at home on a touchscreen device.&lt;/p&gt;
&lt;p&gt;Now, in order to replicate that behavior in iPadOS 26, you have to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open the dock;&lt;/li&gt;
&lt;li&gt;Drag an app into a new window;&lt;/li&gt;
&lt;li&gt;Resize it manually;&lt;/li&gt;
&lt;li&gt;Position it where you want;&lt;/li&gt;
&lt;li&gt;(and to recall it) Open back the dock or the Application Switcher and touch the app.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It works, but it’s not Slide Over.&lt;/p&gt;
&lt;p&gt;iPadOS 26 feels conflicted. It’s the most powerful version of this OS, but it’s also the most complicated. Multitasking has turned into a labyrinth of swipes that don’t feel native on a touchscreen device.&lt;/p&gt;
&lt;p&gt;Slide Over and Split View tamed this complexity by providing a narrow choice of options, choices that felt more opinionated. But, they made the iPad feel approachable, in contrast to iPadOS 26, that feels bolted with desktop paradigms.&lt;/p&gt;
&lt;p&gt;The iPad was never meant to be a Mac. Its magic was rethinking how computing could work on a slab of glass.&lt;/p&gt;
&lt;p&gt;Sent from my iPad&lt;/p&gt;</description><category>Apple</category><category>Misc</category></item><item><title>A short review of the new iPhones (2025)</title><link>https://eliseomartelli.it/blog/2025-09-19-a-short-review-of-the-new-iphones-2025/</link><pubDate>Fri, 19 Sep 2025 19:59:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-09-19-a-short-review-of-the-new-iphones-2025/</guid><description>&lt;p&gt;Hey, it&amp;rsquo;s been a while since I last wrote about Apple products, and the last
time I did, I was &lt;a href="https://eliseomartelli.it/blog/2025-03-02-apple-quality/"&gt;pretty critical&lt;/a&gt; of the
state of the software. Today, the software doesn&amp;rsquo;t seem to have improved much,
but as a nerd, I couldn&amp;rsquo;t resist getting my hands on the new iPhones, and doing a
little trip to the Apple Store.&lt;/p&gt;
&lt;p&gt;Today is the day of the launch of the new iPhones, and since I had a preorder
in for a family member, I offered myself to accompany them to the Apple Store
to get a change of experiening all the other products.&lt;/p&gt;
&lt;p&gt;The Apple Store in Via Roma, Turin was packed with influencers and videomakers,
trying to get the shots of the new iPhones with their DJI Osmo
something-something.&lt;/p&gt;
&lt;p&gt;I want to provide a short overview/review of all the models, since I had the
opportunity to try them all.&lt;/p&gt;
&lt;h2 id="iphone-17"&gt;iPhone 17&lt;/h2&gt;
&lt;p&gt;The iPhone 17 is the new entry-level model, and it comes in a variety of
colors, that loook like they were chosen because they were out of the paints
leftover from last year.&lt;/p&gt;
&lt;p&gt;Othen than that, the iPhone 17 is a solid phone, basically last year&amp;rsquo;s iPhone
but with better cameras, better screen (now with ProMotion) and the same
pricetag as last year.&lt;/p&gt;
&lt;p&gt;I think this is the only new iPhone that is worth considering.&lt;/p&gt;
&lt;h2 id="iphone-17-pro"&gt;iPhone 17 Pro&lt;/h2&gt;
&lt;p&gt;The iPhone 17 Pro is the new high-end model, and it comes with a all-new
aluminum body. The new aluminum body is a bit of a mixed bag: on one hand, it
is better thermally than the titanium of the previous models, but on the other
hand, it feels a bit cheaper.&lt;/p&gt;
&lt;p&gt;The cheapness is echoed by the Camera Plateau, which with the new material, is
subject to scratches and dents. The new color options are also a bit
questionable, I think I hate the orange one the most.&lt;/p&gt;
&lt;p&gt;I’m not convinced about the durability of the aluminum, considering that my
iPhone 16’s sides corroded because of my sweat (and I use my phone for 2 hours
a day max).&lt;/p&gt;
&lt;p&gt;The window glass on the back is also an element of concern. It&amp;rsquo;s not an elegant
iPhone.&lt;/p&gt;
&lt;h2 id="iphone-17-air"&gt;iPhone 17 Air&lt;/h2&gt;
&lt;p&gt;The iPhone 17 Air is the new mid-range model, and it comes with a new design
that is incredible.
It&amp;rsquo;s incredibly thin and light, and it feels like a piece of jewelry.&lt;/p&gt;
&lt;p&gt;The polished titanium rails are a fingerprint magnet, but they look
incredible when clean.
The light blue one is my favorite.&lt;/p&gt;
&lt;p&gt;The iPhone 17 Air is a beauty, but is full of compromises, like zero stereo
imaging, a smaller battery, and a single camera.&lt;/p&gt;
&lt;h2 id="apple-watch-series-11"&gt;Apple Watch Series 11&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s basically the same as the Series 10, but now with the possibility of 5G.&lt;/p&gt;
&lt;h2 id="apple-watch-ultra-3"&gt;Apple Watch Ultra 3&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s basically the same as the Ultra 2, but now with the possibility of 5G.&lt;/p&gt;
&lt;h2 id="apple-watch-se-3"&gt;Apple Watch SE 3&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s basically a Series 9 but without the ECG and the blood oxygen sensor.
It&amp;rsquo;s the one to get.&lt;/p&gt;
&lt;h2 id="airpods-pro-3"&gt;AirPods Pro 3&lt;/h2&gt;
&lt;p&gt;They sound great, but the case materials and the case’s lid that has a lot of
play (compared to my AirPods Pro (1st generation)) don’t convince me.&lt;/p&gt;
&lt;p&gt;In conclusion, I think this year Apple has made some questionable choices,
both in terms of design and materials, and in the way they are distributing
features across the lineup.&lt;/p&gt;
&lt;p&gt;This year I decided to not get any of the new iPhones, and I will stick
with my iPhone 16 Pro for another year.&lt;/p&gt;
&lt;p&gt;I think I will do a review of the new platform releaeses in the next weeks,
since I used for a big part of the summer the various betas.&lt;/p&gt;</description><category>Apple</category><category>Misc</category></item><item><title>Samsung makes intelligent phones for stupid people</title><link>https://eliseomartelli.it/blog/2025-09-06-samsung-makes-intelligent-phones-for-stupid-people/</link><pubDate>Sat, 06 Sep 2025 10:30:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-09-06-samsung-makes-intelligent-phones-for-stupid-people/</guid><description>&lt;p&gt;I wonder what&amp;rsquo;s up with the marketing team at Samsung. Lately I&amp;rsquo;ve seen some of
their ads popup on YouTube, I think they just released some phones, I don&amp;rsquo;t
closely pay attention to them as a company since I think their phones are
unusable, as of my recent experience with a Samsung Galaxy S25 Ultra, an A56
and an S24 Ultra.&lt;/p&gt;
&lt;p&gt;&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/KsQSrP0Rzn4?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/rh7LhYT3jiI?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/1WWk6hGnP4c?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t know what is the appeal for this kind of product, a product that
&amp;ldquo;probabilistically&amp;rdquo; gives you options from the real world, a product that
actively cuts you from human interaction, and a product that removes from you
spontaneous choices.&lt;/p&gt;
&lt;p&gt;Advertising a product that cuts you the reading, the thinking and ultimately
creativity is, in my opinion, harmful.&lt;/p&gt;
&lt;p&gt;I think that Samsung should focus more on their core experiences, and less on
bulking up their phones with features no one asked for.&lt;/p&gt;
&lt;p&gt;One example of their core interactions that went bad? Sharing a contact to
WhatsApp.&lt;/p&gt;
&lt;p&gt;I open the Phone app, select the contact I want to share, and tap on
Share.&lt;/p&gt;
&lt;p&gt;A prompt appears asking if I want to share as a VCF or as text, this should not
be on final software that goes in the hands of millions of users, I know what
VCF is because I’m a technical person, but imagine your grandma trying to
understand what VCF is.&lt;/p&gt;
&lt;p&gt;I choose VCF, and it takes me to another activity where I can select the fields
to include in the VCF. This activity isn&amp;rsquo;t part of the Phone/Contacts app.&lt;/p&gt;
&lt;p&gt;After selecting the fields, I proceed with sharing and choose WhatsApp. A toast
message pops up saying, &amp;ldquo;WhatsApp does not support this VCF file.&amp;rdquo; Meanwhile,
the activity that initiated the sharing has closed, dropping me back into the
Contacts app.&lt;/p&gt;
&lt;p&gt;I tap Share again, but this time it doesn&amp;rsquo;t ask me how I want to share the
contact. So, I close the app and try again. This time, I select &amp;ldquo;Text&amp;rdquo; in the
sharing menu, but I can&amp;rsquo;t find WhatsApp among the options, only apps like
Samsung Notes appear.&lt;/p&gt;
&lt;p&gt;Frustrated, I hit the back button, open the contact again, but accidentally tap
it the wrong way™️, triggering a call. I end the call, tap the contact again,
copy the number, and finally paste it manually into WhatsApp.&lt;/p&gt;
&lt;p&gt;How&amp;rsquo;s that a good experience?&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/j0m3sPU8sVU?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;</description><category>Misc</category></item><item><title>LensRenamer</title><link>https://eliseomartelli.it/blog/2025-07-21-lensrenamer/</link><pubDate>Mon, 21 Jul 2025 15:09:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-07-21-lensrenamer/</guid><description>&lt;p&gt;Anecdote time. I recently bought a Leica, and suddenly my perfectly organized
photo library became a guessing game. Why? Because manual and vintage lenses
don’t talk to the camera, so EXIF is incomplete—or just plain missing. Which
Summicron? Was that the Brightin Star or the Zeiss? What focal length?&lt;/p&gt;
&lt;p&gt;So, I built &lt;strong&gt;LensRenamer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;LensRenamer is a macOS app that batch-writes lens metadata—make, model, serial,
focal length—directly into your image files. If you use manual or adapted
glass, especially on a Leica, you know why this matters. Your cataloging tools
depend on that EXIF, and missing data can turn a simple search into a rabbit
hole.&lt;/p&gt;
&lt;h2 id="features"&gt;Features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Drop your images, select your lens, and go.&lt;/li&gt;
&lt;li&gt;Build your own collection of lens profiles, ready to stamp onto any batch.&lt;/li&gt;
&lt;li&gt;No extra installs. No Terminal dance.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="requirements"&gt;Requirements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;macOS (Intel or Apple Silicon)&lt;/li&gt;
&lt;li&gt;Nothing else—ExifTool is bundled&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="get-it"&gt;Get It&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Download: &lt;a href="https://github.com/eliseomartelli/LensRenamer/releases"&gt;Latest
Release&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><category>Misc</category></item><item><title>How Last Week Went: Computers Are Your Friends*</title><link>https://eliseomartelli.it/blog/2025-04-18-last-week/</link><pubDate>Fri, 18 Apr 2025 13:58:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-04-18-last-week/</guid><description>&lt;p&gt;At the start of the week, I added a new phrase to my vocabulary: &amp;ldquo;Sgnacca il
butun,&amp;rdquo; a quirky Piemontese expression meaning &amp;ldquo;push the button.&amp;rdquo;  &lt;/p&gt;
&lt;p&gt;I had a meeting Maria from work after four long months. It&amp;rsquo;s fascinating how
time apart makes reunions feel both familiar and fresh. I can&amp;rsquo;t wait to
&amp;ldquo;chiamare una giornata&amp;rdquo; (literally, calling it a day) with her.&lt;/p&gt;
&lt;p&gt;Later that day, I packed up for a trip back to Abruzzo, to embrace the Easter
festivities. The journey was a blast, literally. My brother and I played music
non-stop for seven hours.&lt;/p&gt;
&lt;p&gt;Just after I got home and checked my blog, I began contemplating the need to
implement pagination. With nearly 50 articles under my belt, it felt like the
right time to enhance navigation.&lt;/p&gt;
&lt;p&gt;On the tech troubleshooting front, on Monday, a Studio Display at work decided
to throw a tantrum, refusing to show any image. After scouring threads &lt;a href="https://x.com/_inside/status/1506048543244627978"&gt;like
this one&lt;/a&gt;, I found a simple
solution (I was already excited about having to do a DFU restore of a Studio
Display). An age-old Computer Science 101 trick worked fine: disconnect all the
cables and reboot.&lt;/p&gt;
&lt;p&gt;Similarly, my parents&amp;rsquo; MacMini7,1 gave me a hard time, crashing repeatedly
between boot and login. Despite attempts with recovery mode, &lt;a href="https://eliseomartelli.it/blog/2025-04-17-oclp-failure/"&gt;progress was
minimal&lt;/a&gt;. Ah, the joys of technology.&lt;/p&gt;
&lt;p&gt;My car also got a little care at the body shop after a kid bumped its scooter
on my fender, while I was waiting for my car back, I posted the review of
&lt;a href="https://eliseomartelli.it/blog/2025-04-16-laserwriter-ii/"&gt;Tamara Shopsin&amp;rsquo;s &amp;ldquo;LaserWriter II&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I took a peaceful stroll in Ortona, heading towards the Castello Aragonese, and
I thoroughly enjoyed capturing the interplay of light and ancient stone.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/2025-04-18-lastweek/aragonese.jpg" alt="Reflections in Castello Aragonese in
Ortona"&gt;&lt;/p&gt;
&lt;p&gt;On Holy Thursday, I attended the traditional procession of hooded penitents in
Lanciano to take some photos. The participants, walked solemnly through the
narrow streets of the historic center, accompanied by drums and prayers.&lt;/p&gt;
&lt;div class="horizontal-carousel" style="display: flex; overflow-x: auto; gap: 1rem; padding: 1rem 0; scroll-snap-type: x mandatory; scrollbar-width: thin; -webkit-overflow-scrolling: touch;"&gt;
&lt;div class="carousel-slide" style="flex: 0 0 80%; scroll-snap-align: start; max-width: 500px;"&gt;
&lt;img src="https://eliseomartelli.it/posts/2025-04-18-lastweek/a.jpg" alt="Carousel Image" style="width: 100%; border-radius: 8px; object-fit: cover; display: block;" /&gt;
&lt;/div&gt;
&lt;div class="carousel-slide" style="flex: 0 0 80%; scroll-snap-align: start; max-width: 500px;"&gt;
&lt;img src="https://eliseomartelli.it/posts/2025-04-18-lastweek/b.jpg" alt="Carousel Image" style="width: 100%; border-radius: 8px; object-fit: cover; display: block;" /&gt;
&lt;/div&gt;
&lt;div class="carousel-slide" style="flex: 0 0 80%; scroll-snap-align: start; max-width: 500px;"&gt;
&lt;img src="https://eliseomartelli.it/posts/2025-04-18-lastweek/c.jpg" alt="Carousel Image" style="width: 100%; border-radius: 8px; object-fit: cover; display: block;" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Oh, I also have some future projects: my mother made some sourdough starter
(that I will gladly take). When I&amp;rsquo;ll be back in Turin, I&amp;rsquo;ll have some new
baking experiments to do!&lt;/p&gt;</description><category>Short</category><category>Misc</category></item><item><title>OCLP on my parent's Mac mini: a failure story</title><link>https://eliseomartelli.it/blog/2025-04-17-oclp-failure/</link><pubDate>Thu, 17 Apr 2025 17:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-04-17-oclp-failure/</guid><description>&lt;p&gt;I returned home for the holidays a few days ago and decided to update my
parents&amp;rsquo; Mac mini, which was stuck on macOS 12 Monterey, to macOS Sequoia using
Open Core Legacy Patcher (having successfully done so on other Macs).&lt;/p&gt;
&lt;p&gt;Unfortunately, yesterday, I made a foolish decision to update to macOS 15.4.1
via OTA. After the usual reboots, I was unable to get past the login screen on
the Mac mini because it crashed every time I tried to enter the password.&lt;/p&gt;
&lt;p&gt;After some troubleshooting steps and reading the helpful and well-detailed
Dortania&amp;rsquo;s Open Core guide, I booted into recovery mode, mounted the EFI
partition, and began editing the Open Core&amp;rsquo;s config.plist. I first added the
argument &lt;code&gt;-igfxvesa&lt;/code&gt; to the boot args because I noticed that on the login
window, transparencies were not working, so I thought it might be an issue with
the iGPU.&lt;/p&gt;
&lt;p&gt;I booted back into recovery mode and tried &lt;code&gt;resetpassword&lt;/code&gt; to set a shorter
password to at least attempt to gain access during the brief flashes of the
login screen. However, this didn&amp;rsquo;t work either.&lt;/p&gt;
&lt;p&gt;After some Google searches, I found this post &lt;a href="https://www.reddit.com/r/MacOS/comments/112qu36/macbook_pro_2019_16_safe_mode_login_screen_loop/"&gt;from the MacOS community on
Reddit&lt;/a&gt;,
that made me recall that my mother&amp;rsquo;s profile picture is a Memoji, everything
suddenly &lt;em&gt;made sense&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;After hours of trial and error, countless reboots, and an ever-growing sense of
despair, I finally admitted defeat. As I type this, I&amp;rsquo;m reinstalling macOS
Monterey, defeated, because I was unable to change my mother&amp;rsquo;s profile picture.&lt;/p&gt;</description><category>Apple</category><category>Short</category></item><item><title>Book Review: LaserWriter II</title><link>https://eliseomartelli.it/blog/2025-04-16-laserwriter-ii/</link><pubDate>Wed, 16 Apr 2025 20:50:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-04-16-laserwriter-ii/</guid><description>&lt;p&gt;I&amp;rsquo;ve finally finished reading Tamara Shopsin&amp;rsquo;s LaserWriter II after a reading
block that lasted for about two months, and it made me do some reflections on
what is happening with consumer electronics. This book is a love letter to a
specific moment in tech history, shaped by Apple not just with products, but
with culture.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Apple didn&amp;rsquo;t make the first consumer laser printer; they made the fourth,
after IBM, Xerox, and HP. The LaserWriter used the same exact Canon engine as
the HP LaserJet, but the LaserWriter was better.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Apple understood something deeper: how to present technology in a way that
people could actually fall in love with.&lt;/p&gt;
&lt;p&gt;That idea feels especially relevant today. Tim Cook, in an interview last year,
stated that Apple is not the first company to race to AI, he said Apple will be
the one that implements it the best for consumers. This statement should still
be relevant today, but we have yet to see the true advantages of Apple
Intelligence.&lt;/p&gt;
&lt;p&gt;It surprised me how many of the book&amp;rsquo;s lines stuck with me. Here are a few
favorites: &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you are ever in doubt, do the right thing&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Courage is as contagious as fear.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Choosing NOT to do something is as much choosing to DO something. Free will
is not just what you do, it is also what you don&amp;rsquo;t.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It was a good read. I enjoyed it, even though some parts, like the
personification of printers&amp;rsquo; internals, were a bit over the top.&lt;br&gt;
The book was also filled with names, but with not so much space to let them
&amp;ldquo;breathe&amp;rdquo;, so not every personality shone through.&lt;/p&gt;
&lt;p&gt;Still, LaserWriter II captures something special: a moment when machines
weren&amp;rsquo;t just tools, but extensions of care, obsession, and idealism.&lt;br&gt;
Can Apple still do what it once did best?&lt;/p&gt;
&lt;p&gt;Oh, and one of the last phrases in the acknowledgments?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Thanks to Apple for making computers that I love to an illogical degree.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="product-card" style="border: 1px solid #ddd; border-radius: 8px; padding: 1rem; margin: 1.5rem 0; display: flex; flex-direction: column; gap: 0.75rem;"&gt;
&lt;div style="display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap;"&gt;
&lt;h4 style="margin: 0; display: flex; align-items: center; gap: 0.5rem;"&gt;
LaserWriter II
&lt;/h4&gt;
&lt;a href="https://www.goodreads.com/book/show/56269270-laserwriter-ii" class="btn-primary" target="_blank" rel="noopener" style="padding: 0.4rem 0.8rem; border-radius: 4px; background: var(--link-color, #a81d27); color: #fff; text-decoration: none; font-weight: bold; font-size: 0.9rem;"&gt;
GoodReads
&lt;/a&gt;
&lt;/div&gt;
&lt;div style="font-size: 0.95rem; opacity: 0.9; line-height: 1.4;"&gt;
&lt;p&gt;LaserWriter II is a coming-of-age tale set in the legendary 90s indie NYC Mac
repair shop TekServe―a voyage back in time to when the internet was new, when
New York City was gritty, and when Apple made off-beat computers for weirdos.
Our guide is Claire, a 19-year-old who barely speaks to her bohemian
co-workers, but knows when it’s time to snap on an antistatic bracelet.&lt;/p&gt;
&lt;p&gt;Tamara Shopsin brings us a classically New York novel that couldn’t feel more
timely. Interweaving the history of digital technology with a tale both
touchingly human and delightfully technical, Shopsin brings an idiosyncratic
cast of characters to life with a light touch, a sharp eye, and an unmistakable
voice.&lt;/p&gt;
&lt;p&gt;Filled with pixelated philosophy and lots of printers, LaserWriter II is, at
its heart, a parable about an apple.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>Misc</category></item><item><title>Notes from a week of presence</title><link>https://eliseomartelli.it/blog/2025-04-14-updates-from-last-week/</link><pubDate>Mon, 14 Apr 2025 09:50:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-04-14-updates-from-last-week/</guid><description>&lt;p&gt;Since I started browsing &lt;a href="https://minifeed.net"&gt;minifeed.net&lt;/a&gt; more frequently,
I&amp;rsquo;ve found myself reading more and more articles and subscribing to more RSS
feeds, this also makes me feel more connected to the indie web.&lt;/p&gt;
&lt;p&gt;Today, I came across this excerpt from
&lt;a href="https://thejaymo.net/2025/04/10/marking-the-occasion-100425/"&gt;thejaymo.net&lt;/a&gt;&amp;rsquo;s
blog that truly resonates with me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A blog in 2025 should be a destination. They don&amp;rsquo;t just distribute — they
accumulate. They hold time, build presence, and concretise.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This resonates with me at an opportune moment, as I&amp;rsquo;ve been writing more
frequently and gradually overcoming my fear of expressing my thoughts in
public.&lt;/p&gt;
&lt;p&gt;Cesare Nai — a friend of mine currently working on
&lt;a href="https://miraggiomag.substack.com"&gt;Miraggio&lt;/a&gt;, a blog/newsletter
built around interviews with photographers — and I had a conversation last
Saturday. Despite the 100 kilometers between us, we simultaneously realized
that our pursuit of perfection in every area of life has been chipping away at
the joy of simply doing things.&lt;/p&gt;
&lt;p&gt;While talking with my friend Elisa, I admitted that I struggle with dating
apps. Not because I&amp;rsquo;m unwilling to try — but mostly because I&amp;rsquo;m &amp;ldquo;me&amp;rdquo;. I have my
quirks, and I&amp;rsquo;m not convinced six pictures and three prompts are enough to
convey them. That&amp;rsquo;s probably why those platforms don&amp;rsquo;t really work for me.&lt;/p&gt;
&lt;p&gt;Still, I&amp;rsquo;m in a phase where I want to meet more people, so I&amp;rsquo;m leaving that
door open for now.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t know if I will close this experiment soon, since I&amp;rsquo;m currently in the
mood to meet more people.&lt;/p&gt;
&lt;p&gt;I finished reading Tamara Shopsin&amp;rsquo;s &amp;ldquo;LaserWriter II&amp;rdquo;, and I&amp;rsquo;m collecting some
highlights from the book to write about it (it helps me to be mindful when
reading).&lt;/p&gt;
&lt;p&gt;I also bought a new photo book in a street market titled &amp;ldquo;Il Fotogiornalismo in
Italia - Linee di Tendenza e Percorsi.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/2025-04-14-updates-from-last-week/gonin.jpeg" alt="A picture of Sala Gonin"&gt;&lt;/p&gt;
&lt;p&gt;Over the weekend, I visited Sala Gonin, a Baroque architectural room located
on the ground floor of the Torino Porta Nuova station. Once used as a waiting
room for the sovereigns and the Savoy court. The frescoes in the room symbolize
the three essential elements required for the functioning of a steam engine:
fire, water, and coal.&lt;/p&gt;
&lt;p&gt;I also watched the F1 race this Sunday. I particularly enjoyed the duel between
Charles Leclerc and Lando Norris. Penny is the reason I&amp;rsquo;ve been getting back
into Formula 1. This was only the second race I’ve watched this season. The
spark was reignited last September, on my birthday, during the 2024 Italian
Grand Prix. As an Italian, I&amp;rsquo;m contractually obligated to be a Ferrari fan.&lt;/p&gt;
&lt;p&gt;To wrap up the week, yesterday I went to take a beer with
&lt;a href="https://nomnp.com"&gt;Francesco&lt;/a&gt;, and we watched the compilation of a C++
codebase (yes, really). I also tried &amp;ldquo;Punt e Mes&amp;rdquo; for the first time, and I
swear I taste sour cherries in it.&lt;/p&gt;
&lt;p&gt;Here are some reflections from the past week:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://eliseomartelli.it/blog/2025-04-09-llms/"&gt;Are we loosing the ability to do research?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eliseomartelli.it/blog/2025-04-10-wantboredom/"&gt;I think I need to be bored&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eliseomartelli.it/blog/2025-04-11-carlostsoul/"&gt;Cars are extremely boring now&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</description><category>Misc</category><category>Short</category></item><item><title>I can't fall in love with cars anymore</title><link>https://eliseomartelli.it/blog/2025-04-11-carlostsoul/</link><pubDate>Fri, 11 Apr 2025 15:15:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-04-11-carlostsoul/</guid><description>&lt;p&gt;Growing up, I was mesmerized by Top Gear and later by The Grand Tour, hosted by
Jeremy Clarkson, James May, and Richard Hammond. The show&amp;rsquo;s final episode,
titled &amp;ldquo;One for the Road,&amp;rdquo; left an indelible mark on me, especially with dire
straits&amp;rsquo; &amp;ldquo;Brothers in Arms&amp;rdquo; playing in the background. It felt like the end of
an era.&lt;/p&gt;
&lt;iframe
allow="autoplay *; encrypted-media *;"
height="150"
class="w-full max-w-2xl overflow-hidden bg-transparent"
sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation"
src="https://embed.music.apple.com/it/album/brothers-in-arms-remastered-1996/1565391787?i=1565392081"
/&gt;
&lt;p&gt;Today, during a conversation with my friend Roberto, I realized that I might
not purchase another car if electric vehicles (EVs) become the sole option
available. Over the past 15 years, EVs, SUVs and crossovers have gained
significant traction on Italian roads, transforming the automotive landscape in
Italy.&lt;/p&gt;
&lt;p&gt;One notable shift is the decline of traditional car ownership. Instead,
short-term leasing and subscription models have become more prevalent. This
trend accelerated with the entry of Chinese EV manufacturers into the European
market, offering affordable and technologically advanced vehicles.
Consequently, consumers are increasingly drawn to convenience and flexibility
rather than long-term commitment.&lt;/p&gt;
&lt;p&gt;From my perspective in computer science, this shift reminds me of the &amp;ldquo;Cattle
vs. Pets&amp;rdquo; model. In IT, &amp;ldquo;pets&amp;rdquo; are unique servers that require individual
attention, while &amp;ldquo;cattle&amp;rdquo; are standardized and easily replaceable. Similarly,
cars have transitioned from cherished possessions to interchangeable tools.&lt;/p&gt;
&lt;p&gt;As someone who has always been passionate about cars, this change is
disheartening. The emotional connection and sense of identity that once came
with car ownership are fading. While modern vehicles are efficient and
environmentally friendly, they often lack the character and individuality that
made driving a thrilling experience.&lt;/p&gt;
&lt;p&gt;The sense of loss for those who view cars as more than just transportation is
growing, and as a car enthusiast, I find it increasingly challenging to fall in
love with a modern car.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/dmyEe2Iw4dk?start=840?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;</description><category>Misc</category><category>Short</category></item><item><title>I want to be bored</title><link>https://eliseomartelli.it/blog/2025-04-10-wantboredom/</link><pubDate>Thu, 10 Apr 2025 12:30:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-04-10-wantboredom/</guid><description>&lt;p&gt;One of the things I detest the most in the world is the feeling of wasting time
doing nothing. For this very reason, I can’t function without a task list
(let’s be honest, multiple task lists) and a tightly booked calendar.&lt;br&gt;
But I think this is a fragile equilibrium. It feels like walking on a tense
cotton yard. You know it will break, but in the meantime you feel comforted by
the sense of tranquillity only cotton could give you.&lt;/p&gt;
&lt;p&gt;Am I living in autopilot?&lt;/p&gt;
&lt;p&gt;Sometimes I wish I could be more spontaneous.&lt;br&gt;
Some weeks ago, I received a
message from &lt;a href="https://nomnp.com"&gt;Francesco&lt;/a&gt; about some of the rules he
implemented in his life:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Limit social media to 30 minutes per day;&lt;/li&gt;
&lt;li&gt;Dedicate at least 30 minutes per day to reading and take notes;&lt;/li&gt;
&lt;li&gt;Reduce caffeine intake.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While I’ve already implemented some of them, such as reducing caffeine
consumption and removing social media from my phone (I still check them on my
iPad or Mac, but I have a physical barrier between me and endless mindless
scrolling), not everything is perfect.&lt;/p&gt;
&lt;p&gt;I realize that I need to simplify my life and embrace boredom. I believe that
boredom can stimulate creativity.&lt;/p&gt;</description><category>Misc</category><category>Short</category></item><item><title>Is your first instinct asking an LLM?</title><link>https://eliseomartelli.it/blog/2025-04-09-llms/</link><pubDate>Wed, 09 Apr 2025 20:50:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-04-09-llms/</guid><description>&lt;p&gt;Suggested video:&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/QEJpZjg8GuA?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;It appears as thought an increasing number of people seem to operate in the
world without realizing these are things they can do themselves. And it really
concerns me.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You’re sitting in front of your computer, the terminal emulator blinking with
the cursor. You’re reading:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Bla bla bla,
Error: something needs something_else version 1234.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Every answer you need is right there on your screen. What’s should be your
first instinct? Ask an LLM?&lt;/p&gt;
&lt;p&gt;This is to be intended as a continuation to this previous article: &lt;a href="https://eliseomartelli.it/blog/2025-03-27-critical-thinking/"&gt;The Death of Critical Thinking&lt;/a&gt;.&lt;/p&gt;</description><category>Misc</category><category>Short</category></item><item><title>What I’m up to?</title><link>https://eliseomartelli.it/blog/2025-04-08-weekrecap/</link><pubDate>Tue, 08 Apr 2025 12:30:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-04-08-weekrecap/</guid><description>&lt;p&gt;Over the past week, I&amp;rsquo;ve been making an effort to strike a balance between my
digital and analog life.&lt;/p&gt;
&lt;p&gt;My goals were to spend more time outdoors, overcome a reading slump, and focus
on a project that brings me value.&lt;/p&gt;
&lt;p&gt;With the beautiful weather in Turin last week, I decided to take more frequent
breaks from screens and return to street photography.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s been challenging to &lt;em&gt;care&lt;/em&gt; about the pictures I&amp;rsquo;m taking, but I&amp;rsquo;m trying
to be patient with myself. I know that with time, I’ll rediscover my
appreciation for the process.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve started reading Tamara Shopsin&amp;rsquo;s &amp;ldquo;LaserWriter II,&amp;rdquo; which, as an Apple fan,
I&amp;rsquo;m really enjoying. It&amp;rsquo;s a nostalgic tribute to the Apple culture of the late
90s and early 2000s, and I&amp;rsquo;m thoroughly absorbed in it. (I&amp;rsquo;m also considering
writing a review of this book.)&lt;/p&gt;
&lt;p&gt;At work, I&amp;rsquo;ve been developing a domain-specific language using Lisp&amp;rsquo;s
S-Expressions. This project is allowing me to explore language design, and it&amp;rsquo;s
a breath of fresh air, since my last project was focused on a deployment of a
bioinformatics-centered analysis platform.&lt;/p&gt;
&lt;p&gt;My recent days have been accompanied by the music of Four Tet and Burial. Their
minimalist and repetitive soundscapes help me enter focused states, as they
effectively silence all background noise in the office environment.&lt;/p&gt;
&lt;p&gt;When I&amp;rsquo;m listening to music for leisure, I&amp;rsquo;m enjoying 80s and 90s pop and rock
songs. As I write this piece, I&amp;rsquo;m listening to Christina Aguilera’s &amp;ldquo;Genie in a
Bottle.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also been experimenting with a gluten-free bun recipe. After several
attempts, I&amp;rsquo;ve finally achieved a result I&amp;rsquo;m satisfied with. I&amp;rsquo;m currently
aiming for a 110% hydration level.&lt;/p&gt;
&lt;p&gt;Last week, two close friends graduated from university, and we had a delightful
outdoor lunch in a nearby town. We laughed a lot and indulged in some tasty
food (more than I&amp;rsquo;d like to admit.)&lt;/p&gt;
&lt;p&gt;In addition to these activities, I&amp;rsquo;ve been dealing with some car issues. The
back driver-side door&amp;rsquo;s strip and the drain holes in the same door aren&amp;rsquo;t
working as expected, and I&amp;rsquo;m still having trouble with the speaker every time
it rains.&lt;/p&gt;</description><category>Misc</category><category>Short</category></item><item><title>The Death of Critical Thinking</title><link>https://eliseomartelli.it/blog/2025-03-27-critical-thinking/</link><pubDate>Thu, 27 Mar 2025 17:30:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-03-27-critical-thinking/</guid><description>&lt;p&gt;I often find myself feeling detached from reality when I’m surrounded by
superficial conversations. It’s not just boredom or a lack of interest. I
experience a genuine mental disconnection, as if my brain refuses to process
information it perceives as empty. This disengagement isn’t about feeling
superior, it’s about feeling disconnected. When discussions lack critical
thinking, logic, or even minimal reflection, they feel less like conversations
and more like noise.&lt;/p&gt;
&lt;p&gt;This effect doesn’t happen only on small talk. Even discussions that should be
interesting, such as tech, philosophy, or culture, can become hollow when
they’re reduced to slogans, blind allegiance, or knee-jerk reactions.&lt;/p&gt;
&lt;p&gt;I believe there’s a profound difference between not knowing something and not
caring to know. In today’s society, people seem to settle too quickly,
satisfied with surface-level knowledge. The habit of questioning, doubting, and
pushing deeper is disappearing.&lt;/p&gt;
&lt;p&gt;I think this is symptomatic of an active disengagement from critical thought.
When faced with new information, too many people reject it outright rather than
interrogating it. They don’t ask: “Is what I’m hearing or reading true?”
“What’s the reasoning behind it?” “Could my previous assumption be wrong?”
Instead, many people have a visceral, emotional, defensive, and ultimately
dismissing reaction, shutting down the possibility of learning (or even
teaching).&lt;/p&gt;
&lt;p&gt;This isn’t just about online debates, it’s about a cultural shift. To this day,
I have low expectations for people to engage meaningfully with bigger, more
complex issues. Many areas of our lives, such as politics, technology, and
ethics, aren’t black and white. However, when people treat conversations as
battles, meaningful discourse dies.&lt;/p&gt;
&lt;p&gt;The answer to the previous three questions is skepticism, curiosity, and
humility.&lt;/p&gt;
&lt;p&gt;Part of the problem is that we’ve built environments that reward certainty over
curiosity. Social media algorithms feed us content that confirms what we
already believe, news sources cater to specific ideological perspectives, and
even our friend groups often form around shared beliefs. When everyone around
you agrees with you, you begin to mistake opinion for fact and preference for
truth.&lt;/p&gt;</description><category>Misc</category></item><item><title>ML model to guess WWDC dates</title><link>https://eliseomartelli.it/blog/2025-03-24-wwdc-prediction/</link><pubDate>Mon, 24 Mar 2025 17:30:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-03-24-wwdc-prediction/</guid><description>&lt;h2 id="update-march-25-2025"&gt;Update: March 25, 2025&lt;/h2&gt;
&lt;p&gt;Guess what? Apple just dropped the news about the conference! Check it out:
&lt;a href="https://www.apple.com/newsroom/2025/03/apples-worldwide-developers-conference-returns-the-week-of-june-9/"&gt;Apple actually announced the
conference!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Oh, and by the way, the predicted announcement date was off by a week.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Hello my fellow Apple nerds, or, as Tim Cook would&amp;rsquo;ve said: &amp;ldquo;Good Morning.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Today, I&amp;rsquo;m proud to present my completely unnecessary and unasked-for adventure
into predicting Apple&amp;rsquo;s WWDC dates using machine learning. Because nothing
says &lt;em&gt;&amp;ldquo;I have free time&amp;rdquo;&lt;/em&gt; like building a RandomForest model to guess when Tim
Cook will send out an email invitation.&lt;/p&gt;
&lt;p&gt;Apple&amp;rsquo;s WWDC is like Christmas for developers—if Christmas moved around
slightly each year and required a ticket.&lt;/p&gt;
&lt;p&gt;After checking out &lt;a href="https://mastodon.social/@_Davidsmith/114218113961193279"&gt;this
post&lt;/a&gt; where David
Smith compiled a list of WWDC dates for past years, I thought:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Why not use machine learning to predict the next one?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, I casually imported half the Python data science ecosystem and started
working on this &lt;em&gt;marvelous&lt;/em&gt; task.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-py" data-lang="py"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;import&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;pandas&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;as&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;pd&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;import&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;numpy&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;as&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;np&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;from&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;datetime&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;import&lt;/span&gt; datetime, timedelta
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;from&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;sklearn.ensemble&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;import&lt;/span&gt; RandomForestRegressor
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;from&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;sklearn.linear_model&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;import&lt;/span&gt; Ridge
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;from&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;sklearn.model_selection&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;import&lt;/span&gt; cross_val_score, LeaveOneOut
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;m using a whopping 21 data points to train my ML models. Data scientists
worldwide are collectively facepalming. But hey, it&amp;rsquo;s all the WWDC data we
have!&lt;/p&gt;
&lt;p&gt;With this &lt;em&gt;massive&lt;/em&gt; dataset, I decided on using Leave-One-Out Cross Validation
which trains on (N-1) samples and tests on the remaining one, repeating for all
points. It&amp;rsquo;s computationally intensive but maximizes the training data for
small datasets.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m training two different models (RandomForest and Ridge) for each target
variable (day, month, lead time, weekday) and picking the winner based on Mean
Absolute Error scores. It&amp;rsquo;s like Masterchef, but for regression models. The
results? RandomForest dominated in most categories:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Day - ridge: MAE = 4.67 (LOSER)
Month - rf: MAE = 0.20 (WINNER)
Lead_time - rf: MAE = 13.75 (WINNER)
Weekday - rf: MAE = -0.00 (TIE)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since WWDC keynotes always happen on Mondays, I adjusted my predictions to
prevent ridiculous results like “Sunday WWDC”, which would make Apple fans
faint.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-py" data-lang="py"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;weekday_diff &lt;span style="color:#666"&gt;=&lt;/span&gt; (target_weekday &lt;span style="color:#666"&gt;-&lt;/span&gt; temp_date&lt;span style="color:#666"&gt;.&lt;/span&gt;weekday()) &lt;span style="color:#666"&gt;%&lt;/span&gt; &lt;span style="color:#40a070"&gt;7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;if&lt;/span&gt; weekday_diff &lt;span style="color:#666"&gt;!=&lt;/span&gt; &lt;span style="color:#40a070"&gt;0&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt; temp_date &lt;span style="color:#666"&gt;+=&lt;/span&gt; timedelta(days&lt;span style="color:#666"&gt;=&lt;/span&gt;weekday_diff)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, the model has spoken! WWDC 2025 is predicted to be on Monday, June 09,
2025, with the announcement coming on Tuesday, April 01, 2025!&lt;/p&gt;
&lt;p&gt;Lead time: 69 days between announcement and event. Nice.&lt;/p&gt;
&lt;p&gt;Stay tuned for my next project, where I&amp;rsquo;ll use a transformer neural network to
predict what color Tim Cook&amp;rsquo;s sweater will be during the keynote! (Spoiler
alert: it&amp;rsquo;s blue or grey with 94.3% confidence).&lt;/p&gt;
&lt;p&gt;Have a look at the code for yourself
&lt;a href="https://gist.github.com/eliseomartelli/58d288e41286b35198a91c37d54fa146"&gt;here&lt;/a&gt;&lt;/p&gt;</description><category>Apple</category><category>Misc</category></item><item><title>I’m Regretting Switching MNO</title><link>https://eliseomartelli.it/blog/2025-03-20-switchmno/</link><pubDate>Thu, 20 Mar 2025 13:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-03-20-switchmno/</guid><description>&lt;p&gt;My recent decision to switch to TIM, one of Italy’s
&lt;a href="https://www.mondomobileweb.it/293381-telefonia-mobile-quote-di-mercato-a-settembre-2024-fastweb-supera-il-5-di-sim-human/"&gt;largest&lt;/a&gt;
telecom providers, turned into a frustrating experience filled with technical
glitches, hidden fees, and customer service challenges. If you’re considering
TIM, you might want to think twice.&lt;/p&gt;
&lt;p&gt;The TIM app has been problematic. It inexplicably logs me out constantly, even
mid-task, without any error message or explanation. This issue has also been
reported by &lt;a href="https://community.tim.it/argomenti/app/mytim/detail/130004"&gt;other
users&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While the mobile plan I subscribed to is competitively priced at €6.99/month
for 150GB of data, unlimited calls, and 200 SMS, I’ve encountered numerous
hidden fees that weren’t present with my previous provider, Iliad. The top
three annoyances are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SMS to Amazon Drivers: Charged €0.10 per message.&lt;/li&gt;
&lt;li&gt;iMessage Activation: Each activation costs €0.13, which adds up since I
restore my devices every couple of months.&lt;/li&gt;
&lt;li&gt;Bank Messages: Priced at €0.16 per message.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These micro-charges feel like a bait-and-switch. While the amounts are small,
they’re not mentioned upfront and diminish trust in the provider’s
transparency.&lt;/p&gt;
&lt;p&gt;The final straw was TIM’s handling of eSIMs. I opted for an eSIM to avoid
waiting for a physical card. The first eSIM email never arrived. Support was
unhelpful and advised me to “try again later.” I then purchased another eSIM
for €10 and was promised a refund for the first. The refund took over a month
to process, involving endless back-and-forth emails with a tone-deaf refund
department. This experience mirrors that of other users who have faced similar
challenges with TIM’s eSIM services.&lt;/p&gt;
&lt;p&gt;Why should customers pay twice for a service that TIM failed to deliver? The
lack of accountability is staggering. Instead of troubleshooting, they shifted
the problem onto me.&lt;/p&gt;
&lt;p&gt;My experience with TIM has been plagued with issues, from app malfunctions and
hidden fees to poor customer support. I can’t wait to switch back to Iliad.&lt;/p&gt;</description><category>Short</category><category>Misc</category></item><item><title>One Week with an iPad 6th Gen</title><link>https://eliseomartelli.it/blog/2025-03-19-ipad-downgrade/</link><pubDate>Wed, 19 Mar 2025 13:45:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-03-19-ipad-downgrade/</guid><description>&lt;p&gt;We often assume newer is always better when it comes to technology. The
marketing, the launch events, and the constant stream of upgrades all reinforce
this notion. But is it always true?&lt;/p&gt;
&lt;p&gt;For the last week, I decided to challenge this assumption by taking a step back
in time. I downgraded from my 2024 iPad Air M2 to an iPad 6th Generation from
2018. This is my assessment of how it feels to take this step backward.&lt;/p&gt;
&lt;p&gt;The Apple Pencil squeeze and double-tap features had become part of my workflow
on the newer iPad. These gestures enhanced my productivity and allowed me to
quickly switch between tools with minimal interruption. On the 6th gen iPad,
these controls are absent, requiring manual tool switching.&lt;br&gt;
Perhaps more noticeably, the convenience of magnetically attaching the Apple
Pencil to charge can&amp;rsquo;t be overstated. Having to plug the 1st generation Pencil
awkwardly into the Lightning port feels precarious.&lt;/p&gt;
&lt;p&gt;One of the most immediately noticeable differences is the non-laminated display
on the iPad 6th gen. This creates a visible gap between the glass and the
screen that makes the Apple Pencil feel less precise.&lt;/p&gt;
&lt;p&gt;This fuels a disconnected sensation when writing that wasn&amp;rsquo;t present with the
newer model&amp;rsquo;s display.&lt;/p&gt;
&lt;p&gt;The inability to connect devices without adapters has been a daily frustration
I hadn&amp;rsquo;t fully anticipated. The Lightning port feels increasingly isolated in a
world moving full throttle toward USB-C.&lt;/p&gt;
&lt;p&gt;An unexpected pain point has been the difference in audio quality and spatial
positioning during media consumption. The stereo speakers on the newer iPad
provided a much more immersive experience that I didn&amp;rsquo;t fully appreciate until
returning to the older model&amp;rsquo;s mono audio setup.&lt;/p&gt;
&lt;p&gt;The battery situation is undeniably problematic. Some degradation is expected
in a device from 2018, but I find myself constantly aware of battery
percentage, especially when using battery-intensive apps like Apple Notes,
which remains surprisingly &lt;a href="https://eliseomartelli.it/blog/2025-03-02-apple-quality/"&gt;power-hungry&lt;/a&gt; even
on this older hardware.&lt;/p&gt;
&lt;p&gt;The A10 Fusion chip and mere 2GB of RAM show their age when multitasking or
running newer applications. App refreshes are frequent, and there&amp;rsquo;s a
perceptible delay when switching between apps that I had forgotten existed.&lt;/p&gt;
&lt;p&gt;Not everything about the older model feels like a compromise. The flat back
design of the 6th gen iPad is refreshingly practical. The iPad sits flush on
surfaces without wobbling, and there&amp;rsquo;s something satisfying about this simple
design choice that newer models with their protruding camera bumps have
abandoned.&lt;/p&gt;
&lt;p&gt;Despite the limitations, the iPad 6th gen does fewer things, but what it does,
it does reliably. Basic browsing, note-taking, and media consumption remain
perfectly adequate. This raises interesting questions about how much of our
upgrade cycle is driven by genuine need versus the allure of new features.&lt;br&gt;
The relentless march forward in technology doesn&amp;rsquo;t always mean previous
generations become unusable, just that our expectations shift.&lt;/p&gt;
&lt;p&gt;This 2018 iPad remains surprisingly capable, and this experience has made me
more thoughtful about future upgrades. Perhaps the question isn&amp;rsquo;t always &amp;ldquo;What
new features does the latest model offer?&amp;rdquo; but rather &amp;ldquo;Which features of my
current device have become essential to my workflow, and which are merely nice
to have?&amp;rdquo;.&lt;/p&gt;</description><category>Apple</category><category>Short</category></item><item><title>Building in public: Knuth Day 0</title><link>https://eliseomartelli.it/blog/2025-03-15-knuth-day0/</link><pubDate>Sat, 15 Mar 2025 21:30:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-03-15-knuth-day0/</guid><description>&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/2025-03-15-knuth-day0/screenshot.jpg" alt="An image of Knuth"&gt;&lt;/p&gt;
&lt;p&gt;I started building a new app called Knuth, named after Donald Knuth, creator of
LaTeX. Why? Because after scouring the App Store, I realized there wasn’t an
app that perfectly fit my needs, so I’m making one myself.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You: Eliseo, don’t you have enough side projects already?&lt;br&gt;
 Me: Yes. Next question.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Knuth will be an app designed for those who frequently work with LaTeX
formulas, whether you&amp;rsquo;re a student, researcher, or math enthusiast.  
The goal is to make writing, saving, and interacting with LaTeX formulas as
seamless as possible, and feeling mostly like a first party iOS/iPadOS/macOS
app.&lt;/p&gt;
&lt;p&gt;I’m planning these 7 core features for this App:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Render LaTeX Formulas (Using iosMath to display formulas);&lt;/li&gt;
&lt;li&gt;Write Formulas with a Keyboard;&lt;/li&gt;
&lt;li&gt;Drag &amp;amp; Drop Formulas as images;&lt;/li&gt;
&lt;li&gt;Save Formulas Locally &amp;amp; in iCloud (Using SwiftData);&lt;/li&gt;
&lt;li&gt;Edit Previously Saved Formulas;&lt;/li&gt;
&lt;li&gt;Attach a Name to Formulas;&lt;/li&gt;
&lt;li&gt;Formula History (Think Time Machine, But for Math).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I would also like to have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PencilKit Integration&lt;/li&gt;
&lt;li&gt;Convert handwritten formulas to LaTeX with Im2LaTeX.&lt;/li&gt;
&lt;li&gt;Import Formulas from Photos&lt;/li&gt;
&lt;li&gt;Custom IME to select, insert, or edit formulas on the fly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Stay tuned for updates as I bring Knuth to life. Any suggestion is welcome!&lt;/p&gt;</description><category>Programming</category><category>Short</category></item><item><title>Building The New Recommendation System</title><link>https://eliseomartelli.it/blog/2025-03-11-recomendation-system/</link><pubDate>Tue, 11 Mar 2025 15:15:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-03-11-recomendation-system/</guid><description>&lt;p&gt;Creating a personalized recommendation system for a blog doesn&amp;rsquo;t require
massive machine learning models or complex neural networks. With some clever
mathematics, we can build a robust content similarity engine that runs entirely
in the browser (or on the server).&lt;/p&gt;
&lt;p&gt;In this article, I&amp;rsquo;ll walk you through the implementation of a lightweight
embedding system that powers the “Writing&amp;quot; section of this blog using TF-IDF
vectorization and cosine similarity.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;When readers finish an article, they often want to explore related content.
Manually curating recommendations for each post becomes increasingly difficult
as your content library grows.&lt;/p&gt;
&lt;p&gt;I experimented with using an
&lt;a href="https://www.eliseomartelli.it/blog/13-05-2023-onehundredpercent-more-ai"&gt;LLM&lt;/a&gt;
in the past, to provide suggested content, but costs and latency was not
reasonable for a personal blog.&lt;/p&gt;
&lt;p&gt;Automated solutions based on tags or categories help, but they lack the nuance
of understanding actual content similarity.&lt;/p&gt;
&lt;h2 id="the-roadmap"&gt;The Roadmap&lt;/h2&gt;
&lt;p&gt;I corroborated a roadmap of what I needed to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Covert post content into mathematical vector representations&lt;/li&gt;
&lt;li&gt;Weight different parts of the content (title, excerpt, tags, body)
appropriately&lt;/li&gt;
&lt;li&gt;Calculate similarity scores between posts using vector mathematics&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="representing-text-as-numbers"&gt;Representing Text as Numbers&lt;/h2&gt;
&lt;p&gt;At the heart of this recommendation system is the concept of vector space
models. How do we transform text into something a computer can compare
mathematically?&lt;/p&gt;
&lt;p&gt;Before anything else, I need to prepare post’s text by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Converting everything to lowercase;&lt;/li&gt;
&lt;li&gt;Removing punctuation;&lt;/li&gt;
&lt;li&gt;Splitting into individual words (tokens);&lt;/li&gt;
&lt;li&gt;Filtering out common stopwords (&amp;ldquo;the&amp;rdquo;, &amp;ldquo;and&amp;rdquo;, &amp;ldquo;is&amp;rdquo;, etc.);&lt;/li&gt;
&lt;li&gt;Removing very short terms.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; stopwords &lt;span style="color:#666"&gt;=&lt;/span&gt; Set([&lt;span style="color:#4070a0"&gt;&amp;#34;the&amp;#34;&lt;/span&gt;, &lt;span style="color:#4070a0"&gt;&amp;#34;and&amp;#34;&lt;/span&gt;, &lt;span style="color:#4070a0"&gt;&amp;#34;is&amp;#34;&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; newText &lt;span style="color:#666"&gt;=&lt;/span&gt; text
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt; .toLowerCase()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt; .replace(&lt;span style="color:#235388"&gt;/[^\w\s]/g&lt;/span&gt;, &lt;span style="color:#4070a0"&gt;&amp;#34; &amp;#34;&lt;/span&gt;) &lt;span style="color:#60a0b0;font-style:italic"&gt;// Replace punctuation with spaces
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt; .split(&lt;span style="color:#235388"&gt;/\s+/&lt;/span&gt;) &lt;span style="color:#60a0b0;font-style:italic"&gt;// Split on whitespace
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt; .filter(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt; (term) &lt;span style="color:#666"&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt; term.length &lt;span style="color:#666"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;this&lt;/span&gt;.minTermLength &lt;span style="color:#666"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;// Remove short terms
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt; &lt;span style="color:#666"&gt;!&lt;/span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;this&lt;/span&gt;.stopWords.has(term),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt; ); &lt;span style="color:#60a0b0;font-style:italic"&gt;// Remove stop words
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This transformation gives us a bag of meaningful words, but we still need to
convert these words into numerical representations.&lt;/p&gt;
&lt;h2 id="some-words-matter-more"&gt;Some Words Matter More&lt;/h2&gt;
&lt;p&gt;TF-IDF (Term Frequency-Inverse Document Frequency) is a mathematical technique
that measures the importance of a word to a document in a collection. It
addresses two key insights:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Words that appear frequently in a document are likely important to that
document (Term Frequency)&lt;/li&gt;
&lt;li&gt;Words that appear in many documents are likely less distinctive (Inverse
Document Frequency)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Term frequency&lt;/strong&gt; measures how often a term appears in a document. The simplest
form is just a raw count, but we normalize it by dividing by the total number
of terms to account for different document lengths.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; tf &lt;span style="color:#666"&gt;=&lt;/span&gt; frequency &lt;span style="color:#666"&gt;/&lt;/span&gt; terms.length;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Inverse Document Frequency&lt;/strong&gt; measures how unique or rare a term is across all documents.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; idf &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#007020"&gt;Math&lt;/span&gt;.log((&lt;span style="color:#007020;font-weight:bold"&gt;this&lt;/span&gt;.documentCount &lt;span style="color:#666"&gt;+&lt;/span&gt; &lt;span style="color:#40a070"&gt;1&lt;/span&gt;) &lt;span style="color:#666"&gt;/&lt;/span&gt; (df &lt;span style="color:#666"&gt;+&lt;/span&gt; &lt;span style="color:#40a070"&gt;1&lt;/span&gt;)) &lt;span style="color:#666"&gt;+&lt;/span&gt; &lt;span style="color:#40a070"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="putting-them-together"&gt;Putting them together&lt;/h3&gt;
&lt;p&gt;The TF-IDF score is simply the multiplication of these two metrics:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;vector[term] &lt;span style="color:#666"&gt;=&lt;/span&gt; tf &lt;span style="color:#666"&gt;*&lt;/span&gt; idf;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This formula gives higher values to terms that appear frequently in a specific
document and appear rarely in other documents.&lt;/p&gt;
&lt;p&gt;For example, in this blog, the word “problem” might have a high TF but a low
IDF (it appears everywhere), while “apple” might have a moderate TF but a high
IDF (it appears in fewer documents).&lt;/p&gt;
&lt;h2 id="not-all-text-is-created-equal"&gt;Not all text is created equal&lt;/h2&gt;
&lt;p&gt;Since a post&amp;rsquo;s title is typically more representative of its content than an
arbitrary sentence in the body. This system accounts for this by weighting
different parts of the content differently.&lt;/p&gt;
&lt;p&gt;This approach boosts the term frequency of words appearing in more important
sections without requiring complex multi-vector representations.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;private&lt;/span&gt; getWeightedPostContent(post: &lt;span style="color:#902000"&gt;Post&lt;/span&gt;)&lt;span style="color:#666"&gt;:&lt;/span&gt; &lt;span style="color:#902000"&gt;string&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;// Repeat elements based on their weights to influence term frequency
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; titleParts &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#007020"&gt;Array&lt;/span&gt;(&lt;span style="color:#007020;font-weight:bold"&gt;this&lt;/span&gt;.weights.title).fill(post.title &lt;span style="color:#666"&gt;||&lt;/span&gt; &lt;span style="color:#4070a0"&gt;&amp;#39;&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; excerptParts &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#007020"&gt;Array&lt;/span&gt;(&lt;span style="color:#007020;font-weight:bold"&gt;this&lt;/span&gt;.weights.excerpt).fill(post.excerpt &lt;span style="color:#666"&gt;||&lt;/span&gt; &lt;span style="color:#4070a0"&gt;&amp;#39;&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; tagParts &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#007020"&gt;Array&lt;/span&gt;.isArray(post.tags)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt; &lt;span style="color:#666"&gt;?&lt;/span&gt; post.tags.join(&lt;span style="color:#4070a0"&gt;&amp;#39; &amp;#39;&lt;/span&gt;).repeat(&lt;span style="color:#007020;font-weight:bold"&gt;this&lt;/span&gt;.weights.tags)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt; &lt;span style="color:#666"&gt;:&lt;/span&gt; &lt;span style="color:#4070a0"&gt;&amp;#39;&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; bodyParts &lt;span style="color:#666"&gt;=&lt;/span&gt; (post.body&lt;span style="color:#666"&gt;?&lt;/span&gt;.raw &lt;span style="color:#666"&gt;||&lt;/span&gt; &lt;span style="color:#4070a0"&gt;&amp;#39;&amp;#39;&lt;/span&gt;).repeat(&lt;span style="color:#007020;font-weight:bold"&gt;this&lt;/span&gt;.weights.body);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;return&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt; ...titleParts,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt; ...excerptParts,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt; tagParts,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt; bodyParts
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt; ].join(&lt;span style="color:#4070a0"&gt;&amp;#39; &amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="measuring-closeness"&gt;Measuring Closeness&lt;/h2&gt;
&lt;p&gt;Once we have our TF-IDF vectors, how do we determine similarity? We use cosine
similarity, which measures the cosine of the angle between two vectors.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;public&lt;/span&gt; calculateCosineSimilarity(vectorA: &lt;span style="color:#902000"&gt;TfIdfVector&lt;/span&gt;, vectorB: &lt;span style="color:#902000"&gt;TfIdfVector&lt;/span&gt;)&lt;span style="color:#666"&gt;:&lt;/span&gt; &lt;span style="color:#902000"&gt;number&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;if&lt;/span&gt; (&lt;span style="color:#666"&gt;!&lt;/span&gt;vectorA &lt;span style="color:#666"&gt;||&lt;/span&gt; &lt;span style="color:#666"&gt;!&lt;/span&gt;vectorB) &lt;span style="color:#007020;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#40a070"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;let&lt;/span&gt; dotProduct &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#40a070"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;let&lt;/span&gt; magnitudeA &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#40a070"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;let&lt;/span&gt; magnitudeB &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#40a070"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;// Calculate dot product and magnitude of vector A
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;for&lt;/span&gt; (&lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; [term, valueA] &lt;span style="color:#007020;font-weight:bold"&gt;of&lt;/span&gt; &lt;span style="color:#007020"&gt;Object&lt;/span&gt;.entries(vectorA)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; valueB &lt;span style="color:#666"&gt;=&lt;/span&gt; vectorB[term] &lt;span style="color:#666"&gt;||&lt;/span&gt; &lt;span style="color:#40a070"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt; dotProduct &lt;span style="color:#666"&gt;+=&lt;/span&gt; valueA &lt;span style="color:#666"&gt;*&lt;/span&gt; valueB;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt; magnitudeA &lt;span style="color:#666"&gt;+=&lt;/span&gt; valueA &lt;span style="color:#666"&gt;*&lt;/span&gt; valueA;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;// Calculate magnitude of vector B (for terms not in A)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;for&lt;/span&gt; (&lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; [term, valueB] &lt;span style="color:#007020;font-weight:bold"&gt;of&lt;/span&gt; &lt;span style="color:#007020"&gt;Object&lt;/span&gt;.entries(vectorB)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;if&lt;/span&gt; (&lt;span style="color:#666"&gt;!&lt;/span&gt;(term &lt;span style="color:#007020;font-weight:bold"&gt;in&lt;/span&gt; vectorA)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18&lt;/span&gt;&lt;span&gt; magnitudeB &lt;span style="color:#666"&gt;+=&lt;/span&gt; valueB &lt;span style="color:#666"&gt;*&lt;/span&gt; valueB;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19&lt;/span&gt;&lt;span&gt; } &lt;span style="color:#007020;font-weight:bold"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20&lt;/span&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;// Terms already in A were calculated above
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21&lt;/span&gt;&lt;span&gt; magnitudeB &lt;span style="color:#666"&gt;+=&lt;/span&gt; valueB &lt;span style="color:#666"&gt;*&lt;/span&gt; valueB;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; magnitude &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#007020"&gt;Math&lt;/span&gt;.sqrt(magnitudeA) &lt;span style="color:#666"&gt;*&lt;/span&gt; &lt;span style="color:#007020"&gt;Math&lt;/span&gt;.sqrt(magnitudeB);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;26&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;27&lt;/span&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;// Handle zero vectors
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;28&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;if&lt;/span&gt; (magnitude &lt;span style="color:#666"&gt;===&lt;/span&gt; &lt;span style="color:#40a070"&gt;0&lt;/span&gt;) &lt;span style="color:#007020;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#40a070"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;29&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;30&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;return&lt;/span&gt; dotProduct &lt;span style="color:#666"&gt;/&lt;/span&gt; magnitude;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;31&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Cosine similarity ranges from -1 (completely opposite) to 1 (exactly the same),
with 0 indicating orthogonality (no relationship). In our context with
non-negative TF-IDF values, it ranges from 0 to 1.&lt;/p&gt;
&lt;p&gt;This is particularly useful for our use case, since it’s length independent
(because it measures angular similarity and not magnitude, so a short blog post
can be similar to a long one).&lt;/p&gt;
&lt;p&gt;The complete embedding system initializes by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Processing all blog posts&lt;/li&gt;
&lt;li&gt;Building a vocabulary and document frequency counts&lt;/li&gt;
&lt;li&gt;Creating TF-IDF vectors for each post&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once initialized, it can provide similar posts or search results.  The beauty
of this recommendation system lies in its simplicity. Without recurring to
bigger solutions, we created a system that: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Understands the essence of content through numerical representation;&lt;/li&gt;
&lt;li&gt;Identifies similar content with reasonable accuracy;&lt;/li&gt;
&lt;li&gt;Requires minimal computational resources.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sometimes, a little bit of math is all you need.&lt;/p&gt;
&lt;p&gt;You can see the recommendation system in action here below!&lt;/p&gt;</description><category>Misc</category><category>Programming</category></item><item><title>Apple's Software Quality Crisis: When Premium Hardware Meets Subpar Software</title><link>https://eliseomartelli.it/blog/2023-09-06-scammer/</link><pubDate>Sun, 02 Mar 2025 19:15:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2023-09-06-scammer/</guid><description>&lt;p&gt;In the age of digital connectivity and social media, our virtual lives have
become increasingly intertwined with the online world. Instagram, one of the
world&amp;rsquo;s most popular social networking platforms, offers us a space to share
moments, connect with friends, and even discover new opportunities. However,
amidst the genuine connections and inspiring content, a darker side lurks: the
realm of scams.&lt;/p&gt;
&lt;p&gt;It all started 2 days ago, with a message like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hi. I&amp;rsquo;ll love to purchase some of your work, if they are available for
purchase. I am interested in purchasing 4 works digitally as NFT You need a
platform to change them to NFT which is their digital form. Then I&amp;rsquo;ll buy
them for 2.70ETH which is $4,900 each. You have a wallet, right?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;I then got forwarded to Telegram, which I kindly declined, but I made them
email me. That&amp;rsquo;s the marketplace where you can upload your art so that I can
purchase it, but you will need to set up an account with them before you can
do that.&lt;/p&gt;
&lt;p&gt;uniquemost.online&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I found myself on a PHP site. I registered an account (with fake credentials),
and started exploring the website. Furthermore, I was trying to find something
to do some kind of XSS or SQL injection, but then, I found something a little
bit more fun.&lt;/p&gt;
&lt;p&gt;A file upload box to upload a profile picture. I tried putting
something that was not an image and the site accepted it. It gave me a path
under the same directory I was under. It tingled a part of my brain. I
suspected a web-shell-type of vulnerability.&lt;/p&gt;
&lt;p&gt;I quickly made a PHP script to test that assumption:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#666"&gt;&amp;lt;?&lt;/span&gt;php
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;phpinfo();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;?&amp;gt;&lt;/span&gt;&lt;span style=""&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Opening later the &amp;ldquo;profile picture&amp;rdquo; resulted in showing phpinfo. Nice!&lt;/p&gt;
&lt;h2 id="payload"&gt;Payload&lt;/h2&gt;
&lt;p&gt;I started writing a little payload to try and get a shell on this site.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#666"&gt;&amp;lt;?=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;`$_GET[0]`&lt;/span&gt;&lt;span style="color:#007020"&gt;?&amp;gt;&lt;/span&gt;&lt;span style=""&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><category>Apple</category></item><item><title>Apple's Software Quality Crisis: When Premium Hardware Meets Subpar Software</title><link>https://eliseomartelli.it/blog/2025-03-02-apple-quality/</link><pubDate>Sun, 02 Mar 2025 19:15:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-03-02-apple-quality/</guid><description>&lt;h2 id="update-march-5-2025"&gt;Update: March 5, 2025&lt;/h2&gt;
&lt;p&gt;I wrote a &lt;a href="https://github.com/eliseomartelli/Thermals"&gt;small application&lt;/a&gt; that outputs
&lt;a href="https://developer.apple.com/documentation/foundation/processinfo/thermalstate"&gt;&lt;code&gt;ProcessInfo.ThermalState&lt;/code&gt;&lt;/a&gt;.
The reported Thermal State is &lt;code&gt;nominal&lt;/code&gt; when the iPad is not heating, &lt;code&gt;fair&lt;/code&gt;
when the iPad started to heat and &lt;code&gt;serious&lt;/code&gt; when the iPad was hurting to touch.
I defintely had the iPad heating more than &lt;code&gt;serious&lt;/code&gt; on other occurrences.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="update-march-4-2025"&gt;Update: March 4, 2025&lt;/h2&gt;
&lt;p&gt;The response to this post has been overwhelming, with sharing on
&lt;a href="https://news.ycombinator.com/item?id=43243075"&gt;Hacker News&lt;/a&gt; and
&lt;a href="https://www.reddit.com/r/MacOS/comments/1j2j7b4/apples_software_quality_crisis_when_premium/"&gt;Reddit&lt;/a&gt;.
It&amp;rsquo;s clear that many others are experiencing frustrations with Apple&amp;rsquo;s
software, indicating quality issue within Apple&amp;rsquo;s OSes portfolio.&lt;/p&gt;
&lt;p&gt;To provide more context, here&amp;rsquo;s the workflow that triggers these issues:
I create a new note at the start of each lecture, add a title and tags
for organization, and begin writing with my Apple Pencil Pro.&lt;br&gt;
The issues manifest after filling roughly one page (or &amp;ldquo;screen&amp;rdquo;)
with handwritten notes. The iPad here starts to overheat, and lag spikes become
increasingly frequent.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;As a long-time Apple user, I&amp;rsquo;ve always appreciated the integration of
hardware and software, signature of the Apple ecosystem. However, recent
experiences with my iPad Air 11&amp;quot; M2 has left me questioning whether
Apple has lost sight of what once made their products great.&lt;/p&gt;
&lt;h2 id="a-brief-timeline"&gt;A Brief Timeline&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;November 6, 2024&lt;/strong&gt;: Visited the Apple Store in Turin with my then new iPad
Air 11&amp;quot; M2 experiencing significant lag and overheating when using Notes and
Freeform with Apple Pencil Pro.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;November 13, 2024&lt;/strong&gt;: Received a replacement unit after store
representatives suggested a hardware swap.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;February 27, 2025&lt;/strong&gt;: The replacement unit is now having identical
performance issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="premium-hardware-struggling-software"&gt;Premium Hardware, Struggling Software&lt;/h2&gt;
&lt;p&gt;In November, I visited the Apple Store in Turin to address persistent issues
with my iPad Air 11&amp;quot; M2 running iPadOS 18.1. Despite having cutting-edge
hardware, I&amp;rsquo;ve been experiencing significant lag when using &lt;em&gt;basic&lt;/em&gt; Apple
applications like Notes and Freeform.&lt;/p&gt;
&lt;p&gt;The performance issues don&amp;rsquo;t stop at sluggish response times. During these
use cases, my iPad overheated, making it uncomfortable to
hold or even rest the palm on, raising concerns about potential long-term
hardware damage.&lt;/p&gt;
&lt;p&gt;What made this particularly frustrating is that these aren&amp;rsquo;t third-party
applications pushing the hardware to its limits. These are Apple&amp;rsquo;s own
applications that should be &lt;em&gt;theoretically&lt;/em&gt; optimized for their hardware.&lt;/p&gt;
&lt;p&gt;After demonstrating the issues in person to Apple Store staff (that were
courteous and professional), the support representative that was handling my
case suggested a hardware replacement.&lt;br&gt;
However, after further discussion, we
both concluded this was likely a software problem rather than a
hardware defect.&lt;/p&gt;
&lt;p&gt;I think this highlights a growing challenge for Apple&amp;rsquo;s support model:
how do you troubleshoot software issues when the traditional solution has been
to replace hardware?&lt;/p&gt;
&lt;p&gt;I think this experience feels symptomatic of a broader decline in Apple&amp;rsquo;s
software quality.&lt;/p&gt;
&lt;p&gt;My replacement iPad Air 11&amp;quot; M2 with the Apple Pencil Pro is once again
struggling with the same basic tasks. Despite multiple iPadOS updates since my
original complaint (we&amp;rsquo;re now on 18.3.1), the fundamental issues remain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Significant lag when using Apple&amp;rsquo;s own Notes and Freeform applications&lt;/strong&gt;:
Input latency increases dramatically after about 5-10 minutes of use, with
stroke rendering delayed by up to 5 seconds;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Device overheating during standard usage scenarios&lt;/strong&gt;: CPU temperatures
reach concerning levels (based on device&amp;rsquo;s external heat) during pencil
input;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Poor responsiveness with the Apple Pencil Pro&lt;/strong&gt;: Palm rejection fails
intermittently, and pressure and roll sensitivity becomes inconsistent.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This recurrence with a replacement device (and other friend&amp;rsquo;s iPads) confirms
what I and the support representative suspected last November - this is a
software optimization problem, not a hardware defect.&lt;/p&gt;
&lt;h2 id="details-worth-noting"&gt;Details Worth Noting&lt;/h2&gt;
&lt;p&gt;Since my original complaint, I&amp;rsquo;ve discovered
&lt;a href="https://www.reddit.com/r/iPadOS/search/?q=overheating"&gt;numerous&lt;/a&gt; forum threads
and social media discussions from iPad users experiencing similar issues. This
suggests a systemic problem rather than isolated incidents.&lt;/p&gt;
&lt;p&gt;I think these are sympthoms of two main problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Thermal Management Issues&lt;/strong&gt;: The SoC should throttle performance
before reaching high external temperatures;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory Management Problems&lt;/strong&gt;: The gradual degradation in performance makes
me think of memory leaks or improper garbage collection in Apple&amp;rsquo;s Note and
Freeform applications.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most concerning is that multiple software updates have failed to address these
performance issues while introducing new features that add more strain to the
system (like Apple Intelligence, but that&amp;rsquo;s a story for another day).&lt;/p&gt;
&lt;h2 id="reconsidering-the-apple-tax"&gt;Reconsidering the &amp;ldquo;Apple Tax&amp;rdquo;&lt;/h2&gt;
&lt;p&gt;For years, many of us have willingly paid the &amp;ldquo;Apple tax&amp;rdquo;, the premium price
for Apple products justified by superior user experience, design, and ecosystem
integration. But if software quality continues to decline, this value
proposition becomes increasingly difficult to defend.&lt;/p&gt;
&lt;p&gt;The persistence of these issues through multiple software updates suggests:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Feature prioritization over optimization&lt;/strong&gt;: Engineering resources appear
focused on adding new capabilities rather than fixing existing performance
problems;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inadequate performance testing&lt;/strong&gt;: Real-world usage scenarios with Apple&amp;rsquo;s
own applications aren&amp;rsquo;t being adequately tested.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As customers paying premium prices for Apple products, we at least deserve:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Transparency&lt;/strong&gt;: Acknowledgment of known performance issues;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focus on fundamentals&lt;/strong&gt;: Updates focused on performance and stability;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extended support&lt;/strong&gt;: Consideration for extended warranties when software
issues impact hardware lifespan.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Apple experience was once defined by the words &amp;ldquo;it just works&amp;rdquo;.&lt;br&gt;
Today, that promise feels hollow as software struggles to keep pace with
hardware capabilities. As users and customers, we need to vocally advocate for
the quality-focused Apple we once knew.&lt;/p&gt;
&lt;p&gt;After months of hoping for improvement, I&amp;rsquo;m faced with difficult decisions
about my future in the Apple ecosystem. This issue, along with several others,
albeit smaller, ones, has severely damaged my confidence in new Apple products.&lt;/p&gt;
&lt;p&gt;Apple should return to its roots - creating products that prioritize user
experience over feature &lt;em&gt;creep&lt;/em&gt;. Apple needs to reclaim that ethos.&lt;/p&gt;</description><category>Apple</category></item><item><title>Introducing: Micromachine</title><link>https://eliseomartelli.it/blog/2025-02-18-micromachine/</link><pubDate>Tue, 18 Feb 2025 17:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2025-02-18-micromachine/</guid><description>&lt;p&gt;I&amp;rsquo;m pleased to announce the release of
&lt;a href="https://github.com/eliseomartelli/micromachine"&gt;Micromachine&lt;/a&gt;, a new
open-source library designed to simplify state management in Go applications.&lt;/p&gt;
&lt;h2 id="what-is-micromachine"&gt;What is Micromachine?&lt;/h2&gt;
&lt;p&gt;Micromachine is a generic state machine implementation in Go that allows you to
define states and transitions between them. It supports any comparable type for
states and ensures thread safety, making it ideal for concurrent applications.
With Micromachine, you can define custom actions to be executed during state
transitions and check if a transition is valid before attempting it.&lt;/p&gt;
&lt;h2 id="key-features"&gt;Key Features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Generic State Management&lt;/strong&gt;: Supports any comparable type for states, providing flexibility in defining your state machine.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Thread Safety&lt;/strong&gt;: Ensures that concurrent access is handled correctly, making it suitable for multi-threaded applications.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom Actions&lt;/strong&gt;: Define custom actions to be executed during state transitions, allowing for complex state management logic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transition Validation&lt;/strong&gt;: Check if a transition is valid before attempting it, preventing runtime errors.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="why-micromachine"&gt;Why Micromachine?&lt;/h2&gt;
&lt;p&gt;Micromachine was born out of the need for a simple yet powerful state
management solution in Go. As a developer, I often found myself implementing
state machines from scratch, which was time-consuming and error-prone.
Micromachine aims to solve this problem.&lt;/p&gt;
&lt;p&gt;You can check out the
&lt;a href="https://github.com/eliseomartelli/micromachine"&gt;Micromachine&lt;/a&gt; repository and
start using it today.&lt;/p&gt;</description><category>Programming</category><category>Short</category></item><item><title>Goodbye. Hello.</title><link>https://eliseomartelli.it/blog/2024-12-31-hello-goodbye/</link><pubDate>Tue, 31 Dec 2024 17:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2024-12-31-hello-goodbye/</guid><description>&lt;p&gt;This year brought 366 sunrises and 366 sunsets—a journey marked by extended
pauses from one of my favorite hobbies and periods of intense stress.&lt;/p&gt;
&lt;p&gt;As I reflect, this serves as a personal reminder to continually strive to be a
better person, even if I fell short on some days.&lt;/p&gt;
&lt;p&gt;From meeting new people and engaging in meaningful conversations to starting a
new job, speaking to large audiences, and, toward the end of the year, taking a
significant step forward, this year has been defined by growth and
introspection.&lt;/p&gt;
&lt;p&gt;I believe that everything we do carries purpose, and this belief shapes how I
channel my energy each day. To stay grounded, I follow three guiding
principles: impact, passion, and alignment.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Impact: To create meaningful contributions that benefit others.&lt;/li&gt;
&lt;li&gt;Passion: To pursue what feels truly meaningful to me.&lt;/li&gt;
&lt;li&gt;Alignment: To ensure my actions align with my values and ethics.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These principles not only guide my decisions but also serve as a constant
reminder to seek purpose in every step of the journey.&lt;/p&gt;
&lt;p&gt;As the new year begins, I want to feel hopeful about making a difference.&lt;/p&gt;</description><category>Misc</category><category>Short</category></item><item><title>Fish on macOS - A week long experiment</title><link>https://eliseomartelli.it/blog/2024-12-16-fishday1/</link><pubDate>Mon, 16 Dec 2024 17:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2024-12-16-fishday1/</guid><description>&lt;p&gt;For the next week, I intend to experiment with utilizing Fish as my primary
shell on macOS. This challenge was initiated this morning by
&lt;a href="http://nomnp.com"&gt;Francesco&lt;/a&gt;, who encouraged me to explore Fish as an
alternative shell.&lt;/p&gt;
&lt;p&gt;Switching to Fish for a week?&lt;/p&gt;
&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;I followed these steps to install Fish:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I used Homebrew to install fish.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;$ brew install fish
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="2"&gt;
&lt;li&gt;I then added fish to the list of available shells.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;$ which fish | sudo tee -a /etc/shells &amp;gt; /dev/null
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="3"&gt;
&lt;li&gt;Then I switched my default shell.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;chsh -s &lt;span style="color:#007020;font-weight:bold"&gt;$(&lt;/span&gt;which fish&lt;span style="color:#007020;font-weight:bold"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="something-cool"&gt;Something cool&lt;/h2&gt;
&lt;p&gt;A positive aspect of Fish is the use of the conf.d directory, which helps
organize configuration files into individual scripts, making it easier to
manage and update customizations without cluttering a single configuration
file.&lt;/p&gt;
&lt;p&gt;One thing I’ve noticed is that Fish feels noticeably faster than my previous
setup with Zsh + Oh My Zsh. It seems more responsive, and the startup time
feels snappier.&lt;/p&gt;
&lt;h2 id="a-first-challenge"&gt;A first challenge&lt;/h2&gt;
&lt;p&gt;The default keybinding to edit a command using $EDITOR is Meta+E (contrary to
other shells ⌃+X ⌃+E). Since I use the ⌥ (Option) key as a &lt;strong&gt;compose key&lt;/strong&gt; for
typing special characters, I cannot remap it to the Meta key.  I had to
configure a custom bind to bring back ⌃+X ⌃+E functionality.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fish" data-lang="fish"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt;# Edit command
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;bind&lt;/span&gt; &lt;span style="color:#4070a0;font-weight:bold"&gt;\c&lt;/span&gt;x&lt;span style="color:#4070a0;font-weight:bold"&gt;\c&lt;/span&gt;e edit_command_buffer
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="dotfiles"&gt;Dotfiles&lt;/h2&gt;
&lt;p&gt;If you want to take a look at my configurations or follow along with the
changes I make throughout this experiment, you can check out my dotfiles
&lt;a href="https://github.com/eliseomartelli/dotfiles"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Throughout the week, I will continue experimenting with Fish and refining my
setup. Expect further updates!&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="day-2"&gt;Day 2&lt;/h1&gt;
&lt;p&gt;Fish is not POSIX-compliant, but that’s intentional. It’s designed as a
user-friendly interactive shell, not as a scripting shell.&lt;/p&gt;
&lt;h2 id="scripts-and-compatibility"&gt;Scripts and Compatibility&lt;/h2&gt;
&lt;p&gt;Many scripts use the following shebangs: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;or&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;or  (even better)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Indeed, these scripts presume POSIX compliance, which Fish does not natively
support. However, they also specify the interpreter to be used for executing
those scripts. As noted in the Gentoo Wiki:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;fish should not be set as the system shell by making it the target of the
/bin/sh symlink; this could result in an inoperable system.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Key Point: Utilize Fish interactively, while maintaining Bash or a
POSIX-compliant shell as the system’s default shell. &lt;/p&gt;
&lt;h2 id="counterpoint"&gt;Counterpoint&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;#include&lt;/span&gt; &lt;span style="color:#007020"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span style="color:#007020"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#902000"&gt;int&lt;/span&gt; &lt;span style="color:#06287e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; &lt;span style="color:#902000"&gt;char&lt;/span&gt; &lt;span style="color:#666"&gt;*&lt;/span&gt;command &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#4070a0"&gt;&amp;#34;echo $SHELL&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5&lt;/span&gt;&lt;span&gt; &lt;span style="color:#06287e"&gt;exit&lt;/span&gt;(&lt;span style="color:#06287e"&gt;system&lt;/span&gt;(command));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;system()&lt;/code&gt; function executes the provided command (in this instance, &lt;code&gt;echo $SHELL&lt;/code&gt;) within a new shell, your default interactive shell (in my case,
&lt;code&gt;fish&lt;/code&gt;). But you &lt;a href="https://www.youtube.com/watch?v=-vpGswuYVg8&amp;amp;t=621s"&gt;shouldn’t really
use&lt;/a&gt; &lt;code&gt;system()&lt;/code&gt;&amp;hellip;&lt;/p&gt;</description><category>Programming</category><category>Short</category></item><item><title>Updates for you macOS app</title><link>https://eliseomartelli.it/blog/2024-09-10-updatesforyourapp/</link><pubDate>Wed, 09 Oct 2024 19:40:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2024-09-10-updatesforyourapp/</guid><description>&lt;p&gt;Keeping your users informed about the latest version of your application
is important for enhancing the user experience.
In this post I will outline how I implemented a feature to notify the user
of the latest update for you app, providing a link to download it.&lt;/p&gt;
&lt;p&gt;It was a &amp;ldquo;rush&amp;rdquo; feature, I worked on this for less than two hours, so it&amp;rsquo;s not
perfect by any means, but it&amp;rsquo;s serviceable.
The latest version will be sourced from GitHub releases.&lt;/p&gt;
&lt;h2 id="connecting-to-the-api"&gt;Connecting to the API&lt;/h2&gt;
&lt;p&gt;The first step I had to take was retrieving the latest version of the app
from the GitHub&amp;rsquo;s API.&lt;/p&gt;
&lt;p&gt;To accomplish this, I designed a function that accepts a closure to forward its
output. Using a closure provides flexibility in handling the result, whether
it’s a successful version retrieval or an error. This design pattern is
particularly useful in asynchronous programming, as it allows the application
to remain responsive while waiting for the network call to complete.&lt;/p&gt;
&lt;p&gt;The GitHub API returns a JSON list of the releases of our application. Each
release contains various pieces of information, but for our purpose, I only
needed the name of the latest version. This simplifies the data I need to
handle.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;import&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;Foundation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;GithubVersion&lt;/span&gt;: Decodable {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;let&lt;/span&gt; &lt;span style="color:#bb60d5"&gt;name&lt;/span&gt;: &lt;span style="color:#007020"&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This struct conforms to the &lt;code&gt;Decodable&lt;/code&gt; protocol, allowing it to be easily
parsed from the JSON response.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;import&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;Foundation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;enum&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;GithubVersionRepositoryError&lt;/span&gt;: Error {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;case&lt;/span&gt; noData
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;case&lt;/span&gt; noVersionFound
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;let&lt;/span&gt; &lt;span style="color:#bb60d5"&gt;GITHUB_API_URL&lt;/span&gt;: URL = URL(string: &lt;span style="color:#4070a0"&gt;&amp;#34;https://api.github.com/repos/&amp;#34;&lt;/span&gt;)&lt;span style="color:#666"&gt;!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;GithubVersionRepository&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;static&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;let&lt;/span&gt; &lt;span style="color:#bb60d5"&gt;shared&lt;/span&gt; = GithubVersionRepository()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#06287e"&gt;getVersion&lt;/span&gt;(completion: @escaping (Result&amp;lt;&lt;span style="color:#007020"&gt;String&lt;/span&gt;, any Error&amp;gt;) -&amp;gt; &lt;span style="color:#007020"&gt;Void&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt; URLSession.shared.dataTask(with: getReleasesUrl(repoUrl: &lt;span style="color:#4070a0"&gt;&amp;#34;eliseomartelli/Cleeb&amp;#34;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt; data, response, error &lt;span style="color:#007020;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;let&lt;/span&gt; &lt;span style="color:#bb60d5"&gt;error&lt;/span&gt; = error {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16&lt;/span&gt;&lt;span&gt; completion(.failure(error))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;guard&lt;/span&gt; data &lt;span style="color:#666"&gt;!=&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;nil&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20&lt;/span&gt;&lt;span&gt; completion(.failure(GithubVersionRepositoryError.noData))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;do&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;let&lt;/span&gt; &lt;span style="color:#bb60d5"&gt;versions&lt;/span&gt; = &lt;span style="color:#007020;font-weight:bold"&gt;try&lt;/span&gt; JSONDecoder().decode([GithubVersion].&lt;span style="color:#007020;font-weight:bold"&gt;self&lt;/span&gt;, from: data!)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;guard&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;let&lt;/span&gt; &lt;span style="color:#bb60d5"&gt;latestVersion&lt;/span&gt; = versions.&lt;span style="color:#007020"&gt;first&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;26&lt;/span&gt;&lt;span&gt; completion(.failure(GithubVersionRepositoryError.noVersionFound))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;27&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;28&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;29&lt;/span&gt;&lt;span&gt; completion(.success(latestVersion.name))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;30&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;31&lt;/span&gt;&lt;span&gt; } &lt;span style="color:#007020;font-weight:bold"&gt;catch&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;32&lt;/span&gt;&lt;span&gt; completion(.failure(error))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;33&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;34&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;35&lt;/span&gt;&lt;span&gt; }.resume()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;36&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;37&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;38&lt;/span&gt;&lt;span&gt; &lt;span style="color:#60a0b0;font-style:italic"&gt;// Obtains the repo URL endpoint for releases.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;39&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#06287e"&gt;getReleasesUrl&lt;/span&gt;(repoUrl : &lt;span style="color:#007020"&gt;String&lt;/span&gt;) -&amp;gt; URL {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;40&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;return&lt;/span&gt; GITHUB_API_URL
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;41&lt;/span&gt;&lt;span&gt; .appendingPathComponent(repoUrl)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;42&lt;/span&gt;&lt;span&gt; .appendingPathComponent(&lt;span style="color:#4070a0"&gt;&amp;#34;releases&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;43&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;44&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the &lt;code&gt;getVersion&lt;/code&gt; function, I initiate a data task with &lt;code&gt;URLSession&lt;/code&gt; to call
the GitHub releases endpoint for my application. If there’s an error during the
request or if no data is returned, the closure is invoked with an appropriate
error message. If the data is successfully retrieved, I decode it into an array
of &lt;code&gt;GithubVersion&lt;/code&gt; instances. I extract the latest version name and
call the completion closure with it.&lt;/p&gt;
&lt;h2 id="making-it-work"&gt;Making it work&lt;/h2&gt;
&lt;p&gt;To effectively manage and display version information, I created a
&lt;code&gt;VersionViewModel&lt;/code&gt;. This class conforms to &lt;code&gt;ObservableObject&lt;/code&gt; protocol,
allowing it to notify SwiftUI for any changes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;import&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;Foundation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;class&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;VersionViewModel&lt;/span&gt;: ObservableObject {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt; @Published &lt;span style="color:#007020;font-weight:bold"&gt;var&lt;/span&gt; &lt;span style="color:#bb60d5"&gt;latestVersion&lt;/span&gt;: &lt;span style="color:#007020"&gt;String&lt;/span&gt; = Bundle.main.buildVersion!
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt; @Published &lt;span style="color:#007020;font-weight:bold"&gt;var&lt;/span&gt; &lt;span style="color:#bb60d5"&gt;errorMessage&lt;/span&gt;: &lt;span style="color:#007020"&gt;String&lt;/span&gt;?
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt; @Published &lt;span style="color:#007020;font-weight:bold"&gt;var&lt;/span&gt; &lt;span style="color:#bb60d5"&gt;isUpdateAvailable&lt;/span&gt;: &lt;span style="color:#007020"&gt;Bool&lt;/span&gt; = &lt;span style="color:#007020;font-weight:bold"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;init&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;self&lt;/span&gt;.fetchLatestVersion()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#06287e"&gt;fetchLatestVersion&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt; GithubVersionRepository.shared.getVersion { result &lt;span style="color:#007020;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt; DispatchQueue.main.async {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;switch&lt;/span&gt; result {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;case&lt;/span&gt; .success(&lt;span style="color:#007020;font-weight:bold"&gt;let&lt;/span&gt; &lt;span style="color:#bb60d5"&gt;version&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;self&lt;/span&gt;.latestVersion = version
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;case&lt;/span&gt; .failure(&lt;span style="color:#007020;font-weight:bold"&gt;let&lt;/span&gt; &lt;span style="color:#bb60d5"&gt;error&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;self&lt;/span&gt;.errorMessage = error.localizedDescription
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;self&lt;/span&gt;.isUpdateAvailable = Bundle.main.buildVersion &lt;span style="color:#666"&gt;!=&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;self&lt;/span&gt;.latestVersion
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="updating-the-ui"&gt;Updating the UI&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;import&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;SwiftUI&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;MainView&lt;/span&gt;: View {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt; @EnvironmentObject &lt;span style="color:#007020;font-weight:bold"&gt;var&lt;/span&gt; &lt;span style="color:#bb60d5"&gt;versionViewModel&lt;/span&gt;: VersionViewModel
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;var&lt;/span&gt; &lt;span style="color:#bb60d5"&gt;body&lt;/span&gt;: some View {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt; VStack {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;if&lt;/span&gt; versionViewModel.isUpdateAvailable {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt; Button(action: openDownloadPage) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt; Text(version)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt; .font(.caption)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt; .background(Color.blue)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt; .foregroundColor(.white)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt; .clipShape(Capsule())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16&lt;/span&gt;&lt;span&gt; .buttonStyle(PlainButtonStyle())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#06287e"&gt;openDownloadPage&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;let&lt;/span&gt; &lt;span style="color:#bb60d5"&gt;url&lt;/span&gt; = URL(string: &lt;span style="color:#4070a0"&gt;&amp;#34;https://github.com/eliseomartelli/Cleeb/releases&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23&lt;/span&gt;&lt;span&gt; NSWorkspace.shared.open(url)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;26&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="integrate-everything"&gt;Integrate Everything&lt;/h2&gt;
&lt;p&gt;Integrate the &lt;code&gt;VersionViewModel&lt;/code&gt; into your app structure to allow your views to
access the latest version data.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;@main
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#0e84b5;font-weight:bold"&gt;YourApp&lt;/span&gt;: App {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt; @StateObject &lt;span style="color:#007020;font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;var&lt;/span&gt; &lt;span style="color:#bb60d5"&gt;versionViewModel&lt;/span&gt; = VersionViewModel()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;var&lt;/span&gt; &lt;span style="color:#bb60d5"&gt;body&lt;/span&gt;: some Scene {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt; Window(&lt;span style="color:#4070a0"&gt;&amp;#34;Your App&amp;#34;&lt;/span&gt;, id: &lt;span style="color:#4070a0"&gt;&amp;#34;yourapp&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt; MainView()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt; .environmentObject(versionViewModel)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now I can notify users of updates to my application!&lt;/p&gt;
&lt;p&gt;If you found this interesting, check out
&lt;a href="https://github.com/eliseomartelli/Cleeb"&gt;Cleeb&lt;/a&gt;, the app I made to help you
clean your macOS laptop keyboard!&lt;/p&gt;</description><category>Programming</category><category>Short</category></item><item><title>Photography Today: Has It Become Too Accessible?</title><link>https://eliseomartelli.it/blog/2024-09-17-photodemocracy/</link><pubDate>Tue, 17 Sep 2024 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2024-09-17-photodemocracy/</guid><description>&lt;p&gt;In a recent conversation with &lt;a href="https://www.instagram.com/aboutnaji/"&gt;Cesare Nai&lt;/a&gt;,
we found ourselves reflecting on the current state of photography and its role
in modern communication.&lt;/p&gt;
&lt;p&gt;In recent years, visual communication has become a crucial tool for sharing
ideas and concepts that people might not easily encounter in their daily lives.
Photography, in particular, is now more widespread than ever, largely due to
the availability of cameras at our fingertips. However, this accessibility has
brought with it a complex issue: photography may have become &amp;ldquo;too democratic.&amp;rdquo;
Let me explain.&lt;/p&gt;
&lt;p&gt;Roland Barthes, in his seminal work &lt;em&gt;Camera Lucida&lt;/em&gt;, explored the emotional
resonance of photographs, using the term &lt;em&gt;punctum&lt;/em&gt; to describe the deeply
personal and striking element in an image that stimulates the viewer, creating
an emotional response.
With the widespread use of smartphones and digital cameras, taking a photograph
has become so simple and routine that its emotional or artistic value is
sometimes diluted. The democratization of photography has given more people the
ability to capture and share images, but this ease of use has also led to a
saturation of visual content. That &lt;em&gt;punctum&lt;/em&gt; is becoming harder to find.&lt;/p&gt;
&lt;p&gt;Despite these challenges, photography remains a valuable communication tool.
Those who understand its potential and have a clear sense of purpose—knowing
why they are taking certain photos and what they aim to convey—can still make a
significant impact. However, it is important to acknowledge that not everyone
approaches photography with the same depth of intention or sensitivity.&lt;/p&gt;
&lt;p&gt;Like many other technical arts, photography has experienced a significant
transformation. Advances in technology have shifted the focus from its original
purpose—capturing meaningful moments or conveying a powerful message—to an
increased emphasis on technical perfection. Nowadays, people often prioritize
elements like lighting, composition, and sharpness, sometimes at the expense of
the deeper meaning or emotional resonance of the photograph.&lt;/p&gt;</description><category>Photography</category></item><item><title>Human Connection Protocol</title><link>https://eliseomartelli.it/blog/2024-08-20-hcp/</link><pubDate>Tue, 20 Aug 2024 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2024-08-20-hcp/</guid><description>&lt;h2 id="abstract"&gt;Abstract&lt;/h2&gt;
&lt;p&gt;This document specifies the Human Connection Protocol (HCP).
HCP standardizes the request, response, and keep-alive mechanisms involved in
interpersonal communication.
This protocol aims to enhance the predictability and reliability of human
interactions by formalizing communication exchanges.&lt;/p&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Human connections are fundamental to social interaction. However, these
connections often suffer from misunderstandings, unpredictability, and
inefficiency due to the lack of standardized communication mechanisms. The
Human Connections Protocol (HCP) provides a structured approach to managing
these interactions, defining clear rules for initiating, responding to, and
maintaining human connections.&lt;/p&gt;
&lt;h2 id="terminology"&gt;Terminology&lt;/h2&gt;
&lt;p&gt;The key words &amp;ldquo;MUST&amp;rdquo;, &amp;ldquo;MUST NOT&amp;rdquo;, &amp;ldquo;REQUIRED&amp;rdquo;, &amp;ldquo;SHALL&amp;rdquo;, &amp;ldquo;SHALL NOT&amp;rdquo;, &amp;ldquo;SHOULD&amp;rdquo;,
&amp;ldquo;SHOULD NOT&amp;rdquo;, &amp;ldquo;RECOMMENDED&amp;rdquo;, &amp;ldquo;MAY&amp;rdquo;, and &amp;ldquo;OPTIONAL&amp;rdquo; in this document are to be
interpreted as described in IETF RFC 2119.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Requester: The entity that initiates the connection.&lt;/li&gt;
&lt;li&gt;Responder: The entity that responds to the connection request.&lt;/li&gt;
&lt;li&gt;Connection: A bidirectional channel of communication established between a
Requester and a Responder.&lt;/li&gt;
&lt;li&gt;KeepAlive: A mechanism for maintaining the established connection.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="protocol-overview"&gt;Protocol Overview&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Request: Initiation of a human connection.&lt;/li&gt;
&lt;li&gt;Response: Acknowledgment and acceptance, rejection, or forwarding (Relay) of
the connection request.&lt;/li&gt;
&lt;li&gt;KeepAlive: Maintenance of the connection over time.&lt;/li&gt;
&lt;li&gt;Termination: Terminates an established communication channel.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="message-format"&gt;Message Format&lt;/h2&gt;
&lt;p&gt;All HCP messages MUST consist of the following fields:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Type: Specifies the message type (Request, Response, KeepAlive).&lt;/li&gt;
&lt;li&gt;Timestamp: The time the message was created.&lt;/li&gt;
&lt;li&gt;Body: The content of the message.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="request-operation"&gt;Request Operation&lt;/h2&gt;
&lt;h3 id="request-format"&gt;Request Format&lt;/h3&gt;
&lt;p&gt;A Request message is used to initiate a connection. It SHOULD contain a brief introduction and an OPTIONAL topic of discussion.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Type: Request
Timestamp: &amp;lt;UTC Time&amp;gt;
Body: &amp;lt;Introduction and Topic&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="request-handling"&gt;Request Handling&lt;/h3&gt;
&lt;p&gt;Upon receiving a Request, the Responder MUST process it and generate a Response message. The Requester MUST wait for this Response before any further action.&lt;/p&gt;
&lt;h3 id="response-operation"&gt;Response Operation&lt;/h3&gt;
&lt;p&gt;Response Types&lt;/p&gt;
&lt;p&gt;There are four possible types of Response messages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Accept: The Responder agrees to establish the connection.&lt;/li&gt;
&lt;li&gt;Reject: The Responder declines the connection request.&lt;/li&gt;
&lt;li&gt;Delay: The Responder acknowledges the request but cannot respond immediately.&lt;/li&gt;
&lt;li&gt;Relay: The Responder forwards the connection request to a third party, who MAY be better suited to respond.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="response-format"&gt;Response Format&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Type: Response (Accept/Reject/Delay/Relay)
Timestamp: &amp;lt;UTC Time&amp;gt;
Body: &amp;lt;Acceptance Message, Rejection Message, Delay Explanation, or Relay Details&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;Accept: Upon receiving an Accept Response, the Requester and Responder SHOULD begin communication over the established connection.&lt;/li&gt;
&lt;li&gt;Reject: If a Reject Response is received, the Requester SHOULD NOT attempt to re-initiate the connection for a predefined cool-off period.&lt;/li&gt;
&lt;li&gt;Delay: A Delay Response indicates the Responder WILL follow up when they are ready.&lt;/li&gt;
&lt;li&gt;Relay: If a Relay Response is received, the Requester SHOULD wait for a response from the third party. The Relay Response MUST include contact details or an introduction to the third party.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="keepalive-operation"&gt;KeepAlive Operation&lt;/h2&gt;
&lt;h3 id="keepalive-format"&gt;KeepAlive Format&lt;/h3&gt;
&lt;p&gt;KeepAlive messages are used to maintain an active connection. These messages SHOULD be sent periodically and MUST be acknowledged by the Responder to ensure the connection remains valid.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Type: KeepAlive
Timestamp: &amp;lt;UTC Time&amp;gt;
Body: &amp;lt;Status Update or Simple Greeting&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="keepalive-handling"&gt;KeepAlive Handling&lt;/h3&gt;
&lt;p&gt;If a KeepAlive message is not acknowledged within a predefined timeout period, the connection SHOULD be considered lost, and both parties MAY reinitiate the connection if desired.&lt;/p&gt;
&lt;h2 id="termination"&gt;Termination&lt;/h2&gt;
&lt;p&gt;A connection CAN be terminated explicitly by either party at any time. Termination MAY be unilateral but MUST be communicated with a final message:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Type: Termination
Timestamp: &amp;lt;UTC Time&amp;gt;
Body: &amp;lt;Closure Explanation&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Upon receiving this message, the other party SHOULD acknowledge the termination, and the connection SHOULD be officially closed.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The Human Connections Protocol (HCP) standardizes human interaction, making it more predictable and reliable. By clearly defining the request, response, and keep-alive mechanisms, including the Relay option, HCP ensures smoother and more flexible interpersonal communication.&lt;/p&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Bradner, S. &amp;ldquo;Key words for use in RFCs to Indicate Requirement Levels&amp;rdquo;, BCP 14, RFC 2119, March 1997.&lt;/li&gt;
&lt;/ul&gt;</description><category>Fun</category></item><item><title>Winget made me lose 40 minutes of my life.</title><link>https://eliseomartelli.it/blog/2024-02-29-windows/</link><pubDate>Fri, 09 Feb 2024 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2024-02-29-windows/</guid><description>&lt;p&gt;Start rant.&lt;/p&gt;
&lt;p&gt;This is my first experience with Windows in a long while, and my first
experience with winget, the package manager.&lt;/p&gt;
&lt;p&gt;I have to use a Windows machine to test libraries and applications for my $job,
and I&amp;rsquo;m running it as a virtual machine on top of my arm64 macOS machine.&lt;/p&gt;
&lt;p&gt;I tried to install GnuMake, from the gnuwin32 project. You can do that by
running this command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cmd" data-lang="cmd"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;winget install -e --id GnuWin32.Make
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And I did so. Winget returned me to a prompt, and I assumed the installation
was successful.&lt;/p&gt;
&lt;p&gt;I quickly typed &amp;ldquo;make&amp;rdquo; into my prompt window, and I was &lt;em&gt;welcomed&lt;/em&gt; by:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;#39;make&amp;#39; is not recognized as an internal or external
command, operable program or batch file.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So, my first thought was to reload the environment. I tried again and I
got the same result.&lt;/p&gt;
&lt;p&gt;I then typed: &lt;code&gt;echo %PATH%&lt;/code&gt; to find out that winget didn&amp;rsquo;t add anything to the
PATH, and only after I explored my filesystem to add the entry to the PATH
manually, I noticed that winget didn&amp;rsquo;t make any changes to the filesystem.&lt;/p&gt;
&lt;p&gt;It failed without any notice, in an interactive user shell.&lt;/p&gt;
&lt;p&gt;Winget is an example of shit software.&lt;/p&gt;
&lt;p&gt;Recap:&lt;/p&gt;
&lt;p&gt;MICROSOFT&amp;rsquo;S OFFICIAL PACKAGE MANAGER, WHICH:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fails silently in an interactive shell.&lt;/li&gt;
&lt;li&gt;Doesn&amp;rsquo;t do what you tell it to.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AN OPERATING SYSTEM WHERE:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The standard is to have binaries scattered around the filesystem randomly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I hate winget (and windows) from the bottom of my heart.&lt;/p&gt;
&lt;p&gt;End rant.&lt;/p&gt;</description><category>Programming</category></item><item><title>Built and deployed a newsletter in an afternoon</title><link>https://eliseomartelli.it/blog/2023-12-02-newsletterquick/</link><pubDate>Sat, 02 Dec 2023 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2023-12-02-newsletterquick/</guid><description>&lt;p&gt;If you are a long-time reader of this blog, you might remember I already had
a newsletter. It was built upon Revue.&lt;/p&gt;
&lt;p&gt;Revue was a platform that allowed users to build a newsletter effortlessly. It
had a user-friendly UI and, as a great plus, it offered an API, so you could
build custom interfaces and integrate them with your site.&lt;/p&gt;
&lt;p&gt;Revue was later acquired by Twitter on January 26, 2021. After Elon Musk
&lt;del&gt;destroyed&lt;/del&gt; bought Twitter for the meme, and he decided to close it down.&lt;/p&gt;
&lt;p&gt;Subsequently, I tried to find an alternative to my rather modest newsletter
needs, but couldn&amp;rsquo;t find any that satisfied all my points.&lt;/p&gt;
&lt;p&gt;At the start of this year, I added &amp;ldquo;build a newsletter&amp;rdquo; to my to-do list, but
never had some spare &amp;ldquo;mental bandwidth&amp;rdquo; to do it. Until the 1st of December 2023.&lt;/p&gt;
&lt;h2 id="the-requirements"&gt;The requirements&lt;/h2&gt;
&lt;p&gt;The list of requirements for this project is rather small:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Users should be able to subscribe and unsubscribe easily;&lt;/li&gt;
&lt;li&gt;I should be able to Create Remove Update Delete (CRUD) a mail draft;&lt;/li&gt;
&lt;li&gt;I ultimately decide when to send the email;&lt;/li&gt;
&lt;li&gt;Users should be able to browse past issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now it was time to sketch the inner workings.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/2023-12-02-newsletterquick/schematic.png" alt="A working schematic"&gt;&lt;/p&gt;
&lt;h2 id="deciding-the-stack"&gt;Deciding the stack&lt;/h2&gt;
&lt;p&gt;After laying down the foundation, it was now time to decide which tools to use
to build the product.&lt;/p&gt;
&lt;p&gt;Since this site is built using the full-stack framework Next.JS, and content
is managed through Contentlayer, I just had to decide what to use to store
subscriptions and how to present past issues to the users.&lt;/p&gt;
&lt;p&gt;At this time, I&amp;rsquo;m deploying this site on Vercel. Vercel offers a suite of
products to meet user needs, one of the products that caught my attention
is &lt;a href="https://vercel.com/docs/storage/vercel-postgres"&gt;Vercel Postgres&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Vercel Postgres is a managed PostgreSQL database. Since I&amp;rsquo;m familiar with
Postgres, I decided to add it to the mix. This project is pretty small, and I
didn&amp;rsquo;t need an ORM, but, with the idea of building other features for this site,
I decided to go with Prisma because it has a type-safe query builder, and it
handles database migrations for me.&lt;/p&gt;
&lt;h2 id="building"&gt;Building&lt;/h2&gt;
&lt;p&gt;How simple can you make the database? Yes.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/2023-12-02-newsletterquick/db.png" alt="Database"&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s just two tables, with one column each. No relationships.
I told you I had basic requirements.&lt;/p&gt;
&lt;p&gt;The Prisma models are rather simple.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-prisma" data-lang="prisma"&gt;...
model Subscriber {
email String @id
}
model LastSent {
sent String @unique
}
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now I had to add a Contentlayer source to handle the issues.
I purposefully decided to use the filename of the issue to indicate the date
to minimize the number of custom fields needed (I just needed the title, later used as the email&amp;rsquo;s subject).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;export&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; Newsletter &lt;span style="color:#666"&gt;=&lt;/span&gt; defineDocumentType(() &lt;span style="color:#666"&gt;=&amp;gt;&lt;/span&gt; ({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt; name&lt;span style="color:#666"&gt;:&lt;/span&gt; &lt;span style="color:#4070a0"&gt;&amp;#34;Newsletter&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt; filePathPattern&lt;span style="color:#666"&gt;:&lt;/span&gt; &lt;span style="color:#4070a0"&gt;`newsletter/*.md`&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt; contentType&lt;span style="color:#666"&gt;:&lt;/span&gt; &lt;span style="color:#4070a0"&gt;&amp;#34;markdown&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt; fields&lt;span style="color:#666"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt; title&lt;span style="color:#666"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;type&lt;/span&gt;&lt;span style="color:#666"&gt;:&lt;/span&gt; &lt;span style="color:#4070a0"&gt;&amp;#34;string&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt; required: &lt;span style="color:#902000"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt;}));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From this point, building the past issues list was trivial.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; newsletterList &lt;span style="color:#666"&gt;=&lt;/span&gt; allNewsletters
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt; .sort((a, b) &lt;span style="color:#666"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;if&lt;/span&gt; (a._id &lt;span style="color:#666"&gt;&amp;lt;&lt;/span&gt; b._id) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#666"&gt;-&lt;/span&gt;&lt;span style="color:#40a070"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;if&lt;/span&gt; (a._id &lt;span style="color:#666"&gt;&amp;gt;&lt;/span&gt; b._id) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#40a070"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#40a070"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt; .reverse()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt; .map((newsletter, i) &lt;span style="color:#666"&gt;=&amp;gt;&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#062873;font-weight:bold"&gt;Link&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt; &lt;span style="color:#4070a0"&gt;href&lt;/span&gt;&lt;span style="color:#666"&gt;=&lt;/span&gt;{&lt;span style="color:#4070a0"&gt;`/&lt;/span&gt;&lt;span style="color:#70a0d0"&gt;${&lt;/span&gt;newsletter._id&lt;span style="color:#70a0d0"&gt;}&lt;/span&gt;&lt;span style="color:#4070a0"&gt;`&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt; &lt;span style="color:#4070a0"&gt;key&lt;/span&gt;&lt;span style="color:#666"&gt;=&lt;/span&gt;{i}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16&lt;/span&gt;&lt;span&gt; &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#062873;font-weight:bold"&gt;p&lt;/span&gt;&amp;gt;{newsletter.title}&amp;lt;/&lt;span style="color:#062873;font-weight:bold"&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18&lt;/span&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#062873;font-weight:bold"&gt;Link&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19&lt;/span&gt;&lt;span&gt; ))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can find all the relative UI code &lt;a href="https://github.com/eliseomartelli/eliseomartelli.it/tree/b1a8d0e0abc8e3cbeffa1218a1326194a5ab27a6/src/app/newsletter"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It was time to tie it all together and start sending emails.&lt;/p&gt;
&lt;p&gt;Firstly, I added three (well, to be honest, four) API routes to my next app:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/api/newsletter/subscribe/[email]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/newsletter/unsubscribe/[email]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/newsletter/send&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/newsletter/test&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then I started writing some logic to make the newsletter work.&lt;/p&gt;
&lt;p&gt;After retrieving the last newsletter from Contentlayer, I check if the email was already sent.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; sent &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;await&lt;/span&gt; prisma.lastSent.findFirst({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt; where&lt;span style="color:#666"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt; sent&lt;span style="color:#666"&gt;:&lt;/span&gt; { equals: &lt;span style="color:#902000"&gt;newsletter._id&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4&lt;/span&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5&lt;/span&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Consequently, I retrieve the list of subscribers and send the newsletter.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;subscribers.map(&lt;span style="color:#007020;font-weight:bold"&gt;async&lt;/span&gt; (e) &lt;span style="color:#666"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;await&lt;/span&gt; transporter.sendMail({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;from&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4&lt;/span&gt;&lt;span&gt; to: &lt;span style="color:#902000"&gt;e.email&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5&lt;/span&gt;&lt;span&gt; subject: &lt;span style="color:#902000"&gt;newsletter.title&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6&lt;/span&gt;&lt;span&gt; html: &lt;span style="color:#902000"&gt;newsletter.body.html&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7&lt;/span&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;8&lt;/span&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Similar logic is used for subscribe and unsubscribe API routes, obviously
changing the checks.&lt;/p&gt;
&lt;p&gt;You can find the full code for the API routes &lt;a href="https://github.com/eliseomartelli/eliseomartelli.it/tree/b1a8d0e0abc8e3cbeffa1218a1326194a5ab27a6/src/app/api/newsletter"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now I can own my newsletter infrastructure and I don&amp;rsquo;t have to pay attention to
commercial decisions by millionaires.&lt;/p&gt;</description><category>Programming</category></item><item><title>Some notes on Polaroid Batteries</title><link>https://eliseomartelli.it/blog/2023-12-01-polainvestigation/</link><pubDate>Fri, 01 Dec 2023 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2023-12-01-polainvestigation/</guid><description>&lt;p&gt;Since I recently got into the vintage Polaroid hobby, I started researching how
I can be less wasteful with the material needed to take pictures with those
cameras.&lt;/p&gt;
&lt;p&gt;One of the researches I conducted is on battery packs for the Polaroid (or
how to not waste a perfectly good battery with each film cartridge).&lt;/p&gt;
&lt;h2 id="the-old"&gt;The old&lt;/h2&gt;
&lt;p&gt;Back in 1947, Edwin H. Land introduced the Polaroid-Land process. In a nutshell,
the negative film gets exposed inside the camera. It then gets squeezed with rollers, which spread a reagent between the two layers.
The reagent is inside a pod present in each film slide.&lt;/p&gt;
&lt;p&gt;The
&lt;a href="https://www.acs.org/education/whatischemistry/landmarks/land-instant-photography.html#introduction_of_instant_photography"&gt;piccancle&lt;/a&gt;
of this invention was the Polaroid SX-70 system, released in 1972.&lt;/p&gt;
&lt;p&gt;One of the innovations brought by the SX-70 system was the PolaPulse battery
present in the film cartridge.&lt;/p&gt;
&lt;p&gt;The PolaPulse battery used zinc-chloride chemistry to operate with the cameras
requirements of high-current demand for the motors.&lt;/p&gt;
&lt;p&gt;The battery used inside the SX-70 film packs was called P80. More info &lt;a href="http://users.rcn.com/fcohen/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="the-impossible-project"&gt;The Impossible Project&lt;/h2&gt;
&lt;p&gt;When The Impossible Project took over the production of Polaroid film, it
started using LiMnO2 batteries in its film packs. In particular, they started
using two CP225040N cells. More info
&lt;a href="https://gmbattery.com/dl/cp4/CP/CP225040.pdf"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The new batteries hold a higher capacity but a lower maximum pulse discharge.&lt;/p&gt;</description><category>Electronics</category><category>Photography</category><category>Short</category></item><item><title>I bought some Polaroids</title><link>https://eliseomartelli.it/blog/2023-11-27-polaroid/</link><pubDate>Mon, 27 Nov 2023 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2023-11-27-polaroid/</guid><description>&lt;p&gt;I kept a close eye on Polaroids since news about &lt;a href="https://en.wikipedia.org/wiki/Polaroid_B.V."&gt;&amp;ldquo;The Impossible Project&amp;rdquo;&lt;/a&gt; started surfacing (I was 12 at the time).&lt;/p&gt;
&lt;p&gt;I never pulled the trigger and bought a Polaroid camera because I was always scared by the film cartridge prices, but last Saturday, I changed that.&lt;/p&gt;
&lt;p&gt;I was reaching the city center with my brother on foot after visiting some friends at &lt;em&gt;Museo Ferroviario Piemontese&lt;/em&gt;, working at the workshop near Ponte Mosca in Turin.
We were already near Porta Palazzo and the historical flea market called &lt;a href="https://it.wikipedia.org/wiki/Balon"&gt;Balon&lt;/a&gt;, a staple of the Aurora district, and after getting lost in the color, smells, lights, and sounds of that market, we reached the booth of a vintage camera and watches seller.&lt;/p&gt;
&lt;p&gt;When we arrived, the seller was already packing back the goods for the day, but, in the rightmost corner of the stand, on top of a velvety red cloth, there were two Polaroid cameras: a Polaroid Land 1000 Camera (with a green button), and a Polaroid 636 Autofocus.&lt;/p&gt;
&lt;p&gt;I made an impromptu decision, asked the seller for a price, and then quickly bought the two cameras (for €15 each).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/polaroids/both.jpeg" alt="The cameras"&gt;&lt;/p&gt;
&lt;p&gt;It was now time to learn some more about the cameras, and after an &lt;em&gt;extreme&lt;/em&gt; Wikipedia and Reddit &lt;a href="https://www.reddit.com/r/Polaroid/"&gt;(r/Polaroid)&lt;/a&gt; search, I found out that the cameras accepted SX-70 film and 600 films, respectively.&lt;/p&gt;
&lt;p&gt;The side-quest for the film was hilarious in its way. I first went to a local electronics retailer, Unieuro, where a Polaroid salesman told me I could get I-type film for my camera. Fortunately, I-type film has a big sticker with &amp;ldquo;Not for vintage cameras&amp;rdquo; written on it, so I backed out and went to another store: La Rinascente.&lt;/p&gt;
&lt;p&gt;La Rinascente was running a sale for Polaroid film, and it was all out-of-stock, so I got out and walked into another shop. Il Grande Marvin. We found some expired SX-70 film, but nothing for my 636. It made me a bit sad. It later occurred to me to check on Amazon, where, surprisingly, I found some 600 films that I immediately ordered.&lt;/p&gt;
&lt;p&gt;At home, we put the SX-70 pack inside the Land 1000. We were in awe at the whirring noises coming out of it. I imagine it was a &amp;ldquo;second birth&amp;rdquo; for that camera since it probably wasn&amp;rsquo;t used in the last 20 to 30 years.
The next day, the film pack for my camera reached our door. I placed it inside the camera and closed the film flap. It whirred. It was as magical as the other one. When I saw the dark slide coming out and getting covered by the &lt;em&gt;tongue&lt;/em&gt;, I was ecstatic. I told my brother to take a picture of me, and the camera flashed. After some milliseconds, the slide got ejected.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/polaroids/polaroid.jpg" alt="Hey! A picture of me!"&gt;
&lt;em&gt;Hey! A picture of me!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I went for a walk with my brother later, when he took a chance and took a shot with his camera.
We heard whirring noises. We had another winner.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/polaroids/polaroid2.jpeg" alt="Riva del Po"&gt;
&lt;em&gt;Riva del Po&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I fell into the Polaroid rabbit hole now. I&amp;rsquo;m planning to mod my camera to use new I-type film. I&amp;rsquo;ll write another post if I succeed in that!&lt;/p&gt;</description><category>Photography</category><category>Short</category></item><item><title>Things happen</title><link>https://eliseomartelli.it/blog/2023-11-25-things-happen/</link><pubDate>Sat, 25 Nov 2023 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2023-11-25-things-happen/</guid><description>&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/happens/happens.jpg" alt="Graduated"&gt;&lt;/p&gt;
&lt;p&gt;A few weeks ago, I was reading The Beekeeper by Maxence Fermine.
It&amp;rsquo;s a book about a boy who has always dreamed big,
and as I scrolled my eyes through those lines, it seemed like I already knew
that boy.&lt;/p&gt;
&lt;p&gt;The essential part of the story is that this fella, against the thoughts of the
people in his small village, started his beekeeping business.
His first season ended abruptly with a fire,
so he decided to travel to seek gold, which, in his case, was honey.&lt;/p&gt;
&lt;p&gt;One thing that united his expeditions was that, every time,
Aurelien found himself in a convoy of travelers, and even if these expeditions didn&amp;rsquo;t succeed, he still had a wealth of experiences to bring home.&lt;/p&gt;
&lt;p&gt;These convoys are a bit like all the people I met in my life.
Each person has made a substantial contribution to how I see things.&lt;/p&gt;
&lt;p&gt;The first expeditions laid the foundations for
growth and shaped me into what I am today
(a bit of a dreamer and a bit foolish).&lt;/p&gt;
&lt;p&gt;When I arrived in Turin,
a bit disoriented,
I tried to anchor myself to new people,
who with so much patience supported and endured me, for which I thank you.&lt;/p&gt;
&lt;p&gt;Aurelien&amp;rsquo;s story concludes with him actually finding gold.
It was right in front of his eyes.&lt;/p&gt;
&lt;p&gt;I would like to express my appreciation to my family for their support and
encouragement throughout this journey. Their love, understanding, and belief in
me have been a constant source of inspiration. I would also like to extend my
thanks to my friends. Their encouragement has been invaluable, and I am
grateful for their friendship. To all, thank you.&lt;/p&gt;
&lt;p&gt;I graduated.&lt;/p&gt;</description><category>Misc</category><category>Short</category></item><item><title>RANT: Photography Influencers Should Be Stopped.</title><link>https://eliseomartelli.it/blog/2023-11-02-photorant/</link><pubDate>Thu, 09 Nov 2023 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2023-11-02-photorant/</guid><description>&lt;p&gt;&amp;lt;rant&amp;gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s so sad to see photography
influencers giving misleading
advice on this platform. One recent example?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I only shoot with my ISO 800 or
higher&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This statement doesn&amp;rsquo;t tell the
full story. ISO, like shutter speed and
aperture, is context dependendant.&lt;/p&gt;
&lt;p&gt;The lesson should be to assess the
situation and then make informed
decisions on what settings to pick.&lt;/p&gt;
&lt;p&gt;Giving away &amp;ldquo;magical settings&amp;rdquo; will
influence a new generation of &amp;ldquo;bad&amp;rdquo;
photographers.&lt;/p&gt;
&lt;p&gt;Assess, set, shoot.&lt;/p&gt;
&lt;p&gt;&amp;lt;/rant&amp;gt;&lt;/p&gt;</description><category>Photography</category><category>Short</category></item><item><title>I'm now 25.</title><link>https://eliseomartelli.it/blog/2023-09-01-happybday/</link><pubDate>Fri, 01 Sep 2023 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2023-09-01-happybday/</guid><description>&lt;p&gt;A quick disclaimer: I will not talk specifically about technology, photography or what else. This is about me. This is about what is the perception of me at the moment and what I’m asking from me for the future. You are looking at an open letter to my future self, but please, if you feel like it, go ahead and read it!&lt;/p&gt;
&lt;video muted autoPlay loop playsInline&gt;
&lt;source src='https://eliseomartelli.it/posts/25/25.mov' type="video/mp4"/&gt;
&lt;/video&gt;
&lt;p&gt;Hey, today I’m turning twenty-five, and, to put it in Taylor Swift’s words, I feel like we are “Dancing With Our Hands Tied”.&lt;/p&gt;
&lt;p&gt;I’m currently in a stretch of time of questioning. One of the questions that is always roaming free in my mind is a question of purpose. Sometimes, out of a sheer of confusion, I feel like I’ve lost purpose.&lt;/p&gt;
&lt;p&gt;As an example, a thing that brought joy to me, for a major part of my life, was coding and programming. Nowadays, I only do it out of necessity. I only deployed two personal projects this past year. Yeah, I know, I had many (lots of many) other tasks to accomplish, and all personal coding projects fall out of importance for me. That’s worrying since I’m a Computer Science student, but, nonetheless, it feels great to not spend my free time in front of a blinking cursor on vim (R.I.P. Bram Moolenaar) on a computer screen.&lt;/p&gt;
&lt;p&gt;The little free time I have is now well spent on photography, hanging out with friends (that I’m really grateful for), making pizza (I’m getting pretty good at it), or, more recently, learning Japanese on Duolingo. こんにちわ！&lt;/p&gt;
&lt;p&gt;I realised that the best thing for my mental health is to avoid setting myself up to unrealistic standards, and, sometimes, have a little positive self-talk to look back on accomplishments. I can’t still move away from thinking of being the worst in the room.&lt;/p&gt;
&lt;p&gt;Don’t get me wrong, this way of thinking has some positive connotations, but I’m still trying to find a healthy balance and to embrace humility without disregarding my own value.&lt;/p&gt;
&lt;p&gt;To be honest, I’m not feeling good with my body (f*ck Hashimoto&amp;rsquo;s thyroiditis) but that’s a given. I have a bad year followed by a good year, with weight oscillations (of around 10 kg) that sometimes make me feel that my body isn&amp;rsquo;t really mine and a face that sometimes I don&amp;rsquo;t recognize.&lt;/p&gt;
&lt;p&gt;The past month was a month of changes. Heck, this year was a year of changes. The pages of my life turned, challenging myself to adapt, learn and, I think, grow.&lt;/p&gt;
&lt;p&gt;I had to make profound self-reflection to reshape the landscape of my emotions and connections.&lt;/p&gt;
&lt;p&gt;I’m now 25.&lt;/p&gt;
&lt;img src="https://eliseomartelli.it/posts/25/b.jpg" alt="" class="shadow-md"/&gt;
&lt;img src="https://eliseomartelli.it/posts/25/c.jpg" alt="" class="shadow-md"/&gt;</description><category>Misc</category><category>Short</category></item><item><title>Stories &amp; Telegram</title><link>https://eliseomartelli.it/blog/2023-07-21-stories/</link><pubDate>Fri, 21 Jul 2023 18:40:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2023-07-21-stories/</guid><description>&lt;p&gt;Today marks the day Telegram decided to roll out Stories (but only for Telegram
Premium users at the moment).&lt;/p&gt;
&lt;p&gt;It adds to the (already long) list of apps having this &amp;ldquo;feature&amp;rdquo;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;echo&lt;/span&gt; &lt;span style="color:#4070a0"&gt;&amp;#34;Telegram&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; apps_with_stories
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s the right time to post a quote from a &lt;a href="https://nomnp.com"&gt;friend of mine&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Every program attempts to expand until it can show stories. Those
programs which cannot so expand are replaced by ones which can.&lt;/p&gt;
&lt;p&gt;&lt;cite&gt;from: &lt;a href="https://nomnp.com"&gt;nomnp.com&lt;/a&gt; &lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &amp;ldquo;stories&amp;rdquo; functionality in apps has become increasingly popular over the
years, and it originated on Snapchat in 2013.
They play on the unapologetically human fear of missing out, creating a sense
of urgency among viewers. This brings an increase of interaction time across
the platform. This is all part of the increasing need to share real-time
updates.&lt;/p&gt;
&lt;p&gt;We don&amp;rsquo;t need stories in messaging apps.&lt;/p&gt;</description><category>Repost</category><category>Short</category></item><item><title>[Un]MuteMic but make it 2023</title><link>https://eliseomartelli.it/blog/2023-07-09-cocoa/</link><pubDate>Sun, 09 Jul 2023 19:40:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2023-07-09-cocoa/</guid><description>&lt;p&gt;[Un]MuteMic is one of my favorite macOS utility apps. It&amp;rsquo;s simple: a button
placed on the menu bar that lets you toggle the microphone status.
Since we are in the age of teleconferencing, it&amp;rsquo;s a handy app, but
it got compiled a long time ago, about four years B.A.S. (Before Apple Silicon).&lt;/p&gt;
&lt;p&gt;One of the first things I did after I got my new MacBook was to compile the app
for aarch64. It worked great!&lt;/p&gt;
&lt;p&gt;Then I started adding a feature: global hotkey. I added this feature on the
existing codebase, initially written in Objective-C.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve never really bonded with Objective-C. I find the syntax unnecessarily
verbose, so two days ago, I decided to rewrite the entire app in Swift and
SwiftUI.&lt;/p&gt;
&lt;p&gt;At WWDC2022, Apple introduced
&lt;a href="https://developer.apple.com/documentation/SwiftUI/MenuBarExtra"&gt;MenuBarExtra&lt;/a&gt;,
a Scene that renders a persistent item in the system menu bar.
It seemed appropriate to rewrite the app using this new Scene.&lt;/p&gt;
&lt;div class="product-card" style="border: 1px solid #ddd; border-radius: 8px; padding: 1rem; margin: 1.5rem 0; display: flex; flex-direction: column; gap: 0.75rem;"&gt;
&lt;div style="display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap;"&gt;
&lt;h4 style="margin: 0; display: flex; align-items: center; gap: 0.5rem;"&gt;
[Un]MuteMic
&lt;/h4&gt;
&lt;a href="https://github.com/eliseomartelli/MuteUnmuteMic" class="btn-primary" target="_blank" rel="noopener" style="padding: 0.4rem 0.8rem; border-radius: 4px; background: var(--link-color, #a81d27); color: #fff; text-decoration: none; font-weight: bold; font-size: 0.9rem;"&gt;
GitHub
&lt;/a&gt;
&lt;/div&gt;
&lt;div style="font-size: 0.95rem; opacity: 0.9; line-height: 1.4;"&gt;
&lt;p&gt;macOS app to mute &amp;amp; unmute the input volume of your microphone.
Perfect for podcasters.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;brew install eliseomartelli/eliseomartelli/muteunmute
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="changelog"&gt;Changelog:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The app is compiled as a Universal Binary, no more Rosetta!&lt;/li&gt;
&lt;li&gt;Complete rewrite in Swift and SwiftUI using MenuBarExtra.&lt;/li&gt;
&lt;li&gt;Accessibility Service so you can decide to have a global hotkey.&lt;/li&gt;
&lt;li&gt;The app icon is redesigned. It feels right at home on modern macOS.&lt;/li&gt;
&lt;li&gt;What input am I using? Now you can know.&lt;/li&gt;
&lt;li&gt;SFSymbols so everything is pixel-perfect.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The app in action:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/muteunmute/shot.png" alt="UnMuteMic in action"&gt;&lt;/p&gt;</description><category>Programming</category><category>Short</category></item><item><title>Ingredients for a great picture</title><link>https://eliseomartelli.it/blog/2023-07-07-informationemotion/</link><pubDate>Fri, 07 Jul 2023 19:40:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2023-07-07-informationemotion/</guid><description>&lt;p&gt;Photography is not just a 3-tuple of light, time and space. Photography is a
whole art form involving creativity, composition, storytelling, and the
ability to convey meaning and evoke emotions. A photograph is an
interpretation of reality by the photographer.&lt;/p&gt;
&lt;p&gt;A great photo needs at least two more ingredients:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Emotions: they play a crucial role in human experience, they influence our
perception.&lt;/li&gt;
&lt;li&gt;Information: data presented in a meaningful way, providing insight about a
particular subject.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The interplay between emotions and information in photography enhances the
impact and resonance of the image.&lt;/p&gt;</description><category>Photography</category><category>Short</category></item><item><title>Featured posts with 100% more AI</title><link>https://eliseomartelli.it/blog/2023-05-13-onehundredpercent-more-ai/</link><pubDate>Sat, 13 May 2023 21:30:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2023-05-13-onehundredpercent-more-ai/</guid><description>&lt;p&gt;I&amp;rsquo;ll admit it: having static &amp;ldquo;Featured Posts&amp;rdquo; for my blog articles was boring.
We left the 2010s for a while. All the young kids are now talking about LLMs, GPTs, world destruction, and strange fingers on AI-generated images that I couldn&amp;rsquo;t help but feel missing out.&lt;/p&gt;
&lt;p&gt;So I did the dumbest thing ever: I added AI-suggested featured posts for a site that has, at the moment, 17 articles.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;~/personalsite $ ls data/blog | wc -l
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#40a070"&gt;17&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first idea I got was to implement a scoring system that took account of the potential interests of the people looking at this site. I then intended to embed that score in each post metadata. Generating the Featured posts section was then easily achieved by ordering posts by that score and picking the top three entries.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;You&amp;#39;re given the text from an article geared toward an audience that is
techy, and generally has interests in computer science, electronics,
music, photography, art, and productivity.
You will calculate an interest score based on the intended audience.
The interest score is a numeric value with one decimal position after
the dot.
The range of the interest score is between 0 and 1.
The response format is JSON and will look like this.
{ &amp;#34;interest&amp;#34;: &amp;#34;&amp;lt;interest&amp;gt;&amp;#34;, &amp;#34;explaination&amp;#34;: &amp;#34;&amp;lt;explaination&amp;gt;&amp;#34; }
What&amp;#39;s the response to the text below? Respond only with the specified
format. Don&amp;#39;t add anything more.
{text}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This prompt kind of worked. The main problem was that it did not solve the first requirement: suggesting more posts for users.&lt;/p&gt;
&lt;p&gt;It was a clear example of &amp;ldquo;blind prompting&amp;rdquo;; for the effect of witchcraft, it worked reasonably well.&lt;/p&gt;
&lt;p&gt;My next idea was to instruct GPT to pick three articles based purely on a (descriptive) filename.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Given this list of filenames:
{filenames}
pick three files you think are most important based on this {file}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;How naïve of me. LLMs don&amp;rsquo;t think. GPT-3.5 started spitting filenames left and right, often not even matching the provided list, prefixing everything with an explanation of why it couldn&amp;rsquo;t do it (ironic, I know).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As an AI language model, I cannot make subjective decisions based on importance as it is relative to individual preferences and interests. However, I can provide suggestions based on the popularity of the articles on the internet.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I then stumbled upon Mitchell Hashimoto&amp;rsquo;s &lt;a href="https://mitchellh.com/writing/prompt-engineering-vs-blind-prompting"&gt;&amp;ldquo;Prompt Engineering vs. Blind Prompting&amp;rdquo;&lt;/a&gt; and &lt;a href="https://lilianweng.github.io/posts/2023-03-15-prompt-engineering/"&gt;&amp;ldquo;Prompt Engineering&amp;rdquo;&lt;/a&gt; by Lilian Weng.&lt;/p&gt;
&lt;p&gt;These two posts gave me some pointers on how to improve my prompt. The second-last iteration of the prompt was:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;You are an editor for a site assigned to pick 3 articles from the possible filenames given a filename. The site treats arguments spanning music, photography, technology, software development, and life.
Output only plain text. No explanation. One filename per line. Don&amp;#39;t change the filename.
The list of possible filenames is this:
{filenames}
Pick files based on this one: {file}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And it &lt;em&gt;kind of&lt;/em&gt; worked until it didn&amp;rsquo;t. During some requests, it added text surrounding the filenames (even after stating that it shouldn&amp;rsquo;t have done that).&lt;/p&gt;
&lt;p&gt;The last iteration includes a level of imperative I wouldn&amp;rsquo;t use during usual conversations, but it is highly effective when interfacing with LLMs.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Pick 3 filenames from the possible filenames.
The files are about topics spanning music,
photography,
technology,
software development,
and life.
RULES:
- OUTPUT plain text ONLY.
- OUTPUT exact filename ONLY.
- OUTPUT one filename per line.
- MUST NOT include explanations.
- MUST NOT include current filename.
- MUST NOT include any other text.
Possible filenames:
{filenames}
Current filename: {file}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Oh, and all caps, lots of all caps.&lt;/p&gt;
&lt;p&gt;It started to work wonders!&lt;/p&gt;
&lt;p&gt;I then created a Python script to proxy requests to GPT-3.5 (I want to use this proxy for other projects as well), packaged it all up, and deployed it on &lt;a href="https://fly.io"&gt;fly.io&lt;/a&gt;. I&amp;rsquo;m using &lt;code&gt;aiohttp&lt;/code&gt; and OpenAI&amp;rsquo;s Python library.&lt;/p&gt;
&lt;p&gt;I call this proxy from a Next.JS API route that doesn&amp;rsquo;t do much other than some output filtering.&lt;/p&gt;
&lt;p&gt;And now, I&amp;rsquo;ll leave you to enjoy this AI feature. I&amp;rsquo;ll add the obligatory &amp;ldquo;AI Prompt Engineer&amp;rdquo; to my resume soon™️.&lt;/p&gt;</description><category>AI</category><category>Misc</category><category>Short</category></item><item><title>I hate my coffee machine</title><link>https://eliseomartelli.it/blog/2023-05-03-coffee/</link><pubDate>Wed, 03 May 2023 19:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2023-05-03-coffee/</guid><description>&lt;p&gt;As a young adult, I made one of the most characterizing purchases for my apartment: A coffee machine.&lt;/p&gt;
&lt;p&gt;As a &lt;em&gt;gen-zer&lt;/em&gt;, I went to Amazon and searched for coffee machines. After reading some reviews, the coffee machine I decided to buy is a Gaggia Viva Style, a modest appliance that, at least in theory, should get the job done.&lt;/p&gt;
&lt;p&gt;And the machine did well, but only for the first two months, then it decided to start leaking from the handle, and it stopped dispensing coffee.&lt;/p&gt;
&lt;h2 id="a-little-chat-with-amazon-support"&gt;A little chat with Amazon support&lt;/h2&gt;
&lt;p&gt;After contacting Amazon support about this problem, they advised me that the handle is a consumable part. They couldn&amp;rsquo;t assist with this component (even if it&amp;rsquo;s a fundamental part of the machine). Their follow-up was: &amp;ldquo;Buy a new handle.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;After checking the price of the said handle, I decided to have my go at tearing it down. I discovered that the sub-component giving me trouble was the &amp;ldquo;pressurized filter-holder.&amp;rdquo; It was trivially swappable, so I went to Gaggia&amp;rsquo;s site, and to my astonishment, they only sell the complete handle.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/lLfOd8rRMo0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;I then went on a journey for third-party sellers (online and not), trying to find this part, but none carried a genuine part (Gaggia doesn&amp;rsquo;t sell those).&lt;/p&gt;
&lt;p&gt;I decided to cave in and bought a new handle from a local seller. It went brilliantly (but only for two months). Then it exhibited the same problems as the original unit shipped with the machine.&lt;/p&gt;
&lt;h2 id="environmental-issues-and-right-to-repair"&gt;Environmental issues and right to repair&lt;/h2&gt;
&lt;p&gt;Gaggia designed one of their most popular machines with a flaw regarding the pressurized filter holder, but it&amp;rsquo;s ok, we are humans, and we all can make errors. The infuriating part is that their support channels direct you to buy a part costing 1/3 of the machine, spotting the same design issues as the original.&lt;/p&gt;
&lt;p&gt;This issue means producing and disposing of a composed piece of plastic, Teflon, and steel, with a consequent environmental impact, when they can sell the individual part (made of Teflon).&lt;/p&gt;
&lt;p&gt;I want to repair my machine. I want my coffee.&lt;/p&gt;
&lt;hr&gt;
&lt;div class="mastodon-toot" style="margin: 1.5rem 0; display: flex; justify-content: center;"&gt;
&lt;iframe src="https://mastodon.social/@eliseomartelli/110321082822817576/embed" class="mastodon-embed" style="max-width: 100%; border: 0; width: 400px; height: 350px;" allowfullscreen="allowfullscreen"&gt;&lt;/iframe&gt;
&lt;script src="https://mastodon.social/embed.js" async="async"&gt;&lt;/script&gt;
&lt;/div&gt;</description><category>Misc</category><category>Short</category></item><item><title>YouTube's PiP but with ⌥+P</title><link>https://eliseomartelli.it/blog/2023-04-27-youtube-pip/</link><pubDate>Thu, 27 Apr 2023 22:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2023-04-27-youtube-pip/</guid><description>&lt;p&gt;I was watching a video on YouTube and I realized that there wasn&amp;rsquo;t a way to invoke the picture-in-picture functionality with a shortcut (at least on Safari 16.4).&lt;br&gt;
My fingers rested on ⌥+P, so it made sense to add a shortcut to toggle the picture-in-picture with that key combination.&lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;m already using UserScripts, the most straightforward thing to do was writing an userscript.&lt;/p&gt;
&lt;div class="product-card" style="border: 1px solid #ddd; border-radius: 8px; padding: 1rem; margin: 1.5rem 0; display: flex; flex-direction: column; gap: 0.75rem;"&gt;
&lt;div style="display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap;"&gt;
&lt;h4 style="margin: 0; display: flex; align-items: center; gap: 0.5rem;"&gt;
&lt;img src="https://is5-ssl.mzstatic.com/image/thumb/Purple116/v4/c1/4f/47/c14f4735-d7be-1199-a6e7-6701e1d28bfa/AppIcon-85-220-4-2x.png/246x0w.webp" width="48rem" alt="UserScripts icon"/&gt; UserScripts
&lt;/h4&gt;
&lt;a href="https://apps.apple.com/it/app/userscripts/id1463298887" class="btn-primary" target="_blank" rel="noopener" style="padding: 0.4rem 0.8rem; border-radius: 4px; background: var(--link-color, #a81d27); color: #fff; text-decoration: none; font-weight: bold; font-size: 0.9rem;"&gt;
Open
&lt;/a&gt;
&lt;/div&gt;
&lt;div style="font-size: 0.95rem; opacity: 0.9; line-height: 1.4;"&gt;
An open source Safari extension that lets you save and run arbitrary bits of JavaScript (and CSS) code for the websites you visit.
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The code is pretty simple, but it gets the job done and, most importantly, saves me a few clicks.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt; * Key combination to use.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt; *
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt; * @param {KeyboardEvent} e - Keyboard Event
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; KEY_COMBINATION &lt;span style="color:#666"&gt;=&lt;/span&gt; (e) =&amp;gt; e.code &lt;span style="color:#666"&gt;===&lt;/span&gt; &lt;span style="color:#4070a0"&gt;&amp;#34;KeyP&amp;#34;&lt;/span&gt; &lt;span style="color:#666"&gt;&amp;amp;&amp;amp;&lt;/span&gt; e.altKey;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt;(&lt;span style="color:#007020;font-weight:bold"&gt;function&lt;/span&gt; () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt; &lt;span style="color:#4070a0"&gt;&amp;#34;use strict&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; video &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#007020"&gt;document&lt;/span&gt;.querySelector(&lt;span style="color:#4070a0"&gt;&amp;#34;video&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020"&gt;document&lt;/span&gt;.onkeydown &lt;span style="color:#666"&gt;=&lt;/span&gt; (e) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;if&lt;/span&gt; (KEY_COMBINATION(e)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;const&lt;/span&gt; isPiP &lt;span style="color:#666"&gt;=&lt;/span&gt; &lt;span style="color:#007020"&gt;document&lt;/span&gt;.pictureInPictureElement &lt;span style="color:#666"&gt;!==&lt;/span&gt; &lt;span style="color:#007020;font-weight:bold"&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt; video.webkitSetPresentationMode(isPiP &lt;span style="color:#666"&gt;?&lt;/span&gt; &lt;span style="color:#4070a0"&gt;&amp;#34;inline&amp;#34;&lt;/span&gt; &lt;span style="color:#666"&gt;:&lt;/span&gt; &lt;span style="color:#4070a0"&gt;&amp;#34;picture-in-picture&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16&lt;/span&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17&lt;/span&gt;&lt;span&gt;})();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you are using an extension that supports userscripts, you can install this extension using the box below.&lt;/p&gt;
&lt;div class="product-card" style="border: 1px solid #ddd; border-radius: 8px; padding: 1rem; margin: 1.5rem 0; display: flex; flex-direction: column; gap: 0.75rem;"&gt;
&lt;div style="display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap;"&gt;
&lt;h4 style="margin: 0; display: flex; align-items: center; gap: 0.5rem;"&gt;
YouTube PiP Shortcut
&lt;/h4&gt;
&lt;a href="https://raw.githubusercontent.com/eliseomartelli/UserScript-YouTube-PiP/main/yt-pip.js" class="btn-primary" target="_blank" rel="noopener" style="padding: 0.4rem 0.8rem; border-radius: 4px; background: var(--link-color, #a81d27); color: #fff; text-decoration: none; font-weight: bold; font-size: 0.9rem;"&gt;
Install
&lt;/a&gt;
&lt;/div&gt;
&lt;div style="font-size: 0.95rem; opacity: 0.9; line-height: 1.4;"&gt;
Open PiP in YouTube with a shortcut
&lt;/div&gt;
&lt;/div&gt;</description><category>Misc</category><category>Short</category></item><item><title>On: solutions</title><link>https://eliseomartelli.it/blog/2023-04-05-masters/</link><pubDate>Wed, 05 Apr 2023 21:30:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2023-04-05-masters/</guid><description>&lt;p&gt;Last week I got stuck on a reverse engineering exercise (using GDB to analyze memory content is a lot of fun. The solution was changing the choice of the starting offset from where to start to view the memory. I was reading &amp;ldquo;no-sense&amp;rdquo; instructions, and I felt stuck).&lt;/p&gt;
&lt;p&gt;I talked to the course teacher, and he didn&amp;rsquo;t provide an immediate solution. He just told me to keep it simple. At that time, I felt even more frustrated. In theory, the steps I was taking were correct. I was missing a little piece.&lt;/p&gt;
&lt;p&gt;Yesterday I finally solved the exercise, and I remembered a quote from a book I read last month:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;hellip;I once asked him why the Master had looked on so long at my futile efforts to draw the bow “spiritually” why he had not insisted on the correct breathing right from the the start. “A great Master”, he replied, “must also be a great teacher. &amp;hellip;Had he begun the lessons with breathing exercises, he would never have been able to convince you that you owe them anything decisive. You had to suffer shipwreck through your own efforts before you were ready to seize the lifebelt he threw you. &amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;cite&gt;from:
Zen in the Art of Archery -
Eugen Herrigel&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Had he told me immediately what I was missing, I could&amp;rsquo;ve concluded the exercise quickly, there&amp;rsquo;s no doubt, but I would&amp;rsquo;ve completely missed the lesson that, sometimes, it&amp;rsquo;s best to have a more expansive vision of the problem and that in many instances, answers are indeed simple.&lt;/p&gt;</description><category>Short</category><category>Studies</category></item><item><title>The joy of photography in the age of social media</title><link>https://eliseomartelli.it/blog/2022-12-07-photostruggles/</link><pubDate>Wed, 07 Dec 2022 21:30:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2022-12-07-photostruggles/</guid><description>&lt;p&gt;I was listening to a podcast about street photography, and the presenter mentioned Vivian Maier.&lt;br&gt;
If you don&amp;rsquo;t know Vivian Maier, she was a street photographer from New York City who was active in the mid-20th century.&lt;br&gt;
Her pictures, never published during her life, were discovered by some collectors and later published.&lt;br&gt;
Her work is candid, intimate, and powerful. She had an innate ability to capture moments and emotions on the streets.&lt;/p&gt;
&lt;p&gt;I think that, as humans, we are fortunate to be able to see her work now. If we think about it, she took photos only for herself.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s what I want back from my photography.&lt;/p&gt;
&lt;p&gt;I was talking to buddying photographers (&lt;a href="https://instagram.com/__the_street"&gt;@__the_street&lt;/a&gt; and &lt;a href="https://instagram.com/sacks"&gt;@sacks&lt;/a&gt;, both of them met on the internet, about battling with the Instagram algorithm.&lt;/p&gt;
&lt;p&gt;As someone who enjoys sharing my work with people (yeah, maybe I&amp;rsquo;m chasing feedback, I recognize that), it can be highly frustrating to battle against the algorithm. It&amp;rsquo;s unpredictable and designed to boost the in-app engagement of the particular platform. Publishing high-quality content is often not enough, and often, your posts get ignored and never shown to people.&lt;/p&gt;
&lt;p&gt;For example, right now, Instagram is pushing Reels to its users. Short and engaging videos that keep people scrolling for the daily dopamine boost.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s hard to compete for attention on a platform that doesn&amp;rsquo;t prioritize stills anymore.&lt;/p&gt;
&lt;p&gt;In my opinion, there will be a renaissance of printing.&lt;br&gt;
Physical media is one of the ways to share your work with others. It doesn&amp;rsquo;t rely on obscure social media algorithms. It&amp;rsquo;s completely analog. In the end, it makes you more intentional with your work.&lt;/p&gt;
&lt;p&gt;Another way of sharing pictures I&amp;rsquo;m exploring it&amp;rsquo;s via this website. It gives me complete control over how I show my work to people and retain community aspects.&lt;/p&gt;
&lt;p&gt;The algorithm is not always evil. Abandoning Instagram could limit your ability to engage with that community and potentially reduce the visibility of your work.&lt;/p&gt;
&lt;p&gt;Instagram&amp;rsquo;s main problem is that it incentivizes people that &amp;ldquo;play by the rules&amp;rdquo;.&lt;br&gt;
But why should I post what people are expecting? I don&amp;rsquo;t want to scroll through an endless feed of similar content. I want uniqueness.&lt;/p&gt;
&lt;p&gt;My end goal is to produce work that matters, work that is meaningful. Ultimately I should care less about numbers and more about how I can create connections through my work.&lt;br&gt;
Numbers and metrics are not the only measure of success or value.&lt;/p&gt;</description><category>Photography</category><category>Short</category></item><item><title>Modular applications in go</title><link>https://eliseomartelli.it/blog/2022-12-02-gomodular/</link><pubDate>Fri, 02 Dec 2022 21:30:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2022-12-02-gomodular/</guid><description>&lt;p&gt;I&amp;rsquo;m working on an unnamed application (I can&amp;rsquo;t tell much about it now, but I will share more when I can) where I needed a way to load plugins dynamically based on the app&amp;rsquo;s configuration.&lt;/p&gt;
&lt;p&gt;I concluded that I needed a way to &amp;ldquo;store&amp;rdquo; the plugins and load them based on the application configuration. It seems that the premise is akin to the &amp;ldquo;registry&amp;rdquo; pattern.&lt;/p&gt;
&lt;h2 id="the-registry"&gt;The registry&lt;/h2&gt;
&lt;p&gt;To have an O(1) time complexity, I decided to store plugins in a &amp;ldquo;map&amp;rdquo;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// Plugin registry.
var Plugins = map[string]Plugin{}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But it doesn&amp;rsquo;t automatically solve our problems. Golang offers an init function that gets called only when we first import a package. You can have any number of init functions inside a package (one per file, for example).&lt;/p&gt;
&lt;p&gt;So the next idea is to use the init function to register our plugins.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// file plugin_name.go
package plugins
type PluginName struct {}
...
func init() {
Plugins[&amp;#34;plugin&amp;#34;] = &amp;amp;PluginName{}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We already have a functioning proof-of-concept, but we can make it better!&lt;/p&gt;
&lt;h2 id="let-it-shine"&gt;Let it shine&lt;/h2&gt;
&lt;p&gt;We will define an interface for our plugins with some methods to manage the plugin life cycle.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;type Plugin interface {
Setup()
Cleanup()
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then I made a function to register a plugin into the register:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// Factory to provide a closure to get the Plugin.
type PluginFactory = func() Plugin
// Register SHOULD BE called by the init() function of a plugin.
// The provider will be added to the Plugin map.
func Register(pluginName string, plugin PluginFactory) {
if _, present := Plugins[pluginName]; present {
log.Fatalf(&amp;#34;Plugin %s already defined.&amp;#34;, pluginName)
}
Plugins[pluginName] = plugin
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We have to change our previous plugin to use this function:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// file plugin_name.go
func init() {
Register(&amp;#34;plugin&amp;#34;, func() Provider { return &amp;amp;PluginName{} })
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="consuming-the-plugin"&gt;Consuming the plugin&lt;/h2&gt;
&lt;p&gt;To use the plugins, we can, for example, iterate on a list of configured plugins, check if they&amp;rsquo;re available in the map, and call the life cycle method to set them up.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;for _, pluginName := range configuredPlugins {
if plugin, present := Plugins[pluginName]; present {
plugin.Setup()
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We now have the tools to build a modular application in go.&lt;/p&gt;</description><category>Programming</category><category>Short</category></item><item><title>Web3 isn't for me</title><link>https://eliseomartelli.it/blog/2022-11-26-cryptobros/</link><pubDate>Sat, 26 Nov 2022 19:40:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2022-11-26-cryptobros/</guid><description>&lt;p&gt;Whenever I read around the web terms like crypto, web3 or nft, the blanket of pure skepticism covers my eyes, and I can&amp;rsquo;t stop myself from asking questions about the subject.&lt;/p&gt;
&lt;p&gt;The shift of ownership to the users, arguably the only point of interest of Web3, is just an afterthought and a promise, so people buy the latest shitcoin.
The term itself is a money-mover, a way for VC to gather cash fast from enthusiasts.&lt;/p&gt;
&lt;p&gt;The vision presented by the crypto-enthusiast is at the edge of dystopia, and sometimes it becomes &lt;em&gt;religious&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;My criticism of Web3 is mainly on the crypto/nft side. Decentralization, on the other end, is a great concept and something that already exists in various forms.&lt;/p&gt;
&lt;p&gt;BitTorrent, for instance, is an example of decentralization. It has been around for at least 20 years without any signs of cryptocurrency.
Other examples are ActivityPub, IPFS, &amp;hellip;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Cryptobros&amp;rdquo; usually call me a hater, but I can&amp;rsquo;t see the class of problems this technology is supposed to solve.
Web3 became the umbrella term for the gamification/reification of everything around us.&lt;/p&gt;</description><category>Misc</category><category>Short</category></item><item><title>My photography journey</title><link>https://eliseomartelli.it/blog/2022-11-25-cameras/</link><pubDate>Fri, 25 Nov 2022 19:40:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2022-11-25-cameras/</guid><description>&lt;p&gt;Photography is certainly not a new thing for me. I started taking snapshots of the things around me when Adobe Photoshop CS4 was still around with a compact digital camera which I received as a gift when my relatives saw my interest in photography. Before that, I had a plastic-lens compact film camera.&lt;/p&gt;
&lt;p&gt;Photography was always something &amp;ldquo;latent&amp;rdquo; in my life. I didn&amp;rsquo;t put lots of thought into that art.
To be fair: I had the basis of composition, knowledge of the exposure triangle, and maybe some lucky shots here and there.
I didn&amp;rsquo;t have a photography genre that caught my interest, and some other things in my life filled the &amp;ldquo;mental bandwidth&amp;rdquo; I could dedicate to photography.&lt;/p&gt;
&lt;p&gt;I then moved to Turin from a small town in Abruzzo. At that moment, something kicked in.
Suddenly, I started to pay attention to colors, shapes, and light around me, and I felt the need to capture what I was seeing, the moments around me.&lt;/p&gt;
&lt;p&gt;One of the first things I bought after moving was a DSLR camera. It was a commitment, something I had on my shelf that reminded me of taking some pictures here and there.
The most important thing was regaining confidence with that tool and slowly taking some steps into the world with that shiny new toy.&lt;/p&gt;
&lt;p&gt;With that new toy, I started taking pictures of landmarks around the city (yeah, I&amp;rsquo;m not proud of every photo I took, but some came out lucky, and I still like them to this day). There&amp;rsquo;s a limited number of landmarks in a city, and it started to become boring. I was worried about the money I spent on something that didn&amp;rsquo;t give me the joy I was expecting. Then something clicked (pun intended).
What if I had people as subjects?&lt;/p&gt;
&lt;p&gt;That was the bloom of what I&amp;rsquo;m photographing today. I took pictures of people in urban typesettings, but something didn&amp;rsquo;t feel right.
What was I doing? What &amp;ldquo;story&amp;rdquo; am I trying to tell?
It turns out that people only have a pre-defined set of actions they do in public, at least here in Turin. I had to find another way to keep shooting.&lt;/p&gt;
&lt;p&gt;At this point, I started seeing photography in another light. What I had was this tool that allowed me to capture the interaction between light and objects. What if I used this interaction as the subject of my pictures?
My current iteration is keeping individuals anonymized by the environment, using existing light to provide contrast in the parts I want to highlight. I started to produce darker images, and, for now, this is my happy place.&lt;/p&gt;
&lt;p&gt;I still have a lot to learn about this art. I&amp;rsquo;m pretty confident that the practice of taking pictures is here to stay for me. I share my photos on Instagram Stories at the moment, but I want to build an archive on this site as soon as possible.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m curious about what will be my next step.&lt;/p&gt;</description><category>Photography</category><category>Short</category></item><item><title>Music streaming sucks, long live music streaming.</title><link>https://eliseomartelli.it/blog/2022-11-24-music-services/</link><pubDate>Thu, 24 Nov 2022 21:30:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2022-11-24-music-services/</guid><description>&lt;p&gt;The evolution of portable, personal, and on-demand music started in 1979 thanks to Sony with their Walkman (ウォークマン), after about 20 years of pocket radios.
The Walkman then evolved into Discman (and similar devices), and at the beginning of the 2000s, the digital audio revolution came and brought Mp3 players, most notably the iPod.&lt;/p&gt;
&lt;p&gt;We are currently in the post-iPod era. The smartphone is the primary device most people use to listen to and consume audio content.&lt;/p&gt;
&lt;p&gt;The always-connected smartphone brought new industries into the tech space. One of these industries is streaming.&lt;/p&gt;
&lt;h2 id="is-music-streaming-worth-the-money-for-me"&gt;Is music streaming worth the money? (for me)&lt;/h2&gt;
&lt;p&gt;Accordingly to my (highly scientific) method, I&amp;rsquo;ve listened to about 1000 songs this year, organized into about 500 albums and singles.&lt;/p&gt;
&lt;p&gt;Considering a more sane music listening habit not skewed by the streaming culture, I would&amp;rsquo;ve bought ten albums in a year. At this time of the year, I would&amp;rsquo;ve spent €100 on music, considering a price of €10 for a single album.&lt;/p&gt;
&lt;p&gt;With streaming, I pay around half the price of that. With that, I get
human-curated content like &amp;ldquo;Apple Music Today&amp;rdquo;;
algorithmic recommendations;
and shared playlists.
Then, I don&amp;rsquo;t need to manage a media server, I don&amp;rsquo;t need to worry about ripping media, and I get high-quality streaming.&lt;/p&gt;
&lt;p&gt;DRM sucks, yes. But contrary to video streaming, the quantity of music I listen to is orders of magnitude larger than the video content I watch. It makes sense economically in my case.&lt;/p&gt;
&lt;p&gt;Long live music streaming.&lt;/p&gt;</description><category>Music</category><category>Short</category></item><item><title>OSPF over Wireguard with VyOS</title><link>https://eliseomartelli.it/blog/2022-09-29-vyos-ospf-wireguard/</link><pubDate>Thu, 29 Sep 2022 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2022-09-29-vyos-ospf-wireguard/</guid><description>&lt;p&gt;My home network is getting another friend, and I wanted to share the experience of setting everything up with you.&lt;br&gt;
I already had a site-to-site VPN setup using Wireguard between three sites, but there was a little problem: I had to set up static routes between these sites. (And maintaining static routes is a big chore, a task no network administrator would like to be involved in).&lt;/p&gt;
&lt;img src="https://eliseomartelli.it/posts/vyos-ospf-wireguard/cover.svg" alt="Three routers connected over WAN" class=""/&gt;
&lt;h2 id="meet-ospf"&gt;Meet OSPF&lt;/h2&gt;
&lt;p&gt;OSPF is a routing protocol that operates inside an autonomous system (AS). OSPF is a link-state protocol that means that every router in the network knows the entire topology of the network. Each router calculates the best next hops from itself to every other possible destination. The list of best next-hops gets appended to the routing table.&lt;/p&gt;
&lt;p&gt;OSPF networks are broken apart into &lt;em&gt;areas&lt;/em&gt;. Area 0 (also called backbone area) is a particular type of OSPF area to which all other areas are connected. Routers in an area form adjacencies when they detect each other.&lt;/p&gt;
&lt;p&gt;OSPF supports different operation modes, depending on what interface it is running on. The default operation mode is &lt;em&gt;broadcast&lt;/em&gt;. Wireguard is an L3 point-to-point tunnel, which means we can&amp;rsquo;t use broadcast. We have to resort to using the &lt;a href="https://datatracker.ietf.org/doc/html/rfc2328#page-130"&gt;&amp;ldquo;point-to-point&amp;rdquo;&lt;/a&gt; operation mode.&lt;/p&gt;
&lt;p&gt;It looks like the perfect candidate for our purposes. Another option we could use is RIP which has some advantages over OSPF in terms of resource utilization on the machine(s) running it but consumes more bandwidth since it transmits the entire routing table every 30 seconds. OSPF, on the other hand, sends only the entries of the routing table that changed.&lt;/p&gt;
&lt;h2 id="network-topology"&gt;Network topology&lt;/h2&gt;
&lt;p&gt;The network has three routers in three different sites, connected with Wireguard over the internet. Each router connects to several networks. Only a subset of these networks should be advertised to other routers.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/vyos-ospf-wireguard/netdiag1.svg" alt="Network diagram"&gt;&lt;/p&gt;
&lt;h2 id="preparation"&gt;Preparation&lt;/h2&gt;
&lt;p&gt;First things firsts, we have to establish the needed Wireguard tunnels.
We are adding an interface for each router pair for a specific reason: we need to set &lt;code&gt;AllowedIPs&lt;/code&gt; to &lt;code&gt;0.0.0.0/0&lt;/code&gt; for every peer. Wireguard &lt;a href="https://lists.zx2c4.com/pipermail/wireguard/2018-December/003704.html"&gt;doesn&amp;rsquo;t support&lt;/a&gt; having the same &lt;code&gt;AllowedIPs&lt;/code&gt; on multiple peers on the same interface.&lt;/p&gt;
&lt;p&gt;Quoting Wireguard&amp;rsquo;s homepage:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;hellip;when sending packets, the list of allowed IPs behaves as a sort of routing table, and when receiving packets, the list of allowed IPs behaves as a sort of access control list&amp;hellip;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Since we have three routers, we have to configure six interfaces, two for each router, spanning three point-to-point subnets.&lt;/p&gt;
&lt;p&gt;As a first step, we decide on our subnets respecting &lt;a href="https://datatracker.ietf.org/doc/html/rfc1818"&gt;RFC1918&lt;/a&gt;. In my case, I can start playing with &lt;code&gt;192.168.69.0/24&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; V
192.168. 69. 0 11000000.10101000.01000101.00000000
# Let&amp;#39;s make 3 /30 subnets
V
192.168. 69. 0 11000000.10101000.01000101.000000 00
192.168. 69. 4 11000000.10101000.01000101.000001 00
192.168. 69. 8 11000000.10101000.01000101.000010 00
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can now start configuring our VyOS routers (I&amp;rsquo;m assuming you already have SSH or console access to the router and a WAN connection).&lt;/p&gt;
&lt;p&gt;SSH to rtr-01:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;rtr-01 $ generate wireguard named-keypairs link_01_to_02
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;rtr-01 $ generate wireguard named-keypairs link_01_to_03
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt;# Take note of the pubkeys&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt;rtr-01 $ show wireguard keypairs pubkey link_01_to_02
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt;rtr-01 $ show wireguard keypairs pubkey link_01_to_03
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt;rtr-01 $ configure
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt;rtr-01 &lt;span style="color:#60a0b0;font-style:italic"&gt;# edit interfaces wireguard wg01&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt;rtr-01 &lt;span style="color:#60a0b0;font-style:italic"&gt;# set address 192.168.69.1/30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt;rtr-01 &lt;span style="color:#60a0b0;font-style:italic"&gt;# set private-key link_01_to_02&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt;rtr-01 &lt;span style="color:#60a0b0;font-style:italic"&gt;# set port 7878&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt;rtr-01 &lt;span style="color:#60a0b0;font-style:italic"&gt;# set description link_01_to_02&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt;rtr-01 &lt;span style="color:#60a0b0;font-style:italic"&gt;# set peer rtr-02 allowed-ips 0.0.0.0/0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16&lt;/span&gt;&lt;span&gt;rtr-01 &lt;span style="color:#60a0b0;font-style:italic"&gt;# set peer rtr-02 pubkey &amp;lt;link_02_to_01 pubkey&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18&lt;/span&gt;&lt;span&gt;rtr-01 &lt;span style="color:#60a0b0;font-style:italic"&gt;# top&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20&lt;/span&gt;&lt;span&gt;rtr-01 &lt;span style="color:#60a0b0;font-style:italic"&gt;# edit interfaces wireguard wg02&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21&lt;/span&gt;&lt;span&gt;rtr-01 &lt;span style="color:#60a0b0;font-style:italic"&gt;# set address 192.168.69.5/30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22&lt;/span&gt;&lt;span&gt;rtr-01 &lt;span style="color:#60a0b0;font-style:italic"&gt;# set private-key link_01_to_03&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23&lt;/span&gt;&lt;span&gt;rtr-01 &lt;span style="color:#60a0b0;font-style:italic"&gt;# set port 7879&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24&lt;/span&gt;&lt;span&gt;rtr-01 &lt;span style="color:#60a0b0;font-style:italic"&gt;# set description link_01_to_03&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25&lt;/span&gt;&lt;span&gt;rtr-01 &lt;span style="color:#60a0b0;font-style:italic"&gt;# set peer rtr-02 allowed-ips 0.0.0.0/0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;26&lt;/span&gt;&lt;span&gt;rtr-01 &lt;span style="color:#60a0b0;font-style:italic"&gt;# set peer rtr-02 pubkey &amp;lt;link_03_to_01 pubkey&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;27&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;28&lt;/span&gt;&lt;span&gt;rtr-01 &lt;span style="color:#60a0b0;font-style:italic"&gt;# commit; save; exit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now SSH to the other routers and apply the &amp;ldquo;same&amp;rdquo; configuration, changing addresses, ports and keys. To streamline the task, you can use your preferred automation tool. I&amp;rsquo;m using &lt;a href="https://www.ansible.com"&gt;Ansible&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t forget to allow the used ports on your firewall configuration.&lt;/p&gt;
&lt;p&gt;At this time, you should be able to ping other routers connected at the other end of the tunnels. If everything is working, we can get to the OSPF configuration.&lt;/p&gt;
&lt;h2 id="a-sprinkle-of-ospf"&gt;A &lt;em&gt;sprinkle&lt;/em&gt; of OSPF&lt;/h2&gt;
&lt;p&gt;As we stated, we have to configure our interfaces to run OSPF In point-to-point mode. Get a shell onto your router and:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rtr-01 $ configure
rtr-01 # edit interfaces wireguard wg01
rtr-01 # set ip ospf network point-to-point
rtr-01 # top
rtr-01 # copy interfaces wireguard wg01 ip to interfaces wireguard wg02 ip
rtr-01 # commit; save; exit
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Follow the same steps on your other routers. Now we are at the fun part!&lt;/p&gt;
&lt;p&gt;We are adding the 0.0.0.0 area on every router, telling the OSPF daemon which point-to-point networks are part of this area, and telling what routes the router should redistribute to every other router.&lt;/p&gt;
&lt;p&gt;You know the drill, SSH to your favorite router first, and tell it what to do! Then, configure the other routers like this first one.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rtr-01 $ configure
rtr-01 # edit protocols ospf area 0.0.0.0
rtr-01 # set network 192.168.69.0/30
rtr-01 # set network 192.168.69.4/30
rtr-01 # set network 192.168.69.8/30
rtr-01 # set redistribute connected
rtr-01 # commit; save; exit
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;rsquo;m not covering how to set up your firewall here since your configuration might be different than mine (I&amp;rsquo;m using zone-based firewall policies). Remember to accept traffic (from and to) the previously defined Wireguard interfaces.&lt;/p&gt;
&lt;h2 id="lets-check-if-everything-is-in-its-place"&gt;Let&amp;rsquo;s check if everything is in its place&lt;/h2&gt;
&lt;p&gt;At this point, everything should be working.&lt;br&gt;
Let&amp;rsquo;s check:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;if the neighboring relationships are getting formed;&lt;/li&gt;
&lt;li&gt;if the link-state database is getting populated;&lt;/li&gt;
&lt;li&gt;if routes are getting injected into the routing table.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="neighboring-relationships"&gt;Neighboring relationships&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rtr-01 $ show ip ospf neighbor
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="link-state-database"&gt;Link-state database&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rtr-01 $ show ip ospf database
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="routes"&gt;Routes&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rtr-01 $ show ip route ospf
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="filtering-routes"&gt;Filtering routes&lt;/h2&gt;
&lt;p&gt;We can use a routing policy to filter the redistributed routes from our routers.
A route map allows us to match a network or an interface (or an as-path, an extcommunity, a metric, a peer, a community, etc.).
For our purposes, we will permit the redistribution of networks attached to some interfaces.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s create a new policy:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rtr-01 $ configure
rtr-01 # edit policy route-map FilteredRoutes
rtr-01 # set rule 10 action permit
rtr-01 # set rule 10 match interface eth1
rtr-01 # commit
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And now apply this policy to the redistribution configuration of the OSPF daemon:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rtr-01 # top
rtr-01 # edit protocols ospf redistribute connected
rtr-01 # set route-map FilteredRoutes
rtr-01 # commit; save; exit
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="bonus-snat"&gt;Bonus: (s)NAT&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say you want to access a web server that sits in a network redistributed by &lt;code&gt;rtr-02&lt;/code&gt; from a client in a network attached to &lt;code&gt;rtr-01&lt;/code&gt;, but we configured this router to don&amp;rsquo;t redistribute that network.
In this case, SNAT is our friend. We are going to translate the source address of our client to the address of the interface connected to &lt;code&gt;rtr-02&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/vyos-ospf-wireguard/netdiag2.svg" alt="Network diagram"&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s add this NAT rule to &lt;code&gt;rtr-01&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rtr-01 $ configure
rtr-01 # edit nat source
rtr-01 # set rule 100 outbound-interface wg01
rtr-01 # set rule 100 source address 192.168.16.0/24 # Change with your network.
rtr-01 # commit; save; exit
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="final-checks"&gt;Final checks&lt;/h2&gt;
&lt;h3 id="neighboring-relationships-1"&gt;Neighboring relationships&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rtr-01 $ show ip ospf neighbor
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="link-state-database-1"&gt;Link-state database&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rtr-01 $ show ip ospf database
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="routes-1"&gt;Routes&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rtr-01 $ show ip route ospf
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can see that the link state database and, consequently, the routing table are leaner than before since we have fewer routes advertised between our routers.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/vyos-ospf-wireguard/hello-from-your-ospf-neighbor.jpg" alt="An image of Adele the singer, with &amp;ldquo;Hello from your OSPF neighbor&amp;rdquo; superimposed"&gt;&lt;/p&gt;
&lt;p&gt;And we are golden!&lt;br&gt;
Enjoy your brand new site-to-site VPN, and forget about adding static routes manually. OSPF will take care of that for you.&lt;/p&gt;</description><category>Network</category></item><item><title>Far out: what's new in Apple hardware?</title><link>https://eliseomartelli.it/blog/2022-09-07-apple-event-september2022/</link><pubDate>Wed, 07 Sep 2022 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2022-09-07-apple-event-september2022/</guid><description>&lt;p&gt;&lt;img src="https://eliseomartelli.it/posts/apple-event-september2022/cover.jpg" alt="Apple Far Out event cover"&gt;&lt;/p&gt;
&lt;p&gt;As of every year, we just had Apple&amp;rsquo;s September event: &amp;ldquo;Far out&amp;rdquo;.&lt;br&gt;
Since I&amp;rsquo;m an Apple user interested in the evolution of Apple products, I decided to gather my thoughts on what they presented today.&lt;/p&gt;
&lt;p&gt;As usual, the conference was tightly packed with &lt;em&gt;sci-fi-esque&lt;/em&gt; transitions, an implacable succession of speakers, and lots of marketing mumbo-jumbo that is necessary to swallow the price of the new products and inspire uses to justify the expense. And it works. I&amp;rsquo;m pretty sure that the retention rate of Apple&amp;rsquo;s conferences is one of the highest in the industry looking at the view numbers on YouTube.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s begin with the new (but also old) kid on the block.&lt;/p&gt;
&lt;h2 id="the-iphone-14-series"&gt;The iPhone 14 series&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://www.apple.com/newsroom/images/product/iphone/standard/Apple-iPhone-14-iPhone-14-Plus-hero-220907_Full-Bleed-Image.jpg.large_2x.jpg" alt="iPhone 14"&gt;&lt;/p&gt;
&lt;p&gt;This year&amp;rsquo;s iPhone is an incremental update to the proven iPhone we all know, bringing small things in, year after year, and smoothing out the edges &lt;em&gt;(pun intended).&lt;/em&gt;&lt;br&gt;
As the silicon for new products gets more streamlined each year, the A16 offers a &lt;a href="https://www.tsmc.com/english/dedicatedFoundry/technology/logic/l_4nm"&gt;TSMC 4nm&lt;/a&gt; production process that helps with power consumption and in keeping things &amp;ldquo;fresh&amp;rdquo; in the constrained thermal envelope of the iPhone.&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;normal&amp;rdquo; iPhone 14 now comes in two sizes: 6.1&amp;quot; and 6.7&amp;quot;, a new thermal design to keep the processor fresh (the processor is the same as the iPhone 13, the A15 bionic). It brings a faster aperture main camera that will help in low light performance and a new 12Mpx front camera with a wider aperture.&lt;/p&gt;
&lt;p&gt;The iPhone will bring Crash Detection, like the Apple Watch, and can automatically notify emergency services even by using satellite connectivity.&lt;/p&gt;
&lt;h3 id="iphone-14-pro"&gt;iPhone 14 Pro&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://www.apple.com/newsroom/images/product/iphone/standard/Apple-iPhone-14-Pro-iPhone-14-Pro-Max-hero-220907_Full-Bleed-Image.jpg.large_2x.jpg" alt="iPhone 14 Pro"&gt;&lt;/p&gt;
&lt;p&gt;iPhone 14 Pro brings an all-new front design featuring a dynamic pill (called Dynamic Island) cut-out that surfaces content and alerts. It has an all-new display with smaller borders and an always-on display that uses LTPO technology to be power efficient.&lt;/p&gt;
&lt;p&gt;It features the new A16 Bionic chip that sips power while bringing great performance to empower amazing new experiences on iPhone.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A classic design, reimagined.&lt;/li&gt;
&lt;li&gt;The fastest chip in an iPhone, yet.&lt;/li&gt;
&lt;li&gt;A brand new pro camera system.&lt;/li&gt;
&lt;li&gt;Dynamic Island.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The iPhone 14 Pro brings a 48Mpx main sensor with a focal length of 24mm. It brings pixel-binning to improve low-light performance, and a new Action Mode to improve the image stabilization on video.&lt;/p&gt;
&lt;p&gt;Sadly, no iPhone in the current lineup supports Wi-Fi 6E.&lt;/p&gt;
&lt;h2 id="apple-watch-series-8"&gt;Apple Watch Series 8&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://www.apple.com/newsroom/images/product/watch/lifestyle/Apple-Watch-SE-aluminum-midnight-220907_inline.jpg.large_2x.jpg" alt="Apple Watch Series 8"&gt;&lt;/p&gt;
&lt;p&gt;The essential companion that&amp;rsquo;s always with you gets updated, it&amp;rsquo;s not only a tech gizmo but a health helper, always ready to help.
The lineup consists of a Pro Apple Watch and the classic 41 and 45mm sizes.&lt;/p&gt;
&lt;p&gt;Apple Watch Series 8 introduces a temperature sensor (used also for menstrual cycle tracking), Crash Detection that calls emergency services in the case of a car crash using machine learning trained on driving and crashes data, and Low Power Mode that slims down on features of the Apple Watch to extend its life up to 36 hours.&lt;/p&gt;
&lt;h2 id="apple-watch-se-second-generation"&gt;Apple Watch SE (second generation)&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://www.apple.com/newsroom/images/product/watch/standard/Apple-Watch-SE-8up-hero-220907_big.jpg.large_2x.jpg" alt="Apple Watch SE"&gt;&lt;/p&gt;
&lt;p&gt;The second generation Apple Watch SE brings new colorways, a redesigned production process, and the same sensors from the Apple Watch Series 8 (along with the same SIP).&lt;/p&gt;
&lt;h2 id="apple-watch-ultra"&gt;Apple Watch Ultra&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://www.apple.com/newsroom/images/product/watch/standard/Apple-Watch-Ultra-3up-hero-220907_Full-Bleed-Image.jpg.medium_2x.jpg" alt="Apple Watch Ultra"&gt;&lt;/p&gt;
&lt;p&gt;Bigger and brighter display that reaches 2000 nits, an action button, and stronger audio.
Apple Watch Ultra brings LTE on all the lineup.&lt;/p&gt;
&lt;p&gt;The Action Button is a programmable button that can be configured on a per-context basis. The bigger battery can keep things running for 36 hours and has a higher resistance to the elements. It has a built-in siren that can be heard at ~200m away, WR100 water resistance, and &lt;a href="https://en.wikipedia.org/wiki/Dive_computer"&gt;EN13319&lt;/a&gt; certification.&lt;/p&gt;
&lt;h2 id="airpods-pro-2"&gt;AirPods Pro 2&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://www.apple.com/newsroom/images/product/airpods/standard/Apple-AirPods-Pro-2nd-gen-hero-220907_big.jpg.large_2x.jpg" alt="AirPods Pro 2"&gt;&lt;/p&gt;
&lt;p&gt;AirPods Pro 2 brings a new H2 chip, a new low distortion audio driver, and 2x noise cancellation performance. It includes a new tip for even smaller ears.
Adaptive Transparency processes audio 48000 times per second to take out loud noises.
AirPods Pro 2 brings a longer battery life (up to 30 hours of listening time), a touch surface to adjust volume, and Precision Finding to the case, along with a speaker, Apple Watch charger compatibility, and a lanyard strap connector.&lt;/p&gt;
&lt;h2 id="final-reflections"&gt;Final reflections&lt;/h2&gt;
&lt;p&gt;Apple brought out some minor and major improvements across their whole lineup, with a focus on safety features and cooperation inside the ecosystem.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m amazed by the satellite connectivity for emergency services and the whole UX around that feature. Moreover Apple&amp;rsquo;s design expertise is truly showing through Dynamic Island.&lt;/p&gt;
&lt;blockquote class="twitter-tweet mx-auto" data-dnt="true"&gt;&lt;p lang="en" dir="ltr"&gt;Ok, this dynamic island looks so smooth!!!&lt;br/&gt;&lt;br/&gt;That alone might be a good reason to get an iPhone 14 Pro!&lt;a href="https://twitter.com/hashtag/AppleEvent?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#AppleEvent&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/iPhone14Pro?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#iPhone14Pro&lt;/a&gt; &lt;br/&gt;&lt;br/&gt; &lt;a href="https://t.co/QXcPiJHZzi"&gt;pic.twitter.com/QXcPiJHZzi&lt;/a&gt;&lt;/p&gt;&amp;mdash; Sergio The One ♪ (@Sergio_TheOne) &lt;a href="https://twitter.com/Sergio_TheOne/status/1567578200418418688?ref_src=twsrc%5Etfw"&gt;September 7, 2022&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;p&gt;However, I&amp;rsquo;m less amazed by the iPhone 14 base model since it doesn&amp;rsquo;t bring anything substantially new to the table and maybe the formula of &amp;ldquo;if it ain&amp;rsquo;t broke don&amp;rsquo;t fix it&amp;rdquo; should not be followed for 3 years in a row.
The same thing can also be said about AirPods Pro.&lt;/p&gt;
&lt;p&gt;Oh, and the 10% price increment here in Italy. So. Pricy.&lt;/p&gt;</description><category>legacy</category></item><item><title>iPadOS, a year-long review</title><link>https://eliseomartelli.it/blog/2020-10-31-ipados_review/</link><pubDate>Sat, 31 Oct 2020 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2020-10-31-ipados_review/</guid><description>&lt;p&gt;What&amp;rsquo;s an iPad? Is it a real computer? You might ask. Well, long story short: you can look at it as a computer only if your &lt;strong&gt;use-cases&lt;/strong&gt; match what Apple envisioned for it. Let’s take a deep dive into it.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/eliseomartelli/illustrations/main/svg/confusedipad.svg" alt="A confused iPad"&gt;&lt;/p&gt;
&lt;p&gt;iPadOS is a &lt;strong&gt;fun computing platform&lt;/strong&gt; designed for a &lt;em&gt;touch-first&lt;/em&gt; interaction model, and let me get this straight: it excels at it.&lt;br&gt;
If you take a look at &lt;strong&gt;competing platforms&lt;/strong&gt; like Windows, ChromeOS, or Android, you will see ecosystems that are &lt;strong&gt;far from perfect&lt;/strong&gt; on tablets.&lt;br&gt;
These platforms are designed with input paradigms tied to a mouse and a keyboard or a smartphone interface.&lt;br&gt;
The iPad, on the other hand, is &lt;em&gt;distinctive&lt;/em&gt; and what sets it apart is the &lt;strong&gt;App ecosystem&lt;/strong&gt;.&lt;br&gt;
However, this shouldn&amp;rsquo;t be an invitation to Apple to stop fixing things or adding new features to the iPad.&lt;/p&gt;
&lt;h2 id="theres-an-app-for-that"&gt;There&amp;rsquo;s an app for that&lt;/h2&gt;
&lt;p&gt;The iPad platform offers a &lt;strong&gt;clean slate&lt;/strong&gt; that developers are happy to use and develop for. The App Store is full of pretty &lt;strong&gt;good iPad apps&lt;/strong&gt;.&lt;br&gt;
The quality of these apps is usually &lt;em&gt;top-notch&lt;/em&gt; and the often powerful chips on these devices help to deliver a smooth experience.&lt;/p&gt;
&lt;p&gt;Looking through an Android user&amp;rsquo;s perspective, one of the most &lt;strong&gt;fascinating&lt;/strong&gt; things is that the developer &lt;strong&gt;adoption&lt;/strong&gt; of new frameworks or new hardware features is always &lt;strong&gt;fast&lt;/strong&gt;. Developers are keen on updating their apps to take advantage of these new features.
There&amp;rsquo;s always something &lt;em&gt;fresh&lt;/em&gt; on the iPad and each iPadOS update brings new features to the table.&lt;/p&gt;
&lt;h3 id="productivity--creativity"&gt;Productivity &amp;amp; Creativity&lt;/h3&gt;
&lt;p&gt;One of the main reasons I bought an iPad is to run productivity apps.
Apple took a &lt;strong&gt;great stance&lt;/strong&gt; with the introduction of the &lt;strong&gt;Apple Pencil&lt;/strong&gt; and the Smart Keyboard(s). The iPad has become a productivity-first device and not another content consumption machine.&lt;/p&gt;
&lt;p&gt;Apps like &lt;a href="https://apps.apple.com/us/app/goodnotes-5/id1444383602"&gt;&lt;strong&gt;GoodNotes&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://apps.apple.com/us/app/pretext/id1347707000"&gt;&lt;strong&gt;Pretext&lt;/strong&gt;&lt;/a&gt;, or &lt;a href="https://apps.apple.com/us/app/concepts/id560586497"&gt;&lt;strong&gt;Concepts&lt;/strong&gt;&lt;/a&gt; feel right at home on the iPad. Starting from iPadOS 13.4, the addition of first-class mouse support on the iPad brought exciting new possibilities to this platform.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Creativity-oriented&lt;/strong&gt; apps on the iPad are &lt;strong&gt;powerful&lt;/strong&gt; yet simple. You will comfortably adapt from a keyboard and mouse workflow to the iPad since creating something on it requires less cognitive load.&lt;br&gt;
I&amp;rsquo;m a big fan of apps like &lt;a href="https://apps.apple.com/us/app/procreate/id425073498"&gt;&lt;strong&gt;Procreate&lt;/strong&gt;&lt;/a&gt; (even though I can&amp;rsquo;t draw). Keep in mind that developers are bringing more and more desktop apps to the iPad. On the App Store, you&amp;rsquo;ll see names like Affinity Designer, Affinity Photo. There&amp;rsquo;s even a slice of Adobe Creative Cloud apps on the iPad!&lt;/p&gt;
&lt;h2 id="input-on-the-ipad"&gt;Input on the iPad&lt;/h2&gt;
&lt;p&gt;I feel like this is an area of the iPad experience that can take a &lt;strong&gt;little revision&lt;/strong&gt;. Input feels fluid, some of the interactions are well presented and understandable but there are times I find them very &lt;strong&gt;confusing&lt;/strong&gt;.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/4Pv3rEjI4fQ?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Just look at this video by Apple Support. It&amp;rsquo;s just scratching the surface. Now add a trackpad, a keyboard, and the Apple Pencil. It&amp;rsquo;s &lt;em&gt;gesture-ville&lt;/em&gt;!&lt;br&gt;
There&amp;rsquo;s even a &lt;em&gt;shake-to-undo&lt;/em&gt; gesture on by default and the best time to discover it is when you&amp;rsquo;re trying to get something done inside a vehicle.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.ibb.co/D4NKn5g/aintthatfun.png" alt="Undo typing popup"&gt;
&lt;em&gt;Oh, Hi! Hello there!&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="apple-pencil-a-must-that-comes-at-a-price"&gt;Apple Pencil, a must that comes at a price&lt;/h3&gt;
&lt;p&gt;The Apple Pencil is one of the accessories that &lt;strong&gt;separate&lt;/strong&gt; the iPad experience &lt;strong&gt;from other tablets&lt;/strong&gt;. It&amp;rsquo;s not an extension of your fingers. It&amp;rsquo;s a &lt;strong&gt;tool&lt;/strong&gt; designed for &lt;strong&gt;content creation&lt;/strong&gt;.&lt;br&gt;
With iPadOS 14 and the introduction of &lt;strong&gt;Scribble&lt;/strong&gt; and &lt;strong&gt;handwriting editing&lt;/strong&gt;, you can use the Apple Pencil on a lot more apps.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/eliseomartelli/illustrations/main/svg/applepencil.svg" alt="The Apple Pencil"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you&amp;rsquo;re using the iPad for anything more other than content consumption, the Pencil is a &lt;strong&gt;must&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The responsiveness of the Pencil is &lt;strong&gt;remarkable&lt;/strong&gt;. It feels like you&amp;rsquo;re writing on paper. There are only one problem and two annoyances:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It &lt;strong&gt;costs&lt;/strong&gt; 1/3 of the cheapest iPad;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Placement&lt;/strong&gt; and &lt;strong&gt;charging&lt;/strong&gt; of the first generation is ridiculous (solved by the second generation);&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not every part&lt;/strong&gt; of the iPad interface &lt;strong&gt;supports it&lt;/strong&gt; (more on this later).&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="mouse-and-keyboard"&gt;Mouse and Keyboard&lt;/h3&gt;
&lt;p&gt;The default software keyboard is okay. It&amp;rsquo;s the only keyboard I know that supports &lt;strong&gt;split typing&lt;/strong&gt; on the iPad. It&amp;rsquo;s &lt;strong&gt;responsive&lt;/strong&gt; most of the time. The only problem I encountered with it is when using a combination of keyboard-based input and the new &amp;ldquo;Scribble&amp;rdquo; feature.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/mqefxehXa_c?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;When you add a physical keyboard and a mouse to the iPad equation, it starts &lt;strong&gt;feeling like a real computer&lt;/strong&gt;. Text interaction is instantly &lt;strong&gt;natural&lt;/strong&gt;. If you&amp;rsquo;re coming from a macOS machine, the majority of the &lt;strong&gt;shortcuts are the same&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The mouse/trackpad experience on the iPad is &lt;strong&gt;far from the desktop-like experience&lt;/strong&gt; and not in a bad way. The cursor is &lt;strong&gt;optimized for touch-first interfaces&lt;/strong&gt; and morphs into the UI element you&amp;rsquo;re trying to point and click and sticks to it, like a magnet.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/ZP0zeUfYt7U?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id="multitasking"&gt;Multitasking&lt;/h2&gt;
&lt;p&gt;iPadOS is a &lt;em&gt;focus-centric&lt;/em&gt; operating system. It has &lt;strong&gt;interesting&lt;/strong&gt; multitasking abilities although, for me, they are meant to be used in a burst when a single app on screen can be limiting.&lt;/p&gt;
&lt;p&gt;I find myself using the &amp;ldquo;Slideover&amp;rdquo; feature a lot when I need to check on something quickly and not strictly related to the main task. Instead, I use the split-screen feature to reference notes and transfer data between apps.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;discoverability&lt;/strong&gt; factor of the various multitasking features is pretty &lt;strong&gt;low&lt;/strong&gt;. How am I supposed to know, at a glance, that there are multiple drop targets for the apps?&lt;/p&gt;
&lt;p&gt;A limitation that may seem small of the iPad multitasking is that it&amp;rsquo;s only limited to apps you have in the &lt;strong&gt;dock&lt;/strong&gt; or dragged from &lt;strong&gt;Spotlight&lt;/strong&gt;. You will quickly realize that it&amp;rsquo;s &lt;strong&gt;not a small limitation&lt;/strong&gt; when you can&amp;rsquo;t trigger Spotlight from other apps if you don&amp;rsquo;t have a keyboard attached.&lt;/p&gt;
&lt;p&gt;Something &lt;strong&gt;funny&lt;/strong&gt; about the multitasking UI is that you &lt;strong&gt;can&amp;rsquo;t use&lt;/strong&gt; it with the &lt;strong&gt;Apple Pencil&lt;/strong&gt;. Strange. I can drag files and apps just fine with the Pencil elsewhere in the system.&lt;/p&gt;
&lt;h2 id="settings"&gt;Settings&lt;/h2&gt;
&lt;p&gt;I feel like settings on iPadOS are &lt;strong&gt;flawed&lt;/strong&gt;. The idea of &lt;strong&gt;centralizing&lt;/strong&gt; everything under one roof looks good even though it&amp;rsquo;s not something every developer adapted their apps for. You&amp;rsquo;ll find a lot of &lt;strong&gt;inconsistencies&lt;/strong&gt; and settings &lt;strong&gt;scattered&lt;/strong&gt; between the app and the Settings app.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.ibb.co/2cstJ5C/IMG-0617.png" alt="Procreate inside Settings App"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.ibb.co/DYgJt3N/IMG-0616.png" alt="Settings inside Procreate"&gt;
&lt;i class="text-center"&gt;Procreate settings example&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;What are the &lt;strong&gt;criteria&lt;/strong&gt; used to move a setting from one screen to another? &lt;strong&gt;I will never know.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="home-screen"&gt;Home Screen&lt;/h2&gt;
&lt;p&gt;The iPad home screen remained &lt;strong&gt;simple&lt;/strong&gt; even with the advent of iPadOS 14. I&amp;rsquo;d love to see widgets on the home screen like the iOS counterpart.
The icon grid is well-spaced. Selecting the &amp;ldquo;more&amp;rdquo; option is something you should do immediately.&lt;/p&gt;
&lt;p&gt;I wish Apple gave us the ability to use the &lt;strong&gt;App Library&lt;/strong&gt; on the iPad since the home screen can get messy when you have a lot of apps.&lt;/p&gt;
&lt;h2 id="file-management"&gt;File Management&lt;/h2&gt;
&lt;p&gt;File management on the iPad happens inside the &amp;ldquo;Files&amp;rdquo; app. You can also see connected &lt;strong&gt;external drives&lt;/strong&gt; in the Files app.&lt;br&gt;
The iPad doesn&amp;rsquo;t expose its whole &lt;strong&gt;filesystem&lt;/strong&gt; to apps. Inter-app communication can happen in two ways: &lt;a href="https://developer.apple.com/documentation/fileprovider"&gt;&lt;strong&gt;FileProvider&lt;/strong&gt;&lt;/a&gt; or the &lt;strong&gt;share sheet.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.ibb.co/NS2G5xS/IMG-0608.jpg" alt="Pretext and iSH"&gt;
&lt;i class="text-center"&gt;Pretext and iSH working togheter via the FileProvider Framework.&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;Apps using the FileProvider Framework can see other apps files and work with them. It&amp;rsquo;s a little feature that can enable powerful workflows on the iPad.&lt;br&gt;
For example, you can connect your camera to your iPad and access pictures from Pixelmator Photo. Edit the images and then save them to Google Drive without switching any app.&lt;/p&gt;
&lt;h2 id="scriptable-goodness"&gt;Scriptable goodness&lt;/h2&gt;
&lt;p&gt;Another powerful app on the iPad is &amp;ldquo;Shortcuts&amp;rdquo;.
&lt;strong&gt;Shortcuts&lt;/strong&gt; are like mini-applets that you can make to &lt;strong&gt;automate&lt;/strong&gt; all sorts of things. You can use them to send commands to other machines via SSH or to consume a REST API.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re an automation geek you&amp;rsquo;ll feel right at home with its interface.&lt;br&gt;
I use Shortcuts to compress images, control my Torrent box, and shorten URLs.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/eliseomartelli/illustrations/main/svg/doilovemyipad.svg" alt="An illustration of me with an iPad and a heart"&gt;&lt;/p&gt;
&lt;h2 id="honorable-mentions"&gt;Honorable Mentions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;HomeKit&lt;/strong&gt; is the fastest way to control my smart home since it controls devices locally and it&amp;rsquo;s not cloud-dependent.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Spotlight&lt;/strong&gt; search is &lt;em&gt;really&lt;/em&gt; fast and apps integrate with it nicely.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;strong&gt;App Store&lt;/strong&gt; &amp;ldquo;Today&amp;rdquo; view is well-curated and often brings up some hidden gems. App discoverability, in general, is stellar on the iPad.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="wish-list"&gt;Wish List&lt;/h2&gt;
&lt;p&gt;I wish Apple took the developer market more seriously with the iPad. A built-in &lt;strong&gt;UNIX shell&lt;/strong&gt; would make me very happy.&lt;br&gt;
Another wish would be better &lt;strong&gt;conferencing&lt;/strong&gt; app support on the iPad since only FaceTime can use the popup view.&lt;/p&gt;
&lt;p&gt;Overall, the iPad is proving a &lt;strong&gt;powerful&lt;/strong&gt; device to learn, create, and consume on. It&amp;rsquo;s making me &lt;strong&gt;curious&lt;/strong&gt; about iOS in general.
The iPad is a device that I&amp;rsquo;m more than happy using. I don&amp;rsquo;t see it parting away from my digital life anytime soon.&lt;/p&gt;</description><category>Apple</category></item><item><title>IoT device segregation on EdgeOS</title><link>https://eliseomartelli.it/blog/2020-09-14-iot-vlan-edgeos/</link><pubDate>Mon, 14 Sep 2020 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2020-09-14-iot-vlan-edgeos/</guid><description>&lt;p&gt;&lt;img src="https://p0.pikist.com/photos/309/174/network-equipment-hardware-internet-data-server-communication-connection-datacenter.jpg" alt="Network Equipment"&gt;&lt;/p&gt;
&lt;p&gt;Having a good firewall in place when building a home network is something that now is more important than ever. Traditionally, home firewalls were made to &lt;strong&gt;protect&lt;/strong&gt; the internal &lt;strong&gt;local network&lt;/strong&gt; from connection that could originate from the internet (That&amp;rsquo;s what you expect from an ISP provided modem/router combo). In this day and age, thanks to the &lt;strong&gt;fast rise&lt;/strong&gt; of &lt;strong&gt;smart home&lt;/strong&gt; gadgets, our home networks are becoming more and more occupied by little computers usually running firmware that is &lt;strong&gt;not possible to check&lt;/strong&gt; or manage. A firewall can help us mitigate the &lt;a href="https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=upnp"&gt;potential issues&lt;/a&gt; that can occur by using this kind of devices.&lt;/p&gt;
&lt;p&gt;The intent of this article is to provide a &lt;strong&gt;sensible&lt;/strong&gt; baseline &lt;strong&gt;configuration&lt;/strong&gt; that you can expand to suit your own needs. These concepts can be applied on a variety of SOHO routers (check yor router for VLAN tagging support and firewall capabilities between different networks).&lt;/p&gt;
&lt;p&gt;The router I&amp;rsquo;ve chosen to use for this post is the small and mighty &lt;a href="https://amzn.to/2FtPBv9"&gt;&lt;strong&gt;Ubiquiti EdgeRouter X&lt;/strong&gt;&lt;/a&gt;, a five port router that&amp;rsquo;s plenty capable of handling a medium-to-large home network without &lt;em&gt;breaking the bank&lt;/em&gt;. This router runs a fork of Vyatta called &lt;strong&gt;EdgeOS&lt;/strong&gt; as the stock operating system.
I&amp;rsquo;ve paired this device with a &lt;a href="https://amzn.to/2DXEkm6"&gt;&lt;strong&gt;Unifi Access Point&lt;/strong&gt;&lt;/a&gt; to satisfy all my Wi-Fi needs.&lt;/p&gt;
&lt;h2 id="high-level-overview"&gt;High-level overview&lt;/h2&gt;
&lt;p&gt;The steps you need to take care of are the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create&lt;/strong&gt; a new &lt;strong&gt;virtual interface&lt;/strong&gt; and assing it an IP address;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Attach&lt;/strong&gt; the virtual interface to an ethernet port;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Setup a firewall&lt;/strong&gt; to isolate this new interface from the rest of your network.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="edgeos-specific-configuration"&gt;EdgeOS specific configuration&lt;/h2&gt;
&lt;p&gt;To configure the EdgeRouter we are going to use the CLI. We are going to &lt;strong&gt;start&lt;/strong&gt; a configuration session by typing &amp;ldquo;configure&amp;rdquo; into the shell.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create&lt;/strong&gt; a network group that targets &lt;a href="https://tools.ietf.org/html/rfc1918"&gt;&lt;strong&gt;RFC1918&lt;/strong&gt;&lt;/a&gt; networks:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall group network-group RFC1918 description &lt;span style="color:#4070a0"&gt;&amp;#39;RFC1918 ranges&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall group network-group RFC1918 network 192.168.0.0/16
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall group network-group RFC1918 network 172.16.0.0/12
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall group network-group RFC1918 network 10.0.0.0/8
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="2"&gt;
&lt;li&gt;Setup the firewall to &lt;strong&gt;block&lt;/strong&gt; traffic to RFC1918 networks but allow DNS and DHCP:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL default-action accept
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL description &lt;span style="color:#4070a0"&gt;&amp;#39;IOT In and Local ruleset.&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;2.1 We are going to &lt;strong&gt;allow established and related&lt;/strong&gt; network traffic so that we can access the IOT devices from other networks.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL rule &lt;span style="color:#40a070"&gt;10&lt;/span&gt; action accept
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL rule &lt;span style="color:#40a070"&gt;10&lt;/span&gt; description &lt;span style="color:#4070a0"&gt;&amp;#39;allow established/related&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL rule &lt;span style="color:#40a070"&gt;10&lt;/span&gt; protocol all
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL rule &lt;span style="color:#40a070"&gt;10&lt;/span&gt; state established &lt;span style="color:#007020"&gt;enable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL rule &lt;span style="color:#40a070"&gt;10&lt;/span&gt; state related &lt;span style="color:#007020"&gt;enable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;2.2 We can&amp;rsquo;t forget to allow &lt;strong&gt;DHCP&lt;/strong&gt; requests so that our devices can get an IP address.
It might be useful to allow &lt;strong&gt;DNS&lt;/strong&gt; requests originating from our IOT VLAN too.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL rule &lt;span style="color:#40a070"&gt;20&lt;/span&gt; action accept
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL rule &lt;span style="color:#40a070"&gt;20&lt;/span&gt; description &lt;span style="color:#4070a0"&gt;&amp;#39;Allow DNS&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL rule &lt;span style="color:#40a070"&gt;20&lt;/span&gt; destination port &lt;span style="color:#40a070"&gt;53&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL rule &lt;span style="color:#40a070"&gt;20&lt;/span&gt; protocol tcp_udp
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL rule &lt;span style="color:#40a070"&gt;30&lt;/span&gt; action accept
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL rule &lt;span style="color:#40a070"&gt;30&lt;/span&gt; description &lt;span style="color:#4070a0"&gt;&amp;#39;Allow DHCP&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;8&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL rule &lt;span style="color:#40a070"&gt;30&lt;/span&gt; destination port &lt;span style="color:#40a070"&gt;67&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;9&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL rule &lt;span style="color:#40a070"&gt;30&lt;/span&gt; protocol udp
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;2.3 The last firewall rule we are going to setup is the one that &lt;strong&gt;blocks&lt;/strong&gt; traffic going to our other private network(s).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL rule &lt;span style="color:#40a070"&gt;40&lt;/span&gt; action drop
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL rule &lt;span style="color:#40a070"&gt;40&lt;/span&gt; description &lt;span style="color:#4070a0"&gt;&amp;#39;Drop RFC1918&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL rule &lt;span style="color:#40a070"&gt;40&lt;/span&gt; destination group network-group RFC1918
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; firewall name IOT_IN_LOCAL rule &lt;span style="color:#40a070"&gt;40&lt;/span&gt; protocol all
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="3"&gt;
&lt;li&gt;&lt;strong&gt;Create&lt;/strong&gt; a new &lt;strong&gt;virtual interface&lt;/strong&gt; for the VLAN intended to be used by our IOT devices:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; interfaces ethernet eth1 vif &lt;span style="color:#40a070"&gt;32&lt;/span&gt; address 10.0.32.1/24
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; interfaces ethernet eth1 vif &lt;span style="color:#40a070"&gt;32&lt;/span&gt; description IOT_VLAN
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; interfaces ethernet eht1 vif &lt;span style="color:#40a070"&gt;32&lt;/span&gt; mtu &lt;span style="color:#40a070"&gt;1500&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="4"&gt;
&lt;li&gt;&lt;strong&gt;Assign&lt;/strong&gt; the firewall ruleset to the &lt;em&gt;in&lt;/em&gt; and &lt;em&gt;local&lt;/em&gt; sides of the firewall.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; interfaces ethernet eth1 vif &lt;span style="color:#40a070"&gt;32&lt;/span&gt; firewall in name IOT_IN_LOCAL
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; interfaces ethernet eth1 vif &lt;span style="color:#40a070"&gt;32&lt;/span&gt; firewall &lt;span style="color:#007020"&gt;local&lt;/span&gt; name IOT_IN_LOCAL
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="5"&gt;
&lt;li&gt;Setup the &lt;strong&gt;DHCP&lt;/strong&gt; server to listen to the requests coming from the new VLAN:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; service dhcp-server shared-network-name IOT_VLAN authoritative disable
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; service dhcp-server shared-network-name IOT_VLAN subnet 10.0.32.0/24 default-router 10.0.32.1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; service dhcp-server shared-network-name IOT_VLAN subnet 10.0.32.0/24 dns-server 10.0.32.1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; service dhcp-server shared-network-name IOT_VLAN subnet 10.0.32.0/24 lease &lt;span style="color:#40a070"&gt;86499&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; service dhcp-server shared-network-name IOT_VLAN start 10.0.32.10 stop 10.0.32.100
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="6"&gt;
&lt;li&gt;Setup the &lt;strong&gt;DNS&lt;/strong&gt; forwarder to listen on this virtual interface:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; service dns forwarding listen-on eth1.32
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="7"&gt;
&lt;li&gt;Configure the &lt;strong&gt;mDNS repeater&lt;/strong&gt; to enable mDNS resolution from our other networks (useful for devices like the Chromecast).&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; service mdns repeater interface eth1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;set&lt;/span&gt; service mdns repeater interface eth1.32
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="8"&gt;
&lt;li&gt;&lt;strong&gt;Save&lt;/strong&gt; your configuration with &amp;ldquo;commit&amp;rdquo; and then &amp;ldquo;save&amp;rdquo;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At this point you should now have a new VLAN that cannot see your other networks but can still access the internet.&lt;/p&gt;
&lt;p class="affiliate-disclosure" style="font-size: 0.9rem; font-style: italic; opacity: 0.8; border-left: 2px solid #ccc; padding-left: 10px; margin: 1.5rem 0;"&gt;
Disclosure: Some links on this page might be affiliate links.
&lt;/p&gt;</description><category>IoT</category><category>Network</category></item><item><title>Configuring Nebula, a simple overlay networking tool</title><link>https://eliseomartelli.it/blog/2020-05-01-nebula/</link><pubDate>Fri, 01 May 2020 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2020-05-01-nebula/</guid><description>&lt;p&gt;VPNs are one of the preferred ways to tie-up multiple servers (and clients) together, but their nature becomes a &lt;strong&gt;bottleneck&lt;/strong&gt; when the infrastructure to &lt;em&gt;link&lt;/em&gt; becomes larger.&lt;br&gt;
A solution to this problem comes in the form of a software defined network &lt;strong&gt;(SDN)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/slackhq/nebula"&gt;Nebula&lt;/a&gt;&lt;/strong&gt; (from SlackHQ) addresses this problem by providing software that helps you build a point-to-point network between devices that can be situated (almost) anywhere in the world!&lt;/p&gt;
&lt;p&gt;The communication between devices is encrypted using the &lt;a href="https://noiseprotocol.org/"&gt;Noise Protocol Framework&lt;/a&gt;, the same used by Signal.&lt;/p&gt;
&lt;p&gt;And you know what&amp;rsquo;s great? Nebula is completely &lt;strong&gt;open source!&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="nebula-vocabulary"&gt;Nebula Vocabulary&lt;/h2&gt;
&lt;p&gt;Nebula&amp;rsquo;s vocabulary is not so different from normal networking, you should only get accostumed to two terms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;lighthouse;&lt;/li&gt;
&lt;li&gt;node.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Lighthouse&lt;/strong&gt; is the term used by Nebula to address &lt;strong&gt;nodes&lt;/strong&gt; that are publicly routable.&lt;br&gt;
A node connects first to the &lt;strong&gt;lighthouse&lt;/strong&gt;, then it discovers the most efficent path to reach other nodes on the network.&lt;/p&gt;
&lt;h2 id="configuring-nebula"&gt;Configuring Nebula&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s get to the fun part!&lt;/p&gt;
&lt;p&gt;As stated earlier, to run a Nebula Lighthouse you need a machine with a &lt;strong&gt;publicly routable&lt;/strong&gt; IP address, if you don&amp;rsquo;t have one, a little 5$ Droplet on &lt;a href="https://m.do.co/c/33e2f0a1e231"&gt;DigitalOcean&lt;/a&gt; will do just fine.&lt;br&gt;
This guide will show you how to run Nebula &lt;strong&gt;as a regular user&lt;/strong&gt;, in constrast with the official documentation that assumes you will run Nebula as the root user.&lt;/p&gt;
&lt;p&gt;On the machines you&amp;rsquo;d like to attach to Nebula, you need to &lt;strong&gt;download the binary&lt;/strong&gt; from the release page on GitHub and then extract it using &lt;em&gt;tar&lt;/em&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;$&lt;/span&gt; curl -OL https://github.com/slackhq/nebula/releases/download/v1.2.0/nebula-linux-amd64.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;$&lt;/span&gt; tar xzvf nebula-linux-amd64.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now you need to &lt;strong&gt;move the Nebula binary&lt;/strong&gt; to /usr/bin/ and set the right permissions.&lt;br&gt;
You can do it manually or you can use the &lt;em&gt;&lt;a href="https://man.cx/install"&gt;install&lt;/a&gt;&lt;/em&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;#&lt;/span&gt; install ./nebula /usr/bin
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Since we don&amp;rsquo;t want to run Nebula as root, it&amp;rsquo;s the right time to add a &lt;em&gt;system&lt;/em&gt; user (so it&amp;rsquo;s &lt;strong&gt;hidden&lt;/strong&gt; from your login manager) for Nebula.&lt;br&gt;
This new user doesn&amp;rsquo;t need a &lt;em&gt;home&lt;/em&gt; directory, so we&amp;rsquo;re instructing the &lt;em&gt;useradd&lt;/em&gt; command to not create one.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;#&lt;/span&gt; useradd --system --no-create-home nebula
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Following the &lt;a href="https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard"&gt;Filesystem Hierarchy Standard&lt;/a&gt; that states that configuration files should reside in the /etc directory, we&amp;rsquo;re going to &lt;strong&gt;create the directories&lt;/strong&gt; to store configuration files and certificates that are related to Nebula.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;#&lt;/span&gt; mkdir /etc/nebula
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;#&lt;/span&gt; mkdir /etc/nebula/certs
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To instantiate a Nebula network you have to &lt;strong&gt;generate a Nebula Certificate authority&lt;/strong&gt;, preferably on another host so you can keep your private key secure. Remember to copy the certificate to your hosts!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;$&lt;/span&gt; ./nebula-cert ca -name &lt;span style="color:#4070a0"&gt;&amp;#34;My really cool organization&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;$&lt;/span&gt; scp ca.crt user@&amp;lt;hostname&amp;gt;:/etc/nebula/certs
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Make sure to &lt;strong&gt;keep the ca.key file in a safe place&lt;/strong&gt;, you will need it if you want to add other hosts to your network.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generate a keypair&lt;/strong&gt; for each host you&amp;rsquo;d like to connect to your Nebula network and copy the pair to your Nebula&amp;rsquo;s hosts:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;$&lt;/span&gt; ./nebula-cert sign -name &lt;span style="color:#4070a0"&gt;&amp;#34;mylighthouse&amp;#34;&lt;/span&gt; -ip &lt;span style="color:#4070a0"&gt;&amp;#34;192.168.100.1/24&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;$&lt;/span&gt; scp mylighthouse.crt user@&amp;lt;hostname&amp;gt;:/etc/nebula/certs
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;$&lt;/span&gt; scp mylighthouse.key user@&amp;lt;hostname&amp;gt;:/etc/nebula/certs
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s time to configure Nebula!&lt;br&gt;
&lt;strong&gt;Create a configuration file&lt;/strong&gt; inside the previously created directory and open it with your text editor of choice. I&amp;rsquo;ve deviced to put my configuration in /etc/nebula/config.yml.
The configuration will look a bit different depending by the node, for example, my Lighthouse node is sporting this configuration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;pki&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;ca&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;/etc/nebula/certs/ca.crt&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;cert&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;/etc/nebula/certs/mylighthouse.crt&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;key&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;/etc/nebula/certs/mylighthouse.key&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;lighthouse&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;am_lighthouse&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;true&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;listen&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;host&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#40a070"&gt;0.0.0.0&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;port&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#40a070"&gt;4242&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;punchy&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;punch&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;true&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;tun&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;dev&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;nebula1&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;drop_local_broadcast&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;false&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;drop_multicast&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;false&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;tx_queue&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#40a070"&gt;500&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;mtu&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#40a070"&gt;1300&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;routes&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;unsafe_routes&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;firewall&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;26&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;conntrack&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;27&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;tcp_timeout&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;120h&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;28&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;udp_timeout&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;3m&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;29&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;default_timeout&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;10m&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;30&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;max_connections&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#40a070"&gt;100000&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;31&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;32&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;outbound&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;33&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt;# Allow all outbound traffic from this node&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;34&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;port&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;any&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;35&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;proto&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;any&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;36&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;host&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;any&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;37&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;38&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;inbound&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;39&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt;# Allow icmp between any nebula hosts&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;40&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;port&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;any&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;41&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;proto&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;icmp&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;42&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;host&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;any&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On my other nodes, the configuration looks something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;pki&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;ca&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;/etc/nebula/certs/ca.crt&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;cert&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;/etc/nebula/certs/mynode.crt&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;key&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;/etc/nebula/certs/mynode.key&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;static_host_map&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;&amp;#34;192.168.100.1&amp;#34;: &lt;/span&gt;[&lt;span style="color:#4070a0"&gt;&amp;#34;&amp;lt;lighthouse_public_ip&amp;gt;:4242&amp;#34;&lt;/span&gt;]&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;lighthouse&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;hosts&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#4070a0"&gt;&amp;#34;192.168.100.1&amp;#34;&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;punchy&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;punch&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;true&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;tun&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;dev&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;nebula1&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;drop_local_broadcast&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;false&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;drop_multicast&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;false&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;tx_queue&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#40a070"&gt;500&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;mtu&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#40a070"&gt;1300&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;routes&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;unsafe_routes&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;firewall&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;26&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;conntrack&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;27&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;tcp_timeout&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;120h&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;28&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;udp_timeout&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;3m&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;29&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;default_timeout&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;10m&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;30&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;max_connections&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#40a070"&gt;100000&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;31&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;32&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;outbound&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;33&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt;# Allow all outbound traffic from this node&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;34&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;port&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;any&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;35&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;proto&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;any&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;36&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;host&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;any&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;37&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;38&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;inbound&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;39&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#60a0b0;font-style:italic"&gt;# Allow icmp between any nebula hosts&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;40&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;port&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;any&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;41&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;proto&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;icmp&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;42&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;host&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;any&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that &lt;strong&gt;nodes&lt;/strong&gt; need to &lt;strong&gt;define their &amp;ldquo;Lighthouse&amp;rdquo;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To make our &amp;ldquo;nebula&amp;rdquo; user the owner of the /etc/nebula directory, we have to change the ownership of it. We should also change the permissions of the certificats to something less permissive.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;#&lt;/span&gt; chown -R nebula:nebula /etc/nebula
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;#&lt;/span&gt; chmod &lt;span style="color:#40a070"&gt;600&lt;/span&gt; /etc/nebula/certs/*
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;re almost done! The second-last thing to do is to add the &lt;em&gt;NET_CAP_ADMIN&lt;/em&gt; capability to the Nebula binary since it needs to be able to create a new interface on the system.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;#&lt;/span&gt; setcap &lt;span style="color:#bb60d5"&gt;cap_net_admin&lt;/span&gt;&lt;span style="color:#666"&gt;=&lt;/span&gt;+pe /usr/bin/nebula
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For our last step, we run Nebula as the nebula user on our hosts.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;#&lt;/span&gt; su nebula
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;$&lt;/span&gt; nebula -config /etc/nebula/config.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To &lt;strong&gt;test Nebula&lt;/strong&gt; open a new shell and try to ping another machine on the Nebula network, &lt;em&gt;it&amp;rsquo;s almost magical&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="nebula-as-a-systemd-unit"&gt;Nebula as a Systemd unit&lt;/h2&gt;
&lt;p&gt;A natural step to take after you&amp;rsquo;ve tested Nebula is to &lt;strong&gt;run it as a service&lt;/strong&gt; so you don&amp;rsquo;t need to start it manually each time you need it. Well, nothing could be simpler!&lt;br&gt;
You just have to create and edit a Systemd unit. An example can be found below:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0"&gt;Description&lt;/span&gt;&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;Nebula Service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0"&gt;After&lt;/span&gt;&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;network.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0"&gt;StartLimitIntervalSec&lt;/span&gt;&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;[Service]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0"&gt;Type&lt;/span&gt;&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;simple&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0"&gt;Restart&lt;/span&gt;&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;always&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0"&gt;RestartSec&lt;/span&gt;&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0"&gt;User&lt;/span&gt;&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;nebula&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0"&gt;ExecStart&lt;/span&gt;&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;/usr/bin/nebula -config /etc/nebula/config.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;[Install]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0"&gt;WantedBy&lt;/span&gt;&lt;span style="color:#666"&gt;=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;multi-user.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that this Systemd unit runs Nebula with the &amp;ldquo;nebula&amp;rdquo; user and starts just after reaching the network target.&lt;/p&gt;
&lt;p&gt;Now you need to &lt;strong&gt;reload&lt;/strong&gt; configuration files for the daemons on your system so Systemd can pick-up the new unit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;#&lt;/span&gt; systemctl daemon-reload
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can now &lt;strong&gt;enable&lt;/strong&gt; the Nebula service so it starts automatically after a reboot. If you&amp;rsquo;re not going to reboot, remember to &lt;strong&gt;start&lt;/strong&gt; the service manually. 😉&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;#&lt;/span&gt; systemctl &lt;span style="color:#007020"&gt;enable&lt;/span&gt; nebula.service
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;#&lt;/span&gt; systemctl start nebula.service
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h4 id="update-launchd-on-macos"&gt;Update: Launchd on macOS&lt;/h4&gt;
&lt;p&gt;If you want to run Nebula as a daemon on &lt;strong&gt;macOS&lt;/strong&gt;, create and edit a launchd plist file as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#007020"&gt;&amp;lt;!DOCTYPE plist PUBLIC &amp;#34;-//Apple//DTD PLIST 1.0//EN&amp;#34; &amp;#34;http://www.apple.com/DTDs/PropertyList-1.0.dtd&amp;#34;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;plist&lt;/span&gt; &lt;span style="color:#4070a0"&gt;version=&lt;/span&gt;&lt;span style="color:#4070a0"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt; &lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt; &lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Label&lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt; &lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/usr/local/bin/nebula&lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt; &lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;LaunchOnlyOnce&lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt; &lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt; &lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;ProgramArguments&lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt; &lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt; &lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/usr/local/bin/nebula&lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt; &lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;-config&lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt; &lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/etc/nebula/config.yml&lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt; &lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt; &lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;RunAtLoad&lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16&lt;/span&gt;&lt;span&gt; &lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17&lt;/span&gt;&lt;span&gt; &lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;&amp;lt;/plist&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Remember to load this new daemon:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#c65d09;font-weight:bold"&gt;#&lt;/span&gt; sudo launchctl load /Library/LaunchDaemons/com.nebula.plist
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Oh, nice to see you here!&lt;br&gt;
If you&amp;rsquo;ve reached the end of this post you can now enjoy your &lt;em&gt;freshly baked&lt;/em&gt; SDN!&lt;/p&gt;
&lt;p class="affiliate-disclosure" style="font-size: 0.9rem; font-style: italic; opacity: 0.8; border-left: 2px solid #ccc; padding-left: 10px; margin: 1.5rem 0;"&gt;
Disclosure: Some links on this page might be affiliate links.
&lt;/p&gt;</description><category>Network</category></item><item><title>Quick Tip: macOS as a cache for Apple Devices</title><link>https://eliseomartelli.it/blog/2020-04-09quick-tip-macos-as-a-cache-for-apple-devices/</link><pubDate>Thu, 09 Apr 2020 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2020-04-09quick-tip-macos-as-a-cache-for-apple-devices/</guid><description>&lt;p&gt;A cache server is a great tool to &lt;strong&gt;speed up&lt;/strong&gt; recurring network requests (example: system updates) and reduce the overall used internet bandwidth.&lt;/p&gt;
&lt;p&gt;Today we&amp;rsquo;ll see how to set-up a cache server for our Apple devices.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say that you have multiple Apple devices on your network and you happen to have a macOS device &lt;strong&gt;always powered on&lt;/strong&gt; (or a macOS vm, ed.), you can use the after-mentioned device as a cache for your other devices.&lt;/p&gt;
&lt;p&gt;To &lt;strong&gt;check&lt;/strong&gt; if it&amp;rsquo;s possible to use your Mac as a caching server, run this command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;$ AssetCacheManagerUtil canActivate
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you get a &amp;ldquo;&lt;strong&gt;yes&lt;/strong&gt;&amp;rdquo; as a response, you can set-up your cache server via this simple command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;$ sudo AssetCacheManagerUtil activate
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Your devices will now discover this cache server automatically after a &lt;strong&gt;reboot&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You can find more information about this macOS feature &lt;a href="https://support.apple.com/guide/mac-help/manage-content-caching-command-line-mac-mchla6d4541e/mac"&gt;here&lt;/a&gt;.&lt;/p&gt;</description><category>Apple</category></item><item><title>How to use pfSense to load balance between two ISPs</title><link>https://eliseomartelli.it/blog/2020-04-05-pfsense_multiple_wan/</link><pubDate>Sun, 05 Apr 2020 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2020-04-05-pfsense_multiple_wan/</guid><description>&lt;p&gt;Today&amp;rsquo;s post aims at helping people that are working/studying from home and don&amp;rsquo;t have a stable internet conection.&lt;br&gt;
Since me and my family are currently &lt;em&gt;staying at home&lt;/em&gt; we are &lt;strong&gt;taxing our ADSL2+&lt;/strong&gt; connection very hard with multiple simultaneous video conferences, so I had to find a way to keep all my family online.&lt;br&gt;
The way I&amp;rsquo;ve chosen to do that is using &lt;strong&gt;multiple WANs&lt;/strong&gt;, one of them being over &lt;strong&gt;LTE&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ibb.co/DWQNfqz"&gt;&lt;img src="https://i.ibb.co/LR1MdGY/Untitled-Artwork.png" alt="Untitled-Artwork" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;A pfSense box with &lt;strong&gt;3+ network interfaces&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;A WAN and a LAN interface already configured inside pfSense;&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;4G/LTE modem&lt;/strong&gt; that connects over ethernet.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you already have the prerequisites sorted out, let&amp;rsquo;s get to balancing!&lt;/p&gt;
&lt;h2 id="configure-the-additional-interface"&gt;Configure the additional interface&lt;/h2&gt;
&lt;p&gt;To start the &lt;em&gt;journey&lt;/em&gt; in load balancing, we&amp;rsquo;ll start by telling our pfSense box where to find the interface that we will later use to connect our LTE router.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ibb.co/5TcLbHh"&gt;&lt;img src="https://i.ibb.co/tBCDVrp/Screenshot-2020-04-05-at-21-36-07.png" alt="Screenshot-2020-04-05-at-21-36-07" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To add an interface you need to open the &lt;strong&gt;Assingnment&lt;/strong&gt; menu under &lt;strong&gt;Interfaces&lt;/strong&gt;, select the desidered interface in the dropdown menu, and then click &lt;strong&gt;add&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ibb.co/rt33d3Z"&gt;&lt;img src="https://i.ibb.co/wL77c7K/Screenshot-2020-04-05-at-21-36-51.png" alt="Screenshot-2020-04-05-at-21-36-51" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We can now &lt;strong&gt;configure&lt;/strong&gt; the chosen interface and assing it a name. In my case I had to setup the address of this interface with DHCP.&lt;/p&gt;
&lt;p&gt;Now it&amp;rsquo;s a right time to &lt;strong&gt;plug&lt;/strong&gt; our 4G/LTE modem to our pfSense box.&lt;/p&gt;
&lt;p&gt;At the bottom of the page I checked block private and bogus networks, then you can hit &lt;strong&gt;save&lt;/strong&gt; and reload the configuration.&lt;/p&gt;
&lt;h2 id="setup-a-multi-gateway"&gt;Setup a multi-gateway&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;re almost there!&lt;br&gt;
We have to &lt;strong&gt;create a multi-gateway&lt;/strong&gt; that we&amp;rsquo;ll later use to route our traffic through.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ibb.co/0VXCMSH"&gt;&lt;img src="https://i.ibb.co/yBR6y1H/Screenshot-2020-04-05-at-21-38-53.png" alt="Screenshot-2020-04-05-at-21-38-53" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We need to set a network tier, remember that a &lt;strong&gt;lower&lt;/strong&gt; number is &lt;strong&gt;preferred&lt;/strong&gt;.&lt;br&gt;
If both interfaces share the same network tier, pfSense will balance packets on both of them.&lt;br&gt;
Remember to change the &amp;ldquo;Trigger Level&amp;rdquo; to &amp;ldquo;Packet Loss or High Latency&amp;rdquo; so you will always use the best connection.&lt;/p&gt;
&lt;h3 id="configure-the-firewall-to-use-the-multi-gateway"&gt;Configure the firewall to use the multi-gateway&lt;/h3&gt;
&lt;p&gt;That&amp;rsquo;s our &lt;strong&gt;last step&lt;/strong&gt;!
The last thing to do is to configure our LAN firewall to &lt;strong&gt;route&lt;/strong&gt; connections to our new &lt;em&gt;gateway group&lt;/em&gt;.&lt;br&gt;
We need to open the &lt;strong&gt;Rules&lt;/strong&gt; page of our firewall, select our &lt;strong&gt;LAN&lt;/strong&gt; interface.&lt;/p&gt;
&lt;p&gt;Our rule will be a &lt;strong&gt;&amp;ldquo;Pass&amp;rdquo;&lt;/strong&gt; rule that will be configured as following:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Interface: Lan
Protocol: Any
Source: Any
Destination: Any
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href="https://ibb.co/fdqV7kv"&gt;&lt;img src="https://i.ibb.co/4RKwx8d/Screenshot-2020-04-05-at-21-40-58.png" alt="Screenshot-2020-04-05-at-21-40-58" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The last thing to do is going to the &lt;strong&gt;advanced&lt;/strong&gt; section of our firewall rule and set the &lt;strong&gt;gateway&lt;/strong&gt; to the previously created gateway group.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ibb.co/Jy7SPvd"&gt;&lt;img src="https://i.ibb.co/Hxz9Qt2/Screenshot-2020-04-05-at-21-41-14.png" alt="Screenshot-2020-04-05-at-21-41-14" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re finally done!&lt;br&gt;
You can now go and test your load balanced network!&lt;/p&gt;</description><category>Network</category></item><item><title>Using Ansible to deploy IoT devices</title><link>https://eliseomartelli.it/blog/2020-03-10-ansible_tasmota/</link><pubDate>Tue, 10 Mar 2020 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2020-03-10-ansible_tasmota/</guid><description>&lt;p&gt;This article is going to show you how an existing tool intended for completely different purposes can be used to automate the boring task of manually setting up and &lt;strong&gt;mantain a fleet of IoT devices.&lt;/strong&gt;&lt;br&gt;
For those of you who don&amp;rsquo;t know what &lt;a href="https://www.ansible.com/"&gt;&lt;strong&gt;Ansible&lt;/strong&gt;&lt;/a&gt; is, Ansible is an automation platform made for enterprises and for those in need of managing a big set of devices, usually servers.&lt;/p&gt;
&lt;h2 id="getting-our-feet-wet-with-ansible"&gt;Getting our feet wet with Ansible&lt;/h2&gt;
&lt;p&gt;Ansible revolves around the concept of &lt;a href="https://en.wikipedia.org/wiki/Infrastructure_as_code"&gt;&lt;em&gt;Infrastructure as Code&lt;/em&gt;&lt;/a&gt; and provides a set of tools to manage said infrastructure.&lt;br&gt;
One advantage of pursuing to keep our configurations within Ansible is the ability to reproduce builds (configurations, in our case).&lt;/p&gt;
&lt;h2 id="ansibles-basic-concepts"&gt;Ansible&amp;rsquo;s basic concepts&lt;/h2&gt;
&lt;p&gt;For the sake of simplicity, we&amp;rsquo;re going to focus on &lt;strong&gt;4 core concepts:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Playbooks&lt;/li&gt;
&lt;li&gt;Tasks&lt;/li&gt;
&lt;li&gt;Modules&lt;/li&gt;
&lt;li&gt;Inventory&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="playbooks"&gt;Playbooks&lt;/h3&gt;
&lt;p&gt;An Ansible Playbook is a YAML file that &lt;strong&gt;describes&lt;/strong&gt; the state of the devices involved in the configuration and what tasks should be done.&lt;/p&gt;
&lt;h3 id="tasks"&gt;Tasks&lt;/h3&gt;
&lt;p&gt;Tasks in Ansible are the &lt;strong&gt;actions&lt;/strong&gt; that will be executed.&lt;br&gt;
They run sequentially.&lt;/p&gt;
&lt;h3 id="modules"&gt;Modules&lt;/h3&gt;
&lt;p&gt;Modules are used in tasks and they express the &lt;strong&gt;&amp;ldquo;type&amp;rdquo; of action&lt;/strong&gt; that should be executed.&lt;/p&gt;
&lt;h3 id="inventory"&gt;Inventory&lt;/h3&gt;
&lt;p&gt;An inventory is a file that contains the hosts and the &lt;strong&gt;groups&lt;/strong&gt; that should be targeted by our tasks.&lt;/p&gt;
&lt;p&gt;After this little introduction, let&amp;rsquo;s get to it!&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;To follow this tutorial you need at least one device with &lt;a href="https://tasmota.github.io/"&gt;&lt;strong&gt;Tasmota&lt;/strong&gt;&lt;/a&gt; already connected to our WiFi network (preferably with a fixed IP address).&lt;br&gt;
You also need a computer with &lt;strong&gt;Python&lt;/strong&gt; and &lt;strong&gt;git&lt;/strong&gt; installed.&lt;/p&gt;
&lt;h2 id="installing-ansible"&gt;Installing Ansible&lt;/h2&gt;
&lt;p&gt;Installing Ansible is quite easy, many Linux distributions have it inside their repositories, you can also use &lt;strong&gt;pip&lt;/strong&gt;, and type this tiny command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;pip install ansible
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="creating-an-inventory"&gt;Creating an inventory&lt;/h2&gt;
&lt;p&gt;To create an inventory you should create an &lt;em&gt;inventory&lt;/em&gt; file and then edit it with your preferred file editor.&lt;/p&gt;
&lt;p&gt;An example file will look something like that:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[plugs]
192.168.0.10
[lights]
192.168.0.11
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As you can see we use square brackets to define host groups.&lt;/p&gt;
&lt;h3 id="using-the-inventory-to-store-variables"&gt;Using the inventory to store variables&lt;/h3&gt;
&lt;p&gt;Another great feature of the inventory is the ability to &lt;strong&gt;store variables&lt;/strong&gt; that can be used inside tasks.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[plugs]
192.168.0.10 friendly_name=&amp;#34;Bathroom Fan&amp;#34;
[lights]
192.168.0.11 friendly_name=&amp;#34;Kitchen Downlight&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="setup-the-tasmota-integration"&gt;Setup the Tasmota integration&lt;/h2&gt;
&lt;p&gt;Since Tasmota is not the typical use of Ansible we need to install a custom role to use the two systems together.&lt;/p&gt;
&lt;p&gt;The role we&amp;rsquo;re going to use today is Tobias Richter&amp;rsquo;s &lt;a href="https://galaxy.ansible.com/tobias_richter/tasmota"&gt;&amp;ldquo;Tasmota&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The command to install this role is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;ansible-galaxy install tobias_richter.tasmota
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="writing-the-playbook"&gt;Writing the playbook&lt;/h2&gt;
&lt;p&gt;Now it&amp;rsquo;s time to write the playbook!&lt;br&gt;
Create a new file named &lt;em&gt;playbook.yaml&lt;/em&gt; and then open it with your file editor.&lt;br&gt;
Now we have to create our first &lt;em&gt;play.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We start by defining the group of hosts we want to target and then we have to disable the &lt;em&gt;gather_facts&lt;/em&gt; function, as for the &lt;a href="https://github.com/tobias-richter/ansible-tasmota#fact-gathering"&gt;documentation&lt;/a&gt; of the custom role.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;hosts&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;all&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;gather_facts&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;no&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can now create our first task and give it a name:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;tasks&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;name&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;Ensure device reports status&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, we have to define what module we&amp;rsquo;re going to use. For this custom role, we have to use the &lt;em&gt;include_role&lt;/em&gt; module.&lt;br&gt;
Our task will become something like that:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;tasks&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;name&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;Ensure device reports status&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;include_role&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;name&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;ansible-tasmota&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now it&amp;rsquo;s time to &lt;strong&gt;define&lt;/strong&gt; the command we&amp;rsquo;re going to send to our devices.&lt;br&gt;
Edit your task as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;tasks&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;name&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;Ensure device is present&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;include_role&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;name&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;ansible-tasmota&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;vars&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;tasmota_commands&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;command&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;Status&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The resulting &lt;em&gt;playbook.yaml&lt;/em&gt; will look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;hosts&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;all&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;gather_facts&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;no&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;tasks&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;name&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;Ensure device is present&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;include_role&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;name&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;ansible-tasmota&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;vars&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;8&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;tasmota_commands&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;9&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;command&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;Status&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we can run our playbook like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;ansible-playbook -i inventory playbook.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can use this as a base to create more complex playbooks.&lt;/p&gt;
&lt;h2 id="another-example"&gt;Another example&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say we want to configure the MQTT connection of our devices, we can use a playbook that looks something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;hosts&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;all&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;gather_facts&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#007020;font-weight:bold"&gt;no&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;vars_files&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- default.yaml&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;tasks&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;name&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;Ensure template is set&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;include_role&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;name&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;ansible-tasmota&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;vars&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;name&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;Ensure mqtt configuration is present&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;include_role&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;name&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;ansible-tasmota&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;vars&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;tasmota_commands&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;command&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;Backlog&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;value&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#4070a0"&gt;&amp;#34;mqttuser &amp;lt;mqttuser&amp;gt;; mqttpassword &amp;lt;mqttpass&amp;gt;; mqttport 1883; mqtthost &amp;lt;mqtthost&amp;gt;;&amp;#34;&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As you can see, using Ansible to &lt;strong&gt;configure&lt;/strong&gt; a fleet of Tasmota devices is an &lt;strong&gt;easy&lt;/strong&gt; task to setup and will help you manage your growing collection of smart devices in the long run.&lt;/p&gt;</description><category>Ansible</category><category>Automation</category><category>IoT</category></item><item><title>Netflix Payments reminder with the help of Home Assistant</title><link>https://eliseomartelli.it/blog/2020-03-02-netflix-payments-home-assistant/</link><pubDate>Mon, 02 Mar 2020 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2020-03-02-netflix-payments-home-assistant/</guid><description>&lt;p&gt;Today&amp;rsquo;s project aims at tracking the &lt;em&gt;invoices&lt;/em&gt; of a shared account thanks to &lt;a href="https://home-assistant.io"&gt;Home Assistant&lt;/a&gt;.&lt;br&gt;
For those of you that don&amp;rsquo;t know what Home Assistant is, well, Home Assistant is a versatile home automation software that can integrate a lot of different devices. It&amp;rsquo;s starting to become an essential part of my living areas thanks to its possibilities.&lt;br&gt;
And sometimes you &lt;em&gt;just&lt;/em&gt; wish that you could use it to automate people (just kiddin&amp;rsquo;).&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re reading this, it&amp;rsquo;s probably too late and you&amp;rsquo;ve already got your feet wet with Home Assistant. Otherwise, you need an installation of Home Assistant. You can install it following the official documentation &lt;a href="https://www.home-assistant.io/getting-started/"&gt;here&lt;/a&gt;.&lt;br&gt;
You also need a Telegram bot, you can create one using Telegram&amp;rsquo;s &lt;a href="https://telegram.me/BotFather"&gt;@botfather&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="counting-in-home-assistant"&gt;Counting in Home Assistant&lt;/h2&gt;
&lt;p&gt;Home Assistant has a built-in way of counting &lt;em&gt;things&lt;/em&gt;: the &lt;a href="https://www.home-assistant.io/integrations/counter/"&gt;counter&lt;/a&gt; integration.&lt;br&gt;
This integration offers a way of storing an integer number and changing its value.&lt;br&gt;
We&amp;rsquo;re going to setup this integration in our configuration.yaml file (situated in the root of our Home Assistant configuration folder).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;counter&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;netflix_user_0&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;initial&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#40a070"&gt;0&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;step&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#40a070"&gt;4&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;name&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;Mario&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;netflix_user_1&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;initial&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#40a070"&gt;0&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;step&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#40a070"&gt;4&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;name&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;Luigi&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;netflix_user_2&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;initial&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#40a070"&gt;0&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;step&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#40a070"&gt;4&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;name&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;Wario&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="scheduling-counters-increments"&gt;Scheduling counter&amp;rsquo;s increments&lt;/h2&gt;
&lt;p&gt;In a &lt;em&gt;nutshell&lt;/em&gt;, Home Assistant makes things happen through automations.
Automations are &lt;em&gt;pieces of code&lt;/em&gt; that will run when something triggers it and/or some conditions are met.&lt;br&gt;
In a home automation system, they are the glue that can tie all of our devices together.&lt;br&gt;
Today we’ll use them to create a recurring task dedicated to incrementing our newly added counters.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;automation&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;alias&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;Increment Counter&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;trigger&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;platform&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;time&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;at&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#4070a0"&gt;&amp;#34;10:00:00&amp;#34;&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;condition&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;condition&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;template&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;value_template&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#4070a0"&gt;&amp;#34;{{ now().day == 25 }}&amp;#34;&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;action&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;service&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;counter.increment&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;entity_id&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- counter.netflix_user_0&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- counter.netflix_user_1&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- counter.netflix_user_2&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As for the above example, our automation will be composed by three distinct parts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A trigger;&lt;/li&gt;
&lt;li&gt;A condition;&lt;/li&gt;
&lt;li&gt;An action.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This automation will &lt;strong&gt;check&lt;/strong&gt; every day at 10:00 am if the current day is the 25th of the month, if the &lt;strong&gt;condition&lt;/strong&gt; is &lt;strong&gt;true&lt;/strong&gt;, it will execute the &lt;strong&gt;action&lt;/strong&gt; of incrementing the counters with the predefined step.&lt;/p&gt;
&lt;h2 id="notify-the-people-in-the-sharing-group"&gt;Notify the people in the sharing group&lt;/h2&gt;
&lt;p&gt;An important part of this project is &lt;strong&gt;notifying&lt;/strong&gt; your friends if there&amp;rsquo;s a change on their &amp;ldquo;debts&amp;rdquo;.
This part is tied to the messaging service of your choice.&lt;br&gt;
I consider myself lucky that our &amp;ldquo;sharing group&amp;rdquo; is on Telegram, widely supported by the official Home Assistant &lt;a href="https://www.home-assistant.io/integrations/telegram/"&gt;integration&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, you might have guessed it, we&amp;rsquo;re using &lt;strong&gt;Telegram&lt;/strong&gt; today.&lt;/p&gt;
&lt;p&gt;To configure this integration you have to put the following inside your configuration.yaml:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;telegram_bot&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;platform&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;polling&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;api_key&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;YOUR_API_KEY&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;allowed_chat_ids&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- YOUR_CHAT_ID&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- GROUP_CHAT_ID&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;notify&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;name&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;NOTIFIER_NAME&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;platform&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;telegram&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;chat_id&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;GROUP_CHAT_ID&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;You can get chat ids using &lt;a href="https://t.me/chatid_echo_bot"&gt;@chatid_echo_bot&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="sending-messages-to-a-telegram-group"&gt;Sending messages to a Telegram group&lt;/h2&gt;
&lt;p&gt;After the setup of the Telegram platform we need to find a way to send notifications to our group.&lt;br&gt;
Let&amp;rsquo;s assume we decide to send a message to our group every time we increment the counters.&lt;br&gt;
To do so we just need to &lt;strong&gt;edit&lt;/strong&gt; our previous automation and add the following &lt;strong&gt;action&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;service&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;notify.netflix_group&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;data_template&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;message&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&amp;gt;&lt;span style="color:#4070a0;font-style:italic"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt; ⚠️ Status:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt; {{ states.counter.netflix_user_0.name }}: € {{ states.counter.netflix_user_0.state }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt; {{ states.counter.netflix_user_1.name }}: € {{ states.counter.netflix_user_1.state }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt; {{ states.counter.netflix_user_2.name }}: € {{ states.counter.netflix_user_2.state }}&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;re using &lt;strong&gt;templates&lt;/strong&gt; so we can &lt;strong&gt;dynamically&lt;/strong&gt; generate the message.&lt;/p&gt;
&lt;p&gt;Reached this point, we can &lt;em&gt;call it a day&lt;/em&gt; and the basics of our system are now working. But at the moment we have no way to track &lt;em&gt;&amp;ldquo;automatically&amp;rdquo;&lt;/em&gt; if a user paid its &lt;em&gt;debt&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="user-input-from-telegram-inline-keyboard"&gt;User input from Telegram inline keyboard&lt;/h2&gt;
&lt;p&gt;One of the options given by Telegram to interact with bots is the &lt;strong&gt;inline keyboard&lt;/strong&gt;.&lt;br&gt;
Inline keyboards are a set of buttons &lt;em&gt;attached&lt;/em&gt; to a message sent by a bot.&lt;br&gt;
Each button &lt;em&gt;triggers&lt;/em&gt; a callback (to which Home Assistant can respond).&lt;/p&gt;
&lt;h3 id="adding-an-inline-keyboard-to-a-message"&gt;Adding an inline keyboard to a message&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://i.ibb.co/xHLSbbQ/Screenshot-20200302-001437-01.jpg" alt="Screenshot of a Telegram chat showing the inline keyboard"&gt;&lt;/p&gt;
&lt;p&gt;Adding an inline keyboard to a message is a &lt;em&gt;trivial&lt;/em&gt; operation, we just need to define our keyboard in our message.&lt;br&gt;
It can be done by editing the previous action by adding:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;data&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;inline_keyboard&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#4070a0"&gt;&amp;#34;User0:/dec0&amp;#34;&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#4070a0"&gt;&amp;#34;User1:/dec1&amp;#34;&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#4070a0"&gt;&amp;#34;User2:/dec2&amp;#34;&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;strong&gt;resulting action&lt;/strong&gt; will look something like that:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;service&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;notify.netflix_group&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;data_template&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;message&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&amp;gt;&lt;span style="color:#4070a0;font-style:italic"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt; ⚠️ Status:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt; {{ states.counter.netflix_user_0.name }}: € {{ states.counter.netflix_user_0.state }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt; {{ states.counter.netflix_user_1.name }}: € {{ states.counter.netflix_user_1.state }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt; {{ states.counter.netflix_user_2.name }}: € {{ states.counter.netflix_user_2.state }}&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;data&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;inline_keyboard&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#4070a0"&gt;&amp;#34;User0:/dec0&amp;#34;&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#4070a0"&gt;&amp;#34;User1:/dec1&amp;#34;&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#4070a0"&gt;&amp;#34;User2:/dec2&amp;#34;&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="reacting-to-telegram-callbacks"&gt;Reacting to Telegram callbacks&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;We&amp;rsquo;re almost there!&lt;/strong&gt; We have counters, buttons, automations and messages.&lt;br&gt;
Now we should let our friends notify our system with the buttons mentioned above.&lt;br&gt;
We have to &lt;strong&gt;react&lt;/strong&gt; to their callbacks: according to the &lt;a href="https://www.home-assistant.io/integrations/telegram_bot#sample-automations-with-callback-queries-and-inline-keyboards"&gt;official documentation&lt;/a&gt;, we need another automation to react to the callbacks.&lt;/p&gt;
&lt;p&gt;Our automation is gonna look like this one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;alias&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;Other Netflix Decrement Counter&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;trigger&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;platform&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;event&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;event_type&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;telegram_callback&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;event_data&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;chat_id&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;GROUP_CHAT_ID&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;condition&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;condition&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;template&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;value_template&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&amp;gt;&lt;span style="color:#4070a0;font-style:italic"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt; {{ &amp;#34;/dec&amp;#34; in trigger.event.data.command }}&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;action&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;service&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;counter.decrement&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;data_template&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;entity_id&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&amp;gt;&lt;span style="color:#4070a0;font-style:italic"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt; counter.netflix_user_{{ trigger.event.data.command | replace(&amp;#34;/dec&amp;#34;, &amp;#34;&amp;#34;) }}&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;strong&gt;condition&lt;/strong&gt; is there to &lt;strong&gt;check&lt;/strong&gt; if the command that we received contains the string: &amp;ldquo;/dec&amp;rdquo;.&lt;br&gt;
In the action we see another &lt;strong&gt;template&lt;/strong&gt;, this one is used to get the right counter based on the button that got pressed.&lt;/p&gt;
&lt;h3 id="updating-the-message"&gt;Updating the message&lt;/h3&gt;
&lt;p&gt;A possible solution to notify of the decrement is to send another message, but I&amp;rsquo;m not using this solution because it appears quite &lt;em&gt;&amp;ldquo;chatty&amp;rdquo;&lt;/em&gt; to me and I don&amp;rsquo;t want to annoy my friends.&lt;br&gt;
Since Telegram supports &lt;strong&gt;editing of messages&lt;/strong&gt;, I&amp;rsquo;ve opted to use this solution.
We just need to add another action to the previous automation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1&lt;/span&gt;&lt;span&gt;- &lt;span style="color:#062873;font-weight:bold"&gt;service&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;telegram_bot.edit_message&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;data_template&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;message_id&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#4070a0"&gt;&amp;#34;{{ trigger.event.data.message.message_id }}&amp;#34;&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;chat_id&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#4070a0"&gt;&amp;#34;{{ trigger.event.data.chat_id }}&amp;#34;&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;title&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#4070a0"&gt;&amp;#34;*Message edit*&amp;#34;&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;message&lt;/span&gt;:&lt;span style="color:#bbb"&gt; &lt;/span&gt;&amp;gt;&lt;span style="color:#4070a0;font-style:italic"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt; ⚠️ Status:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt; {{ states.counter.netflix_user_0.name }}: € {{ states.counter.netflix_user_0.state }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt; {{ states.counter.netflix_user_1.name }}: € {{ states.counter.netflix_user_1.state }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13&lt;/span&gt;&lt;span&gt;&lt;span style="color:#4070a0;font-style:italic"&gt; {{ states.counter.netflix_user_2.name }}: € {{ states.counter.netflix_user_2.state }}&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;&lt;span style="color:#062873;font-weight:bold"&gt;inline_keyboard&lt;/span&gt;:&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#4070a0"&gt;&amp;#34;User0:/dec0&amp;#34;&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#4070a0"&gt;&amp;#34;User1:/dec1&amp;#34;&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17&lt;/span&gt;&lt;span&gt;&lt;span style="color:#bbb"&gt; &lt;/span&gt;- &lt;span style="color:#4070a0"&gt;&amp;#34;User2:/dec2&amp;#34;&lt;/span&gt;&lt;span style="color:#bbb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="final-steps"&gt;Final Steps&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;re ready to deploy!&lt;br&gt;
Add your &lt;em&gt;new, and fresh baked&lt;/em&gt; Telegram bot to your sharing group.&lt;br&gt;
Your friends will be grateful for this simple and effective reminder 😇.&lt;/p&gt;</description><category>Misc</category></item><item><title>How to self-host Bitwarden (with Docker)</title><link>https://eliseomartelli.it/blog/2019-02-20-bitwardenrs/</link><pubDate>Wed, 20 Feb 2019 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2019-02-20-bitwardenrs/</guid><description>&lt;p&gt;A &lt;strong&gt;password manager&lt;/strong&gt; is one of the most essential tools that a modern &lt;em&gt;citizen&lt;/em&gt; of the internet should have since 99.99% of services require one to provide a series of characters to later login.&lt;br&gt;
Managing passwords becomes more and more difficult if you want to meet &lt;strong&gt;secure&lt;/strong&gt; standards. That&amp;rsquo;s why, lately, password managers like Bitwarden, LastPass and 1Password are gaining traction with lots of users.&lt;br&gt;
My journey with password managers started a &lt;em&gt;long time ago&lt;/em&gt; but now I&amp;rsquo;ve been using BitWarden for over an year and at the beginning of this month I&amp;rsquo;ve moved to a self hosted solution.&lt;/p&gt;
&lt;h2 id="the-importance-of-self-hosting"&gt;The importance of self-hosting&lt;/h2&gt;
&lt;p&gt;Self-hosting, in this day and age, is an important practice that, if possible, should be adopted by more and more people.&lt;br&gt;
&lt;em&gt;It&amp;rsquo;s not always fun and games&lt;/em&gt; but tools like Docker can come to the rescue!&lt;br&gt;
There&amp;rsquo;s nothing to loose going with a self hosted solution (ok, maybe time), but if you take a look at the vantages, worries should just fade away.&lt;/p&gt;
&lt;h2 id="instructions"&gt;Instructions&lt;/h2&gt;
&lt;h3 id="0-requisites"&gt;0. Requisites&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A computer or a NAS that you can use as a server;&lt;/li&gt;
&lt;li&gt;Some cli bravery;&lt;/li&gt;
&lt;li&gt;A router that can do port-forwarding (if you want to expose your instance to the internet).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="1-installing-docker"&gt;1. Installing Docker&lt;/h3&gt;
&lt;h4 id="11-on-macos-with-brew"&gt;1.1 On macOS (with Brew)&lt;/h4&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;brew cask install docker
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="12-on-windows-with-choco"&gt;1.2 On Windows (with Choco)&lt;/h4&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;choco install docker-desktop
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="13-on-linux"&gt;1.3 On Linux&lt;/h4&gt;
&lt;p&gt;Every distro handles the installation of Docker in a slightly different way. Check out the &lt;a href="https://docs.docker.com/install/"&gt;Docker&amp;rsquo;s official doc&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;### &lt;/p&gt;</description><category>tutorial</category><category>privacy</category><category>selfhost</category></item><item><title>Going Cashless As A Student [Turin 2018]</title><link>https://eliseomartelli.it/blog/2018-12-22-going-cashless-as-a-student/</link><pubDate>Sat, 22 Dec 2018 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2018-12-22-going-cashless-as-a-student/</guid><description>&lt;p&gt;&lt;img src="https://eliseomartelli.it/assets/static/posts/2018-12-22-going_cashless_as_a_student/paymentshero.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Before we start talking about &lt;em&gt;going cashless&lt;/em&gt; we need to talk about the reason behind this choice.&lt;br&gt;
Going &lt;em&gt;(almost)&lt;/em&gt; completely cashless ain&amp;rsquo;t &lt;strong&gt;easy&lt;/strong&gt; and requires at least one rollback solution if something goes wrong (and yep, it &lt;strong&gt;happens&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;As someone who loves tech but has a &lt;em&gt;love-hate&lt;/em&gt; relationship with money (especially with cash), going cashless means worrying less about the things you&amp;rsquo;ve got in your pocket.&lt;/p&gt;
&lt;p&gt;Going cashless means relying on payment services and below you can find a list of the things I use daily to fulfill my payments needs.&lt;/p&gt;
&lt;h2 id="services-i-use"&gt;Services I Use&lt;/h2&gt;
&lt;h3 id="apps"&gt;Apps&lt;/h3&gt;
&lt;h4 id="micropayments-below-10"&gt;Micropayments (Below €10)&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://lh3.googleusercontent.com/GVfRs1866eQnPDaSktJNQSSAaVPiKM9X_o2PFARerSAVkMl-BiP20lHQY9zMiF6Sj0I=s360-rw" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://satispay.it"&gt;Satispay&lt;/a&gt;.
I use &lt;strong&gt;Satispay&lt;/strong&gt; regularly for my &lt;em&gt;day-to-day&lt;/em&gt; payments of small (and &lt;em&gt;not-vital&lt;/em&gt;) things.
It&amp;rsquo;s a &lt;em&gt;small and dependable&lt;/em&gt; app that&amp;rsquo;s available on both iOS and Android.
But a payment app has the priority of being adopted and here, in Turin, a lot of places accept Satispay.&lt;/p&gt;
&lt;p&gt;This app is like the &lt;em&gt;Venmo of Italy&lt;/em&gt;.&lt;/p&gt;
&lt;h4 id="normal-payments-10---100"&gt;Normal Payments (€10 - €100)&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://lh3.googleusercontent.com/E21ns8ZOS6wRk6CgX-GR02QBmUydHJYXrEVbrCJu7QOA6wu66FOAFcPTi73Lt4McPa4v=s360-rw" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Normal payments&lt;/strong&gt; make up the most of my monthly expenses and the 99% of them go through my phone.&lt;br&gt;
The main app I use for this kind of payments is &lt;a href="https://www.intesasanpaolo.com/"&gt;Intesa San Paolo Mobile&lt;/a&gt; (my bank).&lt;br&gt;
The app is &lt;strong&gt;sleek&lt;/strong&gt; and &lt;strong&gt;modern&lt;/strong&gt;, supports fingerprint and &lt;strong&gt;NFC&lt;/strong&gt; payments.&lt;br&gt;
Since the widespread adoption of NFC point of sales is &lt;strong&gt;continuously rising&lt;/strong&gt;, NFC mobile payments are a well-made possibility that&amp;rsquo;s secure and practical.&lt;/p&gt;
&lt;h4 id="big-payments-100"&gt;Big Payments (100€+)&lt;/h4&gt;
&lt;p&gt;For bigger payments I use a mix of techniques.&lt;br&gt;
While using mostly credit/debit cards for my day to day payments, I&amp;rsquo;ve taken a different approach if the expense is recurring.
&lt;strong&gt;Wire transfers&lt;/strong&gt;, as old as they are, can still be used for this kind of things.&lt;/p&gt;
&lt;h3 id="worst-cases-and-how-i-fix-them"&gt;Worst cases (and how I fix them)&lt;/h3&gt;
&lt;h4 id="public-transportation"&gt;Public Transportation&lt;/h4&gt;
&lt;p&gt;Public transport tickets here in Turin can be a bit cumbersome for new-comers.&lt;br&gt;
You need to have a &lt;a href="https://bip.piemonte.it/carta-bip-cose-e-come-funziona/"&gt;BipCard&lt;/a&gt; or some sorts of smartcard issued by your University (or workplace) then you can add bus tickets on that card.&lt;br&gt;
Paying for tickets can be done by going to a tobacconist (and using cash) or you can use the &lt;a href="https://play.google.com/store/apps/details?id=it.to.gtt.tomove"&gt;GttToMove&lt;/a&gt; and pay online (my preferred solution).&lt;/p&gt;
&lt;h4 id="farmers-market"&gt;Farmer&amp;rsquo;s Market&lt;/h4&gt;
&lt;p&gt;As someone who loves cooking and knows that the freshness of the ingredients is one of the most important things, Farmer&amp;rsquo;s Markets are a &lt;strong&gt;must&lt;/strong&gt;.&lt;br&gt;
The problem? Stands &lt;em&gt;aren&amp;rsquo;t so happy to accept credit cards&lt;/em&gt;, especially for small payments.
Cleanest solution? Rolling back to cash.&lt;br&gt;
It takes an extra step, but it&amp;rsquo;s not that bad. I &lt;strong&gt;plan my expenses&lt;/strong&gt; in advance to &lt;strong&gt;minimize&lt;/strong&gt; the amount of spare cash left in the wallet so I&amp;rsquo;m almost cashless 😂.&lt;/p&gt;
&lt;h3 id="whats-in-my-wallet"&gt;What&amp;rsquo;s in my wallet?&lt;/h3&gt;
&lt;p&gt;One of the most asked questions to me is: &amp;ldquo;Hey, Eliseo, what&amp;rsquo;s in your wallet?&amp;rdquo;.
As someone that declares to be for the most part cashless, I&amp;rsquo;ve seen a &lt;em&gt;growing interest&lt;/em&gt; in my wallet from third parties.&lt;br&gt;
So here it is!&lt;/p&gt;
&lt;h4 id="my-wallet"&gt;My wallet&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Credit card(s);&lt;/li&gt;
&lt;li&gt;University badge;&lt;/li&gt;
&lt;li&gt;A bottle opener;&lt;/li&gt;
&lt;li&gt;A paper cutter;&lt;/li&gt;
&lt;li&gt;A USB stick (yep, still a thing);&lt;/li&gt;
&lt;li&gt;Driving license;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;rsquo;s &lt;em&gt;simple&lt;/em&gt;, it&amp;rsquo;s a part of my daily check &lt;strong&gt;triple patting&lt;/strong&gt; (Wallet, phone and keys) and if I&amp;rsquo;m not going to drive, I can &lt;em&gt;leave it&lt;/em&gt; behind at home.&lt;/p&gt;
&lt;h3 id="managing-my-expenses"&gt;Managing my expenses&lt;/h3&gt;
&lt;p&gt;You can argue that using cashless payment methods can alienate yourself from your own spending, and yeah, I&amp;rsquo;m with you on this one.&lt;br&gt;
You need to have a method to track what are you spending (and how) and after months of using a Google Drive Spreadsheet I&amp;rsquo;ve graduated to &lt;a href="https://firefly-iii.org/"&gt;Firefly III&lt;/a&gt;, a self-hosted money tracking solution.&lt;/p&gt;
&lt;p&gt;Going cashless is &lt;strong&gt;possible&lt;/strong&gt;, is convenient and less stressful (at least for me).
Thank you for your time and &lt;strong&gt;#GoCashless&lt;/strong&gt;!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Do you want to talk to me about projects, fun stuff and other things that might be interesting?&lt;/p&gt;
&lt;p&gt;Hit me up on &lt;a href="http://twitter.com/eliseomartelli"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;</description><category>finance</category></item><item><title>Using Home Assistant to control computers through SSH</title><link>https://eliseomartelli.it/blog/2018-10-14-homeassistant-ssh-remote/</link><pubDate>Sun, 14 Oct 2018 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2018-10-14-homeassistant-ssh-remote/</guid><description>&lt;p&gt;&lt;img src="https://image.ibb.co/gispJp/Screenshot-2018-10-14-at-12-06-28.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Home Assistant is starting to take a nice shape here in my college bedroom and I&amp;rsquo;m trying to add even more commodities to it.&lt;br&gt;
The last one? Opening apps on my Mac Mini when I&amp;rsquo;m laying in bed at night.&lt;/p&gt;
&lt;p&gt;Yesterday I was researching on how I could control my Mac remotely, but then I thought: &amp;ldquo;what if I use the tools that Apple baked in macOS?&amp;rdquo;. SSH became the obvious choice.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the steps I took to make it possible:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Enable SSH on your computer. On a Mac you do this with &lt;em&gt;System Preferencess.app&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;System Preferences.app &amp;gt; Sharing &amp;gt; Remote Login
&lt;/code&gt;&lt;/pre&gt;&lt;ol start="2"&gt;
&lt;li&gt;On the machine that runs Home Assistant, generate a new key pair using the user that runs Home Assistant.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ &amp;gt; ssh-keygen -t rsa
&lt;/code&gt;&lt;/pre&gt;&lt;ol start="3"&gt;
&lt;li&gt;Then, appended the generated public key to the Mac authorized_keys file.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ &amp;gt; cat .ssh/id_rsa.pub | ssh &amp;lt;mac_user&amp;gt;@&amp;lt;mac_ip&amp;gt; &amp;#39;cat &amp;gt;&amp;gt; .ssh/authorized_keys&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;ol start="4"&gt;
&lt;li&gt;Add something like that to your Home Assistant configuration file:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;shell_command:
open_website_fullscreen: ssh &amp;lt;mac_user&amp;gt;@&amp;lt;mac_ip&amp;gt; &amp;#34;open -a Google\ Chrome -n --args -app=&amp;lt;website_url&amp;gt; --start-fullscreen&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;ol start="5"&gt;
&lt;li&gt;Restart Home Assistant&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now you&amp;rsquo;ll see the shell scommand expsed as a service.&lt;br&gt;
From now on you can add this service to your frontend, call it from your smart speaker or use it inside your automations.&lt;/p&gt;</description><category>iot</category><category>automation</category><category>diy</category></item><item><title>Introducing: General Purpose Lightroom Preset System</title><link>https://eliseomartelli.it/blog/2018-08-07-introducing-presets/</link><pubDate>Tue, 07 Aug 2018 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2018-08-07-introducing-presets/</guid><description>&lt;p&gt;I love computer science and programming but my heart is a pretty big place and it can handle a lot of things.&lt;br&gt;
Photograpy is one of those.&lt;/p&gt;
&lt;p&gt;This is my first public preset pack and it&amp;rsquo;s an expandable system.&lt;br&gt;
It&amp;rsquo;s the first Lightroom/Adobe Camera Raw preset pack published on Github that I&amp;rsquo;m aware of so anyone can submit new things to the pack.&lt;/p&gt;
&lt;h2 id="examples"&gt;Examples&lt;/h2&gt;
&lt;h4 id="before"&gt;Before&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://ibb.co/cN5wTz"&gt;&lt;img src="https://preview.ibb.co/im9O8z/IMG_0442_2.jpg" alt="IMG_0442_2"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="after"&gt;After&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://ibb.co/mVTi8z"&gt;&lt;img src="https://preview.ibb.co/eZoi8z/IMG_0442.jpg" alt="IMG_0442"&gt;&lt;/a&gt;
&lt;a href="https://ibb.co/m9Z4gK"&gt;&lt;img src="https://preview.ibb.co/d3SPgK/IMG_3364.jpg" alt="IMG_3364"&gt;&lt;/a&gt;
&lt;a href="https://ibb.co/chRH1K"&gt;&lt;img src="https://preview.ibb.co/ffuRve/IMG_3364_2.jpg" alt="IMG_3364_2"&gt;&lt;/a&gt;
&lt;a href="https://ibb.co/idskoz"&gt;&lt;img src="https://preview.ibb.co/hNRpFe/IMG_8511.jpg" alt="IMG_8511"&gt;&lt;/a&gt;
&lt;a href="https://ibb.co/hK2eFe"&gt;&lt;img src="https://preview.ibb.co/c3fAMK/IMG_8511_2.jpg" alt="IMG_8511_2"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/eliseomartelli/LR-General-Purpose/archive/master.zip"&gt;Download&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Do you want to talk to me about projects, fun stuff and other things that might be interesting?&lt;/p&gt;
&lt;p&gt;Hit me up on &lt;a href="http://twitter.com/eliseomartelli"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;</description><category>legacy</category></item><item><title>My Cheap Smart Home Setup - How do I get the public transport status?</title><link>https://eliseomartelli.it/blog/2017-11-19-public-transport-state-ha/</link><pubDate>Sun, 19 Nov 2017 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2017-11-19-public-transport-state-ha/</guid><description>&lt;p&gt;Turin is a &lt;strong&gt;great city&lt;/strong&gt; and I&amp;rsquo;m enjoying it.&lt;br&gt;
The only problem is that &lt;strong&gt;I use public transport&lt;/strong&gt; for almost anything.&lt;br&gt;
Finding out that there&amp;rsquo;s an &lt;em&gt;unexpected&lt;/em&gt; public transports &lt;em&gt;strike&lt;/em&gt; is always a &lt;strong&gt;pain.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I had to solve this problem.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;m working on my Smart Home project, I decided to &lt;strong&gt;include &amp;ldquo;strike data&amp;rdquo;&lt;/strong&gt; into it so when I wake-up in the morning and I look at my &lt;a href="http://www.eliseomartelli.it/iot/automation/diy/2017/11/15/wall-mount-controller-homeassistant.html"&gt;Wall Mounted Controller&lt;/a&gt; I&amp;rsquo;ll get to know the &lt;strong&gt;status of the service&lt;/strong&gt;, I&amp;rsquo;ll never get a nasty surprise.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.imgur.com/QWWLoaP.png" alt="Public Transport"&gt;&lt;/p&gt;
&lt;h3 id="the-route"&gt;The Route&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Public transportation in Turin&lt;/strong&gt; is mainly managed by &lt;a href="http://www.gtt.to.it/cms/"&gt;&lt;strong&gt;GTT&lt;/strong&gt; (Gruppo Torinese Trasporti)&lt;/a&gt; and they&amp;rsquo;ve a nice &lt;a href="http://www.gtt.to.it/cms/avvisi-e-informazioni-di-servizio"&gt;page&lt;/a&gt; where they publish line changes, news and strikes.&lt;br&gt;
They also offer an &lt;strong&gt;RSS feed&lt;/strong&gt; of this page &lt;a href="http://www.gtt.to.it/cms/avvisi-e-informazioni-di-servizio?format=feed&amp;amp;type=rss"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now we need to decide &lt;strong&gt;how to get the data&lt;/strong&gt; to our Home Assistant instance.&lt;br&gt;
I decided to take the &lt;strong&gt;IFTTT way.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t know IFTTT, well, &lt;strong&gt;IFTTT&lt;/strong&gt; is a &lt;strong&gt;easy&lt;/strong&gt; and &lt;strong&gt;free&lt;/strong&gt; web-service you can use to create chains of conditional statements called &lt;strong&gt;applets&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Today we&amp;rsquo;ll use two IFTTT &lt;strong&gt;services&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RSS Feed;&lt;/li&gt;
&lt;li&gt;Web Hooks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first one will &lt;strong&gt;read&lt;/strong&gt; the RSS feed, the second one will &lt;strong&gt;post the data&lt;/strong&gt; to our Home Assistant instance.&lt;/p&gt;
&lt;h3 id="setup"&gt;Setup&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;You should be registered to IFTTT to continue&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Open&lt;/strong&gt; &lt;a href="http://ifttt.com"&gt;ifttt.com&lt;/a&gt; and login to your account;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Click&lt;/strong&gt; on your account name on the top right and choose &amp;ldquo;New Applet&amp;rdquo;;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Click&lt;/strong&gt; on the &amp;ldquo;this&amp;rdquo; button;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://i.imgur.com/oKNJoCD.png" alt="Imgur"&gt;
4. &lt;strong&gt;Click&lt;/strong&gt; the &amp;ldquo;RSS Feed&amp;rdquo; tile;
5. &lt;strong&gt;Click&lt;/strong&gt; the &amp;ldquo;New feed item matches&amp;rdquo; tile;
6. &lt;strong&gt;Set&lt;/strong&gt; the keyword to the one used by your public transit company (in my case I use: &amp;ldquo;sciopero&amp;rdquo;);
7. &lt;strong&gt;Set&lt;/strong&gt; the Feed URL to the one your company publishes the info on;
8. &lt;strong&gt;Click&lt;/strong&gt; on &amp;ldquo;Create trigger&amp;rdquo;;
9. &lt;strong&gt;Click&lt;/strong&gt; on the &amp;ldquo;that&amp;rdquo; link;
10. &lt;strong&gt;Click&lt;/strong&gt; on the &amp;ldquo;Webhooks&amp;rdquo; tile;
11. &lt;strong&gt;Click&lt;/strong&gt; on the &amp;ldquo;Make a web request&amp;rdquo; tile;
12. &lt;strong&gt;Set&lt;/strong&gt; the URL to your Home Assistant API endpoint:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;http://&amp;lt;YOUR HOMEASSISTANT IP&amp;gt;:8123/api/states/sensor.&amp;lt;YOUR SENSOR NAME&amp;gt;?api_password=&amp;lt;YOUR HOMEASSISTANT PASSWORD&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;ol start="13"&gt;
&lt;li&gt;&lt;strong&gt;Set&lt;/strong&gt; the method to &amp;ldquo;POST&amp;rdquo;;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set&lt;/strong&gt; the Content Type to &amp;ldquo;application/json&amp;rdquo;;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set&lt;/strong&gt; the body to:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; {
&amp;#34;state&amp;#34;: &amp;#34;{% raw %}{{EntryTitle}}{% endraw %}&amp;#34;,
&amp;#34;attributes&amp;#34;: {
&amp;#34;friendly_name&amp;#34;: &amp;#34;&amp;lt;YOUR SENSOR FRIENDLY NAME&amp;gt;&amp;#34;,
&amp;#34;icon&amp;#34;: &amp;#34;mdi:bus&amp;#34;
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;ol start="16"&gt;
&lt;li&gt;&lt;strong&gt;Click&lt;/strong&gt; the &amp;ldquo;Create action&amp;rdquo; button;&lt;/li&gt;
&lt;li&gt;You&amp;rsquo;re &lt;strong&gt;ready&lt;/strong&gt; to go!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;strong&gt;next time&lt;/strong&gt; your public transportation company &lt;strong&gt;posts&lt;/strong&gt; a strike notice you&amp;rsquo;ll &lt;strong&gt;get the info&lt;/strong&gt; in your Home Assistant instance.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://i.imgur.com/JA5IxyC.png" alt="IFTTT on Phone"&gt;
&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;As you can see &lt;strong&gt;IFTTT blends perfectly&lt;/strong&gt; with Home Assistant.&lt;/p&gt;
&lt;p&gt;These two platforms can bring us &lt;strong&gt;endless possibilities.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can &lt;strong&gt;check&lt;/strong&gt; my Home Assistant configuration &lt;strong&gt;files&lt;/strong&gt; &lt;a href="https://github.com/eliseomartelli/HomeAssistant-Config"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Let me know what you think&lt;/em&gt; of this project and this article using the &lt;strong&gt;comments&lt;/strong&gt; below or by hitting me up on &lt;a href="http://twitter.com/eliseomartelli"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;</description><category>IoT</category></item><item><title>My Cheap Smart Home Setup - Wall Mounted Controller</title><link>https://eliseomartelli.it/blog/2017-11-15-wall-mount-controller-homeassistant/</link><pubDate>Wed, 15 Nov 2017 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2017-11-15-wall-mount-controller-homeassistant/</guid><description>&lt;p&gt;&lt;strong&gt;Smart Homes&lt;/strong&gt; (or bedrooms) are a &lt;strong&gt;great conversation starters&lt;/strong&gt;, that&amp;rsquo;s true only if &lt;strong&gt;you&amp;rsquo;re the one that controls them.&lt;/strong&gt;
When your &lt;strong&gt;guests&lt;/strong&gt; aren&amp;rsquo;t able to use your smart home setup &lt;strong&gt;it ain&amp;rsquo;t fun for them.&lt;/strong&gt;
The &lt;strong&gt;solution&lt;/strong&gt;-route I decided to take is the one that includes a &lt;strong&gt;&lt;em&gt;Wall Mounted Panel.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&amp;ldquo;&lt;a href="https://i.imgur.com/Z06ipnW.jpg?1%22"&gt;https://i.imgur.com/Z06ipnW.jpg?1&amp;quot;&lt;/a&gt; style={{ maxWidth: &amp;lsquo;400px&amp;rsquo; }} alt=&amp;ldquo;Wall Mounted Panel&amp;rdquo; /&amp;gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="guide"&gt;Guide&lt;/h3&gt;
&lt;p&gt;To &lt;strong&gt;make&lt;/strong&gt; this project you need:&lt;/p&gt;
&lt;h4 id="hardware"&gt;Hardware&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IKEA PLUTTS&lt;/strong&gt; (0.99$) &lt;a href="http://www.ikea.com/us/en/catalog/products/80347101/"&gt;[LINK]&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;Wall Adapter (Free with the tablet);&lt;/li&gt;
&lt;li&gt;El Cheapo &lt;strong&gt;Android Tablet&lt;/strong&gt; (5$ from &lt;a href="http://subito.it"&gt;Subito.it&lt;/a&gt;, an italian Craiglist-like site).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="software-raspberry-pi-side"&gt;Software (Raspberry Pi Side)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Home Assistant&lt;/strong&gt; (check out the guide &lt;a href="http://www.eliseomartelli.it/iot/automation/2017/11/09/cheap-smart-home-setup_1.html"&gt;here&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="software-android-tablet-side"&gt;Software (Android Tablet Side)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.android.chrome"&gt;Google Chrome;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.dreamori.hidebar"&gt;Hide System Bar.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;P.S.:&lt;/em&gt; You&amp;rsquo;ll need to &lt;strong&gt;root&lt;/strong&gt; your Android Tablet if you want to &lt;strong&gt;hide the system bar.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id="installation-of-appdaemon"&gt;Installation of AppDaemon&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;AppDaemon&lt;/strong&gt; is a sandboxed environment for writing &lt;strong&gt;apps for Home Assistant.&lt;/strong&gt;&lt;br&gt;
You can find the documentation &lt;a href="http://appdaemon.readthedocs.io/en/latest/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;re the steps to install AppDaemon:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We first need to &lt;strong&gt;retrieve&lt;/strong&gt; the &amp;ldquo;appdaemon&amp;rdquo; package. You can use &lt;strong&gt;pip3&lt;/strong&gt; to retrieve and install it.
&lt;pre tabindex="0"&gt;&lt;code&gt;$ sudo pip3 install appdaemon
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Now we need to &lt;strong&gt;create a file&lt;/strong&gt; called &lt;em&gt;appdaemon.yaml&lt;/em&gt; somewhere.&lt;br&gt;
I decided to put it in my Home Assistant Configuration directory.
&lt;pre tabindex="0"&gt;&lt;code&gt;cd /path/to/homeassistant/dir
mkdir appdaemon
cd appdaemon
touch appdaemon.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Then we need to &lt;strong&gt;edit&lt;/strong&gt; the appdaemon &lt;strong&gt;configuration file&lt;/strong&gt;.
&lt;pre tabindex="0"&gt;&lt;code&gt;nano appdaemon.yaml
&lt;/code&gt;&lt;/pre&gt;The &lt;strong&gt;minimal&lt;/strong&gt; configuration is something like that:
&lt;pre tabindex="0"&gt;&lt;code&gt;AppDaemon:
disable_apps: 1
threads: 10
HASS:
ha_key: YOUR_HOMEASSISTANT_PASSWORD
ha_url: YOUR_HOMEASSISTANT_URL
&lt;/code&gt;&lt;/pre&gt;You can copy-paste it and change the values accordingly to your needs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that we have AppDaemon installed we need to &lt;strong&gt;daemonize&lt;/strong&gt; it.&lt;/p&gt;
&lt;p&gt;To daemonize it you need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Create&lt;/strong&gt; a new file inside &lt;em&gt;/etc/systemd/system/&lt;/em&gt;.
&lt;pre tabindex="0"&gt;&lt;code&gt;$ sudo touch /etc/systemd/system/appdaemon.service
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Now we need to &lt;strong&gt;edit&lt;/strong&gt; that file with the editor of our choice and describe our service.
&lt;pre tabindex="0"&gt;&lt;code&gt;[Unit]
Description=AppDaemon service for Home Assistant
[Service]
ExecStart=/usr/local/bin/appdaemon -c /path/to/homeassistant/dir
[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;We&amp;rsquo;re almost done. We need to &lt;strong&gt;activate our service.&lt;/strong&gt; To do so you&amp;rsquo;ve to type:
&lt;pre tabindex="0"&gt;&lt;code&gt;$ sudo systemctl daemon-reload
$ sudo systemctl enable appdaemon.service --now
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;We&amp;rsquo;re able to &lt;strong&gt;start&lt;/strong&gt; the service now.
&lt;pre tabindex="0"&gt;&lt;code&gt;$ sudo service appdaemon start
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="installation-of-hadashboard"&gt;Installation of HADashboard&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;HADashboard&lt;/strong&gt; will be the heart of our wall mounted panel.&lt;/p&gt;
&lt;p&gt;To install it you need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Edit&lt;/strong&gt; the &lt;em&gt;appdaemon.yaml&lt;/em&gt; configuration file.
Open it with the text editor of your choice.&lt;br&gt;
Now we need to &lt;strong&gt;append&lt;/strong&gt; to that file the HADashboard configuration.
&lt;pre tabindex="0"&gt;&lt;code&gt;HADashboard:
dash_url: http://YOUR_HA_IP:5050
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Now you&amp;rsquo;ve to &lt;strong&gt;create some folders&lt;/strong&gt; inside the configuration directory.
&lt;pre tabindex="0"&gt;&lt;code&gt;mkdir compiled
mkdir compiled/css
mkdir compiled/javascript
mkdir compiled/html
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Finally we have to &lt;strong&gt;make another folder&lt;/strong&gt; to store our dashboards.
&lt;pre tabindex="0"&gt;&lt;code&gt;mkdir dashboards
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Now we&amp;rsquo;ve to &lt;strong&gt;relaunch&lt;/strong&gt; the appdaemon service. We can do it by sending this command:
&lt;pre tabindex="0"&gt;&lt;code&gt;$ sudo service appdaemon restart
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Fire up your browser and try to &lt;strong&gt;load the dashboard url&lt;/strong&gt;, you should see a warning page.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You&amp;rsquo;re all set! Now we have to &lt;strong&gt;build our dashboards&lt;/strong&gt;. To learn how, follow this guide &lt;a href="http://appdaemon.readthedocs.io/en/latest/DASHBOARD_CREATION.html"&gt;[LINK]&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="creation-of-the-wall-mount"&gt;Creation of the wall mount&lt;/h4&gt;
&lt;iframe
src='https://www.youtube.com/embed/fgMB4wWaEBU'
style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', border: 0 }}
allowFullScreen
/&gt;
&lt;h4 id="ready-to-rock"&gt;Ready To Rock&lt;/h4&gt;
&lt;p&gt;Here&amp;rsquo;re the &lt;strong&gt;last steps!&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Snap&lt;/strong&gt; your tablet in your tablet mount;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Launch&lt;/strong&gt; Google Chrome and to to your dashboard URL;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open&lt;/strong&gt; the menu and &lt;strong&gt;click&lt;/strong&gt; &amp;ldquo;add to homescreen&amp;rdquo;;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Close&lt;/strong&gt; Chrome and &lt;strong&gt;launch&lt;/strong&gt; Hide System Bar;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Click&lt;/strong&gt; the &amp;ldquo;Hide System Bar&amp;rdquo; menu entry;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Close&lt;/strong&gt; Hide System Bar;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Click&lt;/strong&gt; on the dashboard shortcut.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You&amp;rsquo;re &lt;strong&gt;all set!&lt;/strong&gt;&lt;br&gt;
You&amp;rsquo;ve a &lt;strong&gt;great&lt;/strong&gt; wall mounted house controller and your &lt;strong&gt;friends and family will love it!&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;I&amp;rsquo;ll&lt;/strong&gt; surely &lt;strong&gt;post more&lt;/strong&gt; guides like this one when I&amp;rsquo;ll get my hands on more &lt;strong&gt;awesome devices.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Let me know what you think&lt;/em&gt; of this dashboard and this article using the &lt;strong&gt;comments&lt;/strong&gt; below or by hitting me up on &lt;a href="http://twitter.com/eliseomartelli"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;</description><category>iot</category><category>automation</category><category>diy</category></item><item><title>My Cheap Smart Home Setup - Intro &amp; Installation</title><link>https://eliseomartelli.it/blog/2017-11-09-cheap-smart-home-setup-1/</link><pubDate>Thu, 09 Nov 2017 00:00:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2017-11-09-cheap-smart-home-setup-1/</guid><description>&lt;p&gt;Oh, Hi! Welcome!&lt;br&gt;
This is my first &lt;strong&gt;&lt;em&gt;&amp;ldquo;real&amp;rdquo;&lt;/em&gt;&lt;/strong&gt; article after a long time.&lt;br&gt;
Today I&amp;rsquo;ll talk about a topic I&amp;rsquo;m really enjoying lately.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Smart Home&lt;/strong&gt; &amp;amp; Smart Home devices.&lt;/p&gt;
&lt;p&gt;My journey in smart home technologies started a while ago but since I &lt;em&gt;moved to a new apartment&lt;/em&gt; (&lt;em&gt;#UniversityLife&lt;/em&gt;), I had to &lt;em&gt;re-do&lt;/em&gt; and &lt;em&gt;re-think&lt;/em&gt; a lot of things.&lt;/p&gt;
&lt;p&gt;My new setup should be: &lt;strong&gt;portable&lt;/strong&gt; (so it can move with me), &lt;strong&gt;unintrusive&lt;/strong&gt; (&amp;lsquo;cause it should blend in the environment) and &lt;strong&gt;cheap&lt;/strong&gt; (&amp;lsquo;cause money aren&amp;rsquo;t unlimited).&lt;/p&gt;
&lt;h3 id="hardware-side"&gt;Hardware Side&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve decided to use a &lt;em&gt;hardly-constrained&lt;/em&gt; hardware platform. A &lt;strong&gt;Raspberry Pi Zero W&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Raspberry Pi Zero W isn&amp;rsquo;t&lt;/strong&gt; what I define a &lt;em&gt;&amp;ldquo;power-house&amp;rdquo;&lt;/em&gt;.
It&amp;rsquo;s a &lt;strong&gt;great&lt;/strong&gt; little device and it has great &lt;strong&gt;potentialities for its price&lt;/strong&gt;.&lt;/p&gt;
&lt;img src="https://c2.staticflickr.com/4/3735/33209069075_99e08d84ec_b.jpg" style="max-width: 400px"/&gt;
&lt;p&gt;&lt;strong&gt;If you aren&amp;rsquo;t familiar&lt;/strong&gt; with its &lt;strong&gt;specs&lt;/strong&gt; the list below should help you understand its capabilities.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1GHz, &lt;strong&gt;single-core CPU&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;512MB &lt;strong&gt;RAM&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;Mini HDMI and USB On-The-Go ports;&lt;/li&gt;
&lt;li&gt;Micro USB power;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;40-pin header&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;Composite video and reset headers;&lt;/li&gt;
&lt;li&gt;CSI camera connector.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And for the &lt;strong&gt;wireless&lt;/strong&gt; side:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;802.11 b/g/n&lt;/strong&gt; wireless LAN;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bluetooth&lt;/strong&gt; 4.1;&lt;/li&gt;
&lt;li&gt;Bluetooth Low Energy (BLE).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As you can see the RPi Zero W is a great little device and its ports are a &lt;em&gt;godsend&lt;/em&gt; for my Smart Home needs.&lt;/p&gt;
&lt;h3 id="software-side"&gt;Software Side&lt;/h3&gt;
&lt;p&gt;After years of &lt;a href="https://www.openhab.org"&gt;&lt;strong&gt;OpenHab&lt;/strong&gt;&lt;/a&gt;, I finally &lt;em&gt;decided to switch&lt;/em&gt; platform.&lt;br&gt;
My new platform of choice is &lt;a href="http://home-assistant.io"&gt;&lt;strong&gt;Home Assistant&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="whats-home-assistant"&gt;What&amp;rsquo;s Home Assistant?&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Home Assistant&lt;/strong&gt; is an &lt;em&gt;open-source&lt;/em&gt; smart home platform written in &lt;strong&gt;Python&lt;/strong&gt;.
It&amp;rsquo;s a really &lt;em&gt;cool&lt;/em&gt; piece of software and you can &lt;strong&gt;extend it endlessly&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s &lt;em&gt;Raspberry Pi-Friendly&lt;/em&gt; and it has a great &lt;em&gt;material design inspired&lt;/em&gt; web interface.&lt;/p&gt;
&lt;p&gt;The only &lt;strong&gt;downside&lt;/strong&gt; is that it&amp;rsquo;s &lt;em&gt;configuration-files heavy&lt;/em&gt; and it&amp;rsquo;s not so &lt;em&gt;beginner friendly&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;If you &lt;em&gt;wrap your head&lt;/em&gt; around it you can build &lt;strong&gt;a lot of great things&lt;/strong&gt; with it.&lt;/p&gt;
&lt;h4 id="installation"&gt;Installation&lt;/h4&gt;
&lt;p&gt;After figuring out the software we need, it&amp;rsquo;s time to &lt;strong&gt;install it&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Since &lt;strong&gt;I want to run other things on my Raspberry Pi&lt;/strong&gt;, I decided to take the &lt;a href="https://home-assistant.io/docs/installation/hassbian/"&gt;Hassbian&lt;/a&gt; route.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hassbian&lt;/strong&gt; is a special flavor of Raspbian that&amp;rsquo;s already &lt;em&gt;configured&lt;/em&gt; and &lt;em&gt;optimized&lt;/em&gt; to run Home Assistant.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This guide is valid for November 2017, please reefer to the &lt;a href="https://home-assistant.io/docs/installation/hassbian/installation/"&gt;official guide&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To install &lt;strong&gt;Hassbian&lt;/strong&gt; you need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A Raspberry Pi;&lt;/li&gt;
&lt;li&gt;A Computer (I&amp;rsquo;m using a Linux machine);&lt;/li&gt;
&lt;li&gt;A Micro SD Card (&amp;gt;= 4Gb).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So here are the &lt;strong&gt;steps&lt;/strong&gt; to follow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Download&lt;/strong&gt; the Raspbian image from the Home Assistant site. &lt;a href="https://github.com/home-assistant/pi-gen/releases/latest"&gt;[Direct Link]&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plug&lt;/strong&gt; the Micro SD card to your computer;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write the Image&lt;/strong&gt; to the Micro SD card.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;On a Mac/Linux you can use dd as follows:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;dd if=&amp;lt;raspbian_img_file&amp;gt; of=&amp;lt;your_device&amp;gt; bs=4M
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;On Windows you can use Win32DiskImager or Etcher. Follow the official guide.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="4"&gt;
&lt;li&gt;Now you&amp;rsquo;ve to decide how to &lt;strong&gt;connect&lt;/strong&gt; your RPi to the network.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;If you wanna take the &lt;strong&gt;ethernet&lt;/strong&gt; way just plug your cable.&lt;/li&gt;
&lt;li&gt;If you wanna take the &lt;strong&gt;Wi-Fi&lt;/strong&gt;, follow the guide below.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="5"&gt;
&lt;li&gt;&lt;strong&gt;Boot&lt;/strong&gt; your Raspberry Pi;&lt;/li&gt;
&lt;li&gt;Wait Home Assistant to &lt;strong&gt;install&lt;/strong&gt; itself.&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id="connect-to-a-wi-fi-network"&gt;Connect to a Wi-Fi network&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Mount&lt;/strong&gt; your Micro SD card &amp;ldquo;boot&amp;rdquo; partition on your machine;&lt;/li&gt;
&lt;li&gt;Create a new &lt;em&gt;wpa_supplicant.conf&lt;/em&gt; file and put in it:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; country=&amp;lt;YOUR_COUNTRY_CODE&amp;gt;
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
ssid=&amp;#34;YOUR_SSID&amp;#34;
psk=&amp;#34;YOUR_PASSWORD&amp;#34;
}
&lt;/code&gt;&lt;/pre&gt;&lt;ol start="3"&gt;
&lt;li&gt;&lt;strong&gt;Save&lt;/strong&gt; the file;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Put&lt;/strong&gt; the Micro SD in your PI.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now that you have Home Assistant &lt;strong&gt;installed&lt;/strong&gt; on your Pi you can play with it by reading the &lt;a href="https://home-assistant.io/docs/"&gt;&lt;strong&gt;documentation&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="a-little-intro-to-my-setup"&gt;A little intro to my setup&lt;/h4&gt;
&lt;p&gt;My setup right now is &lt;em&gt;pretty basic&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I have a &lt;em&gt;DHT22&lt;/em&gt; sensor connected to my Raspberry Pi Zero W that&amp;rsquo;s able to monitor &lt;em&gt;temperature&lt;/em&gt; and &lt;em&gt;humidity&lt;/em&gt; of my bedroom.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;outlets&lt;/em&gt; I use to control my appliances are some generic &lt;strong&gt;433Mhz&lt;/strong&gt; ones.&lt;br&gt;
You can buy a 3-pack of them for around 20€.
It&amp;rsquo;s fairly easy to control them using a &lt;strong&gt;433Mhz transmitter&lt;/strong&gt; connected to the PI.&lt;/p&gt;
&lt;p&gt;To control my main bedroom light I use a &lt;strong&gt;433Mhz lamp holder.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;cost&lt;/strong&gt; of this &amp;ldquo;smart-home&amp;rdquo; project floats around 45€ (including the RPi Zero W).&lt;/p&gt;
&lt;p&gt;As you can see it&amp;rsquo;s &lt;strong&gt;simple&lt;/strong&gt; to bring down the &lt;em&gt;entry point&lt;/em&gt; of smart home devices.&lt;/p&gt;
&lt;p&gt;Considering that a &lt;a href="http://amzn.to/2jdcaJa"&gt;&lt;strong&gt;Philips Hue Kit&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;costs&lt;/strong&gt; around &lt;strong&gt;80€&lt;/strong&gt; and after that you&amp;rsquo;re &lt;strong&gt;locked&lt;/strong&gt; down to only a platform, &lt;strong&gt;the benefits&lt;/strong&gt; of running your Home Automation Hub &lt;strong&gt;are pretty clear&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I&amp;rsquo;ll write more&lt;/em&gt; about my home automation project explaining things in depth when &lt;em&gt;I&amp;rsquo;ll reach a &amp;ldquo;ready for production&amp;rdquo; state.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Let me know what you think&lt;/em&gt; of Home Assistant and this article using the &lt;strong&gt;comments&lt;/strong&gt; below or by hitting me up on &lt;a href="http://twitter.com/eliseomartelli"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;</description><category>iot</category><category>automation</category></item><item><title>Linux Day IoT Talk</title><link>https://eliseomartelli.it/blog/2017-10-28-linux-day-iot-talk/</link><pubDate>Wed, 18 Oct 2017 14:37:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2017-10-28-linux-day-iot-talk/</guid><description>&lt;p&gt;This was my first &lt;a href="http://linuxdaytorino.org/2017/"&gt;&lt;strong&gt;Linux Day&lt;/strong&gt;&lt;/a&gt; in Turin, and I was &lt;em&gt;late&lt;/em&gt;, like &lt;em&gt;very late&lt;/em&gt; (as always 😂 ).&lt;/p&gt;
&lt;p&gt;I decided to attend the &lt;strong&gt;IoT&lt;/strong&gt; talk &amp;lsquo;cause I&amp;rsquo;m interested about this topic.&lt;/p&gt;
&lt;p&gt;The speaker, &lt;a href="https://twitter.com/cesco_78"&gt;@cesco_78&lt;/a&gt;, talked about how the tech companies are deploying a lot of devices, rapidly and they haven&amp;rsquo;t so much worries about security and stuffs like that.&lt;/p&gt;
&lt;p&gt;You can find the notes here:&lt;/p&gt;
&lt;h3 id="iot"&gt;IoT&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s a network of talking devices&lt;/li&gt;
&lt;li&gt;IoT devices are:
&lt;ul&gt;
&lt;li&gt;Sensors&lt;/li&gt;
&lt;li&gt;Home Appliances&lt;/li&gt;
&lt;li&gt;Vehicles&lt;/li&gt;
&lt;li&gt;Thermostats&lt;/li&gt;
&lt;li&gt;Complex Systems&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s gaining a big momentum&lt;/li&gt;
&lt;li&gt;They aren&amp;rsquo;t secure&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="therere-a-lot-of-iot-devices"&gt;There&amp;rsquo;re a lot of IoT devices&lt;/h3&gt;
&lt;h4 id="problems"&gt;Problems&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;IPv4 addresses aren&amp;rsquo;t infinite.&lt;/li&gt;
&lt;li&gt;A lot of them are in mobility so 4G cells are full&lt;/li&gt;
&lt;li&gt;The range is a big problem&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="solution"&gt;Solution&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;SigFox&lt;/li&gt;
&lt;li&gt;LoRa&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They&amp;rsquo;re a Low Power Wide Area Network.
They use free frequencies.
The range is great.&lt;/p&gt;
&lt;p&gt;LPWAN aren&amp;rsquo;t Internet backed.&lt;/p&gt;
&lt;p&gt;So a cluster of sensors is connected to a gateway. The gateway is then connected to internet.&lt;/p&gt;
&lt;h4 id="sigfox"&gt;SigFox&lt;/h4&gt;
&lt;p&gt;Less data and slow.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Packets (Upstream) are of 12 bytes.&lt;/li&gt;
&lt;li&gt;Packets (Downstream) are of 8 bytes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;12 bytes/3 seconds&lt;/p&gt;
&lt;p&gt;SigFox uses resellers and it&amp;rsquo;s a commercial network.
The price is big and it&amp;rsquo;s billed &amp;ldquo;per-device&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Western Europe is fully covered.&lt;/p&gt;
&lt;h4 id="the-advantages-are"&gt;The advantages are:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Low energy required;&lt;/li&gt;
&lt;li&gt;Free Roaming;&lt;/li&gt;
&lt;li&gt;Easy to use.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="compatible-devices"&gt;Compatible devices:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;MkrFox1200;&lt;/li&gt;
&lt;li&gt;Nettrotter BIB.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="lora"&gt;LoRa&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s like SigFox for the tech specifications.&lt;/li&gt;
&lt;li&gt;It has unlimited data rates.&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s an open standard.&lt;/li&gt;
&lt;li&gt;There are a lot of providers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Choosing a low power, data service is difficult.
The decision has to be taken &amp;ldquo;per-project&amp;rdquo;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3 id="alternatives"&gt;Alternatives&lt;/h3&gt;
&lt;p&gt;If you have a network (WiFi/3G/4G) and if you want to transfer data without using a lot of bandwidth you can use:&lt;/p&gt;
&lt;h4 id="mqtt"&gt;MQTT&lt;/h4&gt;
&lt;p&gt;Message Queue Telemetry Transport&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It has a little overhead;&lt;/li&gt;
&lt;li&gt;It uses the IP protocol;&lt;/li&gt;
&lt;li&gt;A lot of big players use it (Facebook, WhatsApp, &amp;hellip;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It uses a broker, subscriber and publisher infrastructure.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A broker lists topics.&lt;/li&gt;
&lt;li&gt;A subscriber can &amp;ldquo;subscribe&amp;rdquo; a topic.&lt;/li&gt;
&lt;li&gt;A publisher can &amp;ldquo;publish&amp;rdquo; on a topic.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Configuring a MQTT connection to/from a broker is dead simple.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;As you can see, IoT is a great thing.&lt;br /&gt;
If we&amp;rsquo;re able to avoid big manufactures we can get back our data.&lt;/p&gt;</description><category>Misc</category></item><item><title>A Little Hack For Android Dimensions</title><link>https://eliseomartelli.it/blog/2016-05-26-a-little-hack-for-android-dimensions/</link><pubDate>Thu, 26 May 2016 14:35:00 +0000</pubDate><guid>https://eliseomartelli.it/blog/2016-05-26-a-little-hack-for-android-dimensions/</guid><description>&lt;p&gt;If you write a lot of &lt;strong&gt;UI&lt;/strong&gt; layouts for &lt;strong&gt;Android&lt;/strong&gt; and your goal is making a
&lt;a href="https://medium.com/google-developers/building-a-responsive-ui-in-android-7dc7e4efcbb3"&gt;responsive Android
App&lt;/a&gt;,
you surely faced the need of having something like “match_parent” or
“wrap_content” in your Android Dimension Resources.&lt;/p&gt;
&lt;p&gt;You might think of typing this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://cdn-images-1.medium.com/max/800/1*wD14kgt4YSNntMhEkLBxbg.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;But it doesn’t work like this. And the compiler will let you know that &lt;strong&gt;it
doesn’t like what you’ve typed. 😡&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://cdn-images-1.medium.com/max/800/1*PGdZAhA-41RikTV5wBeZUw.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;We’ve to go a &lt;strong&gt;little bit deeper&lt;/strong&gt; and see &lt;strong&gt;how&lt;/strong&gt; the Android Source Code
&lt;strong&gt;manages LayoutParams.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Looking to the &lt;a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/view/ViewGroup.java"&gt;source
code&lt;/a&gt;
you can see that:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;MATCH_PARENT&lt;/em&gt; and &lt;em&gt;WRAP_CONTENT&lt;/em&gt; are two static final ints and they’re set
respectively to -1 and -2.&lt;/p&gt;
&lt;p&gt;When you set the width or the height to a view, &lt;strong&gt;the value is stored in an
&lt;em&gt;int&lt;/em&gt;&lt;/strong&gt; (using px as a measurement unit).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Px isn’t the only measurement unit.&lt;/strong&gt; Android uses a lot of them (dp, sp, mm)
but most of them depends on other things such as the screen density. &lt;strong&gt;Px is
density independent and this is good for us!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At this point you can &lt;strong&gt;try putting -1px in place of match_parent&lt;/strong&gt; in one of
your layouts and it will work as expected!&lt;/p&gt;
&lt;p&gt;Observing this we can say that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MATCH_PARENT equals to -1px,&lt;/li&gt;
&lt;li&gt;WRAP_CONTENT equals to -2px. Nice, uh?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-you-can-do-using-this-trick"&gt;What you can do using this trick?&lt;/h2&gt;
&lt;p&gt;Using this trick you can build responsive apps exploiting the dimension
resources xml file and resize things to reach your screen boundaries without
recurring to Java but only using xml.&lt;/p&gt;
&lt;h3 id="example"&gt;Example:&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://cdn-images-1.medium.com/max/800/1*788-mHtIgkGtiUYXeM9uLA.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Go and &lt;strong&gt;spread the word&lt;/strong&gt; &amp;lsquo;cause we need to #BuildBetterApps!&lt;/p&gt;</description><category>Misc</category></item></channel></rss>