Bridgetown2023-10-29T17:40:03+00:00https://theworkaround.com/feed.xmlTheWorkAroundThe infinite ramblings and ideas of Brandon HicksBrandon HicksDevise-OTP without jQuery2023-03-13T00:00:00+00:002023-03-13T00:00:00+00:00repo://posts.collection/_posts/2023-03-13-devise-otp-without-jquery.md<p>Ruby/Rails boasts a unique diversity of available libraries and gems, including several options for authentication such as <a href="https://github.com/heartcombo/devise">Devise</a>, <a href="https://github.com/omniauth/omniauth">omniauth</a>, <a href="https://github.com/thoughtbot/clearance">clearance</a>, <a href="https://github.com/sorcery/sorcery">sorcery</a>, <a href="https://github.com/jeremyevans/rodauth">rodauth</a>, among others. While there are many options to choose from, I personally prefers Devise for its modularity and ease of use.</p> <p>However, any modern application needs more than just an authentication system; you also want to ensure you build on permissions permissions <em>(for authorization)</em>, 2FA, and a solid hash library for additional secure measures.</p> <p>There are several <a href="https://en.wikipedia.org/wiki/Multi-factor_authentication">2fa</a> libraries that you can integrate with Devise, such as <a href="https://github.com/tinfoil/devise-two-factor">devise-two-factor</a>, <a href="https://github.com/wmlele/devise-otp">devise-otp</a>, <a href="https://github.com/williamatodd/devise-2fahttps://github.com/williamatodd/devise-2fa">devise-2fa</a>, <a href="https://github.com/Houdini/two_factor_authentication">two_factor_authentication</a>, among others. I honestly prefer <a href="https://github.com/wmlele/devise-otp">Devise-OTP</a> for its ease of use and adherence to <a href="https://datatracker.ietf.org/doc/html/rfc6238">RFC 6238</a> implementation using the <a href="https://github.com/mdp/rotp">ROTP</a> gem.</p> <p>However I did encounter an issue with Devise-OTP in that it still assumes applications using it are using Sprockets and jQuery, and the <a href="https://github.com/wmlele/devise-otp/blob/master/app/assets/javascripts/qrcode.js">qrcode JS</a> file it includes requires jQuery. While I still use jQuery regularly at work, I have moved to using StimulusJS for personal projects.</p> <p>After attempting to use several npm packages <em>(like <a href="https://github.com/soldair/node-qrcode">node-qrcode</a>)</em> for generating the users 2fa QRcodes, I also found that 1Password had issues when scanning for a valid 2FA QR code; which could be a significant concern for many users. It is noted that the gem has a handy <a href="https://github.com/wmlele/devise-otp/blob/master/lib/devise_otp_authenticatable/controllers/helpers.rb#L148">helper</a> <code class="highlighter-rouge">otp_authenticator_token_image_google</code> for generating QR codes using the Google Chart API, but generating QR codes with a third party like Google may not be the most security-conscious decision.</p> <p><img src="/images/posts/devise_otp_without_query/qrcode.png" alt="qrcode" class="img-fluid w-3/12" /></p> <p>After some playing around I was able to find an alternative method to get the qrcodes genreted in the application and without requiring jQuery to be included.</p> <p>First to modify devise-opts views I ran the generator so they would be in the app, rather than in the gem.</p> <p><code class="highlighter-rouge">rails g devise_otp:views</code></p> <p>Next I added the follow gems to my Gemfile and bundled</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">gem</span> <span class="s1">'rotp'</span> <span class="n">gem</span> <span class="s1">'rqrcode'</span> </code></pre></div></div> <p>In my users helper I added the following method to generate the users 2fa QRcode</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">otp_rqr_image</span><span class="p">(</span><span class="n">otp_url</span><span class="p">)</span> <span class="n">qr_code</span> <span class="o">=</span> <span class="no">RQRCode</span><span class="o">::</span><span class="no">QRCode</span> <span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">otp_url</span><span class="p">)</span> <span class="p">.</span><span class="nf">as_png</span><span class="p">(</span><span class="ss">resize_exactly_to: </span><span class="mi">300</span><span class="p">)</span> <span class="p">.</span><span class="nf">to_data_url</span> <span class="n">image_tag</span><span class="p">(</span><span class="n">qr_code</span><span class="p">)</span> <span class="k">end</span> </code></pre></div></div> <p>Now within the view files that the gem generated you should have <code class="highlighter-rouge">app/views/devise/otp_tokens/_token_secret.html.slim</code>. This <a href="https://github.com/wmlele/devise-otp/blob/master/app/views/devise/otp_tokens/_token_secret.html.erb#L4">file</a> is where the qrcode is generated for the user enabling 2fa for their account. I replaced the <code class="highlighter-rouge">otp_authenticator_token_image</code> (jQuery based) method with <code class="highlighter-rouge">otp_rqr_image</code> that we created earlier. You should now have a valid 2fa QRcode which is also scanable by 1Password and most of password managers or 2fa authenticators.</p> <p><img src="/images/posts/devise_otp_without_query/2fa_user_qrcode.png" alt="qrcode" class="img-fluid w-4/5" /></p>BrandonSetting up a Pi-Hole2019-08-27T00:00:00+00:002019-08-27T00:00:00+00:00repo://posts.collection/_posts/2019-08-27-setting-up-pi-hole.md<p>Before we get started, if you haven’t heard of the <a href="https://pi-hole.net/">PiHole</a> module. It’s an Open Source networking product that’s <a href="https://www.reddit.com/r/pihole/">exploding</a> in the consumer networking world. Let me begin by saying at my families house we have multiple TV’s (a smart TV and a TV with a Roku), the kids each have tablets, and my wife and I have our phones and laptops, as well as a few personal servers (local NAS and development servers). And for the majority of the time, most of these devices are sending and receiving traffic at any given time.</p> <p>For several months in a row our households bandwidth peaked over our xfinity (<em>Comcast</em>) data cap. I mean $10/$20 extra every month doesn’t sound like much, but it adds up (especially when you have kids). So I started to monitoring our networks traffic a little more thoroughly, and it turns out a pretty sizeable amount of our throughput was from advertisements. I’m not going to deny watching Netflix, Hulu, Disney+, and YouTube doesn’t consume a ton of bandwidth; but our network was congested with a ton of traffic, that honestly we had no idea was even being used.</p> <p>Let me say since I setup our PiHole our network speed seems to have dramatically increased and as you can see from the picture below our network traffic have halved. I’m only theorizing here, but the increase in network speed seems to be from from a combination of having a local DNS within our home network, as well as denying network traffic to a <em><strong>TON</strong></em> of external resources.</p> <p><img src="/images/posts/setting_up_a_pihole/network_traffic.png" alt="" class="img-fluid w-3/12 sm-w-100" /></p> <p>I won’t be walking you through how to setup the RasberryPi with Rasbian, but if you haven’t set it up yet I would suggest starting with their <a href="https://www.raspberrypi.org/documentation/raspbian/">documentation</a>. (I setup my Pi using <a href="https://www.raspberrypi.org/downloads/raspbian/">Rasbian Lite</a> to reduce the number of wasted resources, since it will be mainly used as a headless network device.)</p> <p>My PiHole setup consists of a <a href="https://www.amazon.com/gp/product/B07TZNW8X2/">Raspberry Pi 4</a> with 4GB of RAM and because of the <a href="https://www.jeffgeerling.com/blog/2019/raspberry-pi-4-needs-fan-heres-why-and-how-you-can-add-one">heat issue</a> that everyone’s complaining about; I also purchased a <a href="https://www.amazon.com/gp/product/B07TXSQ47L">case with a cooling fan</a>. I know, 4GB is way overboard for a for such a <a href="https://pi-hole.net/2017/05/24/how-much-traffic-can-pi-hole-handle/">lightweight process</a> and used as a local DNS server, but I’m also using it for a number of other services as well.</p> <h3 id="begin-setup">Begin setup</h3> <p>This first step isn’t required, but I ran the following command (<code class="highlighter-rouge">lsb_release -a &amp;&amp; uname -a</code>) so you could see what Rasbian release this setup was build on.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>No LSB modules are available. Distributor ID: Raspbian Description: Raspbian GNU/Linux 10 <span class="o">(</span>buster<span class="o">)</span> Release: 10 Codename: buster Linux Falion 4.19.58-v7l+ <span class="c">#1245 SMP Fri Jul 12 17:31:45 BST 2019 armv7l GNU/Linux</span> </code></pre></div></div> <h3 id="adjust-rasbian-configuaton">Adjust Rasbian Configuaton</h3> <p>Next you’ll need to run <code class="highlighter-rouge">sudo raspi-config</code> and update the configurations listed below.</p> <ul> <li>Setup new password</li> <li>Set Hostname</li> <li>Set locales (en_us.utf-8, timezone, keyboard layout, country)</li> <li>Update raspi-cofig tool</li> </ul> <p>After these changes, lets restart the Pi so the new configuration changes will be applied <code class="highlighter-rouge">shutdow now -r</code></p> <p>Since this will be an important component in your network, lets start out on the right foot and remember that we need to be thinking about security with every step along the way. Once your Pi has finished restarting, you’ll need to change the root password.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>su <span class="c"># This is used to switch to the root user</span> passwd <span class="c"># Used to change the current users password</span> </code></pre></div></div> <p>Next, lets update all currently installed packages and ensure security when installing optional packages.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Update all software on the Pi</span> apt-get update <span class="o">&amp;&amp;</span> apt-get upgrade <span class="nt">-y</span> <span class="o">&amp;&amp;</span> apt-get <span class="nb">install </span>apt-transport-https ca-certificates <span class="nt">-y</span> </code></pre></div></div> <h3 id="hardening-the-pi">Hardening the Pi</h3> <p>I’m not going to cover every single step of hardening you’re RaspberryPi, but if you need an even deeper understanding of Linux security there are an endless supply of guides on how to harden a Debian based distro.</p> <p>Lets start by create an additional user, and removing the default <code class="highlighter-rouge">pi</code> user. This is more of a <a href="https://en.wikipedia.org/wiki/Security_through_obscurity">security by obscurity</a>, because everyone will knows that by default your devices will have a user named <code class="highlighter-rouge">pi</code> which gets them one step closer to having access to your device.</p> <p>First we need to create the new user <code class="highlighter-rouge">sudo adduser user_name</code></p> <p>Now lets add the new user to the list of sudoers</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>nano /etc/sudoers.d/010_pi-nopasswd <span class="c"># This allows the new user to run sudo, but requires a valid password to entered first</span> user_name <span class="nv">ALL</span><span class="o">=(</span>ALL<span class="o">)</span> PASSWD: ALL <span class="c"># You can also run the following</span> <span class="nb">sudo</span> /usr/sbin/useradd <span class="nt">--groups</span> <span class="nb">sudo</span> <span class="nt">-m</span> user_name </code></pre></div></div> <p>You’ll now need to be sure to completely kill the current terminal session as the <code class="highlighter-rouge">pi</code> user, I’d suggest typing <code class="highlighter-rouge">logout</code> to make sure the session is properly killed.</p> <p>The next step is to login as the user we just created, remove the default <code class="highlighter-rouge">pi</code> user that, and then remove this user from the list of sudoers. (This may seem like a bunch of unnecessary steps, but we want to ensure at least a minimum level of security.)</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># now ssh into the pi using the new user</span> ssh [email protected] <span class="nb">sudo </span>su <span class="c"># Remove the pi user</span> <span class="nb">sudo </span>deluser <span class="nt">-remove-home</span> pi <span class="c"># Remove the user from the sudoer list as well</span> <span class="nb">sudo </span>nano /etc/sudoers.d/010_pi-nopasswd -&gt; pi <span class="nv">ALL</span><span class="o">=(</span>ALL<span class="o">)</span> NOPASSWD: ALL </code></pre></div></div> <h4 id="lets-start-with-ssh">Let’s start with SSH</h4> <p>Now since I’m running this as a headless device on my network I’ll only be accessing this device through a shell terminal. So the next step is to begin by locking down OpenSSH. I know this complicates things to another level, but in my setup I generally prefer to to have a different SSH key a number of services. I use one key for my DigitalOcean servers, one key for Github, another key for my works Gitlab account, etc etc. The biggest issue with this is managing a growing number of keys, but if you keep up with configuring your <a href="https://linuxize.com/post/using-the-ssh-config-file/">SSH config</a> file <code class="highlighter-rouge">~/.ssh/config</code> as you generate the keys your maintenance should be pretty minimal. My rational behind this is; some people use a single key for every service which is logical, I mean it’s <a href="https://security.stackexchange.com/questions/112718/difficulty-of-breaking-private-key-password">not exactly</a> easy to crack a 4096 encryption key. But if you somehow happen to leak that one private key, any and all services you have associate with it are now compromised. If you have a different RSA key for each major service you use and one key gets comprised the impact should minimal rather than effecting a large number of services.</p> <p>Now it may be a personal preference but rather than just generating a generic RSA key I prefer to use <a href="https://ed25519.cr.yp.to">ed25519 keys</a>, mainly because they’re small, fast, and very secure. (If you need instructions on how to <a href="https://theworkaround.com/2016/11/11/enhancing-ssh-security.html#newer-key-types">generate ed25519 keys</a>, I have a snip on one of my previous posts.)</p> <p>Now we need to ensure we have the latest version of OpenSSH installed, to do this run the following command: <code class="highlighter-rouge">sudo apt install openssh-server</code></p> <p>The configuration I use follows pretty closely to a combination of the <a href="https://infosec.mozilla.org/guidelines/openssh">Mozilla SSH Guidelines (Modern)</a> and <a href="http://tldp.org/LDP/solrhe/Securing-Optimizing-Linux-RH-Edition-v1.3/chap15sec122.html"><abbr title="The Linux Documentation Project">TLDP</abbr></a> recommendations.</p> <p>Lets open our ssh_config file and replace the configuration with the snippet posted below - <code class="highlighter-rouge">sudo nano /etc/ssh/sshd_config</code></p> <div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Adapted from the "Modern" configuration detailed on the Mozilla Security # Guidelines wiki (https://wiki.mozilla.org/Security/Guidelines/OpenSSH). # https://github.com/mozilla/socorro-infra/blob/master/puppet/modules/socorro/files/etc_ssh/sshd_config # http://tldp.org/LDP/solrhe/Securing-Optimizing-Linux-RH-Edition-v1.3/chap15sec122.html # docs: http://tldp.org/LDP/solrhe/Securing-Optimizing-Linux-RH-Edition-v1.3/chap15sec122.html # Package generated configuration file # See the sshd_config(5) manpage for details </span> <span class="err">Port</span> <span class="err">22</span> <span class="err">Protocol</span> <span class="err">2</span> <span class="err">HostKey</span> <span class="err">/etc/ssh/ssh_host_ed25519_key</span> <span class="err">HostKey</span> <span class="err">/etc/ssh/ssh_host_rsa_key</span> <span class="err">HostKey</span> <span class="err">/etc/ssh/ssh_host_ecdsa_key</span> <span class="err">KexAlgorithms</span> <span class="err">[email protected],ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256</span> <span class="err">Ciphers</span> <span class="err">[email protected],[email protected],[email protected],aes256-ctr,aes192-ctr,aes128-ctr</span> <span class="err">MACs</span> <span class="err">[email protected],[email protected],[email protected],hmac-sha2-512,hmac-sha2-256,[email protected]</span> <span class="c"># Completely disable password based logins, PublicKey login is required </span><span class="err">PubkeyAuthentication</span> <span class="err">yes</span> <span class="c">#AuthenticationMethods publickey,keyboard-interactive:pam # enables multiset authentication PublicKey-&gt;Password </span><span class="err">AuthenticationMethods</span> <span class="err">publickey</span> <span class="err">KbdInteractiveAuthentication</span> <span class="err">yes</span> <span class="c"># Lifetime and size of ephemeral version 1 server key </span><span class="err">KeyRegenerationInterval</span> <span class="err">3600</span> <span class="c"># to prevent descryption, regenerate connection keys </span><span class="err">ServerKeyBits</span> <span class="err">1024</span> <span class="c"># Limit to users/groups </span><span class="err">AllowUsers</span> <span class="err">tarellel</span> <span class="c"># Don't read the user's ~/.rhosts and ~/.shosts files </span><span class="err">IgnoreRhosts</span> <span class="err">yes</span> <span class="c"># prevents login from trusted networks #RhostsAuthentication no </span><span class="err">RhostsRSAAuthentication</span> <span class="err">no</span> <span class="c"># Logging #SyslogFacility AUTH </span><span class="err">SyslogFacility</span> <span class="err">AUTHPRIV</span> <span class="err">LogLevel</span> <span class="err">INFO</span> <span class="c"># Log sftp level file access (read/write/etc.) that would not be easily logged otherwise. </span><span class="err">Subsystem</span> <span class="err">sftp</span> <span class="err">/usr/lib/openssh/sftp-server</span> <span class="err">-f</span> <span class="err">AUTHPRIV</span> <span class="err">-l</span> <span class="err">INFO</span> <span class="c"># Authentication: </span><span class="err">PermitRootLogin</span> <span class="err">No</span> <span class="err">UsePrivilegeSeparation</span> <span class="err">sandbox</span> <span class="c"># prevent user privilege escalation </span><span class="err">LoginGraceTime</span> <span class="err">30</span> <span class="c"># default 120/2m </span><span class="err">StrictModes</span> <span class="err">yes</span> <span class="c"># checks user [~] permissions </span> <span class="err">X11Forwarding</span> <span class="err">no</span> <span class="c"># may wish to turn this off for security purposes it was defaulted to yes </span><span class="err">AllowTcpForwarding</span> <span class="err">no</span> <span class="c"># ClientAliveCountMax 2 # max amount of concurrently connected clients # http://serverfault.com/questions/275669/ssh-sshd-how-do-i-set-max-login-attempts # MaxAuthTries 1 # 1 login attempt per connection, before being dropped # MaxSessions Specifies the maximum number of open sessions permitted per network connection # MaxSessions 2 </span> <span class="c"># https://patrickmn.com/aside/how-to-keep-alive-ssh-sessions/ # Keep idle TCP connections alive (kill idle connections) </span><span class="err">TCPKeepAlive</span> <span class="err">no</span> <span class="err">ClientAliveInterval</span> <span class="err">120</span> <span class="c"># how long the connection can be idle (seconds) </span> <span class="c"># NOTE: It's best to disable this when forwarding is also disabled </span><span class="err">Compression</span> <span class="err">no</span> <span class="err">PasswordAuthentication</span> <span class="err">no</span> <span class="err">PermitEmptyPasswords</span> <span class="err">no</span> <span class="err">ChallengeResponseAuthentication</span> <span class="err">yes</span> <span class="c"># Login/Logout messages </span><span class="err">PrintMotd</span> <span class="err">no</span> <span class="c"># Banner /etc/issue.net </span> <span class="err">UsePAM</span> <span class="err">yes</span> <span class="c"># Ensure /bin/login is not used so that it cannot bypass PAM settings for sshd. </span><span class="err">UseLogin</span> <span class="err">no</span> <span class="err">UseDNS</span> <span class="err">no</span> </code></pre></div></div> <p>Next you’ll need to ensure your personal machines public ssh-key is on the Raspberry, so you won’t be locked out of the device. On your local machine run <code class="highlighter-rouge">pbcopy &lt; ~/.ssh/.ssh/localnetwork_id.pub</code> this copys the contents of your SSH’s publickey to your clip. (As I mentioned before, I prefer to generate SSH keys for different services. The SSH-key <code class="highlighter-rouge">localnetwork</code> was generated for all devices in my local home network).</p> <p>Now go back to the terminal and ensure the authorized_keys file creates for the current user in the ssh folder <code class="highlighter-rouge">mkdir ~/.ssh; chmod 0700 ~/.ssh; touch ~/.ssh/authorized_keys; nano ~/.ssh/authorized_keys</code>. The last part of this command opens up the <code class="highlighter-rouge">authorized_keys</code> file in nano, since you previously copied the contents of you public key to your clipboard lets go ahead paste into the this file and hit CTRL-X and save the file. After the contents have been saved you’ll need to chmod it to ensure access to it’s contents is limited <code class="highlighter-rouge">chmod 0600 ~/.ssh/authorized_keys</code>. In order for all these changes to be applied you’ll need to restart the ssh process <code class="highlighter-rouge">sudo systemctl restart ssh</code> (you could always do something like <code class="highlighter-rouge">systemctl reload ssh</code> but I prefer to just restart the process).</p> <h4 id="lets-add-a-firewall">Lets add a firewall</h4> <p>I’m sure there will be some moaning that I’m using <a href="https://launchpad.net/ufw">UFW</a> over <a href="https://linux.die.net/man/8/iptables">IPtables</a>, but I find UFW to be easy to get started with and it does exactly what I need.</p> <p>Let’s begin by installing the UFW package <code class="highlighter-rouge">sudo apt-get install ufw</code>. But for me, after intalling UFW I started getting “an IP tables error has occured” when trying to start the UFW server. So again I restarted the Pi and after it had finished reloading all errors were gone. Below are the UFW rules used to begin security incoming network services for your device, before running these commands it helps to <code class="highlighter-rouge">su sudo</code> so you’re creating these rules as a root user.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Some people also prefer to be extra cautious and also begin by blocking all outgoing connects `ufw default deny outgoing` ports.</span> ufw default deny incoming <span class="c"># Block ALL incoming ports</span> ufw allow ssh ufw allow 53 <span class="c"># DNS port</span> ufw allow http <span class="c"># Port 80 &amp; 443 are used by PiHole to display a dashboard with PHP/lighttpd</span> ufw allow https <span class="c"># Limit the number of login attempts on SSH port</span> ufw limit ssh/tcp <span class="c"># Allow FTL pihole engine from LAN (if you are using a different subnet specify this instead)</span> ufw allow from 192.168.1.0/24 to any port 4711 proto tcp ufw <span class="nb">enable</span> </code></pre></div></div> <p>Now that a few basic firewall rules have been creates let’s reload the UFW service in order for these rules to start being applies <code class="highlighter-rouge">sudo ufw reload</code>.</p> <h4 id="fail2ban">Fail2Ban</h4> <p>Next we’ll setup <a href="https://www.fail2ban.org/wiki/index.php/Main_Page">Fail2Ban</a>, which is one of my favorite tools to prevent <a href="https://en.wikipedia.org/wiki/Brute-force_attack">BruteForcing</a> and/or <a href="https://www.owasp.org/index.php/Credential_stuffing">credential stuffing</a> login attempts. With the firewall setup; we’ve already taken the measure of preventing network requests to a large number of ports and services. But whenever there’s an open SSH port, I guarentee you it will get hit with requests. (I averted this by only having a VPN port on my router and in order to access my network from outside the network requires you to conenct via VPN into my network, but I’ll cover that farther down.)</p> <p>Begin by installing the fail2ban package <code class="highlighter-rouge">apt install fail2ban</code>, than we’ll adjust the configuration to begin monitoring any and all ssh login attemps</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo cp</span> /etc/fail2ban/jail.conf /etc/fail2ban/jail.local <span class="nb">sudo </span>nano /etc/fail2ban/jail.local </code></pre></div></div> <p>Once nano has opened up <code class="highlighter-rouge">/etc/fail2ban/jail.local</code> hit Ctrl+W and search for <code class="highlighter-rouge">[sshd]</code> and change the configuration for ssh to the following.</p> <div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[sshd]</span> <span class="py">enabled</span> <span class="p">=</span> <span class="s">true</span> <span class="py">port</span> <span class="p">=</span> <span class="s">ssh</span> <span class="py">filter</span> <span class="p">=</span> <span class="s">sshd</span> <span class="py">logpath</span> <span class="p">=</span> <span class="s">/var/log/auth.log</span> <span class="py">maxretry</span> <span class="p">=</span> <span class="s">3</span> <span class="py">bantime</span> <span class="p">=</span> <span class="s">-1</span> </code></pre></div></div> <p>Now lets restart and enable fail2ban and verify the process and configuration have properly loaded</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service fail2ban restart<span class="p">;</span> service fail2ban start <span class="nb">enable</span> <span class="c"># Verify fail2ban is running</span> service fail2ban status </code></pre></div></div> <p>This step isn’t required, but I like to get the fail2ban client status to ensure the sshd jails (or any others you may add) are enabled <code class="highlighter-rouge">sudo fail2ban-client status</code></p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Status <span class="k">for </span>the jail: sshd |- Filter | |- Currently failed: 0 | |- Total failed: 0 | <span class="sb">`</span>- File list: /var/log/auth.log <span class="sb">`</span>- Actions |- Currently banned: 0 |- Total banned: 0 <span class="sb">`</span>- Banned IP list: </code></pre></div></div> <h4 id="unattended-upgrades">Unattended Upgrades</h4> <p>Another one of my favorite tools on a <a href="https://www.debian.org/">Debian</a> based distro is <a href="https://wiki.debian.org/UnattendedUpgrades">Unattended Upgrades</a>. For those of you who have never used it before, it will systematically apply security updates, distro updates, or whatever kind of upgrades you choose to allow it to update. I prefer to keep everything up to do, but it can be danger to enable allowing automatic updates to something such as a webserver. If this is the case, I’d suggest only enabling security updates; because major package upgrades can and will break your software.</p> <p>Again let’s begin by installing the required packages <code class="highlighter-rouge">sudo apt-get install unattended-upgrades -y</code>. Once the package has installed you’ll need to update it’s default configuration <code class="highlighter-rouge">sudo nano /etc/apt/apt.conf.d/50unattended-upgrades</code> to the following: <em>(The contents of the file specify what updates to look for.)</em></p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Unattended</span><span class="o">-</span><span class="nx">Upgrade</span><span class="p">::</span><span class="nx">Origins</span><span class="o">-</span><span class="nx">Pattern</span> <span class="p">{</span> <span class="c1">// Codename based matching:</span> <span class="c1">// This will follow the migration of a release through different</span> <span class="c1">// archives (e.g. from testing to stable and later oldstable).</span> <span class="c1">// Software will be the latest available for the named release,</span> <span class="c1">// but the Debian release itself will not be automatically upgraded.</span> <span class="dl">"</span><span class="s2">origin=Debian,codename=${distro_codename}-updates</span><span class="dl">"</span><span class="p">;</span> <span class="c1">// origin=Debian,codename=${distro_codename}-proposed-updates";</span> <span class="dl">"</span><span class="s2">origin=Debian,codename=${distro_codename},label=Debian</span><span class="dl">"</span><span class="p">;</span> <span class="dl">"</span><span class="s2">origin=Debian,codename=${distro_codename},label=Debian-Security</span><span class="dl">"</span><span class="p">;</span> <span class="c1">// Archive or Suite based matching:</span> <span class="c1">// Note that this will silently match a different release after</span> <span class="c1">// migration to the specified archive (e.g. testing becomes the</span> <span class="c1">// new stable).</span> <span class="c1">// "o=Debian,a=stable";</span> <span class="c1">// "o=Debian,a=stable-updates";</span> <span class="c1">// "o=Debian,a=proposed-updates";</span> <span class="c1">// "o=Debian Backports,a=${distro_codename}-backports,l=Debian Backports";</span> <span class="p">};</span> </code></pre></div></div> <p>The next step is to open up <code class="highlighter-rouge">/etc/apt/apt.conf.d/20auto-upgrades</code> and configure what components of unattended-upgrades you want to enable. In order to keep my packages up to date with the latest changes I changed mine to the following configuration:</p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">APT</span><span class="p">::</span><span class="nx">Periodic</span><span class="p">::</span><span class="nx">Update</span><span class="o">-</span><span class="nx">Package</span><span class="o">-</span><span class="nx">Lists</span> <span class="dl">"</span><span class="s2">1</span><span class="dl">"</span><span class="p">;</span> <span class="nl">APT</span><span class="p">::</span><span class="nx">Periodic</span><span class="p">::</span><span class="nx">Download</span><span class="o">-</span><span class="nx">Upgradeable</span><span class="o">-</span><span class="nb">Packages</span> <span class="dl">"</span><span class="s2">1</span><span class="dl">"</span><span class="p">;</span> <span class="nl">APT</span><span class="p">::</span><span class="nx">Periodic</span><span class="p">::</span><span class="nx">Unattended</span><span class="o">-</span><span class="nx">Upgrade</span> <span class="dl">"</span><span class="s2">1</span><span class="dl">"</span><span class="p">;</span> <span class="nl">APT</span><span class="p">::</span><span class="nx">Periodic</span><span class="p">::</span><span class="nx">Verbose</span> <span class="dl">"</span><span class="s2">1</span><span class="dl">"</span><span class="p">;</span> <span class="nl">APT</span><span class="p">::</span><span class="nx">Periodic</span><span class="p">::</span><span class="nx">AutocleanInterval</span> <span class="dl">"</span><span class="s2">7</span><span class="dl">"</span><span class="p">;</span> </code></pre></div></div> <p>Now let’s enable unattended upgraded as a low priority process <code class="highlighter-rouge">sudo dpkg-reconfigure --priority=low unattended-upgrades</code> to being monitoring and applying future updates in order to keep our device secure.</p> <h2 id="installing-pihole">Installing PiHole</h2> <p>I know thus far, it’s been like provisioning any other Linux box; but now comes the fun part. To get started quickly run the following command <code class="highlighter-rouge">curl -sSL https://install.pi-hole.net | bash</code>, it does take a few moments because it attempts to install any additional required libraries. It also required to select a number of configurations based on how you like your PiHole setup.</p> <p><img src="/images/posts/setting_up_a_pihole/installing_pihole_1.png" alt="Beginning Install (15%)" class="img-fluid w-50 sm-w-100" /></p> <p><img src="/images/posts/setting_up_a_pihole/installing_pihole_2.png" alt="PiHole Network Notice" class="img-fluid w-50 sm-w-100" /></p> <p>After going through a few of the setup screens you’ll be presented with your fix big choice. Which DNS Upstream do you with to use? You’ll be presented with a number of choices, including but not limited to Google, OpenDNS, Level3, CloudFlare and a number of other choices. I’m my opinion I suggest picking CloudFlare their <a href="https://www.cloudflare.com/dns/">DNS</a> service is extremely fast and their whole suite of services is about providing security by default.</p> <p><img src="/images/posts/setting_up_a_pihole/installing_pihole_3.png" alt="Selecting Upstream Provider" class="img-fluid w-50 sm-w-100 sm-w-100" /></p> <p>Next you’ll be present with of thirdparty blocklists to choose from, this is completely up to you and what you want to block. And you can always add more later (which we do with this walkthrough).</p> <p><img src="/images/posts/setting_up_a_pihole/installing_pihole_4.png" alt="Choosing your blocklists" class="img-fluid w-50 sm-w-100" /></p> <p><img src="/images/posts/setting_up_a_pihole/installing_pihole_5.png" alt="Installing additional packages" class="img-fluid w-50 sm-w-100" /></p> <p>Since we setup UFW network requests should be filtered. Part of the installation script ensures that the proper ports available in order for the pihole to function properly.</p> <p><img src="/images/posts/setting_up_a_pihole/installing_pihole_6.png" alt="Opening up the firewall" class="img-fluid w-50 sm-w-100" /></p> <p><img src="/images/posts/setting_up_a_pihole/installing_pihole_7.png" alt="Terminal display of PiHole Installing" class="img-fluid w-50 sm-w-100" /></p> <p>Once the installation has finished installing you should be presented with a final menu telling you your PiHoles dashboard address and password. (The address should be something like <code class="highlighter-rouge">http://192.168.1.5/admin</code>). When visiting the dashboard you should be presented with something similar to the following image:</p> <p><img src="/images/posts/setting_up_a_pihole/installing_pihole_8.png" alt="PiHole Dashboard" class="img-fluid w-50 sm-w-100" /></p> <p>Now for the most part you’re almost, next we’ll need to configure our router to query the PiHole service for any DNS requests. To do this you’ll need to change your Routers DNS settings to use the internal IP Address for the RaspberryPi. I also applied CloudFlares DNS IP Addresses <code class="highlighter-rouge">1.1.1.1</code> and <code class="highlighter-rouge">1.0.0.1</code>. This is because for any reason the Pi is shutdown or inaccessible your network won’t come a dead halt, it’ll fallback to the secondary DNS server (Cloudflare).</p> <p><img src="/images/posts/setting_up_a_pihole/installing_pihole_9.png" alt="PiHole Dashboard" class="img-fluid w-50 sm-w-100" /></p> <h3 id="setting-up-vpn-access">Setting up VPN Access</h3> <p><a href="http://www.pivpn.io/">PiVPN</a> is an insanely easy to install and the Pi alternative to installing OpenVPN on an ARM chip.</p> <p>To install it lets run the following command <code class="highlighter-rouge">curl -L https://install.pivpn.io | bash</code></p> <p>Again the commandline menu screen will come up and walk you through the various steps to install PiVPN, some of the steps are pretty quick but once you get to where it needs to generate a certificate for your VPN it will take a while. Once OpenVPN started generating a certificate for the VPN I stepped away because for a snack, but it took a good 5 or 10 minutes to generate.</p> <p>Something to note, I ended up having to set the VPN’s DNS settings as the PI’s static IP Address. It may have just been the situation, but when I was VPN’ing in and the DNS was set as the IP address of the router <code class="highlighter-rouge">192.168.1.1</code> it was causing my computer the pull DNS from the actual network I was connected to rather then through the PiHole service. In order to mitigate this I ended up changing the VPNs DNS settings to use it’s own IP address as the DNS server.</p> <p>To do this we’ll need to modify the openvpn config <code class="highlighter-rouge">nano /etc/openvpn/server.conf</code> and change the following <code class="highlighter-rouge">push "dhcp-option DNS 192.168.1.1"</code> to <code class="highlighter-rouge">push "dhcp-option DNS 192.168.71.1"</code> or to whatever you Pi’s ip address is.</p> <p>If you don’t know your current local IP address you can get this by running <code class="highlighter-rouge">ifconfig</code> and getting the ip address listed under the eth0 network adapter.</p> <p>After this step was complete I ended up rebooting the Pi again, so all the new configuration changes and services would be applied.</p> <p>We’re still a ways off from being finished with setting up the VPN, once the Pi has finished rebooting we need to add users to the VPN. This can be done by running the command <code class="highlighter-rouge">pivpn add</code>. You may want to just have a user and make them authenticate with the signed key, I’m a little cautious and decided to have my user <em>(of course me)</em> require to have a password as well as the signed key to authenticate to the VPN. If you end up needing help or finding additional commands for the VPN you can run the command <code class="highlighter-rouge">pivpn help</code>.</p> <p>The next step I did was pull my generated VPN file to my computer so I could add it to my computer and phone. If you do plan on using your phone, for security reasons I suggest having a seperate VPN key for your computer and mobile phone.</p> <p>To pull the openvpn signature file to you computer using scp, you can run a command similar to the following: <em>(Towards the end of this post I’ll explain how to use the on ur phone or macbook)</em></p> <p><code class="highlighter-rouge">scp -i /Users/pi_user/.ssh/localnetwork_id [email protected]:/home/tarellel/ovpns/tarellelRemote.ovpn .</code></p> <p>The next step is to enable access to the VPN ports, otherwise we’ll still never be able to VPN into the network through the PI. I started by enabling the UDP VPN port <code class="highlighter-rouge">ufw allow 1194/udp</code>, but for some reason I had issues. To get around this I ended up having to remove this rule and just enable access to the 1194 port in general <code class="highlighter-rouge">ufw allow 1194</code>.</p> <p>After opening up the VPN ports I decided to reload it’s configuration, it may be a bit redundent but what’s it going to hurt.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo</span> /etc/init.d/openvpn reload <span class="c"># Just to verify openvpn reloaded properly</span> <span class="nb">sudo </span>systemctl status openvpn </code></pre></div></div> <p>Now we need to update openvpn’s network devices priority <code class="highlighter-rouge">sudo nano /etc/pihole/setupVars.conf</code> and add the following <code class="highlighter-rouge">PIHOLE_INTERFACE=tun0</code> below eth0. The pretty much tells openVPN to use <code class="highlighter-rouge">eth0</code> as it’s primary device and <code class="highlighter-rouge">tun0</code> as the pihole virtual network device.</p> <p>Next we’ll need to list the devices in which we want to allow to make DNS requests through the <a href="http://www.thekelleys.org.uk/dnsmasq/doc.html">dnsmasq</a> network service. To add the devices lets open it up <code class="highlighter-rouge">nano /etc/dnsmasq.d/01-pihole.conf</code> and add the following list of devices.</p> <div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">interface</span><span class="p">=</span><span class="s">eth0</span> <span class="py">interface</span><span class="p">=</span><span class="s">tun0</span> </code></pre></div></div> <p>Since we’ve just make some more changes to the OpenVPN configuration and firewall lets reset these services <em>(yes again, I know)</em>.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service openvpn reload service openvpn restart systemctl <span class="nb">enable </span>ufw </code></pre></div></div> <h2 id="install-log2ram">Install log2ram</h2> <p>This next step is pretty important, the reason being SD cards aren’t meant to have files written on and removed from at a constant pace, especially when it comes to generating logs. It’s like havine a piece of paper writing on it and than erasing it, over and over again. Eventually that piece of paper will become useless, the same can be said for SD cards. To mitigate this, we end up using <a href="https://github.com/azlux/log2ram">log2ram</a> which will save our logs in memory and once it consumes X amount of memory it’ll save as an actual log file.</p> <p>The following steps are copied directory from the projects documentation.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-Lo</span> log2ram.tar.gz https://github.com/azlux/log2ram/archive/master.tar.gz <span class="nb">tar </span>xf log2ram.tar.gz <span class="nb">cd </span>log2ram-master <span class="nb">chmod</span> +x install.sh <span class="o">&amp;&amp;</span> <span class="nb">sudo</span> ./install.sh <span class="nb">cd</span> .. <span class="nb">rm</span> <span class="nt">-r</span> log2ram-master </code></pre></div></div> <p>Now before doing anything else we’ll need to restart the server again <code class="highlighter-rouge">shutdown now -r</code>. After your device has came back up, we’ll need to adjust the log2ram’s configuration by editing the following file <code class="highlighter-rouge">/etc/log2ram.conf</code>. And chance the following:</p> <div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Change </span><span class="py">SIZE</span><span class="p">=</span><span class="s">40M</span> <span class="c"># Lets increase the RAM log size to 100M </span><span class="py">SIZE</span><span class="p">=</span><span class="s">100M</span> <span class="c"># You also want to disbale creating report error mails </span><span class="py">MAIL</span><span class="p">=</span><span class="s">true</span> <span class="c"># Change it to falsse </span><span class="py">MAIL</span><span class="p">=</span><span class="s">false</span> </code></pre></div></div> <p>I know you’re getting tired of it, but again we’ll need to restart the Pi in order for the new RAM/log configuration to be properly come into effect. <code class="highlighter-rouge">shutdown now -r</code></p> <h2 id="update-raspberry-pis-bootloader">Update Raspberry Pi’s bootloader</h2> <p>If you’re device is pretty fresh out of the box I can almost guarentee you that your Pi will need to have it’s bootloader updated. I’m just going to list the steps take in order to verify and apply a bootloader update</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get update <span class="o">&amp;&amp;</span> apt-get <span class="nb">install </span>rpi-eeprom <span class="c"># enable checking for bootloader updates</span> systemctl unmask rpi-eeprom-update <span class="c"># For the O/S to check for a bootlaoder update</span> rpi-eeprom-update <span class="c"># If the results show an update you'll need the Pi to prepare the page for update</span> rpi-eeprom-update <span class="nt">-a</span> <span class="c"># In order for the bootloader update to be applied a restart is required</span> shutdown now <span class="nt">-r</span> </code></pre></div></div> <p>If you’re device requires an update your results should look similar to the following screenshot.</p> <p><img src="/images/posts/setting_up_a_pihole/bootloader_update.png" alt="Bootloader Update" class="img-fluid sm-w-100" /></p> <h2 id="update-pihole-blocklists">Update PiHole Blocklists</h2> <p>By default the basic list that your Pihole uses is pretty decent it blocks quite a bit of the heavy ad and trackying systems, but I prefer to block more. This is because as I watch my traffic I noticed several of the devices in my houses are still sending requests to various tracking URLs including my smart TV, my kids tablets, my printer, etc.</p> <p><img src="/images/posts/setting_up_a_pihole/pihole_stats.png" alt="Pihole Request Stats" class="img-fluid w-50 sm-w-100" /></p> <p>First you’ll want setup your <a href="https://docs.pi-hole.net/guides/whitelist-blacklist/">blocklist</a> of DNS requests to start blocking as many trackers as you possibly can. My list of blocklists is ended up including about 2 million different links and ends up blocking anywhere between 40-70% of my daily traffic <em>(<a href="https://gist.githubusercontent.com/tarellel/40296f278405e48365cf91b319a9dd3d/raw/af63425d1506dbb03aa47b57aae7ed32bbd7f92a/PiHole_Blocklists.txt">my list of blocklists</a>)</em>. Once you add these lists to your piholes blocklist and update you Pi gravity rating you’ll almost instant notice pages are loading faster and you traffics congestion has been massive reduced.</p> <p>Not lets add some <a href="https://docs.pi-hole.net/ftldns/regex/tutorial/">regex</a> filters to filter out any of those DNS that haven’t been caught by the blocklists. The <a href="https://gist.githubusercontent.com/tarellel/40296f278405e48365cf91b319a9dd3d/raw/af63425d1506dbb03aa47b57aae7ed32bbd7f92a/PiHole_Regex_filers.txt">regex filters I use</a>, I believe I got some piecing together a few reddit posts together. They are specifically setup to catch any DNS requests that have the phrase <code class="highlighter-rouge">tracker, traffic, ads, analytics</code> or various other phrases</p> <p>Next you’ll need to add some your whitelists, mine if a bit liberal and I need to go through and trim it down. But I started out by making it pretty broad, because otherwise spccific services and devices would no longer work on my home network because they uptime ping backs were completely disabled. These included; our xbox, Spotify, updating my kids android devices, updating our LG Smart TV, using our Plex server, Hulu, accessing namecheap, and blocking facebook <em>(which I’d prefer, but my wife can’t list without)</em> from the network. My whitelist can also be found also be found on <a href="https://gist.githubusercontent.com/tarellel/40296f278405e48365cf91b319a9dd3d/raw/af63425d1506dbb03aa47b57aae7ed32bbd7f92a/PiHole_Whitelist.txt">github as a gist</a>. After updating and all all these URL’s and snips to your piholes list, you’ll also want to update and reload your piholes gravity list again. This is to ensure anything added to the whitelist hopefully won’t still be filtered and any regex you added will have hopefully be properly filtered.</p> <h2 id="applications-to-use">Applications to use</h2> <p>In order to VPN into your network <em>(if you want to use the Pihole when outside your network)</em>, you’ll need to download the specified VPN client. If you are using an iPhone you’ll need to use <a href="https://apps.apple.com/us/app/openvpn-connect/id590379981">OpenVPN</a> iOS client, for android you can download the <a href="https://play.google.com/store/apps/details?id=net.openvpn.openvpn">OpenVPN</a> app, and for OSx I used <a href="https://tunnelblick.net/downloads.html">Tunnelblick</a>.</p> <p>In order to use the VPN client on your iPhone you will need to connect it to your computer, similar to how you would sync data between the two or doing a backup. <img src="/images/posts/setting_up_a_pihole/trust_iphone.png" alt="Bootloader Update" class="img-fluid w-50 sm-w-100" /></p> <p>Than on the list of options available for your phone , you need to click the files tab, this will allow you to access the files on your phone. We’ll now need to find the VPN key we generated and <code class="highlighter-rouge">scp</code>‘ed from the Raspberry Pi earlier. And you’ll need to dump it on the OpenVPN application. It may prompt you if you’d like to trust the file be transfered to your phone. Accept it and it should setup the VPN connection configuration for you on your phone.</p> <p><img src="/images/posts/setting_up_a_pihole/iphone_application_list.png" alt="Bootloader Update" class="img-fluid w-3/12 sm-w-100" /></p> <p>This won’t work while you’re in the same network, but if you turn off wifi or connect from outside the network you should be able to connect like the pictures below. (if you added a password to your VPN key you may also need to occasionally input the password before it will allow you to connect or use the key).</p> <p><img src="/images/posts/setting_up_a_pihole/VPN_list.png" alt="List of VPNs on iPhone" class="img-fluid w-3/12 sm-w-100" /> <img src="/images/posts/setting_up_a_pihole/VPN_connected.png" alt="Connected to VPN through the Pi-Hole" class="img-fluid w-3/12 sm-w-100" /></p> <h3 id="references">References</h3> <ul> <li><a href="https://docs.pi-hole.net/">PiHole Documentation</a></li> <li><a href="https://www.raspberrypi.org/documentation/raspbian/">Rasbian Documentation</a></li> </ul>BrandonDocker Swarm Persistent Storage2019-05-15T00:00:00+00:002019-05-15T00:00:00+00:00repo://posts.collection/_posts/2019-05-15-docker-swarm-persistent-storage.md<p>Unless you’ve been living under a rock, you should need no explanation what <a href="https://www.docker.com/">Docker</a> is. Using Docker over the last year has drastically improved my deployment ease and with coupled with <a href="https://about.gitlab.com/">GitLab’s</a> CI/CD has made deployment extremely ease. Mind you, not all our applications being deployed have the same requirements, some are extremely simple and others are extraordinarily complex. So when we start a new project we have a base docker build to begin from and based on the applications requirements we add/remove as needed.</p> <h3 id="a-little-about-docker-swarm">A little about Docker Swarm</h3> <p>For the large majority of most of our applications, having a volume associated with the deployed containers and storing information is the database fits the applications needs.</p> <p>In front of all our applications we used to use <a href="https://proxy.dockerflow.com/">Docker Flow Proxy</a> to quickly integrate our application into our deployed environment and assign it a subdomain based on it’s service. For a few months we experienced issues with the proxy hanging up, resources not being cleared, and lots of dropped connections. Since than I have rebuilt our docker infrastructure and now we use <a href="https://traefik.io/">Traefik</a> for our proxy routing and it has been absolutely amazing! It’s extremely fast, very robust and extensible, and easy to manipulate to fit your needs. Heck before even deploying it I was using <a href="https://docs.docker.com/compose/">docker-compose</a> to build a local network proxy to ensure it was what we needed. While Traefik was running in compose I was hitting domains such as <code class="highlighter-rouge">http://whoami.localhost/</code> and this was a great way to learn the basic configuration before pushing it into a staging/production swarm environment. <em>(That explaing how we got started with Traefik is a whole other post of it’s own.)</em></p> <p>Now back to our docker swarm, I know the big thing right now is <a href="https://kubernetes.io/">Kubernetes</a>. But every organization has their specific needs, for their different environments, application, types, and deployment mechanisms. In my opinion the current docker environment we’ve got running right now is pretty robust. We’ve got dozens of nodes, a number of deployment environments (cybersec, staging, and production), dozens of applications running at once, and some of then requiring a number of services in order to function properly.</p> <p>A few of the things that won me over on the docker swarm in the first place is it’s load balancing capabilities, it’s very fault-tolerant, and the self-healing mechanism that it uses in case a container crashes, a node locks up or drops, or a number of other issues. <em>(We’ve had a number of servers go down due to networking issues or a rack server crapping out and with the docker swarm running you could never even tel we were having issues as an end user to our applications.)</em></p> <p><em class="small">(Below is an image showing traffic hitting the swarm. If you have an application replicated upon deployment, traffic will be distributed amongst the nodes to prevent bottle necks.)</em></p> <p><img src="/images/posts/docker-swarm-persistent-storage/SwarmTraffic.svg" alt="Docker Swarm Traffic" class="img-fluid" /></p> <h3 id="why-would-you-need-persistent-storage">Why would you need persistent storage?</h3> <p>Since the majority of our applications are data orientated, (with most of them hitting several databases in a single request) we hadn’t really had to worry about persistent storage. This is because once we deployed the applications; their volumes held all of their required assets and any data they needed was fetched from the database.</p> <p>The easiest way to explain volumes, is when a container is deployed to a node (if specified) it will put aside a section of storage specifically for that container. For example say we have an application called DogTracker the was deployed on node A and B. This application can create and store files in their volumes on those nodes. But what happens when there’s an issue with the container on node A and the container cycles to node C? The data created by the container is left in the volume on node A an no longer available, until that applications container cycles back to node A.</p> <p>And from this arises the problem we began to face. We were starting to develop applications that were starting to require files to be shared amongst each other. We also have numerous applications that require files to be saved and distributed without them being dumped into the database as a blob. And these files were required to be available without cycling volumes and/or dumping them into the containers during build time. And because of this, we needed to be able to have some form of persistent and distributed file storage across our containers.</p> <p><em class="small">(Below is an image showing how a docker swarms volumes are oriented)</em></p> <p><img src="/images/posts/docker-swarm-persistent-storage/DockerSwarm.svg" alt="Docker Swarm Diagram" class="img-fluid" /></p> <h3 id="how-we-got-around-this">How we got around this!</h3> <p>Now in this day an age there’s got to be ways to get around this. There’s at least 101 ways to do just about anything and it doesn’t always have to be newest shiniest toy everyone’s using. I know saying this while using Docker is kind of a hypocritical statement, but shared file systems have been around for decades. You’ve been able to mount network drives, ftp drives, have organizational based shared folders, the list can go on for days.</p> <p>But the big question is, how do we get a container to mount a local shared folder or distribute volumes across all swarm nodes? Well, there’s a whole list of distributed filesystems and modern storage mechanisms in the <a href="https://docs.docker.com/engine/extend/legacy_plugins/">docker documentation</a>. Below is a list of the top recommended alternatives I found for <a href="https://en.wikipedia.org/wiki/Distributed_File_System_(Microsoft)">distributed file systems</a> or <a href="https://en.wikipedia.org/wiki/Network_File_System">NFS’s</a> for the docker stratosphere around container development.</p> <ul> <li><a href="https://ceph.com/">Ceph</a></li> <li><a href="https://github.com/rancher/convoy">Convoy</a></li> <li><a href="https://github.com/rexray/rexray">RexRay</a></li> <li><a href="https://portworx.com/use-case/docker-persistent-storage/">PortWorx</a></li> <li><a href="https://github.com/pvdbleek/storageos">StorageOS</a></li> <li><a href="http://www.xtreemfs.org/">xtreemfs</a></li> </ul> <p>I know you’re wondering why we didn’t use <a href="https://aws.amazon.com/s3/">S3</a>, <a href="https://www.digitalocean.com/products/spaces/">DigitalOcean Spaces</a>, <a href="https://cloud.google.com/storage/docs/">GCS</a>, or some other cloud storage. But internally we have a finite amount of resources and we can spin up VM’s and be rolling in a matter of moments. Especially considering we have build a number of <a href="https://www.ansible.com/">Ansible</a> playbooks to quickly provision our servers. Plus, why throw resources out on the cloud, when it’s not needed. Especially when we can metaphorically create our own network based file system and have our own cloud based storage system.</p> <p><em class="small">(Below is an image showing we want to distribute file system changes)</em></p> <p><img src="/images/posts/docker-swarm-persistent-storage/DockerSwarm_wStorage.svg" alt="" class="img-fluid" /></p> <p>After looking at several methods I settled on <a href="https://www.gluster.org/">GlusterFS</a> a scalable network filesystem. Don’t get me wrong, a number of the other alternatives are pretty ground breaking and some amazing work as been put into developing them. But I don’t have thousands of dollars to drop on setting up a network file system, that may or may not work for our needs. There were also several others that I did look pretty heavily into, such as <a href="https://github.com/pvdbleek/storageos">StorageOS</a> and <a href="https://ceph.com/">Ceph</a>. With StorageOS I really liked the idea of a container based file system that stores, synchronizing, and distributes files to all other storage nodes within the swarm. And it may just be me, but Ceph looked like the prime competitor to Gluster. They both have their <a href="https://technologyadvice.com/blog/information-technology/ceph-vs-gluster/">high points</a> and seem to work very reliable. But at the time; it wasn’t for me and after using Gluster for a few months, I believe that I made the right choice and it’s served it’s purpose well.</p> <p><a href="https://www.gluster.org/"><img src="/images/posts/docker-swarm-persistent-storage/gluster-ant.png" alt="Gluster Ant" class="img-fluid w-3/12" /></a></p> <h4 id="gluster-notes">Gluster Notes</h4> <p><em>(<strong>Note:</strong> The following steps are to be used on a Debian/Ubuntu based install.)</em></p> <p>Documentation for using Gluster can be found on their <a href="https://docs.gluster.org/en/latest/">docs</a>. Their installation instructions are very brief and explain how to install the gluster packages, but they don’t go into depth in how to setup a Gluster network. I also suggest thoroughly reading through to documentation to understand Gluster volumes, bricks, pools, etc.</p> <h3 id="installing-glusterfs">Installing GlusterFS</h3> <p>To begin you will need to list all of the Docker Swarm nodes you wish to connect in the <code class="highlighter-rouge">/etc/hosts</code> files of each server. On linux (Debian/Ubuntu), you can get the current nodes IP Address run the following command <code class="highlighter-rouge">hostname -I | awk '{print $1}'</code></p> <p><em class="fa fa-info-circle text-primary"> </em> <em class="small">(The majority of the commands listed below need to be ran on each and every node simultaneously unless specified. To do this I opened a number of terminal tabs and connected to each server in a different tab.)</em></p> <div class="language-config highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># /etc/hosts </span><span class="m">10</span>.<span class="m">10</span>.<span class="m">10</span>.<span class="m">1</span> <span class="n">staging1</span>.<span class="n">example</span>.<span class="n">com</span> <span class="n">staging1</span> <span class="m">10</span>.<span class="m">10</span>.<span class="m">10</span>.<span class="m">2</span> <span class="n">staging2</span>.<span class="n">example</span>.<span class="n">com</span> <span class="n">staging2</span> <span class="m">10</span>.<span class="m">10</span>.<span class="m">10</span>.<span class="m">3</span> <span class="n">staging3</span>.<span class="n">example</span>.<span class="n">com</span> <span class="n">staging3</span> <span class="m">10</span>.<span class="m">10</span>.<span class="m">10</span>.<span class="m">4</span> <span class="n">staging4</span>.<span class="n">example</span>.<span class="n">com</span> <span class="n">staging4</span> <span class="m">10</span>.<span class="m">10</span>.<span class="m">10</span>.<span class="m">5</span> <span class="n">staging5</span>.<span class="n">example</span>.<span class="n">com</span> <span class="n">staging5</span> </code></pre></div></div> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Update &amp; Upgrade all installed packages</span> apt-get update <span class="o">&amp;&amp;</span> apt-get upgrade <span class="nt">-y</span> <span class="c"># Install gluster dependencies</span> <span class="nb">sudo </span>apt-get <span class="nb">install </span>python-software-properties <span class="nt">-y</span> </code></pre></div></div> <p>Add the GlusterFS <a href="https://itsfoss.com/ppa-guide/">PPA</a> package the list of trusted packages to install from a community repository.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>add-apt-repository ppa:gluster/glusterfs-3.10<span class="p">;</span> <span class="nb">sudo </span>apt-get update <span class="nt">-y</span> <span class="o">&amp;&amp;</span> <span class="nb">sudo </span>apt-get update </code></pre></div></div> <p>Now lets install gluster</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nb">install</span> <span class="nt">-y</span> glusterfs-server attr </code></pre></div></div> <p>Now before starting the Gluster service but I had to copy some files into systemd <em>(you may or may not have to do this)</em>. But since Gluster was developed by <a href="https://www.redhat.com/en/technologies/storage/gluster">RedHat</a> primarily for <a href="https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux">RedHat</a> and <a href="https://www.centos.org/">CentOS</a>, I had a few issues starting the system service.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo cp</span> /etc/init.d/glusterfs-server /etc/systemd/system/ </code></pre></div></div> <p>Let’s start and enable the glusterfs system service</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl <span class="nb">enable </span>glusterfs-server<span class="p">;</span> systemctl start glusterfs-server </code></pre></div></div> <p>This step isn’t necessary, but I like to verify that</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Verify the gluster service is enabled</span> systemctl is-enabled glusterfs-server <span class="c"># Check the system service status of the gluster-server</span> systemctl status glusterfs-server </code></pre></div></div> <p>If for some reason you haven’t done this yet, each and every node should have it’s own ssh key generated.</p> <p><em class="small">(The only reason I can think of why they wouldn’t have a different key is if a VM was provisioned and than cloned for similar use across a swarm.)</em></p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># This is to generate a very basic SSH key, you may want to specify a key type such as ED25519 or bit length if required.</span> ssh-keygen <span class="nt">-t</span> rsa </code></pre></div></div> <p>Dependant on your Docker Swarm environment and which server you’re running as a manager; you’ll probably want one of the node managers to also be a gluster node manager as well. I’m going to say server <code class="highlighter-rouge">staging1</code> is one of our node managers, so on this server we’re going to probe all other gluster nodes to add them to the gluster pool. (Probing them essentially is saying this manager is telling all servers on this list to connect to each-other.)</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gluster peer probe staging1<span class="p">;</span> gluster peer probe staging2<span class="p">;</span> gluster peer probe staging3<span class="p">;</span> gluster peer probe staging4<span class="p">;</span> gluster peer probe staging5<span class="p">;</span> </code></pre></div></div> <p>It’s not required, but probably good practice to ensure all of the nodes have connected to the pool before setting up the file system.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gluster pool list <span class="c"># =&gt; You should get results similar to the following</span> UUID Hostname State a8136a2b-a2e3-437d-a003-b7516df9520e staging3 Connected 2a2f93f6-782c-11e9-8f9e-2a86e4085a59 staging2 Connected 79cb7ec0-f337-4798-bde9-dbf148f0da3b staging4 Connected 3cfc23e6-782c-11e9-8f9e-2a86e4085a59 staging5 Connected 571bed3f-e4df-4386-bd46-3df6e3e8479f localhost Connected <span class="c"># You can also run the following command to another set of results</span> gluster peer status </code></pre></div></div> <p>Now lets create the gluster data storage directories <em>(<strong>It’s very important you do this on every node.</strong> This is because this directory is where all gluster nodes will store the distributed files locally.)</em></p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo mkdir</span> <span class="nt">-p</span> /gluster/brick </code></pre></div></div> <p>Now lets create a gluster volume across all nodes (again run this on the master node/node manager).</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>gluster volume create staging-gfs replica 5 staging1:/gluster/brick staging2:/gluster/brick staging3:/gluster/brick staging4:/gluster/brick staging5:/gluster/brick force </code></pre></div></div> <p>The next step is to initialize the glusterFS to begin synchronizing across all nodes.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gluster volume start staging-gfs </code></pre></div></div> <p>This step is also not required, but I prefer to verify the gluster volume replicated across all of the designated nodes.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gluster volume info </code></pre></div></div> <p>No let’s ensure we have gluster mount the <code class="highlighter-rouge">/mtn</code> directory for it’s shared directory especially on a reboot. <strong><em>(It’s important to run these commands on all gluster nodes.)</em></strong></p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>umount /mnt <span class="nb">sudo echo</span> <span class="s1">'localhost:/staging-gfs /mnt glusterfs defaults,_netdev,backupvolfile-server=localhost 0 0'</span> <span class="o">&gt;&gt;</span> /etc/fstab <span class="nb">sudo </span>mount.glusterfs localhost:/staging-gfs /mnt <span class="nb">sudo chown</span> <span class="nt">-R</span> root:docker /mnt </code></pre></div></div> <p><em class="small">(You may have noticed the setting of file permissions using <code class="highlighter-rouge">chown -R root:docker</code> this is to ensure docker will have read/write access to the files in the specified directory.)</em></p> <p>If for some reason you’ve already deployed your staging gluster-fs and need to remount the staging-gfs volume you can run the following command. Otherwise you should be able to skip this step.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>umount /mnt<span class="p">;</span> <span class="nb">sudo </span>mount.glusterfs localhost:/staging-gfs /mnt<span class="p">;</span> <span class="nb">sudo chown</span> <span class="nt">-R</span> root:docker /mnt </code></pre></div></div> <p>Let’s list all of our mounted partitions and ensure that the <code class="highlighter-rouge">staging-gfs</code> is listed.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">df</span> <span class="nt">-h</span> <span class="c"># =&gt; staging-gfs should be listed in the partitions/disks listed</span> localhost:/staging-gfs 63G 13G 48G 21% /mnt </code></pre></div></div> <p>Now that all of the work is pretty much done, now comes the fun part lets test to make sure it all works. Lets <code class="highlighter-rouge">cd</code> into the <code class="highlighter-rouge">/mnt</code> directory and create a few files to make sure they will sync across all nodes. <em>(I know this is one of the first things I wanted to try out.)</em> You can do one of the following commands to generate a random file in the <code class="highlighter-rouge">/mnt</code> directory. Now depending on your servers and network connections this should sync up across all nodes almost instantly. The way I tested this I was in the <code class="highlighter-rouge">/mtn</code> directory on several nodes in several terminals. And as soon as I issued the command I was running the <code class="highlighter-rouge">ls</code> command in the other tabs. And depending on the file size, it may not sync across all nodes instantly, but is at least accessible.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># This creates a 24MB file full of zeros</span> <span class="nb">dd </span><span class="k">if</span><span class="o">=</span>/dev/zero <span class="nv">of</span><span class="o">=</span>output.dat <span class="nv">bs</span><span class="o">=</span>24M <span class="nv">count</span><span class="o">=</span>1 <span class="c"># Creates a 2MB file of random characters</span> <span class="nb">dd </span><span class="k">if</span><span class="o">=</span>/dev/urandom <span class="nv">of</span><span class="o">=</span>output.log <span class="nv">bs</span><span class="o">=</span>1M <span class="nv">count</span><span class="o">=</span>2 </code></pre></div></div> <h3 id="using-glusterfs-with-docker">Using GlusterFS with Docker</h3> <p>Now that all the fun stuff is done if you have looked at docker <a href="https://docs.docker.com/storage/volumes/">volumes</a> or <a href="https://docs.docker.com/storage/bind-mounts/">bind</a> mounts this would probably be a good time. Usually docker will store a volumes contents in a folder structure similar to the following: <code class="highlighter-rouge">/var/lib/docker/volumes/DogTracker/_data</code>.</p> <p>But in your <code class="highlighter-rouge">docker-compose.yml</code> or <code class="highlighter-rouge">docker-stack.yml</code> you can specify specific mount points for the docker volumes. If you look at the following <a href="https://en.wikipedia.org/wiki/YAML">YAML</a> snippet you will notice I’m saying to store the containers <code class="highlighter-rouge">/opt/couchdb/data</code> directory on the local mount point <code class="highlighter-rouge">/mnt/staging_couch_db</code>.</p> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">3.7'</span> <span class="na">services</span><span class="pi">:</span> <span class="na">couchdb</span><span class="pi">:</span> <span class="na">image</span><span class="pi">:</span> <span class="s">couchdb:2.3.0</span> <span class="na">volumes</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">bind</span> <span class="na">source</span><span class="pi">:</span> <span class="s">/mnt/staging_couch_db</span> <span class="na">target</span><span class="pi">:</span> <span class="s">/opt/couchdb/data</span> <span class="na">networks</span><span class="pi">:</span> <span class="pi">-</span> <span class="s">internal</span> <span class="na">deploy</span><span class="pi">:</span> <span class="na">resources</span><span class="pi">:</span> <span class="na">limits</span><span class="pi">:</span> <span class="na">cpus</span><span class="pi">:</span> <span class="s1">'</span><span class="s">0.30'</span> <span class="na">memory</span><span class="pi">:</span> <span class="s">512M</span> <span class="na">reservations</span><span class="pi">:</span> <span class="na">cpus</span><span class="pi">:</span> <span class="s1">'</span><span class="s">0.15'</span> <span class="na">memory</span><span class="pi">:</span> <span class="s">256M</span> </code></pre></div></div> <p>Now as we had previously demonstrated any file(s) saved, created, and/or deleted in the <code class="highlighter-rouge">/mtn</code> directory will be synchronized across all of the GlusterFS nodes.</p> <p>I’d just like to mention this may not work for everyone, but this is the method that worked best for use. We’ve been running a number of different Gluster networks for several months now with no issues <em>thus far</em>.</p>BrandonRuby’s Year of Performance (2018)2018-06-04T00:00:00+00:002018-06-04T00:00:00+00:00repo://posts.collection/_posts/2018-06-04-rubys-year-of-performance-2018.md<p>Many people claim Ruby is no longer relevant and quite a few people have moved on to Elixir, Go, Rust, and Node. This is because Ruby was not originally built for speed, it was built for ease of use. It does have its limitations and Rails is a monstrosity with all it’s services, workers, etc. But I’ve never had an issue with this, I started using Ruby because of its ease of use. I can from a world of using PHP and ugly spaghetti code to Ruby where coding is more of a thing of art.</p> <p>But 2018 has been a big year for Ruby releases and trying to meet their <a href="https://blog.heroku.com/ruby-3-by-3">3x3</a> performance goals (<a href="https://developers.redhat.com/blog/2018/03/22/ruby-3x3-performance-goal/">RedHat Writeup</a>). And in the last year alone, <a href="https://twitter.com/tenderlove">Arron Patterson (tenderlove)</a> and various contributors have made amazing advances in improving Ruby’s performance. And the addition of a <a href="https://www.ruby-lang.org/en/news/2018/05/31/ruby-2-6-0-preview2-released/">JIT compiler</a> to Ruby is no ease feat that looks like it will also have a HUGE effect on Ruby to regain some ground and no longer be known as a “slow language”. I admit <a href="https://github.com/oracle/truffleruby">Ruby Truffle</a> has some extreme potential to improve Rubys performance using the <a href="https://www.graalvm.org/">GraalVM</a> but since Oracle does have a tendency to take people to court and suing them, for using their various technological components.</p> <p>Another modification that I have found that dramatically improves performance and reduces memory usage is by adding <a href="http://jemalloc.net/">jemalloc</a>. By default, the MRI version uses the glibc memalloc library.</p> <p>To use jemalloc with ruby lets first install the library, so we can use it when compiling out ruby binaries.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># OSx</span> brew <span class="nb">install </span>jemalloc <span class="c"># Ubuntu/Debian</span> <span class="nb">sudo </span>apt-get update <span class="nb">sudo </span>apt-get <span class="nb">install </span>libjemalloc1 libjemalloc-dev </code></pre></div></div> <p>Many of people prefer <a href="https://github.com/rbenv/ruby-build">ruby-build</a> for compiling new ruby versions but I prefer <a href="https://rvm.io/">RVM</a> because of it’s ease of use. Now to compile with jemmalloc, we need to add the flags for RVM to compile using the jemalloc library.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rvm <span class="nb">install </span>2.5 <span class="nt">-C</span> <span class="nt">--with-jemalloc</span> <span class="nt">--autolibs</span><span class="o">=</span>disable </code></pre></div></div> <p>I tend to use the <a href="https://fishshell.com/">Fish</a> shell, it has dramatically increased my productivity with its ease of use, auto completion libraries, and great features. So to make compiling a new RVM instance easier I created a function titled <code class="highlighter-rouge">rvm_install</code> so now when I want to compile a new Ruby version with the jemalloc flag I issue a command similar to the following <code class="highlighter-rouge">rvm_install 2.6</code> and wait. Below is a copy of the function I created, I know I should probably issue some validation to verify the value of the argument, but I’m the only one using this on my computer and it’s works wonders for what I need it for.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Install the specified Ruby version through RVM, with the jemalloc library included</span> <span class="k">function </span>rvm_install <span class="c"># verify a version number was specified</span> <span class="k">if </span>count <span class="nv">$argv</span> <span class="o">&gt;</span> /dev/null <span class="nb">echo</span> <span class="s2">"Installing Ruby-</span><span class="nv">$argv</span><span class="s2"> with jemalloc"</span> rvm <span class="nb">install</span> <span class="nv">$argv</span> <span class="nt">-C</span> <span class="nt">--with-jemalloc</span> <span class="k">else </span><span class="nb">echo</span> <span class="s2">"Please specify a version to install."</span> end end </code></pre></div></div> <p>Now lets take a look at a few Ruby versions to test how well they perform with and without jemalloc. Sam Saffron’s <a href="https://github.com/SamSaffron/allocator_bench/blob/master/stress_mem.rb">stress test</a> is a great way to compare performance gains and memory allocation. And let me reiterate that I didn’t just run this test a single time and compare the results. These use the averages after running each stress test several times.</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Results for Ruby_v2.5.0p0</span> <span class="no">Duration</span><span class="p">:</span> <span class="mf">9.542045</span> <span class="no">RSS</span><span class="p">:</span> <span class="mi">137860</span> <span class="c1"># Ruby_v2.5.0p0 with jemmalloc</span> <span class="no">Duration</span><span class="p">:</span> <span class="mf">7.420393</span> <span class="no">RSS</span><span class="p">:</span> <span class="mi">129616</span> <span class="no">Faster</span> <span class="n">w</span><span class="o">/</span><span class="no">Jemalloc</span><span class="p">:</span> <span class="mf">0.222347725251767</span> <span class="o">==&gt;</span> <span class="mi">22</span><span class="o">%</span> <span class="n">faster</span> <span class="c1"># Ruby_v2.6.0-preview2 with jemalloc</span> <span class="no">Duration</span><span class="p">:</span> <span class="mf">6.743956</span> <span class="no">RSS</span><span class="p">:</span> <span class="mi">144108</span> <span class="c1"># Faster than 2.5</span> <span class="mf">0.09115918792980371</span> <span class="o">==&gt;</span> <span class="mi">9</span><span class="o">%</span> <span class="n">faster</span> </code></pre></div></div> <p>As you can see, just adding jemalloc to MRI ruby adds quite a noticeable performance gain. And even when not using a different memory allocator, each new Ruby release has had quite a significant impact on building on the languages potential.</p> <p>~ <strong>NOTE:</strong> These tests are performed on a MBP with OSx 10.12, a 2.2ghz i7, 16GB of RAM, and a SSD. So the results may vary from device to device.</p>BrandonMikroTik - Mobile Rate-Limiting2018-05-05T00:00:00+00:002018-05-05T00:00:00+00:00repo://posts.collection/_posts/2018-05-05-mikrotik---mobile-rate-limiting.md<p><img src="/images/posts/mikrotik.png" alt="" class="img-fluid w-3/12" /></p> <h3 id="a-brief-intro">A Brief Intro</h3> <p>I’ve been using Mikrotik routers for about a year now and I’ve had nothing but an amazing experience thus far. I haven’t taken any of the <a href="https://mikrotik.com/training/">certification</a> training courses (MTCNA, MTCRE, MTCWE) for the Mikrotik routers and thus far all my learning has been hands more of a hands on experience and following the RouterOS <a href="https://wiki.mikrotik.com/wiki/Manual:TOC">documentation</a>.</p> <p>The main reason I got into was Mikrotik routers, was when I started at my current position a little over a year go I was expected to “make some magic happen”. You see I work a nonprofit that occupies several buildings all interconnected, with the internet piped through a fiber line. I knew there was issues after my first couple of days, because they were using a consumer grade NetGear (<a href="https://www.netgear.com/landings/ad7200/">NightHawk x10</a>) router to try and support everything. There’s generally anywhere between 30-70 people in the building at any time; and considering everyone has a laptop or 2, a VoIP Phone, a cellphone, some have PC’s, and some people even have various smart devices connected as well. This was by far, way to many devices for the basic hone grade router to manage. And the network congestion was terrible; calls were always dropping, active IP addresses were being reassigned, your Upload and Download rates were absolutely terrible.</p> <p>I has never seen anything like it and I was definitely out of my scope of knowledge. I’m a web developer, not a networking administrator. But this issue with working at a nonprofit if options are limited and as the IT guy I’m expected to solve a large amount of issues at any given time. To help resolve this issue I contacted an acquaintance of mine manages the networking infrastructure of a chain of convenience stores, rest stores, and office buildings within the area.</p> <p>He recommended I use MikroTik routers because their cheap, highly efficient, easy to learn, and you don’t have to pay any crazy licensing fees. So my first introduction to the router was jumping straight into using out of the <a href="https://mikrotik.com/product/CCR1016-12G">CCR1016-12G</a> Cloud-Core Router. And I’ve have had nothing but excellent results with it, thus far.</p> <hr /> <h3 id="now-to-the-issue">Now To the Issue</h3> <p>Across the facility here are is a large number of devices assigned static IP addresses; ie. printers, VoIP phones, and several special purpose devices. Now we always have people coming from all over for meetings, training, consultation, etc. So in any one day you may have a few hundred devices connect to the network. And having a huge wireless network with a plethora of devices always connecting to the network can be a nightmare. To ensure a quality network experience, I reduced DHCP lifespans to 10 minutes this removes devices from the DHCP table after a short amount of time. I also setup a rollover subnet, so when the basic subnet chain was full it starts assigning IP addresses to a secondary subnet. I also have a default rate-limit (Queues) for when a new device connects to the network.</p> <p>Now one of the biggest issues we have is people using a ton of bandwidth on social media. Part of the problem is; we have several people who do marketing, advertising, and outreach across various social media mediums including but not limited to facebook, twitter, youtube, and few others. But we all known, people like to stream videos, baseball games, concerts, and tons of high bandwidth streams with their phones when visiting these sites.</p> <p>To counter this, I decided it’d be a good idea to specifically rate-limit/set Queue speeds for mobile devices. This isn’t a fool proof method, but it does tend to catch about 99% of all mobile devices that connect to the network. It compares the devices $hostname to a regular expression list of mobile device manufacturers. To setup up the regular expression you goto: IP&gt;Firewall&gt; [Tab] layer7 Protocols, you’ll than create a new Firewall L7 protocol and label it <code class="highlighter-rouge">mobileDevices</code> with the following regex.</p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">^</span><span class="p">.</span><span class="o">*</span><span class="p">(</span><span class="nx">android</span><span class="o">|</span><span class="nx">ANDROID</span><span class="o">|</span><span class="nx">AppleWatch</span><span class="o">|</span><span class="nx">BLACKBERRY</span><span class="o">|</span><span class="nx">Galaxy</span><span class="o">|</span><span class="nx">HTC</span><span class="o">|</span><span class="nx">Huaweu</span><span class="o">|</span><span class="nx">iPhone</span><span class="o">|</span><span class="nx">iPhne</span><span class="o">|</span><span class="nx">Moto</span><span class="o">|</span><span class="nx">SAMSUNG</span><span class="o">|</span><span class="nx">Xperia</span><span class="p">).</span><span class="o">*</span><span class="nx">$</span> </code></pre></div></div> <p>You’ll now create a scheduler by going to System&gt;Scheduler and clicking the blue plus button. I went conservatively assigned it to loop through every 5 minutes, if you’re in a pretty busy office I’d say you may even want to do 2 minute intervals. I’d also say part of this script is unnecessary, but I decided to reassign the queue limits to non mobile devices just for secondary measures. And the second loop in the script is because I have various VIP devices that need higher bandwidth limits than the rest of the network. So they are specifically assigned static IP address with their queue limits. Like I said, about half this script isn’t necessary, but I implemented it in just to take precautions.</p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">/</span><span class="nx">queue</span> <span class="nx">simple</span> <span class="nx">remove</span> <span class="p">[</span><span class="sr">/queue simple find</span><span class="err">] </span><span class="p">:</span><span class="nb">global</span> <span class="nx">layer7</span> <span class="p">[</span><span class="sr">/ ip firewall layer7-protocol find name="mobileDevices"</span><span class="err">] </span><span class="p">:</span><span class="nb">global</span> <span class="nx">mobileDevices</span> <span class="p">[</span><span class="sr">/ ip firewall layer7-protocol get $layer7 regexp</span><span class="err">] </span><span class="p">:</span><span class="nb">global</span> <span class="nx">mobileLimit</span> <span class="dl">"</span><span class="s2">1024k/1024k</span><span class="dl">"</span> <span class="p">:</span><span class="nb">global</span> <span class="nx">pcLimit</span> <span class="dl">"</span><span class="s2">3M/5M</span><span class="dl">"</span> <span class="c1">// if the specified device's IP address is being assigned with DHCP</span> <span class="p">:</span><span class="nx">foreach</span> <span class="nx">i</span> <span class="k">in</span><span class="o">=</span><span class="p">[</span><span class="sr">/ip dhcp-server lease find dynamic=yes] do=</span><span class="err">{ </span> <span class="p">:</span><span class="nx">local</span> <span class="nx">ipAddr</span> <span class="p">[</span><span class="sr">/ip dhcp-server lease get $i address]</span><span class="err">; </span> <span class="p">:</span><span class="nx">local</span> <span class="nx">hostname</span> <span class="p">[</span><span class="sr">/ip dhcp-server lease get $i host-name]</span><span class="err">; </span> <span class="p">:</span><span class="nx">local</span> <span class="nx">macAddress</span> <span class="p">[</span><span class="sr">/ip dhcp-server lease get $i mac-address</span><span class="err">] </span> <span class="p">:</span><span class="nx">local</span> <span class="nx">queueName</span> <span class="dl">"</span><span class="s2">Client - $macAddress</span><span class="dl">"</span> <span class="p">:</span><span class="k">if</span> <span class="p">(</span><span class="nx">$hostname</span> <span class="o">~</span> <span class="nx">$mobileDevices</span><span class="o">=</span> <span class="kc">true</span><span class="p">)</span> <span class="k">do</span><span class="o">=</span><span class="p">{</span> <span class="c1">// if the device has been found to be a mobile device, reduce it's bandwidth - $mobileLimit</span> <span class="o">/</span><span class="nx">queue</span> <span class="nx">simple</span> <span class="nx">add</span> <span class="nx">name</span><span class="o">=</span><span class="dl">"</span><span class="s2">$queueName</span><span class="dl">"</span> <span class="nx">comment</span><span class="o">=</span><span class="dl">"</span><span class="s2">$hostname</span><span class="dl">"</span> <span class="nx">target</span><span class="o">=</span><span class="dl">"</span><span class="s2">$ipAddr</span><span class="dl">"</span> <span class="nx">max</span><span class="o">-</span><span class="nx">limit</span><span class="o">=</span><span class="dl">"</span><span class="s2">$mobileLimit</span><span class="dl">"</span> <span class="p">}</span> <span class="k">else</span><span class="o">=</span><span class="p">{</span> <span class="c1">// otherwise set the devices bandwidth limits to the default bandwidth limits - $pcLimit</span> <span class="o">/</span><span class="nx">queue</span> <span class="nx">simple</span> <span class="nx">add</span> <span class="nx">name</span><span class="o">=</span><span class="dl">"</span><span class="s2">$queueName</span><span class="dl">"</span> <span class="nx">comment</span><span class="o">=</span><span class="dl">"</span><span class="s2">$hostname</span><span class="dl">"</span> <span class="nx">target</span><span class="o">=</span><span class="dl">"</span><span class="s2">$ipAddr</span><span class="dl">"</span> <span class="nx">max</span><span class="o">-</span><span class="nx">limit</span><span class="o">=</span><span class="dl">"</span><span class="s2">$pcLimit</span><span class="dl">"</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">// If device is connected with a static IP address or not using DHCP to assign it's IP</span> <span class="p">:</span><span class="nx">foreach</span> <span class="nx">i</span> <span class="k">in</span><span class="o">=</span><span class="p">[</span><span class="sr">/ip dhcp-server lease find dynamic=no] do=</span><span class="err">{ </span> <span class="p">:</span><span class="nx">local</span> <span class="nx">ipAddr</span> <span class="p">[</span><span class="sr">/ip dhcp-server lease get $i address]</span><span class="err">; </span> <span class="p">:</span><span class="nx">local</span> <span class="nx">hostname</span> <span class="p">[</span><span class="sr">/ip dhcp-server lease get $i host-name]</span><span class="err">; </span> <span class="p">:</span><span class="nx">local</span> <span class="nx">macAddress</span> <span class="p">[</span><span class="sr">/ip dhcp-server lease get $i mac-address</span><span class="err">] </span> <span class="p">:</span><span class="nx">local</span> <span class="nx">queueName</span> <span class="dl">"</span><span class="s2">Client - $macAddress</span><span class="dl">"</span> <span class="p">:</span><span class="nx">local</span> <span class="nx">vipLimit</span> <span class="dl">"</span><span class="s2">10M/10M</span><span class="dl">"</span> <span class="c1">// hostnames for VIP devices in which to have a high bandwidth limit - $vipLimit</span> <span class="p">:</span><span class="k">if</span> <span class="p">(</span><span class="nx">$hostname</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">VIPdesktops</span><span class="dl">"</span> <span class="o">||</span> <span class="nx">$hostname</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">VIPlaptops</span><span class="dl">"</span> <span class="o">||</span> <span class="nx">$hostname</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">VIPdevice</span><span class="dl">"</span><span class="p">)</span> <span class="k">do</span><span class="o">=</span><span class="p">{</span> <span class="o">/</span><span class="nx">queue</span> <span class="nx">simple</span> <span class="nx">add</span> <span class="nx">name</span><span class="o">=</span><span class="dl">"</span><span class="s2">$queueName</span><span class="dl">"</span> <span class="nx">comment</span><span class="o">=</span><span class="dl">"</span><span class="s2">$hostname</span><span class="dl">"</span> <span class="nx">target</span><span class="o">=</span><span class="dl">"</span><span class="s2">$ipAddr</span><span class="dl">"</span> <span class="nx">max</span><span class="o">-</span><span class="nx">limit</span><span class="o">=</span><span class="dl">"</span><span class="s2">$vipLimit</span><span class="dl">"</span> <span class="p">}</span> <span class="k">else</span><span class="o">=</span><span class="p">{</span> <span class="c1">// otherwise set the devices bandwidth limits to the default bandwidth limits - $pcLimit</span> <span class="o">/</span><span class="nx">queue</span> <span class="nx">simple</span> <span class="nx">add</span> <span class="nx">name</span><span class="o">=</span><span class="dl">"</span><span class="s2">$queueName</span><span class="dl">"</span> <span class="nx">comment</span><span class="o">=</span><span class="dl">"</span><span class="s2">$hostname</span><span class="dl">"</span> <span class="nx">target</span><span class="o">=</span><span class="dl">"</span><span class="s2">$ipAddr</span><span class="dl">"</span> <span class="nx">max</span><span class="o">-</span><span class="nx">limit</span><span class="o">=</span><span class="dl">"</span><span class="s2">$pcLimit</span><span class="dl">"</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>This isn’t a fool proof method, but it does catch the vast majority of mobile devices. This is because by default their devices are labeled with their specific manufacturer as part of the device name. And for the most rarely does anyone ever rename their devices hostname. While watching the network traffic, I can say I’ve only saw a handful of mobile devices that haven’t been labeled with either Samsung, Iphone, or Galaxy.</p> <p>~ <strong>Note:</strong> This may not be the best or the most effective script for what I wanted to achieve, but it accomplished what I needed to do. And it’s been tested and proven to work effectively for exactly what I needed.</p> <h5 id="updated-53118">UPDATED: <small class="text-muted">5/31/18</small></h5> <p>After updating our Routers’ packages and routerboad to v6.42.3, I begun having issues with the script showing above. So I removed the Layer7 and revamped the script to use it hostname matches through a variable string. The new revision appears to run a bit faster than the previous version and in my opinion, it seems to be a bit easier to read.</p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">/</span><span class="nx">queue</span> <span class="nx">simple</span> <span class="nx">remove</span> <span class="p">[</span><span class="sr">/queue simple find</span><span class="err">] </span><span class="p">:</span><span class="nb">global</span> <span class="nx">mobileDevices</span> <span class="dl">"</span><span class="s2">android|ANDROID|AppleWatch|BLACKBERRY|Galaxy|HTC|Huawei|iPad|iPhone|iphone|iPhne|Moto|SAMSUNG|Unknown|Xperia</span><span class="dl">"</span> <span class="p">:</span><span class="nb">global</span> <span class="nx">mobileLimit</span> <span class="dl">"</span><span class="s2">1024k/1024k</span><span class="dl">"</span> <span class="p">:</span><span class="nb">global</span> <span class="nx">pcLimit</span> <span class="dl">"</span><span class="s2">2M/5M</span><span class="dl">"</span> <span class="p">:</span><span class="nb">global</span> <span class="nx">vipDevices</span> <span class="dl">"</span><span class="s2">VIPdesktops|VIPlaptops|VIPdevice|VIPservers|MikroTik|CapAC</span><span class="dl">"</span> <span class="p">:</span><span class="nb">global</span> <span class="nx">vipLimit</span> <span class="dl">"</span><span class="s2">10M/10M</span><span class="dl">"</span> <span class="o">/</span><span class="nx">ip</span> <span class="nx">dhcp</span><span class="o">-</span><span class="nx">server</span> <span class="nx">lease</span> <span class="p">{</span> <span class="p">:</span><span class="nx">foreach</span> <span class="nx">i</span> <span class="k">in</span><span class="o">=</span><span class="p">[</span><span class="nx">find</span> <span class="p">(</span><span class="nx">dynamic</span> <span class="o">&amp;&amp;</span> <span class="nx">status</span><span class="o">=</span><span class="dl">"</span><span class="s2">bound</span><span class="dl">"</span><span class="p">)]</span> <span class="k">do</span><span class="o">=</span><span class="p">{</span> <span class="p">:</span><span class="nx">local</span> <span class="nx">activeAddress</span> <span class="p">[</span><span class="kd">get</span> <span class="nx">$i</span> <span class="nx">active</span><span class="o">-</span><span class="nx">address</span><span class="p">]</span> <span class="p">:</span><span class="nx">local</span> <span class="nx">activeMacAddress</span> <span class="p">[</span><span class="kd">get</span> <span class="nx">$i</span> <span class="nx">active</span><span class="o">-</span><span class="nx">mac</span><span class="o">-</span><span class="nx">address</span><span class="p">]</span> <span class="p">:</span><span class="nx">local</span> <span class="nx">macAddress</span> <span class="p">[</span><span class="kd">get</span> <span class="nx">$i</span> <span class="nx">mac</span><span class="o">-</span><span class="nx">address</span><span class="p">]</span> <span class="p">:</span><span class="nx">local</span> <span class="nx">hostname</span> <span class="p">[</span><span class="kd">get</span> <span class="nx">$i</span> <span class="nx">host</span><span class="o">-</span><span class="nx">name</span><span class="p">]</span> <span class="p">:</span><span class="nx">local</span> <span class="nx">ipAddr</span> <span class="p">[</span><span class="kd">get</span> <span class="nx">$i</span> <span class="nx">address</span><span class="p">]</span> <span class="p">:</span><span class="nx">local</span> <span class="nx">queueName</span> <span class="dl">"</span><span class="s2">Client - $macAddress</span><span class="dl">"</span> <span class="p">:</span><span class="k">if</span> <span class="p">(</span><span class="nx">$hostname</span> <span class="o">~</span> <span class="nx">$mobileDevices</span><span class="o">=</span> <span class="kc">true</span><span class="p">)</span> <span class="k">do</span><span class="o">=</span><span class="p">{</span> <span class="o">/</span><span class="nx">queue</span> <span class="nx">simple</span> <span class="nx">add</span> <span class="nx">name</span><span class="o">=</span><span class="dl">"</span><span class="s2">$queueName</span><span class="dl">"</span> <span class="nx">comment</span><span class="o">=</span><span class="dl">"</span><span class="s2">$hostname</span><span class="dl">"</span> <span class="nx">target</span><span class="o">=</span><span class="dl">"</span><span class="s2">$ipAddr</span><span class="dl">"</span> <span class="nx">max</span><span class="o">-</span><span class="nx">limit</span><span class="o">=</span><span class="dl">"</span><span class="s2">$mobileLimit</span><span class="dl">"</span> <span class="p">}</span> <span class="k">else</span><span class="o">=</span><span class="p">{</span> <span class="o">/</span><span class="nx">queue</span> <span class="nx">simple</span> <span class="nx">add</span> <span class="nx">name</span><span class="o">=</span><span class="dl">"</span><span class="s2">$queueName</span><span class="dl">"</span> <span class="nx">comment</span><span class="o">=</span><span class="dl">"</span><span class="s2">$hostname</span><span class="dl">"</span> <span class="nx">target</span><span class="o">=</span><span class="dl">"</span><span class="s2">$ipAddr</span><span class="dl">"</span> <span class="nx">max</span><span class="o">-</span><span class="nx">limit</span><span class="o">=</span><span class="dl">"</span><span class="s2">$pcLimit</span><span class="dl">"</span> <span class="p">}</span> <span class="p">}</span> <span class="p">:</span><span class="nx">foreach</span> <span class="nx">i</span> <span class="k">in</span><span class="o">=</span><span class="p">[</span><span class="nx">find</span> <span class="p">(</span><span class="o">!</span><span class="nx">dynamic</span> <span class="o">&amp;&amp;</span> <span class="nx">status</span><span class="o">=</span><span class="dl">"</span><span class="s2">bound</span><span class="dl">"</span><span class="p">)]</span> <span class="k">do</span><span class="o">=</span><span class="p">{</span> <span class="p">:</span><span class="nx">local</span> <span class="nx">activeAddress</span> <span class="p">[</span><span class="kd">get</span> <span class="nx">$i</span> <span class="nx">active</span><span class="o">-</span><span class="nx">address</span><span class="p">]</span> <span class="p">:</span><span class="nx">local</span> <span class="nx">activeMacAddress</span> <span class="p">[</span><span class="kd">get</span> <span class="nx">$i</span> <span class="nx">active</span><span class="o">-</span><span class="nx">mac</span><span class="o">-</span><span class="nx">address</span><span class="p">]</span> <span class="p">:</span><span class="nx">local</span> <span class="nx">macAddress</span> <span class="p">[</span><span class="kd">get</span> <span class="nx">$i</span> <span class="nx">mac</span><span class="o">-</span><span class="nx">address</span><span class="p">]</span> <span class="p">:</span><span class="nx">local</span> <span class="nx">hostname</span> <span class="p">[</span><span class="kd">get</span> <span class="nx">$i</span> <span class="nx">host</span><span class="o">-</span><span class="nx">name</span><span class="p">]</span> <span class="p">:</span><span class="nx">local</span> <span class="nx">ipAddr</span> <span class="p">[</span><span class="kd">get</span> <span class="nx">$i</span> <span class="nx">address</span><span class="p">]</span> <span class="p">:</span><span class="nx">local</span> <span class="nx">queueName</span> <span class="dl">"</span><span class="s2">Client - $macAddress</span><span class="dl">"</span> <span class="p">:</span><span class="k">if</span> <span class="p">(</span><span class="nx">$hostname</span> <span class="o">~</span> <span class="nx">$vipDevices</span><span class="o">=</span> <span class="kc">true</span><span class="p">)</span> <span class="k">do</span><span class="o">=</span><span class="p">{</span> <span class="o">/</span><span class="nx">queue</span> <span class="nx">simple</span> <span class="nx">add</span> <span class="nx">name</span><span class="o">=</span><span class="dl">"</span><span class="s2">$queueName</span><span class="dl">"</span> <span class="nx">comment</span><span class="o">=</span><span class="dl">"</span><span class="s2">$hostname</span><span class="dl">"</span> <span class="nx">target</span><span class="o">=</span><span class="dl">"</span><span class="s2">$ipAddr</span><span class="dl">"</span> <span class="nx">max</span><span class="o">-</span><span class="nx">limit</span><span class="o">=</span><span class="dl">"</span><span class="s2">$vipLimit</span><span class="dl">"</span> <span class="p">}</span> <span class="k">else</span><span class="o">=</span><span class="p">{</span> <span class="o">/</span><span class="nx">queue</span> <span class="nx">simple</span> <span class="nx">add</span> <span class="nx">name</span><span class="o">=</span><span class="dl">"</span><span class="s2">$queueName</span><span class="dl">"</span> <span class="nx">comment</span><span class="o">=</span><span class="dl">"</span><span class="s2">$hostname</span><span class="dl">"</span> <span class="nx">target</span><span class="o">=</span><span class="dl">"</span><span class="s2">$ipAddr</span><span class="dl">"</span> <span class="nx">max</span><span class="o">-</span><span class="nx">limit</span><span class="o">=</span><span class="dl">"</span><span class="s2">$pcLimit</span><span class="dl">"</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div>Brandon2017 - Was a Year of Change2018-03-01T00:00:00+00:002018-03-01T00:00:00+00:00repo://posts.collection/_posts/2018-03-01-2017---a-year-of-change.md<p>Let me start off by saying, it’s been one heck of a year and so much has changed in my life, it’s almost unrecognizable. To start, I had a few career changes; I went from doing IT support for a car rental agency to being laid off. Being laid off was quite an uncomfortable experience, because I have kids and a family to support. While waiting to get hired I used this downtime as a learning experience and worked on various projects. At least, until I got some contract work. At first, it was a dream come true, working as a developer, getting paid for what I loved. There were several very smart developers in which I was working with and learning from.</p> <p>That was until the owner slowly started cutting everyone’s checks back and saying “Oh sorry I’ll add it onto your next check.” And then after a few missed and partial payments, he completely disappeared. Or at least stopped all contact with my team. One-by-one, we were all removed from the various SasS services in which we had used. We were removed from the GitHub repos, time-trackers, invoicing, etc. Luckily I still have a local copy of the git repo, if I ever have to prove any of my work.</p> <p>From here I was kind of desperate for work and took the first thing that came along. For several months, I actually worked for a local locksmith and it was quite the learning experience. Don’t get my wrong; I respect the hell out of any locksmith out there it just wasn’t for me.</p> <p>But I lucked out and for the past year or, so I’ve been the Web Developer/IT Coordinator for a pretty sizable Non-Profit within my area. For the most part of really enjoy my job, but like any job some days it can be quite trying. This is because some days I’m doing data entry and adding info to one of the databases or helping an older lady re-sync her email, or do a sum formula in excel. And other days I’m mounting a new rack router, patch panel, and running Cat6 through one of our several buildings. And sometimes I’m working on one of the several websites our organization operates. I’m always writing code to assist with certain tasks, it may go unnoticed. But I think of it as; if it gets noticed then I did something wrong.</p> <p>One of my biggest accomplishments thus far has to be the upgrade of the organizations networking. First of all, I’m not a networking/telecommunications expert, but I know when your not using the right equipment. When I first started they had networking equipment that would have been great for a home based setup. But when you have 70+ users at any given time with multiple devices hooked up to the network. Each person has the following VoIP Phone, Laptop, and usually their cellphones as well. This just don’t work well with a low quality home router. Their network was always congested, phone calls were choppy, their DHCP server was only allowing a range of 70 devices so every few minutes several peoples phones, laptops, or other devices were getting kicked from the network.</p> <p>I came in and pulled out several of the little crappy routers and added a few centralized switches. And hooked everything into a <a href="https://mikrotik.com/">MikroTik</a> Cloud-Core Router. I’ve spent the last several months learning the ins, outs, and little gotchas of this machine (and far from being an expert). At first, it was <em>extremely</em> intimidating, but the more I play with it; the more I’ve come to love and appreciate it. I’m absolutely locked on using MikroTik routers now, they’re great for home, business, or whatever needs you may require. Heck they even have some amazing Hotspots you can manage through the routers as well. One of my other favorite things about the MikroTik Routers’, is unlike your regular home routers you can build scripts, schedules, or filters to do whatever you absolutely want with your device.</p> <p>My accomplishments aren’t limited to just networking, since working at my current position I’ve helped build several websites. Most of them are for various causes in which we are associated with and others are to promote our various programs, or generate revenue for the organization. We’ve got an Outreach/Media guy and a Public Relations associate. But seeing as how I’m really the only one who does any web development/programming everything that’s been put on my plate, has been a pick-and-choose opportunity. Some projects are exciting are quite exiting to work on. But occasionally I’ve had more than a full plate and had to pass on them.</p> <p>One of the great things about not being limited to a specific technology by the organization is it has allowed me to pickup and learn various other technologies. Over the last several months I’ve picked up and learned <a href="https://vuejs.org/">VueJS</a>, which is an absolutely amazing JavaScript framework. I’ve never been a heavy JavaScript user, more like I had been disgusted with the state of JavaScript. jQuery made the world a mess and old snippet sites such as <a href="http://dynamicdrive.com/">Dynamic Drive</a> are out of touch with modern needs. But <a href="https://babeljs.io/learn-es2015/">ES6</a> combined with <a href="https://reactjs.org/">React</a>, <a href="https://angular.io/">Angular</a>, <a href="https://vuejs.org/">Vue</a>, and <a href="https://www.emberjs.com/">Ember</a> have completely changed the dynamic of modern web applications. With the introduction to VueJS my love for frontend development has returned and I’ve been jumping at various projects to expand on my knowledge, experience, and to try new and exciting things.</p>BrandonMapping an ArcGIS Parcel dataset as a Mapbox tileset2016-12-15T00:00:00+00:002016-12-15T00:00:00+00:00repo://posts.collection/_posts/2016-12-15-mapping-an-arcgis-parcel-dataset-as-a-mapbox-tileset.md<p>Before I get begin on how I mapped out several large datasets for <a href="http://sanjuanmaps.com/">SanJuanMaps</a>, I would like to say that there was plenty of <a href="https://www.mapbox.com/gallery/">inspiration</a> behind my actions. I was especially inspired by <a href="https://twitter.com/caged">Justin Palmer’s</a> - <a href="http://labratrevenge.com/pdx/#12/45.4800/-122.6706">The Age of a City</a>. Where he tastefully mapped out all the building ages of Portland, Oregon. But what sets my county maps apart from various other is that I didn’t just map out buildings, people, or objects. I mapped out several sets of data including: building age, building zoning types, current active well sites, and recent crimes.</p> <h2 id="loading-the-parcel-map">Loading the Parcel Map</h2> <p>Depending on your city, state, or county it may be a struggle struggle to get a hold of your local GIS information. But for me, I was able to find my counties GIS information for all buildings (not located on federal and/or local reservation land). It took jumping through a few hoops in order to make the data loadable and usable for processing.</p> <p>To begin San Juan County currently allows you to download a 240MB zip file, thats supposed to contain all GIS information through out the area. This zip file contains numerous files, but the most important one is a <a href="http://www.dbase.com/">dBase</a> file <code class="highlighter-rouge">*.dbf</code>. This database table is linked to several ArcGis index files <code class="highlighter-rouge">*.atx</code>, meaning that each database column has a unique atx file (phys_address, ownerName, ect). Now that we have access to the GIS dataset, there are a few ways we can access the data in which in contains. Now let me ask; who wants to pay <a href="http://www.arcgis.com/features/plans/pricing.html">hundreds</a> of dollars to access a GIS file that’ll we’ll be only using a handful of times? Don’t get me wrong, ArcGIS is one hell of a product and if you plan on working with GIS information often or with large datasets it’s worth it. But since this is a one in a blue moon type thing for me I’ll stick with using <a href="http://www.qgis.org/">QGIS</a>, which is an Open-Source GIS projection application. In order to get the application running there are several components that we’ll require, but in the long run it’s well worth.</p> <p>Now lets= begin with starting up QGIS and opening up the <code class="highlighter-rouge">.dbx</code> file as stated before. Since this is a pretty significant file with a ton of datapoints and components, it will take a few moments for everything to load. Once the parcel project loads, you may be faced with a very intricate map that looks similar to CAD wireframe. Your on the right path, lets slow down for a moment. Now an easy way to think of it, is to consider these map components as vector points similar to using the Pen Tool to build shapes in Adobe Illustrator.</p> <p><img src="/images/posts/mapping_arcgis/gis_grid500.jpg" alt="GIS Grid" class="img-fluid center-block" /></p> <p>Except this GIS map doesn’t just include points to create a layout of buildings (these is quite a bit of meta-data included with each component on the map). If you zoom in and switch to the Identify Features/vector information tool and click individual plots or buildings you’ll notice it allows you to view information for each parcel. Which consists of tons of information used by the county to identify the area (PARCELNO, GrossAcres, PhysAddr, ACCTTYPE, etc.). Now this doesn’t seem like it’ll be anything important, but when we convert the dBase table to a PostGIS table each one of these attributes will be used as a column to identify each and every building throughout the county.</p> <h2 id="understanding-coordinate-reference-systems-and-projections">Understanding Coordinate Reference Systems and Projections</h2> <p><em>~ <strong>Note:</strong> This section is not require to read, but helps with understanding why we need to perform a CRS conversion.</em></p> <p>Before we get started on converting the parcel dataset to a database table, lets talk about <a href="https://docs.qgis.org/2.8/en/docs/gentle_gis_introduction/coordinate_reference_systems.html">Coordinate Reference Systems (CRS)</a>. Wait, why aren’t we just using latitude/longitude? With different maps, we use different coordinate systems. Geographic Coordinate Systems use latitude/longitude and Projected Coordinate Systems use points (X and Y) that originate at a specified lat/long. Think of Projected Coordinate Systems as a window frame, it’s got its one size, area, and dimensions. But no matter how you look at it, all these glass panes end up going together and making one big window.</p> <p>Lets try to look at why there have been several methods developed to map the Earth’s surface (<a href="https://en.wikipedia.org/wiki/Mercator_projection">Mercator</a>, <a href="https://en.wikipedia.org/wiki/Sinusoidal_projection">Flamsteed-Sinusoidal</a>, <a href="https://en.wikipedia.org/wiki/Cylindrical_equal-area_projection">Equal area</a>, <a href="https://en.wikipedia.org/wiki/Azimuthal_equidistant_projection">Equidistant</a>, <a href="https://en.wikipedia.org/wiki/Albers_projection">Albers</a>, <a href="https://en.wikipedia.org/wiki/Lambert_projection">Lambert</a>, and <a href="http://webhelp.esri.com/arcgisdesktop/9.2/index.cfm?TopicName=List_of_supported_map_projections">many more</a>). Now I know what your thinking, “What are all these different projections for? And why would I care?” Throughout the years <a href="https://en.wikipedia.org/wiki/List_of_cartographers">cartographers</a> have experimented with creating projections in which they thought was best for mapping out land, water, cities, and the various features of our planet. And many of them were quite accurate for their time, while others have been slightly <a href="http://www.livescience.com/14754-ingenious-flat-earth-theory-revealed-map.html">narrow minded</a> at depicting the earth.</p> <p>As we can see, it’s no easy task depicting a spherical planet and all of it’s features as a flat surface. This topographical projection types are called an <a href="https://en.wikipedia.org/wiki/Earth_ellipsoid">ellipsoid</a>. No matter what sort of map is used, they have always been an important asset for traveling, freight, military operations, and space travel. Some of these projections are better at depicting land forms, street planning, distances, or scale and accuracy but each one had its place and time. Now the point is, it <a href="http://www.directionsmag.com/site/latlong-converter/">doesn’t matter</a> if your using degrees (25 is degrees, 35 is minutes, and 22.3 is seconds), meters, decimal degrees (25.58952777777778), or whatever plot point method you <em>should</em> always going to end up at approximately the same place.</p> <p>So <a href="https://www.maptoaster.com/maptoaster-topo-nz/articles/projection/datum-projection.html">what is a datum</a>? In my opinion the easiest way to explain a <a href="http://oceanservice.noaa.gov/facts/datum.html">datum</a> is by stating it is a standard or method for mapping geographic coordinates to a projection. A datum can consist of various datasets or an individual GIS vector, but generally consists of a large set of data points to be mapped out (roads, buildings, elevation changes, land formations, water, etc).</p> <p>Mapping with GIS can be a really complicated task, but with the right tools it can make things quite a bit easier.</p> <h2 id="lets-convert-it-to-usable-data">Let’s convert it to usable data</h2> <p>The ArcGIS data in which we obtained through the county is geo-formatted using the <a href="https://en.wikipedia.org/wiki/GRS_80">GRS:80</a> reference system. QGIS will display this and various other attributes when loading up the vector map projection. When looking at the data you should see something similar to the following attributes upon loading up the map.</p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">USER</span><span class="p">:</span><span class="mi">1000001</span> <span class="p">(</span><span class="o">*</span> <span class="nx">Generated</span> <span class="nx">CRS</span> <span class="p">(</span><span class="o">+</span><span class="nx">proj</span><span class="o">=</span><span class="nx">tmerc</span> <span class="o">+</span><span class="nx">lat_0</span><span class="o">=</span><span class="mi">31</span> <span class="o">+</span><span class="nx">lon_0</span><span class="o">=-</span><span class="mf">107.8333333333333</span> <span class="o">+</span><span class="nx">k</span><span class="o">=</span><span class="mf">0.9999166666666667</span> <span class="o">+</span><span class="nx">x_0</span><span class="o">=</span><span class="mf">829999.9999999998</span> <span class="o">+</span><span class="nx">y_0</span><span class="o">=</span><span class="mi">0</span> <span class="o">+</span><span class="nx">ellps</span><span class="o">=</span><span class="nx">GRS80</span> <span class="o">+</span><span class="nx">towgs84</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span> <span class="o">+</span><span class="nx">units</span><span class="o">=</span><span class="nx">us</span><span class="o">-</span><span class="nx">ft</span> <span class="o">+</span><span class="nx">no_defs</span><span class="p">))</span> </code></pre></div></div> <p>We are now very much on our way to having a data projection of usable data for mapping out the county. The only issue we are going to have at this point is the majority of online mapping tools use the <a href="https://confluence.qps.nl/pages/viewpage.action?pageId=29855173">EPSG:4326/WGS84</a> standard for projecting data. This can easy be achieved by reprojecting the layer with WGS84 coordinates. <strong>Go To:</strong> <code class="highlighter-rouge">Menu -&gt; Processing -&gt; Toolbox -&gt; QGIS geoalgorithms -&gt; Vector General Tools -&gt; Reproject Layer</code>. Another method is if you have the Toolbox side already open you can just search for the tool <code class="highlighter-rouge">Reproject Layer</code></p> <p><img src="/images/posts/mapping_arcgis/reproject_layer.png" alt="Reprojected Layer" class="img-fluid center-block" /></p> <p>You than set the “Target CRS” as EPSG:4326/WGS84 in the dropdown menu, but we warned depending on your computer this may take a while. Because some of the structures have numerous repetitive points, I had to reduce the point precision in order to keep my computer from freezing up when I would reproject the layer. This can be be found in the toolbox under <code class="highlighter-rouge">Grass -&gt; Vector -&gt; v.clean</code>. Be warned if you do need to use this feature, I suggest that you use it sparingly because it can and will readjust the boundaries of various structures.</p> <p>Now lets save the data as a <a href="https://en.wikipedia.org/wiki/Well-known_text">WTK</a> CSV file, to make it easier to load all the counties properties and their attributes into a database. To do this, rather than saving the project you right-click the Layer and Save-As. From here make sure you need to change the CRS to <code class="highlighter-rouge">Default CRS (EPSG:4326 - WGS 84)</code> and that the format is <code class="highlighter-rouge">CSV</code>. The next important thing is to force the Geomerty to be expost as <code class="highlighter-rouge">AS_WKT</code> and the CSV seperator is <code class="highlighter-rouge">COMMA</code>. Now saving may take a while, because there’s quite a few properties that need to have its properties converted to csv.</p> <p>Many people may not realize it, but CSV files aren’t just large datafiles that you can open up in excel. If you think about, it’s pretty much a plain text database file. It’s a pretty efficient way for accessing data but it can be bulking and slow depending on your editor. With 81000 or so rows, it locks up and freezes <a href="https://atom.io/">Atom</a> and it even slows down <a href="https://www.sublimetext.com/3">Sublime</a> significantly, with 16gb of RAM. But we’re not going to be editing them in an IDE, we’ll be loading them in <a href="https://postgresapp.com/">Postgres</a>. This because Postgres supports <a href="http://postgis.net/">GIS</a> data structures and is very efficient at processing large amounts of data.</p> <p>Before we import the data into Postgres, we need to insure we have proper table structure. And at the moment, one of my favorite tools for building database into out of CSV files by using <a href="https://csvkit.readthedocs.io/en/0.9.1/index.html">csvkit</a>. If you’ve never used it, it lets you print column names, convert CSV to JSON, reorder columns, import to SQL, and much more.</p> <p>Now normally we’d just attempt to build the database scheme with the following command.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>csvsql <span class="nt">-i</span> postgresql Buildings.csv </code></pre></div></div> <p>But since there are so many rows, the python script will probably freeze up or start consuming a HUGE amount of memory. So in order to make it more efficient, we’ll get the first 20 rows of data. And using command line piping we’ll send the data to the csv tool for processing.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">head</span> <span class="nt">-n</span> 20 Buildings.csv | csvsql <span class="nt">--no-constraints</span> <span class="nt">--table</span> buildings </code></pre></div></div> <p>Depending on the information available, your SQL output should be something similar to the following. Several of these columns we’ll never even use, but for now it’s better to not chance corrupting any of our fields. Before you go any farther be sure to change the geometry field names to geom and if CSVKIT labels geom as a VARCHAR for the geometry property type.</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="nv">"buildings"</span> <span class="p">(</span> <span class="n">geom</span> <span class="n">geometry</span><span class="p">,</span> <span class="n">ogc_fid</span> <span class="nb">INTEGER</span><span class="p">,</span> <span class="n">parcelno</span> <span class="nb">BIGINT</span><span class="p">,</span> <span class="n">accountno</span> <span class="nb">VARCHAR</span><span class="p">,</span> <span class="n">book</span> <span class="nb">INTEGER</span><span class="p">,</span> <span class="n">page</span> <span class="nb">INTEGER</span><span class="p">,</span> <span class="n">grossacres</span> <span class="nb">FLOAT</span><span class="p">,</span> <span class="n">accttype</span> <span class="nb">VARCHAR</span><span class="p">,</span> <span class="n">ownername</span> <span class="nb">VARCHAR</span><span class="p">,</span> <span class="n">ownername2</span> <span class="nb">VARCHAR</span><span class="p">,</span> <span class="n">owneraddr</span> <span class="nb">VARCHAR</span><span class="p">,</span> <span class="n">ownctystzp</span> <span class="nb">VARCHAR</span><span class="p">,</span> <span class="n">subdivis</span> <span class="nb">VARCHAR</span><span class="p">,</span> <span class="n">legaldesc</span> <span class="nb">VARCHAR</span><span class="p">,</span> <span class="n">legaldesc2</span> <span class="nb">VARCHAR</span><span class="p">,</span> <span class="n">weblink</span> <span class="nb">VARCHAR</span><span class="p">,</span> <span class="n">physaddr</span> <span class="nb">VARCHAR</span><span class="p">,</span> <span class="n">physcity</span> <span class="nb">VARCHAR</span><span class="p">,</span> <span class="n">physzip</span> <span class="nb">VARCHAR</span> <span class="p">);</span> </code></pre></div></div> <p><strong>NOTE</strong>: Depending on the GIS software you used, I had various issues with the CSV file that I needed to correct before importing it. The issues that we’ll encounter involve the geometry field. Since CSV fields are separated by commas Postgres tries to import the polygons coordinates as separate columns as well.</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">POLYGON</span> <span class="p">((</span><span class="o">-</span><span class="mi">108</span><span class="p">.</span><span class="mi">195455993822</span> <span class="mi">36</span><span class="p">.</span><span class="mi">979941009114</span><span class="p">)),</span><span class="mi">1</span><span class="p">,</span><span class="mi">2075189351198</span><span class="p">,</span><span class="n">R0051867</span><span class="p">,,,</span><span class="mi">57</span><span class="p">.</span><span class="mi">06</span><span class="p">,</span><span class="n">EXEMPT</span><span class="p">,</span><span class="n">UNITED</span> <span class="n">STATES</span> <span class="k">OF</span> <span class="n">AMERICA</span> <span class="n">US</span> <span class="n">DEPT</span> <span class="k">OF</span> <span class="n">INTE</span><span class="p">,,</span><span class="mi">6251</span> <span class="n">COLLEGE</span> <span class="n">BLVD</span> <span class="n">STE</span> <span class="n">A</span><span class="p">,</span><span class="nv">"FARMINGTON, NM 87402"</span><span class="p">,,</span><span class="n">A</span> <span class="n">TRACT</span> <span class="k">OF</span> <span class="n">LAND</span> <span class="k">IN</span> <span class="n">THE</span> <span class="n">SESW</span> <span class="k">AND</span> <span class="n">NESW</span> <span class="k">AND</span> <span class="n">NWSE</span> <span class="k">OF</span> <span class="mi">153213</span> <span class="n">DESCRIBED</span> <span class="k">ON</span> <span class="n">PERM</span> <span class="n">CARD</span> <span class="n">BK</span><span class="p">.</span><span class="mi">1172</span> <span class="n">PG</span><span class="p">.</span><span class="mi">996</span><span class="p">,,</span><span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="n">propery</span><span class="p">.</span><span class="n">url</span><span class="p">,</span><span class="n">NM</span> <span class="mi">170</span><span class="p">,</span><span class="n">LA_PLATA</span><span class="p">,</span> </code></pre></div></div> <p>In order to fix this, we need to quote the geometry column in order to make it a field of it’s own. With Sublime’s find/replace REGEX functionality this would be very straight forward step. In order to find the rows with the issue, I used the following <code class="highlighter-rouge">^(?!"POLYGON)</code>. Out of 81000 or so columns, there were only a few hundred rows with this issue that needed to be changed.</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">"POLYGON ((-108.195455993822 36.979941009114,-108.195717330387 36.987213809214,-108.19557299488 36.987214765187))"</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">2075189351198</span><span class="p">,</span><span class="n">R0051867</span><span class="p">,,,</span><span class="mi">57</span><span class="p">.</span><span class="mi">06</span><span class="p">,</span><span class="n">EXEMPT</span><span class="p">,</span><span class="n">UNITED</span> <span class="n">STATES</span> <span class="k">OF</span> <span class="n">AMERICA</span> <span class="n">US</span> <span class="n">DEPT</span> <span class="k">OF</span> <span class="n">INTE</span><span class="p">,,</span><span class="mi">6251</span> <span class="n">COLLEGE</span> <span class="n">BLVD</span> <span class="n">STE</span> <span class="n">A</span><span class="p">,</span><span class="nv">"FARMINGTON, NM 87402"</span><span class="p">,,</span><span class="n">A</span> <span class="n">TRACT</span> <span class="k">OF</span> <span class="n">LAND</span> <span class="k">IN</span> <span class="n">THE</span> <span class="n">SESW</span> <span class="k">AND</span> <span class="n">NESW</span> <span class="k">AND</span> <span class="n">NWSE</span> <span class="k">OF</span> <span class="mi">153213</span> <span class="n">DESCRIBED</span> <span class="k">ON</span> <span class="n">PERM</span> <span class="n">CARD</span> <span class="n">BK</span><span class="p">.</span><span class="mi">1172</span> <span class="n">PG</span><span class="p">.</span><span class="mi">996</span><span class="p">,,</span><span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="n">propery</span><span class="p">.</span><span class="n">url</span><span class="p">,</span><span class="n">NM</span> <span class="mi">170</span><span class="p">,</span><span class="n">LA_PLATA</span><span class="p">,</span> </code></pre></div></div> <h3 id="importing-the-data-into-postgres">Importing the data into Postgres</h3> <p>Now before we import the dataset, lets enable the <a href="http://postgis.net/">PostGIS</a> extension so the database can process the buildings vectors properly.</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="n">EXTENSION</span> <span class="n">postgis</span><span class="p">;</span> </code></pre></div></div> <p>Now depending on how your managing your database, you either import the CSV file through pgAdmin or through the command line. I chose the the command line, because of its speed and convenience.</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">copy</span> <span class="n">buildings</span> <span class="k">FROM</span> <span class="s1">'/Users/Tarellel/Desktop/SJC_GIS/Exported/Buildings.csv'</span> <span class="k">DELIMITER</span> <span class="s1">','</span> <span class="n">CSV</span> <span class="n">HEADER</span><span class="p">;</span> </code></pre></div></div> <p>Compared to loading data in IDE’s and/or Excel, Postgres is extremely fast at accessing, modifying, and deleting rows, columns, and fields. Depending on what data you plan on mapping you may not need to make any additional changes. I’m going to be mapping out all building ages in the county, so I needed to add the <code class="highlighter-rouge">built_in</code> column to the table, for the year in which the structure was built.</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">ALTER</span> <span class="k">TABLE</span> <span class="n">buildings</span> <span class="k">ADD</span> <span class="n">built_in</span> <span class="nb">integer</span><span class="p">;</span> </code></pre></div></div> <p>If you’re like me, you probably already started doing queries to verify the integrity of the information imported. Something you may notice is that the structures geometry field no longer looks like <code class="highlighter-rouge">"POLYGON ((-108.195455993822 36.979941009114,-108.195717330387 36.987213809214,-108.19557299488 36.987214765187))"</code>. That is because PostGIS stores the locations as a binary specification, but when queried the information is viewed as a hex-encoded string. This makes it easier for PostGIS to store and project the data as various data projection formats.</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">geom</span> <span class="k">FROM</span> <span class="n">buildings</span> <span class="k">LIMIT</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">--------------------</span> <span class="mi">01030000000100000005000000</span><span class="n">F9A232DD00065BC034E0D442D7594240F0567E50FB055BC094B9B760D55942409E97C29CFE055BC0CAC4E0D8BB594240080B801404065BC0440327CEBD594240F9A232DD00065BC034E0D442D7594240</span> <span class="p">(</span><span class="mi">1</span> <span class="k">row</span><span class="p">)</span> <span class="c1">--------------------</span> </code></pre></div></div> <h3 id="lets-fetch-the-buildproperties-initial-build-date">Lets fetch the build/properties initial build date</h3> <p>In order to get each and every buildings initial build date, I ended up using nokogiri to scan the county assessors property listings for their initial build dates. Don’t get me wrong, I tried to look for an easy way to get the information. But they have no publicly accessible API for pulling data requests and never received a response from anyone I attempted to contact. So, I used the next best thing, farmed the counties web site for property listing information.</p> <p><em>For warning,</em> This script was build to be quick and simple solution, rather than being well formatted and following best practices.</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s1">'pg'</span> <span class="nb">require</span> <span class="s1">'open-uri'</span> <span class="nb">require</span> <span class="s1">'nokogiri'</span> <span class="nb">require</span> <span class="s1">'restclient'</span> <span class="c1"># Fetch current buildings across the County and determine their build date</span> <span class="k">class</span> <span class="nc">FetchYear</span> <span class="vi">@conn</span> <span class="o">=</span> <span class="kp">nil</span> <span class="c1">###</span> <span class="c1"># Setup initial required variables, for processing all properties</span> <span class="c1"># ----------</span> <span class="c1"># A connection to the database is required for request and updates</span> <span class="k">def</span> <span class="nf">initialize</span> <span class="vi">@conn</span> <span class="o">=</span> <span class="no">PG</span><span class="p">.</span><span class="nf">connect</span><span class="p">(</span><span class="ss">dbname: </span><span class="s1">'SJC_Properties'</span><span class="p">)</span> <span class="vi">@base_link</span> <span class="o">=</span> <span class="s1">'http://county_assessor.url'</span> <span class="k">end</span> <span class="c1">###</span> <span class="c1"># Fetches the next X(number) of buildings</span> <span class="c1"># all selects require that built_in year field to be empty, to prevent an infinite loop</span> <span class="k">def</span> <span class="nf">fetch_buildings</span> <span class="vi">@conn</span><span class="p">.</span><span class="nf">exec</span><span class="p">(</span><span class="s1">'SELECT * FROM buildings WHERE weblink IS NOT NULL AND built_in IS NULL AND built_in != 0 LIMIT 50'</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">results</span><span class="o">|</span> <span class="c1"># if any results are found, begin processing them to get the properties link/year build</span> <span class="k">if</span> <span class="n">results</span><span class="p">.</span><span class="nf">any?</span> <span class="n">results</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">result</span><span class="o">|</span> <span class="c1"># for some reason, some buildings are empty on most fields</span> <span class="c1"># if no weblink is supplied, skip it</span> <span class="c1"># ie: "parcelno"=&gt;"2063170454509"</span> <span class="k">next</span> <span class="k">if</span> <span class="n">result</span><span class="p">[</span><span class="s1">'weblink'</span><span class="p">].</span><span class="nf">nil?</span> <span class="n">link</span><span class="p">,</span> <span class="n">built_in</span> <span class="o">=</span> <span class="s1">''</span> <span class="n">link</span> <span class="o">=</span> <span class="n">fetch_weblink</span><span class="p">(</span><span class="n">result</span><span class="p">)</span> <span class="n">link</span> <span class="o">||=</span> <span class="s1">''</span> <span class="c1"># Attempt to fetch the properties build_date</span> <span class="c1"># Note: some properties are EXEMPT and/or Vacacent</span> <span class="c1"># so no built_date will be specified</span> <span class="n">built_in</span> <span class="o">=</span> <span class="p">(</span><span class="o">!</span><span class="n">link</span><span class="p">.</span><span class="nf">empty?</span> <span class="p">?</span> <span class="n">fetch_yearbuilt</span><span class="p">(</span><span class="n">link</span><span class="p">)</span> <span class="p">:</span> <span class="mi">0</span><span class="p">)</span> <span class="n">update_build_year</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="n">built_in</span><span class="p">)</span> <span class="k">end</span> <span class="k">else</span> <span class="nb">puts</span> <span class="s1">'It appears all database rows have been processed'</span> <span class="nb">abort</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> <span class="kp">private</span> <span class="c1"># Private: Fetches the properties unique information URI from the requested</span> <span class="c1"># SJC assessors page</span> <span class="c1"># ----------------------------------------</span> <span class="c1"># building - all attributes for the current building/residence being processed</span> <span class="c1"># Returns: Returns the properties unique information UIR</span> <span class="k">def</span> <span class="nf">fetch_weblink</span><span class="p">(</span><span class="n">building</span><span class="p">)</span> <span class="n">link</span> <span class="o">=</span> <span class="p">[]</span> <span class="c1"># If it processes successfully attempt to load the Redidental/Commercial page</span> <span class="c1"># Other output the error</span> <span class="k">begin</span> <span class="c1"># Page required the visitor be logged in as a guest, with a GET form request</span> <span class="n">page</span> <span class="o">=</span> <span class="no">Nokogiri</span><span class="o">::</span><span class="no">HTML</span><span class="p">(</span> <span class="no">RestClient</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="n">building</span><span class="p">[</span><span class="s1">'weblink'</span><span class="p">],</span> <span class="ss">cookies: </span><span class="p">{</span> <span class="ss">isLoggedInAsPublic: </span><span class="s1">'true'</span> <span class="p">}</span> <span class="p">)</span> <span class="p">)</span> <span class="c1"># Each property contains a unique URL similar to:</span> <span class="c1"># http://county_assessor.url/account.jsp?accountNum=R0070358&amp;doc=R0070358.COMMERCIAL1.1456528185534</span> <span class="c1"># Winthin the left sidebar there is a link table that contains the accttype</span> <span class="c1"># and based on the accttype/property-type each link will be different</span> <span class="k">case</span><span class="p">(</span><span class="n">building</span><span class="p">[</span><span class="s1">'accttype'</span><span class="p">])</span> <span class="c1"># Select the property when it is a class of Residential</span> <span class="k">when</span> <span class="s1">'RESIDENTIAL'</span><span class="p">,</span> <span class="s1">'MULTI_FAMILY'</span><span class="p">,</span> <span class="s1">'RES_MIX'</span> <span class="n">link</span> <span class="o">=</span> <span class="n">page</span><span class="p">.</span><span class="nf">css</span><span class="p">(</span><span class="s1">'#left'</span><span class="p">).</span><span class="nf">xpath</span><span class="p">(</span><span class="s1">'//a[contains(text(), "Residential")]'</span><span class="p">).</span><span class="nf">css</span><span class="p">(</span><span class="s1">'a[href]'</span><span class="p">)</span> <span class="c1"># Process Commercial based Properties</span> <span class="k">when</span> <span class="s1">'COMMERCIAL'</span><span class="p">,</span> <span class="s1">'COMM_MIX'</span><span class="p">,</span> <span class="s1">'MH_ON_PERM'</span><span class="p">,</span> <span class="s1">'PARTIAL_EXEMPT'</span> <span class="n">link</span> <span class="o">=</span> <span class="n">page</span><span class="p">.</span><span class="nf">css</span><span class="p">(</span><span class="s1">'#left'</span><span class="p">).</span><span class="nf">xpath</span><span class="p">(</span><span class="s1">'//a[contains(text(), "Commercial/Ag")]'</span><span class="p">).</span><span class="nf">css</span><span class="p">(</span><span class="s1">'a[href]'</span><span class="p">)</span> <span class="c1"># Some Vacant and EXEMPT properties only have land listed with no building data available</span> <span class="c1"># When this is the case or default to no type, return a 0.</span> <span class="c1"># Because no building info is currently available</span> <span class="k">when</span> <span class="s1">'EXEMPT'</span><span class="p">,</span> <span class="s1">'VACANT_LAND'</span><span class="p">,</span> <span class="s1">'AGRICULTURAL'</span> <span class="k">else</span> <span class="k">return</span> <span class="s1">''</span> <span class="k">end</span> <span class="c1"># Some properties have several links, pull the first/original building info</span> <span class="n">first_link</span><span class="p">(</span><span class="n">link</span><span class="p">)</span> <span class="k">if</span> <span class="o">!</span><span class="n">link</span><span class="p">.</span><span class="nf">empty?</span> <span class="k">rescue</span> <span class="no">OpenURI</span><span class="o">::</span><span class="no">HTTPError</span> <span class="o">=&gt;</span> <span class="n">e</span> <span class="k">if</span> <span class="n">e</span><span class="p">.</span><span class="nf">message</span> <span class="o">==</span> <span class="s1">'404 Not Found'</span> <span class="nb">puts</span> <span class="s1">'Page not found'</span> <span class="k">else</span> <span class="nb">puts</span> <span class="s1">'Error loading this page'</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">fetch_yearbuilt</span><span class="p">(</span><span class="n">link</span><span class="o">=</span><span class="s1">''</span><span class="p">)</span> <span class="k">return</span> <span class="s1">''</span> <span class="k">if</span> <span class="n">link</span><span class="p">.</span><span class="nf">empty?</span> <span class="k">begin</span> <span class="c1"># Load the URL for the buildings summary</span> <span class="n">summary</span> <span class="o">=</span> <span class="no">Nokogiri</span><span class="o">::</span><span class="no">HTML</span><span class="p">(</span> <span class="no">RestClient</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span> <span class="n">link</span><span class="p">,</span> <span class="ss">cookies: </span><span class="p">{</span> <span class="ss">isLoggedInAsPublic: </span><span class="s1">'true'</span> <span class="p">}</span> <span class="p">)</span> <span class="p">)</span> <span class="n">yearbuilt</span> <span class="o">=</span> <span class="n">summary</span><span class="p">.</span><span class="nf">css</span><span class="p">(</span><span class="s1">'tr'</span><span class="p">).</span><span class="nf">xpath</span><span class="p">(</span><span class="s1">'//span[contains(text(), "Actual Year Built")]'</span><span class="p">).</span><span class="nf">first</span><span class="p">.</span><span class="nf">parent</span><span class="p">.</span><span class="nf">search</span><span class="p">(</span><span class="s1">'span'</span><span class="p">).</span><span class="nf">last</span><span class="p">.</span><span class="nf">content</span> <span class="c1"># year must be stripped and turned into an into an integer</span> <span class="c1"># because it trails with an invisible space '&amp;nbsp;'</span> <span class="n">yearbuilt</span><span class="p">.</span><span class="nf">strip</span><span class="p">.</span><span class="nf">to_i</span> <span class="k">rescue</span> <span class="no">OpenURI</span><span class="o">::</span><span class="no">HTTPError</span> <span class="o">=&gt;</span> <span class="n">e</span> <span class="nb">puts</span> <span class="s1">'--------------------'</span> <span class="nb">puts</span> <span class="s1">'Error loading property summary page'</span> <span class="nb">puts</span> <span class="s2">"URL: </span><span class="si">#{</span><span class="n">link</span><span class="si">}</span><span class="s2">"</span> <span class="nb">puts</span> <span class="s1">'--------------------'</span> <span class="k">end</span> <span class="k">end</span> <span class="c1">###</span> <span class="c1"># Process the current link:</span> <span class="c1"># ----------</span> <span class="c1"># some properties have their attctype listed several times causing errors when processing</span> <span class="c1"># this is because of upgrades or additions added to the current property</span> <span class="c1"># Returns the first link for the property array</span> <span class="k">def</span> <span class="nf">first_link</span><span class="p">(</span><span class="n">link</span><span class="p">)</span> <span class="n">link</span><span class="p">.</span><span class="nf">length</span> <span class="o">&lt;=</span> <span class="mi">1</span> <span class="p">?</span> <span class="vi">@base_link</span> <span class="o">+</span> <span class="n">link</span><span class="p">.</span><span class="nf">attribute</span><span class="p">(</span><span class="s1">'href'</span><span class="p">).</span><span class="nf">to_s</span> <span class="p">:</span> <span class="vi">@base_link</span> <span class="o">+</span> <span class="n">link</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">attribute</span><span class="p">(</span><span class="s1">'href'</span><span class="p">).</span><span class="nf">to_s</span> <span class="k">end</span> <span class="c1">###</span> <span class="c1"># Update the database record with the year it was built</span> <span class="c1"># ----------</span> <span class="c1"># record (hash): The current record/addr in which to update</span> <span class="c1"># year (int): This will be the year/value that needs to be updated in the record</span> <span class="k">def</span> <span class="nf">update_build_year</span><span class="p">(</span><span class="n">record</span><span class="p">,</span> <span class="n">year</span><span class="o">=</span><span class="s1">'0'</span><span class="p">)</span> <span class="nb">puts</span> <span class="s2">"</span><span class="si">#{</span><span class="n">record</span><span class="p">[</span><span class="s1">'accountno'</span><span class="p">]</span><span class="si">}</span><span class="s2"> &gt;/ </span><span class="si">#{</span><span class="n">record</span><span class="p">[</span><span class="s1">'physaddr'</span><span class="p">]</span><span class="si">}</span><span class="s2"> - </span><span class="si">#{</span><span class="n">year</span><span class="si">}</span><span class="s2">"</span> <span class="vi">@conn</span><span class="p">.</span><span class="nf">exec</span><span class="p">(</span><span class="s2">"UPDATE buildings SET built_in=</span><span class="si">#{</span><span class="n">year</span><span class="si">}</span><span class="s2"> WHERE accountno='</span><span class="si">#{</span><span class="n">record</span><span class="p">[</span><span class="s1">'accountno'</span><span class="p">]</span><span class="si">}</span><span class="s2">' AND physaddr='</span><span class="si">#{</span><span class="n">record</span><span class="p">[</span><span class="s1">'physaddr'</span><span class="p">]</span><span class="si">}</span><span class="s2">'"</span><span class="p">)</span> <span class="k">end</span> <span class="k">end</span> <span class="n">fetch</span> <span class="o">=</span> <span class="no">FetchYear</span><span class="p">.</span><span class="nf">new</span> <span class="mi">500</span><span class="p">.</span><span class="nf">times</span> <span class="k">do</span> <span class="o">|</span><span class="n">x</span><span class="o">|</span> <span class="n">fetch</span><span class="p">.</span><span class="nf">fetch_buildings</span> <span class="k">end</span> </code></pre></div></div> <p>I ran this script over night and came back with a fully populated database and ready for to be fully utilized. Depending on the server load, each request usually took a few seconds for each building/property. But one thing that really surprised me, was the counties server had no rate-limiting setup (at least non that I ever experiences). So I was getting results as fast as my script could run and connect. I’m sure the next morning it might of looked like a small DDOS attack by a script kiddy with thousands of page loads from a single source. But I tried to minimize the effect by doing all my data retrieval in the middle of the night, when it would have as little effect as possible.</p> <h3 id="how-the-script-works">How the Script Works</h3> <p>Now, before you start scratching your head thinking “what the hell?” let me explain. Each and every property has a unique weblink associated with the count assessors office. When you access the page a cookie <code class="highlighter-rouge">isLoggedInAsPublic</code> is set to true, using a get request. I believe this is used in order to prevent general web scraping, because if the cookie isn’t set when the page is loaded redirected to a user login.</p> <p>I know it looks like a mess and over complicated by let me explain a few things. But some of the properties are owned by the same owner so we can’t exactly relay on the <code class="highlighter-rouge">accountno</code> to update properties. And we can’t use <code class="highlighter-rouge">physaddr</code> because some properties have more than one building on them, so we rely on updating several properties.</p> <p>And what about the EXEMPT and Vacant properties? Well the majority of them consists of Churches, Post Offices, Government Buildings, the Airport, and unclassified BLM land. And yes there are quite a few in the generate area. This is the surrounding area is heavily funded by the government, we’re also a city bordering the edge of several reservations.</p> <h3 id="lets-convert-it-to-a-geojson-file-for-vectoring">Lets convert it to a GeoJson file for vectoring</h3> <p>For those of you who have never used used it, Postgres has amazing support for exporting data as <a href="http://www.json.org/">json</a>. Some people seem to be unaware that you can resolve to exporting your database queries in different data formats. And some databases (such as Postgres) also allow you to export your database query returns to various file formats.</p> <p>I can assure you, I didn’t get this right on the first even second try. I’ll honestly admit it took me a few hours to build a query that use the columns I required and would export as a valid <a href="http://geojson.org/">GeoJson</a> format. I’d say I probably rebuild the query maybe 20 times, each one being completely different each and everyone. But in the end, I ended up with a swatch of subqueries and assignments that ended exporting the data extremely fast and well structured. As you can see below, the query may look like a complicated mess. The funny thing is, it’s a heck of a lot simpler than some of the other query selections I ended up trying to come up with.</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">COPY</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="n">row_to_json</span><span class="p">(</span><span class="n">collection</span><span class="p">)</span> <span class="k">FROM</span><span class="p">(</span> <span class="k">SELECT</span> <span class="s1">'FeatureCollection'</span> <span class="k">AS</span> <span class="k">type</span><span class="p">,</span> <span class="n">array_to_json</span><span class="p">(</span><span class="n">array_agg</span><span class="p">(</span><span class="n">feature</span><span class="p">))</span> <span class="k">AS</span> <span class="n">features</span> <span class="k">FROM</span><span class="p">(</span> <span class="k">SELECT</span> <span class="s1">'Feature'</span> <span class="k">AS</span> <span class="k">type</span><span class="p">,</span> <span class="n">row_to_json</span><span class="p">(</span> <span class="p">(</span><span class="k">SELECT</span> <span class="n">l</span> <span class="k">FROM</span> <span class="p">(</span><span class="k">SELECT</span> <span class="n">initcap</span><span class="p">(</span><span class="n">accttype</span><span class="p">)</span> <span class="k">AS</span> <span class="k">type</span><span class="p">,</span> <span class="n">built_in</span><span class="p">)</span> <span class="k">AS</span> <span class="n">l</span><span class="p">)</span> <span class="p">)</span> <span class="k">AS</span> <span class="n">properties</span><span class="p">,</span> <span class="n">ST_AsGeoJSON</span><span class="p">(</span><span class="n">lg</span><span class="p">.</span><span class="n">geom</span><span class="p">)::</span><span class="n">json</span> <span class="k">AS</span> <span class="n">geometry</span> <span class="k">FROM</span> <span class="n">buildings</span> <span class="k">AS</span> <span class="n">lg</span> <span class="k">WHERE</span> <span class="n">geom</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="p">)</span> <span class="k">AS</span> <span class="n">feature</span> <span class="p">)</span> <span class="k">AS</span> <span class="n">collection</span> <span class="p">)</span> <span class="k">TO</span> <span class="s1">'/Users/Tarellel/Desktop/buildings.json'</span><span class="p">;</span> </code></pre></div></div> <p>Just a heads up you may be expecting a nice and beautiful json structure similar to the following.</p> <p><small>Example from <em><a href="http://geojson.org/geojson-spec.html">geojson-spec data</a></em></small></p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"FeatureCollection"</span><span class="p">,</span><span class="w"> </span><span class="nl">"features"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Feature"</span><span class="p">,</span><span class="w"> </span><span class="nl">"geometry"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Point"</span><span class="p">,</span><span class="w"> </span><span class="nl">"coordinates"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">102.0</span><span class="p">,</span><span class="w"> </span><span class="mf">0.5</span><span class="p">]},</span><span class="w"> </span><span class="nl">"properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"prop0"</span><span class="p">:</span><span class="w"> </span><span class="s2">"value0"</span><span class="p">}</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Feature"</span><span class="p">,</span><span class="w"> </span><span class="nl">"geometry"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LineString"</span><span class="p">,</span><span class="w"> </span><span class="nl">"coordinates"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">[</span><span class="mf">102.0</span><span class="p">,</span><span class="w"> </span><span class="mf">0.0</span><span class="p">],</span><span class="w"> </span><span class="p">[</span><span class="mf">103.0</span><span class="p">,</span><span class="w"> </span><span class="mf">1.0</span><span class="p">],</span><span class="w"> </span><span class="p">[</span><span class="mf">104.0</span><span class="p">,</span><span class="w"> </span><span class="mf">0.0</span><span class="p">],</span><span class="w"> </span><span class="p">[</span><span class="mf">105.0</span><span class="p">,</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="nl">"properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"prop0"</span><span class="p">:</span><span class="w"> </span><span class="s2">"value0"</span><span class="p">,</span><span class="w"> </span><span class="nl">"prop1"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Feature"</span><span class="p">,</span><span class="w"> </span><span class="nl">"geometry"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Polygon"</span><span class="p">,</span><span class="w"> </span><span class="nl">"coordinates"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">[</span><span class="mf">100.0</span><span class="p">,</span><span class="w"> </span><span class="mf">0.0</span><span class="p">],</span><span class="w"> </span><span class="p">[</span><span class="mf">101.0</span><span class="p">,</span><span class="w"> </span><span class="mf">0.0</span><span class="p">],</span><span class="w"> </span><span class="p">[</span><span class="mf">101.0</span><span class="p">,</span><span class="w"> </span><span class="mf">1.0</span><span class="p">],</span><span class="w"> </span><span class="p">[</span><span class="mf">100.0</span><span class="p">,</span><span class="w"> </span><span class="mf">1.0</span><span class="p">],</span><span class="w"> </span><span class="p">[</span><span class="mf">100.0</span><span class="p">,</span><span class="w"> </span><span class="mf">0.0</span><span class="p">]</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="nl">"properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"prop0"</span><span class="p">:</span><span class="w"> </span><span class="s2">"value0"</span><span class="p">,</span><span class="w"> </span><span class="nl">"prop1"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"this"</span><span class="p">:</span><span class="w"> </span><span class="s2">"that"</span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>The query I used ends exporting the data more like a crunched dataset. Which is great because it dramatically reduces filesize and is a must when optimizing for loading on webpages. Plus no matter how you use it, it’s not like whatever processor your using is really going to worry about how beautiful your file is, as long as the data is valid.</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="nl">"type"</span><span class="p">:</span><span class="s2">"FeatureCollection"</span><span class="p">,</span><span class="nl">"features"</span><span class="p">:[{</span><span class="nl">"type"</span><span class="p">:</span><span class="s2">"Feature"</span><span class="p">,</span><span class="nl">"geometry"</span><span class="p">:{</span><span class="nl">"type"</span><span class="p">:</span><span class="s2">"Point"</span><span class="p">,</span><span class="nl">"coordinates"</span><span class="p">:[</span><span class="mi">102</span><span class="p">,</span><span class="mf">0.5</span><span class="p">]},</span><span class="nl">"properties"</span><span class="p">:{</span><span class="nl">"prop0"</span><span class="p">:</span><span class="s2">"value0"</span><span class="p">}},{</span><span class="nl">"type"</span><span class="p">:</span><span class="s2">"Feature"</span><span class="p">,</span><span class="nl">"geometry"</span><span class="p">:{</span><span class="nl">"type"</span><span class="p">:</span><span class="s2">"LineString"</span><span class="p">,</span><span class="nl">"coordinates"</span><span class="p">:[[</span><span class="mi">102</span><span class="p">,</span><span class="mi">0</span><span class="p">],[</span><span class="mi">103</span><span class="p">,</span><span class="mi">1</span><span class="p">],[</span><span class="mi">104</span><span class="p">,</span><span class="mi">0</span><span class="p">],[</span><span class="mi">105</span><span class="p">,</span><span class="mi">1</span><span class="p">]]},</span><span class="nl">"properties"</span><span class="p">:{</span><span class="nl">"prop0"</span><span class="p">:</span><span class="s2">"value0"</span><span class="p">,</span><span class="nl">"prop1"</span><span class="p">:</span><span class="mi">0</span><span class="p">}},{</span><span class="nl">"type"</span><span class="p">:</span><span class="s2">"Feature"</span><span class="p">,</span><span class="nl">"geometry"</span><span class="p">:{</span><span class="nl">"type"</span><span class="p">:</span><span class="s2">"Polygon"</span><span class="p">,</span><span class="nl">"coordinates"</span><span class="p">:[[[</span><span class="mi">100</span><span class="p">,</span><span class="mi">0</span><span class="p">],[</span><span class="mi">101</span><span class="p">,</span><span class="mi">0</span><span class="p">],[</span><span class="mi">101</span><span class="p">,</span><span class="mi">1</span><span class="p">],[</span><span class="mi">100</span><span class="p">,</span><span class="mi">1</span><span class="p">],[</span><span class="mi">100</span><span class="p">,</span><span class="mi">0</span><span class="p">]]]},</span><span class="nl">"properties"</span><span class="p">:{</span><span class="nl">"prop0"</span><span class="p">:</span><span class="s2">"value0"</span><span class="p">,</span><span class="nl">"prop1"</span><span class="p">:{</span><span class="nl">"this"</span><span class="p">:</span><span class="s2">"that"</span><span class="p">}}}]}</span><span class="w"> </span></code></pre></div></div> <p>Now that you’ve exported the dataset as a GeoJson file, it’s all downhill from here. You’ll just need to use the Mapbox <a href="https://www.mapbox.com/api-documentation/#uploads">upload API</a> to upload the file. From here you can edit and <a href="https://www.mapbox.com/help/getting-started-studio-datasets/">style</a> the data and vector points the way in which you see fit.</p> <hr /> <h3 id="references">References</h3> <ul> <li>Coordinate Reference Systems <ul> <li><a href="https://www.nceas.ucsb.edu/~frazier/RSpatialGuides/OverviewCoordinateReferenceSystems.pdf">Overview of Coordinate Reference Systems</a></li> <li><a href="http://docs.qgis.org/2.0/en/docs/gentle_gis_introduction/coordinate_reference_systems.html">Understanding of Coordinate Reference Systems</a></li> <li><a href="https://www.nceas.ucsb.edu/~frazier/RSpatialGuides/OverviewCoordinateReferenceSystems.pdf">Overview of Coordinate Reference Systems</a></li> <li><a href="http://www.ogcnetwork.net/node/338">Latitude and longitude coordinates are not unique</a></li> <li><a href="http://gis.stackexchange.com/questions/23690/is-wgs84-itself-a-coordinate-reference-system">Is WGS84 itself a Coordinate Reference System?</a></li> <li><a href="http://www.ogcnetwork.net/webgeoformats">Web-Friendly Geo formats</a></li> <li><a href="https://en.wikipedia.org/wiki/Spatial_reference_system">Spatial_reference_system</a></li> <li><a href="https://en.wikipedia.org/wiki/Geodetic_datum">Geodetic datum</a></li> <li><a href="http://gis.stackexchange.com/questions/664/whats-the-difference-between-a-projection-and-a-datum">What’s the difference between a projection and a datum?</a></li> </ul> </li> <li>Round Earth, Flat Maps <ul> <li><a href="http://piecubed.co.uk/flat-world-map-lies/">Maps are all lies – Representing a spherical earth on a flat world map</a></li> <li><a href="http://www.nationalgeographic.com/features/2000/projections/">Round Earth, Flat Maps (National Geographic)</a></li> <li><a href="http://www.icsm.gov.au/mapping/images/MapProjections.pdf">Map Projections: From Spherical Earth to Flat Map</a></li> <li><a href="http://www.progonos.com/furuti/MapProj/Normal/CartHow/HowSanson/howSanson.html">Deducing the Sanson-Flamsteed (sinusoidal) Projection</a></li> <li><a href="http://www.progonos.com/furuti/MapProj/Dither/CartProp/DistPres/distPres.html">Useful Map Properties: Distances and Scale</a></li> </ul> </li> <li>GIS : Coordinate Converters <ul> <li><a href="http://webhelp.esri.com/arcgisdesktop/9.2/index.cfm?TopicName=Converting_Degrees_Minutes_Seconds_values_to_Decimal_Degree_values">Converting Degrees Minutes Seconds values to Decimal Degree values</a></li> <li><a href="http://www.pgc.umn.edu/tools/conversion">PGC Coordinate Converter</a></li> </ul> </li> <li>GeoForms <ul> <li><a href="http://www.ogcnetwork.net/webgeoformats">Web-Friendly Geo formats</a></li> </ul> </li> <li>Postgres / PostGIS <ul> <li><a href="http://www.bostongis.com/PrinterFriendly.aspx?content_name=postgis_tut01">Getting Started With PostGIS</a></li> </ul> </li> </ul>BrandonEnhancing SSH Security2016-11-11T00:00:00+00:002016-11-11T00:00:00+00:00repo://posts.collection/_posts/2016-11-11-enhancing-ssh-security.md<p>With the whole security/privacy revolution rolling throughout the internet, it has recently come to my attention that specific services are being heavily focused on, while others are completely neglected. When securing your server and it’s services you need to attempt to secure the whole stack rather than a few specific services. For example lets take a look at web servers, they’re full of new ideas and technology, innovative, and always changing. And recently the world was recently introduced to <a href="https://letsencrypt.org/">Lets Encrypt</a> which makes them and your data a magnitude of times more secure using HTTPS (when properly configured).</p> <p>Another very important service that I’d like you to really think about is SSH. It’s another service that we use for tons of uses, but you don’t think “Does it need secured”, because everyone automatically seems to assume that it’s hardened by default. But in my own words, I’d say “It’s easy to use by default, but not necessarily ready for use”.</p> <p>I’m going to assume you’ve already hardened your SSH config with the basic settings (disable root login, AllowUsers, AllowGroups, MaxRetries, Fail2ban, LoginGraceTime, etc.) There’s tons of variable configurations to setup, one place I would suggest starting at would be <a href="http://tldp.org/LDP/solrhe/Securing-Optimizing-Linux-RH-Edition-v1.3/chap15sec122.html">Securing and Optimizing Linux</a> to get a basic configuration setup.</p> <p>Now lets get to the main topic of this post, enhanced security. Lately, I’ve seen quite a bit of talk suggesting everyone disable password authentication and only using key and/or certificate encryptions to secure SSH connections. I mean, really how probable is it that someone will be able to generate your exact ssh-key similar to the one below?</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4MCqOxhfmNP/uv8sl5EYSIqQSGuV4v17B50xMWXMcwTJrriOi9W6nNfxF8wu/i2HB1/nUUuSu+ZxQdYaD2cRkelzSGcq191z+b8lNY2lz+bxB547H465U5EQPlxJ5w7WU6QOV1hrZ7quWh/GYrDnU1aZrhEQ++EV5chQIUxoP3YgBSSb8D5Bpns9gR0IZVtlEqhF8eyCypZSiyKumQxK8e/W8Y8iHWCtRfvZbh+bnemCkHrXI/xc+CuCY9TQmWZkwFfTRBJQo3pmoRSZAZpqwYSl1kySrasw771rfy2rowFiCogkBYu2W9FTR2kMwB4btBrpA4Af97AjxwzkHyXUt [email protected] </code></pre></div></div> <p>Well, lets take a look how vulnerable your keys actually are. If you’re like the vast majority of developers just using a <code class="highlighter-rouge">id_rsa.pub</code> or <code class="highlighter-rouge">id_dsa.pub</code> key for the vast majority of your logins and connections, than you may already be an easy target. You may be asking, how and why? Well let’s take a look at some keys people use with github. If for example you use Github, or a number of services your public key is already out in the open. This is mainly for system wide user verification purposes. For example lets take a moment to look at user <a href="https://github.com/GrahamCampbell.keys">GrahamCampbell’s publickey</a>, now is his key is available what about <a href="https://gist.github.com/paulmillr/2657075/">everyone else’s</a>. If you still don’t trust me try it out with your own profile/username <a href="https://github.com/username.keys">https://github.com/username.keys</a>. Now tell me, how many services do you actually user ur id_rsa key with? If your using this one key for DigitalOcean, GitHub, Google Cloud, AWS, your companies servers, your local NAS, vagrant/Docker and who knows what else, than your services are ready for the taking. Now the simple questions is: How can we fix this? Well, to begin we can begin by using the <a href="https://linux.die.net/man/5/ssh_config">ssh_config</a> file ‘~/.ssh/config’. This allows us to assign the appropriate keys to be used when trying to access specific services/addresses. This is part of the security method known as “security by obscurity”, you reduce the amount of parameters an attacker can access by making things more complicated. If someone managers to get ahold of one of your keys, they won’t have access to every single web service you use.</p> <p>An example of how of how you can specify ssh to connect to specific services:</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Host github.com user sample_user HostName github.com IdentityFile ~/.ssh/github_rsa IdentitiesOnly <span class="nb">yes </span>Host digitalocean.com HostName digitalocean.com IdentityFile ~/.ssh/digitalocean_key IdentitiesOnly <span class="nb">yes </span>And the list goes on... </code></pre></div></div> <p>Another step in order to make things a bit more complicated is to add a password to your SSH-keys.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-keygen <span class="nt">-t</span> rsa <span class="nt">-q</span> <span class="nt">-N</span> <span class="s1">'sample_password'</span> <span class="nt">-f</span> ~/.ssh/sample_id <span class="nt">-C</span> <span class="s2">"[email protected]"</span> </code></pre></div></div> <p>You probably know by now, that by adding a password to your keys can be a pain in the butt. Because every time you connect to a service or do a push request, etc. it’ll ask you for that crazy password you used <code class="highlighter-rouge">XL7pa5wnV/nQgUqi5mf7oQ6uG0hk5NwGh+5OYU+Mu6</code>. Well you can solve this by using a service such as <a href="http://www.funtoo.org/Keychain">keychain</a> and/or using your ssh-agent with the <a href="https://linux.die.net/man/1/ssh-add">ssh-add</a> command. If configured properly, they require to only input the password once person login session. So in other words, you won’t have to re-input your crazy password until your next reboot.</p> <h3 id="newer-key-types">Newer Key types</h3> <p>Another very important thing to look as is your key encryption method if your still using id_dsa and/or id_rsa, please update to using <a href="https://ed25519.cr.yp.to/">Ed25519</a> immediately. I admit, some services like GitHub and DigitalOcean may have issues when with this encryption type, but if your connecting to ssh I’d highly suggest it. I’m no Cryptologist and I can’t tell you how and why a shorter key is more secure, other than it uses Elliptic curve cryptography methods. But at the moment ED25519 is the recommended standard and as we know the higher the bit length and more rounds we encrypt the key, the more secure it’ll be.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-keygen <span class="nt">-o</span> <span class="nt">-a</span> 100 <span class="nt">-b</span> 4096 <span class="nt">-t</span> ed25519 <span class="nt">-q</span> <span class="nt">-f</span> ~/.ssh/dev_key <span class="nt">-N</span> <span class="s1">'sample_password123'</span> <span class="nt">-C</span> <span class="s1">'sample_user'</span> <span class="c"># ~/.ssh/dev_key</span> ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBCz0L+cnm3RSHawNK/h7hkCs7ZQIeeAyjKs4S+tHnPF sample_user </code></pre></div></div> <h3 id="duel-method-authentication">Duel Method Authentication</h3> <p>Another enhancement you may wish to add to your SSH connections is requiring two forms of authentication. This a technique in which you can also require a two methods of encryption in order to connect to the server (publickey and password). In other words if a hacker “somehow” manages to get ahold of your publickey or your password, you’re server won’t be completely vulnerable to be victimized. Now let me warn you, this may cause issues with Capistrano, Ansible, Vagrant, or various other services. Because the majority of them attempt to authenticate with the server by password or by publickey and not both. But if that’s not that case and you’re really wanting to secure your SSH connections, this can be achieved with ease. This is because we’ll be using <a href="https://www.kernel.org/pub/linux/libs/pam/whatispam.html">PAM</a> which should be fairly straight forward because most modern Linux installations have various PAM modules installed by default. This can be achieved by changing your AuthenticationMethods used in /etc/ssh/sshd_config to something similar to the following:</p> <div class="language-config highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">AuthenticationMethods</span> <span class="n">publickey</span>,<span class="n">keyboard</span>-<span class="n">interactive</span>:<span class="n">pam</span> </code></pre></div></div> <p>In other words, in order to connect the the specified server you will be required to supply the proper publickey as well a valid password. It also helps to enforce higher levels of encryption, using modern cyphers, and locking down specific users and/or groups. If you’re looking to properly secure your SSH server, I suggest you begin is by taking a very thorough look at Mozilla’s <a href="https://wiki.mozilla.org/Security/Guidelines/OpenSSH">OpenSSH Guidelines</a>.</p> <h3 id="important">Important</h3> <p><strong><em>Please note</em></strong> that the methods suggested for securing your SSH in this post are not the only methods required in order to properly secure your SSH server. They mostly consist of method to enhance existing security methods for your SSH connections.</p> <h3 id="references">References</h3> <ul> <li><a href="https://wiki.mozilla.org/Security/Guidelines/OpenSSH">Mozilla SSH Guidelines</a></li> </ul>BrandonInstalling Fish on OSx2016-10-11T00:00:00+00:002016-10-11T00:00:00+00:00repo://posts.collection/_posts/2016-10-11-installing-fish-on-osx.md<p>As developers we all use the command line, to make things happen. And by now you know there’s several shell environments ranging from <a href="http://www.zsh.org/">zsh</a>, <a href="http://www.kornshell.com/">kornshell</a>, <a href="http://www.grymoire.com/Unix/Csh.html">C-Shell</a>, <a href="https://www.gnu.org/software/bash/">bash</a> and to who knows what variants of there out there. Some of these shells are complicated beasts and some of them include a scripting language of their own. But they all have a purpose and they have their dedicated users. I for one can say for the past several years I’ve been a heavy user of zsh along with the addition of <a href="http://ohmyz.sh/">oh-my-zsh</a> for the customization, easy plugins, and user friendly terminal.</p> <p><a href="https://github.com/laughedelic/fish_logo"><img src="/images/posts/installing_fish_on_OSx/fish_logo.png" alt="Fish Logo" class="img-fluid mx-auto" /></a></p> <p>From what I could find the majority of POSIX developers tend to stick with BASH which is very universal, widely used, and easy to learn. And mac OS users, tend to stick with either bash or zsh, I mean why change what’s not broken, right? Well it never hurts to try something new, I don’t mean the newest shiniest toy that’s available. And who know’s it could always make a huge difference in your productivity. Well after hearing quite a bit of hype from various developers about the <a href="https://fishshell.com/">fish</a> shell I decided to give it a try. And thus far, I’m loving it. It’s very smooth, has a quick response time, seems less bogged down like various other shells. It also has an amazing auto-complete/command prediction behavior that makes things quite a bit easier and faster for the user.</p> <p>In this article, I am going to show you how I documented installing the fish terminal, numerous of it’s extensions, and themes.</p> <h1 id="installing-fish-on-osx">Installing Fish on OSx</h1> <p>First of all if your a mac OS developer and looking to install custom software, you more than likely already have <a href="http://brew.sh/">homebrew</a>. Brew makes installing programs through the command-line a heck of a lot easier than building with a makefile, or having to bundle dependencies and packages. And than having the compilation fail on you.</p> <p>To install fish it’s quite simple, just begin by issuing the following commands:</p> <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew update brew install fish </code></pre></div></div> <p>Now you’re not quite ready to go, you need to add Fish to your systems shell listing in <code class="highlighter-rouge">/etc/shells</code>, this does require administrative access.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo echo</span> <span class="s1">'/usr/local/bin/fish'</span> <span class="o">&gt;&gt;</span> /etc/shells </code></pre></div></div> <p>Now that fish is installed, in order to use it lets begin by setting it as your default terminal shell environment.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chsh <span class="nt">-s</span> /usr/local/bin/fish </code></pre></div></div> <p>Now in order to load fish as your shell you’ll need to either open a new window, tab, session, etc. It may look like the same old terminal that your used to, but it’s not. If you would like to see some of the features fish offers, I’d suggest checking out their <a href="https://fishshell.com/docs/current/tutorial.html">tutorials</a> page. Now for some reason when I first installed fish it didn’t update/load the autocompletions, if this is the case for you issue the following command <code class="highlighter-rouge">fish_update_completions</code>, it’ll take a moment because there’s quite a few to download. If your looking for a rough idea of what they have autocompletions consist of, I’d suggest checking out their list of <a href="https://github.com/fish-shell/fish-shell/tree/master/share/completions">autocompletion</a> files.</p> <h3 id="lets-talk-plugins">Lets talk plugins</h3> <p>Part of using fish it make life easier on you, to get things done faster and more efficient. That’s why no matter what we do, we’re always looking for plugins, shortcuts, and extensions. Just like using brew, well install a fish plugin manager in order to make installing plugins as smooth as possible. At the moment there’s a variety of fish plugin in manager including: <a href="http://fisherman.sh/">fisherman</a>, <a href="https://github.com/tuvistavie/fundle">fundle</a>, <a href="http://oh-my.fish/">oh-my-fish</a> and a few others. In my opinion the more mature one seems to be fisherman, which is what quite a few fish users seem to prefer using. On the other hand, I chose oh-my-fish because of it’s ease of use and simple commands. I mean as developers we’ve learned 100’s of linux commands, functions, methods, attributes, etc. who wants to complicate our lives and add even more to memorize. So lets install oh-my-fish to get the customization started.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-L</span> http://get.oh-my.fish | fish </code></pre></div></div> <p>Now if would like to get a rough idea of the oh-my-fish commands available, lets just issue the <code class="highlighter-rouge">omf</code> command.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$:</span> omf Usage: omf <span class="nb">install</span> <span class="o">[</span>&lt;name&gt;|&lt;url&gt;] omf theme <span class="o">[</span>&lt;name&gt;] omf remove <span class="o">[</span>&lt;name&gt;] omf search <span class="o">[</span>&lt;name&gt;] omf update omf <span class="nb">help</span> <span class="o">[</span>&lt;<span class="nb">command</span><span class="o">&gt;]</span> Commands: list List <span class="nb">local </span>packages. describe Get information about what packages <span class="k">do</span><span class="nb">.</span> <span class="nb">install </span>Install one or more packages. theme List / Use themes. remove Remove a theme or package. update Update Oh My Fish. <span class="nb">cd </span>Change directory to plugin/theme directory. new Create a new package from a template. search Search <span class="k">for </span>a package or theme. submit Submit a package to the registry. destroy Uninstall Oh My Fish. doctor Troubleshoot Oh My Fish. <span class="nb">help </span>Shows <span class="nb">help </span>about a specific action. Options: <span class="nt">--help</span> Display this help. <span class="nt">--version</span> Display version. For more information visit → git.io/oh-my-fish </code></pre></div></div> <p>By now you’ve probably installed several programs that generate several .dotfile folder directories in your <code class="highlighter-rouge">$HOME</code> directory. Fish/oh-my-fish is no different, except instead of piling up even more in your <code class="highlighter-rouge">$HOME</code> directory, fish config files will be stored in <code class="highlighter-rouge">~/.config/fish</code>. The .config path is used by several programs in order to reduce clutter being built up in your $HOME directory. Because I’m sure if you entered in ‘ls -alH’ in the prompt you get something similar to the following.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.oh-my-zsh .rbenv .rvm .vim .tux .ssh .npm .irbrc <span class="c"># And tons of others as well</span> </code></pre></div></div> <p>Now let it be a new install or not, I always suggest updating your package managers to have the newest database and latest packages available.Lets issue the <code class="highlighter-rouge">omf update</code> command and continue on. By now, you’ve probably noticed that OMF will generate an entire set of config files of its own. I believe it’s best practice to use the OMF config files for your aliases, functions, etc. But I stuck with using the basic fish configure directory/files. I guarentee you will have to set several of your $PATH directories to access your programs. If you wish to see those in which are currently set use printf <code class="highlighter-rouge">printf "%s\n" $PATH</code> to print out what you have access to executing.</p> <p>Now lets begin installing plugins</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># fish supports atom out of the box, but I also use SublimeText as well</span> <span class="c"># this enables the sublime command 'subl', if it can be found</span> omf <span class="nb">install </span>sublime <span class="c"># To enable quick access to your rbenv without doing anything extra</span> omf <span class="nb">install </span>rbenv <span class="c"># https://github.com/oh-my-fish/plugin-osx</span> <span class="c"># enables OSx based commands: flushdns, showhidden, trash, updatedb, etc.</span> omf <span class="nb">install </span>osx </code></pre></div></div> <p>You may or may not want to install grc, it is used for highlighting output command for specified commands. But I believe it comes in handy for various commands and makes reading output easier.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install </span>grc omf <span class="nb">install </span>grc <span class="c"># this should automatically load grc to highlight fields rather than having to issue the following command</span> <span class="nb">source</span> /usr/local/etc/grc.bashrc </code></pre></div></div> <p>If you install it, you can verify it’s working properly by issuing a command with assorted output, ie: <code class="highlighter-rouge">ps au</code>. You should get a colorized version of something similar to the below table.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND Tarellel 17590 5.2 0.0 2446204 4976 s005 S+ 12:16AM 0:00.04 /usr/bin/python /usr/local/bin/grc <span class="nt">-es</span> <span class="nt">--colour</span><span class="o">=</span>auto ps au Tarellel 14325 1.5 0.0 2500316 5860 s005 S 11:13PM 0:00.73 <span class="nt">-fish</span> root 17591 0.0 0.0 2444720 1172 s005 R+ 12:16AM 0:00.00 ps au Tarellel 15150 0.0 0.0 2492120 5428 s002 S+ 11:36PM 0:00.40 <span class="nt">-fish</span> </code></pre></div></div> <p>Now back to installing some essential plugins:</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># similar to zsh term, allows u to open new tab with current directory</span> omf <span class="nb">install </span>tab <span class="c"># Add all brew paths to fish $PATH</span> omf <span class="nb">install </span>brew <span class="c"># used to help enfore 256 color terminal support</span> <span class="c"># https://github.com/oh-my-fish/plugin-ssh</span> omf <span class="nb">install </span>ssh <span class="c"># used to make ssh term colors consistent</span> <span class="c"># https://github.com/oh-my-fish/plugin-ssh</span> omg <span class="nb">install </span>ssh-term-helper </code></pre></div></div> <p>Now if you’re like me, I had several aliases, custom functions, variables and config in my zsh settings. Fish’s function and alias declarations are slightly different than zsh and bash but not to hard to understand. And now would be the perfect time to begin adding them before attempt to do a heck of a lot more. A basic example of variable declarations below, is assigning the $EDITOR variable to use atom.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># -U makes it a univerasal variable</span> <span class="nb">set</span> <span class="nt">-U</span> EDITOR atom </code></pre></div></div> <h3 id="lets-make-it-beautiful">Lets make it beautiful</h3> <p>Now you may thing it’s hipster as hell to want custom colors, fonts, and outputs in your terminal. But you want whats comfortable for you, whats easy on the eyes, and is easy to read.</p> <p>For all I know you may like the default colors and theme that you have setup for your terminal. But you want to spruce it up, I suggest taking a look at the oh-my-zsh <a href="https://github.com/oh-my-fish/oh-my-fish/blob/master/docs/Themes.md">theme</a> page, several of them are very similar to their oh-my-zsh counterparts.</p> <p><strong>Powerline</strong> - As you browse through the themes you’ll notice several of the themes require powerline installed in order to fully function. This is a python package and python comes installed by default on most *nix and POSIX operating systems. Depending on your operating system and setup you may want to look at the powerline <a href="https://powerline.readthedocs.io/en/latest/installation/osx.html">installation instructions</a>.</p> <div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip install --user powerline-status </code></pre></div></div> <p>Now powerline uses HD fonts with special characters for its stylized output, so you’ll need to install various powerline fonts and symbols.</p> <p>Downloads/Install powerline <a href="https://github.com/powerline/powerline/raw/develop/font/PowerlineSymbols.otf">symbols</a></p> <p>You can either install it through the Font-Book app or by moving it to <code class="highlighter-rouge">~/Library/Fonts/</code></p> <p>Next lets install all the available powerline fonts, each one looks amazing and adds a whole new experience to your terminal.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/powerline/fonts.git <span class="nb">sudo</span> ./fonts/install.sh </code></pre></div></div> <p>Now if you’re using one of those themes that does require powerline and a powerline font. You’ll need to set your terminal applications font to use the powerline fonts. In order to do this in iTerm2 goto the following menus <code class="highlighter-rouge">iterm &gt; prefernces &gt; profiles &gt; text</code> and make sure the fonts used are ones with a powerline tag on in. Plain old <code class="highlighter-rouge">Courier</code> or <code class="highlighter-rouge">Monaco</code> font just won’t cut it.</p> <h3 id="my-setup">My setup</h3> <p>oh-my-fish theme: <a href="https://github.com/oh-my-fish/oh-my-fish/blob/master/docs/Themes.md#bobthefish">bobthefish</a></p> <p>iTerm theme colors: <a href="https://github.com/chriskempson/base16-iterm2">base16-ocean-dark.256</a></p> <p>Terminal fonts used</p> <ul> <li>Regular Font: <code class="highlighter-rouge">14pt Inconsolata for Powerline</code></li> <li>Non-ASCII font: <code class="highlighter-rouge">12pt Sauce Code Powerline</code></li> </ul> <p>As you can see below, I tend to enjoy using smooth charcoal gray themes, with colors that aren’t to vibrant.</p> <p><img src="/images/posts/installing_fish_on_OSx/smooth_fish_terminal.png" alt="iTerm with Multitabs" class="img-fluid mx-auto" /></p> <hr /> <h3 id="resources">Resources;</h3> <ul> <li><a href="https://github.com/fisherman/awesome-fish">Awesome-fish</a></li> <li><a href="http://ohmyz.sh/">oh-my-fish</a></li> <li>iTerm2 theme colors: <ul> <li><a href="https://github.com/chriskempson/base16-iterm2">base16</a></li> <li><a href="https://draculatheme.com/iterm/">dracula</a></li> <li><a href="https://github.com/romainl/flattened">flattened</a></li> <li><a href="https://github.com/altercation/solarized/tree/master/iterm2-colors-solarized">solarized-dark</a></li> <li><a href="http://color.smyck.org/">smych</a></li> <li><a href="https://github.com/chriskempson/tomorrow-theme">tomorrow theme</a></li> <li><a href="http://iterm2colorschemes.com/">Iterm2-color-schemes</a> (big list of colors)</li> <li><a href="https://github.com/MartinSeeler/iterm2-material-design">iterm2-material-design</a></li> <li><a href="https://github.com/ahmetsulek/flat-terminal">flat-terminal</a></li> <li><a href="https://github.com/raphamorim/lucario">lucario</a></li> <li><a href="https://terminal.sexy/">terminal.sexy</a> : terminal color schemer</li> </ul> </li> </ul>BrandonSetting up tmux with Rails2016-08-29T00:00:00+00:002016-08-29T00:00:00+00:00repo://posts.collection/_posts/2016-08-29-setting-up-tmux-with-rails.md<h2 id="why-use-tmux">Why use tmux?</h2> <p>You may have a similar workflow the way I do. When I’m working on a rails project I tend to have several tabs and/or windows open in the terminal. After a while it tends to be a heaping mess and I end up having to close the terminal down and start all over again. And other times, I have just became tired of having to switch between different tabs and windows, trying to find the one that I need.</p> <p>As you can see from the image below, this method works. But having to switch from tab to tab, just to view your output logs or guard builds/tests gets to be a pain after a while.</p> <p><img src="/images/posts/tmux_wRails/multitabs.png" alt="iTerm with Multitabs" class="img-fluid mx-auto" /></p> <p>So what is tmux? It’s a mutliplex and very similar to the *nix screen process. It allows you to perform several processes all within the same screen, without having to switch tabs or windows. And there are tons more applications you can do with than building a rails application, but at the moment it’s a great example of why it makes such a great tool.</p> <p>I can tell you this much, so far I’ve only used tmux for a short period of time but it has become an essential tool in my development workflow. Tmux’s window and session management makes it very easy to get started and increase my productivity.</p> <h3 id="setting-up-tmux">Setting up tmux</h3> <p>I’m just going to assume you’re on OSx, with homebrew installed. If you are using Debian, Ubuntu, or some other distro it’s pretty close the same commands. You’ll just be using different package manager commands. Instead of using <code class="highlighter-rouge">brew</code>, you will be making the installs with <code class="highlighter-rouge">apt-get</code>, <code class="highlighter-rouge">pacman</code>, etc.</p> <p>Lets begin by install tmux</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install </span>tmux <span class="c"># allows you to access OSx clipboard (pbcopy &amp; pbpaste) through tmux</span> brew <span class="nb">install </span>reattach-to-user-namespace </code></pre></div></div> <p>Now if this is your first time installing tmux we’ll need to create a <code class="highlighter-rouge">.tmux.conf</code> file.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># this will contain colors, settings, key-bindings, etc.</span> <span class="nb">touch</span> ~/.tmux.conf </code></pre></div></div> <p>Now lets install the tux Plugin Manager (yes I know another one).</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># make a .tmux directory to store everything</span> <span class="nb">mkdir</span> <span class="nt">-p</span> ~/.tmux/plugins <span class="c"># While we're at it, lets install tmux package manager</span> git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm </code></pre></div></div> <p>The tmux plugin manager, makes it easy to install, update, and remove any and all plugins you may wish to use.</p> <p>Lets begin by adding our first plugin to tmux, tmux-sensible. You you dive in, it won’t seem like much but it contains various handy default values, bindings, etc. that will make tmux easier to use. The best part of this plugin, is that it’s not supposed to overwrite any config values you may have in you <code class="highlighter-rouge">~/.tmux.conf</code> file. No add the following to very bottom of your <code class="highlighter-rouge">~/.tmux.conf</code> file.</p> <div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">set</span> -<span class="n">g</span> @<span class="n">plugin</span> <span class="s1">'tmux-plugins/tmux-sensible'</span> <span class="c"># recommened tmux defaults </span><span class="n">set</span> -<span class="n">g</span> @<span class="n">plugin</span> <span class="s1">'tmux-plugins/tmux-yank'</span> <span class="c"># allows copying to system vie tmux </span> <span class="c"># Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf) </span><span class="n">run</span> <span class="s1">'~/.tmux/plugins/tpm/tpm'</span> </code></pre></div></div> <p>Now is where it gets tricky, with tmux you have a prefix-key combo that you have to use in order issue particular commands. By default the prefix keys are <code class="highlighter-rouge">CTRL+b</code>. With some commands/key-combos it makes a difference between <code class="highlighter-rouge">b</code> and <code class="highlighter-rouge">B</code> or <code class="highlighter-rouge">T</code> and <code class="highlighter-rouge">t</code>.</p> <p>Now lets fire up, tmux first the first time. At first it won’t be anything we haven’t seen before. But once you learn the keys and get the right plugins, it’ll be a whole new experience.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># fire up tmux by issuing the following command</span> tmux </code></pre></div></div> <p>You’ll terminal will flicker for a second and you have a very generic/ASCII based status bar appear at the bottom. This means you’re doing great. Now lets install the tmux plugins by doing the following key combo <code class="highlighter-rouge">CTRL+b I</code>. You may have to wait a few minutes, but it’ll install all specified plugins in the bottom of the tmux.conf file.</p> <p><img src="/images/posts/tmux_wRails/basic_tmux.png" alt="Basic tmux" class="img-fluid center-block" /></p> <p>Now if you want to play around with tmux, to get additional windows, panes, sessions, etc. Great. As you can see from the image, tmux is a multiplexer which means it allows you to have several processes all running within a single terminal screen/tab.</p> <h3 id="setup-and-install-tmuxinator">Setup and Install Tmuxinator</h3> <p>Now I’m going to assume you have some version of ruby installed already (various *nix OS’s are coming with it preinstalled). So up next, we will will install <a href="https://github.com/tmuxinator/tmuxinator">tmuxinator</a>, this is ruby gem that makes it a heck of a lot easier to make preconfigured panes and windows with default commands that run when they’re created.</p> <p>We first need to install tmuxinator and verifiy all the required requirements are met in order to use it.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># install tmuxinator</span> gem <span class="nb">install </span>tmuxinator <span class="c"># verify everything is ready</span> tmuxinator doctor </code></pre></div></div> <p>You will now need to add tmuxinator to your shell, for consistency sake I’ll just assume your using zsh/oh-my-zsh. You will need to add <code class="highlighter-rouge">source ~/.bin/tmuxinator.zsh</code> to your .zshrc file.</p> <p>Now you’ll be thinking, but no file by the name <code class="highlighter-rouge">~/.bin/tmuxinator.zsh</code> even exists. What gives? Well lets download the required completion file and get started. And if you’re not using zsh, there’s files for bash and fish as well.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Download zsh completion file</span> curl <span class="nt">-O</span> https://raw.githubusercontent.com/tmuxinator/tmuxinator/master/completion/tmuxinator.zsh <span class="nb">mkdir</span> ~/.bin <span class="nb">mv </span>tmuxinator.zsh ~/.bin/ </code></pre></div></div> <p>Now in order to make sure everything is loaded and configured, reload the current terminal.</p> <h3 id="lets-create-a-tmuxinator-project">Lets create a tmuxinator project</h3> <p>Tmuxinator projects let us setup a predefined layout, with panes performing specific tasks. Rather than mess around and build a layout every time we want to open up tmux.</p> <p>To create a tmuxinator project, lets issue the following command:</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tmuxinator new <span class="o">[</span>project_name] <span class="c"># for example</span> tmuxinator new red_willow </code></pre></div></div> <p>This will generate a project.yml file in the <code class="highlighter-rouge">~/.tmuxinator</code> folder. It should also open up your specified $EDITOR allowing you to get a rough idea on what tmuxinator’s configuration looks. It’s very basic theres panes, windows, even layouts, the <code class="highlighter-rouge">root: ~/[project_name]</code> at the top is the folder you specify for tmuxinator to open up all the tmux terminals to.</p> <p>Below is an example of a pretty basic rails based tmux session, built with tmuxinator.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ~/.tmuxinator/tasky.yml</span> <span class="na">name</span><span class="pi">:</span> <span class="s">tasky</span> <span class="na">root</span><span class="pi">:</span> <span class="s">~/Desktop/red_willow</span> <span class="na">windows</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">main</span><span class="pi">:</span> <span class="c1"># http://stackoverflow.com/questions/9812000/specify-pane-percentage-in-tmuxinator-project</span> <span class="c1"># use: tmux list-windows to get coords and window sizes</span> <span class="na">layout</span><span class="pi">:</span> <span class="s">b147,208x73,0,0[208x62,0,0,208x10,0,63{104x10,0,63,103x10,105,63}]</span> <span class="na">panes</span><span class="pi">:</span> <span class="pi">-</span> <span class="s">rails server thin</span> <span class="pi">-</span> <span class="s">guard</span> <span class="pi">-</span> <span class="s">atom .</span> <span class="pi">-</span> <span class="s">sleep 7 &amp;&amp; rails -t</span> <span class="pi">-</span> <span class="s">rails c</span> <span class="c1"># - foreman start</span> <span class="pi">-</span> <span class="na">logs</span><span class="pi">:</span> <span class="s">tail -f log/development.log</span> </code></pre></div></div> <p>Now issue the following command to initiate the tmux session <code class="highlighter-rouge">tmuxinator [project_name]</code> From here it may take a few seconds for everything to load up and start running. You should now have terminal session similar to the image below. Your sessions colors, panes, and windows may be a bit different from what it looks like. That’s because you will need to setup your tmux config and trust me figuring out all the settings to use can be a major beast to deal with. So to make things a little easier and somewhere to start from, here’s a copy of my current <a href="https://raw.githubusercontent.com/tarellel/dotfiles/master/rc/tmux.conf">tmux.conf</a>. And if your lazy, you can always search <a href="https://github.com/">github</a> for .dotfile repositories because there are tons and tons of tmux.conf files listed.</p> <p><img src="/images/posts/tmux_wRails/tmuxinator_wRails.png" alt="tmuxinator with Rails" class="img-fluid center-block" /></p> <p>How it’s time to let you loose on your own and see what kind of project configure best suites your needs.</p> <hr /> <h3 id="tmux-commandsshortcuts">tmux commands/shortcuts</h3> <p>Your prefix key can be configured in <code class="highlighter-rouge">~/.tmux.conf</code>, but I’ve chose to leave it as <code class="highlighter-rouge">CTRL+b</code>, because it means there’s no way I will accidentally close or move between a window, pane, session, etc. And this is just a small batch of commands you can do, tmux is a massive program with tons of features I haven’t even begin to think about.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#All of these require you to first initiate the prefix key combo:</span> <span class="c"># Default Prefix: CTRL+B and than whatever you want it to do</span> <span class="s2">" - Horizontal split % - Vertical split &lt;arrow buttom&gt; - moves between session panes z - loads window focus to current tmux panel c - load up new tmux window &lt;window number&gt; - switches between specified window d - detach current tmux session x - kill current pane in focus [space] - toggle between layouts </span></code></pre></div></div> <p>And here a few essential tmux console commands:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># to view list of current tmux sessions</span> tmux <span class="nb">ls</span> <span class="c"># to reattach tmux panels (number specified will be specified tmux session from tmux ls)</span> tmux attach <span class="nt">-t</span> 0 <span class="c">#To kill specific tmux session</span> tmux kill-session <span class="nt">-t</span> 0 <span class="c"># kills the last tmux session on the list</span> tmux kill-session <span class="c"># to completely kill tmux</span> tmux kill-server </code></pre></div></div> <ul> <li> <p><a href="http://hyperpolyglot.org/multiplexers">Terminal Multiplexers commands and shortcuts</a></p> </li> <li> <p><a href="https://tmuxcheatsheet.com/">tmux cheatsheet</a></p> </li> <li> <p><a href="tmux &amp; screen cheat-sheet">tmux and screen cheatsheet</a></p> </li> </ul> <hr /> <h3 id="additional-resources">Additional Resources</h3> <ul> <li> <p><a href="http://manpages.ubuntu.com/manpages/precise/en/man1/tmux.1.html">tmux man page</a></p> </li> <li> <p><a href="https://robots.thoughtbot.com/a-tmux-crash-course">A tmux crash course</a> : thoughtbot</p> </li> <li> <p><a href="https://www.sitepoint.com/tmux-a-simple-start/">Tmux: A Simple Start</a></p> </li> <li> <p><a href="http://www.hamvocke.com/blog/a-guide-to-customizing-your-tmux-conf/">Making tmux Pretty and Usable</a></p> </li> <li> <p><a href="https://github.com/tmuxinator/tmuxinator">tmuxinator</a></p> </li> </ul>Brandon