<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Jake Jarvis</title>
        <link>https://jarv.is</link>
        <description>Hi there! I'm a frontend web developer based in Boston, Massachusetts specializing in TypeScript, React, Next.js, and other JavaScript frameworks.</description>
        <lastBuildDate>Wed, 10 Jun 2026 13:11:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Jake Jarvis</title>
            <url>https://jarv.is/_next/static/media/opengraph-image.2500w2npfyzt5.jpg</url>
            <link>https://jarv.is</link>
        </image>
        <copyright>https://spdx.org/licenses/CC-BY-4.0.html</copyright>
        <atom:link href="https://jarv.is/feed.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[I Was Wrong About Tailwind CSS]]></title>
            <link>https://jarv.is/notes/tailwind-hater</link>
            <guid isPermaLink="false">https://jarv.is/notes/tailwind-hater</guid>
            <pubDate>Thu, 08 May 2025 18:33:10 GMT</pubDate>
            <description><![CDATA[I never thought I'd say this, but Tailwind CSS made my frontend code cleaner, simpler, and even enjoyable.]]></description>
            <content:encoded><![CDATA[<p>I have a confession. I’ve <strong>hated</strong> <a href="https://tailwindcss.com/">Tailwind CSS</a>. For years.</p>
<p>Every time a Tailwind <a href="https://dev.to/kerryboyko/tailwindcss-adds-complexity-does-nothing-3hpn">“hit piece”</a> went viral, I quietly enjoyed the warm, fuzzy feeling of confirmation bias.</p>
<p>I’ve also never been the biggest advocate of anything AI (beyond providing a smarter autocomplete), but that’s a post for another day. I do, however, appreciate what companies like <a href="https://vercel.com/home">Vercel</a> are doing with tools like <a href="https://v0.dev/"><strong>v0</strong></a>, or <a href="https://stackblitz.com/">Stackblitz</a> with <a href="https://bolt.new/"><strong>Bolt.new</strong></a> (or especially new startups like <a href="https://lovable.dev/"><strong>Lovable</strong></a>). They’re embarrassingly bad at generating anything close to full-stack right now, but are incredible at prototyping small drop-in React components like a <a href="https://v0.dev/chat/custom-copy-button-6giyaNd8fmn">copy button</a> or even <a href="https://v0.dev/chat/next-js-charts-eygG8PAxk8z">beautiful charts</a>.</p>
<p>So what’s the conundrum? These tools <em>love</em> Tailwind (and its offspring — <a href="https://ui.shadcn.com/">shadcn/ui</a> being the poster child). And I wanted to figure out why.</p>

<p>As I usually do with any piece of software I want to learn more about, I used <a href="https://github.com/jakejarvis/jarv.is">this website</a> as a testing ground. I started with a few one-liner components, like my styled <a href="https://github.com/jakejarvis/jarv.is/tree/v6/components/Blockquote"><code>&#x3C;Blockquote></code></a> for Markdown content. I quickly stumbled upon dozens of CSS to Tailwind “conversion” tools, but just as quickly realized that actually digging into Tailwind would be much easier. This lead to my first discovery:</p>
<p>🏆 <strong><a href="https://tailwindcss.com/docs/styling-with-utility-classes">Tailwind’s documentation</a> is some of the best I’ve ever seen.</strong></p>
<p>Seriously, it’s so good that even if you’re not using Tailwind, their docs clearly explain and visually demonstrate all of the most <a href="https://tailwindcss.com/docs/flex-basis">nonsensical</a> CSS “features” I’ve always struggled to master.</p>
<p>After converting a few more components, I started to feel better and better each time I deleted a <a href="https://github.com/jakejarvis/jarv.is/blob/v6/components/Blockquote/Blockquote.module.css"><code>.module.css</code></a> file. What I thought would make my code infinitely more complicated and messy was actually making it simpler and cleaner. Each component no longer needed its own folder. Colors and spacing were becoming more consistent. Pixels I struggled to line up previously were now coincidentally falling in line. Best of all, <strong>I didn’t have to name anything anymore!</strong> 🎉</p>

<p>Don’t get me wrong, I still think the syntax Tailwind forces you to write is an abomination. <strong>But honestly, so was my CSS.</strong></p>
<p>Maybe that’s on me, or maybe not, but my primary reason to hate on Tailwind for years — <em>“it makes my HTML/JSX ugly and design doesn’t belong sprinkled throughout a markup language”</em> — just flew out the window either way. Sure, I tried to make my CSS consistent and logical, making tons of variables for colors and sizes and border radii. But that wasn’t nearly as comforting as being certain that <code>w–12</code> will <strong>always</strong> be twice the width of <code>w–6</code> no matter how badly I mess things up.</p>
<p>And on top of all of the AI tools mentioned above being Tailwind experts, the <a href="https://tailwindcss.com/docs/editor-setup">IDE support</a> is also excellent. One click to install the official <a href="https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss">IntelliSense extension</a> for VS Code, and suddenly everywhere I wrote <code>text–sky–400</code> throughout my code had a lovely little light blue square next to it. The official <a href="https://github.com/tailwindlabs/prettier-plugin-tailwindcss">Prettier extension</a> ensures the order of class names doesn’t cause unexpected specificity problems from a rule four layers up overriding a rule you thought you were currently looking at — historically my biggest painpoint of CSS by far.</p>
<p>All of these tools together actually made the <a href="https://github.com/jakejarvis/jarv.is/pull/2387">process</a> of revamping this site oddly fun. It shined a spotlight on a lot of issues I had no idea were there — especially by forcing me to think <a href="https://tailwindcss.com/docs/responsive-design#working-mobile-first">“mobile-first”</a> — and gave me an opportunity to put a new coat of paint on a design I haven’t made major changes to <a href="/notes/hugo-to-nextjs">since my last blog post</a>…three years ago.</p>
<p>So, if you’re a closeted Tailwind hater like I was, try it out. I don’t think I’ll ever love Tailwind, to be honest. But I certainly like it a lot more than I ever liked CSS.</p>
        <p><a href="https://jarv.is/notes/tailwind-hater"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[Revenge of the JavaScript: Moving from Hugo to Next.js]]></title>
            <link>https://jarv.is/notes/hugo-to-nextjs</link>
            <guid isPermaLink="false">https://jarv.is/notes/hugo-to-nextjs</guid>
            <pubDate>Thu, 07 Apr 2022 14:53:33 GMT</pubDate>
            <description><![CDATA[The next chapter in this website's history of overengineering, from static HTML with Hugo to React everywhere with Next.js.]]></description>
            <content:encoded><![CDATA[<p>I’ll say right off the bat: this website has a <em>loooong</em> history of going overboard with its tech stack. I use this domain as a vehicle to <a href="https://www.jvt.me/talks/overengineering-your-personal-website/">learn new things</a>, and given <a href="https://stackoverflow.blog/2018/01/11/brutal-lifecycle-javascript-frameworks/">how frequently</a> the tides turn in the frontend development waters these days, things can (and did) get messy pretty quickly.</p>
<p>I discovered <a href="https://gohugo.io/"><strong>Hugo</strong></a> and started using it almost four years ago (<a href="https://github.com/jakejarvis/jarv.is/commit/6fff44e8e2773d0ca417879b4dd69860871cd89c"><code>6fff44e</code></a>) in an effort to get back to the basics. Static site generators and buzzwordy <a href="https://www.cloudflare.com/learning/performance/what-is-jamstack/">JAMstack</a> philosophies were all the rage, and the potential simplicity of having a website with HTML that I could have understood <a href="/previously">as a kid</a> sounded fun and refreshing. Very quickly, I became a huge Hugo <a href="https://github.com/jakejarvis?tab=repositories&#x26;q=hugo">cheerleader</a>, and I didn’t even add <em>any</em> JavaScript for over a year as a kind of challenge to myself. But as I saw certain JS frameworks and tools explode in popularity, some FOMO started setting in, and bringing things like <a href="https://reactjs.org/">React</a> into the mix became incredibly tempting.</p>
<p>Turning to lighter frameworks like <a href="https://lit.dev/">Lit</a> and then <a href="https://preactjs.com/">Preact</a> allowed me to tell myself I was still being a minimalist while dipping my toes in the component-filled waters. I added Preact (<a href="https://github.com/jakejarvis/jarv.is/commit/b755b66d1982892b3de914f9cc5bded2d7b99bc7"><code>b755b66</code></a>) to power the <a href="/projects">/projects</a> page, the <a href="/contact">contact form</a>, and the hit counter at the top of posts like this one. These components were made dynamic via <a href="https://github.com/jakejarvis/jarv.is/tree/v4/api">serverless functions</a> written in — you guessed it — JavaScript.</p>
<p>By the end of the Hugo chapter, I was using a <a href="https://github.com/jakejarvis/jarv.is/blob/v4/package.json">cornucopia</a> of build tools behind the scenes to keep everything glued together: <a href="https://webpack.js.org/">Webpack</a>, <a href="https://gulpjs.com/">Gulp</a>, <a href="https://esbuild.github.io/">esbuild</a>, <a href="https://babeljs.io/">Babel</a>, <a href="https://terser.org/">Terser</a>, <a href="https://postcss.org/">PostCSS</a>, <a href="https://github.com/sass/node-sass">node-sass</a>, <a href="https://github.com/imagemin/imagemin">imagemin</a>… That’s a <strong><em>lot</em></strong> of JavaScript tooling for a site that’s allegedly avoiding JavaScript at all costs, and realizing this let me <em>finally</em> give myself permission to go all-in.</p>
<p>Enter <a href="https://nextjs.org/"><strong>Next.js</strong></a>, which caught my eye over other JS-centric SSGs (like <a href="https://www.gatsbyjs.com/">Gatsby</a>, <a href="https://www.11ty.dev/">11ty</a>, or <a href="https://hexo.io/">Hexo</a>) because it sounded like the best of both worlds. Next outputs a fully static HTML version of your site just like Hugo does <strong>and</strong> uses React to render pages dynamically (a bit like a single-page web app would behave). And once I learned how its <a href="https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration">incremental static regeneration</a> feature works, I realized that my Hugo site was actually <strong>less static</strong> than it could be with a framework like Next! Pages like <a href="/projects">/projects</a>, which pulls data from the GitHub API, had no actual content if you were to right-click and view source — but with Next, the project data actually exists in the static HTML initially sent by the server and is <a href="https://reactjs.org/docs/react-dom.html#hydrate">“hydrated”</a> with the same data via React on the client-side. If you host everything on <a href="https://vercel.com/">Vercel</a>, this becomes even more seamless. (More on that later.)</p>
<h2>My requirements for Next.js</h2>
<ul>
<li><strong>Page sizes remain well under 1 MB.</strong> Minus images and third-party things like Twitter embeds, of course, I think I’ve achieved this on every page — the <a href="/">homepage</a> still hovers around only 512 kB in total! Being <em>super</em> careful when choosing dependencies (<a href="https://bundlephobia.com/">Bundlephobia</a> is awesome, btw) and making sure <a href="https://next-code-elimination.vercel.app/">not to send</a> any server-side code to the browser can make a huge difference.</li>
<li><strong>All content and core functionality is still available without JavaScript.</strong> Go ahead and try it! Besides the obvious reasons, this was also crucial in keeping the <a href="http://jarvis2i2vp4j4tbxjogsnqdemnte5xhzyi7hziiyzxwge3hzmh57zad.onion/">🧅 Tor mirror</a> functional.</li>
<li><strong>Fewer <code>devDependencies</code> and consolidated build tooling.</strong> I don’t want to look at another Gulp task for as long as possible. Next’s <a href="https://nextjs.org/docs/api-reference/next.config.js/custom-webpack-config">built-in</a> Webpack and Babel support has come in clutch here.</li>
<li><strong>Same (or better) <a href="https://web.dev/learn/#lighthouse">Lighthouse scores</a>.</strong> The heavier load of JS has certainly affected performance a bit, but any modern browser can easily keep up with any React code I’ll be using at this scale. And because of Next’s static page generation (and <a href="https://github.com/garmeeh/next-seo">next-seo</a>) nothing has changed in the realm of SEO.</li>
</ul>

<h2>Things I still miss from Hugo</h2>
<ul>
<li><strong>Native <a href="https://daringfireball.net/projects/markdown/">Markdown</a> support with <a href="https://gohugo.io/content-management/shortcodes/">shortcodes</a>.</strong> I’m enjoying the React-ified <a href="https://mdxjs.com/">MDX</a> flavor of Markdown, but wrestling with <a href="https://www.npmjs.com/package/@next/mdx">@next/mdx</a>, <a href="https://github.com/hashicorp/next-mdx-remote">next-mdx-remote</a>, and/or <a href="https://github.com/kentcdodds/mdx-bundler">mdx-bundler</a> adds a whole new layer of ~~potential~~ breakage. Nothing beats the native parser that comes with Hugo when it comes to convenience and compatibility.</li>
<li><strong>Pure HTML output.</strong> This is a bit of a fallacy, though, considering the number of serverless Lambda functions I ended up having.</li>
<li><strong>Knowing every corner of the frontend code and how it works.</strong> Don’t ask me to explain what the <code>152-c95759cd62a.js</code> file you just downloaded does. Because I have no clue.</li>
<li><strong>Total and complete portability.</strong> In exchange for a <em>lot</em> of convenience, I accepted a bit of vendor lock-in when I put all of my eggs in <a href="https://vercel.com/solutions/nextjs">Vercel’s</a> basket. Next.js is their creation, and other hosts (including <a href="https://docs.netlify.com/configure-builds/common-configurations/next-js/">Netlify</a>, <a href="https://render.com/docs/deploy-nextjs-app">Render</a>, <a href="https://docs.digitalocean.com/tutorials/app-nextjs-deploy/">DigitalOcean</a>, etc.) have begun adding support for some of the core functionality. But quite a few of the more exciting features (like <a href="https://vercel.com/docs/concepts/image-optimization">image optimization</a> and <a href="https://nextjs.org/analytics">analytics</a>) are clearly <a href="https://nextjs.org/docs/deployment#managed-nextjs-with-vercel">designed for Vercel</a> and Vercel only.</li>
</ul>
<p>Doing the actual migration was pretty boring and uneventful (besides the wealth of React knowledge I got to teach myself, which I’m sure is still less than 2% of what’s out there). I found the goal with the least mental friction was to build a 1:1 replica of the Hugo site with Next.js, to the point where a visitor wouldn’t have been able to tell there was a change, except for <em>much</em> faster page transition times. You can track how that went in <a href="https://github.com/jakejarvis/jarv.is/pull/711">this pull request</a> and judge for yourself…</p>
<p>As always, this entire site is <a href="https://github.com/jakejarvis/jarv.is">open source</a>, and I’ve also archived the Hugo version in <a href="https://github.com/jakejarvis/jarv.is-hugo">a separate repository</a> for reference and comparison.</p>
<p>Let me know what you think about the “new” site as well as frameworks like React and hybrid static/server tools like Next.js. To anyone else currently using Hugo or building pure HTML sites, have you been tempted recently by a JavaScript framework du-jour? Or are you ignoring the hype and waiting for the next trendy project to come along and “change everything” yet again? (<a href="https://remix.run/">Remix</a> is my prediction, for what it’s worth…)</p>
        <p><a href="https://jarv.is/notes/hugo-to-nextjs"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[How To: Add Dark Mode to a Website 🌓]]></title>
            <link>https://jarv.is/notes/dark-mode</link>
            <guid isPermaLink="false">https://jarv.is/notes/dark-mode</guid>
            <pubDate>Fri, 15 Oct 2021 12:56:33 GMT</pubDate>
            <description><![CDATA[Simple dark mode switching using local storage, OS preference detection, and minimal JavaScript.]]></description>
            <content:encoded><![CDATA[<p>Love it or hate it, it seems that the <a href="https://en.wikipedia.org/wiki/Light-on-dark_color_scheme">dark mode fad</a> is here to stay, especially now that more and more devices have <a href="https://www.macrumors.com/2019/10/21/ios-13-dark-mode-extends-iphone-battery-life/">OLED screens</a> that display true blacks… which means that these trendsetters might go blind from your site’s insanely white background if you’re behind the curve and don’t offer your own dark mode.</p>
<p>It <em>is</em> possible to use <a href="https://css-tricks.com/dark-modes-with-css/">pure CSS3 media queries to do this</a> by reading a user’s system and/or browser preference, which might be enough if you’re okay with <strong>only</strong> supporting the <a href="https://caniuse.com/#feat=prefers-color-scheme">latest, cutting-edge browsers</a> and OSes. But if you want your own button on your website that switches back and forth between the two modes, there’s no avoiding getting your hands a little dirty with some JavaScript.</p>
<p>I’ve written a simple implementation below, which…</p>
<ul>
<li>Defaults to a user’s system preference, until they press your toggle to set it themselves</li>
<li>Listens for clicks on any element of your choosing — just set the class to <code>dark-mode-toggle</code>. For example:</li>
</ul>
<pre><code class="language-html">&#x3C;button class="dark-mode-toggle">💡 Switch Themes&#x3C;/button>
</code></pre>
<ul>
<li>Remembers the visitor’s preference between visits using the <a href="https://www.w3schools.com/html/html5_webstorage.asp">local storage</a> of the their browser (not cookies, please don’t use cookies!)</li>
<li>Switches your <code>&#x3C;body></code>’s class between <code>light</code> and <code>dark</code>…</li>
</ul>
<p>…meaning that any CSS selectors beginning with <code>body.dark</code> or <code>body.light</code> will only apply when the respective mode is active. A good place to start is by separating any color rules — your background, text, links, etc. — into a different section of your CSS. Using <a href="https://sass-lang.com/">SASS or SCSS</a> makes this a whole lot <a href="https://sass-lang.com/guide#topic-3">easier with nesting</a> but is not required; this was written with a <a href="https://www.youtube-nocookie.com/embed/O58A7MJfOwU?hl=en&#x26;fs=1&#x26;showinfo=1&#x26;rel=0&#x26;iv_load_policy=3">KISS</a> mentality.</p>

<p>A <em>very</em> barebones example is embedded above (<a href="https://github.com/jakejarvis/dark-mode-example">view the source here</a>, or <a href="https://jakejarvis.github.io/dark-mode-example/">open in a new window</a> if your browser is blocking the frame) and you can try it out on this site by clicking the 💡 lightbulb in the upper right corner of this page. You’ll notice that the dark theme sticks when refreshing this page, navigating between other pages, or if you were to return to this example weeks from now.</p>
<hr>
<h3>⚡ Update: Now Available on <a href="https://www.npmjs.com/package/dark-mode-switcheroo">NPM</a>!</h3>
<p>I have cleaned up this code a bit, added a few features, and packaged it as an <a href="https://www.npmjs.com/package/dark-mode-switcheroo">📦 NPM module</a> (zero dependencies and still <a href="https://bundlephobia.com/package/dark-mode-switcheroo">only ~500 bytes</a> minified and gzipped!). Here’s a small snippet of the updated method for the browser (pulling the module from <a href="https://unpkg.com/browse/dark-mode-switcheroo/">UNPKG</a>), but definitely <a href="https://github.com/jakejarvis/dark-mode#readme">read the readme</a> for much more detail on the API.</p>
<pre><code class="language-html">&#x3C;button class="dark-mode-toggle" style="visibility: hidden;">
  💡 Click to see the light... or not.
&#x3C;/button>

&#x3C;script src="https://unpkg.com/dark-mode-switcheroo/dist/dark-mode.min.js">&#x3C;/script>
&#x3C;script>
  window.darkMode.init({
    toggle: document.querySelector(".dark-mode-toggle"),
    classes: {
      light: "light",
      dark: "dark",
    },
    default: "light",
    storageKey: "dark_mode_pref",
    onInit: function (toggle) {
      toggle.style.visibility = "visible"; // toggle appears now that we know JS is enabled
    },
    onChange: function (theme, toggle) {
      console.log("Theme is now " + theme);
    },
  });
&#x3C;/script>
</code></pre>
<p>You can also install it <a href="https://www.npmjs.com/package/dark-mode-switcheroo">straight from NPM</a> (<code>npm install dark-mode-switcheroo</code> or <code>yarn add dark-mode-switcheroo</code>) and simply include the ESM module, which works great when bundling using <a href="https://webpack.js.org/">Webpack</a>, <a href="https://browserify.org/">Browserify</a>, <a href="https://parceljs.org/">Parcel</a>, <a href="https://esbuild.github.io/">esbuild</a>, etc.</p>
<pre><code class="language-js">import { init } from "dark-mode-switcheroo";

init({
  // ...same options as browser code.
});
</code></pre>
<p>The <a href="#html-css">example HTML and CSS below</a> is still helpful for reference.</p>
<hr>
<h3><a href="https://raw.githubusercontent.com/jakejarvis/dark-mode-example/gh-pages/dark-mode.min.js">Minified JS</a> (410 bytes gzipped! 📦):</h3>

<pre><code class="language-js">/*! Dark mode switcheroo | MIT License | jrvs.io/darkmode */
(function(){var e=window,t=e.document,i=t.body.classList,a=localStorage,c="dark_mode_pref",d=a.getItem(c),n="dark",o="light",r=o,s=t.querySelector(".dark-mode-toggle"),m=r===n,l=function(e){i.remove(n,o);i.add(e);m=e===n};d===n&#x26;&#x26;l(n);d===o&#x26;&#x26;l(o);if(!d){var f=function(e){return"(prefers-color-scheme: "+e+")"};e.matchMedia(f(n)).matches?l(n):e.matchMedia(f(o)).matches?l(o):l(r);e.matchMedia(f(n)).addListener((function(e){e.matches&#x26;&#x26;l(n)}));e.matchMedia(f(o)).addListener((function(e){e.matches&#x26;&#x26;l(o)}))}if(s){s.style.visibility="visible";s.addEventListener("click",(function(){if(m){l(o);a.setItem(c,o)}else{l(n);a.setItem(c,n)}}),!0)}})();
</code></pre>
<h3>Full JS:</h3>

<pre><code class="language-js">/*! Dark mode switcheroo | MIT License | jrvs.io/darkmode */

(function () {
  // improve variable mangling when minifying
  var win = window;
  var doc = win.document;
  var body = doc.body;
  var classes = body.classList;
  var storage = localStorage;

  // check for preset `dark_mode_pref` preference in local storage
  var pref_key = 'dark_mode_pref';
  var pref = storage.getItem(pref_key);

  // change CSS via these &#x3C;body> classes:
  var dark = 'dark';
  var light = 'light';

  // which class is &#x3C;body> set to initially?
  var default_theme = light;

  // use an element with class `dark-mode-toggle` to trigger swap when clicked
  var toggle = doc.querySelector('.dark-mode-toggle');

  // keep track of current state no matter how we got there
  var active = (default_theme === dark);

  // receives a class name and switches &#x3C;body> to it
  var activateTheme = function (theme) {
    classes.remove(dark, light);
    classes.add(theme);
    active = (theme === dark);
  };

  // if user already explicitly toggled in the past, restore their preference
  if (pref === dark) activateTheme(dark);
  if (pref === light) activateTheme(light);

  // user has never clicked the button, so go by their OS preference until/if they do so
  if (!pref) {
    // returns media query selector syntax
    var prefers = function (theme) {
      return '(prefers-color-scheme: ' + theme + ')';
    };

    // check for OS dark/light mode preference and switch accordingly
    // default to `default_theme` set above if unsupported
    if (win.matchMedia(prefers(dark)).matches)
      activateTheme(dark);
    else if (win.matchMedia(prefers(light)).matches)
      activateTheme(light);
    else
      activateTheme(default_theme);

    // real-time switching if supported by OS/browser
    win.matchMedia(prefers(dark)).addListener(function (e) { if (e.matches) activateTheme(dark); });
    win.matchMedia(prefers(light)).addListener(function (e) { if (e.matches) activateTheme(light); });
  }

  // don't freak out if page happens not to have a toggle
  if (toggle) {
    // toggle re-appears now that we know user has JS enabled
    toggle.style.visibility = 'visible';

    // handle toggle click
    toggle.addEventListener('click', function () {
      // switch to the opposite theme &#x26; save preference in local storage
      if (active) {
        activateTheme(light);
        storage.setItem(pref_key, light);
      } else {
        activateTheme(dark);
        storage.setItem(pref_key, dark);
      }
    }, true);
  }
})();
</code></pre>
<h3>HTML &#x26; CSS Example:</h3>

<pre><code class="language-html">&#x3C;!doctype html>
&#x3C;html>
&#x3C;head>
  &#x3C;style>
    /* rules that apply globally */
    body {
      font-family: system-ui, -apple-system, sans-serif;
      text-align: center;
    }
    a {
      text-decoration: none;
    }
    .dark-mode-toggle {
      cursor: pointer;
      padding: 1em;

      /* hide toggle until we're sure user has JS enabled */
      visibility: hidden;
    }

    /* theme-specific rules -- you probably only want color-related stuff here */
    /* SCSS makes this a whole lot easier by allowing nesting, but is not required */
    body.light {
      background-color: #fff;
      color: #222;
    }
    body.light a {
      color: #06f;
    }
    body.dark {
      background-color: #222;
      color: #fff;
    }
    body.dark a {
      color: #fe0;
    }
  &#x3C;/style>
&#x3C;/head>
&#x3C;body class="light">
  &#x3C;h1>Welcome to the dark side 🌓&#x3C;/h1>
  &#x3C;p>&#x3C;a href="https://github.com/jakejarvis/dark-mode-example">View the source code.&#x3C;/a>&#x3C;/p>

  &#x3C;button class="dark-mode-toggle">💡 Click to see the light... or not.&#x3C;/button>

  &#x3C;script src="dark-mode.min.js">&#x3C;/script>
&#x3C;/body>
&#x3C;/html>
</code></pre>
<hr>
<h3>Further reading:</h3>
<ul>
<li><a href="https://charlesrt.uk/blog/the-dark-web-rises/">The Dark (Mode) Web Rises</a></li>
<li><a href="https://www.nngroup.com/articles/dark-mode/">Dark Mode vs. Light Mode: Which Is Better?</a></li>
<li><a href="https://uxdesign.cc/the-dawn-of-dark-mode-9636d1c9bcf0">The dawn of Dark Mode</a></li>
<li><a href="https://stuffandnonsense.co.uk/blog/redesigning-your-product-and-website-for-dark-mode">Redesigning your product and website for dark mode</a></li>
<li><a href="https://medium.com/@mwichary/dark-theme-in-a-day-3518dde2955a">Dark theme in a day</a></li>
</ul>
        <p><a href="https://jarv.is/notes/dark-mode"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[How To: Safely Rename master Branch on GitHub ✊🏾]]></title>
            <link>https://jarv.is/notes/github-rename-master</link>
            <guid isPermaLink="false">https://jarv.is/notes/github-rename-master</guid>
            <pubDate>Sun, 28 Jun 2020 13:28:52 GMT</pubDate>
            <description><![CDATA[Some of the most popular open-source projects are renaming their default branch from "master" on GitHub. Here's how to do so, and safely.]]></description>
            <content:encoded><![CDATA[<p>In the midst of this year’s long-overdue support of the <a href="https://blacklivesmatters.carrd.co/"><strong>Black Lives Matter</strong></a> movement and calls to action in the US and around the world, a <a href="https://mail.gnome.org/archives/desktop-devel-list/2019-May/msg00066.html">new spotlight</a> has been placed on unchecked invocations of racially charged language in the computer science world, no matter how big or small — like the long-standing and, until recently, widely accepted terms <a href="https://tools.ietf.org/id/draft-knodel-terminology-00.html#master-slave">“master” and “slave”</a> as an oppressive metaphor for ownership/importance.</p>
<p>When somebody pointed out the negative connotations of Git projects being created with a branch named <code>master</code> by default, and the possibility of this making minorities feel even more unwelcome in an industry already <a href="https://www.informationisbeautiful.net/visualizations/diversity-in-tech/">lacking diversity</a>, GitHub CEO <a href="https://github.com/nat">Nat Friedman</a> quietly <a href="https://twitter.com/natfriedman/status/1271253144442253312">announced a plan</a> to change this on Twitter (ignore the replies for your sanity).</p>

<p>I think many people misunderstood this tweet to mean GitHub will forcefully rename the <code>master</code> branch of all existing projects, which would break <em>millions</em> of programmers’ workflows. If anything, it’s more likely a name such as <code>main</code> will replace <code>master</code> as <strong>the default when creating a new repository</strong>, but that change hasn’t been made yet. <a href="https://gitlab.com/gitlab-org/gitlab/-/issues/221164">GitLab is also discussing</a> a similar switch to <code>main</code> as the default name. (Ideally, these changes would be made in tandem with the actual Git codebase, too. <a href="https://lore.kernel.org/git/CAOAHyQwyXC1Z3v7BZAC+Bq6JBaM7FvBenA-1fcqeDV==apdWDg@mail.gmail.com/t/">~~But this doesn’t seem likely.~~</a>)</p>
<blockquote>
<p><strong>Update:</strong> GitHub has <a href="https://github.com/github/renaming">published more details about their plan</a> to move from <code>master</code> to <code>main</code> and it will indeed be voluntary and configurable. To my surprise, the Git maintainers have <a href="https://sfconservancy.org/news/2020/jun/23/gitbranchname/">also agreed to add</a> a <code>init.defaultBranch</code> setting to customize the default branch for new repositories in Git 2.28.</p>
</blockquote>
<p>But this means in the meantime, project owners are free to rename their branches as they please — and it’s pretty simple to do so, usually with minimal disruption. <a href="https://github.com/desktop/desktop/issues/6478">Some</a> <a href="https://github.com/cli/cli/issues/929">of</a> <a href="https://github.com/sindresorhus/awesome/issues/1793">the</a> <a href="https://github.com/rust-lang/rustlings/issues/437">biggest</a> <a href="https://github.com/twbs/bootstrap/pull/31050">OSS</a> <a href="https://github.com/ohmyzsh/ohmyzsh/issues/9015">projects</a> have already voluntarily done so. Here’s how to join them.</p>
<hr>
<h3>1. Move your <code>master</code> branch to <code>main</code>:</h3>
<p>…or <code>development</code>, <code>unstable</code>, <code>trunk</code>, <code>live</code>, <code>original</code>; your choice!</p>
<p>We use <code>branch -m</code> to <strong>move</strong> the branch locally instead of creating a new one with <code>checkout -b</code> like you might be used to.</p>
<pre><code class="language-bash">git branch -m master main
</code></pre>
<h3>2. Push the new branch to GitHub:</h3>
<p>The first command is probably familiar. <code>-u</code> sets the new branch as the local default at the same time, and the second line ensures our local <code>HEAD</code> points to our new branch on GitHub.</p>
<pre><code class="language-bash">git push -u origin main
git remote set-head origin main
</code></pre>
<p>You can verify this worked by running <code>git branch -r</code>. You should see something like <code>origin/HEAD -> origin/main</code>.</p>
<h3>3. Change the default branch in your repository’s settings:</h3>
<p>Setting the default branch remotely is the only step that can’t be done on the command line (although you can technically <a href="https://github.com/erbridge/github-branch-renamer">use the GitHub API</a>). Head to <strong>Settings → Branches</strong> on GitHub to <a href="https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/changing-the-base-branch-of-a-pull-request">change the default branch</a>.</p>

<h3>4. Delete the old <code>master</code> branch on GitHub:</h3>
<p>We used <code>-m</code> (move) to <strong>rename</strong> the <code>master</code> branch locally, but GitHub will still have two identical branches at this point (as you saw in the previous step). Deleting it can be a little nerve-racking, so poke around your repository on GitHub and make sure your new branch is there and the commit history is correct.</p>
<p>You can say good riddance to <code>master</code> <a href="https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-and-deleting-branches-within-your-repository#deleting-a-branch">through the GitHub UI</a> or with this command:</p>
<pre><code class="language-bash">git push origin --delete master
</code></pre>
<h3>5. Scan your code, scripts, automation, etc. for references to <code>master</code>:</h3>
<p>Do a quick search of your codebase for <code>master</code> to manually replace any dead references to it.</p>
<p>Pay attention to CI files — <code>.travis.yml</code>, <code>.github/workflows/</code>, <code>.circleci/config.yml</code>, etc. — and make sure there aren’t any external services relying on <code>master</code> being there. For example, I almost forgot to change the branch <a href="https://docs.netlify.com/site-deploys/overview/#branches-and-deploys">Netlify triggers auto-deploys</a> from to build this site:</p>

<p>~~Unfortunately, GitHub won’t redirect links containing <code>master</code> to the new branch (as of now), so look for any <a href="https://github.com/">github.com</a> URLs as well.~~</p>
<blockquote>
<p><strong>Update:</strong> GitHub is now <a href="https://github.blog/changelog/2020-07-17-links-to-deleted-branches-now-redirect-to-the-default-branch/">redirecting deleted branches</a> to the default branch!</p>
</blockquote>
<h3>Bonus points:</h3>
<p>None of this will work on a brand new repository with zero commits. But we can hack around this limitation pretty easily…</p>
<p>You can create a <a href="https://git-scm.com/book/en/v2/Git-Basics-Git-Aliases">Git alias</a> in your local environment’s <code>.gitconfig</code> to make <code>main</code> the default branch name for new repositories. Git doesn’t let you override some native commands like <code>git init</code>, so we’ll create our own <code>git new</code> command instead (<a href="https://twitter.com/johnsyweb/status/1269881549056438272">h/t @johnsyweb</a>):</p>
<pre><code class="language-bash">git config --global alias.new '!git init &#x26;&#x26; git symbolic-ref HEAD refs/heads/main'
</code></pre>
<hr>
<p>This may be a small gesture, but anything we can do to make even one more volunteer feel more welcome in the OSS community will <em>always</em> be worth the extra 10 to 15 minutes of inconvenience on my end. ✊🏾</p>
<p>And while we’re at it, Nat… <strong>It’s time to finally <a href="https://github.com/drop-ice/dear-github-2.0">#DropICE</a>.</strong> 🧊</p>
<hr>
<h3>Further reading:</h3>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Master/slave_(technology)"><em>Master/slave (technology)</em> on Wikipedia</a></li>
<li><a href="https://mail.gnome.org/archives/desktop-devel-list/2019-May/msg00066.html">History of “master” in BitKeeper → Git</a></li>
<li><a href="https://twitter.com/jpaulreed/status/1272038656799211521">More great research into context of “master/slave” in BitKeeper</a></li>
<li><a href="https://lore.kernel.org/git/CAOAHyQwyXC1Z3v7BZAC+Bq6JBaM7FvBenA-1fcqeDV==apdWDg@mail.gmail.com/t/">Proposal to change “master” default on Git mailing list</a> (it did <strong>not</strong> go well…)</li>
<li><a href="https://github.com/django/django/pull/2692">Django removes “master/slave” terminology (2014)</a></li>
<li><a href="https://www.blacktechforblacklives.com/">Black Tech for Black Lives</a></li>
</ul>
        <p><a href="https://jarv.is/notes/github-rename-master"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[I Made A Thing, Powered by Windows Me™]]></title>
            <link>https://jarv.is/notes/y2k-sandbox</link>
            <guid isPermaLink="false">https://jarv.is/notes/y2k-sandbox</guid>
            <pubDate>Sat, 06 Jun 2020 14:05:23 GMT</pubDate>
            <description><![CDATA[Introducing the Y2K Sandbox: fully featured, fully isolated, on-demand Windows Millennium Edition® virtual machines.]]></description>
            <content:encoded><![CDATA[<p>A few months ago, I stumbled upon <a href="https://jakejarvis.github.io/my-first-website/">my first website ever</a> on an old floppy disk. Despite the instant cringing, I <a href="https://github.com/jakejarvis/my-first-website">uploaded it</a> to GitHub, <a href="/previously/">collected other iterations</a>, and made an <a href="https://github.com/jakejarvis/awesome-first-code">#awesome-list</a> of others who were brave and/or shameless enough to do the same. But why not take that ~~one~~ 1,000 steps further?</p>
<p>Introducing the <a href="https://y2k.pages.dev"><strong>Y2K Sandbox</strong></a> — with fully-featured, fully-isolated, on-demand <a href="https://www.youtube.com/watch?v=CaNDeyYP98A"><strong>Windows Millennium Edition®</strong></a> virtual machines, simply to experience my first website in its natural Internet Explorer 5 habitat. And maybe play some <a href="https://en.wikipedia.org/wiki/Full_Tilt!_Pinball#3D_Pinball_for_Windows_%E2%80%93_Space_Cadet">3D Pinball: Space Cadet</a>. Oh, and <a href="https://en.wikipedia.org/wiki/Microsoft_Bob">Microsoft Bob</a> is there too if you want to say hello and catch up. 🤓</p>
<p>
<em><a href="https://y2k.pages.dev"><strong>Play in the Y2K Sandbox, at your own risk.</strong></a></em></p>
<p>The backend is powered by <a href="https://www.qemu.org/"><strong>QEMU</strong></a> (as a Pentium III emulator) inside isolated <strong>Docker</strong> containers, <a href="https://github.com/joewalnes/websocketd"><strong>websocketd</strong></a> (an <strong><em>awesome</em></strong> lightweight WebSockets server written in Go), and <a href="https://www.cloudflare.com/products/tunnel/"><strong>Cloudflare Tunnels</strong></a> (for some protection), all tied together with some <a href="https://github.com/jakejarvis/y2k/blob/main/container/bin/boot.rb">Ruby code</a> and <a href="https://github.com/jakejarvis/y2k/tree/main/host">shell scripts</a>. ~~I’ll push the backend scripts up to GitHub once I have a chance to untangle the spaghetti code. 🍝~~</p>
<p>The frontend is <em>much</em> simpler with <a href="https://github.com/jakejarvis/y2k/blob/main/frontend/app.js">a few lines of JavaScript</a> and <a href="https://github.com/novnc/noVNC"><strong>noVNC</strong></a>, a VNC-via-WebSockets library, hosted by <a href="https://pages.cloudflare.com/"><strong>Cloudflare Pages</strong></a>.</p>
<h3>🎉 Update: <a href="https://github.com/jakejarvis/y2k">Everything is now on GitHub!</a></h3>
<p>I must give credit to both <a href="https://charlie.bz/">charlie.bz</a> and <a href="https://benjojo.co.uk/">benjojo.co.uk</a>, similar websites I was enamored with when they were posted on Hacker News a few years ago. Think we’ll see some websites like these with Windows 29 in a decade?</p>
<p>
<em><strong>@microsoft</strong> Please don’t sue me.</em></p>
<p>Feel free to <a href="https://github.com/jakejarvis/y2k/issues">open an issue on GitHub</a> if you run into connection glitches or have any nostalgic inspiration for software you think would be cool to install persistently on the OS image. I certainly can’t help with any actual Windows Me crashes, though — it was beyond help a long, long time ago. Like, <a href="https://books.google.com/books?id=Jbft8HXJZwQC&#x26;lpg=PP1&#x26;pg=PA76#v=onepage&#x26;q&#x26;f=false">the day it came out</a>. But it will always have a soft spot in my heart.</p>
<p>Anyways… quarantine boredom is a crazy thing, am I right? 😷</p>
        <p><a href="https://jarv.is/notes/y2k-sandbox"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[COVID-19 vs. the Open Source Community ⚔️]]></title>
            <link>https://jarv.is/notes/coronavirus-open-source</link>
            <guid isPermaLink="false">https://jarv.is/notes/coronavirus-open-source</guid>
            <pubDate>Mon, 23 Mar 2020 19:17:09 GMT</pubDate>
            <description><![CDATA[The open source community is rallying together like no other to provide coronavirus information to the public in innovative ways.]]></description>
            <content:encoded><![CDATA[<p>We’re all quickly learning that worldwide pandemics can bring out both <a href="https://www.vox.com/culture/2020/3/13/21179293/coronavirus-italy-covid19-music-balconies-sing">the best</a> and <a href="https://twitter.com/9NewsAUS/status/1236088663093608448">the worst</a> of humanity. But one thing has become readily apparent to me — outside of the large teams of medical professionals risking their lives right this minute, the open source community stands alone in its ability to rapidly organize in the midst of chaos to give back to the world and, in this case, make it safer for all of us.</p>
<p>These are just a few incredible open source projects that didn’t exist a few months ago, but rapidly formed teams of dozens of contributors to fill both big needs and small niches in the fight to defeat the novel coronavirus, aka <a href="https://www.cdc.gov/coronavirus/2019-nCoV/index.html"><strong>COVID-19</strong></a>.</p>
<h2><a href="https://covidtracking.com/">The COVID Tracking Project</a> </h2>
<p>Now that Americans are <em>finally</em> starting to get tested for the coronavirus, information and statistics about the results are being released state-by-state, which has led to a scattering of primary sources across the web, each releasing <a href="https://docs.google.com/document/d/1OyN6_1UeDePwPwKi6UKZB8GwNC7-kSf1-BO2af8kqVA/edit">different figures in different forms</a>. The <a href="https://covidtracking.com/">COVID Tracking Project</a> collects as much information as possible from each local health authority’s website and puts everything together in <a href="https://covidtracking.com/data/">easy-to-digest tables</a>, as well as <a href="https://docs.google.com/spreadsheets/u/2/d/e/2PACX-1vRwAqp96T9sYYq2-i7Tj0pvTf6XVHjDSMIKBdZHXiCGGdNC0ypEU9NbngS8mxea55JuCFuua1MUeOj5/pubhtml">spreadsheets</a> and a <a href="https://covidtracking.com/api/">public API</a>.</p>
<p>The maintainers are also <a href="https://covidtracking.com/about-tracker/">fully transparent</a> about their process and take great care to annotate individual figures with the methodology used to arrive at each, which has earned them the <a href="https://covidtracking.com/#press">trust</a> of even the largest national news organizations reporting on COVID-19.</p>

<h2><a href="https://findthemasks.com/">#findthemasks</a> </h2>
<p>This one might be my favorite, simply because of its laser-like focus on solving a very specific (yet catastrophic) problem. The United States is <a href="https://www.nytimes.com/2020/03/19/health/coronavirus-masks-shortage.html">already running out</a> of <a href="https://www.fda.gov/medical-devices/general-hospital-devices-and-supplies/personal-protective-equipment-infection-control">personal protective equipment (PPE)</a> for the healthcare professionals on the front lines of this crisis. <a href="https://findthemasks.com/">#findthemasks.com</a> has gathered specific donation requests and points of contact from hospitals around the country in desperate need of basic supplies.</p>
<p><em>Please</em> look up your local hospitals on <a href="https://findthemasks.com/#sites">#findthemasks</a> and follow their instructions to donate anything you have hoarded — it’s likely the single most impactful thing you can do at this point. If you don’t see your local hospital, or don’t feel comfortable shipping equipment to any hospital listed, you can also visit <a href="https://ppelink.org/ppe-donations/">PPE Link</a> and they will connect you with hospitals in your area.</p>

<h2><a href="https://staythefuckhome.com/">#StayTheFuckHome</a> </h2>
<p>I figured I’d throw in this cheeky website broadcasting a simple but serious message: <strong>STAY THE FUCK HOME!!!</strong> If you’re <em>still</em> not convinced of the importance of this “suggestion,” give their <a href="https://staythefuckhome.com/">“Self-Quarantine Manifesto”</a> a quick read. Now.</p>
<p>The <a href="https://github.com/flore2003/staythefuckhome/pulls?q=is%3Apr">GitHub community</a> has translated the instructional essay into over a dozen different languages — including a <a href="https://staythefuckhome.com/sfw/">safe-for-work version</a>, if that helps — and they’re <a href="https://github.com/flore2003/staythefuckhome#contributing">looking for more translators</a> if you’re multilingual and need something besides Netflix to fill your time with while you <strong><em>stay the fuck home!</em></strong> 😉</p>

<h2><a href="https://covid19dashboards.com/">COVID-19 Dashboards</a> </h2>
<p>This collection of various visualizations is fascinating (and sobering) to look at. If you’re smarter than I am and have experience in data analysis, their team (led by a <a href="https://github.com/hamelsmu">GitHub engineer</a>) would be more than happy to <a href="https://github.com/github/covid19-dashboard/blob/master/CONTRIBUTING.md">add your contribution</a> to the site — they’re using <a href="https://jupyter.org/">Jupyter Notebooks</a> and <a href="https://github.com/fastai/fastpages">fastpages</a>.</p>

<h2><a href="https://coronatracker.samabox.com/">CoronaTracker</a> </h2>
<p>CoronaTracker is a <em>beautiful</em> cross-platform app for iOS and macOS with intuitive maps and charts fed by reputable live data. Apple is <a href="https://developer.apple.com/news/?id=03142020a">being justifiably picky</a> about “non-official” Coronavirus apps in their App Store (<a href="https://blog.google/inside-google/company-announcements/coronavirus-covid19-response/">so is Google</a>, by the way) but you can still <a href="https://coronatracker.samabox.com/">download the macOS app directly</a> or <a href="https://github.com/MhdHejazi/CoronaTracker#1-ios-app">compile the iOS source code</a> yourself using Xcode if you wish.</p>

<h2><a href="https://stayinghome.club/">Staying Home Club</a> </h2>
<p>A bit more family-friendly than <a href="https://staythefuckhome.com/">#StayTheFuckHome</a>, the <a href="https://stayinghome.club/">Staying Home Club</a> is maintaining a running list of over a thousand companies and universities mandating that employees and students work from home, as well as events that have been canceled or moved online. Quarantining yourself might feel lonely, but here’s solid proof that you’re far from alone right now.</p>

<h2><a href="https://nextstrain.org/ncov">Nextstrain for nCoV</a> </h2>
<p>This one is a bit over my head, but apparently <a href="https://nextstrain.org/">Nextstrain</a> is a pretty impressive open-source service targeted at genome data analysis and visualization of different pathogens. Their <a href="https://nextstrain.org/ncov">COVID-19 page</a> is still awe-inspiring to look at for a layman like me, but probably a thousand times more so if you’re an actual scientist — in which case, the <a href="https://github.com/nextstrain/ncov">genome data they’ve open-sourced</a> might be of interest to you.</p>

<h2><a href="https://systems.jhu.edu/research/public-health/ncov/">Johns Hopkins 2019-nCoV Data</a> </h2>
<p>Johns Hopkins University’s <a href="https://www.arcgis.com/apps/opsdashboard/index.html#/bda7594740fd40299423467b48e9ecf6">visual COVID-19 global dashboard</a> has been bookmarked as my go-to source of information since the beginning of this crisis earlier this year. Now, JHU’s <a href="https://systems.jhu.edu/">Center for Systems Science and Engineering</a> has open-sourced <a href="https://github.com/CSSEGISandData/COVID-19">their data and analysis</a> for anybody to use.</p>

<h2><a href="https://neherlab.org/covid19/">COVID-19 Scenarios</a> </h2>
<p>COVID-19 Scenarios will probably hit everyone in a different way, depending on your levels of optimism and/or pessimism right now. It uses <a href="https://neherlab.org/covid19/about">advanced scientific models</a> to predict the future of the virus based on past data and future variables and assumptions you can tinker with yourself.</p>
<p>The maintainers at the <a href="https://neherlab.org/">Neher Lab in Basel, Switzerland</a> even have a <a href="https://github.com/neherlab/covid19_scenarios/issues/18">discussion thread</a> and an <a href="https://spectrum.chat/covid19-scenarios/general/questions-discussions~8d49f461-a890-4beb-84f7-2d6ed0ae503a">open chatroom</a> set up for both scientists and non-scientists to ask questions and post ideas, which I find really nice of them!</p>

<h2><a href="https://coronadatascraper.com/#home">Corona Data Scraper</a> </h2>
<p>Similar to the <a href="https://covidtracking.com/">COVID Tracking Project</a> above, the <a href="https://coronadatascraper.com/#home">Corona Data Scraper</a> has set up an automated process to scrape verified data from across the web to form massive CSV spreadsheets and JSON objects. They even <a href="https://github.com/lazd/coronadatascraper#source-rating">rate the quality</a> of each source to prioritize data accordingly.</p>

<h2><a href="https://foldingathome.org/covid19/">Folding@home</a> </h2>
<p><a href="https://foldingathome.org/">Folding@home</a> has been around <a href="https://en.wikipedia.org/wiki/Folding@home"><em>forever</em></a>. I remember installing it on my family’s home computer as a curious kid and making my father infuriated over how slow it got. But they <a href="https://foldingathome.org/2020/03/15/coronavirus-what-were-doing-and-how-you-can-help-in-simple-terms/">switched gears this month</a> from using our computers to crunch various proteins and molecules in the background, and all of their power is now going towards discovering unknown “folds” in the coronavirus, which might be able to lead scientists to find better cures and potential vaccines.</p>
<p>You can <a href="https://foldingathome.org/start-folding/">download their software here</a> to donate some idle computing power to their efforts — they definitely know what they’re doing by now, after pioneering en-masse distributed computing 20 years ago.</p>
<p><strong>Fun fact:</strong> The team behind Folding@home has seen a <a href="https://www.reddit.com/r/pcmasterrace/comments/flgm7q/ama_with_the_team_behind_foldinghome_coronavirus/"><strong>huge</strong> spike in computational power</a> this month after cryptominers started mining coronavirus proteins instead of boring, old Ethereum with their insanely overpowered GPUs! 👏</p>

<h2><a href="https://coronavirus-tracker-api.herokuapp.com/v2/locations">Coronavirus Tracker API</a> </h2>
<p>To wrap this list up, I thought I’d include <a href="https://github.com/ExpDev07/coronavirus-tracker-api">yet another API</a> fed by multiple data sources that you can use to create your own open-source project if any of these inspired you. This one is incredibly flexible in terms of <a href="https://github.com/ExpDev07/coronavirus-tracker-api#api-endpoints">query parameters and endpoints</a> but they all return simple JSON responses like we all know and love.</p>

<h3>Stay safe (and <a href="https://staythefuckhome.com/" title="One last time...">home</a>), friends! ❤️</h3>
        <p><a href="https://jarv.is/notes/coronavirus-open-source"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[Why I’m Dropping Dropbox]]></title>
            <link>https://jarv.is/notes/dropping-dropbox</link>
            <guid isPermaLink="false">https://jarv.is/notes/dropping-dropbox</guid>
            <pubDate>Wed, 20 Nov 2019 21:22:43 GMT</pubDate>
            <description><![CDATA[I'm finally canceling my Dropbox Pro account and moving to iCloud Drive for synchronized cloud storage.]]></description>
            <content:encoded><![CDATA[<p>I’ve been a loyal Dropbox user since its inception as a <a href="https://www.ycombinator.com/apply/dropbox/">Y Combinator startup</a> ten years ago. Having a folder on all of my devices that instantly synchronized with each other was a game-changer for me, and I grew dependent on it more and more as they gave out free storage like candy — 48 GB for having a Samsung Chromebook, 1 GB for “Posting &#x3C;3 to Twitter,” and so on — until I needed to upgrade to Dropbox Pro. But this month I canceled my Pro subscription after a few too many strikes.</p>
<p>
<em>Deleting 401,907 files from Dropbox… 😬</em></p>
<h2>Five strikes, you’re out…</h2>
<p>Decisions made by the top folks at Dropbox gave me an increasingly sour taste in my mouth over the past few years. The biggest red flags were:</p>
<ul>
<li>Removing my long-standing 48 GB promotion for Samsung Chromebooks from 2014 with little notice, offering a free 3 GB instead and preventing me from adding new files until I forked over $11.99/month for Dropbox Pro.</li>
<li>Adding a <a href="https://help.dropbox.com/account/computer-limit">3-device limit</a> for free accounts, triggering another hostage negotiation resulting in me upgrading to Pro.</li>
<li>Continuously forcing <a href="https://www.theverge.com/2019/6/11/18661673/new-dropbox-desktop-app-google-docs-slack-atlassian">bloated updates</a> to their once-simple macOS app down users’ throats, to the point where <a href="https://blog.dropbox.com/topics/product-tips/new-dropbox">“the new Dropbox”</a> was consistently eating up <em>over a gigabyte of RAM</em> and a non-negligible chunk of CPU usage thanks to an entire web browser being embedded into it:</li>
</ul>

<ul>
<li>Explicitly <a href="https://news.ycombinator.com/item?id=20844363">dropping support for symlinking</a> (aka making aliases to) files outside of the literal <code>~/Dropbox</code> folder, which was incredibly helpful for nerds — once their main audience and biggest cheerleaders — with things like <a href="https://github.com/jakejarvis/dotfiles">dotfiles</a> and Git repositories.</li>
<li>…and as a bonus, making the process of canceling Dropbox Pro incredibly convoluted, annoying, and sketchy. Here’s a video demonstration via <a href="https://twitter.com/jwyattd">Justin Dunham</a>:</li>
</ul>

<h2>Seeking an alternative…</h2>
<p>The infamous <a href="https://medium.com/swlh/the-irresistible-lure-of-the-apple-ecosystem-81bf8d66294a">Apple Ecosystem™</a> has held me firmly in its grasp for over a decade now, and the main requirement of a replacement cloud storage service for me was smooth interoperability between my MacBook, iPhone, and iPad.</p>

<p>I’ve never been a proponent of leaving all your eggs in one basket. But it’s hard to ignore the convenience of Apple’s streamlined (and <a href="https://www.imore.com/developers-encounter-major-icloud-issues-ios-13-beta">finally</a> reliable) <a href="https://www.apple.com/icloud/"><strong>iCloud Drive</strong></a>, which is already installed on all of my devices (and actually cheaper than Dropbox gigabyte-for-gigabyte, at $9.99/month for 2 TB). In fact, it’s nearly invisible on macOS: I can simply save files in my Documents or Desktop folders as I always have and they’re uploaded in the background. Git repositories now sync just fine and my files reappeared without a hitch after I recently formatted my Mac.</p>

<p>I still use (and highly recommend) <a href="https://www.backblaze.com/"><strong>Backblaze</strong></a> (<a href="https://secure.backblaze.com/r/00x84e">referral link</a>) to backup my home folder and add a second layer of redundancy to storing all of my most important files on <a href="https://www.zdnet.com/article/stop-saying-the-cloud-is-just-someone-elses-computer-because-its-not/">“someone else’s computer.”</a> And as long as I remember to plug in my external SSD every so often, they’re also backed up locally via <a href="https://support.apple.com/en-us/HT201250">Time Machine</a>.</p>
<hr>
<p>There are already a few Dropbox features I’m beginning to miss, like <a href="https://help.dropbox.com/installs-integrations/sync-uploads/selective-sync-overview">selective sync</a>, third-party integration, easier sharing, and an Android app (a man can dream, right?). But hopefully Apple continues to iterate on iCloud Drive, and it serves me well enough to not want to seek out another service for another ten years.</p>
<p>Thank you, Dropbox, for a fine relationship and for pioneering the consumer cloud storage industry. But for now, it’s just not going to work between us. 💔</p>
        <p><a href="https://jarv.is/notes/dropping-dropbox"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[Netlify Analytics Review]]></title>
            <link>https://jarv.is/notes/netlify-analytics-review</link>
            <guid isPermaLink="false">https://jarv.is/notes/netlify-analytics-review</guid>
            <pubDate>Wed, 13 Nov 2019 13:21:22 GMT</pubDate>
            <description><![CDATA[Netlify has released Netlify Analytics, a tracking tool that's the only one of its kind, prioritizing privacy and speed.]]></description>
            <content:encoded><![CDATA[<p>I’ve been trying out <a href="https://www.netlify.com/products/analytics/">Netlify Analytics</a> on this site for over a month now and have some quick thoughts about this unique offering in a world full of bloated and invasive tracking scripts.</p>

<h2>👍  Pros</h2>
<p>Pretty much all of the benefits of Netlify Analytics stem from the fact that it’s <strong>purely server-side software</strong>. This is what singularly sets it apart from <a href="https://analytics.google.com/analytics/web/">Google Analytics</a> — by <em>far</em> the <a href="https://trends.builtwith.com/analytics/Google-Analytics">status quo</a> — and even self-hosted, open-source applications I’ve tried like <a href="https://github.com/matomo-org/matomo">Matomo</a> and <a href="https://github.com/usefathom/fathom">Fathom</a>.</p>
<h3>⚡  Speed</h3>
<p>To start using Netlify Analytics, you press a few buttons on the Netlify dashboard and voilà. No need to copy and paste some obfuscated JavaScript snippet into the <code>&#x3C;head></code> of each page, which is a painful task for those of us who care about speed and efficiency on the web.</p>
<p>On top of sending yet another DNS request to one of Google’s domains — and more HTTP payloads for each outgoing click, file downloaded, etc. — Google’s <code>analytics.js</code> script is currently 43 KB. For a site like <a href="https://www.nytimes.com/">nytimes.com</a>, which transfers <strong>nearly 20 MB</strong> on its homepage, this is negligible. But for simple sites like mine, which I’ve <a href="https://gtmetrix.com/reports/jarv.is/uOzCBKlv">painstakingly optimized</a> (mostly for fun, don’t judge), that doubles the size of my homepage. Matomo’s script, weighing in at 65 KB, made it even worse.</p>
<h3>🕵️‍♂️  Privacy</h3>
<p>This is the big one.</p>
<p>In the age of GDPR (the <a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation">General Data Protection Regulation</a> in Europe), when using analytics tools and trackers without popping up a <a href="https://www.freeprivacypolicy.com/blog/cookie-consent-examples/">cookie consent prompt</a> on each new visit can get you fined millions of euros, Netlify Analytics stands alone. Netlify promises its product is <a href="https://docs.netlify.com/monitor-sites/analytics/">fully GDPR compliant</a>. CEO Matt Biilmann <a href="https://thenewstack.io/the-netlify-web-platform-adds-server-side-analytics-aimed-at-privacy/">explains</a>:</p>
<blockquote>
<p>“One of the things that has come out of GDPR is that a lot of large companies do intensive tracking of individual users — running scripts across a lot of different sites that capture a lot of detailed information from the browser. That puts you in the position where you have a ton of data on all kinds of people.”</p>
</blockquote>
<p>And even outside of Europe, scrapping the tracking scripts on your site just makes you a courteous netizen (God, I hate that word). Not only does Google Analytics provide <em>you</em> with detailed information on your visitors; by default, you’re also sharing that data with Google itself, to the point where they can pinpoint your <a href="https://support.google.com/analytics/answer/2799357?hl=en">age, gender, and even your interests</a> by cross-referencing data with your Google account and your behavior on other sites using Google Analytics.</p>
<p>Instead, Netlify Analytics pulls and compiles data from server logs on each of their CDN edge nodes, rather than having the visitor’s browser push data about itself back up to a third-party’s endpoint.</p>
<p>Netlify does store some short-term data, like IP addresses, as any normal hosting provider does. But for the purposes of analytics, the data is anonymized and only used to determine things like unique visitors vs. individual page views — and not shown to the customer. <a href="https://www.netlify.com/gdpr/">Netlify’s DPA</a> (Data Processing Agreement) is one of the most conservative I’ve seen on the web.</p>
<h3>🛑  AdBlock Immunity</h3>
<p>Ad blocking is becoming commonplace on the World Wide Web with <a href="https://www.statista.com/statistics/804008/ad-blocking-reach-usage-us/">over 25% of users</a> reportedly installing extensions to do so as soon as their new browser touches the net. And for good reason, since most of them also <a href="https://moz.com/blog/analytics-black-holes">block cross-site tracking scripts</a> like Google’s by default.</p>
<p>That’s a <em>huge</em> chunk of visitors missing that Netlify Analytics gains back for you — and probably far more if your audience is tech-savvy like those reading this post likely are. (Some might even <a href="https://www.gnu.org/philosophy/javascript-trap.en.html">block JavaScript completely</a> using extensions like <a href="https://addons.mozilla.org/en-US/firefox/addon/noscript/">NoScript</a>.)</p>

<p>Another tangential benefit you simply don’t get from JavaScript-based tools like Google Analytics is the “Resources Not Found” box, which separates out URLs that resulted in a 404 Not Found error. Because of the 404 tracking, I discovered how many people were still subscribed to my posts via RSS from when I used WordPress <em>years</em> ago, and I was able to redirect <code>/feed</code> and <code>/rss</code> to the new location.</p>
<p><em>Side note: This section has also become cluttered with requests from script kiddies who are scanning the internet for files like <code>login.php</code> and <code>/wp-admin</code> and <code>AspCms_Config.asp</code> (huh?) — but that’s a whole separate problem for another day.</em></p>
<h2>👎  Cons</h2>
<h3>💰  Price</h3>
<p>Netlify is one of the most awesome free-as-in-beer services on the web today, providing a fast CDN and instant deployments at zero cost (up to a pretty insane amount, of course). But if you want to add Netlify Analytics, your bill suddenly jumps to <a href="https://www.netlify.com/pricing/#analytics">$9 a month</a>. <strong>Nine dollars!</strong> That’s over <strong>$100 per year!</strong> If you have more than 250,000 visitors per month, the cost can be even higher (to the point where you’ll need to contact Netlify’s sales team).</p>
<p>It makes sense that Netlify needs to subsidize the cost of providing free enterprise-grade web hosting for the rest of its non-enterprise users to stay alive. But when Google Analytics is free, this is a pretty tough ask for any hobbyist — even if Google is <a href="https://support.google.com/analytics/answer/1011397?hl=en">getting more from them</a> than they are from Google. 😬</p>
<h3>📈  Accuracy</h3>

<p>Clearly, as much as I wish they did, 60,000+ visitors didn’t type my website directly into the URL bar in the past month. Some of my articles have been circulating on Hacker News, Reddit, Twitter, etc. — none of which have even made a blip on the dashboard.</p>
<p>There are various possible reasons that referrers aren’t being sent, mostly relating to HTTP headers and <a href="https://blog.mozilla.org/blog/2019/06/04/firefox-now-available-with-enhanced-tracking-protection-by-default/">increasingly sensible</a> browser defaults, that aren’t Netlify’s fault. But this section is the most obvious example of important data you can miss out on by not tracking incoming visitors via JavaScript.</p>
<p>Another benefit of using Google’s own analytics service becomes glaringly apparent here: I have <strong>no idea</strong> which search terms were used to reach which page. Netlify could mitigate this a bit by separating out referrers for each individual page, though, so at least I’d know which pages were having the most organic success on search engines.</p>
<p>One more note: since Netlify doesn’t process IP addresses or user agents, bots crawling your site (like <a href="https://support.google.com/webmasters/answer/182072?hl=en">Googlebot</a> and <a href="https://www.bing.com/webmaster/help/which-crawlers-does-bing-use-8c184ec0">Bingbot</a>) get counted towards your stats, possibly overinflating your ego a little more than it should.</p>
<h3>⏱️  Historical Data</h3>

<p>Trying out Netlify Analytics meant switching this site from <a href="https://pages.github.com/">GitHub Pages</a> to Netlify — something I still have mixed feelings about. But if I had been on Netlify the entire time, I would have gotten thirty days of historical stats backfilled right off the bat, from before I even started paying for Analytics.</p>
<p>Sure, this is a cool bonus. However, “thirty days” has another meaning on Netlify Analytics: it’s the <strong>absolute maximum amount of data</strong> you can access. Period, full stop. On your Analytics dashboard, you can see a window of the past month on your site — and that’s all. Day 31 is gone, seemingly forever.</p>
<p>I hope Netlify proves me wrong in Version 2, since analyzing trends over the course of a year (or two, or five) is an <strong>integral reason</strong> to track visitor behavior in the first place. Otherwise, it’s nearly impossible to tell which piece of content or which new feature caused your website to explode in popularity, unless you’re meticulously watching it happen in real time.</p>
<hr>
<p>I’m <em>super</em> happy to see an investment in privacy-minded solutions for analytics, and <a href="https://www.youtube.com/watch?v=jMo0oQwTVak">the Netlify team should be proud</a> of what they’ve built. And for the time being, I’m willing to continue forking over the nine bucks per month to give Netlify a chance to keep building upon this awesome (and, dare I say, <a href="https://www.theverge.com/2016/9/7/12838024/apple-iphone-7-plus-headphone-jack-removal-courage">courageous</a>) concept. But only time will tell if others are willing to do the same — and if they are, how long they’re willing to wait before resorting back to injecting bloated JavaScript snippets and hoarding invasive amounts of our data to share with the <a href="https://www.google.com/">behemoths of the internet</a>.</p>
<p>Hopefully it happens within a window of 30 days, though, or else Netlify will be none the wiser! 😉</p>
        <p><a href="https://jarv.is/notes/netlify-analytics-review"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[Ranking 2020 Presidential Candidates’s 404 Pages]]></title>
            <link>https://jarv.is/notes/presidential-candidates-404-pages</link>
            <guid isPermaLink="false">https://jarv.is/notes/presidential-candidates-404-pages</guid>
            <pubDate>Wed, 30 Oct 2019 17:58:39 GMT</pubDate>
            <description><![CDATA[Each of the 2020 presidential candidates's 404 Not Found pages, ranked.]]></description>
            <content:encoded><![CDATA[<p>
<em>President Barack H. Obama, probably ranking some of these 404 pages.</em></p>
<p>Ever since <a href="https://arstechnica.com/information-technology/2012/11/built-to-win-deep-inside-obamas-campaign-tech/">President Obama injected technology</a> into presidential politics in a historic way, one of the few bright spots of the incredibly long and exhausting race for me has been inspecting each candidate’s campaign website. They end up revealing a great deal about how much each of them is willing to invest in the internet, and how young and innovative (and potentially funny) the staff members they attract are.</p>
<p>More recently, though, little-known hidden Easter eggs on <a href="https://en.wikipedia.org/wiki/HTTP_404">“404 Not Found”</a> pages have become an outlet for the candidates’ overworked web designers to let out some humor in a sea of otherwise serious policy pages. Below are the 404 pages on each of the current candidate’s websites, along with my ranking of them — setting politics aside, for once.</p>
<h2>1. Elizabeth Warren — <a href="https://elizabethwarren.com/asdfasdf404">elizabethwarren.com</a></h2>
<p>I’m a <em>huge</em> sucker for Kate McKinnon’s spot-on impression of Warren on Saturday Night Live. And <a href="https://twitter.com/realdonaldtrump/status/1097116612279316480">unfortunately</a>, seeing a campaign embrace SNL is like a breath of fresh air these days. <a href="https://www.nbc.com/saturday-night-live/cast/kate-mckinnon-15056/impersonation/elizabeth-warren-287903">Watch all of the Kate McWarren videos so far here; you won’t regret it.</a></p>

<h2>2. Bernie Sanders — <a href="https://berniesanders.com/asdfasdf404/">berniesanders.com</a></h2>
<p>Although the designer who selected this GIF likely had <em>thousands</em> of choices when searching “<a href="https://www.google.com/search?q=Bernie+finger+wagging+GIF&#x26;tbm=isch&#x26;tbs=itp:animated">Bernie finger wagging GIF</a>,” the text beside it is well-written and funny — even though we both know putting a page at <a href="https://berniesanders.com/zxcliaosid/">berniesanders.com/zxcliaosid</a> probably won’t be a top priority of a President Sanders.</p>

<h2>3. Joe Biden — <a href="https://joebiden.com/asdfasdf404">joebiden.com</a></h2>
<p>Uncle Joe has a nice and simple 404 page. I like it, along with the Ray-Bans and his choice of vanilla ice cream.</p>

<h2>4. Beto O’Rourke — <a href="https://betoorourke.com/asdfasdf404">betoorourke.com</a></h2>
<p>A ballsy move, considering Beto’s infamous <a href="https://www.politifact.com/texas/statements/2019/mar/14/club-growth/beto-orourke-arrested-dwi-flee-scene/">DUI arrest</a> in the ’90s — but still a clever ask for a donation and a great use of a GIF, even if it’s left over from his Senate campaign.</p>

<h2>5. Kamala Harris — <a href="https://kamalaharris.org/asdfasdf404">kamalaharris.org</a></h2>
<p>Another clean and simple page with a top-notch GIF. It injected some emotion into visiting <a href="https://kamalaharris.com/alskdjf">kamalaharris.com/alskdjf</a>.</p>

<h2>6. Pete Buttigeg — <a href="https://peteforamerica.com/asdfasdf404/">peteforamerica.com</a></h2>
<p>I love, love, <em>love</em> Pete’s design for his whole campaign, and his beautiful 404 page is no exception. In case you didn’t know, Pete for America has an entire <a href="https://design.peteforamerica.com/">“Design Toolkit”</a> publicly available for all to view and use, with really cool and in-depth explanations for all of their choices — even their <a href="https://design.peteforamerica.com/colors">color palette</a>. Very progressive indeed.</p>

<h2>7. Cory Booker — <a href="https://corybooker.com/asdfasdf404/">corybooker.com</a></h2>
<p>Love the photo choice. But although pains me to go against my Senator from my home state, I still <em>cannot stand</em> his choice of font. Oh well, I guess that’s now a criterion for running for president in 2020.</p>

<h2>8. Andrew Yang — <a href="https://www.yang2020.com/asdfasdf404">yang2020.com</a></h2>
<p>Not sure if donating to Yang 2020 will help put a page at <a href="https://www.yang2020.com/alsdjfzoif">yang2020.com/alsdjfzoif</a> — the actual URL I visited to grab this screenshot — but the Bitmoji Andrew looks pretty chill.</p>

<h2>9. Amy Klobuchar — <a href="https://amyklobuchar.com/asdfasdf404">amyklobuchar.com</a></h2>
<p>This is the 404 page of someone who won’t forget the <a href="https://en.wikipedia.org/wiki/Uff_da">Midwestern roots</a> she comes from once she moves into the White House…or writes a memoir about her campaign from her Minnesota home.</p>

<h2>10. Steve Bullock — <a href="https://stevebullock.com/asdfasdf404">stevebullock.com</a></h2>
<p>I’ll never publicly say anything against a good Dad joke. This is no exception.</p>

<h2>11. Michael Bennet — <a href="https://michaelbennet.com/asdfasdf404">michaelbennet.com</a></h2>
<p>Another quality Dad joke here.</p>

<h2>12. John Delaney — <a href="https://www.johndelaney.com/asdfasdf404">johndelaney.com</a></h2>
<p>Yet another Dad joke? I honestly had the hardest time ranking these three.</p>

<h2>13. Marianne Williamson — <a href="https://www.marianne2020.com/asdfasdf404">marianne2020.com</a></h2>
<p>A 404 page only a motivational author and speaker running for president could envision.</p>

<h2>14. The Donald — <a href="https://donaldjtrump.com/asdfasdf404">donaldjtrump.com</a></h2>
<p>I guess this would be slightly humorous…four years ago. Time to move on from your middle-school crush, Donny.</p>

<hr>
<h2>Disqualified Candidates…</h2>
<p>These candidates haven’t configured a custom 404 page, settling for the default Drupal or WordPress text. Do they <em>really</em> think they can run the free world with their websites in this shape? 🙄 <em>&#x3C;/s></em></p>
<h3>15. Julián Castro — <a href="https://www.julianforthefuture.com/asdfasdf404">julianforthefuture.com</a></h3>

<h3>16. Wayne Messam — <a href="https://wayneforusa.com/asdfasdf404">wayneforusa.com</a></h3>

<h3>17. Tulsi Gabbard — <a href="https://www.tulsi2020.com/asdfasdf404">tulsi2020.com</a></h3>

<h3>18. Joe Sestak — <a href="https://www.joesestak.com/asdfasdf404">joesestak.com</a></h3>
        <p><a href="https://jarv.is/notes/presidential-candidates-404-pages"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[I ❤️ GitHub Actions]]></title>
            <link>https://jarv.is/notes/github-actions</link>
            <guid isPermaLink="false">https://jarv.is/notes/github-actions</guid>
            <pubDate>Fri, 25 Oct 2019 17:58:39 GMT</pubDate>
            <description><![CDATA[I've found a new hobby of making cool GitHub Actions, the latest tool in the CI world. Here's why.]]></description>
            <content:encoded><![CDATA[<p>Since being accepted into the beta for <a href="https://github.com/features/actions">GitHub Actions</a> a few months ago, I’ve found a new side hobby of whipping up new (and ideally creative) actions for anybody to add to their CI pipeline. Actions are modular steps that interact with a GitHub repository and can be coded with <a href="https://github.com/actions/hello-world-docker-action">Docker</a> or <a href="https://github.com/actions/hello-world-javascript-action">JavaScript/Node</a> — and either way, they can be as <a href="https://github.com/jakejarvis/wait-action">simple</a> or as <a href="https://github.com/jakejarvis/lighthouse-action">complex</a> as you want. But in both cases, they’re incredibly fun to make and the results always scratch my itch for instant gratification.</p>
<p>My favorite so far is my <a href="https://github.com/jakejarvis/lighthouse-action">Lighthouse Audit action</a>, which spins up a headless Google Chrome instance in an Ubuntu container and runs <a href="https://developers.google.com/web/tools/lighthouse">Google’s Lighthouse tool</a>, which scores webpages on performance, accessibility, SEO, etc. and provides actual suggestions to improve them. It’s a perfect example of the power of combining containers with Git workflows.</p>
<p>
<em>The results of a Lighthouse audit on this website, after running tests in a headless Google Chrome.</em></p>
<p>It’s also been a fantastic avenue to dip my feet into the collaborative nature of GitHub and the open-source community. I’ve made some small apps in the past but these are the first projects where I’m regularly receiving new issues to help out with and impressive pull requests to merge. It’s a great feeling!</p>
<p>Here are the actions I’ve made so far, sorted by popularity as of this posting:</p>
<ul>
<li><strong><a href="https://github.com/jakejarvis/lighthouse-action">💡 🏠 Lighthouse Audit</a></strong> — Run a <a href="https://developers.google.com/web/tools/lighthouse">Google Chrome Lighthouse</a> audit on a webpage.</li>
<li><strong><a href="https://github.com/jakejarvis/s3-sync-action">🔄 🧺 S3 Bucket Sync</a></strong> — Sync/upload a directory with a remote AWS S3 bucket.</li>
<li><strong><a href="https://github.com/jakejarvis/cloudflare-purge-action">🗑️ Cloudflare Purge Cache</a></strong> — Purge a website’s cache via the Cloudflare API.</li>
<li><strong><a href="https://github.com/jakejarvis/hugo-build-action">✏️ Hugo Build</a></strong> — The static site generator <a href="https://github.com/gohugoio">Hugo</a> as an action, with support for legacy versions and extended features.</li>
<li><strong><a href="https://github.com/jakejarvis/firebase-deploy-action">🔥 Firebase Deploy</a></strong> — Deploy a static site to <a href="https://firebase.google.com/docs/hosting">Firebase Hosting</a>.</li>
<li><strong><a href="https://github.com/jakejarvis/backblaze-b2-action">🔄 Backblaze B2 Sync</a></strong> — Sync a directory with a remote <a href="https://www.backblaze.com/b2/cloud-storage.html">Backblaze B2</a> storage bucket.</li>
<li><strong><a href="https://github.com/jakejarvis/wait-action">💤 Wait</a></strong> — A very, very simple action to sleep for a given amount of time (10s, 2m, etc.)</li>
</ul>
<hr>
<p>As an example of an <em>extremely</em> simple (and almost completely unnecessary) action, the <a href="https://github.com/jakejarvis/wait-action">Wait action</a> takes one input — a unit of time — and has the pipeline sleep for that amount of time. The <a href="https://github.com/jakejarvis/wait-action/blob/master/Dockerfile"><code>Dockerfile</code></a> is as simple as this:</p>

<p>…with a super-short <a href="https://github.com/jakejarvis/wait-action/blob/master/entrypoint.sh"><code>entrypoint.sh</code></a>:</p>

<p>Using an action is also surprisingly simple, and more intuitive than <a href="https://travis-ci.com/">Travis CI</a> or <a href="https://circleci.com/">CircleCI</a>, in my humble opinion. Pipelines in GitHub Actions are called <a href="https://help.github.com/en/github/automating-your-workflow-with-github-actions/configuring-a-workflow">“workflows,”</a> and live in a file with <a href="https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions">YAML syntax</a> in <code>.github/workflows</code>. An example of a <code>workflow.yml</code> file that uses the above action to wait 10 seconds (on both pushes and pull requests) would look something like:</p>

<hr>
<p>For a more complex example, when I forked <a href="https://github.com/gohugoio/hugo">Hugo</a> (the static site generator used to build this website) to make some small personalized changes, I also translated <a href="https://github.com/gohugoio/hugo/blob/master/.travis.yml">their <code>.travis.yml</code> file</a> into a <a href="https://github.com/jakejarvis/hugo-custom/blob/master/.github/workflows/workflow.yml"><code>workflow.yml</code> file</a> for practice, which simultaneously runs comprehensive unit tests on <strong>three operating systems</strong> (Ubuntu 18.04, Windows 10, and macOS 10.14) with the latest two Go versions <em>each!</em> If the tests are all successful, it builds a Docker image and pushes it to both <a href="https://hub.docker.com/r/jakejarvis/hugo-custom">Docker Hub</a> and the <a href="https://github.com/jakejarvis/hugo-custom/packages">GitHub Package Registry</a> (also <a href="https://github.com/features/package-registry">in beta</a>).</p>

<p>Then another workflow, which <a href="https://github.com/jakejarvis/jarv.is/blob/master/.github/workflows/gh-pages.yml">lives in this website’s repository</a>, pulls that Docker image, builds the Hugo site, and pushes it to GitHub Pages. All astoundingly fast. All for free.</p>
<hr>
<p>A plethora of actions is already published on the <a href="https://github.com/marketplace?type=actions">GitHub Marketplace</a>, with dozens more being added every week. If you are not yet in the beta, I urge you to <a href="https://github.com/features/actions">sign up here</a> and give it a shot. GitHub has been very <a href="https://github.community/t5/GitHub-Actions/bd-p/actions">receptive to feedback</a> (so far) and I can’t wait to see GitHub Actions evolve into an enterprise-grade CI tool at the level of other competitors in this space. ❤️</p>
        <p><a href="https://jarv.is/notes/github-actions"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[My First Code: Jake’s Bulletin Board]]></title>
            <link>https://jarv.is/notes/my-first-code</link>
            <guid isPermaLink="false">https://jarv.is/notes/my-first-code</guid>
            <pubDate>Tue, 01 Oct 2019 12:34:25 GMT</pubDate>
            <description><![CDATA[My first full coding project ever: a PHP bulletin board creatively titled Jake's Bulletin Board, circa 2003.]]></description>
            <content:encoded><![CDATA[<p>I recently published my terrible, horrible, no good, very bad <a href="https://jakejarvis.github.io/my-first-website/">first HTML site</a> and <a href="https://github.com/jakejarvis/jbb#readme">first PHP project</a> ever and developed a new addiction to Web 1.0 nostalgia, fed by others who were brave enough to do the same.</p>
<p>So, I started compiling an <a href="https://github.com/jakejarvis/awesome-first-code">awesome-list of other “first code” on GitHub</a>. It was originally aimed towards those of us who grew up in the Geocities and FrontPage and Macromedia Flash era, but coders of all ages are welcome to dust off that floppy disk or 256MB USB thumb drive (or the <a href="https://archive.org/web/">Wayback Machine</a>, if you can remember your first screen name 😬) and commit your first project unmodified to GitHub for posterity — and proudly <a href="https://github.com/jakejarvis/awesome-first-code/edit/master/readme.md">link to it</a> on the list! (I’m trying very hard to make this a cool trend, if you couldn’t tell.)</p>
<p>Hopefully we can all look back at our first projects and be proud of how far we’ve come since then — no embarrassment allowed! Okay, maybe a little is fine…</p>
<hr>
<p>
<em><a href="https://github.com/jakejarvis/jbb">Jake’s Bulletin Board</a></em></p>
<p>Aside from my <a href="https://jakejarvis.github.io/my-first-website/">first HTML creation</a> (circa 2001), my first real coding project was in 2003: a PHP 4 masterpiece creatively titled <strong>Jake’s Bulletin Board</strong>. I’ve published the <a href="https://github.com/jakejarvis/jbb">source code in full on GitHub</a> for your viewing pleasure and highlighted the best/worst parts below.</p>
<h2>Usage</h2>
<p>If you’re bored on a rainy day, potential activities could include:</p>
<ul>
<li>Easiest code review you’ll do in your entire career. (Or hardest, depending on your attitude.)</li>
<li>Hacking speed-runs to boost your infosec self-esteem.</li>
<li>Beating the <a href="http://goldenbookofrecords.com/longest-laughter/">world record for longest laugh</a>, currently held by Mr. Belachew Girma of Ethiopia with 3 hours and 6 minutes.</li>
<li>Actually getting this to run in 2019.</li>
</ul>
<h2>Embarrassing Highlights</h2>
<p>Who cares if somebody wants to delete a post with the ID “<code>*</code>” no matter the author? (<a href="https://github.com/jakejarvis/jbb/blob/87b606797414b2fe563af85e269566fc5e076cc5/delete_reply_submit.php#L9">delete_reply_submit.php</a>)</p>
<pre><code class="language-php">&#x3C;?php
  $query2 = "DELETE FROM jbb_replies
WHERE replyID ='$replyID'";
$result2 = mysql_query ($query2)
        or die ($query2);
?>
</code></pre>
<p>Sessions based on storing an auto-incremented user ID in a cookie. (<a href="https://github.com/jakejarvis/jbb/blob/87b606797414b2fe563af85e269566fc5e076cc5/login_submit.php#L28">login_submit.php</a>)</p>
<pre><code class="language-php">&#x3C;?php
session_id($user->userID);
session_start();
$_SESSION["ck_userID"] = $user->userID;
$_SESSION["ck_username"] = $user->username;
$_SESSION["ck_groupID"] = $user->groupID;
?>
</code></pre>
<p>Viewing a “private” message based solely on a sequential message ID. (<a href="https://github.com/jakejarvis/jbb/blob/87b606797414b2fe563af85e269566fc5e076cc5/pm_view.php#L13">pm_view.php</a>)</p>
<pre><code class="language-php">&#x3C;?php
$query1 = "SELECT * FROM jbb_pm WHERE pmID = '$pmID'";
?>
</code></pre>
<p>Incredibly ambitious emoticon and <a href="https://en.wikipedia.org/wiki/BBCode">BBCode</a> support. I honestly can’t begin to explain this logic. (<a href="https://github.com/jakejarvis/jbb/blob/87b606797414b2fe563af85e269566fc5e076cc5/functions.php#L18">functions.php</a>)</p>
<pre><code class="language-php">&#x3C;?php
$replacement = '&#x3C;img src=images/emoticons/smile.gif>';
$replacement2 = '&#x3C;img src=images/emoticons/bigsmile.gif>';
$replacement3 = '&#x3C;img src=images/emoticons/frown.gif>';
$replacement4 = '&#x3C;img src=images/emoticons/crying.gif>';
$replacement5 = '&#x3C;img src=images/emoticons/blush.gif>';
// ... yada yada yada ...
$replacement21 = '&#x3C;a href="';
$replacement22 = '">';
$replacement23 = '&#x3C;/a>';
$replacement24 = '&#x3C;FONT COLOR="';
$replacement25 = '&#x3C;/FONT>';
$replacement26 = '&#x3C;FONT SIZE="';
$replacement27 = '&#x3C;BR>';

$topicval = str_replace(':)', $replacement, $topicval);
$topicval = str_replace(':D', $replacement2, $topicval);
$topicval = str_replace(':(', $replacement3, $topicval);
$topicval = str_replace(':crying:', $replacement4, $topicval);
$topicval = str_replace(':blush:', $replacement5, $topicval);
// you get the point...
$topicval = str_replace('[URL=', $replacement21, $topicval);
$topicval = str_replace(':]', $replacement22, $topicval);
$topicval = str_replace('[/URL]', $replacement23, $topicval);
$topicval = str_replace('[FONT COLOR=', $replacement24, $topicval);
$topicval = str_replace('[/FONT]', $replacement25, $topicval);
$topicval = str_replace('[FONT SIZE=', $replacement26, $topicval);
$topicval = str_replace('
', $replacement27, $topicval);

// repeated five more times throught the code...
?>
</code></pre>
<p>Saving new passwords as plaintext — probably the least problematic problem. (<a href="https://github.com/jakejarvis/jbb/blob/87b606797414b2fe563af85e269566fc5e076cc5/register_submit.php#L10">register_submit.php</a>)</p>
<pre><code class="language-php">&#x3C;?php
$query = "INSERT INTO jbb_users (username, password, email, avatar) VALUES ('$username','$password','$email','images/avatars/noavatar.gif')";
?>
</code></pre>
<p>I guess I gave up on counting <code>$query</code>s by ones… (<a href="https://github.com/jakejarvis/jbb/blob/87b606797414b2fe563af85e269566fc5e076cc5/functions.php#L231">functions.php</a>)</p>
<pre><code class="language-php">&#x3C;?php
while ($topic = mysql_fetch_object($result30)) {
    $query40 = "SELECT * FROM jbb_users WHERE userID = '$topic->userID'";
    $result20 = mysql_query($query40)
        or die ($query40);

    $query50 = "SELECT * FROM jbb_replies WHERE replyID = '$replyID'";
    $result50 = mysql_query($query50)
        or die ($query50);

    $reply = mysql_fetch_object($result50);

    $query60 = "SELECT * FROM jbb_users WHERE userID = '$reply->userID'";
    $result60 = mysql_query($query60)
        or die ($query60);

    $user = mysql_fetch_object($result60);

    $query7 = "SELECT * FROM jbb_topics WHERE userID = '$reply->userID'";
    $result7 = mysql_query($query7)
        or die ($query7);

    $query8 = "SELECT * FROM jbb_replies WHERE userID = '$reply->userID'";
    $result8 = mysql_query($query8)
        or die ($query8);

    $usertopics = mysql_numrows($result7);

    $userreplies = mysql_numrows($result8);
}
?>
</code></pre>
<p>The installation “wizard” (that’s the joke, I presume…) (<a href="https://github.com/jakejarvis/jbb/blob/87b606797414b2fe563af85e269566fc5e076cc5/setup/sql_submit.php">sql_submit.php</a>)</p>
<p>
<em>JBB Installation Wizard</em></p>
<p>And finally, JBB’s actual interface… or literally as much of it as I could get to function in 2019. (<a href="https://github.com/jakejarvis/jbb/blob/87b606797414b2fe563af85e269566fc5e076cc5/index.php">index.php</a>)</p>
<p>
<em>JBB Homepage</em></p>
<p>
<em>JBB Post</em></p>
        <p><a href="https://jarv.is/notes/my-first-code"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[Fascinating & Frightening Shodan Search Queries (AKA: The Internet of Sh*t)]]></title>
            <link>https://jarv.is/notes/shodan-search-queries</link>
            <guid isPermaLink="false">https://jarv.is/notes/shodan-search-queries</guid>
            <pubDate>Thu, 19 Sep 2019 13:56:10 GMT</pubDate>
            <description><![CDATA[I've collected some interesting and scary search queries for Shodan, the internet-of-things search engine. Some return fun results, while others return serious vulnerabilities.]]></description>
            <content:encoded><![CDATA[<p>Over time, I’ve collected an assortment of interesting, funny, and depressing search queries to plug into <a href="https://www.shodan.io/">Shodan</a>, the (<a href="https://www.vice.com/en_uk/article/9bvxmd/shodan-exposes-the-dark-side-of-the-net">literal</a>) internet search engine. Some return facepalm-inducing results, while others return serious and/or ancient vulnerabilities in the wild.</p>
<p>
<em><a href="https://account.shodan.io/register"><strong>Most search filters require a Shodan account.</strong></a></em></p>
<p>You can assume these queries only return unsecured/open instances when possible. For your own legal benefit, do not attempt to login (even with default passwords) if they aren’t! Narrow down results by adding filters like <code>country:US</code> or <code>org:"Harvard University"</code> or <code>hostname:"nasa.gov"</code> to the end.</p>
<p>The world and its devices are quickly becoming more connected through the shiny new <a href="https://motherboard.vice.com/en_us/topic/internet-of-shit">Internet of ~~Things~~ Sh*t</a> — and exponentially <a href="https://blog.malwarebytes.com/101/2017/12/internet-things-iot-security-never/">more dangerous</a> as a result. To that end, I hope this list spreads awareness (and, quite frankly, pant-wetting fear) rather than harm.</p>
<p><strong>And as always, <a href="https://www.bugcrowd.com/resource/what-is-responsible-disclosure/">discover and disclose responsibly</a>! 😊</strong></p>
<hr>
<h3>Table of Contents:</h3>
<ul>
<li><a href="#industrial-control-systems">Industrial Control Systems</a></li>
<li><a href="#remote-desktop">Remote Desktop</a></li>
<li><a href="#network-infrastructure">Network Infrastructure</a></li>
<li><a href="#network-attached-storage-nas">Network Attached Storage (NAS)</a></li>
<li><a href="#webcams">Webcams</a></li>
<li><a href="#printers-copiers">Printers &#x26; Copiers</a></li>
<li><a href="#home-devices">Home Devices</a></li>
<li><a href="#random-stuff">Random Stuff</a></li>
</ul>
<hr>
<h2>Industrial Control Systems</h2>
<h3>Samsung Electronic Billboards <a href="https://www.shodan.io/search?query=%22Server%3A+Prismview+Player%22">🔎 →</a></h3>
<pre><code>"Server: Prismview Player"
</code></pre>

<h3>Gas Station Pump Controllers <a href="https://www.shodan.io/search?query=%22in-tank+inventory%22+port%3A10001">🔎 →</a></h3>
<pre><code>"in-tank inventory" port:10001
</code></pre>

<h3>Automatic License Plate Readers <a href="https://www.shodan.io/search?query=P372+%22ANPR+enabled%22">🔎 →</a></h3>
<pre><code>P372 "ANPR enabled"
</code></pre>

<h3>Traffic Light Controllers / Red Light Cameras <a href="https://www.shodan.io/search?query=mikrotik+streetlight">🔎 →</a></h3>
<pre><code>mikrotik streetlight
</code></pre>
<h3>Voting Machines in the United States <a href="https://www.shodan.io/search?query=%22voter+system+serial%22+country%3AUS">🔎 →</a></h3>
<pre><code>"voter system serial" country:US
</code></pre>
<h3>Telcos Running <a href="https://www.cisco.com/c/en/us/td/docs/switches/lan/catalyst6500/ios/12-2SX/lawful/intercept/book/65LIch1.html">Cisco Lawful Intercept</a> Wiretaps <a href="https://www.shodan.io/search?query=%22Cisco+IOS%22+%22ADVIPSERVICESK9_LI-M%22">🔎 →</a></h3>
<pre><code>"Cisco IOS" "ADVIPSERVICESK9_LI-M"
</code></pre>
<p>Wiretapping mechanism outlined by Cisco in <a href="https://tools.ietf.org/html/rfc3924">RFC 3924</a>:</p>
<blockquote>
<p>Lawful intercept is the lawfully authorized interception and monitoring of communications of an intercept subject. The term “intercept subject” […] refers to the subscriber of a telecommunications service whose communications and/or intercept related information (IRI) has been lawfully authorized to be intercepted and delivered to some agency.</p>
</blockquote>
<h3>Prison Pay Phones <a href="https://www.shodan.io/search?query=%22%5B2J%5BH+Encartele+Confidential%22">🔎 →</a></h3>
<pre><code>"[2J[H Encartele Confidential"
</code></pre>
<h3><a href="https://www.tesla.com/powerpack">Tesla PowerPack</a> Charging Status <a href="https://www.shodan.io/search?query=http.title%3A%22Tesla+PowerPack+System%22+http.component%3A%22d3%22+-ga3ca4f2">🔎 →</a></h3>
<pre><code>http.title:"Tesla PowerPack System" http.component:"d3" -ga3ca4f2
</code></pre>

<h3>Electric Vehicle Chargers <a href="https://www.shodan.io/search?query=%22Server%3A+gSOAP%2F2.8%22+%22Content-Length%3A+583%22">🔎 →</a></h3>
<pre><code>"Server: gSOAP/2.8" "Content-Length: 583"
</code></pre>
<h3>Maritime Satellites <a href="https://www.shodan.io/search?query=%22Cobham+SATCOM%22+OR+%28%22Sailor%22+%22VSAT%22%29">🔎 →</a></h3>
<p>Shodan made a pretty sweet <a href="https://shiptracker.shodan.io/">Ship Tracker</a> that maps ship locations in real time, too!</p>
<pre><code>"Cobham SATCOM" OR ("Sailor" "VSAT")
</code></pre>

<h3>Submarine Mission Control Dashboards <a href="https://www.shodan.io/search?query=title%3A%22Slocum+Fleet+Mission+Control%22">🔎 →</a></h3>
<pre><code>title:"Slocum Fleet Mission Control"
</code></pre>
<h3><a href="https://www.carel.com/product/plantvisor">CAREL PlantVisor</a> Refrigeration Units <a href="https://www.shodan.io/search?query=%22Server%3A+CarelDataServer%22+%22200+Document+follows%22">🔎 →</a></h3>
<pre><code>"Server: CarelDataServer" "200 Document follows"
</code></pre>

<h3><a href="https://www.nordex-online.com/en/products-services/wind-turbines.html">Nordex Wind Turbine</a> Farms <a href="https://www.shodan.io/search?query=http.title%3A%22Nordex+Control%22+%22Windows+2000+5.0+x86%22+%22Jetty%2F3.1+%28JSP+1.1%3B+Servlet+2.2%3B+java+1.6.0_14%29%22">🔎 →</a></h3>
<pre><code>http.title:"Nordex Control" "Windows 2000 5.0 x86" "Jetty/3.1 (JSP 1.1; Servlet 2.2; java 1.6.0_14)"
</code></pre>
<h3><a href="https://www.mobile-devices.com/our-products/c4-max/">C4 Max</a> Commercial Vehicle GPS Trackers <a href="https://www.shodan.io/search?query=%22%5B1m%5B35mWelcome+on+console%22">🔎 →</a></h3>
<pre><code>"[1m[35mWelcome on console"
</code></pre>

<h3><a href="https://www.dicomstandard.org/about/">DICOM</a> Medical X-Ray Machines <a href="https://www.shodan.io/search?query=%22DICOM+Server+Response%22+port%3A104">🔎 →</a></h3>
<p>Secured by default, thankfully, but these 1,700+ machines still <a href="https://documents.trendmicro.com/assets/rpt/rpt-securing-connected-hospitals.pdf">have no business</a> being on the internet.</p>
<pre><code>"DICOM Server Response" port:104
</code></pre>
<h3><a href="https://electroind.com/all-products/">GaugeTech</a> Electricity Meters <a href="https://www.shodan.io/search?query=%22Server%3A+EIG+Embedded+Web+Server%22+%22200+Document+follows%22">🔎 →</a></h3>
<pre><code>"Server: EIG Embedded Web Server" "200 Document follows"
</code></pre>

<h3>Siemens Industrial Automation <a href="https://www.shodan.io/search?query=%22Siemens%2C+SIMATIC%22+port%3A161">🔎 →</a></h3>
<pre><code>"Siemens, SIMATIC" port:161
</code></pre>
<h3>Siemens HVAC Controllers <a href="https://www.shodan.io/search?query=%22Server%3A+Microsoft-WinCE%22+%22Content-Length%3A+12581%22">🔎 →</a></h3>
<pre><code>"Server: Microsoft-WinCE" "Content-Length: 12581"
</code></pre>
<h3>Door / Lock Access Controllers <a href="https://www.shodan.io/search?query=%22HID+VertX%22+port%3A4070">🔎 →</a></h3>
<pre><code>"HID VertX" port:4070
</code></pre>
<h3>Railroad Management <a href="https://www.shodan.io/search?query=%22log+off%22+%22select+the+appropriate%22">🔎 →</a></h3>
<pre><code>"log off" "select the appropriate"
</code></pre>
<hr>
<h2>Remote Desktop</h2>
<h3>Unprotected VNC <a href="https://www.shodan.io/search?query=%22authentication+disabled%22+%22RFB+003.008%22">🔎 →</a></h3>
<pre><code>"authentication disabled" "RFB 003.008"
</code></pre>
<p><a href="https://images.shodan.io/">Shodan Images</a> is a great supplementary tool to browse screenshots, by the way! <a href="https://images.shodan.io/?query=%22authentication+disabled%22+%21screenshot.label%3Ablank">🔎 →</a></p>
<p>
<em>The first result right now. 😞</em></p>
<h3>Windows RDP <a href="https://www.shodan.io/search?query=%22%5Cx03%5Cx00%5Cx00%5Cx0b%5Cx06%5Cxd0%5Cx00%5Cx00%5Cx124%5Cx00%22">🔎 →</a></h3>
<p>99.99% are secured by a secondary Windows login screen.</p>
<pre><code>"\x03\x00\x00\x0b\x06\xd0\x00\x00\x124\x00"
</code></pre>
<hr>
<h2>Network Infrastructure</h2>
<h3><a href="https://www.weave.works/oss/scope/">Weave Scope</a> Dashboards <a href="https://www.shodan.io/search?query=title%3A%22Weave+Scope%22+http.favicon.hash%3A567176827">🔎 →</a></h3>
<p>Command-line access inside Kubernetes pods and Docker containers, and real-time visualization/monitoring of the entire infrastructure.</p>
<pre><code>title:"Weave Scope" http.favicon.hash:567176827
</code></pre>

<h3>MongoDB <a href="https://www.shodan.io/search?query=product%3AMongoDB+-authentication">🔎 →</a></h3>
<p>Older versions were insecure by default. <a href="https://krebsonsecurity.com/tag/mongodb/">Very scary.</a></p>
<pre><code>"MongoDB Server Information" port:27017 -authentication
</code></pre>

<h3><a href="https://github.com/mongo-express/mongo-express">Mongo Express</a> Web GUI <a href="https://www.shodan.io/search?query=%22Set-Cookie%3A+mongo-express%3D%22+%22200+OK%22">🔎 →</a></h3>
<p>Like the <a href="https://www.cvedetails.com/vulnerability-list/vendor_id-784/Phpmyadmin.html">infamous phpMyAdmin</a> but for MongoDB.</p>
<pre><code>"Set-Cookie: mongo-express=" "200 OK"
</code></pre>

<h3>Jenkins CI <a href="https://www.shodan.io/search?query=%22X-Jenkins%22+%22Set-Cookie%3A+JSESSIONID%22+http.title%3A%22Dashboard%22">🔎 →</a></h3>
<pre><code>"X-Jenkins" "Set-Cookie: JSESSIONID" http.title:"Dashboard"
</code></pre>

<h3>Docker APIs <a href="https://www.shodan.io/search?query=%22Docker+Containers%3A%22+port%3A2375">🔎 →</a></h3>
<pre><code>"Docker Containers:" port:2375
</code></pre>
<h3>Docker Private Registries <a href="https://www.shodan.io/search?query=%22Docker-Distribution-Api-Version%3A+registry%22+%22200+OK%22+-gitlab">🔎 →</a></h3>
<pre><code>"Docker-Distribution-Api-Version: registry" "200 OK" -gitlab
</code></pre>
<h3><a href="https://pi-hole.net/">Pi-hole</a> Open DNS Servers <a href="https://www.shodan.io/search?query=%22dnsmasq-pi-hole%22+%22Recursion%3A+enabled%22">🔎 →</a></h3>
<pre><code>"dnsmasq-pi-hole" "Recursion: enabled"
</code></pre>
<h3>Already Logged-In as <code>root</code> via Telnet <a href="https://www.shodan.io/search?query=%22root%40%22+port%3A23+-login+-password+-name+-Session">🔎 →</a></h3>
<pre><code>"root@" port:23 -login -password -name -Session
</code></pre>
<h3>Android Root Bridges <a href="https://www.shodan.io/search?query=%22Android+Debug+Bridge%22+%22Device%22+port%3A5555">🔎 →</a></h3>
<p>A tangential result of Google’s dumb fractured update approach. 🙄 <a href="https://medium.com/p/root-bridge-how-thousands-of-internet-connected-android-devices-now-have-no-security-and-are-b46a68cb0f20">More information here.</a></p>
<pre><code>"Android Debug Bridge" "Device" port:5555
</code></pre>
<h3>Lantronix Serial-to-Ethernet Adapter <a href="https://www.bleepingcomputer.com/news/security/thousands-of-serial-to-ethernet-devices-leak-telnet-passwords/">Leaking Telnet Passwords</a> <a href="https://www.shodan.io/search?query=Lantronix+password+port%3A30718+-secured">🔎 →</a></h3>
<pre><code>Lantronix password port:30718 -secured
</code></pre>
<h3>Citrix Virtual Apps <a href="https://www.shodan.io/search?query=%22Citrix+Applications%3A%22+port%3A1604">🔎 →</a></h3>
<pre><code>"Citrix Applications:" port:1604
</code></pre>

<h3>Cisco Smart Install <a href="https://www.shodan.io/search?query=%22smart+install+client+active%22">🔎 →</a></h3>
<p><a href="https://2016.zeronights.ru/wp-content/uploads/2016/12/CiscoSmartInstall.v3.pdf">Vulnerable</a> (kind of “by design,” but especially when exposed).</p>
<pre><code>"smart install client active"
</code></pre>
<h3>PBX IP Phone Gateways <a href="https://www.shodan.io/search?query=PBX+%22gateway+console%22+-password+port%3A23">🔎 →</a></h3>
<pre><code>PBX "gateway console" -password port:23
</code></pre>
<h3><a href="https://www.polycom.com/hd-video-conferencing.html">Polycom</a> Video Conferencing <a href="https://www.shodan.io/search?query=http.title%3A%22-+Polycom%22+%22Server%3A+lighttpd%22">🔎 →</a></h3>
<pre><code>http.title:"- Polycom" "Server: lighttpd"
</code></pre>
<p>Telnet Configuration: <a href="https://www.shodan.io/search?query=%22Polycom+Command+Shell%22+-failed+port%3A23">🔎 →</a></p>
<pre><code>"Polycom Command Shell" -failed port:23
</code></pre>

<h3><a href="https://www.beyondtrust.com/remote-support/integrations">Bomgar Help Desk</a> Portal <a href="https://www.shodan.io/search?query=%22Server%3A+Bomgar%22+%22200+OK%22">🔎 →</a></h3>
<pre><code>"Server: Bomgar" "200 OK"
</code></pre>
<h3>Intel Active Management <a href="https://www.exploit-db.com/exploits/43385">CVE-2017-5689</a> <a href="https://www.shodan.io/search?query=%22Intel%28R%29+Active+Management+Technology%22+port%3A623%2C664%2C16992%2C16993%2C16994%2C16995">🔎 →</a></h3>
<pre><code>"Intel(R) Active Management Technology" port:623,664,16992,16993,16994,16995
</code></pre>
<h3>HP iLO 4 <a href="https://nvd.nist.gov/vuln/detail/CVE-2017-12542">CVE-2017-12542</a> <a href="https://www.shodan.io/search?query=HP-ILO-4+%21%22HP-ILO-4%2F2.53%22+%21%22HP-ILO-4%2F2.54%22+%21%22HP-ILO-4%2F2.55%22+%21%22HP-ILO-4%2F2.60%22+%21%22HP-ILO-4%2F2.61%22+%21%22HP-ILO-4%2F2.62%22+%21%22HP-iLO-4%2F2.70%22+port%3A1900">🔎 →</a></h3>
<pre><code>HP-ILO-4 !"HP-ILO-4/2.53" !"HP-ILO-4/2.54" !"HP-ILO-4/2.55" !"HP-ILO-4/2.60" !"HP-ILO-4/2.61" !"HP-ILO-4/2.62" !"HP-iLO-4/2.70" port:1900
</code></pre>
<h3>Outlook Web Access:</h3>
<h4>Exchange 2007 <a href="https://www.shodan.io/search?query=%22x-owa-version%22+%22IE%3DEmulateIE7%22+%22Server%3A+Microsoft-IIS%2F7.0%22">🔎 →</a></h4>
<pre><code>"x-owa-version" "IE=EmulateIE7" "Server: Microsoft-IIS/7.0"
</code></pre>

<h4>Exchange 2010 <a href="https://www.shodan.io/search?query=%22x-owa-version%22+%22IE%3DEmulateIE7%22+http.favicon.hash%3A442749392">🔎 →</a></h4>
<pre><code>"x-owa-version" "IE=EmulateIE7" http.favicon.hash:442749392
</code></pre>

<h4>Exchange 2013 / 2016 <a href="https://www.shodan.io/search?query=%22X-AspNet-Version%22+http.title%3A%22Outlook%22+-%22x-owa-version%22">🔎 →</a></h4>
<pre><code>"X-AspNet-Version" http.title:"Outlook" -"x-owa-version"
</code></pre>

<h3>Lync / Skype for Business <a href="https://www.shodan.io/search?query=%22X-MS-Server-Fqdn%22">🔎 →</a></h3>
<pre><code>"X-MS-Server-Fqdn"
</code></pre>
<hr>
<h2>Network Attached Storage (NAS)</h2>
<h3>SMB (Samba) File Shares <a href="https://www.shodan.io/search?query=%22Authentication%3A+disabled%22+port%3A445">🔎 →</a></h3>
<p>Produces ~500,000 results…narrow down by adding “Documents” or “Videos”, etc.</p>
<pre><code>"Authentication: disabled" port:445
</code></pre>
<p>Specifically domain controllers: <a href="https://www.shodan.io/search?query=%22Authentication%3A+disabled%22+NETLOGON+SYSVOL+-unix+port%3A445">🔎 →</a></p>
<pre><code>"Authentication: disabled" NETLOGON SYSVOL -unix port:445
</code></pre>
<p>Concerning <a href="https://quickbooks.intuit.com/learn-support/en-us/help-articles/set-up-folder-and-windows-access-permissions-to-share-company/01/201880">default network shares of QuickBooks</a> files: <a href="https://www.shodan.io/search?query=%22Authentication%3A+disabled%22+%22Shared+this+folder+to+access+QuickBooks+files+OverNetwork%22+-unix+port%3A445">🔎 →</a></p>
<pre><code>"Authentication: disabled" "Shared this folder to access QuickBooks files OverNetwork" -unix port:445
</code></pre>
<h3>FTP Servers with Anonymous Login <a href="https://www.shodan.io/search?query=%22220%22+%22230+Login+successful.%22+port%3A21">🔎 →</a></h3>
<pre><code>"220" "230 Login successful." port:21
</code></pre>
<h3>Iomega / LenovoEMC NAS Drives <a href="https://www.shodan.io/search?query=%22Set-Cookie%3A+iomega%3D%22+-%22manage%2Flogin.html%22+-http.title%3A%22Log+In%22">🔎 →</a></h3>
<pre><code>"Set-Cookie: iomega=" -"manage/login.html" -http.title:"Log In"
</code></pre>

<h3>Buffalo TeraStation NAS Drives <a href="https://www.shodan.io/search?query=Redirecting+sencha+port%3A9000">🔎 →</a></h3>
<pre><code>Redirecting sencha port:9000
</code></pre>

<h3>Logitech Media Servers <a href="https://www.shodan.io/search?query=%22Server%3A+Logitech+Media+Server%22+%22200+OK%22">🔎 →</a></h3>
<pre><code>"Server: Logitech Media Server" "200 OK"
</code></pre>

<h3><a href="https://www.plex.tv/">Plex</a> Media Servers <a href="https://www.shodan.io/search?query=%22X-Plex-Protocol%22+%22200+OK%22+port%3A32400">🔎 →</a></h3>
<pre><code>"X-Plex-Protocol" "200 OK" port:32400
</code></pre>
<h3><a href="https://github.com/Tautulli/Tautulli">Tautulli / PlexPy</a> Dashboards <a href="https://www.shodan.io/search?query=%22CherryPy%2F5.1.0%22+%22%2Fhome%22">🔎 →</a></h3>
<pre><code>"CherryPy/5.1.0" "/home"
</code></pre>

<hr>
<h2>Webcams</h2>
<p>Example images not necessary. 🤦</p>
<h3>Yawcams <a href="https://www.shodan.io/search?query=%22Server%3A+yawcam%22+%22Mime-Type%3A+text%2Fhtml%22">🔎 →</a></h3>
<pre><code>"Server: yawcam" "Mime-Type: text/html"
</code></pre>
<h3>webcamXP/webcam7 <a href="https://www.shodan.io/search?query=%28%22webcam+7%22+OR+%22webcamXP%22%29+http.component%3A%22mootools%22+-401">🔎 →</a></h3>
<pre><code>("webcam 7" OR "webcamXP") http.component:"mootools" -401
</code></pre>
<h3>Android IP Webcam Server <a href="https://www.shodan.io/search?query=%22Server%3A+IP+Webcam+Server%22+%22200+OK%22">🔎 →</a></h3>
<pre><code>"Server: IP Webcam Server" "200 OK"
</code></pre>
<h3>Security DVRs <a href="https://www.shodan.io/search?query=html%3A%22DVR_H264+ActiveX%22">🔎 →</a></h3>
<pre><code>html:"DVR_H264 ActiveX"
</code></pre>
<hr>
<h2>Printers &#x26; Copiers</h2>
<h3>HP Printers <a href="https://www.shodan.io/search?query=%22Serial+Number%3A%22+%22Built%3A%22+%22Server%3A+HP+HTTP%22">🔎 →</a></h3>
<pre><code>"Serial Number:" "Built:" "Server: HP HTTP"
</code></pre>

<h3>Xerox Copiers/Printers <a href="https://www.shodan.io/search?query=ssl%3A%22Xerox+Generic+Root%22">🔎 →</a></h3>
<pre><code>ssl:"Xerox Generic Root"
</code></pre>

<h3>Epson Printers <a href="https://www.shodan.io/search?query=%22SERVER%3A+EPSON_Linux+UPnP%22+%22200+OK%22">🔎 →</a></h3>
<pre><code>"SERVER: EPSON_Linux UPnP" "200 OK"
</code></pre>
<pre><code>"Server: EPSON-HTTP" "200 OK"
</code></pre>

<h3>Canon Printers <a href="https://www.shodan.io/search?query=%22Server%3A+KS_HTTP%22+%22200+OK%22">🔎 →</a></h3>
<pre><code>"Server: KS_HTTP" "200 OK"
</code></pre>
<pre><code>"Server: CANON HTTP Server"
</code></pre>

<hr>
<h2>Home Devices</h2>
<h3>Yamaha Stereos <a href="https://www.shodan.io/search?query=%22Server%3A+AV_Receiver%22+%22HTTP%2F1.1+406%22">🔎 →</a></h3>
<pre><code>"Server: AV_Receiver" "HTTP/1.1 406"
</code></pre>

<h3>Apple AirPlay Receivers <a href="https://www.shodan.io/search?query=%22%5Cx08_airplay%22+port%3A5353">🔎 →</a></h3>
<p>Apple TVs, HomePods, etc.</p>
<pre><code>"\x08_airplay" port:5353
</code></pre>
<h3>Chromecasts / Smart TVs <a href="https://www.shodan.io/search?query=%22Chromecast%3A%22+port%3A8008">🔎 →</a></h3>
<pre><code>"Chromecast:" port:8008
</code></pre>
<h3><a href="https://www.crestron.com/Products/Market-Solutions/Residential-Solutions">Crestron Smart Home</a> Controllers <a href="https://www.shodan.io/search?query=%22Model%3A+PYNG-HUB%22">🔎 →</a></h3>
<pre><code>"Model: PYNG-HUB"
</code></pre>
<hr>
<h2>Random Stuff</h2>
<h3>OctoPrint 3D Printer Controllers <a href="https://www.shodan.io/search?query=title%3A%22OctoPrint%22+-title%3A%22Login%22+http.favicon.hash%3A1307375944">🔎 →</a></h3>
<pre><code>title:"OctoPrint" -title:"Login" http.favicon.hash:1307375944
</code></pre>

<h3>Etherium Miners <a href="https://www.shodan.io/search?query=%22ETH+-+Total+speed%22">🔎 →</a></h3>
<pre><code>"ETH - Total speed"
</code></pre>

<h3>Apache Directory Listings <a href="https://www.shodan.io/search?query=http.title%3A%22Index+of+%2F%22+http.html%3A%22.pem%22">🔎 →</a></h3>
<p>Substitute <code>.pem</code> with any extension or a filename like <code>phpinfo.php</code>.</p>
<pre><code>http.title:"Index of /" http.html:".pem"
</code></pre>
<h3>Misconfigured WordPress <a href="https://www.shodan.io/search?query=http.html%3A%22*+The+wp-config.php+creation+script+uses+this+file%22">🔎 →</a></h3>
<p>Exposed <a href="https://github.com/WordPress/WordPress/blob/master/wp-config-sample.php"><code>wp-config.php</code></a> files containing database credentials.</p>
<pre><code>http.html:"* The wp-config.php creation script uses this file"
</code></pre>
<h3>Too Many Minecraft Servers <a href="https://www.shodan.io/search?query=%22Minecraft+Server%22+%22protocol+340%22+port%3A25565">🔎 →</a></h3>
<pre><code>"Minecraft Server" "protocol 340" port:25565
</code></pre>
<h3>Literally <a href="https://www.vox.com/2014/12/22/7435625/north-korea-internet">Everything</a> in North Korea 🇰🇵 <a href="https://www.shodan.io/search?query=net%3A175.45.176.0%2F22%2C210.52.109.0%2F24">🔎 →</a></h3>
<pre><code>net:175.45.176.0/22,210.52.109.0/24,77.94.35.0/24
</code></pre>
<h3>TCP Quote of the Day <a href="https://www.shodan.io/search?query=port%3A17+product%3A%22Windows+qotd%22">🔎 →</a></h3>
<p>Port 17 (<a href="https://tools.ietf.org/html/rfc865">RFC 865</a>) has a <a href="https://en.wikipedia.org/wiki/QOTD">bizarre history</a>…</p>
<pre><code>port:17 product:"Windows qotd"
</code></pre>
<h3>Find a Job Doing This! 👩‍💼 <a href="https://www.shodan.io/search?query=%22X-Recruiting%3A%22">🔎 →</a></h3>
<pre><code>"X-Recruiting:"
</code></pre>
<hr>
<p>If you’ve found any other juicy Shodan gems, whether it’s a search query or a specific example, <a href="https://github.com/jakejarvis/awesome-shodan-queries">open an issue/PR on GitHub!</a></p>
<p>Bon voyage, fellow penetrators! 😉</p>
        <p><a href="https://jarv.is/notes/shodan-search-queries"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[How To: Automatically Backup a Linux VPS to a Separate Cloud Storage Service]]></title>
            <link>https://jarv.is/notes/how-to-backup-linux-server</link>
            <guid isPermaLink="false">https://jarv.is/notes/how-to-backup-linux-server</guid>
            <pubDate>Sun, 09 Jun 2019 23:03:10 GMT</pubDate>
            <description><![CDATA[A walkthrough for backing up a Linux server to an external storage provider like Amazon S3 automatically.]]></description>
            <content:encoded><![CDATA[<p>
<em><strong>The Cloud-pocalypse:</strong> Coming soon(er than you think) to a server near you.</em></p>
<p>Last month, the founder of <a href="https://raisup.com/">a small startup</a> got quite a bit of <a href="https://twitter.com/w3Nicolas/status/1134529316904153089">attention on Twitter</a> (and <a href="https://news.ycombinator.com/item?id=20064169">Hacker News</a>) when he called out <a href="https://www.digitalocean.com/">DigitalOcean</a> who, in his words, “killed” his company. Long story short: DigitalOcean’s automated abuse system flagged the startup’s account after they spun up about ten powerful droplets for some CPU-intensive jobs and deleted them shortly after — which is literally <strong>the biggest selling point</strong> of a “servers by the hour” company like DigitalOcean, by the way — and, after replying to the support ticket, an unsympathetic customer support agent <a href="https://twitter.com/w3Nicolas/status/1134529372172509184">declined to reactivate</a> the account without explanation. <a href="https://twitter.com/w3Nicolas">Nicolas</a> had no way of even accessing his data, turning the inconvenient but trivial task of migrating servers into a potentially fatal situation for his company.</p>

<p>Predictably, there were <a href="https://twitter.com/kolaente/status/1134897543643615238">a</a> <a href="https://twitter.com/hwkfr/status/1135164281731911681">lot</a> <a href="https://twitter.com/joestarstuff/status/1135406188114276352">of</a> <a href="https://twitter.com/FearbySoftware/status/1134717875351052288">Monday</a>-<a href="https://twitter.com/mkozak/status/1134557954785587200">morning</a> <a href="https://twitter.com/MichMich/status/1134547174447026181">quarterbacks</a> who weighed in, scolding him for not having backups (<a href="https://twitter.com/w3Nicolas/status/1134529374676500482">he did</a>, but they were also stored on DigitalOcean) and not paying a boatload of non-existent money for expensive load balancers pointing to multiple cloud providers. Hindsight is always 20/20, of course, but if we’re talking about a small side project that exploded into a full-fledged startup with Fortune 500 clients seemingly overnight, I <em>completely</em> understand Nicolas’ thought process. <em>“Let’s just take advantage of cloud computing’s #1 selling point: press a few buttons to make our servers <a href="https://www.youtube.com/watch?v=x84m3YyO2oU">harder, better, faster, stronger</a> and get back to coding!”</em></p>
<p>Most of the popular one-click server providers (including <a href="https://www.digitalocean.com/docs/images/backups/overview/">DigitalOcean</a>, as well as <a href="https://www.linode.com/backups">Linode</a>, <a href="https://www.vultr.com/docs/vps-automatic-backups">Vultr</a>, and <a href="https://www.ovh.com/world/vps/backup-vps.xml">OVH</a>) provide their own backup offerings for an additional monthly cost (usually proportional to your plan). But as Nicolas learned the hard way, any amount of backups are just more eggs in the same basket if everything is under one account with one credit card on one provider.</p>
<p>Luckily, crafting a DIY automated backup system using a second redundant storage provider isn’t as daunting (nor as expensive) as it might sound. The following steps are how I backup my various VPSes to a totally separate cloud in the sky.</p>
<hr>
<p>There are quite a few tools that have been around for decades that could accomplish this task — namely <a href="https://en.wikipedia.org/wiki/Rsync"><code>rsync</code></a> — but an <a href="https://github.com/restic/restic">open-source</a> tool named <a href="https://restic.net/"><strong>Restic</strong></a> has won my heart for both its simplicity and the wide range of destinations it natively supports, including but not limited to:</p>
<ul>
<li><a href="https://aws.amazon.com/s3/">Amazon AWS S3</a></li>
<li><a href="https://www.backblaze.com/b2/cloud-storage.html">Backblaze B2</a></li>
<li><a href="https://cloud.google.com/storage/">Google Cloud Storage</a></li>
<li><a href="https://azure.microsoft.com/en-us/services/storage/">Azure Storage</a></li>
<li>…and <a href="https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html#sftp">literally anywhere</a> you can SFTP (or SSH) into.</li>
</ul>
<p>Backups are encrypted by default, too, which is a tasty cherry on top!</p>
<p>Setting up Restic is certainly easier than a low-level tool like <code>rsync</code>, but it can still be tricky. Follow these steps and you’ll have a fully automated system making easily restorable backups of your important files in preparation for your own (likely inevitable) Cloud-pocalypse.</p>
<hr>
<h2>0. Sign up for a second cloud service</h2>
<p>I host most of my projects on <a href="https://www.linode.com/?r=0c5aeace9bd591be9fbf32f96f58470295f1ee05">Linode</a> (affiliate link) and chose <a href="https://aws.amazon.com/s3/">Amazon’s S3</a> as my backup destination. S3 is easily the gold-standard in random file storage and I’d highly recommend it — unless your servers are also on Amazon with EC2, of course. My second choice would be <a href="https://www.backblaze.com/b2/cloud-storage.html">Backblaze’s B2</a>, which is comparable to S3 in semantics and price.</p>
<p>Writing steps to create an S3 bucket would be incredibly redundant, so here are Amazon’s writeups on creating one (make sure the bucket is <strong><em>fully private;</em></strong> the other default settings are fine) as well as grabbing your account’s “access keys” which will be used to authenticate Restic with S3.</p>
<ul>
<li><a href="https://docs.aws.amazon.com/quickstarts/latest/s3backup/step-1-create-bucket.html">How Do I Create an S3 Bucket?</a></li>
<li><a href="https://docs.aws.amazon.com/general/latest/gr/managing-aws-access-keys.html">Understanding and Getting Your Security Credentials</a></li>
</ul>
<h2>1. Install Restic</h2>
<p>Restic might be included in your OS’s default repositories (it is on Ubuntu) but it’s better to opt for the releases on <a href="https://github.com/restic/restic/releases">Restic’s GitHub page</a>. The binary you’ll get from <code>apt</code> or <code>yum</code> will be several versions behind, and the GitHub flavor auto-updates itself anyways.</p>
<p>Find the latest version of Restic on their <a href="https://github.com/restic/restic/releases/latest">GitHub releases page</a>. Since I’m assuming this is a Linux server, we only want the file ending in <code>_linux_amd64.bz2</code>. (For a 32-bit Linux server, find <code>_linux_386.bz2</code>. Windows, macOS, and BSD binaries are also there.) Right-click and copy the direct URL for that file and head over to your server’s command line to download it into your home directory:</p>
<pre><code class="language-bash">cd ~
wget https://github.com/restic/restic/releases/download/v0.9.5/restic_0.9.5_linux_amd64.bz2
</code></pre>
<p>Next, we’ll unzip the download in place:</p>
<pre><code class="language-bash">bunzip2 restic_*
</code></pre>
<p>This should leave us with a single file: the Restic binary. In order to make Restic available system-wide and accessible with a simple <code>restic</code> command, we need to move it into the <code>/usr/local/bin</code> folder, which requires <code>sudo</code> access:</p>
<pre><code class="language-bash">sudo mv restic_* /usr/local/bin/restic
sudo chmod a+x /usr/local/bin/restic
</code></pre>
<p>Now’s a good time to run <code>restic</code> to make sure we’re good to move on. If you see the version number we downloaded, you’re all set!</p>
<pre><code class="language-bash">restic version
</code></pre>
<h2>2. Connect Restic to S3</h2>
<p>This step is a <em>little</em> different for each cloud provider. My walkthrough assumes S3, but <a href="https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html">Restic’s documentation</a> lays out the variables you’ll need to authenticate with different providers.</p>
<p>If you haven’t already <a href="https://docs.aws.amazon.com/quickstarts/latest/s3backup/step-1-create-bucket.html">created a new S3 bucket</a> and grabbed your <a href="https://docs.aws.amazon.com/general/latest/gr/managing-aws-access-keys.html">access key and secret</a> from the AWS console, do so now.</p>
<p>We need to store these keys as environment variables named <code>AWS_ACCESS_KEY_ID</code> and <code>AWS_SECRET_ACCESS_KEY</code>. For now, we’ll set these temporarily until we automate everything in the next step.</p>
<pre><code class="language-bash">export AWS_ACCESS_KEY_ID="your AWS access key"
export AWS_SECRET_ACCESS_KEY="your AWS secret"
</code></pre>
<p>We’ll also need to tell Restic where the bucket is located and set a secure password to encrypt the backups. You can generate a super-secure 32-character password by running <code>openssl rand -base64 32</code> — just make sure you store it somewhere safe!</p>
<pre><code class="language-bash">export RESTIC_REPOSITORY="s3:s3.amazonaws.com/your-bucket-name"
export RESTIC_PASSWORD="passw0rd123-just-kidding"
</code></pre>
<h2>3. Initialize the backup repository</h2>
<p>Now we’re ready to have Restic initialize the repository. This saves a <code>config</code> file in your S3 bucket and starts the encryption process right off the bat. You only need to run this once.</p>
<pre><code class="language-bash">restic init
</code></pre>
<p>If successful, you should see a message containing <code>created restic backend</code>. If not, make sure you set all four environment variables correctly and try again.</p>
<h2>4. Make our first backup</h2>
<p>Now that the hard parts are done, creating a backup (or “snapshot” in Restic terms) is as simple as a one-line command. All we need to specify is the directory you want to backup.</p>
<pre><code class="language-bash">restic backup /srv/important/data
</code></pre>
<p>Running <code>restic snapshots</code> will list every snapshot you’ve stored. You should see one listed at this point if everything went according to plan.</p>
<h2>5. Automate backups using a cron job</h2>
<p>All of this is fine and good, but doing this manually whenever you happen to remember to won’t be very helpful if trouble strikes. Thankfully, Linux makes it incredibly easy to automate scripts using <a href="https://en.wikipedia.org/wiki/Cron">cron jobs</a>. So let’s set one up for this.</p>
<p>Make a new file at a convenient location on your server and name it <code>backup.sh</code>. This is where we’ll replicate everything we did above. Here’s my full <code>.sh</code> file:</p>
<pre><code class="language-bash">#!/bin/bash

export AWS_ACCESS_KEY_ID="xxxxxxxx"
export AWS_SECRET_ACCESS_KEY="xxxxxxxx"
export RESTIC_REPOSITORY="s3:s3.amazonaws.com/xxxxxxxx"
export RESTIC_PASSWORD="xxxxxxxx"

restic backup -q /srv/xxxxxxxx
</code></pre>
<p>(The <code>-q</code> flag silences the output, since we won’t be able to see it anyways.)</p>
<p>I highly recommend adding one final command to the end of the file: Restic’s <code>forget</code> feature. Constantly storing multiple snapshots a day to S3 without pruning them will rack up your bill more than you’d probably like. Using <code>forget</code>, we can specify how many snapshots we want to keep and from when.</p>
<p>This command keeps one snapshot from each of the last <strong>six hours</strong>, one snapshot from each of the last <strong>seven days</strong>, one snapshot from each of the last <strong>four weeks</strong>, and one snapshot from each of the last <strong>twelve months</strong>.</p>
<pre><code class="language-bash">restic forget -q --prune --keep-hourly 6 --keep-daily 7 --keep-weekly 4 --keep-monthly 12
</code></pre>
<p>Reading <a href="https://restic.readthedocs.io/en/latest/060_forget.html#removing-snapshots-according-to-a-policy">the documentation</a> for different <code>forget</code> options can be helpful if you want to customize these.</p>
<p>Save the shell script and close the editor. Don’t forget to make the script we just wrote actually executable:</p>
<pre><code class="language-bash">chmod +x backup.sh
</code></pre>
<p>Lastly, we need to set the actual cron job. To do this, run <code>sudo crontab -e</code> and add the following line to the end:</p>
<pre><code class="language-bash">0 * * * * /root/backup.sh
</code></pre>
<p>The first part specifies how often the script should run. <code>0 * * * *</code> runs it right at the top of every hour. Personally, I choose to run it at the 15th minute of every <em>other</em> hour, so mine looks like <code>15 */2 * * *</code>. <a href="https://crontab.guru/#0_*_*_*_*">crontab.guru</a> is a nifty “calculator” to help you customize this expression to your liking — it’s definitely not the most intuitive syntax.</p>
<p>The second part specifies where the script we just wrote is located, of course, so set that to wherever you saved <code>backup.sh</code>.</p>
<h2>6. Verifying and restoring snapshots</h2>
<p><strong>Side note:</strong> In order to use <code>restic</code> in future shell sessions, we need to make the four environment variables permanent by adding them to your <code>.bash_profile</code> or <code>.bashrc</code> file in your home directory. Simply copy and paste the four <code>export</code> lines from the <code>backup.sh</code> file we created above to the end of either dotfile.</p>
<p>Take note of the next time that your new cron job <em>should</em> run, so we can check that it was automatically triggered. After that time — at the top of the next hour if you used my defaults in the last step — you can run <code>restic snapshots</code> like we did before to make sure there’s an additional snapshot listed, and optionally take the IDs of each snapshot and run <code>restic diff ID_1 ID_2</code> to see what’s changed between the two.</p>
<p>To restore a snapshot to a certain location, grab the ID from <code>restic snapshots</code> and use <code>restore</code> like so:</p>
<pre><code class="language-bash">restic restore 420x69abc --target ~/restored_files
</code></pre>
<p>You can also replace the ID with <code>latest</code> to restore the latest snapshot.</p>
<p>~~If~~ When trouble strikes and you’re setting up a new server, just re-follow steps one, two, and six and you’ll have all your files back before you know it!</p>
<p>There are a few other neat options for browsing and restoring snapshots, like <code>mount</code>ing a snapshot as a disk on your file system. Read more about that on the <a href="https://restic.readthedocs.io/en/latest/050_restore.html">“restoring from backup” docs</a> page.</p>
<hr>
<p>Again, <a href="https://restic.readthedocs.io/en/latest/index.html">Restic’s documentation</a> is really, really well written, so I definitely recommend skimming it to see what else is possible.</p>
<p>Literally every company’s Doomsday protocols can <em>always</em> be improved, and external backups are just one part of redundancy. But pat yourself on the back — this might have been a convoluted process, but hopefully you’ll be able to sleep better at night knowing your startup or personal server now has a <strong>far</strong> better chance of surviving whatever the cloud rains down upon you!</p>
<p>If you have any questions, feel free to leave a comment or <a href="https://twitter.com/jakejarvis">get in touch with me</a>. Be safe out there!</p>
        <p><a href="https://jarv.is/notes/how-to-backup-linux-server"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[Bernie Sanders’ Creepy “BERN” App Wants Your Data…From Your Best Friends]]></title>
            <link>https://jarv.is/notes/bernie-sanders-bern-app-data</link>
            <guid isPermaLink="false">https://jarv.is/notes/bernie-sanders-bern-app-data</guid>
            <pubDate>Wed, 08 May 2019 14:31:02 GMT</pubDate>
            <description><![CDATA[The team behind Bernie's campaign has a new app named BERN. It's undoubtedly a smart move, but also a concerning one for privacy advocates.]]></description>
            <content:encoded><![CDATA[<p>The team behind Bernie Sanders’ 2020 campaign <a href="https://www.nbcnews.com/politics/2020-election/bernie-sanders-2020-campaign-unveils-app-increase-its-voter-database-n999206">released a new web app</a> last month named <a href="https://app.berniesanders.com/">BERN</a>. The goal of BERN is simple: to gather as much information as they can on as many voters in the United States as they can, and make their grassroots army of enthusiastic supporters do the work. It’s undoubtedly a smart strategy, but also a concerning one for myself and other privacy advocates.</p>

<p>BERN has two features: one called “Friend-to-Friend” (described as “add everyone in your network”) and another called “Community Canvassing” (described as “talk to people around you every day, e.g. on the bus, outside the grocery store, at a park”). Both of these involve phoning home to Sanders HQ with the following information on anybody you know or meet:</p>
<ul>
<li>Level of support for Bernie Sanders (from 1 to 7)</li>
<li>How you know this person (family, neighbor, classmate, etc.)</li>
<li>If they’re a student (and specifically which college they attend)</li>
<li>If they’re a union member (and which union)</li>
<li>Issues that are “most important” to them</li>
<li>List of other candidates they might support</li>
<li>Their phone number</li>
<li>Their email address</li>
</ul>
<p>But how do I know who I know, you might ask? BERN’s <a href="https://app.berniesanders.com/help/app-frequently-asked-questions/">FAQ page</a> helpfully answers this:</p>
<blockquote>
<p><strong>Brainstorm a list of your contacts.</strong> Think about who you have come to know over the course of your life. How do you know who you know? On a piece of paper or in a spreadsheet, write down as many names of people you know. Some ideas for brainstorming:</p>
<ul>
<li>Go through your phone book or, if you use Facebook, your Facebook friend list.</li>
<li>Who would you invite to your birthday party or wedding? Where have you lived throughout your life? Who did you know in each of the places you have lived?</li>
</ul>
<p><strong>Which people can I add to my contact list the BERN app?</strong> <em>[sic]</em>
We use the word “friend” very broadly: You can add anyone you have met and known in your life to the app.</p>
</blockquote>
<p>Using either feature, a volunteer starts with a search of the database for the voter he or she is (theoretically) talking to by entering the voter’s first name, last name, and location (which only requires entering the state but can be narrowed down by town and/or ZIP code). Every match pops up with each voter’s age, sex, and location along with an option to add the information of someone who’s not listed or still needs to register to vote.</p>
<p>Here’s one of the instructional videos provided internally to volunteers:</p>

<p>…and a few privacy-related questions about the friend-to-friend feature were answered by campaign staff in a separate closed webinar for volunteers this week:</p>


<p>Defenders of the BERN app have pointed out that the information used is already available from public voter rolls maintained independently by each state. This is true. But these public records have never been tied to a campaign’s internal voter files through a tool that’s wide open to the entire internet, with incentives to add valuable data that benefits one candidate.</p>
<p>There were even unverified claims that <a href="https://info.idagent.com/blog/bern-app-exposes-150m-voter-records">BERN was leaking voter ID numbers</a>, which are the same as one’s driver’s license ID numbers in some states, through JSON responses in the first few days after its release. There don’t be appear to be strict rate limits on calls to the API either, potentially inviting malicious actors from around the world — wink wink — to scrape personal data on tens of millions of Americans en masse.</p>
<p>
<em>BERN’s API response in Chrome DevTools</em></p>
<p>Others have noted that web-based organizing tools like BERN have been used by campaigns at all levels since President Obama’s well-oiled, futuristic machine in 2007. This is also true, and I’m a big fan of the trend they started.</p>
<p>But the latter category of databases — like <a href="https://nationbuilder.com/">NationBuilder</a> and, more notably, <a href="https://act.ngpvan.com/votebuilder">NGP VAN’s VoteBuilder</a> software based on the Obama campaign’s inventions and now used by almost all Democratic campaigns across the United States — are secured and strictly guarded. Volunteer accounts need to be created and approved by paid campaign organizers and are locked down to provide the bare minimum amount of information necessary for one to canvass or phone bank a shortlist of voters. Every single click is also recorded in a <a href="https://ijyxfbpcm3itvdly.public.blob.vercel-storage.com/sanders-campaign-audit-OhdPk62Ift9VDPbJb9EYBArz1NUXIm.pdf">detailed log</a> down to the millisecond. (This is how <a href="https://time.com/4155185/bernie-sanders-hillary-clinton-data/">Bernie’s organizers got busted</a> snooping around Hillary’s VoteBuilder data last cycle, by the way.)</p>
<p>
<em><a href="https://ijyxfbpcm3itvdly.public.blob.vercel-storage.com/sanders-campaign-audit-OhdPk62Ift9VDPbJb9EYBArz1NUXIm.pdf">NGP VAN’s audit of the Sanders campaign’s VoteBuilder activity</a></em></p>
<p>BERN is taking this to an unprecedented level. Allowing anybody on the internet to sign up and add others’ personal information to the campaign’s database without their knowledge is troubling, especially when you consider the gamified “points” system they’ve added as an incentive to report as much information on as many people as possible.</p>
<p>
<em><a href="https://www.reddit.com/r/SandersForPresident/comments/bi15la/new_get_the_official_bernie_sanders_2020_app_bern/elxi85m/">BERN discussion on /r/SandersForPresident thread</a></em></p>
<p>In addition to the points system, it was revealed in the webinar mentioned above that the campaign is planning on giving out shiny rewards based on how many friends one adds, setting expectations at 50+ contacts to reach the “Bernie Super Bundler” tier — whatever that means.</p>

<p>In the middle of the webinar, the organizer also paused the presentation for <em>fifteen minutes</em> — complete with a countdown clock — and told volunteers to race to add as many of their friends as possible in that time. She announced afterwards that participants added 20 to 40 friends into the app on average, with some allegedly adding close to 100 in fifteen minutes.</p>

<p>The <a href="https://berniesanders.com/privacy-policy/">Privacy Policy link</a> at the bottom of the app links to a generic policy that looks like it’s been copied from a default Wix website. There’s no mention of the BERN app, no details of how they explicitly use our information, and no sign of an opt-out procedure.</p>
<p>Without getting too political — everyone who knows me already knows <a href="/notes/millenial-with-hillary-clinton/">what I think of Bernie</a> — it’s hard to refute that his “bros” are <a href="https://www.washingtonpost.com/news/the-fix/wp/2016/06/07/the-bernie-bros-are-out-in-full-force-harassing-female-reporters/?utm_term=.795f3a6a6ac9">notorious for harassment</a> and internet trolling. Giving them any additional information beyond the Twitter handles of their targets is surely not going to help detoxify the discourse this time around.</p>
<p>Count me out of feeling the Bern and the BERN. Just regular old heartburn for me. 🤢</p>
        <p><a href="https://jarv.is/notes/bernie-sanders-bern-app-data"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[Does Cloudflare’s 1.1.1.1 DNS Block Archive.is?]]></title>
            <link>https://jarv.is/notes/cloudflare-dns-archive-is-blocked</link>
            <guid isPermaLink="false">https://jarv.is/notes/cloudflare-dns-archive-is-blocked</guid>
            <pubDate>Sat, 04 May 2019 13:35:12 GMT</pubDate>
            <description><![CDATA[Short answer: no. Quite the opposite, actually — Archive.is is intentionally blocking 1.1.1.1 users. Here's why.]]></description>
            <content:encoded><![CDATA[<p><strong>tl;dr:</strong> No. Quite the opposite, actually — <a href="https://archive.is/">Archive.is</a>’s owner is intentionally blocking 1.1.1.1 users.</p>

<p>A <a href="https://news.ycombinator.com/item?id=19828317">recent post on Hacker News</a> pointed out something I’ve noticed myself over the past year — the <a href="https://archive.is/">Archive.is</a> website archiving tool (aka <a href="https://archive.today/">Archive.today</a> and a few other TLDs) appears unresponsive when I’m on my home network, where I use Cloudflare’s fantastic public DNS service, <a href="https://1.1.1.1/">1.1.1.1</a>. I didn’t connect the two variables until I read this post, where somebody noticed that the Archive.is domain resolves for <a href="https://developers.google.com/speed/public-dns/">Google’s 8.8.8.8</a> DNS, but not 1.1.1.1. An interesting and timeless debate on <a href="https://www.adweek.com/digital/why-consumers-are-increasingly-willing-to-trade-privacy-for-convenience/">privacy versus convenience</a> ensued.</p>
<p><a href="https://twitter.com/eastdakota">Matthew Prince</a>, the CEO and co-founder of <a href="https://www.cloudflare.com/">Cloudflare</a> (who’s also <a href="https://news.ycombinator.com/user?id=eastdakota">very active</a> on Hacker News), responded to the observation <a href="https://news.ycombinator.com/item?id=19828702">with a detailed explanation</a> of what’s happening behind the scenes, revealing that Archive.is’s owner is actively refusing to resolve their own website for 1.1.1.1 users because Cloudflare’s DNS offers <strong><em>too much</em></strong> privacy. Excerpt below, emphasis mine:</p>
<blockquote>
<p>We don’t block archive.is or any other domain via 1.1.1.1. […] Archive.is’s authoritative DNS servers <strong>return bad results to 1.1.1.1 when we query them</strong>. I’ve proposed we just fix it on our end but our team, quite rightly, said that too would violate the integrity of DNS and the privacy and security promises we made to our users when we launched the service. […] The archive.is owner has explained that <strong>he returns bad results to us because we don’t pass along the EDNS subnet information</strong>. This information leaks information about a requester’s IP and, in turn, sacrifices the privacy of users. <a href="https://news.ycombinator.com/item?id=19828702">Read more »</a></p>
</blockquote>
<p>In other words, Archive.is’s nameservers throw a hissy fit and return a bogus IP when Cloudflare <strong>doesn’t</strong> leak your geolocation info to them via the optional <a href="https://tools.ietf.org/html/rfc7871">EDNS client subnet feature</a>. The owner of Archive.is has plainly admitted this with <a href="https://twitter.com/archiveis/status/1018691421182791680">a questionable claim</a> (in my opinion) about the lack of EDNS information causing him “so many troubles.”</p>

<p>He’s even gone as far as <a href="https://community.cloudflare.com/t/archive-is-error-1001/18227/7">replying to support requests</a> by telling people to switch to Google’s DNS, which — surprise! — offers your location to nameservers <a href="https://developers.google.com/speed/public-dns/docs/ecs">with pleasure</a>.</p>
<p>I wrote the <a href="https://news.ycombinator.com/item?id=19828898">following reply</a> to Matthew, praising his team’s focus on the big picture:</p>
<blockquote>
<p>Honestly, Cloudflare choosing <em>not</em> to hastily slap a band-aid on a problem like this just makes me feel more compelled to continue using 1.1.1.1.</p>
<p>I hesitate to compare this to Apple calling themselves “courageous” when removing the headphone jack, but in this case, I think the word is appropriate. I’ll happily stand behind you guys if you take some PR hits while forcing the rest of the industry to make DNS safer — since it is understandable, admittedly, for users to conclude that “Cloudflare is blocking websites, sound the alarms!” at first glance.</p>
</blockquote>
<p>Sure, it’s annoying that I’ll need to use a VPN or change my DNS resolvers to use a pretty slick (and otherwise convenient) website archiver. But I’m more happy to see that Cloudflare is playing the privacy long-game, even at the risk of their users concluding that they’re blocking websites accessible to everyone else on the internet.</p>
        <p><a href="https://jarv.is/notes/cloudflare-dns-archive-is-blocked"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[Animated Waving Hand Emoji 👋 Using CSS]]></title>
            <link>https://jarv.is/notes/css-waving-hand-emoji</link>
            <guid isPermaLink="false">https://jarv.is/notes/css-waving-hand-emoji</guid>
            <pubDate>Wed, 17 Apr 2019 18:20:10 GMT</pubDate>
            <description><![CDATA[How to make the 👋 waving hand emoji actually wave using pure CSS animation!]]></description>
            <content:encoded><![CDATA[<h2>Howdy, friends! 👋</h2>
<p>If you examine <a href="/">my homepage</a> long enough, you might notice the 👋 hand emoji at the top subtly waving at you. This was easily accomplished using a few lines of CSS with a feature called <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@keyframes"><code>@keyframes</code></a> — no bulky GIFs involved, and no JS mess or jQuery overkill required.</p>
<p>Below are the code snippets you can grab and customize to make your own <a href="https://emojipedia.org/waving-hand-sign/">“waving hand” 👋</a> emojis <strong><em>actually wave</em></strong>, and a <a href="https://codepen.io/jakejarvis/pen/pBZWZw">CodePen playground</a> for live testing.</p>
<h2>Demo</h2>

<h2>CSS</h2>

<pre><code class="language-css">.wave {
  animation-name: wave-animation;  /* Refers to the name of your @keyframes element below */
  animation-duration: 2.5s;        /* Change to speed up or slow down */
  animation-iteration-count: infinite;  /* Never stop waving :) */
  transform-origin: 70% 70%;       /* Pivot around the bottom-left palm */
  display: inline-block;
}

@keyframes wave-animation {
    0% { transform: rotate( 0.0deg) }
   10% { transform: rotate(14.0deg) }  /* The following five values can be played with to make the waving more or less extreme */
   20% { transform: rotate(-8.0deg) }
   30% { transform: rotate(14.0deg) }
   40% { transform: rotate(-4.0deg) }
   50% { transform: rotate(10.0deg) }
   60% { transform: rotate( 0.0deg) }  /* Reset for the last half to pause */
  100% { transform: rotate( 0.0deg) }
}
</code></pre>
<h2>HTML</h2>
<pre><code class="language-html">&#x3C;h1>Hi there! &#x3C;span class="wave">👋&#x3C;/span>&#x3C;/h1>
</code></pre>
<hr>
<p>That’s it! More hands and skin tones can be <a href="https://emojipedia.org/search/?q=waving+hand">found on 📕 Emojipedia</a>.</p>
<p><strong>👋🏼 Toodles!</strong></p>
        <p><a href="https://jarv.is/notes/css-waving-hand-emoji"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[How To: Fork a GitHub Repository & Submit a Pull Request]]></title>
            <link>https://jarv.is/notes/how-to-pull-request-fork-github</link>
            <guid isPermaLink="false">https://jarv.is/notes/how-to-pull-request-fork-github</guid>
            <pubDate>Tue, 09 Apr 2019 06:17:03 GMT</pubDate>
            <description><![CDATA[Walkthrough of forking a GitHub repository, cloning it, committing your changes to a new branch, and pushing it back upstream.]]></description>
            <content:encoded><![CDATA[<p>Contributing to an open-source project can be intimidating at first. The convoluted process of submitting your improvements for approval via a <a href="https://help.github.com/en/articles/about-pull-requests"><strong>pull request</strong></a> certainly doesn’t help.</p>
<p>The following steps to submit a pull request will work on Git repositories hosted anywhere — on <a href="https://gitlab.com/">GitLab</a>, <a href="https://bitbucket.org/">Bitbucket</a>, <a href="https://azure.microsoft.com/en-us/services/devops/repos/">Azure DevOps</a>, etc. — but most open-source repositories one would want to contribute to are likely on <a href="https://github.com/"><strong>GitHub</strong></a>, which is what we’ll be using.</p>
<p>Starting from the very beginning, we’ll fork an existing repository to our account, clone the fork locally, commit your changes to a new branch, and push it back upstream to GitHub to submit for approval.</p>
<h2>1. Forking the Repository</h2>
<p>Assuming you’re using GitHub, this step is easy. Just find the repository you’re contributing to and press the Fork button in the upper right. This will create an exact copy of the repository (and all of its branches) under your own username.</p>

<h2>2. Clone your new fork locally</h2>
<p>GitHub will automatically redirect you to the forked repository under your username. This is the repository you need to clone to your local development environment, <strong>not</strong> the original. Grab the URL GitHub provides under the green “Clone or Download” button and plug it into the command below.</p>
<pre><code class="language-bash">git clone git@github.com:jakejarvis/react-native.git
</code></pre>

<h2>3. Track the original repository as a remote of the fork</h2>
<p><em>This step is technically optional, but important if you plan to continue contributing to a project in the future, so we might as well…</em></p>
<p>Once you’ve forked a repository, changes to the original (or “upstream”) repository are not pushed to your fork. We need to tell the new repository to follow changes made upstream to keep it fresh via <a href="https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes">remotes</a>.</p>
<p>Switch directories to the forked repository you just cloned and run the following commands. Replace the last part of the first line with the <strong>original</strong> repository clone URL — similar to the how you grabbed the URL in step 2, but this <strong>isn’t</strong> the one with your username.</p>
<p>This links the fork back to the original repository as a remote, which we’ll name <code>upstream</code>, and then fetch it.</p>
<pre><code class="language-bash">git remote add --track master upstream git@github.com:facebook/react-native.git
git fetch upstream
</code></pre>
<h2>4. Create a new branch for your changes</h2>
<p>It’s possible to make changes directly to the <code>master</code> branch, but this might FUBAR things down the road for complicated reasons. It’s best to <a href="https://git-scm.com/docs/git-checkout"><code>checkout</code></a> a new branch for <strong>each</strong> change/improvement you want to make. Replace <code>fix-readme-typo</code> with a more descriptive name for your changes, like <code>add-mobile-site</code> or <code>update-dependencies</code>.</p>
<pre><code class="language-bash">git checkout -b fix-readme-typo upstream/master
</code></pre>
<h2>5. Make your changes!</h2>
<p>This is either the easiest part or the hardest part, depending on how you look at it. 😉 At this point, you’re isolated in the new branch you just created, and it’s safe to open whatever text editor or IDE you use and go wild.</p>
<h2>6. Add, commit, and push the changes</h2>
<p>You’re probably used to these commands. Add the files you’ve changed and commit them with a descriptive message.</p>
<pre><code class="language-bash">git add .
git commit -m "Fix grammar mistakes in the readme file"
</code></pre>
<p>The one difference is the branch you’re pushing to. You likely usually push to <code>master</code>, but in this case, we’re pushing to the branch with the name you created in step 4.</p>
<pre><code class="language-bash">git push -u origin fix-readme-typo
</code></pre>
<h2>7. Submit your pull request</h2>
<p>You’re now all ready to submit the improvement you’ve made to the project’s maintainers for approval. Head over to the original repositories Pull Requests tab, and you should see an automatic suggestion from GitHub to create a pull request from your new branch.</p>


<hr>
<p>I’ll admit, I need to refer back to these notes sometimes when I’m preparing to contribute to an open-source project. It’s certainly not the most <a href="https://ohshitgit.com/">intuitive</a> process, but at least it’s <strong>exactly the same</strong> wherever the project is located — for example, I host my own <a href="https://git.jarv.is/">small Gitea server</a> to back up some of my GitHub account. This instant compatibility between completely different services is precisely what makes Git great! 🏆</p>
        <p><a href="https://jarv.is/notes/how-to-pull-request-fork-github"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[Finding Candidates for Subdomain Takeovers]]></title>
            <link>https://jarv.is/notes/finding-candidates-subdomain-takeovers</link>
            <guid isPermaLink="false">https://jarv.is/notes/finding-candidates-subdomain-takeovers</guid>
            <pubDate>Sun, 10 Mar 2019 15:19:48 GMT</pubDate>
            <description><![CDATA[A subdomain takeover occurs when a subdomain points to a shared hosting account that is abandoned by its owner, leaving the endpoint available to claim for yourself.]]></description>
            <content:encoded><![CDATA[<p>A <strong>subdomain takeover</strong> occurs when a subdomain (like <em>example</em>.jarv.is) points to a shared hosting account that is abandoned by its owner, leaving the endpoint available to claim for yourself.</p>
<p>Not only are takeovers a fun way to dip your toes into <a href="https://www.cloudflare.com/learning/security/glossary/what-is-penetration-testing/">penetration testing</a>, but they can also be incredibly lucrative thanks to <a href="https://en.wikipedia.org/wiki/Bug_bounty_program">bug bounty programs</a> on services like <a href="https://hackerone.com/hacktivity?order_direction=DESC&#x26;order_field=popular&#x26;filter=type%3Aall&#x26;querystring=subdomain%20takeover">HackerOne</a> and <a href="https://bugcrowd.com/programs">Bugcrowd</a>, where corporations pay pentesters for their discoveries.</p>
<p>
<em><a href="https://hackerone.com/hacktivity?querystring=subdomain%20takeover">Huge rewards for subdomain takeovers on HackerOne!</a></em></p>
<p>For a deep dive on the implications of takeovers, which can be a pretty serious vector of attack for malicious actors to obtain information from users of the targeted company, <a href="https://twitter.com/0xpatrik">Patrik Hudak</a> wrote a <a href="https://0xpatrik.com/subdomain-takeover/">great post here</a>. Definitely take some time to skim through it and come back here when you’re ready to hunt for a potential takeover yourself.</p>
<p>The most common services eligible for takeovers of abandoned subdomains are the following:</p>
<ul>
<li>Amazon S3</li>
<li>~~Amazon CloudFront~~ <a href="https://github.com/EdOverflow/can-i-take-over-xyz/issues/29">(no longer vulnerable?)</a></li>
<li>Microsoft Azure</li>
<li>Heroku</li>
<li>GitHub Pages</li>
<li>Fastly</li>
<li>Pantheon.io</li>
<li>Shopify</li>
<li>Tumblr</li>
<li><a href="https://github.com/EdOverflow/can-i-take-over-xyz#all-entries">…and many more.</a></li>
</ul>
<hr>
<p>On <a href="https://github.com/jakejarvis/">my GitHub profile</a>, you’ll find a Go-based tool named <a href="https://github.com/jakejarvis/subtake"><code>subtake</code></a> (based on <a href="https://github.com/haccer/subjack"><code>subjack</code></a>).</p>
<p>This tool takes a list of <a href="https://support.dnsimple.com/articles/cname-record/">CNAME records</a> to check and outputs potential takeover candidates pointing to these services. But how in the world do we get a list of every CNAME on the internet?</p>
<p>Conveniently, <a href="https://www.rapid7.com/">Rapid7</a> publishes a monthly list for us through their <a href="https://www.rapid7.com/research/project-sonar/">Project Sonar</a> survey!</p>
<blockquote>
<p><a href="https://opendata.rapid7.com/about/">Project Sonar</a> is a security research project by Rapid7 that conducts internet-wide surveys across different services and protocols to gain insights into global exposure to common vulnerabilities. The data collected is available to the public in an effort to enable security research.</p>
</blockquote>
<p>One of their free monthly datasets is called <a href="https://opendata.rapid7.com/sonar.fdns_v2/">Forward DNS</a>, where you’ll find <code>.json</code> files named <code>xxxx-fdns_cname.json.gz</code>. Within the <a href="https://github.com/jakejarvis/subtake"><code>subtake</code></a> repository, there’s an automated script named <a href="https://github.com/jakejarvis/subtake/blob/master/sonar.sh"><code>sonar.sh</code></a>, which downloads the dataset for you and outputs a simple text file of CNAMEs pointed to any of the services listed above. Once you’ve <a href="https://github.com/jakejarvis/subtake">cloned the <code>subtake</code> repository</a> and grabbed the timestamp part of the filename (the string that precedes <code>-fdns_cname.json.gz</code>), usage of the script is as follows:</p>
<pre><code class="language-bash">./sonar.sh 2019-03-30-1553989414 sonar_output.txt
</code></pre>
<p>This new text file contains <em>both active and abandoned</em> subdomains pointing to any of the services listed above — we still need to narrow it down to the takeover candidates by attempting to actually resolve each of them, which is where <code>subtake</code> comes into play. To install <code>subtake</code>, make sure <a href="https://golang.org/doc/install#install">Go is installed first</a> and run the following:</p>
<pre><code class="language-bash">go get github.com/jakejarvis/subtake
</code></pre>
<p>For a detailed description of the different options you can play around with, see the <a href="https://github.com/jakejarvis/subtake#usage">full readme on GitHub</a> — but here’s a simple example command that uses 50 threads to take the CNAMEs listed in <code>sonar_output.txt</code> and outputs potentially vulnerable subdomains to <code>vulnerable.txt</code>.</p>
<pre><code class="language-bash">subtake -f sonar_output.txt -c fingerprints.json -t 50 -ssl -a -o vulnerable.txt
</code></pre>
<p>This could take quite a while — up to a day, depending on your CPU, memory, and bandwidth — so I usually run it on a VM in the cloud and use <a href="https://www.howtoforge.com/linux_screen">Linux’s <code>screen</code> command</a> to keep it running and check in periodically. There will also be many unavoidable false positives that you’ll need to check yourself by trying to claim the abandoned name on the corresponding service’s portal, which is why I keep using the term <em>potential</em> takeovers.</p>
<p>I also have a collection of root domains of companies offering bounties through <a href="https://hackerone.com/directory/">HackerOne</a> or <a href="https://bugcrowd.com/programs">Bugcrowd</a> at a <a href="https://github.com/jakejarvis/bounty-domains/">different GitHub repository</a>. Using the <a href="https://github.com/jakejarvis/bounty-domains/blob/master/grep.txt"><code>grep</code>-friendly text file</a>, it’s easy to use <a href="https://man7.org/linux/man-pages/man1/grep.1.html"><code>grep</code></a> to narrow down your <code>vulnerable.txt</code> list even more:</p>
<pre><code class="language-bash">grep -f grep.txt vulnerable.txt
</code></pre>
<hr>
<p>In my view, takeovers are a fantastic way to begin a side hustle in bug bounties, simply due to the fact that once you’ve taken over a subdomain, you don’t need to worry about another hunter beating you to the punch and reporting it before you.</p>
<p>Since you have this luxury of time, it becomes <strong><em>extremely important</em></strong> that you let your adrenaline subside and follow <a href="https://www.bugcrowd.com/resource/what-is-responsible-disclosure/">responsible disclosure</a> guidelines — especially in the creation of a “proof of concept” file with your username at an obscure location, <strong>not</strong> at <code>index.html</code>. I won’t go over the details of writing a report because <a href="https://twitter.com/0xpatrik">Patrik Hudak</a> wrote another <a href="https://0xpatrik.com/takeover-proofs/">great post about it here</a>. This is an example of one of my own reports (company name censored because it has not been publicly disclosed) on <a href="https://bugcrowd.com/programs">Bugcrowd</a>:</p>
<blockquote>
<p>I have found three subdomains of ********.com vulnerable to takeovers via unclaimed endpoints at <a href="https://azure.microsoft.com/en-us/services/traffic-manager/">Azure’s Traffic Manager</a>. I have claimed these endpoints and redirected them to a blank page to prevent a bad actor from doing so in the meantime, and hosted a POC file at obscure URLs. These are the following domains I discovered and the outdated endpoints on Azure to which they point:</p>
<p>xxxx.********.com —> aaa.trafficmanager.net</p>
<p>yyyy.********.com —> bbb.trafficmanager.net</p>
<p>zzzz.********.com —> ccc.trafficmanager.net</p>
<p>…and the proof-of-concept files are at the following locations:</p>
<p><a href="#">http://xxxx.********.com/poc-d4ca9e8ceb.html</a></p>
<p><a href="#">http://yyyy.********.com/poc-d4ca9e8ceb.html</a></p>
<p><a href="#">http://zzzz.********.com/poc-d4ca9e8ceb.html</a></p>
<p>I have not hosted any other file nor attempted any other vector of attack. You’re probably familiar with takeovers like this by now, but through this vulnerability, it would be possible for an attacker to obtain cookies and other sensitive information from your users via phishing, cookie hijacking, or XSS. It is also possible to obtain SSL certificates for ********.com subdomains from CAs that only require domain validation such as <a href="https://letsencrypt.org/how-it-works/">Let’s Encrypt</a>, but I have not attempted to do so. More info on possible attack vectors <a href="https://0xpatrik.com/subdomain-takeover/">can be found here</a>.</p>
<p>Please let me know when you’ve received this report and I’ll delete the endpoints from my personal Azure account, so you can either reclaim them or remove the subdomains entirely from your DNS records. Thanks!</p>
</blockquote>
<p>I removed the company’s name because an important part of responsible <em>disclosure</em> is the <em>disclosure</em>, or lack thereof. Until the company explicitly gives permission to publicly disclose the vulnerability after patching it — and there are built-in features on both HackerOne and Bugcrowd to request this — it’s <strong>not okay</strong> to talk about it publicly.</p>
<p>The <code>poc-d4ca9e8ceb.html</code> proof-of-concept file contained this single, hidden line:</p>
<pre><code class="language-html">&#x3C;!-- subdomain takeover POC by @jakejarvis on Bugcrowd -->
</code></pre>
<p>No self-promotional links or redirects, no examples of XSS/cookie hijacking to be “helpful” (no matter how harmless), no funny business of any kind.</p>
<hr>
<p>I have several more <a href="https://github.com/jakejarvis/subtake#to-do">improvements</a> I want to make to <code>subtake</code> (like integrating the <code>sonar.sh</code> script into the main Go executable, polishing the <a href="https://hub.docker.com/r/jakejarvis/subtake">all-in-one automated Docker image</a>, a self-updating list of service fingerprints, etc.) but still feel free to <a href="https://github.com/jakejarvis/subtake/issues">make a suggestion</a> and/or contribute to the repository in the meantime.</p>
<p>Happy hunting, fellow penetrators! 😉</p>
        <p><a href="https://jarv.is/notes/finding-candidates-subdomain-takeovers"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[Adding Security Headers Using Cloudflare Workers]]></title>
            <link>https://jarv.is/notes/security-headers-cloudflare-workers</link>
            <guid isPermaLink="false">https://jarv.is/notes/security-headers-cloudflare-workers</guid>
            <pubDate>Thu, 28 Feb 2019 07:18:10 GMT</pubDate>
            <description><![CDATA[How to add important security headers to your website using Cloudflare Workers before delivering the response to the user.]]></description>
            <content:encoded><![CDATA[<p>
<em>An <a href="https://securityheaders.com/?q=jarv.is&#x26;followRedirects=on">A+ security grade</a> for this website!</em></p>
<p>In 2019, it’s becoming more and more important to harden websites via HTTP response headers, which all modern browsers parse and enforce. Multiple standards have been introduced over the past few years to protect users from various attack vectors, including <code>Content-Security-Policy</code> for injection protection, <code>Strict-Transport-Security</code> for HTTPS enforcement, <code>X-XSS-Protection</code> for cross-site scripting prevention, <code>X-Content-Type-Options</code> to enforce correct MIME types, <code>Referrer-Policy</code> to limit information sent with external links, <a href="https://www.netsparker.com/whitepaper-http-security-headers/">and many, many more</a>.</p>
<p><a href="https://www.cloudflare.com/products/cloudflare-workers/">Cloudflare Workers</a> are a great feature of <a href="https://www.cloudflare.com/">Cloudflare</a> that allows you to modify responses on-the-fly between your origin server and the user, similar to <a href="https://aws.amazon.com/lambda/">AWS Lambda</a> (but much simpler). We’ll use a Worker to add the headers.</p>

<p>Workers can be enabled for $5/month via the <a href="https://dash.cloudflare.com/">Cloudflare Dashboard</a>. (It’s worth noting, once enabled, Workers can be used on <em>any zone</em> on your account, not just one website!).</p>
<p>If you run your own server, these can be added by way of your Apache or nginx configuration. But if you’re using a shiny static site host like <a href="https://pages.github.com/">GitHub Pages</a>, <a href="https://aws.amazon.com/s3/">Amazon S3</a>, <a href="https://surge.sh/">Surge</a>, etc. it may be difficult or impossible to do so.</p>
<p>The following script can be added as a Worker and customized to your needs. Some can be extremely picky with syntax, so be sure to <a href="https://www.netsparker.com/whitepaper-http-security-headers/">read the documentation</a> carefully. You can fiddle with it in <a href="https://cloudflareworkers.com/">the playground</a>, too. Simply modify the current headers to your needs, or add new ones to the <code>newHeaders</code> or <code>removeHeaders</code> arrays.</p>
<pre><code class="language-js">let addHeaders = {
  "Content-Security-Policy": "default-src 'self'; upgrade-insecure-requests",
  "Strict-Transport-Security": "max-age=1000",
  "X-XSS-Protection": "1; mode=block",
  "X-Frame-Options": "SAMEORIGIN",
  "X-Content-Type-Options": "nosniff",
  "Referrer-Policy": "same-origin",
};

let removeHeaders = ["Server", "Public-Key-Pins", "X-Powered-By", "X-AspNet-Version"];

addEventListener("fetch", (event) => {
  event.respondWith(fetchAndApply(event.request));
});

async function fetchAndApply(request) {
  // Fetch the original page from the origin
  let response = await fetch(request);

  // Make response headers mutable
  response = new Response(response.body, response);

  // Set each header in addHeaders
  Object.keys(addHeaders).map(function (name, index) {
    response.headers.set(name, addHeaders[name]);
  });

  // Delete each header in removeHeaders
  removeHeaders.forEach(function (name) {
    response.headers.delete(name);
  });

  // Return the new mutated page
  return response;
}
</code></pre>
<p>Once you’re done, you can analyze your website’s headers and get a letter grade with <a href="https://scotthelme.co.uk/">Scott Helme</a>’s awesome <a href="https://securityheaders.com/">Security Headers</a> tool. His free <a href="https://report-uri.com/">Report-URI</a> service is another great companion tool to monitor these headers and report infractions your users run into in the wild.</p>
<p>You can view my website’s <a href="https://github.com/jakejarvis/jarv.is/blob/ededcc05c4a5b2650d5a7eb6f8d00496b61221e3/worker.js">full Worker script here</a> and check out <a href="https://securityheaders.com/?q=https%3A%2F%2Fjarv.is%2F">the resulting A+ grade</a>!</p>
        <p><a href="https://jarv.is/notes/security-headers-cloudflare-workers"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[Cool Bash Tricks for Your Terminal’s “Dotfiles”]]></title>
            <link>https://jarv.is/notes/cool-bash-tricks-for-your-terminal-dotfiles</link>
            <guid isPermaLink="false">https://jarv.is/notes/cool-bash-tricks-for-your-terminal-dotfiles</guid>
            <pubDate>Tue, 11 Dec 2018 00:01:50 GMT</pubDate>
            <description><![CDATA[Bashfiles usually contain shortcuts compatible with Bash terminals to automate convoluted commands. Here's a summary of the ones I find most helpful that you can add to your own .bash_profile or .bashrc file.]]></description>
            <content:encoded><![CDATA[<p>You may have noticed the recent trend of techies <a href="https://github.com/topics/dotfiles">posting their “dotfiles” on GitHub</a> for the world to see. These usually contain shortcuts compatible with Bash terminals to automate convoluted commands that, I’ll admit, I needed to Google every single time.</p>
<p>My <a href="https://github.com/jakejarvis/dotfiles">full dotfiles are posted at this Git repository</a>, but here’s a summary of the ones I find most helpful that you can add to your own <code>.bash_profile</code> or <code>.bashrc</code> file.</p>
<hr>
<p>Check your current IP address (IPv4 or IPv6 or both) — uses <a href="https://github.com/jakejarvis/simpip">my ⚡ fast simpip server!</a></p>
<pre><code class="language-bash">alias ip4="curl -4 simpip.com --max-time 1 --proto-default https --silent"
alias ip6="curl -6 simpip.com --max-time 1 --proto-default https --silent"
alias ip="ip4; ip6"
</code></pre>
<p>Check your current local IP address:</p>
<pre><code class="language-bash">alias iplocal="ipconfig getifaddr en0"
</code></pre>
<p>Check, clear, set (<a href="https://developers.google.com/speed/public-dns/">Google DNS</a> or <a href="https://1.1.1.1/">Cloudflare DNS</a> or custom), and flush your computer’s DNS, overriding your router:</p>
<pre><code class="language-bash">alias dns-check="networksetup -setdnsservers Wi-Fi"
alias dns-clear="networksetup -getdnsservers Wi-Fi"

alias dns-set-cloudflare="dns-set 1.1.1.1 1.0.0.1"
alias dns-set-google="dns-set 8.8.8.8 8.8.4.4"
alias dns-set-custom="networksetup -setdnsservers Wi-Fi "   # example: dns-set-custom 208.67.222.222 208.67.220.220

alias dns-flush="sudo killall -HUP mDNSResponder; sudo killall mDNSResponderHelper; sudo dscacheutil -flushcache"
</code></pre>
<p>Start a simple local web server in current directory:</p>
<pre><code class="language-bash">alias serve="python -c 'import SimpleHTTPServer; SimpleHTTPServer.test()'"
</code></pre>
<p>Test your internet connection’s speed (uses 100MB of data):</p>
<pre><code class="language-bash">alias speed="wget -O /dev/null http://cachefly.cachefly.net/100mb.test"
</code></pre>
<p>Query DNS records of a domain:</p>
<pre><code class="language-bash">alias digg="dig @8.8.8.8 +nocmd any +multiline +noall +answer"   # example: digg google.com
</code></pre>
<p>Make a new directory and change directories into it.</p>
<pre><code class="language-bash">mkcd() {
    mkdir -p -- "$1" &#x26;&#x26;
    cd -P -- "$1"
}
</code></pre>
<p>Unhide and rehide hidden files and folders on macOS:</p>
<pre><code class="language-bash">alias unhide="defaults write com.apple.finder AppleShowAllFiles -bool true &#x26;&#x26; killall Finder"
alias rehide="defaults write com.apple.finder AppleShowAllFiles -bool false &#x26;&#x26; killall Finder"
</code></pre>
<p>Force empty trash on macOS:</p>
<pre><code class="language-bash">alias forcetrash="sudo rm -rf ~/.Trash /Volumes/*/.Trashes"
</code></pre>
<p>Quickly lock your screen on macOS:</p>
<pre><code class="language-bash">alias afk="/System/Library/CoreServices/Menu\ Extras/User.menu/Contents/Resources/CGSession -suspend"
</code></pre>
<p>Update Homebrew packages, global NPM packages, Ruby Gems, and macOS in all one swoop:</p>
<pre><code class="language-bash">alias update="brew update; brew upgrade; brew cleanup; npm install npm -g; npm update -g; sudo gem update --system; sudo gem update; sudo gem cleanup; sudo softwareupdate -i -a;"
</code></pre>
<p>Copy your public key to the clipboard:</p>
<pre><code class="language-bash">alias pubkey="more ~/.ssh/id_rsa.pub | pbcopy | echo '=> Public key copied to pasteboard.'"
</code></pre>
<p>Undo the most recent commit in current Git repo:</p>
<pre><code class="language-bash">alias gundo="git push -f origin HEAD^:master"
</code></pre>
<p>Un-quarantine an “unidentified developer’s” application <a href="https://support.apple.com/en-us/HT202491">blocked by Gatekeeper</a> on macOS’s walled ~~prison~~ garden:</p>
<pre><code class="language-bash">alias unq="sudo xattr -rd com.apple.quarantine"
</code></pre>
<p>Quickly open a Bash prompt in a running Docker container:</p>
<pre><code class="language-bash">docker-bash() {
    docker exec -ti $1 /bin/bash
}
</code></pre>
<p>Pull updates for all Docker images with the tag “latest”:</p>
<pre><code class="language-bash">alias docker-latest="docker images --format '{{.Repository}}:{{.Tag}}' | grep :latest | xargs -L1 docker pull"
</code></pre>
<p>This odd hack is needed to run any of these aliases as sudo:</p>
<pre><code class="language-bash">alias sudo="sudo "
</code></pre>
<hr>
<p><a href="https://github.com/jakejarvis/dotfiles">View all of my dotfiles here</a> or <a href="https://dotfiles.github.io/">check out other cool programmers’ dotfiles over at this amazing collection</a>.</p>
        <p><a href="https://jarv.is/notes/cool-bash-tricks-for-your-terminal-dotfiles"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[How To: Shrink a Linux Virtual Machine Disk with VMware]]></title>
            <link>https://jarv.is/notes/how-to-shrink-linux-virtual-disk-vmware</link>
            <guid isPermaLink="false">https://jarv.is/notes/how-to-shrink-linux-virtual-disk-vmware</guid>
            <pubDate>Tue, 04 Dec 2018 23:10:04 GMT</pubDate>
            <description><![CDATA[VMware is bad at shrinking Linux VMs when space is freed up. How to optimize and shrink virtual disks.]]></description>
            <content:encoded><![CDATA[<p>
<em><code>df -dh</code> = WTF</em></p>
<p><strong><a href="https://www.vmware.com/products/workstation-pro.html">VMware Workstation</a></strong> and <strong><a href="https://www.vmware.com/products/fusion.html">Fusion</a></strong> normally work hard to minimize the size of virtual hard disks for optimizing the amount of storage needed on your host machine . On Windows virtual machines, <a href="https://docs.vmware.com/en/VMware-Fusion/11/com.vmware.fusion.using.doc/GUID-6BB29187-F47F-41D1-AD92-1754036DACD9.html">VMware has a “clean up” function</a>, which detects newly unused space and makes the size of the virtual hard disk smaller accordingly. You’ll notice that even if you create a virtual machine with a capacity of 60 GB, for example, the actual size of the VMDK file will dynamically resize to fit the usage of the guest operating system. 60 GB is simply the maximum amount of storage allowed; if your guest operating system and its files amount to 20 GB, the VMDK file will simply be 20 GB.</p>
<p>VMware can be set to automatically optimize and shrink virtual hard disks as you add and, more importantly, remove files — but <a href="https://docs.vmware.com/en/VMware-Fusion/11/com.vmware.fusion.using.doc/GUID-6BB29187-F47F-41D1-AD92-1754036DACD9.html">this automatic “clean up” setting is disabled by default</a>. Either way, cleaning up virtual machines works like a charm…when you have Windows as a guest operating system with an NTFS disk.</p>
<p>As a developer, I have several VMs with various Linux-based guest OSes — and, for some reason, VMware doesn’t know how to optimize these. If you poke around in VMware, you’ll find that the clean up button is greyed-out under the settings of a Linux VM.</p>
<p>Commonly, I’ll use a few gigabytes of storage for a project and then delete the files from the guest when I’m done. Let’s say that my Debian guest starts at 10 GB and I use 5 GB for my project, totaling 15 GB. The VMDK file will be, obviously, 15 GB. I finish the project and delete the 5 GB of its files. On a Windows guest, VMware would be able to shrink the volume back down to 10 GB — but you’ll quickly notice, annoyingly, that a Linux disk will remain at 15 GB, even though you’re no longer using that much. On a portable machine like my MacBook Air, this can be a <em>huge</em> waste!</p>
<p>The “clean up” feature that VMware has developed for Windows guests can be applied to Linux guests as well, but it’s pretty convoluted — we need to essentially clean up the VM ourselves, trick VMware to detect the free space, and manually shrink the volume.</p>
<p><strong><em>A tiny caveat:</em></strong> This only works on VMs without any snapshots. Sadly, you either need to delete them or, if you care about keeping snapshots, you can backup the VM as-is to an external disk and then delete the local snapshots.</p>
<p>Once you’re ready, here’s how to shrink your Linux-based VM:</p>
<hr>
<h3>Update (Dec. 30, 2018):</h3>
<p>The open-source version of VMware Tools for Linux, <a href="https://github.com/vmware/open-vm-tools">open-vm-tools</a>, has added a simple command to automate the above steps in the latest version. Make sure you have the latest update through either apt or yum, and then run the following command in the <strong>guest</strong> terminal:</p>
<pre><code class="language-bash">vmware-toolbox-cmd disk shrink /
</code></pre>
<p>Thank you to <a href="https://jake.wordpress.com/2018/12/04/how-to-shrink-linux-virtual-disk-vmware/#comment-21">commenter Susanna</a> for pointing this out! The manual way below still works exactly the same.</p>
<hr>
<h2>Step 1: Clean up time</h2>
<p>Boot up your Linux virtual machine. We’ll start by optimizing the OS as much as possible before shrinking it. In addition to manually deleting files you no longer use, running this command in your terminal can free up a little more space by removing some installation caches left behind by old versions of software you’ve installed and updated:</p>
<pre><code class="language-bash">sudo apt-get clean
</code></pre>
<h2>Step 2: Make “empty” space actually empty</h2>
<p>This step is the crucial one. In order for VMware to detect the newly free space, we need to free it up ourselves using a little trickery. We’re going to have Linux overwrite the free space with a file full of zeros — the size of this file will be the size of however much space we’re freeing up (5 GB, in the example above) — and then delete it. These commands will create the file, wait a moment, and then delete the file:</p>
<pre><code class="language-bash">cat /dev/zero > zero.fill
sync
sleep 1
sync
rm -f zero.fill
</code></pre>
<p>Depending on how much space we’re freeing, this could take a while. Let it finish or else you’ll be left with an actual, real file that will occupy a ton of space — the opposite of what we’re trying to accomplish!</p>
<h2>Step 3: Letting VMware know we’ve done its dirty work</h2>
<p>The final step is to tell VMware we’ve done this, and manually trigger the clean up function that works so well on Windows VMs. You’ll do this step <strong>outside</strong> of the virtual machine, so shut it down fully and exit VMware. These directions are for macOS hosts specifically — if you’re on a Linux host, I’ll assume you are able to find the VMDK file, but <a href="https://www.howtogeek.com/112674/how-to-find-files-and-folders-in-linux-using-the-command-line/">here’s some help if you need</a>.</p>
<p>VMware on macOS makes this a little tricky, since it packages VMs in what looks like a “.vmwarevm” file, which is actually a folder. Browse to wherever you’ve saved your virtual machines, probably somewhere in your home folder, and find the location of this “.vmwarevm” androgynous item. If you click on this folder, though, it’ll just open VMware again.</p>
<p>We need to right click on the .vmwarevm “file,” and select <strong>Show Package Contents</strong> to see what’s really in there. You should see the actual .VMDK file sitting there — normally we’re looking for the plain VMDK file (named <em>Virtual Disk.vmdk</em> by default) without a bunch of numbers after it, but if you have snapshots associated with your VM, this might not be the file we actually want. But run the command below with it anyways, and the output will tell you if you need to use a different file.</p>

<p>Now, we’re going to run our final command in our <strong>host</strong> terminal, so open that up. Linux installations of VMware Workstation should have a simple map to the <em>vmware-vdiskmanager</em> utility that you can run anywhere, but on macOS we need to tell it exactly where that’s located: in the Applications folder, where Fusion is installed.</p>
<p>We’re going to feed this command the exact location of the VMDK file we’re shrinking. You can either do this by typing the <strong>full path</strong> to it, or by simply dragging the VMDK file onto the terminal after typing the first part of the command (up to and including “-d”). The “-d” argument will defragment the disk.</p>
<pre><code class="language-bash">/Applications/VMware\ Fusion.app/Contents/Library/vmware-vdiskmanager -d &#x3C;path to your .VMDK file>
</code></pre>
<p>The final command should look something like this, with your VMDK file instead:</p>
<pre><code class="language-bash">/Applications/VMware\ Fusion.app/Contents/Library/vmware-vdiskmanager -d /Users/jake/Documents/Virtual\ Machines/Debian9.vmwarevm/Virtual\ Disk.vmdk
</code></pre>
<p>If you’ve done this correctly, you’ll see it defragmenting the file, and then return “Defragmentation completed successfully.” If it returns a different error, such as “This disk is read-only in the snapshot chain,” it should tell you which disk you should actually shrink. Just run the command again with that VMDK file instead.</p>
<p>After the defragmentation completes, we need to finally shrink the image. We do this by running the same command as you did above, but replacing the “-d” with “-k” as follows:</p>
<pre><code class="language-bash">/Applications/VMware\ Fusion.app/Contents/Library/vmware-vdiskmanager -k &#x3C;path to the same .VMDK file>
</code></pre>
<h2>Step 4: Storage Profit!</h2>
<p>Obviously, this is a really annoying way to perform a feature that only takes one click to execute on Windows virtual machines. I don’t recommend going through this entire process every time you delete a few random files. However, if you notice the free space on your host OS is mysteriously lower than it should be, the time this takes can be well worth it.</p>
<p>Let’s hope this will be integrated in VMware Tools in the near future — feel free to <a href="https://my.vmware.com/group/vmware/get-help?p_p_id=getHelp_WAR_itsupport&#x26;p_p_lifecycle=0&#x26;_getHelp_WAR_itsupport_execution=e1s2">nudge VMware about it</a> in the meantime!</p>
        <p><a href="https://jarv.is/notes/how-to-shrink-linux-virtual-disk-vmware"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
        <item>
            <title><![CDATA[Why This Millennial Is With Hillary Clinton Now — and Why We All Need To Be In November]]></title>
            <link>https://jarv.is/notes/millenial-with-hillary-clinton</link>
            <guid isPermaLink="false">https://jarv.is/notes/millenial-with-hillary-clinton</guid>
            <pubDate>Mon, 29 Feb 2016 04:10:26 GMT</pubDate>
            <description><![CDATA[I am a 24-year-old "millennial" and I passionately support Hillary Clinton for the 45th President of the United States. Yes, we exist.]]></description>
            <content:encoded><![CDATA[<p>
<em><a href="https://medium.com/@HillaryForNH">Hillary for New Hampshire</a> Winter Fellows with <a href="https://medium.com/@HillaryClinton">Hillary Clinton</a> in Derry, NH (<a href="https://www.flickr.com/photos/hillaryclinton/24707394571/">February 3, 2016</a>)</em></p>
<h2>Keeping in mind the big picture…</h2>
<p>I am a 24-year-old “millennial” and I passionately support <a href="https://www.hillaryclinton.com/">Hillary Clinton</a> for the 45th President of the United States. Yes, we exist.</p>
<p>My goal here isn’t to convince every Bernie believer to jump ship and support her as passionately as I do, although I feel obligated to try. I totally understand the passion for Bernie. I smile inside every time I see a young person (like my sister) become interested in politics for the first time and become directly involved in influencing the course of their own future, no matter which candidate triggered it for them. For me, it was admittedly Senator Obama. I would, however, like to put the Democratic Party primary process back into perspective, because it’s turned into a bloodsport that isn’t helpful for <em>anybody</em> in the long run — not for either candidate, not for our party, and certainly not for our country.</p>
<p><strong>News Flash:</strong> We aren’t in the general election right now. Not even close. We’re in the middle of <em>our own party’s</em> primary, where the field of opponents we are choosing from are all our friends. They’re both on our side. They both agree on an overall vision for our country. Of course as individuals we choose one who we like better than the other, and root for her or him and ideally invest some time and money to help however we can. I chose Hillary a long time ago because I feel she is, if anything, overqualified for the position. Especially during this increasingly turbulent period of foreign affairs, we can’t afford to allow an entry-level applicant to experiment with our standing in the world and learn our relationships with other nations on-the-fly.</p>
<p>After working for months as a fellow on Hillary’s campaign in New Hampshire leading up to the first primary in the country, I could feed you all the standard campaign talking points in my sleep: After graduating from Yale Law she went to work at the <a href="https://www.childrensdefense.org/">Children’s Defense Fund</a>, not a high-paying New York law firm. She <a href="https://www.nytimes.com/2015/12/28/us/politics/how-hillary-clinton-went-undercover-to-examine-race-in-education.html?_r=0">went undercover</a> in Alabama to investigate discrimination in public schools. She <a href="https://www.huffingtonpost.com/entry/huffpost-criminal-justice-survey-democratics_us_56bb85eae4b0b40245c5038b">got juveniles out of adult prisons</a>. She <a href="https://www.hillaryclinton.com/briefing/factsheets/2015/12/23/hillary-clintons-lifelong-fight-for-quality-affordable-health-care-for-all-americans/">gave 8 million children healthcare</a>. But there’s just one thing that, for some reason, is hard for people to believe: at her core she is a good, caring, and loving person who has had only selfless intentions her entire life. I promise you.</p>
<p>
<em>The best birthday gift. 🎉</em></p>
<p>I had the incredible chance to meet Hillary the weekend before the New Hampshire primary. Her motorcade plowed through a quiet suburb in Manchester around noon and she hopped out to go knock on the doors of some lucky families. As neighbors started coming out of their houses to shake her hand, I couldn’t restrain myself from at least trying to get close and wave hello. (By the way, it’s amazing how casual the people in New Hampshire are about meeting presidential candidates.)</p>
<p>I walked up nervously and told her that it was my birthday (it was) and all I wanted was for her to win, which got her attention, and I thanked her for the spotlight she had been shining on the rampant addiction epidemic in the state. Instead of nodding her head and thanking me for my support and moving along like I assumed she would — she knew she would have my vote no matter what — she locked eyes with me and asked me how I’d been affected by the issue. It felt as though she dropped everything in her life and literally put her jam-packed schedule on pause to make sure I was okay and to learn more about some dude she just met ten seconds ago. I told her that I had fallen into the trap myself when I was younger, and that the <a href="https://www.hillaryclinton.com/issues/addiction/">part of her detailed plan</a> that addresses the overprescription of narcotics by doctors could have prevented me from doing so. As my conversation with her grew longer and longer, and as she respectfully asked me more and more questions about my story, I totally forgot I was casually chatting on the sidewalk with a freaking former First Lady, Senator, and Secretary of State. I promise you again: She. Is. A. Real. Person.</p>
<blockquote>
<p>“I know I have some work to do, particularly with young people, but I will repeat again what I have said this week. Even if they are not supporting me now, I support them.” <a href="https://www.vox.com/2016/2/9/10956458/hillary-clinton-new-hampshire">»</a></p>
</blockquote>
<p>But at the end of the day, all I ask is for you to keep in mind the stakes in this overall election. They have never been higher. Last year, the spectacle of Donald “The Donald” Trump running to be the leader of the free world was purely comical and impossible not to laugh at, from the moment he entered the race <a href="https://www.youtube.com/watch?v=Ab9AnZaLL1U">via gold-plated escalator</a> whilst blasting Neil Young. But as this racist xenophobic pumpkin is rapidly racking up <em>actual real-life delegates</em> thanks to votes from the <a href="https://www.vox.com/2016/2/24/11107788/donald-trump-poorly-educated">poorly educated</a> and/or the <a href="https://www.huffingtonpost.com/entry/donald-trump-white-supremacist-sec-primary_us_56cf4437e4b0bf0dab31222f">white supremacists</a>, the thought of him being within striking distance of the desk in the Oval Office is slowly twisting a knife into the pit of my stomach. This is real. This is the big picture. This is why we need to team up and work together in any way possible as soon as possible.</p>
<p>I’m aware of the street cred young Democrats collect by claiming they hated Hillary before hating Hillary was cool. Hating on HRC has gone more viral than Damn Daniel. But when you ask these young voters to explain why they think she’s a liar or untrustworthy or a criminal, they can rarely put their distaste for her into actual words — or if they do, they just vomit hashtag-ready soundbites from Fox News or The Young Turks. #Benghazi. #Emails. #ReleaseTheTranscripts. Joining in on the Republican-led attacks and stooping to their level is no way to advocate for the candidate you support. If you support Bernie for the nomination, you do that by going out and talking to others about why <strong>his</strong> policies rock, what <strong>his</strong> life story is, how <strong>your</strong> story relates to <strong>his</strong> story and <strong>his</strong> policies, etc. — not by spending your day mercilessly assassinating the character of a woman you’ve never met and a woman you might very well be voting for in eight short months, unless you’re able to stomach the idea of President Trump. During primary season, you win by focusing on the merits of your own candidate, not the flaws you see in another.</p>
<p>As <a href="https://medium.com/u/cdc04a9799f6">Bill Maher</a> (an avid Bernie supporter) <a href="https://www.youtube.com/watch?v=rd1gpjkjcfc">said this weekend</a>, some in our party need to “learn the difference between an imperfect friend and a deadly enemy.” I don’t agree with everything Hillary has said or done. I don’t unconditionally defend every single chapter in her public record over the past 30 years (and <a href="https://www.washingtonpost.com/blogs/post-partisan/wp/2016/02/25/hillary-clinton-responds-to-activist-who-demanded-apology-for-superpredator-remarks/">neither does she</a>, by the way). I don’t think that’s possible for any voter to find in a politician. But if you identify as a Democrat, she is the farthest thing from your enemy. Plain and simple. Like you and Bernie, she wants to prevent a Republican from winning in November and reversing so much of the progress we’ve made over the past seven years on their first day in office. That is our number one goal right now. And whether it gets accomplished by a President Clinton or a President Sanders, I am 100% on board either way. Let’s stop fighting each other and start fighting together.</p>

<hr>
<p><strong>Update:</strong> The campaign has included my snowy New Hampshire interaction with her in one of the DNC convention videos! See a few short seconds of my joy at 1:24.</p>
        <p><a href="https://jarv.is/notes/millenial-with-hillary-clinton"><strong>Continue reading...</strong></a></p>]]></content:encoded>
            <author>Jake Jarvis</author>
        </item>
    </channel>
</rss>