<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="https://gurdiga.com/atom.xml" rel="self" type="application/atom+xml" /><link href="https://gurdiga.com/" rel="alternate" type="text/html" /><updated>2026-05-09T08:54:01+03:00</updated><id>https://gurdiga.com/atom.xml</id><title type="html">Geeky stories</title><subtitle>My personal blog</subtitle><author><name>Vlad GURDIGA</name></author><entry><title type="html">Make a bot list from access.log and use it</title><link href="https://gurdiga.com/blog/2023/06/09/extracting-a-bot-list-from-access-log-and-using-it/" rel="alternate" type="text/html" title="Make a bot list from access.log and use it" /><published>2023-06-09T10:34:00+03:00</published><updated>2023-06-14T14:46:14+03:00</updated><id>https://gurdiga.com/blog/2023/06/09/extracting-a-bot-list-from-access-log-and-using-it</id><content type="html" xml:base="https://gurdiga.com/blog/2023/06/09/extracting-a-bot-list-from-access-log-and-using-it/"><![CDATA[<p>The other day, after playing a bit with <a href="https://goaccess.io/">GoAccess</a> I found that the numbers didn’t add up with my in-app tracking, and I decided to take a slice of access.log and eyeball it to see what’s going on. What I found was discouraging: lots an lots of bot and crawler requests. As disappointing as it was, it was still good to find this out and adjust my perception to reality.</p>

<p>It was also disappointing that <code class="language-plaintext highlighter-rouge">--ignore-crawlers</code> of GoAccess was still letting through a lot of bot requests, so from the knowledge that I gathered from looking through access.log, I decided I will let it do its thing — the pretty charts and stats, but filter the log records myself.</p>

<p>To do the filtering I needed a list of known bot user-agent names, and when I couldn’t google it, I decided I will extract my own bot list from the some 11 months of logs I had from <a href="https://feedsubscription.com/">FeedSubscription.com</a>. I’m posting the list below, just for reference.</p>

<p>Here is the UNIX pipeline that I used to extract it:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">base_re</span><span class="o">=</span><span class="s1">'\w*(bot|crawler|spider)\w*'</span>

zcat <span class="nt">-f</span> /var/log/access.log<span class="k">*</span> | <span class="c"># look into compressed and uncompressed logs</span>
<span class="nb">grep</span> <span class="nt">-Eoi</span> <span class="s1">'" [0-9]+ [0-9]+ ".*'</span><span class="s2">"</span><span class="nv">$base_re</span><span class="s2">"</span> | <span class="c"># only lines with something-BOT-something in the UA string (or referrer)</span>
<span class="nb">grep</span> <span class="nt">-Eoi</span> <span class="s2">"</span><span class="nv">$base_re</span><span class="s2">"</span> |
</code></pre></div></div>

<p>Essentially, I look for words that contain “bot” or “crawler” in the user-agent string. Besides that, I found some UA strings that didn’t match, but I recognized the names, and added included them in the list:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Chrome-Lighthouse
Google-InspectionTool
HeadlessChrome
scaninfo@paloaltonetworks.com
Feedly
Go-http-client
Nmap Scripting Engine
facebookexternalhit
facebookcatalog
</code></pre></div></div>

<p>Combining these two lists, I get stats that are much closer to what I see in the built-in tracking.</p>

<p>Here is how I use this list to filter out bot requests:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">bot_list_re</span><span class="o">=</span><span class="s2">"(</span><span class="si">$(</span><span class="nb">cat </span>bot-list.txt | <span class="nb">paste</span> <span class="nt">-sd</span> <span class="s1">'|'</span><span class="si">)</span><span class="s2">)"</span>

zcat <span class="nt">-f</span> /var/log/access.log<span class="k">*</span> |
<span class="nb">grep</span> <span class="nt">-vPi</span> <span class="s2">".*</span><span class="nv">$bot_list_re</span><span class="s2">.*"</span>
</code></pre></div></div>

<p>…which is essentially combining them all in a large regex like <code class="language-plaintext highlighter-rouge">(name1|name2|name3|name4)</code>.</p>

<h2 id="bot-listtxt">bot-list.txt</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>AdsBot
AhrefsBot
aiHitBot
Applebot
bingbot
BitSightBot
bot
bots
BSbot
CCBot
Chrome-Lighthouse
crawler
DataForSeoBot
domainsbot
DotBot
Exabot
facebookcatalog
facebookexternalhit
Facebot
Feedly
Go-http-client
Google-InspectionTool
Googlebot
GulperBot
HeadlessChrome
LinkedInBot
MJ12bot
MojeekBot
msnbot
Nicecrawler
Nmap Scripting Engine
org_bot
Pinterestbot
QBOT
redditbot
RepoLookoutBot
robot
robots
RU_Bot
scaninfo@paloaltonetworks.com
SemrushBot
serpstatbot
SeznamBot
Slackbot
SuperBot
t3versionsBot
TelegramBot
top1mbot
Twitterbot
VelenPublicWebCrawler
Vercelbot
webprosbot
WhatStuffWhereBot
YandexBot
YandexRenderResourcesBot
ZaldamoSearchBot
ZoominfoBot
</code></pre></div></div>]]></content><author><name>Vlad GURDIGA</name></author><category term="grep" /><category term="unix" /><category term="scripting" /><summary type="html"><![CDATA[The other day, after playing a bit with GoAccess I found that the numbers didn’t add up with my in-app tracking, and I decided to take a slice of access.log and eyeball it to see what’s going on. What I found was discouraging: lots an lots of bot and crawler requests. As disappointing as it was, it was still good to find this out and adjust my perception to reality.]]></summary></entry><entry><title type="html">My simple meditation routine</title><link href="https://gurdiga.com/blog/2023/01/26/my-simple-meditation-routine/" rel="alternate" type="text/html" title="My simple meditation routine" /><published>2023-01-26T20:16:00+02:00</published><updated>2025-12-29T21:38:41+02:00</updated><id>https://gurdiga.com/blog/2023/01/26/my-simple-meditation-routine</id><content type="html" xml:base="https://gurdiga.com/blog/2023/01/26/my-simple-meditation-routine/"><![CDATA[<p>My current meditation practice is a sort of mindfulness meditation: it’s intended as a way of training attention, focus, and self-awareness.</p>

