<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Amjad Abujamous</title>
    <description>The latest articles on DEV Community by Amjad Abujamous (@amjadmh73).</description>
    <link>https://dev.to/amjadmh73</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F297165%2Fb8311c79-5f38-4091-a529-c843c8ff0ee9.jpeg</url>
      <title>DEV Community: Amjad Abujamous</title>
      <link>https://dev.to/amjadmh73</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/amjadmh73"/>
    <language>en</language>
    <item>
      <title>The Local AI Powerhouse</title>
      <dc:creator>Amjad Abujamous</dc:creator>
      <pubDate>Fri, 13 Mar 2026 18:25:54 +0000</pubDate>
      <link>https://dev.to/amjadmh73/the-local-ai-powerhouse-28j</link>
      <guid>https://dev.to/amjadmh73/the-local-ai-powerhouse-28j</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Keeping up with everything AI-related in the recent months has got me wondering about the magic behind it, so I started searching extensively about running it locally and eventually devised a plan to run AI models on my own hardware.&lt;br&gt;
Once I did the hardware research, I settled on choosing a mini PC running AMD Strix Halo, the GMKTec EVO X2. After ordering it from Ali Express (which ships worldwide), configuring it with Linux Mint, Llama cpp, and its dependencies, I managed to run models like GPT-OSS-120b, Qwen-3-Next-80-A3b, and GLM 4.7 Flash.&lt;br&gt;
This article walks you through how the whole thing was done and the reasoning behind each decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;The ability to run AI locally allows for ownership, control, the ability to work offline, and maximum flexibility. The idea is to be able to have a device powerful enough to accomplish the tasks needed without having to subscribe to a cloud provider and need an internet connection to function. Further, as AI technologies are rapidly advancing, such a device is considered an investment given that the research is heading towards providing the same value from smaller models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware Research
&lt;/h2&gt;

&lt;p&gt;Now that the motivation is clear, it is time to choose the ideal device for the task, keeping both the cost and the benefit in mind. After following brilliant people like Alex Ziskind, Network Chuck, and many others on YouTube over several months, I eventually reached the decision of buying a Mini PC both due to its portability and its ability to provide decent performance while maintaining a reasonable temperature.&lt;br&gt;
Now the question that follows, which Mini PC do I buy for this purpose? The answer to that is one of three:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mac Mini. This is the ideal one since Apple M Chips are best in class. Only con is it is too expensive once you need enough RAM.&lt;/li&gt;
&lt;li&gt;Nvidia GB10. This one is great, but by the time NVIDIA's GB10 became available, AMD's Strix Halo had already established itself in the market&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GMKTec Evo X2&lt;/strong&gt; (or the Framework Desktop). This one provides nearly as good performance as the other two for a much more affordable price range, making it ideal both for the tinkerer and the small organization that wants to run AI on their own hardware. The GMKTec Evo X2 I ordered came with 128GB of RAM. What makes this device special is that up to 96GB can be allocated to the integrated GPU, giving it enough memory to run models with 80B+ parameters at reasonable speeds.
With that in mind, I went for choice #3, the local AI powerhouse.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The AI runtime
&lt;/h2&gt;

&lt;p&gt;There are multiple available runtimes for inference on the market. Mainly, we have both LM Studio and Ollama, and both run Llama cpp under the hood. This means that &lt;strong&gt;Llama cpp is ideal to run inference locally&lt;/strong&gt; given that it is blazing fast, and it recently got its Web UI which is a massive plus. Setting up Llama.cpp on Linux Mint required installing Vulkan drivers and building from source with Vulkan support enabled, since the Radeon 8060s iGPU relies on Vulkan rather than CUDA for GPU acceleration. Once that was done, inference worked out of the box.&lt;br&gt;
Another client used with this local AI runtime was OpenCode for agentic coding purposes, and its configuration to connect to a local instance was a little tricky, but it was done nonetheless. More on that can be found here. In short, configuring OpenCode to connect to a local Llama.cpp instance involved creating an opencode.json file under ~/.config/opencode/ with the provider set to @ai-sdk/openai-compatible and the base URL pointing to &lt;a href="http://127.0.0.1:8080/v1" rel="noopener noreferrer"&gt;http://127.0.0.1:8080/v1&lt;/a&gt;. It took some trial and error, but once configured, OpenCode picks up the local model automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  The models of choice
&lt;/h2&gt;

&lt;p&gt;Three models were chosen, each for a distinct reason:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Industry Standard: I went for GPT OSS 120B by OpenAI, since OpenAI currently sets the industry standard as of now.&lt;/li&gt;
&lt;li&gt;Multilingual Capabilities: Qwen3-Next-80B-A3B is a surprisingly capable model that can do essentially anything, and you can speak to it using any language of choice.&lt;/li&gt;
&lt;li&gt;Coding: GLM 4.7 Flash has a good reputation online for being a capable local model. I tested it and connected it to OpenCode, and built a 2d Mario Game using it. I have to say it is decent, but not as good as Opus by Anthropic or Codex by Open AI. The cloud models still rule here.
After using all three for a few weeks, GPT OSS felt slow at 120B parameters for everyday use, and GLM 4.7 Flash, while great for coding, was too narrow in scope for general tasks. Qwen3-Next struck the best balance, fast inference thanks to its mixture-of-experts architecture (only 3B parameters active at a time), strong multilingual support, and solid performance across both conversation and code. Finally, I settled for Qwen 3 Next. That thing rocks!&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;What surprised me most was how capable local models have become, for most day-to-day tasks, the experience is comparable to cloud providers. If I were to do it differently, I would have gone with Linux Mint from day one rather than experimenting with other distros first. As for what is next, I am keeping an eye on smaller, more efficient models as they continue to improve, and I plan to integrate this setup into my development workflow more deeply using tools like OpenCode and n8n. If you are considering running AI locally and deliberating whether it is a good choice, I hope this article was of help to you. Good luck.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>qwen</category>
      <category>localllm</category>
      <category>gptoss</category>
    </item>
    <item>
      <title>"Just use AI"</title>
      <dc:creator>Amjad Abujamous</dc:creator>
      <pubDate>Mon, 24 Mar 2025 12:10:13 +0000</pubDate>
      <link>https://dev.to/amjadmh73/just-use-ai-57m0</link>
      <guid>https://dev.to/amjadmh73/just-use-ai-57m0</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Many people nowadays are saying "Just use AI" when it comes to doing pretty much any task. This reminds me of the time our Math professor referenced an article with the title "Is Google making us stupid" &lt;a href="https://www.theatlantic.com/magazine/archive/2008/07/is-google-making-us-stupid/306868/" rel="noopener noreferrer"&gt;by Nicholas Carr on the Atlantic&lt;/a&gt; to differentiate the completion of the task from the knowledge of the individual executing that task.&lt;/p&gt;

