<?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: Toby Chui</title>
    <description>The latest articles on DEV Community by Toby Chui (@tobychui).</description>
    <link>https://dev.to/tobychui</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%2F493190%2Fac9cc59e-ba0b-498d-8945-05bcd7220f01.png</url>
      <title>DEV Community: Toby Chui</title>
      <link>https://dev.to/tobychui</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tobychui"/>
    <language>en</language>
    <item>
      <title>Zoraxy vs Nginx Proxy Manager</title>
      <dc:creator>Toby Chui</dc:creator>
      <pubDate>Sat, 23 Nov 2024 11:26:01 +0000</pubDate>
      <link>https://dev.to/tobychui/zoraxy-vs-nginx-proxy-manager-2k48</link>
      <guid>https://dev.to/tobychui/zoraxy-vs-nginx-proxy-manager-2k48</guid>
      <description>&lt;p&gt;Recently I got a lot of questions from Nginx Proxy Manager users asking how to switch over to Zoraxy. As the developer of Zoraxy, this sometime bothers me as Zoraxy, at its core, it is not powered by the same engine as Nginx Proxy Manager, but an independently developed reverse proxy core named "Dynamic Proxy" (or dpcore for short). &lt;/p&gt;

&lt;p&gt;For those who have never heard of Zoraxy, here is a brief introduction of Zoraxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  🌐 What is Zoraxy?
&lt;/h2&gt;

&lt;p&gt;Zoraxy is a general purpose HTTP reverse proxy and forwarding tool, with tons of different network utilities and management feature that suitable for a small homelab setup. &lt;/p&gt;

&lt;p&gt;Although it provides a lot of function, the main purpose for this project is to act as the gateway of my own homelab, which provide web services for my ArozOS instance (web desktop system that I can access all my storage nodes) and reverse proxy to my Apache web server which is hosting my blog and personal homepage. But most people find its reverse proxy tool and ACME tool helpful, and promoting it is a Nginx Proxy Manager replacement. &lt;/p&gt;

&lt;p&gt;More details can be found on the project homepage&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://zoraxy.aroz.org/" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fzoraxy.aroz.org%2Fimg%2Fog.png" height="420" class="m-0" width="800"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://zoraxy.aroz.org/" rel="noopener noreferrer" class="c-link"&gt;
          Reverse Proxy Server | Zoraxy
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          A reverse proxy server and cluster network gateway for noobs
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fzoraxy.aroz.org%2Ffavicon.png" width="512" height="512"&gt;
        zoraxy.aroz.org
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Source code is available on my Github&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tobychui" rel="noopener noreferrer"&gt;
        tobychui
      &lt;/a&gt; / &lt;a href="https://github.com/tobychui/zoraxy" rel="noopener noreferrer"&gt;
        zoraxy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/tobychui/zoraxy./img/title.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftobychui%2Fzoraxy.%2Fimg%2Ftitle.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Zoraxy&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Features&lt;/h3&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Simple to use interface with detail in-system instructions&lt;/li&gt;
&lt;li&gt;Reverse Proxy (HTTP/2)
&lt;ul&gt;
&lt;li&gt;Virtual Directory&lt;/li&gt;
&lt;li&gt;WebSocket Proxy (automatic, no set-up needed)&lt;/li&gt;
&lt;li&gt;Basic Auth&lt;/li&gt;
&lt;li&gt;Alias Hostnames&lt;/li&gt;
&lt;li&gt;Custom Headers&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Redirection Rules&lt;/li&gt;
&lt;li&gt;TLS / SSL setup and deploy
&lt;ul&gt;
&lt;li&gt;ACME features like auto-renew to serve your sites in http&lt;strong&gt;s&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;SNI support (and SAN certs)&lt;/li&gt;
&lt;li&gt;DNS Challenge for Let's Encrypt and &lt;a href="https://go-acme.github.io/lego/dns/" rel="nofollow noopener noreferrer"&gt;these DNS providers&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Blacklist / Whitelist by country or IP address (single IP, CIDR or wildcard for beginners)&lt;/li&gt;
&lt;li&gt;Global Area Network Controller Web UI (ZeroTier not included)&lt;/li&gt;
&lt;li&gt;Stream Proxy (TCP &amp;amp; UDP)&lt;/li&gt;
&lt;li&gt;Integrated Up-time Monitor&lt;/li&gt;
&lt;li&gt;Web-SSH Terminal&lt;/li&gt;
&lt;li&gt;Utilities
&lt;ul&gt;
&lt;li&gt;CIDR IP converters&lt;/li&gt;
&lt;li&gt;mDNS Scanner&lt;/li&gt;
&lt;li&gt;Wake-On-Lan&lt;/li&gt;
&lt;li&gt;Debug Forward Proxy&lt;/li&gt;
&lt;li&gt;IP Scanner&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Others
&lt;ul&gt;
&lt;li&gt;Basic single-admin management mode&lt;/li&gt;
&lt;li&gt;External permission management system for easy system integration&lt;/li&gt;
&lt;li&gt;SMTP config for password reset&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Downloads&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_windows_amd64.exe" rel="noopener noreferrer"&gt;Windows&lt;/a&gt;
/ &lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_amd64" rel="noopener noreferrer"&gt;Linux (amd64)&lt;/a&gt;
/ &lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_arm64" rel="noopener noreferrer"&gt;Linux (arm64)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For other systems or architectures…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tobychui/zoraxy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  ⚖️ The core difference of Zoraxy and Nginx Proxy Manager (NPM)
&lt;/h2&gt;

&lt;p&gt;The core difference of Zoraxy and NPM is that NPM is based on a stable, old reverse proxy product named "Nginx". The NPM technically is just a PHP web interface that help you inject configurations into the Nginx server and do some basic management work for you like restarting it after the configuration is written. NPM has been here for long enough that basically all open source project use the Nginx proxy server behavior as the de-facto standard, even if it is sometime not following latest HTTP standard due to legacy reasons. &lt;/p&gt;

&lt;p&gt;Meanwhile in Zoraxy, everything is done differently. The core reverse proxy feature of Zoraxy is powered by its "Dynamic Proxy", which as its name suggested, works dynamically instead of statically. Compare to Nginx, where any config change will require a service restart, Zoraxy's config can be dynamically changed in runtime with no downtime, together with some smart logic to handle domain sniffing, runtime protocol switch and behavior adjustment due to some other outdated open source project weird behavior, make it one of the best option for beginner who have no idea what they are doing and want to tinker around.&lt;/p&gt;

&lt;h3&gt;
  
  
  📘 Quick Comparison
&lt;/h3&gt;

&lt;p&gt;Here is a quick comparison between Zoraxy and NPM&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Zoraxy&lt;/th&gt;
&lt;th&gt;Nginx Proxy Manger&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTTP(s) Reverse Proxy&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebSocket support&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stream Proxy (TCP / UDP)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beginner Friendly UI&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACME Support&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SNI Support&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (Require manually linking certificate to host name)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access Control&lt;/td&gt;
&lt;td&gt;Easy (IP / GeoIP based access control rule, adjustable from web UI)&lt;/td&gt;
&lt;td&gt;Advance (Manually input nginx settings)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request handling speed&lt;/td&gt;
&lt;td&gt;Slower&lt;/td&gt;
&lt;td&gt;Faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restart required on config change&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Level of Customization&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Level of Automation&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Require Docker&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runs on Windows&lt;/td&gt;
&lt;td&gt;Yes (Natively)&lt;/td&gt;
&lt;td&gt;Kinda (with some magic)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reverse Proxy Engine&lt;/td&gt;
&lt;td&gt;DPCore (Modify from Golang offical http proxy)&lt;/td&gt;
&lt;td&gt;Nginx&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  🤔 Which one should I choose for my homelab?
&lt;/h2&gt;

&lt;p&gt;This question really depends on your own knowledge in homelab networking and self-hosting. &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%2F4ruuegd4gu33vpy76j5p.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%2F4ruuegd4gu33vpy76j5p.png" alt="npm" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Nginx Proxy Manager
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;More users: if you have any question, you can easily find someone to help you&lt;/li&gt;
&lt;li&gt;Faster: Nginx is a stable and old reverse proxy server that have been intensively optimized for performance. With its static configuration design, all request can be handled in faster speed and allowing much better resources utilization. Suitable for use cases where you are tight on computing power or hardware resources like hosting on a single Raspberry Pi. &lt;/li&gt;
&lt;li&gt;Higher customization: NPM gives you much better control on what to do with the routing. If you have some specific setup that require detail fine tuning, NPM (with custom configuration) will be a better option here. &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%2Fase7o782bph3tv5yc5e3.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%2Fase7o782bph3tv5yc5e3.png" alt="zoraxy" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Zoraxy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Newer: Zoraxy is less than 3 years old, making its UI and UX design much more modern and user friendly.&lt;/li&gt;
&lt;li&gt;Automation: Zoraxy have tons of automation logic in place, allowing it to do runtime adjustment with minimal setup input. For example, it has automatic TLS sniffing (for automatic upstream configuration), SNI (picking the correct certificate for you base on request hostname) and automatic websocket proxy (no need to manually setup websocket proxy per hostname. Zoraxy will do an automatic protocol switch based on header sniffing &amp;amp; matching).&lt;/li&gt;
&lt;li&gt;No docker: Zoraxy runs without docker, that means with a single binary file, you can easily deploy it everywhere easily without going through the docker setup. It even works on Windows!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary of Zoraxy vs. Nginx Proxy Manager
&lt;/h2&gt;

&lt;p&gt;Zoraxy is a modern HTTP reverse proxy and forwarding tool designed for small homelab setups. Its distinguishing feature is its Dynamic Proxy Core (DPCore), enabling real-time configuration changes without restarts. It offers tools like ACME support, SNI, and access control while being highly automated and beginner-friendly. Zoraxy doesn't require Docker and runs natively on Windows, making it accessible for users with minimal technical knowledge. However, it has lower customization options and slower request handling compared to Nginx Proxy Manager (NPM).&lt;/p&gt;

&lt;p&gt;Nginx Proxy Manager (NPM) relies on the robust, mature Nginx engine with a PHP-based UI for configuration management. Known for its speed and high resource efficiency, NPM excels in performance and fine-tuning for advanced setups. However, its static configuration requires service restarts for changes, and it lacks modern automation features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Differences&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Zoraxy: Dynamic, automated, Docker-free, beginner-friendly, runs on Windows.&lt;/p&gt;

&lt;p&gt;NPM: Faster, highly customizable, optimized for resource-constrained environments, requires Docker.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Choose NPM for performance and customization, especially on limited hardware like Raspberry Pi. Opt for Zoraxy for simplicity, automation, and a modern interface. Other than that, there are also other open source reverse proxy project like Caddy, which are also worth taking a look into if you are into a more professional solution. &lt;/p&gt;

&lt;p&gt;That is all for today, see you in the future posts :)&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>proxy</category>
      <category>opensource</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Writing the best custom header UX for Zoraxy</title>
      <dc:creator>Toby Chui</dc:creator>
      <pubDate>Mon, 17 Jun 2024 13:33:49 +0000</pubDate>
      <link>https://dev.to/tobychui/writing-the-best-custom-header-ux-for-zoraxy-367n</link>
      <guid>https://dev.to/tobychui/writing-the-best-custom-header-ux-for-zoraxy-367n</guid>
      <description>&lt;p&gt;Recently, I am working on improving the &lt;a href="https://github.com/tobychui/zoraxy" rel="noopener noreferrer"&gt;Zoraxy&lt;/a&gt; (my open source reverse proxy server written in Golang). As this is not a post about reverse proxy but UX, I am gonna sum up this project in one sentence: Zoraxy is a reverse proxy server that uses UX first design.&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%2F7wwdn1oeg066snvep1o4.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%2F7wwdn1oeg066snvep1o4.png" alt="Image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And one of the most challenging part I recently encounter is designing a UI for the header rewrite rule sets. &lt;/p&gt;

&lt;h2&gt;
  
  
  What is a header rewrite rule?
&lt;/h2&gt;

