<?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: Juan Rodriguez</title>
    <description>The latest articles on DEV Community by Juan Rodriguez (@jbrodriguez).</description>
    <link>https://dev.to/jbrodriguez</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%2F606150%2F97bb2454-188f-4841-a002-c21ec1883843.png</url>
      <title>DEV Community: Juan Rodriguez</title>
      <link>https://dev.to/jbrodriguez</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jbrodriguez"/>
    <language>en</language>
    <item>
      <title>React Native app built with zustand and tailwind</title>
      <dc:creator>Juan Rodriguez</dc:creator>
      <pubDate>Sun, 18 Jul 2021 14:32:11 +0000</pubDate>
      <link>https://dev.to/jbrodriguez/react-native-app-built-with-zustand-and-tailwind-lce</link>
      <guid>https://dev.to/jbrodriguez/react-native-app-built-with-zustand-and-tailwind-lce</guid>
      <description>&lt;p&gt;This post was originally published at &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.jbrio.net%2Fpost%2Frn-zustand%2F" 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%2Fblog.jbrio.net%2Fpost%2Frn-zustand%2F" alt="https://blog.jbrio.net/post/rn-zustand/"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;In this article I'm going to share my impressions after migrating my React Native app from &lt;a href="https://reduxjs.org" rel="noopener noreferrer"&gt;redux&lt;/a&gt;/&lt;a href="https://redux-saga.js.org/" rel="noopener noreferrer"&gt;sagas&lt;/a&gt; to &lt;a href="https://github.com/pmndrs/zustand" rel="noopener noreferrer"&gt;zustand&lt;/a&gt; for state management.&lt;/p&gt;

&lt;p&gt;The app is &lt;a href="https://www.apertoire.com/controlr/" rel="noopener noreferrer"&gt;ControlR&lt;/a&gt;, a mobile app to manage &lt;a href="https://unraid.net/" rel="noopener noreferrer"&gt;Unraid&lt;/a&gt; servers.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://unraid.net/" rel="noopener noreferrer"&gt;Unraid&lt;/a&gt; is a software solution for storing your media content in-house.&lt;/p&gt;

&lt;p&gt;I purchased my first Pro license in 2010 and the second one in 2013.&lt;br&gt;
I'm sort of an early adopter, being among the first 2.5k owners of a key :)&lt;/p&gt;

&lt;p&gt;I have a third license (Plus), which was very graciously offered by the Unraid owners to support the development of ControlR for Unraid.&lt;/p&gt;

&lt;p&gt;Today's approach of storing content in the cloud &amp;amp; subscribing to multiple streaming services is convenient, but it does have some down sides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content can diseappear from time to time&lt;/li&gt;
&lt;li&gt;You need to over subscribe in order to access all the content you want&lt;/li&gt;
&lt;li&gt;You're not really in control of the media you want to consume&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unraid is a solution that helps you overcome these issues.&lt;/p&gt;
&lt;h2&gt;
  
  
  What about ControlR ?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.apertoire.com/controlr/" rel="noopener noreferrer"&gt;ControlR&lt;/a&gt; is a React Native cross-platform (Android and iOS) mobile app, born out of my necessity of having an easy way to manage my Unraid servers.&lt;/p&gt;

&lt;p&gt;I first published it in 2016.&lt;/p&gt;

&lt;p&gt;Fast forward to 2021, and having hit the 5 years milestone :), the app was still using the same architecture and UI/UX from day one.&lt;/p&gt;

&lt;p&gt;It definitely needed a UI overhaul, to support recent mobile features (notch, system color scheme, etc.)&lt;/p&gt;

&lt;p&gt;This led me to create ControlR 5.&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%2F5yoh84hljw18ziasnltl.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%2F5yoh84hljw18ziasnltl.png" alt="rn-zustand-servers"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;In 2016, the most popular option for state management was &lt;a href="https://redux.js.org/" rel="noopener noreferrer"&gt;redux&lt;/a&gt;, which I used, as well as &lt;a href="https://redux-saga.js.org/" rel="noopener noreferrer"&gt;redux-saga&lt;/a&gt; middleware to handle business logic.&lt;/p&gt;