&lt;p&gt;This article takes a different approach in elaborating as to why the individual should only use AI as an assistant as opposed to totally depending on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The human knowledge
&lt;/h2&gt;

&lt;p&gt;In his 2008 essay Is Google Making Us Stupid?, Nicholas Carr reflects on how the internet is reshaping our cognitive abilities, noting, "Once I was a scuba diver in the sea of words. Now I zip along the surface like a guy on a Jet Ski." (&lt;a href="https://www.goodreads.com/work/quotes/19136888-is-google-making-us-stupid?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Goodreads&lt;/a&gt;). This shows the difference between someone who is an intellectual and someone else who can only get the job done and forget the details of what it was shortly afterwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Studies
&lt;/h2&gt;

&lt;p&gt;Studies have shown that when used appropriately, AI can significantly boost productivity. For instance, &lt;a href="https://mitsloan.mit.edu/ideas-made-to-matter/how-generative-ai-can-boost-highly-skilled-workers-productivity?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;research from MIT Sloan&lt;/a&gt; indicates that generative AI can enhance the performance of highly skilled workers by nearly 40% compared to those who don't use it. However, over-reliance on AI can lead to diminished critical thinking and decision-making skills. &lt;/p&gt;

&lt;p&gt;Another study by MIT Media Lab and OpenAI found that excessive use of AI tools such as Chat GPT may result in emotional dependence and problematic behaviors, such as reduced autonomy and confidence in personal decision-making. (&lt;a href="https://www.businessinsider.com/openai-chatgpt-brainstorming-addiction-dependence-negative-consequences-mit-research-2025-3?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Business Insider&lt;/a&gt;)&lt;/p&gt;

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

&lt;p&gt;Striking the right balance in AI dependence can be visualized as a normal distribution curve, where the "sweet spot" lies at the peak—representing optimal integration of AI assistance without compromising human agency. This equilibrium ensures that AI serves as a tool to augment human capabilities rather than supplant them. After all, AI should be in the passenger seat, assisting the driver rather than taking the wheel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Futl4jvzy8ptgukfgisby.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Futl4jvzy8ptgukfgisby.png" alt="Normal distribution in AI realization" width="800" height="513"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Image credit: Generated by Claude AI&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;Ironically, this exploration of AI’s role was conducted with the assistance of both Chat GPT and Claude AI. Yet, the verification and organization of information were carefully managed to maintain the integrity of the information presented in this article.&lt;/p&gt;

&lt;p&gt;Cheers!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>openai</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Six useful tools for corporate tasks</title>
      <dc:creator>Amjad Abujamous</dc:creator>
      <pubDate>Sat, 08 Mar 2025 10:09:39 +0000</pubDate>
      <link>https://dev.to/amjadmh73/six-useful-tools-for-corporate-tasks-en7</link>
      <guid>https://dev.to/amjadmh73/six-useful-tools-for-corporate-tasks-en7</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This article discusses underrated useful tools that can help accomplish a lot more in their corporate job in tasks that are around development and not development itself.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDF X-Change Editor&lt;/li&gt;
&lt;li&gt;Microsoft Paint&lt;/li&gt;
&lt;li&gt;Xournal&lt;/li&gt;
&lt;li&gt;Excalidraw.com&lt;/li&gt;
&lt;li&gt;SVG Trace&lt;/li&gt;
&lt;li&gt;Google Slides&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first three tools are Desktop-based, while the last three are Web-based. Let's go over these tools one by one.&lt;/p&gt;

&lt;h3&gt;
  
  
  PDF X-Change Editor
&lt;/h3&gt;

&lt;p&gt;PDF X-Change Editor is the first tool among the desktop-based ones in this article. It is a powerful PDF tool that allows users to edit, annotate, and manipulate PDF files with ease. Unlike basic PDF readers, it provides advanced markup features, including adding comments, highlighting text, and inserting images or shapes. You can use it to merge or split PDF documents, and you can type, annotate, draw, and use its OCR feature all in its free edition. This makes it a reliable and accessible alternative to expensive software like Adobe Acrobat.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ni6couvru2chb0zmj3e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ni6couvru2chb0zmj3e.png" alt="PDF XChange Editor" width="800" height="429"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://pdf-xchange.eu/pdf-xchange-editor/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Microsoft Paint
&lt;/h3&gt;

&lt;p&gt;Microsoft Paint is a simple yet powerful tool for quick image editing, annotation, and diagram creation. If you don't have access to a powerful program such as PowerPoint, Paint is the perfect alternative for quick markups, cropping images, and adding basic shapes or text. For professionals who occasionally need to edit screenshots or create simple visuals, Paint is a handy built-in tool that requires no extra setup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhhe7uejnukaw10wo3vd3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhhe7uejnukaw10wo3vd3.png" alt="Microsoft Paint" width="800" height="429"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.microsoft.com/en-us/windows/paint" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Xournal (or Xournal++)
&lt;/h3&gt;

&lt;p&gt;If you're not on Windows or do not like Paint, Xournal and its enhanced version, Xournal++, serve as a strong alternative, offering more flexibility, especially for handwriting and PDF annotation. It is open-source and it a solid tool for note-taking, sketching ideas, and marking up on PDF documents. With stylus support, it is particularly useful to you if you prefer writing by hand, making it a great choice for digital notebooks or collaborative brainstorming, or drawing diagrams. It is available on Windows, Mac, and Linux.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://xournalpp.github.io/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/xournalpp/xournalpp/" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Excalidraw.com
&lt;/h3&gt;

&lt;p&gt;Starting with Web-based tools, Excalidraw.io is an open-source, web-based drawing tool that allows teams to create hand-drawn-style diagrams and flowcharts as quickly as in minutes. Unlike rigid diagramming software, it offers a more natural and informal way to visualize ideas, making it ideal for brainstorming sessions, UI mockups, and technical explanations. Also, note that it has a Library which gives you templates that are ready to be filled and shared.&lt;/p&gt;

&lt;h4&gt;
  
  
  Excaldrawio
&lt;/h4&gt;

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

&lt;h4&gt;
  
  
  Library
&lt;/h4&gt;

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

&lt;p&gt;&lt;a href="https://excalidraw.com/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;br&gt;
&lt;a href="https://libraries.excalidraw.com/?target=_excalidraw&amp;amp;referrer=https%3A%2F%2Fexcalidraw.com%2F&amp;amp;useHash=true&amp;amp;token=B-r4vJqFe9Ay1QakFgv_G&amp;amp;theme=dark&amp;amp;version=2&amp;amp;sort=default" rel="noopener noreferrer"&gt;Library&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/excalidraw/excalidraw" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  SVG Trace
&lt;/h3&gt;

&lt;p&gt;SVG Trace is a web-based tool that converts raster images (like PNGs or JPGs) into scalable vector graphics (SVGs). This is particularly useful for designers, marketers, or engineers who need to create high-quality logos, icons, or diagrams that scale without losing resolution. If you're in a situation where you're only met with a logo or an illustration as an image and you'd like to have a vector instead, this tool is the one you're looking for. Note that the image can be enhanced if you used their paid version.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://svgtrace.com/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Google Slides
&lt;/h3&gt;

&lt;p&gt;While not used as much as other presentation tools, Google Slides is a versatile web-based platform for collaboration, storytelling, and creating presentations right in the browser. You could work on presentations with your team in real time, add comments to slides for others to see or follow up, and work on the same presentation using your PCs at the same time. You could also edit PowerPoint presentations in it, which allows you edit presentations and sending them back to your team in case you don't have PowerPoint on your PC.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://workspace.google.com/products/slides/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;This article presented a few useful tools that can increase your productivity concerning tasks around Development and not development itself. Hope it was useful for you.&lt;/p&gt;

&lt;p&gt;Cheers!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://unsplash.com/photos/black-laptop-computer-on-white-textile-XLqiL-rz4V8?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Image Credit&lt;/a&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>tooling</category>
      <category>career</category>
      <category>workplace</category>
    </item>
    <item>
      <title>SFTP Client using .NET</title>
      <dc:creator>Amjad Abujamous</dc:creator>
      <pubDate>Sat, 18 Jan 2025 15:56:43 +0000</pubDate>
      <link>https://dev.to/amjadmh73/sftp-client-using-net-3an2</link>
      <guid>https://dev.to/amjadmh73/sftp-client-using-net-3an2</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;You'd like to add, fetch, and possibly delete files on a locally available or remote server, and you're looking for the best way to do it.&lt;/p&gt;

&lt;p&gt;Well, SFTP is the solution you're looking for since it is, secure, offers authentication, easy to use, needs a single connection, offers resumable transfers, and is very well documented.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating an SFTP server
&lt;/h2&gt;

&lt;p&gt;Once can create an SFTP server by following the instructions for their operating system. For instance, here is &lt;a href="https://www.ionos.com/digitalguide/server/configuration/set-up-an-ubuntu-sftp-server/" rel="noopener noreferrer"&gt;how to do it on Ubuntu/Debian&lt;/a&gt;. Also, one can use a readily available sftp server such  as &lt;a href="https://filezilla-project.org/" rel="noopener noreferrer"&gt;FileZilla's&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Methods of usage
&lt;/h2&gt;

&lt;p&gt;Similar to hosting a server, one can either use an SFTP client or write a script to automate a process using SFTP. This article illustrates how to write an SFTP client using modern .NET.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Client
&lt;/h2&gt;

&lt;p&gt;The client is self explanatory and pretty easy to use by another service. One can also turn it into a library. Without further ado, let us start creating it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency - SSH.NET
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.nuget.org/packages/ssh.net/" rel="noopener noreferrer"&gt;SSH.NET&lt;/a&gt; is the dependency we would use in order to communicate with the SFTP server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code
&lt;/h3&gt;

&lt;p&gt;The code works as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect to the SFTP server when the application starts.&lt;/li&gt;
&lt;li&gt;Be ready to do any SFTP-related operation using that connection.&lt;/li&gt;
&lt;li&gt;Close the connection upon program termination&lt;/li&gt;
&lt;li&gt;The service supports dependency injection out of the box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;appsettings.json&lt;/code&gt; configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;before...&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nl"&gt;"Sftp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"IsConnected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"192.168.XXX.YYY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sftpUserHere"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sftpPasswordHere"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"BasePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/Server/MyApp/Files"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;after...&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;IsConnected&lt;/code&gt; is needed in cases where one needs to use dependency injection for their service/API while the sftp server is unavailable at the moment.&lt;/p&gt;

&lt;p&gt;The ISftpUtility interface&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ISftpUtillity&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;)&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;DownloadFileBytesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;remotePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Base64Content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;)&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;DownloadFileBase64Async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;remotePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;DisposeConnection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;And below is the SftpUtility&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Renci.SshNet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SftpUtility&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ISftpUtillity&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;SftpClient&lt;/span&gt; &lt;span class="n"&gt;_sftpClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;SftpUtility&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;isSftp&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sftp:IsConnected"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isSftp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sftp:Host"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sftp:Username"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sftp:Password"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="n"&gt;_sftpClient&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SftpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;_sftpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;)&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;DownloadFileBytesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;remotePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;memoryStream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MemoryStream&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_sftpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DownloadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remotePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memoryStream&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memoryStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;GetContentTypeFromPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remotePath&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Error downloading file: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Base64Content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;)&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;DownloadFileBase64Async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;remotePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;DownloadFileBytesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remotePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToBase64String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;DisposeConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_sftpClient&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_sftpClient&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetContentTypeFromPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;".pdf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"application/pdf"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;".jpg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;".jpeg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"image/jpeg"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;".png"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"image/png"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;".txt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;".xlsx"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="c1"&gt;// Add more content types as needed&lt;/span&gt;
            &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"application/octet-stream"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Default for unknown types&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Disposing the connection
