<?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: chama-chomo</title>
    <description>The latest articles on Forem by chama-chomo (@chamachomo).</description>
    <link>https://forem.com/chamachomo</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%2F446794%2Fca54f77e-a31a-4007-a224-b51aefba0977.jpeg</url>
      <title>Forem: chama-chomo</title>
      <link>https://forem.com/chamachomo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/chamachomo"/>
    <language>en</language>
    <item>
      <title>Lyrion Music Server - A Comprehensive Solution for Home Music Listening</title>
      <dc:creator>chama-chomo</dc:creator>
      <pubDate>Tue, 27 Jan 2026 20:31:33 +0000</pubDate>
      <link>https://forem.com/chamachomo/lyrion-music-server-a-comprehensive-solution-for-home-music-listening-5b70</link>
      <guid>https://forem.com/chamachomo/lyrion-music-server-a-comprehensive-solution-for-home-music-listening-5b70</guid>
      <description>&lt;p&gt;I would like to introduce you to a somewhat lesser-known, yet in my opinion unjustly overlooked solution for music listening – whether from local storage or through paid streaming services.&lt;/p&gt;

&lt;p&gt;Setting up LMS may seem not entirely trivial at first and requires some time to gather the necessary information and understand the entire architecture. Therefore, I decided to try to describe how I use this seemingly complex system at first glance.&lt;/p&gt;

&lt;p&gt;While setting up LMS is not entirely trivial and requires some time, I believe my overview will motivate you to try this community-driven open-source solution that I use for listening through both headphones and speakers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In this presentation, I focus exclusively on the LMS ecosystem. Other components of the audio chain (DAC, amplifier, headphones...) are intentionally omitted for clarity.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  LMS SERVICE SETUP (NETWORK LAYER/SERVER)
&lt;/h2&gt;