<p>I have started many times in the last ten years or so, and then it faded away only to start again as heard about its benefits in a podcast, article, or book.</p>

<h2 id="my-reasons">My reasons</h2>

<p>My reasons changed over time, and now I’ve settled on its more practical aspects: to be able to observe the state of affairs in my mind, and intervene before it gets too bad. Another reason, which has some overlap with the first one is to be able to keep my emotional balance in difficult conversations. I think I can check off the first one, but I’ve only got partial success on the second — working on it!</p>

<h2 id="my-how">My how</h2>

<p>Here are some of the more technical aspects of my practice:</p>

<ul>
  <li><strong>Frequency</strong>: I try to do it every day, but I skip a day every now and then, with the most success in the last year or so. I’ve learned to not make an issue about skipping a day or three.</li>
  <li><strong>Time of day</strong>: I try to do it in the morning, before starting to work, but when the morning is busy, I do it in the afternoon.</li>
  <li><strong>Duration</strong>: In the very beginning started real small, with 2–3 minutes, then 5, then 10, then 15, then gradually grew it to 25 in the last 3 weeks.</li>
  <li><strong>How</strong>: I sit comfortably on a chair, spine vertical as if trying to reach the ceiling, breathe in deeply first in the belly and then into the lugs, breathe out in reverse order. Focus on the sensation of breathing.</li>
  <li><strong>Succes criteria</strong>:
    <ul>
      <li>Mind feels refreshed, feet and palms warm up.</li>
      <li>Breathing get automatic and rhythmic after a few minutes.</li>
      <li>Forehead is relaxed.</li>
      <li>Time flies.</li>
    </ul>
  </li>
  <li><strong>Side effects</strong>:
    <ul>
      <li>I can get rid of most headaches.</li>
      <li>They say that breathing into the belly gives a nice massage to the internal organs.</li>
    </ul>
  </li>
  <li><strong>Commitment device</strong>: The Seinfeld Strategy.</li>
</ul>

<h2 id="my-inspiration">My inspiration</h2>

<p>My initial inspiration probably came from the Zen Habits blog, and the most recent is Nick Wignal’s blog.</p>

<p>I will only recommend one article: <a href="https://nickwignall.com/how-to-start-a-mindfulness-practice/">How to Start a Mindfulness Practice</a>.</p>]]></content><author><name>Vlad GURDIGA</name></author><category term="ease-into-it" /><summary type="html"><![CDATA[My current meditation practice is a sort of mindfulness meditation: it’s intended as a way of training attention, focus, and self-awareness.]]></summary></entry><entry><title type="html">make watch-app</title><link href="https://gurdiga.com/blog/2022/11/14/make-watch-app/" rel="alternate" type="text/html" title="make watch-app" /><published>2022-11-14T05:18:00+02:00</published><updated>2025-12-29T21:38:41+02:00</updated><id>https://gurdiga.com/blog/2022/11/14/make-watch-app</id><content type="html" xml:base="https://gurdiga.com/blog/2022/11/14/make-watch-app/"><![CDATA[<p>My name Vlad, and I still like to <code class="language-plaintext highlighter-rouge">make</code> things in 2022, as in <a href="https://www.gnu.org/software/make/">GNU Make</a>. 🙃</p>

<p>On <a href="https://github.com/gurdiga/rss-email-subscription">my side project</a>, I’ve made a monitoring system based largely on <code class="language-plaintext highlighter-rouge">tail</code>, <code class="language-plaintext highlighter-rouge">grep</code>, <code class="language-plaintext highlighter-rouge">jq</code>, and <code class="language-plaintext highlighter-rouge">ssmtp</code>, all weaved together into a <a href="https://en.wikipedia.org/wiki/Pipeline_(Unix)">UNIX pipeline</a> and packaged as a <code class="language-plaintext highlighter-rouge">make</code> task. It’s started from the <a href="https://en.wikipedia.org/wiki/Cron">crontab</a> as system boot, and sends me an email every time a notable event is recorded to the log.</p>

<p>Here is the pipeline:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">tail</span> <span class="nt">-n0</span> <span class="nt">--follow</span><span class="o">=</span>name <span class="nt">--retry</span> logs/feedsubscription/<span class="o">{</span>app,api<span class="o">}</span>.log |
<span class="nb">grep</span> <span class="nt">--line-buffered</span> <span class="nt">-E</span> <span class="se">\</span>
        <span class="nt">-e</span> <span class="s1">'"severity":"(error|warning)"'</span> <span class="se">\</span>
        <span class="nt">-e</span> <span class="s1">'"message":"Sending report"'</span> <span class="se">\</span>
    |
<span class="k">while </span><span class="nb">read</span> <span class="nt">-r</span> _skip_timestamp _skip_namespace _skip_app json<span class="p">;</span> <span class="k">do</span>
    <span class="o">(</span>
        <span class="nb">echo</span> <span class="s2">"Subject: RES App </span><span class="si">$(</span>jq <span class="nt">-r</span> .severity <span class="o">&lt;&lt;&lt;</span><span class="s2">"</span><span class="nv">$json</span><span class="s2">"</span><span class="si">)</span><span class="s2">"</span>
        <span class="nb">echo</span> <span class="s2">"From: watch-app@feedsubscription.com"</span>
        <span class="nb">echo
        </span>jq <span class="nb">.</span> <span class="o">&lt;&lt;&lt;</span><span class="s2">"</span><span class="nv">$json</span><span class="s2">"</span>
    <span class="o">)</span> |
    <span class="k">if</span> <span class="o">[</span> <span class="nt">-t</span> 1 <span class="o">]</span><span class="p">;</span> <span class="k">then </span><span class="nb">cat</span><span class="p">;</span> <span class="k">else </span>ssmtp gurdiga@gmail.com<span class="p">;</span> <span class="k">fi</span><span class="p">;</span>