&lt;/h2&gt;

&lt;p&gt;In ASP.NET, the connection can be disposed by tapping in the ApplicationStopping lifetime method, like so&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Program.cs&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lifetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplicationStopping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Application is stopping..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScope&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sftpUtility&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ISftpUtillity&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;sftpUtility&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DisposeConnection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Room for improvement
&lt;/h2&gt;

&lt;p&gt;One can easily use the same library to upload or delete files on the remote server.&lt;/p&gt;

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

&lt;p&gt;This was a short tutorial illustrating the usage and benefits of using SFTP in the case of writing an API or a service when file management is needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://unsplash.com/photos/assorted-color-folder-lot-o6GEPQXnqMY" rel="noopener noreferrer"&gt;Photo Credit&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>sftp</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Install a NATS server on Debian/Ubuntu</title>
      <dc:creator>Amjad Abujamous</dc:creator>
      <pubDate>Mon, 04 Nov 2024 15:08:08 +0000</pubDate>
      <link>https://dev.to/amjadmh73/install-nats-on-debianubuntu-2op3</link>
      <guid>https://dev.to/amjadmh73/install-nats-on-debianubuntu-2op3</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This tutorial teaches you how to install a NATS server directly on Debian-based linux (not via Docker).&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;The installation consists of multiple steps each of which will be explained in detail. Those steps are as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a user with name &lt;code&gt;nats&lt;/code&gt; who belongs to the group &lt;code&gt;nats&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Download the latest NATS release build.&lt;/li&gt;
