<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[NucuLabs]]></title><description><![CDATA[Programming, Cloud and Engineering! 👨‍🔬]]></description><link>https://newsletter.nuculabs.dev</link><image><url>https://substackcdn.com/image/fetch/$s_!1s0Q!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F283ee63e-e120-4110-826a-1d818bbcfb62_1000x1000.png</url><title>NucuLabs</title><link>https://newsletter.nuculabs.dev</link></image><generator>Substack</generator><lastBuildDate>Thu, 16 Apr 2026 01:59:06 GMT</lastBuildDate><atom:link href="https://newsletter.nuculabs.dev/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[NucuLabs.dev]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[nuculabs@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[nuculabs@substack.com]]></itunes:email><itunes:name><![CDATA[Denis Nutiu]]></itunes:name></itunes:owner><itunes:author><![CDATA[Denis Nutiu]]></itunes:author><googleplay:owner><![CDATA[nuculabs@substack.com]]></googleplay:owner><googleplay:email><![CDATA[nuculabs@substack.com]]></googleplay:email><googleplay:author><![CDATA[Denis Nutiu]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[How to Resize an LVM Volume in Linux]]></title><description><![CDATA[Introduction]]></description><link>https://newsletter.nuculabs.dev/p/how-to-resize-an-lvm-volume-in-linux</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/how-to-resize-an-lvm-volume-in-linux</guid><dc:creator><![CDATA[Denis Nutiu]]></dc:creator><pubDate>Sun, 12 Apr 2026 11:18:06 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-Ckw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea70b9a1-2354-40c8-b0dc-12c46fc90add_1707x1280.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Introduction</h1><p>Hi everyone!</p><p>This post is a quick how-to resize a LVM volume for a Linux system. One mistake that I&#8217;ve made when setting up my mini PC Linux server with Alma Linux is that I didn&#8217;t pay attention to the defaults when partitioning the disk.</p><p>I woke up a few months later unable to install packages and several docker services failing, the main reason: insufficient disk space on /.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-Ckw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea70b9a1-2354-40c8-b0dc-12c46fc90add_1707x1280.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-Ckw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea70b9a1-2354-40c8-b0dc-12c46fc90add_1707x1280.jpeg 424w, https://substackcdn.com/image/fetch/$s_!-Ckw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea70b9a1-2354-40c8-b0dc-12c46fc90add_1707x1280.jpeg 848w, https://substackcdn.com/image/fetch/$s_!-Ckw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea70b9a1-2354-40c8-b0dc-12c46fc90add_1707x1280.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!-Ckw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea70b9a1-2354-40c8-b0dc-12c46fc90add_1707x1280.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-Ckw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea70b9a1-2354-40c8-b0dc-12c46fc90add_1707x1280.jpeg" width="1456" height="1092" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ea70b9a1-2354-40c8-b0dc-12c46fc90add_1707x1280.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1092,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!-Ckw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea70b9a1-2354-40c8-b0dc-12c46fc90add_1707x1280.jpeg 424w, https://substackcdn.com/image/fetch/$s_!-Ckw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea70b9a1-2354-40c8-b0dc-12c46fc90add_1707x1280.jpeg 848w, https://substackcdn.com/image/fetch/$s_!-Ckw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea70b9a1-2354-40c8-b0dc-12c46fc90add_1707x1280.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!-Ckw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea70b9a1-2354-40c8-b0dc-12c46fc90add_1707x1280.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It turns out that by default the <code>/home</code> partition got 400GB of space allocated and the <code>/</code> partition got only ~70GB.</p><p>That is bad.</p><pre><code><code>Filesystem                  Size  Used Avail Use% Mounted on
/dev/mapper/almalinux-root   70G   42G   29G  60% /
/dev/mapper/almalinux-home  400G  8.0G  392G   2% /home
</code></code></pre><p>Since I was using LVM and <code>/home</code> has an XFS filesystem type, resizing it was not straightforward. Luckily with the help of an AI assistant I&#8217;ve managed to find a solution and I&#8217;m sharing it here for reference.</p><h1>Resizing Home</h1><p>Here&#8217;s how you typically resize the <code>/home</code> partition.</p><h2>ext4</h2><p>If home is ext4 then things are a bit easier, you&#8217;d want to boot into recovery, unmount it and shrink it.</p><pre><code><code>sudo umount /home
sudo e2fsck -f /dev/mapper/almalinux-home
sudo resize2fs /dev/mapper/almalinux-home 100G
sudo lvreduce -L 100G /dev/mapper/almalinux-home
</code></code></pre><p>Then extend <code>/</code></p><pre><code><code>sudo lvextend -r -L +300G /dev/mapper/almalinux-root
</code></code></pre><p>Note: I have not tested this flow.</p><h2>xfs</h2><p>If the /home is XFS, then things are a bit complicated. Home cannot be resized and it has to be recreated.</p><p>You can use the following commands to check for available space. Note that this output is after I resized the partition.</p><pre><code><code>&#10140;  ~ sudo lvs
  LV   VG        Attr       LSize   Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  home almalinux -wi-ao----  70.00g                                                    
  root almalinux -wi-ao---- 399.50g                                                    
  swap almalinux -wi-ao----   5.85g                                                    
&#10140;  ~ sudo lvs
  LV   VG        Attr       LSize   Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  home almalinux -wi-ao----  70.00g                                                    
  root almalinux -wi-ao---- 399.50g                                                    
  swap almalinux -wi-ao----   5.85g                                                    
&#10140;  ~ sudo pvs
  PV             VG        Fmt  Attr PSize   PFree
  /dev/nvme0n1p3 almalinux lvm2 a--  475.35g    0 
</code></code></pre><p>If there&#8217;s no space left then you can&#8217;t grow /. You will have to destroy and recreate the /home.</p><p>To do that you need to do the following steps:</p><ol><li><p>Back-up the contents from /home</p></li></ol><pre><code><code>sudo rsync -aXS /home/ /root/home-backup/
</code></code></pre><p>I didn&#8217;t have much data in it, only the gitlab-runner config and in my case it was a fast and small back-up.</p><ol><li><p>Recreate the /home</p></li></ol><p>The following commands will destroy all the data in /home and will create it with 70G of space</p><pre><code><code>sudo umount /home
sudo lvremove /dev/mapper/almalinux-home
sudo lvcreate -L 70G -n home almalinux
sudo mkfs.xfs /dev/mapper/almalinux-home
sudo mount /home
</code></code></pre><p>If you encounter errors when unmounting and mounting /home check the following section for troubleshooting and fixing tips.</p><h1>Troubleshooting mount and unmount</h1><p>You won&#8217;t be able to unmount /home if an user is using it. I had to shut down gitlab runner before unmounting.</p><p>You can check what is keeping fs busy with the following commands:</p><pre><code><code>&#10140;  ~ sudo fuser -vm /home
                     USER        PID ACCESS COMMAND
/home:               root     kernel mount /home
                     root       1449 ..c.. gitlab-runner

&#10140;  ~ sudo lsof +D /home | head -100
COMMAND    PID USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
gitlab-ru 1449 root  cwd    DIR  253,2        6 16777344 /home/gitlab-runner
</code></code></pre><p>If you get an invalid uid when mounting home you will need to edit <code>/etc/fstab</code> file.</p><p>You can get the uid of the new /home partition with the following commands:</p><pre><code><code>
&#10140;  ~ sudo lsblk --fs
NAME               FSTYPE      FSVER    LABEL UUID                                   FSAVAIL FSUSE% MOUNTPOINTS
nvme0n1                                                                                             
&#9500;&#9472;nvme0n1p1        vfat        FAT32          E5B7-DC76                               589.9M     1% /boot/efi
&#9500;&#9472;nvme0n1p2        xfs                        b11659d8-531b-4d08-a809-e6740a32d0c5    333.8M    65% /boot
&#9492;&#9472;nvme0n1p3        LVM2_member LVM2 001       Tsnalq-dvtn-CVFS-h85H-aBcz-sOrs-jeQkfK                
  &#9500;&#9472;almalinux-root xfs                        7e852464-4b83-4f16-a831-a9d794e584b7    350.6G    12% /
  &#9500;&#9472;almalinux-swap swap        1              7e6ce228-b5f2-454b-b7fe-61480d34fcbd                  [SWAP]
  &#9492;&#9472;almalinux-home xfs                        c88fb303-2e9a-4ec1-8654-45ac7a0a675f     68.3G     2% /home