<span class="k">done</span>
</code></pre></div></div>

<p>Here are the interesting bits:</p>

<ol>
  <li>
    <p>Starts with <code class="language-plaintext highlighter-rouge">tail</code>ing the appropriate logs. I use <code class="language-plaintext highlighter-rouge">-n0</code> to only look at the new lines as they are added, skip the existing ones. The <code class="language-plaintext highlighter-rouge">--follow=name --retry</code> is to allow <code class="language-plaintext highlighter-rouge">tail</code> to work smoothly with log rotation.</p>
  </li>
  <li>
    <p>I filter the lines with <code class="language-plaintext highlighter-rouge">grep</code> to only get the ones that interest me. I use <code class="language-plaintext highlighter-rouge">--line-buffered</code> to prevent block buffering: when <code class="language-plaintext highlighter-rouge">grep</code> delays the output until it collects a bunch of lines. The <code class="language-plaintext highlighter-rouge">-E</code> is to use some basic regexp magic. A few <code class="language-plaintext highlighter-rouge">-e</code> args to define the filter conditions, for example <code class="language-plaintext highlighter-rouge">-e '"severity":"(error|warning)"'</code> catches any error or warning (my logs are in JSON format). I add more as I need them.</p>
  </li>
  <li>
    <p>I collect the logs from all of the system’s containers to a syslog <code class="language-plaintext highlighter-rouge">syslog-ng</code> container, and so I end up with some additional meta-data fields for every line. This is why I use <code class="language-plaintext highlighter-rouge">read</code> to split the line into fields, and only look at the actual JSON log message. The <code class="language-plaintext highlighter-rouge">-r</code> flag is to prevent <code class="language-plaintext highlighter-rouge">read</code> from interpreting backslashes in the JSON string as shell escapes. I throw away the meta-data fields because I don’t need them here: <code class="language-plaintext highlighter-rouge">_skip_timestamp _skip_namespace _skip_app</code>.</p>
  </li>
  <li>
    <p>I then use <code class="language-plaintext highlighter-rouge">jq</code> to extract some bits from the JSON log line, and build an email. For example, I extract the <code class="language-plaintext highlighter-rouge">severity</code> field and put it in the email subject, and jus dump the entire JSON line, prettified with <code class="language-plaintext highlighter-rouge">jq</code>, into the email body. Here is an example:</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="p">{</span><span class="w">
   </span><span class="nl">"severity"</span><span class="p">:</span><span class="w"> </span><span class="s2">"info"</span><span class="p">,</span><span class="w">
   </span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sending report"</span><span class="p">,</span><span class="w">
   </span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
     </span><span class="nl">"module"</span><span class="p">:</span><span class="w"> </span><span class="s2">"email-sending"</span><span class="p">,</span><span class="w">
     </span><span class="nl">"feedId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dexonline"</span><span class="p">,</span><span class="w">
     </span><span class="nl">"report"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
       </span><span class="nl">"sentExpected"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
       </span><span class="nl">"sent"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
       </span><span class="nl">"failed"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
     </span><span class="p">}</span><span class="w">
   </span><span class="p">}</span><span class="w">
 </span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>The lil <code class="language-plaintext highlighter-rouge">if</code> at the end is to help me with debugging: when I run the pipeline manually from the command line, just print the email to console with <code class="language-plaintext highlighter-rouge">cat</code> instead of sending it out with <code class="language-plaintext highlighter-rouge">ssmtp</code>.</p>
  </li>
</ol>

<p>Happy making and shell pipelining! 🙂</p>]]></content><author><name>Vlad GURDIGA</name></author><category term="make" /><category term="unix" /><category term="shell-scripting" /><summary type="html"><![CDATA[My name Vlad, and I still like to make things in 2022, as in GNU Make. 🙃]]></summary></entry><entry><title type="html">Project-based learning</title><link href="https://gurdiga.com/blog/2022/03/27/project-based-learning/" rel="alternate" type="text/html" title="Project-based learning" /><published>2022-03-27T19:46:00+03:00</published><updated>2025-12-29T21:38:41+02:00</updated><id>https://gurdiga.com/blog/2022/03/27/project-based-learning</id><content type="html" xml:base="https://gurdiga.com/blog/2022/03/27/project-based-learning/"><![CDATA[<p>Recently I was discussing with a friend that was thinking about developing some programming skills.</p>

<p>My go-to advice in these situations is to start building a copy of something that is conceivably useful. This is how I started and grew as a developer, and it seems to have worked quite well.</p>

<p>I like this approach because it keeps me focused on a finite user value. I came to appreciate specifically the “finite” aspect of it because it keeps the work contained and prevents scope creep.</p>

<p>One other reason why I find this approach practical is that it allowed me to touch multiple layers of the software stack: in my case it was an e-commerce website, so I got to dip my toes in:</p>

<ul>
  <li>web frontend: HTML, CSS, and JS, HTTP parts relevant to the frontend like caching and asset optimisation, performance on the frontend;</li>
  <li>backend: Apache, PHP, MySQL, email, image processing, performance on the backend.</li>
  <li>devops: although at that time it meant SSH, Makefiles, rsync and coreutils, it still exposed enough of the deployment activities to get some understanding of what’s involved there.</li>
</ul>

<p>Besides giving a wide perspective of what an app is made of, this is also an opportunity to pick one layer to specialise in, both of which I think are important for a beginner which tries to find their way in software development.</p>]]></content><author><name>Vlad GURDIGA</name></author><category term="learning" /><summary type="html"><![CDATA[Recently I was discussing with a friend that was thinking about developing some programming skills.]]></summary></entry><entry><title type="html">Google Apps Script</title><link href="https://gurdiga.com/blog/2021/12/19/google-apps-script/" rel="alternate" type="text/html" title="Google Apps Script" /><published>2021-12-19T21:02:00+02:00</published><updated>2025-12-29T21:38:41+02:00</updated><id>https://gurdiga.com/blog/2021/12/19/google-apps-script</id><content type="html" xml:base="https://gurdiga.com/blog/2021/12/19/google-apps-script/"><![CDATA[<p>For a while, I was collecting the DMARC reports, just in case I can find something useful in them. I didn’t, but I liked the lil script that I wrote in this Apps Script from Google. It’s like VBA for Microsoft Office — if you ever heard of it. If not, it’s automation for Google apps and services.</p>