&lt;li&gt;Unzip the folder and copy it to &lt;code&gt;/usr/bin&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add NATS to &lt;code&gt;systemd&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run the NATS server and confirm it is working.&lt;/li&gt;
&lt;li&gt;Enable it, so it can run at startup.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 1: Create the user nats
&lt;/h3&gt;

&lt;p&gt;In order to add a user, we will use the &lt;code&gt;useradd&lt;/code&gt; command, like so.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo useradd -s /bin/bash -d /home/nats/ -m -G sudo nats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To add a password for the user (not really needed, just a standard practice):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo passwd nats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Download NATS
&lt;/h3&gt;

&lt;p&gt;As per the documentation, one can download NATS by using the following shell command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -L https://github.com/nats-io/nats-server/releases/download/v2.10.22/nats-server-v2.10.22-linux-amd64.zip -o nats-server.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that you may want to change the version of NATS if there are newer releases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Add NATS to /usr/bin
&lt;/h3&gt;

&lt;p&gt;This step is straightforward. First, open the file explorer and unzip NATS.&lt;/p&gt;

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

&lt;p&gt;Now copy the unzipped folder to /usr/bin&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo cp nats-server/nats-server-v2.10.22-linux-amd64/nats-server /usr/bin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Add NATS to systemd
&lt;/h3&gt;

&lt;p&gt;This one is a little tricky, but this guide simplifies it for you.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create &lt;code&gt;/etc/nats-server.conf&lt;/code&gt;. No need to add content to it, since it is only there for customization and overriding the default behavior.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create &lt;code&gt;/etc/systemd/system/nats.service&lt;/code&gt; and add the following content to it.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Unit]
Description=NATS Server
After=network-online.target ntp.service

[Service]
PrivateTmp=true
Type=simple
ExecStart=/usr/bin/nats-server -c /etc/nats-server.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s SIGINT $MAINPID
User=nats
Group=nats
# The nats-server uses SIGUSR2 to trigger using Lame Duck Mode (LDM) shutdown
KillSignal=SIGUSR2
# You might want to adjust TimeoutStopSec too.

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Save and exit.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Windows Subsystem for Linux
&lt;/h3&gt;

&lt;p&gt;For &lt;code&gt;WSL&lt;/code&gt; users, please make sure the user &lt;code&gt;nats&lt;/code&gt; has the access permission on &lt;code&gt;/usr/bin/nats-server&lt;/code&gt;. Here is how:&lt;br&gt;
Change the user to &lt;code&gt;nats&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;su - nats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add read and execute permissions to that folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo chmod +rx /usr/bin/nats-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Run the NATS server
&lt;/h3&gt;

&lt;p&gt;This one is easy. We reload the daemon and then run NATS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl daemon-reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl start nats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To confirm that it is running, run &lt;code&gt;systemctl status nats&lt;/code&gt;, and you should get that it is active.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Step 6: Enable NATS
&lt;/h3&gt;

&lt;p&gt;To enable NATS which would run it at startup, simply run the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl enable nats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;This simple tutorial ensures that the installation of NATS on Debian-based linux is smooth and easy. Let me know below if you have any questions or inquiries about it.&lt;/p&gt;

&lt;p&gt;Happy building!&lt;/p&gt;

</description>
      <category>nats</category>
      <category>microservices</category>
      <category>eventdriven</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>JSON.parse without errors</title>
      <dc:creator>Amjad Abujamous</dc:creator>
      <pubDate>Sat, 12 Oct 2024 15:28:56 +0000</pubDate>
      <link>https://dev.to/amjadmh73/jsonparse-but-without-errors-4cgo</link>
      <guid>https://dev.to/amjadmh73/jsonparse-but-without-errors-4cgo</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;We've all been in the situation where we simply want to call JSON.parse and not get an error if the value we're trying to parse is null or undefined.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSON.tryParse to the rescue
&lt;/h2&gt;

&lt;p&gt;What we can do to fix it is simply introduce the method &lt;code&gt;JSON.tryParse&lt;/code&gt; instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Simply define this function in your application at the beginning and make it globally available.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tryParse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Let's say you want to retreive a cached user without having to try/cacth. This is how:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tryParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// returns "null" instead of throwing an error in case there is no entry&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;This tutorial has helped us work with parsing JSON objects without having to worry about catchig errors every single time.&lt;/p&gt;

&lt;p&gt;Happy developing!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>json</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Synchronize Files between your servers</title>
      <dc:creator>Amjad Abujamous</dc:creator>
      <pubDate>Sun, 08 Sep 2024 18:17:47 +0000</pubDate>
      <link>https://dev.to/amjadmh73/synchronize-files-between-devices-3bgi</link>
      <guid>https://dev.to/amjadmh73/synchronize-files-between-devices-3bgi</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This tutorial teaches you how to automate the synchronization of two or more folders on different PCs or servers on the same network.&lt;/p&gt;