&lt;p&gt;I really like how &lt;code&gt;redux&lt;/code&gt;/&lt;code&gt;sagas&lt;/code&gt; helps you keep the business logic separate from the UI.&lt;/p&gt;

&lt;p&gt;Conceptually, you get a very clear MVC (Model/View/Controller) pattern.&lt;/p&gt;

&lt;p&gt;For example, I had a metrics saga to collect analytics, taking advantage of redux's single store centralized actions flow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SCollect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nc"&gt;GSCollect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;__DEV__&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&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;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;Analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trackEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;content&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sagas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;takeEvery&lt;/span&gt;&lt;span class="p"&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="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SCollect&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is completely separate from the code that defines and renders the UI.&lt;/p&gt;

&lt;p&gt;Using redux did mean you needed to write quite a lot of boilerplate code, but this can be solved today by using &lt;a href="https://redux-toolkit.js.org/" rel="noopener noreferrer"&gt;Redux Toolkit (RTK)&lt;/a&gt;, as well as &lt;a href="https://redux-toolkit.js.org/rtk-query/overview" rel="noopener noreferrer"&gt;RTK Query&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When I started working on the rewrite of the app (at the beginning of 2020), I actually started with RTK and it reduced the boilerplate dramatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSlice&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;env&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;envInitialState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reducers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;PayloadAction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;EnvSetup&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;themes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;setRefreshing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;PayloadAction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refreshing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;setLoadingApps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;PayloadAction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loadingApps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&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="na"&gt;extraReducers&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;setTheme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EnvState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;PayloadAction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Theme&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;themes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I had to suspend the rewrite, though, due to the real-life™ well known events.&lt;/p&gt;

&lt;p&gt;That code laid dormant for about a year, until I had to release an app update to fix an issue users were reporting.&lt;/p&gt;

&lt;p&gt;At that point in time, I decided to explore state management alternatives.&lt;/p&gt;

&lt;p&gt;But before digging into that, let me briefly mention &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;tailwindcss&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Styling
&lt;/h2&gt;

&lt;p&gt;The previous version of the app used &lt;a href="https://github.com/tachyons-css/react-native-style-tachyons" rel="noopener noreferrer"&gt;react-native-tachyons&lt;/a&gt;, which one could argue is a precursor of tailwind.&lt;/p&gt;

&lt;p&gt;I've always enjoyed functional styling libraries, for how immediate they are when applying style to a component/element and the learning curve is a matter of hours, few days at most.&lt;/p&gt;

&lt;p&gt;I decided to switch from tachyons to tailwind because I had already used it for a web app and enjoyed doing so.&lt;/p&gt;

&lt;p&gt;Out of the box, &lt;code&gt;tailwindcss&lt;/code&gt; offered everything I needed to style my mobile app, so I was very satisfied with how everything turned out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter zustand
&lt;/h2&gt;

&lt;p&gt;I took a look at &lt;a href="https://react-query.tanstack.com/" rel="noopener noreferrer"&gt;react-query&lt;/a&gt;, &lt;a href="https://github.com/pmndrs/zustand" rel="noopener noreferrer"&gt;zustand&lt;/a&gt;, &lt;a href="https://github.com/pmndrs/jotai" rel="noopener noreferrer"&gt;jotai&lt;/a&gt; and &lt;a href="https://recoiljs.org/" rel="noopener noreferrer"&gt;recoil&lt;/a&gt;, eventually settling on &lt;code&gt;zustand&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's a very easy to use but quite powerful library, hooks based, with built-ins such as persistence and a strong redux likeness.&lt;/p&gt;

&lt;p&gt;My redux env slice became the zustand env store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useEnvStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;hydrated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;firstRun&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clearFirstRun&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;firstRun&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="na"&gt;setHydrated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hydrated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;env&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;getStorage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;whitelist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firstRun&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;onRehydrateStorage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHydrated&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most of the code here is for setting up persistence.&lt;/p&gt;