<p>TLDR: It was checking if there are any new messages with “DMARC Reports” label in my GMail, and if yes, then would get the attached file and put it in the “DMARC Reports” folder in my GDrive. Then send me an email with a textual report about what happened. Pretty neat! 😎</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// GmailApp: https://developers.google.com/apps-script/reference/gmail/gmail-app</span>
<span class="c1">// DriveApp: https://developers.google.com/apps-script/reference/drive/drive-app</span>

<span class="kd">const</span> <span class="nx">labelName</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">DMARC reports</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">folderName</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">DMARC reports</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">function</span> <span class="nx">importReports</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">label</span> <span class="o">=</span> <span class="nx">GmailApp</span><span class="p">.</span><span class="nx">getUserLabelByName</span><span class="p">(</span><span class="nx">labelName</span><span class="p">);</span>

  <span class="k">if</span> <span class="p">(</span><span class="nx">label</span><span class="p">.</span><span class="nx">getUnreadCount</span><span class="p">()</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="kd">let</span> <span class="nx">report</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>

  <span class="kd">const</span> <span class="nx">say</span> <span class="o">=</span> <span class="p">(</span><span class="nx">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">report</span> <span class="o">+=</span> <span class="s2">`\n</span><span class="p">${</span><span class="nx">string</span><span class="p">}</span><span class="s2">`</span><span class="p">;</span>
    <span class="nx">Logger</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">string</span><span class="p">);</span>
  <span class="p">};</span>

  <span class="nx">say</span><span class="p">(</span><span class="s2">`Found </span><span class="p">${</span><span class="nx">label</span><span class="p">.</span><span class="nx">getUnreadCount</span><span class="p">()}</span><span class="s2"> new messages`</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">unreadThreads</span> <span class="o">=</span> <span class="nx">label</span><span class="p">.</span><span class="nx">getThreads</span><span class="p">().</span><span class="nx">filter</span><span class="p">((</span><span class="nx">t</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">t</span><span class="p">.</span><span class="nx">isUnread</span><span class="p">());</span>
  <span class="kd">const</span> <span class="nx">folder</span> <span class="o">=</span> <span class="nx">DriveApp</span><span class="p">.</span><span class="nx">getFoldersByName</span><span class="p">(</span><span class="nx">folderName</span><span class="p">).</span><span class="nx">next</span><span class="p">();</span>

  <span class="nx">unreadThreads</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">thread</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">attachment</span> <span class="o">=</span> <span class="nx">thread</span><span class="p">.</span><span class="nx">getMessages</span><span class="p">()[</span><span class="mi">0</span><span class="p">].</span><span class="nx">getAttachments</span><span class="p">()[</span><span class="mi">0</span><span class="p">];</span>
    <span class="kd">const</span> <span class="nx">file</span> <span class="o">=</span> <span class="nx">DriveApp</span><span class="p">.</span><span class="nx">createFile</span><span class="p">(</span><span class="nx">attachment</span><span class="p">.</span><span class="nx">getAllBlobs</span><span class="p">()[</span><span class="mi">0</span><span class="p">]);</span>

    <span class="nx">folder</span><span class="p">.</span><span class="nx">addFile</span><span class="p">(</span><span class="nx">file</span><span class="p">);</span>
    <span class="nx">thread</span><span class="p">.</span><span class="nx">markRead</span><span class="p">();</span>

    <span class="nx">say</span><span class="p">(</span><span class="nx">attachment</span><span class="p">.</span><span class="nx">getName</span><span class="p">());</span>
  <span class="p">});</span>

  <span class="nx">GmailApp</span><span class="p">.</span><span class="nx">sendEmail</span><span class="p">(</span><span class="dl">"</span><span class="s2">gurdiga@gmail.com</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">New DMARC reports</span><span class="dl">"</span><span class="p">,</span> <span class="nx">report</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I guess one could do pretty advanced stuff with this if they use multiple Google services and need to juggle things between them. At the very least it can be another useful tool.</p>]]></content><author><name>Vlad GURDIGA</name></author><category term="google apps script" /><summary type="html"><![CDATA[For a while, I was collecting the DMARC reports, just in case I can find something useful in them. I didn’t, but I liked the lil script that I wrote in this Apps Script from Google. It’s like VBA for Microsoft Office — if you ever heard of it. If not, it’s automation for Google apps and services.]]></summary></entry><entry><title type="html">moreutils ifne run function</title><link href="https://gurdiga.com/blog/2021/11/28/moreutils-ifne-run-function/" rel="alternate" type="text/html" title="moreutils ifne run function" /><published>2021-11-28T13:00:00+02:00</published><updated>2025-12-29T21:38:41+02:00</updated><id>https://gurdiga.com/blog/2021/11/28/moreutils-ifne-run-function</id><content type="html" xml:base="https://gurdiga.com/blog/2021/11/28/moreutils-ifne-run-function/"><![CDATA[<p>There is this lil program in moreutils called <a href="https://linux.die.net/man/1/ifne"><code class="language-plaintext highlighter-rouge">ifne</code></a>. I use it in shell pipelines, to execute a given command when there is some output. A quick example:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="s2">"^</span><span class="sb">`</span><span class="nb">date</span> +%F<span class="sb">`</span><span class="s2">"</span> logs/feedsubscription/api.log <span class="se">\</span>
| <span class="nb">grep</span> <span class="s1">'"message":"subscribe"'</span> <span class="se">\</span>
| ifne mail <span class="nt">-s</span> <span class="s2">"Subscription requests"</span> root
</code></pre></div></div>

<p>This will grep the API log, and email me today’s subscription requests, but only if there are any. It won’t do anything if there are no records found — and that’s exactly its whole purpose: only run the command if anything comes into STDIN.</p>

<p>Its limitation is that I can only run <em>one command</em> on that STDIN, and I wanted to do some more processing on it before passing it to the next command in the pipeline. Specifically this: grep some log file and email the matching lines, but first prepend some headers, which would look like this:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="s1">'something'</span> file <span class="se">\</span>
| <span class="o">(</span>
    <span class="nb">echo</span> <span class="s2">"Subject: Subscribe report"</span>
    <span class="nb">echo</span> <span class="s2">"From: subscribe-report@feedsubscription.com"</span>
    <span class="nb">echo</span> <span class="s2">""</span>
    <span class="nb">cat</span>
<span class="o">)</span> <span class="se">\</span>
| ssmtp gurdiga@gmail.com
</code></pre></div></div>

<p>This doesn’t work as I wanted because it would send the email even if no lines matched. So I thought: if I pack the pre-processing and the sending in a <code class="language-plaintext highlighter-rouge">send_report</code> function, I’d be able to call that function with <code class="language-plaintext highlighter-rouge">ifne</code> only when there are matching lines, something like this:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="s1">'something'</span> file | ifne send_report
send_report: No such file or directory
</code></pre></div></div>

<p>…it seems like it doesn’t work with <em>functions</em>. After some googling, I found this approach:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export</span> <span class="nt">-f</span> send_report
<span class="nb">grep</span> <span class="s1">'something'</span> file | ifne bash <span class="nt">-c</span> send_report
</code></pre></div></div>

<p>It’s a bit awkward, but still readable and does what it needs to do.</p>]]></content><author><name>Vlad GURDIGA</name></author><category term="unix" /><category term="scripting" /><category term="grep" /><summary type="html"><![CDATA[There is this lil program in moreutils called ifne. I use it in shell pipelines, to execute a given command when there is some output. A quick example:]]></summary></entry><entry><title type="html">Simple log aggregation for Docker containers</title><link href="https://gurdiga.com/blog/2021/09/19/simple-log-aggregation-for-docker-containers/" rel="alternate" type="text/html" title="Simple log aggregation for Docker containers" /><published>2021-09-19T11:16:00+03:00</published><updated>2025-12-29T21:38:41+02:00</updated><id>https://gurdiga.com/blog/2021/09/19/simple-log-aggregation-for-docker-containers</id><content type="html" xml:base="https://gurdiga.com/blog/2021/09/19/simple-log-aggregation-for-docker-containers/"><![CDATA[<h2 id="the-why">The Why</h2>

