<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>tech, life &amp; music.</title>
  <subtitle></subtitle>
  <link href="https://richardneililagan.com/feed.xml" rel="self" />
  <link href="https://richardneililagan.com" />

  <updated>2025-11-22T00:00:00Z</updated>
  <id>https://richardneililagan.com</id>
  <author>
    <name>Richard Neil Ilagan</name>
    <email>otearai+richardneililagan.com@pm.me</email>
  </author>

  
  
  <entry>
    <title>I&#39;m blocking private IPv4 addresses on my servers by default from now on</title>
    <link href="https://richardneililagan.com/posts/blocking-rfc1918/" />
    <updated>2025-11-22T00:00:00Z</updated>
    <id>https://richardneililagan.com/posts/blocking-rfc1918/</id>
    <content type='html'>&lt;p&gt;Yesterday, I was setting up a new &lt;a href=&quot;https://www.raspberrypi.com/products/raspberry-pi-zero-2-w/&quot;&gt;Raspberry Pi Zero 2W&lt;/a&gt; in my home network.
I&#39;m going to use it to revive an old Brother printer I have lying around
and install &lt;a href=&quot;https://www.cups.org/&quot;&gt;CUPS&lt;/a&gt; on it, so that I can use said printer as a network printer on the cheap.&lt;/p&gt;
&lt;p&gt;I just had the Pi flashed with a headless Ubuntu, and preconfigured it to connect
to my home network via WiFi. I even gave it an &lt;code&gt;example.local&lt;/code&gt; hostname so that
I can talk to it as soon as I plug it in. But the hostname didn&#39;t seem to be working
when I turned it on; I couldn&#39;t SSH into the Pi from another computer in my network.
That&#39;s not a big problem; I didn&#39;t know it&#39;s resolved IPv4 address, but I can always just
run a network scan for it.&lt;/p&gt;
&lt;p&gt;So I jumped onto a terminal and did just that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;nmap &lt;span class=&quot;token parameter variable&quot;&gt;-p22&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;192.168&lt;/span&gt;.1.0/24&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;oops.&quot; tabindex=&quot;-1&quot;&gt;Oops.&lt;/h2&gt;
&lt;p&gt;What I didn&#39;t realize was that that terminal was currently live, tunneled into one of my remote servers via &lt;code&gt;ssh&lt;/code&gt;. This particular one was a baremetal I was renting off Hetzner.
Never mind that I had a live &lt;code&gt;ssh&lt;/code&gt; terminal left open, shame on my cow!&lt;/p&gt;
&lt;p&gt;That must&#39;ve rung alarm bells pretty swiftly over on &lt;a href=&quot;https://hetzner.com/&quot;&gt;Hetzner&lt;/a&gt;, because I got my server
locked and isolated as a result.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/gif&quot; srcset=&quot;https://richardneililagan.com/i/6as16dRfMe-880.gif 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/6as16dRfMe-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/6as16dRfMe-880.gif&quot; width=&quot;880&quot; height=&quot;388&quot; alt=&quot;The email I got from Hetzner&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;The email I got from Hetzner&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;h2 id=&quot;protecting-myself-from-myself&quot; tabindex=&quot;-1&quot;&gt;Protecting myself from myself&lt;/h2&gt;
&lt;p&gt;As recommended by the support team I talked to, it&#39;s probably a good idea to
block outgoing network packets from my box to private address spaces,
(defined as &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc1918&quot;&gt;RFC1918&lt;/a&gt;) especially
if I don&#39;t expect my machine to be talking to other servers inside the same space anyway.
This particular one wasn&#39;t, so I didn&#39;t have a problem with it.&lt;/p&gt;
&lt;p&gt;You can do this by setting an outgoing block in your &lt;code&gt;iptables&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;iptables &lt;span class=&quot;token parameter variable&quot;&gt;-A&lt;/span&gt; OUTPUT &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10.0&lt;/span&gt;.0.0/8 &lt;span class=&quot;token parameter variable&quot;&gt;-j&lt;/span&gt; DROP&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;iptables &lt;span class=&quot;token parameter variable&quot;&gt;-A&lt;/span&gt; OUTPUT &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;172.16&lt;/span&gt;.0.0/12 &lt;span class=&quot;token parameter variable&quot;&gt;-j&lt;/span&gt; DROP&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;iptables &lt;span class=&quot;token parameter variable&quot;&gt;-A&lt;/span&gt; OUTPUT &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;192.168&lt;/span&gt;.0.0/16 &lt;span class=&quot;token parameter variable&quot;&gt;-j&lt;/span&gt; DROP&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or, if you&#39;re like me and prefer using &lt;code&gt;ufw&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;ufw deny out to &lt;span class=&quot;token number&quot;&gt;10.0&lt;/span&gt;.0.0/8&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;ufw deny out to &lt;span class=&quot;token number&quot;&gt;172.16&lt;/span&gt;.0.0/12&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;ufw deny out to &lt;span class=&quot;token number&quot;&gt;192.168&lt;/span&gt;.0.0/16&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;m likely adding those commands into my initialization playbook, which I follow
whenever I&#39;m setting up baremetals from scratch.&lt;/p&gt;
</content>
  </entry>
  
  
  <entry>
    <title>Visiting localhost from a physical Android device</title>
    <link href="https://richardneililagan.com/posts/visit-localhost-android-device/" />
    <updated>2025-04-07T00:00:00Z</updated>
    <id>https://richardneililagan.com/posts/visit-localhost-android-device/</id>
    <content type='html'>&lt;p&gt;I recently had to test a web app I was developing on an &lt;strong&gt;real&lt;/strong&gt; mobile phone,
so I had to figure out how to (1) run the development server on my computer, and
(2) access it from the Android phone I had in my hand.&lt;/p&gt;
&lt;p&gt;Most of the how-tos I found online were either outdated, or felt like they were
a bit too complicated for what I was trying to achieve. I did &lt;em&gt;not&lt;/em&gt; want to have
to switch my computer network every time I needed to test something on my phone;
just thinking about having to switch back and forth between WiFi and a mobile hotspot
tires me out.&lt;/p&gt;
&lt;p&gt;I&#39;d rather have a solution that I set up once, and then just use it from then on out.&lt;/p&gt;
&lt;h2 id=&quot;so-how-then%3F&quot; tabindex=&quot;-1&quot;&gt;So how then?&lt;/h2&gt;
&lt;p&gt;You might already have &lt;code&gt;adb&lt;/code&gt; installed on your computer, but if you don&#39;t,
you can install it from the &lt;a href=&quot;https://developer.android.com/tools/releases/platform-tools&quot;&gt;Android SDK Platform Tools&lt;/a&gt; site.&lt;/p&gt;
&lt;p&gt;Next, you need to enable USB debugging on your Android device.
This lets you communicate to your phone from your computer over USB.
&lt;a href=&quot;https://developer.android.com/studio/debug/dev-options#Enable-debugging&quot;&gt;The steps to do that can be found here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After all that is done, you&#39;re ready to go.
Connect your phone to your computer via USB --- you can optionally check if your
phone is visible to your computer by running the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;adb devices&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see your device listed, you&#39;re good to go.&lt;/p&gt;
&lt;p&gt;Finally, whenever you want to access your localhost server from your phone,
you just need to run the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;adb reverse tcp:8080 tcp:8080&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This assumes that your development server is running on port &lt;code&gt;8080&lt;/code&gt;.
Now, as long as your phone is connected to your computer via USB, you can
use any browser on your phone to access &lt;code&gt;http://localhost:8080&lt;/code&gt; and
it will be forwarded to your computer&#39;s localhost server.&lt;/p&gt;
</content>
  </entry>
  
  
  <entry>
    <title>Patching Berkeley Mono v2 with Nerd Font glyphs</title>
    <link href="https://richardneililagan.com/posts/patching-berkeley-mono-v2-nerd-fonts/" />
    <updated>2025-02-22T00:00:00Z</updated>
    <id>https://richardneililagan.com/posts/patching-berkeley-mono-v2-nerd-fonts/</id>
    <content type='html'>&lt;p&gt;I love me a great font, and considering that I look at a text editor &lt;em&gt;at least&lt;/em&gt; half of the time I&#39;m in front of a computer every day (which is a lot!), I think it&#39;s worthwhile making sure I like what I&#39;m looking at. Since the start of 2025, that font&#39;s been &lt;a href=&quot;https://usgraphics.com/products/berkeley-mono&quot;&gt;Berkeley Mono&lt;/a&gt;, which has recently just been updated to version 2.&lt;/p&gt;
&lt;p&gt;Unfortunately, this one doesn&#39;t come with &lt;a href=&quot;https://www.nerdfonts.com/&quot;&gt;Nerd Font&lt;/a&gt; glyphs (like with other fonts that come with paid licenses), so I need to patch it myself. Not a problem, since there&#39;s a handy dandy &lt;a href=&quot;https://github.com/ryanoasis/nerd-fonts?tab=readme-ov-file#option-10-patch-your-own-font&quot;&gt;Nerd Font patcher&lt;/a&gt; tool available. I&#39;ve used it before, and having it available as a container makes it even easier to use.&lt;/p&gt;
&lt;aside&gt;
  There&#39;s a problem with the &lt;code&gt;.OTF&lt;/code&gt; version of Berkeley Mono v2 that results &lt;a href=&quot;https://github.com/ryanoasis/nerd-fonts/issues/1772#issuecomment-2566596719&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;in a botched font after patching&lt;/a&gt;. You&#39;ll want to use the &lt;code&gt;.TTF&lt;/code&gt; version if you&#39;re trying this yourself.
&lt;/aside&gt;
&lt;p&gt;If you&#39;re trying it yourself, this is the command I used to patch it. It should put in all icons available via Nerd Fonts. (Note that I did this with the semi-condensed version, which I kinda like best.)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; ./original:/in &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; ./patched:/out &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  nerdfonts/patcher &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;--complete&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  --single-width-glyphs &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  --adjust-line-height&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This assumes my Berkeley Mono v2 font files are in the &lt;code&gt;./original&lt;/code&gt; directory, of course. Patched fonts will be put in the &lt;code&gt;./patched&lt;/code&gt; directory as well. Adjust these to your needs.&lt;/p&gt;
</content>
  </entry>
  
  
  <entry>
    <title>Configuring Paperless-ngx to monitor emails from a Proton Mail account</title>
    <link href="https://richardneililagan.com/posts/paperless-ngx-with-proton-mail/" />
    <updated>2024-01-18T00:00:00Z</updated>
    <id>https://richardneililagan.com/posts/paperless-ngx-with-proton-mail/</id>
    <content type='html'>&lt;p&gt;I&#39;ve been setting up a &lt;a href=&quot;https://docs.paperless-ngx.com/&quot;&gt;Paperless-ngx&lt;/a&gt; instance on my homelab
as part of my 2024 resolution to immerse myself more into managing a baremetal server.
One of the things I want it to do is to &lt;mark&gt;monitor my email inbox for documents&lt;/mark&gt;
I receive regularly (think bills, receipts, etc.) so that they are automatically
fetched into the digital archive.&lt;/p&gt;
&lt;p&gt;However, I use &lt;a href=&quot;https://proton.me/mail&quot;&gt;Proton Mail&lt;/a&gt;, and as part of their security measures,
they don&#39;t provide direct access to their IMAP or SMTP servers like how you&#39;d expect
with most email providers. I do appreciate this, but it does make it difficult
to hook up Paperless-ngx to monitor my mail.&lt;/p&gt;
&lt;aside&gt;&lt;header&gt;Sidenote:&lt;/header&gt;
Paperless-ngx has been &lt;strong&gt;amazingggggggg&lt;/strong&gt; so far.
I&#39;ve just started scratching the surface on what it can do, but reading up on
the doc&#39;s recommended workflow, for example, I can just imagine how useful this will
be over the long term.
&lt;p&gt;I really should write a separate post for it.&lt;/p&gt;
&lt;/aside&gt;
&lt;h2 id=&quot;how-i-got-it-to-work&quot; tabindex=&quot;-1&quot;&gt;How I got it to work&lt;/h2&gt;
&lt;p&gt;Proton does provide a &lt;a href=&quot;https://github.com/ProtonMail/proton-bridge&quot;&gt;bridge&lt;/a&gt; that connects to their servers on your behalf,
and exposes an IMAP and SMTP server that you can connect to. Simply put, this
acts like a middleman between your email client and Proton&#39;s servers.&lt;/p&gt;
&lt;p&gt;However, the bridge has a hard requirement to &lt;a href=&quot;https://github.com/ProtonMail/proton-bridge/blob/f84067de3e596c9d103210d3ee34c1504becfc02/internal/constants/constants.go#L68-L69&quot;&gt;only accept connections from
&lt;code&gt;127.0.0.1&lt;/code&gt; (or &lt;code&gt;localhost&lt;/code&gt;)&lt;/a&gt;. This is a problem since I run my Paperless-ngx stack
in a Docker container as part of a Compose stack, so network packets from the container
will look like they&#39;re coming from somewhere external to my server, even though they&#39;re
actually running in the same machine. This is complicated even more by a requirement
I have to maintain a separate network for my containers, so the bridge and my
apps aren&#39;t even going to be running in the same network.&lt;/p&gt;
&lt;p&gt;An interesting project I came across is the &lt;a href=&quot;https://github.com/shenxn/protonmail-bridge-docker&quot;&gt;ProtonMail IMAP/SMTP Bridge Docker Container&lt;/a&gt;,
which wraps the bridge in a Docker container. But the real magic it does is that it
uses &lt;a href=&quot;http://www.dest-unreach.org/socat/&quot;&gt;&lt;code&gt;socat&lt;/code&gt;&lt;/a&gt; to &lt;a href=&quot;https://github.com/shenxn/protonmail-bridge-docker/blob/1a8b879f907f1c4abbd8f13b801225ebad007b53/build/entrypoint.sh#L26-L27&quot;&gt;forward network packets the container receives to the actual ports the bridge is listening on&lt;/a&gt;. This will make it appear to the bridge that the packets are coming from &lt;code&gt;localhost&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So making the two containers work together can be as simple as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token key atrule&quot;&gt;my-network&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token key atrule&quot;&gt;paperless&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ghcr.io/paperless&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;ngx/paperless&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;ngx&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;latest&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token key atrule&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; unless&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;stopped&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token key atrule&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; my&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;network&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token key atrule&quot;&gt;proton-bridge&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; shenxn/protonmail&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;bridge&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;docker&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;latest&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token key atrule&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; unless&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;stopped&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token key atrule&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; my&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;network&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The bridge will be visible to the Paperless-ngx container via host name &lt;code&gt;proton-bridge&lt;/code&gt;,
and will be listening on the default ports &lt;code&gt;25&lt;/code&gt; and &lt;code&gt;143&lt;/code&gt; (&lt;em&gt;not&lt;/em&gt; &lt;code&gt;1025&lt;/code&gt; and &lt;code&gt;1143&lt;/code&gt;).&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/oH349Kjz83-715.png 715w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/oH349Kjz83-715.webp 715w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/oH349Kjz83-715.png&quot; width=&quot;715&quot; height=&quot;454&quot; alt=&quot;Successful connection&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;I have to admit it took me too long to finally get this working&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;h2 id=&quot;caveats&quot; tabindex=&quot;-1&quot;&gt;Caveats&lt;/h2&gt;
&lt;p&gt;One very obvious consideration you have to make is that you&#39;re potentially exposing your Proton Mail inbox
to more consumers than what it was originally designed for. There&#39;s tons of discussion on this topic
online, so I&#39;m not going to go into that here. What you should know is that by doing this,
any other entity in the &lt;code&gt;my-network&lt;/code&gt; network &lt;mark&gt;can potentially read your emails&lt;/mark&gt;.&lt;/p&gt;
&lt;p&gt;Another thing I had to do was patch the bridge container so that the base build image uses &lt;code&gt;golang:1.20&lt;/code&gt; instead
of what it&#39;s currently using. I haven&#39;t dived too deeply into it, but the current &lt;code&gt;master&lt;/code&gt; on project
&lt;strong&gt;does not&lt;/strong&gt; build, since the Proton bridge codebase now seems to require at least Go 1.20.&lt;/p&gt;
&lt;p&gt;Finally, when registering the IMAP server to Paperless-ngx through the web UI,
I couldn&#39;t get it to connect successfully with either &lt;code&gt;SSL&lt;/code&gt; or &lt;code&gt;STARTTLS&lt;/code&gt;. You might have noticed that the connection above had &lt;code&gt;No encryption&lt;/code&gt; set.&lt;/p&gt;
&lt;p&gt;If you go through the steps to register your Proton Mail account to the bridge, you&#39;ll confirm that the bridge
does protect the IMAP server using &lt;code&gt;STARTTLS&lt;/code&gt;. I should investigate this further, but
it may be that this is a side effect of using &lt;code&gt;socat&lt;/code&gt; to route network packets to the bridge.&lt;/p&gt;
</content>
  </entry>
  
  
  <entry>
    <title>I&#39;m trying out Nobara Linux for my gaming PC</title>
    <link href="https://richardneililagan.com/posts/nobara-linux-gaming/" />
    <updated>2023-12-22T00:00:00Z</updated>
    <id>https://richardneililagan.com/posts/nobara-linux-gaming/</id>
    <content type='html'>&lt;p&gt;I&#39;ve been very vocal about wanting to try it out before, but I never got around to it.
Yesterday, I finally did: I reformatted my gaming PC and installed a Linux distro.&lt;/p&gt;
&lt;figure class=&quot;fluid&quot;&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/Z-nCpFcYFq-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/Z-nCpFcYFq-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/Z-nCpFcYFq-880.png&quot; width=&quot;880&quot; height=&quot;495&quot; alt=&quot;Nobara 38 GNOME&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Path of Exile is first on my must-install list&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;nobara%3F&quot; tabindex=&quot;-1&quot;&gt;Nobara?&lt;/h2&gt;
&lt;p&gt;I went with &lt;a href=&quot;https://nobaraproject.org/&quot;&gt;Nobara Linux 38&lt;/a&gt;. As much as I&#39;d like to say that I&#39;m comfortable with
getting my hands dirty with all the techno mumbo-jumbo that notoriously comes with working with Linux,
I wouldn&#39;t trust myself with the stuff that make gaming actually work. Let&#39;s leave that to the experts.&lt;/p&gt;
&lt;p&gt;Nobara is a modified Fedora distro that is geared towards gaming. It has a lot of customizations
and hand-picked tweaks that are supposed to make your games run better on it. It was created by
&lt;a href=&quot;https://github.com/GloriousEggroll&quot;&gt;Thomas Crider aka GloriousEggroll&lt;/a&gt;. Among other things, he&#39;s also the creator of &lt;a href=&quot;https://github.com/GloriousEggroll/proton-ge-custom&quot;&gt;Proton-GE&lt;/a&gt;,
a custom version of Valve&#39;s Proton with an emphasis on being tech-forward.&lt;/p&gt;
&lt;aside&gt;&lt;header&gt;From the project page:&lt;/header&gt;
This project aims to fix most of those issues and offer a better gaming, streaming, and content creation experience out of the box. More importantly, we want to be more point and click friendly, and avoid the basic user from having to open the terminal. It’s not that the terminal and/or terminal usage are a bad thing by any means, power users are more than welcome to continue with using the terminal, but for new users, point and click ease of use is usually expected.
&lt;/aside&gt;
&lt;figure class=&quot;fluid&quot;&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/zKIdDTQL4N-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/zKIdDTQL4N-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/zKIdDTQL4N-880.png&quot; width=&quot;880&quot; height=&quot;396&quot; alt=&quot;Nobara Welcome Wizard&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;It also comes with a welcome wizard that was very helpful in setting up the stuff I needed, like OBS Studio.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;h2 id=&quot;how-did-the-install-go%3F&quot; tabindex=&quot;-1&quot;&gt;How did the install go?&lt;/h2&gt;
&lt;p&gt;It definitely wasn&#39;t without problems, &lt;mark&gt;but that&#39;s not as bad as it sounds&lt;/mark&gt;.&lt;/p&gt;
&lt;p&gt;I was coming from a Windows 10 machine, and I used &lt;a href=&quot;https://rufus.ie/en/&quot;&gt;Rufus&lt;/a&gt; to create a bootable USB drive containing Nobara.
I used to use &lt;a href=&quot;https://etcher.balena.io/&quot;&gt;Etcher&lt;/a&gt; for this, but it seems to be creating corrupted boot disks with Nobara,
so I had to drop that. If you want to try it out yourself, you can grab a &lt;strong&gt;Nobara ISO&lt;/strong&gt; you can use &lt;a href=&quot;https://nobaraproject.org/download-nobara/&quot;&gt;from here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I really wanted to install the KDE/Plasma version because of the customization options I keep hearing about,
but for some reason it just won&#39;t run correctly. Booting up Nobara 38 KDE, I&#39;d always end up with a
frozen screen a few minutes into a session.&lt;/p&gt;
&lt;p&gt;So I went with the GNOME version instead. This installed without a hitch, and is the desktop
environment in the image I have above. This is working pretty well so far.&lt;/p&gt;
&lt;p&gt;I did try to install the KDE version from the GNOME installation, &lt;a href=&quot;https://nobaraproject.org/docs/upgrade-troubleshooting/how-do-i-switch-from-gnome-to-kde/&quot;&gt;following the steps on this page&lt;/a&gt;,
but I ended up with a borked system after all of it. Instead of frozen screens, I&#39;d end up with
black screens after a few minutes into a session. Following the &lt;a href=&quot;https://nobaraproject.org/docs/nvidia-troubleshooting/black-screen-after-update/&quot;&gt;troubleshooting steps on the site&lt;/a&gt;
didn&#39;t really lead me anywhere, because the commands didn&#39;t quite work. &lt;code&gt;dnf&lt;/code&gt; wouldn&#39;t let me run
the following commands from the page because of protections in place:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# This doesn&#39;t work for me.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; dnf remove *nvidia*&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; dnf update nobara-login &lt;span class=&quot;token parameter variable&quot;&gt;--refresh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In any case, I&#39;ve decided to stick with the GNOME version for now.
I&#39;ll try to switch over to KDE/Plasma once Nobara 39 is out, which chatter seems to suggest is going to be soon.&lt;/p&gt;
&lt;h2 id=&quot;how&#39;s-the-gaming%3F&quot; tabindex=&quot;-1&quot;&gt;How&#39;s the gaming?&lt;/h2&gt;
&lt;p&gt;I&#39;ve only given it a quick whirl so far, but I&#39;m somewhat happy with the results.&lt;/p&gt;
&lt;p&gt;Part of the reason why I wanted to try this out with Path of Exile first is because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it doesn&#39;t have a native Linux client: I have to use Proton to run it,&lt;/li&gt;
&lt;li&gt;I normally play PoE with a bunch of other tools alongside it, like &lt;a href=&quot;https://pathofbuilding.community/&quot;&gt;Path of Building&lt;/a&gt; and &lt;a href=&quot;https://github.com/SnosMe/awakened-poe-trade&quot;&gt;Awakened PoE Trade&lt;/a&gt;, so I needed to make sure these also work in-game, and&lt;/li&gt;
&lt;li&gt;it&#39;s not &lt;a href=&quot;https://richardneililagan.com/posts/create-game-server-aws-parsec/&quot;&gt;Crysis&lt;/a&gt;, but PoE can be somewhat demanding with lots of particles, effects, and bloom.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I gave that a whirl last night, and I have to say: I kinda feel like this is actually playing better
than when I was running it on Windows 10.&lt;/p&gt;
&lt;div&gt;
      &lt;div className=&quot;video-player&quot; style=&quot;position: relative; height: 0; padding-bottom: 56.25%; padding-top: 0;&quot;&gt;
        &lt;iframe src=&quot;https://youtube.com/embed/8zq4NDVolfk&quot; frameBorder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowFullScreen=&quot;&quot; style=&quot;position: absolute; top: 0; left: 0; width: 100%; height: 100%;&quot;&gt;&lt;/iframe&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;p&gt;I&#39;m giving it a few weeks with various other games though, just to make sure it&#39;s not placebo or anything.&lt;/p&gt;
