<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Miklos Halasz</title>
    <description>The latest articles on Forem by Miklos Halasz (@kubenetic).</description>
    <link>https://forem.com/kubenetic</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2848121%2F9a125962-1a43-4279-ad02-947a9016b1f2.png</url>
      <title>Forem: Miklos Halasz</title>
      <link>https://forem.com/kubenetic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/kubenetic"/>
    <language>en</language>
    <item>
      <title>Why My Laptop Drained Overnight While Docked (and How a GRUB Change Fixed It)</title>
      <dc:creator>Miklos Halasz</dc:creator>
      <pubDate>Mon, 09 Feb 2026 07:40:53 +0000</pubDate>
      <link>https://forem.com/kubenetic/why-my-laptop-drained-overnight-while-docked-and-how-a-grub-change-fixed-it-32ne</link>
      <guid>https://forem.com/kubenetic/why-my-laptop-drained-overnight-while-docked-and-how-a-grub-change-fixed-it-32ne</guid>
      <description>&lt;p&gt;I experienced a confusing and frustrating issue while using a Dell WD19 USB-C dock on Ubuntu. In the evening, everything worked as expected: the laptop was charging properly, external displays were active, and the wired network was connected. The next morning the laptop battery was completely empty — even though it had remained connected to the dock the entire time.&lt;/p&gt;

&lt;p&gt;Even more confusing, this problem did not only occur overnight. Sometimes, after the laptop woke up from sleep, the dock would continue to provide display output and network connectivity, yet would no longer supply any power. In that state, the system would quietly run on battery while appearing to be fully docked.&lt;/p&gt;

&lt;p&gt;At first glance, this behavior seems impossible. A dock that can drive displays and provide Ethernet should also provide power. But with modern USB-C docks and Linux power management, this assumption is unfortunately not always true.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual problem
&lt;/h2&gt;

&lt;p&gt;The root of the issue lies in how Linux handles power management during idle and suspend states, combined with how USB-C Power Delivery (PD) is negotiated.&lt;/p&gt;

&lt;p&gt;While the laptop was idle overnight, it entered a low-power state known as &lt;code&gt;s2idle&lt;/code&gt; (also called Modern Standby). In this state, the system is not fully asleep. The CPU is mostly idle, but many components — including USB controllers — remain logically active. This allows for fast wake-up times, but it also means devices are expected to manage their own low-power behavior correctly.&lt;/p&gt;

&lt;p&gt;Linux, trying to reduce power usage further, enables USB autosuspend by default. Autosuspend allows the kernel to put USB devices into a low-power state when they appear idle. This works well for simple peripherals, but the WD19 dock is not a simple device. Internally, it contains a USB hub, a USB-C Power Delivery controller, a DisplayPort alt-mode controller, and a network adapter.&lt;/p&gt;

&lt;p&gt;During the night, USB autosuspend put part of the dock into a suspended state. When that happened, the dock’s Power Delivery controller dropped the negotiated power contract. From the laptop’s perspective, external power simply disappeared. The system silently switched to battery power and continued running in &lt;code&gt;s2idle&lt;/code&gt; until the battery was fully drained. Because the USB connection itself was never fully reset, Linux never retried power negotiation, and no warning was shown.&lt;/p&gt;

&lt;p&gt;This is why the system looked fine before sleep, yet was dead in the morning.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: disabling USB autosuspend
&lt;/h2&gt;

&lt;p&gt;The fix was to disable USB autosuspend globally by altering the GRUB configuration and adding the following kernel parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;usbcore.autosuspend&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After updating GRUB and rebooting, the kernel no longer attempts to automatically suspend USB devices. As a result, the dock’s Power Delivery controller remains fully powered and active, even while the system is idle or suspended in s2idle. The power contract between the dock and the laptop stays intact, and the laptop continues charging throughout the night.&lt;/p&gt;

&lt;p&gt;This change does not meaningfully increase power consumption. The additional power usage is negligible compared to what the dock and external monitors already consume, and system stability is significantly improved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where s2idle and S3 fit into this
&lt;/h2&gt;

&lt;p&gt;To understand why this fix is effective, it helps to distinguish between the two common Linux sleep states.&lt;/p&gt;

&lt;p&gt;In s2idle, the system is only partially asleep. Devices remain logically connected, and power management relies heavily on drivers behaving correctly. This is where USB-C docks often fail, because power delivery can be dropped without a full hardware reset.&lt;/p&gt;

&lt;p&gt;In contrast, S3 sleep (also called deep sleep or suspend to RAM) fully powers down the CPU and most devices, including USB controllers. When the system wakes up, everything — including USB-C power delivery — is reinitialized from scratch. This makes S3 far more robust, especially when docks are involved.&lt;/p&gt;

&lt;p&gt;Disabling USB autosuspend makes s2idle behave more safely by preventing the dock from entering a half-suspended state where power delivery can be lost. In many cases, this alone is sufficient and avoids the need to force S3 sleep.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this works reliably
&lt;/h2&gt;

&lt;p&gt;By altering GRUB and disabling USB autosuspend, the system no longer allows the dock’s internal controllers to power down independently during idle. This prevents the silent loss of USB-C power delivery that caused the overnight battery drain. The dock remains fully operational, the charging contract stays active, and the laptop behaves as users naturally expect: if it is docked, it stays charged.&lt;/p&gt;

&lt;p&gt;In short, the problem was not a faulty battery or charger, but a subtle interaction between modern Linux power states and USB-C dock firmware. A single kernel parameter was enough to restore predictable and reliable behavior.&lt;/p&gt;

</description>
      <category>ubuntu</category>
      <category>linux</category>
      <category>grub</category>
      <category>dell</category>
    </item>
    <item>
      <title>Building a Reliable NAS and Integrating UPS Monitoring with TrueNAS and Home Assistant</title>
      <dc:creator>Miklos Halasz</dc:creator>
      <pubDate>Tue, 07 Oct 2025 08:18:33 +0000</pubDate>
      <link>https://forem.com/kubenetic/building-a-reliable-nas-and-integrating-ups-monitoring-with-truenas-and-home-assistant-1j7j</link>
      <guid>https://forem.com/kubenetic/building-a-reliable-nas-and-integrating-ups-monitoring-with-truenas-and-home-assistant-1j7j</guid>
      <description>&lt;p&gt;I built a NAS from spare parts — an &lt;strong&gt;MSI X99A Raider motherboard&lt;/strong&gt;, a &lt;strong&gt;6-core Intel Xeon CPU&lt;/strong&gt;, &lt;strong&gt;128 GB ECC DDR4 RAM&lt;/strong&gt;, a &lt;strong&gt;10G NIC&lt;/strong&gt;, and an &lt;strong&gt;HBA card&lt;/strong&gt; for the HDDs. The HDDs are configured in &lt;strong&gt;ZFS RAID-Z2&lt;/strong&gt;, allowing the system to tolerate up to two simultaneous disk failures. The operating system is installed on two SSDs configured in &lt;strong&gt;RAID10&lt;/strong&gt; for redundancy.&lt;/p&gt;

&lt;p&gt;Is it overkill for a homelab? Maybe. But I want to store my documents, family photos, and other personal data &lt;strong&gt;reliably&lt;/strong&gt;, without relying on any cloud provider.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why TrueNAS?
&lt;/h2&gt;

&lt;p&gt;I think &lt;strong&gt;TrueNAS&lt;/strong&gt; is an excellent choice. It offers an intuitive web interface, straightforward setup, and clear configuration options — making it ideal for homelab environments as well.&lt;/p&gt;

&lt;p&gt;Recently, I purchased an &lt;strong&gt;APC UPS&lt;/strong&gt;. While &lt;strong&gt;ZFS (RAID-Z2)&lt;/strong&gt; provides strong data integrity and fault tolerance, it doesn’t protect against sudden power loss. I didn’t want to &lt;em&gt;leave it to chance&lt;/em&gt;, so I integrated the UPS to ensure the NAS can &lt;strong&gt;gracefully shut down&lt;/strong&gt; when the battery level is low.&lt;/p&gt;

&lt;p&gt;To achieve this, I installed &lt;strong&gt;NUT (Network UPS Tools)&lt;/strong&gt; on one of my &lt;strong&gt;Raspberry Pis&lt;/strong&gt;, connecting the UPS via USB. The Raspberry Pi acts as the UPS server and communicates with the TrueNAS system over the network.&lt;/p&gt;




&lt;h2&gt;
  
  
  Configuring UPS Integration in TrueNAS
&lt;/h2&gt;

&lt;p&gt;After setting up NUT on the Raspberry Pi, I logged into the TrueNAS admin interface and navigated to&lt;br&gt;
&lt;strong&gt;System → Services → UPS (pencil icon)&lt;/strong&gt; to configure the UPS client.&lt;/p&gt;

&lt;p&gt;In the configuration form, I entered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;identifier&lt;/strong&gt; for the UPS connection&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;IP address&lt;/strong&gt;, &lt;strong&gt;port&lt;/strong&gt;, and &lt;strong&gt;credentials&lt;/strong&gt; for the NUT observer user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once saved, I activated the &lt;strong&gt;UPS service&lt;/strong&gt; and enabled &lt;strong&gt;auto-start&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This setup allows the &lt;strong&gt;NUT client&lt;/strong&gt; on the TrueNAS server to monitor the UPS and automatically shut down the NAS when the UPS battery level becomes critically low.&lt;/p&gt;


&lt;h2&gt;
  
  
  Monitor UPS with Home Assistant
&lt;/h2&gt;

&lt;p&gt;TrueNAS can run containerized &lt;strong&gt;applications&lt;/strong&gt; using Docker Compose under the hood.&lt;br&gt;
Some applications are available directly from the TrueNAS catalog (and only require minimal configuration), but you can also deploy &lt;strong&gt;custom “freestyle” applications&lt;/strong&gt;, where you define everything manually.&lt;/p&gt;

&lt;p&gt;If you install a catalog app but need more granular control, you can &lt;strong&gt;convert it to a freestyle application&lt;/strong&gt;, giving you direct access to the underlying Docker Compose configuration.&lt;/p&gt;