&#10140;  ~ sudo blkid
/dev/mapper/almalinux-swap: UUID="7e6ce228-b5f2-454b-b7fe-61480d34fcbd" TYPE="swap"
/dev/nvme0n1p3: UUID="Tsnalq-dvtn-CVFS-h85H-aBcz-sOrs-jeQkfK" TYPE="LVM2_member" PARTUUID="69c5165d-0709-43ec-8791-9a4227c61164"
/dev/nvme0n1p1: UUID="E5B7-DC76" BLOCK_SIZE="512" TYPE="vfat" PARTLABEL="EFI System Partition" PARTUUID="3e215e38-446f-4f9e-a6d6-109f69cd1e04"
/dev/nvme0n1p2: UUID="b11659d8-531b-4d08-a809-e6740a32d0c5" BLOCK_SIZE="512" TYPE="xfs" PARTUUID="7d56dc6b-8ba3-43d8-a3a6-cf76fee5b8cb"
/dev/mapper/almalinux-home: UUID="c88fb303-2e9a-4ec1-8654-45ac7a0a675f" BLOCK_SIZE="512" TYPE="xfs"
/dev/mapper/almalinux-root: UUID="7e852464-4b83-4f16-a831-a9d794e584b7" BLOCK_SIZE="512" TYPE="xfs"
</code></code></pre><p>Then edit /etc/fstab with nano and replace the UID part (UUID=c88fb303-2e9a-4ec1-8654-45ac7a0a675f), after that <code>mount /home</code> should work.</p><pre><code><code>&#10140;  ~ tail /etc/fstab 
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
UUID=7e852464-4b83-4f16-a831-a9d794e584b7 /                       xfs     defaults        0 0
UUID=b11659d8-531b-4d08-a809-e6740a32d0c5 /boot                   xfs     defaults        0 0
UUID=E5B7-DC76          /boot/efi               vfat    umask=0077,shortname=winnt 0 2
UUID=c88fb303-2e9a-4ec1-8654-45ac7a0a675f /home                   xfs     defaults        0 0
</code></code></pre><ol><li><p>Restore the back-up</p></li></ol><p>Restore the home back-up the following command and start stopped services.</p><pre><code><code>sudo rsync -aHAX /root/home-backup/ /home/
</code></code></pre><ol><li><p>Extend /</p></li></ol><p>Extend the <code>/</code> volume with the 100% of FREE space with issuing of the following command.</p><pre><code><code>sudo lvextend -r -l +100%FREE /dev/mapper/almalinux-root
</code></code></pre><h1>Conclusion</h1><p>If we don&#8217;t pay attention when installing a new Linux distro on a server we might get a surprise. Sometimes defaults aren&#8217;t that good. In this post we&#8217;ve explored how to shrink the <code>/home</code> volume and grow the <code>/</code> volume.</p><p>If the <code>/home</code> filesystem is of type ext4 then we just shrink it and grow <code>/</code>, otherwise if <code>/home</code> is xfs filesystem we need to back up the data, recreate the <code>/home</code> partition from scratch, restore the back-up and then grow <code>/</code>.</p><p>Thank you for reading!</p>]]></content:encoded></item><item><title><![CDATA[ Kafka Connect Basics ]]></title><description><![CDATA[Hi, this article is about Kafka connect!]]></description><link>https://newsletter.nuculabs.dev/p/kafka-connect-basics</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/kafka-connect-basics</guid><dc:creator><![CDATA[Denis Nutiu]]></dc:creator><pubDate>Thu, 02 Apr 2026 15:15:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!7gKp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F242ad162-3cc0-4b85-b626-461a7dd96a54_800x915.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi, this article is about Kafka connect!</p><h2>Introduction</h2><p>Kafka connect is a tool for streaming data between Kafka and other systems. It is distributed and scalable by<br>default and since it&#8217;s a standardized tool there are lots of connectors already available.</p><p>Connectors connect Kafka to a system or vice versa. There are two types of connectors</p><ul><li><p><strong>Source</strong>: Source connectors grab data from an existing system e.g: MariaDB, PostgreSQL, S3, Jira, and others, and stream the data into one or more Kafka topics.</p></li><li><p><strong>Sink</strong>: Sink connectors grab the data from the topics and ingests it to a new system, eg: MongoDB, Snowflake, S3.</p></li></ul><p>If you want to stream change data capture events from your databases, the <a href="https://debezium.io/">Debezium</a> provides<br>connectors that allow you<br>to do just that. CDC is an append only log that identifies changes in databases, using a cdc stream you can<br>replicate or reconstruct a database, additionally you can react on events by processing them in an external system.</p><p>Kafka connect can be deployed in standalone mode or distributed as a cluster of workers.</p><p>It features a RESTful interface for interacting with it:</p><ul><li><p>configuring connectors</p></li><li><p>starting, stopping, pausing connectors</p></li><li><p>viewing connector status</p></li><li><p>resting connector offsets</p></li></ul><p>It also allows you to apply various transformations on a message.</p><p>Apache Kafka has an amazing documentation section<br>on <a href="https://kafka.apache.org/42/kafka-connect/user-guide/#running-kafka-connect">Kafka Connect</a>.</p><p>Here&#8217;s a diagram of a system built with Kafka connect, it replicates data from PostgreSQL and MariaDB into<br>ElasticSearch. ElasticSearch offers a lot of tools for searching through the data with fast and good accuracy.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7gKp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F242ad162-3cc0-4b85-b626-461a7dd96a54_800x915.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7gKp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F242ad162-3cc0-4b85-b626-461a7dd96a54_800x915.webp 424w, https://substackcdn.com/image/fetch/$s_!7gKp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F242ad162-3cc0-4b85-b626-461a7dd96a54_800x915.webp 848w, https://substackcdn.com/image/fetch/$s_!7gKp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F242ad162-3cc0-4b85-b626-461a7dd96a54_800x915.webp 1272w, https://substackcdn.com/image/fetch/$s_!7gKp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F242ad162-3cc0-4b85-b626-461a7dd96a54_800x915.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7gKp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F242ad162-3cc0-4b85-b626-461a7dd96a54_800x915.webp" width="800" height="915" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/242ad162-3cc0-4b85-b626-461a7dd96a54_800x915.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:915,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot; &quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt=" " title=" " srcset="https://substackcdn.com/image/fetch/$s_!7gKp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F242ad162-3cc0-4b85-b626-461a7dd96a54_800x915.webp 424w, https://substackcdn.com/image/fetch/$s_!7gKp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F242ad162-3cc0-4b85-b626-461a7dd96a54_800x915.webp 848w, https://substackcdn.com/image/fetch/$s_!7gKp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F242ad162-3cc0-4b85-b626-461a7dd96a54_800x915.webp 1272w, https://substackcdn.com/image/fetch/$s_!7gKp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F242ad162-3cc0-4b85-b626-461a7dd96a54_800x915.webp 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Rest API</h2><p>For reference, I&#8217;ve copied all the operations from<br>the <a href="https://kafka.apache.org/42/kafka-connect/user-guide/#rest-api">REST API</a><br>documentation and put them into a table.</p><p>Method Path Description GET /connectors return a list of active connectors. POST /connectors create a new connector. GET /connectors/{name} get information about a specific connector. DELETE /connectors/{name} deletes a connector. GET /connectors/{name}/config get the configuration parameters for a specific connector. PUT /connectors/{name}/config update the configuration parameters for a specific connector. PATCH /connectors/{name}/config patch the configuration parameters for a specific connector. GET /connectors/{name}/status get current status of the connector. GET /connectors/{name}/tasks get a list of tasks currently running for a connector. GET /connectors/{name}/tasks/{taskid}/status get current status of the task. PUT /connectors/{name}/pause pause the connector and its tasks, which stops message processing until the connector is resumed. PUT /connectors/{name}/stop stop the connector and shut down its tasks. PUT /connectors/{name}/resume resume a paused or stopped connector. POST /connectors/{name}/restart restart a connector and its tasks instances. POST /connectors/{name}/tasks/{taskId}/restart restart an individual task. PUT /connectors/{name}/topics/reset send a request to empty the set of active topics of a connector. GET /connectors/{name}/offsets get the current offsets for a connector. DELETE /connectors/{name}/offsets reset the offsets for a connector. GET /connector-plugins return a list of connector plugins installed in the Kafka Connect cluster. GET /connector-plugins/{plugin-type}/config get the configuration definition for the specified plugin. PUT /connector-plugins/{connector-type}/config/validate validate the provided configuration values against the configuration definition.</p><p>To start a new connector instance you would usually use POST on <code>/connectors</code> with a config body:<br></p><pre><code><code>{
  "name": "my-jdbc-source",
  "config": {
    "connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector",
    "tasks.max": "1",

    "connection.url": "jdbc:postgresql://localhost:5432/mydb",
    "connection.user": "myuser",
    "connection.password": "mypassword",

    "mode": "incrementing",
    "incrementing.column.name": "id",
    "table.whitelist": "users",
    "poll.interval.ms": "5000",

    "topic.prefix": "pg.",

    "value.converter": "org.apache.kafka.connect.json.JsonConverter",
    "value.converter.schemas.enable": "false",

    "transforms": "maskSensitive",
    "transforms.maskSensitive.type": "org.apache.kafka.connect.transforms.MaskField$Value",
    "transforms.maskSensitive.fields": "email,phone",
    "transforms.maskSensitive.replacement": "****"
  }
}
</code></code></pre><h2>Converters</h2><p>Converters are used by connect in order to convert values from a type to another. Converts apply to the kafka message<br>key and kafka message value. For example, if you have the following JSON message:<br></p><pre><code><code>{"data": 1}</code></code></pre><p>A string converter will put that message as a string in the Kafka topic, where as a JSON converter will keep it JSON.<br>There are also binary format converters like <a href="https://en.wikipedia.org/wiki/Apache_Avro">Avro</a> and ProtoBuf,<br>that help reduce the message size by packing the message into the compact format. A downside of this format is that you<br>need the message schema in order to deserialize it.</p><p>You can also write your own converter and load it into Kafka connect.</p><p>To set the converters you use the following keys:</p><ul><li><p>key.converter: Sets the converter for the message key.</p></li><li><p>value.converter: Sets the converter for the message value.</p></li></ul><p>Here are some common converter classes:</p><ul><li><p>org.apache.kafka.connect.storage.StringConverter</p></li><li><p>org.apache.kafka.connect.json.JsonConverter</p></li><li><p>org.apache.kafka.connect.converters.ByteArrayConverter</p></li><li><p>io.confluent.connect.json.JsonSchemaConverter (Requires schema registry)</p></li><li><p>io.confluent.connect.protobuf.ProtobufConverter (Requires schema registry)</p></li><li><p>io.confluent.connect.avro.AvroConverter (Requires schema registry)</p></li></ul><p>And you usually set a converter with:<br></p><pre><code><code>{
  "key.converter": "org.apache.kafka.connect.json.JsonConverter",
  "value.converter": "org.apache.kafka.connect.json.JsonConverter",
  "value.converter.schemas.enable": "true",
  "key.converter.schemas.enable": "true"
}
</code></code></pre><p>By also setting <code>value.converter.schemas.enable</code> to <code>true</code> you will receive the schema of the JSON message along<br>with the payload.</p><h3>Schema Registry</h3><p>The schema registry is another component that acts as a cache for the message schemas.</p><p>Binary formats like Avro or Protobuf cannot be decoded by their receiver without the message&#8217;s schema, and<br>sending the schema with each message increases the message size.</p><p>The purpose of the schema registry is to keep all schemas together in a database and let producers and consumers<br>request the schema only when needed, so that messages can be produced in the kafka topic without including the schema.</p><p>This component is optional, and it&#8217;s only required when using binary formats like Avro or Protobuf.</p><h2>Transforms</h2><p>You can apply various transformations on messages that are processed by the connector.</p><p>Common transforms include masking fields, dropping fields, replacing values, renaming fields and more.</p><ul><li><p>Cast - Cast fields or the entire key or value to a specific type</p></li><li><p>DropHeaders - Remove headers by name</p></li><li><p>ExtractField - Extract a specific field from Struct and Map and include only this field in results</p></li><li><p>Filter - Removes messages from all further processing. This is used with a predicate to selectively filter certain messages</p></li><li><p>Flatten - Flatten a nested data structure</p></li><li><p>HeaderFrom - Copy or move fields in the key or value to the record headers</p></li><li><p>HoistField - Wrap the entire event as a single field inside a Struct or a Map</p></li><li><p>InsertField - Add a field using either static data or record metadata</p></li><li><p>InsertHeader - Add a header using static data</p></li><li><p>MaskField - Replace field with valid null value for the type (0, empty string, etc) or custom replacement (non-empty string or numeric value only)</p></li><li><p>RegexRouter - modify the topic of a record based on original topic, replacement string and a regular expression</p></li><li><p>ReplaceField - Filter or rename fields</p></li><li><p>SetSchemaMetadata - modify the schema name or version</p></li><li><p>TimestampConverter - Convert timestamps between different formats</p></li><li><p>TimestampRouter - Modify the topic of a record based on original topic and timestamp. Useful when using a sink that needs to write to different tables or indexes based on timestamps</p></li><li><p>ValueToKey - Replace the record key with a new key formed from a subset of fields in the record value</p></li></ul><p>Source: <a href="https://kafka.apache.org/41/kafka-connect/user-guide/#transformations">https://kafka.apache.org/41/kafka-connect/user-guide/#transformations</a></p><p>To apply transforms you would include them into the connector config:<br></p><pre><code><code>{
  "connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector",
  "transforms": "maskSensitive",
  "transforms.maskSensitive.type": "org.apache.kafka.connect.transforms.MaskField$Value",
  "transforms.maskSensitive.fields": "sensitiveField",
  "transforms.maskSensitive.replacement": "****"
}
</code></code></pre><h2>Docker Compose</h2><p>You can start a pre-configured Kafka Connect instance along with a Kafka cluster for development or playing around<br>using this docker-compose file.<br></p><pre><code><code>services:

  broker:
    image: confluentinc/cp-kafka:8.0.0
    hostname: broker
    container_name: broker
    ports:
      - "9092:9092"
      - "9101:9101"
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT'
      KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://broker:29092,PLAINTEXT_HOST://localhost:9092'
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
      KAFKA_JMX_PORT: 9101
      KAFKA_JMX_HOSTNAME: localhost
      KAFKA_PROCESS_ROLES: 'broker,controller'
      KAFKA_CONTROLLER_QUORUM_VOTERS: '1@broker:29093'
      KAFKA_LISTENERS: 'PLAINTEXT://broker:29092,CONTROLLER://broker:29093,PLAINTEXT_HOST://0.0.0.0:9092'
      KAFKA_INTER_BROKER_LISTENER_NAME: 'PLAINTEXT'
      KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
      KAFKA_LOG_DIRS: '/tmp/kraft-combined-logs'
      # Replace CLUSTER_ID with a unique base64 UUID using "bin/kafka-storage.sh random-uuid"
      # See https://docs.confluent.io/kafka/operations-tools/kafka-tools.html#kafka-storage-sh
      CLUSTER_ID: 'MkU3OEVBNTcwNTJENDM2Qk'

#  schema-registry:
#    image: confluentinc/cp-schema-registry:8.0.0
#    hostname: schema-registry
#    container_name: schema-registry
#    depends_on:
#      - broker
#    ports:
#      - "8081:8081"
#    environment:
#      SCHEMA_REGISTRY_HOST_NAME: schema-registry
#      SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: 'broker:29092'
#      SCHEMA_REGISTRY_LISTENERS: http://0.0.0.0:8081

  connect:
    image: confluentinc/cp-kafka-connect:8.1.2
    hostname: connect
    container_name: connect
    depends_on:
      - broker