&lt;p&gt;It's concise yet expressive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redux vs Zustand
&lt;/h2&gt;

&lt;p&gt;Redux and sagas allowed great control of the overall app flow, sagas excel at that kind of orchestration, I could even navigate screens from sagas.&lt;/p&gt;

&lt;p&gt;With zustand it feels like you have more UI coupling.&lt;/p&gt;

&lt;p&gt;I think you can eventually move everything into zustand store actions, but I still need to dig a bit deeper into it.&lt;/p&gt;

&lt;p&gt;For example, one feature that was very easy with redux/sagas was navigate to the detail screen of a server if there was only one item in the list.&lt;/p&gt;

&lt;p&gt;I haven't found a way to replicate this with zustand (aided by my existing constraints), although I haven't put a lot of effort into it yet.&lt;/p&gt;

&lt;p&gt;I've also experienced some verbosity: binding state and actions can get a bit repetitive, although I've read this can be improved.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useServers&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setCurrent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useServers&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setCurrent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;servers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useServers&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;refreshing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useServers&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refreshing&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;refresh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useServers&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deleteServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useServers&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deleteServer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;zustand works perfectly for my app and I have enjoyed using it.&lt;/p&gt;

&lt;p&gt;There are other features I want to implement (use immer for example), I'll tackle them as part of the learning process in upcoming releases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Addenda
&lt;/h2&gt;

&lt;p&gt;Check out &lt;a href="https://unraid.net" rel="noopener noreferrer"&gt;Unraid&lt;/a&gt;, it's extremely versatile and once you're in, you will be hooked.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.youtube.com/c/SpaceinvaderOne" rel="noopener noreferrer"&gt;Spaceinvader One&lt;/a&gt; Youtube channel has many tutorials on getting the most out of your Unraid server.&lt;/p&gt;

&lt;p&gt;Also check out &lt;a href="https://www.apertoire.com/controlr/" rel="noopener noreferrer"&gt;ControlR for Unraid&lt;/a&gt;, I can assure it'll help you manage your Unraid servers (shameless plug 🙌 )&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>zustand</category>
      <category>tailwindcss</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Running AdGuard Home on an EdgeRouter 6P</title>
      <dc:creator>Juan Rodriguez</dc:creator>
      <pubDate>Tue, 30 Mar 2021 23:05:32 +0000</pubDate>
      <link>https://dev.to/jbrodriguez/running-adguard-home-on-an-edgerouter-6p-51do</link>
      <guid>https://dev.to/jbrodriguez/running-adguard-home-on-an-edgerouter-6p-51do</guid>
      <description>&lt;p&gt;&lt;a href="https://jbrio.net/posts/er6p-adguard/" rel="noopener noreferrer"&gt;This article was originally published on my personal blog and can be found here.&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Note: Apr 2023
&lt;/h3&gt;

&lt;p&gt;Some fixes to the original article are available in my blog (linked above)&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%2Fcwehlksi9zdwin9mdzas.jpeg" 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%2Fcwehlksi9zdwin9mdzas.jpeg" alt="adguard"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;I've recently switched from running &lt;a href="https://vyos.io/" rel="noopener noreferrer"&gt;vyos&lt;/a&gt; on a custom-built router to &lt;a href="https://www.ui.com/" rel="noopener noreferrer"&gt;edgeos&lt;/a&gt; on an &lt;a href="https://www.amazon.com/gp/product/B07BMJ91Q8/ref=ppx_yo_dt_b_asin_title_o03_s00?ie=UTF8&amp;amp;psc=1" rel="noopener noreferrer"&gt;EdgeRouter 6P&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Both solutions are derived from the &lt;a href="https://en.wikipedia.org/wiki/Vyatta" rel="noopener noreferrer"&gt;vyatta router&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;vyos still keeps its open source roots and has recently gone through a sweeping change of the software architecture, bringing it closer to modern standards.&lt;/p&gt;

