<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Aadarsha Dhakal]]></title><description><![CDATA[Aadarsha Dhakal]]></description><link>https://blog.aadarshadhakal.com.np</link><generator>RSS for Node</generator><lastBuildDate>Tue, 15 Sep 2026 20:52:45 GMT</lastBuildDate><atom:link href="https://blog.aadarshadhakal.com.np/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Seamlessly Access Proxmox SDN Virtual Machines Remotely Using Tailscale Subnet Routing]]></title><description><![CDATA[If you are a homelab owner with a remote cluster of servers running on Proxmox VE, you might have already used the Software-Defined Networking (SDN) capabilities of Proxmox VE. Isolating workloads is ]]></description><link>https://blog.aadarshadhakal.com.np/seamlessly-access-proxmox-sdn-virtual-machines-remotely-using-tailscale-subnet-routing</link><guid isPermaLink="true">https://blog.aadarshadhakal.com.np/seamlessly-access-proxmox-sdn-virtual-machines-remotely-using-tailscale-subnet-routing</guid><dc:creator><![CDATA[Aadarsha Dhakal]]></dc:creator><pubDate>Tue, 04 Aug 2026 15:36:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/62156cd52c936b90065dc137/cb763c1b-2034-4802-9bc0-d657ed11a4c0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you are a homelab owner with a remote cluster of servers running on <strong>Proxmox VE</strong>, you might have already used the Software-Defined Networking (SDN) capabilities of Proxmox VE. Isolating workloads is very clean with the help of simple SDN zone and virtual network.</p>
<p>But the next problem is obvious: How can you logon securely from your laptop to those isolated VMs, which are not in your home network, without opening ports to the public internet?</p>
<p>In this guide, I'll explain how to connect your Tailscale mesh network to a Proxmox SDN virtual network with Subnet Routing so that you can log into any VM on your private SDN easily via SSH.</p>
<h2>The Architecture Overview</h2>
<p>Rather than having to install the Tailscale client on each individual virtual machine, we can make our Proxmox host (or a gateway container/VM on it) a Tailscale Subnet Router.</p>
<p>Tailscale Network (Tailnet): Your secure overlay network, over all your personal devices.</p>
<p>Simple SDN Zone: Your isolated internal network (e.g., <code>10.10.10.0/24</code>) in which your VMs reside.</p>
<img src="https://cdn.hashnode.com/uploads/covers/62156cd52c936b90065dc137/96742a0c-6afe-4c9b-a63a-5ebd8df79bb2.png" alt="" style="display:block;margin:0 auto" />

<p>The Bridge: Tailscale runs on the Proxmox host, which advertises the SDN subnet route, which means that traffic securely passes through the encrypted tunnel directly into your internal VMs.</p>
<h2>The Process</h2>
<p>The first step is to advertise the SDN Subnet on the Proxmox Host.</p>
<p>Log in your remote Proxmox host using SSH. If you've already installed Tailscale on the host, you must inform Tailscale to advertise the internal IP range of your SDN to your tailnet.</p>
<p>Execute the following command (substitute <code>10.10.10.0/24</code> with the range of your Proxmox Simple SDN network):</p>
<pre><code class="language-bash">sudo tailscale up --advertise-routes=10.10.10.0/24
</code></pre>
<blockquote>
<p>As a note, if your Tailscale daemon was already running, you can use the command <code>tailscale set --advertise-routes=10.10.10.0/24</code> instead.</p>
</blockquote>
<p>Next, you need to enable the IP Forwarding function in the Linux kernel.</p>
<p>By default, Linux will drop packets received from other networks as foreign. Your Proxmox host must be configured as a router between Tailscale (<code>tailscale0</code>) and your SDN bridge, so you need to set up IPv4 and IPv6 packet forwarding.</p>
<ol>
<li>Make a sysctl config file for Tailsale:</li>
</ol>
<pre><code class="language-bash">echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
</code></pre>
<ol>
<li>Apply the changes right away:</li>
</ol>
<pre><code class="language-bash">sudo sysctl -p /etc/sysctl.d/99-tailscale.conf
</code></pre>
<p><img src="align=%22center%22" alt="" /></p>
<p>Then, In the Tailscale admin console, approve the route.</p>
<p>For security reasons, Tailscale doesn't automatically trust nodes that start routing traffic for external subnets. You have to explicitly approve it:</p>
<p>Go to <a href="https://login.tailscale.com/admin/machines">Tailscale Admin Console</a>.</p>
<p>Find your Proxmox host in the list. If you're using a mobile device, click on the three-dot menu on the right and then on “Edit route settings.”</p>
<p>Check the box to <strong>Approve</strong> the advertised subnet route (<code>10.10.10.0/24</code>).</p>
<img src="https://cdn.hashnode.com/uploads/covers/62156cd52c936b90065dc137/5c135ce0-ca00-4624-bd12-2f0438680ec1.png" alt="" style="display:block;margin:0 auto" />