<p>The Docker composition on one of my side projects has recently got to 6 containers: SMTP-in, SMTP-out, app, subscription, website, and certbot. Since I have deployed a <code class="language-plaintext highlighter-rouge">0.1.0</code> version to DigitalOcean a couple of weeks ago, I caught myself feeling increasingly anxious about losing logs every time I deployed a new version of the container.</p>

<!-- excerpt -->

<p>Every container is streaming its logs to STDOUT. I wanted to have a persistent log file for each container, which <a href="https://docs.docker.com/config/containers/logging/local/">Docker almost does</a> out-of-the-box, with the “little” caveat that those files are removed every time the container is rebuilt. 🙃</p>

<p>One of the things that I’ve challenged myself to do on this side project is to have it as self-contained as possible. — This is why I’ve set up my own SMTP server instead of buying a transactional email subscription. 😎</p>

<p>This is the kind of simplicity I was referring to when I said “simple log aggregation” in the title: no third-party services. I wanted it to be a Docker composition that I can start on my laptop as easily and as cleanly as on a VM in some cloud.</p>

<p>At the end of the day, all I needed is to have those logs — plain text or JSON — stored somewhere so that I can <code class="language-plaintext highlighter-rouge">grep</code> or <a href="https://stedolan.github.io/jq/"><code class="language-plaintext highlighter-rouge">jq</code></a> them whenever I want to do see what happened when.</p>

<p>After googling around for a few mornings, I ended up deploying an additional logger container that runs Syslog and then instructed all the other containers to stream their logs into it. I’ve seen this called “the sidecar pattern” in other places. The logger container’s files live in a mounded directory, so they can have a life outside Docker, and across the container life cycle.</p>

<h2 id="some-dirty-details">Some dirty details</h2>

<h3 id="the-logger">The logger</h3>

<p>I’ve started with the <a href="https://hub.docker.com/r/mumblepins/syslog-ng-alpine/"><code class="language-plaintext highlighter-rouge">mumblepins/syslog-ng-alpine</code></a> for the proof of concept, but then I saw a warning about old log format, or something. On a closer inspection, I realized that it hasn’t been updated in 4 years. Hm… Looking at <a href="https://github.com/mumblepins-docker/syslog-ng-alpine/blob/df1e224/Dockerfile">its Dockerfile on GitHub</a> I found that it was manually compiling Syslog from the source. Umm… OK. On the next day, inspired by that I’ve created <a href="https://github.com/gurdiga/rss-email-subscription/blob/70fa0e4/docker-services/logger/Dockerfile">my own Dockerfile</a>, which used Alpine’s built-in package of Syslog.</p>

<p>Extracted a copy of its config file from the running container, added <a href="https://github.com/mumblepins-docker/syslog-ng-alpine/blob/58eac18/syslog-ng.conf">the bits</a> from <code class="language-plaintext highlighter-rouge">mumblepins</code> that made it listen on the network, and with that I had it both up to date and working. Open Source is awesome. 😎</p>

<h3 id="the-docker-compose-config">The docker-compose config</h3>

<p>To tell a container to stream its logs to a Syslog is <a href="https://docs.docker.com/config/containers/logging/syslog/">well documented</a>, and quite straightforward:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">app</span><span class="pi">:</span>
  <span class="c1"># ...more settings...</span>
  <span class="na">depends_on</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">logger</span><span class="pi">]</span>
  <span class="na">logging</span><span class="pi">:</span>
    <span class="na">driver</span><span class="pi">:</span> <span class="s">syslog</span>
    <span class="na">options</span><span class="pi">:</span>
      <span class="na">syslog-address</span><span class="pi">:</span> <span class="s">tcp://127.0.0.1:514</span>
      <span class="na">syslog-format</span><span class="pi">:</span> <span class="s">rfc3164</span>
      <span class="na">tag</span><span class="pi">:</span> <span class="s">subscription</span>