&lt;p&gt;A header rewrite rule set is a few lines of header key-value that is gonna be injected into the HTTP request header when the proxy is processing the request. The rewrite can happens in two directions. One is when the request is proxying from downstream to upstream (aka from client like user's browsers to origin server like web server), another one is from upstream to downstream. Not to mention each direction there will be two operations, either you can add a new header to the request header, or delete a header from the current request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design for beginners vs professionals
&lt;/h2&gt;

&lt;p&gt;For those who have networking background, it is a really simple thing to understand. However, Zoraxy is aiming for users who have little to no networking background, how to express the correct meaning to user gives me some great headache in designing the UI for this function. &lt;/p&gt;

&lt;h3&gt;
  
  
  UI for adding custom headers
&lt;/h3&gt;

&lt;p&gt;Eventually, I come up with a solution. So first I created a web form that try to avoid the upstream and downstream technical terms and favor for a more easily understandable diagrams. Next, adding the next step of the operations as a new sets of buttons below the direction choosing buttons.&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%2Fskyqzd02v95wg9opea0n.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%2Fskyqzd02v95wg9opea0n.png" alt="Image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then now, we have a very clear and easy to operate web  form for creating and editing custom headers. Next, how can we list the current existing custom headers?&lt;/p&gt;

&lt;h3&gt;
  
  
  UI for Listing Current Custom Headers
&lt;/h3&gt;

&lt;p&gt;To list the custom headers with direction indication, we can reuse the cognitive awareness that the user learnt while creating the custom header. So in the table of custom headers, I uses the same color and direction arrows to indicate the header add / remove directions. I also pick the same add and remove icon as the operations buttons. So now, we can easily get a sense on how and when the header is being rewritten. &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%2F5c3vy708fbzucrfzjeyj.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%2F5c3vy708fbzucrfzjeyj.png" alt="Image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And well, sometime user forgets their experience in creating custom headers. They might be setting this up last year and now revising the table and forgotten what those arrows means. So I added two lines of instruction at the bottom of the table. For the "add" and "remove" icon, I think that is common sense enough that I don't need to explain them so I didn't left a remarks for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Permission-Policy
&lt;/h2&gt;

&lt;p&gt;Permission-Policy is a HTTP header that defines what policy / sensors that your website can access. It is a terribly long header that looks something like this&lt;/p&gt;

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

Permissions-Policy: accelerometer=*, ambient-light-sensor=(self),
 autoplay=*, battery=(), camera=*, cross-origin-isolated=(), 
display-capture=*, document-domain=(self), encrypted-media=*, 
execution-while-not-rendered=*, fullscreen=(self), geolocation=*, 
gyroscope=(), keyboard-map=(self), magnetometer=*, microphone=*,
 midi=*, navigation-override=*, payment=*, picture-in-picture=(),
 publickey-credentials-get=*, screen-wake-lock=*,
 sync-xhr=(self), usb=*, web-share=*, xr-spatial-tracking=*,
 clipboard-read=*, clipboard-write=(self), gamepad=(),
 speaker-selection=(self), conversion-measurement=*,
 focus-without-user-activation=*, hid=(), idle-detection=*,
 interest-cohort=*, serial=(), sync-script=(self),
 trust-token-redemption=*, unload=(self), window-placement=*,
 vertical-scroll=*


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

&lt;/div&gt;
&lt;p&gt;If I am developing for professionals, I could have just left a text-area and call it a day. However, Zoraxy is design for beginner and if I leave a text-area in place, people are gonna fill in some weird things and open bug issues claiming Zoraxy is not working and buggy. That is why I designed a web form for it again. &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%2Fikoofxaadfl78wz4gqnr.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%2Fikoofxaadfl78wz4gqnr.png" alt="Image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the bottom of the list, there are a separately color section for experimental policies. This can help notify the user that these might not be supported by all browsers.&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%2Fnjvk0e9qftrp7w90dd2r.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%2Fnjvk0e9qftrp7w90dd2r.png" alt="Image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this is probably the longest web form I have developed for this project. But at least this details here can make sure no one fill in invalid stuffs into the Permission-Policy header field (I hope)&lt;/p&gt;

&lt;p&gt;Anyway, this project really challenge my UX design experience. I guess nowadays most devs will just use framework and make the UI looks good, but UI looking good is not the same as good UX. Sometime attention to details like this might helps a lot in allow user to stick with your software and willing to contribute.&lt;/p&gt;

&lt;p&gt;Lastly, here is the git repo if you are looking for an easy to use reverse proxy server and you are interested to give it a try. &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tobychui" rel="noopener noreferrer"&gt;
        tobychui
      &lt;/a&gt; / &lt;a href="https://github.com/tobychui/zoraxy" rel="noopener noreferrer"&gt;
        zoraxy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/tobychui/zoraxy./img/title.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftobychui%2Fzoraxy.%2Fimg%2Ftitle.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Zoraxy&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Features&lt;/h3&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Simple to use interface with detail in-system instructions&lt;/li&gt;
&lt;li&gt;Reverse Proxy (HTTP/2)
&lt;ul&gt;
&lt;li&gt;Virtual Directory&lt;/li&gt;
&lt;li&gt;WebSocket Proxy (automatic, no set-up needed)&lt;/li&gt;
&lt;li&gt;Basic Auth&lt;/li&gt;
&lt;li&gt;Alias Hostnames&lt;/li&gt;
&lt;li&gt;Custom Headers&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Redirection Rules&lt;/li&gt;

&lt;li&gt;TLS / SSL setup and deploy

&lt;ul&gt;
&lt;li&gt;ACME features like auto-renew to serve your sites in http&lt;strong&gt;s&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;SNI support (and SAN certs)&lt;/li&gt;
&lt;li&gt;DNS Challenge for Let's Encrypt and &lt;a href="https://go-acme.github.io/lego/dns/" rel="nofollow noopener noreferrer"&gt;these DNS providers&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Blacklist / Whitelist by country or IP address (single IP, CIDR or wildcard for beginners)&lt;/li&gt;

&lt;li&gt;Global Area Network Controller Web UI (ZeroTier not included)&lt;/li&gt;

&lt;li&gt;Stream Proxy (TCP &amp;amp; UDP)&lt;/li&gt;

&lt;li&gt;Integrated Up-time Monitor&lt;/li&gt;

&lt;li&gt;Web-SSH Terminal&lt;/li&gt;

&lt;li&gt;Utilities

&lt;ul&gt;
&lt;li&gt;CIDR IP converters&lt;/li&gt;
&lt;li&gt;mDNS Scanner&lt;/li&gt;
&lt;li&gt;Wake-On-Lan&lt;/li&gt;
&lt;li&gt;Debug Forward Proxy&lt;/li&gt;
&lt;li&gt;IP Scanner&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Others

&lt;ul&gt;
&lt;li&gt;Basic single-admin management mode&lt;/li&gt;
&lt;li&gt;External permission management system for easy system integration&lt;/li&gt;
&lt;li&gt;SMTP config for password reset&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Downloads&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_windows_amd64.exe" rel="noopener noreferrer"&gt;Windows&lt;/a&gt;
/ &lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_amd64" rel="noopener noreferrer"&gt;Linux (amd64)&lt;/a&gt;
/ &lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_arm64" rel="noopener noreferrer"&gt;Linux (arm64)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For other systems or architectures…&lt;/p&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tobychui/zoraxy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;
 

&lt;p&gt;That is all for today. Have a nice day :)&lt;/p&gt;

</description>
      <category>ux</category>
      <category>ui</category>
      <category>html</category>
      <category>design</category>
    </item>
    <item>
      <title>Zoraxy v3 - The brand new Reverse Proxy Server for Noobs</title>
      <dc:creator>Toby Chui</dc:creator>
      <pubDate>Thu, 18 Apr 2024 04:04:00 +0000</pubDate>
      <link>https://dev.to/tobychui/zoraxy-v3-the-brand-new-reverse-proxy-server-for-noobs-f57</link>
      <guid>https://dev.to/tobychui/zoraxy-v3-the-brand-new-reverse-proxy-server-for-noobs-f57</guid>
      <description>&lt;p&gt;Recently I have been working on quite some &lt;a href="https://zoraxy.arozos.com/" rel="noopener noreferrer"&gt;Zoraxy&lt;/a&gt; updates. For those who have never heard of Zoraxy, it is my open source reverse proxy server with features more than enough to run your tiny cluster in your homelab or server room. &lt;/p&gt;

&lt;h4&gt;
  
  
  TL;DR
&lt;/h4&gt;

&lt;p&gt;If you don't care how Zoraxy works and just want a noobs friendly reverse proxy server that works on Windows and Linux, click &lt;a href="https://github.com/tobychui/zoraxy/releases" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is new?
&lt;/h2&gt;

&lt;p&gt;Well, if you have used Zoraxy v2 before, every time you open the web management portable you will find yourself feeling depressed. It is because during the development of v2, I didn't have time to consider the color scheme or how to make it looks more attractive (as an open source project wise). &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%2Ffpf04wk444z80mxwuan8.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%2Ffpf04wk444z80mxwuan8.png" alt="v2 homepage"&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%2Fkop20yp7jpxo8h1c9yie.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%2Fkop20yp7jpxo8h1c9yie.png" alt="v2 proxy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is how the v3 UI looks like.&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%2F5qjqbxqko85vjjghe0xp.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%2F5qjqbxqko85vjjghe0xp.png" alt="homepage"&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%2Fxj6lm3sea47xnnzzoday.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%2Fxj6lm3sea47xnnzzoday.png" alt="default site"&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%2Fw8ir7kn8qgbcls0hx1xt.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%2Fw8ir7kn8qgbcls0hx1xt.png" alt="proxy"&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%2F480z7tcaakfayzl1oyvh.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%2F480z7tcaakfayzl1oyvh.png" alt="virtual directory"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Other than UI updates, there are also tons of new feature added to optimize the proxy core function (or I would say, trying to be as similar to Nginx Proxy Manager (NPM) as possible due to user requests). Generally speaking, this is not a NPM replacement, but more like a system that lets you easier to switch between services for testing and debugging. But anyway, I don't mind people asking for new features that feels like NPM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Default Site
&lt;/h2&gt;

&lt;p&gt;In Zoraxy v3, we added support for Default Site and multiple hostnames support. &lt;br&gt;
The predecessor of Zoraxy (which is called "Web Proxy", a sub-service of the ArozOS system) was designed to handle only one domain reverse proxy only, the v2 design of adding additional support for subdomain and other host name was messy. People are confusing about "Proxy", "Subdomain Proxy" and "proxy Root". That is why in the v3 design, a new interface and setup logic was introduced. With Default Site logic, now people can easily adapt Zoraxy just like Nginx Proxy Manager. &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%2Ftm0bumuimgeibbkk5wqu.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%2Ftm0bumuimgeibbkk5wqu.png" alt="Default site example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The default site provide 4 options to choose from. For starters, you might want to use the Internal Static Web Server and put an "index.html" file as your website homepage using the "Static Web Server" function which Zoraxy also provides. This is more like a traditional Apache kind of experience, where if no routing is match in the apache.conf, all the routing goes into the build in static web server and start serving files from your &lt;code&gt;/var/www/html&lt;/code&gt; folder. &lt;/p&gt;

&lt;p&gt;Redirect and 404 Not Found are also quite straight forward. For redirect, you can enter a target domain / ip address to redirect. This is useful when you are pointing your old (sub)domains to a new one or just straight out blocking those requests for unknown / out-dated subdomains that used to exists.&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%2Fess9qa3seojilzmy5c47.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%2Fess9qa3seojilzmy5c47.png" alt="redirect options"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Certificate for wildcard domains / SNI
&lt;/h2&gt;

&lt;p&gt;In v3, we introduced SNI in TLS/SSL certificate lookup logic. What difference of Zoraxy SNI and other implementation is that it do not require user input for "linking" a certificate with a given host name. &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%2Fhejpv246fqgdlstja000.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%2Fhejpv246fqgdlstja000.png" alt="SNI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In v2, users need to manually set each certificates to a matching domain. For example, if you have a certificate that cover a.example.com and b.example.com, you need to manually set it the matching keyword to "example.com" in order for it to work. Now with the Zoraxy v3 automatic certificate lookup logic, you dont need to link anything. Just upload your certificate (or use the build in ACME tool to generate one from your prefered CA) and Zoraxy will resolve it for you automatically. The only catch is that it will be slightly slower for certificates containing multiple hostnames (e.g. domain.com and anotherdomain.com, might take O(n) time complexity to check all the certificates in the system), but for personal / homelab purpose it is good enough. &lt;/p&gt;
&lt;h2&gt;
  
  
  Access Filters
&lt;/h2&gt;

&lt;p&gt;In the Zoraxy v3.0.2, a host rule independent access filter was introduced. This is yet another cool function in Zoraxy where each proxy rule have their own access filters, which the filter itself also include blacklist / whitelist of country code and/or ip address.&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%2Fcx7b8271fjq36ye15ko6.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%2Fcx7b8271fjq36ye15ko6.png" alt="list filtering"&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%2F8tu41zxdwad42z1mdll1.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%2F8tu41zxdwad42z1mdll1.png" alt="access rule editor"&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%2Fabedo6e7rmjgcvxvnier.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%2Fabedo6e7rmjgcvxvnier.png" alt="selecting an access rule for proxy rule"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Compare to v2 where the global filter rule is applied, the v3 uses a per proxy host rule design, which allow each proxy hostname to have different access rule filter. I personally don't use it as I only host a single domain with my server, but seems many user request for this for more than a year now. So I added this in to make other's life easier.&lt;/p&gt;

&lt;p&gt;For those who still love to use global access rule design, you can modify the "default" filter instead. The default filter works exactly like the previous global access filter and all proxy host by default use the "default" filter as the access control policy.&lt;/p&gt;
&lt;h2&gt;
  
  
  Alias
&lt;/h2&gt;

&lt;p&gt;Another cool feature we added in Zoraxy v3.0.2 is the ability to create alias of a given hostname. People are complaining about cannot create alias host name on Zoraxy. So now we got support for alias host names! You can edit an existing Proxy host name and add alias for router matching.&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%2Fowqxn6wx3656zfnpfu3j.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%2Fowqxn6wx3656zfnpfu3j.png" alt="alias"&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%2F6byqj93cwf17gbms71qc.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%2F6byqj93cwf17gbms71qc.png" alt="alias button"&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%2Fjr401u7xja6mhvp769hj.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%2Fjr401u7xja6mhvp769hj.png" alt="alias editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But there is a trade-off. Using alias is a bit slower than a direct hit in host name resolving (which obviously make sense as it needs to iterate through and check if any hostname or hostname with wildcard matches the request header). So personally, as I only have one domain name, I will just add more proxy entries or redirection instead.&lt;/p&gt;
&lt;h2&gt;
  
  
  Redirection Regex Support
&lt;/h2&gt;

&lt;p&gt;Also, due to high demands for complex rewrite rules, in Zoraxy v3.0.1 we introduced the redirection regex support. If enabled, you can use regular expression in setting up redirection URL.&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%2Fdoofgpjog8l2f666n53j.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%2Fdoofgpjog8l2f666n53j.png" alt="regex redirect"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is not useful for me (as I am the kind of person think regex is like magic spells), but maybe someone will find it useful.&lt;/p&gt;
&lt;h2&gt;
  
  
  Forward Proxy
&lt;/h2&gt;

&lt;p&gt;It is interesting to see Zoraxy can also be used as a forward proxy! Well if you don't want to get a VPN setup and only want to access a few site when you are traveling, you can use Zoraxy as a web-vpn-server kind of thing. This feature is called "Forward Proxy" and you can find Forward Proxy setting in most modern browser. Just make sure you have enabled port forward on your NAT router (and docker container) so you can connect to the forward proxy server with the port you assigned.&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%2F3jzfu3tfo1b055veq79v.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%2F3jzfu3tfo1b055veq79v.png" alt="forward proxy"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Wake-On-LAN
&lt;/h2&gt;

&lt;p&gt;In the Zoraxy v3, the Wake-On-LAN helper is also introduced. This helps with kick starting your cluster in case there is a power loss and your UPS notifiy all servers to shut down. This function is design for people who have a setup that they uses a small form factor PC as software router which it will not be powered off during power outage (powered via UPS maybe?). This node will be used as the restarting node for kick starting the cluster when power resume. &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%2Fcj73balkjurdgvxwgz1h.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%2Fcj73balkjurdgvxwgz1h.png" alt="wol"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using this function and, if you have a few nodes with &lt;a href="https://arozos.com" rel="noopener noreferrer"&gt;"ArozOS"&lt;/a&gt; installed, you can easily add all nodes into the Zoraxy WoL table and kick start them one by one remotely. &lt;/p&gt;
&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Anyway, that is all for today! Zoraxy v3 offers so many new features that now it has become one of my major services powering my distributed cluster. If you are interested to know more or get your hands dirty by digging into the source code, you can find everything you need on my Github repo and project homepage.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tobychui" rel="noopener noreferrer"&gt;
        tobychui
      &lt;/a&gt; / &lt;a href="https://github.com/tobychui/zoraxy" rel="noopener noreferrer"&gt;
        zoraxy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/tobychui/zoraxy./img/title.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftobychui%2Fzoraxy.%2Fimg%2Ftitle.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Zoraxy&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Features&lt;/h3&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Simple to use interface with detail in-system instructions&lt;/li&gt;