&lt;p&gt;Buckle up!&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Static IP Address
&lt;/h3&gt;

&lt;p&gt;The first step is to set a static IP address on each of the devices in the network.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://kb.netgear.com/27476/How-do-I-set-a-static-IP-address-in-Windows" rel="noopener noreferrer"&gt;Windows&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tdstelecom.com/support/internet/ip-static-ip-configure-mac.html" rel="noopener noreferrer"&gt;Mac OS&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=0mfsE8t_7yY" rel="noopener noreferrer"&gt;Ubuntu&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Setup ssh keys on both devices
&lt;/h3&gt;

&lt;p&gt;Open up a terminal or WSL on windows and type in the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; rsa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-copy-id 192.168.1.15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;192.168.1.15&lt;/code&gt; is the IP address of the secondary (or replica) server.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Install Unison
&lt;/h3&gt;

&lt;h4&gt;
  
  
  The file synchronization tool
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://www.cis.upenn.edu/~bcpierce/unison/" rel="noopener noreferrer"&gt;Unison&lt;/a&gt; is a reliable file synchronization tool designed to be cross platform, fast, and efficient. One can install it via the &lt;a href="https://github.com/bcpierce00/unison/releases/tag/v2.53.5" rel="noopener noreferrer"&gt;releases page&lt;/a&gt; in their &lt;a href="https://github.com/bcpierce00/unison" rel="noopener noreferrer"&gt;github repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note that the tool must exist on all PCs that need to synchronize their folders.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Configure Unison
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Folders and parameters
&lt;/h4&gt;

&lt;p&gt;Configuring Unison is quite easy since it has a built-in GUI. Also, the configuration only needs to be done on the "primary" machine, and the others will periodically (or almost instantly) receive replicas of the files in a specific folder.&lt;/p&gt;

&lt;h4&gt;
  
  
  4.1 Extract Unison Archive
&lt;/h4&gt;

&lt;p&gt;Extract the archive which has unison in it to your desired folder. In my case, it is &lt;code&gt;~/Tools/Unison&lt;/code&gt;.&lt;/p&gt;

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

&lt;h4&gt;
  
  
  4.2 Launch Unison GUI
&lt;/h4&gt;

&lt;p&gt;Navigate to the &lt;code&gt;bin&lt;/code&gt; folder and execute the file &lt;code&gt;unison-gui&lt;/code&gt;.&lt;/p&gt;

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

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

&lt;h4&gt;
  
  
  4.3 Create a profile
&lt;/h4&gt;

&lt;p&gt;A Unison profile specifies how the synchronization process will take place and which folders on which machines will be synchronized. To create one, click on "Add" and follow the Profile creation wizard.&lt;/p&gt;

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

&lt;p&gt;Profile Description:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Profile name: default&lt;/li&gt;
&lt;li&gt;Description: Default profile&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Connection Setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Synchronization kind: using ssh&lt;/li&gt;
&lt;li&gt;Host: 192.168.1.15&lt;/li&gt;
&lt;li&gt;User: remote_user (replaced by your username on the secondary device)&lt;/li&gt;
&lt;li&gt;Enable compression: true&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Directory selection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local Directory: Documents&lt;/li&gt;
&lt;li&gt;Remote Directory: /home/remote_user/Documents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Specific Options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[Unchecked] Synchronization invloving a FAT partition.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;h4&gt;
  
  
  4.4 Configure the profile
&lt;/h4&gt;

&lt;p&gt;Select &lt;code&gt;default&lt;/code&gt; and click on "Edit" in the GUI.&lt;/p&gt;

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

&lt;p&gt;Add the following options by click on "Add", selecting the option, and then applying the value.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;auto&lt;/code&gt;: &lt;code&gt;true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;batch&lt;/code&gt;: &lt;code&gt;true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sshcmd&lt;/code&gt;: &lt;code&gt;/home/remote_user/Tools/Unison/bin/unison&lt;/code&gt; (replace with the executable location on the remote/secondary machine)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  4.5 Run unison
&lt;/h4&gt;

&lt;p&gt;You can either run &lt;code&gt;unison&lt;/code&gt; using the UI by selecting the profile and click on &lt;code&gt;Open&lt;/code&gt; which will run the tool or by navigating to the executable location and typing&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./unison default.prf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Now the contents of the folder on the first machine should be mirrored on the second one. Note that the profile name would be different in case you'd want to run another.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Automate Unison
&lt;/h3&gt;

&lt;p&gt;On the Linux bash, Mac Terminal, and WSL, one can automate the command and run it at a certain interval. On Ubuntu, for instance, the command below after navigting to the unison executables folder will do the trick.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; ./unison default.prf&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;10&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;10&lt;/code&gt; is the number of seconds.&lt;/p&gt;

&lt;p&gt;More on bash automation and &lt;code&gt;sh&lt;/code&gt; files &lt;a href="https://ioflood.com/blog/bash-infinite-loop/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Congratulations! You made it to the end of this tutorial. Hope this tutorial enabled you to do file synchronization seamlessly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.anyviewer.com/screenshot/others/illustration/files.png" rel="noopener noreferrer"&gt;Cover image credit&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>synchronization</category>
      <category>production</category>
      <category>sre</category>
      <category>automation</category>
    </item>
    <item>
      <title>Easy Postgresql 16 Replication on Ubuntu</title>
      <dc:creator>Amjad Abujamous</dc:creator>
      <pubDate>Sat, 31 Aug 2024 22:11:17 +0000</pubDate>
      <link>https://dev.to/amjadmh73/easy-postgresql-16-replication-on-ubuntu-48n0</link>
      <guid>https://dev.to/amjadmh73/easy-postgresql-16-replication-on-ubuntu-48n0</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Db Administration tasks can be a little intimidating to developers. Particularly, setting up database replication in this case. This article links two other articles which have been the only ones to work smoothly and easily for setting up replication for postgresql 16.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Static IP
&lt;/h2&gt;