&lt;p&gt;edgeos commercialized the software, adding hardware to its product line, as well as beautiful user interface on top of the always present command line interface.&lt;/p&gt;

&lt;p&gt;In any case, the biggest win this change brought for me has been power consumption: the ER6P barely draws 7w when it's busy, normally hovers around 6.5w.&lt;/p&gt;

&lt;p&gt;My previous router normally hovered above 20w.&lt;/p&gt;

&lt;h2&gt;
  
  
  First steps
&lt;/h2&gt;

&lt;p&gt;Since I was using vyos, I was able to reuse some of the configuration commands, in order to shape traffic the way I needed.&lt;/p&gt;

&lt;p&gt;The only thing I've been unable to find is &lt;code&gt;fq-codel&lt;/code&gt; for the shaper traffic policy, but so far so good in terms of my expectations.&lt;/p&gt;

&lt;h2&gt;
  
  
  No more tracking
&lt;/h2&gt;

&lt;p&gt;I was aware of pihole and people using it to prevent tracking at the network level.&lt;/p&gt;

&lt;p&gt;Articles such as&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.troyhunt.com/mmm-pi-hole/" rel="noopener noreferrer"&gt;Mmm... Pi-hole...&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://scotthelme.co.uk/securing-dns-across-all-of-my-devices-with-pihole-dns-over-https-1-1-1-1/" rel="noopener noreferrer"&gt;Securing DNS across all of my devices with Pi-Hole + DNS-over-HTTPS + 1.1.1.1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.thepolyglotdeveloper.com/2020/10/block-ads-network-level-pi-hole-edgerouter/" rel="noopener noreferrer"&gt;Block Ads at a Network Level with Pi-Hole and an Ubiquiti EdgeRouter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;really made me want to try this out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The berry is darker on the other side
&lt;/h2&gt;

&lt;p&gt;So I purchased a Raspberry Pi 4, installed pi-hole and just sat and watched how many requests were being blocked&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%2F3bchp24mvep9pj2tktxx.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%2F3bchp24mvep9pj2tktxx.png" alt="pihole"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Life was good !&lt;/p&gt;

&lt;p&gt;But I started thinking about the whole setup and what bothered me was the fact that the pi needed to be always up, otherwise I'd lose dns resolution.&lt;/p&gt;

&lt;p&gt;What some people do is have a backup pi, but I didn't really see that much benefit in that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter the new contender
&lt;/h2&gt;

&lt;p&gt;So I looked at some alternatives, I ruled out installing pihole on the router, and then, almost by chance, I found out about &lt;a href="https://adguard.com/en/adguard-home/overview.html" rel="noopener noreferrer"&gt;AdGuard Home&lt;/a&gt; (&lt;a href="https://github.com/AdguardTeam/AdGuardHome" rel="noopener noreferrer"&gt;github&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;It's built with &lt;a href="https://golang.org/" rel="noopener noreferrer"&gt;go&lt;/a&gt;, so I knew it could run almost anywhere.&lt;/p&gt;

&lt;p&gt;I ran it as a docker in one of my linux boxes to get a taste and it worked really great.&lt;/p&gt;

&lt;p&gt;That's when I decided to install it on the EdgeRouter 6P.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fast track to sweetness
&lt;/h2&gt;

&lt;p&gt;After some careful considerations, I settled on the following architecture&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The router has Cloudflare's 1.1.1.2 and 1.0.0.2 as its name servers, so it has its own route to name resolution.&lt;/li&gt;
&lt;li&gt;LAN clients are fed 192.168.3.1 as their dns server, which is handled by AGH on port 53.&lt;/li&gt;
&lt;li&gt;AGH upstream server is Cloudflare Dns-Over-Https&lt;/li&gt;
&lt;li&gt;I also set up some custom rules to delegate local domain queries to the dnsmasq service running on the router&lt;/li&gt;
&lt;li&gt;Anything that is not 192.168.3.1, is being forced to go through AGH for dns&lt;/li&gt;
&lt;/ul&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%2Ft5587fabdpy22a7zrxwl.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%2Ft5587fabdpy22a7zrxwl.png" alt="diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step by Step
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Set system name servers
&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%2Fydg49m9t5x1gq8k2tlmg.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%2Fydg49m9t5x1gq8k2tlmg.png" alt="system name servers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Change dnsmasq port and set dns server for dhcp clients
&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%2F53re5m8dwghw1s86moux.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%2F53re5m8dwghw1s86moux.png" alt="dnsmasq"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The dnsmasq options allow us to do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# set the default dns server for dhcp clients&lt;/span&gt;
dhcp-option&lt;span class="o"&gt;=&lt;/span&gt;6,192.168.3.1

&lt;span class="c"&gt;# set the port where dnsmasq listens on&lt;/span&gt;
&lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5353
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also note that I told dnsmasq to use the system's name servers (we'll check that later with the full set of cli commands)&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up AdGuard Home
&lt;/h3&gt;