&lt;li&gt;Reverse Proxy (HTTP/2)
&lt;ul&gt;
&lt;li&gt;Virtual Directory&lt;/li&gt;
&lt;li&gt;WebSocket Proxy (automatic, no set-up needed)&lt;/li&gt;
&lt;li&gt;Basic Auth&lt;/li&gt;
&lt;li&gt;Alias Hostnames&lt;/li&gt;
&lt;li&gt;Custom Headers&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Redirection Rules&lt;/li&gt;
&lt;li&gt;TLS / SSL setup and deploy
&lt;ul&gt;
&lt;li&gt;ACME features like auto-renew to serve your sites in http&lt;strong&gt;s&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;SNI support (and SAN certs)&lt;/li&gt;
&lt;li&gt;DNS Challenge for Let's Encrypt and &lt;a href="https://go-acme.github.io/lego/dns/" rel="nofollow noopener noreferrer"&gt;these DNS providers&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Blacklist / Whitelist by country or IP address (single IP, CIDR or wildcard for beginners)&lt;/li&gt;
&lt;li&gt;Global Area Network Controller Web UI (ZeroTier not included)&lt;/li&gt;
&lt;li&gt;Stream Proxy (TCP &amp;amp; UDP)&lt;/li&gt;
&lt;li&gt;Integrated Up-time Monitor&lt;/li&gt;
&lt;li&gt;Web-SSH Terminal&lt;/li&gt;
&lt;li&gt;Utilities
&lt;ul&gt;
&lt;li&gt;CIDR IP converters&lt;/li&gt;
&lt;li&gt;mDNS Scanner&lt;/li&gt;
&lt;li&gt;Wake-On-Lan&lt;/li&gt;
&lt;li&gt;Debug Forward Proxy&lt;/li&gt;
&lt;li&gt;IP Scanner&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Others
&lt;ul&gt;
&lt;li&gt;Basic single-admin management mode&lt;/li&gt;
&lt;li&gt;External permission management system for easy system integration&lt;/li&gt;
&lt;li&gt;SMTP config for password reset&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Downloads&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_windows_amd64.exe" rel="noopener noreferrer"&gt;Windows&lt;/a&gt;
/ &lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_amd64" rel="noopener noreferrer"&gt;Linux (amd64)&lt;/a&gt;
/ &lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_arm64" rel="noopener noreferrer"&gt;Linux (arm64)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For other systems or architectures…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tobychui/zoraxy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://zoraxy.arozos.com/" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fzoraxy.arozos.com%2Fimg%2Fog.png" height="auto" class="m-0"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://zoraxy.arozos.com/" rel="noopener noreferrer" class="c-link"&gt;
          Reverse Proxy Server | Zoraxy
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          A reverse proxy server and cluster network gateway for noobs
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fzoraxy.arozos.com%2Ffavicon.png"&gt;
        zoraxy.arozos.com
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;See you in the next dev post! &lt;/p&gt;

</description>
      <category>networking</category>
      <category>webdev</category>
      <category>go</category>
      <category>proxy</category>
    </item>
    <item>
      <title>How I grow my projects based on my OWN needs</title>
      <dc:creator>Toby Chui</dc:creator>
      <pubDate>Sun, 24 Sep 2023 11:26:39 +0000</pubDate>
      <link>https://dev.to/tobychui/how-i-grow-my-projects-based-on-my-own-needs-18ih</link>
      <guid>https://dev.to/tobychui/how-i-grow-my-projects-based-on-my-own-needs-18ih</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sNDHQqW0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/seb4xnj8j91djtdrd5b9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sNDHQqW0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/seb4xnj8j91djtdrd5b9.png" alt="Image description" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a master student working in a lab that research networking technology, I embarked on a journey that combined my passion for coding and my desire for self-convenience. What began as a personal project for fun soon evolved into something much more significant—a versatile, low-power, and highly customizable reverse proxy server that I named Zoraxy. &lt;/p&gt;

&lt;p&gt;To be honest, I have no idea what I am writing (or trying to create), but it seems it is a fun thing to write my journey in a post that not sure how many people will read, just to let you know it is ok to create something that might be useless other than yourself.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tobychui"&gt;
        tobychui
      &lt;/a&gt; / &lt;a href="https://github.com/tobychui/zoraxy"&gt;
        zoraxy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      General purpose (reverse) proxy and forwarding tool for networking noobs. Now written in Go!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/tobychui/zoraxy./img/title.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0Gkt5NYT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/tobychui/zoraxy./img/title.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1 id="user-content-zoraxy"&gt;&lt;a class="heading-link" href="https://github.com/tobychui/zoraxy#zoraxy"&gt;Zoraxy&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;General purpose request (reverse) proxy and forwarding tool for low power devices. Now written in Go!&lt;/p&gt;
&lt;h3 id="user-content-features"&gt;&lt;a class="heading-link" href="https://github.com/tobychui/zoraxy#features"&gt;Features&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Simple to use interface with detail in-system instructions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reverse Proxy&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Subdomain Reverse Proxy&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Virtual Directory Reverse Proxy&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Redirection Rules&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TLS / SSL setup and deploy&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Blacklist by country or IP address (single IP, CIDR or wildcard for beginners)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Global Area Network Controller Web UI (ZeroTier not included)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Integrated Up-time Monitor&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Web-SSH Terminal&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Utilities&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CIDR IP converters&lt;/li&gt;
&lt;li&gt;mDNS Scanner&lt;/li&gt;
&lt;li&gt;IP Scanner&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Others&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Basic single-admin management mode&lt;/li&gt;
&lt;li&gt;External permission management system for easy system integration&lt;/li&gt;
&lt;li&gt;SMTP config for password reset&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="user-content-build-from-source"&gt;&lt;a class="heading-link" href="https://github.com/tobychui/zoraxy#build-from-source"&gt;Build from Source&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Require Go 1.20 or above&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;git clone https://github.com/tobychui/zoraxy
cd ./zoraxy/src/
go mod tidy
go build
sudo ./zoraxy -port=:8000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="user-content-usage"&gt;&lt;a class="heading-link" href="https://github.com/tobychui/zoraxy#usage"&gt;Usage&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Zoraxy provide basic authentication system for standalone mode. To use it in standalone mode, follow the instruction below for your desired deployment platform.&lt;/p&gt;
&lt;h3 id="user-content-standalone-mode"&gt;&lt;a class="heading-link" href="https://github.com/tobychui/zoraxy#standalone-mode"&gt;Standalone Mode&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Standalone mode is the default mode for…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tobychui/zoraxy"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Why it is named Zoraxy?
&lt;/h2&gt;

&lt;p&gt;5 years ago I have another projected named ArozOS and I in need for a solution to set it up with my already existing infrastructure (Apache + wordpress). My first thought is just use Apache as a reverse proxy server, but it turns out to be a bad idea as every time I want to change something or test a new local site, I need to dig deep into the apache config and change a text file that might crash my own system during service restart.&lt;/p&gt;

&lt;p&gt;Nginx proxy manager is something I would want to try, but it require dockers (and probably used a lot of glue code within it), I kind of drop that options when I trying to understand their project.&lt;/p&gt;

&lt;p&gt;That is why I decided to write my own, modified from a really old project of mine named "Reverse Proxy", which is an ArozOS subservice design to, as the name suggest, reverse proxy another applications with web interface but not supporting iframe embedding in ArozOS web desktop interface.  &lt;/p&gt;

&lt;p&gt;So later on, as I am adding more and more features to it, I decided to separate it into another project that makes maintaining it more easy. That is why I came up with the name Zoraxy - reverse proXY for ArOZ (reversed, so "Zora")&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X4OO7sji--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/huxrqzg2t9h1918lr3tj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X4OO7sji--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/huxrqzg2t9h1918lr3tj.png" alt="Image description" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Make Zoraxy Special?
&lt;/h2&gt;

&lt;p&gt;One of the standout features of Zoraxy is its user-friendly interface, complete with detailed in-system instructions. It's designed with both beginners and experienced users in mind, ensuring that anyone, regardless of their level of expertise, can harness its power. But what truly sets Zoraxy apart are the plethora of functionalities it offers. If you have a features that you don't know how to use, you can always find a small text under the button, a side bar with instruction or a collapsible section that explain how things work. That means even without a proper documentation, user still know how to operate them with ease.&lt;/p&gt;

&lt;p&gt;At its core, Zoraxy is a robust reverse proxy server (which I copied some of the http transport settings from Caddy). It excels in handling subdomain and virtual directory reverse proxy configurations, making it easy to route incoming requests to the appropriate destinations. &lt;/p&gt;

&lt;p&gt;It also got tons of other features that I am just too lazy to type them out here. If you are interested on the functionality perspective of this project, go take a look at the official website &lt;a href="https://zoraxy.arozos.com"&gt;over here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nUuhdA64--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/64egj5ckaqd7g6oh464p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nUuhdA64--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/64egj5ckaqd7g6oh464p.png" alt="Image description" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How a Reverse Proxy server become a Swiss knife?
&lt;/h2&gt;

&lt;p&gt;To be honest I have no idea. I just add in things that I need (and people needs) into the project and it started to become larger and larger. &lt;/p&gt;

&lt;p&gt;At first I was like "Maybe I can add a new feature that shows the up time of each of my subdomain target", and then when there are a lot of people visiting my site, I was wondering "Where do all these people came from?" From that, I added in a statistic collector and find a lot of them actually bots and crawler scripts. Then I added in access control system that blocks some of the bots and crawler from wasting my bandwidth.&lt;/p&gt;

&lt;p&gt;Another issue is that I have a few nodes placed in the university where there is way to reach them via public internet. That is why I added in the GAN support (zerotier based) and a lot of other tools to help with the routing and networking of such complex environment.&lt;/p&gt;

&lt;p&gt;After a year of developments and tons of feature added, a few collaborator who are interested in this project hopped in and help with the ACME / TLS auto renew modules, which gives it a much higher popularity among the homelab community.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learn from creating yet another open source project
&lt;/h2&gt;

&lt;p&gt;In the realm of software development, we often find ourselves in situations where existing tools and software solutions don't quite fit our needs. It's a common predicament faced by both beginners and seasoned professionals alike. But what if I told you that this situation could be an incredible opportunity for personal and professional growth? The answer lies in a simple yet profound piece of advice: if you can't find the right software, consider building it yourself.&lt;/p&gt;

&lt;p&gt;Because of the Zoraxy project, I got the chances to share my project on different open source conference, events and a new chat  topic with my friends in CS. Now, I understand that the prospect of developing your own software can be daunting, especially if you're new to programming. However, it's precisely this challenge that makes it an invaluable learning experience. Writing your own software forces you to dive deep into the world of code, algorithms, and problem-solving. It's a crash course in programming that no tutorial or online course can replicate.&lt;/p&gt;

&lt;p&gt;By tackling a software project from scratch, you open doors to networking and mentorship opportunities you might not have encountered otherwise. You'll meet individuals who are equally passionate about technology, innovation, and problem-solving. These connections can lead to collaborations, job offers, or simply a broader network of peers who can provide insights and support throughout your career.&lt;/p&gt;

&lt;p&gt;So, the next time you find yourself frustrated by the limitations of existing software, consider building it yourself :)&lt;/p&gt;

</description>
      <category>go</category>
      <category>beginners</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Reverse Proxy Server for noobs (not Nginx / Apache!)</title>
      <dc:creator>Toby Chui</dc:creator>
      <pubDate>Sun, 04 Jun 2023 03:41:43 +0000</pubDate>
      <link>https://dev.to/tobychui/reverse-proxy-server-for-noobs-not-nginx-apache-352d</link>
      <guid>https://dev.to/tobychui/reverse-proxy-server-for-noobs-not-nginx-apache-352d</guid>
      <description>&lt;p&gt;I guess most of the web devs out there know what is a reverse proxy server. It is especially useful when you have multiple services deployed on a single homelab rack or a cluster of servers and want to expose them to the internet using a single gateway server with different subdomains. In this blog post, I will simply share my experience on why I wrote my own reverse proxy server in Go to replace apache / nginx in my distributed homelab setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Reverse Proxy Server?
&lt;/h2&gt;

&lt;p&gt;Imagine you're at a music festival, grooving to your favorite tunes, and suddenly you need a refreshing drink. You don't want to miss a beat, so you send your friend, the reverse proxy, to fetch it for you. Your friend navigates through the crowd, stands in line, and brings back that ice-cold beverage without interrupting your dance moves. In this scenario, the reverse proxy server acts as your go-between, shielding you (the user) from the chaos of the festival (the backend servers) and delivering the content you desire. &lt;/p&gt;

&lt;p&gt;Modern reverse proxy do more things just to relay and fetch webpage for you. For example, load balancing, failover, some managers like Nginx Proxy Manager (NPM, not the blackhole npm) even have build in automatic SSL certificates renew and so on. That is why reverse proxy is usually considered the "heart" of a homelab system (or any kind of web systems)&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I am writing my own Reverse Proxy Server?
&lt;/h2&gt;

&lt;p&gt;If you have never experience with the pain of writing an apache or nginx config as a senior dev-ops, it is a really memorable experience. You try copy things from the internet, modify them according to your knowledge and figure out its routing is unpredictable. Sometime the routing only works in testing environment but not the productions, and many web services that depends on some special settings in rewrite rules and headers. It is just pain in the *** to make all the service running.&lt;/p&gt;

&lt;p&gt;Besides, it is just inconvenience to ssh into your gateway server and modify the config when you just want to add a temporary rules for proxying a testing environment or systems. &lt;/p&gt;

&lt;p&gt;There are alternatives or solutions to these issue in the open source world. Like NPM is the one that most recommended by Redditors in r/selfhost. However, it have been outdated and it is known to break a lot of stuffs with the latest updates. That is why I decided to write my own to solve my issue in the foreseeable future. &lt;/p&gt;