&lt;h3&gt;
  
  
  Traefik
&lt;/h3&gt;

&lt;p&gt;Because I already had multiple applications running on my TrueNAS system, I wanted to route their traffic based on &lt;strong&gt;hostnames&lt;/strong&gt; rather than remembering port numbers. To achieve this, I installed &lt;strong&gt;Traefik&lt;/strong&gt; as a reverse proxy using the following configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;traefik&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--api.insecure=true'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--providers.docker=true'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--providers.docker.network=proxy'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--entrypoints.web.address=:80'&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;TZ&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Europe/Brussels&lt;/span&gt;
    &lt;span class="na"&gt;group_add&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;568&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxy.homelab.arpa&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.io/library/traefik:v3&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;proxy&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ingress&lt;/span&gt;
        &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp&lt;/span&gt;
        &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ingress&lt;/span&gt;
        &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp&lt;/span&gt;
        &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8088&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ingress&lt;/span&gt;
        &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp&lt;/span&gt;
        &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8443&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
    &lt;span class="na"&gt;pull_policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bind&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/run/docker.sock&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/run/docker.sock&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before installation, I created the &lt;strong&gt;Docker bridge network&lt;/strong&gt; for Traefik to communicate with other containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker network create &lt;span class="nt"&gt;--driver&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bridge proxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then attached the Traefik container to this network.&lt;br&gt;
For simplicity, I’m currently running without TLS, so I enabled insecure API access and allowed Traefik’s &lt;strong&gt;Docker provider&lt;/strong&gt; to automatically discover containers. The Docker socket is mounted as a bind volume, giving Traefik visibility into running containers.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;pull_policy: always&lt;/code&gt; ensures the container automatically updates to the latest Traefik v3 image on restart.&lt;/p&gt;


&lt;h3&gt;
  
  
  Home Assistant
&lt;/h3&gt;

&lt;p&gt;Below is an excerpt from the &lt;strong&gt;Home Assistant&lt;/strong&gt; Docker Compose configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;homeassistant&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
  &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;home-assistant&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.enable=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.homeassistant.entrypoints=web&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.homeassistant.rule=Host(`homeassistant.homelab.arpa`)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.homeassistant.service=homa-svc&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.services.homa-svc.loadbalancer.server.port=30103&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;proxy&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;homeassistant&lt;/span&gt;

  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
    &lt;span class="s"&gt;networks&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;homeassistant&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I installed Home Assistant from the TrueNAS catalog and then &lt;strong&gt;converted it to a custom app&lt;/strong&gt; (via the three-dot menu → &lt;em&gt;Convert to custom app&lt;/em&gt;). This allowed me to edit its Docker Compose configuration directly, as shown above.&lt;/p&gt;

&lt;p&gt;I attached the &lt;strong&gt;proxy&lt;/strong&gt; network so Traefik could route traffic to the container, and created an additional &lt;strong&gt;homeassistant&lt;/strong&gt; network for internal communication with the PostgreSQL database. This separation keeps database traffic isolated from the proxy network.&lt;/p&gt;

&lt;p&gt;Next, I added &lt;strong&gt;Traefik labels&lt;/strong&gt; to enable routing based on hostname.&lt;/p&gt;

&lt;p&gt;However, after setting everything up, accessing the Home Assistant dashboard returned an &lt;strong&gt;HTTP 400 (Bad Request)&lt;/strong&gt; error. The fix was documented here:&lt;br&gt;
&lt;a href="https://community.home-assistant.io/t/home-assistant-400-bad-request-docker-proxy-solution/322163" rel="noopener noreferrer"&gt;Home Assistant (400 Bad Request) Docker + Proxy – Solution&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To resolve the issue, I updated Home Assistant’s configuration file located at&lt;br&gt;
&lt;code&gt;/mnt/.ix-apps/app_mounts/home-assistant/config/configuration.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;use_x_forwarded_for&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;trusted_proxies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;Subnet of the proxy Docker network, e.g. 172.27.0.0/24 or Traefik’s IP&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After restarting the Home Assistant service, the dashboard loaded correctly.&lt;/p&gt;




&lt;h3&gt;
  
  
  Monitoring
&lt;/h3&gt;

&lt;p&gt;With everything connected, I created a &lt;strong&gt;Home Assistant card&lt;/strong&gt; to display the UPS status.&lt;br&gt;
Here’s the YAML configuration for the dashboard card:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vertical-stack&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Server Room Rack UPS&lt;/span&gt;
&lt;span class="na"&gt;cards&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;history-graph&lt;/span&gt;
    &lt;span class="na"&gt;entities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Status&lt;/span&gt;
        &lt;span class="na"&gt;entity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sensor.rack_ups_1_status&lt;/span&gt;
    &lt;span class="na"&gt;hours_to_show&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;horizontal-stack&lt;/span&gt;
    &lt;span class="na"&gt;cards&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gauge&lt;/span&gt;
        &lt;span class="na"&gt;entity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sensor.rack_ups_1_load&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UPS Load&lt;/span&gt;
        &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;green&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
          &lt;span class="na"&gt;yellow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;70&lt;/span&gt;
          &lt;span class="na"&gt;red&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;90&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gauge&lt;/span&gt;
        &lt;span class="na"&gt;entity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sensor.rack_ups_1_battery_charge&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Battery Charge&lt;/span&gt;
        &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;green&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;
          &lt;span class="na"&gt;yellow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
          &lt;span class="na"&gt;red&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This dashboard displays the &lt;strong&gt;UPS status&lt;/strong&gt; (e.g., on battery, charging, etc.), &lt;strong&gt;current battery level&lt;/strong&gt;, and &lt;strong&gt;load percentage&lt;/strong&gt; in real time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqoalt3cdov9xdugrtx8b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqoalt3cdov9xdugrtx8b.png" alt="UPS Monitoring Dashboard" width="483" height="306"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt;&lt;br&gt;
This setup provides a &lt;strong&gt;robust and self-contained NAS system&lt;/strong&gt; with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fault-tolerant ZFS RAID-Z2 storage&lt;/li&gt;
&lt;li&gt;Power-loss protection through UPS integration&lt;/li&gt;
&lt;li&gt;Automated monitoring and alerting via Home Assistant&lt;/li&gt;
&lt;li&gt;Flexible hostname-based routing through Traefik&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s a powerful, reliable homelab configuration — and best of all, it’s completely under my control.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>homeassistant</category>
      <category>truenas</category>
      <category>linux</category>
    </item>
    <item>
      <title>Install NUT on RaspberryPi with AlmaLinux</title>
      <dc:creator>Miklos Halasz</dc:creator>
      <pubDate>Mon, 06 Oct 2025 20:18:57 +0000</pubDate>
      <link>https://forem.com/kubenetic/install-nut-on-raspberrypi-with-almalinux-1dk8</link>
      <guid>https://forem.com/kubenetic/install-nut-on-raspberrypi-with-almalinux-1dk8</guid>
      <description>&lt;p&gt;I run one of my Raspberry Pi devices with &lt;strong&gt;AlmaLinux&lt;/strong&gt;, installed on an external SSD connected through one of the USB 3 ports. This setup provides better performance and reliability compared to running from an SD card.&lt;/p&gt;

&lt;p&gt;I followed &lt;a href="https://www.jeffgeerling.com/blog/2025/nut-on-my-pi-so-my-servers-dont-die" rel="noopener noreferrer"&gt;Jeff Geerling’s guide&lt;/a&gt; to install the &lt;strong&gt;Network UPS Tools (NUT)&lt;/strong&gt; server on the Pi to monitor my UPS. However, since AlmaLinux differs from Debian-based distributions, I needed to perform several additional steps to make everything work properly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tweak SELinux Flags
&lt;/h2&gt;

&lt;p&gt;AlmaLinux enforces &lt;strong&gt;SELinux (Security-Enhanced Linux)&lt;/strong&gt; by default, which provides a robust layer of system security. However, it also restricts services like &lt;code&gt;httpd&lt;/code&gt; (installed automatically with &lt;code&gt;nut-server&lt;/code&gt;) from performing certain network operations by default.&lt;/p&gt;

&lt;p&gt;To allow the &lt;code&gt;httpd&lt;/code&gt; service to connect to external ports, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;setsebool &lt;span class="nt"&gt;-P&lt;/span&gt; httpd_can_network_connect on
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This permanently sets the SELinux boolean that enables outbound network connections for &lt;code&gt;httpd&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Configure the Firewall
&lt;/h2&gt;

&lt;p&gt;AlmaLinux uses &lt;strong&gt;firewalld&lt;/strong&gt; with a &lt;strong&gt;default deny-all&lt;/strong&gt; policy, meaning all incoming connections are blocked unless explicitly allowed.&lt;/p&gt;

&lt;p&gt;By default, the NUT web interface (served by &lt;code&gt;httpd&lt;/code&gt;) listens on port &lt;strong&gt;8808&lt;/strong&gt;, so this port must be opened in the firewall:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--add-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8808/tcp &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;public
&lt;span class="nb"&gt;sudo &lt;/span&gt;firewall-cmd &lt;span class="nt"&gt;--reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After reloading, connections to port &lt;code&gt;8808/tcp&lt;/code&gt; will be permitted in the &lt;code&gt;public&lt;/code&gt; zone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Start the Driver
&lt;/h2&gt;

&lt;p&gt;At this stage, the installation appeared to be successful — but the NUT server wouldn’t start. Checking the logs with &lt;code&gt;journalctl&lt;/code&gt; revealed that the UPS driver hadn’t initialized.&lt;/p&gt;