#      - schema-registry
    ports:
      - "8083:8083"
    environment:
      CONNECT_BOOTSTRAP_SERVERS: 'broker:29092'
      CONNECT_REST_ADVERTISED_HOST_NAME: connect
      CONNECT_GROUP_ID: compose-connect-group
      CONNECT_CONFIG_STORAGE_TOPIC: docker-connect-configs
      CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: 1
      CONNECT_OFFSET_FLUSH_INTERVAL_MS: 10000
      CONNECT_OFFSET_STORAGE_TOPIC: docker-connect-offsets
      CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: 1
      CONNECT_STATUS_STORAGE_TOPIC: docker-connect-status
      CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: 1
      CONNECT_KEY_CONVERTER: org.apache.kafka.connect.json.JsonConverter
      CONNECT_VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter
#      CONNECT_VALUE_CONVERTER_SCHEMA_REGISTRY_URL: http://schema-registry:8081
      CONNECT_PLUGIN_PATH: "/usr/share/java,/usr/share/confluent-hub-components"
</code></code></pre><p>Additionally, you can also <a href="https://kafka.apache.org/community/downloads/">download</a> the kafka binary archive and run<br>connect with <code>bin/connect-standalone.sh config/connect-standalone.properties</code>.</p><p>That&#8217;s all, I hope this article gave you a rough idea of Kafka Connect and it&#8217;s capabilities.</p><h2>References</h2><ul><li><p><a href="https://kafka.apache.org/41/kafka-connect/user-guide/">https://kafka.apache.org/41/kafka-connect/user-guide/</a></p></li><li><p><a href="https://www.confluent.io/blog/kafka-connect-deep-dive-converters-serialization-explained/">https://www.confluent.io/blog/kafka-connect-deep-dive-converters-serialization-explained/</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[An introduction to types in Ruby]]></title><description><![CDATA[Hello everyone!]]></description><link>https://newsletter.nuculabs.dev/p/an-introduction-to-types-in-ruby</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/an-introduction-to-types-in-ruby</guid><dc:creator><![CDATA[Denis Nutiu]]></dc:creator><pubDate>Wed, 28 Jan 2026 07:35:12 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!_Ezd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd15110b6-6897-4b42-badb-1d01d89a0e5d_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_Ezd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd15110b6-6897-4b42-badb-1d01d89a0e5d_2752x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_Ezd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd15110b6-6897-4b42-badb-1d01d89a0e5d_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!_Ezd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd15110b6-6897-4b42-badb-1d01d89a0e5d_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!_Ezd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd15110b6-6897-4b42-badb-1d01d89a0e5d_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!_Ezd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd15110b6-6897-4b42-badb-1d01d89a0e5d_2752x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_Ezd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd15110b6-6897-4b42-badb-1d01d89a0e5d_2752x1536.png" width="724" height="404.2664835164835" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d15110b6-6897-4b42-badb-1d01d89a0e5d_2752x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:724,&quot;bytes&quot;:6009967,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.nuculabs.dev/i/186053664?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd15110b6-6897-4b42-badb-1d01d89a0e5d_2752x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_Ezd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd15110b6-6897-4b42-badb-1d01d89a0e5d_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!_Ezd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd15110b6-6897-4b42-badb-1d01d89a0e5d_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!_Ezd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd15110b6-6897-4b42-badb-1d01d89a0e5d_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!_Ezd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd15110b6-6897-4b42-badb-1d01d89a0e5d_2752x1536.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Hello everyone!</p><p>I started this year by re-learning the Ruby programming language. Ruby is a lovely and elegant programming language, and It was one of my first three languages that I&#8217;ve learned back in the day on CodeCademy.</p><p>In January, I finished reading the <a href="https://www.manning.com/books/the-well-grounded-rubyist-fourth-edition">Well-Grounded Rubyist, Fourth Edition</a> book which got me a good start with Ruby. I&#8217;m by no means proficient in it yet, but I&#8217;ve also bought several Ruby books that I hope to finish reading this year.</p><p>The reason for learning a new language every year is that I find it enjoyable to try new things and read books.<br>In the last three years I&#8217;ve read about Rust, Kotlin and Dart, and I&#8217;ve also done a few side projects in them.</p><p>Back when I was learning Python we had no types, everything was blank and it was difficult to tell what a method was doing by just looking at its signature, luckily for us <a href="https://peps.python.org/pep-0484/">PEP 484</a> added type hints, and we could write code like this:</p><pre><code><code>def greeting(name: str) -&gt; str:
    return 'Hello ' + name
</code></code></pre><p>Ruby also has support for types and the reason I&#8217;m writing this article is to show you how to add types to your Ruby project using <a href="https://sorbet.org/">Sorbet</a> and <a href="https://github.com/ruby/rbs">RBS</a>.</p><p>Both approaches are different, let&#8217;s explore!</p><p></p><p>You can read the full article on my blog:</p><p><a href="https://nuculabs.dev/programming/2026/01/28/types-in-ruby.html">https://nuculabs.dev/programming/2026/01/28/types-in-ruby.html</a></p>]]></content:encoded></item><item><title><![CDATA[Self Hosting Gitlab]]></title><description><![CDATA[A practical how-to self host Gitlab]]></description><link>https://newsletter.nuculabs.dev/p/self-hosting-gitlab</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/self-hosting-gitlab</guid><dc:creator><![CDATA[Denis Nutiu]]></dc:creator><pubDate>Sun, 04 Jan 2026 09:46:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!nPi8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b9f3dd4-6aa6-4ba6-972b-3c967d262c08_1707x1280.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello everyone and happy new year!</p><h1>Introduction</h1><p>I&#8217;ve been running GitLab as my software forge for the last three weeks and everything was smooth. I replaced my Forgejo instance with it because it has a better UI and UX. Gitlab is more resource consuming than Forgejo and my 5$/month VPS turned into 17$/month in order to host and use it without lag.</p><p>Well, 204$ a year is a bit too much in my opinion just to host the software forge, so I decided to buy a MiniPC and host it myself instead, and with the help of Cloudflare I can safely expose it to the internet using tunnels.</p><p>The idea behind tunnels is that you run a cloudflare agent on the PC and you won&#8217;t need to expose ports to the outside. All traffic is received through the tunnel directly, and since I host my DNS with Cloudflare this solution works well.</p><p>My power bill is also low, much lower than my 5$/month initial VPS. I have a smart plug which tracks energy usage, and I&#8217;m using a power cord that powers: Gitlab MiniPC, Old Gaming PC that I use as a gitlab runner, a network switch and overall I&#8217;m averaging about 1KW a day.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nPi8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b9f3dd4-6aa6-4ba6-972b-3c967d262c08_1707x1280.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nPi8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b9f3dd4-6aa6-4ba6-972b-3c967d262c08_1707x1280.jpeg 424w, https://substackcdn.com/image/fetch/$s_!nPi8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b9f3dd4-6aa6-4ba6-972b-3c967d262c08_1707x1280.jpeg 848w, https://substackcdn.com/image/fetch/$s_!nPi8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b9f3dd4-6aa6-4ba6-972b-3c967d262c08_1707x1280.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!nPi8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b9f3dd4-6aa6-4ba6-972b-3c967d262c08_1707x1280.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nPi8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b9f3dd4-6aa6-4ba6-972b-3c967d262c08_1707x1280.jpeg" width="1456" height="1092" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3b9f3dd4-6aa6-4ba6-972b-3c967d262c08_1707x1280.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1092,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!nPi8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b9f3dd4-6aa6-4ba6-972b-3c967d262c08_1707x1280.jpeg 424w, https://substackcdn.com/image/fetch/$s_!nPi8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b9f3dd4-6aa6-4ba6-972b-3c967d262c08_1707x1280.jpeg 848w, https://substackcdn.com/image/fetch/$s_!nPi8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b9f3dd4-6aa6-4ba6-972b-3c967d262c08_1707x1280.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!nPi8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b9f3dd4-6aa6-4ba6-972b-3c967d262c08_1707x1280.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This article&#8217;s focus is a how to host Gitlab and perform basic operations for maintenance.</p><p>You can read the article on my blog:</p><p>https://nuculabs.dev/posts/2026/self-hosting-gitlab/</p>]]></content:encoded></item><item><title><![CDATA[Self Hosting Navidrome - Your personal music streaming service ]]></title><description><![CDATA[Hello everyone!]]></description><link>https://newsletter.nuculabs.dev/p/self-hosting-navidrome-your-personal</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/self-hosting-navidrome-your-personal</guid><dc:creator><![CDATA[Denis Nutiu]]></dc:creator><pubDate>Sat, 01 Nov 2025 09:35:29 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!cnhc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b6a2b4-5f07-4602-b634-cd1003117553_800x402.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello everyone! &#128075;</p><p>In this article I will show you how to self-host Navidrome, your personal music streaming<br>service.</p><p>I will provide you with an Ansible playbook that you can run from your machine.</p><h1>Navidrome</h1><p>Navidrome is an open-source self hosted music streaming server. You can deploy it, load your music and then<br>enjoy it using various clients like a web browser or a mobile application.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cnhc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b6a2b4-5f07-4602-b634-cd1003117553_800x402.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cnhc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b6a2b4-5f07-4602-b634-cd1003117553_800x402.webp 424w, https://substackcdn.com/image/fetch/$s_!cnhc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b6a2b4-5f07-4602-b634-cd1003117553_800x402.webp 848w, https://substackcdn.com/image/fetch/$s_!cnhc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b6a2b4-5f07-4602-b634-cd1003117553_800x402.webp 1272w, https://substackcdn.com/image/fetch/$s_!cnhc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b6a2b4-5f07-4602-b634-cd1003117553_800x402.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cnhc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b6a2b4-5f07-4602-b634-cd1003117553_800x402.webp" width="800" height="402" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d8b6a2b4-5f07-4602-b634-cd1003117553_800x402.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:402,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cnhc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b6a2b4-5f07-4602-b634-cd1003117553_800x402.webp 424w, https://substackcdn.com/image/fetch/$s_!cnhc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b6a2b4-5f07-4602-b634-cd1003117553_800x402.webp 848w, https://substackcdn.com/image/fetch/$s_!cnhc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b6a2b4-5f07-4602-b634-cd1003117553_800x402.webp 1272w, https://substackcdn.com/image/fetch/$s_!cnhc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b6a2b4-5f07-4602-b634-cd1003117553_800x402.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>You can find more information about Navidrome by visiting their website:</p><ul><li><p><a href="https://www.navidrome.org/about/">https://www.navidrome.org/about/</a></p></li></ul><p>Since Navidrome is open source and free to use for everyone you can also support its<br>authors with a small donation:</p><ul><li><p><a href="https://ko-fi.com/deluan">https://ko-fi.com/deluan</a></p></li></ul><p></p><p>Continue reading on my blog:</p><p><a href="https://nuculabs.dev/">https://nuculabs.dev/</a></p>]]></content:encoded></item><item><title><![CDATA[Self Hosting Forgejo]]></title><description><![CDATA[Hello everyone!]]></description><link>https://newsletter.nuculabs.dev/p/self-hosting-forgejo</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/self-hosting-forgejo</guid><dc:creator><![CDATA[Denis Nutiu]]></dc:creator><pubDate>Sun, 07 Sep 2025 08:10:28 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b824a4f2-dfee-4f7a-b787-431a7f2b72ac_2048x2048.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello everyone!</p><p>I&#8217;m writing this article to quickly show you how-to self-host <a href="https://forgejo.org/">Forgejo</a>, the Git software forge, and how to do common maintenance operations.</p><p>Forgejo is a lightweight platform for hosting git software repositories, similar to GitHub. It is easy to self-host and manage.</p><p>If you&#8217;re curious about it you can read it on my blog:</p><p><a href="https://blog.nuculabs.dev/posts/2025/self-hosting-forgejo/">https://blog.nuculabs.dev/posts/2025/self-hosting-forgejo/</a></p><p>If you need help with self-hosting Forgejo or the article contains inaccurate information you may reach out to me by email or on the forum (preferred).</p><p><a href="https://forum.nuculabs.dev/threads/self-hosting-forgejo.124/#post-209">https://forum.nuculabs.dev/threads/self-hosting-forgejo.124/#post-209</a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Containerizing a Python Project with UV]]></title><description><![CDATA[I wrote a new blog post on containerizing a Python project that uses uv.]]></description><link>https://newsletter.nuculabs.dev/p/containerizing-a-python-project-with</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/containerizing-a-python-project-with</guid><dc:creator><![CDATA[Denis Nutiu]]></dc:creator><pubDate>Thu, 07 Aug 2025 15:52:03 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ufzL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12f7c97c-b87a-42bb-af7f-7ca70b7e6f85_2048x2048.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ufzL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12f7c97c-b87a-42bb-af7f-7ca70b7e6f85_2048x2048.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ufzL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12f7c97c-b87a-42bb-af7f-7ca70b7e6f85_2048x2048.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ufzL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12f7c97c-b87a-42bb-af7f-7ca70b7e6f85_2048x2048.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ufzL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12f7c97c-b87a-42bb-af7f-7ca70b7e6f85_2048x2048.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ufzL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12f7c97c-b87a-42bb-af7f-7ca70b7e6f85_2048x2048.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ufzL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12f7c97c-b87a-42bb-af7f-7ca70b7e6f85_2048x2048.jpeg" width="1456" height="1456" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/12f7c97c-b87a-42bb-af7f-7ca70b7e6f85_2048x2048.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1456,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:481252,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.nuculabs.dev/i/170371332?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12f7c97c-b87a-42bb-af7f-7ca70b7e6f85_2048x2048.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ufzL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12f7c97c-b87a-42bb-af7f-7ca70b7e6f85_2048x2048.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ufzL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12f7c97c-b87a-42bb-af7f-7ca70b7e6f85_2048x2048.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ufzL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12f7c97c-b87a-42bb-af7f-7ca70b7e6f85_2048x2048.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ufzL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12f7c97c-b87a-42bb-af7f-7ca70b7e6f85_2048x2048.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I wrote a new blog post on containerizing a Python project that uses uv.</p><p>You can read it on my blog:</p><p>&#127479;&#127476; - <a href="https://blog.nuculabs.dev/ro/posts/2025/2025-08-06-containerizing-uv-python-projects/">Containerizarea unui proiect de Python cu UV</a></p><p>&#127482;&#127480; - <a href="https://blog.nuculabs.dev/posts/2025/2025-08-06-containerizing-uv-python-projects/">Containerizing a Python Project with UV</a></p>]]></content:encoded></item><item><title><![CDATA[Introduction to MCP Servers and writing one in Python ]]></title><description><![CDATA[How to write an MCP server in Python what why it is useful]]></description><link>https://newsletter.nuculabs.dev/p/introduction-to-mcp-servers-and-writing</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/introduction-to-mcp-servers-and-writing</guid><dc:creator><![CDATA[Denis Nutiu]]></dc:creator><pubDate>Sun, 27 Jul 2025 07:38:30 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!J4sZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d3f3a4b-257c-4061-8d60-7dc803221be3_987x845.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!J4sZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d3f3a4b-257c-4061-8d60-7dc803221be3_987x845.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!J4sZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d3f3a4b-257c-4061-8d60-7dc803221be3_987x845.webp 424w, https://substackcdn.com/image/fetch/$s_!J4sZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d3f3a4b-257c-4061-8d60-7dc803221be3_987x845.webp 848w, https://substackcdn.com/image/fetch/$s_!J4sZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d3f3a4b-257c-4061-8d60-7dc803221be3_987x845.webp 1272w, https://substackcdn.com/image/fetch/$s_!J4sZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d3f3a4b-257c-4061-8d60-7dc803221be3_987x845.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!J4sZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d3f3a4b-257c-4061-8d60-7dc803221be3_987x845.webp" width="987" height="845" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7d3f3a4b-257c-4061-8d60-7dc803221be3_987x845.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:845,&quot;width&quot;:987,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:78856,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.nuculabs.dev/i/169356827?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d3f3a4b-257c-4061-8d60-7dc803221be3_987x845.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!J4sZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d3f3a4b-257c-4061-8d60-7dc803221be3_987x845.webp 424w, https://substackcdn.com/image/fetch/$s_!J4sZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d3f3a4b-257c-4061-8d60-7dc803221be3_987x845.webp 848w, https://substackcdn.com/image/fetch/$s_!J4sZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d3f3a4b-257c-4061-8d60-7dc803221be3_987x845.webp 1272w, https://substackcdn.com/image/fetch/$s_!J4sZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d3f3a4b-257c-4061-8d60-7dc803221be3_987x845.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The picture of this article is the output of Claude, using a local MCP server that gives it the output of the <code>ls -l</code> command on a given path. Notice how the LLM likes to praise me, exaggerating a bit, In my opinion this is just a way to keep your users hooked on their product. Who doesn't like to be praised and approved with everything they say, right? :D</p><p>Originally published here: <a href="https://www.nuculabs.dev/threads/introduction-to-mcp-servers-and-writing-one-in-python.115/#post-186">https://www.nuculabs.dev/threads/introduction-to-mcp-servers-and-writing-one-in-python.115/#post-186</a></p><p>Hello everyone,</p><p>I wanted to write an article on MCP servers since I just written one in Python last week.</p><p>Theory&#8203;</p><p>MCP stands for Model Context Protocol, and it is a standardized protocol designed to connect LLMs with tools, resources and prompt templates. Tools are functions that get executed by LLMs on demand. This is quite nice because once you have an MCP server set up you can use it with multiple LLMs, imagine the model calls your server's functions and you only need to tell the model which parameters the functions take and what data does it return, and of course some hints for the LLM on how to use your defined tools</p><p>Servers supports two transport layers, the STDIO mode and HTTP Streaming.</p><ul><li><p>STDIO uses standard IO and is used for local processes. I believe that you just call the server like you call a CLI tool and then you capture it's output, please correct me if I'm wrong.</p></li><li><p>HTTP Streaming servers uses <a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events">Server-Sent Events</a> with <a href="https://www.jsonrpc.org/specification">JSON-RPC</a>, which is just JSON like:</p></li></ul><pre><code><code>