&lt;p&gt;Setting up static IPs on the primary and secondary servers can be done by following the steps &lt;a href="https://www.youtube.com/watch?v=0mfsE8t_7yY" rel="noopener noreferrer"&gt;in this video&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note: If &lt;code&gt;DNS&lt;/code&gt; switch is greyed out on your system, just set the DNS values separarted by a comma without switching it off, it should still work.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Replication Steps
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.youtube.com/watch?v=VtBVofpdXyk" rel="noopener noreferrer"&gt;video&lt;/a&gt; [and &lt;a href="https://www.cherryservers.com/blog/how-to-set-up-postgresql-database-replication" rel="noopener noreferrer"&gt;article&lt;/a&gt;] by cherry servers was the one to work best with no issues whatsoever. It has the secondary server stream every single change from the primary server in realtime without issues at all (my addition was synchronous commits to ensure that standbby has the same data).&lt;/p&gt;

&lt;p&gt;Also set these on the primary server&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;max_wal_senders&lt;/span&gt; = &lt;span class="m"&gt;5&lt;/span&gt;     &lt;span class="c"&gt;# max number of walsender processes
&lt;/span&gt;                &lt;span class="c"&gt;# (change requires restart)
&lt;/span&gt;&lt;span class="n"&gt;max_replication_slots&lt;/span&gt; = &lt;span class="m"&gt;10&lt;/span&gt;  &lt;span class="c"&gt;# max number of replication slots
&lt;/span&gt;

&lt;span class="n"&gt;synchronous_commit&lt;/span&gt; = &lt;span class="n"&gt;on&lt;/span&gt;     &lt;span class="c"&gt;# synchronization level;
&lt;/span&gt;
&lt;span class="n"&gt;synchronous_standby_names&lt;/span&gt; = &lt;span class="s1"&gt;'*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the secondary server is turned off, the primary server stops accepting changes because it loses the ability to [synchronously] replicate. Only use this when the secondary server is always present, or disable replication when done with it.&lt;/li&gt;
&lt;li&gt;Although the &lt;code&gt;wal-level&lt;/code&gt; is set to &lt;code&gt;logical&lt;/code&gt;, the replication type between primary and secondary is in fact &lt;code&gt;streaming&lt;/code&gt;, since &lt;code&gt;streaming&lt;/code&gt; &amp;lt; &lt;code&gt;logical&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Disabling Replication
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Primary server
&lt;/h2&gt;

&lt;p&gt;To disable, &lt;strong&gt;restore the settings&lt;/strong&gt; on the primary server to the initial ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On /etc/postgresql/16/main/postgresql.conf

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;wal_level = logical&lt;/code&gt; --&amp;gt; &lt;code&gt;wal_level = replica&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;max_wal_senders = 5&lt;/code&gt; --&amp;gt; &lt;code&gt;# max_wal_senders = 5&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;max_replication_slots = 10&lt;/code&gt; --&amp;gt; &lt;code&gt;# max_replication_slots = 10&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wal_log_hints = on&lt;/code&gt; --&amp;gt; &lt;code&gt;#wal_log_hints = on&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;ensure &lt;code&gt;#archive_mode = on&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;synchronous_commit = on&lt;/code&gt; --&amp;gt; &lt;code&gt;#synchronous_commit = on&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;synchronous_standby_names = '*'&lt;/code&gt; --&amp;gt; &lt;code&gt;#synchronous_standby_names = '*'&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;On /etc/postgresql/16/main/pg_hba.conf
Change this line
&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;host&lt;/span&gt;    &lt;span class="n"&gt;replication&lt;/span&gt;     &lt;span class="n"&gt;replica_user&lt;/span&gt;        &lt;span class="m"&gt;192&lt;/span&gt;.&lt;span class="m"&gt;168&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;/&lt;span class="m"&gt;24&lt;/span&gt;          &lt;span class="n"&gt;md5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;192.168.1.1/24&lt;/code&gt; is the IP address of the replica server.&lt;br&gt;
Back to this line&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;host&lt;/span&gt;    &lt;span class="n"&gt;replication&lt;/span&gt;     &lt;span class="n"&gt;all&lt;/span&gt;         &lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;/&lt;span class="m"&gt;0&lt;/span&gt;           &lt;span class="n"&gt;md5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Secondary server
&lt;/h2&gt;

&lt;p&gt;Remove the file &lt;code&gt;standby.signal&lt;/code&gt; from the data directory which is located at &lt;code&gt;/var/lib/postgresql/16/main&lt;/code&gt;. (according to &lt;a href="https://stackoverflow.com/a/69932124/6336270" rel="noopener noreferrer"&gt;this&lt;/a&gt;. to be tested.)&lt;/p&gt;

&lt;h1&gt;
  
  
  Final Notes
&lt;/h1&gt;

&lt;p&gt;This article is more of a diary as to how this process went so I can get back to it in the future if needed, and it can also be helpful to you. &lt;br&gt;
&lt;strong&gt;Cover Image Credit:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://cdn.prod.website-files.com/665de628e01a0041a62ecd14/665de628e01a0041a62ed338_logical_replication_1-p-1080.jpg" rel="noopener noreferrer"&gt;https://cdn.prod.website-files.com/665de628e01a0041a62ecd14/665de628e01a0041a62ed338_logical_replication_1-p-1080.jpg&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>replication</category>
    </item>
    <item>
      <title>DbGate - a fast database client</title>
      <dc:creator>Amjad Abujamous</dc:creator>
      <pubDate>Wed, 14 Aug 2024 08:29:55 +0000</pubDate>
      <link>https://dev.to/amjadmh73/dbgate-a-fast-cross-database-client-26ca</link>
      <guid>https://dev.to/amjadmh73/dbgate-a-fast-cross-database-client-26ca</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;While Dbeaver and Dbvis are quite good cross-database clients, they an consume a lot of memeory and sometimes "think" before executing an operation. I recently found a post on reddit where someone recommended DbGate, and I am not looking back.&lt;/p&gt;

&lt;h1&gt;
  
  
  Features
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Ability to connect to multiple databases including, Postgres, MongoDb, SqlServer, and many others.&lt;/li&gt;
&lt;li&gt;Cross patform: Windows, Mac, and Linux.&lt;/li&gt;
&lt;li&gt;Ability to generate clear ER diagrams with the option of choosing which tables to be included and which ones to be discarded.&lt;/li&gt;
&lt;li&gt;Export data to any of the popular formats including CSV, Excel, XML, JSON, and others.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h1&gt;
  
  
  Try it out!
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://dbgate.org/" rel="noopener noreferrer"&gt;https://dbgate.org/&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Final Notes
&lt;/h1&gt;