&lt;p&gt;I found a helpful &lt;a href="https://www.reddit.com/r/homelab/comments/breugb/ups_nut_in_raspberrypi_cant_connect_to_ups_no/" rel="noopener noreferrer"&gt;Reddit thread&lt;/a&gt; explaining that the driver must start &lt;strong&gt;before&lt;/strong&gt; the NUT server. That made sense, so I manually started the services in the correct order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;upsdrvctl start
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start nut-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And voilà — everything started working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;upsdrvctl status
Network UPS Tools upsdrvctl - UPS driver controller 2.8.3 release
UPSNAME              UPSDRV     RUNNING PF_PID  S_RESPONSIVE    S_PID   S_STATUS
office-ups       usbhid-ups     RUNNING 702     RESPONSIVE      702     &lt;span class="s2"&gt;"OL"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However — there’s always a “but.” 😄&lt;br&gt;
After rebooting the Raspberry Pi, the services didn’t start automatically. Some additional configuration was needed to ensure the NUT driver and related services started on boot.&lt;/p&gt;


&lt;h2&gt;
  
  
  Enable Required Services on Boot
&lt;/h2&gt;

&lt;p&gt;To ensure that the NUT driver and enumerator start automatically after a reboot, I enabled several related systemd services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Pull in NUT’s umbrella target (often required)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; nut.target

&lt;span class="c"&gt;# Keep the looped enumerator running&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; nut-driver-enumerator-daemon.service

&lt;span class="c"&gt;# Optional but recommended: restart driver on ups.conf changes&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; nut-driver-enumerator-daemon-activator.path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, I modified the &lt;strong&gt;&lt;code&gt;nut-driver-enumerator-daemon.service&lt;/code&gt;&lt;/strong&gt; unit file to delay startup until the network was fully online and to ensure it used a safe start delay for the UPS driver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;Unit]
&lt;span class="nv"&gt;Requires&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network-online.target
&lt;span class="nv"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network-online.target

&lt;span class="o"&gt;[&lt;/span&gt;Service]
&lt;span class="nv"&gt;ConditionPathExists&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/ups/ups.conf
&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/libexec/nut-driver-enumerator.sh &lt;span class="nt"&gt;--daemon-after&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--daemon-after=60&lt;/code&gt; option ensures that the NUT driver starts 60 seconds after boot, giving the system and connected USB devices time to initialize properly.&lt;/p&gt;

&lt;p&gt;After these adjustments, the NUT server started reliably on every boot, running cleanly and consistently.&lt;/p&gt;

&lt;p&gt;Now my NAS and other connected systems are protected — if a power outage occurs, the NUT server communicates with the UPS and ensures all devices shut down gracefully.&lt;/p&gt;




&lt;h2&gt;
  
  
  Useful Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://networkupstools.org/docs/man/nut-driver-enumerator.html" rel="noopener noreferrer"&gt;NUT driver enumerator documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://firewalld.org/documentation/howto/open-a-port-or-service.html" rel="noopener noreferrer"&gt;Firewalld documentation: Opening a port or service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tecadmin.net/configure-selinux-for-apache-new-directory/" rel="noopener noreferrer"&gt;A practical guide to configuring SELinux for Apache&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>raspberrypi</category>
      <category>linux</category>
      <category>nut</category>
      <category>ups</category>
    </item>
    <item>
      <title>Bridged Networking on Fedora Workstation for Virtual Machines</title>
      <dc:creator>Miklos Halasz</dc:creator>
      <pubDate>Mon, 30 Jun 2025 09:42:17 +0000</pubDate>
      <link>https://forem.com/kubenetic/bridged-networking-on-fedora-workstation-for-virtual-machines-5d51</link>
      <guid>https://forem.com/kubenetic/bridged-networking-on-fedora-workstation-for-virtual-machines-5d51</guid>
      <description>&lt;p&gt;When running virtual machines on Fedora Workstation, one common requirement is allowing them to communicate directly with your local network (LAN)—as if they were physical machines. The default NAT-based networking isolates them, which is fine for outbound traffic, but insufficient when inbound access or LAN integration is needed.&lt;/p&gt;

&lt;p&gt;This post walks through setting up a &lt;strong&gt;Linux bridge interface&lt;/strong&gt; using &lt;code&gt;nmcli&lt;/code&gt;, explains what’s going on behind the scenes, and introduces key networking concepts like bridge slaves and virtual routing bridges (VRBs). If you're using KVM, QEMU, or libvirt for virtualization, this is essential knowledge.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Needed a Bridge
&lt;/h2&gt;

&lt;p&gt;I had VMs running on Fedora 42, and I wanted them to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get an IP address from my home DHCP server (like other devices on the LAN)&lt;/li&gt;
&lt;li&gt;Be accessible via their own IP addresses from any other machine on the network&lt;/li&gt;
&lt;li&gt;Act like any other physical device in the network&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To achieve this, I needed &lt;strong&gt;bridged networking&lt;/strong&gt;, which effectively connects the VM to the same Layer 2 network as the host’s physical Ethernet.&lt;/p&gt;




&lt;h2&gt;
  
  
  Configuration Steps
&lt;/h2&gt;

&lt;p&gt;Here’s what I ran to set up the bridge using &lt;code&gt;nmcli&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nmcli connection delete localan
nmcli con add ifname br0 &lt;span class="nb"&gt;type &lt;/span&gt;bridge autoconnect &lt;span class="nb"&gt;yes &lt;/span&gt;con-name br0 ipv4.method auto
nmcli con add &lt;span class="nb"&gt;type &lt;/span&gt;bridge-slave autoconnect &lt;span class="nb"&gt;yes &lt;/span&gt;con-name br-slave-eno1 ifname eno1 master br0
nmcli connection up br0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Delete the old connection managing the physical interface (eno1)&lt;/li&gt;
&lt;li&gt;Create a new bridge interface named br0&lt;/li&gt;
&lt;li&gt;Attach eno1 to the bridge as a slave&lt;/li&gt;
&lt;li&gt;Bring up the bridge&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once complete, I configured my virtual machines to use br0 as their network interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Was the Existing Connection Deleted?
&lt;/h3&gt;

&lt;p&gt;The default profile (&lt;code&gt;localan&lt;/code&gt;) was already managing eno1. NetworkManager does not allow a device to be controlled by multiple profiles simultaneously. Before you can enslave a physical interface to a bridge, it must be released from any existing configuration.&lt;/p&gt;

&lt;p&gt;This is a critical step. Without removing the existing connection, adding &lt;code&gt;eno1&lt;/code&gt; to the bridge would fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Bridge Interface?
&lt;/h2&gt;

&lt;p&gt;A bridge interface acts like a virtual switch at Layer 2 (Data Link Layer). It connects multiple network interfaces and forwards Ethernet frames between them.&lt;/p&gt;

&lt;p&gt;In this setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;br0&lt;/code&gt; acts like a virtual switch.&lt;/li&gt;
&lt;li&gt;The physical NIC (&lt;code&gt;eno1&lt;/code&gt;) is a port on that switch.&lt;/li&gt;
&lt;li&gt;Virtual machines are plugged into the same switch, gaining full LAN access.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bridge itself is the only interface that receives an IP address. The physical NIC becomes a passive conduit—passing traffic between the bridge and the network.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Does "Controller Waiting for Ports" Mean?
&lt;/h3&gt;