</code></pre></div></div>

<p>This worked, but having this for 6 services looked too non-<a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a> for my taste, so I googled around for a morning or two and found about <a href="https://yaml.org/type/merge.html">YAML merge type</a> which is described under the <a href="https://docs.docker.com/compose/compose-file/compose-file-v3/#extension-fields"><strong>Extension fields</strong></a> section of Docker Compose references, and which gave me this:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">x-logging</span><span class="pi">:</span> <span class="nl">&amp;logging</span>
  <span class="na">depends_on</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">logger</span><span class="pi">]</span>
  <span class="na">logging</span><span class="pi">:</span>
    <span class="na">driver</span><span class="pi">:</span> <span class="s">syslog</span>
    <span class="na">options</span><span class="pi">:</span>
      <span class="na">syslog-address</span><span class="pi">:</span> <span class="s">tcp://127.0.0.1:514</span>
      <span class="na">syslog-format</span><span class="pi">:</span> <span class="s">rfc3164</span>
      <span class="na">tag</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{​{.Name}}"</span>
</code></pre></div></div>

<p>I added the <code class="language-plaintext highlighter-rouge">tag:</code> option to have the symbolic container name in logs instead of its hex ID. And so this snippet now allows me to include it in every service’s config like this:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">smtp-in</span><span class="pi">:</span>
  <span class="na">container_name</span><span class="pi">:</span> <span class="s">smtp-in</span>
  <span class="c1"># ...</span>
  <span class="na">&lt;&lt;</span><span class="pi">:</span> <span class="nv">*logging</span>
</code></pre></div></div>

<p>This is neat. 🤓</p>]]></content><author><name>Vlad GURDIGA</name></author><category term="docker" /><summary type="html"><![CDATA[The Why The Docker composition on one of my side projects has recently got to 6 containers: SMTP-in, SMTP-out, app, subscription, website, and certbot. Since I have deployed a 0.1.0 version to DigitalOcean a couple of weeks ago, I caught myself feeling increasingly anxious about losing logs every time I deployed a new version of the container.]]></summary></entry><entry><title type="html">An easy TDD exercise and some more</title><link href="https://gurdiga.com/blog/2021/08/22/an-easy-tdd-exercise-and-some-more/" rel="alternate" type="text/html" title="An easy TDD exercise and some more" /><published>2021-08-22T12:57:00+03:00</published><updated>2025-12-29T21:49:49+02:00</updated><id>https://gurdiga.com/blog/2021/08/22/an-easy-tdd-exercise-and-some-more</id><content type="html" xml:base="https://gurdiga.com/blog/2021/08/22/an-easy-tdd-exercise-and-some-more/"><![CDATA[<p>A couple of months ago I’ve set out to write a lil <a href="https://github.com/gurdiga/rss-email-subscription">RSS-checking program</a> that I thought would be useful for a friend. As a small green-field project, I thought it’s going to be perfect to — once again — try that hippy TDD method that everyone was talking about 10 years ago. In retrospect, a few things came in really handy while writing this program, and wanted to share them.</p>

<p>So, in no particular order, here they come:</p>

<h2 id="programming-by-intention">Programming by intention</h2>

<p>It’s a concept I’ve seen recently <a href="https://www.youtube.com/playlist?list=PLD-LK0HSm0Hpp-OspFpZ32uY766YntGVQ">applied by James Shore</a> and the TLDR version of it is this: I write some pseudo-code that reflects how would I like my code to look like given my current understanding. The good thing about it is that it guides me towards putting together the API of the code, and get some understanding of the pieces involved, with the smallest effort investment possible — it’s just a comment.</p>

<p>Here is a quick pseudo-example:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="cm">/*
  const dataDir = getDataDirFromFirstCliArg();
  const feedUrl = getFeedUrl(dataDir);
  const lastItemTimestamp = getLastItemTimestamp(dataDir);
  const rssItems = getRssItems(feedUrl);
  const newItems = getNewItems(rssItems, lastItemTimestamp);
  storeNewItems(newItems);
  */</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I can mess with it until I like how it reads, and then, when I have some clarity about what’s supposed to do, I can take each of those pieces and TDD it.</p>

<p>I think this approach has something in common with <a href="https://tom.preston-werner.com/2010/08/23/readme-driven-development.html">Readme Driven Development</a>.</p>

<h2 id="logic-sandwich">“Logic sandwich”</h2>

<p>This is a concept I’ve learned — again — from <a href="https://www.jamesshore.com/v2/blog/2018/testing-without-mocks#logic-sandwich">James Shore</a>. Since this RSS-checking program would involve a significant portion of IO, I found that using the “logic sandwich” approach helped me keep the IO and business logic unentangled, which in turn, made more of the code TDD-able.</p>