--&gt; [
        {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
        {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
        {"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"},
        {"foo": "boo"},
        {"jsonrpc": "2.0", "method": "foo.get", "params": {"name": "myself"}, "id": "5"},
        {"jsonrpc": "2.0", "method": "get_data", "id": "9"}
    ]
&lt;-- [
        {"jsonrpc": "2.0", "result": 7, "id": "1"},
        {"jsonrpc": "2.0", "result": 19, "id": "2"},
        {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null},
        {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "5"},
        {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"}
    ]
</code></code></pre><p>You can read more about MCP's specification here: <a href="https://modelcontextprotocol.io/specification/2025-06-18">Specification - Model Context Protocol</a>.</p><h1>Use cases&#8203;</h1><p>I can think about various use-cases on how I would use custom MCP servers, as a homelab user, hobbies, not for business. Some use cases imply online LLMs (OpenAI, Mistral, Claude) when I don't care about privacy and some use cases with offline LLMs in order to preserve my privacy.</p><pre><code><code>Email prioritization, Most email provides offer you access to read emails using IMAP[1] and POP protocols. You could write an MCP server that exposes some tools for reading emails and let the LLM analyze them.
Endpoint security. Write a local MCP server that provides information about files on the system, permissions and use an LLM to analyze them
Weather information: Write a MCP server that exposes tools for reading data from various sources i.e sensors, weather APIs and a tool for alerting when values are out of range or an unwanted event may occur, such as thunderstorms.
News summarization. You can write an MCP server that scrapes your favorite news sites and provides structured output to an LLM for summarizing them.
</code></code></pre><p>You can have lots of fun with MCP servers. Even if LLMs hallucinate or provide incorrect output, building a weather alert system in an language agnostic way with some calls to a local or remote LLM has never been easier.</p><p>Claude also uses MCP servers in the form of Connectors.</p><p>[1] - <a href="https://docs.python.org/3/library/imaplib.html">imaplib &#8212; IMAP4 protocol client</a></p><h1>A Python MCP Server&#8203;</h1><p>To write MCP server I recommend that you use the official <a href="https://modelcontextprotocol.io/docs/sdk">SDKs</a>. At the current time there are SDKs for Python, Java, JavaScript, TypeScript, Rust, Ruby, Go, Kotlin, Swift and C#.</p><p>To follow along you will need UV installed.<br></p><pre><code><code>

curl -LsSf https://astral.sh/uv/install.sh | sh

uv init nuculabs-mcp
cd nuculabs-mcp

uv add mcp[cli]
</code></code></pre><p>The asyncio Python-based MCP HTTP Streaming server can look like this:<br></p><pre><code><code>


import asyncio
import logging
import subprocess

from mcp.server.fastmcp import FastMCP
from mcp.types import ToolAnnotations
from pydantic import BaseModel, Field


class LsOutput(BaseModel):
    """
        LsOutput is a model class that represents the output of the LS command, this simple example for educational
        purposes only shows a simple text field. You can parse the ls output it to return more meaningful fields
        like the file type, name, date, permissions and so on.
    """
    output: str = Field(..., title="The output of the `ls` command.")


class SystemAdminTools:

    def __init__(self):
        self._logger = logging.getLogger("dev.nuculabs.mcp.SystemAdminTools")
        self._logger.info("hello world")

    async def get_ls_output(self, directory: str) -&gt; LsOutput:
        """
            Returns the output of the `ls` command for the specified directory.
        :param directory: - The directory path
        :return: The LSOutput.
        """
        self._logger.info(f"get_ls_output tool called with: {directory}")
        # This is a dummy example for educational purposes, but you can write a tool that parses the output and returns a meaningful model
        # instead of a simple text file. You could also extend it to remote machines.
        # Security Warning: when taking user input such as the 'directory' variable here, always sanitize and validate it,
        # using this code as it is malicious output can be used to highjack the target system.
        result = subprocess.run(['ls', "-l", directory], capture_output=True, text=True)
        output = result.stdout
        return LsOutput(output=output)


async def main():
    logging.basicConfig(level="INFO")
    # Create an MCP server
    mcp = FastMCP(
        "NucuLabs System Files MCP",
        instructions="You're an expert T-shaped system administrator assistant.",
        host="0.0.0.0",  # open to the world :-)
    )
    tools = SystemAdminTools()

    mcp.add_tool(
        tools.get_ls_output,
        "get_ls_output",
        title="Tool that returns the output from the list files command.",
        description="This tool executes ls on the given directory path and returns all the information from the command.",
        annotations=ToolAnnotations(
            readOnlyHint=True,
            destructiveHint=False,
            idempotentHint=True,
            openWorldHint=False,
        ),
        structured_output=True,
    )

    await mcp.run_streamable_http_async()


if __name__ == "__main__":
    asyncio.run(main())
</code></code></pre><h1>Testing the Server&#8203;</h1><p>To validate and test the MCP server quickly and without using an LLM you can use an MCP server testing tool such as MCP Inspector or Postman. To use MCP Inspector you only need to have NodeJS &gt;= 22 installed on your system and then run.<br></p><pre><code><code>npx @modelcontextprotocol/inspector
</code></code></pre><p>The command will open up a browser with the MCP Inspector server. I haven't managed to test the server using the aforementioned tool.</p><p>I had a positive experience with Postman's MCP client.</p><h1>Testing with an LLM&#8203;</h1><p>To test the server with an LLM you can use the <a href="https://github.com/agno-agi/agno">agno-framework</a> or your client SDK if you already have one. Agno is pretty easy to use.<br></p><pre><code><code>

import asyncio

from agno.agent import Agent
from agno.models.anthropic import Claude
from agno.tools.mcp import MCPTools

async def main():
    async with MCPTools(url="http://localhost:8000/mcp", transport="streamable-http") as mcp_tools:
        agent = Agent(
            # set api key in ANTHROPIC_API_KEY environment variable or pass it as a parameter
            model=Claude(),
            tools=[
                mcp_tools
            ],
            instructions="Use tools to analyze systems.",
            markdown=False,
        )

        await agent.aprint_response(
            "Describe my personality based on the contents of the following directory: /home/dnutiu",
            stream=True,
            show_full_reasoning=True,
            stream_intermediate_steps=True,
        )


if __name__ == '__main__':
    asyncio.run(main())
</code></code></pre><p>After running the snippet I get the following output from Claude:<br></p><pre><code><code>&#9487;&#9473; Message &#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9491;
&#9475;                                                                              &#9475;
&#9475; Describe my personality based on the contents of the following directory:    &#9475;
&#9475; /home/dnutiu                                                                 &#9475;
&#9475;                                                                              &#9475;
&#9495;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9499;
&#9487;&#9473; Tool Calls &#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9491;
&#9475;                                                                              &#9475;
&#9475; &#8226; get_ls_output(directory=/home/dnutiu)                                      &#9475;
&#9475;                                                                              &#9475;
&#9495;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9499;
&#9487;&#9473; Response (9.2s) &#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9491;
&#9475;                                                                              &#9475;
&#9475; Let me check the contents of that directory for you.Based on the contents of &#9475;
&#9475; your home directory, I can tell that you're a software developer with        &#9475;
&#9475; diverse interests in programming and technology. Here's what I can deduce    &#9475;
&#9475; about your personality:                                                      &#9475;
&#9475;                                                                              &#9475;
&#9475; 1. You're versatile and multi-skilled: You have multiple IDE project         &#9475;
&#9475; directories (CLion, PyCharm, PhpStorm, IntelliJ IDEA, Rustrover) which shows &#9475;
&#9475; you work with various programming languages including Python, PHP, Java,     &#9475;
&#9475; C/C++, and Rust.                                                             &#9475;
&#9475;                                                                              &#9475;
&#9475; 2. You're organized: Your directory structure is well-maintained with clear  &#9475;
&#9475; separations for different types of content (Documents, Downloads, Pictures,  &#9475;
&#9475; etc.).                                                                       &#9475;
&#9475;                                                                              &#9475;
&#9475; 3. You're technically inclined: The presence of DevOps directory and Postman &#9475;
&#9475; suggests you're involved in backend development and API testing.             &#9475;
&#9475;                                                                              &#9475;
&#9475; 4. You're likely a professional developer: The presence of multiple          &#9475;
&#9475; development tools and project directories suggests this is more than just a  &#9475;
&#9475; hobby.                                                                       &#9475;
&#9475;                                                                              &#9475;
&#9475; 5. You use Linux (KDE specifically): This shows you're comfortable with      &#9475;
&#9475; technical tools and prefer having control over your computing environment.   &#9475;
&#9475;                                                                              &#9475;
&#9475; 6. You might have some personal projects: The "nuculabs" directory might be  &#9475;
&#9475; a personal project or endeavor.                                              &#9475;
&#9475;                                                                              &#9475;
&#9475; 7. You keep up with modern development tools: You have the JetBrains toolbox &#9475;
&#9475; installed, showing you use current development tools.                        &#9475;
&#9475;                                                                              &#9475;
&#9475; 8. You're interested in multiple areas of software development: From web     &#9475;
&#9475; development (PHP) to systems programming (Rust, C++) to scripting (Python),  &#9475;
&#9475; showing a broad range of interests in programming.                           &#9475;
&#9475;                                                                              &#9475;
&#9475; Your directory structure reveals someone who is methodical, technically      &#9475;
&#9475; sophisticated, and has a strong interest in various aspects of software      &#9475;
&#9475; development and technology.                                                  &#9475;
&#9475;                                                                              &#9475;
&#9495;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9473;&#9499;
</code></code></pre><p>If you have subscription and a supported OS you can also test it with Claude Desktop. On Linux Ollama has some models which support MCP tools.</p><p>That's about it! I hope you've enjoyed this article!</p>]]></content:encoded></item><item><title><![CDATA[ Authenticating a generic client with Spring Security OAuth2 Client]]></title><description><![CDATA[Hello everyone &#128075;,]]></description><link>https://newsletter.nuculabs.dev/p/authenticating-a-generic-client-with</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/authenticating-a-generic-client-with</guid><dc:creator><![CDATA[Denis Nutiu]]></dc:creator><pubDate>Thu, 24 Jul 2025 15:31:20 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/4b8d5bd1-dc89-48b2-ab04-7c2f40e92341_1000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello everyone &#128075;,</p><p>I recently worked on a small side project written in Java with the Spring Framework and I had difficulties authenticating to an external OAuth2 client using spring security. The solution to my problem was clear after I perused the code and documentation of the Spring OAuth Client package.</p><p>I thought that I'll have to write custom classes to configure it for my specific need but it turned out that my issues was strictly configuration related.</p><p>The issues was that I had a Confidential OAuth2 client and the default configuration of Spring Security assumes that the client is Public.</p><p>The <a href="https://datatracker.ietf.org/doc/html/rfc6749#section-2.1">RFC</a> describes the client types as follows:</p><ul><li><p>The Confidential client type is for applications that can keep the client secret safe, for example a back-end web application.</p></li><li><p>The Public client type is for applications that cannot keep their client secrets safe, for example a mobile applications or front end application. The user can always browse the source code and obtain the secrets.</p></li></ul><p>In my Spring project, I've configured the authentication using the applications.properties file. Notice that the configuration of the client and provider is under the nuculabs key.</p><pre><code><code># OAuth2 Client Configuration
spring.security.oauth2.client.registration.nuculabs.client-id=xxx
spring.security.oauth2.client.registration.nuculabs.client-secret=xxx
spring.security.oauth2.client.registration.nuculabs.scope=user:read,read:user
spring.security.oauth2.client.registration.nuculabs.authorizationGrantType=authorization_code
spring.security.oauth2.client.registration.nuculabs.redirectUri=http://localhost:8080/login/oauth2/code/nuculabs
spring.security.oauth2.client.registration.nuculabs.client-authentication-method=client_secret_post

spring.security.oauth2.client.provider.nuculabs.authorization-uri=https://www.example.com/oauth2/authorize
spring.security.oauth2.client.provider.nuculabs.token-uri=https://www.example.com/api/oauth2/token
spring.security.oauth2.client.provider.nuculabs.user-info-uri=https://www.example.com/api/me
spring.security.oauth2.client.provider.nuculabs.user-name-attribute=me
</code></code></pre><p>And the configuration that I was missing was client_secret_post.</p><p>Once that was completed I only had to add the following SecurityConfig class:</p><pre><code><code>

package dev.nuculabs.xenchat.xenchat.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    // OAuth2AuthorizationCodeGrantRequestEntityConverter

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorize -&gt; authorize
                        .requestMatchers("/error", "/webjars/**", "/login").permitAll()
                        .requestMatchers("/").authenticated()
                        .anyRequest().authenticated()
                )
                .oauth2Login(oauth2 -&gt; oauth2
                        .loginPage("/login")
                        .defaultSuccessUrl("/", true)
                )
                .logout(logout -&gt; logout
                        .logoutSuccessUrl("/login")
                        .permitAll()
                );

        return http.build();
    }
}</code></code></pre><p>And when I want to authenticate users using my external OAuth client, I only have to redirect them to an internal link in order to trigger the authentication flow implemented by Spring Security:</p><pre><code><code>/oauth2/authorization/nuculabs</code></code></pre><p>The final step is to configure the external OAuth client with the following URL:</p><pre><code><code>http://localhost:8080/login/oauth2/code/nuculabs</code></code></pre><p>This will technically complete the authentication flow. What will happen next is that Spring will try to grab the user data from the user-info-uri, in my case the API is secured by the access token and it will respond with a "me" object that looks like this:</p><pre><code><code>{

    "me": {
    "about": "I&#8217;m Denis, a Software Engineer living in Romania. I&#8217;m passionate about cloud computing and software development &#10024;.",
    "activity_visible": false,
    "alert_optout": [],
    "allow_post_profile": "members",
    "allow_receive_news_feed": "members",
    "allow_send_personal_conversation": "members",
    "allow_view_identities": "members",
    "allow_view_profile": "members",
    "name": "Denis",
    ...
}</code></code></pre><p>What I had to do was to specify me as the user-name attribute as follows:</p><pre><code><code>spring.security.oauth2.client.provider.nuculabs.user-name-attribute=me</code></code></pre><p>Then I can access all those fields from a controller like so:</p><pre><code><code>package dev.nuculabs.xenchat.xenchat.controller;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.LinkedHashMap;