&lt;p&gt;When activating the bridge, NetworkManager returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;nmcli connection up br0
Connection successfully activated &lt;span class="o"&gt;(&lt;/span&gt;controller waiting &lt;span class="k"&gt;for &lt;/span&gt;ports&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/25&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This message means that &lt;code&gt;br0&lt;/code&gt; is active but waiting for its slave interfaces (like &lt;code&gt;eno1&lt;/code&gt;) to become fully ready—i.e., for link negotiation to complete. It’s typically harmless and resolves within seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Are Bridge Slaves and Why Are They Needed?
&lt;/h3&gt;

&lt;p&gt;Any device added to a bridge is called a slave. Slaves pass data to and from the bridge but no longer operate independently.&lt;/p&gt;

&lt;p&gt;In our case, &lt;code&gt;eno1&lt;/code&gt; became a bridge slave of br0. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It no longer gets its own IP address.&lt;/li&gt;
&lt;li&gt;It merely passes packets between the bridge and the physical network.&lt;/li&gt;
&lt;li&gt;All IP configuration is now handled by the bridge (br0).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup allows your virtual machines, which are also connected to the bridge, to be treated like independent physical devices on the network.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why a Bridge Is Needed for VM LAN Access
&lt;/h3&gt;

&lt;p&gt;By default, virtualization platforms like &lt;code&gt;libvirt&lt;/code&gt; configure VMs with NAT-based networking. This allows VMs to access the internet but keeps them isolated from the LAN.&lt;/p&gt;

&lt;p&gt;To make a VM accessible from other devices on your network—via ping, SSH, or services—you need a bridge. It gives the VM a direct Layer 2 path to the LAN, enabling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IP address assignment from the LAN’s DHCP server&lt;/li&gt;
&lt;li&gt;Direct access to and from any device on the LAN&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What’s Happening Behind the Scenes?
&lt;/h3&gt;

&lt;p&gt;Here’s a high-level overview:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Bridge creation (br0) defines a virtual switch.&lt;/li&gt;
&lt;li&gt;Physical NIC (eno1) is enslaved to br0, losing its own IP.&lt;/li&gt;
&lt;li&gt;Bridge gets an IP address and becomes the primary network device.&lt;/li&gt;
&lt;li&gt;VMs are connected to br0, making them peers on the LAN.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Linux handles the bridging logic via kernel modules. NetworkManager coordinates the configuration, making &lt;code&gt;nmcli&lt;/code&gt; a powerful and reliable tool for managing this setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Quick Word on Virtual Routing Bridges (VRB)
&lt;/h2&gt;

&lt;p&gt;A Virtual Routing Bridge (VRB) is a more advanced networking concept used in cloud or SDN environments. Unlike a standard bridge that only operates at Layer 2, a VRB combines Layer 2 bridging with Layer 3 routing.&lt;/p&gt;

&lt;p&gt;This means a VRB can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route between VLANs or subnets&lt;/li&gt;
&lt;li&gt;Apply policies and firewall rules&lt;/li&gt;
&lt;li&gt;Act as both a switch and a router&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;VRBs are used in complex setups like OpenStack or Kubernetes CNI plugins. For most homelab or desktop VM scenarios, a standard Linux bridge is simpler, faster, and entirely sufficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Setting up bridged networking on Fedora using &lt;code&gt;nmcli&lt;/code&gt; is a clean and efficient way to allow VMs to function as full citizens of your LAN. Once configured, it just works—and avoids the limitations of NAT-based networking.&lt;/p&gt;

&lt;p&gt;Whether you're running development servers, containers, or full VM stacks, understanding Linux bridges is essential knowledge for any modern Linux user managing a virtualized environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.cyberciti.biz/faq/how-to-add-network-bridge-with-nmcli-networkmanager-on-linux/" rel="noopener noreferrer"&gt;How to Add Network Bridge with nmcli (Cyberciti.biz)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>network</category>
      <category>devops</category>
    </item>
    <item>
      <title>Terraform: Infrastructure as Code or Just a Fancy Wrapper?</title>
      <dc:creator>Miklos Halasz</dc:creator>
      <pubDate>Sun, 29 Jun 2025 08:59:24 +0000</pubDate>
      <link>https://forem.com/kubenetic/terraform-infrastructure-as-code-or-just-a-fancy-wrapper-5h4f</link>
      <guid>https://forem.com/kubenetic/terraform-infrastructure-as-code-or-just-a-fancy-wrapper-5h4f</guid>
      <description>&lt;p&gt;I developed a healthy skepticism for tools that claim to make life easier by abstracting "complexity". Terraform is one of those tools.&lt;/p&gt;

&lt;p&gt;It’s become something of a standard in the DevOps world. And I get why—it’s clean, declarative, and integrates well into GitOps and CI/CD workflows. It tracks state, it has a vibrant ecosystem of modules, and it supports everything from AWS to VMware to local libvirt. But despite its power, Terraform isn’t magic. In fact, it’s often just a more elegant wrapper around the APIs and CLI tools I was already using.&lt;/p&gt;

&lt;p&gt;Let’s talk about what Terraform does well, and more importantly, where the abstraction leaks.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Unified Language—But Not Really
&lt;/h2&gt;

&lt;p&gt;One of the biggest marketing points behind Terraform is its "unified language" for infrastructure. You're told you can write a VM definition once and simply switch providers—from local libvirt to AWS or GCP—without changing much.&lt;/p&gt;

&lt;p&gt;But here's the reality: you will change &lt;em&gt;almost everything&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The configuration syntax may be consistent in terms of how blocks and attributes are defined, but the actual resource models between providers are completely different. For a virtual machine, AWS expects an AMI ID and an instance type. Libvirt wants memory and CPU counts and a reference to a QCOW2 image. That’s not a minor difference. It’s a complete redefinition of what a "machine" even is.&lt;/p&gt;

&lt;p&gt;In practice, you don’t get portability. You get familiarity with the &lt;em&gt;tooling syntax&lt;/em&gt;, but you still have to learn every provider's API model. At that point, are you really gaining anything over just writing some shell scripts for &lt;code&gt;virsh&lt;/code&gt; or &lt;code&gt;awscli&lt;/code&gt;, especially if you're only using one platform anyway?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wrapper Dilemma
&lt;/h2&gt;

&lt;p&gt;Terraform doesn’t manage infrastructure by itself. It's just a structured way to call the APIs of your underlying tools. Whether that’s the AWS EC2 API, the Proxmox REST interface, or the local libvirt sockets, it depends on "providers"—which are essentially plugins maintained by HashiCorp or the community.&lt;/p&gt;

&lt;p&gt;That dependency chain introduces friction. If Proxmox changes something in its API and the provider hasn't caught up yet, you’re stuck. You either wait for a fix or fork the provider yourself. It could be frustrating when you're blocked from using a feature that is &lt;em&gt;technically available&lt;/em&gt;, just not &lt;em&gt;exposed yet&lt;/em&gt; in Terraform.&lt;/p&gt;

&lt;p&gt;With traditional shell scripts, you’d just call the new feature directly. It may be ugly, but it works now. That immediacy is something that I miss when relying on Terraform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Easier Than Bash, Until It’s Not
&lt;/h2&gt;

&lt;p&gt;I’ll be honest: writing infrastructure logic in HCL (HashiCorp Configuration Language) is much more pleasant than hacking away at brittle Bash scripts with a million conditionals and &lt;code&gt;jq&lt;/code&gt; parsers. Terraform gives you a structured, declarative way to define your environment. That’s a win.&lt;/p&gt;

&lt;p&gt;But that only goes so far. The moment you need to express logic Terraform doesn't support natively—loops beyond what &lt;code&gt;for_each&lt;/code&gt; or &lt;code&gt;count&lt;/code&gt; can handle, or conditional resources that depend on multiple variables—you end up writing external tooling anyway. That’s when the clean declarative abstraction starts to feel a lot like YAML with lipstick.&lt;/p&gt;

&lt;h2&gt;
  
  
  State Management: A Blessing and a Curse
&lt;/h2&gt;

&lt;p&gt;One of Terraform’s key strengths is that it tracks the state of your infrastructure. It knows what you created, what changed, and what needs to be destroyed. This makes &lt;code&gt;terraform plan&lt;/code&gt; and &lt;code&gt;terraform apply&lt;/code&gt; very powerful, especially when you're working in teams.&lt;/p&gt;

&lt;p&gt;But that state file is also a liability. Lose it, and you're flying blind. Mismanage it, and you can destroy production resources. Using remote backends like S3 or Git backends helps, but the whole concept of syncing and locking state adds a layer of fragility that simply doesn't exist when you're managing things manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Terraform Works Best
&lt;/h2&gt;

&lt;p&gt;Despite my criticisms, I do use Terraform. It shines in environments where infrastructure is complex, distributed, and managed by teams. It’s excellent when used to define cloud resources, manage networking, integrate with CI pipelines, and implement GitOps workflows. The repeatability and visibility into planned changes are things no ad-hoc shell script can give you cleanly.&lt;/p&gt;

&lt;p&gt;If you’re spinning up identical environments across dev, staging, and production, Terraform’s modules and workspaces are a godsend. And if your team spans different roles (devs, ops, SREs), the shared syntax helps build common ground.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Terraform is a powerful tool, but it’s not the abstraction layer it’s often sold as. It doesn’t free you from learning your infrastructure platform—in fact, it &lt;em&gt;requires&lt;/em&gt; that you know the platform well enough to map its concepts into Terraform’s provider model.&lt;/p&gt;

&lt;p&gt;It's not a silver bullet. It's an elegant tool for automating and tracking infrastructure, especially in cloud-native environments. But for someone like me, who grew up close to the bare metal and still sees the value in understanding what’s under the hood, Terraform is best thought of as a structured wrapper with lifecycle management—not a replacement for knowing how things work.&lt;/p&gt;

&lt;p&gt;Use it when it fits, but don’t be afraid to reach for &lt;code&gt;virsh&lt;/code&gt;, &lt;code&gt;awscli&lt;/code&gt;, or even plain Bash when you need control Terraform doesn’t yet expose.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>infrastructureascode</category>
      <category>linux</category>
      <category>devops</category>
    </item>
    <item>
      <title>Jakarta validation annotations on the wrong place</title>
      <dc:creator>Miklos Halasz</dc:creator>
      <pubDate>Fri, 13 Jun 2025 18:37:48 +0000</pubDate>
      <link>https://forem.com/kubenetic/jakarta-validation-annotations-on-the-wrong-place-4bjh</link>
      <guid>https://forem.com/kubenetic/jakarta-validation-annotations-on-the-wrong-place-4bjh</guid>
      <description>&lt;p&gt;During work on my current project I tried to find the optimal solution how to validate the arguments of some service level methods. I don't want to write every time an &lt;code&gt;if&lt;/code&gt; clause for nullcheck or add additional validation logic, for example ensure a numeric value not to be less then 0. &lt;/p&gt;

&lt;p&gt;I added &lt;code&gt;jakarta.validation&lt;/code&gt; annotations as you can see below, but first on the implementations of the functions. During the build this error was thrown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HV000151: A method overriding another method must not redefine the parameter constraint configuration ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This occurs when you define validation annotations on a method parameter that overrides a method from an interface or superclass that already has validation constraints. Jakarta Bean Validation enforces constraints defined by interfaces or base classes and does not allow the redefinition of constraints on overridden methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Why does this happen?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Bean Validation requires consistency in method-level constraints between interfaces (or parent classes) and their implementations. If your interface method parameters have no constraints, your implementation methods cannot introduce them. If the interface method parameters have constraints, your implementation must match them exactly.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;How to fix it:&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;You have a couple of choices:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Move constraints to the interface (recommended):&lt;/strong&gt;&lt;br&gt;
Define your validation annotations directly in the interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;performAction&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@NotNull&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyServiceImpl&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;performAction&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Implementation code&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This clearly defines constraints at the interface level and ensures all implementations have consistent constraints.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;2. Use constraints only in the implementation class:&lt;/strong&gt;&lt;br&gt;
If your interface doesn't specify constraints, and you still want constraints in your implementation, you should avoid parameter constraints on overridden methods. Instead, you can use field-level validation or manually validate parameters within the method body.&lt;/p&gt;

&lt;p&gt;Example using manual validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyServiceImpl&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;performAction&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IllegalArgumentException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input must not be null"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// Implementation code&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, move constraints to fields or separate objects.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;3. Separate constraints into DTOs or dedicated validated objects:&lt;/strong&gt;&lt;br&gt;
This is a robust, widely used approach.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;performAction&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ActionRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActionRequest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@NotNull&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// getters, setters&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyServiceImpl&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;performAction&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Valid&lt;/span&gt; &lt;span class="nc"&gt;ActionRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Implementation&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach clearly separates concerns and validation logic into dedicated classes, avoiding the original issue.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Recommended Best Practice:&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Usually, constraints belong clearly at the interface or domain model level, or within DTOs/request objects. Defining constraints in interfaces or separate request objects ensures clear, reusable, and maintainable validation logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary of the best solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Define constraints in interfaces&lt;/strong&gt; if they logically apply to all implementations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use DTO/request classes&lt;/strong&gt; with annotations if constraints vary or if method signatures should remain clean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid redefining constraints&lt;/strong&gt; at the implementation method directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This will effectively resolve the error and ensure a clear, maintainable validation strategy.&lt;/p&gt;

</description>
      <category>java</category>
      <category>programming</category>
      <category>jakarta</category>
      <category>validation</category>
    </item>
    <item>
      <title>10 Real-World DevOps Team Lead Scenarios and How to Master Them</title>
      <dc:creator>Miklos Halasz</dc:creator>
      <pubDate>Fri, 13 Jun 2025 18:32:38 +0000</pubDate>
      <link>https://forem.com/kubenetic/10-real-world-devops-team-lead-scenarios-and-how-to-master-them-5dn9</link>
      <guid>https://forem.com/kubenetic/10-real-world-devops-team-lead-scenarios-and-how-to-master-them-5dn9</guid>
      <description>&lt;p&gt;As part of my journey into leadership in the DevOps space, I recently walked through ten common but complex scenarios that a DevOps Team Lead might face.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: These scenarios were generated by ChatGPT at my request as part of preparing for a DevOps Team Lead interview. The answers and strategies are entirely my own, reflecting how I would handle each situation.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Junior Engineers Relying Too Much on Seniors
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Two junior engineers were constantly blocking senior engineers with minor requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; I initiated 1:1s to understand their blockers and assess if the tasks assigned to them matched their current skill level. I discovered they often lacked confidence or context. To balance support and senior focus time, I introduced scheduled daily sync slots for juniors to raise their questions in batches. I also encouraged them to maintain a simple 'engineering diary' to document recurring issues and solutions. Over time, these diaries would evolve into lightweight internal documentation. For the long term, I planned structured mentoring sessions and a gradual introduction of pair programming to accelerate their learning.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. A Team Bypasses CI/CD for Direct Production Pushes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; A backend team was deploying directly to production, ignoring DevOps pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; I set up a conversation with their lead developers to understand their reasoning. It turned out their perception of our pipeline was that it was slow and overly rigid. I took their feedback seriously and initiated a performance audit on our CI/CD system. We made targeted improvements — including caching, parallel steps, and clearer logging — and communicated those changes back. I also held a knowledge-sharing session to explain the purpose of each stage in the pipeline, showing how it protects quality and security. When necessary, I escalated recurring issues to leadership, framing it as a risk management concern, not a power struggle.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Handling an Outage and a Critical Security CVE Simultaneously
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; An outage occurred while a critical container CVE was discovered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; I triaged the issues by impact — production outages take precedence. I assembled an incident response team and assigned clear roles: incident commander, communications lead, and engineers on fix duty. I shielded the team from non-essential noise and kept leadership updated at regular intervals. Meanwhile, I scoped the CVE’s risk profile: Was it actively exploitable? What environments were affected? I documented its severity and began drafting a patching plan. Once the outage was resolved and we had breathing room, we immediately prioritized the CVE, patched it across all environments, and updated our base images and image scanning automation to catch similar issues earlier.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Breaking Down Silos in a Disconnected DevOps Team
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; A siloed DevOps team with tribal knowledge and no documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; I started by holding 1:1 meetings with each team member to understand their roles, tools they maintained, and workflows they followed. I asked everyone to start logging their daily tasks and edge cases into a shared internal wiki. Then, I introduced biweekly "Knowledge Share Days," where engineers gave 10-15 minute walkthroughs of tools, configs, or lessons learned. To reinforce the behavior, I tied documentation and internal knowledge sharing to team OKRs. Over time, this shifted the team from isolated individuals to a learning-oriented, cross-skilled unit.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. A Brilliant Engineer with Poor Team Behavior
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; A senior engineer was dismissive and critical, affecting team morale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; I had a candid 1:1 to explore the source of their frustration. I found they felt overburdened and underappreciated. I acknowledged their technical value but also explained clearly how their communication style was damaging junior growth and team trust. I encouraged them to take on a mentorship role — not as extra work, but as a chance to shape our engineering culture. I supported them in improving soft skills through informal coaching and clear feedback. If they had continued being disruptive, I had a plan to escalate with HR and formalize a behavior improvement plan.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Convincing Leadership to Adopt a New Build Tool
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Engineering wanted to migrate pipelines to a new build tool, but product leadership was hesitant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; I proposed running a proof of concept (PoC) on an internal tool that wasn’t customer-facing. I collaborated with a willing dev lead to migrate the project, documented every step, captured pain points, and measured build performance before and after. I also recorded developer feedback on clarity, speed, and ease of use. Using this evidence, I created a pitch deck that compared the current and proposed tools, showed the migration playbook, and presented a phased rollout plan. I highlighted risk mitigation, dev velocity improvements, and long-term maintenance savings to product leadership.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Enabling Developer Self-Service at Scale
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Reduce DevOps bottlenecks by empowering devs with self-service tooling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; I started by identifying key personas who would use the platform: backend devs, QA, data engineers. I conducted workflow mapping sessions and internal surveys to uncover repetitive or ticket-based requests — provisioning environments, triggering builds, creating dashboards. We prioritized quick wins and built an MVP using Backstage, backed by Terraform automation and GitOps where possible. I documented the entire workflow, published how-to guides, and measured impact with KPIs like time-to-onboard and support ticket volume. Developer feedback loops helped shape future iterations and ensured the tools were truly enabling.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Explaining DevOps to Non-Technical Stakeholders
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Communicating DevOps value to execs and PMs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; I framed DevOps not as a toolset, but as a culture and capability — a way of enabling product delivery by aligning dev, ops, and security. I kept technical jargon to a minimum and focused on how DevOps improves release reliability, lead time, and resilience. I used real numbers — deployments per week, MTTR, incidents avoided — and tied them to business outcomes: faster time-to-market, fewer outages, more secure products. I presented our roadmap in terms of impact, not implementation, and encouraged questions so stakeholders felt included and informed.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Integrating Security Without Slowing Down Developers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Security tools were slowing down CI/CD pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; I first audited our existing tooling and classified it by complexity and runtime cost. I proposed a tiered model: run SAST and dependency checks on every PR, nightly DAST and container scans on the dev branch, and deeper compliance scans before release. I reviewed the feedback from developers and iterated to improve speed and false positive handling. I emphasized security as part of quality — a shared responsibility, not overhead. I tracked and reported on the number of critical issues caught early and improved scan completion rates as KPIs.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Building and Sustaining a High-Performing DevOps Team
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Defining what makes a DevOps team truly high-performing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; A high-performing DevOps team acts as a &lt;strong&gt;platform enabler&lt;/strong&gt;, not a ticket resolver. It drives automation, champions best practices, and acts as a multiplier for engineering throughput. I look for signs like high deployment frequency, fast MTTR, strong documentation, and visible team ownership. To sustain performance, I prioritize onboarding, mentorship, internal docs, and setting clear expectations. I track burnout risk, encourage retrospectives, and rotate responsibilities to prevent silos. My goal is a team that’s curious, collaborative, and always evolving — one that grows capability, not just velocity.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Leading a DevOps team isn’t just about tools — it’s about people, empathy, systems thinking, and cross-functional communication. These scenarios reflect how I think through challenges, advocate for the team, and align technical goals with organizational strategy.&lt;/p&gt;

&lt;p&gt;If you’re on the path to leadership or already there, I hope these reflections help you level up your own approach.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>career</category>
      <category>leadership</category>
    </item>
    <item>
      <title>Aspect Oriented Programming in JAVA</title>
      <dc:creator>Miklos Halasz</dc:creator>
      <pubDate>Wed, 11 Jun 2025 07:57:03 +0000</pubDate>
      <link>https://forem.com/kubenetic/aspect-oriented-programming-in-java-3k0l</link>
      <guid>https://forem.com/kubenetic/aspect-oriented-programming-in-java-3k0l</guid>
      <description>&lt;p&gt;Today, I explored Aspect-Oriented Programming (AOP), particularly its application within a Spring Boot context for handling cross-cutting concerns. Initially, I described a use-case from my Spring Boot application, where various operations — like changing job properties, uploading attachments, or deleting resources — trigger events that should be audited and logged consistently. My goal was to find patterns for handling these events effectively.&lt;/p&gt;

&lt;p&gt;One of the first term that I met and needed some clarification was the &lt;strong&gt;cross-cutting concerns&lt;/strong&gt;. It refers to common functionalities repeated across various layers or modules of an application, such as logging, auditing, transaction management, and security checks. The essential benefit of &lt;strong&gt;AOP&lt;/strong&gt; is to encapsulate these repetitive tasks in one place and then apply them wherever necessary, keeping the main business logic clean.&lt;/p&gt;

&lt;p&gt;After that I had an "AHAaa" moment about Spring Boot:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Spring Boot with annotation-based configuration is essentially a combination of annotations and AOP."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And indeed. Spring Boot heavily relies on declarative programming using annotations (e.g., &lt;code&gt;@Service&lt;/code&gt;, &lt;code&gt;@Autowired&lt;/code&gt;, &lt;code&gt;@Transactional&lt;/code&gt;) to manage cross-cutting concerns seamlessly. This declarative, annotation-driven paradigm is a significant aspect of Spring’s philosophy.&lt;/p&gt;

&lt;p&gt;To illustrate how to practically apply these concepts, I decided to build a simple audit logging system leveraging custom annotations and AOP. I created an entity named &lt;code&gt;AuditLog&lt;/code&gt; for logging audit information, structured as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuditLog&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;IDENTITY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;LocalDateTime&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Constructors, getters, setters omitted for brevity&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This entity stores basic details like the user responsible for the action, the type of action, the timestamp, and additional information (e.g., method parameters).&lt;/p&gt;

&lt;p&gt;Next, I introduced a Spring Data JPA repository for persisting these logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;AuditLogRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JpaRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AuditLog&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To easily trigger audit logs from specific methods, I created a custom annotation named &lt;code&gt;@Auditable&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Retention&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RetentionPolicy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RUNTIME&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Target&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ElementType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;METHOD&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nd"&gt;@interface&lt;/span&gt; &lt;span class="nc"&gt;Auditable&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core of my AOP-based logging system is the &lt;code&gt;AuditAspect&lt;/code&gt; class, defined as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Aspect&lt;/span&gt;
&lt;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuditAspect&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;AuditLogRepository&lt;/span&gt; &lt;span class="n"&gt;auditLogRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AuditAspect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AuditLogRepository&lt;/span&gt; &lt;span class="n"&gt;auditLogRepository&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;auditLogRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auditLogRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@AfterReturning&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pointcut&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"@annotation(auditable)"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;returning&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"result"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;logAudit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JoinPoint&lt;/span&gt; &lt;span class="n"&gt;joinPoint&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Auditable&lt;/span&gt; &lt;span class="n"&gt;auditable&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getCurrentUser&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// placeholder for real user retrieval logic&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auditable&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Arrays&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joinPoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getArgs&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;arg&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"null"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;joining&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;", "&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="nc"&gt;AuditLog&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AuditLog&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setUsername&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAction&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTimestamp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LocalDateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDetails&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;auditLogRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getCurrentUser&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"system"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// replace later with integration like Spring Security&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, I leveraged Spring AOP's &lt;code&gt;@AfterReturning&lt;/code&gt; advice, which executes after the successful completion of the annotated method. This approach conveniently provides access to the method's return value (&lt;code&gt;result&lt;/code&gt;). For instance, annotating methods with &lt;code&gt;@Auditable&lt;/code&gt; automatically generates audit entries whenever the methods run successfully.&lt;/p&gt;

&lt;p&gt;An example usage in a business service looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JobService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Auditable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Updated job property"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;updateJob&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Job&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// business logic&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Auditable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Uploaded attachment"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;uploadAttachment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;jobId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MultipartFile&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// business logic&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Through this setup, audit logging becomes clean, consistent, and easily maintainable.&lt;/p&gt;

&lt;p&gt;Later, I sought deeper insights into the mechanics of Spring AOP annotations. Besides &lt;code&gt;@AfterReturning&lt;/code&gt;, I learned other essential annotations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@Before&lt;/code&gt;: Executes before the method runs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@AfterThrowing&lt;/code&gt;: Executes if the method throws an exception.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@After&lt;/code&gt;: Executes after method execution, regardless of the outcome.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Around&lt;/code&gt;: Provides complete control, allowing me to manipulate method arguments, return values, or even bypass method execution entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Moreover, the &lt;code&gt;JoinPoint&lt;/code&gt; object provided by Spring AOP contains metadata about the current execution point, including the method name, parameters, target object, and the context where the aspect is triggered.&lt;/p&gt;

&lt;p&gt;For example, a simple logging aspect using a &lt;code&gt;JoinPoint&lt;/code&gt; could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Before&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"execution(* com.example.service.*.*(..))"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;logBeforeMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JoinPoint&lt;/span&gt; &lt;span class="n"&gt;joinPoint&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Calling method: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;joinPoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSignature&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Arguments: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;Arrays&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joinPoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getArgs&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thus, Spring AOP elegantly facilitates handling cross-cutting concerns declaratively and transparently.&lt;/p&gt;

&lt;p&gt;In summary, today's exploration reinforced the understanding that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cross-cutting concerns&lt;/strong&gt; represent repeated functionalities across the system, ideally abstracted out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aspect-Oriented Programming&lt;/strong&gt; neatly addresses these concerns.&lt;/li&gt;
&lt;li&gt;Spring Boot extensively utilizes annotation-driven AOP, making development declarative, readable, and maintainable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, I noted that a well-designed audit logging system in Spring Boot can significantly benefit from AOP’s flexibility, readability, and simplicity.&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>aop</category>
      <category>programming</category>
    </item>
    <item>
      <title>Remote access with RustDesk</title>
      <dc:creator>Miklos Halasz</dc:creator>
      <pubDate>Fri, 25 Apr 2025 17:56:36 +0000</pubDate>
      <link>https://forem.com/kubenetic/remote-access-with-rustdesk-km4</link>
      <guid>https://forem.com/kubenetic/remote-access-with-rustdesk-km4</guid>
      <description>&lt;p&gt;Recently I traveled abroad and need a solution to use my workstation. I tried the RustDesk remote desktop. It needs a self-hosted server if you don't want to use their cloud solution and yet I don't want to :) So after I created a virtual machine on my Proxmox host with the official suggested hardware capabilities and installed an OS (AlmaLinux), I went through the following steps described below: &lt;/p&gt;