&lt;h2&gt;
  
  
  That is why Zoraxy was born
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tobychui" rel="noopener noreferrer"&gt;
        tobychui
      &lt;/a&gt; / &lt;a href="https://github.com/tobychui/zoraxy" rel="noopener noreferrer"&gt;
        zoraxy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/tobychui/zoraxy./img/title.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftobychui%2Fzoraxy.%2Fimg%2Ftitle.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Zoraxy&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Features&lt;/h3&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Simple to use interface with detail in-system instructions&lt;/li&gt;
&lt;li&gt;Reverse Proxy (HTTP/2)
&lt;ul&gt;
&lt;li&gt;Virtual Directory&lt;/li&gt;
&lt;li&gt;WebSocket Proxy (automatic, no set-up needed)&lt;/li&gt;
&lt;li&gt;Basic Auth&lt;/li&gt;
&lt;li&gt;Alias Hostnames&lt;/li&gt;
&lt;li&gt;Custom Headers&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Redirection Rules&lt;/li&gt;
&lt;li&gt;TLS / SSL setup and deploy
&lt;ul&gt;
&lt;li&gt;ACME features like auto-renew to serve your sites in http&lt;strong&gt;s&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;SNI support (and SAN certs)&lt;/li&gt;
&lt;li&gt;DNS Challenge for Let's Encrypt and &lt;a href="https://go-acme.github.io/lego/dns/" rel="nofollow noopener noreferrer"&gt;these DNS providers&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Blacklist / Whitelist by country or IP address (single IP, CIDR or wildcard for beginners)&lt;/li&gt;
&lt;li&gt;Global Area Network Controller Web UI (ZeroTier not included)&lt;/li&gt;
&lt;li&gt;Stream Proxy (TCP &amp;amp; UDP)&lt;/li&gt;
&lt;li&gt;Integrated Up-time Monitor&lt;/li&gt;
&lt;li&gt;Web-SSH Terminal&lt;/li&gt;
&lt;li&gt;Utilities
&lt;ul&gt;
&lt;li&gt;CIDR IP converters&lt;/li&gt;
&lt;li&gt;mDNS Scanner&lt;/li&gt;
&lt;li&gt;Wake-On-Lan&lt;/li&gt;
&lt;li&gt;Debug Forward Proxy&lt;/li&gt;
&lt;li&gt;IP Scanner&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Others
&lt;ul&gt;
&lt;li&gt;Basic single-admin management mode&lt;/li&gt;
&lt;li&gt;External permission management system for easy system integration&lt;/li&gt;
&lt;li&gt;SMTP config for password reset&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Downloads&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_windows_amd64.exe" rel="noopener noreferrer"&gt;Windows&lt;/a&gt;
/ &lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_amd64" rel="noopener noreferrer"&gt;Linux (amd64)&lt;/a&gt;
/ &lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_arm64" rel="noopener noreferrer"&gt;Linux (arm64)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For other systems or architectures…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tobychui/zoraxy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
Zoraxy is a Go-Powered Reverse Proxy and Forwarding Tool for Web Developers

&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%2F5ij8hnfey6vgqmv0kicz.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%2F5ij8hnfey6vgqmv0kicz.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking for a versatile and user-friendly reverse proxy and forwarding tool? Look no further than Zoraxy! Built from the ground up using the power of Go, Zoraxy is a go-to solution for low-power devices and beyond. With its comprehensive set of features and straightforward interface, Zoraxy empowers both senior and casual web developers to effortlessly manage their web services and enhance their online presence.&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%2Fvmyfcu7yb56syb1nu5ma.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%2Fvmyfcu7yb56syb1nu5ma.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's delve into the exciting world of Zoraxy and explore its impressive feature set. First and foremost, Zoraxy shines as a reverse proxy, allowing you to seamlessly route incoming requests to the appropriate backend servers. Whether you need subdomain reverse proxy capabilities or virtual directory reverse proxy functionality, Zoraxy has got you covered.&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%2Fchukt69kuwtco9g4td4f.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%2Fchukt69kuwtco9g4td4f.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But that's not all! Zoraxy goes above and beyond with its extensive array of features. Need to set up redirection rules? Zoraxy has you covered. &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%2Fra77b6e251u3xdjtjol0.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%2Fra77b6e251u3xdjtjol0.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking to implement TLS/SSL for enhanced security? Zoraxy has your back there too. &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%2Fx71wurei50801ss01qye.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%2Fx71wurei50801ss01qye.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Worried about unwanted traffic? Zoraxy offers the ability to blacklist specific countries or IP addresses, providing you with granular control over your network.&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%2Fddxqp4muzz0k3q2gul2r.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%2Fddxqp4muzz0k3q2gul2r.png" alt="Image description"&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%2Fm1acjw0f2csy2j7el1ly.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%2Fm1acjw0f2csy2j7el1ly.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Zoraxy doesn't stop at just being a reverse proxy either. It comes packed with handy utilities like CIDR IP converters, mDNS Scanner, and IP Scanner, making your web development tasks a breeze. &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%2Ffi374x9d8fyxw3xaycvb.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%2Ffi374x9d8fyxw3xaycvb.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And for those times when you need remote access, Zoraxy even provides a convenient Web-SSH Terminal.&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%2F8fe4tv8oxtnyg4b7gp95.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%2F8fe4tv8oxtnyg4b7gp95.png" alt="Image description"&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%2Fryd0a06t391xor5mv0a1.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%2Fryd0a06t391xor5mv0a1.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Want to know who are visiting your site? Zoraxy also got a build in statistic viewer where you can get an overview on the traffics on your site without the need of cookies (non-personalized tracking)&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%2Fhvaq2g1ynoprwbwuk0e5.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%2Fhvaq2g1ynoprwbwuk0e5.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Managing Zoraxy is a breeze thanks to its simple-to-use interface and in-depth instructions built right into the system. Whether you're a seasoned developer or just starting out, Zoraxy offers a basic single-admin management mode (like a home router) that ensures a hassle-free experience. If you already got an upstream reverse proxy server with permission management set-up, you can even run Zoraxy in no-auth mode, making it integrate with your current infrastructure with ease.&lt;/p&gt;
&lt;h2&gt;
  
  
  Work In Progress
&lt;/h2&gt;

&lt;p&gt;As this was just open source a few weeks ago, I still got a few more functions that I wanted to add and it is working in progress. One of the most requested features are the ACME automatic certificate update. However, I am still working on the details on how ACME works. &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%2Fw8qm6wefurpbew6zx56q.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%2Fw8qm6wefurpbew6zx56q.png" alt="Image description"&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%2F8lwun0zer8hm4qqrudc5.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%2F8lwun0zer8hm4qqrudc5.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Meanwhile, there are a few experimental functions that is nearly completed. Like the TCP Proxy and ZeroTier controller integrations. &lt;/p&gt;
&lt;h2&gt;
  
  
  Know More
&lt;/h2&gt;

&lt;p&gt;If you are interested to learn more about this open source project, feel free to take a look at our website at&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://zoraxy.arozos.com/" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fzoraxy.arozos.com%2Fimg%2Fog.png" height="auto" class="m-0"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://zoraxy.arozos.com/" rel="noopener noreferrer" class="c-link"&gt;
          Reverse Proxy Server | Zoraxy
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          A reverse proxy server and cluster network gateway for noobs
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fzoraxy.arozos.com%2Ffavicon.png"&gt;
        zoraxy.arozos.com
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;or our github repo over at&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tobychui" rel="noopener noreferrer"&gt;
        tobychui
      &lt;/a&gt; / &lt;a href="https://github.com/tobychui/zoraxy" rel="noopener noreferrer"&gt;
        zoraxy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/tobychui/zoraxy./img/title.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftobychui%2Fzoraxy.%2Fimg%2Ftitle.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Zoraxy&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Features&lt;/h3&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Simple to use interface with detail in-system instructions&lt;/li&gt;
&lt;li&gt;Reverse Proxy (HTTP/2)
&lt;ul&gt;
&lt;li&gt;Virtual Directory&lt;/li&gt;
&lt;li&gt;WebSocket Proxy (automatic, no set-up needed)&lt;/li&gt;
&lt;li&gt;Basic Auth&lt;/li&gt;
&lt;li&gt;Alias Hostnames&lt;/li&gt;
&lt;li&gt;Custom Headers&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Redirection Rules&lt;/li&gt;
&lt;li&gt;TLS / SSL setup and deploy
&lt;ul&gt;
&lt;li&gt;ACME features like auto-renew to serve your sites in http&lt;strong&gt;s&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;SNI support (and SAN certs)&lt;/li&gt;
&lt;li&gt;DNS Challenge for Let's Encrypt and &lt;a href="https://go-acme.github.io/lego/dns/" rel="nofollow noopener noreferrer"&gt;these DNS providers&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Blacklist / Whitelist by country or IP address (single IP, CIDR or wildcard for beginners)&lt;/li&gt;
&lt;li&gt;Global Area Network Controller Web UI (ZeroTier not included)&lt;/li&gt;
&lt;li&gt;Stream Proxy (TCP &amp;amp; UDP)&lt;/li&gt;
&lt;li&gt;Integrated Up-time Monitor&lt;/li&gt;
&lt;li&gt;Web-SSH Terminal&lt;/li&gt;
&lt;li&gt;Utilities
&lt;ul&gt;
&lt;li&gt;CIDR IP converters&lt;/li&gt;
&lt;li&gt;mDNS Scanner&lt;/li&gt;
&lt;li&gt;Wake-On-Lan&lt;/li&gt;
&lt;li&gt;Debug Forward Proxy&lt;/li&gt;
&lt;li&gt;IP Scanner&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Others
&lt;ul&gt;
&lt;li&gt;Basic single-admin management mode&lt;/li&gt;
&lt;li&gt;External permission management system for easy system integration&lt;/li&gt;
&lt;li&gt;SMTP config for password reset&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Downloads&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_windows_amd64.exe" rel="noopener noreferrer"&gt;Windows&lt;/a&gt;
/ &lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_amd64" rel="noopener noreferrer"&gt;Linux (amd64)&lt;/a&gt;
/ &lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_arm64" rel="noopener noreferrer"&gt;Linux (arm64)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For other systems or architectures…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tobychui/zoraxy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;If you like this project, please give us a star so we know there are users waiting and looking forward to this project to enter Beta phrase. Thanks for the support :)&lt;/p&gt;

</description>
      <category>linux</category>
      <category>networking</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Hack the Golang httputil Reverse Proxy settings to handle more requests</title>
      <dc:creator>Toby Chui</dc:creator>
      <pubDate>Sat, 03 Jun 2023 12:36:24 +0000</pubDate>
      <link>https://dev.to/tobychui/hack-the-golang-httputil-reverse-proxy-settings-to-handle-more-requests-1aia</link>
      <guid>https://dev.to/tobychui/hack-the-golang-httputil-reverse-proxy-settings-to-handle-more-requests-1aia</guid>
      <description>&lt;p&gt;Recently I run into a problem where the Golang offical httputil reverse proxy get stuck when testing my open source reverse proxy server &lt;a href="http://zoraxy.arozos.com/" rel="noopener noreferrer"&gt;Zoraxy&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tobychui" rel="noopener noreferrer"&gt;
        tobychui
      &lt;/a&gt; / &lt;a href="https://github.com/tobychui/zoraxy" rel="noopener noreferrer"&gt;
        zoraxy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/tobychui/zoraxy./img/title.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftobychui%2Fzoraxy.%2Fimg%2Ftitle.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Zoraxy&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Features&lt;/h3&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Simple to use interface with detail in-system instructions&lt;/li&gt;
&lt;li&gt;Reverse Proxy (HTTP/2)
&lt;ul&gt;
&lt;li&gt;Virtual Directory&lt;/li&gt;
&lt;li&gt;WebSocket Proxy (automatic, no set-up needed)&lt;/li&gt;
&lt;li&gt;Basic Auth&lt;/li&gt;
&lt;li&gt;Alias Hostnames&lt;/li&gt;
&lt;li&gt;Custom Headers&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Redirection Rules&lt;/li&gt;
&lt;li&gt;TLS / SSL setup and deploy
&lt;ul&gt;
&lt;li&gt;ACME features like auto-renew to serve your sites in http&lt;strong&gt;s&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;SNI support (and SAN certs)&lt;/li&gt;
&lt;li&gt;DNS Challenge for Let's Encrypt and &lt;a href="https://go-acme.github.io/lego/dns/" rel="nofollow noopener noreferrer"&gt;these DNS providers&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Blacklist / Whitelist by country or IP address (single IP, CIDR or wildcard for beginners)&lt;/li&gt;
&lt;li&gt;Global Area Network Controller Web UI (ZeroTier not included)&lt;/li&gt;
&lt;li&gt;Stream Proxy (TCP &amp;amp; UDP)&lt;/li&gt;
&lt;li&gt;Integrated Up-time Monitor&lt;/li&gt;
&lt;li&gt;Web-SSH Terminal&lt;/li&gt;
&lt;li&gt;Utilities
&lt;ul&gt;
&lt;li&gt;CIDR IP converters&lt;/li&gt;
&lt;li&gt;mDNS Scanner&lt;/li&gt;
&lt;li&gt;Wake-On-Lan&lt;/li&gt;
&lt;li&gt;Debug Forward Proxy&lt;/li&gt;
&lt;li&gt;IP Scanner&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Others
&lt;ul&gt;
&lt;li&gt;Basic single-admin management mode&lt;/li&gt;
&lt;li&gt;External permission management system for easy system integration&lt;/li&gt;
&lt;li&gt;SMTP config for password reset&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Downloads&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_windows_amd64.exe" rel="noopener noreferrer"&gt;Windows&lt;/a&gt;
/ &lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_amd64" rel="noopener noreferrer"&gt;Linux (amd64)&lt;/a&gt;
/ &lt;a href="https://github.com/tobychui/zoraxy/releases/latest/download/zoraxy_linux_arm64" rel="noopener noreferrer"&gt;Linux (arm64)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For other systems or architectures…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tobychui/zoraxy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Have you ever try to open a website that is under heavy load? The connection just freeze there and do nothing until other objects in your site is loaded and free up some connections, then your content continue to load. This should not be happening as Go is known to be able to handle many requests in the same time using its magical go-routine. That is why I started to question about the implementation of the httputil.reverseproxy library.&lt;/p&gt;