</content>
  </entry>
  
  
  <entry>
    <title>Copying files to a volume on Fly.io</title>
    <link href="https://richardneililagan.com/posts/copying-files-to-fly-io-volume/" />
    <updated>2023-01-19T00:00:00Z</updated>
    <id>https://richardneililagan.com/posts/copying-files-to-fly-io-volume/</id>
    <content type='html'>&lt;p&gt;Today, I finally did something I&#39;ve been meaning to do since last year:
I migrated my &lt;a href=&quot;https://github.com/richardneililagan/vaultwarden-ecs-fargate&quot;&gt;Vaultwarden&lt;/a&gt; installation from &lt;a href=&quot;https://aws.amazon.com/ecs&quot;&gt;Amazon ECS&lt;/a&gt;
to &lt;a href=&quot;https://fly.io/&quot;&gt;Fly.io&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The migration really was very simple and mostly painless to do —
the only part that slowed me down was figuring out how to copy files up to
a (disk) volume I&#39;ve set up on the &lt;a href=&quot;http://fly.io/&quot;&gt;Fly.io&lt;/a&gt; platform.
Vaultwarden uses SQLite, so all of its data was on the file system.
For a complete migration, I will need to copy the files from my old installation
to the new one.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Given a &lt;a href=&quot;http://fly.io/&quot;&gt;Fly.io&lt;/a&gt; app:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token key property&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;my-sample-app&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token key property&quot;&gt;dockerfile&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Dockerfile&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;mount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token key property&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;my_volume&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token key property&quot;&gt;destination&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/data&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have a single volume &lt;code&gt;my_volume&lt;/code&gt; mounted to our app at &lt;code&gt;/data&lt;/code&gt;.
We might be able to &lt;code&gt;scp&lt;/code&gt; into the app but &lt;a href=&quot;http://fly.io/&quot;&gt;Fly.io&lt;/a&gt; apps now
&lt;a href=&quot;https://community.fly.io/t/announcement-shared-anycast-ipv4/9384&quot;&gt;don&#39;t automatically come with a static IPv4 address&lt;/a&gt;.
You still can get an IPv4 address, but this will cost USD 2.00/month.&lt;/p&gt;
&lt;p&gt;I wanted to look for a way to do this with just default resources.
Unfortunately, it also gets a bit complicated.&lt;/p&gt;
&lt;h2 id=&quot;so%2C-how%3F&quot; tabindex=&quot;-1&quot;&gt;So, how?&lt;/h2&gt;
&lt;p&gt;First, we grab a &lt;a href=&quot;https://www.wireguard.com/&quot;&gt;Wireguard&lt;/a&gt; config file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;$  fly wireguard create&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a config file on your machine which you can then import into
Wireguard. Doing so gives you network visibility to your app via a private VPN connection.
&lt;mark&gt;While connected to the VPN, your app will be available at &lt;code&gt;&amp;lt;app-name&amp;gt;.internal&lt;/code&gt;&lt;/mark&gt;.
(For example, the app above will be at &lt;code&gt;my-sample-app.internal&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Next, we create a (temporary) SSH key:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;$  fly &lt;span class=&quot;token function&quot;&gt;ssh&lt;/span&gt; issue&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a pair of SSH keys on your machine that you can use to connect
to your app. For example, &lt;code&gt;my-app-key&lt;/code&gt; and &lt;code&gt;my-app-key-cert.pub&lt;/code&gt;.
We&#39;re only interested in your &lt;em&gt;private&lt;/em&gt; key here, &lt;strong&gt;not&lt;/strong&gt; the one with a &lt;code&gt;.pub&lt;/code&gt; extension.&lt;/p&gt;
&lt;p&gt;To test connectivity, just run &lt;code&gt;ssh root@&amp;lt;app-name&amp;gt;.internal -i &amp;lt;your-key-name&amp;gt;&lt;/code&gt;.
For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;$  &lt;span class=&quot;token function&quot;&gt;ssh&lt;/span&gt; root@my-sample-app.internal &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; my-app-key&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If everything is OK, you should now be in a tunneled SSH session inside your app.
Exit the session by typing &lt;code&gt;exit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since we can now &lt;code&gt;ssh&lt;/code&gt; into our app normally (i.e. without using &lt;code&gt;fly ssh console&lt;/code&gt;),
we can now also use the same credentials to run &lt;code&gt;scp&lt;/code&gt; to copy files to/fro our app.&lt;/p&gt;
&lt;p&gt;For example, to copy &lt;code&gt;foo.txt&lt;/code&gt; from my machine&#39;s current directory to
&lt;code&gt;/data/foo.txt&lt;/code&gt; in the app (and therefore, into the volume):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;$  &lt;span class=&quot;token function&quot;&gt;scp&lt;/span&gt; foo.txt root@my-sample-app.internal:/data/foo.txt&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;caveats&quot; tabindex=&quot;-1&quot;&gt;Caveats&lt;/h2&gt;
&lt;p&gt;Something that you will have to make sure of is that the container that your
app is running actually has &lt;code&gt;scp&lt;/code&gt; available to it. Otherwise, the operation will fail.&lt;/p&gt;
&lt;p&gt;If that&#39;s not the case, you can generally install it using something similar to
this in your &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;RUN&lt;/span&gt; apt-get update &amp;amp;&amp;amp; &lt;span class=&quot;token operator&quot;&gt;&#92;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    apt-get install -y openssh-client&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  
  <entry>
    <title>I used ChatGPT instead of Stack Overflow today</title>
    <link href="https://richardneililagan.com/posts/using-chatgpt-instead-of-stack-overflow/" />
    <updated>2022-12-05T00:00:00Z</updated>
    <id>https://richardneililagan.com/posts/using-chatgpt-instead-of-stack-overflow/</id>
    <content type='html'>&lt;p&gt;There&#39;s been no shortage of people harping about the amazing things you can do
with OpenAI&#39;s ChatGPT. I admit to losing exorbitant amounts of sleep over the weekend
because I was trying to &amp;quot;break&amp;quot; it --- I kept asking it practical but somewhat obscure
technical questions to see what it can say.&lt;/p&gt;
&lt;p&gt;Here&#39;s an example:&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/tozhEFbDLC-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/tozhEFbDLC-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/tozhEFbDLC-880.png&quot; width=&quot;880&quot; height=&quot;1185&quot; alt=&quot;ChatGPT explaining how to run a function in response to a change in a GPIO pin state in a Raspberry Pi using Rust&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;ChatGPT seems to know how to use Rust to read state from a Raspberry Pi&#39;s GPIO pins&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;p&gt;This is amazing. I personally don&#39;t know how to do that myself, but even if this
response from ChatGPT has inaccuracies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it&#39;s pointed me to the &lt;code&gt;rppal&lt;/code&gt; crate which looks to be completely within context of what we&#39;re trying to do (&lt;a href=&quot;https://crates.io/crates/rppal&quot;&gt;crate documentation here&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;it&#39;s specified that the example it&#39;s given is for version &lt;code&gt;0.12.0&lt;/code&gt;, so I can work towards grokking this in comparison to the latest version &lt;code&gt;0.14.1&lt;/code&gt; at the time of this writing&lt;/li&gt;
&lt;li&gt;it&#39;s shown how to get a handle on a specific pin, and even configured it to (seemingly) behave as an input GPIO pin, which is exactly what we want,&lt;/li&gt;
&lt;li&gt;and shows I can poll a pin with a specified interval duration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is more than enough information to get me going if I was really doing something like this now.
I don&#39;t expect it to be 100% accurate and up-to-date, but it&#39;s more than enough to &lt;mark&gt;point me in the right direction of where to focus studying more about this topic&lt;/mark&gt;.&lt;/p&gt;
&lt;h2 id=&quot;using-it-to-work-through-today&#39;s-advent-of-code-problem&quot; tabindex=&quot;-1&quot;&gt;Using it to work through today&#39;s Advent of Code problem&lt;/h2&gt;
&lt;p&gt;So I thought, why not try to use ChatGPT as a learning assistant?
I could use it instead of something like Stack Overflow, and instead of sifting through
pages and pages of search results of varying quality and mismatched contexts,
ChatGPT &lt;em&gt;should&lt;/em&gt; be able to give me responses that are more direct to the point,
within context, and (hopefully) much more up-to-date.&lt;/p&gt;
&lt;p&gt;I&#39;ve been using Advent of Code this year to learn how to code in &lt;a href=&quot;https://ruby-lang.org/&quot;&gt;Ruby&lt;/a&gt;.
While I do have the advantage of generally knowing what I have to do to solve problems,
I currently don&#39;t know exactly &lt;em&gt;how&lt;/em&gt; those are achieved in Ruby.
So instead of asking ChatGPT right out to solve a problem for me,
I&#39;ll instead ask it how something can be done in Ruby as I go along.&lt;/p&gt;
&lt;p&gt;For reference, here is &lt;a href=&quot;https://adventofcode.com/2022/day/5&quot;&gt;today&#39;s Advent of Code problem (2022 day 5)&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;splitting-arrays&quot; tabindex=&quot;-1&quot;&gt;Splitting arrays&lt;/h3&gt;
&lt;p&gt;The input data for today comes in two parts: (1) an initial crate configuration, and
(2) a sequential list of instructions for moving crates across the system.&lt;/p&gt;
&lt;p&gt;My idea was to read off the entire input file per line into an array, and split that
into the two constituents on the empty line.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/tkyNjw7UcF-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/tkyNjw7UcF-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/tkyNjw7UcF-880.png&quot; width=&quot;880&quot; height=&quot;914&quot; alt=&quot;ChatGPT on how to split an array&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;p&gt;That&#39;s implemented &lt;a href=&quot;https://github.com/richardneililagan/aoc-2022/blob/413ac09cf9c0e2265d641c9b02fe965f5ac87a8d/challenges/2022/05/solution.rb#L9-L10&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;splitting-a-string-in-groups-of-n-characters&quot; tabindex=&quot;-1&quot;&gt;Splitting a string in groups of N characters&lt;/h3&gt;
&lt;p&gt;Here is the sample crate arrangement input:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;    [D]&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;[N] [C]&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;[Z] [M] [P]&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt; 1   2   3&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because of the nature of the formatting, I can take each line of that input,
collect the characters in groups of 4, and just drop everything but the 2nd character
(which is the crate ID itself).&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/xa70tIk020-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/xa70tIk020-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/xa70tIk020-880.png&quot; width=&quot;880&quot; height=&quot;747&quot; alt=&quot;ChatGPT on how to collect characters into fixed size groups&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;p&gt;That&#39;s implemented &lt;a href=&quot;https://github.com/richardneililagan/aoc-2022/blob/413ac09cf9c0e2265d641c9b02fe965f5ac87a8d/challenges/2022/05/solution.rb#L18-L21&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;parsing-instructions-using-regex&quot; tabindex=&quot;-1&quot;&gt;Parsing instructions using regex&lt;/h3&gt;
&lt;p&gt;Here is the sample instructions input:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;move 1 from 2 to 1&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;move 3 from 1 to 3&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;move 2 from 2 to 1&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;move 1 from 1 to 2&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I wanted to use regular expressions to capture the three values in each instruction:
(1) how many crates to move, (2) from which column, (3) to which column.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/BxtZNwIKSv-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/BxtZNwIKSv-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/BxtZNwIKSv-880.png&quot; width=&quot;880&quot; height=&quot;972&quot; alt=&quot;ChatGPT on using regex capture groups&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;p&gt;Here I realized that I wanted to use &lt;em&gt;named&lt;/em&gt; capture groups instead of just plain ones.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/wf4ftW8aRM-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/wf4ftW8aRM-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/wf4ftW8aRM-880.png&quot; width=&quot;880&quot; height=&quot;723&quot; alt=&quot;ChatGPT on using regex named capture groups&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;p&gt;This is implemented &lt;a href=&quot;https://github.com/richardneililagan/aoc-2022/blob/413ac09cf9c0e2265d641c9b02fe965f5ac87a8d/challenges/2022/05/solution.rb#L37-L39&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But then I wondered if it&#39;s possible to destructure the result of that into its
constituent variables (instead of doing &lt;code&gt;captures[&amp;quot;count&amp;quot;]&lt;/code&gt;, etc.).&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/N99TfOj0Of-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/N99TfOj0Of-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/N99TfOj0Of-880.png&quot; width=&quot;880&quot; height=&quot;889&quot; alt=&quot;ChatGPT on how to destructure a hash into specific variables&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;p&gt;This is implemented &lt;a href=&quot;https://github.com/richardneililagan/aoc-2022/blob/413ac09cf9c0e2265d641c9b02fe965f5ac87a8d/challenges/2022/05/solution.rb#L43&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;concatenating-arrays&quot; tabindex=&quot;-1&quot;&gt;Concatenating arrays&lt;/h3&gt;
&lt;p&gt;I realized I didn&#39;t know how to actually append elements of one array into another array
short of doing something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;array_1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1.&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_a&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;array_2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6.&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;.10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_a&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;array_2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;item&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; array_1 &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; item &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So I thought to ask anyway:&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/Bn_f2fnnVH-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/Bn_f2fnnVH-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/Bn_f2fnnVH-880.png&quot; width=&quot;880&quot; height=&quot;756&quot; alt=&quot;ChatGPT on how to concatenate 2 arrays&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;p&gt;And sure enough, there &lt;em&gt;was&lt;/em&gt; an easier way. This is implemented &lt;a href=&quot;https://github.com/richardneililagan/aoc-2022/blob/413ac09cf9c0e2265d641c9b02fe965f5ac87a8d/challenges/2022/05/solution.rb#L45-L46&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;how-did-that-feel%3F&quot; tabindex=&quot;-1&quot;&gt;How did that feel?&lt;/h2&gt;
&lt;p&gt;Honestly, really well. Most of the time when I&#39;d be searching for how something is done
in a specific language / framework / platform, it&#39;s a huge bulk of sifting through various
search results, evaluating blog posts, or adapting a working solution for a slightly
different problem than I was going through. When I was using ChatGPT, it felt like I had
the rough equivalent of a mentor / paired programmer that was working on the same thing
beside me. The responses were on point most of the time, and I am very appreciative of
the time that saves for me.&lt;/p&gt;
&lt;p&gt;When I was still a Solutions Architect for AWS, one of the things we&#39;ve really wanted to
build was a theoretical system we called an &amp;quot;SA-in-a-box&amp;quot;. It was going to be an interface
where you could ask it some of the most common questions SAs get asked by customers all
the time, and it would spit out valid answers, ideally within context.&lt;/p&gt;
&lt;p&gt;I tried that with ChatGPT today too:&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/lm3SKMfR_q-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/lm3SKMfR_q-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/lm3SKMfR_q-880.png&quot; width=&quot;880&quot; height=&quot;977&quot; alt=&quot;ChatGPT on how to optimize Amazon EKS cluster costs&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;p&gt;I wouldn&#39;t just follow any of those suggestions blindly, but heck, I&#39;d be damned if they
weren&#39;t a good start to work off of.&lt;/p&gt;
</content>
  </entry>
  
  
  <entry>
    <title>I&#39;m building my own hitbox-style fightstick controller</title>
    <link href="https://richardneililagan.com/posts/hitbox-fightstick/" />
    <updated>2022-10-06T00:00:00Z</updated>
    <id>https://richardneililagan.com/posts/hitbox-fightstick/</id>
    <content type='html'>&lt;p&gt;I am absolutely hyped up for Street Fighter VI.&lt;/p&gt;
&lt;figure class=&quot;fluid&quot;&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://richardneililagan.com/i/Modr60ca1J-880.jpeg 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/Modr60ca1J-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/Modr60ca1J-880.jpeg&quot; width=&quot;880&quot; height=&quot;495&quot; alt=&quot;Street Fighter VI press release graphic&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;p&gt;When I was still in high school, a huge amount of my free time went to visiting the video game arcades.
The fighting games were my go-to then. I got hooked with Capcom&#39;s &lt;a href=&quot;https://en.wikipedia.org/wiki/Marvel_Super_Heroes_(video_game)&quot;&gt;Marvel Super Heroes&lt;/a&gt;
back in middle school, but it was definitely high school when I got totally hooked.
Me and my friends would easily gobble up whatever the fighting game of the season was then,
and just spend the afternoons after school (and our lunch money) &lt;em&gt;plonking&lt;/em&gt; coins into arcade cabinets.&lt;/p&gt;
&lt;p&gt;I&#39;d eventually move on to spending even more exorbitant amounts of time in rhythm games
(my favorite was Konami&#39;s &lt;a href=&quot;https://en.wikipedia.org/wiki/GuitarFreaks_and_DrumMania&quot;&gt;DrumMania series&lt;/a&gt;), but fighting games never really left.
To this day, I&#39;d argue that &lt;a href=&quot;https://en.wikipedia.org/wiki/Street_Fighter_III:_3rd_Strike&quot;&gt;Street Fighter III: 3rd Strike&lt;/a&gt; is one of the absolute best
fighting games of all time.&lt;/p&gt;
&lt;p&gt;That, and that the small amount of teaser content that&#39;s been released for the upcoming Street Fighter VI
seems to suggest that Capcom&#39;s really steering this next one into great gaming waters,
made me decide that, hey, I want to have a fightstick of my own to play with — why not build my own?&lt;/p&gt;
&lt;h2 id=&quot;yeah%2C-totally-logical&quot; tabindex=&quot;-1&quot;&gt;Yeah, totally logical&lt;/h2&gt;
&lt;p&gt;The very first thing I did was look for prior art.&lt;/p&gt;
&lt;p&gt;I very quickly came across the &lt;a href=&quot;https://github.com/OpenStickFoundation/GP2040-CE&quot;&gt;GP2040-CE project&lt;/a&gt;, an open source firmware
for fightsticks that ran on an &lt;mark&gt;RP2040 MCU&lt;/mark&gt;. It seemed great — it has support for a good amount
of consoles and machines, allowed for 8+2 action buttons out of the box (&lt;code&gt;P1&lt;/code&gt;-&lt;code&gt;P4&lt;/code&gt;, &lt;code&gt;K1&lt;/code&gt;-&lt;code&gt;K4&lt;/code&gt;, &lt;code&gt;L3&lt;/code&gt;, &lt;code&gt;R3&lt;/code&gt;),
had what looked to be a convenient customization interface, and (most importantly) the option
for RGB LEDs.&lt;/p&gt;
&lt;p&gt;Yeah, this looked like a stellar starting point.&lt;/p&gt;
&lt;p&gt;Looking for projects based on GP2040-CE, I think one of the better ones I found was &lt;a href=&quot;https://github.com/jfedor2/flatbox&quot;&gt;Flatbox&lt;/a&gt;
(specifically, the &lt;mark&gt;Flatbox rev 4&lt;/mark&gt;).&lt;/p&gt;
&lt;p&gt;To be honest, Flatbox looked to be almost perfect to me, but I really wanted to put in a few
changes (improvements?) for myself:&lt;/p&gt;
&lt;h3 id=&quot;1.-slightly-bigger-buttons&quot; tabindex=&quot;-1&quot;&gt;1. Slightly bigger buttons&lt;/h3&gt;
&lt;p&gt;I have very chonky hands, and the Flatbox uses &lt;code&gt;24mm&lt;/code&gt; diameter buttons.
As much as I really want a small form factor fightstick, I feel like this size might feel a bit
too cramped for me. I&#39;m thinking a more standard &lt;code&gt;28 / 30 / 32mm&lt;/code&gt; size would be better.&lt;/p&gt;
&lt;h3 id=&quot;2.-slightly-angled-button-positioning&quot; tabindex=&quot;-1&quot;&gt;2. Slightly angled button positioning&lt;/h3&gt;
&lt;p&gt;I still want to keep the fightstick as small as possible so that it&#39;s portable, just like
the Flatbox. But that also means that, unlike a traditional arcade panel, a player&#39;s arms
will have to come into the fightstick at a more constricted angle, just like with a
traditional keyboard.&lt;/p&gt;
&lt;p&gt;And just like with keyboards, we want to really want to keep the wrists as straight and
parallel with the arms as possible. To make the board a bit more ergonomic, I want to
tilt the buttons towards the inside of the board, so that they receive the player&#39;s
hands at a more natural wrist angle.&lt;/p&gt;
&lt;h3 id=&quot;3.-optional-rgb-leds&quot; tabindex=&quot;-1&quot;&gt;3. Optional RGB LEDs&lt;/h3&gt;
&lt;p&gt;The Flatbox PCB does not have routing for any LEDs.
I&#39;m not totally sure yet if I&#39;d want LEDs on my fightstick, but I&#39;d at least want to
have the option to do so if I wanted.&lt;/p&gt;
&lt;p&gt;Maybe at the very least, have support for underglow. Even better if per-key LEDs
can be fixed onto the board.&lt;/p&gt;
&lt;h3 id=&quot;4.-acrylic-case&quot; tabindex=&quot;-1&quot;&gt;4. Acrylic case&lt;/h3&gt;
&lt;p&gt;For some reason, I really want this in an acrylic case.
In my head, a clear acrylic case will let me (or anybody else that uses this) customize
the board much more easily than a 3D printed one.&lt;/p&gt;
&lt;p&gt;Or that frosted / translucent acrylic can better scatter LED glow. I don&#39;t know.&lt;/p&gt;
&lt;h2 id=&quot;a-first-draft&quot; tabindex=&quot;-1&quot;&gt;A first draft&lt;/h2&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://richardneililagan.com/i/aFbUHrDekl-880.jpeg 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/aFbUHrDekl-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/aFbUHrDekl-880.jpeg&quot; width=&quot;880&quot; height=&quot;727&quot; alt=&quot;A photo of a fightstick layout, with buttons extremely angled towards the inside.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;My first layout idea for a fightstick&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;p&gt;I first came up with a squarish layout idea.
I was thinking that the left hand index finger can quite easily position between the
right hand index and middle fingers on buttons, so the right hand can bring itself more into
the inside of the board.&lt;/p&gt;
&lt;p&gt;After a bit of testing and consulting with other folks, I shelved that idea for now.
I still want to maybe pursue it someday, but everyone I asked felt like it would feel
unnecessarily cramped.&lt;/p&gt;
&lt;p&gt;One thing I like about this though is that the &lt;code&gt;K1&lt;/code&gt; button is in a very good position
in relation to the &lt;code&gt;UP&lt;/code&gt; button at the bottom. Theoretically this would make it easier
to use one&#39;s right thumb to press either of those. &lt;code&gt;K1&lt;/code&gt; is already very commonly hit with
one&#39;s thumb (e.g. with &lt;code&gt;P1+K1&lt;/code&gt; and &lt;code&gt;P1 &amp;gt; K1 &amp;gt; P2&lt;/code&gt;); having the &lt;code&gt;UP&lt;/code&gt; button within small
hitting distance gives you two options for a jump.&lt;/p&gt;
&lt;h2 id=&quot;a-second-draft&quot; tabindex=&quot;-1&quot;&gt;A second draft&lt;/h2&gt;
&lt;p&gt;For my second try at a design, I started with a more standard layout first, and adjusted accordingly.&lt;/p&gt;
&lt;figure class=&quot;fluid&quot;&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/hQacU9NX4n-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/hQacU9NX4n-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/hQacU9NX4n-880.png&quot; width=&quot;880&quot; height=&quot;553&quot; alt=&quot;A photo of a fightstick layout, with buttons in a more standard arrangement, with a slight tilt to the inside.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;My second layout idea looked much saner.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;p&gt;This layout is actually based off of &lt;a href=&quot;http://slagcoin.com/joystick/layout.html&quot;&gt;the button layout generalized from Vewlix arcade cabinets&lt;/a&gt;.
I&#39;ve adjusted that a bit by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tilting the buttons into the center by about 12 degrees,&lt;/li&gt;
&lt;li&gt;Removed the stick, of course,&lt;/li&gt;
&lt;li&gt;Mirrored the &lt;code&gt;P1&lt;/code&gt;, &lt;code&gt;P2&lt;/code&gt;, and &lt;code&gt;P3&lt;/code&gt; buttons on the other side, also giving it a slant,&lt;/li&gt;
&lt;li&gt;Placed the auxiliary buttons on the center of the board — we don&#39;t expect to use them much, but it&#39;d nice if we do have to hit &lt;code&gt;L3&lt;/code&gt; / &lt;code&gt;R3&lt;/code&gt; to just right be there,&lt;/li&gt;
&lt;li&gt;And finally, eyeballed the positions of the &lt;code&gt;UP&lt;/code&gt; button in relation with the positions of the two hand clusters.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I haven&#39;t done any serious prototyping with this layout yet.
I &lt;em&gt;have&lt;/em&gt; printed it on paper 1:1 and the buttons &lt;em&gt;seem&lt;/em&gt; to be in good positions for my hands, so I&#39;m hopeful.
The &lt;code&gt;UP&lt;/code&gt; button is still accessible to both thumbs, and &lt;code&gt;K1&lt;/code&gt; is positioned well for the right thumb too.&lt;/p&gt;
&lt;p&gt;The PCB is a bit bigger than I was initially hoping for at &lt;code&gt;236 x 147 mm&lt;/code&gt;,
but it&#39;s still compact enough for travel.&lt;/p&gt;
&lt;p&gt;Now if only we can ensure that the entire build won&#39;t be too thick as well.
I&#39;m going to try to keep it under &lt;code&gt;2 cm&lt;/code&gt; thick — ideally around &lt;code&gt;1.0&lt;/code&gt; to &lt;code&gt;1.5&lt;/code&gt; would be great.&lt;/p&gt;
&lt;p&gt;The next steps would largely be designing the PCB.&lt;/p&gt;
&lt;p&gt;If you&#39;re interested in following along the build, I&#39;m keeping track of progress
&lt;a href=&quot;https://github.com/richardneililagan/ergobox&quot;&gt;in this Github repository&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  
  <entry>
    <title>Build log: Vortex Pok3r hotswap mod</title>
    <link href="https://richardneililagan.com/posts/vortex-pok3r-hotswap-mod/" />
    <updated>2022-09-17T00:00:00Z</updated>
    <id>https://richardneililagan.com/posts/vortex-pok3r-hotswap-mod/</id>
    <content type='html'>&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://richardneililagan.com/i/SbDrh2i9PT-880.jpeg 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/SbDrh2i9PT-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/SbDrh2i9PT-880.jpeg&quot; width=&quot;880&quot; height=&quot;416&quot; alt=&quot;A Vortex Pok3r with mostly blank black keycaps.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;p&gt;My first mechanical keyboard is a &lt;a href=&quot;https://drop.com/buy/vortex-poker-iii-compact-keyboard&quot;&gt;Vortex Pok3r&lt;/a&gt;, which I&#39;ve had since 2015.
It&#39;s a &lt;strong&gt;60% keyboard&lt;/strong&gt; — so it doesn&#39;t have arrow, function, navigation and numpad keys.
It was a very difficult transition at the time, having only worked with standard
keyboards ever since, but I&#39;ve grown very accustomed to it, and now work pretty darn
fast with it. Point is, it&#39;s been something close to my heart, I suppose.&lt;/p&gt;
&lt;p&gt;I&#39;ve since tried many more keyboards, having also built a &lt;a href=&quot;https://nextkeyboard.club/product-tag/lily58-pro/&quot;&gt;Lily58 Pro&lt;/a&gt; and a
&lt;a href=&quot;https://nextkeyboard.club/product-tag/corne-v3-0-1-mx/&quot;&gt;Corne v3&lt;/a&gt;, and one thing I&#39;ve really come to realize is that the MX clear
switches on my Pok3r were really heavy. I thought it&#39;d be really nice if my Pok3r
was hotswappable, so I can just switch out new, um, switches whenever I wanted to try
something new.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;disassembly&quot; tabindex=&quot;-1&quot;&gt;Disassembly&lt;/h2&gt;
&lt;p&gt;Taking the PCB out of the case is a piece of cake: after removing all the keycaps,
there will be &lt;strong&gt;six screws&lt;/strong&gt; that secure the PCB down onto the metal case.
Removing them lets you take it out easily.&lt;/p&gt;
&lt;figure class=&quot;fluid&quot;&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://richardneililagan.com/i/AdsBH9QrY2-880.jpeg 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/AdsBH9QrY2-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/AdsBH9QrY2-880.jpeg&quot; width=&quot;880&quot; height=&quot;491&quot; alt=&quot;The locations of the screws on the Pok3r.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;The locations of the screws on the Pok3r.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;p&gt;The PCB assembly is made up of two major components: the (black) metal plate where
the switches secure themselves onto, and the (red) PCB underneath where the switches
are soldered onto. The two pieces are only secured to each other by the switches
themselves being soldered onto the PCB — so to separate them, we&#39;ll have to desolder
&lt;em&gt;all&lt;/em&gt; of the switches one by one. That would be 61 switches to desolder.&lt;/p&gt;
&lt;h2 id=&quot;desoldering&quot; tabindex=&quot;-1&quot;&gt;Desoldering&lt;/h2&gt;
&lt;p&gt;Switches are soldered on the underside of the PCB.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://richardneililagan.com/i/aF7lct67Bt-880.jpeg 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/aF7lct67Bt-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/aF7lct67Bt-880.jpeg&quot; width=&quot;880&quot; height=&quot;416&quot; alt=&quot;The underside of the Pok3r PCB.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;The underside of the Pok3r PCB.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;p&gt;Each switch will have two solder points that will need to be desoldered.
I didn&#39;t have an RGB Pok3r (so my switches didn&#39;t have LEDs), but an RGB Pok3r would
also have solder points for the LEDs that will need to be removed. Look for each switch&#39;s
bottom housing foot (the stem that is under each switch), and the switch contacts
will be easy to identify.&lt;/p&gt;
&lt;p&gt;Note that even on a non-RGB Pok3r like mine, the &lt;code&gt;Caps Lock&lt;/code&gt; switch will have an LED
mounted anyway, so that will also have to be removed.&lt;/p&gt;
&lt;figure class=&quot;fluid&quot;&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://richardneililagan.com/i/PiNxr3hZf6-880.jpeg 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/PiNxr3hZf6-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/PiNxr3hZf6-880.jpeg&quot; width=&quot;880&quot; height=&quot;413&quot; alt=&quot;The solder contacts for each switch.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Desolder each switch contact (and LED contact on an RGB Pok3r).&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;p&gt;I used a desoldering pump to get most of the solder out of the PCB, and desoldering
wick to clean up what the pump couldn&#39;t suck out. The wick is really great for keeping
the contact points as clean as possible for later as well.&lt;/p&gt;
&lt;p&gt;Once a switch has been cleanly desoldered, you should be able to easily pull it out
using a switch puller.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://richardneililagan.com/i/LijFYZHPzo-880.jpeg 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/LijFYZHPzo-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/LijFYZHPzo-880.jpeg&quot; width=&quot;880&quot; height=&quot;416&quot; alt=&quot;The bottom row of switches desoldered.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;The bottom row of switches desoldered.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;p&gt;Once all the switches have been removed, the plate and PCB will separate.
This would be a good time to clean the components. Mine had a lot of dirt and dead skin
that had accumulated over the last seven years!&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://richardneililagan.com/i/cA7eVOMXnP-880.jpeg 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/cA7eVOMXnP-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/cA7eVOMXnP-880.jpeg&quot; width=&quot;880&quot; height=&quot;416&quot; alt=&quot;The PCB and plate separated.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;h2 id=&quot;hotswap-sockets&quot; tabindex=&quot;-1&quot;&gt;Hotswap sockets&lt;/h2&gt;
&lt;p&gt;These days, custom keyboards can be fitted with &lt;a href=&quot;https://www.amazon.com/Hot-swappable-Socket-CPG151101S11-Mechanical-Keyboard/dp/B07K8CCMQZ&quot;&gt;hotswap sockets&lt;/a&gt;
that are simple to install on a PCB. The Pok3r PCB is not particularly made to
accommodate these kinds of hotswap sockets however, so we can&#39;t use them unfortunately.&lt;/p&gt;
&lt;p&gt;There&#39;s a few options that we can use — I personally used &lt;a href=&quot;https://www.te.com/usa-en/product-6-1437514-7.html&quot;&gt;holtites&lt;/a&gt; so that
I didn&#39;t have to solder them onto the board anymore. Another common alternative is
&lt;a href=&quot;https://docs.keebd.com/information/mill-max-sockets/&quot;&gt;mill-max sockets&lt;/a&gt;, which would need soldering.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://richardneililagan.com/i/4jkxCeYHJG-880.jpeg 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/4jkxCeYHJG-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/4jkxCeYHJG-880.jpeg&quot; width=&quot;880&quot; height=&quot;880&quot; alt=&quot;A single holtite socket.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;A single holtite socket.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;p&gt;Holtites are &lt;strong&gt;really small&lt;/strong&gt; and very easy to lose if mishandled. I used pointy-tip
tweezers to put them into the right PCB sockets by &amp;quot;poking&amp;quot; a tip into a holtite, then
using that to position into the hole. Occasionally, I&#39;d use a pair of long nose pliers
to hold the holtite in place inside the socket as I removed it from the tweezers.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://richardneililagan.com/i/PZVjlWtYRO-880.jpeg 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/PZVjlWtYRO-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/PZVjlWtYRO-880.jpeg&quot; width=&quot;880&quot; height=&quot;495&quot; alt=&quot;A photo showing holtites placed in PCB sockets.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Holtites placed in PCB sockets.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;p&gt;To secure holtites into place, you&#39;ll need to heat them up with a soldering iron as you
push them down into the hole. The heat makes them more malleable and will let them find a better fit in the socket. I used a blunt tip for my soldering iron to do this so that I can more evenly provide force into the push.&lt;/p&gt;
&lt;p&gt;&lt;mark&gt;You don&#39;t need to solder the holtites into place at all&lt;/mark&gt;.&lt;/p&gt;
&lt;p&gt;Once secure in the holes, the holtites will protrude from the PCB by a small amount
on the underside. On the top side, however, they should lie flush with the surface as much as possible. They should also feel fixed in place, and should not give when you try
to push them out of the sockets.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://richardneililagan.com/i/vgQJTyHoBb-880.jpeg 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/vgQJTyHoBb-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/vgQJTyHoBb-880.jpeg&quot; width=&quot;880&quot; height=&quot;495&quot; alt=&quot;A photo showing the underside of the PCB with the holtites protruding slightly.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Holtites protrude a bit on the underside of the PCB when installed.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;h2 id=&quot;pe-foam-mod&quot; tabindex=&quot;-1&quot;&gt;PE foam mod&lt;/h2&gt;
&lt;p&gt;One modification I did to my Corne v3 that I ended up really liking was to &lt;a href=&quot;https://switchandclick.com/pe-foam-mod/&quot;&gt;put some PE foam&lt;/a&gt; on the PCB for the switches to rest on. They&#39;re meant to provide a more snug fit
for the switches once they&#39;re put in place, and helps minimize clatter when you type on
them. In my case, they also seem to help give me a more &lt;code&gt;thocky&lt;/code&gt; sound even with tactile
switches, which I really like.&lt;/p&gt;
&lt;p&gt;To make this easier for myself, I use pre-cut PE foam &amp;quot;stickers&amp;quot; that I can just stick
onto the PCB directly, right where the switches would be. I&#39;ve got enough left over from
my Corne v3 build, so this works out nicely.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://richardneililagan.com/i/YLq7mKSC0j-880.jpeg 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/YLq7mKSC0j-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/YLq7mKSC0j-880.jpeg&quot; width=&quot;880&quot; height=&quot;640&quot; alt=&quot;A photo showing a portion of the PCB with PE foam installed.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;These PE foam stickers are very convenient!&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://richardneililagan.com/i/2bF48UHBX3-880.jpeg 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/2bF48UHBX3-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/2bF48UHBX3-880.jpeg&quot; width=&quot;880&quot; height=&quot;416&quot; alt=&quot;A photo showing the entire PCB with PE foam installed.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;This might just be the most enjoyable part of this build.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;h2 id=&quot;re-assembly&quot; tabindex=&quot;-1&quot;&gt;Re-assembly&lt;/h2&gt;
&lt;p&gt;Now that the holtites are in place (and we&#39;ve got PE foam installed as well), we can
put the keyboard back together.&lt;/p&gt;
&lt;p&gt;I started by placing switches in the corners of the plate, along with a couple in the very center. I then aligned this with the PCB and just placed the switches snug into place.
With the holtites installed, you&#39;ll need to give each switch a firm push so that the
contact pins fit into the sockets. The holtites should hold the switches in place firmly.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://richardneililagan.com/i/yhUanm9yu0-880.jpeg 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/yhUanm9yu0-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/yhUanm9yu0-880.jpeg&quot; width=&quot;880&quot; height=&quot;398&quot; alt=&quot;Switches placed to align the plate with the PCB.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;I&#39;m using C3 Kiwis here, except for the bottom right ones, where I&#39;m using U4S Bobas.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://richardneililagan.com/i/OXTn-NTfN0-880.jpeg 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/OXTn-NTfN0-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/OXTn-NTfN0-880.jpeg&quot; width=&quot;880&quot; height=&quot;416&quot; alt=&quot;A photo showing the underside of the PCB, with switch contact pins snug inside holtites.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Holtites will grip the switch contact pins.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;p&gt;And, really, you just keep on going from there by putting your switch of choice back into the plate and PCB. Once you have all the switches in place, the plate and PCB will hold together on their own.&lt;/p&gt;
&lt;p&gt;Just take care not to bend the switch contact pins are you push them into the sockets.
If you do, take the switch out again and use a pair of pliers to straighten the pin out
and try again.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://richardneililagan.com/i/MpyuztfVfq-880.jpeg 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/MpyuztfVfq-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/MpyuztfVfq-880.jpeg&quot; width=&quot;880&quot; height=&quot;416&quot; alt=&quot;All of the switches back in place on the PCB and plate.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;p&gt;&lt;mark&gt;Before you screw the PCB back into the case, you should test the keyboard first and make sure that all the keys are registering correctly&lt;/mark&gt;.&lt;/p&gt;
&lt;h2 id=&quot;et-voila&quot; tabindex=&quot;-1&quot;&gt;Et voila&lt;/h2&gt;
&lt;p&gt;And there you have it. Just screw the PCB back into the case (again, there are six screw points!), and place your choice of keycaps, and the Pok3r is good as new. It&#39;s now been given a breath of fresh air, and is now much easier to swap switches around to try new stuff out in the future!&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://richardneililagan.com/i/sSaK5B_WmC-880.jpeg 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/sSaK5B_WmC-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/sSaK5B_WmC-880.jpeg&quot; width=&quot;880&quot; height=&quot;416&quot; alt=&quot;A photo showing a completed Pok3r with white Japanese keycaps.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Phew!&lt;/figcaption&gt;
    &lt;/figure&gt;
</content>
  </entry>
  
  
  <entry>
    <title>Validation checks in AWS CDK constructs</title>
    <link href="https://richardneililagan.com/posts/aws-cdk-validation-checks/" />
    <updated>2022-08-03T00:00:00Z</updated>
    <id>https://richardneililagan.com/posts/aws-cdk-validation-checks/</id>
    <content type='html'>&lt;p&gt;Working with quite a lot of AWS CDK for our prototypes, it&#39;s very important to be able
to ensure our &lt;code&gt;Stack&lt;/code&gt;s and &lt;code&gt;Construct&lt;/code&gt;s are in a valid state at all times. When your
infrastructure is layers upon layers of nested components, it&#39;s nice to have that
guarantee at the back of your mind that your system is within expectations, and that
none of our components are entering invalid configurations.&lt;/p&gt;
&lt;p&gt;Yes we can write up unit and integration tests using something like Jest, but full-fledged
tests can be a bit heavy-handed at times. Sometimes we just want to make sure our system
is in an acceptable state right before deployment or synthesis. That&#39;s where validation
checks can help best.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;cdk-constructs-and-.validate()&quot; tabindex=&quot;-1&quot;&gt;CDK constructs and &lt;code&gt;.validate()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;I surprisingly found very little on this topic &lt;a href=&quot;https://docs.aws.amazon.com/cdk/api/v2/docs/aws-construct-library.html&quot;&gt;from official documentation&lt;/a&gt;.
It appears that all CDK constructs, when mapped in the construct tree,
&lt;a href=&quot;https://docs.aws.amazon.com/cdk/api/v2/docs/constructs.Node.html#validate&quot;&gt;has a &lt;code&gt;.validate()&lt;/code&gt; function&lt;/a&gt;, which the CDK uses to determine whether or not its in a valid state. This returns &lt;code&gt;string[]&lt;/code&gt; which is a list of validation error messages --- if it&#39;s an empty list, then the construct is valid.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;.validate()&lt;/code&gt; function seems to be &lt;a href=&quot;https://docs.aws.amazon.com/cdk/api/v2/docs/constructs.IValidation.html&quot;&gt;called by the CDK during stack synthesis&lt;/a&gt; (so, as part of a &lt;code&gt;cdk synth&lt;/code&gt; operation). This also has the neat side effect that
validation also occurs &lt;em&gt;right before&lt;/em&gt; deployment actually happens on &lt;code&gt;cdk deploy&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This lets us create some nice guardrails around our systems that prevents deployment
if something is not as we&#39;d expect, for example.&lt;/p&gt;
&lt;h3 id=&quot;adding-validation-checks&quot; tabindex=&quot;-1&quot;&gt;Adding validation checks&lt;/h3&gt;
&lt;p&gt;Of course, that&#39;s only useful if we can add validation checks in the first place.
Turns out all CDK constructs have access to an &lt;code&gt;.addValidation()&lt;/code&gt; hook through their
&lt;code&gt;.node&lt;/code&gt; accessor. &lt;code&gt;.addValidation&lt;/code&gt; accepts an argument with the shape &lt;code&gt;{ validate: () =&amp;gt; string[] }&lt;/code&gt;. The object&#39;s &lt;code&gt;.validate()&lt;/code&gt; function is just a validation function that, again, returns a list of validation error messages. In a gist, &lt;code&gt;construct.node.validate()&lt;/code&gt; calls the &lt;code&gt;.validate()&lt;/code&gt; function of all validators added via &lt;code&gt;.addValidation()&lt;/code&gt;, and collects the validation error messages. If there is at least one such error message, then the construct is in an invalid state (and synthesis ends with an error).&lt;/p&gt;
&lt;h2 id=&quot;an-example-instead%3F&quot; tabindex=&quot;-1&quot;&gt;An example instead?&lt;/h2&gt;
&lt;p&gt;Let&#39;s create a construct that will trigger an event on a specified hour every day,
just like a cron job. We&#39;ll use Amazon EventBridge to create a rule the fires on the
specified hour --- we can then use this to, for example, invoke a Lambda function,
or start a downstream process (like retraining an ML model).&lt;/p&gt;
&lt;p&gt;We&#39;ll start with a simple scaffold:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; cdk &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;aws-cdk-lib&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; events &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;aws-cdk-lib/aws-events&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Construct &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;constructs&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: ---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ScheduleConstruct&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Construct&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scope&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Construct&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scope&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;// :: This creates our daily rule.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;//    For now, this will trigger at 8:00 AM daily.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; rule &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;events&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Rule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;my-daily-rule&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      schedule&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Schedule&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cron&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;        hour&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;8&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;        minute&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;0&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We could let this construct be configurable through props passed into it,
so let&#39;s try that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript/2-4,8,16/7,15/&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;ins class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ScheduleConstructProps&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;
&lt;ins class=&quot;code-line&quot;&gt;  hour&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;/ins&gt;
&lt;ins class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/ins&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ScheduleConstruct&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Construct&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;del class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scope&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Construct&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/del&gt;
&lt;ins class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scope&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Construct&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; props&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ScheduleConstructProps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scope&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;// :: This creates our daily rule.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;//    For now, this will trigger at 8:00 AM daily.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; rule &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;events&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Rule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;my-daily-rule&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      schedule&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Schedule&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cron&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;del class=&quot;code-line&quot;&gt;        hour&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;8&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/del&gt;
&lt;ins class=&quot;code-line&quot;&gt;        hour&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hour&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/ins&gt;
&lt;span class=&quot;code-line&quot;&gt;        minute&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;0&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, putting aside the possibility that &lt;code&gt;events.Schedule.cron()&lt;/code&gt; does validation of its
own (I haven&#39;t checked, to be honest), we&#39;d want to ensure whatever we&#39;re passing into
our construct is within acceptable bounds. Specifically, we want to make sure that
&lt;code&gt;props.hour&lt;/code&gt; is always gonna be an integer &lt;code&gt;&amp;gt;= 0&lt;/code&gt; and &lt;code&gt;&amp;lt; 24&lt;/code&gt;, but in a &lt;code&gt;string&lt;/code&gt; type.
Otherwise, we&#39;ll have a construct trying to set a scheduled trigger at an unheard of
time like 37:00 pm or something.&lt;/p&gt;
&lt;h3 id=&quot;let&#39;s-add-in-validations&quot; tabindex=&quot;-1&quot;&gt;Let&#39;s add in validations&lt;/h3&gt;
&lt;p&gt;We can guard our construct from those cases by adding in validation checks:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ScheduleConstruct&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Construct&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scope&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Construct&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; props&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ScheduleConstructProps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scope&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;// :: Add our validation checks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addValidation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      &lt;span class=&quot;token function-variable function&quot;&gt;validate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; messages&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; hourInteger &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Number&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parseInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hour&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Number&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isNaN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hourInteger&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; messages&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Hour provided is not a valid integer.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hourInteger &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; messages&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Hour must be non-negative.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hourInteger &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; messages&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Hour must be less than 24.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; messages&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that our validation checks are in place, any time you try to run &lt;code&gt;cdk synth&lt;/code&gt;
or &lt;code&gt;cdk deploy&lt;/code&gt; with an invalid provided &lt;code&gt;hour&lt;/code&gt; value, you&#39;ll get an error message,
and the CDK will refuse to progress.&lt;/p&gt;
&lt;p&gt;For example, if I try:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ScheduleConstruct&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;my-schedule&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  hour&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;30&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;λ  cdk synth&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;Error: Validation failed with the following errors:&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;CdkTestStack/my-schedule&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; Hour must be &lt;span class=&quot;token function&quot;&gt;less&lt;/span&gt; than &lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And my mind is more at ease as a result.&lt;/p&gt;
</content>
  </entry>
  
  
  <entry>
    <title>Zip files from Amazon S3 without saving them locally</title>
    <link href="https://richardneililagan.com/posts/zip-files-in-amazon-s3/" />
    <updated>2022-06-01T00:00:00Z</updated>
    <id>https://richardneililagan.com/posts/zip-files-in-amazon-s3/</id>
    <content type='html'>&lt;p&gt;I recently had an engineering requirement where I had to build a &lt;a href=&quot;https://aws.amazon.com/lambda&quot;&gt;Lambda function&lt;/a&gt;
that would grab an arbitrary number of files stored in S3, compress them into a single ZIP archive,
then upload the archive into another S3 bucket. This was eventually going to be given to a
user using a presigned URL to access.&lt;/p&gt;
&lt;p&gt;It would have been simple enough to implement if we could just download all the files into
the Lambda function, zip them up, then upload. However, the number of files (and total
size of all of it) was unknown and completely arbitrary. Lambda functions only come with
512 MB of ephemeral storage. You can &lt;a href=&quot;https://aws.amazon.com/blogs/aws/aws-lambda-now-supports-up-to-10-gb-ephemeral-storage/&quot;&gt;extend this up to 10 GB at a time&lt;/a&gt;, but the
storage costs can add up. Besides, there&#39;s still no guarantee we can keep under 10 GB.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;let&#39;s-not-download-the-files-then&quot; tabindex=&quot;-1&quot;&gt;Let&#39;s not download the files then&lt;/h2&gt;
&lt;p&gt;It turns out that &lt;a href=&quot;https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property&quot;&gt;we can use a stream to upload content to Amazon S3 when using the AWS SDK&lt;/a&gt;.
This is true at least with the v2 of the AWS SDK.&lt;/p&gt;
&lt;p&gt;This can be very useful. Using something like &lt;a href=&quot;https://www.npmjs.com/package/archiver&quot;&gt;&lt;code&gt;archiver&lt;/code&gt;&lt;/a&gt;, we can directly
stream files into an archive. If we can then pipe that stream directly into the S3 upload call, then
we&#39;re technically uploading a ZIP file, without having to compress the contents locally.&lt;/p&gt;
&lt;p&gt;First, let&#39;s create the archival stream. This is simple enough to do:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; archiver &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;archiver&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: ---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; archiveStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;archiver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;zip&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;archiveStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;error&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Archival encountered an error:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#39;ll then set up a &lt;mark&gt;passthrough&lt;/mark&gt; stream into an S3 upload. The idea here is that whatever
data is put into the stream will be passed through directly to the output --- in this case,
an object in an S3 bucket.&lt;/p&gt;
&lt;p&gt;Before anything else though, we have to make sure that our S3 client keeps connections
open long enough for us to finish uploading. Remember that we&#39;re opening up what&#39;s essentially
a pipe into an S3 object file, and will be dumping data into it continuously until we&#39;re
ready to finalize / close the stream.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;AWS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;aws-sdk&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: ---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; s3 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;AWS&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;S3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  httpOptions&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    timeout&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// :: 10 minutes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; stream &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;node:stream&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: ---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;TARGET_BUCKET_NAME&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ARCHIVE_KEY&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: ---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; passthrough &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;PassThrough&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: We wrap this in a promise so we have something to await.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; uploadTask &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  s3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;upload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      Bucket&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;TARGET_BUCKET_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      Key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ARCHIVE_KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      Body&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; passthrough&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      ContentType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;application/zip&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;// :: This callback fires when the stream is closed.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;//    As you can see, we&#39;re just resolving the promise here,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;//    so whatever awaits this is notified.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Zip uploaded.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TARGET_BUCKET_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ARCHIVE_KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then finally, we just pipe the archive stream into the upload stream. Anything we put into
the archive stream, eventually finds itself passed through the upload stream.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;archiveStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;passthrough&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that out of the way, all we need to do now is to actually put the files into the stream.
Let&#39;s talk about that now.&lt;/p&gt;
&lt;h2 id=&quot;throw-the-files-in&quot; tabindex=&quot;-1&quot;&gt;Throw the files in&lt;/h2&gt;
&lt;p&gt;If we already know which files stored in S3 we want to include in the archive, then we can
just use the AWS SDK to grab the file contents, then put them in the stream. Thankfully,
the &lt;code&gt;getObject&lt;/code&gt; operation using the AWS SDK gives us a &lt;code&gt;Buffer&lt;/code&gt; of the object. We can use
this &lt;code&gt;Buffer&lt;/code&gt; directly with our archive stream.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; path &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;node:path&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: ---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SOURCE_BUCKET_NAME&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OBJECT_KEYS&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: ---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; key &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OBJECT_KEYS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; params &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Bucket&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SOURCE_BUCKET_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; key &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; s3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;// :: `response.Body` is a Buffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  archiveStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;basename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: When all the files have been added, then we can&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//    finalize the archive stream. This eventually closes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//    the stream, and subsequently closes the passthrough&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//    stream we created in the upload task.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;archiveStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;finalize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To finish things off, we can &lt;code&gt;await&lt;/code&gt; our &lt;code&gt;uploadTask&lt;/code&gt; earlier somewhere in the application,
and get the resulting bucket name + object key when it resolves.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: Remember that this task resolves only when the&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//    passthrough stream closes, and the passthrough&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//    stream closes only when the archive stream&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//    (that is piping into it) closes too.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//    When we finalize the archive stream, pretty much&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//    everything else collapses, and this resolves.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;BUCKET_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OBJECT_KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; uploadTask&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: Now we can do whatever we want with this.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//    How about we generate a presigned URL?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; params &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  Bucket&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;BUCKET_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  Key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OBJECT_KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  Expires&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// :: 24 hours&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; s3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSignedUrlPromise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;getObject&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And there you have it. We just archived files stored in an S3 bucket, and stored that
into another S3 bucket without having to save the files locally first.&lt;/p&gt;
</content>
  </entry>
  
  
  <entry>
    <title>Tracing through your distributed applications using AWS X-Ray</title>
    <link href="https://richardneililagan.com/posts/using-aws-xray/" />
    <updated>2020-07-11T00:00:00Z</updated>
    <id>https://richardneililagan.com/posts/using-aws-xray/</id>
    <content type='html'>&lt;p&gt;On my last livestream session with the AWS &lt;a href=&quot;https://www.twitch.tv/videos/674407919&quot;&gt;DevAx Connect&lt;/a&gt; team,
I talked about how you can build observability into distributed applications
on AWS Lambda (among other things), particularly using &lt;a href=&quot;https://aws.amazon.com/xray&quot;&gt;AWS X-Ray&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With all the positive buzz around the whole idea of microservices and immensely
distributed applications, one of the very real problems it creates is that --- now that
most everything in your application is a Lego piece in a great, big whole --- how do
you keep an eye on all of those moving pieces? When one of your users complains that your
service is responding very slowly, which of the hundreds (or even thousands) of pieces
a single API call even lightly interacts with could be the culprit? &lt;mark&gt;
When an exception or error breaks out, where exactly did it come from?&lt;/mark&gt;&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/2pDpCDUK72-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/2pDpCDUK72-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/2pDpCDUK72-880.png&quot; width=&quot;880&quot; height=&quot;462&quot; alt=&quot;A typical microservices communications mesh.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;A sample figure caption.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;p&gt;This is a question that&#39;s been asked of me time and time again,
and something that I&#39;m always a bit surprised how it&#39;s not given more thought
by more people --- at least not until much, &lt;em&gt;much&lt;/em&gt; later in the engineering process.
Oftentimes, by then there would already be a fire being put out, and everyone would then
be wishing that they had focused on a solution much, &lt;em&gt;much&lt;/em&gt; sooner.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;setting-the-stage&quot; tabindex=&quot;-1&quot;&gt;Setting the stage&lt;/h2&gt;
&lt;p&gt;In the livestream, I shared that I&#39;ve prepared a &lt;a href=&quot;https://github.com/richardneililagan/project-name-generator-serverless&quot;&gt;demonstration serverless project&lt;/a&gt;
using the &lt;a href=&quot;https://serverless.com/&quot;&gt;Serverless Framework&lt;/a&gt;. Once deployed, it exposes a single API endpoint
that generates a random string you can then use to name one of your new projects.
&lt;em&gt;Snazzy!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When an HTTP request is received through &lt;a href=&quot;https://aws.amazon.com/api-gateway&quot;&gt;Amazon API Gateway&lt;/a&gt;,
an entrypoint &lt;a href=&quot;https://aws.amazon.com/lambda&quot;&gt;AWS Lambda&lt;/a&gt; function handles it. This handler then selects,
at random, one Lambda handler from each of five different services:
nouns, adjectives, verbs, colors, and descriptors. Each service, by default, has
ten Lambda functions up for grabs.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/WTKrr1zHUB-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/WTKrr1zHUB-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/WTKrr1zHUB-880.png&quot; width=&quot;880&quot; height=&quot;462&quot; alt=&quot;The demonstration project architecture&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;p&gt;Now, it has to be said that this definitely isn&#39;t how something like this &lt;em&gt;should&lt;/em&gt;
be built, and the setup is, as the &lt;code&gt;README&lt;/code&gt; file says, somewhat convoluted.
But while it&#39;s not quite how a real-life application would be built (at least I hope not!),
it does mimic how a bunch of small services can talk to one another to address an
end-user request.&lt;/p&gt;
&lt;h2 id=&quot;enabling-tracing-through-aws-x-ray&quot; tabindex=&quot;-1&quot;&gt;Enabling tracing through AWS X-Ray&lt;/h2&gt;
&lt;p&gt;Let&#39;s start putting in the tracing functionality.&lt;/p&gt;
&lt;p&gt;Enabling tracing on your Lambda functions is pretty easy —
you just have to tick a checkbox on the Lambda function&#39;s dashboard.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/TFzNTUthi6-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/TFzNTUthi6-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/TFzNTUthi6-880.png&quot; width=&quot;880&quot; height=&quot;378&quot; alt=&quot;AWS Lambda X-Ray configuration toggle&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;p&gt;However, when you&#39;re managing multiple Lambda functions in your application (as you most
probably are going to be), toggling it this way, going through your functions
one by one, is &lt;strong&gt;not&lt;/strong&gt; gonna be fun.&lt;/p&gt;
&lt;p&gt;If you&#39;re using the Serverless Framework, it fortunately has an easy way to enable
tracing across your entire workload. Just add the following to your
&lt;code&gt;serverless.yml&lt;/code&gt; manifest, under the &lt;code&gt;provider&lt;/code&gt; section:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml/6-8,14-15/_/&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; aws&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token key atrule&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nodejs12.x&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token key atrule&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; dev&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token key atrule&quot;&gt;region&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ap&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;southeast&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;ins class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token key atrule&quot;&gt;tracing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/ins&gt;
&lt;ins class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token key atrule&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;/ins&gt;
&lt;ins class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token key atrule&quot;&gt;apiGateway&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;/ins&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token key atrule&quot;&gt;iamRoleStatements&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Allow&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      &lt;span class=&quot;token key atrule&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; lambda&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;InvokeFunction&lt;/span&gt;
&lt;ins class=&quot;code-line&quot;&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; xray&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;PutTraceSegments&lt;/ins&gt;
&lt;ins class=&quot;code-line&quot;&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; xray&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;PutTelemetryRecords&lt;/ins&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#39;re adding in a couple of IAM permissions at the bottom just to be sure ---
when you&#39;re toggling tracing on the Lambda console, these will be automatically added
to your Lambda function&#39;s execution role if they&#39;re not there yet.
But, of course, if you know you need it, it&#39;s better to be explicit about it,
so let&#39;s just add those in.&lt;/p&gt;
&lt;p&gt;These changes make it so that whenever requests go into the API Gateway, and invoke
any of the Lambda functions on this application, X-Ray will know about it, and will
be able to map your traces accordingly.&lt;/p&gt;
&lt;p&gt;What this &lt;strong&gt;doesn&#39;t&lt;/strong&gt; do is trace whatever else your Lambda functions may be calling ---
if your functions talk to other AWS services (e.g. it writes into an &lt;a href=&quot;https://aws.amazon.com/dynamodb&quot;&gt;Amazon DynamoDB&lt;/a&gt;
table, or it fetches a file from an &lt;a href=&quot;https://aws.amazon.com/s3&quot;&gt;Amazon S3&lt;/a&gt; bucket), &lt;mark&gt;we won&#39;t be able to
trace those yet.&lt;/mark&gt;&lt;/p&gt;
&lt;p&gt;So let&#39;s add that functionality in.&lt;/p&gt;
&lt;h2 id=&quot;going-the-full-mile-with-tracing&quot; tabindex=&quot;-1&quot;&gt;Going the full mile with tracing&lt;/h2&gt;
&lt;p&gt;Extending the traces to whatever else we&#39;re calling from our Lambda functions is
really simple to do as well --- and it works with anything that you&#39;re using the
&lt;a href=&quot;https://aws.amazon.com/getting-started/tools-sdks/&quot;&gt;AWS SDK&lt;/a&gt; in, not just Lambda functions.&lt;/p&gt;
&lt;p&gt;We&#39;ll need to add in the &lt;a href=&quot;https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-nodejs.html&quot;&gt;AWS X-Ray SDK&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--save&lt;/span&gt; aws-xray-sdk&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;m using the &lt;strong&gt;AWS X-Ray SDK for Node.js&lt;/strong&gt; here,
but there&#39;s a handful of other options you can use depending on your preferred language.
Check out the &lt;a href=&quot;https://docs.aws.amazon.com/xray/latest/devguide/aws-xray.html&quot;&gt;AWS Developer Guide&lt;/a&gt; for more info.&lt;/p&gt;
&lt;p&gt;Afterwards, all you have to do is to wrap the AWS SDK with the AWS X-Ray SDK
everywhere in your code that you&#39;re using it. So, for example, in
&lt;code&gt;src/handlers/entrypoint.js&lt;/code&gt; in the demo project, you can do it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: bring in the X-Ray SDK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; AWSXRay &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;aws-xray-sdk-core&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: ... then you can either wrap your AWS SDK like this:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;AWS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; AWSXRay&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;captureAWS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;aws-sdk&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: ... or maybe like this:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Lambda &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; AWSXRay&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;captureAWS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;aws-sdk&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, most any other AWS service we call from our Lambda function will automatically
have tracing information embedded in them, so when we view your service map later on
AWS X-Ray, you&#39;ll see exactly where and when your requests are jumping to.&lt;/p&gt;
&lt;h3 id=&quot;at-this-point%2C-you&#39;re-already-done.&quot; tabindex=&quot;-1&quot;&gt;At this point, you&#39;re already done.&lt;/h3&gt;
&lt;p&gt;Your application will start tracing all the requests going through it. &lt;mark&gt;
If you want to check out how it looks in action, however, read on.&lt;/mark&gt;
We&#39;ll set up an installation that will send a lot of requests into it so you can see
how the tracing is done.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;you&#39;re-curious-to-see-how-it-looks&quot; tabindex=&quot;-1&quot;&gt;You&#39;re curious to see how it looks&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Of course you are.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If everything was set up correctly, all requests going through the application
(through the API Gateway) will now have tracing information that&#39;s followed by X-Ray
across all your AWS resources.&lt;/p&gt;
&lt;h3 id=&quot;setting-up-a-load-testing-platform&quot; tabindex=&quot;-1&quot;&gt;Setting up a load testing platform&lt;/h3&gt;
&lt;p&gt;We&#39;ll generate a huge load of requests, just like how you&#39;d most probably do it if
you were load testing an application. (Which you really should be doing, by the way.)&lt;/p&gt;
&lt;p&gt;I like to use Nordstrom&#39;s &lt;a href=&quot;https://github.com/Nordstrom/serverless-artillery&quot;&gt;serverless-artillery&lt;/a&gt; for this --- which coincidentally
also uses the Serverless Framework, so setting it up is going to be pretty familiar.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-g&lt;/span&gt; serverless-artillery&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; loadtesting     &lt;span class=&quot;token comment&quot;&gt;# :: or wherever you want to store your load testing manifest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; loadtesting&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;#  :: this will generate a load testing starter manifest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;slsart script&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will generate a &lt;code&gt;script.yml&lt;/code&gt; file containing the information that
serverless-artillery will need to run your load tests. Try switching out the contents
of that file with the following (feel free to tweak, if you&#39;d like):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml/2,3&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;# :: Change this to the URL of your API Gateway entrypoint&lt;/span&gt;&lt;/span&gt;
&lt;mark class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token key atrule&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://xxx.execute-api.yyy.amazonaws.com/dev/generate&#39;&lt;/span&gt;&lt;/mark&gt;
&lt;mark class=&quot;code-line&quot;&gt;&lt;/mark&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;# :: These are your testing phases.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;#    This configuration sets up the load test so that:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;#      1/ For the first 15 seconds,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;#         start with 10 requests / sec,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;#         and steadily increase that to 25 requests / sec,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;#      2/ Then for the next 60 seconds,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;#         maintain a constant 25 requests / sec rate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token key atrule&quot;&gt;phases&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      &lt;span class=&quot;token key atrule&quot;&gt;arrivalRate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      &lt;span class=&quot;token key atrule&quot;&gt;rampTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;25&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      &lt;span class=&quot;token key atrule&quot;&gt;arrivalRate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;25&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# :: This section allows you to define more complicated scenarios,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;#    but we won&#39;t use this for now.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;#    What this setting currently does is just send the HTTP GET requests to the root&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;#    of the target URL as specified above.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;scenarios&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;flow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;          &lt;span class=&quot;token key atrule&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/&#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we need to deploy that to your AWS account as well:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;slsart deploy &lt;span class=&quot;token parameter variable&quot;&gt;--stage&lt;/span&gt; my-test-setup&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;ll take a few minutes, but when the deploy is done, we can then instruct it to run
the load testing scenario above:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;slsart invoke &lt;span class=&quot;token parameter variable&quot;&gt;--stage&lt;/span&gt; my-test-setup&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;inspecting-the-traces&quot; tabindex=&quot;-1&quot;&gt;Inspecting the traces&lt;/h3&gt;
&lt;p&gt;It&#39;ll take a few seconds for the tracing information from the requests to be available
at AWS X-Ray, but go ahead and go to your &lt;a href=&quot;https://console.aws.amazon.com/xray&quot;&gt;X-Ray Console&lt;/a&gt; anyway.&lt;/p&gt;
&lt;p&gt;Navigate to your &lt;strong&gt;Service Map&lt;/strong&gt;, and you should get a pretty cool graph of nodes in
your application, as well as the connections between them:&lt;/p&gt;
&lt;figure class=&quot;fluid&quot;&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/niJ0n8KplS-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/niJ0n8KplS-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/niJ0n8KplS-880.png&quot; width=&quot;880&quot; height=&quot;462&quot; alt=&quot;AWS X-Ray Service Map&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Ooh, circles and lines.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;p&gt;Awesome, but aside from a few nice latency and transaction rate numbers we can pat
ourselves on the back with, there isn&#39;t much else this is telling us. So let&#39;s try
introducing some errors into the stack, and see how that will affect our map.&lt;/p&gt;
&lt;h3 id=&quot;add-in-some-artificial-defects&quot; tabindex=&quot;-1&quot;&gt;Add in some artificial defects&lt;/h3&gt;
&lt;p&gt;I&#39;ll change some of the Lambda handlers so that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;one of them takes a much longer time to respond, and&lt;/li&gt;
&lt;li&gt;one of them generates an &lt;code&gt;HTTP 4xx&lt;/code&gt; error half of the time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We have a total of 50 handler functions here, and just having to fish out two faulty
ones without tracing tooling is a pretty tedious effort --- imagine if we had hundreds
of these services up and running.&lt;/p&gt;
&lt;p&gt;I&#39;ll add the following code blocks to the handlers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: src/handlers/adjective.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: mock a handler that takes an abnormally long time to resolve&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;faultyHandler&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// :: resolve after 2 sec&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: src/handlers/verb.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// :: throw an error half of the time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;faultyHandler&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;// :: ---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;DEFECT&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we&#39;ll adjust &lt;code&gt;src/handlers/functions.js&lt;/code&gt; to take the faulty handlers into account:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;createHandler&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; i&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isFaulty &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;adjective&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;verb&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handler &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; isFaulty &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;src/handlers/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;type&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.faultyHandler&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;src/handlers/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;type&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.handler&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; handler &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we&#39;ll deploy the faulty handlers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;sls deploy &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; adjective-4&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;sls deploy &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; verb-9&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run the load tests again.&lt;/p&gt;
&lt;p&gt;This time, when you look at your service map, you&#39;ll easily see where in your application
you&#39;re experiencing degraded performance:&lt;/p&gt;
&lt;figure class=&quot;fluid&quot;&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/WejMAEOB3P-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/WejMAEOB3P-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/WejMAEOB3P-880.png&quot; width=&quot;880&quot; height=&quot;462&quot; alt=&quot;AWS X-Ray Service Map with an error&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Let&#39;s see if you can find exactly where our application is hitting errors.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;p&gt;You can select any node in the map to look more into it.
When you select a node, you can isolate the map to just that part of the application,
and see the resulting distribution of performance results.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/gif&quot; srcset=&quot;https://richardneililagan.com/i/L1x_O4uG_K-880.gif 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/L1x_O4uG_K-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/L1x_O4uG_K-880.gif&quot; width=&quot;880&quot; height=&quot;462&quot; alt=&quot;AWS X-Ray Service Map zoom to error&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;p&gt;You can also click through the &lt;strong&gt;Analyze Traces&lt;/strong&gt; button to view the traces in detail.
Let&#39;s do that to investigate our response time behavior.&lt;/p&gt;
&lt;p&gt;Let&#39;s say we wanted to investigate all the requests in our application that are taking
longer than &lt;strong&gt;1.5 seconds&lt;/strong&gt; to complete --- we can do that by searching for
&lt;code&gt;responsetime &amp;gt; 1.5&lt;/code&gt; on the search input bar. (You can click on the help button
to see how the search syntax works.)&lt;/p&gt;
&lt;p&gt;This will give you a list of traces in your application that logged a response time of,
expectedly, longer than 1.5 seconds. Click on one of the traces, and you&#39;ll see a
dashboard with a trace map of all the services involved in that trace, as well as a
timeline of events making up that entire trace:&lt;/p&gt;
&lt;figure class=&quot;fluid&quot;&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/c5NJrinZsb-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/c5NJrinZsb-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/c5NJrinZsb-880.png&quot; width=&quot;880&quot; height=&quot;401&quot; alt=&quot;AWS X-Ray trace detail error&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;It&#39;s clear to see which particular service was at fault for the slow overall response time with this request.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;h2 id=&quot;there&#39;s-definitely-more-to-this&quot; tabindex=&quot;-1&quot;&gt;There&#39;s definitely more to this&lt;/h2&gt;
&lt;p&gt;Having active tracing available for your applications make it so much simpler
to perform investigations for debugging, as well as for just keeping an eye on all
your services, and making sure they&#39;re behaving as expected.&lt;/p&gt;
&lt;p&gt;This is definitely just scratching the surface of it, and if you want to know more
about what else you can do with AWS X-Ray, then don&#39;t miss checking out the
&lt;a href=&quot;https://docs.aws.amazon.com/xray/latest/devguide/aws-xray.html&quot;&gt;AWS Developer Guide&lt;/a&gt; for it.&lt;/p&gt;
</content>
  </entry>
  
  
  <entry>
    <title>Working with the Amazon Chime SDK</title>
    <link href="https://richardneililagan.com/posts/working-with-amazon-chime-sdk/" />
    <updated>2020-06-19T00:00:00Z</updated>
    <id>https://richardneililagan.com/posts/working-with-amazon-chime-sdk/</id>
    <content type='html'>&lt;p&gt;I was one of those people that were pleasantly surprised, waking up one morning earlier this month,
to the news of &lt;a href=&quot;https://slackhq.com/slack-aws-drive-development-agility&quot;&gt;Slack announcing a partnership with AWS&lt;/a&gt;.
While the decision to adopt or strengthen their AWS posture isn&#39;t exactly something new for a lot of companies,
the real standout for me was that Slack will also &lt;a href=&quot;https://aws.amazon.com/blogs/business-productivity/customers-like-slack-choose-the-amazon-chime-sdk-for-real-time-communications/&quot;&gt;migrate their voice and video call functionality
to use the Amazon Chime service&lt;/a&gt; moving forward.&lt;/p&gt;
&lt;p&gt;Now, I use Slack very significantly in my day-to-day,
and &lt;a href=&quot;https://slack.com/help/articles/115003498363-Slack-calls--the-basics&quot;&gt;Slack already has voice and video calls available&lt;/a&gt;,
so actively migrating that functionality over to a different platform feels like a really major move.&lt;/p&gt;
&lt;p&gt;So, in the spirit of discovery, wonder, and that itch to build,
I spent a bit of time working with the Amazon Chime service to get a better feel
of what it has to offer.&lt;/p&gt;
&lt;h2 id=&quot;the-skinny&quot; tabindex=&quot;-1&quot;&gt;The skinny&lt;/h2&gt;
&lt;p&gt;The Amazon Chime SDK allows you to create managed online meetings where attendees can do
simultaneous audio and video streaming, as well as share content across all attendees
(e.g. a screen share). It does a great job of abstracting a lot of the details away,
so you (as the developer) won&#39;t have to deal too much about the intricacies of working with
websockets, WebRTC, and media handling.&lt;/p&gt;
&lt;p&gt;Even better, you don&#39;t need to create servers and storage to support your users ---
the technology resources required to run your meetings are automatically managed for you.
This means that you don&#39;t have to think about how much infrastructure to prepare
to potentially support so and so amount of users; all of it just automatically adjust and scale for you.&lt;/p&gt;
&lt;p&gt;At the time of this writing, &lt;a href=&quot;https://aws.amazon.com/chime/pricing/#Chime_SDK_&quot;&gt;the pricing for the Amazon Chime SDK is also very cost-effective&lt;/a&gt;:
it amounts to just &lt;strong&gt;USD 0.0017 / minute connected&lt;/strong&gt; for every attendee. Easy.&lt;/p&gt;
&lt;p&gt;To successfully create and run an online meeting on Chime essentially requires two steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create the Chime meeting&lt;/li&gt;
&lt;li&gt;Connect attendees to the Chime meeting&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;creating-an-amazon-chime-meeting&quot; tabindex=&quot;-1&quot;&gt;Creating an Amazon Chime meeting&lt;/h3&gt;
&lt;p&gt;Creating the actual Chime meeting is done using the &lt;a href=&quot;https://aws.amazon.com/tools&quot;&gt;AWS SDK&lt;/a&gt;.
This is the general SDK that&#39;s used for most of the stuff you can do on the AWS platform.
(Yes, the Amazon Chime SDK is different --- we&#39;ll get to that in a bit.)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Chime &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;aws-sdk&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; chime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Chime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;region&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;us-east-1&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// :: has to be us-east-1 for now&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/** @returns {ChimeMeetingInstance} */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createChimeMeeting&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; params &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;ClientRequestToken&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;some-random-string-of-your-choice&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;MediaRegion&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ap-southeast-1&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// :: where your call is actually hosted&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; chime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createMeeting&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The instance of the &lt;code&gt;AWS.Chime&lt;/code&gt; object currently &lt;mark&gt;has to be in &lt;strong&gt;us-east-1&lt;/strong&gt;&lt;/mark&gt;.&lt;/p&gt;
&lt;p&gt;This is not where your Chime meeting will be hosted; you can specify that using the
&lt;code&gt;MediaRegion&lt;/code&gt; field on the call to &lt;code&gt;Chime.createMeeting(...)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Attendees that you want to bring into the meeting will also require a sort of join ticket.
Later, back at the actual client application, users that want to join a Chime meeting
will need to present attendee information that was explicitly generated for them.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt; * @param {ChimeMeetingInstance} meetingInstance&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt; * @returns {ChimeMeetingAttendee}&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createChimeAttendee&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;meetingInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; MeetingId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; meetingInstance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;meeting&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; params &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    MeetingId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;ExternalUserId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;unique-user-identifier&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; chime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createAttendee&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, you just have to give the client application both the meeting and attendee information,
and they can use that to actually connect themselves into the meeting.&lt;/p&gt;
&lt;p&gt;For example, if you were using AWS Lambda behind an API Gateway,
you might be doing it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; meeting &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createChimeMeeting&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; attendee &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createChimeAttendee&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;meeting&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; payload &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; meeting&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; attendee &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;statusCode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;payload&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;connecting-to-an-amazon-chime-meeting&quot; tabindex=&quot;-1&quot;&gt;Connecting to an Amazon Chime meeting&lt;/h3&gt;
&lt;p&gt;Back on your client application, you will need both the &lt;code&gt;meeting&lt;/code&gt; and &lt;code&gt;attendee&lt;/code&gt; information
we generated above to complete the connection to Amazon Chime.&lt;/p&gt;
&lt;p&gt;Also as mentioned above, the &lt;a href=&quot;https://github.com/aws/amazon-chime-sdk-js&quot;&gt;Amazon Chime SDK&lt;/a&gt; is different from the AWS SDK ---
this is probably because client applications (like a web app, or mobile app) don&#39;t need
all the functionality behind the main SDK, and are just interested in connecting to the meeting.&lt;/p&gt;
&lt;p&gt;There are also Amazon Chime SDKs for iOS and Android, if you need them.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--save&lt;/span&gt; amazon-chime-sdk-js&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Connecting to a Chime meeting is very straightforward.
Once you have both a meeting instance (which can be shared across all attendees),
and an attendee ticket (which is unique to every attendee),
you can then use the Chime SDK to complete the connection.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript/21-25,30&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  ConsoleLogger&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  DefaultDeviceController&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  DefaultMeetingSession&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  LogLevel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  MeetingSessionConfiguration&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;amazon-chime-sdk-js&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; logger &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConsoleLogger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Chime Logs&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; LogLevel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;INFO&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; deviceController &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DefaultDeviceController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;logger&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt; * @param {ChimeMeetingInstance} meeting&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt; * @param {ChimeMeetingAttendee} attendee&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt; * @returns {ChimeMeetingSession}&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;connectToChimeMeeting&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;meeting&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; attendee&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; meetingConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MeetingSessionConfiguration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;meeting&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; attendee&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; meetingSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DefaultMeetingSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;mark class=&quot;code-line&quot;&gt;    meetingConfig&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;
&lt;mark class=&quot;code-line&quot;&gt;    logger&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;
&lt;mark class=&quot;code-line&quot;&gt;    deviceController&lt;/mark&gt;
&lt;mark class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/mark&gt;
&lt;mark class=&quot;code-line&quot;&gt;&lt;/mark&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;// TODO --- configure other stuff for the meeting&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Starting the Chime meeting!&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  meetingSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;audioVideo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;mark class=&quot;code-line&quot;&gt;&lt;/mark&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; meetingSession&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Chime SDK will take care of abstracting out the details of juggling your websockets,
attendees syncing, content and media shares, etc. Pretty convenient!&lt;/p&gt;
&lt;h3 id=&quot;connecting-audio&quot; tabindex=&quot;-1&quot;&gt;Connecting audio&lt;/h3&gt;
&lt;p&gt;So, doing everything above gets you in the Chime meeting,
which gives you ready access to everything that&#39;s being thrown around in there ---
but unless you bind those things to actual elements in your client application,
you won&#39;t see a lot of use here.&lt;/p&gt;
&lt;p&gt;One thing you will want to do is configure audio --- both sending out the audio
coming from your machine, and playing the audio that you are receiving
(coming from everyone else on the call). The Chime SDK does a great job of simplifying this,
but you still need to push it a bit more.&lt;/p&gt;
&lt;p&gt;To specify what audio device to use as an input (most of the time, this will be your microphone),
you can do the following. The Chime SDK will automatically bind the audio coming in through
the selected device, and broadcast that across all attendees in the meeting.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/** @param {ChimeMeetingSession} session */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setMeetingAudioInputDevice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;// :: This will select the default audio input device on your machine, generally.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;//&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;// :: You will probably want to let the user select&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;//    which device they specifically want to use.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; availableAudioInputDevices &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;audioVideo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listAudioInputDevices&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; deviceId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; availableAudioInputDevices&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deviceId&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;audioVideo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;chooseAudioInputDevice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deviceId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To actually play the audio that you receive in the meeting (e.g. someone speaking),
you need to bind an &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; element on your page using the Chime SDK.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- we just need this somewhere on the page --&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;audio&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;my-audio-element&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;audio&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/** @param {ChimeMeetingSession} session */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bindMeetingAudioOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;// :: or however you prefer to get a reference&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;//    to the &amp;lt;audio&gt; element above&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; audioElement &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my-audio-element&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;audioVideo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bindAudioElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;audioElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;connecting-video&quot; tabindex=&quot;-1&quot;&gt;Connecting video&lt;/h3&gt;
&lt;p&gt;Connecting and displaying video and content is done pretty similarly to audio above.&lt;/p&gt;
&lt;p&gt;To share video out into the Chime meeting (like, for example, your webcam output),
you need a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/MediaStream&quot;&gt;MediaStream&lt;/a&gt; object you can pass through the Chime SDK.&lt;/p&gt;
&lt;p&gt;If you&#39;re already using a third-party library to capture and display what your camera
sees onto the page (e.g. &lt;a href=&quot;https://github.com/mozmorris/react-webcam&quot;&gt;react-webcam&lt;/a&gt; for React), there&#39;s a good chance
that it also exposes a &lt;code&gt;MediaStream&lt;/code&gt; object you can readily use.&lt;/p&gt;
&lt;p&gt;To broadcast out a video stream:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt; * @param {ChimeMeetingSession} session&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt; * @param {MediaStream} videoStream&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;broadcastVideo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;session&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; videoStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;audioVideo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startContentShare&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;videoStream&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you just want to share what&#39;s on your screen though,
the Chime SDK has a convenience method that does this for you automatically:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/** @param {ChimeMeetingSession} session */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;shareScreen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;audioVideo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startContentShareFromScreenCapture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To display incoming video you&#39;re receiving through the Chime meeting,
you will need to bind to a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element just like with &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; above.&lt;/p&gt;
&lt;p&gt;However, unlike with audio, you will need to determine when video content is actually being shared.
My guess here is that this is because, unlike with audio where it&#39;s trivial to combine multiple
audio signals together into one, with multiple video streams, you will need to handle them
one by one. So you will actually need to listen in on how what video streams are being shared
(e.g. multiple webcams, screen shares, etc), and bind them to individual &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; components
as you see fit.&lt;/p&gt;
&lt;p&gt;To do this, you will need an observer object that has a &lt;code&gt;.videoTileDidUpdate(...)&lt;/code&gt; method implementation,
and register that into the meeting session. This observer will be notified whenever video content
is started or stopped in the meeting, so you can adjust accordingly.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Again, you will need video elements on the page.&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;     Unlike with audio elements though, you may want to have to create these dynamically,&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;     since you may need more than one element at a time.&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;--&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;video&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;my-video-element&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;video&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/** @param {ChimeMeetingSession} session */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;displaySharedVideoContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; observer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;// :: a tile represents a single instance of shared video content&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token function-variable function&quot;&gt;videoTileDidUpdate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;tile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Received content with ID:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tileId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      &lt;span class=&quot;token comment&quot;&gt;// :: TODO: get a video element specifically for this tile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; videoElement &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my-video-element&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;      session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;audioVideo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bindVideoElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tileId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; videoElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;  session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;audioVideo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;observer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;that-should-be-it!&quot; tabindex=&quot;-1&quot;&gt;That should be it!&lt;/h2&gt;
&lt;p&gt;You&#39;ll probably also want to know how to pause or stop content shares, mute your audio,
see who the attendees are in your meeting ---
all of those are in the &lt;a href=&quot;https://aws.github.io/amazon-chime-sdk-js/modules/apioverview.html&quot;&gt;documentation for the Amazon Chime SDK&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As a sort of cherry on top, here&#39;s me making a fool of myself
testing the Chime meeting video latency. The Chime meeting here is hosted in
the AWS Singapore region (&lt;code&gt;ap-southeast-1&lt;/code&gt;), with me being in the Philippines.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/gif&quot; srcset=&quot;https://richardneililagan.com/i/xLh59unjFy-640.gif 640w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/xLh59unjFy-640.webp 640w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/xLh59unjFy-640.gif&quot; width=&quot;640&quot; height=&quot;169440&quot; alt=&quot;Foo&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;!-- &lt;picture&gt;
  &lt;source srcset=&#39;/media/20200619/chime-sdk-test.webp&#39; type=&#39;image/webp&#39; /&gt;
  &lt;source srcset=&#39;/media/20200619/chime-sdk-test.gif&#39; type=&#39;image/gif&#39; /&gt;
  &lt;img src=&#39;/media/20200619/chime-sdk-test.gif&#39; type=&#39;image/gif&#39; /&gt;
&lt;/picture&gt; --&gt;
</content>
  </entry>
  
  
  <entry>
    <title>Benchmarking the AWS Graviton2 processors</title>
    <link href="https://richardneililagan.com/posts/aws-graviton2-benchmark/" />
    <updated>2020-06-16T00:00:00Z</updated>
    <id>https://richardneililagan.com/posts/aws-graviton2-benchmark/</id>
    <content type='html'>&lt;p&gt;There&#39;s been a lot of fanfare about the new &lt;a href=&quot;https://aws.amazon.com/ec2/graviton&quot;&gt;AWS Graviton2 processors&lt;/a&gt;,
and the performance and cost benefits of using them compared to their
alternatives in the cloud.&lt;/p&gt;
&lt;p&gt;These ARM-based processors power the &lt;a href=&quot;https://youtu.be/LNqRvP6Xvrw&quot;&gt;new M6g, C6g, and R6g family of EC2 instance types&lt;/a&gt;,
with the previous (first) generation powering the &lt;a href=&quot;https://aws.amazon.com/ec2/instance-types/a1/&quot;&gt;A1 family of EC2 instances&lt;/a&gt;.
AWS promises that the Graviton2 offers significant improvements over the current 5th gen x86 EC2 mainstays.
(20% lower cost and 40% higher performance, based on AWS&#39; own internal testing,
according to the &lt;a href=&quot;https://aws.amazon.com/ec2/graviton&quot;&gt;AWS Graviton2 landing page&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;So, I wanted to have a bit of fun and do some light benchmarking myself.&lt;/p&gt;
&lt;h2 id=&quot;methodology&quot; tabindex=&quot;-1&quot;&gt;Methodology&lt;/h2&gt;
&lt;p&gt;I wanted to gather as many data points as I could without spending too much effort on it,
so I created a bash script that&#39;s meant to be run as an EC2 instance&#39;s user data.
I&#39;ve always thought that the naming was pretty off --- &lt;a href=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html#user-data-shell-scripts&quot;&gt;EC2 user data&lt;/a&gt; scripts execute on launch.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/richardneililagan/e66bca28939a4677a498ac5ae4e0b2aa&quot;&gt;You can take a look at the script here&lt;/a&gt;.
It will essentially do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gather some information from the &lt;a href=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html&quot;&gt;instance metadata&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;Install &lt;code&gt;sysbench&lt;/code&gt; and the AWS CLI,&lt;/li&gt;
&lt;li&gt;Set the instance to terminate when it&#39;s shut down (instead of just stopping),&lt;/li&gt;
&lt;li&gt;In sets of powers of 2, for the available cores in the machine:
&lt;ul&gt;
&lt;li&gt;Run benchmarking tests,&lt;/li&gt;
&lt;li&gt;Format and append the results into a CSV file&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Upload the results to an Amazon S3 bucket,&lt;/li&gt;
&lt;li&gt;Shutdown the instance (effectively terminating it)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To automate the entire process, I created an &lt;a href=&quot;https://docs.aws.amazon.com/autoscaling/ec2/userguide/LaunchTemplates.html&quot;&gt;EC2 Launch Template&lt;/a&gt;
that uses this user data script, and created an &lt;a href=&quot;https://docs.aws.amazon.com/autoscaling/ec2/userguide/AutoScalingGroup.html&quot;&gt;EC2 Autoscaling Group&lt;/a&gt; from it.
The EC2 instances will run the benchmark tests and terminate themselves,
and the autoscaling groups will happily create new instances to replace them.
Left those running for a day or so, just collecting results on S3.&lt;/p&gt;
&lt;p&gt;Once the data points are ready, accessing them is simply just a matter of using &lt;a href=&quot;https://aws.amazon.com/glue&quot;&gt;AWS Glue&lt;/a&gt;
and &lt;a href=&quot;https://aws.amazon.com/athena/&quot;&gt;Amazon Athena&lt;/a&gt; to run some SQL queries directly on the CSV files to crunch the numbers.&lt;/p&gt;
&lt;h2 id=&quot;results&quot; tabindex=&quot;-1&quot;&gt;Results&lt;/h2&gt;
&lt;p&gt;After a weekend of running the benchmarks, I managed to get the sample pool below.&lt;/p&gt;
&lt;figure class=&quot;fluid&quot;&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/gif&quot; srcset=&quot;https://richardneililagan.com/i/1hUJ1e5cue-880.gif 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/1hUJ1e5cue-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/1hUJ1e5cue-880.gif&quot; width=&quot;880&quot; height=&quot;462&quot; alt=&quot;Count of data points for each instance type.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Count of data points for each instance type.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;p&gt;I wanted to get benchmarking results for when all the cores in the instance are used together,
as well as when constraining the testing to just a single core.
All the instance types I used have 2 cores, with varying levels of memory.
If you use the user data script above for your own testing, however,
it should also test for other core combinations (e.g. 1, 2, 4, 8, etc).&lt;/p&gt;
&lt;p&gt;These are the summarized results from the &lt;strong&gt;single-thread&lt;/strong&gt; benchmark tests:&lt;/p&gt;
&lt;figure class=&quot;fluid&quot;&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/oqwrnUGQmQ-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/oqwrnUGQmQ-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/oqwrnUGQmQ-880.png&quot; width=&quot;880&quot; height=&quot;462&quot; alt=&quot;Results for single-thread benchmarking&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Results when benchmarking was constrained to a single thread.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;p&gt;And these are the summary from the &lt;strong&gt;multi-thread&lt;/strong&gt; benchmark tests (all of which effectively have 2 threads):&lt;/p&gt;
&lt;figure class=&quot;fluid&quot;&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/jUzBQiA8zq-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/jUzBQiA8zq-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/jUzBQiA8zq-880.png&quot; width=&quot;880&quot; height=&quot;462&quot; alt=&quot;Results for 2-core benchmarking&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Results when benchmarking using both cores on each instance type.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;p&gt;I had a few minutes to spare, so I went ahead and looked at how much improvement
each Graviton2 instance gave over each other non-Graviton2 option:&lt;/p&gt;
&lt;figure class=&quot;fluid&quot;&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/3kOrrq0ljH-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/3kOrrq0ljH-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/3kOrrq0ljH-880.png&quot; width=&quot;880&quot; height=&quot;462&quot; alt=&quot;Total events improvement of Graviton2 instances compared to non-Graviton2&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Percent increase of average total events of Graviton2 instances.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;p&gt;For really quick, shallow numbers, I think these are definitely quite telling.
Granted, the &lt;em&gt;real&lt;/em&gt; litmus test of any new tech is on their application to real scenarios
(and not just tests like these that lack context),
but I&#39;d say that the numbers as they are are indicative that the Graviton2 processors
definitely offer pronounced improvements over the other options.&lt;/p&gt;
&lt;p&gt;It&#39;d be nice to find some time to be able to run an applied benchmarking test,
like &lt;a href=&quot;https://docs.keydb.dev/blog/2020/03/02/blog-post/&quot;&gt;how KeyDB did theirs&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  
  
  <entry>
    <title>How to: Create a Remote Gaming Server using Parsec on AWS</title>
    <link href="https://richardneililagan.com/posts/create-game-server-aws-parsec/" />
    <updated>2020-04-26T00:00:00Z</updated>
    <id>https://richardneililagan.com/posts/create-game-server-aws-parsec/</id>
    <content type='html'>&lt;p&gt;As is with many people, I&#39;m stuck at home because of the COVID-19 lockdowns most everywhere.
Unfortunately, even with the increased time at home that I can use to game, I&#39;m also stuck with
a old-ish gaming rig that&#39;s wedged with a video card that is showing it&#39;s age.
As much as I&#39;d like to order new equipment, I can&#39;t do that with the restrictions on deliveries.&lt;/p&gt;
&lt;p&gt;I&#39;ve heard of &lt;a href=&quot;https://parsecgaming.com/&quot;&gt;Parsec&lt;/a&gt; some years back, but never really paid it a lot of attention.
Being stuck as I was, I decided to give it a shot (also due to the coaxing of some friends),
and, boy, was I surprised at the results. The whole setup of having a remote gaming server running Parsec
while another (local) machine played the games on it works just as well as described.&lt;/p&gt;
&lt;p&gt;So here we are. I thought I&#39;d share how I did my setup on &lt;a href=&quot;https://aws.amazon.com/&quot;&gt;AWS&lt;/a&gt;, as well as throw in some recommendations
on how to maximize your fun, while minimizing your costs as you do it.&lt;/p&gt;
&lt;p&gt;If you wanna skip all the auxiliary details and go straight to the build,
just click any of these links to jump straight to it.&lt;/p&gt;
&lt;!-- &lt;TOC links={[
[&#39;Create a Windows-based EC2 instance&#39;, &#39;#create-a-windows-based-ec2-instance&#39;],
[&#39;Allocate an Elastic IP&#39;, &#39;#allocate-an-elastic-ip&#39;],
[&#39;Create an IAM user for the Parsec installation&#39;, &#39;#create-an-iam-user-for-the-parsec-installation&#39;],
[&#39;Prepare your EC2 instance&#39;, &#39;#prepare-your-ec2-instance&#39;],
[&#39;Install Parsec&#39;, &#39;#install-parsec&#39;],
[&#39;Finishing up&#39;, &#39;#other-stuff-to-take-care-of&#39;]
]} /&gt; --&gt;
&lt;h2 id=&quot;parsec%3F&quot; tabindex=&quot;-1&quot;&gt;Parsec?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://parsecgaming.com/&quot;&gt;Parsec&lt;/a&gt; is a bit of software that works very similarly to how &lt;a href=&quot;https://en.wikipedia.org/wiki/Remote_Desktop_Protocol&quot;&gt;Remote Desktop&lt;/a&gt;
works for something like a Windows machine. The idea is that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have a (beefy) machine where your games are installed (called the &lt;strong&gt;host&lt;/strong&gt;),&lt;/li&gt;
&lt;li&gt;You have another (less powerful) machine where you want to play your games (called the &lt;strong&gt;client&lt;/strong&gt;),&lt;/li&gt;
&lt;li&gt;Parsec handles the connection between the two so that you can run your games on the
stronger machine, while you&#39;re controlling your game on the weaker one.
Parsec uses networking magic to reduce the lag by a significant amount, while maintaining
great graphics quality.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;fluid&quot;&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/iGF-bzdJ6p-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/iGF-bzdJ6p-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/iGF-bzdJ6p-880.png&quot; width=&quot;880&quot; height=&quot;462&quot; alt=&quot;Parsec architecture on AWS&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;How Parsec works&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;h2 id=&quot;costs&quot; tabindex=&quot;-1&quot;&gt;Costs&lt;/h2&gt;
&lt;p&gt;I opted for a Windows-based &lt;code&gt;g4dn.xlarge&lt;/code&gt; EC2 instance in the AWS Hong Kong region.
As a spot instance, it&#39;s currently costing me &lt;strong&gt;USD 0.427&lt;/strong&gt; per hour it&#39;s turned on,
which is about a 55% discount on the on-demand price.
Since this is only turned on when I&#39;m actually playing, that&#39;s not a bad deal at all.&lt;/p&gt;
&lt;p&gt;Your Parsec server will also need storage --- I opted to stick with the General Purpose SSDs.
This &lt;strong&gt;will&lt;/strong&gt; cost you even when the server is turned off, so keep this in mind.
You will need enough storage to install the games you want to play, but you can always
install / uninstall games over time to minimize this.
In the AWS HK region, I&#39;m getting this at about &lt;strong&gt;USD 0.132&lt;/strong&gt; per GB per month.&lt;/p&gt;
&lt;p&gt;There are also networking costs involved (since Parsec is streaming your game video out),
but I haven&#39;t metered and done the math there yet.&lt;/p&gt;
&lt;p&gt;So assuming I&#39;m playing an hour every day for a month, with a 100 GB machine,
my playtime is coming up to around &lt;strong&gt;USD 0.867&lt;/strong&gt; for every hour of play.&lt;/p&gt;
&lt;p&gt;Not bad.&lt;/p&gt;
&lt;h2 id=&quot;but-will-it-run-crysis%3F&quot; tabindex=&quot;-1&quot;&gt;But will it run Crysis?&lt;/h2&gt;
&lt;p&gt;Of course, as is with any gaming rig, it &lt;em&gt;has&lt;/em&gt; to run &lt;a href=&quot;https://en.wikipedia.org/wiki/Crysis_2&quot;&gt;Crysis&lt;/a&gt;, right?
No way was I not gonna try that out.&lt;/p&gt;
&lt;div&gt;
      &lt;div className=&quot;video-player&quot; style=&quot;position: relative; height: 0; padding-bottom: 56.25%; padding-top: 0;&quot;&gt;
        &lt;iframe src=&quot;https://youtube.com/embed/mnVfDD4n_MM&quot; frameBorder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowFullScreen=&quot;&quot; style=&quot;position: absolute; top: 0; left: 0; width: 100%; height: 100%;&quot;&gt;&lt;/iframe&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;p&gt;This Parsec host was running from the AWS Hong Kong region, and was being streamed to a local laptop
here in the Philippines, with a reported ping of around 60-80 ms.
Assuming we&#39;re hitting 60 FPS, that comes to around being 4-5 frames behind (probably double that,
since our control input has to go back round to the host server too).
Even at that level of lag, though, the game was pretty much playable, and the graphics, while
not the best, was extremely better than I expected.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;let&#39;s-get-started&quot; tabindex=&quot;-1&quot;&gt;Let&#39;s get started&lt;/h2&gt;
&lt;p&gt;While Parsec will need to be installed on both the host and client machines,
the host &lt;a href=&quot;https://support.parsecgaming.com/hc/en-us/articles/115002699192-Hardware-Requirements-For-Hosting-A-Gaming-Session-On-Parsec&quot;&gt;will need to be a Windows machine&lt;/a&gt; to function correctly.&lt;/p&gt;
&lt;p&gt;You can also review the &lt;a href=&quot;https://support.parsecgaming.com/hc/en-us/articles/115002702391-Hardware-Requirements-For-Connecting-To-Another-Computer-With-Parsec&quot;&gt;hardware requirements for the client machine here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;create-a-windows-based-ec2-instance&quot; tabindex=&quot;-1&quot;&gt;Create a Windows-based EC2 instance&lt;/h3&gt;
&lt;p&gt;Unlike what the &lt;a href=&quot;https://support.parsecgaming.com/hc/en-us/articles/115002701631-Required-Dependencies-To-Set-Up-Your-Own-Cloud-Gaming-PC-Without-Parsec-Templates&quot;&gt;Parsec documentation says&lt;/a&gt;,
we&#39;ll use the new &lt;mark&gt;g4dn.xlarge&lt;/mark&gt; instance type on Amazon EC2.
This is the newest generation of the G-type of EC2 instances, and comes with
4 vcpus, 16 GB of RAM, 16 GB of VRAM, and a Nvidia T4 Tensor Core video card.
It&#39;s also signficantly cheaper than the older generations, even though it looks to be
much more powerful. We&#39;ll also use &lt;a href=&quot;https://aws.amazon.com/ec2/spot/&quot;&gt;Spot Instances&lt;/a&gt; to lower our costs a huge deal.&lt;/p&gt;
&lt;aside&gt;&lt;header&gt;Spot Instances?&lt;/header&gt;
AWS lets you create spot instances on Amazon EC2 instead of the normal on-demand type.
I like to think of this as like an auction, and is a very effective way to get EC2 instances on the cheap.
&lt;p&gt;When launching spot instances, you specify how much you&#39;re willing to pay for your EC2 instances
(called the &lt;strong&gt;bid price&lt;/strong&gt;). AWS pretty much always has extra machines in their data centers,
so this excess is auctioned out at discounted prices as spot instances. The price of these
extra machines changes in real time, based on availability and demand (called the &lt;strong&gt;spot price&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;When launching spot instances, you get your EC2 instances if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;there is excess capacity, and&lt;/li&gt;
&lt;li&gt;your bid price is higher than the current spot price.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No matter what happens, you will only ever pay the current spot price for your instance
(not your bid price).&lt;/p&gt;
&lt;p&gt;The catch here is, if ever the spot price goes above your bid price, or if for some reason
there isn&#39;t enough extra capacity anymore, your EC2 instance will be terminated and
reclaimed. If that sounds like something you&#39;d prefer to avoid, then you might want
to use normal on-demand instances instead.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Go to your &lt;a href=&quot;https://console.aws.amazon.com/ec2&quot;&gt;Amazon EC2 dashboard&lt;/a&gt;, and launch a new instance.
Make sure you&#39;re in the AWS region that makes most sense from where you&#39;ll be playing.
(I&#39;m in the Philippines right now, so I opted for the Hong Kong region.)&lt;/p&gt;
&lt;h4 id=&quot;step-1%3A&quot; tabindex=&quot;-1&quot;&gt;Step 1:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Select &lt;strong&gt;Windows Server 2019 Base&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;step-2%3A&quot; tabindex=&quot;-1&quot;&gt;Step 2:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Find and select &lt;strong&gt;g4dn.xlarge&lt;/strong&gt;.
This would be under the &lt;em&gt;GPU instances&lt;/em&gt; category.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;step-3%3A&quot; tabindex=&quot;-1&quot;&gt;Step 3:&lt;/h4&gt;
&lt;p&gt;This will be the most involved step. Hang in there.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Purchasing options:&lt;/strong&gt; opt for Spot Instances.
Put in your bid price (I normally set bid price to be the normal on-demand price of the instance).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Persistent request:&lt;/strong&gt; you&#39;ll want this checked, because it allows you to configure ...&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Interruption behavior:&lt;/strong&gt; set this to &lt;mark&gt;Stop&lt;/mark&gt;.
This allows you to stop your Parsec server whenever you&#39;re not actually playing,
saving you more money.&lt;/p&gt;
&lt;p&gt;If you don&#39;t do this, you can only terminate your server if you want to turn it off,
and that&#39;s probably not something you want.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those are really the most important settings here, but feel free to change stuff up
if you want to (like, say, the subnets wherein the instance launches).&lt;/p&gt;
&lt;h4 id=&quot;step-4%3A&quot; tabindex=&quot;-1&quot;&gt;Step 4:&lt;/h4&gt;
&lt;p&gt;This is just about storage --- you&#39;ll want to put in as much storage as you think you&#39;ll need here.
Keep in mind though that storage will continue to cost you &lt;strong&gt;even when your machine is stopped&lt;/strong&gt;,
so give this a good thought.&lt;/p&gt;
&lt;p&gt;You&#39;ll also probably want to leave this at the &lt;strong&gt;General Purpose (gp2)&lt;/strong&gt; setting.&lt;/p&gt;
&lt;p&gt;You also can set up a small &lt;strong&gt;gp2&lt;/strong&gt; SSD as your root volume, then go nuts with a second
storage disk set to &lt;strong&gt;Throughput Optimized (st1)&lt;/strong&gt; if you plan on using it as the default
install location for your games. (For example, Steam allows you to do this.) Setting it up
this way will lead to cheaper storage costs (&lt;strong&gt;st1&lt;/strong&gt; costs about half of &lt;strong&gt;gp2&lt;/strong&gt;).&lt;/p&gt;
&lt;h4 id=&quot;step-5%3A&quot; tabindex=&quot;-1&quot;&gt;Step 5:&lt;/h4&gt;
&lt;p&gt;You don&#39;t have to change anything here, but it&#39;s always a good idea to at least
put in a &lt;strong&gt;Name&lt;/strong&gt; tag for your server, just so you can easily find it later.&lt;/p&gt;
&lt;h4 id=&quot;step-6%3A&quot; tabindex=&quot;-1&quot;&gt;Step 6:&lt;/h4&gt;
&lt;p&gt;This step is crucial, because this is where you define what networking connections are allowed
to and fro your machine.&lt;/p&gt;
&lt;p&gt;At the very least, you&#39;ll want to open the following &lt;strong&gt;inbound&lt;/strong&gt; rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;(RDP) TCP/3389&lt;/strong&gt; from Anywhere&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UDP/8000-8200&lt;/strong&gt; from Anywhere&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UDP/9000-9200&lt;/strong&gt; from Anywhere&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TCP/5900&lt;/strong&gt; from Anywhere — &lt;mark&gt;only if you want to access your machine via VNC&lt;/mark&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;fluid&quot;&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/LXgfNgmupu-880.png 880w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/LXgfNgmupu-880.webp 880w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/LXgfNgmupu-880.png&quot; width=&quot;880&quot; height=&quot;462&quot; alt=&quot;Parsec Host Server security group&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;If you know the IP addresses where you&#39;ll be connecting from, you&#39;ll want to change those here too.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;aside&gt;&lt;header&gt;Important&lt;/header&gt;
Note that your are opening inbound ports on your machine,
accessible from **anywhere**. If you kow your exact client 
IP address/es, you should put them in here instead of 
allowing connections from anywhere in the world.
&lt;/aside&gt;
&lt;h4 id=&quot;step-7%3A&quot; tabindex=&quot;-1&quot;&gt;Step 7:&lt;/h4&gt;
&lt;p&gt;Confirm your settings, here and proceed with launching.&lt;/p&gt;
&lt;p&gt;When you launch, you will be prompted for a &lt;strong&gt;key pair&lt;/strong&gt;.
Opt to &lt;mark&gt;Create a new key pair&lt;/mark&gt;, download it, and keep it somewhere safe.&lt;/p&gt;
&lt;p&gt;Your machine will take a couple of minutes to launch, but we don&#39;t have to wait
for it to finish to do the next step.&lt;/p&gt;
&lt;h3 id=&quot;allocate-an-elastic-ip&quot; tabindex=&quot;-1&quot;&gt;Allocate an Elastic IP&lt;/h3&gt;
&lt;p&gt;Your Parsec host server will need to have a public IPv4 address that doesn&#39;t change.
To do this, we&#39;ll allocate an &lt;strong&gt;Elastic IP&lt;/strong&gt; address on our AWS account, and assign it
to the instance we just launched.&lt;/p&gt;
&lt;p&gt;Still on your EC2 dashboard, go to &lt;strong&gt;Elastic IPs&lt;/strong&gt; on the left sidebar,
and click &lt;strong&gt;Allocate Elastic IP address&lt;/strong&gt; on the top of the screen.
Just accept the defaults, and confirm.&lt;/p&gt;
&lt;p&gt;Once you&#39;re back on the dashboard, select the Elastic IP you just created, then
select &lt;strong&gt;Actions &amp;gt; Associate Elastic IP address&lt;/strong&gt;.
On the form that pops up, select the instance you just created, and
select its private IP address as well (there should only be one anyway).
Confirm that, and you&#39;re done.&lt;/p&gt;
&lt;h3 id=&quot;create-an-iam-user-for-the-parsec-installation&quot; tabindex=&quot;-1&quot;&gt;Create an IAM user for the Parsec installation&lt;/h3&gt;
&lt;p&gt;During the installation process for Parsec, it will need an IAM user
to download GRID drivers to augment the video card. Let&#39;s prepare this now.&lt;/p&gt;
&lt;p&gt;Go to your &lt;a href=&quot;https://console.aws.amazon.com/iam&quot;&gt;IAM dashboard&lt;/a&gt;, select &lt;strong&gt;Users&lt;/strong&gt;, and then select
&lt;strong&gt;Add user&lt;/strong&gt;. Give your user a name (something like &lt;mark&gt;parsec-host-server&lt;/mark&gt; works great),
and check &lt;strong&gt;Programmatic Access&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;On the next screen, opt to &lt;strong&gt;Attach existing policies directly&lt;/strong&gt;,
then attach the &lt;mark&gt;AdministratorAccess&lt;/mark&gt; policy. Complete the user creation.&lt;/p&gt;
&lt;p&gt;Once the user is created, you&#39;ll be given the user&#39;s &lt;strong&gt;Access Key&lt;/strong&gt; and
&lt;strong&gt;Secret Access Key&lt;/strong&gt;. Store this somewhere safe for now --- we&#39;ll use it in a bit.&lt;/p&gt;
&lt;h3 id=&quot;prepare-your-ec2-instance&quot; tabindex=&quot;-1&quot;&gt;Prepare your EC2 instance&lt;/h3&gt;
&lt;p&gt;By now the EC2 instance you created should have finished launching already.
You will then need to connect to it via &lt;strong&gt;Remote Desktop&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Select your EC2 instance, then click &lt;strong&gt;Connect&lt;/strong&gt; at the top.
Follow the instructions for a Remote Desktop connection.&lt;/p&gt;
&lt;p&gt;Once you&#39;re connected, you&#39;ll want to do a few things before you actually
start installing Parsec on the machine.&lt;/p&gt;
&lt;h4 id=&quot;change-the-administrator-password&quot; tabindex=&quot;-1&quot;&gt;Change the Administrator password&lt;/h4&gt;
&lt;p&gt;You&#39;ll probably want to change the password to something saner.
Totally optional, but I prefer changing the password to something else so that
the key pair we generated for the machine effectively becomes useless too.&lt;/p&gt;
&lt;h4 id=&quot;disable-ctrl%2Balt%2Bdelete-to-unlock&quot; tabindex=&quot;-1&quot;&gt;Disable CTRL+ALT+DELETE to unlock&lt;/h4&gt;
&lt;p&gt;By default, Windows is set up to require you to press CTRL+ALT+DELETE whenever you
unlock the machine --- once Parsec is set up (and we&#39;re not using RDP anymore), we won&#39;t
be able to do that, so we&#39;ll need to disable this (so that Parsec can also autologin for us).&lt;/p&gt;
&lt;p&gt;From your Start Menu, search for &lt;strong&gt;Local Security Policy&lt;/strong&gt;.
Navigate to &lt;strong&gt;Local Policies &amp;gt; Security Options&lt;/strong&gt;, and change the
&lt;strong&gt;Interactive Logon: Do not require CTRL + ALT + DEL&lt;/strong&gt; option to &lt;mark&gt;Enable&lt;/mark&gt;.&lt;/p&gt;
&lt;h3 id=&quot;install-parsec&quot; tabindex=&quot;-1&quot;&gt;Install Parsec&lt;/h3&gt;
&lt;p&gt;Here we go. Open up Powershell from your Start Menu, then paste in the following commands.
(Alternatively, go to the &lt;a href=&quot;https://github.com/jamesstringerparsec/Parsec-Cloud-Preparation-Tool&quot;&gt;script&#39;s Github repo&lt;/a&gt;, and grab the one there.)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell/1-3,4&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;[Net.ServicePointManager]&lt;/span&gt;::SecurityProtocol = &lt;span class=&quot;token string&quot;&gt;&quot;tls12, tls11, tls&quot;&lt;/span&gt;&lt;/span&gt;
&lt;mark class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;New-Object&lt;/span&gt; System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Net&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WebClient&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DownloadFile&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://github.com/jamesstringerparsec/Parsec-Cloud-Preparation-Tool/archive/master.zip&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$ENV&lt;/span&gt;:UserProfile&#92;Downloads&#92;Parsec-Cloud-Preparation-Tool.zip&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/mark&gt;
&lt;mark class=&quot;code-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;New-Item&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token variable&quot;&gt;$ENV&lt;/span&gt;:UserProfile&#92;Downloads&#92;Parsec-Cloud-Preparation-Tool &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ItemType Directory&lt;/mark&gt;
&lt;mark class=&quot;code-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Expand-Archive&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$ENV&lt;/span&gt;:UserProfile&#92;Downloads&#92;Parsec-Cloud-Preparation-Tool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Zip &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;DestinationPath &lt;span class=&quot;token variable&quot;&gt;$ENV&lt;/span&gt;:UserProfile&#92;Downloads&#92;Parsec-Cloud-Preparation-Tool&lt;/mark&gt;
&lt;mark class=&quot;code-line&quot;&gt;CD &lt;span class=&quot;token variable&quot;&gt;$ENV&lt;/span&gt;:UserProfile&#92;Downloads&#92;Parsec-Cloud-Preparation-Tool&#92;Parsec-Cloud-Preparation-Tool-master&#92;&lt;/mark&gt;
&lt;span class=&quot;code-line&quot;&gt;Powershell&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exe &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;File &lt;span class=&quot;token variable&quot;&gt;$ENV&lt;/span&gt;:UserProfile&#92;Downloads&#92;Parsec-Cloud-Preparation-Tool&#92;Parsec-Cloud-Preparation-Tool-master&#92;Loader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ps1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell/1-3/4&quot;&gt;&lt;span class=&quot;code-line&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;[Net.ServicePointManager]&lt;/span&gt;::SecurityProtocol = &lt;span class=&quot;token string&quot;&gt;&quot;tls12, tls11, tls&quot;&lt;/span&gt;&lt;/span&gt;
&lt;ins class=&quot;code-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;New-Object&lt;/span&gt; System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Net&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WebClient&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DownloadFile&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://github.com/jamesstringerparsec/Parsec-Cloud-Preparation-Tool/archive/master.zip&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$ENV&lt;/span&gt;:UserProfile&#92;Downloads&#92;Parsec-Cloud-Preparation-Tool.zip&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/ins&gt;
&lt;ins class=&quot;code-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;New-Item&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token variable&quot;&gt;$ENV&lt;/span&gt;:UserProfile&#92;Downloads&#92;Parsec-Cloud-Preparation-Tool &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ItemType Directory&lt;/ins&gt;
&lt;ins class=&quot;code-line&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Expand-Archive&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$ENV&lt;/span&gt;:UserProfile&#92;Downloads&#92;Parsec-Cloud-Preparation-Tool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Zip &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;DestinationPath &lt;span class=&quot;token variable&quot;&gt;$ENV&lt;/span&gt;:UserProfile&#92;Downloads&#92;Parsec-Cloud-Preparation-Tool&lt;/ins&gt;
&lt;del class=&quot;code-line&quot;&gt;CD &lt;span class=&quot;token variable&quot;&gt;$ENV&lt;/span&gt;:UserProfile&#92;Downloads&#92;Parsec-Cloud-Preparation-Tool&#92;Parsec-Cloud-Preparation-Tool-master&#92;&lt;/del&gt;
&lt;span class=&quot;code-line&quot;&gt;Powershell&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exe &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;File &lt;span class=&quot;token variable&quot;&gt;$ENV&lt;/span&gt;:UserProfile&#92;Downloads&#92;Parsec-Cloud-Preparation-Tool&#92;Parsec-Cloud-Preparation-Tool-master&#92;Loader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ps1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This script will download the &lt;a href=&quot;https://github.com/jamesstringerparsec/Parsec-Cloud-Preparation-Tool&quot;&gt;Parsec preparation script by James Stringer&lt;/a&gt;,
and execute that in the Powershell context. It&#39;ll basically prepare most everything for you,
and you&#39;ll just need to watch it go to town.&lt;/p&gt;
&lt;p&gt;It will ask you a few things from time to time, so don&#39;t leave it for too long.&lt;/p&gt;
&lt;p&gt;Once Parsec has been installed, make sure you log into your Parsec account in it.&lt;/p&gt;
&lt;h4 id=&quot;parsec-configuration-changes&quot; tabindex=&quot;-1&quot;&gt;Parsec configuration changes&lt;/h4&gt;
&lt;p&gt;I had to make a few config tweaks on Parsec to make it work completely:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Host&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hosting Enabled&lt;/strong&gt;: Enabled (of course)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Host name&lt;/strong&gt;: Change this to something that makes more sense for you&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Network&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Client Port&lt;/strong&gt;: 9000&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Host Start Port&lt;/strong&gt;: 9100&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The install script above should have taken care of most everything else.&lt;/p&gt;
&lt;h3 id=&quot;other-stuff-to-take-care-of&quot; tabindex=&quot;-1&quot;&gt;Other stuff to take care of&lt;/h3&gt;
&lt;p&gt;The install script should&#39;ve taken care of installing Xbox software (for gamepad compatibility),
and Razer software (for audio). To complete the setup, we&#39;ll need to do a few more things.&lt;/p&gt;
&lt;h4 id=&quot;set-up-auto-logon&quot; tabindex=&quot;-1&quot;&gt;Set up Auto Logon&lt;/h4&gt;
&lt;p&gt;On your desktop should be an &lt;strong&gt;Auto Logon&lt;/strong&gt; folder.
Open it and run the script inside. Type in your user name as &lt;mark&gt;Administrator&lt;/mark&gt;,
and the computer password (you should&#39;ve changed this by now).
Confirm that auto logon has been completed successfully.&lt;/p&gt;
&lt;h4 id=&quot;run-gpu-auto-updater&quot; tabindex=&quot;-1&quot;&gt;Run GPU Auto-updater&lt;/h4&gt;
&lt;p&gt;Also on your desktop is a shortcut for the &lt;strong&gt;GPU Auto-updater&lt;/strong&gt; script.
This will install any other missing drivers / software required for your video card.&lt;/p&gt;
&lt;p&gt;Once you run this, it will prompt you for a few things --- just say yes when it asks
if it&#39;s OK to reboot. This will also ask for IAM user credentials --- put in the IAM user
access key and secret access key we prepared earlier here.&lt;/p&gt;
&lt;p&gt;Your machine will need to reboot once or twice to complete this step.
When the machine reboots, just connect to it again via RDP, and run the GPU Auto-updater tool again.
It will tell you if the installation is complete when you do.&lt;/p&gt;
&lt;h4 id=&quot;set-up-auto-shutdown&quot; tabindex=&quot;-1&quot;&gt;Set up auto-shutdown&lt;/h4&gt;
&lt;p&gt;Also installed by the script earlier is a script that sets up automatic shutdown for the machine
when it has been idle for a while. Consider running this so that the EC2 instance stops
when you haven&#39;t been using it (and saves you money).&lt;/p&gt;
&lt;p&gt;I&#39;ve personally set mine to auto-shutdown after 20 minutes of inactivity.&lt;/p&gt;
&lt;h3 id=&quot;confirm-that-you-can-connect-via-parsec&quot; tabindex=&quot;-1&quot;&gt;Confirm that you can connect via Parsec&lt;/h3&gt;
&lt;p&gt;Make sure your remote desktop session is disconnected at this point.&lt;/p&gt;
&lt;p&gt;From &lt;mark&gt;another machine&lt;/mark&gt;, install Parsec (not using the install script this time).
Log into your Parsec account, and just confirm that you can connect to your Parsec host server.&lt;/p&gt;
&lt;p&gt;Once you&#39;re connected, Parsec will function just like Remote Desktop earlier, but it
will generally feel more fluid and faster.&lt;/p&gt;
&lt;p&gt;At this point, you can then install your games (e.g. Steam), and your gaming apps
(e.g. Discord), and use Parsec to play remotely.&lt;/p&gt;
&lt;h2 id=&quot;cleanup&quot; tabindex=&quot;-1&quot;&gt;Cleanup&lt;/h2&gt;
&lt;p&gt;There&#39;s a bunch of things you&#39;ll want to do once you&#39;ve confirmed that you can connect via Parsec.&lt;/p&gt;
&lt;h3 id=&quot;close-down-your-rdp-and-vnc-ports-in-your-security-group&quot; tabindex=&quot;-1&quot;&gt;Close down your RDP and VNC ports in your security group&lt;/h3&gt;
&lt;p&gt;Remember the security group we created earlier for our EC2 instance?&lt;/p&gt;
&lt;p&gt;Once you&#39;ve set up your Parsec connection, you won&#39;t need to remote desktop and/or use VNC anymore
(and even if you do, you can always just open the ports again).&lt;/p&gt;
&lt;p&gt;So you&#39;ll want to modify the security group to remove the RDP and VNC ports to make your machine
a bit more secure.&lt;/p&gt;
&lt;h3 id=&quot;remove-the-access-credential-from-your-iam-user&quot; tabindex=&quot;-1&quot;&gt;Remove the access credential from your IAM user&lt;/h3&gt;
&lt;p&gt;Also at this point, your EC2 instance won&#39;t need the IAM user you created earlier anymore.&lt;/p&gt;
&lt;p&gt;To ensure it can never be used again (remember: this has Administrator permissions!),
you can either delete the IAM user itself, or revoke the access credentials you&#39;ve given it
in IAM.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;outcome&quot; tabindex=&quot;-1&quot;&gt;Outcome&lt;/h2&gt;
&lt;p&gt;Your latency will greatly depend on which AWS region you set up your machine,
and where you&#39;re playing from. I set up Parsec hosts in the Singapore, Tokyo, and Hong Kong regions
in AWS, with best results streaming to the Philippines from Hong Kong.&lt;/p&gt;
&lt;p&gt;It also seems like the lag differs from game to game as well, even if the network latency is the same.
I found that the gameplay experience of, for example, Crysis 2 and Witcher 3 were really good,
but Borderlands 3 was very difficult to play --- even if the latency across all three games were the same.&lt;/p&gt;
&lt;p&gt;Here are some more sample videos:&lt;/p&gt;
&lt;h3 id=&quot;dota-2&quot; tabindex=&quot;-1&quot;&gt;Dota 2&lt;/h3&gt;
&lt;div&gt;
      &lt;div className=&quot;video-player&quot; style=&quot;position: relative; height: 0; padding-bottom: 56.25%; padding-top: 0;&quot;&gt;
        &lt;iframe src=&quot;https://youtube.com/embed/J7Z5p1Rtq_s&quot; frameBorder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowFullScreen=&quot;&quot; style=&quot;position: absolute; top: 0; left: 0; width: 100%; height: 100%;&quot;&gt;&lt;/iframe&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;h3 id=&quot;witcher-3&quot; tabindex=&quot;-1&quot;&gt;Witcher 3&lt;/h3&gt;
&lt;div&gt;
      &lt;div className=&quot;video-player&quot; style=&quot;position: relative; height: 0; padding-bottom: 56.25%; padding-top: 0;&quot;&gt;
        &lt;iframe src=&quot;https://www.youtube.com/embed/qiFBnUbrkkY&quot; frameBorder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowFullScreen=&quot;&quot; style=&quot;position: absolute; top: 0; left: 0; width: 100%; height: 100%;&quot;&gt;&lt;/iframe&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;h3 id=&quot;borderlands-3&quot; tabindex=&quot;-1&quot;&gt;Borderlands 3&lt;/h3&gt;
&lt;div&gt;
      &lt;div className=&quot;video-player&quot; style=&quot;position: relative; height: 0; padding-bottom: 56.25%; padding-top: 0;&quot;&gt;
        &lt;iframe src=&quot;https://youtube.com/embed/_FirBl5_Tn8&quot; frameBorder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowFullScreen=&quot;&quot; style=&quot;position: absolute; top: 0; left: 0; width: 100%; height: 100%;&quot;&gt;&lt;/iframe&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;h3 id=&quot;subnautica&quot; tabindex=&quot;-1&quot;&gt;Subnautica&lt;/h3&gt;
&lt;div&gt;
      &lt;div className=&quot;video-player&quot; style=&quot;position: relative; height: 0; padding-bottom: 56.25%; padding-top: 0;&quot;&gt;
        &lt;iframe src=&quot;https://youtube.com/embed/gRb7tqMiT5g&quot; frameBorder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowFullScreen=&quot;&quot; style=&quot;position: absolute; top: 0; left: 0; width: 100%; height: 100%;&quot;&gt;&lt;/iframe&gt;
      &lt;/div&gt;
    &lt;/div&gt;
</content>
  </entry>
  
  
  <entry>
    <title>Setting up Microphone Audio Filters in OBS using Reaper plugins</title>
    <link href="https://richardneililagan.com/posts/set-up-microphone-audio-filters-obs/" />
    <updated>2020-04-13T00:00:00Z</updated>
    <id>https://richardneililagan.com/posts/set-up-microphone-audio-filters-obs/</id>
    <content type='html'>&lt;p&gt;In this time of global pandemic, not only are there more people jumping onto remote online meetings,
but there seems to be more people trying out livestreaming and recording videos for the benefit of others online.&lt;/p&gt;
&lt;p&gt;However, unlike most any other element you&#39;re including in your recording, audio from a microphone will tend to be very finicky.
There&#39;s a lot of factors that go into the quality of spoken voice --- the background noises from where you do your recordings,
the quality of your actual microphone, and even the nuances of your own voice.&lt;/p&gt;
&lt;p&gt;If you&#39;re using &lt;a href=&quot;https://obsproject.com/&quot;&gt;Open Broadcaster Software (OBS)&lt;/a&gt; to record, I&#39;d like to share a handful of things you can do beforehand
to help ensure your voice sounds cleaner, crisper, and much more professional.&lt;/p&gt;
&lt;aside&gt;
&lt;header&gt;Before all that though,&lt;/header&gt;
&lt;p&gt;Admittedly, I&#39;m definitely no audio engineer.
I&#39;ve looked into setting up a livestreaming setup a few years back for my own video games, and this is the equivalent of me
pulling out my old (handwritten!) notes on the topic and sharing them out (and effectively digitizing them as well).
I just wish I still had info on where I got most of my notes from for attribution.&lt;/p&gt;
&lt;p&gt;I&#39;m pretty sure someone who knows more about the subject than I do is bound to chime in and point me to better ways of doing these,
and that&#39;s alright. If you&#39;ve got better ideas, please do share.&lt;/p&gt;
&lt;/aside&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;obs-and-reaper&quot; tabindex=&quot;-1&quot;&gt;OBS and Reaper&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://obsproject.com/&quot;&gt;&lt;abbr title=&quot;Open Broadcaster Software&quot;&gt;OBS&lt;/abbr&gt;&lt;/a&gt; is free and open-source software for video recording and livestreaming.
It&#39;s very powerful, and while it&#39;s not the simplest tool if you just want to get set up and running quickly,
it&#39;s relatively easy to use considering what you can get out of it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://reaper.fm/&quot;&gt;Reaper&lt;/a&gt; is an impressive software suite for working with digital audio.
The suite costs &lt;strong&gt;USD 60.00&lt;/strong&gt; for a personal license (which a lot of my audiophile colleagues say is worth the investment).
It&#39;s traditionally set up so that the audio from your microphone flows into Reaper, wherein you can set up a pipeline of audio filters,
which eventually is then output into wherever it needs to go (normally, your recording).
That&#39;s &lt;em&gt;not&lt;/em&gt; what we&#39;re doing today (although it&#39;s an interesting setup to learn about as well).&lt;/p&gt;
&lt;p&gt;Fortunately, a good amount of the audio filters that Reaper uses is available as a set of VST plugins for absolutely free.
Even better, OBS also allows you to bring in third-party VST plugins to supplement and extend what you already get out of the box.
So what we&#39;ll eventually end up building is a pipeline of Reaper audio filters that clean up your microphone audio before it goes into
your stream or recording, all within the OBS software interface. Spiffy.&lt;/p&gt;
&lt;p&gt;The only caveat is that the Reaper VST plugins are only available on Windows platforms, so you&#39;ll only be able to do this on a Windows machine.&lt;/p&gt;
&lt;h2 id=&quot;building-the-setup&quot; tabindex=&quot;-1&quot;&gt;Building the setup&lt;/h2&gt;
&lt;p&gt;You&#39;ll need to have OBS already installed on your machine --- we&#39;re assuming you already have that.&lt;/p&gt;
&lt;h3 id=&quot;download-reaplugs&quot; tabindex=&quot;-1&quot;&gt;Download ReaPlugs&lt;/h3&gt;
&lt;p&gt;The Reaper VST plugins are collectively called &lt;strong&gt;ReaPlugs&lt;/strong&gt;, and you can &lt;a href=&quot;https://reaper.fm/reaplugs&quot;&gt;download the installer for your system here&lt;/a&gt;.
Make sure you&#39;re downloading the correct executable for your computer (which is probably going to be the 64-bit version).&lt;/p&gt;
&lt;p&gt;Conveniently, when you run the installer, it should automatically register itself into your machine so that OBS can discover it for the next steps.&lt;/p&gt;
&lt;h3 id=&quot;set-our-microphone-input-to-a-comfortable-pickup-level&quot; tabindex=&quot;-1&quot;&gt;Set our microphone input to a comfortable pickup level&lt;/h3&gt;
&lt;p&gt;If you haven&#39;t configured your mic yet to a default working setup yet, now is a good time to do so.
This is important because each physical microphone model behaves and picks up sound differently, and all the next steps will build on top of how
your specific microphone performs.&lt;/p&gt;
&lt;p&gt;If your microphone has a physical volume knob, it&#39;s oftentimes best to set the mic to 100% volume &lt;strong&gt;in Windows&lt;/strong&gt;, and only adjust the physical volume knob
whenever you need to. This minimizes the potential distortion that your computer can introduce to the sound.&lt;/p&gt;
&lt;p&gt;If your microphone does not have a physical volume knob, open your mic&#39;s settings on your computer, and initially set it at 100% with max microphone boost.
Continuously speak into your microphone in about as loud a voice as you&#39;d expect yourself to be doing while recording (louder than normal), and adjust the levels so that
the audio meter shows the sound &lt;em&gt;almost&lt;/em&gt; going to the very top on average, but not all the way. Whenever the audio meter goes to the max, there is a real
possibility of the audio signal being clipped, and that&#39;s not something we want.&lt;/p&gt;
&lt;p&gt;Once you&#39;ve set your initial microphone pickup levels, you generally won&#39;t want to touch those again.&lt;/p&gt;
&lt;h3 id=&quot;set-up-the-audio-filters-in-obs&quot; tabindex=&quot;-1&quot;&gt;Set up the audio filters in OBS&lt;/h3&gt;
&lt;p&gt;In OBS, you&#39;ll ideally have your microphone already set up as an input audio device.
If you right click on your microphone source, and select &lt;strong&gt;Filters&lt;/strong&gt;, you can add in audio filters that OBS will run your signal through during a recording.&lt;/p&gt;
&lt;p&gt;Since the filters are evaluated in order (from top to bottom), I&#39;ll talk about these in order as well.&lt;/p&gt;
&lt;h4 id=&quot;remove-ambient-%2F-background-noise&quot; tabindex=&quot;-1&quot;&gt;Remove ambient / background noise&lt;/h4&gt;
&lt;p&gt;Unless you&#39;re working in an effectively soundproofed environment, you&#39;re bound to have some background noise where you&#39;re recording.
Could be the ongoing hum of your computer, or your room&#39;s fan / airconditioning, or the general ambient wind noise from outside.
What we want to do before everything else is to try to cancel out all that background noise.
Thankfully, that&#39;s easy to do with the &lt;strong&gt;Reafir&lt;/strong&gt; filter, and also one of the easiest filters to set up.&lt;/p&gt;
&lt;p&gt;Add a VST plugin into your filters, and select the Reafir plugin on the right side of the OBS panel. Afterwards, go into the plugin settings.&lt;/p&gt;
&lt;p&gt;In the settings panel, set the mode to &lt;strong&gt;Subtract&lt;/strong&gt; (since we&#39;re essentially removing sound),
then click on &lt;strong&gt;Automatically build noise profile&lt;/strong&gt; --- leave it on for 3-5 seconds, then turn it back off.
Don&#39;t talk or make any noise during this time.&lt;/p&gt;
&lt;p&gt;During that time, the plugin will listen on your audio signal, and automatically determine the audio frequencies (and at what levels) it will need
to adjust output audio signal to. Since it&#39;s set to subtract mode, we&#39;re essentially telling the filter to remove the audio it heard during those 3-5 seconds
from any audio signal that goes through it in the future.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/lPD37bmmw3-614.png 614w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/lPD37bmmw3-614.webp 614w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/lPD37bmmw3-614.png&quot; width=&quot;614&quot; height=&quot;442&quot; alt=&quot;Reafir plugin configuration&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;My Reafir plugin configuration after building my personal noise profile&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;h4 id=&quot;remove-everything-else-aside-from-your-voice-(ideally)&quot; tabindex=&quot;-1&quot;&gt;Remove everything else aside from your voice (ideally)&lt;/h4&gt;
&lt;p&gt;The above will work wonderfully for static noise, but for things like keyboard clacking, random noises, accidentally
clicking your tongue, etc. it&#39;s not going to do a lot. Since ideally we&#39;d only want our own voice to carry through
to the recording, we also need to proactively remove most other arbitrary sounds.
This is what a &lt;strong&gt;noise gate&lt;/strong&gt; is normally for.&lt;/p&gt;
&lt;p&gt;A noise gate works by continuously listening in on a specific volume threshold --- if the audio is within this threshold
continuously for a set amount of time (generally what happens when we talk), then the noise gate will open, and let the
audio signal through. Otherwise, the noise gate prevents any audio signal from getting through at all.
It&#39;s a bit of an all or nothing.&lt;/p&gt;
&lt;p&gt;So add another VST plugin into your filters, and set it to a Reaper noise gate.
Again, just jump into its configuration.&lt;/p&gt;
&lt;p&gt;There&#39;s a bunch of stuff here that we&#39;ll need to tweak. So here goes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;To the left is a vertical volume &lt;strong&gt;threshold&lt;/strong&gt; meter.
Speak into your microphone continuously in your most normal cadence, volume, and tone ---
how you&#39;ll be speaking most of the time during your recording.&lt;/p&gt;
&lt;p&gt;The green meter will bob up and down as you speak, so you&#39;ll want to bring the slider &lt;strong&gt;down&lt;/strong&gt;
so that it&#39;s just below of the general peak area of the green meter when you&#39;re speaking normally.&lt;/p&gt;
&lt;p&gt;You&#39;re essentially telling the noise gate to consider only audio that go above that volume threshold level
and discard anything lower.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pre-open&lt;/strong&gt; is how long the audio has to be (continuously) above the threshold level before the noise gate allows audio through.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Attack&lt;/strong&gt; is how quickly the gate opens when it determines that it needs to allow audio through.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hold&lt;/strong&gt; is the opposite of pre-open --- once the noise gate is open, this determines how long the audio has to be
(continuously) below the threshold before it closes back again.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Release&lt;/strong&gt; is the opposite of attack --- how quickly does the gate close when it needs to.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hysteresis&lt;/strong&gt; turns your threshold from (1) above into a range instead of just a single number.
To vastly oversimplify things, if the noise gate only used a single threshold number for both deciding when to open and when to close,
the audio tends to have a lot of chattering.&lt;/p&gt;
&lt;p&gt;Your hysteresis setting tells your noise gate what your &lt;strong&gt;closing&lt;/strong&gt; threshold is (as you want this lower than your opening threshold).
If you have a threshold of -20 dB, and a hysteresis of -5 dB, then your noise gate uses &lt;strong&gt;-20 dB&lt;/strong&gt; to determine when to open,
and &lt;strong&gt;-25 dB&lt;/strong&gt; to determine when to close.&lt;/p&gt;
&lt;p&gt;I&#39;ve read that a value of -6 to -8 dB is a good starting value for this.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lowpass&lt;/strong&gt; and &lt;strong&gt;highpass&lt;/strong&gt; tells the noise gate what range of audio frequencies to consider for deciding when to open and close.
As you can imagine, this works hand-in-hand with your threshold setting above.&lt;/p&gt;
&lt;p&gt;I&#39;ve read that 85-255 Hz (that is, a lowpass of 255, and a highpass of 85) is a really good average for male and female voices,
but you&#39;ll definitely want to tweak this to better match your own vocal tone.&lt;/p&gt;
&lt;p&gt;Setting this correctly is going to very significantly affect how much benefit you get from your noise gate,
because it&#39;ll be better at pinpointing your voice from other noises (instead of just relying on how loud your voice is).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/bl-NWqU9ux-496.png 496w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/bl-NWqU9ux-496.webp 496w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/bl-NWqU9ux-496.png&quot; width=&quot;496&quot; height=&quot;355&quot; alt=&quot;Reaper noise gate configuration&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Your noise gate config will vary based on your own vocal nuances.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;h4 id=&quot;equalizing&quot; tabindex=&quot;-1&quot;&gt;Equalizing&lt;/h4&gt;
&lt;p&gt;I personally think an equalizer is the hardest thing to tweak in this whole set, largely because there&#39;s so many factors
that go into it, and that it&#39;s the filter that is most dependent on the nature of your own voice.&lt;/p&gt;
&lt;p&gt;I&#39;ve received the following recommendations that I&#39;ve used for my own setup, which only use 2 bands:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Band (1) is a &lt;strong&gt;High Shelf&lt;/strong&gt; filter, meant to give a bit more oomph to the pops in your own speech (like consonants or stops).
Set this to around 3000 Hz first with a bandwidth of 3 octaves, and a gain of around 10 dB,
then adjust based on how your own voice sounds in recordings.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Band (2) is a &lt;strong&gt;High Pass&lt;/strong&gt; filter, meant to cut some of the bass in your voice.
Set this to around 60-80 Hz, and play around with it to match your preference.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Obviously, you can do more with this, but I honestly don&#39;t know enough about it to tell you more.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/urTlG348p3-529.png 529w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/urTlG348p3-529.webp 529w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/urTlG348p3-529.png&quot; width=&quot;529&quot; height=&quot;385&quot; alt=&quot;Reaper equalizer configuration, band 1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/udcyf3E3Kg-529.png 529w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/udcyf3E3Kg-529.webp 529w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/udcyf3E3Kg-529.png&quot; width=&quot;529&quot; height=&quot;385&quot; alt=&quot;Reaper equalizer configuration, band 2&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Your equalizer settings are going to heavily depend on your own voice.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;h4 id=&quot;cut-out-sudden-loud-shouts&quot; tabindex=&quot;-1&quot;&gt;Cut out sudden loud shouts&lt;/h4&gt;
&lt;p&gt;Most especially if you&#39;re livestreaming video games like I do, you&#39;re bound to find yourself in situations that just
make you suddenly scream loudly, or shout at your teammates, or other similar things.
While this is all fun and exhilirating for the player, anyone watching wouldn&#39;t appreciate being suddenly shot
an earful of maxed-out vocals without a volume warning.&lt;/p&gt;
&lt;p&gt;This is what an audio compressor is for. A compressor will weaken the volume of loud sounds, as well as amplify the volume of quiet sounds,
so you kind of hit two birds with one stone --- it&#39;ll cut out the volume whenever you shout, but it will also make your own whispers
more pronounced and intelligible. Perfect when you&#39;re sneaking around a game!&lt;/p&gt;
&lt;p&gt;Throw in another VST plugin, and set to a compressor &lt;mark&gt;(not the compressorx!)&lt;/mark&gt;, and jump into the settings.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Attack&lt;/strong&gt; and &lt;strong&gt;release&lt;/strong&gt; work in pretty much the same ways as the ones for your noise gate above.
I&#39;ve been recommended 5 and 50 ms for these respectively as a starting point.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ratio&lt;/strong&gt; is an indication of how much the filter compresses the audio signal --- a 6.0:1 ratio should be a good start.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You&#39;ll probably want to set &lt;strong&gt;lowpass&lt;/strong&gt; and &lt;strong&gt;highpass&lt;/strong&gt; to the same settings you&#39;ve used for your noise gate too.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Threshold&lt;/strong&gt; will need a bit more TLC.
You&#39;ll want to speak continuously at a low volume --- around as soft as you&#39;d speak when you&#39;re trying to be quiet in your recordings ---
and set the threshold a little below that. If you&#39;re seeing that your voice is not registering at the threshold meter when you&#39;re
speaking softly, that&#39;s probably a good indication that you need to tweak your noise gate a bit more.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The goal here is to tweak the settings above so that when you&#39;re speaking --- all across soft, normal, and loud voices --- your output audio
is registering just below peak on average (probably around &lt;mark&gt;-6 to -12 dB&lt;/mark&gt; is good). You definitely don&#39;t want to have it go into
the positive, as this indicates audio clipping.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/a3Xgw8jXJz-496.png 496w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/a3Xgw8jXJz-496.webp 496w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/a3Xgw8jXJz-496.png&quot; width=&quot;496&quot; height=&quot;355&quot; alt=&quot;Reaper compressor configuration&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Make sure your audio output at the right does not go into the positive across your expected vocal range.&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;h4 id=&quot;plugging-overly-loud-sounds&quot; tabindex=&quot;-1&quot;&gt;Plugging overly loud sounds&lt;/h4&gt;
&lt;p&gt;Your compressor will do pretty much everything to keep your audio output within a certain acceptable volume ---
unless you happen to speak so overly loud that it just overwhelms the compressor.&lt;/p&gt;
&lt;p&gt;You can optionally put in a master limiter to your audio filter chain to prevent those (supposedly) very rare occassions.&lt;/p&gt;
&lt;p&gt;Again, throw in another VST plugin into your chain. There won&#39;t be a standalone limiter filter in the choices,
so select &lt;strong&gt;ReaJS&lt;/strong&gt;, and in the configuration settings, search for &lt;strong&gt;masterLimiter&lt;/strong&gt; in the &lt;strong&gt;loser&lt;/strong&gt; category.&lt;/p&gt;
&lt;p&gt;You&#39;ll pretty much only want to tweak two things here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;set &lt;strong&gt;threshold&lt;/strong&gt; to around -3 dB, and&lt;/li&gt;
&lt;li&gt;set &lt;strong&gt;limit&lt;/strong&gt; to around 0.1 dB&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This will effectively stop your audio signal from ever going into the positive.&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/8AAOXobpv7-508.png 508w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/8AAOXobpv7-508.webp 508w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/8AAOXobpv7-508.png&quot; width=&quot;508&quot; height=&quot;575&quot; alt=&quot;Reaper master limiter configuration&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;h4 id=&quot;maybe-a-gain%3F&quot; tabindex=&quot;-1&quot;&gt;Maybe a gain?&lt;/h4&gt;
&lt;p&gt;If at the end of this all, your microphone audio seems to be lower in volume than you initially expected
(which shouldn&#39;t really happen noticeably), you may want to put in a gain filter to help boost the mic level back up.
Totally optional.&lt;/p&gt;
&lt;h3 id=&quot;a-final-look&quot; tabindex=&quot;-1&quot;&gt;A final look&lt;/h3&gt;
&lt;p&gt;So my final audio filter chain looks like this after all that:&lt;/p&gt;
&lt;figure&gt;
      &lt;picture&gt;
        &lt;source type=&quot;image/png&quot; srcset=&quot;https://richardneililagan.com/i/Fa4CkVbsh_-863.png 863w&quot; sizes=&quot;100vw&quot; /&gt;
&lt;source type=&quot;image/webp&quot; srcset=&quot;https://richardneililagan.com/i/Fa4CkVbsh_-863.webp 863w&quot; sizes=&quot;100vw&quot; /&gt;
        &lt;img src=&quot;https://richardneililagan.com/i/Fa4CkVbsh_-863.png&quot; width=&quot;863&quot; height=&quot;758&quot; alt=&quot;Final audio filter chain&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
    &lt;/figure&gt;
&lt;p&gt;You should have an ultimately cleaner, crisper, and oomphier microphone audio after correctly setting up yours.&lt;/p&gt;
</content>
  </entry>
  
</feed>