&lt;h2&gt;
  
  
  Create user for running the server
&lt;/h2&gt;

&lt;p&gt;I made a &lt;code&gt;group&lt;/code&gt; and &lt;code&gt;user&lt;/code&gt; named &lt;em&gt;rustdesk&lt;/em&gt; on that system and created the &lt;code&gt;/home/rustdesk/.config/containers/systemd/&lt;/code&gt; path because I wanted to run the server containerized with Podman handled by Systemd.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;groupadd &lt;span class="nt"&gt;--gid&lt;/span&gt; 990 &lt;span class="nt"&gt;--system&lt;/span&gt; rustdesk
useradd &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--create-home&lt;/span&gt; &lt;span class="nt"&gt;--gid&lt;/span&gt; 990 rustdesk
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; rustdesk &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /home/rustdesk/.config/containers/systemd/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the server have to be start at boot I enabled lingering for the &lt;code&gt;rustdesk&lt;/code&gt; user so in this way it doesn't need an active session.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;loginctl enable-linger rustdesk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure firewall
&lt;/h2&gt;

&lt;p&gt;Based on the official documentation RustDesk needs a couple port to communicate with clients. I created a service named &lt;code&gt;rustdesk&lt;/code&gt; and add ports to it. Later assign the newly created service to the &lt;code&gt;public&lt;/code&gt; zone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--new-service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rustdesk
firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rustdesk &lt;span class="nt"&gt;--set-description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Ports which are necessary to operate with rustdesk'&lt;/span&gt;
firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rustdesk &lt;span class="nt"&gt;--add-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;21115/tcp
firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rustdesk &lt;span class="nt"&gt;--add-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;21116/tcp
firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rustdesk &lt;span class="nt"&gt;--add-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;21116/udp
firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rustdesk &lt;span class="nt"&gt;--add-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;21118/tcp
firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rustdesk &lt;span class="nt"&gt;--add-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;21117/tcp
firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rustdesk &lt;span class="nt"&gt;--add-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;21119/tcp
firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;public &lt;span class="nt"&gt;--add-service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rustdesk
firewall-cmd &lt;span class="nt"&gt;--reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Check the firewall configuration
&lt;/h3&gt;