&lt;p&gt;If you don't know there is an official http reverse proxy in the Golang source code, this is the one I am talking about.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://go.dev/src/net/http/httputil/reverseproxy.go" rel="noopener noreferrer"&gt;https://go.dev/src/net/http/httputil/reverseproxy.go&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And within the source code, you will notice there is a line in the reverse proxy struct object that make use of something called the http.Transport&lt;/p&gt;

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

// The transport used to perform proxy requests.

// If nil, http.DefaultTransport is used.

Transport http.RoundTripper


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

&lt;/div&gt;

&lt;p&gt;After debugging a few days and tracing the Go source, I notice the issue was that the http.DefaultTransport pre-set a relatively low limits on the maximum connections counts. This setting is reasonable as most of them time this reverse proxy is used to serve single user instead of using it as a replacement of nginx or apache reverse proxy. &lt;/p&gt;

&lt;p&gt;That is why in order to fix it, I included a modified version of the reverse proxy into Zoraxy project with the following modifications:&lt;/p&gt;

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

//Hack the default transporter to handle more connections
thisTransporter := http.DefaultTransport
thisTransporter.(*http.Transport).MaxIdleConns = 3000
thisTransporter.(*http.Transport).MaxIdleConnsPerHost = 3000
thisTransporter.(*http.Transport).IdleConnTimeout = 10 * time.Second
thisTransporter.(*http.Transport).MaxConnsPerHost = 0


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

&lt;/div&gt;

&lt;p&gt;Now, my reverse proxy can serve much more request in a given time compare to the http.DefaultTransport settings used in the original reverse proxy utilities!&lt;/p&gt;

&lt;p&gt;---------- Updates -------------&lt;br&gt;
After a few months of further investigation and testing, I found that it would be better for each of the reverse proxy object (aka it will only have 1 host handled by each Transport object) to have the following configurations&lt;/p&gt;

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

//Hack the default transporter to handle more connections
thisTransporter := http.DefaultTransport
optimalConcurrentConnection := 32
thisTransporter.(*http.Transport).MaxIdleConns = optimalConcurrentConnection * 2
thisTransporter.(*http.Transport).MaxIdleConnsPerHost = optimalConcurrentConnection
thisTransporter.(*http.Transport).IdleConnTimeout = 30 * time.Second
thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2
thisTransporter.(*http.Transport).DisableCompression = true


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

&lt;/div&gt;

&lt;p&gt;Compare to the previous settings which uses around 85% of my bandwidth, this settings can utilize up to around 90 - 92% of my outbound bandwidth. I have no idea where these numbers come from, but it works great. &lt;/p&gt;

</description>
      <category>go</category>
      <category>webdev</category>
      <category>networking</category>
    </item>
    <item>
      <title>Announcing ArozOS 2.0 - 5 years journey into my own Web Desktop OS</title>
      <dc:creator>Toby Chui</dc:creator>
      <pubDate>Fri, 12 May 2023 03:58:45 +0000</pubDate>
      <link>https://dev.to/tobychui/announcing-arozos-20-5-years-journey-into-my-own-web-desktop-os-5anp</link>
      <guid>https://dev.to/tobychui/announcing-arozos-20-5-years-journey-into-my-own-web-desktop-os-5anp</guid>
      <description>&lt;p&gt;This is a story about a poor student who wants to build his own storage solution with cheap computers. It all starts with just a few php scripts serving my mp3 files from my server, and now it become one of the most complex and (maybe the) best designed web desktop system out there in the open source world.&lt;/p&gt;