<p>Finally, tell your local machine to accept routes:</p>
<p>For Tailscale to be able to propagate custom routes advertised by your tailnet nodes, it requires you to tell it that this is OK from your local client machine (the laptop you are connecting from).</p>
<p>Run this on your local machine:</p>
<pre><code class="language-bash">sudo tailscale set --accept-routes=true
</code></pre>
<p>Once you have finished, you can verify your work and SSH.</p>
<p>The tunnel is now in use. You should now be able to ping or SSH directly into any VMs that you have in your Proxmox SDN, using their local IP address, from your local machine.</p>
<pre><code class="language-bash">ssh username@10.10.10.55
</code></pre>
<h3>Pro-Tip: Firewall Checks</h3>
<p>If your connection hangs or times out, you can check that UFW or your Proxmox host's firewall are not aggressively blocking forwarding rules from the <code>tailscale0</code> interface. Consider temporarily allowing traffic from Tailsace with:</p>
<pre><code class="language-bash">sudo ufw allow in on tailscale0
</code></pre>
<h2>Conclusion</h2>
<p>And that's it! You now have secure, encrypted access to your virtual machines running in your isolated Proxmox SDN from anywhere in the world, developer-ready without having to punch a hole in your firewall or have to put in a complex port-forwarding rule on your home router.</p>
]]></content:encoded></item><item><title><![CDATA[A Deep Dive into Langfuse]]></title><description><![CDATA[I've recently completed a thorough testing of Langfuse (v3.95.2 OSS), and I wanted to share my observations on its capabilities and overall suitability for AI model observability. My overall impression is that Langfuse is a robust and beneficial tool...]]></description><link>https://blog.aadarshadhakal.com.np/a-deep-dive-into-langfuse</link><guid isPermaLink="true">https://blog.aadarshadhakal.com.np/a-deep-dive-into-langfuse</guid><category><![CDATA[langfuse]]></category><category><![CDATA[#AIObservability ]]></category><dc:creator><![CDATA[Aadarsha Dhakal]]></dc:creator><pubDate>Sun, 10 Aug 2025 17:15:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/0Jk1QCGMz5o/upload/63c0f8bf8731febb85d7d149a5f936d7.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've recently completed a thorough testing of <a target="_blank" href="https://langfuse.com/">Langfuse</a> (v3.95.2 OSS), and I wanted to share my observations on its capabilities and overall suitability for AI model observability. My overall impression is that Langfuse is a robust and beneficial tool, particularly given its specialized focus on AI. While it leverages OpenTelemetry for instrumentation, meaning some features might overlap with other general observability tools, Langfuse's dedicated AI-centric functionalities really give it a distinct advantage, especially in areas like scoring, evaluation, and prompt management.</p>
<h2 id="heading-detailed-observations">Detailed Observations</h2>
<h3 id="heading-multiple-projects-amp-permission-management">Multiple Projects &amp; Permission Management</h3>
<p>One of the first things that stood out to me was Langfuse's sophisticated and granular permission management system. It's incredibly well-designed for environments needing precise control. I found it supports creating multiple projects, each with its own distinct roles and permissions. The availability of both organization-level and project-level roles allows for incredibly fine-tuned control over user access and actions. For instance, an "Owner" at the organization level has extensive permissions, covering everything from project creation and organization management (like API keys, updates, deletion, member management, and billing) to specific project actions. Within individual projects, the roles are even more detailed, covering comprehensive control over functionalities like:</p>
<ul>
<li><p><strong>Project Management:</strong> The ability to read, update, and delete projects, manage members, API keys, and integrations.</p>
</li>
<li><p><strong>Data &amp; Observability:</strong> Permissions to publish, bookmark, tag objects, delete traces, manage scores and configurations, and handle datasets, prompts, models, and evaluation templates.</p>
</li>
<li><p><strong>LLM Integration:</strong> Control over LLM API keys, schemas, and tools.</p>
</li>
<li><p><strong>Data Export &amp; Automation:</strong> Managing batch exports, comments, annotation queues, prompt experiments, audit logs, dashboards, and automations.</p>
</li>
</ul>
<p>This level of control is genuinely impressive and crucial for larger teams or projects with varying access needs.</p>
<h3 id="heading-key-features-i-noticed">Key Features I Noticed</h3>
<p>Several features really caught my attention during testing:</p>
<ol>
<li><p><strong>LLM Playground:</strong> The dedicated LLM Playground is a great addition, making it easy to experiment with different large language models.</p>
</li>
<li><p><strong>LLM Model Connections &amp; Pricing:</strong> Langfuse offers extensive out-of-the-box connections to a wide array of LLM models, and crucially, includes their associated price details. This is incredibly valuable for cost tracking and optimization, something often overlooked in other tools.</p>
</li>
<li><p><strong>Slack Notification Support:</strong> The platform supports Slack notifications, which is fantastic for real-time alerts and keeping teams updated on critical events.</p>
</li>
<li><p><strong>Trace Data Export:</strong> The ability to export trace data to S3-compatible external storages, with the option to schedule these exports, is a practical feature for data retention and analysis.</p>
</li>
<li><p><strong>Dashboards:</strong></p>
<ul>
<li><p><strong>Prebuilt Dashboards:</strong> Langfuse provides useful prebuilt dashboards for cost, usage, and latency. <strong>However, I did notice that community dashboards seem limited at this point.</strong></p>
</li>
<li><p><strong>Custom Dashboards:</strong> While custom dashboards and widgets are supported, a notable limitation I found was the <strong>absence of an option to export or share these dashboards across different projects</strong>.</p>
</li>
</ul>
</li>
<li><p><strong>Prompt Engineering &amp; Management:</strong> This is a significant strength of Langfuse.</p>
<ul>
<li><p>It offers a <strong>centralized repository for managing prompts</strong>, which is a huge advantage for organization.</p>
</li>
<li><p>Users can organize prompts into folders, which is helpful for larger prompt libraries.</p>
</li>
<li><p><strong>Version control for prompts</strong> is supported, allowing for tracking changes and easy rollbacks – a critical feature for iterative prompt development.</p>
</li>
<li><p>Prompts can be called directly within a project using the Langfuse client, which really streamlines integrating managed prompts into applications.</p>
</li>
<li><p>The support for webhook calls and automations on any CRUD changes to a prompt enables dynamic responses to prompt modifications, which opens up interesting possibilities.</p>
</li>
</ul>
</li>
<li><p><strong>Tracing &amp; Metrics:</strong></p>
<ul>
<li><p><strong>Traces:</strong> Langfuse's tracing capabilities are robust, including token and cost tracking. The rate of trace collection can also be throttled using a sampling value.</p>
</li>
<li><p><strong>Sensitive Information Masking:</strong> The platform allows for masking sensitive information before it's sent to the server. This is achieved by writing a masking function that matches sensitive information by pattern and applying it to the Langfuse client – a crucial security feature.</p>
</li>
<li><p><strong>Observations, Sessions, and Users Tracking:</strong> Comprehensive tracking of observations, sessions, and users is well-facilitated.</p>
</li>
<li><p><strong>SDK Support:</strong> My testing primarily used the Python SDK, but it has support for other languages, including JavaScript, Langchain Python, and Langchain JS.</p>
</li>
<li><p><strong>UI Features:</strong> The UI offers various features for analyzing trace data, including span and timeline views, filtering, searching, data download, cost breakdown, and temperature details – making analysis quite user-friendly.</p>
</li>
</ul>
</li>
<li><p><strong>Playground:</strong> The playground has strong out-of-the-box LLM connections for major providers like Google Vertex AI, Google AI Studio, Anthropic, OpenAI, Azure, and Bedrock. It also supports other LLMs through custom API calls, offering flexibility.</p>
</li>
<li><p><strong>Asynchronous Batch Data Collection:</strong> Langfuse is designed to send trace data in batches asynchronously.</p>
</li>
<li><p><strong>Security Monitoring:</strong> I noted that the platform includes capabilities for security monitoring, specifically collecting traces about guardrails like <code>llm_guards</code> and their effectiveness in blocking banned topics.</p>
</li>
<li><p><strong>MCP Server Integration:</strong> The inclusion of an MCP server simplifies integrating langfuse tracing in an application. MCB is available for various AI coding agents like Cursor and Copilot.</p>
</li>
<li><p><strong>Evaluation:</strong> The evaluation framework is a definite standout.</p>
<ul>
<li><p><strong>LLM as Evaluator:</strong> The ability to leverage LLMs themselves to act as evaluators is a powerful feature for automated assessment.</p>
</li>
<li><p><strong>Scoring Categories &amp; Manual Assignment:</strong> The platform allows for setting up scoring configs and manually assigning scores to observations.</p>
</li>
<li><p><strong>Dataset-Based Evaluation:</strong> Running evaluations against predefined datasets of questions and expected outputs provides a structured and repeatable approach to assessing model performance.</p>
</li>
</ul>
</li>
</ol>
<h4 id="heading-pricing">Pricing</h4>
<p>Langfuse offers a sensible tiered pricing model, which I think caters well to various user needs. The most appealing aspect for many will be the <strong>self-hosted (open-source) option</strong>, which provides all core features (observability, evaluation, prompt management, and datasets) for free. This gives users complete control over their data and infrastructure. Langfuse Cloud is also available for those who prefer a managed service.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>To sum it up, Langfuse is, in my observation, a highly effective and specialized tool for AI model observability. Its dedicated features for AI, particularly its robust scoring, evaluation, and prompt management capabilities, provide significant advantages over more general-purpose observability tools. While its use of OpenTelemetry means some functional overlap exists, Langfuse's focused approach gives it a distinct edge in the AI domain. The strong permission management, extensive LLM integrations, and comprehensive tracing and evaluation features make it a truly valuable asset for anyone working with AI models. I found its strengths in prompt management and its advanced evaluation features to be particularly handy.</p>
<h1 id="heading-example-using-annotations">Example Using Annotations</h1>
<pre><code class="lang-plaintext">from google.genai import Client, types
from langfuse import Langfuse, observe
from dotenv import load_dotenv
from openinference.instrumentation.google_genai import GoogleGenAIInstrumentor

load_dotenv()

client = Client(api_key="")
langfuse = Langfuse()
GoogleGenAIInstrumentor().instrument()


@observe(as_type="generation")
def generate():
    langfuse.update_current_trace(user_id="bishal")
    prompt = langfuse.get_prompt("glaze/female", label="production")
    langfuse.update_current_generation(
        prompt=prompt,
    )
    compined_prompt = prompt.compile(name="")
    response = client.models.generate_content(
        model="gemini-2.5-flash",
        config=types.GenerateContentConfig(
            temperature=0.2,

        ),
        contents=compined_prompt,
    )
    print(response.text)


generate()
</code></pre>
<h1 id="heading-screenshots">Screenshots</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754845788245/9ebf2f1b-8901-46e7-8a38-bf209dea43cc.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754845819259/06c39af8-fd5b-409f-a225-f9e27705036f.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754845826721/2042b1bc-1c9f-4032-b57c-0d1b6f1ea2af.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754845833151/a8d7e0e4-67ed-4173-a279-e1b7bd8704f0.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754845840013/5655c275-2bda-4edc-a6a7-1de53c4b4a57.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754845848138/a31a169e-89bf-472e-adbb-febc9873fa76.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754845854638/5e16d752-3321-4062-9bdb-bca0a61c1787.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754845859155/b8a9c0de-d06c-4c27-830b-d71d0d0f1fb3.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754845864457/717826dc-3fdb-45a8-9204-64c2741a08c4.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[When VM Data Goes Missing]]></title><description><![CDATA[It was a tough spot. One of our valued clients, who I've worked with for a while, recently had a major headache: a key virtual machine (VM) on their office server lost important data. Everything was gone – all the VM configuration files and critical ...]]></description><link>https://blog.aadarshadhakal.com.np/when-vm-data-goes-missing</link><guid isPermaLink="true">https://blog.aadarshadhakal.com.np/when-vm-data-goes-missing</guid><dc:creator><![CDATA[Aadarsha Dhakal]]></dc:creator><pubDate>Tue, 15 Jul 2025 06:34:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/6N3QeZpMVUs/upload/185d6453d7d55de709b67a6ec49c1b77.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It was a tough spot. One of our valued clients, who I've worked with for a while, recently had a major headache: a key virtual machine (VM) on their office server lost important data. Everything was gone – all the VM configuration files and critical binary files. This post will walk you through how I stepped in to help them get everything back, what I learned from the experience, and how I'm now working with them to make sure this never happens again.</p>
<h2 id="heading-the-incident">The Incident</h2>
<p>It all started during what should have been routine virtual machine management at my client's site. Without anyone deleting anything on purpose, a system anomaly led to the inadvertent removal of critical files from an office server VM. This immediately caused problems with their services, disrupted daily internal operations, and raised significant concerns about their data integrity.</p>
<h2 id="heading-the-recovery-process">The Recovery Process</h2>
<p>My recovery plan for the client was clear: to restore the entire VM's functionality, including its configuration, binary files, and any application data like MySQL tables. Just copying files wasn't an option, as crucial setup information was missing. I had to approach this with precision and care.</p>
<h3 id="heading-getting-ready-for-recovery">Getting Ready for Recovery</h3>
<ol>
<li><p><strong>Initial Assessment and Containment:</strong> As soon as I was brought in, my first priority was to quickly assess the full extent of the data loss and immediately contain the incident. This was crucial to prevent any further data corruption or potential spread of the issue.</p>
</li>
<li><p><strong>Set Up the Recovery Environment:</strong> I established a safe, isolated recovery environment at the client's location. First, I took a backup of what was left on the affected system. This involved securing all their existing backups too, and making sure I had the right permissions to do the work without affecting their live systems.</p>
</li>
</ol>
<h3 id="heading-1-attempting-direct-disk-recovery-and-vm-reconstruction">1. Attempting Direct Disk Recovery and VM Reconstruction</h3>
<p>This phase involved my initial, fastest attempt at recovery directly from the affected disk, followed by VM reconstruction if needed.</p>
<ol>
<li><p><strong>Attempting Direct File Recovery with TestDisk (My First Line of Defense):</strong></p>
<p> My client's last full VM backup was 15 days old, meaning a direct restore would lead to a significant data loss gap. To minimize this, I first attempted to recover files directly from the deleted disk. This was my fastest path to potentially recovering the most recent data.</p>
<p> The reason this approach holds promise is due to how file deletion typically works in Linux systems. When a file is "deleted" on a Linux filesystem, the data itself isn't immediately erased from the disk sectors. Instead, the operating system primarily removes the file's metadata (like its name, location, and size) from the filesystem's index. The sectors where the data resides are merely marked as available for new data. Until new data overwrites those sectors, the original data might still be present.</p>
<p> I utilized <strong>TestDisk</strong>, a powerful free and open-source data recovery utility, designed to recover lost partitions and make non-booting disks bootable again, but it's also highly effective at recovering deleted files.</p>
<p> Here's a simplified overview of how I used <strong>TestDisk</strong>:</p>
<ul>
<li><p><strong>Installation:</strong> TestDisk was installed on a separate live Linux environment.</p>
<pre><code class="lang-bash">  sudo apt-get install testdisk
</code></pre>
</li>
<li><p><strong>Disk Selection:</strong> I launched <code>testdisk</code> and selected the affected disk drive where the VM data was lost.</p>
</li>
<li><p><strong>Partition Analysis:</strong> <code>TestDisk</code> was instructed to analyze the partition structure, looking for lost partitions or deleted files.</p>
</li>
<li><p><strong>File Recovery:</strong> I navigated through the detected filesystem structure, searching for the critical VM configuration files and binary files that were reported missing. <code>TestDisk</code> attempts to scan the unallocated sectors and rebuild the file metadata, allowing for recovery.</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-outcome-of-testdisk">Outcome of TestDisk:</h3>
<p>    Unfortunately, despite my best efforts and <code>TestDisk</code>'s capabilities, in this specific instance, the tool did not yield the desired results for the critical VM configuration and and binary files. This suggested that the sectors containing the crucial metadata or parts of the files might have already been overwritten or were too fragmented for a complete recovery in this complex VM environment. While this was a setback, it was a necessary and rapid first attempt to minimize data loss.</p>
<h3 id="heading-2-identifying-post-backup-changes-and-data-location">2. Identifying Post-Backup Changes and Data Location</h3>
<p>After <code>TestDisk</code> didn't provide a full recovery, I shifted focus to identifying what data might have changed since the 15-day-old backup. I determined that:</p>
<ul>
<li><p>Most of the core system configuration files would likely be the same as in the backup.</p>
</li>
<li><p>The Gitea repository (for code) and its commit data would definitely have changed.</p>
</li>
<li><p>The MySQL database data would also have been updated.</p>
</li>
</ul>
<p>Luckily, I found that the <code>/var/lib</code> directory on the corrupt disk still contained the data for both MySQL and Gitea. This was a crucial discovery, as these directories typically store the application's persistent data.</p>
<h3 id="heading-3-vm-reconstruction-and-configuration-restoration">3. VM Reconstruction and Configuration Restoration:</h3>
<p>Since a direct recovery wasn't fully successful, I proceeded to recreate the VM environment.</p>
<ul>
<li><p><strong>New VM Setup:</strong> I set up a brand new Ubuntu 22 VM (matching the client's original operating system) to serve as the recovery target.</p>
</li>
<li><p><strong>Package Installation:</strong> All necessary software packages were installed on this new VM.</p>
</li>
<li><p><strong>Configuration Replacement:</strong> I then replaced the default configuration files on the new VM with the configuration files recovered from the 15-day-old backup. This brought the system's core settings back to a known good state.</p>
</li>
</ul>
<h4 id="heading-firewall-rules-and-gitea-data-recovery">Firewall Rules and Gitea Data Recovery:</h4>
<ul>
<li><p><strong>Firewall Rules:</strong> The client's firewall rules were well-documented in their change logs. I manually recreated these rules on the new VM, a process that did not take much time.</p>
</li>
<li><p><strong>Gitea Data Recovery:</strong> For the Gitea application, I simply replaced its data directory on the new VM with the Gitea data directory I had recovered from the corrupt disk's <code>/var/lib</code> location. After carefully managing file permissions and ownership, database recovery was the only thing left to bring Gitea and other services online with its latest data.</p>
</li>
</ul>
<h3 id="heading-4-mysql-data-recovery-the-core-challenge">4. MySQL Data Recovery – The Core Challenge</h3>
<p>After the configurations were restored, the most important task was recovering the MySQL data. This required a detailed approach, especially since the <code>.ibd</code> files were crucial and directly copying them wasn't straightforward due to missing metadata.</p>
<ol>
<li><p><strong>Database Structure Re-initialization:</strong></p>
<p> The foundational step was to re-establish the database's structural integrity. I did this by importing an SQL dump from the older, 15-day-old backup. This recreated all database schemas, table definitions, and initial configurations.</p>
<pre><code class="lang-bash"> sudo mysql &lt; backup.sql
</code></pre>
</li>
<li><p><strong>User Privilege Configuration:</strong></p>
<p> Following the schema restoration, I recreated the necessary database user accounts for the client and reinstated their respective privileges. All user credentials were securely retrieved from backed-up configurations files.</p>
</li>
<li><p><strong>Understanding InnoDB and</strong> <code>.ibd</code> File Restoration:</p>
<p> To effectively restore the client's MySQL database, I leveraged my basic understanding of the MySQL InnoDB storage engine, particularly its transactional properties and recovery mechanisms. This was crucial for handling the <code>.ibd</code> files.</p>
<ul>
<li><p><strong>Temporarily Disable Database Rules:</strong></p>
<p>  To ensure a smooth import process and prevent any integrity errors, I temporarily turned off foreign key checks.</p>
<pre><code class="lang-sql">  <span class="hljs-keyword">SET</span> FOREIGN_KEY_CHECKS = <span class="hljs-number">0</span>;
</code></pre>
</li>
<li><p><strong>Discard Existing Data Links (Tablespaces):</strong> To prepare for the seamless import of the new <code>.ibd</code> files, I first told the database to forget where its current data files were. This effectively unlinks the table definitions from their associated data files, clearing the way for the new ones.</p>
<p>  <strong>Important Note:</strong> This operation disconnects the current data. I always ensure my clients have a complete and verified backup before performing this step!</p>
<pre><code class="lang-sql">  <span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">CONCAT</span>(<span class="hljs-string">'ALTER TABLE `'</span>, TABLE_SCHEMA, <span class="hljs-string">'`.`'</span>, TABLE_NAME, <span class="hljs-string">'` DISCARD TABLESPACE;'</span>) <span class="hljs-keyword">AS</span> sql_command <span class="hljs-keyword">FROM</span> INFORMATION_SCHEMA.TABLES <span class="hljs-keyword">WHERE</span> TABLE_SCHEMA = &lt;DatabaseName&gt; <span class="hljs-keyword">AND</span> <span class="hljs-keyword">ENGINE</span> = <span class="hljs-string">'InnoDB'</span>;
</code></pre>
<p>  I executed each command generated by this query individually to maintain precise control.</p>
</li>
<li><p><strong>Copy and Secure the Data Files:</strong></p>
<p>  After discarding the old links, I carefully copied recovered <code>.ibd</code> files from the corrupt disk into the corresponding database folder on the new VM. It was crucial to set the right ownership for these files. Incorrect permissions would have prevented MySQL from accessing them.</p>
<pre><code class="lang-bash">  /old/var/lib/mysql/&lt;DatabaseName&gt;/ <span class="hljs-comment"># Source path on the corrupt disk</span>
  chown mysql:mysql /var/lib/mysql/&lt;DatabaseName&gt;/*
</code></pre>
</li>
<li><p>Import the New Data (Tablespaces):</p>
<p>  With the .ibd files correctly positioned and permissions accurately configured, the next step was to instruct MySQL to use these new data files. This action successfully linked the table designs with their actual data, bringing them back online.</p>
<pre><code class="lang-sql">  <span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">CONCAT</span>(<span class="hljs-string">'ALTER TABLE `'</span>, TABLE_SCHEMA, <span class="hljs-string">'`.`'</span>, TABLE_NAME, <span class="hljs-string">'` IMPORT TABLESPACE;'</span>) <span class="hljs-keyword">AS</span> sql_command <span class="hljs-keyword">FROM</span> INFORMATION_SCHEMA.TABLES <span class="hljs-keyword">WHERE</span> TABLE_SCHEMA = &lt;DatabaseName&gt; <span class="hljs-keyword">AND</span> <span class="hljs-keyword">ENGINE</span> = <span class="hljs-string">'InnoDB'</span>;
</code></pre>
<p>  I executed the resulting commands sequentially for each table.</p>
</li>
<li><p><strong>Re-enable Database Rules:</strong> Upon the successful import of all data, I promptly turned the foreign key checks back on. This restored the rules that maintain data consistency and integrity within the database for all future operations.</p>
<pre><code class="lang-sql">  <span class="hljs-keyword">SET</span> FOREIGN_KEY_CHECKS = <span class="hljs-number">1</span>;
</code></pre>
</li>
</ul>
</li>
</ol>
<h3 id="heading-5-addressing-advanced-recovery-challenges-schema-mismatches">5. Addressing Advanced Recovery Challenges (Schema Mismatches):</h3>
<p>A significant hurdle emerged when I discovered that 19 out of 127 tables of "Gitea" had different schema. This was primarily due to recent updates to the Gitea application, which prevented a straightforward <code>.ibd</code> file restoration for these specific tables.</p>
<h4 id="heading-leveraging-ibd2sql-for-data-extraction">Leveraging <code>ibd2sql</code> for Data Extraction:</h4>
<p>For tables with schema discrepancies or where standard recovery methods were ineffective, I employed a specialized tool called <strong>ibd2sql</strong>. This utility is designed to parse InnoDB <code>.ibd</code> files and extract the contained data as SQL <code>INSERT</code> statements, even when a complete MySQL instance or <code>.frm</code> files are unavailable.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ibd2sql
<span class="hljs-keyword">for</span> path <span class="hljs-keyword">in</span> $(realpath /old/var/lib/mysql/&lt;DatabaseName&gt;); <span class="hljs-keyword">do</span> python3 main.py <span class="hljs-variable">$path</span> --sql --ddl | mysql; <span class="hljs-keyword">done</span>
</code></pre>
<p><strong>Important Note:</strong> While <strong>ibd2sql</strong> is a powerful recovery tool, it may not fully support all complex data types, and I always recommend thorough post-recovery data validation.</p>
<p>In instances where <code>ibd2sql</code> provided table designs that didn't perfectly align with the current schema, manual intervention was required. I meticulously consulted the client's application's (Gitea's) official database schema changelogs and migration scripts. These resources provided the precise Data Definition Language (DDL) for each table at various points in time, allowing me to accurately reconstruct the correct table definitions.</p>
<p>After carefully modifying the table designs to reflect the correct schema gleaned from the changelogs, I tested them. This involved creating temporary tables and performing small-scale data imports to ensure the design precisely matched the <code>.ibd</code> file structure and that data integrity was fully maintained. This meticulous manual process, combined with leveraging historical schema information, was crucial for successfully recovering data from files that initially seemed lost.</p>
<h3 id="heading-6-validation-and-final-checks">6. Validation and Final Checks</h3>
<ol>
<li><p><strong>System Integrity Checks:</strong> After all files were restored, we performed comprehensive checks to ensure the VM and all its applications were running correctly and without errors. This included verifying file integrity and system services.</p>
</li>
<li><p><strong>Data Validation:</strong> For critical applications like the database and Gitea, I worked closely with the client to validate the restored data, ensuring its accuracy and completeness.</p>
</li>
</ol>
<h2 id="heading-in-conclusion">In Conclusion</h2>
<p>The VM data loss incident at my client's site was undoubtedly a demanding experience, but it also proved to be a profound learning opportunity for both them and me. It underscored the critical importance of proactive prevention, meticulous planning, and the ability to maintain composure and execute methodically under pressure. Getting their data back successfully has strengthened their reliance on robust data management practices, and I am proud to have been their partner in this recovery. By implementing these reinforced best practices, I am confident that I can help my clients turn potential data catastrophes into manageable incidents, ensuring their services run smoothly and their data remains safe.</p>
]]></content:encoded></item><item><title><![CDATA[Self-Hosting Deepseek with GPU Passthrough on Proxmox]]></title><description><![CDATA[The AI world is buzzing about Deepseek, a powerful language model making waves for its performance and open availability. It's generating a lot of hype right now because it offers a compelling alternative to closed-source models, allowing developers ...]]></description><link>https://blog.aadarshadhakal.com.np/self-hosting-deepseek-with-gpu-passthrough-on-proxmox</link><guid isPermaLink="true">https://blog.aadarshadhakal.com.np/self-hosting-deepseek-with-gpu-passthrough-on-proxmox</guid><category><![CDATA[GPU passthrough]]></category><category><![CDATA[proxmox]]></category><category><![CDATA[DeepSeekR1]]></category><category><![CDATA[Deepseek]]></category><category><![CDATA[self-hosted]]></category><category><![CDATA[ollama]]></category><category><![CDATA[GPU]]></category><dc:creator><![CDATA[Aadarsha Dhakal]]></dc:creator><pubDate>Sat, 08 Feb 2025 22:18:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/o9ZvZfNaovA/upload/c2eefdd1950e979cfffaa7dab5a429f0.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The AI world is buzzing about Deepseek, a powerful language model making waves for its performance and open availability. It's generating a lot of hype right now because it offers a compelling alternative to closed-source models, allowing developers and enthusiasts to experiment and build upon it freely. Thanks to Meta's Ollama and Deepseek's commitment to open source, we can even host this impressive model on our own hardware. And that's precisely what I did – breathing new life into my dusty old laptop to run Deepseek locally.</p>
<p>My Hardware:</p>
<ul>
<li><p>CPU: 8 Core, Intel(R) Core(TM) i5-9300H CPU @ 2.40GHz</p>
</li>
<li><p>Memory: 2x 8 GB DDR4</p>
</li>
<li><p>GPU: Nvidia GeForce GTX 1650 Mobile / Max-Q</p>
</li>
<li><p>Proxmox Version: 8.3.2</p>
</li>
</ul>
<p>It's a testament to the fact that you don't necessarily need cutting-edge hardware to explore the world of large language models.</p>
<p>In this guide, I will explain step by step how to set up Deepseek with GPU passthrough in Proxmox. A big shoutout to <a target="_blank" href="https://gist.github.com/KasperSkytte/6a2d4e8c91b7117314bceec84c30016b">KasperSkytte's gist</a> for guiding the GPU passthrough setup.</p>
<h2 id="heading-setup-proxmox-for-gpu-passthrough">Setup Proxmox for GPU passthrough</h2>
<p>First and foremost, we have to make some changes to our Proxmox host to enable GPU passthrough. Let’s start by updating the grub. In the <code>/etc/default/grub</code> file make these changes and update grub.</p>
<pre><code class="lang-plaintext">GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt pcie_acs_override=downstream,multifunction nofb nomodeset video=vesafb:off,efifb:off"
</code></pre>
<pre><code class="lang-plaintext">update-grub
</code></pre>
<p>Next, we have to load VFIO modules. Edit <code>/etc/modules</code> file and add these lines:</p>
<pre><code class="lang-plaintext">vfio
vfio_iommu_type1
vfio_pci
vfio_virqfd
</code></pre>
<p>The commands below enable IOMMU interrupt mapping.</p>
<pre><code class="lang-plaintext">echo "options vfio_iommu_type1 allow_unsafe_interrupts=1" &gt; /etc/modprobe.d/iommu_unsafe_interrupts.conf
echo "options kvm ignore_msrs=1" &gt; /etc/modprobe.d/kvm.conf
</code></pre>
<p>We don’t want our Proxmox host to use the GPU, so let us blacklist the Nvidia drivers in the Proxmox host so that our Proxmox host does not use the GPU.</p>
<pre><code class="lang-plaintext">echo "blacklist radeon" &gt;&gt; /etc/modprobe.d/blacklist.conf
echo "blacklist nouveau" &gt;&gt; /etc/modprobe.d/blacklist.conf
echo "blacklist nvidia" &gt;&gt; /etc/modprobe.d/blacklist.conf
</code></pre>
<p>Now, adding a GPU to VFIO can be tricky. The device IDs and vendor IDs can be confusing, and there's a high chance of making typos that could cause your setup to fail. So, be extra careful during this step.</p>
<p>Run this command.</p>
<pre><code class="lang-plaintext">lspci -v
</code></pre>
<p>This will output all your PCI devices. Look for the lines that show your GPU. It'll look something like this:</p>
<blockquote>
<p><strong>01:00.0</strong> VGA compatible controller: NVIDIA Corporation GP104 [GeForce GTX 1070] (rev a1) (prog-if 00 [VGA controller])</p>
<p><strong>01:00.1</strong> Audio device: NVIDIA Corporation GP104 High Definition Audio Controller (rev a1)</p>
</blockquote>
<p>Make note of the first set of numbers (e.g. 01:00.0 and 01:00.1). We'll need them for the next step.</p>
<p>Run the command below. Replace 01:00 with whatever number was next to your GPU when you ran the previous command:</p>
<pre><code class="lang-plaintext">lspci -n -s 01:00
</code></pre>
<p>Doing this should output your GPU card's <em>Vendor IDs</em>, usually one ID for the GPU and one ID for the Audio bus. It'll look a little something like this:</p>
<blockquote>
<p><strong>01:00.0 0000: 10de:1b81 (rev a1)</strong></p>
<p><strong>01:00.1 0000: 10de:10f0 (rev a1)</strong></p>
</blockquote>
<p>What we want to keep, are these vendor id codes: 10de:1b81 and 10de:10f0.</p>
<p>Now we add the GPU's vendor id's to the VFIO (remember to replace the id's with your own!):</p>
<pre><code class="lang-plaintext">echo "options vfio-pci ids=10de:1b81,10de:10f0 disable_vga=1"&gt; /etc/modprobe.d/vfio.conf
</code></pre>
<p>Finally, run this command. This will update initramfs and reboot your Proxmox Host.</p>
<pre><code class="lang-plaintext">update-initramfs -u
reboot
</code></pre>
<h2 id="heading-change-vm-configuration">Change VM Configuration</h2>
<p>I had already created a Ubuntu VM in my Proxmox Host before this all. So, I just updated my VM configurations. You can create a new VM using the regular VM creation process. But there are a few things you need to configure for your VM to get direct access to the GPU.</p>
<p>Your VM config file, located in <code>/etc/pve/qemu-server/&lt;vmid&gt;.conf</code> should have these settings:</p>
<pre><code class="lang-plaintext">args: -cpu 'host,+kvm_pv_unhalt,+kvm_pv_eoi,hv_vendor_id=NV43FIX,kvm=off'
balloon: 0
bios: ovmf
cpu: host,hidden=1,flags=+pcid
hostpci0: 0000:01:00,pcie=1 # Replace with your GPU's Device ID
machine: q35
</code></pre>
<p>You can add the above lines anywhere in the config file and Proxmox will automatically format it in their respective place on save.</p>
<p>You can also change these configurations from Proxmox GUI.</p>
<h3 id="heading-disable-secure-boot-in-vm">Disable Secure Boot in VM</h3>
<p>If secure boot is enabled in your BIOS, the NVIDIA kernel module might not load properly. So, make sure to disable secure boot in the VM BIOS settings. To enter the VM BIOS, press the Esc key while the Proxmox logo is showing at the start of the boot.</p>
<p><img src="https://i.redd.it/3wcqff3x96491.png" alt="Secure Boot is greyed out in ProxMox... : r/virtualization" /></p>
<h2 id="heading-setting-up-ollama-and-deepseek">Setting up Ollama and Deepseek</h2>
<p>Ollama makes it super easy to run large language models like Deepseek right on your own computer. It handles all the messy bits, so you can focus on using the model.</p>
<p>First, install Ollama. Just open VM console and paste this command:</p>
<pre><code class="lang-plaintext">curl -fsSL https://ollama.com/install.sh | sh
</code></pre>
<p>This command will download and install Ollama, and even take care of setting up the necessary NVIDIA drivers for your GPU.</p>
<p>Next, grab the Deepseek model itself:</p>
<pre><code class="lang-plaintext">ollama pull deepseek-r1
</code></pre>
<p>This downloads the Deepseek model files to your computer. You can see a list of all available models (including Deepseek) by typing:</p>
<pre><code class="lang-plaintext">ollama ls
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739052504345/24956cea-0e73-44f6-a2f0-347753c37755.png" alt="A command line interface displays the results of the command `ollama ls`, showing a container named &quot;deepseek-r1:latest&quot; with ID &quot;0a8c26691023&quot;, size &quot;4.7 GB&quot;, and modified time &quot;9 minutes ago&quot;." class="image--center mx-auto" /></p>
<p>Now, fire up the Deepseek model:</p>
<pre><code class="lang-plaintext">ollama run deepseek-r1
</code></pre>
<p>To check which models are currently running, and to see if your GPU is being used (which it should be ), use this command:</p>
<pre><code class="lang-plaintext">ollama ps
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739052624710/91df5405-695b-4de3-9953-3b42fe336c6a.png" alt class="image--center mx-auto" /></p>
<p>Deepseek should be now running and accessible through Ollama's API. It will be listening on port 11434 on localhost.</p>
<p>To talk to Deepseek using API, you can use a tool like <code>curl</code>. Here's an example:</p>
<pre><code class="lang-plaintext">curl --location 'http://localhost:11434/api/chat' \
--header 'Content-Type: application/json' \
--data '{
  "model": "deepseek-r1",
  "messages": [
    {
      "role": "user",
      "content": "Will AI replace software engineers?"
      }
  ],
  "stream": false
}'
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739052662853/91a0a0d8-8b64-481a-bb6c-d8d78d0a75c9.png" alt class="image--center mx-auto" /></p>
<p>Thank you!</p>
]]></content:encoded></item><item><title><![CDATA[Unattended Upgrades: A Love-Hate Relationship?]]></title><description><![CDATA[Since Ubuntu 16.04, unattended upgrades have been a standard feature. I think, at first glance, the idea of automatic security updates sounds fantastic. It's like having a little helper constantly patching those pesky vulnerabilities while you sleep ...]]></description><link>https://blog.aadarshadhakal.com.np/unattended-upgrades-a-love-hate-relationship</link><guid isPermaLink="true">https://blog.aadarshadhakal.com.np/unattended-upgrades-a-love-hate-relationship</guid><category><![CDATA[unattended-upgrades]]></category><category><![CDATA[Ubuntu]]></category><category><![CDATA[System administration]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Aadarsha Dhakal]]></dc:creator><pubDate>Sat, 01 Feb 2025 15:32:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/xbEVM6oJ1Fs/upload/715e209fd8d288a2848369f7e3750251.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Since Ubuntu 16.04, unattended upgrades have been a standard feature. I think, at first glance, the idea of automatic security updates sounds fantastic. It's like having a little helper constantly patching those pesky vulnerabilities while you sleep (or, you know, work). Essentially, it automatically installs security updates, which is a big plus for keeping your system safe.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738423686853/86c46798-c02f-49a0-9c0a-bb1c0e6d30ba.png" alt class="image--center mx-auto" /></p>
<p>By default, unattended upgrades focus on installing security updates. It usually handles updates from the <code>-security</code> repository. A typical configuration, split across two files, might look like this:</p>
<p><code>/etc/apt/apt.conf.d/20auto-upgrades</code></p>
<pre><code class="lang-plaintext">APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
</code></pre>
<p><code>/etc/apt/apt.conf.d/50unattended-upgrades</code><sup>1</sup></p>
<pre><code class="lang-plaintext">Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}";
    "${distro_id}:${distro_codename}-security";
    "${distro_id}ESMApps:${distro_codename}-apps-security";
    "${distro_id}ESM:${distro_codename}-infra-security";
};
Unattended-Upgrade::Package-Blacklist {
};
Unattended-Upgrade::DevRelease "auto";
</code></pre>
<p>While I appreciate the security focus, I've also seen some downsides. In a corporate environment, where a dedicated team handles patching, unattended upgrades can be a bit of a headache. Imagine a critical system getting updated without the sysadmins knowing! This can lead to unexpected downtime or compatibility issues. That's why Debian itself recommends monitoring your systems closely if you use unattended upgrades. They suggest installing <code>apt-listchanges</code> to get email notifications about updates. You can also check the logs at <code>/var/log/dpkg.log</code> and <code>/var/log/unattended-upgrades/</code> for details on what's been updated.</p>
<p>So, what if you decide unattended upgrades aren't for you? Disabling them is pretty straightforward. You can edit the unattended-upgrades configuration files and adjust the settings. For example, setting <code>APT::Periodic::Unattended-Upgrade</code> to "0" in <code>/etc/apt/apt.conf.d/20auto-upgrades</code> would disable the automatic upgrades. Alternatively, you can remove the <code>unattended-upgrades</code> package altogether:</p>
<p>Bash</p>
<pre><code class="lang-plaintext">sudo apt remove unattended-upgrades
</code></pre>
<p>Beyond just removing the package, you can also use <code>systemctl</code> to mask and disable the related services, providing an extra layer of prevention:</p>
<p>Bash</p>
<pre><code class="lang-plaintext">sudo systemctl mask unattended-upgrades.service
sudo systemctl disable unattended-upgrades.service
</code></pre>
<p>Masking prevents the service from being easily started, even manually, and disabling it prevents it from starting on boot.</p>
<p>If you want to customize the frequency of the unattended upgrades, you can adjust the systemd timers. More information on modifying the download and upgrade schedules can be found here: <a target="_blank" href="https://wiki.debian.org/UnattendedUpgrades#Modifying_download_and_upgrade_schedules_.28on_systemd.29">https://wiki.debian.org/UnattendedUpgrades#Modifying_download_and_upgrade_schedules_.28on_systemd.29</a></p>
<p>In my opinion, unattended upgrades feel a bit like a "Windows" approach. The Linux world, in my experience, generally prefers more control and transparency. That's probably why Debian has changed its default behavior in Debian 12 (Bookworm). It no longer installs <code>unattended-upgrades</code> by default with GNOME. Instead, it notifies you about available updates and lets you decide when to install them. I think this is a much better approach. People who want automatic updates can still opt-in, but it's not forced on everyone.</p>
<p>Ultimately, the decision of whether or not to use unattended upgrades depends on your specific needs and preferences. If you're a home user and want a hands-off approach to security, it might be a good fit. But in a corporate environment, where control and monitoring are crucial, it might be best to manage updates manually.</p>
]]></content:encoded></item><item><title><![CDATA[Backup-Induced CPU Spike in Guest VMs: Proxmox]]></title><description><![CDATA[Proxmox VE is a powerful virtualization platform that allows you to easily create and manage virtual machines (VMs). One of the most important aspects of running VMs is backing them up regularly. This ensures that you can restore your VMs in case of ...]]></description><link>https://blog.aadarshadhakal.com.np/backup-induced-cpu-spike-in-guest-vms-proxmox</link><guid isPermaLink="true">https://blog.aadarshadhakal.com.np/backup-induced-cpu-spike-in-guest-vms-proxmox</guid><category><![CDATA[backup fleecing]]></category><category><![CDATA[proxmox]]></category><category><![CDATA[proxmox backup]]></category><dc:creator><![CDATA[Aadarsha Dhakal]]></dc:creator><pubDate>Thu, 09 Jan 2025 17:16:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736442927018/4dd74a9a-dc5a-4a21-9769-c1580cdfdad2.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Proxmox VE is a powerful virtualization platform that allows you to easily create and manage virtual machines (VMs). One of the most important aspects of running VMs is backing them up regularly. This ensures that you can restore your VMs in case of a hardware failure or other disaster.</p>
<p>However, backing up VMs can sometimes be a performance-intensive task. This can lead to high CPU usage on the Proxmox VE server, as well as slow VM performance.</p>
<p>High CPU usage on the Proxmox VE server can be due to many factors like no of IO workers running, type of compression being used, speed of the underlying storage disks and many more.</p>
<p>In this blog we are going to investigate high CPU usage in the guest VMs during backups.</p>
<h2 id="heading-cause">Cause</h2>
<p>When a backup job for a VM is initiated, QEMU implements a "copy-before-write" mechanism in its block layer. This mechanism ensures that data required for the backup is securely transferred to the backup target <em>before</em> the guest VM overwrites it. To achieve this, the VM's I/O operations are temporarily blocked until the backup process catches up. If the transfer process is slow, the guest VM uses more CPU cycles waiting for I/O operations to finish, resulting in a significant rise in CPU utilization.</p>
<p>In simple words, During the backup transfer process VM I/O is blocked to prevent inconsistencies. If this transfer is slow, I/O operations in the VM will have to wait longer. This block on I/O operations increases the I/O wait time thus increasing the CPU usage.</p>
<p>If you are using Proxmox Backup Server, you can identify the bottleneck using this command.</p>
<pre><code class="lang-plaintext">proxmox-backup-client benchmark --repository &lt;Your repository&gt;
</code></pre>
<p>Example Output:</p>
<pre><code class="lang-plaintext">Uploaded 10 chunks in 17 seconds. 
Time per request: 1742814 microseconds. 
TLS speed: 2.41 MB/s 
SHA256 speed: 1831.82 MB/s 
Compression speed: 555.06 MB/s 
Decompress speed: 664.12 MB/s 
AES256/GCM speed: 1487.40 MB/s 
Verify speed: 483.77 MB/s
</code></pre>
<p>In the above example you can see that TLS speed is the main bottleneck. So, backups are transferring at the slow rate of 2.41 MB/s.</p>
<h2 id="heading-backup-fleecing">Backup Fleecing</h2>
<p>Proxmox VE 8.2 introduced a significant enhancement to its backup process by introducing backup fleecing. This innovative feature aims to mitigate the performance impact of backups on guest VMs, particularly the CPU-intensive "copy-before-write" mechanism.</p>
<p><strong>How Backup Fleecing Works</strong></p>
<ul>
<li><p><strong>Traditional Backup:</strong> In a traditional backup, QEMU ensures data consistency by transferring modified blocks to the backup target <em>before</em> the guest VM overwrites them. This can lead to significant I/O latency for the guest VM, resulting in increased CPU usage and potential performance degradation.</p>
</li>
<li><p><strong>Backup Fleecing in Action:</strong> With fleecing enabled, instead of directly sending modified blocks to the backup target, Proxmox VE temporarily caches them in a "fleecing image" on fast local storage. This local caching significantly reduces I/O latency for the guest VM, as it no longer needs to wait for slower network transfers during the initial write phase.</p>
</li>
</ul>
<p><strong>Benefits of Backup Fleecing:</strong></p>
<ul>
<li><p><strong>Improved Guest VM Performance:</strong> Reduced I/O latency translates to lower CPU usage within the guest VM, minimizing performance impacts and preventing potential freezes.</p>
</li>
<li><p><strong>Enhanced Backup Reliability:</strong> By minimizing I/O contention, fleecing can contribute to more stable and reliable backup operations.</p>
</li>
</ul>
<p><strong>Considerations for Fleecing Storage:</strong></p>
<ul>
<li><p><strong>Performance:</strong> Choose a fast local storage option with high I/O throughput (e.g., LVM-thin, RBD, ZFS with sparse 1).</p>
</li>
<li><p><strong>Thin Provisioning:</strong> Utilizing thin provisioning techniques (like LVM-thin or ZFS with sparse 1) optimizes storage utilization by only allocating space as needed for the fleecing images.</p>
</li>
<li><p><strong>Discard Support:</strong> If your storage supports discard operations, enable them to reclaim unused space within the fleecing images.</p>
</li>
</ul>
<p><strong>Enabling Backup Fleecing:</strong></p>
<ul>
<li><p><strong>Node-wide Configuration:</strong> Edit <code>/etc/vzdump.conf</code> and add:</p>
<pre><code class="lang-plaintext">  fleecing: enabled=true,storage=local-lvm
</code></pre>
<p>  (Replace <code>local-lvm</code> with the name of your chosen fleecing storage.)</p>
</li>
<li><p><strong>Job Specific Configuration using Proxmox VE UI:</strong> Configure fleecing in the "Advanced" tab when editing a specific backup job.</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Backup Fleecing is a valuable feature in Proxmox VE that significantly improves the performance and reliability of backup operations by minimizing the impact on guest VM performance. By strategically implementing fleecing and selecting appropriate storage, you can ensure that your backups complete efficiently while maintaining optimal performance for your virtualized workloads.</p>
]]></content:encoded></item><item><title><![CDATA[A Guide to Installing Proxmox on Dell PowerEdge Server]]></title><description><![CDATA[So, you've got your hands on a Dell server and you're itching to transform it into a powerhouse for virtualization. Enter Proxmox, an open-source virtualization platform that can take your server to the next level. In this guide, we'll walk you throu...]]></description><link>https://blog.aadarshadhakal.com.np/a-guide-to-installing-proxmox-on-dell-poweredge-server</link><guid isPermaLink="true">https://blog.aadarshadhakal.com.np/a-guide-to-installing-proxmox-on-dell-poweredge-server</guid><dc:creator><![CDATA[Aadarsha Dhakal]]></dc:creator><pubDate>Thu, 09 Jan 2025 15:33:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/qwtCeJ5cLYs/upload/ab05e458788228a6fd7bf86c0fd4ba91.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So, you've got your hands on a Dell server and you're itching to transform it into a powerhouse for virtualization. Enter Proxmox, an open-source virtualization platform that can take your server to the next level. In this guide, we'll walk you through the steps to install Proxmox on your Dell server, unlocking its full potential for virtualization tasks.</p>
<h2 id="heading-preparing-your-server-for-proxmox-installation">Preparing Your Server for Proxmox Installation</h2>
<p>Before diving into the Proxmox installation process, there are a few crucial steps to prepare your Dell server:</p>
<h3 id="heading-1-convert-to-non-raid-disk">1. Convert to Non-RAID Disk</h3>
<p>To ensure seamless compatibility with Proxmox, you'll need to switch your server's disks to non-RAID mode. Follow these steps:</p>
<ol>
<li><p>Open Lifecycle Control.</p>
</li>
<li><p>Navigate to System Setup.</p>
</li>
<li><p>Access Advanced Hardware Configuration.</p>
</li>
<li><p>Go to Device Settings.</p>
</li>
<li><p>Under RAID Controller, select Configure.</p>
</li>
<li><p>Opt to Convert to Non-RAID disk, ensuring to check all options before confirming.</p>
</li>
</ol>
<h3 id="heading-2-switch-embedded-sata-to-ahci-mode">2. Switch Embedded SATA to AHCI Mode</h3>
<p>Next up, it's time to configure the SATA settings for optimal performance with Proxmox:</p>
<ol>
<li><p>Within Lifecycle Controller, access System Setup.</p>
</li>
<li><p>Navigate to System BIOS.</p>
</li>
<li><p>Locate SATA Settings.</p>
</li>
<li><p>Choose AHCI mode for the Embedded SATA from the dropdown menu.</p>
</li>
</ol>
<h2 id="heading-installation-steps">Installation Steps</h2>
<p>Now that your server is primed and ready, let's dive into the installation process for Proxmox:</p>
<ol>
<li><p>Open the Virtual Console from iDRAC for remote management.</p>
</li>
<li><p>Download the Proxmox ISO image from the official website.</p>
</li>
<li><p>Create a bootable USB drive with the Proxmox image and connect it to your server.</p>
</li>
<li><p>Access the virtual console and boot from the USB drive.</p>
</li>
<li><p>Select "Install using Graphical Method" to initiate the installation wizard.</p>
</li>
<li><p>Accept the license agreement and proceed.</p>
</li>
<li><p>Choose the BOSS disk (with a 250GB NVMe SSD) and select XFS as the file system.</p>
</li>
<li><p>Set your country, keyboard layout, timezone, and continue.</p>
</li>
<li><p>Enter the hostname, Server Management IP, Gateway, DNS, and choose the Management Interface.</p>
</li>
<li><p>Verify your settings and continue with the installation.</p>
</li>
<li><p>Once completed, reboot your server to finalize the installation.</p>
</li>
</ol>
<h2 id="heading-knowledge-base">Knowledge Base</h2>
<h3 id="heading-understanding-ahci-mode">Understanding AHCI Mode</h3>
<p>AHCI, or Advanced Host Controller Interface, is a standard interface that facilitates communication between software and SATA devices. It supports features like Hot Swapping and Native Command Queueing, optimizing performance for HDDs. While not specifically tailored for SSDs (NVMe is preferred for SSDs), AHCI remains a vital component in ensuring efficient data transfer and storage management.</p>
<h3 id="heading-numa-and-its-significance">NUMA and Its Significance</h3>
<p>Non-Uniform Memory Access (NUMA) architecture plays a crucial role in modern computing environments, particularly in systems with multiple processors or cores. Understanding NUMA can greatly enhance your server's performance and resource allocation efficiency. Check out <a target="_blank" href="https://www.youtube.com/watch?v=Vmb8xGD-LV8">this video</a> for a detailed explanation.</p>
<h2 id="heading-troubleshooting-unable-to-access-proxmox-from-web-browser">Troubleshooting: Unable to Access Proxmox from Web Browser</h2>
<p>Encountering difficulties accessing Proxmox via your web browser? Here's a quick fix:</p>
<p><strong>Solution:</strong> Ensure that the hostname specified in both <code>/etc/hostname</code> and <code>/etc/hosts</code> files matches the hostname displayed in the shell prompt (<code>root@hostname</code>). Consistency between these files is crucial for seamless web access.</p>
<p>By following these steps and understanding the underlying concepts, you can successfully install Proxmox on your Dell server, unleashing its full potential for virtualization tasks. Whether you're setting up a home lab or managing enterprise-level workloads, Proxmox on Dell servers offers a reliable and flexible solution for your virtualization needs.</p>
]]></content:encoded></item><item><title><![CDATA[The GNU Privacy Guard [ A Quick Reference Guide ]]]></title><description><![CDATA[GNUPG is a powerful tool that provides cryptographic privacy and authentication for your data communication. It allows you to sign, encrypt, and decrypt programs, disks, and even emails.
Generating and Managing Keys

Generate a key pair: Use gpg --fu...]]></description><link>https://blog.aadarshadhakal.com.np/the-gnu-privacy-guard-a-quick-reference-guide</link><guid isPermaLink="true">https://blog.aadarshadhakal.com.np/the-gnu-privacy-guard-a-quick-reference-guide</guid><category><![CDATA[gnupg]]></category><category><![CDATA[gpg]]></category><category><![CDATA[manual]]></category><dc:creator><![CDATA[Aadarsha Dhakal]]></dc:creator><pubDate>Sat, 04 May 2024 03:40:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Nel8STCcWy8/upload/f6c5c18291e0b9f66f53f547ae24d1a8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>GNUPG is a powerful tool that provides cryptographic privacy and authentication for your data communication. It allows you to sign, encrypt, and decrypt programs, disks, and even emails.</p>
<h3 id="heading-generating-and-managing-keys">Generating and Managing Keys</h3>
<ul>
<li><p><strong>Generate a key pair:</strong> Use <code>gpg --full-generate-key</code> to create a unique pair of keys for encryption and signing.</p>
</li>
<li><p><strong>List key pairs:</strong> View all your available keys with <code>gpg --list-keys --keyid-format=long</code>.</p>
</li>
<li><p><strong>Display your public key:</strong> Share your public key with others using <code>gpg -a --export &lt;Your Key ID&gt;</code>. <strong>Never share your private key!</strong></p>
</li>
<li><p><strong>Add a subkey:</strong> Subkeys enhance security. Use <code>gpg --edit-key [Your Key ID]</code> followed by <code>addkey</code> and <code>save</code> to add one.</p>
</li>
<li><p><strong>Display subkeys:</strong> After listing keys with <code>gpg --list-keys</code>, edit a key using <code>gpg --edit-key &lt;Your Key ID&gt;</code>. Copy the subkey ID and use <code>gpg -a --export &lt;KEY ID&gt;</code> to display it.</p>
</li>
</ul>
<h3 id="heading-sharing-and-revoking-keys">Sharing and Revoking Keys</h3>
<ul>
<li><p><strong>Send your public key to a public server:</strong> Public keys are meant to be shared. Use <code>gpg --send-keys &lt;Your KEY ID&gt;</code> to upload it.</p>
</li>
<li><p><strong>Generate a revocation certificate:</strong> If your key is compromised, create a revocation certificate with <code>gpg --output revoke.asc --gen-revoke &lt;Your Key ID&gt;</code>. Import it and upload it again to revoke the key.</p>
</li>
</ul>
<h3 id="heading-searching-for-and-importing-public-keys">Searching for and Importing Public Keys</h3>
<ul>
<li><p><strong>Search for a public key:</strong> Find a key using its ID with <code>gpg --search-keys &lt;KEY ID&gt;</code>.</p>
</li>
<li><p><strong>Import a public key:</strong> Once found, import the key with <code>gpg --recv-keys &lt;Key ID&gt;</code>.</p>
</li>
</ul>
<h3 id="heading-understanding-trust">Understanding Trust</h3>
<ul>
<li><p><strong>Beware of key impersonation:</strong> Anyone can create a key with your email address. To verify a key's authenticity, compare fingerprints.</p>
</li>
<li><p><strong>Verify fingerprints:</strong> Use <code>gpg --fingerprint &lt;Person's KEY ID&gt;</code> to see a key's fingerprint. Contact the person and confirm it matches.</p>
</li>
</ul>
<h3 id="heading-the-web-of-trust">The Web of Trust</h3>
<p>The web of trust allows you to trust others based on established trust relationships.</p>
<ul>
<li><p><strong>Trust a key:</strong> Use <code>gpg --edit-key &lt;Person's Key ID&gt;</code>, then <code>trust</code> and <code>save</code> to trust someone's key.</p>
</li>
<li><p><strong>Sign a key:</strong> Signing a key verifies its ownership. Edit the key with <code>gpg --ask-cert-level --edit-key &lt;Person's Key ID&gt;</code>. Look for "Full" trust on the left side of the user ID. Use <code>check</code> to see who signed the key and <code>sign</code> to add your signature. Upload the signed key again to the public server.</p>
</li>
<li><p><strong>Revoke your signature:</strong> If a signed key becomes invalid, use <code>gpg --edit-key &lt;Person's Key ID&gt;</code>, then <code>revsig</code> and <code>save</code> to revoke your signature. Upload the updated key information.</p>
</li>
</ul>
<h3 id="heading-encryption-vs-signing">Encryption vs. Signing</h3>
<ul>
<li><p><strong>Encryption</strong> uses the receiver's public key to scramble the message. Only their private key can decrypt it. Share your public key beforehand for them to receive encrypted messages.</p>
</li>
<li><p><strong>Signing</strong> uses your private key to create a digital signature that verifies the message's origin and integrity. The receiver uses your public key to confirm the signature.</p>
</li>
</ul>
<h3 id="heading-using-gpg-with-git">Using GPG with Git</h3>
<ul>
<li><p><strong>Signed commits:</strong> Add your GPG public key to your GitHub account.</p>
<p>  Go to: <a target="_blank" href="https://github.com/settings/keys">https://github.com/settings/keys</a></p>
<p>  Scroll to the bottom. You'll find Add GPG Key button in the GPG keys section.</p>
<p>  Export your public key using this command:</p>
<p>  <code>gpg --export --armor &lt;KEY ID&gt;</code></p>
<p>  Replace &lt;KEY ID&gt; with your actual KEY ID which you can find by listing your keys using this command: <code>gpg --list-keys</code>.</p>
<p>  If you have not created a gpg key yet, check this out: <a target="_blank" href="https://blog.aadarshadhakal.com.np/the-gnu-privacy-guard-a-quick-reference-guide?t=1725018775683#heading-generating-and-managing-keys">Generating and Managing Keys</a></p>
<p>  Copy the exported public key content and paste it in the Github and save.</p>
<p>  <strong><em><mark>Note:</mark></em></strong> <em><mark> The email associated to your key should match the primary email in your github and email in your local git.</mark></em></p>
</li>
<li><p><strong>Sign a single commit</strong>: Use <code>git commit -S &lt;Your Key ID&gt; -m "message"</code> to create signed commits.</p>
</li>
<li><p><strong>Global configuration:</strong> Set global Git settings with:</p>
<ul>
<li><p><code>git config --global user.signingkey "&lt;Your KEY ID&gt;"</code></p>
</li>
<li><p><code>git config --global commit.gpgsign true</code></p>
</li>
<li><p><code>git config --global tag.gpgsign true</code></p>
</li>
</ul>
</li>
<li><p><strong>Commit email:</strong> Ensure the primary email in your key matches your Git commit email. Set it with:</p>
<ul>
<li><code>git config --global</code> <a target="_blank" href="http://user.email"><code>user.email</code></a> <code>"&lt;Email associated to your Key&gt;"</code></li>
</ul>
</li>
<li><p><strong>Verify signed commits:</strong> Use <code>git log --show-signature</code> to see which commits are signed.</p>
</li>
</ul>
<h3 id="heading-using-gpg-to-encrypt-emails">Using GPG to Encrypt Emails</h3>
<p>GPG keys can encrypt emails. Upload your private key to a trusted email client like Thunderbird. You can then choose to digitally sign or encrypt emails based on the recipient's public key availability.</p>
]]></content:encoded></item><item><title><![CDATA[Dell Server Setup: Unleashing the Power of iDRAC]]></title><description><![CDATA[In this third article of our HCI series, we're diving into the practicalities of deploying our Dell PowerEdge R6525 servers, and a key component of that is setting up the Integrated Dell Remote Access Controller (iDRAC) and Lifecycle Controller(LCC)....]]></description><link>https://blog.aadarshadhakal.com.np/dell-server-setup-unleashing-the-power-of-idrac</link><guid isPermaLink="true">https://blog.aadarshadhakal.com.np/dell-server-setup-unleashing-the-power-of-idrac</guid><category><![CDATA[lcc]]></category><category><![CDATA[Lifecycle Controller]]></category><category><![CDATA[Dell EMC]]></category><category><![CDATA[idrac]]></category><category><![CDATA[server]]></category><dc:creator><![CDATA[Aadarsha Dhakal]]></dc:creator><pubDate>Mon, 25 Mar 2024 16:00:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/vIQDv6tUHYk/upload/ae6a7c092df1b332d40e4554ef81731b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this third article of our HCI series, we're diving into the practicalities of deploying our Dell PowerEdge R6525 servers, and a key component of that is setting up the Integrated Dell Remote Access Controller (iDRAC) and Lifecycle Controller(LCC).</p>
<h3 id="heading-what-are-idrac-and-lcc">What are iDRAC and LCC?</h3>
<p>Let's start with the basics: what exactly is this iDRAC? Well, picture this – you've got your shiny new Dell PowerEdge R6525 servers unboxed and powered up, but how do you manage them without being glued to the server room 24/7? That's where iDRAC comes in.</p>
<p>The Integrated Dell Remote Access Controller (iDRAC) is designed to enhance the productivity of server administrators and improve the overall availability of PowerEdge servers. iDRAC alerts administrators to server problems, enabling remote server management, and reducing the need for an administrator to physically visit the server. iDRAC with Lifecycle Controller allows administrators to deploy, update, monitor and manage Dell servers from any location without the use of agents in a one-to-one or one-to-many method. This out-of-band management allows configuration changes and firmware updates to be managed from Dell EMC, appropriate third-party consoles, and custom scripting directly to iDRAC with Lifecycle Controller using supported industry-standard API’s.</p>
<p>Now, let's talk setup. Getting iDRAC up and running is a crucial first step.</p>
<h3 id="heading-getting-started-setting-up-idrac-and-lcc">Getting Started: Setting Up iDRAC and LCC</h3>
<p>We have three Dell PowerEdge servers at our disposal. I'll walk you through the setup process for just one server. But, no need to worry. The procedure remains identical for the other two. The only variation lies in the IP addresses, which will be unique to each server.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711382105688/47425dc7-1356-4df6-8958-bde629a55943.png" alt class="image--center mx-auto" /></p>
<p>First off, we need to give our server the network details it needs for remote access – think IP and Gateway. During the initial setup phase, our physical presence and direct connection to the server are required. Once this setup is complete, we gain access to iDRAC through a web interface. Simply enter the provided IP address into your browser, and you'll be greeted with a user-friendly interface, resembling the image below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711382016327/821862cf-77bb-45bd-a070-ce98b9d69b04.png" alt class="image--center mx-auto" /></p>
<p>Once that's sorted, it's time to ensure we're up to date. It is very important to keep the server firmware updated to ensure our server is secure and performant. Heading over to the Dell website's support page, we can find the drivers and downloads section for our specific model. Here, we're interested in the BIOS and iDRAC with Lifecycle Controller updates.</p>
<p>Once we've got the firmware files downloaded, it's time to check our current version. Logging into iDRAC, we navigate to the Virtual Console, then the Lifecycle Controller, where we can view our current firmware versions. If there's a newer version available, it's as simple as initiating the upgrade process.</p>
<p>I won't explain the upgrade process here, because it's fairly straightforward. You can upgrade system using iDRAC Web Interface.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711382176769/8511ca8f-3144-4cb4-a9e2-97bc20857834.png" alt class="image--center mx-auto" /></p>
<p>Now, let's get back to the Lifecycle Controller (LCC). This handy feature is like a mini operating system embedded in our Dell servers, allowing us to manage them remotely via iDRAC. It's only booted up when needed, keeping resource usage to a minimum.</p>
<p>When setting up our servers initially, we'll also have the option to configure the network for the Lifecycle Controller. While it's not mandatory, doing so enables us to remotely upgrade drivers from any server within the network.</p>
<p>Just remember, the IP addresses for iDRAC and LCC cannot be the same, nor can they match the OS IP. However, the LCC and OS IPs can be the same, given they're not booted up simultaneously.</p>
<p>And there you have it – the ins and outs of setting up iDRAC and Lifecycle Controller for efficient server management. Stay tuned for more insights as we continue our HCI journey!</p>
]]></content:encoded></item><item><title><![CDATA[Configuring High Availability in Ruijie Switches]]></title><description><![CDATA[Hey there, tech enthusiasts!
In our ongoing journey of building a hyper-converged infrastructure (HCI), we've reached an exciting phase where we're diving into configuring high availability (HA) and link aggregation in our Ruijie Company Switches. Th...]]></description><link>https://blog.aadarshadhakal.com.np/configuring-high-availability-in-ruijie-switches</link><guid isPermaLink="true">https://blog.aadarshadhakal.com.np/configuring-high-availability-in-ruijie-switches</guid><category><![CDATA[vsu]]></category><category><![CDATA[Link Aggregation]]></category><category><![CDATA[ruijie]]></category><category><![CDATA[VSL]]></category><dc:creator><![CDATA[Aadarsha Dhakal]]></dc:creator><pubDate>Sun, 24 Mar 2024 12:14:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/qTEj-KMMq_Q/upload/bd7c35335845e48e5d3a3684aeb32b26.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>Hey there, tech enthusiasts!</p>
<p>In our ongoing journey of building a hyper-converged infrastructure (HCI), we've reached an exciting phase where we're diving into configuring high availability (HA) and link aggregation in our Ruijie Company Switches. These crucial steps will not only enhance the reliability and performance of our infrastructure but also ensure seamless operation even in the face of failures.</p>
<p>Now, before we dive into the nitty-gritty details, I want to mention that we'll skip over the basic setup tasks like assigning IP addresses and enabling SSH. Instead, let's jump straight into the action!</p>
<h3 id="heading-configure-virtual-switch-unit-vsu">Configure Virtual Switch Unit (VSU)</h3>
<p>In Ruijie switches, the concept of Virtual Switch Unit (VSU) is akin to switch stacking in other vendors' switches like CISCO. It allows us to virtualize multiple physical switches into one logical unit for simplified management and enhanced redundancy. Here's how we set it up:</p>
<p>First, we'll configure Virtual Switch Unit (VSU) on each switch. This helps combine multiple switches into one logical unit for easier management. Each VSU gets a unique domain ID, but multiple switches can share the same ID if they're part of the same VSU.</p>
<p>Next, we'll assign a priority number to each switch. This number determines which switch is the main one (active) and which is the backup (passive). The active switch handles configuration changes, while the passive one waits in case the active switch goes down.</p>
<p>Then, we'll configure the interfaces for the Virtual Switch Link (VSL). The number of interfaces needed depends on the switch model, usually between 2 to 8. You can check your switch's configuration guide for details. Here, I've used Ten Gig Interfaces 23 and 24 from both switches.</p>
<p>Finally, we'll switch the mode to virtual. This process backs up the old standalone switch configuration and reloads it when you remove the VSU setup later on. It's like putting the switch into a special mode where it behaves as part of a larger virtual unit.</p>
<h4 id="heading-on-switch-1">On Switch 1:</h4>
<pre><code class="lang-bash">sw_A&gt; <span class="hljs-built_in">enable</span>
sw_A<span class="hljs-comment"># configure terminal</span>
sw_A(config)<span class="hljs-comment"># switch virtual domain 100</span>
sw_A(config-vs-domain)<span class="hljs-comment"># switch 1</span>
sw_A(config-vs-domain)<span class="hljs-comment"># switch 1 priority 200</span>
sw_A(config-vs-domain)<span class="hljs-comment"># switch 1 description sw_A</span>
sw_A(config-vs-domain))<span class="hljs-comment"># exit</span>
sw_A(config)<span class="hljs-comment"># vsl-port</span>
sw_A(config-vsl-port)<span class="hljs-comment"># port-member interface tengigabitethernet 0/23</span>
sw_A(config-vsl-port)<span class="hljs-comment"># port-member interface tengigabitethernet 0/24</span>
sw_A(config)<span class="hljs-comment"># exit</span>
sw_A<span class="hljs-comment"># switch convert mode virtual</span>
</code></pre>
<h4 id="heading-on-switch-2">On Switch 2:</h4>
<pre><code class="lang-bash">sw_B&gt; <span class="hljs-built_in">enable</span>
sw_B<span class="hljs-comment"># configure terminal</span>
sw_B(config)<span class="hljs-comment"># switch virtual domain 100</span>
sw_B(config-vs-domain)<span class="hljs-comment"># switch 2</span>
sw_B(config-vs-domain)<span class="hljs-comment"># switch 2 priority 150</span>
sw_B(config-vs-domain)<span class="hljs-comment"># switch 2 description sw_B</span>
sw_B(config-vs-domain))<span class="hljs-comment"># exit</span>
sw_B(config)<span class="hljs-comment"># vsl-port</span>
sw_B(config-vsl-port)<span class="hljs-comment"># port-member interface Tengigabitethernet 0/23</span>
sw_B(config-vsl-port)<span class="hljs-comment"># port-member interface Tengigabitethernet 0/24</span>
sw_B(config-vsl-port)<span class="hljs-comment"># exit</span>
sw_B<span class="hljs-comment"># switch convert mode virtual</span>
</code></pre>
<h3 id="heading-configuration-vlan">Configuration VLAN</h3>
<p>If you're here after reading my previous article, you might recall that we're planning to use separate VLANs for VM migration and CEPH. Now, let's dive into configuring those. If you haven't had a chance to read the previous article yet, you can find it <a target="_blank" href="https://blog.aadarshadhakal.com.np/building-a-hyperconverged-infrastructure-a-journey-with-ruijie-switches-and-dell-servers">here</a>.</p>
<pre><code class="lang-bash">virtual_switch(config)<span class="hljs-comment"># vlan 4</span>
virtual_switch(config-vlan)<span class="hljs-comment"># name migration</span>
virtual_switch(config-vlan)<span class="hljs-comment"># exit</span>

virtual_switch(config)<span class="hljs-comment"># vlan 5</span>
virtual_switch(config-vlan)<span class="hljs-comment"># name storage</span>
virtual_switch(config-vlan)<span class="hljs-comment"># exit</span>
</code></pre>
<h3 id="heading-configure-link-aggregation-lag">Configure Link Aggregation (LAG)</h3>
<blockquote>
<p>For simplicity, I'll demonstrate the LAG configuration for just one aggregate port, which we'll call aggregatePort3. However, in our setup, we've actually configured LAG for other aggregate ports as well, specifically aggregatePort 4, 5, 9, 10, and 11.</p>
</blockquote>
<p>Link Aggregation is essential for increasing throughput and ensuring redundancy by combining multiple physical links into a single logical link. Let's walk through the steps:</p>
<ol>
<li><strong>Create a Port Group</strong>: Assign two ports from different switches to a group.</li>
</ol>
<pre><code class="lang-bash">// Select an interface of switch 1
virtual_switch(config)<span class="hljs-comment"># interface tenGigabitEthernet 1/0/3</span>
virtual_switch(config-if-TenGigabitEthernet 1/0/3)<span class="hljs-comment"># port-group 3 mode active </span>
virtual_switch(config-if-TenGigabitEthernet 1/0/3)<span class="hljs-comment"># exit</span>

// Select an interface of switch 2
virtual_switch(config)<span class="hljs-comment"># interface tenGigabitEthernet 1/0/3</span>
virtual_switch(config-if-TenGigabitEthernet 1/0/3)<span class="hljs-comment"># port-group 3 mode active  // 3 is group ID </span>
virtual_switch(config-if-TenGigabitEthernet 1/0/3)<span class="hljs-comment"># exit</span>
</code></pre>
<p>Here, we've set up an active-active configuration. This means we're maximizing both throughput and failover safety.</p>
<ol start="2">
<li><strong>Show Aggregate Port Status</strong>: Check the status of the aggregate port.</li>
</ol>
<pre><code class="lang-bash">virtual_switch(config)<span class="hljs-comment"># show aggregatePort 3 summary</span>
// You can see switchport mode is access
</code></pre>
<ol start="3">
<li><strong>Convert to Trunk Mode</strong>: Convert the access mode to trunk. Ruijie switches, LAG operates exclusively in Trunk mode and requires explicit setup.</li>
</ol>
<pre><code class="lang-bash">virtual_switch(config)<span class="hljs-comment"># interface aggregatePort3 </span>
virtual_switch(config-if-AggregatePort 3)<span class="hljs-comment"># switchport mode trunk</span>
virtual_switch(config-if-AggregatePort 3)<span class="hljs-comment"># switchport trunk allowed vlan add 4,5</span>
virtual_switch(config-if-AggregatePort 3)<span class="hljs-comment"># end</span>
</code></pre>
<ol start="4">
<li><strong>Save Configuration</strong>: Ensure all configurations are saved for persistence across reboots.</li>
</ol>
<pre><code class="lang-bash">virtual_switch<span class="hljs-comment"># write</span>
</code></pre>
<p>And there you have it! With VSU for high availability and LAG for improved link utilization and redundancy, our hyperconverged infrastructure is becoming more resilient and efficient by the minute.</p>
<p>Stay tuned for more updates as we continue to fine-tune our setup and explore new horizons in the world of IT infrastructure.</p>
<p>Keep exploring!</p>
]]></content:encoded></item><item><title><![CDATA[Building a Hyperconverged Infrastructure: A Journey with Ruijie Switches and Dell Servers]]></title><description><![CDATA[Hey everyone,
As an IT Infrastructure Engineering Intern, I've had the incredible opportunity to dive headfirst into the world of hyperconverged infrastructure (HCI). It's been a whirlwind of learning, problem-solving, and hands-on experience, and I ...]]></description><link>https://blog.aadarshadhakal.com.np/building-a-hyperconverged-infrastructure-a-journey-with-ruijie-switches-and-dell-servers</link><guid isPermaLink="true">https://blog.aadarshadhakal.com.np/building-a-hyperconverged-infrastructure-a-journey-with-ruijie-switches-and-dell-servers</guid><category><![CDATA[ruijie]]></category><category><![CDATA[HCI]]></category><category><![CDATA[proxmox]]></category><category><![CDATA[switching]]></category><dc:creator><![CDATA[Aadarsha Dhakal]]></dc:creator><pubDate>Sun, 24 Mar 2024 10:43:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/aWslrFhs1w4/upload/91a4b5297adea919ce8dd64c2bca97c8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey everyone,</p>
<p>As an IT Infrastructure Engineering Intern, I've had the incredible opportunity to dive headfirst into the world of hyperconverged infrastructure (HCI). It's been a whirlwind of learning, problem-solving, and hands-on experience, and I couldn't be more excited to share my journey with you.</p>
<p>Let's start by breaking down the components of our setup. We're working with two Ruijie Company Switches, specifically the RG-CS86-24XMG4XS4VS-UPD model. These bad boys pack quite the punch with an ARM processor clocked at 1.25 GHz, 1GB of flash memory, and 1GB of SDRAM. With a plethora of ports ranging from 100M to 10GE, including support for PoE/PoE+/PoE++ and modular power supply and fan slots, these switches are the backbone of our infrastructure.</p>
<p>But what good are switches without servers, right? That's where the Dell Poweredge R6525 servers come into play. We've got three of these powerhouses ready to rock and roll. Equipped with powerful processors, ample memory, and storage capabilities, these servers are the muscle behind our HCI setup.</p>
<p>Now, let's talk about the heart and soul of our hyperconverged infrastructure: the software. We've chosen Proxmox VE as our hypervisor and CEPH for cluster storage. Proxmox VE provides us with a robust virtualization platform that's not only easy to manage but also offers high availability and scalability. And with CEPH handling our storage needs, we can ensure data redundancy, fault tolerance, and seamless scalability across our cluster.</p>
<p>Here is a simple Network Diagram, what we are going to cover in the next few articles.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711276949884/05624704-139c-46b6-9051-b84cf1930f76.png" alt class="image--center mx-auto" /></p>
<p>Let me explain the network diagram to you. We'll mostly skip the Fortinet part for now, as it's not relevant to this series.</p>
<p>Here's the bigger picture of what we'll do:</p>
<p>We'll stack two switches and configure Link Aggregation. We'll define aggregatePort 3, 4, and 5 for server internal communication and aggregatePort 9, 10, and 11 for server management. The links from aggregate port 3, 4, and 5 will connect to the ten-gigabit interfaces of the servers, while links from aggregate port 9, 10, and 11 will connect to the one-gigabit interfaces of the servers. We'll use interface 1 for the uplink and the management port as the management interface.</p>
<p>To manage the Dell server remotely, we'll set up iDRAC. For iDRAC, we'll connect the Fortinet and iDRAC port directly.</p>
<p>On the server, we'll install Proxmox VE, which is a KVM-based open-source hypervisor. We'll then create a cluster of all three nodes. After that, we'll set up Ceph for storage clustering.</p>
<p>Also, we'll create two bonded interfaces: one for server internal communication, which will consist of two ten-gigabit interfaces, and another for management, which will consist of two one-gigabit interfaces.</p>
<p>Additionally, we'll set up VLAN 5 for Ceph and VLAN 4 for VM migration.</p>
<h3 id="heading-are-you-excited">Are you excited ??</h3>
<p>As we embark on this journey, I'll be sharing my insights, challenges, and triumphs with you all. From configuring our switches and servers to setting up our hypervisor and storage cluster, there's a lot to cover. But fear not, I'll do my best to explain the technical jargon along the way without overwhelming you.</p>
<p>So buckle up, folks! We're about to dive deep into the world of hyper-converged infrastructure, and I can't wait to take you along for the ride.</p>
]]></content:encoded></item><item><title><![CDATA[Exploring File Systems for Hyperconverged Infrastructure]]></title><description><![CDATA[When it comes to managing data on our systems, one fundamental aspect to consider is the file system. It's the backbone of how data is stored, accessed, and managed on our storage devices. Among the plethora of file systems available, two prominent c...]]></description><link>https://blog.aadarshadhakal.com.np/exploring-file-systems-for-hyperconverged-infrastructure</link><guid isPermaLink="true">https://blog.aadarshadhakal.com.np/exploring-file-systems-for-hyperconverged-infrastructure</guid><category><![CDATA[ext4]]></category><category><![CDATA[btrfs]]></category><category><![CDATA[ceph]]></category><category><![CDATA[zfs]]></category><category><![CDATA[xfs]]></category><category><![CDATA[proxmox]]></category><dc:creator><![CDATA[Aadarsha Dhakal]]></dc:creator><pubDate>Sun, 24 Mar 2024 07:51:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/M5tzZtFCOfs/upload/ed5ca5b011adcbbbfa799bee404dffad.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When it comes to managing data on our systems, one fundamental aspect to consider is the file system. It's the backbone of how data is stored, accessed, and managed on our storage devices. Among the plethora of file systems available, two prominent categories stand out: Copy on Write (COW) and In Place File Systems. Let's delve into their characteristics, advantages, and use cases.</p>
<p><strong>Copy on Write (COW) vs. In-Place File Systems</strong></p>
<p>At the core of the distinction lies the mechanism of handling write operations:</p>
<ul>
<li><p><strong>Copy on Write (COW)</strong>: In a COW file system, modifications are executed by creating entirely new blocks. These modifications are then linked to the metadata pointers, ensuring atomic writes. This approach is write-intensive but mitigates the risk of corrupted blocks while taking snapshots. We can take snapshots at any point in time without freezing I/O.</p>
</li>
<li><p><strong>In Place File Systems</strong>: Conversely, in an In Place File System, modifications occur by updating existing blocks. These changes are recorded in a journal, facilitating recovery in the event of data corruption due to unforeseen disasters. While less write-intensive, in-place filesystems are not consistent at every point in time. So, to take snapshots of in-place file systems we have to freeze I/O to ensure the snapshot data is not corrupted.</p>
</li>
</ul>
<p><em>Examples</em>: Notable COW file systems include ZFS and BTRFS, while XFS and EXT4 represent In Place File Systems.</p>
<p><strong>ZFS</strong>: Renowned for its advanced features, ZFS encrypts data on write and offers capabilities like storage pooling, scalability, encryption, snapshot backups, and compression. However, these features demand higher CPU and RAM resources.</p>
<p><strong>BTRFS</strong>: Although not as mature as ZFS, BTRFS shares similar characteristics and drawbacks. Its snapshot functionality isn't as robust as ZFS's.</p>
<p><strong>EXT4</strong>: As one of the most mature and fastest file systems, EXT4 is widely used but lacks the advanced features of its counterparts.</p>
<p><strong>XFS</strong>: Preferred by industry leaders like RedHat, XFS is fast, scalable up to exabytes, and suitable for enterprise environments.</p>
<p>Selecting the optimal file system depends on specific use cases and requirements. If you prioritize performance over. In our use case, we opted for the XFS file system. The decision to opt for XFS over ZFS or BTRFS was driven by the requirements of setting up an HCI (Hyperconverged Infrastructure) where CEPH serves as the primary storage option. With CEPH providing all the advanced features of ZFS and BTRFS, the focus shifted towards a faster and more stable file system, making XFS the ideal choice for the task at hand.</p>
<p><strong>In Conclusion</strong></p>
<p>Understanding the nuances between Copy on Write and in-place File Systems empowers us to make informed decisions regarding data management strategies. Whether prioritizing performance, scalability, or disaster recovery, the choice of file system plays a pivotal role in shaping the efficiency and resilience of our storage infrastructure.</p>
]]></content:encoded></item><item><title><![CDATA[Trustworthy Time Tracking for Remote Work: Introducing an Open-Source Solution]]></title><description><![CDATA[Introduction:
Since the COVID-19 pandemic, more and more people are working remotely. It has become a popular choice for organizations and individuals. But there's one big problem: trust. Companies can't always be sure if their remote employees are w...]]></description><link>https://blog.aadarshadhakal.com.np/trustworthy-time-tracking-for-remote-work-introducing-an-open-source-solution</link><guid isPermaLink="true">https://blog.aadarshadhakal.com.np/trustworthy-time-tracking-for-remote-work-introducing-an-open-source-solution</guid><category><![CDATA[Appwrite]]></category><category><![CDATA[Appwrite Hackathon]]></category><category><![CDATA[Flutter]]></category><dc:creator><![CDATA[Aadarsha Dhakal]]></dc:creator><pubDate>Wed, 14 Jun 2023 20:02:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1686771646307/790c4e16-46c8-4ba9-a675-7061e6f0a36b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction:</h2>
<p>Since the COVID-19 pandemic, more and more people are working remotely. It has become a popular choice for organizations and individuals. But there's one big problem: trust. Companies can't always be sure if their remote employees are working or just slacking off. To solve this, many use time tracker software. However, the problem is that employees don't trust these tools. They worry about their privacy and how their data is being used. That's why I came up with an idea for a project: creating an open-source time tracker.</p>
<p>As a freelancer who works remotely, I faced the same trust issues. Existing time trackers just didn't cut it. They were closed-source and made me worried about how my data was being handled. Most of them ran silently in the background, taking screenshots and tracking my keystrokes. This raised privacy concerns and made me even more skeptical. It was clear that we needed a more transparent approach to time tracking.</p>
<p>That's where my project "<strong>Scout"</strong> comes in. I developed an open-source time tracker using <a target="_blank" href="https://hashnode.com/?source=appwrite-hackathon"><strong>Appwrite Cloud</strong></a> for Appwrite Cloud and <a target="_blank" href="https://hashnode.com/?source=appwrite-hackathon"><strong>Hashnode</strong></a> Hackathon, that fosters transparency and trust. By making it open-source, both companies and employees can have a say in how it's built and ensure that it's trustworthy. With open-source software, anyone can look at the code to make sure there are no hidden features compromising privacy or misusing data.</p>
<h2 id="heading-author">Author</h2>
<ul>
<li>Aadarsha Dhakal - @aadarshadhakalg</li>
</ul>
<h2 id="heading-description-of-project">Description of Project</h2>
<p>Scout is a desktop application that currently supports the GNU/Linux operating system. To access Scout, users can easily authenticate using their phone number, and the process is quick and hassle-free.</p>
<p>With Scout, users can send or receive contracts. A contract represents the details of a specific job or position, whether it's a project in freelancing or a corporate job role. Before starting work on a contract, the receiver must accept it. If the receiver is not interested in working on the project, they can simply decline the contract.</p>
<p>Likewise, the sender of the contract has the ability to either end or hold the contract if needed.</p>
<p>When it comes to tracking work hours, employees can clock in to start recording their time using the tracker feature. To ensure that the client is actively working on the project, the tracker will randomly capture screenshots. Once the employee has finished working, they can clock out. They can have multiple such work sessions, and all the records of these sessions are securely saved in the Appwrite cloud database. Both the contract employer and the employee can see the work diary of the contract. The work diary contains the list of the work sessions and the screenshots captured. The screenshots taken during work sessions are stored in the Appwrite cloud storage for easy access and reference.</p>
<h2 id="heading-tech-stack">Tech Stack</h2>
<ul>
<li><p>Flutter</p>
<ul>
<li><p>flutter_bloc</p>
</li>
<li><p>get_it</p>
</li>
</ul>
</li>
<li><p>Appwrite Cloud</p>
<ul>
<li><p>Authentication</p>
</li>
<li><p>Database</p>
</li>
<li><p>Storage</p>
</li>
</ul>
</li>
</ul>
<p>Scout uses Flutter for the frontend and Appwrite Cloud for the backend. It utilizes Appwrite's phone authentication for secure login and its database and storage features to store user data and screenshots</p>
<p>Appwrite's amazing support for Flutter through the <a target="_blank" href="https://pub.dev/packages/appwrite">package</a> made it an excellent choice for Scout.</p>
<h2 id="heading-challenges-i-faced">Challenges I Faced</h2>
<p>During the development of this app, I encountered a few challenges. The primary hurdle was the lack of an existing package in Flutter that could capture desktop screenshots in Linux. To overcome this, I had to develop my own plugin. You can find a blog post detailing the process and development of the plugin.</p>
<p>Plugin: <a target="_blank" href="https://pub.dev/packages/screenshotx">ScreenshotX</a></p>
<p>Blog: <a target="_blank" href="https://blog.aadarshadhakal.com.np/interacting-with-system-services-using-dbus-dart">Interacting with System Services using DBus Dart</a></p>
<p>I spent a significant portion of my time and effort in building this plugin. As a result, the remaining time for project completion was limited, and I started to worry about meeting the deadline.</p>
<p>However, this is where Appwrite proved invaluable. With its comprehensive backend features, I was able to leverage its capabilities and complete the project on time, despite the limited timeframe.</p>
<h2 id="heading-public-code-repo">Public Code Repo</h2>
<p><a target="_blank" href="https://github.com/aadarshadhakalg/scout">https://github.com/aadarshadhakalg/scout</a></p>
<h2 id="heading-demo-link">Demo Link</h2>
<p><a target="_blank" href="https://youtu.be/bOMTwdCgdt4">https://youtu.be/bOMTwdCgdt4</a></p>
]]></content:encoded></item><item><title><![CDATA[Interacting with System Services using DBus Dart]]></title><description><![CDATA[The Appwrite Cloud hackathon was in full swing, and I was eagerly participating, but I still needed to come up with an idea. Suddenly, a brilliant idea popped into my head (I'll keep it a secret for now since the hackathon isn't over yet). The projec...]]></description><link>https://blog.aadarshadhakal.com.np/interacting-with-system-services-using-dbus-dart</link><guid isPermaLink="true">https://blog.aadarshadhakal.com.np/interacting-with-system-services-using-dbus-dart</guid><category><![CDATA[dbus]]></category><category><![CDATA[Linux]]></category><category><![CDATA[Dart]]></category><category><![CDATA[Flutter]]></category><dc:creator><![CDATA[Aadarsha Dhakal]]></dc:creator><pubDate>Fri, 09 Jun 2023 05:37:23 GMT</pubDate><content:encoded><![CDATA[<p>The Appwrite Cloud hackathon was in full swing, and I was eagerly participating, but I still needed to come up with an idea. Suddenly, a brilliant idea popped into my head (I'll keep it a secret for now since the hackathon isn't over yet). The project I decided to pursue involved adding a screenshot feature to a desktop app, allowing users to capture their desktop screen. As a Flutter fanboy, there are no better options for software development other than Flutter. However, I faced a hurdle—there was no existing screenshot plugin that worked seamlessly across Linux, Windows, and macOS. I had two options: either build the plugin myself or abandon the project.</p>
<p>Being someone who loves a challenge, I decided to take the difficult path and create a screenshot plugin specifically for Linux (since I was using Linux myself). I embarked on a journey of researching available resources and studying documentation.</p>
<p>After a day of research, I discovered that Linux has a system called DBus. DBus can be used to call a service that can take desktop screenshots and many other system services.</p>
<h2 id="heading-what-is-dbus">What is Dbus?</h2>
<p>DBus simply is an inter-process communication (IPC) and Remote Procedure Call (RPC) mechanism used in Linux and other Unix-like operating systems. DBus is widely used in the Linux desktop environment for communication between different components, such as applications, system services, and hardware devices. It allows applications to interact with the desktop environment, access system services, and provide functionality to other applications. DBus is just a specification. There exist many libraries that implement this specification. Some of them are, GDbus, QtDbus, libddbus, dbus-java and dbus.dart.</p>
<p>DBus supports two main types of buses: the system bus and the session bus. The system bus is available to all users and processes on the system and provides access to system-wide services. The session bus is specific to a user's login session and facilitates communication between applications within that session.</p>
<h2 id="heading-taking-a-screenshot-using-dbus">Taking a Screenshot Using DBus</h2>
<p><strong>xdg-desktop-portal</strong> is the process we are going to use to take a screenshot. It exposes a series of D-Bus interfaces known as portals under the well-known name <code>org.freedesktop.portal.Desktop</code> and object path <code>/org/freedesktop/portal/desktop</code>.</p>
<p>Objects in DBus are the software entities exposed by processes on the bus, allowing other processes to interact with them. Each object is associated with one or more interfaces that define the methods, signals, and properties that the object supports.</p>
<p>There is a <code>org.freedesktop.portal.Screenshot</code> interface associated with <code>/org/freedesktop/portal/desktop</code> object that has a method to take screenshots.</p>
<p>The Screenshot method takes two parameters, parent_window and options.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686278725645/6be330d2-72cd-4fda-a464-8b7fa4f8100e.png" alt class="image--center mx-auto" /></p>
<p>To validate the functionality of D-Bus before implementing it in my application, I decided to perform a proof of concept. Using the DFeet client, I executed the method, and to my delight, it successfully captured the screenshot and saved it in the user's Pictures folder.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686278840852/5b249d3d-8af1-4987-bdd4-a722c5061566.gif" alt class="image--center mx-auto" /></p>
<h1 id="heading-using-dbus-dart">Using DBus Dart</h1>
<p>There is a Dart client implementation of DBus that allows us to interact with D-Bus from your Dart programs. To add it as a dependency in your Flutter project, you can use the following command:</p>
<pre><code class="lang-bash">flutter pub add dbus
</code></pre>
<p>Additionally, DBus Dart provides a command line tool that enables you to generate Dart classes from D-Bus interface definitions. To install this tool, you can use the following command:</p>
<pre><code class="lang-bash">flutter pub global activate dbus
</code></pre>
<p>With the DBus Dart package and the command line tool installed, you have the necessary tools to work with D-Bus in your Flutter projects. You can leverage the DBus Dart client implementation to communicate with D-Bus services and utilize the command line tool to generate Dart classes that facilitate D-Bus interaction in your codebase.</p>
<p>Now to generate Dart classes we need interface definition. Interface definitions are written in XML. To capture a screenshot using D-Bus, we utilize two interfaces:</p>
<p>The <code>org.freedesktop.portal.Screenshot</code> interface allows us to call the screenshot method.</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">node</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">xmlns:doc</span>=<span class="hljs-string">"http://www.freedesktop.org/dbus/1.0/doc.dtd"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">interface</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"org.freedesktop.portal.Screenshot"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">method</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Screenshot"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">arg</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"s"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"parent_window"</span> <span class="hljs-attr">direction</span>=<span class="hljs-string">"in"</span>/&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">arg</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"a{sv}"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"options"</span> <span class="hljs-attr">direction</span>=<span class="hljs-string">"in"</span>/&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">arg</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"o"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"handle"</span> <span class="hljs-attr">direction</span>=<span class="hljs-string">"out"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">method</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">method</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"PickColor"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">arg</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"s"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"parent_window"</span> <span class="hljs-attr">direction</span>=<span class="hljs-string">"in"</span>/&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">arg</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"a{sv}"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"options"</span> <span class="hljs-attr">direction</span>=<span class="hljs-string">"in"</span>/&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">arg</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"o"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"handle"</span> <span class="hljs-attr">direction</span>=<span class="hljs-string">"out"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">method</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"version"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"u"</span> <span class="hljs-attr">access</span>=<span class="hljs-string">"read"</span>/&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">interface</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">node</span>&gt;</span>
</code></pre>
<p>The <code>org.freedesktop.portal.Request</code> interface allows us to listen to the response signal, which provides us with the path of the captured screenshot image.</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">node</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">xmlns:doc</span>=<span class="hljs-string">"http://www.freedesktop.org/dbus/1.0/doc.dtd"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">interface</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"org.freedesktop.impl.portal.Request"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">method</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Close"</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">method</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">interface</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">node</span>&gt;</span>
</code></pre>
<p>To generate Dart classes from these interface definitions, you can use the DBus Dart command line tool. Make sure you have the tool installed (as mentioned earlier), and then you can run the following command:</p>
<pre><code class="lang-bash">dart-dbus generate-object org.freedesktop.impl.portal.Request.xml -o request.dart
dart-dbus generate-object org.freedesktop.portal.Screenshot.xml -o screenshot.dart
</code></pre>
<p>Once you have generated the Dart classes from the D-Bus interface definitions, you can place them inside the <code>lib</code> folder of your Flutter project. This ensures that the classes are accessible within your project's codebase.</p>
<p>After placing the generated Dart classes in the <code>lib</code> folder, you are now ready to implement the necessary code to interact with D-Bus and perform the desired actions, such as capturing screenshots.</p>
<p>First, make sure you have the necessary dependencies added to your project.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:dbus/dbus.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'request.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'screenshot.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:uuid/uuid.dart'</span>;
</code></pre>
<p>To begin, you need to initialize a D-Bus session bus client. Alongside the client initialization, we need uuid package to generate a unique identifier token. The purpose of using a token is to ensure that the request and its corresponding response are associated correctly. The token can contain only alphanumeric characters.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> token = <span class="hljs-keyword">const</span> Uuid()
      .v4()
      .replaceAll(<span class="hljs-string">'-'</span>, <span class="hljs-string">''</span>)
      .replaceAll(<span class="hljs-string">'{'</span>, <span class="hljs-string">''</span>)
      .replaceAll(<span class="hljs-string">'}'</span>, <span class="hljs-string">''</span>);
  <span class="hljs-keyword">var</span> client = DBusClient.session();
</code></pre>
<p>To effectively listen to the response of the Screenshot method, we need to set up a response listener. Prior to that, we must obtain the unique name acquired by the current client. Once we have obtained the name, we can proceed to create the request object using the unique name, client, and the unique token we generated earlier. With the request object in place, we can then activate the response listener.</p>
<p>When a new response event is received, the "results" property of the event will contain the URI (Uniform Resource Identifier) of the captured screenshot. This URI serves as the location or path where the screenshot image is stored. By accessing this URI from the response event, we can retrieve the necessary information about the captured screenshot and utilize it as desired within our application.</p>
<pre><code class="lang-dart">client.nameAcquired.listen(
      (event) {
        <span class="hljs-built_in">String?</span> serviceName = event.replaceAll(<span class="hljs-string">':'</span>, <span class="hljs-string">''</span>).replaceAll(<span class="hljs-string">'.'</span>, <span class="hljs-string">'_'</span>);

        <span class="hljs-keyword">var</span> request = OrgFreedesktopPortalRequest(
          client,
          <span class="hljs-string">'org.freedesktop.portal.Desktop'</span>,
          path: DBusObjectPath(
              <span class="hljs-string">"/org/freedesktop/portal/desktop/request/<span class="hljs-subst">$serviceName</span>/<span class="hljs-subst">$token</span>"</span>),
        );

        <span class="hljs-built_in">RegExp</span> filepathReg =
            <span class="hljs-built_in">RegExp</span>(<span class="hljs-string">r"(?:(?:file?|ftp):\/\/)?[\w\/\-?=%.]+\.[\w\/\-?=%.]+"</span>);
        request.response.listen(
          (event) {
            screenshotPath = filepathReg
                .allMatches(event.results[<span class="hljs-string">'uri'</span>].toString())
                .first
                .group(<span class="hljs-number">0</span>);
          },
        );
      },
    );
</code></pre>
<p>With the response listener set up, we can proceed to create a function that invokes the Screenshot method. To initiate the Screenshot method, we need to create an object of the Screenshot interface. Once the object is created, we can call the Screenshot method by providing the unique token that we generated earlier as a parameter. This token helps to ensure the proper association of the request with its corresponding response. By invoking the Screenshot method, we trigger the process of capturing the desired screenshot.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">void</span> takeScreenshot() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">var</span> object = OrgFreedesktopPortalScreenshot(
      client,
      <span class="hljs-string">'org.freedesktop.portal.Desktop'</span>,
      path: DBusObjectPath(
        <span class="hljs-string">"/org/freedesktop/portal/desktop"</span>,
      ),
    );
    <span class="hljs-keyword">await</span> object.callScreenshot(
      <span class="hljs-string">""</span>,
      {
        <span class="hljs-string">"handle_token"</span>: DBusString(token),
        <span class="hljs-string">"interactive"</span>: <span class="hljs-keyword">const</span> DBusBoolean(<span class="hljs-keyword">false</span>),
      },
    );
  }
</code></pre>
<p>It is important to remember to close the client once we have finished using it.</p>
<pre><code class="lang-dart">client.close();
</code></pre>
<p>By calling the takeScreenshot method, we can capture the screenshot and receive the path of the screenshot image file through the listener. This demonstrates how we can interact with system services in Linux.</p>
<p>While this tutorial focused on the Screenshot interface as an example, you can utilize the same approach to create applications that handle file management, network management, notification handling, and more.</p>
<p>Here is the complete code for this tutorial:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:dbus/dbus.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'org.freedesktop.portal.Request.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'org.freedesktop.portal.Screenshot.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:uuid/uuid.dart'</span>;

<span class="hljs-keyword">void</span> main() {
  runApp(<span class="hljs-keyword">const</span> MyApp());
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> MyApp({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-comment">// This widget is the root of your application.</span>
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> MaterialApp(
      title: <span class="hljs-string">'Flutter Demo'</span>,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: <span class="hljs-keyword">true</span>,
      ),
      home: <span class="hljs-keyword">const</span> MyHomePage(title: <span class="hljs-string">'Flutter Demo Home Page'</span>),
    );
  }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyHomePage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-keyword">const</span> MyHomePage({<span class="hljs-keyword">super</span>.key, <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.title});
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> title;

  <span class="hljs-meta">@override</span>
  State&lt;MyHomePage&gt; createState() =&gt; _MyHomePageState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_MyHomePageState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">MyHomePage</span>&gt; </span>{
  <span class="hljs-keyword">final</span> token = <span class="hljs-keyword">const</span> Uuid()
      .v4()
      .replaceAll(<span class="hljs-string">'-'</span>, <span class="hljs-string">''</span>)
      .replaceAll(<span class="hljs-string">'{'</span>, <span class="hljs-string">''</span>)
      .replaceAll(<span class="hljs-string">'}'</span>, <span class="hljs-string">''</span>);
  <span class="hljs-keyword">var</span> client = DBusClient.session();
  <span class="hljs-keyword">var</span> screenshotPath;

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-keyword">super</span>.initState();
    client.nameAcquired.listen(
      (event) {
        <span class="hljs-built_in">String?</span> serviceName = event.replaceAll(<span class="hljs-string">':'</span>, <span class="hljs-string">''</span>).replaceAll(<span class="hljs-string">'.'</span>, <span class="hljs-string">'_'</span>);

        <span class="hljs-keyword">var</span> request = OrgFreedesktopPortalRequest(
          client,
          <span class="hljs-string">'org.freedesktop.portal.Desktop'</span>,
          path: DBusObjectPath(
              <span class="hljs-string">"/org/freedesktop/portal/desktop/request/<span class="hljs-subst">$serviceName</span>/<span class="hljs-subst">$token</span>"</span>),
        );

        <span class="hljs-built_in">RegExp</span> filepathReg =
            <span class="hljs-built_in">RegExp</span>(<span class="hljs-string">r"(?:(?:file?|ftp):\/\/)?[\w\/\-?=%.]+\.[\w\/\-?=%.]+"</span>);
        request.response.listen(
          (event) {
            screenshotPath = filepathReg
                .allMatches(event.results[<span class="hljs-string">'uri'</span>].toString())
                .first
                .group(<span class="hljs-number">0</span>);
          },
        );
      },
    );
  }

  <span class="hljs-keyword">void</span> takeScreenshot() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">var</span> object = OrgFreedesktopPortalScreenshot(
      client,
      <span class="hljs-string">'org.freedesktop.portal.Desktop'</span>,
      path: DBusObjectPath(
        <span class="hljs-string">"/org/freedesktop/portal/desktop"</span>,
      ),
    );
    <span class="hljs-keyword">await</span> object.callScreenshot(
      <span class="hljs-string">""</span>,
      {
        <span class="hljs-string">"handle_token"</span>: DBusString(token),
        <span class="hljs-string">"interactive"</span>: <span class="hljs-keyword">const</span> DBusBoolean(<span class="hljs-keyword">false</span>),
      },
    );
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> dispose() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> client.close();
    <span class="hljs-keyword">super</span>.dispose();
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &lt;Widget&gt;[
            <span class="hljs-keyword">const</span> Text(
              <span class="hljs-string">'File:'</span>,
            ),
            Text(
              <span class="hljs-string">'<span class="hljs-subst">$screenshotPath</span>'</span>,
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: takeScreenshot,
        tooltip: <span class="hljs-string">'Capture'</span>,
        child: <span class="hljs-keyword">const</span> Icon(Icons.add),
      ),
    );
  }
}
</code></pre>
<p>I hope you found this tutorial helpful. Please feel free to leave any comments or feedback.</p>
]]></content:encoded></item><item><title><![CDATA[You Don't Want To Use RedHat As Your Daily Driver!!]]></title><description><![CDATA[It was Exam days. I was just scrolling through the google news feed and my eyes got into a very appealing headline. RedHat Linux can now be downloaded and installed for free. If you don't know RedHat Linux is an enterprise Linux and it needs the doug...]]></description><link>https://blog.aadarshadhakal.com.np/you-dont-want-to-use-redhat-as-your-daily-driver</link><guid isPermaLink="true">https://blog.aadarshadhakal.com.np/you-dont-want-to-use-redhat-as-your-daily-driver</guid><category><![CDATA[Linux]]></category><category><![CDATA[linux for beginners]]></category><category><![CDATA[redhat]]></category><category><![CDATA[Daily Driver]]></category><category><![CDATA[Personal Computer]]></category><dc:creator><![CDATA[Aadarsha Dhakal]]></dc:creator><pubDate>Fri, 17 Jun 2022 16:53:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/MU8w72PzRow/upload/67e037f0016f0c2f0fba3d97c32f1957.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It was Exam days. I was just scrolling through the google news feed and my eyes got into a very appealing headline. RedHat Linux can now be downloaded and installed for free. If you don't know RedHat Linux is an enterprise Linux and it needs the dough to get it into your hardware. I was like a dog with two tails. I was always fascinated by the term 'Enterprise Linux'. And wanted to try something different than a Debian-based distribution. Something that others rarely use. That time I didn't care about my next-day board exam and just hit the blog. In no time I downloaded the RedHat Linux iso file and get ready a bootable USB drive.</p>
<h2 id="heading-installation-sucks">Installation Sucks</h2>
<p>Disclaimer: RedHat is one of the greatest contributors to the Linux Kernel. And RedHat Linux is mainly created for servers and high-performance computing. So, using it on a personal computer was never going to be a good idea. But, what's wrong with trying right?</p>
<p>I inserted the USB, hit the power button, and began the installation process. The installation flow was very confusing. I had to go back and forth between some screens and it was not so smooth. You don't see what needs to be done next. And, it's very likely that you miss some steps.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655469260864/4qiaY3uzY.png" alt="60_rhel8_begin_install.png" /></p>
<p>Things you need to do to install RedHat and other Linux are not the same. You don't have to log in to your account and activate your subscription in the other Linux. You don't see software selection and the Kdump thing in other Linux installations. I had to perform installation twice because I chose 'Workstation' at first and it didn't have a GUI. Later, I choose 'Server with GUI'. And the hardest part was choosing a security policy. There were so many security policies that it was a very hard choice to make. But hey, you believe I am a genius right? Yes? You can say that again!</p>
<p>I chose NONE. And proceed with the normal installation. Since it was a network install, I had to make sure that the internet connection was fast.</p>
<h2 id="heading-red-like-rose">Red Like Rose</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655471141076/VZvz3jAgz.png" alt="desktop.png" /></p>
<p>The desktop look is beautiful. RedHat comes with Gnome Desktop Environment by default. The wallpaper was looking so nice with the RedHat Enterprise Linux branding on the bottom left of the desktop.  RedHat used Wayland for display management. It also supports the touchpad gestures to reveal menus and switch desktops. You can do a certain level of customizations using gnome-extensions and gnome-tweak-tools. It's nothing out of the world but it gives the 'Linux Vibes'.</p>
<p>RedHat Linux comes with the nouveau driver for Nvidia Graphics Card. But, it can be easily replaced with the official Nvidia proprietary driver.</p>
<p>Overall, The UI feels so smooth, Looks nice in red, so the GUI experience was good. But it didn't last long.</p>
<h2 id="heading-the-game-of-thron">The Game of 'Thron'</h2>
<p>It's not all sunshine and rainbows. I was enjoying the looks but the real pain was waiting for me. You can feel the real pain of RedHat Linux as a daily driver once you step into package installation. I couldn't find the most essential software in the RedHat package repository. For this, I had to add and enable multiple third-party repositories but still, lots of packages were missing. There is no promise that you'll get the packages you need. I had to install snap to download very common applications like Spotify, Intellij, VS Code, GIMP, and Inkscape. This shows how painful it is to install the software in RedHat Linux. There is no straightforward way to install applications and for every app to install it's very possible that you'll either fail completely or the way to get it is jagged.</p>
<p>Also, I don't know why but my Bluetooth was not functioning properly. Every time I want to connect a new device or reboot my system, I had to restart the Bluetooth service. Since it was working great with Arch Linux, I can say that device driver availability is also not good.</p>
<h2 id="heading-the-conclusion">The Conclusion</h2>
<p>All of these issues compelled me to replace the RedHat Linux with Pop OS. I am not saying Pop OS is the best distribution for your personal computer but RedHat is definitely not. </p>
<p>RedHat might be the best option for the servers; I think it is; as it offers high security, stability and very good tech support. From my 10 days of RedHat experience on my personal laptop, I can say that it doesn't worth a try. </p>
]]></content:encoded></item></channel></rss>