&lt;p&gt;This is one the trickier things in this whole setup.&lt;/p&gt;

&lt;p&gt;We need AGH to be available in these 2 scenarios&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;router restart&lt;/li&gt;
&lt;li&gt;router firmware upgrade&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the first case, turns out AGH helps us with its &lt;code&gt;-s install&lt;/code&gt; argument. It creates systemd services that just work :)&lt;/p&gt;

&lt;p&gt;For the second case, my approach was to create a &lt;code&gt;/config/adguard&lt;/code&gt; folder, and make everything AGH-related to live there.&lt;/p&gt;

&lt;p&gt;Let's get down to the details&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up the folders
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /config/&lt;span class="o"&gt;{&lt;/span&gt;config,work&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Install AGH
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /config
curl &lt;span class="nt"&gt;-sL&lt;/span&gt; https://static.adguard.com/adguardhome/release/AdGuardHome_linux_mips64_softfloat.tar.gz | &lt;span class="nb"&gt;tar &lt;/span&gt;xvz
&lt;span class="nb"&gt;mv &lt;/span&gt;AdGuardHome bin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;tar creates a /config/AdGuardHome folder, which we move to a bin folder&lt;/p&gt;

&lt;p&gt;&lt;em&gt;note&lt;/em&gt;: confirm whether your router is little-endian architecture or not, as there are different binaries for each architecture.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up AGH
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /config/bin
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./AdGuardHome &lt;span class="nt"&gt;-c&lt;/span&gt; /config/adguard/config/AdGuardHome.yaml &lt;span class="nt"&gt;-w&lt;/span&gt; /config/adguard/work
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just follow the instructions, set AGH to listen on the lan interface and the web ui on some port other than 80, since it's owned by the edgeos web ui&lt;/p&gt;

&lt;p&gt;Now set up the upstream dns server and dns resolution for local domains&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%2Fj5zvnlof9184j4qbrhpl.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%2Fj5zvnlof9184j4qbrhpl.png" alt="upstream"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I left the bootstrap servers as per the default&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make AGH run after restarts
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; ./AdGuard &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; /config/adguard/config/AdGuardHome.yaml &lt;span class="nt"&gt;-w&lt;/span&gt; /config/adguard/work
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So I install AGH, to create the systemd service required for it to start during boot&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make AGH survive a firmware upgrade&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I created the following bash script on &lt;code&gt;/config/scripts/firstboot.d/adguard&lt;/code&gt;, based on &lt;a href="https://community.ui.com/questions/EdgeOS-file-system-layout-and-firmware-images/b5e5f4c8-20b1-4fae-8689-638ab48cb595" rel="noopener noreferrer"&gt;this filesystem layout reference&lt;/a&gt; and &lt;a href="https://github.com/britannic/install-edgeos-packages" rel="noopener noreferrer"&gt;this tool to persist debian packages&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="nb"&gt;cd&lt;/span&gt; /config/adguard/bin

./AdGuard &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; /config/adguard/config/AdGuardHome.yaml &lt;span class="nt"&gt;-w&lt;/span&gt; /config/adguard/work

&lt;span class="nb"&gt;cd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Force hardcoded dns to go through AGH&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After reading &lt;a href="https://scotthelme.co.uk/catching-naughty-devices-on-my-home-network/" rel="noopener noreferrer"&gt;this article&lt;/a&gt; by Scott Helme, I decided it was worth the extra effort.&lt;/p&gt;

&lt;p&gt;I modeled the setup based on &lt;a href="https://github.com/stevejenkins/unifi-linux-utils/blob/master/config.gateway.json/force-dns-to-pihole.json" rel="noopener noreferrer"&gt;this reference&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    nat {
        rule 1 {
            description "Redirect DNS requests"
            destination {
                port 53
            }
            inbound-interface eth1
            inside-address {
                address 192.168.3.1
                port 53
            }
            log disable
            protocol tcp_udp
            source {
                address !192.168.3.1
            }
            type destination
        }
        rule 5011 {
            description "masquerade for DNS to LAN"
            destination {
                address 192.168.23.1
                port 53
            }
            log disable
            outbound-interface eth1
            protocol tcp_udp
            type masquerade
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Great Success !!
&lt;/h2&gt;

&lt;p&gt;This is the full set of cli commands needed for this to work&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;set &lt;/span&gt;system name-server 1.0.0.2
&lt;span class="nb"&gt;set &lt;/span&gt;system name-server 1.1.1.2

&lt;span class="nb"&gt;set &lt;/span&gt;service dns forwarding cache-size 10000
&lt;span class="nb"&gt;set &lt;/span&gt;service dns forwarding listen-on eth1
&lt;span class="nb"&gt;set &lt;/span&gt;service dns forwarding listen-on eth2
&lt;span class="nb"&gt;set &lt;/span&gt;service dns forwarding listen-on wg0
&lt;span class="nb"&gt;set &lt;/span&gt;service dns forwarding options dhcp-option&lt;span class="o"&gt;=&lt;/span&gt;6,192.168.23.1
&lt;span class="nb"&gt;set &lt;/span&gt;service dns forwarding options &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5353
&lt;span class="nb"&gt;set &lt;/span&gt;service dns forwarding system

&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 1 description &lt;span class="s1"&gt;'Redirect DNS requests'&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 1 destination port 53
&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 1 inbound-interface eth1
&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 1 inside-address address 192.168.3.1
&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 1 inside-address port 53
&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 1 log disable
&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 1 protocol tcp_udp
&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 1 &lt;span class="nb"&gt;source &lt;/span&gt;address &lt;span class="s1"&gt;'!192.168.23.1'&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 1 &lt;span class="nb"&gt;type &lt;/span&gt;destination

&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 5010 description &lt;span class="s1"&gt;'masquerade for WAN'&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 5010 outbound-interface eth0
&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 5010 &lt;span class="nb"&gt;type &lt;/span&gt;masquerade
&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 5011 description &lt;span class="s1"&gt;'masquerade for DNS to LAN'&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 5011 destination address 192.168.3.1
&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 5011 destination port 53
&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 5011 log disable
&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 5011 outbound-interface eth1
&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 5011 protocol tcp_udp
&lt;span class="nb"&gt;set &lt;/span&gt;service nat rule 5011 &lt;span class="nb"&gt;type &lt;/span&gt;masquerade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;I have my EdgeRouter 6P running AdGuardHome to block ads, without extra hardware thus decreasing potential points of failure.&lt;/p&gt;

&lt;p&gt;AdGuardHome uses the same blocklists as pihole, so I can just use those.&lt;/p&gt;

&lt;p&gt;I've set up AGH to survive firmware upgrades and reboots, so dns resolution and ad blocking is always available.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/jbrodriguezio/status/1377033227005739009" rel="noopener noreferrer"&gt;Let me know any comments/suggestions you may have&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>adguard</category>
      <category>edgerouter</category>
      <category>vyos</category>
      <category>pihole</category>
    </item>
  </channel>
</rss>