<p>Conceptually, it means that the code is structured in code chunks that do either IO or business logic, but not both. Something like this:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">input</span> <span class="o">=</span> <span class="nx">infrastructure</span><span class="p">.</span><span class="nx">readData</span><span class="p">();</span>
  <span class="kd">const</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">logic</span><span class="p">.</span><span class="nx">processInput</span><span class="p">(</span><span class="nx">input</span><span class="p">);</span>

  <span class="nx">infrastructure</span><span class="p">.</span><span class="nx">writeData</span><span class="p">(</span><span class="nx">output</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="microtest-tdd">Microtest TDD</h2>

<p>It’s a concept I’ve heard a few months ago <a href="https://www.geepawhill.org/2020/06/12/microtest-tdd-more-definition/">from GeePaw Hill</a>, and it’s a particular style of TDD where the emphasis is on a couple of things:</p>

<ul>
  <li>unit test size leans on the smaller side, the smaller the better: as in “micro.” Yeah, I know: that’s just Unit Testing 101, right? 😉</li>
  <li>unit tests are “gray-box” tests (as opposed to “black-box” tests) — they know some of the things that happen in the code under test, which helps in keeping them more practical.</li>
</ul>

<h2 id="functional-core-imperative-shell">Functional Core, Imperative Shell</h2>

<p>It’s a concept I’ve heard years ago <a href="https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell">from Gary Bernhardt</a> and means that the program “tree” is structured in a way that the “leaf” parts are written in a functional style, and the parts closer to the “trunk” are written in an imperative style.</p>

<p>In this particular program, the imperative part is the <code class="language-plaintext highlighter-rouge">main</code> function that acts like a controller that wires all the more functional pieces together. I found that the code written in a more functional style is easier to TDD.</p>

<h2 id="result-type">Result type</h2>

<p>This is a concept I saw used in typed functional languages like Haskell, Elm, and F#. FWIW, it even has <a href="https://en.wikipedia.org/wiki/Result_type">its own Wikipedia page</a>. 🙂</p>

<p>I find it brings clarity to the program flow by clearly delineating the failure paths, and, in a well-typed language like TypeScript, this makes for pretty readable and resilient code.</p>

<h2 id="try-easy">Try easy!</h2>

<p>Last but not least, it’s a meta-idea that I got from the <a href="https://www.goodreads.com/book/show/8360431-accidental-genius">“Accidental Genius” book</a>: it’s called “Try easy!”. Its essence — seemingly counterintuitive at first — is that you can actually achieve more when you’re <em>not</em> trying to give 105% of yourself to an activity, but on the contrary, to relax a bit, maybe to 90%.</p>

<p>That way you’re more flexible and keep the ability to work around the obstacles that inevitable come up with every project. Yes, this is all metaphorical and meta, but I still found it valuable. On this project specifically, I actually started twice: once with a straight face, saying to myself “I’m going to TDD this to death!” and then gave up on this idea after a week of struggling because this attitude of perfectionism was making me feel crippled.</p>

<p>Then, after few days of sadness and disappointment, I’ve changed my posture to “try easy” and said something to the effect of “Let’s see if I can do just this one little thingy over here.” Then, another “little thingy,” and then another.</p>

<p>Happy coding!</p>]]></content><author><name>Vlad GURDIGA</name></author><category term="tdd" /><category term="testing" /><category term="programming" /><category term="typescript" /><category term="fp" /><summary type="html"><![CDATA[A couple of months ago I’ve set out to write a lil RSS-checking program that I thought would be useful for a friend. As a small green-field project, I thought it’s going to be perfect to — once again — try that hippy TDD method that everyone was talking about 10 years ago. In retrospect, a few things came in really handy while writing this program, and wanted to share them.]]></summary></entry><entry><title type="html">A couple of tips to ease into meditation breathing</title><link href="https://gurdiga.com/blog/2021/08/22/a-couple-tips-to-ease-into-meditation-breathing/" rel="alternate" type="text/html" title="A couple of tips to ease into meditation breathing" /><published>2021-08-22T11:14:00+03:00</published><updated>2025-12-29T21:49:49+02:00</updated><id>https://gurdiga.com/blog/2021/08/22/a-couple-tips-to-ease-into-meditation-breathing</id><content type="html" xml:base="https://gurdiga.com/blog/2021/08/22/a-couple-tips-to-ease-into-meditation-breathing/"><![CDATA[<p>My meditation breathing exercises have been on and off in the last few years, and I have lately stumbled upon a couple of things that made it easier for me.</p>

<h2 id="1-use-some-background-sound">1. Use some background sound</h2>

<p>I found it’s easier to stay focused when I put on some background sound. 2 that worked well for me lately were RainyMood and Abraham Hicks’ guided meditation music, with the words stripped out, but any soothing sounds should work. I find it’s especially useful when it guides towards a breathing rhythm of 5–6 breaths per minute.</p>

<h2 id="2-push-on-the-exhale">2. Push on the exhale</h2>

<p>Usually, the inhale is the intentional part when breathing, and the exhale is somewhat automatic, and, for that reason, somewhat shallow. Going through a couple of books on breathing (<a href="https://www.goodreads.com/book/show/48890486-breath">1</a>, <a href="https://www.goodreads.com/book/show/28220630-breathe">2</a>) lately, one recurring theme I found was the focus on the exhale part of the breathing. For me, because it’s somewhat an unusual way to breathe, it helps me keep my attention focused.</p>

<h2 id="3-breath-horizontally">3. Breath “horizontally”</h2>

<p>“Horizontal” breathing is a term I found in the second book, and it means to breathe in and out of your belly while keeping your shoulders still. Besides some health benefits for the internal organs, it helps me focus.</p>

<h2 id="the-measure-of-success">The measure of success</h2>

<p>I’m guessing everyone has their own measure of success when it comes to breathing and meditation. To me, it’s when I can feel the forehead part of my scalp relaxing. I found that during focused work I have it tensed for the most part, and so releasing that tension is what I’m looking for.</p>

<p>Enjoy!</p>]]></content><author><name>Vlad GURDIGA</name></author><category term="mindfulness" /><category term="health" /><category term="life" /><summary type="html"><![CDATA[My meditation breathing exercises have been on and off in the last few years, and I have lately stumbled upon a couple of things that made it easier for me.]]></summary></entry><entry><title type="html">Email server setup with SPF, DKIM, and DMARC</title><link href="https://gurdiga.com/blog/2021/08/19/email-server-setup-with-spf-dkim-and-dmarc/" rel="alternate" type="text/html" title="Email server setup with SPF, DKIM, and DMARC" /><published>2021-08-19T20:24:00+03:00</published><updated>2025-12-29T21:38:41+02:00</updated><id>https://gurdiga.com/blog/2021/08/19/email-server-setup-with-spf-dkim-and-dmarc</id><content type="html" xml:base="https://gurdiga.com/blog/2021/08/19/email-server-setup-with-spf-dkim-and-dmarc/"><![CDATA[<p>I have recently played with the idea of setting up an SMTP server for a friend’s custom domain, and wanted to share the working setup.</p>