@Controller
public class HomeController {

@GetMapping("/")
public String home(@AuthenticationPrincipal OAuth2User principal, Model model) {
LinkedHashMap&lt;String, Object&gt; userData = principal.getAttribute("me");
        // Add user attributes to the model
        model.addAttribute("name", userData.get("username"));
        model.addAttribute("email", userData.get("email"));
        model.addAttribute("title", userData.get("custom_title"));
        model.addAttribute("profileUrl", userData.get("view_url"));
        model.addAttribute("isModerator", userData.get("is_moderator"));

        // Add all attributes for debugging
        model.addAttribute("attributes", principal.getAttributes());

return "home";
    }

@GetMapping("/login")
public String login() {
return "login";
    }
}
</code></code></pre><p>That's about it, I hope this article helped! :D</p><p>Thanks for reading!</p>]]></content:encoded></item><item><title><![CDATA[Apache Kafka: How-to set offsets to a fixed time]]></title><description><![CDATA[Hello This is a short article about setting offsets in Apache Kafka for a consumer group.]]></description><link>https://newsletter.nuculabs.dev/p/apache-kafka-how-to-set-offsets-to</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/apache-kafka-how-to-set-offsets-to</guid><dc:creator><![CDATA[Denis Nutiu]]></dc:creator><pubDate>Sun, 20 Jul 2025 08:42:31 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!vOgg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe632efa9-5fad-467c-97dc-fa978f7117bc_600x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vOgg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe632efa9-5fad-467c-97dc-fa978f7117bc_600x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vOgg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe632efa9-5fad-467c-97dc-fa978f7117bc_600x600.png 424w, https://substackcdn.com/image/fetch/$s_!vOgg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe632efa9-5fad-467c-97dc-fa978f7117bc_600x600.png 848w, https://substackcdn.com/image/fetch/$s_!vOgg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe632efa9-5fad-467c-97dc-fa978f7117bc_600x600.png 1272w, https://substackcdn.com/image/fetch/$s_!vOgg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe632efa9-5fad-467c-97dc-fa978f7117bc_600x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vOgg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe632efa9-5fad-467c-97dc-fa978f7117bc_600x600.png" width="600" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e632efa9-5fad-467c-97dc-fa978f7117bc_600x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:600,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:125651,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.nuculabs.dev/i/168765365?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe632efa9-5fad-467c-97dc-fa978f7117bc_600x600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vOgg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe632efa9-5fad-467c-97dc-fa978f7117bc_600x600.png 424w, https://substackcdn.com/image/fetch/$s_!vOgg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe632efa9-5fad-467c-97dc-fa978f7117bc_600x600.png 848w, https://substackcdn.com/image/fetch/$s_!vOgg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe632efa9-5fad-467c-97dc-fa978f7117bc_600x600.png 1272w, https://substackcdn.com/image/fetch/$s_!vOgg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe632efa9-5fad-467c-97dc-fa978f7117bc_600x600.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Hello<br><br>This is a short article about setting offsets in Apache Kafka for a consumer group.<br><br>Normally, to reset offsets in Kafka you need to use the <strong>kafka-consumer-groups.sh</strong> tool, this means downloading the zip archive with Kafka's source code and setting up the Java SDK. All Kafka's tools are dependent on Java and this isn't that nice or developer friendly...<br><br>Sometimes getting Java correctly and getting the tools to run they don't work.</p><p>Either the tool versions are incompatible with the Kafka version on the server or the command executes successfully but it doesn't seem to do anything...<br><br>Another method to set offsets for a consumer it is to use a Kafka library, and to do it through code.<br><br>I have Python installed on my setup and all I need to do is to install the confluent-kafka library:</p><pre><code><code>pip install confluent-kafka</code></code></pre><p>And then run the following code snippet to reset the consumer's offsets to a specific timestamp:</p><pre><code><code>from confluent_kafka import Consumer, TopicPartition
import time

# Configuration
consumer_config = {
    'bootstrap.servers': 'localhost:9092',
    'group.id': 'my-consumer-group',
    'auto.offset.reset': 'earliest',
    'enable.partition.eof': True
}