&lt;p&gt;If you want to check the completed settings you can list the services assigned to the public zone and list the opened ports of the service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;firewall-cmd &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;public &lt;span class="nt"&gt;--list-services&lt;/span&gt;
firewall-cmd &lt;span class="nt"&gt;--service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rustdesk &lt;span class="nt"&gt;--get-ports&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create systemd resources
&lt;/h2&gt;

&lt;p&gt;Podman has a great integration with systemd. You can create and configure containers, volumes and networking. I did not mind the networking in this configuration, but I wanted to persist the &lt;code&gt;/root&lt;/code&gt; folder as the official documentation suggested. Because I only needed a simple volume I just created the &lt;code&gt;rustdesk-hbbs.volume&lt;/code&gt; and &lt;code&gt;rustdesk-hbbr.volume&lt;/code&gt; files under the &lt;code&gt;/home/rustdesk/.config/containers/systemd/&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Volume]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Based on the configuration above the tool &lt;code&gt;Quadlet&lt;/code&gt; - that installed alongside Podman - creates the appropriate systemd configurations and the end of the process a podman volume creates on the machine.&lt;/p&gt;

&lt;p&gt;In the next step I created the configuration for the server containers. It's like n ordinary systemd service file, but it has an extra section named &lt;code&gt;[Container]&lt;/code&gt;. The possible configurations are described here: &lt;a href="https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html" rel="noopener noreferrer"&gt;podman-systemd.unit (5)&lt;/a&gt;. You can notice I used the standard &lt;code&gt;Restart&lt;/code&gt; parameter under the &lt;code&gt;[Service]&lt;/code&gt; section to configure when should the container restarts. You can find more information about the configurations here: &lt;a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html" rel="noopener noreferrer"&gt;systemd.service&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;hbbs.container&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Unit]
Description=Rustdesk HBBS cotnainer
After=local-fs.target

[Container]
AutoUpdate=registry
Image=ghcr.io/rustdesk/rustdesk-server:latest
ContainerName=hbbs
PublishPort=21115:21115
PublishPort=21116:21116
PublishPort=21116:21116/udp
PublishPort=21118:21118
Volume=rustdesk-hbbs.volume:/root
Exec=hbbs

[Service]
Restart=on-failure

[Install]
WantedBy=multi-user.target default.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;hbbr.container&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Unit]
Description=Rustdesk HBBR cotnainer
After=local-fs.target

[Container]
AutoUpdate=registry
Image=ghcr.io/rustdesk/rustdesk-server:latest
ContainerName=hbbr
PublishPort=21117:21117
PublishPort=21119:21119
Volume=rustdesk-hbbr.volume:/root
Exec=hbbr

[Service]
Restart=on-failure

[Install]
WantedBy=multi-user.target default.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Start the application
&lt;/h2&gt;