&lt;p&gt;2 years ago, I have written another post sharing the v1.0 release and the back story of the system. If you are interested in the evolution of this system starting from 2018, you can check out the following post.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/tobychui" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F493190%2Fac9cc59e-ba0b-498d-8945-05bcd7220f01.png" alt="tobychui"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/tobychui/i-write-my-own-web-desktop-os-for-3-years-and-this-is-what-it-looks-like-now-2903" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;I write my own web desktop OS for 3 years and this is what it looks like now&lt;/h2&gt;
      &lt;h3&gt;Toby Chui ・ Apr 4 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#go&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#linux&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ux&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;And this is how it looks like now.&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%2Fj151d2vl3lrswuosrrlv.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%2Fj151d2vl3lrswuosrrlv.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tobychui" rel="noopener noreferrer"&gt;
        tobychui
      &lt;/a&gt; / &lt;a href="https://github.com/tobychui/arozos" rel="noopener noreferrer"&gt;
        arozos
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Web Desktop Operating System for low power platforms, Now written in Go!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/tobychui/arozosimg/banner.png?raw=true"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftobychui%2Farozosimg%2Fbanner.png%3Fraw%3Dtrue" alt="Image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/3e7e87b362e1a8f03281bebf8030dd549c7ad783b64f4fa34f631c0e146c27d0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d47504c76332d626c7565"&gt;&lt;img src="https://camo.githubusercontent.com/3e7e87b362e1a8f03281bebf8030dd549c7ad783b64f4fa34f631c0e146c27d0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d47504c76332d626c7565"&gt;&lt;/a&gt; &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/1ad13cd26fa8dc67de66bbe9d2837d6affb8d06f482555ab3c2219502d7eb389/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4465766963652d5261737062657272792532305069253230334225324225323025324625323034422d726564"&gt;&lt;img src="https://camo.githubusercontent.com/1ad13cd26fa8dc67de66bbe9d2837d6affb8d06f482555ab3c2219502d7eb389/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4465766963652d5261737062657272792532305069253230334225324225323025324625323034422d726564"&gt;&lt;/a&gt; &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/4b170781349ea931aa1b4e2133d36f987e287a2f6eb6390c7703f83248d85e02/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d616465253230496e253230486f6e672532304b6f6e672de9a699e6b8afe9968be799bc2d626c756576696f6c6574"&gt;&lt;img src="https://camo.githubusercontent.com/4b170781349ea931aa1b4e2133d36f987e287a2f6eb6390c7703f83248d85e02/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d616465253230496e253230486f6e672532304b6f6e672de9a699e6b8afe9968be799bc2d626c756576696f6c6574"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;User Interface&lt;/h3&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Web Desktop Interface&lt;/li&gt;
&lt;li&gt;Ubuntu remix Windows style startup menu and task bars&lt;/li&gt;
&lt;li&gt;Clean and easy to use File Manager (Support drag drop, upload etc)&lt;/li&gt;
&lt;li&gt;Simplistic System Setting Menu&lt;/li&gt;
&lt;li&gt;No-bull-shit module naming scheme&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Networking&lt;/h3&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Basic Realtime Network Statistic&lt;/li&gt;
&lt;li&gt;Static Web Server (with build in Web Editor!)&lt;/li&gt;
&lt;li&gt;mDNS discovery + SSDP broadcast&lt;/li&gt;
&lt;li&gt;UPnP Port Forwarding&lt;/li&gt;
&lt;li&gt;WiFi Management (Support wpa_supplicant for Rpi or nmcli for Armbian)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;File / Disk Management&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Mount Disk Utilities&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local File Systems (ext4, NTFS, FAT etc)&lt;/li&gt;
&lt;li&gt;Remote File Systems (WebDAV, SMB, SFTP etc)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build in Network File Sharing Servers&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FTP, WebDAV, SFTP&lt;/li&gt;
&lt;li&gt;Basic Auth based simple HTTP interface for legacy devices with outdated browser&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Virtual File System + Sandbox Architecture&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;File Sharing (Similar to Google Drive)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Basic File Operations with Real-time Progress (Copy / Cut / Paste / New File or Folder etc)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Security&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;oAuth&lt;/li&gt;
&lt;li&gt;LDAP&lt;/li&gt;
&lt;li&gt;IP White / Blacklist&lt;/li&gt;
&lt;li&gt;Exponential login timeout&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Extensibility&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;ECMA5 (JavaScript…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tobychui/arozos" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Why v2.0?
&lt;/h2&gt;

&lt;p&gt;The ArozOS v1 is a nice, stable system that enable me to access my files anywhere with internet and a browser from any kind of devices. It perfectly fits my needs when I am a university student studying in a local university. However, as I have started my Master research in another country, now I need to worry about redundancy and remote management issues.&lt;/p&gt;

&lt;p&gt;That is why I have rewritten ArozOS to support mounting and sandbox network file shares into virtual drives. As this is not a technical post, I will simply summarize the update as: I rewrote the base of the whole system so that I can have multiple servers around the world and access them through one single gateway nodes. Just like your PC with mounted network drives!&lt;/p&gt;

&lt;h2&gt;
  
  
  How ArozOS looks like after 5 years
&lt;/h2&gt;

&lt;p&gt;After 5 years of development, it finally matches the definition of modern OS design in all sense. &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%2Fk8t7eujw1zbq3bdmmhey.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%2Fk8t7eujw1zbq3bdmmhey.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We rewritten the UI for the File Manager, which I will describe in details in later sections.&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%2F5t106i9lbxtwbfcz5n3n.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%2F5t106i9lbxtwbfcz5n3n.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As usually, the system comes with build in players for all kind of media files. From music, video and photo to professional formats like psd and raw (support via 3rd party plugins)&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%2F8s2rxt9vcxlgzrxp2m1e.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%2F8s2rxt9vcxlgzrxp2m1e.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We continue to work on a simple web development tools that allows users with little to no web development experience to create and host their website on ArozOS with a few clicks. &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%2F59jc2p244ycsw7jrxxoe.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%2F59jc2p244ycsw7jrxxoe.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For professional use cases, we also provided a new tools that provide Serverless API script powered by ECMA5 (JavaScript like stuffs). So you can host some basic API services with your ArozOS running on your home network. More details below.&lt;/p&gt;
&lt;h2&gt;
  
  
  File Manager
&lt;/h2&gt;

&lt;p&gt;The largest update of ArozOS v2 is the File Manager. The File Manage now contain much better RWD interface for mobile and desktop devices with different screen sizes.&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%2Fk4z0295kqb4mrltjr16u.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%2Fk4z0295kqb4mrltjr16u.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As requested by a lot of users, the File Manager have a new "Detail" viewing mode. This mode allow a simple overview of all file properties within the current folder.&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%2Fuptov9ujjksnp0u2d9vd.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%2Fuptov9ujjksnp0u2d9vd.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A properties sidebar is added to provide detail views over the selected item. This is very helpful when you do not want the "Details" / "Grid" view in a folder containing tons of files while wanting to have a quick way to check the properties of the selected file.&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%2Fdc3071pnl1zwebhn5fxl.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%2Fdc3071pnl1zwebhn5fxl.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also toke a lot of time optimizing the minor places to make user experience much better than the old versions. Including a dynamic adjusted folder path viewer (instead of the old version where it is hard coded to show only 3 folder name at a time)&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%2Fyz4hws0u476ufcm7kqmm.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%2Fyz4hws0u476ufcm7kqmm.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And it even support inline editing function!&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%2F8nthaqug88gjmxc8i0r3.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%2F8nthaqug88gjmxc8i0r3.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We optimized the rename workflow for desktop users. Instead of a popup window asking for the new file name, now we got inline filename editing as well.&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%2Fggomoz33rhtduaqxlxlp.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%2Fggomoz33rhtduaqxlxlp.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Operation popups are moved to the side. So as to optimize for mobile interfaces as well as making the css less painful to maintain due to the old vertical align center design.&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%2Fvnwuj5iqev9wmr3h7z5m.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%2Fvnwuj5iqev9wmr3h7z5m.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Storage Pools
&lt;/h2&gt;

&lt;p&gt;In ArozOS v2, we get a brand new design in the Storage Pool setting interface as well as the backend architecture. In v1.0, you can only mount local disk into ArozOS as "virtual drive", which is basically a mount point on ArozOS, to v2.0's file system abstraction driver design, where you can mount anything that resemble a file system. From local disk, ram disk, network file sharing protocols like SMB, WebDAV, SFTP and FTP, all of them can be mounted as a "disk" in the new ArozOS system.&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%2Fyghvcfw1uotuewmoyoct.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%2Fyghvcfw1uotuewmoyoct.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can easily add new disk into ArozOS via the (+) button. By mounting another ArozOS server into your gateway server, you can access all ArozOS storage pools within a single server, making it much easier to manage.&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%2F6obgmeta09r83isk4wpn.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%2F6obgmeta09r83isk4wpn.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also redesigned the File System Handler properties editor to make it more simplistic and automated. Now many settings are default hidden and only show if you pick something related to it. Making it much more user friendly. &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%2F2159wic651489tksctjz.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%2F2159wic651489tksctjz.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  File Servers
&lt;/h2&gt;

&lt;p&gt;What happens if you want to share files from ArozOS to your devices? In v2.0, you can do it in a lot of ways. For starters, you can use the Share API to share a file from File Manager.&lt;/p&gt;
&lt;h3&gt;
  
  
  Share API
&lt;/h3&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%2Fwjsmsesrs1fm0i9967rr.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%2Fwjsmsesrs1fm0i9967rr.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the new Share API, we added new Share modes so you can share to a particular user group or people you want.&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%2F10iz0hk0oclvllkj0jt8.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%2F10iz0hk0oclvllkj0jt8.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The share interface got a bit update as well.&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%2F6j1zbguiqf6csw8ifn6z.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%2F6j1zbguiqf6csw8ifn6z.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Under each of the preview interface, if it is a web compatible file, you will see an automatically generated embed link, where you can use it in your website like wordpress.&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%2Fjinurrl6cj0pupjee104.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%2Fjinurrl6cj0pupjee104.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order to make the share looks pretty on social network platforms, I added open graph support to the share API, so when you share the link to others, like discord for example, the preview will show up as a background rendered summary image file.&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%2F38g0j11vo12k967un6ov.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%2F38g0j11vo12k967un6ov.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Network File Servers
&lt;/h3&gt;

&lt;p&gt;If you want to access the file on other computers without browser, you can use the build in file server functions. &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%2Fq85xz2ckrdh8e0fdmd0p.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%2Fq85xz2ckrdh8e0fdmd0p.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The build in file server support WebDAV, SFTP and FTP, where if you have a corresponding client like &lt;a href="https://winscp.net/eng/download.php" rel="noopener noreferrer"&gt;WinSCP&lt;/a&gt; or &lt;a href="https://support.apple.com/en-ae/guide/mac-help/mchlp1546/mac" rel="noopener noreferrer"&gt;MacOS build in "Connect to server" function&lt;/a&gt;, you can access your file remotely from any devices.&lt;/p&gt;
&lt;h3&gt;
  
  
  Legacy Browsers Support
&lt;/h3&gt;

&lt;p&gt;Legacy support is always important for ArozOS because even it is 2023, there are still people using Windows 7 or even Windows XP. That is why having backward compatibility is important. In v2.0, I have added a "Directory Server" function in which you can use basic auth to login to your ArozOS and download files from them. &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%2Fv9pvv8faphqxi1vslu8y.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%2Fv9pvv8faphqxi1vslu8y.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This interface provide the most basic css and html where it takes little to no resources to load. I guess it might even works on NDS's browser. Note that basic auth is generally considered not safe. After you are done with it, you should turn it off.&lt;/p&gt;
&lt;h2&gt;
  
  
  Web Development and Serverless
&lt;/h2&gt;

&lt;p&gt;The last update is about web development and serverless. Many of the ArozOS client are design professionals who seeks for a webpage  for showcasing their work. That is why in ArozOS, we included some basic web development tools to get people started with their self hosted webpage.&lt;br&gt;
Personal webpage can be enabled in System Settings &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%2Fob28wkj9qqvgnj0td9rr.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%2Fob28wkj9qqvgnj0td9rr.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few of my friends who are using ArozOS have a bit of programming skills. With request from them and my interest to try out Serverless, we added the Serverless app, where you can insert a ECMA5 code and let it run when a GET or POST request is received.&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%2F4l7cdb7wobm6reklahf4.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%2F4l7cdb7wobm6reklahf4.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;He also insert another script into scheduler so the currency exchange rage will be updated every night just to make his Telegram currency conversion bot works. In my opinion this is just another cool hacky way to use ArozOS to make things works!&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%2Fidr2cbc0t1pdc06ogwdh.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%2Fidr2cbc0t1pdc06ogwdh.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  More?
&lt;/h1&gt;

&lt;p&gt;In this update, we added a lot of minor changes to make the UX better from the v1 releases. From Network Statistic Graphs&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%2Fjlu6q2g0x3dk18cg8go6.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%2Fjlu6q2g0x3dk18cg8go6.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;to background tasks viewers&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%2Fqsg0qr2ix6ly9919ixq1.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%2Fqsg0qr2ix6ly9919ixq1.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and brand new document viewer&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%2Fxad1px15xr2gp9hzz96z.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%2Fxad1px15xr2gp9hzz96z.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to experience this wonderful system yourself, the system can be easily set up using a Raspberry Pi, old PC or laptop with a few line of bash command. &lt;/p&gt;

&lt;p&gt;Hope you like this project! We are continuing updating the modules of this system to better fit our use cases. If you are interested to try it out or even contribute to this project, feel frees to find the source code and give us a star 🌟 in the attached &lt;a href="https://github.com/tobychui/arozos" rel="noopener noreferrer"&gt;Github link&lt;/a&gt; below.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tobychui" rel="noopener noreferrer"&gt;
        tobychui
      &lt;/a&gt; / &lt;a href="https://github.com/tobychui/arozos" rel="noopener noreferrer"&gt;
        arozos
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Web Desktop Operating System for low power platforms, Now written in Go!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/tobychui/arozosimg/banner.png?raw=true"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftobychui%2Farozosimg%2Fbanner.png%3Fraw%3Dtrue" alt="Image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/3e7e87b362e1a8f03281bebf8030dd549c7ad783b64f4fa34f631c0e146c27d0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d47504c76332d626c7565"&gt;&lt;img src="https://camo.githubusercontent.com/3e7e87b362e1a8f03281bebf8030dd549c7ad783b64f4fa34f631c0e146c27d0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d47504c76332d626c7565"&gt;&lt;/a&gt; &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/1ad13cd26fa8dc67de66bbe9d2837d6affb8d06f482555ab3c2219502d7eb389/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4465766963652d5261737062657272792532305069253230334225324225323025324625323034422d726564"&gt;&lt;img src="https://camo.githubusercontent.com/1ad13cd26fa8dc67de66bbe9d2837d6affb8d06f482555ab3c2219502d7eb389/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4465766963652d5261737062657272792532305069253230334225324225323025324625323034422d726564"&gt;&lt;/a&gt; &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/4b170781349ea931aa1b4e2133d36f987e287a2f6eb6390c7703f83248d85e02/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d616465253230496e253230486f6e672532304b6f6e672de9a699e6b8afe9968be799bc2d626c756576696f6c6574"&gt;&lt;img src="https://camo.githubusercontent.com/4b170781349ea931aa1b4e2133d36f987e287a2f6eb6390c7703f83248d85e02/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d616465253230496e253230486f6e672532304b6f6e672de9a699e6b8afe9968be799bc2d626c756576696f6c6574"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;User Interface&lt;/h3&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Web Desktop Interface&lt;/li&gt;
&lt;li&gt;Ubuntu remix Windows style startup menu and task bars&lt;/li&gt;
&lt;li&gt;Clean and easy to use File Manager (Support drag drop, upload etc)&lt;/li&gt;
&lt;li&gt;Simplistic System Setting Menu&lt;/li&gt;
&lt;li&gt;No-bull-shit module naming scheme&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Networking&lt;/h3&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Basic Realtime Network Statistic&lt;/li&gt;
&lt;li&gt;Static Web Server (with build in Web Editor!)&lt;/li&gt;
&lt;li&gt;mDNS discovery + SSDP broadcast&lt;/li&gt;
&lt;li&gt;UPnP Port Forwarding&lt;/li&gt;
&lt;li&gt;WiFi Management (Support wpa_supplicant for Rpi or nmcli for Armbian)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;File / Disk Management&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Mount Disk Utilities&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local File Systems (ext4, NTFS, FAT etc)&lt;/li&gt;
&lt;li&gt;Remote File Systems (WebDAV, SMB, SFTP etc)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build in Network File Sharing Servers&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FTP, WebDAV, SFTP&lt;/li&gt;
&lt;li&gt;Basic Auth based simple HTTP interface for legacy devices with outdated browser&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Virtual File System + Sandbox Architecture&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;File Sharing (Similar to Google Drive)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Basic File Operations with Real-time Progress (Copy / Cut / Paste / New File or Folder etc)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Security&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;oAuth&lt;/li&gt;
&lt;li&gt;LDAP&lt;/li&gt;
&lt;li&gt;IP White / Blacklist&lt;/li&gt;
&lt;li&gt;Exponential login timeout&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Extensibility&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;ECMA5 (JavaScript…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tobychui/arozos" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>webdav</category>
      <category>go</category>
      <category>linux</category>
      <category>ux</category>
    </item>
    <item>
      <title>Vanilla JS for selecting a local text file and reading its content</title>
      <dc:creator>Toby Chui</dc:creator>
      <pubDate>Sun, 05 Mar 2023 09:28:20 +0000</pubDate>
      <link>https://dev.to/tobychui/vanilla-js-for-selecting-a-local-text-file-and-reading-its-content-31oh</link>
      <guid>https://dev.to/tobychui/vanilla-js-for-selecting-a-local-text-file-and-reading-its-content-31oh</guid>
      <description>&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%2Fo83hovl6yf7ml330p58y.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%2Fo83hovl6yf7ml330p58y.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Recently I am working a small side project that aims to build a mechanical 4-keys macropad with the lowest cost possible. To make it more user friendly, I need to develop a website that let user load their previous config file (JSON) and allow them to continue their "graphical programming". I search online for a Javascript only solution that do the trick but it took me some time to reach what I need. &lt;/p&gt;

&lt;p&gt;To save other's time (and make sure myself won't forget this in the future), here is how you allow user to select a text file, upload it using a File Selection Dialog and read its contents in Vanilla JS&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;let&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;multiple&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onchange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;read&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readAsBinaryString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onloadend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
            &lt;span class="c1"&gt;//Content of the file selected&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;


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

&lt;/div&gt;

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

</description>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Wrote a simple Up-time Monitor in Golang for My PHP Server</title>
      <dc:creator>Toby Chui</dc:creator>
      <pubDate>Fri, 30 Dec 2022 13:57:35 +0000</pubDate>
      <link>https://dev.to/tobychui/i-wrote-a-simple-up-time-monitor-in-golang-for-my-php-server-5f21</link>
      <guid>https://dev.to/tobychui/i-wrote-a-simple-up-time-monitor-in-golang-for-my-php-server-5f21</guid>
      <description>&lt;p&gt;Up-time monitors are crucial because they help you keep track of your servers' availability and can help you detect outages quickly. This can help prevent prolonged downtime and ensure that your users are able to access your services as much as possible.&lt;/p&gt;

&lt;p&gt;If you want to get started with an up-time monitor, there are plenty of options available like Pingdom, Datadog, and NewRelic. You can also choose to write one yourself if you know how to code.&lt;/p&gt;

&lt;p&gt;As I only need a really simple and effective way to oversee my servers, and I already got a PHP server running for my blog, I decided to make one with PHP. &lt;/p&gt;

&lt;h2&gt;
  
  
  One problem: How to make PHP ping a website every x minutes?
&lt;/h2&gt;

&lt;p&gt;As my server was built from an old Windows 7 PC, I decided to write a small Go program to make the website status check and store the data in RAM. The data was never written to disk unless it is required because writing data constantly to an SSD will quickly use up all its R/W count. &lt;/p&gt;

&lt;p&gt;Then you might be wondering, how could I pass information to PHP for displaying then? To access the results by the PHP script, I setup a simple http listener on the Go program and allow php to request the buffered online status from the go program using HTTP request.&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%2Fbgxvp8nbj39es34jd4aj.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%2Fbgxvp8nbj39es34jd4aj.png" alt="ui" width="750" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This up-time monitor is simple to use and easy to integrate to your existing LAMP infrastructure. Simply download the binary and the php script, move them to a suitable location and start the binary. Your php will then show the uptime information of your servers every time you request that script.&lt;/p&gt;

&lt;p&gt;I am only using this for my web servers. However, it is designed to be extendable in which different protocols can also be implemented without the need to change anything on the front-end side. If you want to try it out, here is the Github link for the project. Feel free to PR this project if you want more protocols to be supported!&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tobychui" rel="noopener noreferrer"&gt;
        tobychui
      &lt;/a&gt; / &lt;a href="https://github.com/tobychui/imusutm" rel="noopener noreferrer"&gt;
        imusutm
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Basic service up-time monitor written in Golang
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/24617523/272955246-f8098c25-ea74-48bc-8837-a73bf831c26c.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDI4NjkwMzIsIm5iZiI6MTc0Mjg2ODczMiwicGF0aCI6Ii8yNDYxNzUyMy8yNzI5NTUyNDYtZjgwOThjMjUtZWE3NC00OGJjLTg4MzctYTczYmY4MzFjMjZjLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAzMjUlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMzI1VDAyMTIxMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTc2OTU1NTVmMTVjMjZmMWJmNDUxOWMxMWNlODRkZjM2OGU4NDE3ZjAyYjYzYzQwNTJhNGIyNTdmZGVjNGZkN2UmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.QcPVCgFOBr8tUf9xqvanBWMEqib5uwjHnFCSo-VsdUU"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F24617523%2F272955246-f8098c25-ea74-48bc-8837-a73bf831c26c.png%3Fjwt%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDI4NjkwMzIsIm5iZiI6MTc0Mjg2ODczMiwicGF0aCI6Ii8yNDYxNzUyMy8yNzI5NTUyNDYtZjgwOThjMjUtZWE3NC00OGJjLTg4MzctYTczYmY4MzFjMjZjLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAzMjUlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMzI1VDAyMTIxMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTc2OTU1NTVmMTVjMjZmMWJmNDUxOWMxMWNlODRkZjM2OGU4NDE3ZjAyYjYzYzQwNTJhNGIyNTdmZGVjNGZkN2UmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.QcPVCgFOBr8tUf9xqvanBWMEqib5uwjHnFCSo-VsdUU" alt="圖片"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;imusutm&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Baisc service up-time monitor and TOTP generator written in Golang&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Double click the binary executable to run the up-time monitor.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Build from Source&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;git clone https://github.com/tobychui/imusutm
cd imusutm
go mod tidy
go build
./utm
(or .\utm.exe)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Setup Monitoring Server&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Create a file named "config.json" and place it in the same folder to the binary executable.&lt;/p&gt;
&lt;div class="highlight highlight-source-json notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;{
 &lt;span class="pl-ent"&gt;"Targets"&lt;/span&gt;: [
    {
        &lt;span class="pl-ent"&gt;"ID"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;imus_homepage&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;, &lt;span class="pl-ii"&gt;//ID of the target&lt;/span&gt;
        &lt;span class="pl-ent"&gt;"Name"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;imuslab Homepage&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;, &lt;span class="pl-ii"&gt;//Name to show on the UI&lt;/span&gt;
        &lt;span class="pl-ent"&gt;"URL"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;https://imuslab.com&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;, &lt;span class="pl-ii"&gt;//URL to request&lt;/span&gt;
        &lt;span class="pl-ent"&gt;"Protocol"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;https&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-ii"&gt;//Protocol to check online&lt;/span&gt;
    },
    &lt;span class="pl-ii"&gt;//More endpoint here&lt;/span&gt;
 ],
 &lt;span class="pl-ent"&gt;"Interval"&lt;/span&gt;: &lt;span class="pl-c1"&gt;300&lt;/span&gt;, &lt;span class="pl-ii"&gt;//Update interval in seconds&lt;/span&gt;
 &lt;span class="pl-ent"&gt;"LogToFile"&lt;/span&gt;: &lt;span class="pl-c1"&gt;false&lt;/span&gt;, &lt;span class="pl-ii"&gt;//Log results to file&lt;/span&gt;
 &lt;span class="pl-ent"&gt;"RecordsInJson"&lt;/span&gt;: &lt;span class="pl-c1"&gt;288&lt;/span&gt; &lt;span class="pl-ii"&gt;//How many records to keep, in this example, 300s (5 min) x 288 records per target = 1 day&lt;/span&gt;
}&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;To start the…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tobychui/imusutm" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Universal File Access Gateway - How I turn my own Web Desktop OS into a gateway for all my file servers</title>
      <dc:creator>Toby Chui</dc:creator>
      <pubDate>Mon, 21 Nov 2022 13:42:03 +0000</pubDate>
      <link>https://dev.to/tobychui/universal-file-access-gateway-how-i-turn-my-own-web-desktop-os-into-a-gateway-for-all-my-file-servers-3oi8</link>
      <guid>https://dev.to/tobychui/universal-file-access-gateway-how-i-turn-my-own-web-desktop-os-into-a-gateway-for-all-my-file-servers-3oi8</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---0QH6C0S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wnutvz66xlaiyup6424z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---0QH6C0S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wnutvz66xlaiyup6424z.png" alt="Image description" width="880" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have been working on my own Web Desktop OS called "ArozOS" for nearly 4+ years now. I have been setting up servers to store my data and provide different kind of services, all using my trusty Web Desktop OS for simple server management. However, separating the resources into so many servers are sometime a bit difficult to manage. &lt;/p&gt;