topic = 'my-topic'
timestamp_ms = int(time.mktime(time.strptime("2025-04-01 12:00:00", "%Y-%m-%d %H:%M:%S")) * 1000) # or time in miliseconds

# Create consumer
consumer = Consumer(consumer_config)

# Get metadata to discover partitions
metadata = consumer.list_topics(topic)
partitions = [TopicPartition(topic, p.id, timestamp_ms) for p in metadata.topics[topic].partitions.values()]

# Lookup offsets for the timestamp
offsets = consumer.offsets_for_times(partitions, timeout=10.0)

# Assign partitions with correct offsets
consumer.assign(offsets)

# Start consuming
try:
    while True:
        msg = consumer.poll(timeout=1.0)
        if msg is None:
            continue
        if msg.error():
            print("Error:", msg.error())
            continue

        print(f"{msg.topic()} [{msg.partition()}] at offset {msg.offset()}: {msg.value().decode('utf-8')}")
        break

except KeyboardInterrupt:
    pass
finally:
    consumer.close()</code></code></pre><p>Thanks for reading!</p><p>Feel free to join the discussion on my forum: https://www.nuculabs.dev/threads/apache-kafka-how-to-set-offsets-to-a-fixed-time.88/</p>]]></content:encoded></item><item><title><![CDATA[Running a PHP Application inside a Container]]></title><description><![CDATA[Hello &#128075;,]]></description><link>https://newsletter.nuculabs.dev/p/running-a-php-application-inside</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/running-a-php-application-inside</guid><dc:creator><![CDATA[Denis Nuțiu]]></dc:creator><pubDate>Sat, 28 Jun 2025 14:21:11 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1s0Q!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F283ee63e-e120-4110-826a-1d818bbcfb62_1000x1000.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello &#128075;,</p><p>In this month&#8217;s blog post I&#8217;ll show you how to run a PHP Application inside a container.</p><p>I&#8217;m quite a fan of online forums and the majority of forum software is written in PHP. To evaluate them quickly I wanted the ability to be able to run and install then locally.</p><p>I&#8217;ve come up with this docker-compose file</p><pre><code><code>services:
  nginx:
    build:
      context: ./
      dockerfile: nginx.dockerfile
    ports:
      - "8080:80" # change port 10080 to any other port
    volumes:
      - ./config/nginx/conf.d:/etc/nginx/conf.d:z
      - ./.data/nginx:/var/log/nginx:z
      - application:/var/www/html:z
      - composer:/root/.composer:z
  php:
    build:
      context: ./
      dockerfile: php83.dockerfile
    volumes:
      - ./config/php.ini:/usr/local/etc/php/php.ini:z
      - application:/var/www/html:z
      - composer:/root/.composer:z
  database:
    image: "postgres:latest"
    ports:
      - 15432:5432
    environment:
      POSTGRES_PASSWORD: denis
      POSTGRES_USER: batman
    volumes:
      - ./.data/postgres/:/var/lib/postgresql/data/:z
  maria:
    image: mariadb
    restart: always
    ports:
      - 13306:3306
    environment:
      MARIADB_ROOT_PASSWORD: example
    volumes:
      - ./.data/maria/:/var/lib/mysql:z
volumes:
    composer:
    application:
      driver: local
      driver_opts:
        type: none
        device: ./application
        o: bind