&lt;p&gt;I struggled to start application and but found the following post on &lt;a href="https://stackoverflow.com/a/73281489" rel="noopener noreferrer"&gt;StackOverflow&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;systemd 248 (released March 2021) introduced support for the syntax -M myuser@ for specifying another user.&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nt"&gt;-M&lt;/span&gt; myuser@ start ap@inst1
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;A side-note: If you want to get an interactive login shell for the user myuser&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;machinectl shell myuser@
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;On my fresh installed AlmaLinux Podman complained about only few user namespaces were allowed, so I increased the number of the namespaces, and enabled IP forwarding for the containers. I edited directly the &lt;code&gt;/etc/sysctl.conf&lt;/code&gt; file, and add / changed the following properties&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;net.ipv4.ip_forward = 1
user.max_user_namespaces = 15000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that I reloaded the settings and started the services in the name of the user &lt;code&gt;rustdesk&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nt"&gt;-M&lt;/span&gt; rustdesk@ daemon-reload
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nt"&gt;-M&lt;/span&gt; rustdesk@ start hbbs.service
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nt"&gt;-M&lt;/span&gt; rustdesk@ start hbbr.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure the clients
&lt;/h2&gt;

&lt;p&gt;In the settings menu of the application use the hostname or IP address of the RustDesk service. For the &lt;code&gt;ID Server&lt;/code&gt; use the 21116 port and for the &lt;code&gt;Relay server&lt;/code&gt; the 21117 port. During the startup the HBBS service generate a keypair, and in the &lt;code&gt;Key&lt;/code&gt; field I inserted the content of the public key.  It can be found under the path &lt;code&gt;/home/rustdesk/.local/share/containers/storage/volumes/systemd-rustdesk-hbbs/_data/id_ed25519.pub&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqgzo0a2jhea1an8ewor2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqgzo0a2jhea1an8ewor2.png" alt="RustDesk network settings" width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Technical Notes:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User Namespaces and Networking&lt;/strong&gt;: I had to increase the maximum allowed user namespaces on my system. This is necessary for containers to run securely with their own isolated environments. If you’re not familiar with namespaces, they essentially provide a layer of isolation between processes, which is crucial for container security.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Podman Networking&lt;/strong&gt;: While I didn’t configure custom networking in this setup, it's an important aspect of containerized applications. You might want to define a specific network for your containers if you need them to communicate with other systems outside the host.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Furter readings
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.redhat.com/en/blog/multi-container-application-podman-quadlet" rel="noopener noreferrer"&gt;Deploying a multi-container application using Podman and Quadlet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.redhat.com/en/blog/quadlet-podman" rel="noopener noreferrer"&gt;Make systemd better for Podman with Quadlet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rustdesk.com/docs/en/self-host/rustdesk-server-oss/docker/" rel="noopener noreferrer"&gt;Install containerized RustDesk server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>rustdesk</category>
      <category>podman</category>
      <category>systemd</category>
    </item>
    <item>
      <title>Generate JAVA code from OpenAPI specification</title>
      <dc:creator>Miklos Halasz</dc:creator>
      <pubDate>Tue, 15 Apr 2025 14:46:23 +0000</pubDate>
      <link>https://forem.com/kubenetic/generate-java-code-from-openapi-specification-4dn6</link>
      <guid>https://forem.com/kubenetic/generate-java-code-from-openapi-specification-4dn6</guid>
      <description>&lt;p&gt;Recently, I started a project and I chose &lt;strong&gt;Gradle&lt;/strong&gt; as the build tool. In this project, I had to integrate with an external service that has an API documented in OpenAPI format. So, first of all, I had to import the &lt;code&gt;OpenAPI Generator&lt;/code&gt; plugin into my project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Import &lt;code&gt;Generator&lt;/code&gt; plugin
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"java-library"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.openapi.generator"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s"&gt;"7.12.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Plugin configuration
&lt;/h2&gt;

&lt;p&gt;After importing the plugin, some configuration was needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;openApiGenerate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;generatorName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spring"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;inputSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$projectDir/src/main/resources/openapi/center-api-v1.yaml"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    
    &lt;span class="n"&gt;outputDir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${layout.buildDirectory.get()}/generated"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;validateSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;modelPackage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.example.my-project.model"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;generateModelDocumentation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;configOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"dateLibrary"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"java8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"useJakartaEE"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"useRuntimeException"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"useSpringBoot3"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;I set the generator to &lt;code&gt;spring&lt;/code&gt;. The &lt;code&gt;generatorName&lt;/code&gt; setting controls what kind of code gets generated based on the OpenAPI spec — essentially, it determines the language and framework you're targeting. This setting influences the structure of the generated code, its dependencies, annotations and libraries it uses.&lt;/li&gt;
&lt;li&gt;With &lt;code&gt;inputSpec&lt;/code&gt; I specified the absolute path of the OpenAPI configuration.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;outputDir&lt;/code&gt; property tells &lt;code&gt;openapi-generator&lt;/code&gt; where to the generated code.&lt;/li&gt;
&lt;li&gt;I wanted to ensure the OpenAPI specification is valid, so I set &lt;code&gt;validateSpec&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;With the &lt;code&gt;modelPackage&lt;/code&gt; property, I specified where the tool should place the generated model classes. I didn't specify the &lt;code&gt;apiPackage&lt;/code&gt; property, because I only needed the DTOs.&lt;/li&gt;
&lt;li&gt;Through the &lt;code&gt;configOptions&lt;/code&gt; you can set many additional properties. Here's a breakdown of what I used:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dateLibrary&lt;/code&gt; set to &lt;code&gt;java8&lt;/code&gt; tells the generator tool to use &lt;code&gt;LocalDate&lt;/code&gt; and &lt;code&gt;LocalDateTime&lt;/code&gt; to store temporal data.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useJakartaEE&lt;/code&gt;set to true ensures the generated code uses the jakarta.* namespace instead of javax.*.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useRuntimeException&lt;/code&gt; set to &lt;code&gt;true&lt;/code&gt; instructs the generator to use unchecked exceptions in the generated code.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useSpringBoot3&lt;/code&gt; set to &lt;code&gt;true&lt;/code&gt; is a essential because I used SpringBoot 3.x in this project. 
Spring Boot 3 dropped support for the old javax.* namespace in favor of jakarta.* (a change introduced with Jakarta EE 9+). Without this flag, the generated code might use outdated annotations and could fail to compile. Additionally, it adjusts dependencies and code structure to align with Spring Boot 3 requirements, making the generated code ready to use out of the box.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Further tweaks
&lt;/h2&gt;

&lt;p&gt;I configured the &lt;code&gt;compileJava&lt;/code&gt; task to automatically run the &lt;code&gt;openApiGenerate&lt;/code&gt; task. This way, running the &lt;code&gt;build&lt;/code&gt; task will generate appropriate JAVA from the OpenAPI specification every time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;named&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;JavaCompile&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"compileJava"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;dependsOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"openApiGenerate"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last, but not least, I had to configure the &lt;strong&gt;source sets&lt;/strong&gt;. Without this configuration, the generated code would be outside of the compiler's scope. The following code snippet adds the directory containing the generated code to put the project's sources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;sourceSets&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;java&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;srcDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${layout.buildDirectory.get()}/generated/src/main/java"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  List &lt;code&gt;sourceSets&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This snippet is just a helpful trick. If you're relatively new to Gralde - like I am - and want to check what's in the &lt;code&gt;sourceSets&lt;/code&gt; after applying the above settings, you can use the following snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"printSourceSetInfo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;DefaultTask&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Get the names and source directories at configuration time&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;sourceSetInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sourceSets"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;SourceSetContainer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;associate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;sourceSet&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;sourceSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;sourceSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;srcDirs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;getAbsolutePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;doLast&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;sourceSetInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;srcDirs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SourceSet: $name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;srcDirs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;dir&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"  -&amp;gt; $dir"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This registers a task named printSourceSetInfo, fetches information about the current sourceSets, and prints each set's name along with its associated source directories.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator-gradle-plugin" rel="noopener noreferrer"&gt;OpenAPI Generator Gradle Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/OpenAPITools/openapi-generator/blob/master/docs/generators/spring.md" rel="noopener noreferrer"&gt;Documentation for the spring Generator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gradle</category>
      <category>openapi</category>
      <category>java</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to configure OpenID authentication in Proxmox VE</title>
      <dc:creator>Miklos Halasz</dc:creator>
      <pubDate>Wed, 09 Apr 2025 09:56:02 +0000</pubDate>
      <link>https://forem.com/kubenetic/how-to-configure-openid-authentication-in-proxmox-ve-agh</link>
      <guid>https://forem.com/kubenetic/how-to-configure-openid-authentication-in-proxmox-ve-agh</guid>
      <description>&lt;p&gt;In my homelab, I installed Keycloak because I thought it would be fun to use SSO login for all my locally installed services.&lt;/p&gt;

&lt;p&gt;I'm using Proxmox VE as my hypervisor platform, and it also offers the option to use an OpenID provider—in my case, Keycloak. The settings are fairly straightforward, but I ran into a few pitfalls. Let me describe those first:&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the CA certificates
&lt;/h2&gt;

&lt;p&gt;I use a self-signed CA to create TLS key pairs for my local services. The CA must be installed on both the Keycloak server and the Proxmox nodes. Until I configured the CA correctly, I kept getting a mysterious HTTP 500 error in the Proxmox UI when I wanted to use the OIDC login option, and there were no useful logs in either Proxmox or Keycloak.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install CA on AlmaLinux
&lt;/h3&gt;

&lt;p&gt;Keycloak is installed on AlmaLinux in my homelab. I copied the CA chain into /etc/pki/ca-trust/source/anchors/ and ran the update-ca-trust command as root.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install CA on Debian
&lt;/h3&gt;

&lt;p&gt;Proxmox VE is based on Debian. It stores trusted CA certificates in the /usr/share/ca-certificates/ directory. I copied my CA file there and ran the update-ca-certificates command.&lt;/p&gt;

&lt;p&gt;Initially, I used a .pem file, but it wasn't recognized until I renamed it to .crt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the correct issuer URL
&lt;/h2&gt;

&lt;p&gt;In many tutorials people suggest use the following URL: &lt;code&gt;https://auth.arpa/auth/realms/&amp;lt;realm-name&amp;gt;&lt;/code&gt; but the Keycloak dropped the &lt;code&gt;auth/&lt;/code&gt; part out of the URL around the beginning of 2023. So the correct URL is: &lt;code&gt;https://auth.arpa/realms/&amp;lt;realm-name&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://forum.proxmox.com/threads/keycloak-latest-with-pve-7-4-3.125940/post-549655" rel="noopener noreferrer"&gt;Proxmox Forum&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Keycloak configuration
&lt;/h2&gt;