&lt;p&gt;The core of the entire solution is &lt;strong&gt;Lyrion Music Server (LMS)&lt;/strong&gt; – an open-source implementation of the former Squeezebox platform. It features an extensive ecosystem of compatible clients, modular extensions, and user interfaces (more at &lt;a href="https://lyrion.org" rel="noopener noreferrer"&gt;https://lyrion.org&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;System Architecture:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;LMS utilizes a distributed client-server architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Server layer&lt;/strong&gt; – a service running on a computing node (PC/NAS) that manages the music library and aggregates access to streaming platforms as well as locally stored files. It does not require direct physical connection to the audio chain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Client layer&lt;/strong&gt; – a software renderer that initiates requests for streamed data and transforms it into a digital audio signal directed to the end audio device (DAC, active speakers, DSP...).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Implementation in my environment:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since I personally use the &lt;strong&gt;TrueNAS&lt;/strong&gt; system for my home storage, it was a natural choice for me to install LMS directly on this device. TrueNAS offers LMS in its native application repository, which greatly simplifies the entire installation – there's no need to handle dependencies or manual service configuration.&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%2Fz6qbma4v2yrroml4oq09.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%2Fz6qbma4v2yrroml4oq09.png" alt="Truenas" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For alternative operating systems, comprehensive documentation is available: &lt;a href="https://lyrion.org/getting-started/" rel="noopener noreferrer"&gt;https://lyrion.org/getting-started/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The configuration involved defining paths to music files stored within the same storage system.&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%2Fd4ddg6bkpqkpwwp0j86q.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%2Fd4ddg6bkpqkpwwp0j86q.png" alt="Paths setup" width="800" height="1162"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  INTERFACE SETUP FOR AUDIO CHAIN INTEGRATION (CLIENT)
&lt;/h2&gt;

&lt;p&gt;The LMS ecosystem offers a wide range of hardware and software clients capable of receiving audio streams from the server and integrating into the existing audio chain as a full-fledged digital source.&lt;/p&gt;

&lt;h3&gt;
  
  
  Squeezelite – headless software renderer
&lt;/h3&gt;

&lt;p&gt;In my implementation, I use the &lt;strong&gt;squeezelite&lt;/strong&gt; application (&lt;a href="https://lyrion.org/players-and-controllers/squeezelite" rel="noopener noreferrer"&gt;https://lyrion.org/players-and-controllers/squeezelite&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Squeezelite is a minimalist &lt;strong&gt;headless&lt;/strong&gt; client (i.e., an application without a graphical interface); squeezelite can work as a service "invisibly" in the background of the system.&lt;/p&gt;

&lt;p&gt;This application enables the transmission of digital signal, for example, via USB interface directly to a D/A converter (i.e., a device that converts digital signal to analog for headphones/speakers), while supporting advanced settings for techniques such as upsampling, frequency filtering, and precise control over audio buffers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some advantages of this solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minimal system requirements&lt;/strong&gt; – consumes less CPU and RAM&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Elimination of interfering processes&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stability&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Advanced configuration&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Real Configuration Example
&lt;/h3&gt;

&lt;p&gt;To illustrate the configuration possibilities of squeezelite, I'm showing my current initialization script:&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;#!/bin/sh&lt;/span&gt;
&lt;span class="nb"&gt;exec &lt;/span&gt;2&amp;gt;&amp;amp;1
iw dev wlp3s0 &lt;span class="nb"&gt;set &lt;/span&gt;power_save off &lt;span class="c"&gt;# deactivate WiFi card power saving mode to ensure stable connection&lt;/span&gt;

&lt;span class="nb"&gt;exec&lt;/span&gt; /opt/lms/player/squeezelite &lt;span class="nt"&gt;-o&lt;/span&gt; hw:CARD&lt;span class="o"&gt;=&lt;/span&gt;DX9 &lt;span class="nt"&gt;-a&lt;/span&gt; 100:4:: &lt;span class="nt"&gt;-b&lt;/span&gt; 40000:400000 &lt;span class="nt"&gt;-p&lt;/span&gt; 45 &lt;span class="nt"&gt;-c&lt;/span&gt; flac,pcm,mp3 &lt;span class="nt"&gt;-r&lt;/span&gt; 352800 &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; v:1A:1:32:95.4:104.6:46 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/var/log/squeezelite.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Brief explanation of individual parameters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Audio output configuration:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-o hw:CARD=DX9&lt;/code&gt; – direct communication with audio device via ALSA (&lt;em&gt;Advanced Linux Sound Architecture&lt;/em&gt;). This approach bypasses the system audio mixer, ensuring so-called &lt;em&gt;bit-perfect&lt;/em&gt; transmission (if required) – audio data reaches the DAC exactly in the format in which it was originally stored, without any modifications.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Buffer management:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-a 100:4::&lt;/code&gt; – audio buffer settings (initial size : period : format : mmap)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-b 40000:400000&lt;/code&gt; – stream buffer (40MB output : 400MB input). A larger buffer means greater resistance to temporary fluctuations in network connection (so-called &lt;em&gt;network jitter&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-p 45&lt;/code&gt; – process priority. A higher value ensures that the operating system gives this process priority over others, minimizing the risk of audio &lt;em&gt;underruns&lt;/em&gt; (a situation when the buffer empties and music is necessarily interrupted).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Codec and sample rate:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-c flac,pcm,mp3&lt;/code&gt; – supported audio formats in order of preference&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-r 352800&lt;/code&gt; – maximum supported sampling frequency (&lt;em&gt;sample rate&lt;/em&gt;) 352.8 kHz; higher value theoretically means higher quality.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-R&lt;/code&gt; – activation of resampling if needed&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Upsampling configuration:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The goal is to move demanding digital processing outside the DAC and provide it with already "prepared" data.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-u v:1A:1:32:95.4:104.6:46&lt;/code&gt; – upsampling algorithm definition&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The upsampling conversions in this case will look like below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;96kHz → 192kHz&lt;/li&gt;
&lt;li&gt;44kHz → 352kHz&lt;/li&gt;
&lt;li&gt;88kHz → 352kHz&lt;/li&gt;
&lt;li&gt;192kHz → 192kHz (passed through)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For detailed parameter configuration, I recommend studying: &lt;a href="https://archimago.blogspot.com/2017/12/howto-musings-playing-with-digital_23.html" rel="noopener noreferrer"&gt;https://archimago.blogspot.com/2017/12/howto-musings-playing-with-digital_23.html&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  I use Squeezelite in two different configurations depending on the type of listening:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Headphone listening:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Passively cooled miniPC (HP T640) with a minimalist Linux distribution and squeezelite running in the background. Connection to the server is wireless, connection to the audio system is via USB interface to the D/A converter. The system is configured to activate automatic sleep when no connected D/A converter is detected, so the PC has minimal maintenance requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speaker listening:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Dedicated network streamer &lt;strong&gt;Holo Audio Red&lt;/strong&gt; (&lt;a href="https://kitsunehifi.com/products/holoaudio-red-ddc-network-streamer-1" rel="noopener noreferrer"&gt;https://kitsunehifi.com/products/holoaudio-red-ddc-network-streamer-1&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Connection to the audio system is also via USB interface to the D/A converter. For connection to the LMS server, I use LAN via a standard network cable.&lt;/p&gt;

&lt;p&gt;The original operating system is replaced with &lt;strong&gt;piCorePlayer&lt;/strong&gt; distribution (&lt;a href="https://www.picoreplayer.org/landingzone_option1.html" rel="noopener noreferrer"&gt;https://www.picoreplayer.org/landingzone_option1.html&lt;/a&gt;) – a dedicated system for audio applications compatible with the device's RPi architecture. This approach again allows me to utilize the advanced settings offered by squeezelite, i.e., custom DSP settings, filter curves, and upsampling methods.&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%2F1uk2laq306kxtr6achjl.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%2F1uk2laq306kxtr6achjl.png" alt="Picoreplayer" width="800" height="667"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  CONTROL AND USER INTERFACES
&lt;/h2&gt;

&lt;p&gt;I would like to emphasize that both mentioned variants (miniPC and Holo Red) represent purely headless solutions – applications without their own graphical interface. For actual control, music selection, and metadata display (information about album, artist, cover art...), I use:&lt;/p&gt;

&lt;h3&gt;
  
  
  Material UI – web interface
&lt;/h3&gt;

&lt;p&gt;A modern responsive web interface running directly on the server, accessible from any device with a web browser. It is one of the most popular community interfaces for LMS, offering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Intuitive navigation in the music library&lt;/li&gt;
&lt;li&gt;Multi-room support (each client can play different content)&lt;/li&gt;
&lt;li&gt;Display of advanced metadata and visualizations&lt;/li&gt;
&lt;li&gt;Playlist and playback queue management&lt;/li&gt;
&lt;li&gt;Responsive design optimized for mobile devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 'Material UI' web user interface provided by my NAS, where LMS provides this interface in my case at the address &lt;code&gt;http://mynasserver.local:31101/&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%2Fky5i730x4nybkvnzz1ly.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%2Fky5i730x4nybkvnzz1ly.png" alt="Material skin 1" width="800" height="568"&gt;&lt;/a&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%2F24c7elzus4yuch2144io.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%2F24c7elzus4yuch2144io.png" alt="Material skin 2" width="800" height="568"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If multiple clients are detected, you can choose what content to play on each one separately.&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%2Frbeamy62xyw7yzm16o5c.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%2Frbeamy62xyw7yzm16o5c.png" alt="Material skin 3" width="800" height="1828"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A brief presentation of the 'Material' web interface...&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%2Fjwezpol3j5d0mkwo2rsa.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%2Fjwezpol3j5d0mkwo2rsa.png" alt="Material skin 4" width="800" height="574"&gt;&lt;/a&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%2Fg3ybpphpk8pp96clshf9.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%2Fg3ybpphpk8pp96clshf9.png" alt="Material skin 5" width="800" height="416"&gt;&lt;/a&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%2Fd8rpwo4zo6nu5eo6ws8m.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%2Fd8rpwo4zo6nu5eo6ws8m.png" alt="Material skin 6" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  XTUNE Player for iOS
&lt;/h3&gt;

&lt;p&gt;In the second case, I often use an application created for the iOS ecosystem called XTune (&lt;a href="https://x-music.app/en" rel="noopener noreferrer"&gt;https://x-music.app/en&lt;/a&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%2Ftyt3ikvijlqkz7pev25o.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%2Ftyt3ikvijlqkz7pev25o.png" alt="Xtune walkthrough 1" width="800" height="400"&gt;&lt;/a&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%2Fvmwjv2b6726wjvlr7ps7.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%2Fvmwjv2b6726wjvlr7ps7.png" alt="Xtune walktrough 2" width="800" height="555"&gt;&lt;/a&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%2Fo502ehto6y10hwv17n9e.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%2Fo502ehto6y10hwv17n9e.png" alt="Xtune walkthrough 3" width="800" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although it is paid, the developer continuously improves it and provides one of the highest quality mobile interfaces within the LMS ecosystem. Of course, various more or less successful applications are available for different platforms (iOS, Android, Linux, Web, etc.).&lt;/p&gt;




&lt;h2&gt;
  
  
  COMMUNITY EXTENSIONS
&lt;/h2&gt;

&lt;p&gt;An undeniable advantage of LMS architecture is the ability to use community plugins. I personally use several of them, which in my opinion can often provide comfort comparable to other, even commercial and professional solutions, but without the monthly fees associated with these services.&lt;/p&gt;

&lt;p&gt;Therefore, I would like to dwell on some of these extensions.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Streaming Platform Integration
&lt;/h3&gt;

&lt;p&gt;LMS allows extension with support for additional streaming services (Tidal, Qobuz...). Configuration is straightforward – in most cases, it's enough to install the appropriate plugin and provide login credentials for the given platform.&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%2Ff9gfp5j3k8kfm35xgcu0.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%2Ff9gfp5j3k8kfm35xgcu0.png" alt="Tidal plugin 1" width="800" height="589"&gt;&lt;/a&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%2Fhypk9nemcmw98no8wv6d.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%2Fhypk9nemcmw98no8wv6d.png" alt="Tidal plugin 2" width="800" height="1648"&gt;&lt;/a&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%2Fgpq06pg1hh8mx20mvwtw.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%2Fgpq06pg1hh8mx20mvwtw.png" alt="Qobus plugin 1" width="800" height="583"&gt;&lt;/a&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%2F6s27dx0t2q56ci37wiko.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%2F6s27dx0t2q56ci37wiko.png" alt="Qobuz plugin 2" width="800" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note on Tidal integration:&lt;/strong&gt; The Tidal implementation via LMS unfortunately does not yet support playback of music files in full Hi-Res quality, but sufficient lossless quality is still available.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Material Skin – advanced web interface
&lt;/h3&gt;

&lt;p&gt;As already mentioned, Material UI represents one of the most popular web interfaces for LMS with modern design and rich customization options.&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%2Fyt5bhrx6af5ssgonbma8.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%2Fyt5bhrx6af5ssgonbma8.png" alt="Material skin plugin" width="800" height="908"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Responsive design optimized for desktops, tablets, and mobile devices&lt;/li&gt;
&lt;li&gt;Multi-room playback support with independent control of individual clients&lt;/li&gt;
&lt;li&gt;Visualization of metadata, album covers, and artists&lt;/li&gt;
&lt;li&gt;Advanced playlist and playback queue management&lt;/li&gt;
&lt;li&gt;Theme customization options&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. SqueezeDSP – parametric equalizer
&lt;/h3&gt;

&lt;p&gt;I came across this extension relatively recently, but it ranks among my most valuable extensions. SqueezeDSP provides a full-featured parametric equalizer (&lt;em&gt;PEQ – Parametric Equalizer&lt;/em&gt;) integrated directly into the LMS architecture.&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%2Fggfhbwfiodruukp251ti.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%2Fggfhbwfiodruukp251ti.png" alt="SqueezeDSP plugin" width="800" height="626"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is a parametric equalizer?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PEQ is a tool enabling precise correction of audio signal frequency characteristics. Unlike simple "bass/treble" controls, PEQ offers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Precise control over specific frequencies (for example, 1.2 kHz)&lt;/li&gt;
&lt;li&gt;Adjustment of the affected frequency band width (so-called &lt;em&gt;Q factor&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Setting the gain or attenuation level in decibels (±dB)&lt;/li&gt;
&lt;li&gt;Ability to create complex filter curves with multiple correction points&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Installation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;SqueezeDSP requires several system dependencies (libraries for DSP processing), but the installation itself is not complicated and is well documented within the official LMS plugin repository.&lt;/p&gt;

&lt;p&gt;In my case, I use SqueezeDSP primarily for correcting the frequency characteristics of headphones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TIP:&lt;/strong&gt; SqueezeDSP supports importing ready-made EQ profiles from the &lt;strong&gt;squig.link&lt;/strong&gt; service (&lt;a href="https://squig.link/" rel="noopener noreferrer"&gt;https://squig.link/&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuration example:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is what the final form of the DSP extension looks like in practice – in my case, a full-featured parametric equalizer optimized for the headphones used:&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%2Fi3x725517sc4kym2b7a7.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%2Fi3x725517sc4kym2b7a7.png" alt="DSP at work" width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  CONCLUSION
&lt;/h2&gt;

&lt;p&gt;I hope my journey has inspired you to at least try LMS. If you have any questions, feel free to contact me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy listening!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>audiointerfaces</category>
      <category>digital</category>
      <category>streaming</category>
    </item>
    <item>
      <title>Setting up neovim for web development in Golang</title>
      <dc:creator>chama-chomo</dc:creator>
      <pubDate>Thu, 26 Sep 2024 08:02:49 +0000</pubDate>
      <link>https://forem.com/chamachomo/setting-up-neovim-for-web-development-in-golang-5lc</link>
      <guid>https://forem.com/chamachomo/setting-up-neovim-for-web-development-in-golang-5lc</guid>
      <description>&lt;p&gt;Since I primarily use Neovim for all my development, configuring my environment for comfortable development of my new web project wasn't a quick task. I've decided to share my experience in hopes that it helps others get started as quickly as possible.&lt;/p&gt;

&lt;p&gt;The main topics I want to cover are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Neovim LSP and Treesitter updates&lt;/li&gt;
&lt;li&gt;Templ formatting configuration&lt;/li&gt;
&lt;li&gt;TailwindCSS configuration&lt;/li&gt;
&lt;li&gt;Enabling HTMX in the project&lt;/li&gt;
&lt;li&gt;Setting up file watchers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As for the structure of my project, I simply follow conventions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F092828lnco3giwlo1e5s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F092828lnco3giwlo1e5s.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Neovim LSP and Treesitter updates
&lt;/h2&gt;

&lt;p&gt;To make the LSP aware of Templ files, I had to introduce a few changes to the "lspconfig." However, before doing that, I needed to ensure that all the necessary components would be installed automatically. For this, we use Mason:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    ensure_installed = {
        "templ",
        "html",
        "htmx",
        "tailwindcss",
        "html",
        "cssls",
        "efm",
        "gopls",
    },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we are ready to update the 'lspconfig' configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    -- templ
    lspconfig.templ.setup({
        capabilities = capabilities,
        on_attach = on_attach,
    })

    -- html
    lspconfig.html.setup({
        on_attach = on_attach,
        capabilities = capabilities,
        filetypes = { "html", "templ" },
    })

    -- htmx
    lspconfig.htmx.setup({
        on_attach = on_attach,
        capabilities = capabilities,
        filetypes = { "html", "templ" },
    })

    -- tailwindcss
    lspconfig.tailwindcss.setup({
        on_attach = on_attach,
        capabilities = capabilities,
        filetypes = { "templ", "astro", "javascript", "typescript", "react" },
        init_options = { userLanguages = { templ = "html" } },
        settings = {
            tailwindCSS = {
                includeLanguages = {
                    templ = "html",
                },
            },
        },

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For proper semantic highlighting provided by Treesitter, I installed the following TS plugins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ensure_installed = {
            "javascript",
            "go",
            "html",
            "templ",
            "css",
        },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Templ formatting
&lt;/h2&gt;

&lt;p&gt;This was possibly the most complicated part to automate. Through EFM, I was unable to get the LSP to trigger formatting specifically for Templ files (format on save). I suspect this might be because multiple LSP servers are running for Templ files, which may confuse the LSP.&lt;/p&gt;

&lt;p&gt;After some struggle, I decided to offload this operation to another plugin called 'conform.'&lt;/p&gt;

&lt;p&gt;I installed the 'conform' plugin in my editor.&lt;/p&gt;

&lt;p&gt;Below is my 'conform' configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return {
    "stevearc/conform.nvim",
    opts = {
        formatters_by_ft = {
            templ = { "templ" },
        },
    },
    config = function()
        require("conform").setup({
            format_on_save = {
                timeout_ms = 500,
                lsp_format = "fallback",
            },
        })
    end,
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once set up, it's time to make 'format on save' for Templ files semi-automatic using 'autocmd.'&lt;/p&gt;

&lt;p&gt;Insert the following snippet somewhere in your Lua config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- conform format
vim.api.nvim_create_autocmd("BufWritePre", {
    pattern = "*.templ",
    callback = function(args)
        require("conform").format({ bufnr = args.buf })
    end,
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Originally the pattern part looked like this:&lt;br&gt;
&lt;code&gt;pattern = "*."&lt;/code&gt;&lt;br&gt;
However, I experienced some formatting issues in my Go files, so I tried to be more specific. While I'm not convinced that these problems were solely caused by this change, I no longer experience such issues (perhaps it was just a coincidence).&lt;/p&gt;
&lt;h2&gt;
  
  
  TailwindCSS configuration
&lt;/h2&gt;

&lt;p&gt;As for TailwindCSS, the setup has been quite straightforward. I simply followed the installation manual. Just make sure that 'lspconfig' is aware of 'tailwindcss,' as described in the LSP setup section.&lt;/p&gt;
&lt;h2&gt;
  
  
  Enabling htmx inside the project
&lt;/h2&gt;

&lt;p&gt;This part is also quite straightforward. To enable HTMX in your project, import the HTMX library from a CDN. In my case, I included it in my &lt;code&gt;base.templ&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;MyProject&amp;lt;/title&amp;gt;
  &amp;lt;link rel="icon" type="image/x-icon" href="/public/favicon.ico" /&amp;gt;
  &amp;lt;meta charset="UTF-8" /&amp;gt;
  &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
  &amp;lt;link rel="stylesheet" href="/public/styles.css" /&amp;gt;
  &amp;lt;script src="https://unpkg.com/htmx.org@1.9.9" defer&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting up file watchers
&lt;/h2&gt;

&lt;p&gt;Here’s a refined version of that section:&lt;/p&gt;

&lt;p&gt;An important part of the workflow is ensuring that all my edited files are being watched for changes, so that my web project gets updated 'on-the-fly.'&lt;/p&gt;

&lt;p&gt;We need to watch for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TailwindCSS changes&lt;/li&gt;
&lt;li&gt;Go file changes&lt;/li&gt;
&lt;li&gt;Templ changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Optionally, you can include all file-watching commands in your Makefile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.PHONY: build
build: tailwind-build templ-gen
    @go build -o bin/app .

.PHONY: tailwind-watch
tailwind-watch:
    npx tailwindcss -i views/css/app.css -o public/styles.css --watch

.PHONY: tailwind-build
tailwind-build:
    npx tailwindcss -i views/css/app.css -o public/styles.min.css --minify

.PHONY: templ-watch
templ-watch:
    templ generate --watch

.PHONY: templ-gen
templ-gen:
    templ generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each part, there is a specific way to set up the watcher.&lt;/p&gt;

&lt;h3&gt;
  
  
  tailwind watcher
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;npx tailwindcss -i views/css/app.css -o public/styles.css --watch&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For production you might want to run with &lt;code&gt;--minify&lt;/code&gt; flag, not &lt;code&gt;--watch&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F0hjb34jph54de61xt4ar.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F0hjb34jph54de61xt4ar.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Go watcher
&lt;/h3&gt;

&lt;p&gt;To detect changes in standard Go files, we can use the 'AIR' tool. Follow the fairly simple AIR&lt;a href="https://github.com/air-verse/air" rel="noopener noreferrer"&gt;&lt;/a&gt; manual. Essentially, all I had to do was execute air init inside the project to create the config file, and I updated it if needed. Finally, executing the air command should suffice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fc5m0hybjqak7eblto0jc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fc5m0hybjqak7eblto0jc.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My configuration for reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root = "."
tmp_dir = "tmp"

[build]
  bin = "./tmp/main"
  cmd = "go build -tags dev -o ./tmp/main ."

  delay = 20
  exclude_dir = ["assets", "tmp", "vendor"]
  exclude_file = []
  exclude_regex = [".*_templ.go"]
  exclude_unchanged = false
  follow_symlink = false
  full_bin = ""
  include_dir = []
  include_ext = ["go", "tpl", "tmpl", "templ", "html"]
  kill_delay = "0s"
  log = "build-errors.log"
  send_interrupt = false
  stop_on_error = true

[color]
  app = ""
  build = "yellow"
  main = "magenta"
  runner = "green"
  watcher = "cyan"

[log]
  time = false

[misc]
  clean_on_exit = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Templ changes
&lt;/h3&gt;

&lt;p&gt;'Templ' tooling provides a convenient way for .templ files monitoring inside your project&lt;br&gt;
&lt;code&gt;templ generate --watch&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fnfewojxnirvvo4o3n2sr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fnfewojxnirvvo4o3n2sr.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For production, you may want to run just &lt;code&gt;templ generate&lt;/code&gt; without the &lt;code&gt;--watch&lt;/code&gt; flag; otherwise, after running your binary, you might encounter some errors about missing files.&lt;/p&gt;

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

&lt;p&gt;Thank you for reading! If you come across any inaccuracies or if you would like to delve deeper into any topic, please feel free to contact me. I welcome your feedback and insights.&lt;/p&gt;

&lt;p&gt;Let me know if you'd like any adjustments!&lt;/p&gt;

</description>
      <category>go</category>
      <category>templ</category>
      <category>neovim</category>
      <category>htmx</category>
    </item>
    <item>
      <title>How to Debug Golang app running in K8S (Okteto) using Neovim</title>
      <dc:creator>chama-chomo</dc:creator>
      <pubDate>Sat, 29 Oct 2022 09:26:18 +0000</pubDate>
      <link>https://forem.com/chamachomo/how-to-debug-golang-app-running-in-k8s-okteto-using-neovim-3oh8</link>
      <guid>https://forem.com/chamachomo/how-to-debug-golang-app-running-in-k8s-okteto-using-neovim-3oh8</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Since it is fairly complicated to achieve state described in a title, I've decided to create this post and share my findings. Without too much splattering around, let's right jump to it.&lt;/p&gt;

&lt;p&gt;Please note, I'm not going to describe all technologies I use in my workflow. Since this is quite specific topic, I assume everyone to be on the same wave.&lt;/p&gt;

&lt;p&gt;What we need to make remote debugging in Neovim real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;delve debugger installed in Okteto's container (this is where our application will be running&lt;/li&gt;
&lt;li&gt;Neovim &amp;gt;= 0.8.0&lt;/li&gt;
&lt;li&gt;set of DAP plugins to be installed for Neovim&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Kubernetes (Okteto) part
&lt;/h1&gt;

&lt;p&gt;In order to replace original container with our development container, let's create a manifest for Okteto first.&lt;/p&gt;

&lt;p&gt;In my case it looks like this (your may vary, of course):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: t-s-generator
build:
  t-s-generator:
    image: golang:1.18-alpine3.14
    dockerfile: ./Dockerfile

deploy:
  - &amp;lt;replace by the command you use to deploy your app in k8s&amp;gt;

dev:
  t-s-generator:
    sync:
      rescanInterval: 15
      folders:
        - t-s-generator:/t-s-generator
    forward:
      - 8080:8080
      - 38697:38697  # this port definition is important, it's  port we are going to use for our debugger running inside Okteto and we need to have it accessible from our local PC, so our DAP client can connect to it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we have Okteto environment set up for our GO application and we're able to execute &lt;code&gt;okteto up&lt;/code&gt; successfully, we need to prepare application's binary, so it can be inspected by the debugger.&lt;br&gt;
This is done by compiling our code with some specific parameters. I do it by running below command on my local PC, where my &lt;code&gt;go.mod&lt;/code&gt; can be normally found.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GOOS=linux GOARCH=amd64 go build  -gcflags=all="-N -l" .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;As you can see, I'm creating a binary that can run inside K8S container, which is Alpine Linux in my case, that's why all these Linux related flags. However, those &lt;code&gt;gcflags&lt;/code&gt; are important at the moment.&lt;/p&gt;

&lt;p&gt;Since we're syncing our local folder with code with container's remote folder, this binary should appear also in our dev container inside K8S, once created.&lt;/p&gt;

&lt;p&gt;Okteto set up, binary ready, the next step is to install and run our debugger in a dev container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
✓  Images successfully pulled
✓  Files synchronized
    Context:   kg01.i2.w.com
    Namespace: chama-chomo
    Name:      t-s-generator
    Forward:   8080 -&amp;gt; 8080
               38697 -&amp;gt; 38697
/app # go install github.com/go-delve/delve/cmd/dlv@latest
       ...
       ..
       .
/app # dlv dap -l 127.0.0.1:38697 --log --log-output="dap"
DAP server listening at: 127.0.0.1:38697
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this stage, we're ready to proceed with debug client setup inside Neovim.&lt;/p&gt;

&lt;h1&gt;
  
  
  Neovim part
&lt;/h1&gt;

&lt;p&gt;I'm not going to describe how to install plugins in Neovim, I'll leave it up to you, use your preferred way. As I use Astronvim for configuring Neovim, I'm going to outline exactly what I have inserted into plugins table in init.lua:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  plugins = {
    init = {
      { "mfussenegger/nvim-dap" },
      {
        "rcarriga/nvim-dap-ui",
        config = function() require("dapui").setup() end,
      },
      {
        "theHamsta/nvim-dap-virtual-text",
        config = function() require("nvim-dap-virtual-text").setup() end,
      },
...
..
.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above plugins have certain prerequisites, such as Treesitter etc., please make sure you have all of them installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  DAP configuration
&lt;/h2&gt;

&lt;p&gt;For using DAP in Neovim we need to configure DAP 'configurations' and 'adapters'. I personally prefer creating one wrapper function that I named &lt;code&gt;DapDebug()&lt;/code&gt;, where I set up all previously mentioned and additionally call real DAP client at the end.&lt;/p&gt;

&lt;p&gt;Please note, that for specifying binary that is supposed to be inspected I use an anonymous function that interactively asks for a binary path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;local dap = require "dap"

function DapDebug()
  dap.configurations.go = {
    {
      type = "delve",
      name = "Debug (Remote binary)",
      request = "launch",
      mode = "exec",
      hostName = "127.0.0.1",
      port = "38697",
      program = function()
        local argument_string = vim.fn.input "Path to binary: "
        vim.notify("Debugging binary: " .. argument_string)
        return vim.fn.split(argument_string, " ", true)[1]
      end,
    },
  }

  -- we want to run delve manually
  dap.adapters.delve = {
    type = "server",
    host = "127.0.0.1",
    port = 38697,
  }

  require("dap").continue()
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though it is fully optional, I've set few key bindings for interacting with debugger inside Neovim. I will share them for you, so you can create your own set of bindings as you like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      ["&amp;lt;leader&amp;gt;ds"] = { "&amp;lt;cmd&amp;gt;lua DapDebug()&amp;lt;CR&amp;gt;", desc = "start-dap-debugger" },
      ["&amp;lt;leader&amp;gt;dC"] = { "&amp;lt;cmd&amp;gt;lua require('dapui').close()&amp;lt;CR&amp;gt;", desc = "close-dap-ui" },
      ["&amp;lt;leader&amp;gt;dT"] = { "&amp;lt;cmd&amp;gt;lua require('dapui').toggle()&amp;lt;CR&amp;gt;", desc = "toggle-dap-ui" },
      ["&amp;lt;leader&amp;gt;dO"] = { "&amp;lt;cmd&amp;gt;lua require('dapui').open()&amp;lt;CR&amp;gt;", desc = "open-dap-ui" },
      ["&amp;lt;leader&amp;gt;dc"] = { "&amp;lt;cmd&amp;gt; lua require'dap'.continue()&amp;lt;CR&amp;gt;", desc = "dap-continue" },
      ["&amp;lt;leader&amp;gt;do"] = { "&amp;lt;cmd&amp;gt; lua require'dap'.step_over()&amp;lt;CR&amp;gt;", desc = "dap-step-over" },
      ["&amp;lt;leader&amp;gt;di"] = { "&amp;lt;cmd&amp;gt; lua require'dap'.step_into()&amp;lt;CR&amp;gt;", desc = "dap-step-into" },
      ["&amp;lt;leader&amp;gt;du"] = { "&amp;lt;cmd&amp;gt; lua require'dap'.step_out()&amp;lt;CR&amp;gt;", desc = "dap-step-out" },
      ["&amp;lt;leader&amp;gt;db"] = { "&amp;lt;cmd&amp;gt;lua require'dap'.toggle_breakpoint()&amp;lt;CR&amp;gt;", desc = "set-breakpoint" },
      ["&amp;lt;leader&amp;gt;"] = {
        "&amp;lt;cmd&amp;gt;lua require'dap'.set_breakpoint(vim.fn.input('breakpoint condition: '))&amp;lt;CR&amp;gt;",
        desc = "set-conditional-breakpoint",
      },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Running debugger
&lt;/h1&gt;

&lt;p&gt;In this part I wanted share with you how debugging session in Neovim for the application deployed in Kubernetes (using Okteto) may look like. Enjoy the fruits!&lt;/p&gt;

&lt;p&gt;starting debugging session with &lt;code&gt;:lua DapDebug()&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media.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%2Ffc7t1of3fqbcr9be6bad.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Ffc7t1of3fqbcr9be6bad.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;it's going to ask to provide where the binary is located on the remote side&lt;br&gt;
&lt;a href="https://media.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%2Ffg8t3917puopk1bo47vu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Ffg8t3917puopk1bo47vu.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and finally.. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F2ge94x9wzgorw90yq5tl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F2ge94x9wzgorw90yq5tl.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>neovim</category>
      <category>kubernetes</category>
      <category>debug</category>
      <category>go</category>
    </item>
  </channel>
</rss>