<p>The <a href="https://hub.docker.com/r/boky/postfix/">boky/postfix</a> Docker image turned out to be quite useful, especially because it’s set up with a mechanism for generating the DKIM keys.</p>

<p>So here are the two steps to sending emails from a custom domain:</p>

<h2 id="1-postfix-setup">1. Postfix setup</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>docker run <span class="nt">--rm</span> <span class="nt">--name</span> postfix <span class="se">\</span>
    <span class="nt">-v</span> <span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span>/opendkim-keys:/etc/opendkim/keys <span class="se">\</span>
    <span class="nt">-e</span> <span class="s2">"ALLOWED_SENDER_DOMAINS=feedsubscription.com"</span> <span class="se">\</span>
    <span class="nt">-e</span> <span class="s2">"DKIM_AUTOGENERATE=yes"</span> <span class="se">\</span>
    <span class="nt">-e</span> <span class="s2">"INBOUND_DEBUGGING=yes"</span> <span class="se">\</span>
    <span class="nt">--no-healthcheck</span> <span class="se">\</span>
    <span class="nt">-p</span> 1587:587 <span class="se">\</span>
    boky/postfix
</code></pre></div></div>

<p>One more thing about the Postfix container: if you want to persist the queue between container restarts/rebuilds, pass in a volume for <code class="language-plaintext highlighter-rouge">/var/spool/postfix</code>, for example:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-v `pwd`/postfix:/var/spool/postfix
</code></pre></div></div>

<h2 id="2-dns-setup-spf-dkim-and-dmarc">2. DNS setup: SPF, DKIM, and DMARC</h2>

<p>We need to add 3 TXT records in the feedsubscription.com domain:</p>

<div style="overflow: auto">
<table>
<tr><th>Purpose</th><th>Hostname</th><th>Value</th><th>Note</th></tr>
<tr><td>SPF</td><td>@</td><td>v=spf1 mx -all</td><td>See below¹</td></tr>
<tr><td>DKIM</td><td>mail._domainkey</td><td>v=DKIM1; h=sha256; k=rsa; s=email; p=XXXX</td><td>See below²</td></tr>
<tr><td>DMARC</td><td>_dmarc</td><td>v=DMARC1; p=reject; rua=mailto:gurdiga@gmail.com; adkim=s; aspf=s</td><td></td></tr>
</table>
</div>

<p>¹ This particular SPF value assumes we already have a proper MX record.</p>

<p>² The complete definition for the DKIM record is generated by the container because of <code class="language-plaintext highlighter-rouge">DKIM_AUTOGENERATE=yes</code>, and in this particular setup can be found in <code class="language-plaintext highlighter-rouge">./opendkim-keys/feedsubscription.com.txt</code>.</p>

<h2 id="3-testing">3. Testing</h2>

<p>I used <code class="language-plaintext highlighter-rouge">ssmtp</code> to test the setup. I needed to have this in <code class="language-plaintext highlighter-rouge">/etc/ssmtp/ssmtp.conf</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mailhub=localhost:1587
</code></pre></div></div>

<p>…then I can send an email:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>ssmtp <span class="nt">-vvv</span> gurdiga@gmail.com &lt; email.txt
</code></pre></div></div>

<p>Check logs:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>docker logs <span class="nt">-f</span> &lt;container_name&gt;
</code></pre></div></div>

<p>I specifically enabled logging in above Postfix setup with <code class="language-plaintext highlighter-rouge">INBOUND_DEBUGGING=yes</code> so that I can see what’s happening.</p>

<h2 id="a-lil-closing-note">A lil closing note</h2>

<p>Although I am a little confused about the value of the DMARC record, without it the emails land in the spam folder.</p>

<p>Happy email sending!</p>

<h2 id="update-aug-29">Update Aug 29</h2>

<p>Because last week we had a phishing incident at work, I googled some more about the mechanics of SPF, DKIM, and DMARC.</p>

<p>When an SMTP server receives an email from a domain, it can use SFP to verify that the sending server is authorized to send emails for that domain. For example, if I bring up a Postfix container on my laptop, and send an email from info@feedsubscription.com, the receiving SMTP server can check with the DNS for feedsubscription.com, get the SPF record for and verify that my laptop is in the list. If it’s not in the list, it can mark the message as spam or reject it altogether.</p>

<p>DKIM is just an additional level of authentication based on <a href="https://en.wikipedia.org/wiki/Public-key_cryptography">public-key cryptography</a>. It adds a header to the email message — <code class="language-plaintext highlighter-rouge">DKIM-Signature</code> — which can be verified by the receiving SMTP server by checking the corresponding DKIM record in the feedsubscription.com DNS.</p>

<p>DMARC builds on both SPF and DKIM, and allows for more sophisticated policies. The receiving SMTP server can look at the DMARC record and decide what to do with every message. And this is why having DMARC — and also SFP and DKIM — gives the sender a higher level of confidence a message will reach its recipient …because the receiver can be more confident.</p>

<p>Here are a couple of links that I found informative:</p>

<ul>
  <li><a href="https://support.google.com/a/answer/33786">About SPF</a></li>
  <li><a href="https://support.google.com/a/answer/10683907">SPF record structure</a></li>
  <li><a href="https://support.google.com/a/answer/174124">About DKIM</a></li>
  <li><a href="https://support.google.com/a/answer/2466580">About DMARC</a></li>
  <li><a href="https://support.google.com/a/answer/10032169">DMARC record structure</a></li>
  <li><a href="https://help.returnpath.com/hc/en-us/articles/222437867-What-are-the-different-tags-of-a-DMARC-record-">More DMARC record structure</a></li>
</ul>]]></content><author><name>Vlad GURDIGA</name></author><category term="smtp" /><category term="dns" /><category term="infrastructure" /><summary type="html"><![CDATA[I have recently played with the idea of setting up an SMTP server for a friend’s custom domain, and wanted to share the working setup.]]></summary></entry></feed>