&lt;p&gt;In Keycloak I created a &lt;em&gt;Realm&lt;/em&gt; that connects to an Active Directory server. Then I configured a &lt;em&gt;Client&lt;/em&gt; for the Proxmox. I didn't provided any description about the properties that I set because the Keycloak UI have some nice description for every of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create a new realm
&lt;/h3&gt;

&lt;p&gt;In the Keycloak UI in the left side there's the realm selector. If you click on the dropdown the &lt;em&gt;Create realm&lt;/em&gt; button appears. Click on it!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhlj5fxl8dfpm04amn4ij.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhlj5fxl8dfpm04amn4ij.png" alt="Keycloak landing page" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then specify the &lt;em&gt;Realm name&lt;/em&gt; and click on the create button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdtxmhgfwfj6fni9u70bn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdtxmhgfwfj6fni9u70bn.png" alt="Create realm" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ensure the newly created realm is selected.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Setup ActiveDirectory as provider
&lt;/h3&gt;

&lt;p&gt;On the navigation bar on the left side click on the &lt;em&gt;User federation&lt;/em&gt; and click on &lt;em&gt;Add LDAP providers&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6bmc0th2jxt1tupbchp3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6bmc0th2jxt1tupbchp3.png" alt="Create user federation provider" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These are the settings that I specified:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;General options&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;UI display name&lt;/em&gt;: Choose a name that you like&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Vendor&lt;/em&gt;: Should be &lt;em&gt;Active Directory&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;em&gt;Connection and authentication settings&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Connection URL&lt;/em&gt;: IP address of the Active Directory. Don't forget to add the protocol (&lt;code&gt;ldap://&lt;/code&gt; or &lt;code&gt;ldaps://&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Bind type&lt;/em&gt;: simple&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Bind DN&lt;/em&gt;: The DN of the user that Keycloak can use to run queries in AD&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Bind credentials&lt;/em&gt;: password  of the bind user&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;em&gt;LDAP searching and updating&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Edit mode&lt;/em&gt;: READ_ONLY&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Users DN&lt;/em&gt;: the distinguished name of the org. unit where the users are stored. E.g.: OU=Users,DC=homelab,DC=be,DC=local&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Username LDAP attribute&lt;/em&gt;: sAMAccountName&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;RDN LDAP attribute&lt;/em&gt;: cn&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;UUID LDAP attribute&lt;/em&gt;: objectGUID&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;User object classes&lt;/em&gt;: person, organizationalPerson, user&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;I left the other settings on their default values.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Create a &lt;em&gt;client&lt;/em&gt; for Proxmox
&lt;/h3&gt;

&lt;p&gt;Select the &lt;em&gt;Clients&lt;/em&gt; option from the menu sidebar, and click on the &lt;em&gt;Create client&lt;/em&gt; button;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9nhtqgrqwl6t02t21sml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9nhtqgrqwl6t02t21sml.png" alt="Create OpenID Connect client" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;General settings&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Client type&lt;/em&gt;: OpenID Connect&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Cliend ID&lt;/em&gt;: Choose an ID for the client. You can reference the client later from proxmox with this ID.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;em&gt;Capability config&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Client authentication&lt;/em&gt;: turn on&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Authorization&lt;/em&gt;: turn on&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;em&gt;Login settings&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Root URL&lt;/em&gt;: &lt;a href="https://proxmox.homelab.arpa:8006/" rel="noopener noreferrer"&gt;https://proxmox.homelab.arpa:8006/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Home URL&lt;/em&gt;: &lt;a href="https://proxmox.homelab.arpa:8006/" rel="noopener noreferrer"&gt;https://proxmox.homelab.arpa:8006/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Valid redirect URIs&lt;/em&gt;: &lt;a href="https://proxmox.homelab.arpa:8006/*" rel="noopener noreferrer"&gt;https://proxmox.homelab.arpa:8006/*&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Web origins&lt;/em&gt;: *&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;And click on the &lt;em&gt;Save&lt;/em&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.keycloak.org/docs/latest/server_admin/index.html#_configuring-realms" rel="noopener noreferrer"&gt;Official documentation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Proxmox configuration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create new realm
&lt;/h3&gt;

&lt;p&gt;To create a new real in Proxmox I used the &lt;a href="https://pve.proxmox.com/pve-docs/pveum.1.html" rel="noopener noreferrer"&gt;&lt;code&gt;pveum&lt;/code&gt;&lt;/a&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pveum realm add keycloak &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--type&lt;/span&gt; openid &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--issuer-url&lt;/span&gt;  https://auth.homelab.arpa/realms/active-directory &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--client-id&lt;/span&gt; XXXX &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--client-key&lt;/span&gt; YYYY &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--username-claim&lt;/span&gt; username
    &lt;span class="nt"&gt;--autocreate&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I turned on the &lt;code&gt;autocreate&lt;/code&gt; property because Proxmox needs users that exists in its own database. And after the login the users created automatically based on the provided information by Keycloak and you don't have to create them manually&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pve.proxmox.com/wiki/User_Management" rel="noopener noreferrer"&gt;More about proxmox user management&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Grant permissions
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create a group for the administrators
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pveum group add admin &lt;span class="nt"&gt;-comment&lt;/span&gt; &lt;span class="s2"&gt;"System Administrators"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Grant administrator privileges for the group
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pveum acl modify / &lt;span class="nt"&gt;-group&lt;/span&gt; admin &lt;span class="nt"&gt;-role&lt;/span&gt; PVEAdmin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add the users from the newly created OpenID realm to the group
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pveum user modify jhon.doe@keycloak &lt;span class="nt"&gt;-group&lt;/span&gt; admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Validate, check user permissions
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pveum user permissions john.doe@keycloak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>proxmox</category>
      <category>keycloak</category>
      <category>linux</category>
      <category>tls</category>
    </item>
    <item>
      <title>How to build containerized JAVA application</title>
      <dc:creator>Miklos Halasz</dc:creator>
      <pubDate>Tue, 08 Apr 2025 13:01:18 +0000</pubDate>
      <link>https://forem.com/kubenetic/how-to-run-containerized-java-application-1iog</link>
      <guid>https://forem.com/kubenetic/how-to-run-containerized-java-application-1iog</guid>
      <description>&lt;p&gt;Take this multi-stage Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;eclipse-temurin:21-jdk-ubi9-minimal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; APP_VERSION&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /application/&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; build/libs/app-${APP_VERSION}.jar app.jar&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;java &lt;span class="nt"&gt;-Djarmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tools &lt;span class="nt"&gt;-jar&lt;/span&gt; app.jar extract &lt;span class="nt"&gt;--layers&lt;/span&gt; &lt;span class="nt"&gt;--launcher&lt;/span&gt;

&lt;span class="c"&gt;################&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; eclipse-temurin:21.0.6_7-jre-ubi9-minimal&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /opt/app/&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; TZ=Europe/Budapest&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /application/app/dependencies/          ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /application/app/snapshot-dependencies/ ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /application/app/spring-boot-loader/    ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /application/app/application            ./&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; exec java ${JAVA_OPTS} org.springframework.boot.loader.launch.JarLauncher&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this &lt;code&gt;Dockerfile&lt;/code&gt;, you can build reproducible container images. It's layered, which means that if you change only the application code, the layers containing the dependencies remain untouched. This saves both space in the image registry and network bandwidth.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;builder&lt;/code&gt; stage uses JDK 21 as the base image and copies the fat (or uber) JAR from the host machine's filesystem, where it was previously built. We use the &lt;code&gt;extract&lt;/code&gt; command in this stage to explode the JAR into separate folders. These folders are then copied into the second stage, with one &lt;code&gt;COPY&lt;/code&gt; command per folder—ensuring each set of files goes into its own layer.&lt;/p&gt;

&lt;p&gt;The second stage uses only the JRE as a base, since we just need the runtime environment to launch the application.&lt;/p&gt;

&lt;p&gt;To run the application, I defined the &lt;code&gt;ENTRYPOINT&lt;/code&gt; instruction. I used the &lt;code&gt;exec&lt;/code&gt; form to ensure the Java process runs as PID 1 inside the container. This is important because, if you wrap the command in a shell script, the script would be PID 1 instead, and when the container is stopped, the shell might not properly forward termination signals to your application—causing the app to shut down abruptly. That can lead to data loss and other issues. &lt;a href="https://stackoverflow.com/a/31840306/14778387" rel="noopener noreferrer"&gt;See explanation on StackOverflow&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My spellchecker complained that I wasn’t using the JSON array syntax for &lt;code&gt;ENTRYPOINT&lt;/code&gt;, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["exec", "java", "${JAVA_OPTS}", "org.springframework.boot.loader.launch.JarLauncher"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, if I used this form, it would fail. In the JSON array form, the &lt;code&gt;${JAVA_OPTS}&lt;/code&gt; variable is &lt;strong&gt;not&lt;/strong&gt; resolved—it is passed literally. The result would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;exec &lt;/span&gt;java &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JAVA_OPTS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; org.springframework.boot.loader.launch.JarLauncher
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which is not what we want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dharampro.substack.com/p/5-ways-i-tune-jvm-in-a-docker-image-ace1d8dba4b3" rel="noopener noreferrer"&gt;5 Ways to tune JVM in a Docker for Spring Boot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.spring.io/spring-boot/specification/executable-jar/launching.html" rel="noopener noreferrer"&gt;Launching Executable Jars&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.spring.io/spring-boot/reference/packaging/container-images/index.html" rel="noopener noreferrer"&gt;Effective container images&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/viascom/spring-boot-3-2-x-jarlauncher-path-a3656f8e69b4" rel="noopener noreferrer"&gt;SpringBoot JARLauncher path&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>containers</category>
      <category>docker</category>
      <category>java</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