&lt;p&gt;In this post, I will show you how I design a new file system for ArozOS and make it link up to all my servers. Creating a truly private cloud experience that pretty much make my life much easier in finding files, backup or even music collections in my sea of hard disks. &lt;/p&gt;

&lt;p&gt;If this is your first time reading my post, I would recommend you to take a look at my old post over here so you can have a glance on how my web desktop OS looks like in general. &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/tobychui" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UEVouvRP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--hwroiC6G--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/493190/ac9cc59e-ba0b-498d-8945-05bcd7220f01.png" alt="tobychui"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/tobychui/i-write-my-own-web-desktop-os-for-3-years-and-this-is-what-it-looks-like-now-2903" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;I write my own web desktop OS for 3 years and this is what it looks like now&lt;/h2&gt;
      &lt;h3&gt;Toby Chui ・ Apr 4 '21 ・ 9 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#go&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#linux&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ux&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Writing Local Program is Simple
&lt;/h2&gt;

&lt;p&gt;Writing local programs that use local disk as storage is pretty straight forward. You can use io/ioutil or the os package to access anything you need locally. This is how the original ArozOS File System was designed. For every file you saw on the web desktop interface, it correspond to a local file in your server's hard disk. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mr999j70--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l07gidy6znvm805vuod3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mr999j70--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l07gidy6znvm805vuod3.png" alt="Image description" width="871" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, I always want to bridge the file systems between servers together using some kind of protocol. Some of the ArozOS users already bridging their own servers using SMB / Samba, WebDAV (with davfs2 on Linux) or SFTP mounting. These solution are quick and dirty way to get things done, however this causes some issues as this created platform dependencies on the arozos system. &lt;/p&gt;

&lt;p&gt;I didn't plan to solve this (As 1st rule of programming: If it works, don't touch it) until a feature request pop up on our Github repo asking for a new function to be implemented: Allows users to mount a webdav folder into arozos as vroot. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_--o7zLL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ok8yeyir8plewiruwczc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_--o7zLL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ok8yeyir8plewiruwczc.png" alt="Image description" width="880" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And of course we can use rsync or davfs2 to get the job done. However, as I am kind of a person who don't like hacky stuffs run on a 24/7 server, I started to think of a proper solution to this issue. &lt;/p&gt;

&lt;h2&gt;
  
  
  Virtual File System Layer
&lt;/h2&gt;

&lt;p&gt;During the old beta development of the web desktop system, we did encounter a lot of security concerning path escape issues. That is why we developed the Virtual File System Layer on the ArozOS 1.0. This VFS layer provide an virtualized path for each file access and make the underlying infrastructure modification much easier. &lt;/p&gt;

&lt;p&gt;Thus, in this update, I designed something that slip between the old VFS layer and Physical File System layer provided by the OS and call it the "File System Abstraction" layer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HtcECr8i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fo3a524czih3qoj0cfyl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HtcECr8i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fo3a524czih3qoj0cfyl.png" alt="Image description" width="873" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  File System Abstraction Driver
&lt;/h2&gt;

&lt;p&gt;To allow ArozOS to support different kind of file system clients, a driver needed to be developed for each of the file system types. For the most basic one, it is the local disk driver. It basically re-map the function from os and ioutil into a special interface structure (which I will show you later) and allow the interface to be used to access local file system. &lt;br&gt;
Other network file system or protocol can also be used this way. By developing a driver and adapt to the arozos file system interface, now any network file system protocol can be used as a local virtual drive in your ArozOS system, making them transparent for the WebApp that lies on top of the Virtual File System layer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sZQpvDt0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/awqyya8ifv1gou2eske6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sZQpvDt0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/awqyya8ifv1gou2eske6.png" alt="Image description" width="861" height="344"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Replacing os, ioutil and more
&lt;/h2&gt;

&lt;p&gt;As local file system is no longer the only file system type that can be used in ArozOS, we need to develop a special interface that allows file operations transparency between the Virtual File System and the target file system. Hence, we will need to define our own file system access interface for all the legacy virtual file system functions. This is where we referenced Afero, a VFS implementation that use for unit testing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4GBKFmko--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/22a4zc34jrxigusiusys.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4GBKFmko--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/22a4zc34jrxigusiusys.png" alt="Image description" width="780" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Afero provide a really similar file system methods to the os package but lacking a lot of features regarding file operation itself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--USli2yks--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0dxqk5o645mw9joa11tv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--USli2yks--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0dxqk5o645mw9joa11tv.png" alt="Image description" width="517" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is why we decided to combine the os.File functions with the afero file system functions. And here are what we came up with.&lt;/p&gt;
&lt;h3&gt;
  
  
  File Abstraction Interface
&lt;/h3&gt;

&lt;p&gt;The file abstraction interface is pretty much the same as os.File Method but in interface{}. This allow any struct that provide the same functions be used as File in the arozos system.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type File interface {
    Chdir() error
    Chmod(mode fs.FileMode) error
    Chown(uid, gid int) error
    Close() error
    Name() string
    Read(b []byte) (n int, err error)
    ReadAt(b []byte, off int64) (n int, err error)
    Readdirnames(n int) (names []string, err error)
    ReadFrom(r io.Reader) (n int64, err error)
    Readdir(n int) ([]fs.FileInfo, error)
    Seek(offset int64, whence int) (ret int64, err error)
    Stat() (fs.FileInfo, error)
    Sync() error
    Truncate(size int64) error
    Write(b []byte) (n int, err error)
    WriteAt(b []byte, off int64) (n int, err error)
    WriteString(s string) (n int, err error)
}

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  File System Abstraction Interface
&lt;/h3&gt;

&lt;p&gt;The File System Abstraction Interface is the most important part of the design. This allow us to perform file operation with ease between two file system types. For example, with these functions, we can do direct data streaming between SMB server and WebDAV server using one of the ArozOS server as middle-man. Or we can easily convert a file that only serve via SMB into a HTTP Stream.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type FileSystemAbstraction interface {
    //Fundamental Functions
    Chmod(string, os.FileMode) error
    Chown(string, int, int) error
    Chtimes(string, time.Time, time.Time) error
    Create(string) (arozfs.File, error)
    Mkdir(string, os.FileMode) error
    MkdirAll(string, os.FileMode) error
    Name() string
    Open(string) (arozfs.File, error)
    OpenFile(string, int, os.FileMode) (arozfs.File, error)
    Remove(string) error
    RemoveAll(string) error
    Rename(string, string) error
    Stat(string) (os.FileInfo, error)
    Close() error

    VirtualPathToRealPath(string, string) (string, error)
    RealPathToVirtualPath(string, string) (string, error)
    FileExists(string) bool
    IsDir(string) bool
    Glob(string) ([]string, error)
    GetFileSize(string) int64
    GetModTime(string) (int64, error)
    WriteFile(string, []byte, os.FileMode) error
    ReadFile(string) ([]byte, error)
    ReadDir(string) ([]fs.DirEntry, error)
    WriteStream(string, io.Reader, os.FileMode) error
    ReadStream(string) (io.ReadCloser, error)
    Walk(string, filepath.WalkFunc) error
}

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

&lt;/div&gt;



&lt;p&gt;You might notice the first section of the interface is pretty close to what Afero offers. For the second half, it is a mix of different functions that commonly available in utilities packages. For example, you will see Glob and Walk from filepath, or ReadDir from ioutil.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming between File System Abstractions
&lt;/h2&gt;

&lt;p&gt;The most important function that make the abstraction interface flexible is the WriteStream and ReadStream function. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TlU-T3D1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m3yjbdv4tatp5js68219.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TlU-T3D1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m3yjbdv4tatp5js68219.png" alt="Image description" width="880" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The WriteStream and ReadStream function allow us to provide and write a byte stream into the target virtual driver, no matter the target virtual drive is a local or remote FS.&lt;/p&gt;

&lt;p&gt;With these two functions, we can do something like doing real-time streaming between two remote FS using different protocol&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8RpRjbGR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x32w5xbbypeqt6mgq57y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8RpRjbGR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x32w5xbbypeqt6mgq57y.png" alt="Image description" width="880" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;or converting a remote FS protocol that is not supported by your device into something that support by basically every devices&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sqKmOO6P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kiuwen1kmj6ojwv324xr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sqKmOO6P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kiuwen1kmj6ojwv324xr.png" alt="Image description" width="880" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  I got myself a universal gateway to all my files!
&lt;/h2&gt;

&lt;p&gt;Currently I am still rewriting majority of my system to support this kind of virtual file system architecture. I guess it might take another 4 - 6 months to complete. But after the hard works, I think I will be getting one of my best written piece of software - A universal gateway to all my files from all my servers, accessible with just a single browser tab! 😀&lt;/p&gt;

</description>
      <category>go</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>linux</category>
    </item>
    <item>
      <title>Upload a huge file with little RAM &amp; space in Go</title>
      <dc:creator>Toby Chui</dc:creator>
      <pubDate>Wed, 22 Jun 2022 11:17:40 +0000</pubDate>
      <link>https://dev.to/tobychui/upload-a-huge-file-with-little-ram-space-in-go-5aie</link>
      <guid>https://dev.to/tobychui/upload-a-huge-file-with-little-ram-space-in-go-5aie</guid>
      <description>&lt;p&gt;&lt;strong&gt;This is an updated version of the previous implementation regarding uploading a file larger than RAM size in Go. If you didn't read it before, you can check it out in the following link.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/tobychui" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F493190%2Fac9cc59e-ba0b-498d-8945-05bcd7220f01.png" alt="tobychui"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/tobychui/upload-a-file-larger-than-ram-size-in-go-4m2i" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Upload a file larger than RAM size in Go&lt;/h2&gt;
      &lt;h3&gt;Toby Chui ・ Jan 15 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#go&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#raspberrypi&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#upload&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#filesystem&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;In last blog post, I tackled the the Go upload method using websocket file chunking implementation to handle cases where the uploaded file is much larger than available RAM on the device. This implementation is really helpful when you are developing applications for some cheap SBCs with as little RAM as 512MB.&lt;/p&gt;

&lt;p&gt;Recently, I encountered another issue when I am trying to migrate my whole Google Drive to my own ARM powered DIY NAS. The issue was that my NAS only have 512MB + 32GB (microSD card) as OS drive, while I have 2 x 512GB HDD attached to the SBC to store files. Uploading a file with size &amp;gt;32GB will causing the system to run out of space and crashing my &lt;a href="https://arozos.com" rel="noopener noreferrer"&gt;ArozOS NAS OS &lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;In the previous implementation, in order to upload a 1GB file, you will need 1GB space in your tmp folder (i.e. the SD card) in order for it to buffer the file chunks received via websocket. In the latest implementation, a new "huge file mode" was added to handle cases where uploading file &amp;gt; tmp folder space by directly writing the upload file chunks to target disk while minimizing the maximum required space on all the system disk. Before I show you the code on how it works, this is the logic for me to decide when to enter "huge file mode"&lt;/p&gt;

&lt;h2&gt;
  
  
  Logic for Optimizing Both Upload Space &amp;amp; Time Occupancy
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;If a file is smaller than 4MB, upload with FORM POST (to reduce overhead, fastest)&lt;/li&gt;
&lt;li&gt;Else if the file is smaller than "The remaining space on tmp" / 16 - 1KB, the file is buffered into the tmp folder (tmp folder should be in fast medium like NVME SSDs or RAM Disk, slower than FORM POST but still fast)&lt;/li&gt;
&lt;li&gt;Otherwise, the file chunks are directly buffered to disk (slowest, but provide us the most space to work with)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  File Merging Procedures
&lt;/h2&gt;

&lt;p&gt;In the previous implementation, the file merging procedures happens like this&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the destination file and open it&lt;/li&gt;
&lt;li&gt;Iterate through each chunks, append it to the opened destination file&lt;/li&gt;
&lt;li&gt;Delete all the chunk files&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;*&lt;em&gt;However, this would take 2x the space of the file being upload. *&lt;/em&gt; It works fine for medium sized files, but not good for huge files. To solve this, I have changed the implementation to the followings.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the definition file and open it&lt;/li&gt;
&lt;li&gt;Iterate through each chunks, append each chunks to the opened destination file, confirm the copy is success and remove the source chunks&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In simple words, by deleting file on the fly, the new upload logic only takes up (x + c) bytes size, where x is the file size and c is the chunk size. In my design, c is 512KB.&lt;/p&gt;

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

&lt;p&gt;There is no change to the front end code except there is an extra GET parameter when opening the websocket to define if the current upload is huge file upload. The following is an example implementation for the websocket object&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;let&lt;/span&gt; &lt;span class="nx"&gt;hugeFileMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&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="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;largeFileCutoffSize&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
       &lt;span class="c1"&gt;//Filesize over cutoff line. Use huge file mode&lt;/span&gt;
       &lt;span class="nx"&gt;hugeFileMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;hugefile=true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/system/file_system/lowmemUpload?filename=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;path=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uploadDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;hugeFileMode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And here is the Go backend side implementation. Note the &lt;br&gt;
isHugeFile flag and //Merge the file section.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;targetUploadLocation&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileExists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MkdirAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0755&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;//Generate an UUID for this upload&lt;/span&gt;
&lt;span class="n"&gt;uploadUUID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewV4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;uploadFolder&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tmp_directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"uploads"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uploadUUID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isHugeFile&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;//Upload to the same directory as the target location.&lt;/span&gt;
    &lt;span class="n"&gt;uploadFolder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;".metadata/.upload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uploadUUID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MkdirAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadFolder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0700&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;//Start websocket connection&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;upgrader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Upgrader&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;upgrader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CheckOrigin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;upgrader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Upgrade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to upgrade websocket connection: "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"500 WebSocket upgrade failed"&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;defer&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;//Handle WebSocket upload&lt;/span&gt;