</code></code></pre><p>All you need to do is place the PHP application inside the <code>./application</code> directory and run:</p><pre><code><code>podman compose up # or docker compose up
</code></code></pre><p>The file will set up the following components:</p><ul><li><p>php - The PHP runtime.</p></li><li><p>nginx - The Nginx web server used to serve requests. You can customize it by editing <code>./config/nginx/conf.d/default.conf</code></p></li><li><p>database - Runs a PostgresSQL database which persists data inside the local <code>./.data</code> directory.</p></li><li><p>maria - Runs a MariaDB database which persists data inside the local <code>./.data</code> directory.</p></li></ul><p>It&#8217;s unlikely that you need both databases running at the same time, feel free to delete the one you don&#8217;t need. Some PHP Applications work with PostgresSQL and some work only with MariaDB/MySQL.</p><p>Thank you for reading, if you have any questions please do reach out on <a href="https://mastodon.social/@nuculabs">@mastodon</a>.</p><p>By the way, I&#8217;m using this method to host my <a href="https://nuculabs.dev">forum</a>.</p>]]></content:encoded></item><item><title><![CDATA[Anubis: Protection against LLMs and Scrapers]]></title><description><![CDATA[Hello everyone!]]></description><link>https://newsletter.nuculabs.dev/p/anubis-protection-against-llms-and</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/anubis-protection-against-llms-and</guid><dc:creator><![CDATA[Denis Nuțiu]]></dc:creator><pubDate>Sun, 18 May 2025 09:42:40 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3bb7080f-82eb-4ec8-abf5-f343c8126889_1271x809.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello everyone! &#128075;</p><p>In this blog post we&#8217;re exploring the option of self hosting <a href="https://anubis.techaro.lol/">Anubis</a>.</p><p>Anubis is a software that defends and protects your services from AI Scrapers and LLMs. Since AI started to get popular more and more scrappers started appearing. They grab your data in order to use it to train their AIs while ignoring any rules such as robots.txt, licenses and intellectual property laws. Meta even <a href="https://www.tomshardware.com/tech-industry/artificial-intelligence/meta-staff-torrented-nearly-82tb-of-pirated-books-for-ai-training-court-records-reveal-copyright-violations">torrented</a> 82TB of books to train their AI.</p><p><a href="https://blog.nuculabs.dev/posts/2025/2025-05-18-anubis-protection-against-llms/">Read the full version here</a></p>]]></content:encoded></item><item><title><![CDATA[Accessing the host address from inside a container]]></title><description><![CDATA[Hello everyone!]]></description><link>https://newsletter.nuculabs.dev/p/accessing-the-host-address-from-inside</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/accessing-the-host-address-from-inside</guid><dc:creator><![CDATA[Denis Nuțiu]]></dc:creator><pubDate>Tue, 06 May 2025 18:24:43 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!P2KW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa141db1d-7883-43a6-bd91-fca68128c69a_600x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!P2KW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa141db1d-7883-43a6-bd91-fca68128c69a_600x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!P2KW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa141db1d-7883-43a6-bd91-fca68128c69a_600x600.png 424w, https://substackcdn.com/image/fetch/$s_!P2KW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa141db1d-7883-43a6-bd91-fca68128c69a_600x600.png 848w, https://substackcdn.com/image/fetch/$s_!P2KW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa141db1d-7883-43a6-bd91-fca68128c69a_600x600.png 1272w, https://substackcdn.com/image/fetch/$s_!P2KW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa141db1d-7883-43a6-bd91-fca68128c69a_600x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!P2KW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa141db1d-7883-43a6-bd91-fca68128c69a_600x600.png" width="600" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a141db1d-7883-43a6-bd91-fca68128c69a_600x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:600,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;blog thumbnail&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="blog thumbnail" title="blog thumbnail" srcset="https://substackcdn.com/image/fetch/$s_!P2KW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa141db1d-7883-43a6-bd91-fca68128c69a_600x600.png 424w, https://substackcdn.com/image/fetch/$s_!P2KW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa141db1d-7883-43a6-bd91-fca68128c69a_600x600.png 848w, https://substackcdn.com/image/fetch/$s_!P2KW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa141db1d-7883-43a6-bd91-fca68128c69a_600x600.png 1272w, https://substackcdn.com/image/fetch/$s_!P2KW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa141db1d-7883-43a6-bd91-fca68128c69a_600x600.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Hello everyone! &#128075;</p><p>This post is about accessing the host URL from inside a Podman (or Docker) container and how to avoid a mistake I made when setting up containers.</p><p>If you have a service running inside a container, and you would like to access the host URL you will have to use a special address called the host-gateway address. Other options include hard-coding the host ip address.</p><p>You can use any of the following URLs when you want to access the host from inside a container:</p><ul><li><p>host.containers.internal</p></li><li><p>host.docker.internal</p></li></ul><p><em>Source:</em></p><pre><code></code></pre><pre><code><code>The host-gateway address is also used by Podman to automatically add the 
host.containers.internal and host.docker.internal hostnames to /etc/hosts. 
</code></code></pre><ul><li><p><a href="https://docs.podman.io/en/v5.3.0/markdown/podman-build.1.html">https://docs.podman.io/en/v5.3.0/markdown/podman-build.1.html</a></p></li></ul><h3>What not to do</h3><p>Continue reading for free on my blog:</p><p><a href="https://blog.nuculabs.dev/posts/2025/2025-05-06-accessing-the-host-from-containers/">https://blog.nuculabs.dev/posts/2025/2025-05-06-accessing-the-host-from-containers/</a></p>]]></content:encoded></item><item><title><![CDATA[How to zip and unzip a directory in Go]]></title><description><![CDATA[Hello everyone!]]></description><link>https://newsletter.nuculabs.dev/p/how-to-zip-and-unzip-a-directory</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/how-to-zip-and-unzip-a-directory</guid><dc:creator><![CDATA[Denis Nuțiu]]></dc:creator><pubDate>Tue, 01 Apr 2025 18:33:49 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ec3de9c5-79f4-48bb-9bbe-4b157b3cc7d4_600x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello everyone! &#128075;</p><p>I wanted to write a short article about zipping and unzipping a directory in Golang.</p><p>The answer is quite simple ;) :</p><p><a href="https://blog.nuculabs.dev/posts/2025/2025-04-01-how-to-zip-and-unzip-a-directory-go/">https://blog.nuculabs.dev/posts/2025/2025-04-01-how-to-zip-and-unzip-a-directory-go/</a></p>]]></content:encoded></item><item><title><![CDATA[How to use RocksDB with Go]]></title><description><![CDATA[Hello everyone!]]></description><link>https://newsletter.nuculabs.dev/p/how-to-use-rocksdb-with-go</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/how-to-use-rocksdb-with-go</guid><dc:creator><![CDATA[Denis Nuțiu]]></dc:creator><pubDate>Sat, 15 Mar 2025 16:03:31 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!v2En!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6ad9982-578e-4b18-beaa-1af107e27d43_600x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!v2En!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6ad9982-578e-4b18-beaa-1af107e27d43_600x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!v2En!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6ad9982-578e-4b18-beaa-1af107e27d43_600x600.png 424w, https://substackcdn.com/image/fetch/$s_!v2En!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6ad9982-578e-4b18-beaa-1af107e27d43_600x600.png 848w, https://substackcdn.com/image/fetch/$s_!v2En!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6ad9982-578e-4b18-beaa-1af107e27d43_600x600.png 1272w, https://substackcdn.com/image/fetch/$s_!v2En!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6ad9982-578e-4b18-beaa-1af107e27d43_600x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!v2En!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6ad9982-578e-4b18-beaa-1af107e27d43_600x600.png" width="600" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a6ad9982-578e-4b18-beaa-1af107e27d43_600x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:600,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;blog thumbnail&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="blog thumbnail" title="blog thumbnail" srcset="https://substackcdn.com/image/fetch/$s_!v2En!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6ad9982-578e-4b18-beaa-1af107e27d43_600x600.png 424w, https://substackcdn.com/image/fetch/$s_!v2En!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6ad9982-578e-4b18-beaa-1af107e27d43_600x600.png 848w, https://substackcdn.com/image/fetch/$s_!v2En!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6ad9982-578e-4b18-beaa-1af107e27d43_600x600.png 1272w, https://substackcdn.com/image/fetch/$s_!v2En!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6ad9982-578e-4b18-beaa-1af107e27d43_600x600.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Hello everyone! &#128075;</p><p>I&#8217;ve had to work with RocksDB recently and in order to use it from Golang I had to jump a few hops before getting it to work properly.</p><p>This article will show you how to compile a Go program and link it with RocksDB using CGO. We<br>will use the <a href="https://github.com/linxGnu/grocksdb">grocksdb</a> library.</p><p></p><div><hr></div><p>Full article: <a href="https://blog.nuculabs.dev/posts/2025/2025-03-16-how-to-use-rocksdb-with-go/">https://blog.nuculabs.dev/posts/2025/2025-03-16-how-to-use-rocksdb-with-go/</a></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.nuculabs.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading NucuLabs! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Self Hosting a Calendar and Contacts server, Baïkal]]></title><description><![CDATA[Hello everyone!]]></description><link>https://newsletter.nuculabs.dev/p/self-hosting-a-calendar-and-contacts</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/self-hosting-a-calendar-and-contacts</guid><dc:creator><![CDATA[Denis Nuțiu]]></dc:creator><pubDate>Sun, 23 Feb 2025 19:39:39 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!y5HT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b72caee-4584-47aa-a0d8-335244d20580_600x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!y5HT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b72caee-4584-47aa-a0d8-335244d20580_600x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!y5HT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b72caee-4584-47aa-a0d8-335244d20580_600x600.png 424w, https://substackcdn.com/image/fetch/$s_!y5HT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b72caee-4584-47aa-a0d8-335244d20580_600x600.png 848w, https://substackcdn.com/image/fetch/$s_!y5HT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b72caee-4584-47aa-a0d8-335244d20580_600x600.png 1272w, https://substackcdn.com/image/fetch/$s_!y5HT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b72caee-4584-47aa-a0d8-335244d20580_600x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!y5HT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b72caee-4584-47aa-a0d8-335244d20580_600x600.png" width="600" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0b72caee-4584-47aa-a0d8-335244d20580_600x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:600,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;blog thumbnail&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="blog thumbnail" title="blog thumbnail" srcset="https://substackcdn.com/image/fetch/$s_!y5HT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b72caee-4584-47aa-a0d8-335244d20580_600x600.png 424w, https://substackcdn.com/image/fetch/$s_!y5HT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b72caee-4584-47aa-a0d8-335244d20580_600x600.png 848w, https://substackcdn.com/image/fetch/$s_!y5HT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b72caee-4584-47aa-a0d8-335244d20580_600x600.png 1272w, https://substackcdn.com/image/fetch/$s_!y5HT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b72caee-4584-47aa-a0d8-335244d20580_600x600.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Hello everyone! &#128075;</p><p>Welcome to another self-host article! This month we&#8217;re hosting a CalDAV and CardDAV server. Ba&#239;kal, the lightweight CalDAV+CardDAV server.</p><p>We&#8217;re going to host it in a Podman container on a Fedora server.</p><ul><li><p><a href="https://sabre.io/baikal/">https://sabre.io/baikal/</a></p></li></ul><p>Read more on my blog: <a href="https://blog.nuculabs.dev/posts/2025/2025-02-23-self-hosting-calendar-and-contacts/">nuculabs.dev</a></p>]]></content:encoded></item><item><title><![CDATA[Self Hosting PeerTube on Fedora Server]]></title><description><![CDATA[How to self host PeerTube on Fedora Server with Podman]]></description><link>https://newsletter.nuculabs.dev/p/self-hosting-peertube-on-fedora-server</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/self-hosting-peertube-on-fedora-server</guid><dc:creator><![CDATA[Denis Nuțiu]]></dc:creator><pubDate>Sun, 26 Jan 2025 09:35:22 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Izco!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb164ab-ca45-4746-8c38-34a9f31dcff2_600x600.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Izco!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb164ab-ca45-4746-8c38-34a9f31dcff2_600x600.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Izco!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb164ab-ca45-4746-8c38-34a9f31dcff2_600x600.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Izco!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb164ab-ca45-4746-8c38-34a9f31dcff2_600x600.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Izco!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb164ab-ca45-4746-8c38-34a9f31dcff2_600x600.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Izco!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb164ab-ca45-4746-8c38-34a9f31dcff2_600x600.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Izco!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb164ab-ca45-4746-8c38-34a9f31dcff2_600x600.jpeg" width="600" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7fb164ab-ca45-4746-8c38-34a9f31dcff2_600x600.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:600,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;thumbnail&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="thumbnail" title="thumbnail" srcset="https://substackcdn.com/image/fetch/$s_!Izco!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb164ab-ca45-4746-8c38-34a9f31dcff2_600x600.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Izco!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb164ab-ca45-4746-8c38-34a9f31dcff2_600x600.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Izco!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb164ab-ca45-4746-8c38-34a9f31dcff2_600x600.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Izco!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fb164ab-ca45-4746-8c38-34a9f31dcff2_600x600.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Hello everyone! &#128075;</p><p>This is my first blog post in 2025, and I&#8217;d like to talk about my experience in self-hosting <a href="https://joinpeertube.org/">PeerTube</a> in <a href="https://fedoraproject.org/en/server/">Fedora Server</a> and how to do it yourself.</p><p>In addition to this I&#8217;ve also added a new category on my blog: <strong><a href="https://blog.nuculabs.dev/categories/self-hosting/">Self Hosting</a></strong></p><p>I also want to celebrate I have 15k unique visitors per month (according to CloudFlare), however most of them are port scans, exploits and LLM-bots scraping everything and ignoring robots.txt. &#128517;</p><div><hr></div><h1>Why PeerTube &#128249;</h1><p>PeerTube is a federated video sharing platform. You can host your own instance or create an account on a popular instance like:</p><ul><li></li></ul><p>https://peertube.tv/</p><ul><li></li></ul><p>https://tilvids.com/</p><ul><li><p><a href="https://instances.joinpeertube.org/instances">https://instances.joinpeertube.org/instances</a></p></li></ul><p>The federation allows every instance to have its own rules and also allow users from different instances to interact with each other.</p><p>Federation also helps prevent monopoly and censorship.</p><p>My YouTube channel is small and my goal is not to become an influencer. I just like making videos from time to time and own my data.</p><h1>Fedora Server &#128039;</h1><p>Before Fedora Server I had PeerTube on a Raspberry Pi5 with 4GB of RAM and then a PC running Ubuntu Server. I&#8217;ve uninstalled Ubuntu Server and installed Fedora Server.</p><p>The PI5 didn&#8217;t work that well in encoding videos department. A simple 5 minutes videos could take up to 20minutes to be encoded in 1080p, 720p and 480p resolutions. There was also a limited number of simultaneous playbacks supported.</p><p>I&#8217;m sticking with Fedora Server thanks to the <a href="https://cockpit-project.org/">Cockpit UI</a>, Podman support and virtual machine support. Having virtual machines run on a separated PC and connecting to them in the browser is a huge benefit for my use cases.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!j49v!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb16dfb4d-1d20-4c8c-aaac-a5b28c46cb0d_2556x949.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!j49v!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb16dfb4d-1d20-4c8c-aaac-a5b28c46cb0d_2556x949.jpeg 424w, https://substackcdn.com/image/fetch/$s_!j49v!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb16dfb4d-1d20-4c8c-aaac-a5b28c46cb0d_2556x949.jpeg 848w, https://substackcdn.com/image/fetch/$s_!j49v!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb16dfb4d-1d20-4c8c-aaac-a5b28c46cb0d_2556x949.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!j49v!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb16dfb4d-1d20-4c8c-aaac-a5b28c46cb0d_2556x949.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!j49v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb16dfb4d-1d20-4c8c-aaac-a5b28c46cb0d_2556x949.jpeg" width="1456" height="541" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b16dfb4d-1d20-4c8c-aaac-a5b28c46cb0d_2556x949.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:541,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;cockpit ui screenshot&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="cockpit ui screenshot" title="cockpit ui screenshot" srcset="https://substackcdn.com/image/fetch/$s_!j49v!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb16dfb4d-1d20-4c8c-aaac-a5b28c46cb0d_2556x949.jpeg 424w, https://substackcdn.com/image/fetch/$s_!j49v!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb16dfb4d-1d20-4c8c-aaac-a5b28c46cb0d_2556x949.jpeg 848w, https://substackcdn.com/image/fetch/$s_!j49v!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb16dfb4d-1d20-4c8c-aaac-a5b28c46cb0d_2556x949.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!j49v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb16dfb4d-1d20-4c8c-aaac-a5b28c46cb0d_2556x949.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h1>Prerequisites</h1><p>To keep this article short, if you want to host PeerTube on a Fedora Server with Podman you&#8217;ll need these prerequisites:</p><ol><li><p>Self Host PeerTube on your PC via Docker</p></li></ol><p>Get started with Docker and Docker Compose and learn how to self-host PeerTube on your PC by reading this guide:</p><ul><li><p><a href="https://docs.joinpeertube.org/install/docker">https://docs.joinpeertube.org/install/docker</a></p></li></ul><ol start="2"><li><p>Have Fedora Server, Podman and a working SSH connection to the server.</p></li></ol><p></p><p>Continue reading on my blog: <a href="https://blog.nuculabs.dev/posts/2025/2025-01-25-self-hosting-peertube/">https://blog.nuculabs.dev/posts/2025/2025-01-25-self-hosting-peertube/</a></p>]]></content:encoded></item><item><title><![CDATA[How to install Redis on Synology NAS (Container Manager)]]></title><description><![CDATA[Hello everyone!]]></description><link>https://newsletter.nuculabs.dev/p/how-to-install-redis-on-synology</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/how-to-install-redis-on-synology</guid><dc:creator><![CDATA[Denis Nuțiu]]></dc:creator><pubDate>Mon, 16 Dec 2024 16:28:18 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!4tYw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F528a3d13-d245-4df6-a153-4590a2c8abab_1270x720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4tYw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F528a3d13-d245-4df6-a153-4590a2c8abab_1270x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4tYw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F528a3d13-d245-4df6-a153-4590a2c8abab_1270x720.png 424w, https://substackcdn.com/image/fetch/$s_!4tYw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F528a3d13-d245-4df6-a153-4590a2c8abab_1270x720.png 848w, https://substackcdn.com/image/fetch/$s_!4tYw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F528a3d13-d245-4df6-a153-4590a2c8abab_1270x720.png 1272w, https://substackcdn.com/image/fetch/$s_!4tYw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F528a3d13-d245-4df6-a153-4590a2c8abab_1270x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4tYw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F528a3d13-d245-4df6-a153-4590a2c8abab_1270x720.png" width="1270" height="720" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/528a3d13-d245-4df6-a153-4590a2c8abab_1270x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:720,&quot;width&quot;:1270,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1283164,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4tYw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F528a3d13-d245-4df6-a153-4590a2c8abab_1270x720.png 424w, https://substackcdn.com/image/fetch/$s_!4tYw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F528a3d13-d245-4df6-a153-4590a2c8abab_1270x720.png 848w, https://substackcdn.com/image/fetch/$s_!4tYw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F528a3d13-d245-4df6-a153-4590a2c8abab_1270x720.png 1272w, https://substackcdn.com/image/fetch/$s_!4tYw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F528a3d13-d245-4df6-a153-4590a2c8abab_1270x720.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Hello everyone! &#128075;</p><p>I run a small set of containers on my NAS at Home to monitor different weather and run automations. Because of this I wanted to install Redis on my NAS as well.</p><p>Redis is a key value database that has a lot of extra modules such as:</p><ul><li><p>Redis Search: A query and indexing engine for Redis, providing secondary indexing, full-text search, vector similarity search and aggregations.</p></li><li><p>Redis JSON: A NO-SQL Document Database</p></li><li><p>Redis Graph: A graph database.</p></li><li><p>Redis TimeSeries: A time series database.</p></li><li><p>Redis Bloom: Bloom and Cuckoo filters implementation.</p></li><li><p>Redis Streams: An append only Log</p></li></ul><p>These functionalities save me a lot of time and improve my software running inside my home. Redis is also a simple service to deploy and maintain.</p><p>So I made a quick tutorial on how to install <a href="https://redis.io/">Redis</a> on a <a href="https://www.synology.com/en-global">Synology NAS</a> using the Container Manager.</p><div id="youtube2-e61r5arEGJU" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;e61r5arEGJU&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/e61r5arEGJU?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div>]]></content:encoded></item><item><title><![CDATA[How to install DaVinci Resolve on Linux (Fedora Edition)]]></title><description><![CDATA[A quick guide to get DaVinci Resolve working in Fedora]]></description><link>https://newsletter.nuculabs.dev/p/how-to-install-davinci-resolve-on</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/how-to-install-davinci-resolve-on</guid><dc:creator><![CDATA[Denis Nuțiu]]></dc:creator><pubDate>Sat, 30 Nov 2024 11:15:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!MN2T!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d276218-ccde-4c2c-872e-5c2cfbdf85bd_1722x1022.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello everyone! &#128075;</p><p>In this article you will learn how to install DaVinci Resolve Free and Studio editions on Fedora 40 and Fedora 41.</p><p>The first step is to download DaVinci Resolve from the official website, either with:</p><ul><li><p><a href="https://www.blackmagicdesign.com/products/davinciresolve/">Free Edition</a></p></li><li><p><a href="https://www.blackmagicdesign.com/support/family/davinci-resolve-and-fusion">Studio Edition</a></p></li></ul><p>The next step is to download and install <a href="https://github.com/H3rz3n/davinci-helper">DaVinci Helper</a>.</p><pre><code><code>sudo dnf copr enable -y herzen/davinci-helper
sudo dnf install -y davinci-helper
</code></code></pre><p>Once you&#8217;ve installed DaVinci Helper, the following steps are simple. Simply circle through the tabs to on the left and select the DaVinci Resolve zip file when prompted.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MN2T!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d276218-ccde-4c2c-872e-5c2cfbdf85bd_1722x1022.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MN2T!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d276218-ccde-4c2c-872e-5c2cfbdf85bd_1722x1022.png 424w, https://substackcdn.com/image/fetch/$s_!MN2T!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d276218-ccde-4c2c-872e-5c2cfbdf85bd_1722x1022.png 848w, https://substackcdn.com/image/fetch/$s_!MN2T!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d276218-ccde-4c2c-872e-5c2cfbdf85bd_1722x1022.png 1272w, https://substackcdn.com/image/fetch/$s_!MN2T!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d276218-ccde-4c2c-872e-5c2cfbdf85bd_1722x1022.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MN2T!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d276218-ccde-4c2c-872e-5c2cfbdf85bd_1722x1022.png" width="1456" height="864" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0d276218-ccde-4c2c-872e-5c2cfbdf85bd_1722x1022.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:864,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;DaVinci Helper&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="DaVinci Helper" title="DaVinci Helper" srcset="https://substackcdn.com/image/fetch/$s_!MN2T!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d276218-ccde-4c2c-872e-5c2cfbdf85bd_1722x1022.png 424w, https://substackcdn.com/image/fetch/$s_!MN2T!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d276218-ccde-4c2c-872e-5c2cfbdf85bd_1722x1022.png 848w, https://substackcdn.com/image/fetch/$s_!MN2T!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d276218-ccde-4c2c-872e-5c2cfbdf85bd_1722x1022.png 1272w, https://substackcdn.com/image/fetch/$s_!MN2T!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d276218-ccde-4c2c-872e-5c2cfbdf85bd_1722x1022.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>After all the steps are done, you can open DaVinci Resolve:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!STcF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1753ad81-d000-4aca-a12c-c5adc3fda3b3_2560x1440.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!STcF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1753ad81-d000-4aca-a12c-c5adc3fda3b3_2560x1440.png 424w, https://substackcdn.com/image/fetch/$s_!STcF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1753ad81-d000-4aca-a12c-c5adc3fda3b3_2560x1440.png 848w, https://substackcdn.com/image/fetch/$s_!STcF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1753ad81-d000-4aca-a12c-c5adc3fda3b3_2560x1440.png 1272w, https://substackcdn.com/image/fetch/$s_!STcF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1753ad81-d000-4aca-a12c-c5adc3fda3b3_2560x1440.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!STcF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1753ad81-d000-4aca-a12c-c5adc3fda3b3_2560x1440.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1753ad81-d000-4aca-a12c-c5adc3fda3b3_2560x1440.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;DaVinci Resolve&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="DaVinci Resolve" title="DaVinci Resolve" srcset="https://substackcdn.com/image/fetch/$s_!STcF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1753ad81-d000-4aca-a12c-c5adc3fda3b3_2560x1440.png 424w, https://substackcdn.com/image/fetch/$s_!STcF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1753ad81-d000-4aca-a12c-c5adc3fda3b3_2560x1440.png 848w, https://substackcdn.com/image/fetch/$s_!STcF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1753ad81-d000-4aca-a12c-c5adc3fda3b3_2560x1440.png 1272w, https://substackcdn.com/image/fetch/$s_!STcF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1753ad81-d000-4aca-a12c-c5adc3fda3b3_2560x1440.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Audio Interface</h2><p>I use a Rode AI-1 audio interface and I wrote an article about it how to get it working properly on Linux.</p><p>You can read it:</p><ul><li><p><a href="https://blog.nuculabs.dev/posts/2022/2022-02-21-how-to-make-rode-audio-interface-to-work-on-linux/">How to make R&#216;DE audio interface work on Linux</a>.</p></li></ul><p>Thank you reading! &#128039;</p>]]></content:encoded></item><item><title><![CDATA[The Go *Options pattern]]></title><description><![CDATA[Explore the Options design pattern in Golang]]></description><link>https://newsletter.nuculabs.dev/p/the-go-options-pattern</link><guid isPermaLink="false">https://newsletter.nuculabs.dev/p/the-go-options-pattern</guid><dc:creator><![CDATA[Denis Nuțiu]]></dc:creator><pubDate>Wed, 27 Nov 2024 16:48:43 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/9cdcdd3c-0c15-4d77-b780-2ab933c877d3_600x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lKen!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dd3a472-63d7-4f94-83ca-3160d3b7af24_600x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lKen!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dd3a472-63d7-4f94-83ca-3160d3b7af24_600x600.png 424w, https://substackcdn.com/image/fetch/$s_!lKen!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dd3a472-63d7-4f94-83ca-3160d3b7af24_600x600.png 848w, https://substackcdn.com/image/fetch/$s_!lKen!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dd3a472-63d7-4f94-83ca-3160d3b7af24_600x600.png 1272w, https://substackcdn.com/image/fetch/$s_!lKen!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dd3a472-63d7-4f94-83ca-3160d3b7af24_600x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lKen!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dd3a472-63d7-4f94-83ca-3160d3b7af24_600x600.png" width="600" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3dd3a472-63d7-4f94-83ca-3160d3b7af24_600x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:600,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:115601,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lKen!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dd3a472-63d7-4f94-83ca-3160d3b7af24_600x600.png 424w, https://substackcdn.com/image/fetch/$s_!lKen!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dd3a472-63d7-4f94-83ca-3160d3b7af24_600x600.png 848w, https://substackcdn.com/image/fetch/$s_!lKen!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dd3a472-63d7-4f94-83ca-3160d3b7af24_600x600.png 1272w, https://substackcdn.com/image/fetch/$s_!lKen!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3dd3a472-63d7-4f94-83ca-3160d3b7af24_600x600.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h1>Introduction</h1><p>Hello everyone! &#128075;</p><p>In this article I&#8217;ll present you the options pattern in Golang. The pattern is useful when you want to create a function that takes different parameters as an option.</p><h1>Code Study: Building a rocket</h1><p>The following code defines a Rocket struct with 3 fields and a NewRocket function which builds an instance of the Rocket.</p><pre><code><code>// Rocket models a rocket.
type Rocket struct {
&#9;name         string
&#9;nose         Nose
&#9;fuelCapacity int
}