&lt;p&gt;This is a good product that increased the productivity of our team and I highly recommend it.&lt;/p&gt;

</description>
      <category>database</category>
      <category>data</category>
      <category>sql</category>
      <category>nosql</category>
    </item>
    <item>
      <title>Modern Dropdown component HTML</title>
      <dc:creator>Amjad Abujamous</dc:creator>
      <pubDate>Wed, 31 Jul 2024 08:18:17 +0000</pubDate>
      <link>https://dev.to/amjadmh73/modern-dropdown-component-html-4g0i</link>
      <guid>https://dev.to/amjadmh73/modern-dropdown-component-html-4g0i</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I wanted to challenge myself to build a custom dropdown component that works with HTML forms and looks the same on all platforms and browsers. I finally managed to make one, and here is how...&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The code can be found on &lt;a href="https://codepen.io/glorious73/pen/XWLNxzQ" rel="noopener noreferrer"&gt;this Code Pen&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it's done
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Create a native web component that extends HTML Element.&lt;/li&gt;
&lt;li&gt;Give it the ability to have multiple themes (using pre-defined colors in CSS).&lt;/li&gt;
&lt;li&gt;Allow to set its items either using &lt;code&gt;setAttribute&lt;/code&gt; or by firing an event which has the items in it.&lt;/li&gt;
&lt;li&gt;Listen to a custom event indicating when an item is selected.&lt;/li&gt;
&lt;li&gt;Give it the ability to be a part of any HTML form. Note that &lt;code&gt;element-internals-polyfill&lt;/code&gt; is needed for this to work on some browsers such as &lt;code&gt;Safari&lt;/code&gt; at the time of writing this article.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  UI of the dropdown component
&lt;/h2&gt;

&lt;p&gt;It is composed of three elements.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;a read-only &lt;code&gt;input&lt;/code&gt; element which displays the currently selected item.&lt;/li&gt;
&lt;li&gt;a (initially hidden) &lt;code&gt;div&lt;/code&gt; which contains the items in the dropdown menu.&lt;/li&gt;
&lt;li&gt;an icon to indicate the state of the dropdown (open or closed). Note that the &lt;code&gt;svg&lt;/code&gt; used is adapted from &lt;a href="https://icons.getbootstrap.com/" rel="noopener noreferrer"&gt;Bootstrap icons&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Business logic
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;When connected, display the component in its selected theme. Choose the default one if none is selected.&lt;/li&gt;
&lt;li&gt;Register the &lt;code&gt;dropdown&lt;/code&gt; and &lt;code&gt;item selected&lt;/code&gt; events passed when declaratively creating the component.&lt;/li&gt;
&lt;li&gt;Once the &lt;code&gt;dropdown&lt;/code&gt; event is fired, capture the items and add them to the list.&lt;/li&gt;
&lt;li&gt;Give the ability to set the selected item programatically. This is particularly useful when the currently selected item is pre-known for the service provider. For instance, a list of locations in a booking site and it should ideally display the current city at which the user resides.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How it looks
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz5tnysa4muag8uo4bqz7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz5tnysa4muag8uo4bqz7.png" alt="Dropdown Component in Codepen" width="531" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;We created a modern customizable dropdown component using the tools available to the browser and it looks the same everywhere. Feel free to let me know if you have any additions or questions.&lt;/p&gt;

&lt;p&gt;Happy developing!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>challenge</category>
    </item>
    <item>
      <title>Cloudflare Tunnels VS ngrok</title>
      <dc:creator>Amjad Abujamous</dc:creator>
      <pubDate>Sat, 18 May 2024 18:48:22 +0000</pubDate>
      <link>https://dev.to/amjadmh73/make-your-server-accessible-from-anywhere-55e4</link>
      <guid>https://dev.to/amjadmh73/make-your-server-accessible-from-anywhere-55e4</guid>
      <description>&lt;h1&gt;
  
  
  TL;DR
&lt;/h1&gt;

&lt;p&gt;Reverse proxy solutions are a great and straightforward method to expose your dev (and possibly production) server to the internet. The two prominent ones are &lt;a href="https://ngrok.com/" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt; and &lt;a href="https://www.cloudflare.com/products/tunnel/" rel="noopener noreferrer"&gt;Cloudflare tunnels&lt;/a&gt;. This article recommends both of them and compares and contrasts them on a high level.&lt;/p&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;As mentioned in my previous article, &lt;a href="https://dev.to/amjadmh73/easily-monitor-your-server-from-anywhere-oma"&gt;one can monitor their server&lt;/a&gt; using one of many solutions, and in order to expose your web or home server without opening a port on your firewall and possibly having to configure a static public IP, you need to use a reverse proxy solution to solve that problem. The two most reliable ones in the market at the time of writing this article are &lt;code&gt;ngrok&lt;/code&gt; and &lt;code&gt;Cloudflare Tunnels&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  ngrok
&lt;/h1&gt;

&lt;p&gt;Ngrok is the one that pops up the most in YouTube videos and online articles since it quite easy to setup and get up and running. The &lt;a href="https://ngrok.com/docs/guides/getting-started/" rel="noopener noreferrer"&gt;steps can be found here&lt;/a&gt; which include running a simple web server, installing ngrok, configuring the auth token, and running the reverse proxy agent.&lt;/p&gt;

&lt;h1&gt;
  
  
  Cloudflare tunnels
&lt;/h1&gt;