&lt;span class="n"&gt;blockCounter&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;chunkName&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;lastChunkArrivalTime&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unix&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;//Setup a timeout listener, check if connection still active every 1 minute&lt;/span&gt;
&lt;span class="n"&gt;ticker&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewTicker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unix&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lastChunkArrivalTime&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c"&gt;//Already 5 minutes without new data arraival. Stop connection&lt;/span&gt;
                &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Upload WebSocket connection timeout. Disconnecting."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&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;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&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="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;

&lt;span class="n"&gt;totalFileSize&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mt&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="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;//Connection closed by client. Clear the tmp folder and exit&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Upload terminated by client. Cleaning tmp folder."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c"&gt;//Clear the tmp folder&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoveAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadFolder&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="c"&gt;//The mt should be 2 = binary for file upload and 1 for control syntax&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrimSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"done"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;//Start the merging process&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;//Unknown operations&lt;/span&gt;

        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;//File block. Save it to tmp folder&lt;/span&gt;
        &lt;span class="n"&gt;chunkFilepath&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadFolder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"upld_"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;strconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Itoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blockCounter&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;chunkName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunkName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunkFilepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;writeErr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ioutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunkFilepath&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="m"&gt;0700&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;writeErr&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;//Unable to write block. Is the tmp folder fulled?&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[Upload] Upload chunk write failed: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`{\"error\":\"Write file chunk to disk failed\"}`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

            &lt;span class="c"&gt;//Close the connection&lt;/span&gt;
            &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&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;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="c"&gt;//Clear the tmp files&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoveAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadFolder&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="c"&gt;//Update the last upload chunk time&lt;/span&gt;
        &lt;span class="n"&gt;lastChunkArrivalTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unix&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c"&gt;//Check if the file size is too big&lt;/span&gt;
        &lt;span class="n"&gt;totalFileSize&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetFileSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunkFilepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;totalFileSize&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;max_upload_size&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;//File too big&lt;/span&gt;
            &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`{\"error\":\"File size too large\"}`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

            &lt;span class="c"&gt;//Close the connection&lt;/span&gt;
            &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&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;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="c"&gt;//Clear the tmp files&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoveAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadFolder&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;blockCounter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;

        &lt;span class="c"&gt;//Request client to send the next chunk&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"next"&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="c"&gt;//Try to decode the location if possible&lt;/span&gt;
&lt;span class="n"&gt;decodedUploadLocation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryUnescape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetUploadLocation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;decodedUploadLocation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;targetUploadLocation&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;//Do not allow % sign in filename. Replace all with underscore&lt;/span&gt;
&lt;span class="n"&gt;decodedUploadLocation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReplaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decodedUploadLocation&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="s"&gt;"_"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;//Merge the file&lt;/span&gt;
&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decodedUploadLocation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_CREATE&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_WRONLY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0755&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to open file:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`{\"error\":\"Failed to open destination file\"}`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&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;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&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;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filesrc&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;chunkName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;srcChunkReader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filesrc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to open Source Chunk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filesrc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;" with error "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`{\"error\":\"Failed to open Source Chunk\"}`&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;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;srcChunkReader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;srcChunkReader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;//Delete file immediately to save space&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filesrc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;//Return complete signal&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OK"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c"&gt;//Stop the timeout listner&lt;/span&gt;
&lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;//Clear the tmp folder&lt;/span&gt;
&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoveAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadFolder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;//Close WebSocket connection after finished&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&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;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


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

&lt;/div&gt;

&lt;h2&gt;
  
  
  And now you can have infinite file upload size
&lt;/h2&gt;

&lt;p&gt;So there you have it. Now you can upload infinitely large file into your system as soon as you have enough disk space to store it. &lt;strong&gt;Notes that this upload method is very slow. It takes more than 2 time the speed than the previous method to actually merge the file as both of the reading file chunks and writing destination file are on the same disk.&lt;/strong&gt; But for my use case, at least it works well enough for files that is too large to fit into the system RAM or the tmp/ folder. &lt;/p&gt;

&lt;p&gt;I have no idea who will ever find this useful other than myself working on the ArozOS project. People with these issues nowadays usually just dump the file to AWS or whatever cloud service provider providing large file storage. But if you find it useful or you got even better implementation, feel free to let me know so we can further improve the design :)&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tobychui" rel="noopener noreferrer"&gt;
        tobychui
      &lt;/a&gt; / &lt;a href="https://github.com/tobychui/arozos" rel="noopener noreferrer"&gt;
        arozos
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Web Desktop Operating System for low power platforms, Now written in Go!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/tobychui/arozosimg/banner.png?raw=true"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftobychui%2Farozosimg%2Fbanner.png%3Fraw%3Dtrue" alt="Image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/3e7e87b362e1a8f03281bebf8030dd549c7ad783b64f4fa34f631c0e146c27d0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d47504c76332d626c7565"&gt;&lt;img src="https://camo.githubusercontent.com/3e7e87b362e1a8f03281bebf8030dd549c7ad783b64f4fa34f631c0e146c27d0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d47504c76332d626c7565"&gt;&lt;/a&gt; &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/1ad13cd26fa8dc67de66bbe9d2837d6affb8d06f482555ab3c2219502d7eb389/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4465766963652d5261737062657272792532305069253230334225324225323025324625323034422d726564"&gt;&lt;img src="https://camo.githubusercontent.com/1ad13cd26fa8dc67de66bbe9d2837d6affb8d06f482555ab3c2219502d7eb389/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4465766963652d5261737062657272792532305069253230334225324225323025324625323034422d726564"&gt;&lt;/a&gt; &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/4b170781349ea931aa1b4e2133d36f987e287a2f6eb6390c7703f83248d85e02/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d616465253230496e253230486f6e672532304b6f6e672de9a699e6b8afe9968be799bc2d626c756576696f6c6574"&gt;&lt;img src="https://camo.githubusercontent.com/4b170781349ea931aa1b4e2133d36f987e287a2f6eb6390c7703f83248d85e02/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d616465253230496e253230486f6e672532304b6f6e672de9a699e6b8afe9968be799bc2d626c756576696f6c6574"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;User Interface&lt;/h3&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Web Desktop Interface&lt;/li&gt;
&lt;li&gt;Ubuntu remix Windows style startup menu and task bars&lt;/li&gt;
&lt;li&gt;Clean and easy to use File Manager (Support drag drop, upload etc)&lt;/li&gt;
&lt;li&gt;Simplistic System Setting Menu&lt;/li&gt;
&lt;li&gt;No-bull-shit module naming scheme&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Networking&lt;/h3&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Basic Realtime Network Statistic&lt;/li&gt;
&lt;li&gt;Static Web Server (with build in Web Editor!)&lt;/li&gt;
&lt;li&gt;mDNS discovery + SSDP broadcast&lt;/li&gt;
&lt;li&gt;UPnP Port Forwarding&lt;/li&gt;
&lt;li&gt;WiFi Management (Support wpa_supplicant for Rpi or nmcli for Armbian)&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;File / Disk Management&lt;/h3&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Mount Disk Utilities&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local File Systems (ext4, NTFS, FAT etc)&lt;/li&gt;
&lt;li&gt;Remote File Systems (WebDAV, SMB, SFTP etc)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Build in Network File Sharing Servers&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FTP, WebDAV, SFTP&lt;/li&gt;
&lt;li&gt;Basic Auth based simple HTTP interface for legacy devices with outdated browser&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Virtual File System + Sandbox Architecture&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;File Sharing (Similar to Google Drive)&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Basic File Operations with Real-time Progress (Copy / Cut / Paste / New File or Folder etc)&lt;/p&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Security&lt;/h3&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;oAuth&lt;/li&gt;
&lt;li&gt;LDAP&lt;/li&gt;
&lt;li&gt;IP White / Blacklist&lt;/li&gt;
&lt;li&gt;Exponential login timeout&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Extensibility&lt;/h3&gt;

&lt;/div&gt;


&lt;ul&gt;

&lt;li&gt;ECMA5 (JavaScript…&lt;/li&gt;

&lt;/ul&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tobychui/arozos" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>webdev</category>
      <category>go</category>
      <category>linux</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Update any application binary with this launcher!</title>
      <dc:creator>Toby Chui</dc:creator>
      <pubDate>Tue, 08 Feb 2022 12:27:45 +0000</pubDate>
      <link>https://dev.to/tobychui/update-any-application-binary-with-this-launcher-106n</link>
      <guid>https://dev.to/tobychui/update-any-application-binary-with-this-launcher-106n</guid>
      <description>&lt;p&gt;Recently, I was working on a software updater for my project and I found that there is no easy way for OTA update that is ready to use and work cross platform. So I decided to write one that is so universal, it works on all kind of application binaries or even interpreted languages, which I later called it the "Automatic Update Launcher"&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tobychui" rel="noopener noreferrer"&gt;
        tobychui
      &lt;/a&gt; / &lt;a href="https://github.com/tobychui/Automatic-Update-Launcher" rel="noopener noreferrer"&gt;
        Automatic-Update-Launcher
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A general purpose updater for updating program binaries when update folder exists
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/tobychui/Automatic-Update-Launcherimg/banner.jpg"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftobychui%2FAutomatic-Update-Launcherimg%2Fbanner.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Automatic Update Launcher&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A general purpose updater for updating (web) applications server when update folder exists&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What is this?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The Automatic update launcher only do one simple task, which is to upgrade your application to the latest version if it finds a later version of such application in the &lt;code&gt;./updates&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;Its logic basically goes like this&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If updates/ folder exists
&lt;ol&gt;
&lt;li&gt;Backup the old application files and data&lt;/li&gt;
&lt;li&gt;Copy the updates for application from updates folder to current folder&lt;/li&gt;
&lt;li&gt;Remove the updates folder&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Start the application&lt;/li&gt;
&lt;li&gt;Check for crash, if crash happens after update
&lt;ol&gt;
&lt;li&gt;Restore the old application files and data&lt;/li&gt;
&lt;li&gt;Remove the backup folder&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Restart the whole process unless crash retry count &amp;gt; max allowed&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Build&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Require Go 1.17 or above&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;go mod tidy
go build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or just grab a copy from the release page.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Configure the launcher with launcher.json. See the example below&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;{
    "version":"1.0"
    "start":"helloworld*",
    "backup":&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tobychui/Automatic-Update-Launcher" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Current Implementations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ❔ How normal Desktop Programs Updates
&lt;/h3&gt;

&lt;p&gt;Most desktop applications will use some kind of updater to update the main program by start and detaching the updater and allow it to do its things. This work on most desktop platforms but it will not works with systemd where the PID of the running application is important. (i.e. cannot change or otherwise systemd will assume it is dead and try to restart it)&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%2Fq5cpx8qd0kaodh9bbkkx.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%2Fq5cpx8qd0kaodh9bbkkx.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  ❔ How embedded devices do OTA updates
&lt;/h3&gt;

&lt;p&gt;Embedded devices usually uses two partitions (one for current running application, the other one for writing update files to) and a bootloader to do the switching. One common example ESP8266 which some projects utilize its bootloader's feature to do OTA update via WiFi or Bluetooth.&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%2F237y9a98gpjewzjj0hm6.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%2F237y9a98gpjewzjj0hm6.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  🌟 How the Automatic Update Launcher (auLauncher) works 🌟
&lt;/h2&gt;

&lt;p&gt;The auLauncher works in a much simpler way following the logic described in the following diagram. &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%2Fhzlfajp475o5fp2500v0.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%2Fhzlfajp475o5fp2500v0.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and the configuration of each steps and file names can be changed easily in a json file located next to the launcher's executable. Here is an example for that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "version":"1.0",
    "start":"helloworld*",
    "backup": ["./*.config","./*.exe"],
    "verbal": true,
    "resp_port": 25576,
    "max_retry": 3,
    "crash_time": 3
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;All arguments that passed to the launcher is then passed through to the main binary. So you can easily slide this in between your starting script and your target binary without the need for changing anything.&lt;/p&gt;
&lt;h3&gt;
  
  
  ❓ How do my program update itself then?
&lt;/h3&gt;

&lt;p&gt;It is simple! If you program want to update itself, just download the update files to the ./updates folder and exit itself with code 0. This way, the launcher will start overwriting the application files based on the defination in the json file and the contents in the ./updates folder and restart your application with given start path. &lt;/p&gt;

&lt;p&gt;This method works for most Go compiled application, but in theory should also work with other compiled binaries and some interpret language like python with a correct starting bash / batch script.&lt;/p&gt;

&lt;p&gt;If you are interested to know more, or just wanna try this launcher and make your application OTA update a new features, feel free to take a look at this repo and left some comments :))&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tobychui" rel="noopener noreferrer"&gt;
        tobychui
      &lt;/a&gt; / &lt;a href="https://github.com/tobychui/Automatic-Update-Launcher" rel="noopener noreferrer"&gt;
        Automatic-Update-Launcher
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A general purpose updater for updating program binaries when update folder exists
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/tobychui/Automatic-Update-Launcherimg/banner.jpg"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftobychui%2FAutomatic-Update-Launcherimg%2Fbanner.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Automatic Update Launcher&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A general purpose updater for updating (web) applications server when update folder exists&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What is this?&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;The Automatic update launcher only do one simple task, which is to upgrade your application to the latest version if it finds a later version of such application in the &lt;code&gt;./updates&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Its logic basically goes like this&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If updates/ folder exists
&lt;ol&gt;
&lt;li&gt;Backup the old application files and data&lt;/li&gt;
&lt;li&gt;Copy the updates for application from updates folder to current folder&lt;/li&gt;
&lt;li&gt;Remove the updates folder&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

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

&lt;li&gt;Check for crash, if crash happens after update

&lt;ol&gt;
&lt;li&gt;Restore the old application files and data&lt;/li&gt;
&lt;li&gt;Remove the backup folder&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Restart the whole process unless crash retry count &amp;gt; max allowed&lt;/li&gt;

&lt;/ol&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Build&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Require Go 1.17 or above&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;go mod tidy
go build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or just grab a copy from the release page.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Configure the launcher with launcher.json. See the example below&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;{
    "version":"1.0"
    "start":"helloworld*",
    "backup":&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tobychui/Automatic-Update-Launcher" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



</description>
      <category>updates</category>
      <category>go</category>
      <category>window</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