// NewRocket returns a new rocket instance.
func NewRocket(name string, nose Nose, fuelCapacity int) Rocket {
&#9;return Rocket{
&#9;&#9;name:         name,
&#9;&#9;nose:         nose,
&#9;&#9;fuelCapacity: fuelCapacity,
&#9;}
}
</code></code></pre><p>To use the NewRocket function one would have to write something like this:</p><pre><code><code>func main() {
&#9;rocket := NewRocket("Techno", PointyNose, 100)

&#9;println(rocket.String())
}
</code></code></pre><p>This is straightforward, but what if we&#8217;d want the name of the rocket, nose and fuel capacity to be optional parameters?</p><p>We could rewrite the <code>NewRocket</code> function to take pointers instead of literals and if we do so we could build a default rocket with:</p><pre><code><code>func main() {
&#9;rocket := NewRocket(nil, nil, nil)

&#9;println(rocket.String())
}
</code></code></pre><p>This is of course not very readable, my eyes start watering only by looking at this code.</p><p>When we want to modify the Rocket struct and add a new field, that is also instantiated we&#8217;d need to remember the position in our code and modify all the NewRocket function usages.</p><p>So far not using the options pattern for instantiating a Rocket has the following disadvantages:</p><ul><li><p>Ugly code.</p></li><li><p>Hard to modify code.</p></li><li><p>Need to refactor every usage of the function.</p></li></ul><h1>The Options Pattern</h1><p>The Options pattern is straightforward to implement and there are many variations, in this article we&#8217;ll explore one simple variation:</p><p>First, define the RocketOptions struct that takes the same configurable fields as the Rocket struct and defines functions for instantiating them.</p><p>The fields are defined as pointers instead of literals because we want to treat the <code>nil</code> value as not user specified.</p><pre><code><code>func NewRocketOptions() *RocketOptions {
&#9;return &amp;RocketOptions{
&#9;&#9;name:         nil,
&#9;&#9;nose:         nil,
&#9;&#9;fuelCapacity: nil,
&#9;}
}

func (o *RocketOptions) WithName(name string) *RocketOptions {
&#9;o.name = &amp;name
&#9;return o
}

func (o *RocketOptions) WithNose(nose Nose) *RocketOptions {
&#9;o.nose = &amp;nose
&#9;return o
}

func (o *RocketOptions) WithFuelCapacity(capacity int) *RocketOptions {
&#9;o.fuelCapacity = &amp;capacity
&#9;return o
}
</code></code></pre><p>Next, modify the NewRocket function to use the RocketOptions struct like so:</p><pre><code><code>// NewRocket returns a new rocket instance.
func NewRocket(options *RocketOptions) Rocket {
&#9;var name string = "Unknown"
&#9;if options.name != nil {
&#9;&#9;name = *options.name
&#9;}

&#9;var nose Nose = PointyNose
&#9;if options.nose != nil {
&#9;&#9;nose = *options.nose
&#9;}

&#9;var fuelCapacity int = 100
&#9;if options.fuelCapacity != nil {
&#9;&#9;fuelCapacity = *options.fuelCapacity
&#9;}

&#9;return Rocket{
&#9;&#9;name:         name,
&#9;&#9;nose:         nose,
&#9;&#9;fuelCapacity: fuelCapacity,
&#9;}
}
</code></code></pre><p>The NewRocket now provides default values for the <code>nil</code> options and when they are specified it uses the user provided option value.</p><h2>Other Variation</h2><p>Another variation can be implemented like this:</p><pre><code><code>type Nose string

const PointyNose Nose = "POINTY"
const RoundNose Nose = "ROUND"

type rocketOptions struct {
&#9;name         string
&#9;nose         Nose
&#9;fuelCapacity int
}

type RocketOption func(options *rocketOptions)

// Rocket models a rocket.
type Rocket struct {
&#9;options rocketOptions
}

func (r *Rocket) String() string {
&#9;return fmt.Sprintf("%s rocket with %s nose and fuel capacity of %d", r.options.name, r.options.nose, r.options.fuelCapacity)
}

// NewRocket returns a new rocket instance.
func NewRocket(options ...RocketOption) Rocket {
&#9;rocketOptions := rocketOptions{
&#9;&#9;name:         "Unknown",
&#9;&#9;nose:         PointyNose,
&#9;&#9;fuelCapacity: 100,
&#9;}

&#9;for _, option := range options {
&#9;&#9;option(&amp;rocketOptions)
&#9;}

&#9;return Rocket{
&#9;&#9;options: rocketOptions,
&#9;}
}

func NameOption(name string) RocketOption {
&#9;return func(options *rocketOptions) {
&#9;&#9;options.name = name
&#9;}
}

func main() {
&#9;rocket := NewRocket()
&#9;println(rocket.String())

&#9;otherRocket := NewRocket(NameOption("Cool"))
&#9;println(otherRocket.String())
}
</code></code></pre><h1>Conclusion</h1><p>We&#8217;ve explored how the Options pattern can significantly enhance the design and usability of functions in Golang, particularly when dealing with multiple optional parameters.</p><p>Thank you for reading!</p>]]></content:encoded></item></channel></rss>