&lt;p&gt;To run the &lt;code&gt;cloudflared&lt;/code&gt; tunnel agent and have one up and running, you need to follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an account with Cloudflare by &lt;a href="https://www.cloudflare.com/plans/" rel="noopener noreferrer"&gt;going to their website here&lt;/a&gt; and choosing the free plan.&lt;/li&gt;
&lt;li&gt;Fill up the information needed, which are an email address and a password at the time of writing this article.&lt;/li&gt;
&lt;li&gt;It will then take you to their dashboard where you need to do two things, register, link, or transfer a domain, and then create a tunnel in their Zero Trust dashboard. The screenshots below explain the steps.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;3.1&lt;/strong&gt; Cloudflare domain&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftkk4qhd488603kp9ij3g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftkk4qhd488603kp9ij3g.png" alt="Cloudflare active domain"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.2&lt;/strong&gt; Cloudflare dashboard w/ &lt;code&gt;ZeroTrust&lt;/code&gt; tab&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1uetjz190dqxvrqxi9wa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1uetjz190dqxvrqxi9wa.png" alt="Cloudflare dashboard w/ zero trust tab"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.3&lt;/strong&gt; Cloudflare tunnel creation steps&lt;br&gt;
&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;cloudflared&lt;/code&gt; client is easy to install yet its installation steps are different based on your operating system.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd5fnda2a177zond95d9t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd5fnda2a177zond95d9t.png" alt="Cloudflare tunnel creation steps"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.4&lt;/strong&gt; Cloudflare active tunnel&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feefzazq7k1039r3qexsb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feefzazq7k1039r3qexsb.png" alt="Cloudflare active tunnel"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  My biased thoughts on the two solutions
&lt;/h1&gt;

&lt;p&gt;As stated earlier, both solutions are reliable and they work well. &lt;/p&gt;

&lt;p&gt;My preference is to use ngrok for dev servers, homelabs, and staging environments. Also, the nice thing about &lt;code&gt;ngrok&lt;/code&gt; it is the fact that it offers subdomains and can work instantly. Also, subdomains are available so you don't have to purchase and maintain one if it's not a requirement.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Cloudflare&lt;/code&gt;, on the other hand, is more useful in production since it has more servers around the world and has more security configurations. But you need a domain name to use it (though easily offered by them).&lt;/p&gt;

&lt;h1&gt;
  
  
  Final Note
&lt;/h1&gt;

&lt;p&gt;No matter whether you need to expose a server, use ssh or RDP, or if you have any other use case, those reverse proxy solutions can certainly be of benefit to you. Choose the one that best suits your needs and proceed with it.&lt;/p&gt;

&lt;p&gt;Happy building!&lt;/p&gt;

</description>
      <category>internet</category>
      <category>proxy</category>
      <category>webdev</category>
      <category>production</category>
    </item>
    <item>
      <title>Easily monitor your server's stats</title>
      <dc:creator>Amjad Abujamous</dc:creator>
      <pubDate>Tue, 16 Apr 2024 00:55:46 +0000</pubDate>
      <link>https://dev.to/amjadmh73/easily-monitor-your-server-from-anywhere-oma</link>
      <guid>https://dev.to/amjadmh73/easily-monitor-your-server-from-anywhere-oma</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;One can use Glances along with a reverse proxy to easily monitor the health of their Server/PC from any location by simply accessing a link. This tutorial walks you through exactly that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Many of us want a straightforward solution to monitoring the health of their Home server, Web server, or essentially any PC. While Grafana and Metabase are good realtime dashboard solutions, their setup can be difficult, which is why we will explore and setup Glances.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steps to set it up
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Install Glances (cross-platform) on your system.&lt;/li&gt;
&lt;li&gt;Enable its monitoring feature via a web browser.&lt;/li&gt;
&lt;li&gt;Run a reverse proxy on the server.&lt;/li&gt;
&lt;li&gt;Access the link from anywhere.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;As is from their &lt;a href="https://github.com/nicolargo/glances" rel="noopener noreferrer"&gt;github repository&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;python&amp;gt;=3.8&lt;/code&gt; (use Glances 3.4.x for lower Python version)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;psutil&lt;/code&gt; (better with latest version)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;defusedxml&lt;/code&gt; (in order to monkey patch xmlrpc)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;packaging&lt;/code&gt; (for the version comparison)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ujson&lt;/code&gt; (an optimized alternative to the standard json module)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pytz&lt;/code&gt; (for the timezone support)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pydantic&lt;/code&gt; (for the data validation support)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Install Glances
&lt;/h2&gt;

&lt;p&gt;Can be done via the &lt;code&gt;pip&lt;/code&gt; package manager for python.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pip install --user 'glances[all]'&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Note that you may need to type &lt;code&gt;pip3&lt;/code&gt; instead of &lt;code&gt;pip&lt;/code&gt;, depending on your Python installation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv49vonw17kzvxkcnfuw8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv49vonw17kzvxkcnfuw8.png" alt="Image description" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Run glances and monitor the system
&lt;/h2&gt;

&lt;p&gt;To run it in the terminal, run the command:&lt;br&gt;
&lt;code&gt;glances&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In our case, we want to run it in a browser, so we type:&lt;br&gt;
&lt;code&gt;glances -w&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;which gives us the following.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekcp84le81ukanejebtl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekcp84le81ukanejebtl.png" alt="Image description" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbxthwfco5n17fufhx6cu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbxthwfco5n17fufhx6cu.png" alt="Image description" width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Access from anywhere
&lt;/h2&gt;

&lt;p&gt;Many good reverse proxy solutions currently exist on the market such as &lt;a href="https://ngrok.com/" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt; and &lt;a href="https://www.cloudflare.com/products/tunnel/" rel="noopener noreferrer"&gt;Cloudflare tunnels&lt;/a&gt;. They give one the ability to reliably run a tunnel and ensure it does not go down. They also offer the ability to securely access their links using whitelisted IP addresses or by using HTTP Basic Authentication.&lt;/p&gt;

&lt;p&gt;For this tutorial, however, we will go with the simpler solution &lt;a href="https://serveo.net/" rel="noopener noreferrer"&gt;serveo.net&lt;/a&gt; for demonstration purposes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gb2tukdtj50hweia6cw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gb2tukdtj50hweia6cw.png" alt="Image description" width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft84joq9q5yd02n5qrq9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft84joq9q5yd02n5qrq9s.png" alt="Image description" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Voila! That link can now allow us to monitor the system from anywhere with very minimal setup and installation.&lt;/p&gt;

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

&lt;p&gt;I hope you found this tutorial useful, and please feel free to comment below if you have questions, additions, modifications, or criticism of this method. All the best!&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>devops</category>
      <category>operations</category>
      <category>systems</category>
    </item>
  </channel>
</rss>
