[{"id":"starting-at-railway","url":"https://mykalmachon.com/posts/starting-at-railway","meta":{"title":"New job at Railway!","pubDate":"2026-06-12T00:00:00.000Z","type":"post","draft":false,"tags":["personal","career"],"description":"I've joined the team as a Support Engineer","heroImage":{"src":"/_astro/starting-at-railway.BMP_Za1O.png","width":1200,"height":630,"format":"png"}},"content":{"md":"I started working at Railway!\n\n*Well, since April 20th, technically. I've just been too busy having fun to write about it.*\n\n## Why I Joined Railway\n\nIf you've even briefly talked with me about software, you'll know I've been a Railway fan for years. **Beyond just loving the product, a huge part of why I took the role is how Railway thinks about support.** Railway treats support as a core signal for product work.\n\n> \"Support can't be a bolt-on or an afterthought. Good support is necessary for good product because they're the same thing.\"\n> - *myself, rambling about this to you in the past*\n\nRailway is one of the few places I've seen actually own this ethos.\n\nI get to talk directly to users, spot recurring friction, and help turn those patterns into product improvements. To me, support is the product’s exception handling: every thread is signal about where the product could be clearer, smoother, or more resilient.\n\nRailway has written about this exact philosophy too: [Support Engineering Is Engineering](https://blog.railway.com/p/support-engineering-is-engineering).\n\n## What I've Been Working On\n\nI'm on week 8 of my onboarding and have been having a blast.\nThe team has been incredibly kind and generous with their time, and I feel privileged to have the opportunity to build alongside everyone.\n\nSo far, the majority of my work has been on improving our workflows around abuse detection, mitigation, and resolution. I won't get into the specifics, but it has been a fantastic opportunity to learn and grow as an engineer in the context of devtooling, security, and automation.\n\nI've also been working on docs and QA for our [sandboxes](https://docs.railway.com/sandboxes) primitive, which has been a ton of fun.\n\n## What's Next\n\nMore building! I'll share more about what I'm building as time goes on.","html":"<p>I started working at Railway!</p><p><em>Well, since April 20th, technically. I've just been too busy having fun to write about it.</em></p><h2>Why I Joined Railway</h2><p>If you've even briefly talked with me about software, you'll know I've been a Railway fan for years. <strong>Beyond just loving the product, a huge part of why I took the role is how Railway thinks about support.</strong> Railway treats support as a core signal for product work.</p><blockquote><p>&quot;Support can't be a bolt-on or an afterthought. Good support is necessary for good product because they're the same thing.&quot;</p><ul><li><em>myself, rambling about this to you in the past</em></li></ul></blockquote><p>Railway is one of the few places I've seen actually own this ethos.</p><p>I get to talk directly to users, spot recurring friction, and help turn those patterns into product improvements. To me, support is the product’s exception handling: every thread is signal about where the product could be clearer, smoother, or more resilient.</p><p>Railway has written about this exact philosophy too: <a href=\"https://blog.railway.com/p/support-engineering-is-engineering\">Support Engineering Is Engineering</a>.</p><h2>What I've Been Working On</h2><p>I'm on week 8 of my onboarding and have been having a blast.The team has been incredibly kind and generous with their time, and I feel privileged to have the opportunity to build alongside everyone.</p><p>So far, the majority of my work has been on improving our workflows around abuse detection, mitigation, and resolution. I won't get into the specifics, but it has been a fantastic opportunity to learn and grow as an engineer in the context of devtooling, security, and automation.</p><p>I've also been working on docs and QA for our <a href=\"https://docs.railway.com/sandboxes\">sandboxes</a> primitive, which has been a ton of fun.</p><h2>What's Next</h2><p>More building! I'll share more about what I'm building as time goes on.</p>"}},{"id":"guiding-principles","url":"https://mykalmachon.com/posts/guiding-principles","meta":{"title":"Guiding principles","pubDate":"2026-03-17T00:00:00.000Z","type":"post","draft":false,"tags":["personal","opinion"],"description":"some questions that I hope will lead me to a full life"},"content":{"md":"At 19, I put a note in my wallet that read \"If you're working on harder problems today than you were last year, then you'll be alright.\" It was a rough estimate of something my uncle had said to me that day that I wanted to remember. The quote stuck at the top of my mind for years, and as I've gotten older, I've picked up a few more pieces of knowledge. I reformulated them as questions and have come to think of them as my guiding principles.\n\nI've never shared these before, but I brought them up with someone the other day, and they were really interested. I thought that it was worth writing them up.\n\n## My guiding principles\n\n### Am I working on harder problems than I was six months ago?\n\nThis one was the original principle I was given. This question aims to set you on a trajectory for consistent growth. Personally, I don't feel great about being in a place where I am constantly stuck in monotony.&#x20;\n\nThis question reminds me to find challenges in my day-to-day. If there are truly none to be found, then it motivates me to move on to greener pastures or build new ways to be challenged!&#x20;\n\nRecently, this has looked like learning piano, working to be a better dad to my son, or getting the ball rolling on tackling significant and systemic technical problems at work without being asked to do so. &#x20;\n\n### Am I building things and skills that compound over time?\n\nThe most satisfying experiences I've had, both professionally and personally, aren't just one-and-dones. They're iterative, evolving, and often end up building momentum in my life somehow.&#x20;\n\nInvesting in my relationships is probably my strongest example of this principle. My wedding was one of the greatest days of my life, but it's just a point in time in the relationship with my wife; another fantastic point on that string is the birth of my son, all the way down to last week when my wife and I witnessed my son saying \"Meow\" (pronounced as \"Boww\") for the first time.&#x20;\n\nThose relationships with my wife, son, family, and friends are living and compounding things. The more you invest in the people, things you build, and skills you develop, the better your outcomes. The obvious parallel here is any technical skill. The closer to the fundamentals in a domain the skill you're building is, the more transferable and compounding it becomes.&#x20;\n\n### Am I around people who push me to be better? If I can push them to be better, do I?\n\nI've come to believe there is always room for improvement! I have found that when I spend time with people who are unmotivated to help others, improve the systems we collectively exist in, or help themselves in times of struggle, I get pulled into that apathetic direction myself.\n\nIn contrast, when I've spent time around people who hold me accountable and hold a higher standard for themselves, it pulls me up. If I surround myself with people who push me, I will become better.&#x20;\n\nIn return, I think it's only fair to hold the people around me to those same standards and help them whenever I can. So many people have done so much to help me throughout my life; it's only fair that I pay that back through helping others if I can. &#x20;\n\n### Am I excited to get out of bed in the morning? Are the sheets light?\n\nI actually added this one to the list recently. I got it from my dad when talking about a big personal decision I'm in the process of making. I thought it was a really strong daily check-in. Even more so than the other questions in my list. Every morning when I get up, is it easy to do so? Am I excited for the day? If the answer is no for 4-5 days, that's okay, but more than a month? more than a few months? It's time to identify the problem and make some ruthless changes to make the sheets light again.  &#x20;\n\n## Guiding principles over long-term goals\n\nAs I've achieved big life goals, I've realized that while they can be a useful driving force, they oftentimes leave me feeling disappointed. When you're in the thick of it, all you can think about is the finish line, then when you get there, you get this free-fall feeling of \"oh, I did it… Now what?\" Not ideal, and maybe not worth the years of toil and stress for a day or two of satisfaction.\n\nAnother issue with long-term goals is that they're virtual and static targets in a world made of physical and shifting ones. I've made countless goals that make sense at the time but become meaningless in 3 months because of external factors, a change of opinion, or an opportunity falling through. Some long-term goals are good bets, but it's really hard to land on them in practice.\n\nIn both of these ways, long-term goals have failed to bring me much beyond a rough sense of direction.\n\nThis is why I've been focusing on the principles above. They're short-term, actionable, and oriented around happiness, satisfaction, and kindness to those around you. They focus on what you can do right now to build momentum by putting yourself in situations that make your day-to-day life great. If I end my day knowing my answer to each of these questions is yes, I know I'm heading in the right direction- and that I had a good day.\n\nPart of the beauty here is that they're also a living set of principles. They can change as my priorities change, and unlike changing a goal, that change is guilt-free. &#x20;","html":"<p>At 19, I put a note in my wallet that read &quot;If you're working on harder problems today than you were last year, then you'll be alright.&quot; It was a rough estimate of something my uncle had said to me that day that I wanted to remember. The quote stuck at the top of my mind for years, and as I've gotten older, I've picked up a few more pieces of knowledge. I reformulated them as questions and have come to think of them as my guiding principles.</p><p>I've never shared these before, but I brought them up with someone the other day, and they were really interested. I thought that it was worth writing them up.</p><h2>My guiding principles</h2><h3>Am I working on harder problems than I was six months ago?</h3><p>This one was the original principle I was given. This question aims to set you on a trajectory for consistent growth. Personally, I don't feel great about being in a place where I am constantly stuck in monotony. </p><p>This question reminds me to find challenges in my day-to-day. If there are truly none to be found, then it motivates me to move on to greener pastures or build new ways to be challenged! </p><p>Recently, this has looked like learning piano, working to be a better dad to my son, or getting the ball rolling on tackling significant and systemic technical problems at work without being asked to do so.  </p><h3>Am I building things and skills that compound over time?</h3><p>The most satisfying experiences I've had, both professionally and personally, aren't just one-and-dones. They're iterative, evolving, and often end up building momentum in my life somehow. </p><p>Investing in my relationships is probably my strongest example of this principle. My wedding was one of the greatest days of my life, but it's just a point in time in the relationship with my wife; another fantastic point on that string is the birth of my son, all the way down to last week when my wife and I witnessed my son saying &quot;Meow&quot; (pronounced as &quot;Boww&quot;) for the first time. </p><p>Those relationships with my wife, son, family, and friends are living and compounding things. The more you invest in the people, things you build, and skills you develop, the better your outcomes. The obvious parallel here is any technical skill. The closer to the fundamentals in a domain the skill you're building is, the more transferable and compounding it becomes. </p><h3>Am I around people who push me to be better? If I can push them to be better, do I?</h3><p>I've come to believe there is always room for improvement! I have found that when I spend time with people who are unmotivated to help others, improve the systems we collectively exist in, or help themselves in times of struggle, I get pulled into that apathetic direction myself.</p><p>In contrast, when I've spent time around people who hold me accountable and hold a higher standard for themselves, it pulls me up. If I surround myself with people who push me, I will become better. </p><p>In return, I think it's only fair to hold the people around me to those same standards and help them whenever I can. So many people have done so much to help me throughout my life; it's only fair that I pay that back through helping others if I can.  </p><h3>Am I excited to get out of bed in the morning? Are the sheets light?</h3><p>I actually added this one to the list recently. I got it from my dad when talking about a big personal decision I'm in the process of making. I thought it was a really strong daily check-in. Even more so than the other questions in my list. Every morning when I get up, is it easy to do so? Am I excited for the day? If the answer is no for 4-5 days, that's okay, but more than a month? more than a few months? It's time to identify the problem and make some ruthless changes to make the sheets light again.   </p><h2>Guiding principles over long-term goals</h2><p>As I've achieved big life goals, I've realized that while they can be a useful driving force, they oftentimes leave me feeling disappointed. When you're in the thick of it, all you can think about is the finish line, then when you get there, you get this free-fall feeling of &quot;oh, I did it… Now what?&quot; Not ideal, and maybe not worth the years of toil and stress for a day or two of satisfaction.</p><p>Another issue with long-term goals is that they're virtual and static targets in a world made of physical and shifting ones. I've made countless goals that make sense at the time but become meaningless in 3 months because of external factors, a change of opinion, or an opportunity falling through. Some long-term goals are good bets, but it's really hard to land on them in practice.</p><p>In both of these ways, long-term goals have failed to bring me much beyond a rough sense of direction.</p><p>This is why I've been focusing on the principles above. They're short-term, actionable, and oriented around happiness, satisfaction, and kindness to those around you. They focus on what you can do right now to build momentum by putting yourself in situations that make your day-to-day life great. If I end my day knowing my answer to each of these questions is yes, I know I'm heading in the right direction- and that I had a good day.</p><p>Part of the beauty here is that they're also a living set of principles. They can change as my priorities change, and unlike changing a goal, that change is guilt-free.  </p>"}},{"id":"excerpt-of-david-foster-wallace-interview-from-1996","url":"https://mykalmachon.com/posts/excerpt-of-david-foster-wallace-interview-from-1996","meta":{"title":"Excerpt of David Foster Wallace interview from 1996","pubDate":"2025-11-03T00:00:00.000Z","type":"link","draft":false,"tags":["personal"],"description":"Helluva quote","url":"https://www.reddit.com/r/nosurf/comments/1lt55t3/excerpt_of_david_foster_wallace_interview_from/"},"content":{"md":"*\"And that as the Internet grows, and as our ability to be linked up, like—I mean, you and I coulda done this through e-mail, and I never woulda had to meet you, and that woulda been easier for me. Right? Like, at a certain point, we’re gonna have to build some machinery, inside our guts, to help us deal with this. Because the technology is just gonna get better and better and better and better. And it’s gonna get easier and easier, and more and more convenient, and more and more pleasurable, to be alone with images on a screen, given to us by people who do not love us but want our money. Which is all right. In low doses, right? But if that’s the basic main staple of your diet, you’re gonna die. In a meaningful way, you’re going to die\"*","html":"<p><em>&quot;And that as the Internet grows, and as our ability to be linked up, like—I mean, you and I coulda done this through e-mail, and I never woulda had to meet you, and that woulda been easier for me. Right? Like, at a certain point, we’re gonna have to build some machinery, inside our guts, to help us deal with this. Because the technology is just gonna get better and better and better and better. And it’s gonna get easier and easier, and more and more convenient, and more and more pleasurable, to be alone with images on a screen, given to us by people who do not love us but want our money. Which is all right. In low doses, right? But if that’s the basic main staple of your diet, you’re gonna die. In a meaningful way, you’re going to die&quot;</em></p>"}},{"id":"copying-to-the-windows-clipboard-from-wsl","url":"https://mykalmachon.com/posts/copying-to-the-windows-clipboard-from-wsl","meta":{"title":"Copying to the Windows Clipboard from WSL","pubDate":"2025-08-29T00:00:00.000Z","type":"post","draft":false,"tags":["tech"],"description":"How to easily copy content from your WSL shell to your Windows clipboard"},"content":{"md":"Found a fun little hack today while trying to copy some test output from my Ubuntu WSL terminal into the Windows clipboard.&#x20;\n\nYeah yeah, I know you can select the text in your terminal and it'll automatically be copied into your clipbaord. There's just one problem with that. *You have to use a mouse* (🤮). If you're anything like me, taking your hands off the keyboard mid-flow is akin to sacrilege.&#x20;\n\nAnyways, here's a quick example:&#x20;\n\n```bash\ncat some_file.txt | clip.exe\n```\n\nThat will copy the content of `some_file.txt` into your windows clipboard. Since it's just piping data into the clip.exe program you can literally drop anything into your clipboard. test output? sure! a whole image? sure!&#x20;\n\nAnyways; I found it useful so thought I would share. &#x20;","html":"<p>Found a fun little hack today while trying to copy some test output from my Ubuntu WSL terminal into the Windows clipboard. </p><p>Yeah yeah, I know you can select the text in your terminal and it'll automatically be copied into your clipbaord. There's just one problem with that. <em>You have to use a mouse</em> (🤮). If you're anything like me, taking your hands off the keyboard mid-flow is akin to sacrilege. </p><p>Anyways, here's a quick example: </p><pre><code class=\"language-bash\">cat some_file.txt | clip.exe</code></pre><p>That will copy the content of <code>some_file.txt</code> into your windows clipboard. Since it's just piping data into the clip.exe program you can literally drop anything into your clipboard. test output? sure! a whole image? sure! </p><p>Anyways; I found it useful so thought I would share.  </p>"}},{"id":"setting-up-the-1-password-cli-on-wsl","url":"https://mykalmachon.com/posts/setting-up-the-1-password-cli-on-wsl","meta":{"title":"Setting up the 1Password CLI on WSL","pubDate":"2025-07-28T00:00:00.000Z","type":"post","draft":false,"tags":["tech"],"description":"1Password's CLI is great but their WSL docs aren't.","location":"the terminal"},"content":{"md":"I've been messing around with the 1Password developer tools and CLI and have found it extremely useful. However, I found the docs on the website regarding setting up the 1Password CLI in a WSL environment to be either incomplete or just non-existent so I wanted to throw together a guide on how I got everything working on my machine with Windows 11 and Ubuntu 24.01 LTS (Noble Numbat).\n\nIt's not an overly complex set of tasks but it took me longer than I'd like to admit to figure out how to get the cli working in WSL.\n\n## Before we get started\n\nI'm going to assume you have the following already setup on your machine:\n\n* [The 1Password Windows Desktop App](https://1password.com/downloads/windows) is installed and configured on your machine.\n* [Windows Hello is setup on your machine](https://support.microsoft.com/en-us/windows/configure-windows-hello-dae28983-8242-bb2a-d3d1-87c9d265a5f0) (this is required for unlock via the terminal)\n\n## Setting up the 1Password CLI\n\nAs a starting point, [just follow the guide for setting things up for Windows.](https://developer.1password.com/docs/cli/get-started/)\n\n**Completely ignore all of the Linux stuff.**\n\nThis threw me off the first time I followed this tutorial cause usually with WSL you just follow the \"Linux\" guide for your distro and things work out. *This is not the case with 1Password.*\n\n### Configuring WSL to call the 1Password CLI via interop\n\nThe trick to getting things working in WSL is just to use the WSL interoperability feature where you can call `.exe` commands from within WSL. If you hop into a WSL window and type `op.exe vault list` you should get a popup asking for you to authorize the request and then see your vaults listed out in your terminal! awesome!\n\nbut we don't want to always have to call op.exe... that's just *weird* in a Linux environment (and would probably cause issues when running on real Linux setups with the `op` installed) so let's do some aliasing:\n\n```bash\n# .zshrc or .bashrc or whatever shell you use \n\n# 1Password Windows/WSL Interoperability\nalias op=\"op.exe\" \n```\n\nafter a quick `source ~/.zshrc` you should be able to run `op vault list` and see something like this:\n\n```bash\nop vault list \n\nID                            NAME\n564bc0a0f9d90a22fc1cb82b5f    Personal\n44fa7a6c974e29a3c682158f03    Applications\n72d299c80a5fecdf3f90f41e8b    Shared\n```\n\n## Managing SSH keys\n\nYou'll also probably want to manage (and import your existing) WSL SSH keys into 1password. [Their guide for this is actually great](https://developer.1password.com/docs/ssh) and the only thing you'll need to do is manually copy over the private key for the SSH keypair you've been using on WSL so far.\n\nYou can even set WSL up to use the SSH key for signing your commits and get that cool little verified badge besides your name in pull reqeusts 😉\n\nFrom there, it should all work as expected.\n\n## Getting around `op run` limitations on WSL\n\nWhen you try and run `op run -- npm run start` (or any op run command) within WSL you'll find that it doesn't execute the latter half of the script within WSL. It executes it withint a Powershell/Windows context. This means that you can't easily use `op run` to easily replace your 1password references at runtime.&#x20;\n\nThankfully you can get around this by using `op inject`!\n\nOn all of your projects, replace your `env.example` with an `env.template` and include the references to the 1password credentials you'd like to replace&#x20;\n\n```bash\nDATABASE_URL=\"op://app-prod/Database: Primary/Connection String\"\nOPENAI_API_KEY=\"op://app-prod/API Key: OpenAI/API Key\"\nSESSION_SECRET=\"op://app-prod/Misc: Session Secret/Password\"\n```\n\nFrom there, all you need to do is run the following command \n\n```bash\n# generate an .env file with your secrets\nop inject -i .env.template -o .env\n\n# run your command that expects .env\nnpm run start\n\n# delete the creds\nrm .env\n```\n\nWhile this is an okay start! I still don't love credentials actually getting written to disk. \nI expanded on this approach a bit and basically just rewrote `op run` myself to work better on WSL.\n\n[You can check that out and install it if you want.](https://github.com/MykalMachon/oprun.sh/tree/v1.0.0)","html":"<p>I've been messing around with the 1Password developer tools and CLI and have found it extremely useful. However, I found the docs on the website regarding setting up the 1Password CLI in a WSL environment to be either incomplete or just non-existent so I wanted to throw together a guide on how I got everything working on my machine with Windows 11 and Ubuntu 24.01 LTS (Noble Numbat).</p><p>It's not an overly complex set of tasks but it took me longer than I'd like to admit to figure out how to get the cli working in WSL.</p><h2>Before we get started</h2><p>I'm going to assume you have the following already setup on your machine:</p><ul><li><a href=\"https://1password.com/downloads/windows\">The 1Password Windows Desktop App</a> is installed and configured on your machine.</li><li><a href=\"https://support.microsoft.com/en-us/windows/configure-windows-hello-dae28983-8242-bb2a-d3d1-87c9d265a5f0\">Windows Hello is setup on your machine</a> (this is required for unlock via the terminal)</li></ul><h2>Setting up the 1Password CLI</h2><p>As a starting point, <a href=\"https://developer.1password.com/docs/cli/get-started/\">just follow the guide for setting things up for Windows.</a></p><p><strong>Completely ignore all of the Linux stuff.</strong></p><p>This threw me off the first time I followed this tutorial cause usually with WSL you just follow the &quot;Linux&quot; guide for your distro and things work out. <em>This is not the case with 1Password.</em></p><h3>Configuring WSL to call the 1Password CLI via interop</h3><p>The trick to getting things working in WSL is just to use the WSL interoperability feature where you can call <code>.exe</code> commands from within WSL. If you hop into a WSL window and type <code>op.exe vault list</code> you should get a popup asking for you to authorize the request and then see your vaults listed out in your terminal! awesome!</p><p>but we don't want to always have to call op.exe... that's just <em>weird</em> in a Linux environment (and would probably cause issues when running on real Linux setups with the <code>op</code> installed) so let's do some aliasing:</p><pre><code class=\"language-bash\"># .zshrc or .bashrc or whatever shell you use # 1Password Windows/WSL Interoperabilityalias op=&quot;op.exe&quot; </code></pre><p>after a quick <code>source ~/.zshrc</code> you should be able to run <code>op vault list</code> and see something like this:</p><pre><code class=\"language-bash\">op vault list ID                            NAME564bc0a0f9d90a22fc1cb82b5f    Personal44fa7a6c974e29a3c682158f03    Applications72d299c80a5fecdf3f90f41e8b    Shared</code></pre><h2>Managing SSH keys</h2><p>You'll also probably want to manage (and import your existing) WSL SSH keys into 1password. <a href=\"https://developer.1password.com/docs/ssh\">Their guide for this is actually great</a> and the only thing you'll need to do is manually copy over the private key for the SSH keypair you've been using on WSL so far.</p><p>You can even set WSL up to use the SSH key for signing your commits and get that cool little verified badge besides your name in pull reqeusts 😉</p><p>From there, it should all work as expected.</p><h2>Getting around <code>op run</code> limitations on WSL</h2><p>When you try and run <code>op run -- npm run start</code> (or any op run command) within WSL you'll find that it doesn't execute the latter half of the script within WSL. It executes it withint a Powershell/Windows context. This means that you can't easily use <code>op run</code> to easily replace your 1password references at runtime. </p><p>Thankfully you can get around this by using <code>op inject</code>!</p><p>On all of your projects, replace your <code>env.example</code> with an <code>env.template</code> and include the references to the 1password credentials you'd like to replace </p><pre><code class=\"language-bash\">DATABASE_URL=&quot;op://app-prod/Database: Primary/Connection String&quot;OPENAI_API_KEY=&quot;op://app-prod/API Key: OpenAI/API Key&quot;SESSION_SECRET=&quot;op://app-prod/Misc: Session Secret/Password&quot;</code></pre><p>From there, all you need to do is run the following command</p><pre><code class=\"language-bash\"># generate an .env file with your secretsop inject -i .env.template -o .env# run your command that expects .envnpm run start# delete the credsrm .env</code></pre><p>While this is an okay start! I still don't love credentials actually getting written to disk.I expanded on this approach a bit and basically just rewrote <code>op run</code> myself to work better on WSL.</p><p><a href=\"https://github.com/MykalMachon/oprun.sh/tree/v1.0.0\">You can check that out and install it if you want.</a></p>"}},{"id":"ai-is-a-lot-like-candy","url":"https://mykalmachon.com/posts/ai-is-a-lot-like-candy","meta":{"title":"AI is a lot like candy","pubDate":"2025-06-18T00:00:00.000Z","type":"post","draft":false,"tags":["ai","opinion"],"description":""},"content":{"md":"I just read [this article](https://time.com/7295195/ai-chatgpt-google-learning-school/) and the linked [MIT study](https://www.media.mit.edu/publications/your-brain-on-chatgpt/) (TLDR: people \"using AI consistently underperformed at neural, linguistic, and behavioral levels\" in testing) over my lunch break. It confirmed a thought I've been having quite often lately; we should probably think of AI a lot like we think of candy. *A nice sweet treat every once in a while, but not something that should make up the majority of your diet.*&#x20;\n\nI think this approach is especially important for those of us who's work can easily be augmented or in some cases replaced by existing AI tools: Software Developers, Systems Analysts, Writers, Academics, etc. Eating candy feels really good, but we know why it shouldn't be the majority of our diet. In the same way, having an AI write your code feels very productive, but the gains are often shallow, and the losses cut deeper than you might realize.\n\n## The first hint of my candy addiction\n\nAround 6 months ago when I setup a new computer that didn't have copilot on it yet. I noticed that I was typing a few characters then, without thinking about it, waiting for code completions. I hadn't even thought about how I was going to implement the function past the first line because I had wired my brain to expect an AI to take a first pass.&#x20;\n\nI turned off copilot's inline code completions then and have had them off 95% of the time since then. This felt like a good compromise to me, and I kept on working.\n\n## When I realized I had a problem\n\nAfter I turned off code completions, I started using GitHub Copilot's \"Edit\" and \"Agent\" modes for very specific work. Some generalized examples of when I was using this mode are:&#x20;\n\n1. I had already written all the core logic for a function but wanted to abstract the business logic into re-usable functions so that the core entry point functions were easier to read.&#x20;\n2. I manually abstracted bare fetch calls into an API wrapper and wanted to replace all of my API calls with wrapper calls.&#x20;\n\nFor a few months this was going well. I felt like I was shipping more code. I thought I was understanding the code I was shipping and more importantly I *thought* everything was working.&#x20;\n\n### Issues with my 1st use case&#x20;\n\nAI is really great at solving the task you put in front of it. When I asked for it to break out my business logic into smaller re-usable functions it did just that. What it wasn't able to do, which in retrospect should have been obvious, was predict how I wanted to use those functions moving forward or think about *why* I'd want things broken out.&#x20;\n\nThe code Copilot generated for me was sort of like this:&#x20;\n\n```typescript\n// all the definitions for our functions\nconst getGroupInfo = (groupData: GroupData): GroupInfo\nconst getItemsForGroup = async (groupData: GroupData): Promise<Item[]>\nconst getSectionsForGroup = async (groupData: GroupData): Promise<Section[]>\nconst createNewGroup = async (items: Item[]): Promise<Group>\nconst addSectionsToGroup = async (id: string, sections: Section[]): Promise<void>\n\n// the main business function\nconst importGroup = async (deps, rawGroupData) => {\n  const { logger, database, cache, api } = deps;\n\n  const { groupName, groupCode } = getGroupInfo(rawGroupData);\n\n  const items = await getItemsForGroup(rawGroupData);\n  const sections = await getSectionsForGroup(rawGroupData);\n\n  const newGroup = await createNewGroup(newGroup.items);\n  await addSectionsToGroup(newGroup.id, sections);\n\n  return newGroup;\n}\n```\n\nThis looks great! what the AI didn't realize is that groups are actually a more specific version of another type called an organization in the API I was using. Every action we're taking in block of code could also be taken on an Organization with exception to managing sections. Only groups can have sections.\n\nSo, while the AI did a good job of solving for what it could see in the codebase, it didn't do what I would have done. Because I've been working with these APIs and this codebase for a while, I know about the Organization and Group relationship. If I would have written this code, I would have abstracted things out either into classes for Organizations and Groups with proper inheritance OR used more generic types and interfaces so that these functions could be used on both organizations AND groups moving forward.\n\nI ended up rewriting this code again and using extended types to solve this and it probably would have just been faster if I had sat down, thought about it for a few minutes, and done this from the start.&#x20;\n\n### Issues with my 2nd use case\n\nFast forward to last week, one of the systems I was working on was logging a whole lot more 404s to a specific route… weird!&#x20;\n\nTurns out, copilot had implemented one of my wrapper functions (from point number 3 above) incorrectly. Let's look at some code as an example:&#x20;\n\n```typescript\n// the old line of code \nconst removedItem = await fetch(`/api/${v}/groups/${group.id}/items/${item.id}`, {\n  method: 'DELETE',\n  headers: { /* some stuff */ }\n});\n\n// the new line that copilot wrote a few weeks ago.\nconst removedItem = await myApiClient.removeItemFromGroup(item.id, group.id);\n```\n\nThat all looks fine right? well what if I told you this was the function signature for that new API client call `myApiClient.removeItemFromGroup(groupId: string, itemId: string) => Promise<Item>`\n\n***Are you seeing where the problem is?***&#x20;\n\nThis is a mistake that I am 99% sure I would not have made if I wasn't using AI. When I'm actually writing code, I read the function signature immediately before filling out parameters. I stupidly thought the AI would be smart enough to infer and interpret the function signature and the local variables correctly and was unfortunately wrong.\n\n### Where I went wrong here\n\nWhile AI did screw up in both cases here, I think the root of the issue is that I didn't understand the code that I was merging into main, and I didn't think about it because it was so easy not to! ***That's still my responsibility.***&#x20;\n\nWhen I am writing code, I typically understand what it's doing because *I* thought about it before I wrote it; AI use takes that away. When I'm doing code review, I *know* I didn't write it. I take a deeper look at things. When using AI agents on my machine that same skepticism doesn't kick in. I shouldn't have trusted this code, I didn't write it, but because it was written on my machine, while I watched, that code-review instinct did not kick in. *This is undoubtedly bad.*&#x20;\n\n## When I allow myself to have a little AI treat&#x20;\n\nTo reference back to the AI is like candy metaphor, I do still think there's a little room for it in your knowledge-worker diet. Here's what I'm currently trying:&#x20;\n\n* **Using AI agents to write code with well-defined test in-place**: not all software is easily testable like this, but if I can define solid tests for a feature or set of functions (given this input, produce this output) agents are pretty solid at iteratively building and running against the test suite until they find a working solution.&#x20;\n* **Automating very small changes**: I have had some success with AI agents or \"edit mode\" in Copilot when you give them a very well-defined issue or bug as well as some test cases to work with. Even then, it seems very hit or miss so far.&#x20;\n* **Code Review:** One use case I have actually really enjoyed is GitHub Copilot's code review features. Whenever I submit a PR for review, Copilot will go over my code and actually submit a code review. That includes finding nitpicks, stylistic issues, or just straight up errors. Sometimes it's off base but I've actually had it catch 3-4 bugs on open PRs so far after only a few weeks.&#x20;","html":"<p>I just read <a href=\"https://time.com/7295195/ai-chatgpt-google-learning-school/\">this article</a> and the linked <a href=\"https://www.media.mit.edu/publications/your-brain-on-chatgpt/\">MIT study</a> (TLDR: people &quot;using AI consistently underperformed at neural, linguistic, and behavioral levels&quot; in testing) over my lunch break. It confirmed a thought I've been having quite often lately; we should probably think of AI a lot like we think of candy. <em>A nice sweet treat every once in a while, but not something that should make up the majority of your diet.</em> </p><p>I think this approach is especially important for those of us who's work can easily be augmented or in some cases replaced by existing AI tools: Software Developers, Systems Analysts, Writers, Academics, etc. Eating candy feels really good, but we know why it shouldn't be the majority of our diet. In the same way, having an AI write your code feels very productive, but the gains are often shallow, and the losses cut deeper than you might realize.</p><h2>The first hint of my candy addiction</h2><p>Around 6 months ago when I setup a new computer that didn't have copilot on it yet. I noticed that I was typing a few characters then, without thinking about it, waiting for code completions. I hadn't even thought about how I was going to implement the function past the first line because I had wired my brain to expect an AI to take a first pass. </p><p>I turned off copilot's inline code completions then and have had them off 95% of the time since then. This felt like a good compromise to me, and I kept on working.</p><h2>When I realized I had a problem</h2><p>After I turned off code completions, I started using GitHub Copilot's &quot;Edit&quot; and &quot;Agent&quot; modes for very specific work. Some generalized examples of when I was using this mode are: </p><ol><li>I had already written all the core logic for a function but wanted to abstract the business logic into re-usable functions so that the core entry point functions were easier to read. </li><li>I manually abstracted bare fetch calls into an API wrapper and wanted to replace all of my API calls with wrapper calls. </li></ol><p>For a few months this was going well. I felt like I was shipping more code. I thought I was understanding the code I was shipping and more importantly I <em>thought</em> everything was working. </p><h3>Issues with my 1st use case </h3><p>AI is really great at solving the task you put in front of it. When I asked for it to break out my business logic into smaller re-usable functions it did just that. What it wasn't able to do, which in retrospect should have been obvious, was predict how I wanted to use those functions moving forward or think about <em>why</em> I'd want things broken out. </p><p>The code Copilot generated for me was sort of like this: </p><pre><code class=\"language-typescript\">// all the definitions for our functionsconst getGroupInfo = (groupData: GroupData): GroupInfoconst getItemsForGroup = async (groupData: GroupData): Promise&lt;Item[]&gt;const getSectionsForGroup = async (groupData: GroupData): Promise&lt;Section[]&gt;const createNewGroup = async (items: Item[]): Promise&lt;Group&gt;const addSectionsToGroup = async (id: string, sections: Section[]): Promise&lt;void&gt;// the main business functionconst importGroup = async (deps, rawGroupData) =&gt; {  const { logger, database, cache, api } = deps;  const { groupName, groupCode } = getGroupInfo(rawGroupData);  const items = await getItemsForGroup(rawGroupData);  const sections = await getSectionsForGroup(rawGroupData);  const newGroup = await createNewGroup(newGroup.items);  await addSectionsToGroup(newGroup.id, sections);  return newGroup;}</code></pre><p>This looks great! what the AI didn't realize is that groups are actually a more specific version of another type called an organization in the API I was using. Every action we're taking in block of code could also be taken on an Organization with exception to managing sections. Only groups can have sections.</p><p>So, while the AI did a good job of solving for what it could see in the codebase, it didn't do what I would have done. Because I've been working with these APIs and this codebase for a while, I know about the Organization and Group relationship. If I would have written this code, I would have abstracted things out either into classes for Organizations and Groups with proper inheritance OR used more generic types and interfaces so that these functions could be used on both organizations AND groups moving forward.</p><p>I ended up rewriting this code again and using extended types to solve this and it probably would have just been faster if I had sat down, thought about it for a few minutes, and done this from the start. </p><h3>Issues with my 2nd use case</h3><p>Fast forward to last week, one of the systems I was working on was logging a whole lot more 404s to a specific route… weird! </p><p>Turns out, copilot had implemented one of my wrapper functions (from point number 3 above) incorrectly. Let's look at some code as an example: </p><pre><code class=\"language-typescript\">// the old line of code const removedItem = await fetch(`/api/${v}/groups/${group.id}/items/${item.id}`, {  method: 'DELETE',  headers: { /* some stuff */ }});// the new line that copilot wrote a few weeks ago.const removedItem = await myApiClient.removeItemFromGroup(item.id, group.id);</code></pre><p>That all looks fine right? well what if I told you this was the function signature for that new API client call <code>myApiClient.removeItemFromGroup(groupId: string, itemId: string) =&gt; Promise&lt;Item&gt;</code></p><p><em><strong>Are you seeing where the problem is?</strong></em> </p><p>This is a mistake that I am 99% sure I would not have made if I wasn't using AI. When I'm actually writing code, I read the function signature immediately before filling out parameters. I stupidly thought the AI would be smart enough to infer and interpret the function signature and the local variables correctly and was unfortunately wrong.</p><h3>Where I went wrong here</h3><p>While AI did screw up in both cases here, I think the root of the issue is that I didn't understand the code that I was merging into main, and I didn't think about it because it was so easy not to! <em><strong>That's still my responsibility.</strong></em> </p><p>When I am writing code, I typically understand what it's doing because <em>I</em> thought about it before I wrote it; AI use takes that away. When I'm doing code review, I <em>know</em> I didn't write it. I take a deeper look at things. When using AI agents on my machine that same skepticism doesn't kick in. I shouldn't have trusted this code, I didn't write it, but because it was written on my machine, while I watched, that code-review instinct did not kick in. <em>This is undoubtedly bad.</em> </p><h2>When I allow myself to have a little AI treat </h2><p>To reference back to the AI is like candy metaphor, I do still think there's a little room for it in your knowledge-worker diet. Here's what I'm currently trying: </p><ul><li><strong>Using AI agents to write code with well-defined test in-place</strong>: not all software is easily testable like this, but if I can define solid tests for a feature or set of functions (given this input, produce this output) agents are pretty solid at iteratively building and running against the test suite until they find a working solution. </li><li><strong>Automating very small changes</strong>: I have had some success with AI agents or &quot;edit mode&quot; in Copilot when you give them a very well-defined issue or bug as well as some test cases to work with. Even then, it seems very hit or miss so far. </li><li><strong>Code Review:</strong> One use case I have actually really enjoyed is GitHub Copilot's code review features. Whenever I submit a PR for review, Copilot will go over my code and actually submit a code review. That includes finding nitpicks, stylistic issues, or just straight up errors. Sometimes it's off base but I've actually had it catch 3-4 bugs on open PRs so far after only a few weeks. </li></ul>"}},{"id":"dotfiles-on-windows","url":"https://mykalmachon.com/posts/dotfiles-on-windows","meta":{"title":"Dotfiles on windows","pubDate":"2025-05-19T00:00:00.000Z","type":"post","draft":false,"tags":["tech"],"description":"automating setup of a windows machine via a dotfiles repo"},"content":{"md":"I just setup a work laptop and and took the chance to look into what setting up a \"dotfiles\" for windows would look like. Specifically, what it would take to keep track of apps/tools that I'd need and automate the process of setup on Windows.\n\nI've setup dotfiles on linux machines before, but never windows as I sort of just assumed that package manager just wasn't an option on Windows. I'd used NInite in the past but it's pretty limited on what exactly you can install; there are some common apps but it doesn't include everything I would want out-of-the gate for a work computer.&#x20;\n\n## Scoop: A package manager for windows&#x20;\n\nSearching for \"Windows Package Manager\" immediately brings up [scoop.sh](https://scoop.sh). This was exactly waht I was looking for and I was blown away it was literally the first thing I found. I had heard of Scoop previously (usually just as an install option in a README.md file) but had never really looked into it. &#x20;\n\nSccop is pretty cool and has a bunch of sweet features that make it easy to use:&#x20;\n\n* All apps are installed portably in your user directory; you don't need admin permissions for anything installed via scoop.&#x20;\n* Scoop makes it really easy for developers to add their apps to the package manager. You can host your own scoop by adding a manifest file to any GItHub repo and you can add it into the official \"bucket\" by submitting a PR into one of the main scoop buckets.&#x20;\n* You can install basically anything with scoop; My windows \"dotfiles\" install script automatically installs CLI apps like lazygit, node.js, golang, and eza while also installing desktop apps like vscode, and discord.    &#x20;\n\nAfter seeing all scoop has to offer it seemd like a pretty easy choice for automating my windows setup. &#x20;\n\n### A runner up: winget\n\nI also found winget, which is an official package manager for windows that's seen more adoption lately. It comes pre-installed with windows, has a direct integration with [the Microsoft Store](https://apps.microsoft.com), and tends to have a larger share of closed-source and GUI applications.  &#x20;\n\nThat said, It has a few drawbacks when compared to scoop. The big one for me was that I was *constantly* prompted for \"A Local Administrator\" via UAC when installing packages. While I can temporarily escalate into local admin if I need to, the process is annoying and not everyone has that option on a work laptop.\n\n## Automating setup with scoop.sh + powershell\n\nFrom there, all we had to do was some Powershell scripting to get everything up and running. I'll include a simplified version of my script below, [but here's a link to the repo if you want to take a look at the nitty gritty + some other terminal styling I do](https://github.com/MykalMachon/dotfiles/tree/main/windows).&#x20;\n\n```powershell\n# Read a list of packages that I want to install via scoop\n$json = Get-Content \"$PSScriptRoot\\packages.json\" | ConvertFrom-Json\n\n# Set execution policy and install scoop\nSet-ExecutionPolicy RemoteSigned -Scope CurrentUser -Force\nif (-not (Test-Path \"$env:USERPROFILE\\scoop\")) {\n    Invoke-RestMethod get.scoop.sh | Invoke-Expression\n}\n\n# Install git first as it's a requirement for scoop's \"buckets\"\nscoop install git\n\n# Add buckets; these are like repos or ppa's for debian's apt package manager\nforeach ($bucket in $json.buckets) {\n    scoop bucket add $bucket\n}\n\n# Install all packages found in packages.json \nforeach ($pkg in $json.cli + $json.gui + $json.fonts) {\n    scoop install $pkg\n}\n```\n\nHere's an example of the `packages.json` file that contains the buckets and packages you want installed:&#x20;\n\n```json\n{\n  \"buckets\": [\"extras\", \"nerd-fonts\"],\n  \"cli\": [\"git\", \"nodejs\", \"lazygit\"],\n  \"gui\": [\"vscode\", \"discord\"],\n  \"fonts\": [\"FiraCode-NF\"]\n}  \n```\n\nAll said and done it's not too bad right? That's definitely cleaner than your typical linux dotfiles installer bash script where you have to add a bunch of custom repos and ppas for newer packages!&#x20;\n\n## Final thoughts&#x20;\n\nThis was one of those projects that I thought would take a lot longer than it did. From start to finish I had this up and running in around 45 minutes with no real issues to speak of! Funny how that works sometimes.\n\nIf you're looking to keep track of the apps you want on your windows computers and be able to easily automate the process of setting up a new machine, I definitely recommend you give scoop a shot.  &#x20;","html":"<p>I just setup a work laptop and and took the chance to look into what setting up a &quot;dotfiles&quot; for windows would look like. Specifically, what it would take to keep track of apps/tools that I'd need and automate the process of setup on Windows.</p><p>I've setup dotfiles on linux machines before, but never windows as I sort of just assumed that package manager just wasn't an option on Windows. I'd used NInite in the past but it's pretty limited on what exactly you can install; there are some common apps but it doesn't include everything I would want out-of-the gate for a work computer. </p><h2>Scoop: A package manager for windows </h2><p>Searching for &quot;Windows Package Manager&quot; immediately brings up <a href=\"https://scoop.sh\">scoop.sh</a>. This was exactly waht I was looking for and I was blown away it was literally the first thing I found. I had heard of Scoop previously (usually just as an install option in a README.md file) but had never really looked into it.  </p><p>Sccop is pretty cool and has a bunch of sweet features that make it easy to use: </p><ul><li>All apps are installed portably in your user directory; you don't need admin permissions for anything installed via scoop. </li><li>Scoop makes it really easy for developers to add their apps to the package manager. You can host your own scoop by adding a manifest file to any GItHub repo and you can add it into the official &quot;bucket&quot; by submitting a PR into one of the main scoop buckets. </li><li>You can install basically anything with scoop; My windows &quot;dotfiles&quot; install script automatically installs CLI apps like lazygit, node.js, golang, and eza while also installing desktop apps like vscode, and discord.     </li></ul><p>After seeing all scoop has to offer it seemd like a pretty easy choice for automating my windows setup.  </p><h3>A runner up: winget</h3><p>I also found winget, which is an official package manager for windows that's seen more adoption lately. It comes pre-installed with windows, has a direct integration with <a href=\"https://apps.microsoft.com\">the Microsoft Store</a>, and tends to have a larger share of closed-source and GUI applications.   </p><p>That said, It has a few drawbacks when compared to scoop. The big one for me was that I was <em>constantly</em> prompted for &quot;A Local Administrator&quot; via UAC when installing packages. While I can temporarily escalate into local admin if I need to, the process is annoying and not everyone has that option on a work laptop.</p><h2>Automating setup with scoop.sh + powershell</h2><p>From there, all we had to do was some Powershell scripting to get everything up and running. I'll include a simplified version of my script below, <a href=\"https://github.com/MykalMachon/dotfiles/tree/main/windows\">but here's a link to the repo if you want to take a look at the nitty gritty + some other terminal styling I do</a>. </p><pre><code class=\"language-powershell\"># Read a list of packages that I want to install via scoop$json = Get-Content &quot;$PSScriptRoot\\packages.json&quot; | ConvertFrom-Json# Set execution policy and install scoopSet-ExecutionPolicy RemoteSigned -Scope CurrentUser -Forceif (-not (Test-Path &quot;$env:USERPROFILE\\scoop&quot;)) {    Invoke-RestMethod get.scoop.sh | Invoke-Expression}# Install git first as it's a requirement for scoop's &quot;buckets&quot;scoop install git# Add buckets; these are like repos or ppa's for debian's apt package managerforeach ($bucket in $json.buckets) {    scoop bucket add $bucket}# Install all packages found in packages.json foreach ($pkg in $json.cli + $json.gui + $json.fonts) {    scoop install $pkg}</code></pre><p>Here's an example of the <code>packages.json</code> file that contains the buckets and packages you want installed: </p><pre><code class=\"language-json\">{  &quot;buckets&quot;: [&quot;extras&quot;, &quot;nerd-fonts&quot;],  &quot;cli&quot;: [&quot;git&quot;, &quot;nodejs&quot;, &quot;lazygit&quot;],  &quot;gui&quot;: [&quot;vscode&quot;, &quot;discord&quot;],  &quot;fonts&quot;: [&quot;FiraCode-NF&quot;]}  </code></pre><p>All said and done it's not too bad right? That's definitely cleaner than your typical linux dotfiles installer bash script where you have to add a bunch of custom repos and ppas for newer packages! </p><h2>Final thoughts </h2><p>This was one of those projects that I thought would take a lot longer than it did. From start to finish I had this up and running in around 45 minutes with no real issues to speak of! Funny how that works sometimes.</p><p>If you're looking to keep track of the apps you want on your windows computers and be able to easily automate the process of setting up a new machine, I definitely recommend you give scoop a shot.   </p>"}},{"id":"cool-tools-cli-edition","url":"https://mykalmachon.com/posts/cool-tools-cli-edition","meta":{"title":"Cool Tools: CLI Edition","pubDate":"2025-03-02T00:00:00.000Z","type":"post","draft":false,"tags":["tech"],"description":"Some CLI stuff I've been using lately."},"content":{"md":"I've noticed I'm spending more and more time with only a terminal open lately, with that, I've found quite a few handy little CLI apps, utilities, and even built setup a pretty shell theme.&#x20;\n\n## What my setup looks like\n\n![](@assets/posts/cool-tools-cli-edition/image.png)\n\nI use [zsh](https://www.zsh.org/) as my primary shell with [oh-my-zsh](https://ohmyz.sh/) and a [powerlevel10k](https://github.com/romkatv/powerlevel10k/tree/master) theme with the [Meslo NerdFont](https://github.com/romkatv/powerlevel10k/blob/master/font.md) to add all the fancy icons and ligatures into my command line. I've also been messing around with plugins in zsh but am still pretty new to how that works. All I've really tried is [the zsh autcomplete plugin](https://github.com/marlonrichert/zsh-autocomplete?tab=readme-ov-file#configuration) which has been a nice to have.\n\nAdmittedly, I don't do a whole lot with zsh that you couldn't do with bash, I just prefer the plugin ecosystem and themes available there over baseline bash.&#x20;\n\n## Some tools I've been using\n\n### [EZA](https://github.com/eza-community/eza)\n\nThis is like an updated version of \\`ls\\` that can do some pretty cool stuff like listing information about folders containing git repos, listing human readable file sizes by default, and rendering out file trees.&#x20;\n\nI also have this aliased to \\`ls\\` so I don't have to fight muscle memory at all.&#x20;\n\n### [LazyGit](https://github.com/jesseduffield/lazygit)\n\nI've only been using `lazygit` for a few weeks but I am already a huge fan. It provides a really solid view into your repo status, history, and staged files. On top of that, it makes things like rebasing easy as well and guides you through the process.&#x20;\n\n![a screenshot of the lazygit CLI showing a view of the git log for a main branch, as well as unstaged files, status, and commits](@assets/posts/cool-tools-cli-edition/lazygit-cli.png)\n\nMy favourite and most used feature for `lazygit` is using it to open new pull requests. If you open `lazygit` from a git repo directory, and hit \\`o\\` it will open a new PR from the currently checked out branch. *So easy.*\n\n### [LazyDocker](https://github.com/jesseduffield/lazydocker)\n\nthis is a similar application to `lazygit`, but this time for Docker! You can use this to either view all the containers, networks, and volumes machine wide or focus in on specific docker compose setups.&#x20;\n\n![A screenshot of the lazydocker ui reviewing a docker compose setup](@assets/posts/cool-tools-cli-edition/lazydocker-cli.png)\n\nI use this app at least once a week for wiping old and unused images, volumes, and networks. I also really like using `lazydocker` with my compose projects. It lets you see current proxied ports, logs, CPU/RAM usage, the current ENV, and stats for each of your service containers. This makes debugging environments and monitoring performance quite easy.&#x20;\n\n### [Superfile](https://superfile.netlify.app/getting-started/tutorial/)\n\n![a screenshot of the superfile CLI looking over a folder structure](@assets/posts/cool-tools-cli-edition/superfile-cli.png)\n\nthis is basically windows explorer, but good, and in your terminal. Superfile is fantastic at viewing file details, traversing in and out of folders when looking for something, sorting files by edit time or name, and copy/cut/delete-ing multiple files at once.&#x20;","html":"<p>I've noticed I'm spending more and more time with only a terminal open lately, with that, I've found quite a few handy little CLI apps, utilities, and even built setup a pretty shell theme. </p><h2>What my setup looks like</h2><p><img src=\"@assets/posts/cool-tools-cli-edition/image.png\" alt=\"\"></p><p>I use <a href=\"https://www.zsh.org/\">zsh</a> as my primary shell with <a href=\"https://ohmyz.sh/\">oh-my-zsh</a> and a <a href=\"https://github.com/romkatv/powerlevel10k/tree/master\">powerlevel10k</a> theme with the <a href=\"https://github.com/romkatv/powerlevel10k/blob/master/font.md\">Meslo NerdFont</a> to add all the fancy icons and ligatures into my command line. I've also been messing around with plugins in zsh but am still pretty new to how that works. All I've really tried is <a href=\"https://github.com/marlonrichert/zsh-autocomplete?tab=readme-ov-file#configuration\">the zsh autcomplete plugin</a> which has been a nice to have.</p><p>Admittedly, I don't do a whole lot with zsh that you couldn't do with bash, I just prefer the plugin ecosystem and themes available there over baseline bash. </p><h2>Some tools I've been using</h2><h3><a href=\"https://github.com/eza-community/eza\">EZA</a></h3><p>This is like an updated version of `ls` that can do some pretty cool stuff like listing information about folders containing git repos, listing human readable file sizes by default, and rendering out file trees. </p><p>I also have this aliased to `ls` so I don't have to fight muscle memory at all. </p><h3><a href=\"https://github.com/jesseduffield/lazygit\">LazyGit</a></h3><p>I've only been using <code>lazygit</code> for a few weeks but I am already a huge fan. It provides a really solid view into your repo status, history, and staged files. On top of that, it makes things like rebasing easy as well and guides you through the process. </p><p><img src=\"@assets/posts/cool-tools-cli-edition/lazygit-cli.png\" alt=\"a screenshot of the lazygit CLI showing a view of the git log for a main branch, as well as unstaged files, status, and commits\"></p><p>My favourite and most used feature for <code>lazygit</code> is using it to open new pull requests. If you open <code>lazygit</code> from a git repo directory, and hit `o` it will open a new PR from the currently checked out branch. <em>So easy.</em></p><h3><a href=\"https://github.com/jesseduffield/lazydocker\">LazyDocker</a></h3><p>this is a similar application to <code>lazygit</code>, but this time for Docker! You can use this to either view all the containers, networks, and volumes machine wide or focus in on specific docker compose setups. </p><p><img src=\"@assets/posts/cool-tools-cli-edition/lazydocker-cli.png\" alt=\"A screenshot of the lazydocker ui reviewing a docker compose setup\"></p><p>I use this app at least once a week for wiping old and unused images, volumes, and networks. I also really like using <code>lazydocker</code> with my compose projects. It lets you see current proxied ports, logs, CPU/RAM usage, the current ENV, and stats for each of your service containers. This makes debugging environments and monitoring performance quite easy. </p><h3><a href=\"https://superfile.netlify.app/getting-started/tutorial/\">Superfile</a></h3><p><img src=\"@assets/posts/cool-tools-cli-edition/superfile-cli.png\" alt=\"a screenshot of the superfile CLI looking over a folder structure\"></p><p>this is basically windows explorer, but good, and in your terminal. Superfile is fantastic at viewing file details, traversing in and out of folders when looking for something, sorting files by edit time or name, and copy/cut/delete-ing multiple files at once. </p>"}},{"id":"my-first-latte-leaf","url":"https://mykalmachon.com/posts/my-first-latte-leaf","meta":{"title":"My first latte leaf","pubDate":"2024-12-20T00:00:00.000Z","type":"photo","draft":false,"tags":["food","personal"],"description":"It only took 2 years of casual practice!","location":"the coffee bar"},"content":{"md":"It's not perfect, but I think I'm finally starting to get the latte art game down.\n\n![](@assets/posts/my-first-latte-leaf/PXL_20241214_232645912.jpg)\n\nI had built some bad habits early on that made it very hard for me to get any art down. Here are some adjustments I made in the last month that got me from white blob to leafy-heart-thing:&#x20;\n\n1. Get an automated milk steam wand. I don't know if my old steam wand was just bunk, but I could not get consistent milk out of it.\n2. Pour onto your espresso on a 40 degree angle. *Pouring onto a flat shot does not work*. This was a huge one for me.\n3. Start by slowly pouring 3-4 inches up from the cup at until you've used 1/3 to 1/2 of your milk. If you pour lower or faster you'll get a \"white-out\" (big white blob). &#x20;\n4. When you want to pour your art, bring it down to around half an inch above the middle of your cup and start pouring a bit faster while moving side to side. &#x20;\n\nI am no expert, so take this with a grain of salt as it's what's been working for me.&#x20;","html":"<p>It's not perfect, but I think I'm finally starting to get the latte art game down.</p><p><img src=\"@assets/posts/my-first-latte-leaf/PXL_20241214_232645912.jpg\" alt=\"\"></p><p>I had built some bad habits early on that made it very hard for me to get any art down. Here are some adjustments I made in the last month that got me from white blob to leafy-heart-thing: </p><ol><li>Get an automated milk steam wand. I don't know if my old steam wand was just bunk, but I could not get consistent milk out of it.</li><li>Pour onto your espresso on a 40 degree angle. <em>Pouring onto a flat shot does not work</em>. This was a huge one for me.</li><li>Start by slowly pouring 3-4 inches up from the cup at until you've used 1/3 to 1/2 of your milk. If you pour lower or faster you'll get a &quot;white-out&quot; (big white blob).  </li><li>When you want to pour your art, bring it down to around half an inch above the middle of your cup and start pouring a bit faster while moving side to side.  </li></ol><p>I am no expert, so take this with a grain of salt as it's what's been working for me. </p>"}},{"id":"debugging-crashing-container","url":"https://mykalmachon.com/posts/debugging-crashing-container","meta":{"title":"Debugging Crashing Containers","pubDate":"2024-12-07T00:00:00.000Z","type":"post","draft":false,"tags":["devops"],"description":"Use this little trick to figure out why your containers won't start"},"content":{"md":"I work with docker containers pretty much every day and debugging containers that crash immediately on startup has been a common headache for me. I came up with a little \"hack\" that has helped me with this problem so I thought I'd share it here.\n\n## The problem\n\nDebugging containers is relatively easy when they're up or online! You can open a shell in the container and peek around to see what's happening, just like you would on your machine. The following command will open up a simple shell in most containers:&#x20;\n\n```bash\ndocker compose exec -it /bin/sh my-job\n```\n\nThe problem is, if your container won't come up at all (or it immediately crashes when it does come up) you can't open a shell in it.&#x20;\n\n*This makes sense.* The container isn't online, so you can't run anything inside of it! Try it, and you'll get this message:&#x20;\n\n```bash\nservice \"my-job\" is not up\n```\n\n## My solution\n\nMy workaround is to replace the startup command for the container with the `sleep` command.\n\n```yml\nservices:\n  my-job: \n    container: my-container:local\n    command: sleep infinity; # this line will replace the startup command\n```\n\nInstead of starting as per the `entrypoint` or `command` that is set in the container, the container's main \"thread\" will go into sleep for an infinite amount of time. Usually you'd never want to do this. However, this will effectively keep your container online and \"healthy\" until you take it down yourself, which is exactly what we want!&#x20;\n\nThen you can just startup your container, open a shell, and start debugging:\n\n```bash\ndocker compose up my-job \ndocker compose exec -it /bin/sh my-job\n```","html":"<p>I work with docker containers pretty much every day and debugging containers that crash immediately on startup has been a common headache for me. I came up with a little &quot;hack&quot; that has helped me with this problem so I thought I'd share it here.</p><h2>The problem</h2><p>Debugging containers is relatively easy when they're up or online! You can open a shell in the container and peek around to see what's happening, just like you would on your machine. The following command will open up a simple shell in most containers: </p><pre><code class=\"language-bash\">docker compose exec -it /bin/sh my-job</code></pre><p>The problem is, if your container won't come up at all (or it immediately crashes when it does come up) you can't open a shell in it. </p><p><em>This makes sense.</em> The container isn't online, so you can't run anything inside of it! Try it, and you'll get this message: </p><pre><code class=\"language-bash\">service &quot;my-job&quot; is not up</code></pre><h2>My solution</h2><p>My workaround is to replace the startup command for the container with the <code>sleep</code> command.</p><pre><code class=\"language-yml\">services:  my-job:     container: my-container:local    command: sleep infinity; # this line will replace the startup command</code></pre><p>Instead of starting as per the <code>entrypoint</code> or <code>command</code> that is set in the container, the container's main &quot;thread&quot; will go into sleep for an infinite amount of time. Usually you'd never want to do this. However, this will effectively keep your container online and &quot;healthy&quot; until you take it down yourself, which is exactly what we want! </p><p>Then you can just startup your container, open a shell, and start debugging:</p><pre><code class=\"language-bash\">docker compose up my-job docker compose exec -it /bin/sh my-job</code></pre>"}},{"id":"dither-it","url":"https://mykalmachon.com/posts/dither-it","meta":{"title":"Dither It!","pubDate":"2024-11-09T00:00:00.000Z","type":"link","draft":false,"tags":["tech"],"description":"Make dithered images with this sweet lil web tool.","location":"The Internet","url":"https://ditherit.com/"},"content":{"md":"I've been using this site for a year or two to create my profile pictures and add some semi-ordered noise to pictures. I've had a few people ask how I do this now, and thankfully the answer is very easy! there's just a website that does it for you 🙂&#x20;\n\nEnjoy!","html":"<p>I've been using this site for a year or two to create my profile pictures and add some semi-ordered noise to pictures. I've had a few people ask how I do this now, and thankfully the answer is very easy! there's just a website that does it for you 🙂 </p><p>Enjoy!</p>"}},{"id":"bots-on-bluesky","url":"https://mykalmachon.com/posts/bots-on-bluesky","meta":{"title":"Bots on Bluesky","pubDate":"2024-11-03T00:00:00.000Z","type":"post","draft":false,"tags":["tech"],"description":"Making bots on Bluesky is super easy! ","location":"localhost"},"content":{"md":"I've been hanging out over on [Bluesky](https://bsky.app/profile/mykal.codes) the last few weeks. While bumming around on there, I found this library called [@skyware/bot](https://www.npmjs.com/package/@skyware/bot) and was surprised by how *simple* building out bots for the app is.\n\n## Minimum viable bot\n\nNo, seriously, it's so easy! All you need is a bit of JavaScript:&#x20;\n\n```javascript\nimport { Bot } from \"@skyware/bot\";\n\nconst bot = new Bot();\n\n// I would use an app password for this if you can!\n// app password: a temporary password you can create and revoke as needed.\nawait bot.login({ identifier: \"your-username\", password: \"your-password\" });\n\nbot.on(\"reply\", async (post) => {\n    await post.reply({ text: \"Thanks for replying to my post!\" });\n    await post.like();\n    await post.author.follow();\n})\n```\n\nThere ya go! you have a bot that:&#x20;\n\n* logs into your Bluesky account.\n* listens for replies to any of your posts.&#x20;\n* likes and responds to replies of your posts.\n* follows anyone who replies to your posts.\n\n## Dice rolling bot\n\nI also spent some time messing around to see if I could get the bot responding to private messages and built out a little bot that will roll a virtual dice for you over private messages.\n\nYou can [check the code out for it on my GitHub](https://github.com/MykalMachon/atbots/blob/main/main.js).&#x20;\n\n## Deploying the bot\n\nNot to harp on, but deploying these bots to [Railway](https://railway.app/) is comically simple. Just log in, create a project, create a repo service, add your username/password as environment variables, and you're good to go.&#x20;\n\nThat said, you could run these things on anything that's internet connected! The bot doesn't need to expose a web server or anything, it just sends API calls out to Bluesky. *You could host it on your freaking laptop if you want*- as long as your computer is online the bot should respond!&#x20;\n\n## Moving forward\n\nI'll probably build a bunch more features into my bot but I was so impressed by how easy this was that I figured I'd share early!\n\nI have a few ideas on how to build out the bot further like an article text-to-speech generator and maybe integrating an LLM to handle the function calling as opposed to strict commands like I have for the dice bot right now.","html":"<p>I've been hanging out over on <a href=\"https://bsky.app/profile/mykal.codes\">Bluesky</a> the last few weeks. While bumming around on there, I found this library called <a href=\"https://www.npmjs.com/package/@skyware/bot\">@skyware/bot</a> and was surprised by how <em>simple</em> building out bots for the app is.</p><h2>Minimum viable bot</h2><p>No, seriously, it's so easy! All you need is a bit of JavaScript: </p><pre><code class=\"language-javascript\">import { Bot } from &quot;@skyware/bot&quot;;const bot = new Bot();// I would use an app password for this if you can!// app password: a temporary password you can create and revoke as needed.await bot.login({ identifier: &quot;your-username&quot;, password: &quot;your-password&quot; });bot.on(&quot;reply&quot;, async (post) =&gt; {    await post.reply({ text: &quot;Thanks for replying to my post!&quot; });    await post.like();    await post.author.follow();})</code></pre><p>There ya go! you have a bot that: </p><ul><li>logs into your Bluesky account.</li><li>listens for replies to any of your posts. </li><li>likes and responds to replies of your posts.</li><li>follows anyone who replies to your posts.</li></ul><h2>Dice rolling bot</h2><p>I also spent some time messing around to see if I could get the bot responding to private messages and built out a little bot that will roll a virtual dice for you over private messages.</p><p>You can <a href=\"https://github.com/MykalMachon/atbots/blob/main/main.js\">check the code out for it on my GitHub</a>. </p><h2>Deploying the bot</h2><p>Not to harp on, but deploying these bots to <a href=\"https://railway.app/\">Railway</a> is comically simple. Just log in, create a project, create a repo service, add your username/password as environment variables, and you're good to go. </p><p>That said, you could run these things on anything that's internet connected! The bot doesn't need to expose a web server or anything, it just sends API calls out to Bluesky. <em>You could host it on your freaking laptop if you want</em>- as long as your computer is online the bot should respond! </p><h2>Moving forward</h2><p>I'll probably build a bunch more features into my bot but I was so impressed by how easy this was that I figured I'd share early!</p><p>I have a few ideas on how to build out the bot further like an article text-to-speech generator and maybe integrating an LLM to handle the function calling as opposed to strict commands like I have for the dice bot right now.</p>"}},{"id":"how-to-test-nginx-configs-locally","url":"https://mykalmachon.com/posts/how-to-test-nginx-configs-locally","meta":{"title":"How to test NGINX configs locally","pubDate":"2024-10-30T00:00:00.000Z","type":"post","draft":false,"tags":["devops"],"description":"How I quickly validate NGINX configs locally via the /etc/hosts file."},"content":{"md":"I've been on a bit of a tear building out an NGINX reverse-proxy and web server repo at work complete with a whole bunch of goodies (automated testing, deploy, monitoring, and log aggregation, oh my!).\n\nWith that in mind, I've been writing a lot of web server config lately for a whole bunch of services. I needed a good way to quickly validate configs locally without pushing to a test server or changing DNS records and I ended up finding one after a bit of tinkering. Imma talk about it here!\n\n## Why testing NGINX locally is a challenge\n\nImagine you're writing an nginx config for a web service called `someservice.machon.cloud`.\n\n```conf\nserver {\n    server_name someservice.machon.cloud;\n    listen 80;\n\n    return 301 https://$host$request_uri;\n}\n\nserver {\n    server_name someservice.machon.cloud;\n    listen 443 ssl;\n\n\t  # Default Headers (HSTS, Cache, etc)\n\t  include /etc/nginx/conf.d/default_headers.conf;\n\n\t  # SSL config\n    include /etc/nginx/conf.d/wildcard_ssl.conf;\n\n    # proxy the site out\n    location / {\n        proxy_set_header Host $http_host; \n        proxy_ssl_verify off; \n        proxy_pass https://internal.machon.cloud:4321;\n    }\n}\n```\n\nSweet! that looks good. It should proxy  `internal.machon.cloud:4321` and expose it through the domain `somservice.machon.cloud` on both port 80 and 443. So what's the problem?\n\nWell, *how do you verify that this actually works*? How are you confident you don't need to proxy some additional headers or information before you deploy?\n\n* NGINX will only serve the site if it sees traffic coming in for `someservice.machon.cloud`\n  * &#x20;if you spin in up and go to localhost:443 or localhost:80 you're not going to see what you expect.&#x20;\n  * You'll probably see the basic NGINX server page.\n* You can't point DNS records directly at your machine (unless you're running a public IP on your dev machine or something).&#x20;\n* So your options are then to deploy this change to your test server, then update your actual DNS records, wait for them to propagate, then test it?\n\n*no way!* what if you already have a service up on that subdomain? seems messy! I don't like it! I won't do it! What else can we do?\n\n## How to override DNS records locally\n\nIn walks the `/etc/hosts` file!  Open up your text editor of choice and load up your hosts file\n\n* If you're on windows, open up `C:\\Windows\\System32\\drivers\\etc\\hosts`.\n* If you're on linux/unix open up... well `/etc/hosts`. That's much easier to remember.\n\nOnce open, you can add a line to map the hostname you want to test with to localhost aka `127.0.0.1` for ipv4 and `::1` for ipv6.\n\n```/etc/hosts\n127.0.0.1  someservice.machon.cloud\n::1        someservice.machon.cloud\n```\n\nYup! it is that easy! Just save the hosts file and whenever you make a request to `somservice.machon.cloud` it'll direct that traffic to your local machine.\n\n## How to test your NGINX configs\n\nTo test your config and your service just spin up your NGINX instance with your new config locally (I do this with docker, but you could probably do this with an actual NGINX install too I guess).\n\nOnce NGINX is up, open `https://someservice.machon.cloud` in your browser and traffic should be forwarded to your local NGINX instance. You can confirm this by checking the NGINX access log for incoming traffic.\n\nYou can then quickly edit your config, and restart NGINX to pick up and test any new changes.\n\n*After you're done testing, either comment out or remove the lines we added to the hosts file.* This will stop forwarding traffic to your local machine and probably save you an hour or two of confusion as to why prod is down but only on your machine.\n\n> One note: you might get some HTTPS errors when testing, this is probably because you don't actually have a valid HTTPS certificate in place locally. This is totally fine and actually expected. In my opinion, that's something I'm comfortable validating in an actual test environment.\n\n## Why does this work?\n\nEssentially, the hosts file just maps IPs to hostnames but only for your machine. Before your machine makes a call out to DNS it checks the hosts file. If there's an entry that matches the domain in the hosts file, it will *always* use the mapped IP.\n\nSo what does that look like when you're actually make an HTTP call in an application?\n\n* Your application will make a call to the network layer requesting an IP for the domain `someservice.machon.cloud`\n* Your system checks the hosts file and finds  `someservice.machon.cloud` there.  Your system returns the mapped IP address to your application.\n* Your application would send traffic over to localhost:80 for http or localhost:443 for https.\n* Since NGINX is listening (or should be) to these ports it processes the traffic.\n* NGINX will recognize the domain and serve the right site!\n\nThis is obviously a pretty oversimplified explanation and there is a lot more going on. This is just explained to the level that I understand it. If this is terribly wrong, in this one case, please let me know. I'm actually sort of interested as to exactly how this works.\n\nHappy NGINX testing y'all!","html":"<p>I've been on a bit of a tear building out an NGINX reverse-proxy and web server repo at work complete with a whole bunch of goodies (automated testing, deploy, monitoring, and log aggregation, oh my!).</p><p>With that in mind, I've been writing a lot of web server config lately for a whole bunch of services. I needed a good way to quickly validate configs locally without pushing to a test server or changing DNS records and I ended up finding one after a bit of tinkering. Imma talk about it here!</p><h2>Why testing NGINX locally is a challenge</h2><p>Imagine you're writing an nginx config for a web service called <code>someservice.machon.cloud</code>.</p><pre><code class=\"language-conf\">server {    server_name someservice.machon.cloud;    listen 80;    return 301 https://$host$request_uri;}server {    server_name someservice.machon.cloud;    listen 443 ssl;\t  # Default Headers (HSTS, Cache, etc)\t  include /etc/nginx/conf.d/default_headers.conf;\t  # SSL config    include /etc/nginx/conf.d/wildcard_ssl.conf;    # proxy the site out    location / {        proxy_set_header Host $http_host;         proxy_ssl_verify off;         proxy_pass https://internal.machon.cloud:4321;    }}</code></pre><p>Sweet! that looks good. It should proxy  <code>internal.machon.cloud:4321</code> and expose it through the domain <code>somservice.machon.cloud</code> on both port 80 and 443. So what's the problem?</p><p>Well, <em>how do you verify that this actually works</em>? How are you confident you don't need to proxy some additional headers or information before you deploy?</p><ul><li>NGINX will only serve the site if it sees traffic coming in for <code>someservice.machon.cloud</code><ul><li> if you spin in up and go to localhost:443 or localhost:80 you're not going to see what you expect. </li><li>You'll probably see the basic NGINX server page.</li></ul></li><li>You can't point DNS records directly at your machine (unless you're running a public IP on your dev machine or something). </li><li>So your options are then to deploy this change to your test server, then update your actual DNS records, wait for them to propagate, then test it?</li></ul><p><em>no way!</em> what if you already have a service up on that subdomain? seems messy! I don't like it! I won't do it! What else can we do?</p><h2>How to override DNS records locally</h2><p>In walks the <code>/etc/hosts</code> file!  Open up your text editor of choice and load up your hosts file</p><ul><li>If you're on windows, open up <code>C:\\Windows\\System32\\drivers\\etc\\hosts</code>.</li><li>If you're on linux/unix open up... well <code>/etc/hosts</code>. That's much easier to remember.</li></ul><p>Once open, you can add a line to map the hostname you want to test with to localhost aka <code>127.0.0.1</code> for ipv4 and <code>::1</code> for ipv6.</p><pre><code class=\"language-/etc/hosts\">127.0.0.1  someservice.machon.cloud::1        someservice.machon.cloud</code></pre><p>Yup! it is that easy! Just save the hosts file and whenever you make a request to <code>somservice.machon.cloud</code> it'll direct that traffic to your local machine.</p><h2>How to test your NGINX configs</h2><p>To test your config and your service just spin up your NGINX instance with your new config locally (I do this with docker, but you could probably do this with an actual NGINX install too I guess).</p><p>Once NGINX is up, open <code>https://someservice.machon.cloud</code> in your browser and traffic should be forwarded to your local NGINX instance. You can confirm this by checking the NGINX access log for incoming traffic.</p><p>You can then quickly edit your config, and restart NGINX to pick up and test any new changes.</p><p><em>After you're done testing, either comment out or remove the lines we added to the hosts file.</em> This will stop forwarding traffic to your local machine and probably save you an hour or two of confusion as to why prod is down but only on your machine.</p><blockquote><p>One note: you might get some HTTPS errors when testing, this is probably because you don't actually have a valid HTTPS certificate in place locally. This is totally fine and actually expected. In my opinion, that's something I'm comfortable validating in an actual test environment.</p></blockquote><h2>Why does this work?</h2><p>Essentially, the hosts file just maps IPs to hostnames but only for your machine. Before your machine makes a call out to DNS it checks the hosts file. If there's an entry that matches the domain in the hosts file, it will <em>always</em> use the mapped IP.</p><p>So what does that look like when you're actually make an HTTP call in an application?</p><ul><li>Your application will make a call to the network layer requesting an IP for the domain <code>someservice.machon.cloud</code></li><li>Your system checks the hosts file and finds  <code>someservice.machon.cloud</code> there.  Your system returns the mapped IP address to your application.</li><li>Your application would send traffic over to localhost:80 for http or localhost:443 for https.</li><li>Since NGINX is listening (or should be) to these ports it processes the traffic.</li><li>NGINX will recognize the domain and serve the right site!</li></ul><p>This is obviously a pretty oversimplified explanation and there is a lot more going on. This is just explained to the level that I understand it. If this is terribly wrong, in this one case, please let me know. I'm actually sort of interested as to exactly how this works.</p><p>Happy NGINX testing y'all!</p>"}},{"id":"12-factor-apps","url":"https://mykalmachon.com/posts/12-factor-apps","meta":{"title":"The Twelve-Factor App","pubDate":"2024-08-09T00:00:00.000Z","type":"link","draft":false,"tags":["devops"],"url":"https://12factor.net/","description":""},"content":{"md":"I came across this site while doing a workshop on kubernetes a few months back. Much like \"[The Grug Brained Developer](https://grugbrain.dev/)\", I keep coming back to it every few weeks for a once over. The site has also been really useful in code-review as a solid set of principles.\n\nI'm a bit late to the party given that the site says it was last updated in 2017. The tools and services it suggests are a bit outdated for modern cloud hosts, and even most on-prem setups. Many of the concepts described on the site are also arguably tablestakes for a software developer in 2024. *[I know that my source code should all be tracked in git](https://12factor.net/codebase), duh!* or *yes, [I should have explicitly declared dependencies in a manifest](https://12factor.net/dependencies)...*\n\nDespite the obvious points, The content’s age and simplicity highlight the enduring value of these as foundational development principles. \n\n## Some good nuggets to get you started\n\nBacking services should always be implemented at a protocol level to avoid vendor lock-in and reduce coupling services in general. For example, you shouldn't have to rewrite model/service code because you're moving from upstash to a self-hosted Redis instance on k8s. Just use the protocol!\n\n> The code for a twelve-factor app makes no distinction between local and third party services. To the app, both are attached resources, accessed via a URL or other locator/credentials stored in the config [...] Likewise, a local SMTP server could be swapped with a third-party SMTP service (such as Postmark) without code changes. In both cases, only the resource handle in the config needs to change.\n> - [The Twelve-Factor App - Backing Services](https://12factor.net/backing-services)\n\nA little tidbit on using environment variables for config over hardcoded or even purpose-built config file formats. While I don't always agree with this, I do think it's a good default. For example, sometimes a remote secret store like Azure Key Vault is a better choice for centralized rotating secrets. \n\n> The twelve-factor app stores config in environment variables (often shortened to env vars or env). Env vars are easy to change between deploys without changing any code; unlike config files, there is little chance of them being checked into the code repo accidentally; and unlike custom config files, or other config mechanisms such as Java System Properties, they are a language- and OS-agnostic standard.\n> - [The Twelve-Factor App - Config](https://12factor.net/config)\n\nA point on good applications being stateless and executed as one or more process as quickly as possible. This design ethos makes your applications fault tolerant and in many cases horizontally and vertically scalable by default.\n\n> Twelve-factor processes are stateless and share-nothing. Any data that needs to persist must be stored in a stateful backing service, typically a database.\n> \n> The memory space or filesystem of the process can be used as a brief, single-transaction cache. For example, downloading a large file, operating on it, and storing the results of the operation in the database. The twelve-factor app never assumes that anything cached in memory or on disk will be available on a future request or job\n> \n> [...]\n> \n> Even when running only one process, a restart [...] will usually wipe out all local (e.g., memory and filesystem) state.\n> - [The Twelve-Factor App - Processes](https://12factor.net/processes)","html":"<p>I came across this site while doing a workshop on kubernetes a few months back. Much like &quot;<a href=\"https://grugbrain.dev/\">The Grug Brained Developer</a>&quot;, I keep coming back to it every few weeks for a once over. The site has also been really useful in code-review as a solid set of principles.</p><p>I'm a bit late to the party given that the site says it was last updated in 2017. The tools and services it suggests are a bit outdated for modern cloud hosts, and even most on-prem setups. Many of the concepts described on the site are also arguably tablestakes for a software developer in 2024. <em><a href=\"https://12factor.net/codebase\">I know that my source code should all be tracked in git</a>, duh!</em> or <em>yes, <a href=\"https://12factor.net/dependencies\">I should have explicitly declared dependencies in a manifest</a>...</em></p><p>Despite the obvious points, The content’s age and simplicity highlight the enduring value of these as foundational development principles.</p><h2>Some good nuggets to get you started</h2><p>Backing services should always be implemented at a protocol level to avoid vendor lock-in and reduce coupling services in general. For example, you shouldn't have to rewrite model/service code because you're moving from upstash to a self-hosted Redis instance on k8s. Just use the protocol!</p><blockquote><p>The code for a twelve-factor app makes no distinction between local and third party services. To the app, both are attached resources, accessed via a URL or other locator/credentials stored in the config [...] Likewise, a local SMTP server could be swapped with a third-party SMTP service (such as Postmark) without code changes. In both cases, only the resource handle in the config needs to change.</p><ul><li><a href=\"https://12factor.net/backing-services\">The Twelve-Factor App - Backing Services</a></li></ul></blockquote><p>A little tidbit on using environment variables for config over hardcoded or even purpose-built config file formats. While I don't always agree with this, I do think it's a good default. For example, sometimes a remote secret store like Azure Key Vault is a better choice for centralized rotating secrets.</p><blockquote><p>The twelve-factor app stores config in environment variables (often shortened to env vars or env). Env vars are easy to change between deploys without changing any code; unlike config files, there is little chance of them being checked into the code repo accidentally; and unlike custom config files, or other config mechanisms such as Java System Properties, they are a language- and OS-agnostic standard.</p><ul><li><a href=\"https://12factor.net/config\">The Twelve-Factor App - Config</a></li></ul></blockquote><p>A point on good applications being stateless and executed as one or more process as quickly as possible. This design ethos makes your applications fault tolerant and in many cases horizontally and vertically scalable by default.</p><blockquote><p>Twelve-factor processes are stateless and share-nothing. Any data that needs to persist must be stored in a stateful backing service, typically a database.</p><p>The memory space or filesystem of the process can be used as a brief, single-transaction cache. For example, downloading a large file, operating on it, and storing the results of the operation in the database. The twelve-factor app never assumes that anything cached in memory or on disk will be available on a future request or job</p><p>[...]</p><p>Even when running only one process, a restart [...] will usually wipe out all local (e.g., memory and filesystem) state.</p><ul><li><a href=\"https://12factor.net/processes\">The Twelve-Factor App - Processes</a></li></ul></blockquote>"}},{"id":"picosnake-v1","url":"https://mykalmachon.com/posts/picosnake-v1","meta":{"title":"PICO-Snake","pubDate":"2024-06-01T00:00:00.000Z","type":"post","draft":false,"tags":["tech"],"description":"A snake clone I put together in PICO-8"},"content":{"md":"<iframe style=\"height:600px;\" src=\"/games/picosnake/picosnake.html\"></iframe>\n\nOne of the first \"technical\" things I remember doing on a computer was [making games in GameMaker 8 while watching old-school youtube tutorials](https://www.youtube.com/watch?v=ij9v1OF5VRY). Playing and making video games was *largely* how I got into programming in the first place. The first \"real\" website I ever made was an Assassins Creed fansite I built for my grade 7 computers class; It was written in PHP and was hosted on a windows server our teacher set up.\n\nThat said, I pretty much write ETL jobs, integrations, web services, and internal tooling at work these days. While that's all a farcry (😉) from game development, my want to make games never really went away. I've dabbled in gamedev over the years and recently I've been messing around with [PICO-8](https://www.lexaloffle.com/pico-8.php) and threw together this little snake clone. \n\n## What is PICO-8?\n\nTheir site says it better than I probably could!\n\n> PICO-8 is a fantasy console for making, sharing and playing tiny games and other computer programs. It feels like a regular console, but runs on Windows / Mac / Linux. When you turn it on, the machine greets you with a commandline, a suite of cartridge creation tools, and an online cartridge browser called SPLORE.\n> - <figcaption><a href=\"https://www.lexaloffle.com/pico-8.php\">lexalofle.com</a></figcaption>\n\nI'd recommend checking their site out if you're interested. You can play a bunch of games other's have made on the site and there's a bunch more information. \n\nI'll also probably write a bit more about PICO-8 and why I find it so engaging in the future. I think it comes down to constraints. You get such a paired-down environment to work in that you learn lots and have to keep your scope small which, for me, means I can actually complete my little game ideas without running out of steam.","html":"<p>&lt;iframe style=&quot;height:600px;&quot; src=&quot;/games/picosnake/picosnake.html&quot;&gt;&lt;/iframe&gt;</p><p>One of the first &quot;technical&quot; things I remember doing on a computer was <a href=\"https://www.youtube.com/watch?v=ij9v1OF5VRY\">making games in GameMaker 8 while watching old-school youtube tutorials</a>. Playing and making video games was <em>largely</em> how I got into programming in the first place. The first &quot;real&quot; website I ever made was an Assassins Creed fansite I built for my grade 7 computers class; It was written in PHP and was hosted on a windows server our teacher set up.</p><p>That said, I pretty much write ETL jobs, integrations, web services, and internal tooling at work these days. While that's all a farcry (😉) from game development, my want to make games never really went away. I've dabbled in gamedev over the years and recently I've been messing around with <a href=\"https://www.lexaloffle.com/pico-8.php\">PICO-8</a> and threw together this little snake clone.</p><h2>What is PICO-8?</h2><p>Their site says it better than I probably could!</p><blockquote><p>PICO-8 is a fantasy console for making, sharing and playing tiny games and other computer programs. It feels like a regular console, but runs on Windows / Mac / Linux. When you turn it on, the machine greets you with a commandline, a suite of cartridge creation tools, and an online cartridge browser called SPLORE.</p><ul><li>&lt;figcaption&gt;&lt;a href=&quot;https://www.lexaloffle.com/pico-8.php&quot;&gt;lexalofle.com&lt;/a&gt;&lt;/figcaption&gt;</li></ul></blockquote><p>I'd recommend checking their site out if you're interested. You can play a bunch of games other's have made on the site and there's a bunch more information.</p><p>I'll also probably write a bit more about PICO-8 and why I find it so engaging in the future. I think it comes down to constraints. You get such a paired-down environment to work in that you learn lots and have to keep your scope small which, for me, means I can actually complete my little game ideas without running out of steam.</p>"}},{"id":"ssh-in-vscode","url":"https://mykalmachon.com/posts/ssh-in-vscode","meta":{"title":"Using VS Code as an SSH Client","pubDate":"2024-04-23T00:00:00.000Z","type":"link","draft":false,"tags":["tech"],"description":"Need to edit files on your server? Just use VS Code.","url":"https://code.visualstudio.com/docs/remote/ssh"},"content":{"md":"[Today I learned you can literally use Visual Studio Code as an SSH client](https://code.visualstudio.com/docs/remote/ssh). You just enter the server address, login, use the built-in terminal, open a folder as a project, and edit all your files as if they were on your machine normally.\n\n*I don't know how I missed this for so long.* I knew that you could use SSH to connect to remote machines or hosts in VS Code, but I always assumed that you had to login and install a \"headless\" version of VS Code ahead of time on the server or that it would somehow be a pain to setup and configure.\n\nNope! you literally just need to: \n\n1. install the remote ssh extension in VS Code\n2. hit `ctrl + shift + p` to open your VS Code command pallette and type \"add new ssh host\". \n3. follow the prompts and sign in as you usually would via ssh. (I just used my existing keys for auth)\n4. *bam*. you're logged into your remote host and can do all the ssh + file editing you need.\n\nI'll probably use this with VMs at work instead of `ssh`ing in and using bare `vi` to edit everything. It should be much nicer to just spint it up in VS Code with all my normal short cuts and bindings.  \n\n## Side note for container nerds\n\nIn writing this I thought of some tech with similar vibes for containers.\n\n[You can hook VS Code into running docker containers and kubernetes environments to achieve a similar result](https://code.visualstudio.com/docs/devcontainers/attach-container#:~:text=To%20attach%20to%20a%20Docker,you%20want%20to%20connect%20to.). I've been doing this for quite awhile and it's been useful for debugging from inside your container's and their virtual network.\n\n[Dev containers are also cool for spinning up ephemeral dev environments.](https://code.visualstudio.com/docs/devcontainers/create-dev-container) Imagine definining your whole dev environment in a dockerfile that VS Code can automatically detect and boot you into every time you open your project.","html":"<p><a href=\"https://code.visualstudio.com/docs/remote/ssh\">Today I learned you can literally use Visual Studio Code as an SSH client</a>. You just enter the server address, login, use the built-in terminal, open a folder as a project, and edit all your files as if they were on your machine normally.</p><p><em>I don't know how I missed this for so long.</em> I knew that you could use SSH to connect to remote machines or hosts in VS Code, but I always assumed that you had to login and install a &quot;headless&quot; version of VS Code ahead of time on the server or that it would somehow be a pain to setup and configure.</p><p>Nope! you literally just need to:</p><ol><li>install the remote ssh extension in VS Code</li><li>hit <code>ctrl + shift + p</code> to open your VS Code command pallette and type &quot;add new ssh host&quot;.</li><li>follow the prompts and sign in as you usually would via ssh. (I just used my existing keys for auth)</li><li><em>bam</em>. you're logged into your remote host and can do all the ssh + file editing you need.</li></ol><p>I'll probably use this with VMs at work instead of <code>ssh</code>ing in and using bare <code>vi</code> to edit everything. It should be much nicer to just spint it up in VS Code with all my normal short cuts and bindings.</p><h2>Side note for container nerds</h2><p>In writing this I thought of some tech with similar vibes for containers.</p><p><a href=\"https://code.visualstudio.com/docs/devcontainers/attach-container#:~:text=To%20attach%20to%20a%20Docker,you%20want%20to%20connect%20to.\">You can hook VS Code into running docker containers and kubernetes environments to achieve a similar result</a>. I've been doing this for quite awhile and it's been useful for debugging from inside your container's and their virtual network.</p><p><a href=\"https://code.visualstudio.com/docs/devcontainers/create-dev-container\">Dev containers are also cool for spinning up ephemeral dev environments.</a> Imagine definining your whole dev environment in a dockerfile that VS Code can automatically detect and boot you into every time you open your project.</p>"}},{"id":"building-slash-follows","url":"https://mykalmachon.com/posts/building-slash-follows","meta":{"title":"Building /follows","pubDate":"2024-03-29T00:00:00.000Z","type":"post","draft":false,"tags":["webdev"],"description":"A page the highlights who's blogs I'm following"},"content":{"md":"[I built out a `/follows` page that lists all the RSS feeds I'm subscribed too](/follows). Thankfully, Astro and Commafeed made this very easy to build out.\nAll I needed to do was: \n1. figure out how to programmatically pull out the blogs I follow from commafeed (my feed reader) \n2. pull the blogs I follow but into an Astro page and create a basic template to display it.  \n3. git commit, do a lil pull request, merge into main, and wait for my deploy to Railway to finish.\n4. Finished! \n\n> ℹ️ Note \n> \n> This was inspired by the follows page over on [Kevin Kipp's blog](https://kevinkipp.com/follows/) where he highlights all the blogs he follows via RSS.\n\n## Pulling the data out of commafeed \n\nI self host an RSS feed reader called commafeed. When I started working on this I just sort of *assumed* they had an API and thankfully they did... It just wasn't a conventional REST API! It's something called a \"fever\" API.\n\nCommafeed gives you an \"API Url\" but that's about it as far as documentation goes for it.\n\n### What's a fever API?\n\nI had absolutely 0 idea what a fever API was so I did a bit of searching. Turns out, once upon a time, there was a RSS feed aggregator called \"fever\" and their API spec seems to have become a defacto unwritten standard for interacting with a feed aggregator. \n\nOn this wild gooes chase, I found a myriad of github pages, README's, and other applications documenting their own fever API integrations. In that mess of pages, [I found this](https://github.com/DigitalDJ/tinytinyrss-fever-plugin/blob/master/fever-api.md). It claims to include a complete copy of the API spec from fever's site (which is now timing out; making this a surviving copy of that lost info).\n\n### Fever API Authentication \n\nIt looks like to \"authenticate\" against a fever API, you have to put your username and password together in the format of `${user}:${pass}` then md5 hash them. You then POST   that string over in the body labelled as `api_key`. \n\nWhile this is definitely a weird user of the `POST` verb that's not too hard! Here's some code I used to verify I could authenticate in node.js \n\n```js\nconst crypto = await import('crypto')\n\n// set username and API key from commafeed  \nconst username = \"user\"\nconst apiKey = \"some-api-key-goes-here\" // you can get this in your commafeed profile\n\n// hash user and \"password\" together in an md5 hash \nconst authStr = `${username}:${apiKey}`\nconst authHash = crypto.createHash('md5').update(authStr).digest('hex')\n\n// make a call to the API \nconst res = await fetch(`https://rss.tbx.sh/rest/fever/user/3?api_key=${authHash}`, {\n  method: 'POST',\n  headers: {\n    'Content-Type': 'application/x-www-form-urlencoded'\n  }\n})\nconst data = await res.json();\n\nconsole.log(data);\n/*\n// here's what the response looks like \n{\n  \"api_version\": 3,\n  \"auth\": 1,\n  \"last_refreshed_on_time\": 1711829713\n}\n// the auth being set to 1 means we are authenticated. \n*/\n```\n\n### Getting feeds from the Fever API\n\nWith authentication figured out, I had to figure out how to get some posts. Thankfully, the docs we have on hand make that pretty obvious as well. It seems all we had to do was slightly alter the URL in our code above from `https://rss.tbx.sh/rest/fever/user/3?api_key=${authHash}` to `https://rss.tbx.sh/rest/fever/user/3?api_key=${authHash}&feeds` aka just add `&feeds` to the end 😎 \n\nIt seems fever APIs mostly return objects based on the query parameters on the URL and usually all live at a singular \"path\". \n\n\n## Moving it to the blog\n\nAwesome, so now we know how to authenticate and get feeds, let's move it into the blog.\n\n- create a `follows.astro` page \n- copy-paste that code from above and adapt it to Astro\n- add some types\n- add some markup \n\n```astro\n---\nimport crypto from 'node:crypto';\nimport Layout from \"@components/layout/Layout.astro\";\n\n// types for the fever data \ntype FeedResponse = {\n  api_version: number,\n  auth: 0 | 1,\n  last_refreshed_on_time: number,\n  feeds: Feed[],\n  feed_groups: [\n    {\n      group_id: number,\n      feeds: string\n    }\n  ]\n}\n\ntype Feed = {\n  id: number,\n  favico_id: number,\n  title: string,\n  url: string,\n  site_url: string,\n  is_spark: number;\n  last_updated_on_time: number;\n}\n\nconst username = import.meta.env.RSS_USERNAME\nconst apiKey = import.meta.env.RSS_API_KEY;\nconst apiUrl = import.meta.env.RSS_API_URL;\nconst authStr = `${username}:${apiKey}`\nconst authHash = crypto.createHash('md5').update(authStr).digest('hex')\n\n// make a call to the API \nconst res = await fetch(`${apiUrl}?api_key=${authHash}&feeds`, {\n  method: 'POST',\n  headers: {\n    'Content-Type': 'application/x-www-form-urlencoded'\n  }\n})\nconst data: FeedResponse = await res.json();\n\nconsole.log(data);\n---\n\n<Layout title=\"Follows\" description=\"the blogs I follow\">\n  <h1>Follows</h1>\n  <p>Below are the blogs I'm currently following.</p>\n  <ul>\n  {data.feeds.map((feed: Feed) => {\n    return (\n      <li>\n        <a href={feed.site_url}>{feed.title}</a>\n      </li>\n    )\n  })}\n  </ul>\n</Layout>\n```\n\n### Cleaning up `site_url`'s\n\nThe `site_url` key in the feed object is *supposed* to reference a URL with human readable content (i.e a home or posts page). However, some of the feeds I follow just set their RSS url in this feed again. Since I want to use this link to point visitors to sites, I want to normalize these URLs. The easiest way I could think of doing this is just passing `site_url` int the `new URL()` constructor and returning the origin from the returned URL object. \n\n\n```astro\n---\n// getting the data up here\n\nconst cleanSiteUrl = (url: string) => {\n  try {\n    const urlObj = new URL(url);\n    return urlObj.origin;\n  } catch(err){\n    return url\n  }\n}\n---\n<!-- some markup -->\n<ul>\n  {data.feeds.map((feed: Feed) => {\n    return (\n      <li>\n        <a href={cleanSiteUrl(feed.site_url)}>{feed.title}</a>\n      </li>\n    )\n  })}\n</ul>\n<!-- some other markup -->\n```\n\n### Adding favicons \n\nNow that I had some data on the page, I realized it would probably be nice to have each blog's favicon besides the name. Thankfully, the fever API exposes an endpoint to fetch all the favicons as well. \n\nIn testing, it looks like the favicon endpoint returns all the favicons in a raw base64 string and takes around 5-6 seconds to return a response (yikes). With that in mind I \n\n- rewrote some of our data fetching to run in parallel.\n  - This should speed things up cause it'll fetch both the feed and favicons at the same time instead of one after the other. \n- set the page up to use astro's prerendering feature. \n  - the page is built into a static files on the site's release instead of being server-rendered on each request. \n  - The main downside with this approach is that the data on the page will often be stale as it only pulls in new data on site build. My follows don't change much though so I'm okay with that.\n- build out some types so I stop getting implicit any errors \n\n```astro\n---\n// this makes it so it only builds this page once\nexport const prerender = true\n\n// imports go here \n// types for the fever data\ntype FeedResponse = {\n  api_version: number;\n  auth: 0 | 1;\n  last_refreshed_on_time: number;\n  feeds: Feed[];\n  feed_groups: [\n    {\n      group_id: number;\n      feeds: string;\n    },\n  ];\n};\n\ntype FaviconResponse = {\n  api_version: number;\n  auth: 0 | 1;\n  last_refreshed_on_time: number;\n  favicons: Favicon[];\n};\n\ntype Feed = {\n  id: number;\n  favicon_id: number;\n  title: string;\n  url: string;\n  site_url: string;\n  is_spark: number;\n  last_updated_on_time: number;\n};\n\ntype Favicon = {\n  id: number;\n  data: string;\n};\n\n// auth hash creation goes here \n\n// get feeds api\nlet feedData: FeedResponse | null = null;\nlet faviconData: FaviconResponse | null = null;\n\ntry {\n  const feedRes = fetch(`${apiUrl}?api_key=${authHash}&feeds`, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/x-www-form-urlencoded',\n    },\n  });\n  const faviconRes = fetch(`${apiUrl}?api_key=${authHash}&favicons`, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/x-www-form-urlencoded',\n    },\n  });\n  \n  const results = await Promise.allSettled([feedRes, faviconRes]);\n  \n  if(results[0].status === 'rejected' || results[1].status === 'rejected') {\n    throw new Error('failed to fetch feeds');\n  }\n\n  feedData = await results[0].value.json();\n  faviconData = await results[1].value.json();\n} catch (err) {\n  console.error('failed to fetch feeds')\n  console.error(err);\n}\n\n// clean site url function goes here\n---\n\n<Layout title=\"Follows\" description=\"the blogs I follow\">\n  <h1>Follows</h1>\n  <p>\n    This page lists all of the blogs, newsletters, and peeps that I follow\n    across the internet in my RSS feed aggregator. I <a\n      href=\"/posts/building-slash-follows\">wrote a post on how I built this</a\n    > if you're interested.\n  </p>\n  <ul>\n    { feedData != null && faviconData != null && \n      feedData.feeds.map((feed: Feed) => {\n        return (\n          <li>\n            <img src={faviconData.favicons.find(({id}) => id === feed.favicon_id)?.data} />\n            <a href={cleanSiteUrl(feed.site_url)}>{feed.title}</a>\n          </li>\n        );\n      })\n    }\n  </ul>\n</Layout>\n```\n\nWith that, we've got the page pre-rendering once per build, got some types in place, and have favicon's beside the feed titles and links.\n\n### Puttin' some paint on it\n\nRight now the page is super basic. The images are huge, all my follows are just in a big boring list, so let's do some stuff to clean it up: \n\n- put follows in a 2-column grid layout.  \n- cleanup the layout for each follow.\n  - the image / title should be center aligned \n  - the images are appropriately sized\n- use a little trick to make the whole area of the li element clickable as of it was an anchor tag \n  - can do this with a little `a:after` trick in the code below. \n\n```astro\n---\n// all the build logic and data fetching goes here\n---\n<Layout title=\"Follows\" description=\"the blogs I follow\">\n  <ul>\n    { feedData != null && faviconData != null && \n      feedData.feeds.map((feed: Feed) => {\n        return (\n          <li>\n            <img src={faviconData.favicons.find(({id}) => id === feed.favicon_id)?.data} />\n            <a href={cleanSiteUrl(feed.site_url)}>{feed.title}</a>\n          </li>\n        );\n      })\n    }\n  </ul>\n</Layout>\n\n<style>\n  ul {\n    display: grid;\n    list-style-type: none;\n    grid-template-columns: 1fr 1fr;\n    padding: 0;\n  }\n  li {\n    position: relative;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: var(--size-2);\n    padding: var(--size-1) var(--size-2);\n    transform-origin: center;\n    border: 1px solid rgba(0, 0, 0, 0);\n    border-left: 1px solid var(--paper-3);\n    transition:\n      transform 0.2s ease,\n      background 0.2s ease;\n  }\n  li:hover,\n  li:focus-within {\n    background: var(--paper-3);\n    transform: scale(1.05);\n    border: 1px solid var(--accent-1);\n    z-index: var(--layer-5);\n  }\n  li img {\n    width: 24px;\n    height: 24px;\n    border-radius: 50%;\n  }\n  li a:after {\n    content: '';\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n  }\n</style>\n```\n\nWith that, [we've got the page all put together](/follows) 🎉","html":"<p><a href=\"/follows\">I built out a <code>/follows</code> page that lists all the RSS feeds I'm subscribed too</a>. Thankfully, Astro and Commafeed made this very easy to build out.All I needed to do was:</p><ol><li>figure out how to programmatically pull out the blogs I follow from commafeed (my feed reader)</li><li>pull the blogs I follow but into an Astro page and create a basic template to display it.</li><li>git commit, do a lil pull request, merge into main, and wait for my deploy to Railway to finish.</li><li>Finished!</li></ol><blockquote><p>ℹ️ Note</p><p>This was inspired by the follows page over on <a href=\"https://kevinkipp.com/follows/\">Kevin Kipp's blog</a> where he highlights all the blogs he follows via RSS.</p></blockquote><h2>Pulling the data out of commafeed</h2><p>I self host an RSS feed reader called commafeed. When I started working on this I just sort of <em>assumed</em> they had an API and thankfully they did... It just wasn't a conventional REST API! It's something called a &quot;fever&quot; API.</p><p>Commafeed gives you an &quot;API Url&quot; but that's about it as far as documentation goes for it.</p><h3>What's a fever API?</h3><p>I had absolutely 0 idea what a fever API was so I did a bit of searching. Turns out, once upon a time, there was a RSS feed aggregator called &quot;fever&quot; and their API spec seems to have become a defacto unwritten standard for interacting with a feed aggregator.</p><p>On this wild gooes chase, I found a myriad of github pages, README's, and other applications documenting their own fever API integrations. In that mess of pages, <a href=\"https://github.com/DigitalDJ/tinytinyrss-fever-plugin/blob/master/fever-api.md\">I found this</a>. It claims to include a complete copy of the API spec from fever's site (which is now timing out; making this a surviving copy of that lost info).</p><h3>Fever API Authentication</h3><p>It looks like to &quot;authenticate&quot; against a fever API, you have to put your username and password together in the format of <code>${user}:${pass}</code> then md5 hash them. You then POST   that string over in the body labelled as <code>api_key</code>.</p><p>While this is definitely a weird user of the <code>POST</code> verb that's not too hard! Here's some code I used to verify I could authenticate in node.js</p><pre><code class=\"language-js\">const crypto = await import('crypto')// set username and API key from commafeed  const username = &quot;user&quot;const apiKey = &quot;some-api-key-goes-here&quot; // you can get this in your commafeed profile// hash user and &quot;password&quot; together in an md5 hash const authStr = `${username}:${apiKey}`const authHash = crypto.createHash('md5').update(authStr).digest('hex')// make a call to the API const res = await fetch(`https://rss.tbx.sh/rest/fever/user/3?api_key=${authHash}`, {  method: 'POST',  headers: {    'Content-Type': 'application/x-www-form-urlencoded'  }})const data = await res.json();console.log(data);/*// here's what the response looks like {  &quot;api_version&quot;: 3,  &quot;auth&quot;: 1,  &quot;last_refreshed_on_time&quot;: 1711829713}// the auth being set to 1 means we are authenticated. */</code></pre><h3>Getting feeds from the Fever API</h3><p>With authentication figured out, I had to figure out how to get some posts. Thankfully, the docs we have on hand make that pretty obvious as well. It seems all we had to do was slightly alter the URL in our code above from <code>https://rss.tbx.sh/rest/fever/user/3?api_key=${authHash}</code> to <code>https://rss.tbx.sh/rest/fever/user/3?api_key=${authHash}&amp;feeds</code> aka just add <code>&amp;feeds</code> to the end 😎</p><p>It seems fever APIs mostly return objects based on the query parameters on the URL and usually all live at a singular &quot;path&quot;.</p><h2>Moving it to the blog</h2><p>Awesome, so now we know how to authenticate and get feeds, let's move it into the blog.</p><ul><li>create a <code>follows.astro</code> page</li><li>copy-paste that code from above and adapt it to Astro</li><li>add some types</li><li>add some markup</li></ul><pre><code class=\"language-astro\">---import crypto from 'node:crypto';import Layout from &quot;@components/layout/Layout.astro&quot;;// types for the fever data type FeedResponse = {  api_version: number,  auth: 0 | 1,  last_refreshed_on_time: number,  feeds: Feed[],  feed_groups: [    {      group_id: number,      feeds: string    }  ]}type Feed = {  id: number,  favico_id: number,  title: string,  url: string,  site_url: string,  is_spark: number;  last_updated_on_time: number;}const username = import.meta.env.RSS_USERNAMEconst apiKey = import.meta.env.RSS_API_KEY;const apiUrl = import.meta.env.RSS_API_URL;const authStr = `${username}:${apiKey}`const authHash = crypto.createHash('md5').update(authStr).digest('hex')// make a call to the API const res = await fetch(`${apiUrl}?api_key=${authHash}&amp;feeds`, {  method: 'POST',  headers: {    'Content-Type': 'application/x-www-form-urlencoded'  }})const data: FeedResponse = await res.json();console.log(data);---&lt;Layout title=&quot;Follows&quot; description=&quot;the blogs I follow&quot;&gt;  &lt;h1&gt;Follows&lt;/h1&gt;  &lt;p&gt;Below are the blogs I'm currently following.&lt;/p&gt;  &lt;ul&gt;  {data.feeds.map((feed: Feed) =&gt; {    return (      &lt;li&gt;        &lt;a href={feed.site_url}&gt;{feed.title}&lt;/a&gt;      &lt;/li&gt;    )  })}  &lt;/ul&gt;&lt;/Layout&gt;</code></pre><h3>Cleaning up <code>site_url</code>'s</h3><p>The <code>site_url</code> key in the feed object is <em>supposed</em> to reference a URL with human readable content (i.e a home or posts page). However, some of the feeds I follow just set their RSS url in this feed again. Since I want to use this link to point visitors to sites, I want to normalize these URLs. The easiest way I could think of doing this is just passing <code>site_url</code> int the <code>new URL()</code> constructor and returning the origin from the returned URL object.</p><pre><code class=\"language-astro\">---// getting the data up hereconst cleanSiteUrl = (url: string) =&gt; {  try {    const urlObj = new URL(url);    return urlObj.origin;  } catch(err){    return url  }}---&lt;!-- some markup --&gt;&lt;ul&gt;  {data.feeds.map((feed: Feed) =&gt; {    return (      &lt;li&gt;        &lt;a href={cleanSiteUrl(feed.site_url)}&gt;{feed.title}&lt;/a&gt;      &lt;/li&gt;    )  })}&lt;/ul&gt;&lt;!-- some other markup --&gt;</code></pre><h3>Adding favicons</h3><p>Now that I had some data on the page, I realized it would probably be nice to have each blog's favicon besides the name. Thankfully, the fever API exposes an endpoint to fetch all the favicons as well.</p><p>In testing, it looks like the favicon endpoint returns all the favicons in a raw base64 string and takes around 5-6 seconds to return a response (yikes). With that in mind I</p><ul><li>rewrote some of our data fetching to run in parallel.<ul><li>This should speed things up cause it'll fetch both the feed and favicons at the same time instead of one after the other.</li></ul></li><li>set the page up to use astro's prerendering feature.<ul><li>the page is built into a static files on the site's release instead of being server-rendered on each request.</li><li>The main downside with this approach is that the data on the page will often be stale as it only pulls in new data on site build. My follows don't change much though so I'm okay with that.</li></ul></li><li>build out some types so I stop getting implicit any errors</li></ul><pre><code class=\"language-astro\">---// this makes it so it only builds this page onceexport const prerender = true// imports go here // types for the fever datatype FeedResponse = {  api_version: number;  auth: 0 | 1;  last_refreshed_on_time: number;  feeds: Feed[];  feed_groups: [    {      group_id: number;      feeds: string;    },  ];};type FaviconResponse = {  api_version: number;  auth: 0 | 1;  last_refreshed_on_time: number;  favicons: Favicon[];};type Feed = {  id: number;  favicon_id: number;  title: string;  url: string;  site_url: string;  is_spark: number;  last_updated_on_time: number;};type Favicon = {  id: number;  data: string;};// auth hash creation goes here // get feeds apilet feedData: FeedResponse | null = null;let faviconData: FaviconResponse | null = null;try {  const feedRes = fetch(`${apiUrl}?api_key=${authHash}&amp;feeds`, {    method: 'POST',    headers: {      'Content-Type': 'application/x-www-form-urlencoded',    },  });  const faviconRes = fetch(`${apiUrl}?api_key=${authHash}&amp;favicons`, {    method: 'POST',    headers: {      'Content-Type': 'application/x-www-form-urlencoded',    },  });    const results = await Promise.allSettled([feedRes, faviconRes]);    if(results[0].status === 'rejected' || results[1].status === 'rejected') {    throw new Error('failed to fetch feeds');  }  feedData = await results[0].value.json();  faviconData = await results[1].value.json();} catch (err) {  console.error('failed to fetch feeds')  console.error(err);}// clean site url function goes here---&lt;Layout title=&quot;Follows&quot; description=&quot;the blogs I follow&quot;&gt;  &lt;h1&gt;Follows&lt;/h1&gt;  &lt;p&gt;    This page lists all of the blogs, newsletters, and peeps that I follow    across the internet in my RSS feed aggregator. I &lt;a      href=&quot;/posts/building-slash-follows&quot;&gt;wrote a post on how I built this&lt;/a    &gt; if you're interested.  &lt;/p&gt;  &lt;ul&gt;    { feedData != null &amp;&amp; faviconData != null &amp;&amp;       feedData.feeds.map((feed: Feed) =&gt; {        return (          &lt;li&gt;            &lt;img src={faviconData.favicons.find(({id}) =&gt; id === feed.favicon_id)?.data} /&gt;            &lt;a href={cleanSiteUrl(feed.site_url)}&gt;{feed.title}&lt;/a&gt;          &lt;/li&gt;        );      })    }  &lt;/ul&gt;&lt;/Layout&gt;</code></pre><p>With that, we've got the page pre-rendering once per build, got some types in place, and have favicon's beside the feed titles and links.</p><h3>Puttin' some paint on it</h3><p>Right now the page is super basic. The images are huge, all my follows are just in a big boring list, so let's do some stuff to clean it up:</p><ul><li>put follows in a 2-column grid layout.</li><li>cleanup the layout for each follow.<ul><li>the image / title should be center aligned</li><li>the images are appropriately sized</li></ul></li><li>use a little trick to make the whole area of the li element clickable as of it was an anchor tag<ul><li>can do this with a little <code>a:after</code> trick in the code below.</li></ul></li></ul><pre><code class=\"language-astro\">---// all the build logic and data fetching goes here---&lt;Layout title=&quot;Follows&quot; description=&quot;the blogs I follow&quot;&gt;  &lt;ul&gt;    { feedData != null &amp;&amp; faviconData != null &amp;&amp;       feedData.feeds.map((feed: Feed) =&gt; {        return (          &lt;li&gt;            &lt;img src={faviconData.favicons.find(({id}) =&gt; id === feed.favicon_id)?.data} /&gt;            &lt;a href={cleanSiteUrl(feed.site_url)}&gt;{feed.title}&lt;/a&gt;          &lt;/li&gt;        );      })    }  &lt;/ul&gt;&lt;/Layout&gt;&lt;style&gt;  ul {    display: grid;    list-style-type: none;    grid-template-columns: 1fr 1fr;    padding: 0;  }  li {    position: relative;    display: flex;    flex-direction: row;    align-items: center;    gap: var(--size-2);    padding: var(--size-1) var(--size-2);    transform-origin: center;    border: 1px solid rgba(0, 0, 0, 0);    border-left: 1px solid var(--paper-3);    transition:      transform 0.2s ease,      background 0.2s ease;  }  li:hover,  li:focus-within {    background: var(--paper-3);    transform: scale(1.05);    border: 1px solid var(--accent-1);    z-index: var(--layer-5);  }  li img {    width: 24px;    height: 24px;    border-radius: 50%;  }  li a:after {    content: '';    position: absolute;    top: 0;    left: 0;    width: 100%;    height: 100%;  }&lt;/style&gt;</code></pre><p>With that, <a href=\"/follows\">we've got the page all put together</a> 🎉</p>"}},{"id":"mdchat","url":"https://mykalmachon.com/posts/mdchat","meta":{"title":"MDChat","pubDate":"2024-01-01T00:00:00.000Z","type":"post","draft":false,"tags":["ai","tech"],"description":"Building a Markdown reading, AI powered, chatbot"},"content":{"md":"AI and LLMs have been *the* hot-topic this year. [While I don't think AI systems are wholly *good*](https://mykal.codes/posts/putting-ai-back-in-the-bag/), I do tend to think that it's impossible to put tech like this \"back in the bag\". You can either learn what's out there and know when it may be an appropriate tool to reach for (this can also be never: crypto had no real use-case, for example), or you can operate without that knowledge and operate as normal with a possible disadvantage. \n\nThat being said, I had some time off for the holidays this year and wanted to do some learning! What better way than to build a project centered around AI. One of my first ideas was to use an LLM to summarize my notes. I take notes in [Obsidian](https://obsidian.md) for my work and personal life everyday so I have literally hundreds of documents outlining my daily work, current projects, personal research, interests, etc that I can feed into an LLM for context.\n  \n## What I built \nEnter [MDChat](https://github.com/mykalmachon/mdchat): a CLI app that enables you to chat with your markdown notes. `MDChat` uses a large language model (LLM) and a method called \"Retrieval Augmented Generation\" to allow you to have conversations with an artificially intelligent \"expert\" on your markdown notes. While initially I built out `MDChat` for use with my personal notes, I've started using it to \"chat with\" large manuals and library documentation as well.\n\n### My general approach \nI built `MDChat` out as an CLI app. I spend a good amount of my work day in a terminal and I figured it'd be nice to be able to spin-up a chat there. I also wanted to keep the UI/UX work minimal as I wanted to focus on learning about and implementing the LLM pieces. \n\nI did a bunch of reading on GitHub ([made a list of useful AI repos](https://github.com/stars/MykalMachon/lists/ai-tooling)), The [OpenAI website](https://cookbook.openai.com/), Mastodon, etc. I learned a lot about how LLM-powered systems work at a surface level, and I got familiar with how people are building with them.\n\nI learned that finding and feeding relevant information into an LLM in your prompt so it can generate answers based on that specialized knowledge is generally known as \"Retrieval Augmented Generation\" or RAG for short[^1]. I also learned that there are a *lot* of projects out there that already do this. They can range from pretty minimal[^2] to massive undertakings in collecting, formatting, normalizing, ingesting, and indexing data for a specific use-case[^3]. \n\nWith that in mind, I decided to start simple and build up as needed.\n\n### Tools & libraries \n\nHere's a brief overview of the tools I used to build MDChat, and some thoughts on how they all tie together.\n\n- Language\n  - Python: Python is huge in the AI space and I use it at work almost everyday so it seemed like an obvious choice.\n- AI toolchain\n  - [LangChain](https://python.langchain.com/docs/get_started/introduction): I used LangChain as a base for interacting with the LLM as they have abstracted out most of the math-y work and make it super easy to get started. [They have a great guide on building RAG applications that I loosely referenced for this project](https://python.langchain.com/docs/use_cases/question_answering/).\n  - [FAISS](https://github.com/facebookresearch/faiss): I used FAISS (or \"Facebook AI Similarity Search\") to ingest all of my notes and build a vector based database of content and an index to search across for similarity.\n  - [Open AI API](https://platform.openai.com/docs/introduction): I used the GPT-3 and GPT-4 models from OpenAI via their API for this project as they were readily available via an API and I was already pretty familiar with their API and offerings in general. \n- Testing & CI/CD\n  - [Pytest](https://docs.pytest.org/en/7.4.x/): I used Pytest to build out some basic tests for the project. I've done a little bit of work with pytest before but most of my testing experience is in Jest or jest-like test libs. It was super easy to work with and automate. \n  - [GitHub Actions](https://github.com/features/actions): I used GitHub Actions to automate and standardize testing on pull-requests into the main branch and releasing new versions of the CLI onto PyPi. If you want to know more, [checkout my workflow files in the repo](https://github.com/MykalMachon/MDChat/tree/main/.github/workflows)! love some good CI/CD.\n- Others\n  - [Typer](https://typer.tiangolo.com/) and [Rich](https://rich.readthedocs.io/en/stable/introduction.html): Typer is an easy to use CLI library and Rich enables some of the CLI rich-text pretty-printing, spinners, and windows used in `MDChat`. \n  - [PyPi](https://pypi.org/): The de-facto package manager for python projects. I published the CLI here to make it easy to install across a wide range of machines. if you can use `pip` you can use `MDChat`\n\n## My results\nI've been using `MDChat` for a few weeks and have some notes on what I've found it useful for and where it still needs more work\n\n### The good\n  - **Strong summarization skills on specific topics**: I've used `MDChat` for summarizing and chatting with README files or documentation for specific libraries. It's also great at talking through specific topcis in a larger set of notes. The context-aware chat makes it easy to talk through a problem or questions you might have while digging into info about a library, project, or even meetings from last week.\n  - **Finding not-so-obvious connections**: I've had the `MDChat` respond along the lines of \"I don't know much about that, but I can see you had a meeting with *insert coworker here* on *insert date here* about a similar topic. Maybe discuss with them?\".\n  - **Rediscovering notes**: There have been multiple times where `MDChat` has surfaced some long forgotten blog post or research note that is relevant to what I'm \"chatting\" with it about.\n  - **Context-aware idea generation**: I've used `MDChat` to help review specific incidents, issues, or projects and asked for advice on specific tasks. I usually get answers that reference previous projects or similar issues. This usecase in particular was pretty unexpected for me; I've been surprised by how many times `MDChat` has been helfpful in thinking through an idea.     \n\n### The not-so-good\n  - **Not great at returning sources**: It seems like around 20-30% of the time (even higher when using GPT-3) the LLM just doesn't return a source. What's often confusing is that it obviously got the information from *somewhere*, but the note just isn't included in the response as a source.\n  - **Retrival and recall issues**: Sometimes `MDChat` just gives up and says it can't find information on a topic that is very obviously covered in the provided notes. This only seems to happen on \"larger\" sets of notes (i.e my obsidian note vault). \n    - For example, I once asked it to summarize what it knows about GitHub Actions Self-Hosted Runners. `MDChat` said it didn't have enough information to work with despite me having 3 files and almost 2000 words of WIP blog posts about the topic in my notes.\n  - **Struggles without very specific instructions**: my notes are pretty wide ranging. I have daily journal style notes, reference notes for topics I'm interested in, lists of todos, blog posts, recipes, etc. If you aren't specific with what your looking for OR there isn't enough info to answer your questions, I've found `MDChat` can get a bit off topic and will start bringing up unrelated information. This seems to be more common on larger sets of notes. From what I've seen, this may be a limit of the RAG approach in general; fine-tuning might be needed for more \"broad\" understanding of my notes conceptually.\n\n### Overall\n\nI'm happy with where `MDChat` is at right now! \n\nI spent around a week *really digging in* on LLMs: Reading, watching videos, just generally sucking up information. During that week I started work on the CLI, got a good bit done, and then in my second week I was able to get a fully working, relatively well tested, and published CLI package out into the world.   \n\n## Future work\nWhile I am happy with what I have so far, there are a few more things I'd like to do with `MDChat`. I think a few changes could make it more useful and more adaptable to people's specific needs:\n\n- **Enable the use of local LLMs**: via something like [Ollama](https://ollama.ai) if you have to keep everything local, can't legally send your notes over the OpenAI API, don't want to pay for an OpenAI API key, or just don't trust them to keep your data private as they've promised local LLMs are a great compromise! \n- **Enable further customization**: ideally users should be able to edit the default prompt that primes LLM conversations and maybe even save multiple \"profiles\" for different uses. \n- **Qualitative testing of the response quality**: It would be great to build some sort of standardized test suite that mesaures performance for specific types of Q&A. Right now all my notes are based on feel. Since LLM chains aren't deterministic and each request has an associated cost they can be hard to test.\n\nI'm sure more will come up as I use the tool and continue to learn as well.\n\n## Try it for yourself \nIf you have python v3.10+ installed, at least one markdown file, and an OpenAI API key, you can try `mdchat` out right now!\n\nOpen up your terminal, install the app, do some config, and get chatting: \n```bash\n# install the CLI app from PyPi\npip install mdchat\n\n# asks for your API key, LLM of choice, and notes folder location\nmdchat config\n\n# to chat the notes folder you just set\nmdchat chat \n\n# to chat with a specific file\nmdchat chat --file \"./your/file/here.md\"\n```\n\nIf you run into issues or have any feedback at all I'd love to hear from you!\n\n[^1]: [a short video on RAG from IBM](https://www.youtube.com/watch?v=T-D1OfcDW1M)\n[^2]: [a simple RAG setup outlined by the people over at LangChain](https://python.langchain.com/docs/use_cases/question_answering/)\n[^3]: [a good overview of RAG systems and how to scale them from a talk at the OpenAI DevDay conference in 2023](https://youtu.be/ahnGLM-RC1Y?si=6O3FEPmtYsQfj3tS&t=709)","html":"<p>AI and LLMs have been <em>the</em> hot-topic this year. <a href=\"https://mykal.codes/posts/putting-ai-back-in-the-bag/\">While I don't think AI systems are wholly <em>good</em></a>, I do tend to think that it's impossible to put tech like this &quot;back in the bag&quot;. You can either learn what's out there and know when it may be an appropriate tool to reach for (this can also be never: crypto had no real use-case, for example), or you can operate without that knowledge and operate as normal with a possible disadvantage.</p><p>That being said, I had some time off for the holidays this year and wanted to do some learning! What better way than to build a project centered around AI. One of my first ideas was to use an LLM to summarize my notes. I take notes in <a href=\"https://obsidian.md\">Obsidian</a> for my work and personal life everyday so I have literally hundreds of documents outlining my daily work, current projects, personal research, interests, etc that I can feed into an LLM for context.</p><h2>What I built</h2><p>Enter <a href=\"https://github.com/mykalmachon/mdchat\">MDChat</a>: a CLI app that enables you to chat with your markdown notes. <code>MDChat</code> uses a large language model (LLM) and a method called &quot;Retrieval Augmented Generation&quot; to allow you to have conversations with an artificially intelligent &quot;expert&quot; on your markdown notes. While initially I built out <code>MDChat</code> for use with my personal notes, I've started using it to &quot;chat with&quot; large manuals and library documentation as well.</p><h3>My general approach</h3><p>I built <code>MDChat</code> out as an CLI app. I spend a good amount of my work day in a terminal and I figured it'd be nice to be able to spin-up a chat there. I also wanted to keep the UI/UX work minimal as I wanted to focus on learning about and implementing the LLM pieces.</p><p>I did a bunch of reading on GitHub (<a href=\"https://github.com/stars/MykalMachon/lists/ai-tooling\">made a list of useful AI repos</a>), The <a href=\"https://cookbook.openai.com/\">OpenAI website</a>, Mastodon, etc. I learned a lot about how LLM-powered systems work at a surface level, and I got familiar with how people are building with them.</p><p>I learned that finding and feeding relevant information into an LLM in your prompt so it can generate answers based on that specialized knowledge is generally known as &quot;Retrieval Augmented Generation&quot; or RAG for short[^1]. I also learned that there are a <em>lot</em> of projects out there that already do this. They can range from pretty minimal[^2] to massive undertakings in collecting, formatting, normalizing, ingesting, and indexing data for a specific use-case[^3].</p><p>With that in mind, I decided to start simple and build up as needed.</p><h3>Tools &amp; libraries</h3><p>Here's a brief overview of the tools I used to build MDChat, and some thoughts on how they all tie together.</p><ul><li>Language<ul><li>Python: Python is huge in the AI space and I use it at work almost everyday so it seemed like an obvious choice.</li></ul></li><li>AI toolchain<ul><li><a href=\"https://python.langchain.com/docs/get_started/introduction\">LangChain</a>: I used LangChain as a base for interacting with the LLM as they have abstracted out most of the math-y work and make it super easy to get started. <a href=\"https://python.langchain.com/docs/use_cases/question_answering/\">They have a great guide on building RAG applications that I loosely referenced for this project</a>.</li><li><a href=\"https://github.com/facebookresearch/faiss\">FAISS</a>: I used FAISS (or &quot;Facebook AI Similarity Search&quot;) to ingest all of my notes and build a vector based database of content and an index to search across for similarity.</li><li><a href=\"https://platform.openai.com/docs/introduction\">Open AI API</a>: I used the GPT-3 and GPT-4 models from OpenAI via their API for this project as they were readily available via an API and I was already pretty familiar with their API and offerings in general.</li></ul></li><li>Testing &amp; CI/CD<ul><li><a href=\"https://docs.pytest.org/en/7.4.x/\">Pytest</a>: I used Pytest to build out some basic tests for the project. I've done a little bit of work with pytest before but most of my testing experience is in Jest or jest-like test libs. It was super easy to work with and automate.</li><li><a href=\"https://github.com/features/actions\">GitHub Actions</a>: I used GitHub Actions to automate and standardize testing on pull-requests into the main branch and releasing new versions of the CLI onto PyPi. If you want to know more, <a href=\"https://github.com/MykalMachon/MDChat/tree/main/.github/workflows\">checkout my workflow files in the repo</a>! love some good CI/CD.</li></ul></li><li>Others<ul><li><a href=\"https://typer.tiangolo.com/\">Typer</a> and <a href=\"https://rich.readthedocs.io/en/stable/introduction.html\">Rich</a>: Typer is an easy to use CLI library and Rich enables some of the CLI rich-text pretty-printing, spinners, and windows used in <code>MDChat</code>.</li><li><a href=\"https://pypi.org/\">PyPi</a>: The de-facto package manager for python projects. I published the CLI here to make it easy to install across a wide range of machines. if you can use <code>pip</code> you can use <code>MDChat</code></li></ul></li></ul><h2>My results</h2><p>I've been using <code>MDChat</code> for a few weeks and have some notes on what I've found it useful for and where it still needs more work</p><h3>The good</h3><ul><li><strong>Strong summarization skills on specific topics</strong>: I've used <code>MDChat</code> for summarizing and chatting with README files or documentation for specific libraries. It's also great at talking through specific topcis in a larger set of notes. The context-aware chat makes it easy to talk through a problem or questions you might have while digging into info about a library, project, or even meetings from last week.</li><li><strong>Finding not-so-obvious connections</strong>: I've had the <code>MDChat</code> respond along the lines of &quot;I don't know much about that, but I can see you had a meeting with <em>insert coworker here</em> on <em>insert date here</em> about a similar topic. Maybe discuss with them?&quot;.</li><li><strong>Rediscovering notes</strong>: There have been multiple times where <code>MDChat</code> has surfaced some long forgotten blog post or research note that is relevant to what I'm &quot;chatting&quot; with it about.</li><li><strong>Context-aware idea generation</strong>: I've used <code>MDChat</code> to help review specific incidents, issues, or projects and asked for advice on specific tasks. I usually get answers that reference previous projects or similar issues. This usecase in particular was pretty unexpected for me; I've been surprised by how many times <code>MDChat</code> has been helfpful in thinking through an idea.</li></ul><h3>The not-so-good</h3><ul><li><strong>Not great at returning sources</strong>: It seems like around 20-30% of the time (even higher when using GPT-3) the LLM just doesn't return a source. What's often confusing is that it obviously got the information from <em>somewhere</em>, but the note just isn't included in the response as a source.</li><li><strong>Retrival and recall issues</strong>: Sometimes <code>MDChat</code> just gives up and says it can't find information on a topic that is very obviously covered in the provided notes. This only seems to happen on &quot;larger&quot; sets of notes (i.e my obsidian note vault).<ul><li>For example, I once asked it to summarize what it knows about GitHub Actions Self-Hosted Runners. <code>MDChat</code> said it didn't have enough information to work with despite me having 3 files and almost 2000 words of WIP blog posts about the topic in my notes.</li></ul></li><li><strong>Struggles without very specific instructions</strong>: my notes are pretty wide ranging. I have daily journal style notes, reference notes for topics I'm interested in, lists of todos, blog posts, recipes, etc. If you aren't specific with what your looking for OR there isn't enough info to answer your questions, I've found <code>MDChat</code> can get a bit off topic and will start bringing up unrelated information. This seems to be more common on larger sets of notes. From what I've seen, this may be a limit of the RAG approach in general; fine-tuning might be needed for more &quot;broad&quot; understanding of my notes conceptually.</li></ul><h3>Overall</h3><p>I'm happy with where <code>MDChat</code> is at right now!</p><p>I spent around a week <em>really digging in</em> on LLMs: Reading, watching videos, just generally sucking up information. During that week I started work on the CLI, got a good bit done, and then in my second week I was able to get a fully working, relatively well tested, and published CLI package out into the world.</p><h2>Future work</h2><p>While I am happy with what I have so far, there are a few more things I'd like to do with <code>MDChat</code>. I think a few changes could make it more useful and more adaptable to people's specific needs:</p><ul><li><strong>Enable the use of local LLMs</strong>: via something like <a href=\"https://ollama.ai\">Ollama</a> if you have to keep everything local, can't legally send your notes over the OpenAI API, don't want to pay for an OpenAI API key, or just don't trust them to keep your data private as they've promised local LLMs are a great compromise!</li><li><strong>Enable further customization</strong>: ideally users should be able to edit the default prompt that primes LLM conversations and maybe even save multiple &quot;profiles&quot; for different uses.</li><li><strong>Qualitative testing of the response quality</strong>: It would be great to build some sort of standardized test suite that mesaures performance for specific types of Q&amp;A. Right now all my notes are based on feel. Since LLM chains aren't deterministic and each request has an associated cost they can be hard to test.</li></ul><p>I'm sure more will come up as I use the tool and continue to learn as well.</p><h2>Try it for yourself</h2><p>If you have python v3.10+ installed, at least one markdown file, and an OpenAI API key, you can try <code>mdchat</code> out right now!</p><p>Open up your terminal, install the app, do some config, and get chatting:</p><pre><code class=\"language-bash\"># install the CLI app from PyPipip install mdchat# asks for your API key, LLM of choice, and notes folder locationmdchat config# to chat the notes folder you just setmdchat chat # to chat with a specific filemdchat chat --file &quot;./your/file/here.md&quot;</code></pre><p>If you run into issues or have any feedback at all I'd love to hear from you!</p><p>[^1]: <a href=\"https://www.youtube.com/watch?v=T-D1OfcDW1M\">a short video on RAG from IBM</a>[^2]: <a href=\"https://python.langchain.com/docs/use_cases/question_answering/\">a simple RAG setup outlined by the people over at LangChain</a>[^3]: <a href=\"https://youtu.be/ahnGLM-RC1Y?si=6O3FEPmtYsQfj3tS&amp;t=709\">a good overview of RAG systems and how to scale them from a talk at the OpenAI DevDay conference in 2023</a></p>"}},{"id":"intro-to-llm-andrej","url":"https://mykalmachon.com/posts/intro-to-llm-andrej","meta":{"title":"Intro to Large Language Models","pubDate":"2023-11-23T00:00:00.000Z","type":"link","draft":false,"tags":["ai"],"description":"A great intro to LLMs by OpenAI's Andrej Karpathy","url":"https://www.youtube.com/watch?v=zjkBMFhNj_g"},"content":{"md":"I just watched this talk by Andrej Karpathy, \"Intro to Large Language Models\". Large Language Models (or LLMs) being the AI systems that power tools like [ChatGPT](https://chat.openai.com).\n\nI'm not neccesarily a stranger to LLMs. I've been exposed to the barage of AI news and watched a few small talks on them. I use tools like GitHub Copilpot and ChatGPT daily and I've even started building some toy/proof-of-concept projects with them (If intersted, see[^1] these[^2] links[^3])\n\nThat said, I'm by no means an expert. This talk served as a good all-around intro and seems to cover a lot of foundational content I've not found covered elsewhere (at least not in a format that stuck with me).\n\nWatching the talk brings me back to interesting lectures from excited profs back during my CompSci program. if you're into that, or just want to learn about how LLMs work, I would recommend giving it a watch!  \n\n[^1]: [chat.mykal.codes](https://github.com/mykalmachon/chat.mykal.codes): chat with my blog via an AI chatbot.\n\n[^2]: [open-copilot](https://github.com/mykalmachon/open-copilot): GitHub Copilot built with open-source tools (very, very, very new).\n\n[^3]: [Ollama](https://ollama.ai): a simple interface to hosting and interfacing with open-source LLMs locally.","html":"<p>I just watched this talk by Andrej Karpathy, &quot;Intro to Large Language Models&quot;. Large Language Models (or LLMs) being the AI systems that power tools like <a href=\"https://chat.openai.com\">ChatGPT</a>.</p><p>I'm not neccesarily a stranger to LLMs. I've been exposed to the barage of AI news and watched a few small talks on them. I use tools like GitHub Copilpot and ChatGPT daily and I've even started building some toy/proof-of-concept projects with them (If intersted, see[^1] these[^2] links[^3])</p><p>That said, I'm by no means an expert. This talk served as a good all-around intro and seems to cover a lot of foundational content I've not found covered elsewhere (at least not in a format that stuck with me).</p><p>Watching the talk brings me back to interesting lectures from excited profs back during my CompSci program. if you're into that, or just want to learn about how LLMs work, I would recommend giving it a watch!</p><p>[^1]: <a href=\"https://github.com/mykalmachon/chat.mykal.codes\">chat.mykal.codes</a>: chat with my blog via an AI chatbot.</p><p>[^2]: <a href=\"https://github.com/mykalmachon/open-copilot\">open-copilot</a>: GitHub Copilot built with open-source tools (very, very, very new).</p><p>[^3]: <a href=\"https://ollama.ai\">Ollama</a>: a simple interface to hosting and interfacing with open-source LLMs locally.</p>"}},{"id":"photos-as-notes-some-thoughts-on-ux","url":"https://mykalmachon.com/posts/photos-as-notes-some-thoughts-on-ux","meta":{"title":"Photos as notes; some thoughts on UX","pubDate":"2023-11-15T00:00:00.000Z","type":"post","draft":false,"tags":["webdev","opinion"],"stage":"budding","description":"I often use photos as notes even though I have a perfectly fine notes app on my phone. Why?","location":"A grocery store"},"content":{"md":"Look at this photo I took at the grocery store a few days ago. \n\n![A danish in a bakery tray. you can clearly see the tag as well as some donuts below. The photo is blurry and not really in focus.](../../assets/posts/photo-as-a-note-example.jpg)\n\nAll I wanted to know was the BIN number so I could easily pay for a pastry at checkout. \n\nLook at all the other *stuff* in this photo though. We got the tray, and the pastries on the tray, the out of focus tray below, the location metadata built into the photo, etc, etc. The data I *needed* was just like 6 characters. But it was *way easier* for me to create a 2.4mb jpeg file. \n\n## Why I use photos as notes\n\nBut... why did I do this? I have a notes app on my phone; why not use that?\n\nLet's look at the steps to taking a photo vs writing a note: \n- Taking a photo\n\t- Take phone out of pocket and double click the lock button to open the camera.\n\t- I aim at the thing I want to \"note\", and press the volume button to take a photo.\n\t- I put the phone back in my pocket and press the lock button.\n- Taking a note\n\t- Take phone out of my pocket.\n\t- Use thumb to unlock phone when it's out of my pocket. \n\t- Open my app drawer and search for my notes app.\n\t- Open my notes app.\n\t- Create a new note.\n\t- Label the note so I don't lose it and so I know what it is. \n\t- Write down what I want and make sure there aren't any typos \n\t- I put the phone back in my pocket and press the lock button \n\nI took a photo because it was extremely low friction. Taking a note would have taken \"too long\". \n\nWhile in reality this is probably a 5-15 second deference, do you see how many more steps are involved in taking a note? Just think about verifying that the note I've taken is error free. Imagine fat-thumbing it and writing down the wrong BIN code; that note becomes almost useless. *A photo doesn't really have that problem*.\n\n## How this ties into UX\nThere's probably some diatribe in me about how this is an example of how convenience will always win over efficiency. I'll skip that and just say that this seemed to be a great example of how just a *little* friction can take something from \"I will use this every day\" to \"I never use this\", even in the case of an arguably worse or very inefficient solution. \n\nSome other examples of this that I can think of:\n- **Apps that use SSO/Social sign-in vs apps that have user/password login only**: I know people that will legitimately only use an app if it has apple login. \n- **Building things with tools you already know**: muscle memory is real and sometimes you reach for a \"hammer\" when you should be using a \"chisel\" (mind the shoe-horned-in tech stack metaphor 😉)[^1]\n\n## Applying this to building stuff\nThis type of friction is *really* easy to slip into user interfaces by accident. Whether it's caused by an assumption someone made at some point, a poorly defined but highly requested feature that got implemented without testing, or tech debt incarnate. *it happens.*  \n\nGetting your stuff (app, tool, game, whatever) in the hands of the people you're building it for as early as possible is a great way to sort this out; they're not afraid to hurt your feelings and tend to pick up on this really early. Even if they don't identify it by name, you can usually sus it out by watching them use your thing.\n\n## Feedback\n\nI would love to hear if any of you have novel examples of friction guiding how you act. What do y'all use that's arguably worse or at-least suboptimal, just cause it's easier?\n\nI feel like this \"why do I use photos for notes\" thing has been stuck in my head for a week and mind mapping it tied it to work, dev, and UX in general; I'm sure that I'm not the first to make this connection.\n\n[^1]: I think \"use the tool you know\" is, in general, good advice. Especially when you're just sussing out an idea or getting started.","html":"<p>Look at this photo I took at the grocery store a few days ago.</p><p><img src=\"../../assets/posts/photo-as-a-note-example.jpg\" alt=\"A danish in a bakery tray. you can clearly see the tag as well as some donuts below. The photo is blurry and not really in focus.\"></p><p>All I wanted to know was the BIN number so I could easily pay for a pastry at checkout.</p><p>Look at all the other <em>stuff</em> in this photo though. We got the tray, and the pastries on the tray, the out of focus tray below, the location metadata built into the photo, etc, etc. The data I <em>needed</em> was just like 6 characters. But it was <em>way easier</em> for me to create a 2.4mb jpeg file.</p><h2>Why I use photos as notes</h2><p>But... why did I do this? I have a notes app on my phone; why not use that?</p><p>Let's look at the steps to taking a photo vs writing a note:</p><ul><li>Taking a photo<ul><li>Take phone out of pocket and double click the lock button to open the camera.</li><li>I aim at the thing I want to &quot;note&quot;, and press the volume button to take a photo.</li><li>I put the phone back in my pocket and press the lock button.</li></ul></li><li>Taking a note<ul><li>Take phone out of my pocket.</li><li>Use thumb to unlock phone when it's out of my pocket.</li><li>Open my app drawer and search for my notes app.</li><li>Open my notes app.</li><li>Create a new note.</li><li>Label the note so I don't lose it and so I know what it is.</li><li>Write down what I want and make sure there aren't any typos</li><li>I put the phone back in my pocket and press the lock button</li></ul></li></ul><p>I took a photo because it was extremely low friction. Taking a note would have taken &quot;too long&quot;.</p><p>While in reality this is probably a 5-15 second deference, do you see how many more steps are involved in taking a note? Just think about verifying that the note I've taken is error free. Imagine fat-thumbing it and writing down the wrong BIN code; that note becomes almost useless. <em>A photo doesn't really have that problem</em>.</p><h2>How this ties into UX</h2><p>There's probably some diatribe in me about how this is an example of how convenience will always win over efficiency. I'll skip that and just say that this seemed to be a great example of how just a <em>little</em> friction can take something from &quot;I will use this every day&quot; to &quot;I never use this&quot;, even in the case of an arguably worse or very inefficient solution.</p><p>Some other examples of this that I can think of:</p><ul><li><strong>Apps that use SSO/Social sign-in vs apps that have user/password login only</strong>: I know people that will legitimately only use an app if it has apple login.</li><li><strong>Building things with tools you already know</strong>: muscle memory is real and sometimes you reach for a &quot;hammer&quot; when you should be using a &quot;chisel&quot; (mind the shoe-horned-in tech stack metaphor 😉)[^1]</li></ul><h2>Applying this to building stuff</h2><p>This type of friction is <em>really</em> easy to slip into user interfaces by accident. Whether it's caused by an assumption someone made at some point, a poorly defined but highly requested feature that got implemented without testing, or tech debt incarnate. <em>it happens.</em></p><p>Getting your stuff (app, tool, game, whatever) in the hands of the people you're building it for as early as possible is a great way to sort this out; they're not afraid to hurt your feelings and tend to pick up on this really early. Even if they don't identify it by name, you can usually sus it out by watching them use your thing.</p><h2>Feedback</h2><p>I would love to hear if any of you have novel examples of friction guiding how you act. What do y'all use that's arguably worse or at-least suboptimal, just cause it's easier?</p><p>I feel like this &quot;why do I use photos for notes&quot; thing has been stuck in my head for a week and mind mapping it tied it to work, dev, and UX in general; I'm sure that I'm not the first to make this connection.</p><p>[^1]: I think &quot;use the tool you know&quot; is, in general, good advice. Especially when you're just sussing out an idea or getting started.</p>"}},{"id":"bun-vs-deno","url":"https://mykalmachon.com/posts/bun-vs-deno","meta":{"title":"Bun vs. Deno","pubDate":"2023-09-11T00:00:00.000Z","type":"note","draft":false,"tags":["tech"],"description":"Bun and Deno seemed mostly the same to me, so I wrote this post to sort out my thoughts."},"content":{"md":"[Bun](https://bun.sh/) [v1.0](https://bun.sh/blog/bun-v1.0) just launched, and I've been very surprised by how hyped people seem to be with the release. [Deno](https://deno.com/) (currently on v1.36) has been around for quite awhile, has seen pretty heavy adoption in certain circles and by certain companies (especially in edge environments like with [Netlify Edge Functions](https://docs.netlify.com/edge-functions/overview/) and [Cloudflare Workers](https://workers.cloudflare.com/)).\n\nI spent the afternoon digging around both Deno and Bun's sites, and talking to a few people, so thought I would solidify my thoughts by writing something up.\n\n## What they have in common\n\nBun and Deno have a lot in common. This is why I was so surprised by all the hype I've seen online for Bun's 1.0. _Why are people so excited about Bun, when Deno seemingly does so much of the same things?_\n\nHere's a quick list of things I've noticed the projects have in common:\n\n- Alternate JavaScript runtimes that run in a server/desktop context.\n- Claim to improve upon than the prototypical server/desktop JS runtime, Node.js. This includes actual runtime and startup improvements.\n  - note: While both Deno and Bun are faster than Node.js, Bun seems to be considerably faster but time will tell if that's true at scale.\n- Provide a number of runtime-native solutions that are typically solved by third-party tools (i.e. bundler, package manager, dev server, test runner, etc.)\n- Provide their own \"standard lib\" of tools that claim to be equivalent, simpler, or faster than the Node.js standard options through better APIs or optimization in their respective languages.\n  - [Bun is built on Zig + JavaScriptCore (Webkit)](https://bun.sh/docs#what-is-a-runtime)\n  - [Deno is built on Rust + V8 (Chrome's JS engine)](https://deno.land/manual@v1.36.4/introduction#introduction)\n- Managed by for-profit, VC-backed, companies, as opposed to Node.js which is in the hands of the [The OpenJS Foundation](https://openjsf.org/), a not-for-profit company.\n  - This isn't necessarily a bad thing, just something to be aware of.\n- Natively support Typescript, JSX, top-level await and a number of other \"developer experience\" goodies.\n- Are open-source.\n\n## Where they're different\n\nThere are a _lot_ of tit-for-tat feature-level differences between Bun and Deno. That said, after reading through their respective sites, **I think the differences all come down to a fundamental difference in how they attempt to answer the same question.** **_How do we replace Node.js_?**\n\n### Bun's approach\n\nBun aims to replace node.js with a drop-in replacement that focusses on improved performance and developer experience without straying to far from the core principles of node. It does this by focusing on optimizing and improving upon existing node.js paradigms while still providing non-breaking changes that improve developer experience.\n\nHere are some examples of this approach in their features:\n\n- Faster install time for packages via [`bun install`](https://bun.sh/docs/cli/install) that uses your existing `package.json` file.\n- Direct replacement for `npx`, [`bunx`](https://bun.sh/docs/cli/bunx) that relies on the optimizations in `bun install`.\n- [Top-level await](https://bun.sh/docs/runtime/modules#top-level-await), [native typescript](https://bun.sh/docs/runtime/typescript) and [JSX](https://bun.sh/docs/runtime/jsx) support in the runtime itself.\n- [A test runner](https://bun.sh/docs/cli/test) and [bundler](https://bun.sh/docs/bundler) as part of Bun's standard library and [more DX features](https://bun.sh/docs/runtime/hot).\n\nNotice how these features enable developers to swap over to bun without changing any of their existing configuration? You can just use your existing `package.json`, dependencies, and scripts with bun. You can just run `bun install` and `bun run start` and see the improvements _instantly_. You can incrementally adopt the other goodies over time.\n\n### Deno's approach\n\nDeno aims to replace node with a bit more of a deviation from the tracks that node.js laid. Deno is taking a more holistic approach, and is aiming to solve the issues present in Node.js at a fundamental level. It offers new solutions to old problems and attempts to rethink node.js from the ground up, avoiding all of the (perceived) mistakes it's made.\n\nHere are some more features, from Deno this time, that justify my thinking:\n\n- Preferring direct [ESM imports](https://deno.land/manual@v1.36.4/basics/modules#overview) and [a `deno.json` file](https://deno.land/manual@v1.36.4/getting_started/configuration_file) instead of `package.json`\n- Having [their own third-party package hosting platform](https://deno.land/x)\n- [Supporting both node.js built-ins and npm packages](https://deno.land/manual@v1.36.4/node), but doing so after v1.0.\n- While Deno is faster than Node.js, [they only mention performance _once_ in their intro](https://deno.land/manual@v1.36.4/introduction). They focus more on DX, better defaults, browser compatibility, better tooling, [etc.](https://deno.land/manual@v1.36.4/introduction#goals)\n\nNotice how Deno pitches itself as more of a wholistic, alternate approach to a JavaScript runtime? Chances are you can't just run your existing node.js app with Deno the same way you could with bun, _and I think that's intentional._ The Deno team seems to believe that the benefits Deno offers are worth changing things up a bit; it's ultimately up to you decide if the tradeoffs are worth the benefits.\n\n_Fun fact: [Deno's development and company are lead by the original developer of Node.js](<https://en.wikipedia.org/wiki/Deno_(software)?useskin=vector#History>), [Ryan Dahl](https://en.wikipedia.org/wiki/Ryan_Dahl?useskin=vector). Imo, if anyone gets to definitively claim node.js has made some mistakes, it's the person who designed the runtime in the first place. [Deno was even announced in a talk about that topic.](https://www.youtube.com/watch?v=M3BM9TB-8yA)_\n\n## My closing thoughts\n\nI've already been using Deno for scripts, automations, and little CLI apps ([Deno compile](https://deno.land/manual@v1.36.4/tools/compiler) is amazing for this btw) for around a year, and have been increasingly happy with it for that. I could see myself starting to adopt it in production systems eventually if it stays as stable as it's been recently. As far as Bun goes, I think it's too early to bet on it for work. I will be keeping my eye it though. I'm sure they'll [sort out some of the issues they're facing](https://github.com/oven-sh/bun/issues/3218), and I'm interested to see how they continue to differentiate themselves from both Node.js and Deno after v1.0.\n\nAs it stands right now, I think I'll probably still be sticking to node.js for most production workloads. Boring I know, but it's proven, and I don't want segfaults to take down prod just because I wanted to run my tests faster 😉","html":"<p><a href=\"https://bun.sh/\">Bun</a> <a href=\"https://bun.sh/blog/bun-v1.0\">v1.0</a> just launched, and I've been very surprised by how hyped people seem to be with the release. <a href=\"https://deno.com/\">Deno</a> (currently on v1.36) has been around for quite awhile, has seen pretty heavy adoption in certain circles and by certain companies (especially in edge environments like with <a href=\"https://docs.netlify.com/edge-functions/overview/\">Netlify Edge Functions</a> and <a href=\"https://workers.cloudflare.com/\">Cloudflare Workers</a>).</p><p>I spent the afternoon digging around both Deno and Bun's sites, and talking to a few people, so thought I would solidify my thoughts by writing something up.</p><h2>What they have in common</h2><p>Bun and Deno have a lot in common. This is why I was so surprised by all the hype I've seen online for Bun's 1.0. <em>Why are people so excited about Bun, when Deno seemingly does so much of the same things?</em></p><p>Here's a quick list of things I've noticed the projects have in common:</p><ul><li>Alternate JavaScript runtimes that run in a server/desktop context.</li><li>Claim to improve upon than the prototypical server/desktop JS runtime, Node.js. This includes actual runtime and startup improvements.<ul><li>note: While both Deno and Bun are faster than Node.js, Bun seems to be considerably faster but time will tell if that's true at scale.</li></ul></li><li>Provide a number of runtime-native solutions that are typically solved by third-party tools (i.e. bundler, package manager, dev server, test runner, etc.)</li><li>Provide their own &quot;standard lib&quot; of tools that claim to be equivalent, simpler, or faster than the Node.js standard options through better APIs or optimization in their respective languages.<ul><li><a href=\"https://bun.sh/docs#what-is-a-runtime\">Bun is built on Zig + JavaScriptCore (Webkit)</a></li><li><a href=\"https://deno.land/manual@v1.36.4/introduction#introduction\">Deno is built on Rust + V8 (Chrome's JS engine)</a></li></ul></li><li>Managed by for-profit, VC-backed, companies, as opposed to Node.js which is in the hands of the <a href=\"https://openjsf.org/\">The OpenJS Foundation</a>, a not-for-profit company.<ul><li>This isn't necessarily a bad thing, just something to be aware of.</li></ul></li><li>Natively support Typescript, JSX, top-level await and a number of other &quot;developer experience&quot; goodies.</li><li>Are open-source.</li></ul><h2>Where they're different</h2><p>There are a <em>lot</em> of tit-for-tat feature-level differences between Bun and Deno. That said, after reading through their respective sites, <strong>I think the differences all come down to a fundamental difference in how they attempt to answer the same question.</strong> <strong><em>How do we replace Node.js</em>?</strong></p><h3>Bun's approach</h3><p>Bun aims to replace node.js with a drop-in replacement that focusses on improved performance and developer experience without straying to far from the core principles of node. It does this by focusing on optimizing and improving upon existing node.js paradigms while still providing non-breaking changes that improve developer experience.</p><p>Here are some examples of this approach in their features:</p><ul><li>Faster install time for packages via <a href=\"https://bun.sh/docs/cli/install\"><code>bun install</code></a> that uses your existing <code>package.json</code> file.</li><li>Direct replacement for <code>npx</code>, <a href=\"https://bun.sh/docs/cli/bunx\"><code>bunx</code></a> that relies on the optimizations in <code>bun install</code>.</li><li><a href=\"https://bun.sh/docs/runtime/modules#top-level-await\">Top-level await</a>, <a href=\"https://bun.sh/docs/runtime/typescript\">native typescript</a> and <a href=\"https://bun.sh/docs/runtime/jsx\">JSX</a> support in the runtime itself.</li><li><a href=\"https://bun.sh/docs/cli/test\">A test runner</a> and <a href=\"https://bun.sh/docs/bundler\">bundler</a> as part of Bun's standard library and <a href=\"https://bun.sh/docs/runtime/hot\">more DX features</a>.</li></ul><p>Notice how these features enable developers to swap over to bun without changing any of their existing configuration? You can just use your existing <code>package.json</code>, dependencies, and scripts with bun. You can just run <code>bun install</code> and <code>bun run start</code> and see the improvements <em>instantly</em>. You can incrementally adopt the other goodies over time.</p><h3>Deno's approach</h3><p>Deno aims to replace node with a bit more of a deviation from the tracks that node.js laid. Deno is taking a more holistic approach, and is aiming to solve the issues present in Node.js at a fundamental level. It offers new solutions to old problems and attempts to rethink node.js from the ground up, avoiding all of the (perceived) mistakes it's made.</p><p>Here are some more features, from Deno this time, that justify my thinking:</p><ul><li>Preferring direct <a href=\"https://deno.land/manual@v1.36.4/basics/modules#overview\">ESM imports</a> and <a href=\"https://deno.land/manual@v1.36.4/getting_started/configuration_file\">a <code>deno.json</code> file</a> instead of <code>package.json</code></li><li>Having <a href=\"https://deno.land/x\">their own third-party package hosting platform</a></li><li><a href=\"https://deno.land/manual@v1.36.4/node\">Supporting both node.js built-ins and npm packages</a>, but doing so after v1.0.</li><li>While Deno is faster than Node.js, <a href=\"https://deno.land/manual@v1.36.4/introduction\">they only mention performance <em>once</em> in their intro</a>. They focus more on DX, better defaults, browser compatibility, better tooling, <a href=\"https://deno.land/manual@v1.36.4/introduction#goals\">etc.</a></li></ul><p>Notice how Deno pitches itself as more of a wholistic, alternate approach to a JavaScript runtime? Chances are you can't just run your existing node.js app with Deno the same way you could with bun, <em>and I think that's intentional.</em> The Deno team seems to believe that the benefits Deno offers are worth changing things up a bit; it's ultimately up to you decide if the tradeoffs are worth the benefits.</p><p><em>Fun fact: <a href=\"https://en.wikipedia.org/wiki/Deno_(software)?useskin=vector#History\">Deno's development and company are lead by the original developer of Node.js</a>, <a href=\"https://en.wikipedia.org/wiki/Ryan_Dahl?useskin=vector\">Ryan Dahl</a>. Imo, if anyone gets to definitively claim node.js has made some mistakes, it's the person who designed the runtime in the first place. <a href=\"https://www.youtube.com/watch?v=M3BM9TB-8yA\">Deno was even announced in a talk about that topic.</a></em></p><h2>My closing thoughts</h2><p>I've already been using Deno for scripts, automations, and little CLI apps (<a href=\"https://deno.land/manual@v1.36.4/tools/compiler\">Deno compile</a> is amazing for this btw) for around a year, and have been increasingly happy with it for that. I could see myself starting to adopt it in production systems eventually if it stays as stable as it's been recently. As far as Bun goes, I think it's too early to bet on it for work. I will be keeping my eye it though. I'm sure they'll <a href=\"https://github.com/oven-sh/bun/issues/3218\">sort out some of the issues they're facing</a>, and I'm interested to see how they continue to differentiate themselves from both Node.js and Deno after v1.0.</p><p>As it stands right now, I think I'll probably still be sticking to node.js for most production workloads. Boring I know, but it's proven, and I don't want segfaults to take down prod just because I wanted to run my tests faster 😉</p>"}},{"id":"npkill","url":"https://mykalmachon.com/posts/npkill","meta":{"title":"npkill","pubDate":"2023-06-09T00:00:00.000Z","type":"link","draft":false,"tags":["tech"],"description":"easily delete all your old node_modules folders","url":"https://www.npmjs.com/package/npkill"},"content":{"md":"I just deleted 8gb of old `node_modules` folders for inactive projects! \n\nJust run `npx npkill` in your terminal, and `npkill` will scan your hard drive for all your `node_modules` folders, display the last time they were accessed, and let you delete them.  \n\nside note, `npx` is great for running node applications in a one-off context; Underappreciated NPM feature in my opinion.","html":"<p>I just deleted 8gb of old <code>node_modules</code> folders for inactive projects!</p><p>Just run <code>npx npkill</code> in your terminal, and <code>npkill</code> will scan your hard drive for all your <code>node_modules</code> folders, display the last time they were accessed, and let you delete them.</p><p>side note, <code>npx</code> is great for running node applications in a one-off context; Underappreciated NPM feature in my opinion.</p>"}},{"id":"markdown-images-are-an-antipattern","url":"https://mykalmachon.com/posts/markdown-images-are-an-antipattern","meta":{"title":"Markdown Images are an anti-pattern","pubDate":"2023-05-25T00:00:00.000Z","type":"link","draft":false,"tags":["webdev","opinion"],"description":"Thank you, Dave Rupert.","url":"https://daverupert.com/2023/05/markdown-images-anti-pattern/"},"content":{"md":"[Thank you for saying it, Dave.](https://daverupert.com/2023/05/markdown-images-anti-pattern/)\n\nMy Markdown linter gets mad whenever I use `<img />` tags. It drives me nuts. Either add proper image formatting to the spec, or let me use my HTML tags!","html":"<p><a href=\"https://daverupert.com/2023/05/markdown-images-anti-pattern/\">Thank you for saying it, Dave.</a></p><p>My Markdown linter gets mad whenever I use <code>&lt;img /&gt;</code> tags. It drives me nuts. Either add proper image formatting to the spec, or let me use my HTML tags!</p>"}},{"id":"systems-analyst-ii-at-ufv","url":"https://mykalmachon.com/posts/systems-analyst-ii-at-ufv","meta":{"title":"Systems Analyst II at UFV","pubDate":"2023-05-21T00:00:00.000Z","type":"post","draft":false,"tags":["personal","career"],"description":"Moving on up as a Systems Analsyt 🎉","heroImage":{"src":"/_astro/systems-analyst-II-at-UFV.D7LR4Yvm.avif","width":1200,"height":800,"format":"avif"}},"content":{"md":"[I started as a Systems Analyst I at UFV on May 10th, 2021](/garden/posts/starting-at-ufv) and am stoked to be moving on up into a Systems Analyst II role as of May 17th, 2023.\n\n## What I've been up to\n\nHonestly, it's hard to believe that it's been 2 years since I started at UFV. I've grown a lot in the role. I can't talk too much about what I've been up to but I can give a brief summary of the _type_ of things I'm working on.\n\nI've had a real taste of legacy-enterprise-land. I'm happy to say I've had the opportunity to modernize quite a few dinosaurs, document a lot of undocumented systems, automate a lot of complexity away, and help our team pick up some DevOps, testing, and cloud chops. I've also written a _lot_ of Docker-powered Python apps and worked extensively in React & Typescript apps in Javascript land. Hopefully, all that work has made our students, staff, and faculty's lives better bit by bit.\n\n## What this promotion means\n\nThe biggest difference between a Systems Analyst I → Systems Analyst II (SA II) role on paper is that SA II is more of a leadership position on the team. I'll be working across the team more and enabling others while working on larger impact and more complex projects.\n\nThat said, I'm excited to take on more responsibility, get more say in how we move forward, and continue to drive our team deeper into sustainable software and systems development practices.\n\n## Thanks, everyone\n\nThere are so many people that I could not have gotten here without. You all know who you are (my wife, friends, coworkers, family, and mentors) and I've thanked you all individually so I won't put you on blast in public 😉\n\nIf the next 2 years are anything like the previous 2, I'm sure it will take me lots of places I never would have expected.","html":"<p><a href=\"/garden/posts/starting-at-ufv\">I started as a Systems Analyst I at UFV on May 10th, 2021</a> and am stoked to be moving on up into a Systems Analyst II role as of May 17th, 2023.</p><h2>What I've been up to</h2><p>Honestly, it's hard to believe that it's been 2 years since I started at UFV. I've grown a lot in the role. I can't talk too much about what I've been up to but I can give a brief summary of the <em>type</em> of things I'm working on.</p><p>I've had a real taste of legacy-enterprise-land. I'm happy to say I've had the opportunity to modernize quite a few dinosaurs, document a lot of undocumented systems, automate a lot of complexity away, and help our team pick up some DevOps, testing, and cloud chops. I've also written a <em>lot</em> of Docker-powered Python apps and worked extensively in React &amp; Typescript apps in Javascript land. Hopefully, all that work has made our students, staff, and faculty's lives better bit by bit.</p><h2>What this promotion means</h2><p>The biggest difference between a Systems Analyst I → Systems Analyst II (SA II) role on paper is that SA II is more of a leadership position on the team. I'll be working across the team more and enabling others while working on larger impact and more complex projects.</p><p>That said, I'm excited to take on more responsibility, get more say in how we move forward, and continue to drive our team deeper into sustainable software and systems development practices.</p><h2>Thanks, everyone</h2><p>There are so many people that I could not have gotten here without. You all know who you are (my wife, friends, coworkers, family, and mentors) and I've thanked you all individually so I won't put you on blast in public 😉</p><p>If the next 2 years are anything like the previous 2, I'm sure it will take me lots of places I never would have expected.</p>"}},{"id":"erosion-of-ownership","url":"https://mykalmachon.com/posts/erosion-of-ownership","meta":{"title":"No one owns their own video games anymore","pubDate":"2023-05-20T00:00:00.000Z","type":"note","draft":false,"tags":["opinion","personal"],"description":"Why I still buy physical media in 2023"},"content":{"md":"I just bought a physical copy of [Legend of Zelda: Tears of the Kingdom](https://en.wikipedia.org/wiki/The_Legend_of_Zelda:_Tears_of_the_Kingdom?useskin=vector). _Why?_ because I want to be able to play it in 4-5 years when Nintendo inevitably retires the Nintendo Switch online store.\n\n**But why would Nintendo shut down their digital shop?** Doesn't this mean everyone who bought digital copies won't be able to re-download their games? Yeah! it does! but check out what [Nintendo did to their 3DS & WiiU store this March](https://en-americas-support.nintendo.com/app/answers/detail/a_id/57847/~/wii-u-%26-nintendo-3ds-eshop-discontinuation-q%26a), or [what they did to their Wii store back in January of 2019](https://en-americas-support.nintendo.com/app/answers/detail/a_id/27560/~/wii-shop-channel-discontinuation).\n\nFrom a business perspective I could see why they can't host their digital library _forever_. Managing all the infrastructure, support, and person-power to keep these things up and running is no small feat; It doesn't make sense to burn money on these services forever if they aren't making money._But at least send out an email and be like \"hey, you can download a copy of that game you bought here along with your save data!\"_ but nope. nothing. Thanks for your money, bye!\n\n## Not just Nintendo\n\nIt's not just Nintendo. I've also noticed a bunch of movies, TV shows, games, and other digital \"things\" just sort of evaporating into thin air. A lot of these shows and movies were never even distributed outside of streaming platforms. What happens to all those Netflix Original shows that get taken down? Are they just gone forever? _really?_\n\nIt makes me pretty sad thinking about all the good art that's going to be lost to data rot and poor preservation over the next x years because of all this.\n\n## My solution for now\n\nOver the last few years I've been doing a few of the following to try and avoid this:\n\n1. Buying physical copies of my favourite games, movies, and tv shows.\n2. Backing up game save data, game images, movies, and tv shows.\n\nThe gaping hole in this strategy is for games, movies, and tv shows that can't be downloaded and don't have physicals. The only option for backup and preservation in those cases is piracy which is absolutely ridiculous, and not something I would recommend.\n\n## TLDR\n\nBuy physical copies of your favourite digital media, take care of them, back them up, and preserve them because their creators sure aren't!","html":"<p>I just bought a physical copy of <a href=\"https://en.wikipedia.org/wiki/The_Legend_of_Zelda:_Tears_of_the_Kingdom?useskin=vector\">Legend of Zelda: Tears of the Kingdom</a>. <em>Why?</em> because I want to be able to play it in 4-5 years when Nintendo inevitably retires the Nintendo Switch online store.</p><p><strong>But why would Nintendo shut down their digital shop?</strong> Doesn't this mean everyone who bought digital copies won't be able to re-download their games? Yeah! it does! but check out what <a href=\"https://en-americas-support.nintendo.com/app/answers/detail/a_id/57847/~/wii-u-%26-nintendo-3ds-eshop-discontinuation-q%26a\">Nintendo did to their 3DS &amp; WiiU store this March</a>, or <a href=\"https://en-americas-support.nintendo.com/app/answers/detail/a_id/27560/~/wii-shop-channel-discontinuation\">what they did to their Wii store back in January of 2019</a>.</p><p>From a business perspective I could see why they can't host their digital library <em>forever</em>. Managing all the infrastructure, support, and person-power to keep these things up and running is no small feat; It doesn't make sense to burn money on these services forever if they aren't making money.<em>But at least send out an email and be like &quot;hey, you can download a copy of that game you bought here along with your save data!&quot;</em> but nope. nothing. Thanks for your money, bye!</p><h2>Not just Nintendo</h2><p>It's not just Nintendo. I've also noticed a bunch of movies, TV shows, games, and other digital &quot;things&quot; just sort of evaporating into thin air. A lot of these shows and movies were never even distributed outside of streaming platforms. What happens to all those Netflix Original shows that get taken down? Are they just gone forever? <em>really?</em></p><p>It makes me pretty sad thinking about all the good art that's going to be lost to data rot and poor preservation over the next x years because of all this.</p><h2>My solution for now</h2><p>Over the last few years I've been doing a few of the following to try and avoid this:</p><ol><li>Buying physical copies of my favourite games, movies, and tv shows.</li><li>Backing up game save data, game images, movies, and tv shows.</li></ol><p>The gaping hole in this strategy is for games, movies, and tv shows that can't be downloaded and don't have physicals. The only option for backup and preservation in those cases is piracy which is absolutely ridiculous, and not something I would recommend.</p><h2>TLDR</h2><p>Buy physical copies of your favourite digital media, take care of them, back them up, and preserve them because their creators sure aren't!</p>"}},{"id":"google-collab","url":"https://mykalmachon.com/posts/google-collab","meta":{"title":"Google Colab","pubDate":"2023-04-21T00:00:00.000Z","type":"link","draft":false,"tags":["ai"],"url":"https://colab.research.google.com/notebooks/intro.ipynb","description":""},"content":{"md":"I was just reading through [Maggie Appleton's post on \"Computational Notebooks\"](https://maggieappleton.com/computational-notebooks)[^1], and came across Google Colab which provides free cloud compute for Jupyter Notebooks. The notebooks are just saved in your google drive account, and it's dead simple to get up and running. Just open the link above, click \"file\" then \"new notebook\" and you're off to the races.  \n\nI've used them for prototyping functions, data visualization, and as interactive documentation for little packages I was working on at the time. If you work with Python regularly, and have not came across these notebooks yet, I'd definitely recommend giving them a try! \n\nI first ran into Python's \"Jupyter Notebooks\" in an AI/ML course I was taking in university. I was a bit hesitant at first, but after going through a few labs where the notebook explained the code and theory we were going to explore, then had executable, editable code examples inline, I was hooked. \n\n[^1]: This is a great intro to computational notebooks in general, definitely a recommended read. Maggie also explores other notebook solutions like Observable which is basically Jupyter Notebooks but for Javascript developers.","html":"<p>I was just reading through <a href=\"https://maggieappleton.com/computational-notebooks\">Maggie Appleton's post on &quot;Computational Notebooks&quot;</a>[^1], and came across Google Colab which provides free cloud compute for Jupyter Notebooks. The notebooks are just saved in your google drive account, and it's dead simple to get up and running. Just open the link above, click &quot;file&quot; then &quot;new notebook&quot; and you're off to the races.</p><p>I've used them for prototyping functions, data visualization, and as interactive documentation for little packages I was working on at the time. If you work with Python regularly, and have not came across these notebooks yet, I'd definitely recommend giving them a try!</p><p>I first ran into Python's &quot;Jupyter Notebooks&quot; in an AI/ML course I was taking in university. I was a bit hesitant at first, but after going through a few labs where the notebook explained the code and theory we were going to explore, then had executable, editable code examples inline, I was hooked.</p><p>[^1]: This is a great intro to computational notebooks in general, definitely a recommended read. Maggie also explores other notebook solutions like Observable which is basically Jupyter Notebooks but for Javascript developers.</p>"}},{"id":"blog-to-mastodon-automation","url":"https://mykalmachon.com/posts/blog-to-mastodon-automation","meta":{"title":"Blog to Mastodon automation","pubDate":"2023-04-06T00:00:00.000Z","type":"note","draft":false,"tags":["webdev"],"description":"I used pipedream to automatically cross-post my content out to Mastodon."},"content":{"md":"I just built a little \"job\" or \"process\" that monitors this site's RSS feed, and posts all new entries to my Mastodon account. So when I make a post here, it automatically gets syndicated out to Mastodon! \n\nTypically, I would build this all out from scratch (in the name of learning, of course!) and spend a whole afternoon on it, maybe even two or three afternoons if I'm being honest with myself. This time, I wanted to take it easy on myself, and try using a tool I heard about awhile ago in the Shop Talk show discord, [Pipedream](https://pipedream.com/).\n\n## What's Pipedream\n\nPipedream is an integration platform, that seems to be targeting the developer crowd. Think of Zapier with all of it's pre-built third-party connections (Facebook, Twitter, SendGrid, Postgres...) and it's easy drag and drop interface. Now imagine that _again_ with a developer slant, and the ability to add in Node.js/Python/Go \"middleware\", proper error logging, and more developer goodies. That's pipdeream! \n\nI've been sold this type of tool a lot before and was honestly pretty skeptical at first. That said, after messing around with it for awhile, it seems pretty solid for your basic workflows and jobs.\n\n## How does the integration work.\n\nAll things considered, it's a pretty simple process. I created a new \"Workflow\" in pipdeream and selected the \"RSS\" trigger. A \"workflow\" in pipedream maps to a job or process, and a trigger is what, well, \"triggers\" that job to run. I choose the \"RSS\" trigger, and pointed it at my RSS feed so it can to listen for new items and trigger the workflow when it detects them. \n\n![photo of the pipedream UI; editing the RSS trigger as described above.](https://res.cloudinary.com/mykalcodes/image/upload/v1680844778/iaw9nfadjoih1qmkvqai.png)\n\nThen I added a \"Node.js\" step: this reads in some data from new item that triggered the workflow, and determines the post \"type\" based on the URL. It then returns that \"type\" so I can use it later in my post.\n\n![photo of the pipedream UI; editing the node processing step as described above.](https://res.cloudinary.com/mykalcodes/image/upload/v1680844852/u99ekzavlnkdxui6xief.png)\n\nFinally you've got a \"Mastodon\" step that handles authenticating into my Mastodon instance, and sending the \"toot\" for me.\n\n![pipedream UI for managing the mastodon step for posting. ](https://res.cloudinary.com/mykalcodes/image/upload/v1680845138/z16rleff7tffdtvdgxcp.png)\n\nAfter you've got that configured, all you need to do is click a little \"test\" or \"deploy\" button and you're done! easy as that.\n\n## Aside: Tentative pipedream review and other ideas\n\nFrom what I can tell, that's sort of _all_ I have to do to get blog to mastodon cross posting working.\n\n_and I did it in like... 20 minutes? maybe?_ including testing, signing in, reading docs for the node middleware, everything. From a time perspective it seems like a huge win over rolling your own solution. **Pretty sweet deal compared to the 2-3 afternoons I would have spent rolling my own bespoke solution.**\n\nI'll probably keep messing around with Pipedream even if it's just for experiments like this. it seems to remove a lot of the groundwork typically involved in shipping tools like this one. You just write your business logic. No worrying about writing integrations, storing credentials, managing deploys, provisioning infra, etc, etc. All signs of a great tool.\n\nThis would also be great for a bunch of other stuff and I've got a few ideas in my head already:\n\n*   handling form submissions from sites: just have your site POST to a webhook trigger, and then send out an email/slack/discord with the contents.\n    \n*   building out MVPs for APIs; want a bare bones version of a feature built out? just do it here first with those code steps. did I mention you can import packages and manage secrets in there too?\n    \n*   Doing basically any small easily automated task on a schedule: with their CRON trigger this can replace a crontab on a raspberry pi in your house, or a job server in your cluster.","html":"<p>I just built a little &quot;job&quot; or &quot;process&quot; that monitors this site's RSS feed, and posts all new entries to my Mastodon account. So when I make a post here, it automatically gets syndicated out to Mastodon!</p><p>Typically, I would build this all out from scratch (in the name of learning, of course!) and spend a whole afternoon on it, maybe even two or three afternoons if I'm being honest with myself. This time, I wanted to take it easy on myself, and try using a tool I heard about awhile ago in the Shop Talk show discord, <a href=\"https://pipedream.com/\">Pipedream</a>.</p><h2>What's Pipedream</h2><p>Pipedream is an integration platform, that seems to be targeting the developer crowd. Think of Zapier with all of it's pre-built third-party connections (Facebook, Twitter, SendGrid, Postgres...) and it's easy drag and drop interface. Now imagine that <em>again</em> with a developer slant, and the ability to add in Node.js/Python/Go &quot;middleware&quot;, proper error logging, and more developer goodies. That's pipdeream!</p><p>I've been sold this type of tool a lot before and was honestly pretty skeptical at first. That said, after messing around with it for awhile, it seems pretty solid for your basic workflows and jobs.</p><h2>How does the integration work.</h2><p>All things considered, it's a pretty simple process. I created a new &quot;Workflow&quot; in pipdeream and selected the &quot;RSS&quot; trigger. A &quot;workflow&quot; in pipedream maps to a job or process, and a trigger is what, well, &quot;triggers&quot; that job to run. I choose the &quot;RSS&quot; trigger, and pointed it at my RSS feed so it can to listen for new items and trigger the workflow when it detects them.</p><p><img src=\"https://res.cloudinary.com/mykalcodes/image/upload/v1680844778/iaw9nfadjoih1qmkvqai.png\" alt=\"photo of the pipedream UI; editing the RSS trigger as described above.\"></p><p>Then I added a &quot;Node.js&quot; step: this reads in some data from new item that triggered the workflow, and determines the post &quot;type&quot; based on the URL. It then returns that &quot;type&quot; so I can use it later in my post.</p><p><img src=\"https://res.cloudinary.com/mykalcodes/image/upload/v1680844852/u99ekzavlnkdxui6xief.png\" alt=\"photo of the pipedream UI; editing the node processing step as described above.\"></p><p>Finally you've got a &quot;Mastodon&quot; step that handles authenticating into my Mastodon instance, and sending the &quot;toot&quot; for me.</p><p><img src=\"https://res.cloudinary.com/mykalcodes/image/upload/v1680845138/z16rleff7tffdtvdgxcp.png\" alt=\"pipedream UI for managing the mastodon step for posting. \"></p><p>After you've got that configured, all you need to do is click a little &quot;test&quot; or &quot;deploy&quot; button and you're done! easy as that.</p><h2>Aside: Tentative pipedream review and other ideas</h2><p>From what I can tell, that's sort of <em>all</em> I have to do to get blog to mastodon cross posting working.</p><p><em>and I did it in like... 20 minutes? maybe?</em> including testing, signing in, reading docs for the node middleware, everything. From a time perspective it seems like a huge win over rolling your own solution. <strong>Pretty sweet deal compared to the 2-3 afternoons I would have spent rolling my own bespoke solution.</strong></p><p>I'll probably keep messing around with Pipedream even if it's just for experiments like this. it seems to remove a lot of the groundwork typically involved in shipping tools like this one. You just write your business logic. No worrying about writing integrations, storing credentials, managing deploys, provisioning infra, etc, etc. All signs of a great tool.</p><p>This would also be great for a bunch of other stuff and I've got a few ideas in my head already:</p><ul><li><p>handling form submissions from sites: just have your site POST to a webhook trigger, and then send out an email/slack/discord with the contents.</p></li><li><p>building out MVPs for APIs; want a bare bones version of a feature built out? just do it here first with those code steps. did I mention you can import packages and manage secrets in there too?</p></li><li><p>Doing basically any small easily automated task on a schedule: with their CRON trigger this can replace a crontab on a raspberry pi in your house, or a job server in your cluster.</p></li></ul>"}},{"id":"kurzgesagt","url":"https://mykalmachon.com/posts/kurzgesagt","meta":{"title":"Kurzgesagt - In a Nutshell","pubDate":"2023-03-28T00:00:00.000Z","type":"link","draft":false,"tags":["personal"],"description":"One of my favourite YouTube channels.","url":"https://www.youtube.com/@kurzgesagt"},"content":{"md":"[This is one of my favourite YouTube channels](https://www.youtube.com/@kurzgesagt). They really capture the _wonder_ of learning in a way I find really rare. Their videos always bring on a sense of nostalgic learning for me. I remember being a kid and reading books about robotics or space in my Oma's basement and just being _stoked_ about the future.\n\nTheir videos on [space sciences](https://www.youtube.com/watch?v=xAUJYP8tnRE&t=361s) and [future-earth](https://www.youtube.com/watch?v=W93XyXHI8Nw&) are always favorites of mine. As a reluctant pessimist, I've always appreciated their realistic optimism; they find ways to highlight how we as humans _can_ band together [to solve problems like climate change](https://www.youtube.com/watch?v=LxgMdjyw8uw), not all hope is lost, _yet_.\n\nThey also just released [a video on how they finance their business](https://www.youtube.com/watch?v=1x-i9z617z4) and their goals which lead me to their shop, and left me with an urge to buy a few of their posters for my office. I always appreciate when businesses are open about their process like this.\n\nCheck them out!","html":"<p><a href=\"https://www.youtube.com/@kurzgesagt\">This is one of my favourite YouTube channels</a>. They really capture the <em>wonder</em> of learning in a way I find really rare. Their videos always bring on a sense of nostalgic learning for me. I remember being a kid and reading books about robotics or space in my Oma's basement and just being <em>stoked</em> about the future.</p><p>Their videos on <a href=\"https://www.youtube.com/watch?v=xAUJYP8tnRE&amp;t=361s\">space sciences</a> and <a href=\"https://www.youtube.com/watch?v=W93XyXHI8Nw&amp;\">future-earth</a> are always favorites of mine. As a reluctant pessimist, I've always appreciated their realistic optimism; they find ways to highlight how we as humans <em>can</em> band together <a href=\"https://www.youtube.com/watch?v=LxgMdjyw8uw\">to solve problems like climate change</a>, not all hope is lost, <em>yet</em>.</p><p>They also just released <a href=\"https://www.youtube.com/watch?v=1x-i9z617z4\">a video on how they finance their business</a> and their goals which lead me to their shop, and left me with an urge to buy a few of their posters for my office. I always appreciate when businesses are open about their process like this.</p><p>Check them out!</p>"}},{"id":"putting-ai-back-in-the-bag","url":"https://mykalmachon.com/posts/putting-ai-back-in-the-bag","meta":{"title":"Putting AI back in the bag","pubDate":"2023-03-26T00:00:00.000Z","type":"note","draft":false,"tags":["ai","opinion"],"stage":"seed","description":"Humans suck at putting things down when they feel like there's potential for good or profit. AI is a great example."},"content":{"md":"I wrote a very \"stream-of-conciousness\" thread on AI and how we probably can't put it back in pandoras box on Mastodon. You can read it as a primer below:\n\n<iframe src=\"https://indieweb.social/@mykalmachon/110090905453454176/embed\" class=\"mastodon-embed\" style=\"max-width: 100%; border: 0\" width=\"400\" allowfullscreen=\"allowfullscreen\"></iframe><script src=\"https://indieweb.social/embed.js\" async=\"async\"></script>\n\nI wanted to elaborate a bit in a longer format where I didn't have to worry about splitting up my posts.\n\n## Generative AI as a power for good\n\nI don't believe that the people working on generative AI aren't doing it out of malice. It's very easy to see there are huge possibilities for _good_ to come from it.\n\nHere's a few things I can think of off the top of my head:\n\n*   Automate away and assist in generating written communications\n    \n*   Making otherwise inaccessible services accessible to this with different abilities (think of grocery shopping using something [like Chat-GPT plugins](https://openai.com/blog/chatgpt-plugins)).\n    \n*   Solid \"chat\" style interfaces for customer support, brainstorming, etc.\n    \n*   Generating a rough creative output with nothing but words as a starting point.\n    \n*   A completely autonomous \"assistant\" that can work through a todo list of digital tasks for you.\n    \n*   Writing, maintaining, and fixing code with a loose guide ([GitHub Copilot X](https://github.com/features/preview/copilot-x))\n    \n*   Providing a plain English \"API\" to more complicated systems ([Microsoft 365 Copilot](https://www.youtube.com/watch?v=S7xTBa93TX8))\n    \n\n## Generative AI as a power for evil\n\nWhile these are great, when you look at the list of unarguably terrible things AI can do without proper guard rails, they lose their sheen.\n\nHere's a few of the things I've been thinking about recently:\n\n*   Anyone can now make an automated, self-replying, convincing, phishing scam that responds by itself and doesn't \"believe\" it's phishing. [This already exists](https://www.csoonline.com/article/3685488/how-ai-chatbot-chatgpt-changes-the-phishing-game.html)\n    \n*   Propagandists can realistically make the \"dead-internet\" a reality and fill faux social media sites with propaganda that will defend it's position automatically like a real person.\n    \n*   Anyone can generate completely fake photos and videos of _whoever_ (politicians, celebrities, a family member with lots of photos online) saying and doing _whatever they want._ **This already exists and isn't hard to find;** I'm not linking to it for hopefully obvious reasons.\n    \n*   Objective truth on the digital internet will effectively be dead once these generative AI tools get sufficiently indistinguishable from reality\n    \n\nAll very fun and very fresh ✌️\n\nThis really feels like a digital equivalent to CFCs burning a hole in our o-zone layer; luckily we were able to regulate their use and prevent the earth from crisping itself.\n\nHopefully we learn to get our collective minds out of the clouds and start regulating this, before we lose the internet as we know it today.","html":"<p>I wrote a very &quot;stream-of-conciousness&quot; thread on AI and how we probably can't put it back in pandoras box on Mastodon. You can read it as a primer below:</p><p>&lt;iframe src=&quot;https://indieweb.social/@mykalmachon/110090905453454176/embed&quot; class=&quot;mastodon-embed&quot; style=&quot;max-width: 100%; border: 0&quot; width=&quot;400&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt;&lt;script src=&quot;https://indieweb.social/embed.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;</p><p>I wanted to elaborate a bit in a longer format where I didn't have to worry about splitting up my posts.</p><h2>Generative AI as a power for good</h2><p>I don't believe that the people working on generative AI aren't doing it out of malice. It's very easy to see there are huge possibilities for <em>good</em> to come from it.</p><p>Here's a few things I can think of off the top of my head:</p><ul><li><p>Automate away and assist in generating written communications</p></li><li><p>Making otherwise inaccessible services accessible to this with different abilities (think of grocery shopping using something <a href=\"https://openai.com/blog/chatgpt-plugins\">like Chat-GPT plugins</a>).</p></li><li><p>Solid &quot;chat&quot; style interfaces for customer support, brainstorming, etc.</p></li><li><p>Generating a rough creative output with nothing but words as a starting point.</p></li><li><p>A completely autonomous &quot;assistant&quot; that can work through a todo list of digital tasks for you.</p></li><li><p>Writing, maintaining, and fixing code with a loose guide (<a href=\"https://github.com/features/preview/copilot-x\">GitHub Copilot X</a>)</p></li><li><p>Providing a plain English &quot;API&quot; to more complicated systems (<a href=\"https://www.youtube.com/watch?v=S7xTBa93TX8\">Microsoft 365 Copilot</a>)</p></li></ul><h2>Generative AI as a power for evil</h2><p>While these are great, when you look at the list of unarguably terrible things AI can do without proper guard rails, they lose their sheen.</p><p>Here's a few of the things I've been thinking about recently:</p><ul><li><p>Anyone can now make an automated, self-replying, convincing, phishing scam that responds by itself and doesn't &quot;believe&quot; it's phishing. <a href=\"https://www.csoonline.com/article/3685488/how-ai-chatbot-chatgpt-changes-the-phishing-game.html\">This already exists</a></p></li><li><p>Propagandists can realistically make the &quot;dead-internet&quot; a reality and fill faux social media sites with propaganda that will defend it's position automatically like a real person.</p></li><li><p>Anyone can generate completely fake photos and videos of <em>whoever</em> (politicians, celebrities, a family member with lots of photos online) saying and doing <em>whatever they want.</em> <strong>This already exists and isn't hard to find;</strong> I'm not linking to it for hopefully obvious reasons.</p></li><li><p>Objective truth on the digital internet will effectively be dead once these generative AI tools get sufficiently indistinguishable from reality</p></li></ul><p>All very fun and very fresh ✌️</p><p>This really feels like a digital equivalent to CFCs burning a hole in our o-zone layer; luckily we were able to regulate their use and prevent the earth from crisping itself.</p><p>Hopefully we learn to get our collective minds out of the clouds and start regulating this, before we lose the internet as we know it today.</p>"}},{"id":"the-end-of-fe-dev","url":"https://mykalmachon.com/posts/the-end-of-fe-dev","meta":{"title":"The End of Front-End Development","pubDate":"2023-03-21T00:00:00.000Z","type":"link","draft":false,"tags":["ai","opinion"],"description":"A great blog post that sums up how I feel about GPT models.","url":"https://www.joshwcomeau.com/blog/the-end-of-frontend-development/"},"content":{"md":"Josh's post on AI, large language models like GPT3/GPT4, and what they mean for the future of Front-End Development (well, really work in general) is great. It sums up a lot of the feelings I have about the topic and is written much better than the mess of point-form notes I've been working on for the past few weeks.\n\nI would be lying if I said I was a little afraid about what a world with these AI models in it looks like, but I think like with every other obstacle in human history we'll probably be okay.\n\nProbably 😅","html":"<p>Josh's post on AI, large language models like GPT3/GPT4, and what they mean for the future of Front-End Development (well, really work in general) is great. It sums up a lot of the feelings I have about the topic and is written much better than the mess of point-form notes I've been working on for the past few weeks.</p><p>I would be lying if I said I was a little afraid about what a world with these AI models in it looks like, but I think like with every other obstacle in human history we'll probably be okay.</p><p>Probably 😅</p>"}},{"id":"containers-and-the-enterprise-vendor-feedback-loop","url":"https://mykalmachon.com/posts/containers-and-the-enterprise-vendor-feedback-loop","meta":{"title":"Containers and The Enterprise-Vendor Feedback Loop","pubDate":"2023-03-07T00:00:00.000Z","type":"post","draft":false,"tags":["devops","opinion"],"description":"Enterprise and Enterprise Vendor's both improve at a glacial pace; why?"},"content":{"md":"I love containers. _I think they're cool_. Like any tech they have their issues[^1], but when done right enable automated deploys, self-repairing systems, and a direct programmatic \"source of truth\" interface for infrastructure. All of which can be a _huge_ asset in an organization with a significant number of server-based applications, jobs, daemons, and systems.\n\n**Despite all the benefits, and our teams success with containers in certain contexts[^2], I've noticed there seems to be a bit of a feedback loop that prevents containers from reaching wide-spread adoption at work.** I've attributed this to something I've been calling the \"enterprise-vendor feedback loop\". Here's an example of what I mean:\n\n- Enterprise wants to move forward with container technology (Docker, K8s, etc.)\n- Niche enterprise vendors don't offer a solution compatible with container technology\n- Enterprise is not able to get enough buy in to move forward in a meaningful way.\n- Enterprises technological growth stagnates.\n\nNow imagine the same thing from the vendor's perspective\n\n- Team within vendor co wants to better their offering by adding containers as a build target.\n- Clients are not \"ready\" for containers because most other vendors aren't offering it\n- Vendor doesn't have enough buy-in from customers to move forward with containers.\n- Vendor's technological growth stagnates.\n\nThis feedback loop seems to keep both enterprise companies and enterprise vendors in a glacial state of continuous improvement. Neither organization will make a substantial change in underlying technology until there is an absolute need for the change. The challenge of moving forward without buy-in from the other group is often above either organization's pain threshold for change.\n\n## Why not just containerize vendor applications?\n\nI've thought about just containerizing vendor applications quite a bit as well. In theory an org could containerize vendor applications as they're on-boarded. Just write up a Docker file + a Docker compose file for each new application during the onboarding process!\n\nWhile that might be fine initially, there are some maintenance risks in doing so:\n\n- The vendor may no longer support the application with a custom container layer on-top. Some vendors just don't support any customizations, and by putting that layer on top of the whole app you've potentially put the whole app out-of-support.\n- Windows server apps often have issues when containerized from what I've seen. Especially so when a custom GUI is involved for setup and configuration tasks in the software.\n- Upgrades become significantly more complicated if the vendors application is heavily refactored and the vendor is not comfortable with containers.\n\n## Not just containers, unfortunately\n\nThis applies to more than just containerization technology though. That feedback loop can prevent a number of advances in a large enterprise organization. Here are some other examples I've seen:\n\n- Desktop virtualization\n- CI/CD pipelines\n- non-conventional environments (server-less, cloud, hybrid)\n\n## Anyone have any ideas?\n\nMaybe I'm missing an obvious solution here, but would be open to any ideas you have around how we can get out of this continuous improvement rut. It may just be that things are moving slower than I'd like and that's the way of the world...\n\n[^1]: Containers are hard to do right. I've spent the better part of the last 3 years learning about them and still feel like I'm just scratching the surface. I've got the dockerfile and docker-compose.yml workflow down, and love how they make deploying in the cloud easy across platforms. That said, after setting up a [K3s envrionment](https://k3s.io/) with some raspberry pi 4's in my office I was absolutely lost. In trying to make a system that can account for every usecase, containers and container orchestration tools end up very complicated™.\n\n[^2]: We containerize most of our in-house developed applications at this point, as well as almost all of the open-source software that we use. Deploys and updates become as easy as `docker-compose up`. Most open source software also has publicly available containers like [redis](https://hub.docker.com/_/redis), [uptime kuma](https://hub.docker.com/r/louislam/uptime-kuma), etc.","html":"<p>I love containers. <em>I think they're cool</em>. Like any tech they have their issues[^1], but when done right enable automated deploys, self-repairing systems, and a direct programmatic &quot;source of truth&quot; interface for infrastructure. All of which can be a <em>huge</em> asset in an organization with a significant number of server-based applications, jobs, daemons, and systems.</p><p><strong>Despite all the benefits, and our teams success with containers in certain contexts[^2], I've noticed there seems to be a bit of a feedback loop that prevents containers from reaching wide-spread adoption at work.</strong> I've attributed this to something I've been calling the &quot;enterprise-vendor feedback loop&quot;. Here's an example of what I mean:</p><ul><li>Enterprise wants to move forward with container technology (Docker, K8s, etc.)</li><li>Niche enterprise vendors don't offer a solution compatible with container technology</li><li>Enterprise is not able to get enough buy in to move forward in a meaningful way.</li><li>Enterprises technological growth stagnates.</li></ul><p>Now imagine the same thing from the vendor's perspective</p><ul><li>Team within vendor co wants to better their offering by adding containers as a build target.</li><li>Clients are not &quot;ready&quot; for containers because most other vendors aren't offering it</li><li>Vendor doesn't have enough buy-in from customers to move forward with containers.</li><li>Vendor's technological growth stagnates.</li></ul><p>This feedback loop seems to keep both enterprise companies and enterprise vendors in a glacial state of continuous improvement. Neither organization will make a substantial change in underlying technology until there is an absolute need for the change. The challenge of moving forward without buy-in from the other group is often above either organization's pain threshold for change.</p><h2>Why not just containerize vendor applications?</h2><p>I've thought about just containerizing vendor applications quite a bit as well. In theory an org could containerize vendor applications as they're on-boarded. Just write up a Docker file + a Docker compose file for each new application during the onboarding process!</p><p>While that might be fine initially, there are some maintenance risks in doing so:</p><ul><li>The vendor may no longer support the application with a custom container layer on-top. Some vendors just don't support any customizations, and by putting that layer on top of the whole app you've potentially put the whole app out-of-support.</li><li>Windows server apps often have issues when containerized from what I've seen. Especially so when a custom GUI is involved for setup and configuration tasks in the software.</li><li>Upgrades become significantly more complicated if the vendors application is heavily refactored and the vendor is not comfortable with containers.</li></ul><h2>Not just containers, unfortunately</h2><p>This applies to more than just containerization technology though. That feedback loop can prevent a number of advances in a large enterprise organization. Here are some other examples I've seen:</p><ul><li>Desktop virtualization</li><li>CI/CD pipelines</li><li>non-conventional environments (server-less, cloud, hybrid)</li></ul><h2>Anyone have any ideas?</h2><p>Maybe I'm missing an obvious solution here, but would be open to any ideas you have around how we can get out of this continuous improvement rut. It may just be that things are moving slower than I'd like and that's the way of the world...</p><p>[^1]: Containers are hard to do right. I've spent the better part of the last 3 years learning about them and still feel like I'm just scratching the surface. I've got the dockerfile and docker-compose.yml workflow down, and love how they make deploying in the cloud easy across platforms. That said, after setting up a <a href=\"https://k3s.io/\">K3s envrionment</a> with some raspberry pi 4's in my office I was absolutely lost. In trying to make a system that can account for every usecase, containers and container orchestration tools end up very complicated™.</p><p>[^2]: We containerize most of our in-house developed applications at this point, as well as almost all of the open-source software that we use. Deploys and updates become as easy as <code>docker-compose up</code>. Most open source software also has publicly available containers like <a href=\"https://hub.docker.com/_/redis\">redis</a>, <a href=\"https://hub.docker.com/r/louislam/uptime-kuma\">uptime kuma</a>, etc.</p>"}},{"id":"cabin-on-an-island","url":"https://mykalmachon.com/posts/cabin-on-an-island","meta":{"title":"Cabin on an Island","pubDate":"2023-02-22T00:00:00.000Z","type":"photo","draft":false,"tags":["personal"],"location":"an Island","photos":[{"url":"https://res.cloudinary.com/mykalcodes/image/upload/v1677125240/Mykal%20Codes/PXL_20230218_211945534.jpg","alt":"A landscape showing the ocean, blocked from view by some trees. taken from a wooden balcony overlooking the ocean."},{"url":"https://res.cloudinary.com/mykalcodes/image/upload/v1677125240/Mykal%20Codes/PXL_20230220_200449938.jpg","alt":"A landscape from within a reading nook. The ocean and other islands are visible from the reading nook. A tree blocks the way."},{"url":"https://res.cloudinary.com/mykalcodes/image/upload/v1677125240/Mykal%20Codes/PXL_20230220_221250627.jpg","alt":"A landscape of a stony beach, with beach + trees on the left side, and ocean on the right side. Other islands are visible in the background."}],"description":""},"content":{"md":"![A landscape showing the ocean, blocked from view by some trees. taken from a wooden balcony overlooking the ocean.](../../assets/posts/coai-1.jpg)\n![A landscape from within a reading nook. The ocean and other islands are visible from the reading nook. A tree blocks the way.](../../assets/posts/coai-2.jpg)\n![A landscape of a stony beach, with beach + trees on the left side, and ocean on the right side. Other islands are visible in the background.](../../assets/posts/coai-3.jpg)\n\nI spent this past weekend at a cabin on an island. It was great.\nI wrote around 2000 words about this cabin on an island, and how it gave me some much needed perspective on life.\n\nI'm not going to share what I wrote though; it was a bit too personal. I'm just going to post some phone photos (forgot the real camera at home, whoops!) of views around said cabin instead.\n\n_Let this post act as a quick reminder to:_\n\n- Treasure your people: They're probably pretty great.\n- Spend time away from screens: Computers are cool but not all the time.\n- Recharge your batteries: Do things that make you happy.\n- Take time to reflect: Sometimes just writing out how you feel helps.","html":"<p><img src=\"../../assets/posts/coai-1.jpg\" alt=\"A landscape showing the ocean, blocked from view by some trees. taken from a wooden balcony overlooking the ocean.\"><img src=\"../../assets/posts/coai-2.jpg\" alt=\"A landscape from within a reading nook. The ocean and other islands are visible from the reading nook. A tree blocks the way.\"><img src=\"../../assets/posts/coai-3.jpg\" alt=\"A landscape of a stony beach, with beach + trees on the left side, and ocean on the right side. Other islands are visible in the background.\"></p><p>I spent this past weekend at a cabin on an island. It was great.I wrote around 2000 words about this cabin on an island, and how it gave me some much needed perspective on life.</p><p>I'm not going to share what I wrote though; it was a bit too personal. I'm just going to post some phone photos (forgot the real camera at home, whoops!) of views around said cabin instead.</p><p><em>Let this post act as a quick reminder to:</em></p><ul><li>Treasure your people: They're probably pretty great.</li><li>Spend time away from screens: Computers are cool but not all the time.</li><li>Recharge your batteries: Do things that make you happy.</li><li>Take time to reflect: Sometimes just writing out how you feel helps.</li></ul>"}},{"id":"hot-dog-renaissance","url":"https://mykalmachon.com/posts/hot-dog-renaissance","meta":{"title":"Hot Dog Renaissance","pubDate":"2023-02-04T00:00:00.000Z","type":"photo","draft":false,"tags":["food","personal"],"location":"University of the Fraser Valley","description":""},"content":{"md":"![a painting by Chris Woods titled \"The Realm of Convenience II\". It depicts a blonde man on his knees with hotdogs in his hands being consoled by another blonde woman with ketchup in her hand. They're located in a convenience store.](../../assets/posts/hdr.jpg)\n\nThe painting above is titled \"The Realm of Convenience II\" and was painted by [Chris Woods](https://www.instagram.com/chris_woods_emporer/) in 1990.\nIt's maintained by and on display in the UFV Permanent Art Collection[^1]\n\n## My history with the painting\n\nThis painting was on display between a bunch of classes I had at UFV and I would see it in passing regularly.\nAs a busy student, I always liked the painting, but never engaged with it more than a 30 second stare and a\n\"huh, that's a cool painting\" on my way to somewhere else. Nevertheless, it left an impression on me as a cool piece of art.\n\nIt was moved closer to my office[^2] a few weeks ago and a friend/coworker asked if I'd seen the \"Hot Dog Renaissance\" painting yet.\nI knew what they meant instantly. **Tell me that _doesn't_ look like a painting titled Hot Dog Renaissance**. We talked about it a bit and it got me wondering about the artist and the painting's background.\n\nI walked down to the painting and took a look at the nameplate to the right of it which displayed the title, year painted, and artist's name;\n_The Realm of Convenience II, 1990, Chris Woods._\n\nThe \"II\" at the end of the painting's name implies that there is at least a \"I\" in this series; [a quick google search proved that to be true](https://i0.wp.com/petleyjones.com/wp-content/uploads/2013/05/Woods-Chris-The-Realm-of-Convenience-I.jpg?ssl=1). After that search I sorta fell down a rabbit hole of Chris's art.\n\n## Keeping record\n\n**I was surprised to find no trace of \"The Realm of Convenience II\" anywhere on the internet.**\n\nGiven I couldn't find any other information about the painting, I figured I'd share a picture and a little writeup of my personal history with the painting here.\nIt's a great painting that I think deserves it's own little spot on the internet even if it's just for me and some friends.\n\nedit: [a crunchy n' crusty scan of the photo exists on the SELIYEMETAXWTEXW art gallery wesbite](https://sag.ufvsoca.ca/sag/1994/05/15/chris-woods/). \n\n## Thanks Chris\n\nThanks to Chris Woods for making good art. \n\n[Check out his Instagram](https://www.instagram.com/chris_woods_emporer/) if you like the painting, he's got a pretty large body of work and appears to still be active. Some personal favourite's are [\"Behold the King\"](https://www.instagram.com/p/Bv2N4cHHju6/) and the [\"Fast Food Nation\" collection](http://www.pondly.com/2013/06/fast-food-nation-illustrations-by-chris-woods/).\n\n[^1]: TIL that my workplace / graduating university has an art collection.\n[^2]: I graduated with a Bachelor of Computer Information Systems from UFV in 2021; Post-grad I got a job in IT as a systems analyst.","html":"<p><img src=\"../../assets/posts/hdr.jpg\" alt=\"a painting by Chris Woods titled &quot;The Realm of Convenience II&quot;. It depicts a blonde man on his knees with hotdogs in his hands being consoled by another blonde woman with ketchup in her hand. They're located in a convenience store.\"></p><p>The painting above is titled &quot;The Realm of Convenience II&quot; and was painted by <a href=\"https://www.instagram.com/chris_woods_emporer/\">Chris Woods</a> in 1990.It's maintained by and on display in the UFV Permanent Art Collection[^1]</p><h2>My history with the painting</h2><p>This painting was on display between a bunch of classes I had at UFV and I would see it in passing regularly.As a busy student, I always liked the painting, but never engaged with it more than a 30 second stare and a&quot;huh, that's a cool painting&quot; on my way to somewhere else. Nevertheless, it left an impression on me as a cool piece of art.</p><p>It was moved closer to my office[^2] a few weeks ago and a friend/coworker asked if I'd seen the &quot;Hot Dog Renaissance&quot; painting yet.I knew what they meant instantly. <strong>Tell me that <em>doesn't</em> look like a painting titled Hot Dog Renaissance</strong>. We talked about it a bit and it got me wondering about the artist and the painting's background.</p><p>I walked down to the painting and took a look at the nameplate to the right of it which displayed the title, year painted, and artist's name;<em>The Realm of Convenience II, 1990, Chris Woods.</em></p><p>The &quot;II&quot; at the end of the painting's name implies that there is at least a &quot;I&quot; in this series; <a href=\"https://i0.wp.com/petleyjones.com/wp-content/uploads/2013/05/Woods-Chris-The-Realm-of-Convenience-I.jpg?ssl=1\">a quick google search proved that to be true</a>. After that search I sorta fell down a rabbit hole of Chris's art.</p><h2>Keeping record</h2><p><strong>I was surprised to find no trace of &quot;The Realm of Convenience II&quot; anywhere on the internet.</strong></p><p>Given I couldn't find any other information about the painting, I figured I'd share a picture and a little writeup of my personal history with the painting here.It's a great painting that I think deserves it's own little spot on the internet even if it's just for me and some friends.</p><p>edit: <a href=\"https://sag.ufvsoca.ca/sag/1994/05/15/chris-woods/\">a crunchy n' crusty scan of the photo exists on the SELIYEMETAXWTEXW art gallery wesbite</a>.</p><h2>Thanks Chris</h2><p>Thanks to Chris Woods for making good art.</p><p><a href=\"https://www.instagram.com/chris_woods_emporer/\">Check out his Instagram</a> if you like the painting, he's got a pretty large body of work and appears to still be active. Some personal favourite's are <a href=\"https://www.instagram.com/p/Bv2N4cHHju6/\">&quot;Behold the King&quot;</a> and the <a href=\"http://www.pondly.com/2013/06/fast-food-nation-illustrations-by-chris-woods/\">&quot;Fast Food Nation&quot; collection</a>.</p><p>[^1]: TIL that my workplace / graduating university has an art collection.[^2]: I graduated with a Bachelor of Computer Information Systems from UFV in 2021; Post-grad I got a job in IT as a systems analyst.</p>"}},{"id":"starting-a-garden","url":"https://mykalmachon.com/posts/starting-a-garden","meta":{"title":"Starting a Garden 🍅","pubDate":"2023-01-30T00:00:00.000Z","type":"post","draft":false,"tags":["personal"],"description":"I redid my site again; this post goes over why, and what's changed this time around."},"content":{"md":"I redid my site! ... again!\n\nI've been messing around with the look and feel of this site on a dev branch for a few months,\nand started a dedicated feature branch for the overhaul a few weeks ago. I'm pretty happy with the\n_look_, but I made more than just stylistic changes. **I added some content types, ripped out some features,\nand moved all my old content over to `/garden/posts`**\n\nSo what gives? what is a digital garden anyways? when's the next redesign 😉?\n\n## What's a Digital Garden\n\nI won't spend too much time digging in, but I stand on the shoulder's of dev giant's here.\n[Maggie Appleton](https://maggieappleton.com/about) has a [great post going over the concept](https://maggieappleton.com/garden-history), and so does [Joel Hooks](https://joelhooks.com/digital-garden). With their explanations in mind, I have a few other things that I think feel _garden-like_ in the way I've designed the site this time around:\n\n### Keep it messy\n\nI don't want everything on this site to have the implicit pressure that a \"Blog Post\" does. Believe it or not, I don't want everything I write to be pristine.\nSometimes I just want to put my thoughts out into the aether, without a lot of formality. See if they stick. Use them as a reference point. See what others think. You know?\n\nWith that in mind, I've added a bunch of new content types: [notes](/garden/notes), [photos](/garden/photos), and [links](/garden/links) to de-stress posting here.\n\nA lot of my ethos for this site was inspired by something from one of Rach Smith's note's:\n\n> I dropped the baggage around “blog post” ... started thinking about my notes as works in progress, learning in public rather than complete pieces of work. The mental shift helped me quit overthinking about what I published.\n>\n> - [Rach Smith - How I wrote more](https://rachsmith.com/how-i-wrote-more/)\n\n_She also calls her site a digital garden... what a coincidence!_\n\n### Keep it simple\n\nI've kept the site architecturally simple by design. There aren't a lot of super-complex features. The post likes and comments I had on here before were great, but were examples of features that had heavy vendor lock-in and just kind of felt \"heavy\".\n\n**This site could realistically live on a 11 year old computer in a basement, but tools like Netlify add goodies on top without weighing things down.**\n\nin 5-10 years, I don't want to lose a bunch of photos, ideas, and little pieces of my personal history because a billion-dollar baby blew up my website. This site is all `.astro` files, markdown, and open-source packages with a little progressively enhanced sugar on top.\n\n### Be a good steward\n\nI see posting to my own site as an act of Internet stewardship. An independent and individual internet is a healthy internet. By [keeping it simple](#keep-it-simple) I've pretty much minimized my barrier of entry to hanging out on the internet and helping take care of it.\n\nThis doesn't just mean posting on this little hermit island of a website, it means sharing and consuming RSS feeds, cross-posting, experimenting with new and equitable internet tech (ActivityPub?) and generally making content accessible and shareable across the internet and for all people. It also means building relationships through the web, which I'm very grateful to say that I've been doing for awhile, Digital Garden or not.\n\nThe IndieWeb page for [POSSE](https://indieweb.org/POSSE) got me thinking about this awhile ago; it's a good read if you're interested in this concept.\n\n## What else has changed\n\nNow that you know what a Digital Garden is, here's a some of the other things that have changed:\n\n### Things added\n\n- Fancy new design with a focus on improved readability (colors, font-size, etc.)\n  - Upgraded CSS variables for styling thanks to [some new variables in Open-Props](https://open-props.style/)\n- Content types: checkout the [garden home page](/garden/) for more on this.\n- Hashed low-res images embedded into HTML `<img />` tags to improve image lazy loading.\n  - Considering writing a post on this one. Was a fun experiment.\n- [Astro 2.0 content collections](https://docs.astro.build/en/guides/content-collections/) for better authoring and type safety.\n- [Multiple RSS feeds](/feeds/) so you can subscribe to what you like and leave the rest\n- WebMentions for comments, likes, boosts, etc on all posts.\n  - _Still more work to do here_; I may enable SSR on content pages to enable dynamic fetching of this data.\n- I'm sure there's more I'm forgetting about.\n\n### Things removed\n\n- GitHub comments via Utteranc.es: easy to setup, didn't like the look.\n  - ✨ Replaced with Webmentions!\n- Custom reactions via Cloudflare: was fun to build, but didn't love how much baggage was attached.\n  - ✨ Replaced with Webmentions!\n- Dark/Light/Custom Themes: These worked great but I was seeing some [FART](https://css-tricks.com/flash-of-inaccurate-color-theme-fart/).\n  - 🤔 These are coming back eventually; I want to solve the flashing theme once and for all and add more themes\n\n## More coming soon!\n\nI've you've made it this far, **thanks for reading.**\n\nI'm going to be posting here more and more so if you're subscribed I'm sure you'll be hearing from me real soon.\n\nHave a great day!","html":"<p>I redid my site! ... again!</p><p>I've been messing around with the look and feel of this site on a dev branch for a few months,and started a dedicated feature branch for the overhaul a few weeks ago. I'm pretty happy with the<em>look</em>, but I made more than just stylistic changes. <strong>I added some content types, ripped out some features,and moved all my old content over to <code>/garden/posts</code></strong></p><p>So what gives? what is a digital garden anyways? when's the next redesign 😉?</p><h2>What's a Digital Garden</h2><p>I won't spend too much time digging in, but I stand on the shoulder's of dev giant's here.<a href=\"https://maggieappleton.com/about\">Maggie Appleton</a> has a <a href=\"https://maggieappleton.com/garden-history\">great post going over the concept</a>, and so does <a href=\"https://joelhooks.com/digital-garden\">Joel Hooks</a>. With their explanations in mind, I have a few other things that I think feel <em>garden-like</em> in the way I've designed the site this time around:</p><h3>Keep it messy</h3><p>I don't want everything on this site to have the implicit pressure that a &quot;Blog Post&quot; does. Believe it or not, I don't want everything I write to be pristine.Sometimes I just want to put my thoughts out into the aether, without a lot of formality. See if they stick. Use them as a reference point. See what others think. You know?</p><p>With that in mind, I've added a bunch of new content types: <a href=\"/garden/notes\">notes</a>, <a href=\"/garden/photos\">photos</a>, and <a href=\"/garden/links\">links</a> to de-stress posting here.</p><p>A lot of my ethos for this site was inspired by something from one of Rach Smith's note's:</p><blockquote><p>I dropped the baggage around “blog post” ... started thinking about my notes as works in progress, learning in public rather than complete pieces of work. The mental shift helped me quit overthinking about what I published.</p><ul><li><a href=\"https://rachsmith.com/how-i-wrote-more/\">Rach Smith - How I wrote more</a></li></ul></blockquote><p><em>She also calls her site a digital garden... what a coincidence!</em></p><h3>Keep it simple</h3><p>I've kept the site architecturally simple by design. There aren't a lot of super-complex features. The post likes and comments I had on here before were great, but were examples of features that had heavy vendor lock-in and just kind of felt &quot;heavy&quot;.</p><p><strong>This site could realistically live on a 11 year old computer in a basement, but tools like Netlify add goodies on top without weighing things down.</strong></p><p>in 5-10 years, I don't want to lose a bunch of photos, ideas, and little pieces of my personal history because a billion-dollar baby blew up my website. This site is all <code>.astro</code> files, markdown, and open-source packages with a little progressively enhanced sugar on top.</p><h3>Be a good steward</h3><p>I see posting to my own site as an act of Internet stewardship. An independent and individual internet is a healthy internet. By <a href=\"#keep-it-simple\">keeping it simple</a> I've pretty much minimized my barrier of entry to hanging out on the internet and helping take care of it.</p><p>This doesn't just mean posting on this little hermit island of a website, it means sharing and consuming RSS feeds, cross-posting, experimenting with new and equitable internet tech (ActivityPub?) and generally making content accessible and shareable across the internet and for all people. It also means building relationships through the web, which I'm very grateful to say that I've been doing for awhile, Digital Garden or not.</p><p>The IndieWeb page for <a href=\"https://indieweb.org/POSSE\">POSSE</a> got me thinking about this awhile ago; it's a good read if you're interested in this concept.</p><h2>What else has changed</h2><p>Now that you know what a Digital Garden is, here's a some of the other things that have changed:</p><h3>Things added</h3><ul><li>Fancy new design with a focus on improved readability (colors, font-size, etc.)<ul><li>Upgraded CSS variables for styling thanks to <a href=\"https://open-props.style/\">some new variables in Open-Props</a></li></ul></li><li>Content types: checkout the <a href=\"/garden/\">garden home page</a> for more on this.</li><li>Hashed low-res images embedded into HTML <code>&lt;img /&gt;</code> tags to improve image lazy loading.<ul><li>Considering writing a post on this one. Was a fun experiment.</li></ul></li><li><a href=\"https://docs.astro.build/en/guides/content-collections/\">Astro 2.0 content collections</a> for better authoring and type safety.</li><li><a href=\"/feeds/\">Multiple RSS feeds</a> so you can subscribe to what you like and leave the rest</li><li>WebMentions for comments, likes, boosts, etc on all posts.<ul><li><em>Still more work to do here</em>; I may enable SSR on content pages to enable dynamic fetching of this data.</li></ul></li><li>I'm sure there's more I'm forgetting about.</li></ul><h3>Things removed</h3><ul><li>GitHub comments via Utteranc.es: easy to setup, didn't like the look.<ul><li>✨ Replaced with Webmentions!</li></ul></li><li>Custom reactions via Cloudflare: was fun to build, but didn't love how much baggage was attached.<ul><li>✨ Replaced with Webmentions!</li></ul></li><li>Dark/Light/Custom Themes: These worked great but I was seeing some <a href=\"https://css-tricks.com/flash-of-inaccurate-color-theme-fart/\">FART</a>.<ul><li>🤔 These are coming back eventually; I want to solve the flashing theme once and for all and add more themes</li></ul></li></ul><h2>More coming soon!</h2><p>I've you've made it this far, <strong>thanks for reading.</strong></p><p>I'm going to be posting here more and more so if you're subscribed I'm sure you'll be hearing from me real soon.</p><p>Have a great day!</p>"}},{"id":"peanut-bowl-recipe","url":"https://mykalmachon.com/posts/peanut-bowl-recipe","meta":{"title":"Spicy Peanut Bowl 🥜","pubDate":"2023-01-28T00:00:00.000Z","type":"note","draft":false,"tags":["food"],"description":"A recipe for one of my favourite feel-good meals."},"content":{"md":"I've had a few friends ask for this recipe so thought I'd write it up on the blog. Commonly served after a hard day at work or when fighting off the cold; spice tends to clear out my sinuses.\n\n- 🧑‍❤️‍👩 Typically serves 2 people\n- ⏲️ Takes around 25-30 minutes to prepare\n\n_I don't have exact measurements for a lot of things so you'll have to do some experimenting; I've been sort of keen to the \"put sh\\*t in a pot\" method of cooking lately._\n\n## Ingredients\n\nAs with most cooking adventures, feel free to add and subtract from this list as you see fit.\n\n- Onion\n- Red and Yellow Bell Peppers\n- Shelled Unsalted Peanuts\n- Edamame\n- Chili pepper\n- Sesame Seeds\n- Fresh Ginger\n- Beyond Ground Beef / Ground beef\n- Udon noodles: Bucatini or lo mein noodles will also work.\n- Olive oil\n- Soy Sauce\n- Sriracha\n- For peanut sauce: can also just use store bought\n  - Smooth PB sauce\n  - Soy Sauce\n  - Chili oil\n  - Sesame Oil\n  - 2 garlic cloves\n  - Hoisin sauce\n  - Fresh Ginger\n  - Rice vinegar\n\n## Recipe\n\nRough steps; feel free to add your own twists and turns in as we go\n\n### Preparation\n\nBefore starting with anything else I find it useful to cut things up\n\n- Cut up the onion and bell peppers and put them aside.\n  - cut diced or into strips depending on preference.\n- Roughly chop the chili pepper up and put it aside.\n- Roughly chop a few cloves of garlic and put them aside.\n- Mince or finely chop the fresh ginger.\n- Take out a pot for boiling the pasta.\n- Take out a large non-stick pan for veggies and protein.\n- Take out a small bowl for the peanut sauce (only if you don't have store bought).\n\n### Cooking noodles\n\nThe noodles will likely take the longest time so I start that before anything else\n\n- add ~6-10 cups of water to the pot depending on the amount of noodles being made.\n- salt the water in the pot to your preference.\n- bring the pot to a boil.\n- add the noodles to the pan and set a timer for the suggested cook time of your noodles.\n- halfway through, add your edamame beans to the pasta water.\n- When the timer is done, strain pasta and edamame and set aside.\n\n### Preparing peanut sauce (optional)\n\n_If you have store bought peanut sauce ignore this step._\n\n- in a small bowl whisk together:\n  - peanut butter (1/4 cup)\n  - soy sauce (1/4 cup)\n  - sesame oil (1 teaspoon)\n  - chilli oil (1 teaspoon)\n  - rice vinegar (1 tablespoon)\n  - ginger (2 teaspoons or more)\n  - garlic cloves (minced from before)\n  - pasta water (1/4 cup: adds starch!)\n- Once it's well incorporated, set aside.\n\n### Preparing protein & veggies\n\nWhile your pasta is cooking, you can get stated with the protein and veggies.\n\n- put your pan on the stove and set it to medium high heat.\n- add olive oil and wait until the pan is hot.\n- when hot, add your preferred protein to the pan and break it up into small pieces.\n- cook protein until it looks close to complete (5-6 minutes).\n- Add peppers, onion, garlic, and chilli pepper to the pan.\n  - you can add a lil' soy sauce / water at this point if it's sticking.\n- Cook and stir often until the veggies seem tender-crisp (2-3 minutes).\n\n### Putting it all together\n\nOnce all your components are done, you can mix everything in the large non-stick pan.\n\n- bring the pan down to medium heat.\n- incorporate the noodles and the edamame into the pan.\n- incorporate the peanut sauce into the pan.\n- Add salt, pepper, chili pepper, and other seasonings to taste.\n- Toss to coat the noodles evenly.\n- Taste test and adjust accordingly.\n\n### Serving\n\nSplit things out into 2 bowls and add the following toppings:\n\n- Peanuts.\n- Sesame seeds.\n- Sriracha (optional, but personally preferred).\n- Cilantro (optional, personally _not_ preferred).\n\nEnjoy 🌶️\n\n## P.S\n\nThe recipe is heavily inspired by a [HelloFresh](https://hellofresh.ca/) box we got ~2 years ago, but I've made some improvements since then. If you want a free HelloFresh box [I've accumulated a bunch of free boxes for friends, take one!](https://www.hellofresh.ca/pages/resolution?c=FIH-QA1IJKXJDZQH&utm_campaign=clipboard&utm_couponvalue=170&utm_invitername=Mykal&utm_medium=referral&utm_source=raf-share&discount_comm_id=1d7ed056-9e72-42f5-a0e0-d5dd12d17fc3).\n\n_Disclaimer, I get a $15 kickback if you sign up for more after your free box._","html":"<p>I've had a few friends ask for this recipe so thought I'd write it up on the blog. Commonly served after a hard day at work or when fighting off the cold; spice tends to clear out my sinuses.</p><ul><li>🧑‍❤️‍👩 Typically serves 2 people</li><li>⏲️ Takes around 25-30 minutes to prepare</li></ul><p><em>I don't have exact measurements for a lot of things so you'll have to do some experimenting; I've been sort of keen to the &quot;put sh*t in a pot&quot; method of cooking lately.</em></p><h2>Ingredients</h2><p>As with most cooking adventures, feel free to add and subtract from this list as you see fit.</p><ul><li>Onion</li><li>Red and Yellow Bell Peppers</li><li>Shelled Unsalted Peanuts</li><li>Edamame</li><li>Chili pepper</li><li>Sesame Seeds</li><li>Fresh Ginger</li><li>Beyond Ground Beef / Ground beef</li><li>Udon noodles: Bucatini or lo mein noodles will also work.</li><li>Olive oil</li><li>Soy Sauce</li><li>Sriracha</li><li>For peanut sauce: can also just use store bought<ul><li>Smooth PB sauce</li><li>Soy Sauce</li><li>Chili oil</li><li>Sesame Oil</li><li>2 garlic cloves</li><li>Hoisin sauce</li><li>Fresh Ginger</li><li>Rice vinegar</li></ul></li></ul><h2>Recipe</h2><p>Rough steps; feel free to add your own twists and turns in as we go</p><h3>Preparation</h3><p>Before starting with anything else I find it useful to cut things up</p><ul><li>Cut up the onion and bell peppers and put them aside.<ul><li>cut diced or into strips depending on preference.</li></ul></li><li>Roughly chop the chili pepper up and put it aside.</li><li>Roughly chop a few cloves of garlic and put them aside.</li><li>Mince or finely chop the fresh ginger.</li><li>Take out a pot for boiling the pasta.</li><li>Take out a large non-stick pan for veggies and protein.</li><li>Take out a small bowl for the peanut sauce (only if you don't have store bought).</li></ul><h3>Cooking noodles</h3><p>The noodles will likely take the longest time so I start that before anything else</p><ul><li>add ~6-10 cups of water to the pot depending on the amount of noodles being made.</li><li>salt the water in the pot to your preference.</li><li>bring the pot to a boil.</li><li>add the noodles to the pan and set a timer for the suggested cook time of your noodles.</li><li>halfway through, add your edamame beans to the pasta water.</li><li>When the timer is done, strain pasta and edamame and set aside.</li></ul><h3>Preparing peanut sauce (optional)</h3><p><em>If you have store bought peanut sauce ignore this step.</em></p><ul><li>in a small bowl whisk together:<ul><li>peanut butter (1/4 cup)</li><li>soy sauce (1/4 cup)</li><li>sesame oil (1 teaspoon)</li><li>chilli oil (1 teaspoon)</li><li>rice vinegar (1 tablespoon)</li><li>ginger (2 teaspoons or more)</li><li>garlic cloves (minced from before)</li><li>pasta water (1/4 cup: adds starch!)</li></ul></li><li>Once it's well incorporated, set aside.</li></ul><h3>Preparing protein &amp; veggies</h3><p>While your pasta is cooking, you can get stated with the protein and veggies.</p><ul><li>put your pan on the stove and set it to medium high heat.</li><li>add olive oil and wait until the pan is hot.</li><li>when hot, add your preferred protein to the pan and break it up into small pieces.</li><li>cook protein until it looks close to complete (5-6 minutes).</li><li>Add peppers, onion, garlic, and chilli pepper to the pan.<ul><li>you can add a lil' soy sauce / water at this point if it's sticking.</li></ul></li><li>Cook and stir often until the veggies seem tender-crisp (2-3 minutes).</li></ul><h3>Putting it all together</h3><p>Once all your components are done, you can mix everything in the large non-stick pan.</p><ul><li>bring the pan down to medium heat.</li><li>incorporate the noodles and the edamame into the pan.</li><li>incorporate the peanut sauce into the pan.</li><li>Add salt, pepper, chili pepper, and other seasonings to taste.</li><li>Toss to coat the noodles evenly.</li><li>Taste test and adjust accordingly.</li></ul><h3>Serving</h3><p>Split things out into 2 bowls and add the following toppings:</p><ul><li>Peanuts.</li><li>Sesame seeds.</li><li>Sriracha (optional, but personally preferred).</li><li>Cilantro (optional, personally <em>not</em> preferred).</li></ul><p>Enjoy 🌶️</p><h2>P.S</h2><p>The recipe is heavily inspired by a <a href=\"https://hellofresh.ca/\">HelloFresh</a> box we got ~2 years ago, but I've made some improvements since then. If you want a free HelloFresh box <a href=\"https://www.hellofresh.ca/pages/resolution?c=FIH-QA1IJKXJDZQH&amp;utm_campaign=clipboard&amp;utm_couponvalue=170&amp;utm_invitername=Mykal&amp;utm_medium=referral&amp;utm_source=raf-share&amp;discount_comm_id=1d7ed056-9e72-42f5-a0e0-d5dd12d17fc3\">I've accumulated a bunch of free boxes for friends, take one!</a>.</p><p><em>Disclaimer, I get a $15 kickback if you sign up for more after your free box.</em></p>"}},{"id":"good-cappuccinos","url":"https://mykalmachon.com/posts/good-cappuccinos","meta":{"title":"Good Cappuccinos","pubDate":"2023-01-21T00:00:00.000Z","type":"photo","draft":false,"tags":["food"],"location":"Abbotsford, BC","description":""},"content":{"md":"![A picture of a beautiful cappuccino with leaf-like latte art sitting on wooden slatted table outside](../../assets/posts/good-cappuccinos.jpg)\n\nI was gifted an espresso machine over the weekend. I've been enjoying experimenting with it and have already seen pretty significant progress.  *That said, I've still got a long way to go.*\n\nGetting the machine has me revisiting some of my favourite coffee spots and thinking about how damn good they are. As with all coffee, there's definitely a level of \"tuning\" you have to do to get \"good coffee\" (regardless of the method) but *man* there is really a bar between the stuff I'm pumping out and the cappuccino's from my favourite shops.\n\nThe cappuccino pictured above is from my favourite coffee shop (sorry, I'm gate keeping). I'm treating it as my bar for quality. I'll be happy when I can get a comparable cup at home.","html":"<p><img src=\"../../assets/posts/good-cappuccinos.jpg\" alt=\"A picture of a beautiful cappuccino with leaf-like latte art sitting on wooden slatted table outside\"></p><p>I was gifted an espresso machine over the weekend. I've been enjoying experimenting with it and have already seen pretty significant progress.  <em>That said, I've still got a long way to go.</em></p><p>Getting the machine has me revisiting some of my favourite coffee spots and thinking about how damn good they are. As with all coffee, there's definitely a level of &quot;tuning&quot; you have to do to get &quot;good coffee&quot; (regardless of the method) but <em>man</em> there is really a bar between the stuff I'm pumping out and the cappuccino's from my favourite shops.</p><p>The cappuccino pictured above is from my favourite coffee shop (sorry, I'm gate keeping). I'm treating it as my bar for quality. I'll be happy when I can get a comparable cup at home.</p>"}},{"id":"2022-2023","url":"https://mykalmachon.com/posts/2022-2023","meta":{"title":"2022 → 2023","pubDate":"2023-01-03T00:00:00.000Z","type":"post","draft":false,"tags":["personal"],"description":"a retrospective on 2022 and some goals for 2023"},"content":{"md":"Happy New Year everyone!\n\nI've never written one of these \"end-of-the-year retrospective\" posts, and I've not been much of a new year's goals _guy_ in the past (you can set goals any day, really). That said, I've seen a bunch of friends post retrospectives and goals for the year, so I wanted to join in and see what all the fun is about. Maybe I'll keep myself accountable to some goals and have a bit of a payoff come this time next year.\n\n## Looking back on 2022\n\n![some people standing on a wooden overlook. They're looking out into a mountainous valley with a river cutting through the middle.  ](https://res.cloudinary.com/mykalcodes/image/upload/v1672717650/Mykal%20Codes/PXL_20221001_201053172.MP_rwnj1f.jpg \"my wife overlooking the squamish BC area on a trail up the sea-to-sky gondola\")\n\nLooking back, this year has gone by so damn fast but it's felt nice to go back and write up a list of the accomplishments and fun stuff that happened throughout the year:\n\n* My wife and I had our first Anniversary. 1 year of marriage and 5 years together have gone by so fast!\n* The family went on a few trips after being cooped up during the pandemic. We had a few notable camping trips, visited Vegas for the first time, and had a few weekend getaways as well.\n* Wrote 29 posts on [my blog](https://mykal.codes) (where you're probably reading this). I'd love to keep this pace up next year.\n* Read 8 books this year! I've [kept track via Goodreads](https://www.goodreads.com/review/list/141327631-mykal?ref=nav_mybooks&shelf=read) and you can check it out there.\n* [Tinybox](https://tinybox.dev) had its best year ever! I have a few stealth projects that I've had lots of fun working on this year and I'm hoping can publicize some of the work this year.\n* I got really into [making coffee](https://mykal.codes/tags/coffee) and have gone from knowing basically nothing to being a semi-proficient at-home coffee fiend.\n\nI didn't keep any goals for 2022 so this is mostly a highlights list. The year was not _all_ great unfortunately but it doesn't really feel emotionally productive to go over the lows here.\n\n## Moving forward into 2023\n\n![My wife and I posing with some goofy happy new years props. I'm wearing a black dress shirt, tie, and pants. My wife is wearing a black dress.](https://res.cloudinary.com/mykalcodes/image/upload/v1672719710/Mykal%20Codes/happy-new-years_bgqmwk.jpg \"My wife and I at my Father In Law's wedding / New Year's Party (unconventional, I know!) it was a great time.\")\n\nHere are a few (mostly) [smart](https://www.smartsheet.com/blog/essential-guide-writing-smart-goals) goals I have for the year. I'm not sure if I'm going to keep the progress private or share it publicly but I don't see a problem with listing them out here:\n\n* Read 12 books or 1 book a month.\n* Open-source 1 functional and well-documented software project on GitHub.\n* Write 24 blog posts, or 2 blog posts a month.\n* Build moderate exercise (30-45 minutes, 3 times a week) and better eating into my life.\n* Get better at separating work time from relaxation/fun time (not sure how to measure that)\n* Go on more dates with my wife; find more casual ways to tell her I appreciate her.\n* Continue [indieweb-ifying](https://indieweb.org/) my presence on the internet.","html":"<p>Happy New Year everyone!</p><p>I've never written one of these &quot;end-of-the-year retrospective&quot; posts, and I've not been much of a new year's goals <em>guy</em> in the past (you can set goals any day, really). That said, I've seen a bunch of friends post retrospectives and goals for the year, so I wanted to join in and see what all the fun is about. Maybe I'll keep myself accountable to some goals and have a bit of a payoff come this time next year.</p><h2>Looking back on 2022</h2><p><img src=\"https://res.cloudinary.com/mykalcodes/image/upload/v1672717650/Mykal%20Codes/PXL_20221001_201053172.MP_rwnj1f.jpg\" alt=\"some people standing on a wooden overlook. They're looking out into a mountainous valley with a river cutting through the middle.  \" title=\"my wife overlooking the squamish BC area on a trail up the sea-to-sky gondola\"></p><p>Looking back, this year has gone by so damn fast but it's felt nice to go back and write up a list of the accomplishments and fun stuff that happened throughout the year:</p><ul><li>My wife and I had our first Anniversary. 1 year of marriage and 5 years together have gone by so fast!</li><li>The family went on a few trips after being cooped up during the pandemic. We had a few notable camping trips, visited Vegas for the first time, and had a few weekend getaways as well.</li><li>Wrote 29 posts on <a href=\"https://mykal.codes\">my blog</a> (where you're probably reading this). I'd love to keep this pace up next year.</li><li>Read 8 books this year! I've <a href=\"https://www.goodreads.com/review/list/141327631-mykal?ref=nav_mybooks&amp;shelf=read\">kept track via Goodreads</a> and you can check it out there.</li><li><a href=\"https://tinybox.dev\">Tinybox</a> had its best year ever! I have a few stealth projects that I've had lots of fun working on this year and I'm hoping can publicize some of the work this year.</li><li>I got really into <a href=\"https://mykal.codes/tags/coffee\">making coffee</a> and have gone from knowing basically nothing to being a semi-proficient at-home coffee fiend.</li></ul><p>I didn't keep any goals for 2022 so this is mostly a highlights list. The year was not <em>all</em> great unfortunately but it doesn't really feel emotionally productive to go over the lows here.</p><h2>Moving forward into 2023</h2><p><img src=\"https://res.cloudinary.com/mykalcodes/image/upload/v1672719710/Mykal%20Codes/happy-new-years_bgqmwk.jpg\" alt=\"My wife and I posing with some goofy happy new years props. I'm wearing a black dress shirt, tie, and pants. My wife is wearing a black dress.\" title=\"My wife and I at my Father In Law's wedding / New Year's Party (unconventional, I know!) it was a great time.\"></p><p>Here are a few (mostly) <a href=\"https://www.smartsheet.com/blog/essential-guide-writing-smart-goals\">smart</a> goals I have for the year. I'm not sure if I'm going to keep the progress private or share it publicly but I don't see a problem with listing them out here:</p><ul><li>Read 12 books or 1 book a month.</li><li>Open-source 1 functional and well-documented software project on GitHub.</li><li>Write 24 blog posts, or 2 blog posts a month.</li><li>Build moderate exercise (30-45 minutes, 3 times a week) and better eating into my life.</li><li>Get better at separating work time from relaxation/fun time (not sure how to measure that)</li><li>Go on more dates with my wife; find more casual ways to tell her I appreciate her.</li><li>Continue <a href=\"https://indieweb.org/\">indieweb-ifying</a> my presence on the internet.</li></ul>"}},{"id":"new-years-2023","url":"https://mykalmachon.com/posts/new-years-2023","meta":{"title":"New Years 2023","pubDate":"2023-01-01T00:00:00.000Z","type":"photo","draft":false,"tags":["personal"],"location":"Chilliwack, BC, Canada","description":""},"content":{"md":"![My wife Kiya and I](../../assets/posts/happy-new-years-2023.jpg)\n\nMy Wife Kiya and I at my Father-In laws wedding.","html":"<p><img src=\"../../assets/posts/happy-new-years-2023.jpg\" alt=\"My wife Kiya and I\"></p><p>My Wife Kiya and I at my Father-In laws wedding.</p>"}},{"id":"flipnote-hatena","url":"https://mykalmachon.com/posts/flipnote-hatena","meta":{"title":"Flipnote Hatena","pubDate":"2022-12-29T00:00:00.000Z","type":"post","draft":false,"tags":["personal"],"description":"A friend of mine shared a Flipnote Hatena archive site that sent me down a rabbit hole.","heroImage":{"src":"/_astro/flipnote-hatena.UdCMd6c8.avif","width":1200,"height":600,"format":"avif"}},"content":{"md":"Flipnote Hatena is a now-defunct online platform based on sharing digital \"Flipnotes\". These were drawn in an app called \"[Flipnote Studio](https://en.wikipedia.org/wiki/Flipnote_Studio)\" released on both the DS and 3DS. Here's an example of an early Flipnote created by one of the devs:\n\n<iframe src=\"https://archive.sudomemo.net/watch/embed/mm3d05l2nc3bcftirpkwckwy1nvc\" scrolling=\"no\" frameborder=\"0\" height=\"429\" width=\"512\"></iframe>\n\nI used this app pretty heavily. As a kid interested in art, animation, and video games who just happened to own a DS, this was a very fun overlap of interests. I created a bunch of little animations and drawings based on events in my life. The most notable in my memory is a small animation depicting me in the passenger seat while my uncle was pulled over for speeding; I remember thinking this was hilarious as a kid.\n\nI also viewed the \"popular flipnotes\" in the app almost daily, I remember there being lots of [stick fights](https://www.sudomemo.net/watch/CBEEA0_0A95B9C620081_000), [music videos](https://archive.sudomemo.net/watch/mg3io13c0czbyyvl24kwn2udpnac) with animated characters, [spooky loops](https://flipnot.es/5UL8E13), and other weird miscellaneous animations:\n\n<iframe src=\"https://archive.sudomemo.net/watch/embed/mw21bqmammy40dtweuatavgl1jbc\" scrolling=\"no\" frameborder=\"0\" height=\"429\" width=\"512\"></iframe>\n\nlast night, I brought this up with a friend who also remembered the site and we talked about it a bit. This morning she sent over [a site that archived all of the original flipnotes on the app](https://archive.sudomemo.net/). Up until today, I had assumed that most of these flipnotes were lost to the sands of time and bitrot. \n\n## Quality and relevancy today\n\nI was, for some reason, surprised at how undeniably cringe so many of these flipnotes were (see [example](https://flipnot.es/MY2918D)), but given I was using the app in middle school, which was smack-dab in the middle of my \"awkward phase\" that checks out. Despite the cringe, there's still a bunch of [high](https://flipnot.es/91YE8FP)-[quality](https://flipnot.es/32XFCYA) [animation](https://flipnot.es/C9DZK64) [to](https://flipnot.es/GES6CZ4) [be](https://flipnot.es/4YRU3D9) [found](https://flipnot.es/5KQLF4A).\n\nIt's also surprising how so much of the content on there _feels a lot like what exists on TikTok today._ People could remix popular flipnotes by slightly editing the animation, or reusing the sound to make their own animations. \"Music Videos\" aka \"MV\" were also very popular, and feel a lot like fan cams or dance videos you'd find on TikTok today.\n\nMaybe what I did with my spare time as a kid isn't that different than what my little cousins are doing now 🤷\n\n## Efforts to keep Flipnote alive\n\nAfter spending some time reading through [the archive's history page](https://archive.sudomemo.net/history/) I found a link to a seemingly semi-active forum where people upload new or lost flipnotes today called \"[Sudomemo](https://www.sudomemo.net/?utm_source=flipnotearchive)\"\n\nThere's also a [GitHub organization](https://github.com/Flipnote-Collective) where fans have reverse-engineered a number of the Flipnote Studio systems to create an open-source and maintainable way to edit and interpret Flipnote files without a DS.\n\nIt's cool to see a semi-active community around something that was on the periphery of my nostalgia brain. It's also crazy to think that this huge repository of animated content was almost lost forever simply because the Flipnote Hatena online service was shut down.  _+1 for federated, sustainable, and not-for-profit internet services!_ \n\nIf you're interested in keeping this nearly-lost piece of the internet alive you can [donate via Ko-fi](https://ko-fi.com/flipnotearchive) to cover some of the hosting and managerial costs.  \n\n## TLDR\n\nAll Flipnotes created in Flipnote studio were preserved by a small community of passionate fans. The internet can be a cool place sometimes I guess!","html":"<p>Flipnote Hatena is a now-defunct online platform based on sharing digital &quot;Flipnotes&quot;. These were drawn in an app called &quot;<a href=\"https://en.wikipedia.org/wiki/Flipnote_Studio\">Flipnote Studio</a>&quot; released on both the DS and 3DS. Here's an example of an early Flipnote created by one of the devs:</p><p>&lt;iframe src=&quot;https://archive.sudomemo.net/watch/embed/mm3d05l2nc3bcftirpkwckwy1nvc&quot; scrolling=&quot;no&quot; frameborder=&quot;0&quot; height=&quot;429&quot; width=&quot;512&quot;&gt;&lt;/iframe&gt;</p><p>I used this app pretty heavily. As a kid interested in art, animation, and video games who just happened to own a DS, this was a very fun overlap of interests. I created a bunch of little animations and drawings based on events in my life. The most notable in my memory is a small animation depicting me in the passenger seat while my uncle was pulled over for speeding; I remember thinking this was hilarious as a kid.</p><p>I also viewed the &quot;popular flipnotes&quot; in the app almost daily, I remember there being lots of <a href=\"https://www.sudomemo.net/watch/CBEEA0_0A95B9C620081_000\">stick fights</a>, <a href=\"https://archive.sudomemo.net/watch/mg3io13c0czbyyvl24kwn2udpnac\">music videos</a> with animated characters, <a href=\"https://flipnot.es/5UL8E13\">spooky loops</a>, and other weird miscellaneous animations:</p><p>&lt;iframe src=&quot;https://archive.sudomemo.net/watch/embed/mw21bqmammy40dtweuatavgl1jbc&quot; scrolling=&quot;no&quot; frameborder=&quot;0&quot; height=&quot;429&quot; width=&quot;512&quot;&gt;&lt;/iframe&gt;</p><p>last night, I brought this up with a friend who also remembered the site and we talked about it a bit. This morning she sent over <a href=\"https://archive.sudomemo.net/\">a site that archived all of the original flipnotes on the app</a>. Up until today, I had assumed that most of these flipnotes were lost to the sands of time and bitrot.</p><h2>Quality and relevancy today</h2><p>I was, for some reason, surprised at how undeniably cringe so many of these flipnotes were (see <a href=\"https://flipnot.es/MY2918D\">example</a>), but given I was using the app in middle school, which was smack-dab in the middle of my &quot;awkward phase&quot; that checks out. Despite the cringe, there's still a bunch of <a href=\"https://flipnot.es/91YE8FP\">high</a>-<a href=\"https://flipnot.es/32XFCYA\">quality</a> <a href=\"https://flipnot.es/C9DZK64\">animation</a> <a href=\"https://flipnot.es/GES6CZ4\">to</a> <a href=\"https://flipnot.es/4YRU3D9\">be</a> <a href=\"https://flipnot.es/5KQLF4A\">found</a>.</p><p>It's also surprising how so much of the content on there <em>feels a lot like what exists on TikTok today.</em> People could remix popular flipnotes by slightly editing the animation, or reusing the sound to make their own animations. &quot;Music Videos&quot; aka &quot;MV&quot; were also very popular, and feel a lot like fan cams or dance videos you'd find on TikTok today.</p><p>Maybe what I did with my spare time as a kid isn't that different than what my little cousins are doing now 🤷</p><h2>Efforts to keep Flipnote alive</h2><p>After spending some time reading through <a href=\"https://archive.sudomemo.net/history/\">the archive's history page</a> I found a link to a seemingly semi-active forum where people upload new or lost flipnotes today called &quot;<a href=\"https://www.sudomemo.net/?utm_source=flipnotearchive\">Sudomemo</a>&quot;</p><p>There's also a <a href=\"https://github.com/Flipnote-Collective\">GitHub organization</a> where fans have reverse-engineered a number of the Flipnote Studio systems to create an open-source and maintainable way to edit and interpret Flipnote files without a DS.</p><p>It's cool to see a semi-active community around something that was on the periphery of my nostalgia brain. It's also crazy to think that this huge repository of animated content was almost lost forever simply because the Flipnote Hatena online service was shut down.  <em>+1 for federated, sustainable, and not-for-profit internet services!</em></p><p>If you're interested in keeping this nearly-lost piece of the internet alive you can <a href=\"https://ko-fi.com/flipnotearchive\">donate via Ko-fi</a> to cover some of the hosting and managerial costs.</p><h2>TLDR</h2><p>All Flipnotes created in Flipnote studio were preserved by a small community of passionate fans. The internet can be a cool place sometimes I guess!</p>"}},{"id":"recovering-from-failed-nextcloud-updates","url":"https://mykalmachon.com/posts/recovering-from-failed-nextcloud-updates","meta":{"title":"Recovering from failed Nextcloud updates","pubDate":"2022-12-13T00:00:00.000Z","type":"post","draft":false,"tags":["devops"],"description":"I've found that my nextcloud updates fail ~1/3 of the time. These steps usually save me."},"content":{"md":"> This is a highly specific post about recovering failed NextCloud updates that will only really be relevant if you're hosting your own NextCloud instance. Feel free to skip this one if you're not interested! \n>\n> In all honesty, I'm mostly writing this for my future self 😅\n\nAt around 11:00 pm tonight I started a NextCloud update on my home lab. I thought \"ehh, this will be fine!\". It was not fine. The update bricked itself and the container crashed right after deleting all the old program files. (aka \"Step 9\" according to \"nextcloud-update.log\"). \n\n```\n2022-12-12T23:38:54-0800 [info] startStep(\"9\")\n2022-12-12T23:38:54-0800 [info] deleteOldFiles()\n2022-12-12T23:39:20-0800 [info] config sample exists\n2022-12-12T23:39:20-0800 [info] themes README exists\n2022-12-12T23:40:07-0800 [info] end of deleteOldFiles()\n2022-12-12T23:40:07-0800 [info] endStep(\"9\")\n2022-12-12T23:40:36-0800 [info] request to updater\n2022-12-12T23:40:36-0800 [info] currentStep()\n2022-12-12T23:40:36-0800 [info] Step 9 is in state \"end\".\n2022-12-12T23:40:36-0800 [info] show HTML page\n2022-12-12T23:40:36-0800 [info] current version: 24.0.6 build time: \n```\n\nAs I'm writing this it's 12:15 am and I _just_ fixed things.\n\nI've had this happen 3-4 times now, and every time I have to rediscover the solution. Instead of doing that again in 3-4 months, I figured I'd write it down here.\n\n## The CLI updater\n\n[NextCloud has a built-in CLI tool for updating](https://docs.nextcloud.com/server/latest/admin_manual/maintenance/update.html#using-the-command-line-based-updater) that you can initialize at any time via PHP. if your update bricks itself, chances are you can restart the upgrade process by re-running that upgrade script. This has been my saving grace literally every time I've had an update process stall out or crash altogether. \n\nThe script seems remarkably good at recovering from whatever half-baked state your installation is in. That said, the solution is surprisingly simple. **Use the CLI updater, not the crash-prone web-based updater.** \n\nI'm sure that the CLI-based updater and the Web-based updater use the same code behind the scenes but when the web app becomes unresponsive or unreachable there literally isn't a way to kick the restart process. the CLI will always be there. \n\n### Using the CLI updater\n\nThe CLI updater is really easy to use, but since I have the [LinuxServer/NextCloud docker container](https://docs.linuxserver.io/images/docker-nextcloud) installed on my home lab the steps aren't exactly as defined in NextCloud's documentation. \n\nTo kick off the CLI Updater after a failed update, just run the following command from the container's bash console. \n\n```bash\n# for some reason, the container makes everything owned by abc\nsudo -u abc php /config/www/nextcloud/updater/updater.phar\n```\n\nOnce you type that in you'll see some prompts confirming that \"hey you were at step  x, do you want us to resume from there?\" or something to that effect. hit a quick `y + enter` and you're on your way!","html":"<blockquote><p>This is a highly specific post about recovering failed NextCloud updates that will only really be relevant if you're hosting your own NextCloud instance. Feel free to skip this one if you're not interested!</p><p>In all honesty, I'm mostly writing this for my future self 😅</p></blockquote><p>At around 11:00 pm tonight I started a NextCloud update on my home lab. I thought &quot;ehh, this will be fine!&quot;. It was not fine. The update bricked itself and the container crashed right after deleting all the old program files. (aka &quot;Step 9&quot; according to &quot;nextcloud-update.log&quot;).</p><pre><code>2022-12-12T23:38:54-0800 [info] startStep(&quot;9&quot;)2022-12-12T23:38:54-0800 [info] deleteOldFiles()2022-12-12T23:39:20-0800 [info] config sample exists2022-12-12T23:39:20-0800 [info] themes README exists2022-12-12T23:40:07-0800 [info] end of deleteOldFiles()2022-12-12T23:40:07-0800 [info] endStep(&quot;9&quot;)2022-12-12T23:40:36-0800 [info] request to updater2022-12-12T23:40:36-0800 [info] currentStep()2022-12-12T23:40:36-0800 [info] Step 9 is in state &quot;end&quot;.2022-12-12T23:40:36-0800 [info] show HTML page2022-12-12T23:40:36-0800 [info] current version: 24.0.6 build time: </code></pre><p>As I'm writing this it's 12:15 am and I <em>just</em> fixed things.</p><p>I've had this happen 3-4 times now, and every time I have to rediscover the solution. Instead of doing that again in 3-4 months, I figured I'd write it down here.</p><h2>The CLI updater</h2><p><a href=\"https://docs.nextcloud.com/server/latest/admin_manual/maintenance/update.html#using-the-command-line-based-updater\">NextCloud has a built-in CLI tool for updating</a> that you can initialize at any time via PHP. if your update bricks itself, chances are you can restart the upgrade process by re-running that upgrade script. This has been my saving grace literally every time I've had an update process stall out or crash altogether.</p><p>The script seems remarkably good at recovering from whatever half-baked state your installation is in. That said, the solution is surprisingly simple. <strong>Use the CLI updater, not the crash-prone web-based updater.</strong></p><p>I'm sure that the CLI-based updater and the Web-based updater use the same code behind the scenes but when the web app becomes unresponsive or unreachable there literally isn't a way to kick the restart process. the CLI will always be there.</p><h3>Using the CLI updater</h3><p>The CLI updater is really easy to use, but since I have the <a href=\"https://docs.linuxserver.io/images/docker-nextcloud\">LinuxServer/NextCloud docker container</a> installed on my home lab the steps aren't exactly as defined in NextCloud's documentation.</p><p>To kick off the CLI Updater after a failed update, just run the following command from the container's bash console.</p><pre><code class=\"language-bash\"># for some reason, the container makes everything owned by abcsudo -u abc php /config/www/nextcloud/updater/updater.phar</code></pre><p>Once you type that in you'll see some prompts confirming that &quot;hey you were at step  x, do you want us to resume from there?&quot; or something to that effect. hit a quick <code>y + enter</code> and you're on your way!</p>"}},{"id":"aoc-2022-solution-for-day-5-1","url":"https://mykalmachon.com/posts/aoc-2022-solution-for-day-5-1","meta":{"title":"AOC 2022: Solution for Day 6","pubDate":"2022-12-06T00:00:00.000Z","type":"post","draft":false,"tags":["tech"],"description":"My solution for advent-of-code day 6"},"content":{"md":"Advent of Code, day 6, is done! This was another quick one.\n\n```python\n    \"\"\"\n    Advent of code day 6:\n    not writing a set of steps for this one because I \n    already got it in my head 😎 \n    \"\"\"\n    from collections import Counter\n    \n    \n    def load_file():\n        \"\"\"load file in from input.txt\n        \"\"\"\n        with open('./input.txt', 'r') as file:\n            return file.read().replace('\\n', '')\n    \n    \n    def string_has_unique_chars(string: str) -> bool:\n        \"\"\"check if a string has all unique characters\n        \"\"\"\n        frequency = Counter(string)\n        return len(frequency) == len(string)\n    \n    \n    def find_packet_start(buffer: str, start_size=4) -> str:\n        \"\"\"find the start of a packet where the start signifier \n        is a string in the buffer of size start_size. \n        \"\"\"\n        window = \"\"\n        for idx, char in enumerate(buffer):\n            # setup the window\n            if len(window) >= start_size:\n                window = window[:start_size-1]\n            window = char + window\n            # if string is a valid size, check for unique\n            if len(window) == start_size:\n                is_unique = string_has_unique_chars(window)\n                if is_unique:\n                    return idx + 1\n    \n    \n    if __name__ == \"__main__\":\n        data = load_file()\n        # part 1\n        packet_start = find_packet_start(data)\n        print(f\"Packet starts at character {packet_start}\")\n    \n        # part 2\n        message_start = find_packet_start(data, start_size=14)\n        print(f\"Message starts at character {message_start}\")\n```","html":"<p>Advent of Code, day 6, is done! This was another quick one.</p><pre><code class=\"language-python\">    &quot;&quot;&quot;    Advent of code day 6:    not writing a set of steps for this one because I     already got it in my head 😎     &quot;&quot;&quot;    from collections import Counter            def load_file():        &quot;&quot;&quot;load file in from input.txt        &quot;&quot;&quot;        with open('./input.txt', 'r') as file:            return file.read().replace('\\n', '')            def string_has_unique_chars(string: str) -&gt; bool:        &quot;&quot;&quot;check if a string has all unique characters        &quot;&quot;&quot;        frequency = Counter(string)        return len(frequency) == len(string)            def find_packet_start(buffer: str, start_size=4) -&gt; str:        &quot;&quot;&quot;find the start of a packet where the start signifier         is a string in the buffer of size start_size.         &quot;&quot;&quot;        window = &quot;&quot;        for idx, char in enumerate(buffer):            # setup the window            if len(window) &gt;= start_size:                window = window[:start_size-1]            window = char + window            # if string is a valid size, check for unique            if len(window) == start_size:                is_unique = string_has_unique_chars(window)                if is_unique:                    return idx + 1            if __name__ == &quot;__main__&quot;:        data = load_file()        # part 1        packet_start = find_packet_start(data)        print(f&quot;Packet starts at character {packet_start}&quot;)            # part 2        message_start = find_packet_start(data, start_size=14)        print(f&quot;Message starts at character {message_start}&quot;)</code></pre>"}},{"id":"aoc-2022-solution-for-day-3","url":"https://mykalmachon.com/posts/aoc-2022-solution-for-day-3","meta":{"title":"AOC 2022: Solution for Day 3","pubDate":"2022-12-05T00:00:00.000Z","type":"post","draft":false,"tags":["tech"],"description":"My solution for advent-of-code day 3"},"content":{"md":"[Today's challenge](https://adventofcode.com/2022/day/3), well uh... wasn't today's challenge 😅\n\nI didn't write any actual code this weekend; instead opting to spend some time resting with family and friends. To be honest, I completely forgot about this challenge until about 10:30 pm today.\n\nSo, with around an hour left in the day, I took a shot at part one of Advent of Code's day 3 challenge. Check out my solution below:\n\n```python\n\"\"\"\nsteps for pt. 1 of today's challenge:\n1. break down the rucksacks into two even pieces. \n2. parse through each of these two even pieces and find a common character\n2.1 this could probably be done with a map of some sort\n1. rank all of these items according to the priority system\n2. sum the ranks fo all of these items\n\"\"\"\n\nfrom typing import List\nfrom functools import reduce\n\n\ndef load_input():\n    \"\"\"loads inputs from a local text file\"\"\"\n    with open('./input.txt', 'r', encoding='utf-8') as file:\n        return [line.strip('\\n') for line in file.readlines()]\n\n\ndef split_rucksack(rucksack: str):\n    \"\"\"splits a rucksack into two components\n    \"\"\"\n    split_point = int(len(rucksack)/2)\n    return [rucksack[:split_point], rucksack[split_point:]]\n\n\ndef get_common_items(lines: List[str]):\n    \"\"\"finds items that are in both compartments in each rucksack\n    \"\"\"\n    items = []\n    compartments = []\n    # get the compartments to process\n    for rucksack in lines:\n        compartments.append(split_rucksack(rucksack))\n\n    for idx, compartment in enumerate(compartments):\n        acc = set(compartment[0])\n        for item in compartment[1]:\n            if {item}.issubset(acc):\n                items.append(item)\n                break\n\n    return items\n\n\ndef get_item_priorities(items: List[str]):\n    \"\"\"receives a list of characters and returns their\n    respective priorities given the challenges index.\n    \"\"\"\n    new_priorities = []\n    for item in items:\n        is_upper = item.isupper()\n        ascii_idx = ord(item.lower()) - 97\n        priority = ascii_idx + 1 \\\n            if is_upper is False \\\n            else ascii_idx + 27\n        new_priorities.append((item, priority))\n    return new_priorities\n\n\nif __name__ == \"__main__\":\n    print(\"starting AOC 2022 Day 03\")\n    data = load_input()\n    common_items = get_common_items(data)\n    priorities = get_item_priorities(common_items)\n    sum_of_priorities = reduce(lambda a, c: a + c[1], priorities, 0)\n    print(f\"Sum of priorities is: {sum_of_priorities} \")\n```\n\nMaybe I'll go back to finish part 2 a little later as it doesn't seem too challenging.","html":"<p><a href=\"https://adventofcode.com/2022/day/3\">Today's challenge</a>, well uh... wasn't today's challenge 😅</p><p>I didn't write any actual code this weekend; instead opting to spend some time resting with family and friends. To be honest, I completely forgot about this challenge until about 10:30 pm today.</p><p>So, with around an hour left in the day, I took a shot at part one of Advent of Code's day 3 challenge. Check out my solution below:</p><pre><code class=\"language-python\">&quot;&quot;&quot;steps for pt. 1 of today's challenge:1. break down the rucksacks into two even pieces. 2. parse through each of these two even pieces and find a common character2.1 this could probably be done with a map of some sort1. rank all of these items according to the priority system2. sum the ranks fo all of these items&quot;&quot;&quot;from typing import Listfrom functools import reducedef load_input():    &quot;&quot;&quot;loads inputs from a local text file&quot;&quot;&quot;    with open('./input.txt', 'r', encoding='utf-8') as file:        return [line.strip('\\n') for line in file.readlines()]def split_rucksack(rucksack: str):    &quot;&quot;&quot;splits a rucksack into two components    &quot;&quot;&quot;    split_point = int(len(rucksack)/2)    return [rucksack[:split_point], rucksack[split_point:]]def get_common_items(lines: List[str]):    &quot;&quot;&quot;finds items that are in both compartments in each rucksack    &quot;&quot;&quot;    items = []    compartments = []    # get the compartments to process    for rucksack in lines:        compartments.append(split_rucksack(rucksack))    for idx, compartment in enumerate(compartments):        acc = set(compartment[0])        for item in compartment[1]:            if {item}.issubset(acc):                items.append(item)                break    return itemsdef get_item_priorities(items: List[str]):    &quot;&quot;&quot;receives a list of characters and returns their    respective priorities given the challenges index.    &quot;&quot;&quot;    new_priorities = []    for item in items:        is_upper = item.isupper()        ascii_idx = ord(item.lower()) - 97        priority = ascii_idx + 1 \\            if is_upper is False \\            else ascii_idx + 27        new_priorities.append((item, priority))    return new_prioritiesif __name__ == &quot;__main__&quot;:    print(&quot;starting AOC 2022 Day 03&quot;)    data = load_input()    common_items = get_common_items(data)    priorities = get_item_priorities(common_items)    sum_of_priorities = reduce(lambda a, c: a + c[1], priorities, 0)    print(f&quot;Sum of priorities is: {sum_of_priorities} &quot;)</code></pre><p>Maybe I'll go back to finish part 2 a little later as it doesn't seem too challenging.</p>"}},{"id":"aoc-2022-solution-for-day-4","url":"https://mykalmachon.com/posts/aoc-2022-solution-for-day-4","meta":{"title":"AOC 2022: Solution for Day 4","pubDate":"2022-12-05T00:00:00.000Z","type":"post","draft":false,"tags":["tech"],"description":"My solution for advent-of-code day 4"},"content":{"md":"[Advent of code challenge #4](https://adventofcode.com/2022/day/4) is done and dealt with!\n\nThese last two challenges have been the first time I've used any sort of discrete math since university. Given all the discrete logic I've crammed into my brain from school, distilling these problems down to simple set math has made them quite a bit easier to work through. \n\nHere's my solution for part 1 and part 2 of day 4's challenge:\n\n```python\n\"\"\"\nhttps://adventofcode.com/2022/day/4\n\nsteps for part 1 of today's challenge:\n1. break input down into lines (\"pairs of elves\")\n2. look at each pair and create ranges based on assignments\n3. use sets to find if either set is a subset of the other \n4.1 if either of the sets are subsets, return True, else False.\n1. return the sum of sets that are subsets of their pair.\n\nsteps for part 2 of today's challenge:\n1. use steps 1-2 of the above\n2. use sets to find if the two elements are disjoint.\n2.1 if the sets are disjoint, there is no overlap, there is if they are\n3. return the sum of sets with any intersect across pairs.\n\"\"\"\n\nfrom typing import List, Tuple\nfrom functools import reduce\n\n\ndef load_input():\n    \"\"\"loads inputs from a local text file\"\"\"\n    with open('./input.txt', 'r', encoding='utf-8') as file:\n        return [line.strip('\\n') for line in file.readlines()]\n\n\ndef get_range_pairs(lines: List[str]) -> List[Tuple]:\n    \"\"\"gets pairs of range from the list\"\"\"\n    range_pairs = []\n    for line in lines:\n        items = line.split(',')\n        item_range_strs = [item.split('-') for item in items]\n        item_range_iter = [range(int(r[0]), int(r[1])+1)\n                           for r in item_range_strs]\n        item_ranges = [{x for x in r} for r in item_range_iter]\n        range_pairs.append(item_ranges)\n    return range_pairs\n\n\ndef find_subsets(pairs: List[List[int]]):\n    \"\"\"find pairs where either subset is a pair of itself\n    This indicates a complete overlap of duties.\n    \"\"\"\n    subsets = []\n    for pair in pairs:\n        if pair[0].issubset(pair[1]) or pair[1].issubset(pair[0]):\n            subsets.append(True)\n        else:\n            subsets.append(False)\n    return subsets\n\n\ndef find_overlap(pairs: List[List[int]]):\n    \"\"\"find pairs where there is any overlap at all\n    \"\"\"\n    overlaps = []\n    for pair in pairs:\n        if pair[0].isdisjoint(pair[1]):\n            overlaps.append(False)\n        else:\n            overlaps.append(True)\n    return overlaps\n\n\nif __name__ == \"__main__\":\n    print(\"starting AOC 2022 Day 04\")\n    input = load_input()\n\n    # part 1: find subset overlaps\n    pairs = get_range_pairs(input)\n    subset_overlap = find_subsets(pairs)\n    subset_overlap_sum = reduce(\n        lambda a, c: a+1 if c is True else a, subset_overlap, 0)\n    print(f\"there are {subset_overlap_sum} completely overlapping schedules\")\n\n    # part 2: find any overlaps at all\n    overlap = find_overlap(pairs)\n    overlap_sum = reduce(lambda a, c: a+1 if c is True else a, overlap, 0)\n    print(f\"there are {overlap_sum} partially overlapping schedules\")\n```\n\nI'm still a day behind on challenges. I'm hoping to get caught up and complete challenge no. 5 tonight.","html":"<p><a href=\"https://adventofcode.com/2022/day/4\">Advent of code challenge #4</a> is done and dealt with!</p><p>These last two challenges have been the first time I've used any sort of discrete math since university. Given all the discrete logic I've crammed into my brain from school, distilling these problems down to simple set math has made them quite a bit easier to work through.</p><p>Here's my solution for part 1 and part 2 of day 4's challenge:</p><pre><code class=\"language-python\">&quot;&quot;&quot;https://adventofcode.com/2022/day/4steps for part 1 of today's challenge:1. break input down into lines (&quot;pairs of elves&quot;)2. look at each pair and create ranges based on assignments3. use sets to find if either set is a subset of the other 4.1 if either of the sets are subsets, return True, else False.1. return the sum of sets that are subsets of their pair.steps for part 2 of today's challenge:1. use steps 1-2 of the above2. use sets to find if the two elements are disjoint.2.1 if the sets are disjoint, there is no overlap, there is if they are3. return the sum of sets with any intersect across pairs.&quot;&quot;&quot;from typing import List, Tuplefrom functools import reducedef load_input():    &quot;&quot;&quot;loads inputs from a local text file&quot;&quot;&quot;    with open('./input.txt', 'r', encoding='utf-8') as file:        return [line.strip('\\n') for line in file.readlines()]def get_range_pairs(lines: List[str]) -&gt; List[Tuple]:    &quot;&quot;&quot;gets pairs of range from the list&quot;&quot;&quot;    range_pairs = []    for line in lines:        items = line.split(',')        item_range_strs = [item.split('-') for item in items]        item_range_iter = [range(int(r[0]), int(r[1])+1)                           for r in item_range_strs]        item_ranges = [{x for x in r} for r in item_range_iter]        range_pairs.append(item_ranges)    return range_pairsdef find_subsets(pairs: List[List[int]]):    &quot;&quot;&quot;find pairs where either subset is a pair of itself    This indicates a complete overlap of duties.    &quot;&quot;&quot;    subsets = []    for pair in pairs:        if pair[0].issubset(pair[1]) or pair[1].issubset(pair[0]):            subsets.append(True)        else:            subsets.append(False)    return subsetsdef find_overlap(pairs: List[List[int]]):    &quot;&quot;&quot;find pairs where there is any overlap at all    &quot;&quot;&quot;    overlaps = []    for pair in pairs:        if pair[0].isdisjoint(pair[1]):            overlaps.append(False)        else:            overlaps.append(True)    return overlapsif __name__ == &quot;__main__&quot;:    print(&quot;starting AOC 2022 Day 04&quot;)    input = load_input()    # part 1: find subset overlaps    pairs = get_range_pairs(input)    subset_overlap = find_subsets(pairs)    subset_overlap_sum = reduce(        lambda a, c: a+1 if c is True else a, subset_overlap, 0)    print(f&quot;there are {subset_overlap_sum} completely overlapping schedules&quot;)    # part 2: find any overlaps at all    overlap = find_overlap(pairs)    overlap_sum = reduce(lambda a, c: a+1 if c is True else a, overlap, 0)    print(f&quot;there are {overlap_sum} partially overlapping schedules&quot;)</code></pre><p>I'm still a day behind on challenges. I'm hoping to get caught up and complete challenge no. 5 tonight.</p>"}},{"id":"aoc-2022-solution-for-day-5","url":"https://mykalmachon.com/posts/aoc-2022-solution-for-day-5","meta":{"title":"AOC 2022: Solution for Day 5","pubDate":"2022-12-05T00:00:00.000Z","type":"post","draft":false,"tags":["tech"],"description":"My solution for advent-of-code day 5"},"content":{"md":"[Advent of Code challenge #5](https://adventofcode.com/2022/day/5) is all done, and I'm officially caught up! \n\nThe \"hard-part\" of this challenge for me was the just making the data usable. The actual task of moving the things around based on steps was pretty quick to implement. These are my-least favorite kind of advent of code challenges because it feels a bit like [artificial difficulty in video games](https://videogaming.fandom.com/wiki/Artificial_Difficulty). \n\nThe functionality that \"solved\" the challenge was 31 loc, but the ingest functionality took 33 loc. Half the work was just getting the information I needed into a usable data structure. _why not just provide things in a useable format from the jump?_ Maybe I'm missing the point and the ingest is supposed to be a fun part of the challenge and just doesn't vibe with me. \n\n/rant.\n\nAnyways, here’s my solution for part 1 and part 2 of day 5’s challenge:\n\n```python\n\"\"\"\nhttps://adventofcode.com/2022/day/5\n\nsteps for part 1 of today's challenge:\n1. load in the cargo stacks as input\n2. load in the steps that you need to take on input\n3. follow the steps in step 2 on the stacks\n4. grab the top items from each stack\n\"\"\"\n\nfrom typing import List\nfrom functools import reduce\n\n\ndef load_cargo():\n    \"\"\"loads the cargo stacks from the input.txt file\"\"\"\n    with open('input.txt', mode='r', encoding='utf-8') as file:\n        lines = [line.strip('\\n') for line in file.readlines()]\n\n        # get number of stacks in the cargo\n        last_line = lines[len(lines)-1].replace(' ', '')\n        num_stacks = range(int(last_line[len(last_line)-1]))\n\n        # create cargo stacks\n        stacks = [[] for stack in num_stacks]\n        for line in lines[:-1]:\n            curr_pos = 0\n            for idx, stack in enumerate(num_stacks):\n                curr_char = line[curr_pos:curr_pos+3]\n                curr_pos += 4\n                if curr_char.replace(' ', '') != \"\":\n                    stacks[idx].append(curr_char)\n        return stacks\n\n\ndef load_steps():\n    \"\"\"load the steps and format them from steps.txt\"\"\"\n    with open('steps.txt', mode='r', encoding='utf-8') as file:\n        lines = [line.strip('\\n') for line in file.readlines()]\n        steps = []\n        for line in lines:\n            tokens = line.split(' ')\n            steps.append({\n                \"num\": int(tokens[1]),\n                \"from\": int(tokens[3]),\n                \"to\": int(tokens[5])\n            })\n        return steps\n\n\ndef move_cargo(cargo: List[List], steps: List[dict]):\n    \"\"\"move cargo from one position to another one by one.\n    \"\"\"\n    for step in steps:\n        to_move = step.get('num')\n        idx_from = step.get('from') - 1\n        idx_to = step.get('to') - 1\n\n        # grab items from cargo[idx_from]\n        items_moving = [cargo[idx_from].pop(0) for x in range(to_move)]\n\n        # move items into cargo[idx_to]\n        for item in items_moving:\n            cargo[idx_to].insert(0, item)\n    return cargo\n\n\ndef move_cargo_groups(cargo: List[List], steps: List[dict]):\n    \"\"\"move cargo from one position to another while holding the original shape.\n    \"\"\"\n    for step in steps:\n        to_move = step.get('num')\n        idx_from = step.get('from') - 1\n        idx_to = step.get('to') - 1\n\n        # grab items from cargo[idx_from]\n        items_moving = cargo[idx_from][0:to_move]\n        cargo[idx_from] = cargo[idx_from][to_move:]\n\n        # move items into cargo[idx_to]\n        cargo[idx_to] = items_moving + cargo[idx_to]\n    return cargo\n\n\nif __name__ == \"__main__\":\n    cargo = load_cargo()\n    steps = load_steps()\n\n    # part 1: move cargo conventionally\n    new_cargo = move_cargo(cargo, steps)\n    top_cargo = reduce(lambda a, c: a + c[0], new_cargo, '')\n\n    print(top_cargo.replace('[', '').replace(']', ''))\n\n    # part 2: margo cargo in groups\n    cargo = load_cargo()\n\n    group_cargo = move_cargo_groups(cargo, steps)\n    top_group_cargo = reduce(\n        lambda a, c: a + c[0] if len(c) > 0 else a, group_cargo, '')\n\n    print(top_group_cargo.replace('[', '').replace(']', ''))\n```","html":"<p><a href=\"https://adventofcode.com/2022/day/5\">Advent of Code challenge #5</a> is all done, and I'm officially caught up!</p><p>The &quot;hard-part&quot; of this challenge for me was the just making the data usable. The actual task of moving the things around based on steps was pretty quick to implement. These are my-least favorite kind of advent of code challenges because it feels a bit like <a href=\"https://videogaming.fandom.com/wiki/Artificial_Difficulty\">artificial difficulty in video games</a>.</p><p>The functionality that &quot;solved&quot; the challenge was 31 loc, but the ingest functionality took 33 loc. Half the work was just getting the information I needed into a usable data structure. <em>why not just provide things in a useable format from the jump?</em> Maybe I'm missing the point and the ingest is supposed to be a fun part of the challenge and just doesn't vibe with me.</p><p>/rant.</p><p>Anyways, here’s my solution for part 1 and part 2 of day 5’s challenge:</p><pre><code class=\"language-python\">&quot;&quot;&quot;https://adventofcode.com/2022/day/5steps for part 1 of today's challenge:1. load in the cargo stacks as input2. load in the steps that you need to take on input3. follow the steps in step 2 on the stacks4. grab the top items from each stack&quot;&quot;&quot;from typing import Listfrom functools import reducedef load_cargo():    &quot;&quot;&quot;loads the cargo stacks from the input.txt file&quot;&quot;&quot;    with open('input.txt', mode='r', encoding='utf-8') as file:        lines = [line.strip('\\n') for line in file.readlines()]        # get number of stacks in the cargo        last_line = lines[len(lines)-1].replace(' ', '')        num_stacks = range(int(last_line[len(last_line)-1]))        # create cargo stacks        stacks = [[] for stack in num_stacks]        for line in lines[:-1]:            curr_pos = 0            for idx, stack in enumerate(num_stacks):                curr_char = line[curr_pos:curr_pos+3]                curr_pos += 4                if curr_char.replace(' ', '') != &quot;&quot;:                    stacks[idx].append(curr_char)        return stacksdef load_steps():    &quot;&quot;&quot;load the steps and format them from steps.txt&quot;&quot;&quot;    with open('steps.txt', mode='r', encoding='utf-8') as file:        lines = [line.strip('\\n') for line in file.readlines()]        steps = []        for line in lines:            tokens = line.split(' ')            steps.append({                &quot;num&quot;: int(tokens[1]),                &quot;from&quot;: int(tokens[3]),                &quot;to&quot;: int(tokens[5])            })        return stepsdef move_cargo(cargo: List[List], steps: List[dict]):    &quot;&quot;&quot;move cargo from one position to another one by one.    &quot;&quot;&quot;    for step in steps:        to_move = step.get('num')        idx_from = step.get('from') - 1        idx_to = step.get('to') - 1        # grab items from cargo[idx_from]        items_moving = [cargo[idx_from].pop(0) for x in range(to_move)]        # move items into cargo[idx_to]        for item in items_moving:            cargo[idx_to].insert(0, item)    return cargodef move_cargo_groups(cargo: List[List], steps: List[dict]):    &quot;&quot;&quot;move cargo from one position to another while holding the original shape.    &quot;&quot;&quot;    for step in steps:        to_move = step.get('num')        idx_from = step.get('from') - 1        idx_to = step.get('to') - 1        # grab items from cargo[idx_from]        items_moving = cargo[idx_from][0:to_move]        cargo[idx_from] = cargo[idx_from][to_move:]        # move items into cargo[idx_to]        cargo[idx_to] = items_moving + cargo[idx_to]    return cargoif __name__ == &quot;__main__&quot;:    cargo = load_cargo()    steps = load_steps()    # part 1: move cargo conventionally    new_cargo = move_cargo(cargo, steps)    top_cargo = reduce(lambda a, c: a + c[0], new_cargo, '')    print(top_cargo.replace('[', '').replace(']', ''))    # part 2: margo cargo in groups    cargo = load_cargo()    group_cargo = move_cargo_groups(cargo, steps)    top_group_cargo = reduce(        lambda a, c: a + c[0] if len(c) &gt; 0 else a, group_cargo, '')    print(top_group_cargo.replace('[', '').replace(']', ''))</code></pre>"}},{"id":"aoc-2022-solution-for-day-2","url":"https://mykalmachon.com/posts/aoc-2022-solution-for-day-2","meta":{"title":"AOC 2022: Solution for Day 2","pubDate":"2022-12-02T00:00:00.000Z","type":"post","draft":false,"tags":["tech"],"description":"My solution for advent-of-code day 2"},"content":{"md":"[Today's challenge](https://adventofcode.com/2022/day/2) took a lot more code to solve than [my solution for day 1](https://mykal.codes/posts/aoc-2022-day-1/). There's just a lot of conditional branching going on. As far as I can tell that's sort of inherent to the classic \"rock paper scissors\" program this is based on, though.\n\nThat said, I'm sure there's a \"cleverer\" way to solve this, with probably less conditional branching code-mess and more maps with pre-defined scores. That said, this was the first thing that came to mind when working through the problem organically. It works, it's mostly readable, so it's what I'm sticking with 😎\n\n```python\ndef get_move_points(move):\n    if move == \"rock\":\n        return 1\n    if move == \"paper\":\n        return 2\n    if move == \"scissors\":\n        return 3\n\n\ndef get_move(input):\n    move_map = {\n        \"A\": \"rock\",\n        \"B\": \"paper\",\n        \"C\": \"scissors\",\n        \"X\": \"rock\",\n        \"Y\": \"paper\",\n        \"Z\": \"scissors\"\n    }\n    return move_map.get(input)\n\n\ndef get_results(row):\n    results = {\"comp\": 0, \"you\": 0}\n\n    # get players moves\n    comp_move = get_move(row[0])\n    your_move = get_move(row[2])\n\n    # add baseline move points\n    results.update({\"comp\": results.get('comp') + get_move_points(comp_move)})\n    results.update({\"you\": results.get('you') + get_move_points(your_move)})\n\n    # figure out who won and who should get points\n    # I intentionally used update here: wanted to see how the syntax worked\n    # I know you can just use [] accessors.\n\n    if comp_move == your_move:\n        # draw results\n        results.update({\"comp\": results.get('comp') + 3})\n        results.update({\"you\": results.get('you') + 3})\n    elif comp_move == \"rock\":\n        if your_move == \"scissors\":\n            results.update({\"comp\": results.get('comp') + 6})\n        if your_move == \"paper\":\n            results.update({\"you\": results.get('you') + 6})\n    elif comp_move == \"paper\":\n        if your_move == \"scissors\":\n            results.update({\"you\": results.get('you') + 6})\n        if your_move == \"rock\":\n            results.update({\"comp\": results.get('comp') + 6})\n    elif comp_move == \"scissors\":\n        if your_move == \"paper\":\n            results.update({\"comp\": results.get('comp') + 6})\n        if your_move == \"rock\":\n            results.update({\"you\": results.get('you') + 6})\n    return results\n\n\ndef get_outcome(input):\n    outcome_map = {\n        \"X\": \"lose\",\n        \"Y\": \"draw\",\n        \"Z\": \"win\",\n    }\n    return outcome_map.get(input)\n\n\ndef get_results_part_two(row):\n    results = {\"comp\": 0, \"you\": 0}\n\n    # get comp move and outcome\n    comp_move = get_move(row[0])\n    outcome = get_outcome(row[2])\n\n    # baseline move points for the computer\n    results[\"comp\"] += get_move_points(comp_move)\n\n    if comp_move == \"rock\":\n        if outcome == \"win\":\n            results[\"comp\"] += 0\n            results[\"you\"] += 6 + get_move_points('paper')\n        if outcome == \"draw\":\n            results[\"comp\"] += 3\n            results[\"you\"] += 3 + get_move_points('rock')\n        if outcome == \"lose\":\n            results[\"comp\"] += 6\n            results[\"you\"] += get_move_points('scissors')\n    elif comp_move == \"paper\":\n        if outcome == \"win\":\n            results[\"comp\"] += 0\n            results[\"you\"] += 6 + get_move_points('scissors')\n        if outcome == \"draw\":\n            results[\"comp\"] += 3\n            results[\"you\"] += 3 + get_move_points('paper')\n        if outcome == \"lose\":\n            results[\"comp\"] += 6\n            results[\"you\"] += get_move_points('rock')\n    elif comp_move == \"scissors\":\n        if outcome == \"win\":\n            results[\"comp\"] += 0\n            results[\"you\"] += 6 + get_move_points('rock')\n        if outcome == \"draw\":\n            results[\"comp\"] += 3\n            results[\"you\"] += 3 + get_move_points('scissors')\n        if outcome == \"lose\":\n            results[\"comp\"] += 6\n            results[\"you\"] += get_move_points('paper')\n    return results\n\n\ndef load_data():\n    with open('./input.txt', 'r') as file:\n        raw_data = file.readlines()\n        data = [line.strip('\\n') for line in raw_data]\n        return data\n\n\nif __name__ == \"__main__\":\n    data = load_data()\n\n    # part one: get your total score given the strategy guide\n    total_scores_p1 = {\"comp\": 0, \"you\": 0}\n    for rps in data:\n        rps_results = get_results(rps)\n        total_scores_p1[\"comp\"] += rps_results[\"comp\"]\n        total_scores_p1[\"you\"] += rps_results[\"you\"]\n\n    print(f\"part 1: Your total points are: {total_scores_p1['you']}\")\n\n    # part two: get your total score given the new interpretation\n    total_scores_p2 = {\"comp\": 0, \"you\": 0}\n    for rps in data:\n        rps_results = get_results_part_two(rps)\n        total_scores_p2[\"comp\"] += rps_results[\"comp\"]\n        total_scores_p2[\"you\"] += rps_results[\"you\"]\n\n    print(f\"part 1: Your total points are: {total_scores_p2['you']}\")\n```","html":"<p><a href=\"https://adventofcode.com/2022/day/2\">Today's challenge</a> took a lot more code to solve than <a href=\"https://mykal.codes/posts/aoc-2022-day-1/\">my solution for day 1</a>. There's just a lot of conditional branching going on. As far as I can tell that's sort of inherent to the classic &quot;rock paper scissors&quot; program this is based on, though.</p><p>That said, I'm sure there's a &quot;cleverer&quot; way to solve this, with probably less conditional branching code-mess and more maps with pre-defined scores. That said, this was the first thing that came to mind when working through the problem organically. It works, it's mostly readable, so it's what I'm sticking with 😎</p><pre><code class=\"language-python\">def get_move_points(move):    if move == &quot;rock&quot;:        return 1    if move == &quot;paper&quot;:        return 2    if move == &quot;scissors&quot;:        return 3def get_move(input):    move_map = {        &quot;A&quot;: &quot;rock&quot;,        &quot;B&quot;: &quot;paper&quot;,        &quot;C&quot;: &quot;scissors&quot;,        &quot;X&quot;: &quot;rock&quot;,        &quot;Y&quot;: &quot;paper&quot;,        &quot;Z&quot;: &quot;scissors&quot;    }    return move_map.get(input)def get_results(row):    results = {&quot;comp&quot;: 0, &quot;you&quot;: 0}    # get players moves    comp_move = get_move(row[0])    your_move = get_move(row[2])    # add baseline move points    results.update({&quot;comp&quot;: results.get('comp') + get_move_points(comp_move)})    results.update({&quot;you&quot;: results.get('you') + get_move_points(your_move)})    # figure out who won and who should get points    # I intentionally used update here: wanted to see how the syntax worked    # I know you can just use [] accessors.    if comp_move == your_move:        # draw results        results.update({&quot;comp&quot;: results.get('comp') + 3})        results.update({&quot;you&quot;: results.get('you') + 3})    elif comp_move == &quot;rock&quot;:        if your_move == &quot;scissors&quot;:            results.update({&quot;comp&quot;: results.get('comp') + 6})        if your_move == &quot;paper&quot;:            results.update({&quot;you&quot;: results.get('you') + 6})    elif comp_move == &quot;paper&quot;:        if your_move == &quot;scissors&quot;:            results.update({&quot;you&quot;: results.get('you') + 6})        if your_move == &quot;rock&quot;:            results.update({&quot;comp&quot;: results.get('comp') + 6})    elif comp_move == &quot;scissors&quot;:        if your_move == &quot;paper&quot;:            results.update({&quot;comp&quot;: results.get('comp') + 6})        if your_move == &quot;rock&quot;:            results.update({&quot;you&quot;: results.get('you') + 6})    return resultsdef get_outcome(input):    outcome_map = {        &quot;X&quot;: &quot;lose&quot;,        &quot;Y&quot;: &quot;draw&quot;,        &quot;Z&quot;: &quot;win&quot;,    }    return outcome_map.get(input)def get_results_part_two(row):    results = {&quot;comp&quot;: 0, &quot;you&quot;: 0}    # get comp move and outcome    comp_move = get_move(row[0])    outcome = get_outcome(row[2])    # baseline move points for the computer    results[&quot;comp&quot;] += get_move_points(comp_move)    if comp_move == &quot;rock&quot;:        if outcome == &quot;win&quot;:            results[&quot;comp&quot;] += 0            results[&quot;you&quot;] += 6 + get_move_points('paper')        if outcome == &quot;draw&quot;:            results[&quot;comp&quot;] += 3            results[&quot;you&quot;] += 3 + get_move_points('rock')        if outcome == &quot;lose&quot;:            results[&quot;comp&quot;] += 6            results[&quot;you&quot;] += get_move_points('scissors')    elif comp_move == &quot;paper&quot;:        if outcome == &quot;win&quot;:            results[&quot;comp&quot;] += 0            results[&quot;you&quot;] += 6 + get_move_points('scissors')        if outcome == &quot;draw&quot;:            results[&quot;comp&quot;] += 3            results[&quot;you&quot;] += 3 + get_move_points('paper')        if outcome == &quot;lose&quot;:            results[&quot;comp&quot;] += 6            results[&quot;you&quot;] += get_move_points('rock')    elif comp_move == &quot;scissors&quot;:        if outcome == &quot;win&quot;:            results[&quot;comp&quot;] += 0            results[&quot;you&quot;] += 6 + get_move_points('rock')        if outcome == &quot;draw&quot;:            results[&quot;comp&quot;] += 3            results[&quot;you&quot;] += 3 + get_move_points('scissors')        if outcome == &quot;lose&quot;:            results[&quot;comp&quot;] += 6            results[&quot;you&quot;] += get_move_points('paper')    return resultsdef load_data():    with open('./input.txt', 'r') as file:        raw_data = file.readlines()        data = [line.strip('\\n') for line in raw_data]        return dataif __name__ == &quot;__main__&quot;:    data = load_data()    # part one: get your total score given the strategy guide    total_scores_p1 = {&quot;comp&quot;: 0, &quot;you&quot;: 0}    for rps in data:        rps_results = get_results(rps)        total_scores_p1[&quot;comp&quot;] += rps_results[&quot;comp&quot;]        total_scores_p1[&quot;you&quot;] += rps_results[&quot;you&quot;]    print(f&quot;part 1: Your total points are: {total_scores_p1['you']}&quot;)    # part two: get your total score given the new interpretation    total_scores_p2 = {&quot;comp&quot;: 0, &quot;you&quot;: 0}    for rps in data:        rps_results = get_results_part_two(rps)        total_scores_p2[&quot;comp&quot;] += rps_results[&quot;comp&quot;]        total_scores_p2[&quot;you&quot;] += rps_results[&quot;you&quot;]    print(f&quot;part 1: Your total points are: {total_scores_p2['you']}&quot;)</code></pre>"}},{"id":"aoc-2022-solution-for-day-1","url":"https://mykalmachon.com/posts/aoc-2022-solution-for-day-1","meta":{"title":"AOC 2022: Solution for Day 1","pubDate":"2022-12-01T00:00:00.000Z","type":"post","draft":false,"tags":["tech"],"description":"My solution for Advent of Code 2022 Day 1"},"content":{"md":"As with most years, the advent of code challenges for day one were pretty simple! Regardless, I had some fun putting together some equally simple solutions.\n\n```python\nfrom functools import reduce\n\n\ndef load_data():\n    with open('./input.txt', 'r') as file:\n        return file.readlines()\n\n\ndef calorie_by_elf(data):\n    results = []\n    curr_elf = 1  # id for the current elf. May need this down the line\n    curr_cals = 0  # accumulator for calories on curr_elf\n    for snack in data:\n        if snack == \"\\n\":\n            # moving to a new elf, push crur to the results list\n            results.append((curr_elf, curr_cals))\n            curr_elf += 1  # move id to the next elf\n            curr_cals = 0  # reset calorie accumulator\n        else:\n            curr_cals += int(snack)  # accumulate curr_elf calories\n    return results\n\n\nif __name__ == \"__main__\":\n    data = load_data()\n\n    # part one: find the elf carrying the most calories. How many are they carrying?\n    elf_calories = calorie_by_elf(data)\n    elf_calories.sort(key=lambda e: e[1], reverse=True)\n    print(f\"The elf with the most calories is #{elf_calories[0][0]} with {elf_calories[0][1]} calories\")\n\n    # part two: find the 3 elves with the most calories. How many are there in total?\n    top_3_elves = elf_calories[0:3]\n    total_calories = reduce(lambda a, c: a + c[1], top_3_elves, 0)\n    print(f\"The total calories carried by the top 3 elves is: {total_calories}\")\n```\n\nNow let's just hope I can keep up and actually finish all the challenges this year 😅","html":"<p>As with most years, the advent of code challenges for day one were pretty simple! Regardless, I had some fun putting together some equally simple solutions.</p><pre><code class=\"language-python\">from functools import reducedef load_data():    with open('./input.txt', 'r') as file:        return file.readlines()def calorie_by_elf(data):    results = []    curr_elf = 1  # id for the current elf. May need this down the line    curr_cals = 0  # accumulator for calories on curr_elf    for snack in data:        if snack == &quot;\\n&quot;:            # moving to a new elf, push crur to the results list            results.append((curr_elf, curr_cals))            curr_elf += 1  # move id to the next elf            curr_cals = 0  # reset calorie accumulator        else:            curr_cals += int(snack)  # accumulate curr_elf calories    return resultsif __name__ == &quot;__main__&quot;:    data = load_data()    # part one: find the elf carrying the most calories. How many are they carrying?    elf_calories = calorie_by_elf(data)    elf_calories.sort(key=lambda e: e[1], reverse=True)    print(f&quot;The elf with the most calories is #{elf_calories[0][0]} with {elf_calories[0][1]} calories&quot;)    # part two: find the 3 elves with the most calories. How many are there in total?    top_3_elves = elf_calories[0:3]    total_calories = reduce(lambda a, c: a + c[1], top_3_elves, 0)    print(f&quot;The total calories carried by the top 3 elves is: {total_calories}&quot;)</code></pre><p>Now let's just hope I can keep up and actually finish all the challenges this year 😅</p>"}},{"id":"mit-sans-elon","url":"https://mykalmachon.com/posts/mit-sans-elon","meta":{"title":"MIT, Sans-elon","pubDate":"2022-11-15T00:00:00.000Z","type":"post","draft":false,"tags":["opinion"],"description":"The MIT license, but Elon Musk and all of his companies are explicitly denied usage.","heroImage":{"src":"/_astro/mit-sans-elon.C6iypJVj.avif","width":1200,"height":600,"format":"avif"}},"content":{"md":"A few friends and I were talking about the hot-topic of the Elon Musk twitter takeover, specifically, we were talking about this tweet:\n\n<blockquote class=\"twitter-tweet\"><p lang=\"en\" dir=\"ltr\">Btw, I’d like to apologize for Twitter being super slow in many countries. App is doing >1000 poorly batched RPCs just to render a home timeline!</p>— Elon Musk (@elonmusk) <a href=\"https://twitter.com/elonmusk/status/1591853644944932865?ref_src=twsrc%5Etfw\">November 13, 2022</a></blockquote> <script async src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\"></script>\n\nThis tweet is ridiculous for so many reasons:\n\n* It doesn't really make any sense, seemingly just some technobabble slapped together to seem intelligent.\n* Even if you give Elon the benefit of the doubt and say by \"RPC\" he meant REST / HTTP calls, Batching them won't necessarily improve performance given HTTP2.\n* The tweet seems to imply this is a new issue, which, if anything, would also imply that [Elon and his antics](https://mobile.twitter.com/elonmusk/status/1592177471654604800) are to blame. \n* Someone was [seemingly fired](https://twitter.com/MattBinder/status/1592193172830318592) from Twitter's Android app team for [pointing out that the tweet was plain wrong](https://twitter.com/EricFrohnhoefer/status/1591902285403418624). Firing an SME on your product, who has likely worked to improve the exact deficiency you're complaining about is a _bad_ look.\n* The whole thing really just feels like something out of HBO's Silicon Valley.\n\nHaving talked about all of this, a friend joked that someone should create an MIT license that explicitly denies Elon Musk or any of his companies' usage of the associated software. [So I did, it's called MIT-SE and you can check it out here.](https://github.com/MykalMachon/MIT-SE)\n\n1. Is this a silly thing that I made more to make a few friends laugh than for any practical reason? _yes!_\n2. Is it likely that I'll use MIT-SE in some of my toy open-source projects, also _yes! see point 1 for why._","html":"<p>A few friends and I were talking about the hot-topic of the Elon Musk twitter takeover, specifically, we were talking about this tweet:</p><p>&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Btw, I’d like to apologize for Twitter being super slow in many countries. App is doing &gt;1000 poorly batched RPCs just to render a home timeline!&lt;/p&gt;— Elon Musk (@elonmusk) &lt;a href=&quot;https://twitter.com/elonmusk/status/1591853644944932865?ref_src=twsrc%5Etfw&quot;&gt;November 13, 2022&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;</p><p>This tweet is ridiculous for so many reasons:</p><ul><li>It doesn't really make any sense, seemingly just some technobabble slapped together to seem intelligent.</li><li>Even if you give Elon the benefit of the doubt and say by &quot;RPC&quot; he meant REST / HTTP calls, Batching them won't necessarily improve performance given HTTP2.</li><li>The tweet seems to imply this is a new issue, which, if anything, would also imply that <a href=\"https://mobile.twitter.com/elonmusk/status/1592177471654604800\">Elon and his antics</a> are to blame.</li><li>Someone was <a href=\"https://twitter.com/MattBinder/status/1592193172830318592\">seemingly fired</a> from Twitter's Android app team for <a href=\"https://twitter.com/EricFrohnhoefer/status/1591902285403418624\">pointing out that the tweet was plain wrong</a>. Firing an SME on your product, who has likely worked to improve the exact deficiency you're complaining about is a <em>bad</em> look.</li><li>The whole thing really just feels like something out of HBO's Silicon Valley.</li></ul><p>Having talked about all of this, a friend joked that someone should create an MIT license that explicitly denies Elon Musk or any of his companies' usage of the associated software. <a href=\"https://github.com/MykalMachon/MIT-SE\">So I did, it's called MIT-SE and you can check it out here.</a></p><ol><li>Is this a silly thing that I made more to make a few friends laugh than for any practical reason? <em>yes!</em></li><li>Is it likely that I'll use MIT-SE in some of my toy open-source projects, also <em>yes! see point 1 for why.</em></li></ol>"}},{"id":"csr-generation-scriptt","url":"https://mykalmachon.com/posts/csr-generation-scriptt","meta":{"title":"CSR generation script","pubDate":"2022-11-10T00:00:00.000Z","type":"post","draft":false,"tags":["devops"],"description":"sometimes the only option is to do SSL manually. This article is for those times."},"content":{"md":"Ideally [Let's Encrypt](https://letsencrypt.org/) and [Certbot](https://certbot.eff.org/) will just ✨automagically⚡ generate all your SSL certificates, but if you're working with internal services (basically anything not exposed to the public internet), or legacy systems that are very specific about the CA / root certificate, you'll still need to generate some certificate signing requests (CSRs) and request a certificate the old-school way from someone like [GoDaddy](https://www.godaddy.com/en-ca/web-security/ssl-certificate), [NameCheap](https://www.namecheap.com/security/ssl-certificates/), or [Entrust](https://www.entrust.com/digital-security/certificate-solutions/products/digital-certificates/tls-ssl-certificates).\n\n## My solution\n\n`openssl` is great, but I find it really hard to remember the commands to generate the CSR goodies you need. To make things a bit easier I used a CSR generation script in my home folder.\n\n```sh\n# makecsr.sh\n# if there is no argumenet error out. \nif [ -z $1 ]\nthen\n        echo \"please provide a fully qualified domain name (i.e. app.mykal.codes)\"\n        echo \"makecsr.sh <fqdn>\"\n        exit 1\nfi\n\n# if there is a valid fully qualified domain name...\n# generate a certificate using SSL \nopenssl req -new -newkey rsa:2048 -nodes -keyout $1__private.key -out $1__csr.csr -subj \"/C=CA/ST=BC/L=Vancouver/O=Tinybox Software Development/OU=Mykal Machon/CN=$1\"\n\n# output the location and filenames for the new CSR and Private key.\necho \"generated a CSR and Private Key for ya!\"\necho \"./$1__csr.csr\"\necho \"./$1__private.key\"\n```\n\nThen when I need a certificate it's as easy as running this in my terminal.\n\n```bash\n~/scripts/makecsr.sh app.mykal.codes\ngenerated a CSR and Private Key for ya!\n./app.mykal.codes__csr.csr\n./app.mykal.codes__private.key\n```\n\nTake those newly generated CSR file off to your certificate authority of choice, and move your private key to the legacy/internal server in preparation for your new SSL certificates.","html":"<p>Ideally <a href=\"https://letsencrypt.org/\">Let's Encrypt</a> and <a href=\"https://certbot.eff.org/\">Certbot</a> will just ✨automagically⚡ generate all your SSL certificates, but if you're working with internal services (basically anything not exposed to the public internet), or legacy systems that are very specific about the CA / root certificate, you'll still need to generate some certificate signing requests (CSRs) and request a certificate the old-school way from someone like <a href=\"https://www.godaddy.com/en-ca/web-security/ssl-certificate\">GoDaddy</a>, <a href=\"https://www.namecheap.com/security/ssl-certificates/\">NameCheap</a>, or <a href=\"https://www.entrust.com/digital-security/certificate-solutions/products/digital-certificates/tls-ssl-certificates\">Entrust</a>.</p><h2>My solution</h2><p><code>openssl</code> is great, but I find it really hard to remember the commands to generate the CSR goodies you need. To make things a bit easier I used a CSR generation script in my home folder.</p><pre><code class=\"language-sh\"># makecsr.sh# if there is no argumenet error out. if [ -z $1 ]then        echo &quot;please provide a fully qualified domain name (i.e. app.mykal.codes)&quot;        echo &quot;makecsr.sh &lt;fqdn&gt;&quot;        exit 1fi# if there is a valid fully qualified domain name...# generate a certificate using SSL openssl req -new -newkey rsa:2048 -nodes -keyout $1__private.key -out $1__csr.csr -subj &quot;/C=CA/ST=BC/L=Vancouver/O=Tinybox Software Development/OU=Mykal Machon/CN=$1&quot;# output the location and filenames for the new CSR and Private key.echo &quot;generated a CSR and Private Key for ya!&quot;echo &quot;./$1__csr.csr&quot;echo &quot;./$1__private.key&quot;</code></pre><p>Then when I need a certificate it's as easy as running this in my terminal.</p><pre><code class=\"language-bash\">~/scripts/makecsr.sh app.mykal.codesgenerated a CSR and Private Key for ya!./app.mykal.codes__csr.csr./app.mykal.codes__private.key</code></pre><p>Take those newly generated CSR file off to your certificate authority of choice, and move your private key to the legacy/internal server in preparation for your new SSL certificates.</p>"}},{"id":"every-company-is-a-tech-company","url":"https://mykalmachon.com/posts/every-company-is-a-tech-company","meta":{"title":"Why every company is a tech company","pubDate":"2022-11-07T00:00:00.000Z","type":"post","draft":false,"tags":["opinion","career"],"description":"Tech is at the core of virtually every business and people would be better off to understand that. "},"content":{"md":"As someone who's worked freelance on and off for years, I've heard quite a few people drop the phrase \"we're not a tech company\".\n\nThis is usually said in a pessimistic tone after someone is pitched on a new website, a new business process automation, an application that could be brought in to save time, or some work to modernize an aging piece of tech already in their business.\n\nEvery. single. time. I hear this I can't help but cringe. **_Almost every company is a tech company._**\n\n## What is a \"tech company\" anyways?\n\nIn my mind, a tech company is any company where technology (i.e. any software-enabled tool) improves your core business (i.e. selling lattes). Through that lens, most companies become tech companies.\n\nDo your customers have phones, computers, or tablets? Do you use a digital system to keep track of spending/finances? Does your business have a social presence? then you're a tech company! It's impossible to avoid being a tech company. At the very least, you're using \"technology\" to communicate with your customers, and if you're not, you're missing out on a whole lot of potential leads and conversions.\n\nGood technology solutions get out of your way and reduce friction in providing your services to customers. If you sell lattes, good tech makes it really damn easy for you to focus on making good lattes, and not much else. With that in mind, there’s probably at least _some_ benefit to adopting good tech in your company, you just need someone on staff, or contract, who can make help you make that call.\n\nBy that definition, there are definitely “tech” tech companies. Think of the [Microsofts](https://microsoft.com) of the world, the [amazon web services](https://aws.amazon.com/), the [railways](https://railway.app), etc. These companies core services are providing tech, but what do they use to help with that process? tech! web apps that make it easy for developers to provision technology and assets. Tech aiding tech.\n\n## What I'm not saying\n\nI'm not saying your local coffee shop should have a technical team on staff to write their own point of sale, website, and delivery system. **What I am saying is that that coffee shop would probably be better off with a generally tech-educated person around who knows when to reach for tech to simplify their core business.**\n\nI’m not saying every company should make tech core to their offerings. There are definitely cases where this doesn’t ring true. **What I am saying is that businesses shouldn’t be afraid to evaluate their current processes and look for things that can be improved with tech.**","html":"<p>As someone who's worked freelance on and off for years, I've heard quite a few people drop the phrase &quot;we're not a tech company&quot;.</p><p>This is usually said in a pessimistic tone after someone is pitched on a new website, a new business process automation, an application that could be brought in to save time, or some work to modernize an aging piece of tech already in their business.</p><p>Every. single. time. I hear this I can't help but cringe. <strong><em>Almost every company is a tech company.</em></strong></p><h2>What is a &quot;tech company&quot; anyways?</h2><p>In my mind, a tech company is any company where technology (i.e. any software-enabled tool) improves your core business (i.e. selling lattes). Through that lens, most companies become tech companies.</p><p>Do your customers have phones, computers, or tablets? Do you use a digital system to keep track of spending/finances? Does your business have a social presence? then you're a tech company! It's impossible to avoid being a tech company. At the very least, you're using &quot;technology&quot; to communicate with your customers, and if you're not, you're missing out on a whole lot of potential leads and conversions.</p><p>Good technology solutions get out of your way and reduce friction in providing your services to customers. If you sell lattes, good tech makes it really damn easy for you to focus on making good lattes, and not much else. With that in mind, there’s probably at least <em>some</em> benefit to adopting good tech in your company, you just need someone on staff, or contract, who can make help you make that call.</p><p>By that definition, there are definitely “tech” tech companies. Think of the <a href=\"https://microsoft.com\">Microsofts</a> of the world, the <a href=\"https://aws.amazon.com/\">amazon web services</a>, the <a href=\"https://railway.app\">railways</a>, etc. These companies core services are providing tech, but what do they use to help with that process? tech! web apps that make it easy for developers to provision technology and assets. Tech aiding tech.</p><h2>What I'm not saying</h2><p>I'm not saying your local coffee shop should have a technical team on staff to write their own point of sale, website, and delivery system. <strong>What I am saying is that that coffee shop would probably be better off with a generally tech-educated person around who knows when to reach for tech to simplify their core business.</strong></p><p>I’m not saying every company should make tech core to their offerings. There are definitely cases where this doesn’t ring true. <strong>What I am saying is that businesses shouldn’t be afraid to evaluate their current processes and look for things that can be improved with tech.</strong></p>"}},{"id":"the-incoming-internet-switch-up","url":"https://mykalmachon.com/posts/the-incoming-internet-switch-up","meta":{"title":"The incoming internet switch-up","pubDate":"2022-10-30T00:00:00.000Z","type":"post","draft":false,"tags":["opinion","personal"],"description":"Things are changing fast and all at once on the internet."},"content":{"md":"It feels like a good chunk of the previously untouchable \"tech giants\" that rose to popularity (and generated buckets of money) over the last ten years are falling apart all at once.\n\n* [Elon just bought Twitter](https://techcrunch.com/2022/10/28/elon-musk-owns-twitter-the-story-so-far/), and I'm not stoked about what he's got planned:\n  * Elon is touting \"free speech\" as the key to making Twitter \"the de facto public town square\".\n  * IMO this is a dog whistle for racists, homophobes and similar gremlins to come to Twitter in droves and feel safe being terrible humans.\n  * Major bummer because Twitter is one of my favourite places to hang out.\n* Meta and its portfolio of social apps are not fun, [and seem to be imploding](https://www.cbsnews.com/news/meta-stock-down-earnings-700-billion-in-lost-value/):\n  * [Spending billions of dollars on R&D in the VR/AR space](https://www.cnbc.com/2022/07/27/meta-reality-labs-lost-2point8-billion-in-q2-2022.html) VR/AR is admittedly pretty interesting to me but feels 10-20 years and many breakthroughs away from validity.\n  * Meta's social apps were funding all of this, but thanks to more granular privacy controls on iPhones they're no longer the cash cows they used to be. [this has apparently cost FB \\~10 billion a year in sales](https://www.macworld.com/article/611551/facebook-app-tracking-transparency-iphone-quarterly-results.html).\n  * Overall most people I know seem \"over\" Facebook and Instagram. Nothing interesting ever seems to be going on there. \n* Reddit is still... Reddit. There are a few nice corners here, but they come and go.\n  * Reddit is a parable for why you _don't_ want to be the free-speech town square of the internet (Twitter's new direction). They've improved recently but are still recovering from years of terrible management.\n  * Was home to droves of [_insanely hateful_ communities](https://en.wikipedia.org/wiki/Controversial_Reddit_communities) protected by years of terrible policies enforcement around hate speech and misconduct.\n  * I'm part of a few healthy small communities on here (specifically around some niche hobbies of mine, more in another post) but they're few and far between.\n* Netflix and the onslaught of \"new\" streaming services are effectively cable bundles 2.0\n  * Netflix is $20/mo, Crave (Canadian HBO+) is $20/mo, Disney plus is $12/mo, etc, etc.\n  * I think the prohibitive cost of these services will spike piracy🏴‍☠️ over the next few years.\n  * I miss when all I needed was Netflix.  \n* The general downturn of high-risk, low-payout investment killing no/low-profit startups\n  * The economy is in the shitter and loans are no longer free! \n  * Startups with low to no profit will likely not do so hot over the next while, effectively killing a bunch of the \"meh\" apps (Snapchat comes to mind here🌶️)\n\n## Predictions for the future\n\nThese sites figuratively crashing and burning is likely going to open up some space for new fun places on the internet.\n\n### Things I'd say are a sure bet\n\nHonestly, I think that a lot of the new \"big things on the internet\" are already out there, and have probably already effectively replaced their aging social counterparts. \n\nHere's what I've been enjoying and am confident will continue to succeed:\n\n* TikTok made me laugh and shattered my productivity. It's the only app I can think of that I used so much I had to uninstall it, I was spending hours a day on the app (yikes). Given the addictive nature and strong user base, I'm sure it'll stay huge.\n* Discord connects me to my friends in person and on the internet. It's low friction, is fun to use, and generally easy to moderate. It has public and private spaces to talk about whatever you like.\n* Youtube is still great if you have good taste. Lots of great content but it occasionally suggests some weird alt-right men's rights videos which are gross, but if I ignore the suggestions and continue as usual they disappear. \n\n### Niche apps I _hope_ will take off\n\nI also have a few niche pieces of tech that I really like using, and hope will take off in a more mainstream way. At the same time, I'm enough of a realist to know that niche apps tend to be a niche for a reason.\n\n* RSS or some generic syndication format for content across the internet. Having a generic protocol for sharing content would open up so many doors.\n* The return of personal websites and blogs powered-up by the modern internet. Imagine how cool Tumblr themes could be with a CSS grid and WASM-enabled apps.\n* Something like [Mastodon](https://joinmastodon.org/) that decentralizes socials (but not in a cringe crypto way). For now, Mastodon is just so damn confusing. I can't figure it out and I'm pretty much their exact target market.\n\n## Something else exciting\n\nI just want something that makes me feel stoked to open a browser again.\n\nWhatever this is, it probably won't end up being a \"social app\" in the lane of \"Facebook 2\". Things like r/place on Reddit, or other internet-enabled communal _experiences_ seem like a happier, more positive, path to go down.\n\n[I don't know, something amazing I guess.](https://www.youtube.com/watch?v=YuWsa8IJblw)","html":"<p>It feels like a good chunk of the previously untouchable &quot;tech giants&quot; that rose to popularity (and generated buckets of money) over the last ten years are falling apart all at once.</p><ul><li><a href=\"https://techcrunch.com/2022/10/28/elon-musk-owns-twitter-the-story-so-far/\">Elon just bought Twitter</a>, and I'm not stoked about what he's got planned:<ul><li>Elon is touting &quot;free speech&quot; as the key to making Twitter &quot;the de facto public town square&quot;.</li><li>IMO this is a dog whistle for racists, homophobes and similar gremlins to come to Twitter in droves and feel safe being terrible humans.</li><li>Major bummer because Twitter is one of my favourite places to hang out.</li></ul></li><li>Meta and its portfolio of social apps are not fun, <a href=\"https://www.cbsnews.com/news/meta-stock-down-earnings-700-billion-in-lost-value/\">and seem to be imploding</a>:<ul><li><a href=\"https://www.cnbc.com/2022/07/27/meta-reality-labs-lost-2point8-billion-in-q2-2022.html\">Spending billions of dollars on R&amp;D in the VR/AR space</a> VR/AR is admittedly pretty interesting to me but feels 10-20 years and many breakthroughs away from validity.</li><li>Meta's social apps were funding all of this, but thanks to more granular privacy controls on iPhones they're no longer the cash cows they used to be. <a href=\"https://www.macworld.com/article/611551/facebook-app-tracking-transparency-iphone-quarterly-results.html\">this has apparently cost FB ~10 billion a year in sales</a>.</li><li>Overall most people I know seem &quot;over&quot; Facebook and Instagram. Nothing interesting ever seems to be going on there.</li></ul></li><li>Reddit is still... Reddit. There are a few nice corners here, but they come and go.<ul><li>Reddit is a parable for why you <em>don't</em> want to be the free-speech town square of the internet (Twitter's new direction). They've improved recently but are still recovering from years of terrible management.</li><li>Was home to droves of <a href=\"https://en.wikipedia.org/wiki/Controversial_Reddit_communities\"><em>insanely hateful</em> communities</a> protected by years of terrible policies enforcement around hate speech and misconduct.</li><li>I'm part of a few healthy small communities on here (specifically around some niche hobbies of mine, more in another post) but they're few and far between.</li></ul></li><li>Netflix and the onslaught of &quot;new&quot; streaming services are effectively cable bundles 2.0<ul><li>Netflix is $20/mo, Crave (Canadian HBO+) is $20/mo, Disney plus is $12/mo, etc, etc.</li><li>I think the prohibitive cost of these services will spike piracy🏴‍☠️ over the next few years.</li><li>I miss when all I needed was Netflix.</li></ul></li><li>The general downturn of high-risk, low-payout investment killing no/low-profit startups<ul><li>The economy is in the shitter and loans are no longer free!</li><li>Startups with low to no profit will likely not do so hot over the next while, effectively killing a bunch of the &quot;meh&quot; apps (Snapchat comes to mind here🌶️)</li></ul></li></ul><h2>Predictions for the future</h2><p>These sites figuratively crashing and burning is likely going to open up some space for new fun places on the internet.</p><h3>Things I'd say are a sure bet</h3><p>Honestly, I think that a lot of the new &quot;big things on the internet&quot; are already out there, and have probably already effectively replaced their aging social counterparts.</p><p>Here's what I've been enjoying and am confident will continue to succeed:</p><ul><li>TikTok made me laugh and shattered my productivity. It's the only app I can think of that I used so much I had to uninstall it, I was spending hours a day on the app (yikes). Given the addictive nature and strong user base, I'm sure it'll stay huge.</li><li>Discord connects me to my friends in person and on the internet. It's low friction, is fun to use, and generally easy to moderate. It has public and private spaces to talk about whatever you like.</li><li>Youtube is still great if you have good taste. Lots of great content but it occasionally suggests some weird alt-right men's rights videos which are gross, but if I ignore the suggestions and continue as usual they disappear.</li></ul><h3>Niche apps I <em>hope</em> will take off</h3><p>I also have a few niche pieces of tech that I really like using, and hope will take off in a more mainstream way. At the same time, I'm enough of a realist to know that niche apps tend to be a niche for a reason.</p><ul><li>RSS or some generic syndication format for content across the internet. Having a generic protocol for sharing content would open up so many doors.</li><li>The return of personal websites and blogs powered-up by the modern internet. Imagine how cool Tumblr themes could be with a CSS grid and WASM-enabled apps.</li><li>Something like <a href=\"https://joinmastodon.org/\">Mastodon</a> that decentralizes socials (but not in a cringe crypto way). For now, Mastodon is just so damn confusing. I can't figure it out and I'm pretty much their exact target market.</li></ul><h2>Something else exciting</h2><p>I just want something that makes me feel stoked to open a browser again.</p><p>Whatever this is, it probably won't end up being a &quot;social app&quot; in the lane of &quot;Facebook 2&quot;. Things like r/place on Reddit, or other internet-enabled communal <em>experiences</em> seem like a happier, more positive, path to go down.</p><p><a href=\"https://www.youtube.com/watch?v=YuWsa8IJblw\">I don't know, something amazing I guess.</a></p>"}},{"id":"always-take-screenshots","url":"https://mykalmachon.com/posts/always-take-screenshots","meta":{"title":"Always take screenshots","pubDate":"2022-10-13T00:00:00.000Z","type":"post","draft":false,"tags":["webdev"],"description":"You can't depend on your work to live forever, take screenshots."},"content":{"md":"I'm in the process of rebuilding [the Tinybox website](https://tinybox.dev). As part of that effort, I'm putting together a bunch of case studies and portfolio pages highlighting previous works. I've been visiting sites I worked on anywhere from 6 months ago to 3 years ago to collect visuals for these pages.\n\nOf the 5 portfolio page drafts I've written up, only 2 of the 5 sites delivered are still similar to how I left them. Bummer. 1 of the companies has shut down, 1 of them changed directions, and 1 of them inspired the twitter rant below:\n\n<blockquote class=\"twitter-tweet\"><p lang=\"en\" dir=\"ltr\">Imagine this:<br>You build a site. You make sure all the t's are crossed, and i's are dotted. You write guides on how to update products. You record screencasts on how to use the CMS. You did all you could. <br><br>You check out the site again 6 months later and it's absolutely destroyed. <a href=\"https://t.co/vhFrf5KsT4\">pic.twitter.com/vhFrf5KsT4</a></p>— Mykal Machon (@mykalmachon) <a href=\"https://twitter.com/mykalmachon/status/1580420659007852546?ref_src=twsrc%5Etfw\">October 13, 2022</a></blockquote> <script async src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\"></script>\n\n## Websites are living documents\n\nWebsites are always changing.\nPeople go into the CMS, they change some words, add some products, if you're really unlucky you even get someone who knows enough to be dangerous and messes around with the HTML, CSS, and JS.\n\nUntil now it hasn't really hit me what that can mean in terms of showcasing work, especially when projects no longer exist online, or get demolished by popups and plugins.\n\nFor Tinybox alone, 3/5 of my most recent significant public projects are either no longer available online, or have been altered to the point where I don't feel comfortable putting my name on them. **So if you can't count on the website being around after launch to show people your work, what are you supposed to do?**\n\nMy first idea was to **use the source!** Why not just boot up a development server and take screenshots of that? Unfortunately it's not always that easy:\n\n1. Does the source code you have require external services? I have a number of small react projects that have dependencies that no longer exist (i.e., a client's API)\n2. Does the source code require some sort of database connection to operate? where does that seed data come from?\n3. Do you still have access to that source code? what if you lost access when the contract ended?\n\n[Andrew Walpole](https://twitter.com/walpolea?s=20&t=Pcu6SxSdDjL0Z_rKz1pPsQ) replied to the aforementioned twitter rant, and jumped right to the logical conclusion for me:\n\n<blockquote class=\"twitter-tweet\"><p lang=\"en\" dir=\"ltr\">This is why all our case studies are videos and images. Often too much of a moving target to link out.</p>— Andrew Walpole (@walpolea) <a href=\"https://twitter.com/walpolea/status/1580435756790665216?ref_src=twsrc%5Etfw\">October 13, 2022</a></blockquote>\n\n## Archive projects on launch day\n\nI do take screenshots and videos of my projects while I'm working on them **_but_** they're typically for approval and documentation purposes, definitely not for showcasing the work I'm doing. They either get deleted after the project, stored away in the clients documentation, or lost in the ethereal realm that is my email outbox.\n\n**Moving forward, I think the best approach is to archive projects on launch day.** Take screenshots of the site, take videos of the fun interactions, and put them somewhere they won't get lost.\n\nAfter doing some thinking this is definitely one of those cases where the simplest solution is the best one:\n\n1. **Easy**: It takes seconds to take a screenshot, and barely a minute to take most videos.\n2. **Dependency free**: media doesn't require a `node_modules` folder. 'nuff said.\n3. **Immutable**: photos and videos sitting in a folder won't change over time.\n4. **Adaptable**: you can open up a bunch of screenshots in Figma, and edit them together into a pretty collage. You can't exactly do that with source code 😅\n5. **Fun**: I'm sure in 5-10 years it'll be fun to look back at all the things I've worked on and how trends have changed.\n\n**TLDR: always take screenshots of your work. You'll thank yourself later.**","html":"<p>I'm in the process of rebuilding <a href=\"https://tinybox.dev\">the Tinybox website</a>. As part of that effort, I'm putting together a bunch of case studies and portfolio pages highlighting previous works. I've been visiting sites I worked on anywhere from 6 months ago to 3 years ago to collect visuals for these pages.</p><p>Of the 5 portfolio page drafts I've written up, only 2 of the 5 sites delivered are still similar to how I left them. Bummer. 1 of the companies has shut down, 1 of them changed directions, and 1 of them inspired the twitter rant below:</p><p>&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Imagine this:&lt;br&gt;You build a site. You make sure all the t's are crossed, and i's are dotted. You write guides on how to update products. You record screencasts on how to use the CMS. You did all you could. &lt;br&gt;&lt;br&gt;You check out the site again 6 months later and it's absolutely destroyed. &lt;a href=&quot;https://t.co/vhFrf5KsT4&quot;&gt;pic.twitter.com/vhFrf5KsT4&lt;/a&gt;&lt;/p&gt;— Mykal Machon (@mykalmachon) &lt;a href=&quot;https://twitter.com/mykalmachon/status/1580420659007852546?ref_src=twsrc%5Etfw&quot;&gt;October 13, 2022&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;</p><h2>Websites are living documents</h2><p>Websites are always changing.People go into the CMS, they change some words, add some products, if you're really unlucky you even get someone who knows enough to be dangerous and messes around with the HTML, CSS, and JS.</p><p>Until now it hasn't really hit me what that can mean in terms of showcasing work, especially when projects no longer exist online, or get demolished by popups and plugins.</p><p>For Tinybox alone, 3/5 of my most recent significant public projects are either no longer available online, or have been altered to the point where I don't feel comfortable putting my name on them. <strong>So if you can't count on the website being around after launch to show people your work, what are you supposed to do?</strong></p><p>My first idea was to <strong>use the source!</strong> Why not just boot up a development server and take screenshots of that? Unfortunately it's not always that easy:</p><ol><li>Does the source code you have require external services? I have a number of small react projects that have dependencies that no longer exist (i.e., a client's API)</li><li>Does the source code require some sort of database connection to operate? where does that seed data come from?</li><li>Do you still have access to that source code? what if you lost access when the contract ended?</li></ol><p><a href=\"https://twitter.com/walpolea?s=20&amp;t=Pcu6SxSdDjL0Z_rKz1pPsQ\">Andrew Walpole</a> replied to the aforementioned twitter rant, and jumped right to the logical conclusion for me:</p><p>&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;This is why all our case studies are videos and images. Often too much of a moving target to link out.&lt;/p&gt;— Andrew Walpole (@walpolea) &lt;a href=&quot;https://twitter.com/walpolea/status/1580435756790665216?ref_src=twsrc%5Etfw&quot;&gt;October 13, 2022&lt;/a&gt;&lt;/blockquote&gt;</p><h2>Archive projects on launch day</h2><p>I do take screenshots and videos of my projects while I'm working on them <strong><em>but</em></strong> they're typically for approval and documentation purposes, definitely not for showcasing the work I'm doing. They either get deleted after the project, stored away in the clients documentation, or lost in the ethereal realm that is my email outbox.</p><p><strong>Moving forward, I think the best approach is to archive projects on launch day.</strong> Take screenshots of the site, take videos of the fun interactions, and put them somewhere they won't get lost.</p><p>After doing some thinking this is definitely one of those cases where the simplest solution is the best one:</p><ol><li><strong>Easy</strong>: It takes seconds to take a screenshot, and barely a minute to take most videos.</li><li><strong>Dependency free</strong>: media doesn't require a <code>node_modules</code> folder. 'nuff said.</li><li><strong>Immutable</strong>: photos and videos sitting in a folder won't change over time.</li><li><strong>Adaptable</strong>: you can open up a bunch of screenshots in Figma, and edit them together into a pretty collage. You can't exactly do that with source code 😅</li><li><strong>Fun</strong>: I'm sure in 5-10 years it'll be fun to look back at all the things I've worked on and how trends have changed.</li></ol><p><strong>TLDR: always take screenshots of your work. You'll thank yourself later.</strong></p>"}},{"id":"cauliflower-fried-rice-wraps","url":"https://mykalmachon.com/posts/cauliflower-fried-rice-wraps","meta":{"title":"Cauliflower fried rice wraps","pubDate":"2022-10-05T00:00:00.000Z","type":"post","draft":false,"tags":["food"],"description":"a 20-minute cauliflower rice recipe "},"content":{"md":"I had a friend ask me for a go-to cauliflower rice recipe so here it is. I do feel like this is more of a \"base\". If there's something that will add a bit of spice🌶️ or variety✨ in your fridge feel free to experiment and throw it in.\n\nI've only made this recipe, as written out, _once_, but have used cauliflower rice similarly lots before. It's also awesome as a direct rice replacement in most stir-fries and curries if you're into that.\n\n## Ingredients\n\n* Cauliflower rice: frozen or fresh is fine\n* 1 bell pepper, diced\n* 1 half white onion, diced\n* 4 cloves of garlic, diced\n* 2 eggs\n* A pack of Tortillas\n* siracha\n* soy sauce\n* butter, or olive oil\n* salt and pepper\n* green onions\n* sesame seeds (optional)\n* sesame oil (optional)\n* a handful of peanuts (optional)\n\n## Instructions\n\n 1. Put a large saucepan on the stove and set it to medium/high heat.\n 2. While the pan is heating, dice all the veggies.\n 3. When hot, add butter or olive oil to the pan (just to lightly coat the pan).\n 4. Add peppers, white onion, garlic, and peanuts. Cook and stir for 3-4 minutes, or until fragrant.\n 5. Add cauliflower rice and stir until incorporated. Cook for another 3-4 minutes.\n 6. Put 2-eggs in a bowl and whisk them together (we're making scrambled!). add salt and pepper.\n 7. Add a healthy amount of soy sauce and siracha to the rice; whatever your preference is.\n 8. Make a well in the centre of the cauliflower rice and add your eggs and a lil' sesame oil.\n 9. Stir regularly until the eggs are scrambled, then incorporate them into the rice.\n10. Cook a little more till you're happy with how things are looking and smelling.\n11. Add sesame seeds, green onion, and more siracha as a topper if you'd like\n12. Portion the rice into tortillas and wrap them up!\n13. You can eat the wraps as is, or fry them in the pan to get 'em a bit crispy.\n14. Enjoy 🌯✨","html":"<p>I had a friend ask me for a go-to cauliflower rice recipe so here it is. I do feel like this is more of a &quot;base&quot;. If there's something that will add a bit of spice🌶️ or variety✨ in your fridge feel free to experiment and throw it in.</p><p>I've only made this recipe, as written out, <em>once</em>, but have used cauliflower rice similarly lots before. It's also awesome as a direct rice replacement in most stir-fries and curries if you're into that.</p><h2>Ingredients</h2><ul><li>Cauliflower rice: frozen or fresh is fine</li><li>1 bell pepper, diced</li><li>1 half white onion, diced</li><li>4 cloves of garlic, diced</li><li>2 eggs</li><li>A pack of Tortillas</li><li>siracha</li><li>soy sauce</li><li>butter, or olive oil</li><li>salt and pepper</li><li>green onions</li><li>sesame seeds (optional)</li><li>sesame oil (optional)</li><li>a handful of peanuts (optional)</li></ul><h2>Instructions</h2><ol><li>Put a large saucepan on the stove and set it to medium/high heat.</li><li>While the pan is heating, dice all the veggies.</li><li>When hot, add butter or olive oil to the pan (just to lightly coat the pan).</li><li>Add peppers, white onion, garlic, and peanuts. Cook and stir for 3-4 minutes, or until fragrant.</li><li>Add cauliflower rice and stir until incorporated. Cook for another 3-4 minutes.</li><li>Put 2-eggs in a bowl and whisk them together (we're making scrambled!). add salt and pepper.</li><li>Add a healthy amount of soy sauce and siracha to the rice; whatever your preference is.</li><li>Make a well in the centre of the cauliflower rice and add your eggs and a lil' sesame oil.</li><li>Stir regularly until the eggs are scrambled, then incorporate them into the rice.</li><li>Cook a little more till you're happy with how things are looking and smelling.</li><li>Add sesame seeds, green onion, and more siracha as a topper if you'd like</li><li>Portion the rice into tortillas and wrap them up!</li><li>You can eat the wraps as is, or fry them in the pan to get 'em a bit crispy.</li><li>Enjoy 🌯✨</li></ol>"}},{"id":"pioneers-settlers-and-city-planners","url":"https://mykalmachon.com/posts/pioneers-settlers-and-city-planners","meta":{"title":"Pioneers, Settlers, and City Planners","pubDate":"2022-09-29T00:00:00.000Z","type":"post","draft":false,"tags":["opinion","career"],"description":"some notes on archetypes and exceling at work."},"content":{"md":"I was watching one of [Theo Browne](https://twitter.com/t3dotgg)'s live streams with [Jason Lengstorf](https://twitter.com/jlengstorf) today and they got around to talking about the archetypes of people who excel in certain roles ([here's a link to that part of the stream](https://youtu.be/nwAr5bFOi1s?t=4489)). \n\nJason brought up this concept of \"Pioneers, Settlers, and City Planners\" that I really identified with: \n\n* **Pioneers:** great at lateral thinking. Map biz needs into technical solutions and break ground on solutions. Find it hard to stick on projects long-term, like to move around a lot in their day-to-day. \n* **Settlers:** great at taking prototypes or partly working solutions and getting them \"90% of the way there\" through a mix of best practice, polish, and design. Excel with clear goals. Can often do a bit of pioneering or city planning themselves as well. \n* **City Planners:** great at maintaining strongly established systems, polishing up that \"last 10%\", and making them scale-able. Very deep understanding of the tech required to optimize and automate. \n\nAfter doing some googling, it seems like [Simon Wardley actually coined the phrase](https://interaction.net.au/articles/pioneers-settlers-town-planners-how-innovation-works/#:\\~:text=Researcher%20Simon%20Wardley%20derived%20the,into%20cell%2Dbased%20organizational%20structures.) (Jason brought this up, I just missed it in my notes). After reading through the article I liked above (and being a little weirded out by the colonialist \"manifest destiny\" of it all), I do really like how this abstraction maps to complex problem solving in development.\n\nI think I'm a bit of a Pioneer at heart (i.e. my freelancing work was mostly greenfield, and countless sort-of-working prototypes on GitHub). In reality I excel at levelling up existing, but non-optimal solutions which in what would be considered the Settler role. The City Planner role is something I've always struggled with in practice. I've very rarely had the patience to sit and optimize solutions that are _mostly_ there (the few exceptions being where it was either required for scale, or user experience).  \n\nIn retrospect, I learned a lot of my web development skills through very Settler behavior. At my first office job, we had an excel sheet that was used for tracking sales, commission, and other business-y stuff; a tale as old as time. This was before cloud solutions were a given for most businesses as well, so it sat on a shared local drive and inherited all the lovely overwriting issues that come with that as well. It was a headache, but the business needed it.  \n\nAfter convincing my boss to let me take a crack at cleaning it up, I migrated the data into firebase and built a web app where users could go in and edit the data, view the reports, and download portions of the data as a CSV. This is peak Settler behavior. I took a hacked together prototype (excel sheet on company hard drive) and got it 90% of the way to optimal (vanilla JS web app with Firebase). The web app was probably not architected well (given that webdev was new to me at the time) but that's besides the point, it worked and I think it stayed in \"production\" for quite awhile after I left.","html":"<p>I was watching one of <a href=\"https://twitter.com/t3dotgg\">Theo Browne</a>'s live streams with <a href=\"https://twitter.com/jlengstorf\">Jason Lengstorf</a> today and they got around to talking about the archetypes of people who excel in certain roles (<a href=\"https://youtu.be/nwAr5bFOi1s?t=4489\">here's a link to that part of the stream</a>).</p><p>Jason brought up this concept of &quot;Pioneers, Settlers, and City Planners&quot; that I really identified with:</p><ul><li><strong>Pioneers:</strong> great at lateral thinking. Map biz needs into technical solutions and break ground on solutions. Find it hard to stick on projects long-term, like to move around a lot in their day-to-day.</li><li><strong>Settlers:</strong> great at taking prototypes or partly working solutions and getting them &quot;90% of the way there&quot; through a mix of best practice, polish, and design. Excel with clear goals. Can often do a bit of pioneering or city planning themselves as well.</li><li><strong>City Planners:</strong> great at maintaining strongly established systems, polishing up that &quot;last 10%&quot;, and making them scale-able. Very deep understanding of the tech required to optimize and automate.</li></ul><p>After doing some googling, it seems like <a href=\"https://interaction.net.au/articles/pioneers-settlers-town-planners-how-innovation-works/#:~:text=Researcher%20Simon%20Wardley%20derived%20the,into%20cell%2Dbased%20organizational%20structures.\">Simon Wardley actually coined the phrase</a> (Jason brought this up, I just missed it in my notes). After reading through the article I liked above (and being a little weirded out by the colonialist &quot;manifest destiny&quot; of it all), I do really like how this abstraction maps to complex problem solving in development.</p><p>I think I'm a bit of a Pioneer at heart (i.e. my freelancing work was mostly greenfield, and countless sort-of-working prototypes on GitHub). In reality I excel at levelling up existing, but non-optimal solutions which in what would be considered the Settler role. The City Planner role is something I've always struggled with in practice. I've very rarely had the patience to sit and optimize solutions that are <em>mostly</em> there (the few exceptions being where it was either required for scale, or user experience).</p><p>In retrospect, I learned a lot of my web development skills through very Settler behavior. At my first office job, we had an excel sheet that was used for tracking sales, commission, and other business-y stuff; a tale as old as time. This was before cloud solutions were a given for most businesses as well, so it sat on a shared local drive and inherited all the lovely overwriting issues that come with that as well. It was a headache, but the business needed it.</p><p>After convincing my boss to let me take a crack at cleaning it up, I migrated the data into firebase and built a web app where users could go in and edit the data, view the reports, and download portions of the data as a CSV. This is peak Settler behavior. I took a hacked together prototype (excel sheet on company hard drive) and got it 90% of the way to optimal (vanilla JS web app with Firebase). The web app was probably not architected well (given that webdev was new to me at the time) but that's besides the point, it worked and I think it stayed in &quot;production&quot; for quite awhile after I left.</p>"}},{"id":"on-the-steam-deck","url":"https://mykalmachon.com/posts/on-the-steam-deck","meta":{"title":"On the Steam Deck","pubDate":"2022-09-28T00:00:00.000Z","type":"post","draft":false,"tags":["personal"],"description":"My initial thoughts on the steam deck after a few weeks with it","heroImage":{"src":"/_astro/on-the-steam-deck.BODGj9IT.avif","width":1200,"height":900,"format":"avif"}},"content":{"md":"After making a reservation on Feb 28th of this year, receiving a \"Your Steam Deck is now available\" email from Valve on Sept 1st, and waiting until Sept 14th, I've finally received my Steam Deck!\n\nFor the last few weeks I've been gaming, experimenting with desktop mode (insert \"I use Kali, btw\" meme here), and trying to remember how to to play shooters on a controller. I've also had a few friends reach out and ask if it was worth the wait, so I figured I'd put together a few bullets on how I feel 2 weeks in:\n\n* Wow this thing is big! I knew to expect it, but it's surprising nonetheless.\n* The carrying case included with the deck is _very_ well built. The stiff construction, thick interior padding, and form-fit mold inside are all confidence inspiring.\n* The build quality of the device itself is great. It feels similar-or-better to most console controllers. Leagues ahead of the Nintendo switch\n  * The physical controls are well done: buttons have a satisfying click, sticks are accurate, d-pad is the best I've used in awhile.\n  * The touchpads were a bit odd at first but I really enjoy using them now. I've noticed the haptics on the right touchpad are more powerful than those on the left touchpad. After some googling this seems like a common issue.\n  * The customization you get via per-game controller profiles is awesome and painless.\n  * The buttons on the backside are a bit awkward to reach. That could have to do more with my hand-size than design.\n* It's _\\~alright_ for long sessions. To be fair, I've never found handheld consoles suitable for longer play sessions\n  * I've done a few 1.5-2 hour sessions with the steam deck already with minimal cramping / hand numbness (for reference, I only make it 30-45 minutes on my Switch)\n* So far I've been able to run every game I've wanted to without significant issues.\n  * Elden ring is by far the hardest hitter as far as I can tell. It can stutter in certain spots but hovers between 40-60fps most of the time.\n  * I've been able to emulate a bunch of Wii and GameCube games at 60fps with v-sync, 4x AA, and 2x native resolution. Some Switch games even run at 60fps.\n  * Other games I've played include: [Titanfall 2](https://store.steampowered.com/app/1237970/Titanfall_2/), [Slay the Spire](https://store.steampowered.com/app/646570/Slay_the_Spire/), [JetLancer](https://store.steampowered.com/app/913060/Jet_Lancer/), [Road Warden](https://store.steampowered.com/app/1155970/Roadwarden/), [Stacklands](https://store.steampowered.com/app/1948280/Stacklands/), and [Portal 2](https://store.steampowered.com/app/620/Portal_2/). They all ran at a consistent 60fps or more.\n  * The \"Great on deck\" filter that is useful for finding well-optimized games. Around 35% of my \\~250 game library is marked as \"great on deck\".\n* Battery life is pretty impressive as well but we'll see how well that lasts\n  * For high intensity 3D / AAA games I've seen anywhere from 2-4 hours regularly.\n  * For more simple 2D games or emulation I've been getting 7-8 hours regularly.\n* Software is good, but not bug free.\n  * I've had my steam deck just _refuse_ to connect to steam servers 2 or 3 times now. This usually happens when wi-fi drops out, then reconnects. The only fix I've found is a full restart.\n  * I've had a few negative encounters with the keyboard popping out in the middle of a game, and then closing itself instantly.\n  * While the interface is mostly consistent, there's definitely [a bit of the usual](https://www.reddit.com/r/Steam/comments/tisoop/the_amazing_consistency_of_steams_ui/).\n\n**_TLDR: I've been really impressed by the device, despite some small QC issues with my touchpads. If you're on the fence and want to game more, but hate sitting in your office while doing it, I'd recommend it._**","html":"<p>After making a reservation on Feb 28th of this year, receiving a &quot;Your Steam Deck is now available&quot; email from Valve on Sept 1st, and waiting until Sept 14th, I've finally received my Steam Deck!</p><p>For the last few weeks I've been gaming, experimenting with desktop mode (insert &quot;I use Kali, btw&quot; meme here), and trying to remember how to to play shooters on a controller. I've also had a few friends reach out and ask if it was worth the wait, so I figured I'd put together a few bullets on how I feel 2 weeks in:</p><ul><li>Wow this thing is big! I knew to expect it, but it's surprising nonetheless.</li><li>The carrying case included with the deck is <em>very</em> well built. The stiff construction, thick interior padding, and form-fit mold inside are all confidence inspiring.</li><li>The build quality of the device itself is great. It feels similar-or-better to most console controllers. Leagues ahead of the Nintendo switch<ul><li>The physical controls are well done: buttons have a satisfying click, sticks are accurate, d-pad is the best I've used in awhile.</li><li>The touchpads were a bit odd at first but I really enjoy using them now. I've noticed the haptics on the right touchpad are more powerful than those on the left touchpad. After some googling this seems like a common issue.</li><li>The customization you get via per-game controller profiles is awesome and painless.</li><li>The buttons on the backside are a bit awkward to reach. That could have to do more with my hand-size than design.</li></ul></li><li>It's <em>~alright</em> for long sessions. To be fair, I've never found handheld consoles suitable for longer play sessions<ul><li>I've done a few 1.5-2 hour sessions with the steam deck already with minimal cramping / hand numbness (for reference, I only make it 30-45 minutes on my Switch)</li></ul></li><li>So far I've been able to run every game I've wanted to without significant issues.<ul><li>Elden ring is by far the hardest hitter as far as I can tell. It can stutter in certain spots but hovers between 40-60fps most of the time.</li><li>I've been able to emulate a bunch of Wii and GameCube games at 60fps with v-sync, 4x AA, and 2x native resolution. Some Switch games even run at 60fps.</li><li>Other games I've played include: <a href=\"https://store.steampowered.com/app/1237970/Titanfall_2/\">Titanfall 2</a>, <a href=\"https://store.steampowered.com/app/646570/Slay_the_Spire/\">Slay the Spire</a>, <a href=\"https://store.steampowered.com/app/913060/Jet_Lancer/\">JetLancer</a>, <a href=\"https://store.steampowered.com/app/1155970/Roadwarden/\">Road Warden</a>, <a href=\"https://store.steampowered.com/app/1948280/Stacklands/\">Stacklands</a>, and <a href=\"https://store.steampowered.com/app/620/Portal_2/\">Portal 2</a>. They all ran at a consistent 60fps or more.</li><li>The &quot;Great on deck&quot; filter that is useful for finding well-optimized games. Around 35% of my ~250 game library is marked as &quot;great on deck&quot;.</li></ul></li><li>Battery life is pretty impressive as well but we'll see how well that lasts<ul><li>For high intensity 3D / AAA games I've seen anywhere from 2-4 hours regularly.</li><li>For more simple 2D games or emulation I've been getting 7-8 hours regularly.</li></ul></li><li>Software is good, but not bug free.<ul><li>I've had my steam deck just <em>refuse</em> to connect to steam servers 2 or 3 times now. This usually happens when wi-fi drops out, then reconnects. The only fix I've found is a full restart.</li><li>I've had a few negative encounters with the keyboard popping out in the middle of a game, and then closing itself instantly.</li><li>While the interface is mostly consistent, there's definitely <a href=\"https://www.reddit.com/r/Steam/comments/tisoop/the_amazing_consistency_of_steams_ui/\">a bit of the usual</a>.</li></ul></li></ul><p><strong><em>TLDR: I've been really impressed by the device, despite some small QC issues with my touchpads. If you're on the fence and want to game more, but hate sitting in your office while doing it, I'd recommend it.</em></strong></p>"}},{"id":"scheduled-python-jobs-linux-vwindows","url":"https://mykalmachon.com/posts/scheduled-python-jobs-linux-vwindows","meta":{"title":"Scheduling Python jobs","pubDate":"2022-09-13T00:00:00.000Z","type":"post","draft":false,"tags":["devops"],"description":"how to run scheduled python jobs on linux and windows"},"content":{"md":"Linux is typically my operating system of choice and if I ever needed to set up a quick scheduled job, I'd typically just do it via `crontab`. For the first time, I'm working on an app that only supports Window Server as a deploy target and had to sit down to figure out how people in Windows land do their task scheduling. \n\nThis post will walk through how to set up a scheduled job on both Linux and Windows. While this is written specifically for Python jobs (which are just python apps written to run as a task), it could be generalized to any job that can be run via a shell command; just swap out Python for your interpreter or executable of choice.\n\n## Prerequisites\n\nAll you need on your system is git and Python. I won't go into how to download them on your platform of choice but it has [a very googleable solution](https://letmegooglethat.com/?q=how+to+install+python+on+linux).\n\n## Jobs on Linux\n\nIf you're on Linux, there are 6 relatively easy steps to set up your scheduled job:\n\n1. git clone your python job repo into `/opt/my-job/`\n2. open crontab via `crontab -e`\n3. write a quick comment explaining what your job does and who wrote it.\n4. copy a CRON expression from [crontab.guru](https://crontab.guru) that matches the schedule you'd like\n5. write out your crontab expression `0 * * * * python /opt/my-job/app.py`\n6. save the crontab file and you've got a scheduled Job on Linux ✨\n\nI like the simplicity crontab offers, just run _this command_ at _this time_. If you stick with this format your crontab file stays pretty readable as well:\n\n```bash\n# crontab file\n\n# send any pending alerts. - Mykal Machon\n15 * * * * python3 /opt/email-sender/app.py\n\n# audit uploads for violations. - Mykal Machon\n5 * * * * python3 /opt/attachemnt-audit/app.py\n\n# audit backups directory for violations. - Mykal Machon\n5 * * * * python3 /opt/backup-audit/app.py\n\n# create backups of all local databasess; wipes expired backups.\n0 5 * * 1 python3 /opt/backup-systems/app.py\n\n#... many more jobs\n```\n\n## Jobs on Windows\n\nOn Windows, there's a bit more work to do when setting up a scheduled job:\n\n 1. git clone your python job repo into `C:\\Program Files\\my-job\\`\n 2. in your git folder, create a new file called `run-my-job.bat`\n    1. paste the command you would use to run your program into this file and save it.\n 3. open \"task scheduler\" by searching for it in the Windows menu\n 4. create a folder by right-clicking on the \"Task Scheduler Library\" and selecting \"New Folder\"\n 5. right click on your new folder and click \"Create task\", a dialogue should pop up.\n 6. Setup basic task information\n    1. Write up a name and a description for the task including the purpose of the job and who to talk to re: questions\n    2. Select \"Run whether the user is logged on or not\"; this will make sure your job runs as long as the computer is on regardless of if a user is logged in.\n 7. Click the triggers tab and start setting up your trigger\n    1. Click \"new\" and a dialogue should pop up asking for info about your trigger.\n    2. Under settings, choose \"One time\" and set the time to 5 minutes in the future. If you only want your script to run daily/weekly then select that instead.\n    3. Under advanced settings, select \"Repeat task every...\" and select how often you'd like it to run (omit this step if you selected a daily/weekly run above).\n    4. Click \"OK\" at the bottom of the dialogue\n 8. Click the actions tab and start setting up your action (running your script is the action)\n    1. Click \"new\" and a dialogue should pop up asking for info about your action.\n    2. for \"Program/script\", select the `C:\\Program Files\\my-job\\run-my-script.bat` file that we created earlier\n    3. For \"Start in\", select the folder of your git repo: `C:\\Program Files\\my-job\\`. This makes sure relative pathing still works.\n    4. Click \"OK\" at the bottom of the dialogue\n 9. Click \"Ok\" one more time to close the new task dialogue.\n10. From there, you should have a scheduled job on Windows ✨\n\nThis is undoubtedly more complicated than a similar setup on Linux, but the whole process has some upsides.\n\nThe Windows Task Scheduler makes it pretty easy to see all your scheduled tasks, error states, when the task last ran, etc., etc. It's not as clean as the crontab file I showed off in the Linux section, but it gets a lot more information across in a unified interface.\n\n## Final thoughts\n\nJust by looking at the number of steps with Windows vs the number of steps with Linux you can tell that Windows requires more config to get up and running. While Linux's crontab is easier to configure, it lacks a lot of the advanced features that Windows task scheduler has.\n\nWindows lets you trigger tasks when a user logs in when a specific event happens in Windows and a whole bunch more. You can also set conditions that make it so the job only runs when something triggers the script _and_ all conditions are true. With Linux, I would have my python code handle these conditions, but Windows makes it possible to avoid writing boilerplate codes for those edge cases.\n\nAll that said, I'm probably staying with Linux, and crontab moving forward. The simplicity of the solution is what makes it so sticky to use. If you have all your job code in source control, moving jobs to a different server is as simple as copying the /opt/ folder, moving over your secrets, and copy-pasting, the crontab file.","html":"<p>Linux is typically my operating system of choice and if I ever needed to set up a quick scheduled job, I'd typically just do it via <code>crontab</code>. For the first time, I'm working on an app that only supports Window Server as a deploy target and had to sit down to figure out how people in Windows land do their task scheduling.</p><p>This post will walk through how to set up a scheduled job on both Linux and Windows. While this is written specifically for Python jobs (which are just python apps written to run as a task), it could be generalized to any job that can be run via a shell command; just swap out Python for your interpreter or executable of choice.</p><h2>Prerequisites</h2><p>All you need on your system is git and Python. I won't go into how to download them on your platform of choice but it has <a href=\"https://letmegooglethat.com/?q=how+to+install+python+on+linux\">a very googleable solution</a>.</p><h2>Jobs on Linux</h2><p>If you're on Linux, there are 6 relatively easy steps to set up your scheduled job:</p><ol><li>git clone your python job repo into <code>/opt/my-job/</code></li><li>open crontab via <code>crontab -e</code></li><li>write a quick comment explaining what your job does and who wrote it.</li><li>copy a CRON expression from <a href=\"https://crontab.guru\">crontab.guru</a> that matches the schedule you'd like</li><li>write out your crontab expression <code>0 * * * * python /opt/my-job/app.py</code></li><li>save the crontab file and you've got a scheduled Job on Linux ✨</li></ol><p>I like the simplicity crontab offers, just run <em>this command</em> at <em>this time</em>. If you stick with this format your crontab file stays pretty readable as well:</p><pre><code class=\"language-bash\"># crontab file# send any pending alerts. - Mykal Machon15 * * * * python3 /opt/email-sender/app.py# audit uploads for violations. - Mykal Machon5 * * * * python3 /opt/attachemnt-audit/app.py# audit backups directory for violations. - Mykal Machon5 * * * * python3 /opt/backup-audit/app.py# create backups of all local databasess; wipes expired backups.0 5 * * 1 python3 /opt/backup-systems/app.py#... many more jobs</code></pre><h2>Jobs on Windows</h2><p>On Windows, there's a bit more work to do when setting up a scheduled job:</p><ol><li>git clone your python job repo into <code>C:\\Program Files\\my-job\\</code></li><li>in your git folder, create a new file called <code>run-my-job.bat</code><ol><li>paste the command you would use to run your program into this file and save it.</li></ol></li><li>open &quot;task scheduler&quot; by searching for it in the Windows menu</li><li>create a folder by right-clicking on the &quot;Task Scheduler Library&quot; and selecting &quot;New Folder&quot;</li><li>right click on your new folder and click &quot;Create task&quot;, a dialogue should pop up.</li><li>Setup basic task information<ol><li>Write up a name and a description for the task including the purpose of the job and who to talk to re: questions</li><li>Select &quot;Run whether the user is logged on or not&quot;; this will make sure your job runs as long as the computer is on regardless of if a user is logged in.</li></ol></li><li>Click the triggers tab and start setting up your trigger<ol><li>Click &quot;new&quot; and a dialogue should pop up asking for info about your trigger.</li><li>Under settings, choose &quot;One time&quot; and set the time to 5 minutes in the future. If you only want your script to run daily/weekly then select that instead.</li><li>Under advanced settings, select &quot;Repeat task every...&quot; and select how often you'd like it to run (omit this step if you selected a daily/weekly run above).</li><li>Click &quot;OK&quot; at the bottom of the dialogue</li></ol></li><li>Click the actions tab and start setting up your action (running your script is the action)<ol><li>Click &quot;new&quot; and a dialogue should pop up asking for info about your action.</li><li>for &quot;Program/script&quot;, select the <code>C:\\Program Files\\my-job\\run-my-script.bat</code> file that we created earlier</li><li>For &quot;Start in&quot;, select the folder of your git repo: <code>C:\\Program Files\\my-job\\</code>. This makes sure relative pathing still works.</li><li>Click &quot;OK&quot; at the bottom of the dialogue</li></ol></li><li>Click &quot;Ok&quot; one more time to close the new task dialogue.</li><li>From there, you should have a scheduled job on Windows ✨</li></ol><p>This is undoubtedly more complicated than a similar setup on Linux, but the whole process has some upsides.</p><p>The Windows Task Scheduler makes it pretty easy to see all your scheduled tasks, error states, when the task last ran, etc., etc. It's not as clean as the crontab file I showed off in the Linux section, but it gets a lot more information across in a unified interface.</p><h2>Final thoughts</h2><p>Just by looking at the number of steps with Windows vs the number of steps with Linux you can tell that Windows requires more config to get up and running. While Linux's crontab is easier to configure, it lacks a lot of the advanced features that Windows task scheduler has.</p><p>Windows lets you trigger tasks when a user logs in when a specific event happens in Windows and a whole bunch more. You can also set conditions that make it so the job only runs when something triggers the script <em>and</em> all conditions are true. With Linux, I would have my python code handle these conditions, but Windows makes it possible to avoid writing boilerplate codes for those edge cases.</p><p>All that said, I'm probably staying with Linux, and crontab moving forward. The simplicity of the solution is what makes it so sticky to use. If you have all your job code in source control, moving jobs to a different server is as simple as copying the /opt/ folder, moving over your secrets, and copy-pasting, the crontab file.</p>"}},{"id":"on-hydroponic-gardens","url":"https://mykalmachon.com/posts/on-hydroponic-gardens","meta":{"title":"On hydroponic gardens","pubDate":"2022-08-24T00:00:00.000Z","type":"post","draft":false,"tags":["personal"],"description":"I'm going to try growing some herbs and veggies inside with hydroponics. ","heroImage":{"src":"/_astro/on-hydroponic-gardens.DY-xl1M_.avif","width":1200,"height":900,"format":"avif"}},"content":{"md":"Kiya and I were talking about how we miss having access to fresh veggies at home a few days ago. We used to have easy access to my parent's garden when I lived with them and they'd regularly have onions, garlic, tomatoes, dill, basil, etc. that we could snag for cooking. In looking for indoor apartment-friendly alternatives, I fell down a bit of a YouTube/blog rabbit hole on indoor hydroponic gardening systems.\n\nAfter a few hours of internet sleuthing, we decided on the \"[Click and Grow Smart Garden 3](https://ca.clickandgrow.com/products/the-smart-garden-3)\". It's a pretty simple unit. All you need to do is pour in your water, drop these nutrient packed soil and seed pods into some slots, and plug it into the wall. It handles the rest for you, besides occasional tasks like refilling water, extending the lighting arm, and trimming your plants.\n\nThe only issue I have with it so far is that you can't alter the lighting schedule (which is 16 hours on, 8 hours off). They literally add a little warning on the plug that says \"hey! don't plug this in until morning because it's gonna be on for 16 hours!\". Seems a little less \"smart\" than advertised 😉. The light is also _really_ bright; Like I know we're supposed to be growing plants with this thing but wow! This is probably more of an issue with me personally than the product though.\n\nEither way, I'm excited to see how this little indoor garden experiment goes. We have three varieties of Basil loaded in right now so we should have our first \"harvest\" around 3-4 weeks from now. I'll be sure to make another post then about how things turned out.\n\nThis project is also giving me the urge to finally bite the bullet and learn some microcontroller programming with [a Raspberry Pi Pico-W](https://www.pishop.ca/product/raspberry-pi-pico-w/) so I can build out a monitoring tool for the garden (temperature, water level, humidity, etc.).","html":"<p>Kiya and I were talking about how we miss having access to fresh veggies at home a few days ago. We used to have easy access to my parent's garden when I lived with them and they'd regularly have onions, garlic, tomatoes, dill, basil, etc. that we could snag for cooking. In looking for indoor apartment-friendly alternatives, I fell down a bit of a YouTube/blog rabbit hole on indoor hydroponic gardening systems.</p><p>After a few hours of internet sleuthing, we decided on the &quot;<a href=\"https://ca.clickandgrow.com/products/the-smart-garden-3\">Click and Grow Smart Garden 3</a>&quot;. It's a pretty simple unit. All you need to do is pour in your water, drop these nutrient packed soil and seed pods into some slots, and plug it into the wall. It handles the rest for you, besides occasional tasks like refilling water, extending the lighting arm, and trimming your plants.</p><p>The only issue I have with it so far is that you can't alter the lighting schedule (which is 16 hours on, 8 hours off). They literally add a little warning on the plug that says &quot;hey! don't plug this in until morning because it's gonna be on for 16 hours!&quot;. Seems a little less &quot;smart&quot; than advertised 😉. The light is also <em>really</em> bright; Like I know we're supposed to be growing plants with this thing but wow! This is probably more of an issue with me personally than the product though.</p><p>Either way, I'm excited to see how this little indoor garden experiment goes. We have three varieties of Basil loaded in right now so we should have our first &quot;harvest&quot; around 3-4 weeks from now. I'll be sure to make another post then about how things turned out.</p><p>This project is also giving me the urge to finally bite the bullet and learn some microcontroller programming with <a href=\"https://www.pishop.ca/product/raspberry-pi-pico-w/\">a Raspberry Pi Pico-W</a> so I can build out a monitoring tool for the garden (temperature, water level, humidity, etc.).</p>"}},{"id":"the-adventures-of-ash","url":"https://mykalmachon.com/posts/the-adventures-of-ash","meta":{"title":"The adventures of Ash","pubDate":"2022-08-02T00:00:00.000Z","type":"post","draft":false,"tags":["personal"],"description":"My cat, Ash, suddenly passed away this weekend. This post is in his memory.","heroImage":{"src":"/_astro/the-adventures-of-ash.D0cxBuoM.avif","width":1200,"height":900,"format":"avif"}},"content":{"md":"On the morning of July 31st, 2022 our cat, Ash, also lovingly known as Bubba, passed away suddenly and without reason while we were on vacation.\n\nThe night before he passed, our cat-sitter noticed he was a bit off and lethargic. That said, he was still moving around and purring when picked up. He had acted like this in the past due to crystals in his urine but had always bounced back after a day or two. We'd talked to vets about the issue previously and they'd said we had a strong young cat, and that as long as symptoms were only for a day or two he'd be fine. Regardless, we booked a vet appointment for the next morning to get him checked out once we heard that he was acting off.\n\nThe following day, when our sitter arrived to check on our cats, they found him cuddled up in one of his many beds and he wouldn't wake up. He was rushed to the vet, who determined he had passed a few hours before with no apparent cause. He was only 4. Our drive home was filled with a type of pain I really hadn't felt up until then.\n\n![a spotted bengal cat sitting on a large cat tree. ](https://res.cloudinary.com/mykalcodes/image/upload/v1659379735/Mykal%20Codes/BC696529-63DA-467B-938A-74595B4E1E8C_xkyma6.jpg \"Ash sitting on his big tower in the living room\")I'm absolutely devasted. We left home on Friday morning with two happy, energetic, loving cats at home and came home to a house with one. I feel like I've been hit by a truck, my head hurts, I've been crying on and off since finding out, and I _can't_ come to terms with the fact that he's gone. I've never lost a pet so young, or so suddenly, and it _hurts._\n\nA big part of why I wanted to write this was to document some of my favourite memories of him and to hopefully aid the grieving process. **I'm sad, this will probably be rambly, but I wanted to write something up for him; feel free to skip the post if you're more interested in my programming/tech stuff.**\n\n## What's left behind\n\nNow that Ash is gone, I've been noticing him throughout the house in ways I never would have previously:\n\n- **The two food bowls:** one for Ash and one for Ebony, this one is simple but was the first thing I noticed after rushing home.\n- **His favourite food:** Chicken + Fish primal raw nuggets in the fridge still thawing, I'd feed him these every morning and every night. We'll probably have to get rid of them now.\n- **His favourite toys:** \"[Cat Tube Spring Toys](https://www.amazon.ca/Interactive-BPA-Free-Colorful-Plastic-Kittens/dp/B08ZRWWN5Z/ref=d_pd_day0_sccl_1_1/145-1237752-8446369?pd_rd_w=TuEgN&content-id=amzn1.sym.a0f07c06-3bfe-427e-9527-5be8cea27b66&pf_rd_p=a0f07c06-3bfe-427e-9527-5be8cea27b66&pf_rd_r=WG92ERZ4TBXSKGC0YJS7&pd_rd_wg=23z6K&pd_rd_r=7a005984-410e-40ed-9efa-fcfcd20451f9&pd_rd_i=B08ZRWWN5Z&psc=1)\" he used to wake me up with these by dropping them in my hand and nudging me to play fetch. He especially loved the yellow and green ones. He also loved his [feather rod toy](https://www.amazon.com/GoCat-Bird-Feather-Cat-Handmade/dp/B001BOVEU4/ref=sr_1_14?crid=3M008LJB5AX7&keywords=cat+feather+toy&qid=1659380596&sprefix=cat+feather+toy%2Caps%2C145&sr=8-14), and would regularly jump 3-4 feet into the air to catch it. This was our favourite party trick when people came over.\n- **Ebony:** Ash has only been gone around a day, and Kiya and I have noticed Ebony (our other cat) looking around for him and meowing at his regular sleeping spots. This is absolutely heartbreaking as well. Hopefully, this one fades away over time.\n- **Basically every blanket:** Ash was a notorious blanket cuddler, and would always find a way into the blankets when he could. Looking at any blanket in the house brings back memories of him flipping over for pets when you'd approach.\n- **His cat post:** he had a small cat post by our bedroom door that only he used. He would hop on it when I walked by and if I pet him he would go around in circles, purr super loud, and sit down so I could give him a hug. He'd sit there as long as you were petting him, and would sometimes even fall asleep mid-pet.\n\n## Quality time\n\nAsh loved quality time, and would often come and snuggle with us. He would either come into bed and cuddle up beside you (occasionally on top of you) or hop on the couch beside you and happily receive some pets. This was especially true in the morning.\n\nSome of my best nights of sleep in hard times were brought on by him snuggling up and purring beside me and then waking me up the same way. Another favourite of mine was when he would sit behind me in my office chair, cuddle up against my back, and purr himself to sleep. When I'd leave, he'd take up the rest of the seat and sleep there all night if I let him.\n\n![](https://res.cloudinary.com/mykalcodes/image/upload/v1659382111/Mykal%20Codes/PXL_20220322_234406870_ghp110.jpg)\n\nAsh was a very affectionate cat. He helped Kiya and me through some great and not-so-great times. He was always there for comfort and usually offered it up before we went to him for it.\n\nThis morning I kept expecting him to hop up on the bed and snuggle up, but that never came which lead to the painful realization that it won't happen anymore.\n\n## Photos\n\nAsh was well loved **and** well documented. Google photos says that I have around 260 photos of him, not including the uncounted hundreds more in the archive on my NAS + Snapchat memories + photos the google AI missed in tagging + photos on Kiya's phone. I've included a few of my favourite recent pictures in this post.\n\n![a brown spotted bengal sitting in a circular hammock, facing a sliding door.](https://res.cloudinary.com/mykalcodes/image/upload/v1659384366/Mykal%20Codes/ash-in-hammock_egnu25.jpg 'Ash in his \"hammock\". He would sleep here quite often')\n\nWhile these are _really_ hard to look at now, I'm sure in a few years they'll make my heart fly.\n\n## Things not done\n\nAsh was only 4, and easily could have lived until I was 40 given an average Bengal's life span. There were a ton of things that I wish we could have done together, but here are a few I've been struggling with since he passed:\n\n![brown spotted bengal cat in the sun, on a patio chair, with a harness on.](https://res.cloudinary.com/mykalcodes/image/upload/v1659383864/Mykal%20Codes/ash-in-harness_xw1jwi.jpg \"Ash in his harness\")\n\n- **Exploring outside:** Ash always tried to escape outdoors and was extremely curious. He would meet you at the door, just to rub up against your leg and run outside. He was just starting to make it out into our patio on a cat harness. I was looking forward to taking him on walks and seeing his big eyes light up at birds and squirrels.\n- **Meeting our kid/kids:** While a kid isn't in the immediate plan right now, Kiya and I are planning on having kids eventually. Ash is a gentle cat, and I always thought he'd probably be really good with kids. I always pictured our family with him in it.\n- **More cuddles and play time:** One of my favourite things to do when feeling down was to cuddle up, or play fetch with Ash. He had so much fun and was such a good time to watch that it always cheered me up. I'm not ready for that to be done.\n- **Just more time:** 4 years is way too dang short for a cat's life, and Ash was a light in so many ways. There were so many more years he was owed and while he was spoiled and loved deeply while he was here, I wish he would have had more time. I wanted to see how he changed as he aged, how he mellowed out or got more particular.\n\n## Moving forward with Ash\n\nAsh also had a bit of a mischievous streak when he wanted more attention than he was getting. He loved to hop on tables, drink water out of our cups, _try_ to stand on the TV, and just generally be places he shouldn't be. This was annoying at the time, but I can only see it as endearing looking back. I wish I could pick him up off the table, hold him like a baby, playfully scold him, and then play fetch with him again.\n\nFor this reason, Kiya and I had a bit of an inside joke that we should write a children's book called \"The adventures of Ash\" that followed his mischievous journey through life and had some sort of underlying message about unconditional love despite little screw ups. Now that he's gone, I think we might try and find a way to make that a possibility even if it's just for us. At the very least we're going to put together a photo book with all the great memories we have of him growing up. We've talked about getting a tattoo of his paw print in his memory as well.\n\nBesides the physical stuff we're doing to remember him, Ash taught me so much about how fulfilling life can be. I know people tend to think of pets as \"just pets\", but he was a massive part of my heart and our family.\n\nOn the last night before leaving for vacation, he came and cuddled up, purred, and slept with me solid through the night. You can never tell what cats know but I can't help but wonder if he knew we were leaving, and that he didn't have much time left.\n\nHe will always be Kiya and I's Bubba and best friend. If there's something after, I hope he's out there off chasing squirrels and eating butter.\n\nLove you, Bubba 💖\n\n![a brown spotted bengal laying in bed, facing away from the camera, being pet](https://res.cloudinary.com/mykalcodes/image/upload/v1659384730/Mykal%20Codes/ashes-last-photo_y38uus.jpg \"The last photo I took with Ash, he came and cuddled with me the night before we left\")","html":"<p>On the morning of July 31st, 2022 our cat, Ash, also lovingly known as Bubba, passed away suddenly and without reason while we were on vacation.</p><p>The night before he passed, our cat-sitter noticed he was a bit off and lethargic. That said, he was still moving around and purring when picked up. He had acted like this in the past due to crystals in his urine but had always bounced back after a day or two. We'd talked to vets about the issue previously and they'd said we had a strong young cat, and that as long as symptoms were only for a day or two he'd be fine. Regardless, we booked a vet appointment for the next morning to get him checked out once we heard that he was acting off.</p><p>The following day, when our sitter arrived to check on our cats, they found him cuddled up in one of his many beds and he wouldn't wake up. He was rushed to the vet, who determined he had passed a few hours before with no apparent cause. He was only 4. Our drive home was filled with a type of pain I really hadn't felt up until then.</p><p><img src=\"https://res.cloudinary.com/mykalcodes/image/upload/v1659379735/Mykal%20Codes/BC696529-63DA-467B-938A-74595B4E1E8C_xkyma6.jpg\" alt=\"a spotted bengal cat sitting on a large cat tree. \" title=\"Ash sitting on his big tower in the living room\">I'm absolutely devasted. We left home on Friday morning with two happy, energetic, loving cats at home and came home to a house with one. I feel like I've been hit by a truck, my head hurts, I've been crying on and off since finding out, and I <em>can't</em> come to terms with the fact that he's gone. I've never lost a pet so young, or so suddenly, and it <em>hurts.</em></p><p>A big part of why I wanted to write this was to document some of my favourite memories of him and to hopefully aid the grieving process. <strong>I'm sad, this will probably be rambly, but I wanted to write something up for him; feel free to skip the post if you're more interested in my programming/tech stuff.</strong></p><h2>What's left behind</h2><p>Now that Ash is gone, I've been noticing him throughout the house in ways I never would have previously:</p><ul><li><strong>The two food bowls:</strong> one for Ash and one for Ebony, this one is simple but was the first thing I noticed after rushing home.</li><li><strong>His favourite food:</strong> Chicken + Fish primal raw nuggets in the fridge still thawing, I'd feed him these every morning and every night. We'll probably have to get rid of them now.</li><li><strong>His favourite toys:</strong> &quot;<a href=\"https://www.amazon.ca/Interactive-BPA-Free-Colorful-Plastic-Kittens/dp/B08ZRWWN5Z/ref=d_pd_day0_sccl_1_1/145-1237752-8446369?pd_rd_w=TuEgN&amp;content-id=amzn1.sym.a0f07c06-3bfe-427e-9527-5be8cea27b66&amp;pf_rd_p=a0f07c06-3bfe-427e-9527-5be8cea27b66&amp;pf_rd_r=WG92ERZ4TBXSKGC0YJS7&amp;pd_rd_wg=23z6K&amp;pd_rd_r=7a005984-410e-40ed-9efa-fcfcd20451f9&amp;pd_rd_i=B08ZRWWN5Z&amp;psc=1\">Cat Tube Spring Toys</a>&quot; he used to wake me up with these by dropping them in my hand and nudging me to play fetch. He especially loved the yellow and green ones. He also loved his <a href=\"https://www.amazon.com/GoCat-Bird-Feather-Cat-Handmade/dp/B001BOVEU4/ref=sr_1_14?crid=3M008LJB5AX7&amp;keywords=cat+feather+toy&amp;qid=1659380596&amp;sprefix=cat+feather+toy%2Caps%2C145&amp;sr=8-14\">feather rod toy</a>, and would regularly jump 3-4 feet into the air to catch it. This was our favourite party trick when people came over.</li><li><strong>Ebony:</strong> Ash has only been gone around a day, and Kiya and I have noticed Ebony (our other cat) looking around for him and meowing at his regular sleeping spots. This is absolutely heartbreaking as well. Hopefully, this one fades away over time.</li><li><strong>Basically every blanket:</strong> Ash was a notorious blanket cuddler, and would always find a way into the blankets when he could. Looking at any blanket in the house brings back memories of him flipping over for pets when you'd approach.</li><li><strong>His cat post:</strong> he had a small cat post by our bedroom door that only he used. He would hop on it when I walked by and if I pet him he would go around in circles, purr super loud, and sit down so I could give him a hug. He'd sit there as long as you were petting him, and would sometimes even fall asleep mid-pet.</li></ul><h2>Quality time</h2><p>Ash loved quality time, and would often come and snuggle with us. He would either come into bed and cuddle up beside you (occasionally on top of you) or hop on the couch beside you and happily receive some pets. This was especially true in the morning.</p><p>Some of my best nights of sleep in hard times were brought on by him snuggling up and purring beside me and then waking me up the same way. Another favourite of mine was when he would sit behind me in my office chair, cuddle up against my back, and purr himself to sleep. When I'd leave, he'd take up the rest of the seat and sleep there all night if I let him.</p><p><img src=\"https://res.cloudinary.com/mykalcodes/image/upload/v1659382111/Mykal%20Codes/PXL_20220322_234406870_ghp110.jpg\" alt=\"\"></p><p>Ash was a very affectionate cat. He helped Kiya and me through some great and not-so-great times. He was always there for comfort and usually offered it up before we went to him for it.</p><p>This morning I kept expecting him to hop up on the bed and snuggle up, but that never came which lead to the painful realization that it won't happen anymore.</p><h2>Photos</h2><p>Ash was well loved <strong>and</strong> well documented. Google photos says that I have around 260 photos of him, not including the uncounted hundreds more in the archive on my NAS + Snapchat memories + photos the google AI missed in tagging + photos on Kiya's phone. I've included a few of my favourite recent pictures in this post.</p><p><img src=\"https://res.cloudinary.com/mykalcodes/image/upload/v1659384366/Mykal%20Codes/ash-in-hammock_egnu25.jpg\" alt=\"a brown spotted bengal sitting in a circular hammock, facing a sliding door.\" title=\"Ash in his &quot;hammock&quot;. He would sleep here quite often\"></p><p>While these are <em>really</em> hard to look at now, I'm sure in a few years they'll make my heart fly.</p><h2>Things not done</h2><p>Ash was only 4, and easily could have lived until I was 40 given an average Bengal's life span. There were a ton of things that I wish we could have done together, but here are a few I've been struggling with since he passed:</p><p><img src=\"https://res.cloudinary.com/mykalcodes/image/upload/v1659383864/Mykal%20Codes/ash-in-harness_xw1jwi.jpg\" alt=\"brown spotted bengal cat in the sun, on a patio chair, with a harness on.\" title=\"Ash in his harness\"></p><ul><li><strong>Exploring outside:</strong> Ash always tried to escape outdoors and was extremely curious. He would meet you at the door, just to rub up against your leg and run outside. He was just starting to make it out into our patio on a cat harness. I was looking forward to taking him on walks and seeing his big eyes light up at birds and squirrels.</li><li><strong>Meeting our kid/kids:</strong> While a kid isn't in the immediate plan right now, Kiya and I are planning on having kids eventually. Ash is a gentle cat, and I always thought he'd probably be really good with kids. I always pictured our family with him in it.</li><li><strong>More cuddles and play time:</strong> One of my favourite things to do when feeling down was to cuddle up, or play fetch with Ash. He had so much fun and was such a good time to watch that it always cheered me up. I'm not ready for that to be done.</li><li><strong>Just more time:</strong> 4 years is way too dang short for a cat's life, and Ash was a light in so many ways. There were so many more years he was owed and while he was spoiled and loved deeply while he was here, I wish he would have had more time. I wanted to see how he changed as he aged, how he mellowed out or got more particular.</li></ul><h2>Moving forward with Ash</h2><p>Ash also had a bit of a mischievous streak when he wanted more attention than he was getting. He loved to hop on tables, drink water out of our cups, <em>try</em> to stand on the TV, and just generally be places he shouldn't be. This was annoying at the time, but I can only see it as endearing looking back. I wish I could pick him up off the table, hold him like a baby, playfully scold him, and then play fetch with him again.</p><p>For this reason, Kiya and I had a bit of an inside joke that we should write a children's book called &quot;The adventures of Ash&quot; that followed his mischievous journey through life and had some sort of underlying message about unconditional love despite little screw ups. Now that he's gone, I think we might try and find a way to make that a possibility even if it's just for us. At the very least we're going to put together a photo book with all the great memories we have of him growing up. We've talked about getting a tattoo of his paw print in his memory as well.</p><p>Besides the physical stuff we're doing to remember him, Ash taught me so much about how fulfilling life can be. I know people tend to think of pets as &quot;just pets&quot;, but he was a massive part of my heart and our family.</p><p>On the last night before leaving for vacation, he came and cuddled up, purred, and slept with me solid through the night. You can never tell what cats know but I can't help but wonder if he knew we were leaving, and that he didn't have much time left.</p><p>He will always be Kiya and I's Bubba and best friend. If there's something after, I hope he's out there off chasing squirrels and eating butter.</p><p>Love you, Bubba 💖</p><p><img src=\"https://res.cloudinary.com/mykalcodes/image/upload/v1659384730/Mykal%20Codes/ashes-last-photo_y38uus.jpg\" alt=\"a brown spotted bengal laying in bed, facing away from the camera, being pet\" title=\"The last photo I took with Ash, he came and cuddled with me the night before we left\"></p>"}},{"id":"remembering-ash","url":"https://mykalmachon.com/posts/remembering-ash","meta":{"title":"Remembering Ash","pubDate":"2022-08-01T00:00:00.000Z","type":"photo","draft":false,"tags":["personal"],"location":"Home","description":""},"content":{"md":"![A brown and black spotted bengal cat sitting in a cat tree. He stares up at the camera.](../../assets/posts/ash-in-hammock.jpg)\n\n![A brown and black spotted bengal cat sitting on top of a pink fuzzy blanket. He is looking out to the left sleepily.](../../assets/posts/ash-on-blanket.jpg)\n\nSome photos of my cat, Ash. I love and miss him.","html":"<p><img src=\"../../assets/posts/ash-in-hammock.jpg\" alt=\"A brown and black spotted bengal cat sitting in a cat tree. He stares up at the camera.\"></p><p><img src=\"../../assets/posts/ash-on-blanket.jpg\" alt=\"A brown and black spotted bengal cat sitting on top of a pink fuzzy blanket. He is looking out to the left sleepily.\"></p><p>Some photos of my cat, Ash. I love and miss him.</p>"}},{"id":"telegraf-and-influx-for-server-monitoring","url":"https://mykalmachon.com/posts/telegraf-and-influx-for-server-monitoring","meta":{"title":"Telegraf and InfluxDB for server monitoring","pubDate":"2022-07-21T00:00:00.000Z","type":"post","draft":false,"tags":["devops"],"description":"A quick guide on system monitoring with Telegraf, and Influx DB.","heroImage":{"src":"/_astro/telegraf-and-influx-for-server-monitoring.BLyWNrvN.avif","width":1200,"height":798,"format":"avif"}},"content":{"md":"I've been looking into how to be more pro-active about monitoring [my HomeLab](http://mykal.codes/uses#-servers--hardware) lately. With that in mind, I went on a bit of a monitoring dive and setup some basic tools on my raspberry pi's. I wanted to keep track of what I did, so why not write it up in a blog post.\n\nMore specifically, this post will give you a quick overview of how I got up and running with InfluxDB and Telegraf for monitoring my servers.\n\n## Why InfluxDB and Telegraf?\n\nI've already used [Grafana](https://grafana.com/) (a dashboarding tool) at work and like how it works; I also know that [Grafana is commonly used for system monitoring](https://grafana.com/grafana/) so I started my search there. After some Googling, I kept seeing a few other tools used alongside Grafana:\n\n- [InfluxDB](https://www.influxdata.com/): a database for efficiently storing and accessing time series data (perfect for sensors, system data, etc.). Grafana has a first party integration with InfluxDB as well.\n- [Telegraf](https://www.influxdata.com/time-series-platform/telegraf/): a server agent that allows you to collect sensor info from your servers and send it back to InfluxDB and other time-series databases like [Prometheus.](https://prometheus.io/)\n\nSweet! I have a few tools to start experimenting with.\n\n## Installing InfluxDB\n\nThe logical first step was to install InfluxDB somewhere so my servers could write back to it. InfluxDB has a docker image that you can use and since I have an Unraid server that's already running around 20 docker images, I threw it on there.\n\nI was pleasantly surprised by how easy setting up InfluxDB was!\n\n1. Followed the setup guide in the web interface and setup an admin user (should be at http://your-servers-ip:8016/).\n2. Created a buckets for each pi to write to (basically a database).\n3. Created a telegraf config for each pi and note the API key, and configuration files (telegraf.conf) InfluxDB provided.\n\nFrom there, InfluxDB seemed to suggest I could setup Telegraf on my pis.\n\n## Installing Telegraf\n\nNow that I had some configurations, I went and did some reading on [how to configure telegraf](https://docs.influxdata.com/influxdb/cloud/write-data/no-code/use-telegraf/manual-config/) on the docs site. I logged in to both my pi's, and used the commands below:\n\n```bash\n# install telegraf\nsudo apt install telegraf -y\n\n# paste in the INFLUXDB_KEY provided by InfluxDB\n# at the bottom of the file.\n# this will automatically define the variable for\n# the user you're logged in as on login.\nvim ~/.bashrc\n\n# paste in the telegraf.conf file provided by InfluxDB\nvim /etc/telegraf/telegraf.conf\n\n# start the telegraf service, and enable it\nsudo systemctl start telegraf\nsudo systemctl enable telegraf\n\n# make sure that the telegraf service has connected to\n# InfluxDB successfully.\nsudo systemctl status telegraf\n\n# the output of the above command should\n# NOT include any 401 errors. if it does,\n# you may need to paste the API key into\n# your telegraf.conf file manually\n```\n\nOnce I got that done I knew that telegraf _should_ be writing back to my InfluxDB database every 10 seconds.\n\n## Seeing server data in InfluxDB\n\nTo confirm that InfluxDB was actually getting that data, I logged back into the InfluxDB web app and clicked the \"dashboards\" link on the sidebar.\nThankfully, InfluxDB comes with a default \"Systems\" dashboard that you can use for reviewing your data, I hopped in there and saw that data was getting into InfluxDB, sweet 🎉\n\n![photo of the dashboard](https://res.cloudinary.com/mykalcodes/image/upload/v1658693116/Mykal%20Codes/making-system-monitoring-easy-2.png)\n\n## Next steps\n\nThere's a whole lot more I can do to expand the monitoring capabilities, here are some ideas:\n\n1. **📊 Grafana dashboards:** I brought Grafana up at the beginning of the post, but I haven't actually implemented it yet. Their dashboards are awesome and it would be a good next step.\n2. **⏰ Usage alerting:** InfluxDB (and Grafana actually) lets you create alerts on specific criteria. For example, if your server goes over 80% usage, or goes down, you can get a slack message.\n3. **📚 Keep learning:** There's a whole job around monitoring and reliability of services, so logically there's a whole lot you can learn in the field.\n\nThanks for reading!","html":"<p>I've been looking into how to be more pro-active about monitoring <a href=\"http://mykal.codes/uses#-servers--hardware\">my HomeLab</a> lately. With that in mind, I went on a bit of a monitoring dive and setup some basic tools on my raspberry pi's. I wanted to keep track of what I did, so why not write it up in a blog post.</p><p>More specifically, this post will give you a quick overview of how I got up and running with InfluxDB and Telegraf for monitoring my servers.</p><h2>Why InfluxDB and Telegraf?</h2><p>I've already used <a href=\"https://grafana.com/\">Grafana</a> (a dashboarding tool) at work and like how it works; I also know that <a href=\"https://grafana.com/grafana/\">Grafana is commonly used for system monitoring</a> so I started my search there. After some Googling, I kept seeing a few other tools used alongside Grafana:</p><ul><li><a href=\"https://www.influxdata.com/\">InfluxDB</a>: a database for efficiently storing and accessing time series data (perfect for sensors, system data, etc.). Grafana has a first party integration with InfluxDB as well.</li><li><a href=\"https://www.influxdata.com/time-series-platform/telegraf/\">Telegraf</a>: a server agent that allows you to collect sensor info from your servers and send it back to InfluxDB and other time-series databases like <a href=\"https://prometheus.io/\">Prometheus.</a></li></ul><p>Sweet! I have a few tools to start experimenting with.</p><h2>Installing InfluxDB</h2><p>The logical first step was to install InfluxDB somewhere so my servers could write back to it. InfluxDB has a docker image that you can use and since I have an Unraid server that's already running around 20 docker images, I threw it on there.</p><p>I was pleasantly surprised by how easy setting up InfluxDB was!</p><ol><li>Followed the setup guide in the web interface and setup an admin user (should be at http://your-servers-ip:8016/).</li><li>Created a buckets for each pi to write to (basically a database).</li><li>Created a telegraf config for each pi and note the API key, and configuration files (telegraf.conf) InfluxDB provided.</li></ol><p>From there, InfluxDB seemed to suggest I could setup Telegraf on my pis.</p><h2>Installing Telegraf</h2><p>Now that I had some configurations, I went and did some reading on <a href=\"https://docs.influxdata.com/influxdb/cloud/write-data/no-code/use-telegraf/manual-config/\">how to configure telegraf</a> on the docs site. I logged in to both my pi's, and used the commands below:</p><pre><code class=\"language-bash\"># install telegrafsudo apt install telegraf -y# paste in the INFLUXDB_KEY provided by InfluxDB# at the bottom of the file.# this will automatically define the variable for# the user you're logged in as on login.vim ~/.bashrc# paste in the telegraf.conf file provided by InfluxDBvim /etc/telegraf/telegraf.conf# start the telegraf service, and enable itsudo systemctl start telegrafsudo systemctl enable telegraf# make sure that the telegraf service has connected to# InfluxDB successfully.sudo systemctl status telegraf# the output of the above command should# NOT include any 401 errors. if it does,# you may need to paste the API key into# your telegraf.conf file manually</code></pre><p>Once I got that done I knew that telegraf <em>should</em> be writing back to my InfluxDB database every 10 seconds.</p><h2>Seeing server data in InfluxDB</h2><p>To confirm that InfluxDB was actually getting that data, I logged back into the InfluxDB web app and clicked the &quot;dashboards&quot; link on the sidebar.Thankfully, InfluxDB comes with a default &quot;Systems&quot; dashboard that you can use for reviewing your data, I hopped in there and saw that data was getting into InfluxDB, sweet 🎉</p><p><img src=\"https://res.cloudinary.com/mykalcodes/image/upload/v1658693116/Mykal%20Codes/making-system-monitoring-easy-2.png\" alt=\"photo of the dashboard\"></p><h2>Next steps</h2><p>There's a whole lot more I can do to expand the monitoring capabilities, here are some ideas:</p><ol><li><strong>📊 Grafana dashboards:</strong> I brought Grafana up at the beginning of the post, but I haven't actually implemented it yet. Their dashboards are awesome and it would be a good next step.</li><li><strong>⏰ Usage alerting:</strong> InfluxDB (and Grafana actually) lets you create alerts on specific criteria. For example, if your server goes over 80% usage, or goes down, you can get a slack message.</li><li><strong>📚 Keep learning:</strong> There's a whole job around monitoring and reliability of services, so logically there's a whole lot you can learn in the field.</li></ol><p>Thanks for reading!</p>"}},{"id":"shopify-as-json","url":"https://mykalmachon.com/posts/shopify-as-json","meta":{"title":"Get any Shopify product as JSON","pubDate":"2022-07-19T00:00:00.000Z","type":"post","draft":false,"tags":["webdev"],"description":"If you ever need to pull Shopify product info, this tip will be a huge help.","heroImage":{"src":"/_astro/shopify-as-json.urF06GEL.avif","width":1200,"height":800,"format":"avif"}},"content":{"md":"I found out that you could get any Shopify product as JSON/XML way too late in my Shopify development game. If there is a public URL for the Shopify product, and the store is using the standard Shopify theme engine, you can grab all the public details as JSON at any time with a quick URL trick.\n\nAll you have to do is add `.json` to the end of the product URL, and it will return the JSON file representation of the object; for XML just add the `.xml` to the end.\n\n## Getting product info in Javascript\n\nHere's a quick demo of how you'd grab a product's info as JSON in Javascript. All you're going to need is the URL where you can find the product, and some way of running javascript (browser, or node.js with `node-fetch`).\n\nLet's use a product from the Allbirds store:\n\n```js\n// product url\nconst productUrl =\n  \"https://www.allbirds.com/products/mens-tree-flyers-blizzard\";\n\n// function to grab the data and return a promise\nconst getProductData = async (productUrl) => {\n  const productUrlJSON = `${productUrl}.json`;\n  const response = await fetch(productUrlJSON);\n  const data = await response.json();\n  return data;\n};\n\n// get the data and then log it, or do whatever!\ngetProductData(productUrl).then((data) => {\n  console.log(data);\n});\n```\n\nThis should log something like this to your console:\n\n```json\n{\n    {\n    \"product\": {\n        \"id\": 6673825890384,\n        \"title\": \"Men's Tree Flyers - Blizzard (Blizzard Sole)\",\n        \"body_html\": \"<p>The Allbirds Tree Flyers for men are a lightweight, springy distance running shoe—including our new SwiftFoam™ midsole, made of natural and recycled materials. </p>\",\n        \"vendor\": \"Allbirds\",\n        \"product_type\": \"Shoes\",\n        \"created_at\": \"2022-04-20T14:39:25-07:00\",\n        \"handle\": \"mens-tree-flyers-blizzard\",\n        \"updated_at\": \"2022-07-19T12:20:43-07:00\",\n        \"published_at\": \"2022-05-15T18:56:08-07:00\",\n        \"template_suffix\": \"mens-tree-flyers\",\n        \"published_scope\": \"global\",\n        \"tags\": \"allbirds::cfId => color-mens-tree-flyers-blizzard-blizzard, allbirds::complete => true, allbirds::edition => limited, allbirds::gender => mens, allbirds::hue => white, allbirds::master => mens-tree-flyers, allbirds::material => tree, allbirds::silhouette => dasher, loop::returnable => true, shoprunner, YCRF_mens-perform-shoes, YGroup_ygroup_mens-tree-flyers\",\n        \"variants\": [],\n        \"options\": [],\n        \"images\": [],\n        \"image\": {\n            \"id\": 30031923576912,\n            \"product_id\": 6673825890384,\n            \"position\": 1,\n            \"created_at\": \"2022-06-02T15:17:26-07:00\",\n            \"updated_at\": \"2022-06-02T15:17:26-07:00\",\n            \"alt\": null,\n            \"width\": 1600,\n            \"height\": 1600,\n            \"src\": \"https://cdn.shopify.com/s/files/1/1104/4168/products/AA001NM_SHOE_ANGLE_GLOBAL_MENS_TREE_FLYER_BLIZZARD_BLIZZARD_91a3e1d2-f0d0-4649-a8e8-0d556a1a4e51.png?v=1654208246\",\n            \"variant_ids\": []\n        }\n    }\n}\n}\n```\n\nFrom here, you can do whatever you need with that data, including:\n\n- custom CTAs/cards for the product.\n- custom landing pages for the product on marketing sites.\n- simple external product inventories via the sitemap.\n- lots of other things...\n\nSpeaking of, if you've used this API for other weird things, feel free to comment down below with examples. I would love to see what elese people are using these for.","html":"<p>I found out that you could get any Shopify product as JSON/XML way too late in my Shopify development game. If there is a public URL for the Shopify product, and the store is using the standard Shopify theme engine, you can grab all the public details as JSON at any time with a quick URL trick.</p><p>All you have to do is add <code>.json</code> to the end of the product URL, and it will return the JSON file representation of the object; for XML just add the <code>.xml</code> to the end.</p><h2>Getting product info in Javascript</h2><p>Here's a quick demo of how you'd grab a product's info as JSON in Javascript. All you're going to need is the URL where you can find the product, and some way of running javascript (browser, or node.js with <code>node-fetch</code>).</p><p>Let's use a product from the Allbirds store:</p><pre><code class=\"language-js\">// product urlconst productUrl =  &quot;https://www.allbirds.com/products/mens-tree-flyers-blizzard&quot;;// function to grab the data and return a promiseconst getProductData = async (productUrl) =&gt; {  const productUrlJSON = `${productUrl}.json`;  const response = await fetch(productUrlJSON);  const data = await response.json();  return data;};// get the data and then log it, or do whatever!getProductData(productUrl).then((data) =&gt; {  console.log(data);});</code></pre><p>This should log something like this to your console:</p><pre><code class=\"language-json\">{    {    &quot;product&quot;: {        &quot;id&quot;: 6673825890384,        &quot;title&quot;: &quot;Men's Tree Flyers - Blizzard (Blizzard Sole)&quot;,        &quot;body_html&quot;: &quot;&lt;p&gt;The Allbirds Tree Flyers for men are a lightweight, springy distance running shoe—including our new SwiftFoam™ midsole, made of natural and recycled materials. &lt;/p&gt;&quot;,        &quot;vendor&quot;: &quot;Allbirds&quot;,        &quot;product_type&quot;: &quot;Shoes&quot;,        &quot;created_at&quot;: &quot;2022-04-20T14:39:25-07:00&quot;,        &quot;handle&quot;: &quot;mens-tree-flyers-blizzard&quot;,        &quot;updated_at&quot;: &quot;2022-07-19T12:20:43-07:00&quot;,        &quot;published_at&quot;: &quot;2022-05-15T18:56:08-07:00&quot;,        &quot;template_suffix&quot;: &quot;mens-tree-flyers&quot;,        &quot;published_scope&quot;: &quot;global&quot;,        &quot;tags&quot;: &quot;allbirds::cfId =&gt; color-mens-tree-flyers-blizzard-blizzard, allbirds::complete =&gt; true, allbirds::edition =&gt; limited, allbirds::gender =&gt; mens, allbirds::hue =&gt; white, allbirds::master =&gt; mens-tree-flyers, allbirds::material =&gt; tree, allbirds::silhouette =&gt; dasher, loop::returnable =&gt; true, shoprunner, YCRF_mens-perform-shoes, YGroup_ygroup_mens-tree-flyers&quot;,        &quot;variants&quot;: [],        &quot;options&quot;: [],        &quot;images&quot;: [],        &quot;image&quot;: {            &quot;id&quot;: 30031923576912,            &quot;product_id&quot;: 6673825890384,            &quot;position&quot;: 1,            &quot;created_at&quot;: &quot;2022-06-02T15:17:26-07:00&quot;,            &quot;updated_at&quot;: &quot;2022-06-02T15:17:26-07:00&quot;,            &quot;alt&quot;: null,            &quot;width&quot;: 1600,            &quot;height&quot;: 1600,            &quot;src&quot;: &quot;https://cdn.shopify.com/s/files/1/1104/4168/products/AA001NM_SHOE_ANGLE_GLOBAL_MENS_TREE_FLYER_BLIZZARD_BLIZZARD_91a3e1d2-f0d0-4649-a8e8-0d556a1a4e51.png?v=1654208246&quot;,            &quot;variant_ids&quot;: []        }    }}}</code></pre><p>From here, you can do whatever you need with that data, including:</p><ul><li>custom CTAs/cards for the product.</li><li>custom landing pages for the product on marketing sites.</li><li>simple external product inventories via the sitemap.</li><li>lots of other things...</li></ul><p>Speaking of, if you've used this API for other weird things, feel free to comment down below with examples. I would love to see what elese people are using these for.</p>"}},{"id":"aeropress-latte","url":"https://mykalmachon.com/posts/aeropress-latte","meta":{"title":"My AeroPress Latte","pubDate":"2022-07-15T00:00:00.000Z","type":"post","draft":false,"tags":["food"],"description":"An AeroPress Latte recipe for the AeroPress GO!","heroImage":{"src":"/_astro/aeropress-latte.BzB7nvVt.avif","width":1200,"height":800,"format":"avif"}},"content":{"md":"A week or so ago I was scrolling around on twitter and came across [a tweet mentioning an \"AeroPress Latte\"](https://twitter.com/TheCodePixi/status/1545476517202894851). I _love_ lattes, but only really have the equipment for pour over or immersion brew coffee at home... or so I thought!\n\nAfter 20-30 minutes of Googling and watching Youtube videos on AeroPress lattes, I was overjoyed to find that they are indeed a thing. You can't actually make espresso with the AeroPress, but you can make a _pseudo espresso_ (concentrated coffee) by using more ground coffee, less water, and a shorter brew time. The rest is just steamed milk + any other goodies you like in your latte.\n\nI've spent the week since this monumental discovery experimenting and I think I've come up with a half-decent recipe.\n\n## Tools and ingredients\n\nBefore we get into the recipe, here's what you'll need:\n\n1. AeroPress: I use the AeroPress GO! but any AeroPress should do.\n2. scale with timer + tare functions: this is for weighing ingredients, and keeping track of your brew-time.\n3. kettle: for hot water. I use filtered water heated to ~95c.\n4. milk frother: preferably a steamer, but all I have is a frother 😅\n5. medium-fine ground coffee: I use some local beans ground between a pour-over medium and espresso fine.\n6. 2% milk or a milk alternative: the milk for your latte.\n\nfor more info on what I use, check out [my coffee setup post](https://mykal.codes/posts/coffee-setup)\n\n## Recipe\n\nThe most involved part of the recipe is the \"espresso\" everything else is pretty easy!\n\n### The \"espresso\"\n\n1. put your paper filter into your AeroPress.\n2. place your AeroPress on a cup, place that cup on a scale, and tare the scale.\n3. pour 14.5g of ground coffee into your AeroPress, even the bed and tare the scale again.\n4. Start the timer on your scale.\n5. pour ~140g of water into your AeroPress and stir immediately until theres no dry coffee.\n6. remove your AeroPress from the scale.\n7. cover the AeroPress with your plunger creating a seal and wait ~20 seconds\n8. push down on the plunger for ~15 seconds or until the AeroPress is finished.\n9. remove the AeroPress from the cup and quickly clean up.\n\n### The milk\n\n1. put your milk into the frother (use as much as you'd like)\n2. froth your milk until it has an even layer of foam on top\n3. pour your \"steamed\" milk into the same cup as your \"espresso\"\n\n### The extras\n\nI like to add 1 small pump of vanilla, or cinnamon to the top of my lattes depending on the day. That said, they're still delicious with nothing but milk + coffee.\n\n## Enjoy your latte!\n\n**If you try the recipe out, let me know what you think!** It's definitely not a traditional coffee-shop-grade latte, but it's a lot better than anything latte adjacent I've made at home previously.","html":"<p>A week or so ago I was scrolling around on twitter and came across <a href=\"https://twitter.com/TheCodePixi/status/1545476517202894851\">a tweet mentioning an &quot;AeroPress Latte&quot;</a>. I <em>love</em> lattes, but only really have the equipment for pour over or immersion brew coffee at home... or so I thought!</p><p>After 20-30 minutes of Googling and watching Youtube videos on AeroPress lattes, I was overjoyed to find that they are indeed a thing. You can't actually make espresso with the AeroPress, but you can make a <em>pseudo espresso</em> (concentrated coffee) by using more ground coffee, less water, and a shorter brew time. The rest is just steamed milk + any other goodies you like in your latte.</p><p>I've spent the week since this monumental discovery experimenting and I think I've come up with a half-decent recipe.</p><h2>Tools and ingredients</h2><p>Before we get into the recipe, here's what you'll need:</p><ol><li>AeroPress: I use the AeroPress GO! but any AeroPress should do.</li><li>scale with timer + tare functions: this is for weighing ingredients, and keeping track of your brew-time.</li><li>kettle: for hot water. I use filtered water heated to ~95c.</li><li>milk frother: preferably a steamer, but all I have is a frother 😅</li><li>medium-fine ground coffee: I use some local beans ground between a pour-over medium and espresso fine.</li><li>2% milk or a milk alternative: the milk for your latte.</li></ol><p>for more info on what I use, check out <a href=\"https://mykal.codes/posts/coffee-setup\">my coffee setup post</a></p><h2>Recipe</h2><p>The most involved part of the recipe is the &quot;espresso&quot; everything else is pretty easy!</p><h3>The &quot;espresso&quot;</h3><ol><li>put your paper filter into your AeroPress.</li><li>place your AeroPress on a cup, place that cup on a scale, and tare the scale.</li><li>pour 14.5g of ground coffee into your AeroPress, even the bed and tare the scale again.</li><li>Start the timer on your scale.</li><li>pour ~140g of water into your AeroPress and stir immediately until theres no dry coffee.</li><li>remove your AeroPress from the scale.</li><li>cover the AeroPress with your plunger creating a seal and wait ~20 seconds</li><li>push down on the plunger for ~15 seconds or until the AeroPress is finished.</li><li>remove the AeroPress from the cup and quickly clean up.</li></ol><h3>The milk</h3><ol><li>put your milk into the frother (use as much as you'd like)</li><li>froth your milk until it has an even layer of foam on top</li><li>pour your &quot;steamed&quot; milk into the same cup as your &quot;espresso&quot;</li></ol><h3>The extras</h3><p>I like to add 1 small pump of vanilla, or cinnamon to the top of my lattes depending on the day. That said, they're still delicious with nothing but milk + coffee.</p><h2>Enjoy your latte!</h2><p><strong>If you try the recipe out, let me know what you think!</strong> It's definitely not a traditional coffee-shop-grade latte, but it's a lot better than anything latte adjacent I've made at home previously.</p>"}},{"id":"why-i-love-self-hosting","url":"https://mykalmachon.com/posts/why-i-love-self-hosting","meta":{"title":"Why I Love Self-Hosting","pubDate":"2022-07-04T00:00:00.000Z","type":"post","draft":false,"tags":["devops"],"description":"A writeup on why I love self-hosting complete with drawbacks.","heroImage":{"src":"/_astro/why-i-love-self-hosting.x8642ddL.avif","width":1024,"height":1024,"format":"avif"}},"content":{"md":"If you're unfamiliar with the term, I would define self-hosting as the practice of hosting your own server and services either in your home or place of business. People do this for an array of reasons (which we'll get into in this post). It's become increasingly popular over the last few years for enthusiasts, developers, and nerds, in general, to set up “homelabs” in their basements, under their desks and in their TV cabinets.\n\nI started self-hosting around 2 years ago after I got a touch of some FOMO from friends with servers set up at home. To be honest I expected to be “over it” a few months after I started. To my surprise, I'm still self-hosting now, and having more fun with it than ever. It's been a fun way for me to learn in a safe/forgiving environment, experiment with new tech, and save some money on online services like Google Drive.\n\nThis post is going to outline a few of the reasons I love self-hosting, and why you might love it too.\n\n## Reuse of otherwise unused hardware\n\nMy self-hosted setup is mostly secondhand hardware. The only thing I've purchased from new are things you typically _don't_ want to buy second-hand (e.g. Hard Drives). By re-using older hardware, I saved quite a bit of money and got up and running almost instantly. _I think I only spent around $350 to get a server up and running_, with all of that cost being [IronWolf 4TB hard drives](https://www.amazon.ca/Seagate-IronWolf-5900RPM-Internal-3-5-Inch/dp/B07H289S79) and a compact case for the server.\n\nI started with a full “desktop-grade” server, but you can start with something as simple as an old laptop, or a Raspberry pi you got at a conference that has been collecting dust for a few years.\n\n## Learning in a safe environment\n\nWhen I started self-hosting I had _heard_ of docker but had not used it. Now, I use docker containers at work daily, and they're a big part of how I do development. I would never have learned as much, as fast, unless I was experimenting on the weekend with my self-hosted services.\n\nI've also learned a lot about:\n\n- Network management/structure\n- Proxy software (NGINX in particular)\n- DNS\n- Monitoring and alerts\n- SSL certificates\n- Virtual machines\n- Virtual private networking (VPNs)\n- I'm sure much more\n\nI'd read a lot about all of these things in school and had even dipped my toes in them previously, but being able to sit down and implement these things has solidified concepts in my head. I definitely wouldn't say I'm an expert in these topics, but I'd be confident enough to propose solutions using this tech now that I've used it.\n\nAnother benefit of learning in a self-hosted environment as opposed to on the job is you don't have to worry about taking down production for a whole company, just your personal stuff.\n\n## Privacy and ownership\n\n> If you're not paying for the product, you and your data _**are**_ the product\n>\n> - someone from the internet\n\nPrivacy was one of the biggest reasons I got into self-hosting. I was able to migrate lots of my data off of Amazon, Facebook, and Google's servers by spinning up self-hosted alternatives, for example:\n\n- Google Drive / Google Photos: _replaced by_ [_NextCloud_](https://nextcloud.com/) _+ a nightly encrypted backup to_ [_BackBlaze B2_](https://www.backblaze.com/b2/cloud-storage.html)\n- Amazon S3 buckets: _replaced by_ [_MINIO_](https://min.io/)\n- Notion / Google Keep: _replaced by_ [_Trilium notes_](https://github.com/zadam/trilium)\n- Google Analytics: [_Umami_](https://umami.is/) _+ PostgreSQL_\n- Cloud databases: _local PostgreSQL, MySQL, MariaDB, and Redis containers_\n- Google Home: [_Home Assistant_](https://www.home-assistant.io/) _virtual machine_\n- Nord VPN: [_WireGuard_](https://www.wireguard.com/) _as a personal VPN on my phone, and laptops_\n- a whole bunch of dev stuff\n\nDon't get me wrong, I still use Gmail for personal email, and I keep all my recent photos on Google Photos still. That said, I was able to move a _lot_ of my data back into my control, and that feels good!\n\n## Easier development environments\n\nHaving a server under your desk with every open-source database installed, and the ability to spin up any niche tool you might need in a few seconds can make creating development or staging environments _easy_.\n\nFor example, when working on freelance projects I typically create a new database for the app or site I'm working on and use that as a development database. Then when I want to leave an app running for testing, I'll just spin up a docker container on my server and send over access to the client for testing. All for effectively $0. Before I was self-hosting, a separate test instance (app + database + Redis) could run anywhere from $10-20/month.\n\n## The Unraid operating system\n\nI use [Unraid](https://unraid.net/) as the operating system on my main server, it allows you to easily set up and manage almost anything you'd want for self-hosting through a local web interface, and even a terminal when I need a bit more access:\n\n- Network drives with parity (via Samba / NFS)\n- Docker containers\n- Virtual machines\n- Unraid specific plugins\n- a [whole bunch more](https://unraid.net/product)\n\nAll you need to do to get rolling with Unraid is download the OS, flash it to a hard drive, and boot off it on your machine. To be honest, I don't think I would have got into self-hosting as much as I did if Unraid wasn't so dang easy to get going with.\n\n## Welcoming community\n\nIn general, the community is welcoming and willing to help out. I've asked a few questions online about my setup, and have always gotten honest and helpful communication back. This could be a bit of selection bias, but as a programmer/developer by trade I was really surprised given how toxic Stack Overflow, Linux, and developer help forums can be.\n\nHere's a list of some of my favourite self-hosted spots online:\n\n- [r/SelfHosted](https://www.reddit.com/r/selfhosted/)\n- [r/Unraid](https://www.reddit.com/r/unRAID)\n- [r/HomeLab](https://www.reddit.com/r/homelab)\n- [Unraid Forums](https://forums.unraid.net/)\n\nEven if you don't plan on getting into self-hosting now, but are generally interested in it, I'd suggest checking these out and seeing all the cool stuff people are doing.\n\n## Self-hosting downsides\n\nWhile it's probably pretty obvious that I'm a fan of self-hosting, there are a few notable downsides to messing around with self-hosting Some of them only apply if you're going to be exposing services to the internet, but I'll make that clear.\n\n### Maintenance\n\nGiven that you're running the hardware yourself, there's always the risk that a hard drive could die, RAM can go bad, or a CPU can just give up. When this happens your services will likely be down (unless you're running something high availability; I'm planning to do this with some Raspberry Pi's so stay tuned for that).\n\nBesides hardware, you're going to want to keep your software up to date and check in regularly. There are monitoring tools that can help with this (monitor uptime, usage, and logins, have the service alert you if anything looks off), but you're still going to want to check-in manually at least once a week in my opinion.\n\n### Security\n\nWhen you're exposing your services, virtual machines, and apps to the internet there are some inherent risks. If you don't properly test and secure your system someone _will_ find a way into your network, your machine, and your data. Be careful with how you're doing things, some common advice would be:\n\n- **Stick to the principle of least privilege:** Give out very limited access by default, and regularly audit what you're making available outside of your network.\n- **Stay up to date:** Make sure to monitor the available updates on your containers and virtual machines, and patch whenever possible. While this isn't a catch-all for security (like most things on the list), it helps to always be on the newest version.\n- **Authenticate everything:** This is more of a personal belief, but if you're going to be putting something on the internet, make sure there's a well-tested authentication scheme, preferably with 2FA protecting the service (e.g. NextCloud w/ strict passwords and 2FA)\n- **Only open the ports you need open:** The only ports I have open on my router are 80 (HTTP), 443 (HTTPS), and 51820 (WireGuard VPN). This is a pretty easy one to manage and doesn't mean as much as it used to but it's still a good rule of thumb.\n- **Use VPN access for really sensitive stuff:** for sensitive data and services, straight up don't expose them to the internet. Just don't risk it. Setup a VPN on your network, connect to the network via the VPN and just connect as you would locally. I use WireGuard which is [trivial to set up via Unraid](https://unraid.net/blog/wireguard-on-unraid)\n\n### Time investment\n\nAs I mentioned at the top of this post, self-hosting is a significant time investment. You'll get out what you put in. If you don't have the time to sit down and mess around on a semi-regular basis (at least until you've got things configured and set up) you're going to be more frustrated than it's worth.\n\n### Internet restrictions\n\nThis one doesn't have much to do with self-hosting itself, but depending on the control your internet service provider (ISP) has over your router, you might not be able to port-forward your router, or even use enough data to make self-hosting worth it past local services. Before getting into it, I would recommend making sure your router allows port-forwarding, and your ISP doesn't have restrictive data caps (upload/download).\n\n## Conclusion\n\nIf after reading all of this, you still want to give it a try, I say go for it! Plug in that laptop, raspberry pi, or old desktop and get tinkering. I've spent countless hours tinkering with my setup (probably too many, to be honest).\n\nIf you have any questions about self-hosting or want to share your own setup feel free to comment below or reach out on the contact page!","html":"<p>If you're unfamiliar with the term, I would define self-hosting as the practice of hosting your own server and services either in your home or place of business. People do this for an array of reasons (which we'll get into in this post). It's become increasingly popular over the last few years for enthusiasts, developers, and nerds, in general, to set up “homelabs” in their basements, under their desks and in their TV cabinets.</p><p>I started self-hosting around 2 years ago after I got a touch of some FOMO from friends with servers set up at home. To be honest I expected to be “over it” a few months after I started. To my surprise, I'm still self-hosting now, and having more fun with it than ever. It's been a fun way for me to learn in a safe/forgiving environment, experiment with new tech, and save some money on online services like Google Drive.</p><p>This post is going to outline a few of the reasons I love self-hosting, and why you might love it too.</p><h2>Reuse of otherwise unused hardware</h2><p>My self-hosted setup is mostly secondhand hardware. The only thing I've purchased from new are things you typically <em>don't</em> want to buy second-hand (e.g. Hard Drives). By re-using older hardware, I saved quite a bit of money and got up and running almost instantly. <em>I think I only spent around $350 to get a server up and running</em>, with all of that cost being <a href=\"https://www.amazon.ca/Seagate-IronWolf-5900RPM-Internal-3-5-Inch/dp/B07H289S79\">IronWolf 4TB hard drives</a> and a compact case for the server.</p><p>I started with a full “desktop-grade” server, but you can start with something as simple as an old laptop, or a Raspberry pi you got at a conference that has been collecting dust for a few years.</p><h2>Learning in a safe environment</h2><p>When I started self-hosting I had <em>heard</em> of docker but had not used it. Now, I use docker containers at work daily, and they're a big part of how I do development. I would never have learned as much, as fast, unless I was experimenting on the weekend with my self-hosted services.</p><p>I've also learned a lot about:</p><ul><li>Network management/structure</li><li>Proxy software (NGINX in particular)</li><li>DNS</li><li>Monitoring and alerts</li><li>SSL certificates</li><li>Virtual machines</li><li>Virtual private networking (VPNs)</li><li>I'm sure much more</li></ul><p>I'd read a lot about all of these things in school and had even dipped my toes in them previously, but being able to sit down and implement these things has solidified concepts in my head. I definitely wouldn't say I'm an expert in these topics, but I'd be confident enough to propose solutions using this tech now that I've used it.</p><p>Another benefit of learning in a self-hosted environment as opposed to on the job is you don't have to worry about taking down production for a whole company, just your personal stuff.</p><h2>Privacy and ownership</h2><blockquote><p>If you're not paying for the product, you and your data <em><strong>are</strong></em> the product</p><ul><li>someone from the internet</li></ul></blockquote><p>Privacy was one of the biggest reasons I got into self-hosting. I was able to migrate lots of my data off of Amazon, Facebook, and Google's servers by spinning up self-hosted alternatives, for example:</p><ul><li>Google Drive / Google Photos: <em>replaced by</em> <a href=\"https://nextcloud.com/\"><em>NextCloud</em></a> <em>+ a nightly encrypted backup to</em> <a href=\"https://www.backblaze.com/b2/cloud-storage.html\"><em>BackBlaze B2</em></a></li><li>Amazon S3 buckets: <em>replaced by</em> <a href=\"https://min.io/\"><em>MINIO</em></a></li><li>Notion / Google Keep: <em>replaced by</em> <a href=\"https://github.com/zadam/trilium\"><em>Trilium notes</em></a></li><li>Google Analytics: <a href=\"https://umami.is/\"><em>Umami</em></a> <em>+ PostgreSQL</em></li><li>Cloud databases: <em>local PostgreSQL, MySQL, MariaDB, and Redis containers</em></li><li>Google Home: <a href=\"https://www.home-assistant.io/\"><em>Home Assistant</em></a> <em>virtual machine</em></li><li>Nord VPN: <a href=\"https://www.wireguard.com/\"><em>WireGuard</em></a> <em>as a personal VPN on my phone, and laptops</em></li><li>a whole bunch of dev stuff</li></ul><p>Don't get me wrong, I still use Gmail for personal email, and I keep all my recent photos on Google Photos still. That said, I was able to move a <em>lot</em> of my data back into my control, and that feels good!</p><h2>Easier development environments</h2><p>Having a server under your desk with every open-source database installed, and the ability to spin up any niche tool you might need in a few seconds can make creating development or staging environments <em>easy</em>.</p><p>For example, when working on freelance projects I typically create a new database for the app or site I'm working on and use that as a development database. Then when I want to leave an app running for testing, I'll just spin up a docker container on my server and send over access to the client for testing. All for effectively $0. Before I was self-hosting, a separate test instance (app + database + Redis) could run anywhere from $10-20/month.</p><h2>The Unraid operating system</h2><p>I use <a href=\"https://unraid.net/\">Unraid</a> as the operating system on my main server, it allows you to easily set up and manage almost anything you'd want for self-hosting through a local web interface, and even a terminal when I need a bit more access:</p><ul><li>Network drives with parity (via Samba / NFS)</li><li>Docker containers</li><li>Virtual machines</li><li>Unraid specific plugins</li><li>a <a href=\"https://unraid.net/product\">whole bunch more</a></li></ul><p>All you need to do to get rolling with Unraid is download the OS, flash it to a hard drive, and boot off it on your machine. To be honest, I don't think I would have got into self-hosting as much as I did if Unraid wasn't so dang easy to get going with.</p><h2>Welcoming community</h2><p>In general, the community is welcoming and willing to help out. I've asked a few questions online about my setup, and have always gotten honest and helpful communication back. This could be a bit of selection bias, but as a programmer/developer by trade I was really surprised given how toxic Stack Overflow, Linux, and developer help forums can be.</p><p>Here's a list of some of my favourite self-hosted spots online:</p><ul><li><a href=\"https://www.reddit.com/r/selfhosted/\">r/SelfHosted</a></li><li><a href=\"https://www.reddit.com/r/unRAID\">r/Unraid</a></li><li><a href=\"https://www.reddit.com/r/homelab\">r/HomeLab</a></li><li><a href=\"https://forums.unraid.net/\">Unraid Forums</a></li></ul><p>Even if you don't plan on getting into self-hosting now, but are generally interested in it, I'd suggest checking these out and seeing all the cool stuff people are doing.</p><h2>Self-hosting downsides</h2><p>While it's probably pretty obvious that I'm a fan of self-hosting, there are a few notable downsides to messing around with self-hosting Some of them only apply if you're going to be exposing services to the internet, but I'll make that clear.</p><h3>Maintenance</h3><p>Given that you're running the hardware yourself, there's always the risk that a hard drive could die, RAM can go bad, or a CPU can just give up. When this happens your services will likely be down (unless you're running something high availability; I'm planning to do this with some Raspberry Pi's so stay tuned for that).</p><p>Besides hardware, you're going to want to keep your software up to date and check in regularly. There are monitoring tools that can help with this (monitor uptime, usage, and logins, have the service alert you if anything looks off), but you're still going to want to check-in manually at least once a week in my opinion.</p><h3>Security</h3><p>When you're exposing your services, virtual machines, and apps to the internet there are some inherent risks. If you don't properly test and secure your system someone <em>will</em> find a way into your network, your machine, and your data. Be careful with how you're doing things, some common advice would be:</p><ul><li><strong>Stick to the principle of least privilege:</strong> Give out very limited access by default, and regularly audit what you're making available outside of your network.</li><li><strong>Stay up to date:</strong> Make sure to monitor the available updates on your containers and virtual machines, and patch whenever possible. While this isn't a catch-all for security (like most things on the list), it helps to always be on the newest version.</li><li><strong>Authenticate everything:</strong> This is more of a personal belief, but if you're going to be putting something on the internet, make sure there's a well-tested authentication scheme, preferably with 2FA protecting the service (e.g. NextCloud w/ strict passwords and 2FA)</li><li><strong>Only open the ports you need open:</strong> The only ports I have open on my router are 80 (HTTP), 443 (HTTPS), and 51820 (WireGuard VPN). This is a pretty easy one to manage and doesn't mean as much as it used to but it's still a good rule of thumb.</li><li><strong>Use VPN access for really sensitive stuff:</strong> for sensitive data and services, straight up don't expose them to the internet. Just don't risk it. Setup a VPN on your network, connect to the network via the VPN and just connect as you would locally. I use WireGuard which is <a href=\"https://unraid.net/blog/wireguard-on-unraid\">trivial to set up via Unraid</a></li></ul><h3>Time investment</h3><p>As I mentioned at the top of this post, self-hosting is a significant time investment. You'll get out what you put in. If you don't have the time to sit down and mess around on a semi-regular basis (at least until you've got things configured and set up) you're going to be more frustrated than it's worth.</p><h3>Internet restrictions</h3><p>This one doesn't have much to do with self-hosting itself, but depending on the control your internet service provider (ISP) has over your router, you might not be able to port-forward your router, or even use enough data to make self-hosting worth it past local services. Before getting into it, I would recommend making sure your router allows port-forwarding, and your ISP doesn't have restrictive data caps (upload/download).</p><h2>Conclusion</h2><p>If after reading all of this, you still want to give it a try, I say go for it! Plug in that laptop, raspberry pi, or old desktop and get tinkering. I've spent countless hours tinkering with my setup (probably too many, to be honest).</p><p>If you have any questions about self-hosting or want to share your own setup feel free to comment below or reach out on the contact page!</p>"}},{"id":"improving-anchor-links","url":"https://mykalmachon.com/posts/improving-anchor-links","meta":{"title":"Improving Anchor Links","pubDate":"2022-06-24T00:00:00.000Z","type":"post","draft":false,"tags":["webdev"],"description":"This post outlines a few little tricks you can use with your anchor links","heroImage":{"src":"/_astro/improving-anchor-links.CnxKUzAh.avif","width":1200,"height":747,"format":"avif"}},"content":{"md":"This post outlines a few tricks that I've been re-discovering over and over again. To save time, I thought I would do a quick write-up documenting them so I know where to look next time I need them.\n\n## What is an anchor link?\n\nLet's start off by defining what an \"anchor link\" is. I would define an anchor link as a fundamental browser feature, that requires a few things to work:\n\n- an HTML element with a unique id: `<h2 id=\"go-to-website-stack\">Go To Website Stack</h2>`\n- a URL that references that unique id: `https://mykal.codes/posts/whats-your-goto-web-stack/#go-to-website-stack`\n\nWhen someone navigates to that URL, it will scroll to the position of the corresponding HTML element on the page.\n\nI'm sure you've seen these used in a table of contents for an extensive blog post, or to jump to a specific section on a restaurant's menu page. **This post is going to summarize some of the cool things you can do with those anchor links.** I use them in [my coffee post](/posts/coffee-setup) for a table of contents.\n\n### Alternate names\n\nthese anchor links go by a bunch of other names as well, you may know them as:\n\n- ID-based links\n- Tagged links\n- Jump links\n- Inline links\n- On-Page links\n\nShoutout to the [d-d-d-discord](https://www.patreon.com/shoptalkshow) for this list.\n\n## Scroll margin on top of the element\n\nIf you want to use these links on a site that has a sticky header element at the top of the viewport, you'll probably notice things won't go to plan. Typically these links are hidden underneath the sticky header.\n\n<p class=\"codepen\" data-height=\"300\" data-default-tab=\"result\" data-customSlug-hash=\"RwQXYrj\" data-user=\"mykalmachon\" style=\"height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;\">\n  <span>See the Pen <a href=\"https://codepen.io/mykalmachon/pen/RwQXYrj\">\n  Anchor Link: Margin Example (Broken)</a> by Mykal Machon (<a href=\"https://codepen.io/mykalmachon\">@mykalmachon</a>)\n  on <a href=\"https://codepen.io\">CodePen</a>.</span>\n</p>\n<script async src=\"https://cpwebassets.codepen.io/assets/embed/ei.js\"></script>\n\nDo you see how the heading with the unique ID is below the sticky header?\n\nThere are a few CSS rules we can throw in to add some space between the top of the viewport, and the associated element.\nSee an example of this fixed here:\n\n<p class=\"codepen\" data-height=\"300\" data-default-tab=\"result\" data-customSlug-hash=\"YzemOGr\" data-user=\"mykalmachon\" style=\"height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;\">\n  <span>See the Pen <a href=\"https://codepen.io/mykalmachon/pen/YzemOGr\">\n  Anchor Link: Margin Example (Broken)</a> by Mykal Machon (<a href=\"https://codepen.io/mykalmachon\">@mykalmachon</a>)\n  on <a href=\"https://codepen.io\">CodePen</a>.</span>\n</p>\n<script async src=\"https://cpwebassets.codepen.io/assets/embed/ei.js\"></script>\n\n## Temporary animations on arrival at element\n\nAnother thing you can do to improve the user experience of these anchor links is adding an animation/style to emphasize the part of the document you've linked to.\n\nI tend to do this by adding a quick \"blink\" animation to the heading in question:\n\n```css\n@import 'https://unpkg.com/open-props';\n\n:target {\n  animation: var(--animation-blink);\n  animation-timing-function: var(--ease-3);\n  animation-duration: 0.5s;\n  animation-iteration-count: 5;\n}\n```\n\nBut you can also add a background to the element, a border, or anything else you can think of doing in CSS.\n\n```css\n@import 'https://unpkg.com/open-props';\n\n:target {\n  background: var(--yellow-6);\n  border-bottom: 2px solid var(--yellow-9);\n}\n```","html":"<p>This post outlines a few tricks that I've been re-discovering over and over again. To save time, I thought I would do a quick write-up documenting them so I know where to look next time I need them.</p><h2>What is an anchor link?</h2><p>Let's start off by defining what an &quot;anchor link&quot; is. I would define an anchor link as a fundamental browser feature, that requires a few things to work:</p><ul><li>an HTML element with a unique id: <code>&lt;h2 id=&quot;go-to-website-stack&quot;&gt;Go To Website Stack&lt;/h2&gt;</code></li><li>a URL that references that unique id: <code>https://mykal.codes/posts/whats-your-goto-web-stack/#go-to-website-stack</code></li></ul><p>When someone navigates to that URL, it will scroll to the position of the corresponding HTML element on the page.</p><p>I'm sure you've seen these used in a table of contents for an extensive blog post, or to jump to a specific section on a restaurant's menu page. <strong>This post is going to summarize some of the cool things you can do with those anchor links.</strong> I use them in <a href=\"/posts/coffee-setup\">my coffee post</a> for a table of contents.</p><h3>Alternate names</h3><p>these anchor links go by a bunch of other names as well, you may know them as:</p><ul><li>ID-based links</li><li>Tagged links</li><li>Jump links</li><li>Inline links</li><li>On-Page links</li></ul><p>Shoutout to the <a href=\"https://www.patreon.com/shoptalkshow\">d-d-d-discord</a> for this list.</p><h2>Scroll margin on top of the element</h2><p>If you want to use these links on a site that has a sticky header element at the top of the viewport, you'll probably notice things won't go to plan. Typically these links are hidden underneath the sticky header.</p><p>&lt;p class=&quot;codepen&quot; data-height=&quot;300&quot; data-default-tab=&quot;result&quot; data-customSlug-hash=&quot;RwQXYrj&quot; data-user=&quot;mykalmachon&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;&lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/mykalmachon/pen/RwQXYrj&quot;&gt;Anchor Link: Margin Example (Broken)&lt;/a&gt; by Mykal Machon (&lt;a href=&quot;https://codepen.io/mykalmachon&quot;&gt;@mykalmachon&lt;/a&gt;)on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;&lt;script async src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;</p><p>Do you see how the heading with the unique ID is below the sticky header?</p><p>There are a few CSS rules we can throw in to add some space between the top of the viewport, and the associated element.See an example of this fixed here:</p><p>&lt;p class=&quot;codepen&quot; data-height=&quot;300&quot; data-default-tab=&quot;result&quot; data-customSlug-hash=&quot;YzemOGr&quot; data-user=&quot;mykalmachon&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;&lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/mykalmachon/pen/YzemOGr&quot;&gt;Anchor Link: Margin Example (Broken)&lt;/a&gt; by Mykal Machon (&lt;a href=&quot;https://codepen.io/mykalmachon&quot;&gt;@mykalmachon&lt;/a&gt;)on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;&lt;script async src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;</p><h2>Temporary animations on arrival at element</h2><p>Another thing you can do to improve the user experience of these anchor links is adding an animation/style to emphasize the part of the document you've linked to.</p><p>I tend to do this by adding a quick &quot;blink&quot; animation to the heading in question:</p><pre><code class=\"language-css\">@import 'https://unpkg.com/open-props';:target {  animation: var(--animation-blink);  animation-timing-function: var(--ease-3);  animation-duration: 0.5s;  animation-iteration-count: 5;}</code></pre><p>But you can also add a background to the element, a border, or anything else you can think of doing in CSS.</p><pre><code class=\"language-css\">@import 'https://unpkg.com/open-props';:target {  background: var(--yellow-6);  border-bottom: 2px solid var(--yellow-9);}</code></pre>"}},{"id":"whats-your-goto-web-stack","url":"https://mykalmachon.com/posts/whats-your-goto-web-stack","meta":{"title":"What's Your Go-To Web Stack","pubDate":"2022-05-16T00:00:00.000Z","type":"post","draft":false,"tags":["webdev"],"description":"An exercise in exploring the stack I'm currently comfy with.","heroImage":{"src":"/_astro/whats-your-goto-web-stack.B-G4SCkh.avif","width":1100,"height":733,"format":"avif"}},"content":{"md":"This post was inspired by [Andrew Walpole's \"What's Your Go-To Web Stack\" post](https://andrewwalpole.com/blog/whats-your-go-to-web-stack/). It seemed like a fun exercise, so I thought I’d give it a shot.\n\nThe stack I'd use for a website is a little different than what I'd use for a webapp, **so I figured I'd answer this question in two ways: what's my webapp stack, and what's my website stack.**\n\n## Go-To Website Stack\n\nIf I’m working on a site for myself or a client, the tools in my belt are pretty well defined.\n\nThe main piece of the stack here is the Static Site Generator (SSG) / \"meta-framework\" used to build the site. Until recently, my go-to was [Next.js](https://nextjs.org/), but in the last ~6 months, I've moved most of my stuff over to [Astro](https://astro.build/). Next.js is [technically more \"feature rich\"](https://nextjs.org/docs/getting-started), but I really like Astro's [zero-js-by-default approach](https://docs.astro.build/en/core-concepts/partial-hydration/). Astro also gives me more room to experiment with JS / CSS.\n\nI've been using [Netlify's Edge and Serverless Functions](https://www.netlify.com/products/functions/) quite a lot as well. While I've been using Netlify for years now, I've only really started digging into their serverless functions to help supplement Astro's lack of server-side functionality.\n\nFor content management, I'll typically stick to markdown files, but if the site needs to be edited by business users, I'll use [Sanity.io](https://www.sanity.io/). Their free plan is super generous and the level of customization makes it easy enough to tweak the CMS to fit the level of control and visibility needed.\n\n### The Stack / TLDR\n\n- Framework: [Astro](https://astro.build/)\n- Content Management System: Either Markdown or [Sanity.io](https://sanity.io/)\n- Styles: SCSS\n- Javascript: Vanilla JS, or [Svelte](https://svelte.dev/) if it's getting a little complex\n- Hosting: [Netlify](https://www.netlify.com/) (Serverless / edge functions + forms are killer)\n\n## Go-To Webapp Stack\n\nMy go-to webapp stack is a bit more up-in-the-air than my website stack. Depending on what I'm working on / the needs of the project, the stack can change a lot. If I _had_ to choose, the tech below is the closest I could get to a \"go-to\" list.\n\nThe main piece of the pie here is, again, the \"Framework\". I've been using [Flask](https://flask.palletsprojects.com/en/2.1.x/) _a lot_ at work lately and have been really loving the back-to-basics web server approach it takes. There are tonnes of resources out there about integrating it with modern tech and practices, plus Python is just a dream to write. In cases where I need something that has a reactive frontend though, I'll still reach for the tried and true [Next.js](https://nextjs.org/).\n\nWe use mostly MS SQL for databases at work, but that is definitely not my DB of choice. I'll reach for [PostgresSQL](https://www.postgresql.org/) whenever I can; it seems to work with almost everything you'd need it to, and benefits from being one of the \"popular\" databases with lots of tooling and educational content available.\n\nFor hosting, [I've really been digging \"dockerized\" setups lately](https://mykal.codes/posts/docker-rocks/), which, by design, means you can kind of host anywhere. I host some stuff on a server in my house, and use cloud services like [Railway](https://railway.app/) to host most production environments for clients.\n\n### The Stack / TLDR\n\n- Framework: [Flask](https://flask.palletsprojects.com/en/2.1.x/) or [Next.js](https://nextjs.org/)\n- Database: [PostgreSQL](https://www.postgresql.org/)\n- Styles: SCSS\n- Javascript: Vanilla JS or [React](https://reactjs.org/) (the ecosystem still has Svelte beat here)\n- Hosting: [Docker](https://www.docker.com/) + On-Prem / Docker + Cloud ([Railway](https://railway.app/), [Digital Ocean](https://www.digitalocean.com/))","html":"<p>This post was inspired by <a href=\"https://andrewwalpole.com/blog/whats-your-go-to-web-stack/\">Andrew Walpole's &quot;What's Your Go-To Web Stack&quot; post</a>. It seemed like a fun exercise, so I thought I’d give it a shot.</p><p>The stack I'd use for a website is a little different than what I'd use for a webapp, <strong>so I figured I'd answer this question in two ways: what's my webapp stack, and what's my website stack.</strong></p><h2>Go-To Website Stack</h2><p>If I’m working on a site for myself or a client, the tools in my belt are pretty well defined.</p><p>The main piece of the stack here is the Static Site Generator (SSG) / &quot;meta-framework&quot; used to build the site. Until recently, my go-to was <a href=\"https://nextjs.org/\">Next.js</a>, but in the last ~6 months, I've moved most of my stuff over to <a href=\"https://astro.build/\">Astro</a>. Next.js is <a href=\"https://nextjs.org/docs/getting-started\">technically more &quot;feature rich&quot;</a>, but I really like Astro's <a href=\"https://docs.astro.build/en/core-concepts/partial-hydration/\">zero-js-by-default approach</a>. Astro also gives me more room to experiment with JS / CSS.</p><p>I've been using <a href=\"https://www.netlify.com/products/functions/\">Netlify's Edge and Serverless Functions</a> quite a lot as well. While I've been using Netlify for years now, I've only really started digging into their serverless functions to help supplement Astro's lack of server-side functionality.</p><p>For content management, I'll typically stick to markdown files, but if the site needs to be edited by business users, I'll use <a href=\"https://www.sanity.io/\">Sanity.io</a>. Their free plan is super generous and the level of customization makes it easy enough to tweak the CMS to fit the level of control and visibility needed.</p><h3>The Stack / TLDR</h3><ul><li>Framework: <a href=\"https://astro.build/\">Astro</a></li><li>Content Management System: Either Markdown or <a href=\"https://sanity.io/\">Sanity.io</a></li><li>Styles: SCSS</li><li>Javascript: Vanilla JS, or <a href=\"https://svelte.dev/\">Svelte</a> if it's getting a little complex</li><li>Hosting: <a href=\"https://www.netlify.com/\">Netlify</a> (Serverless / edge functions + forms are killer)</li></ul><h2>Go-To Webapp Stack</h2><p>My go-to webapp stack is a bit more up-in-the-air than my website stack. Depending on what I'm working on / the needs of the project, the stack can change a lot. If I <em>had</em> to choose, the tech below is the closest I could get to a &quot;go-to&quot; list.</p><p>The main piece of the pie here is, again, the &quot;Framework&quot;. I've been using <a href=\"https://flask.palletsprojects.com/en/2.1.x/\">Flask</a> <em>a lot</em> at work lately and have been really loving the back-to-basics web server approach it takes. There are tonnes of resources out there about integrating it with modern tech and practices, plus Python is just a dream to write. In cases where I need something that has a reactive frontend though, I'll still reach for the tried and true <a href=\"https://nextjs.org/\">Next.js</a>.</p><p>We use mostly MS SQL for databases at work, but that is definitely not my DB of choice. I'll reach for <a href=\"https://www.postgresql.org/\">PostgresSQL</a> whenever I can; it seems to work with almost everything you'd need it to, and benefits from being one of the &quot;popular&quot; databases with lots of tooling and educational content available.</p><p>For hosting, <a href=\"https://mykal.codes/posts/docker-rocks/\">I've really been digging &quot;dockerized&quot; setups lately</a>, which, by design, means you can kind of host anywhere. I host some stuff on a server in my house, and use cloud services like <a href=\"https://railway.app/\">Railway</a> to host most production environments for clients.</p><h3>The Stack / TLDR</h3><ul><li>Framework: <a href=\"https://flask.palletsprojects.com/en/2.1.x/\">Flask</a> or <a href=\"https://nextjs.org/\">Next.js</a></li><li>Database: <a href=\"https://www.postgresql.org/\">PostgreSQL</a></li><li>Styles: SCSS</li><li>Javascript: Vanilla JS or <a href=\"https://reactjs.org/\">React</a> (the ecosystem still has Svelte beat here)</li><li>Hosting: <a href=\"https://www.docker.com/\">Docker</a> + On-Prem / Docker + Cloud (<a href=\"https://railway.app/\">Railway</a>, <a href=\"https://www.digitalocean.com/\">Digital Ocean</a>)</li></ul>"}},{"id":"coffee-setup","url":"https://mykalmachon.com/posts/coffee-setup","meta":{"title":"My Coffee Setup","pubDate":"2022-05-10T00:00:00.000Z","type":"post","draft":false,"tags":["food"],"description":"I've gotten into Coffee lately (cliché, I know) and wanted to summarize my coffee setup.","heroImage":{"src":"/_astro/coffee-setup.BarOMXKc.avif","width":1170,"height":780,"format":"avif"}},"content":{"md":"I've really gotten into Coffee lately (cliché, I know) and I wanted to summarize my setup in a post.\n\n**If you're one of the people who asked me about my Coffee setup, this is for you!**\n\n> Disclaimer: I am not a Coffee expert. The info I'm sharing in this post is based on my own research and experimentation. Taste is subjective!\n\n## Coffee Beans \n\nI have a pretty standard set of practices for buying and preparing my coffee beans. I typically try to buy beans that are from local specialty shops, and that list their farms / origins explicitly. See [James Hoffman's video on how to buy beans](https://www.youtube.com/watch?v=O9YnLFrM7Fs) for more info here.\n\n### Where I Get My Beans\n\nI typically get beans from one of the following roasters:\n\n1.  [Smoking Gun Coffee](https://smokingguncoffee.com/)\n2.  [Monogram Coffee Co](https://monogramcoffee.com/)\n\nThey list where their beans are purchased from (even listing the farms at times), and generally support sustainable coffee growing which is a plus!\n\n### How I Grind My Beans\n\nI ended up buying [a budget-priced Bodum burr grinder](https://www.bodum.com/ca/en/11750-01us-bistro). I chose it because it had metal burrs, and decent reviews.\n\nBased on what I've read so far, porcelain burrs tend to get dull and chip; meaning you'll have to replace them relatively frequently. Metal burrs tend to last a bit longer, and be more maintainable over time.\n\nI have the exact grind settings I use in the [Pour Over](#pour-over) and [AeroPress](#aeropress) Sections below.\n\n## Brewing Methods\n\nWith the gear I have at home right now, I can brew either pour over, or aerorpess coffee. That said, I've picked up some things that you'll need regardless of brewing method.\n\n1.  [Digital Scale with timer](https://www.amazon.ca/gp/product/B07L2KV94G/ref=ppx_yo_dt_b_asin_title_o02_s00?ie=UTF8&psc=1): any scale with .1g accuracy, and a tare feature should work (a timer is a plus).\n2.  [POLIVIAR Electric Gooseneck Kettle](https://www.amazon.ca/POLIVIAR-Electric-Gooseneck-Kettle-JX2020-EKG/dp/B08XGN42YT/?th=1): if you're not doing pour over, you can stick with a cheaper standard kettle\n\n### Pour Over \n\nI started out with Pour Over ~2 months ago, and have kept with it since. That said, I'm pretty meticulous in the steps I take, and I'm sure there are much simpler ways to do things.\n\n#### Equipment\n\n1.  HARIO V60 - 02 Acrylic Dripper\n2.  HARIO V60 - 02 Filter Papers\n3.  HARIO V60 - 01 Glass Range Server\n\n#### Brew Instructions\n\n1.  measure out 28g of coffee\n2.  measure out 550g of water into your kettle\n3.  start boiling water (90 C for light/medium roasts, 95 C for dark roasts)\n4.  grind coffee to 1 setting past “pour over” on my grinder\n5.  place your V60 dripper onto your server, and place a paper filter inside\n6.  with some warm tap water, gently “rinse” the filter in the dripper. when done empty the server\n7.  pour your grounds into the setup and level them out, dig a slight hole in the middle with your fingers\n8.  place your setup onto the scale and tare it\n9.  once your water is to temperature, start the timer and pour in 60g of water; this starts the “resting” phase.\n10. place the kettle down, and “swirl” the setup until you see all of the grounds are wet\n11. let it sit until the timer hits 45 seconds\n12. slowly pour water into the setup in a circular motion until the scale reads ~448g of water\n13. let the dripper work a little bit, and then give the entire setup another swirl to clear some of the grounds of the filter walls\n14. wait until the setup completes draw down (should take around 3, to 3 ½ minutes.\n15. Enjoy your coffee! ☕\n\n### AeroPress\n\nI just got my AeroPress last week, and oh boy! I think it might replace my Pour Over setup for a daily brew. In my experience, a Pour Over brew is super easy to mess up (this could just be  a symptom of my inexperience), and I've yet to have a bad cup of coffee with my AeroPress.\n\n#### Equipment\n\n1.  AeroPress GO: all in one kit that includes a drinking cup, stir stick, coffee scoop, 350 filters, and an AeroPress!\n\n#### Brew Instructions\n\n1.  measure out 11g of coffee\n2.  measure out at least 275g of water\n3.  start boiling water (90 C for light/medium roasts, 95 C for dark roasts)\n4.  grind coffee to 2 settings past pour over on my grinder\n5.  place a filter in your AeroPress, pour your coffee in, place your AeroPress on your digital scale, and tare it.\n6.  once your water is to temperature, start the timer, and pour in 200g of water in a circular motion.\n7.  place the plunger on top of the AeroPress (just enough to get a seal) and take it off the scale\n8.  when your timer reaches 1:30, swirl the AeroPress (make sure you're holding the cup and the AeroPress)\n9.  when your timer reaches 2:00 slowly push down on the plunger and extract your coffee\n10. Enjoy your coffee! ☕","html":"<p>I've really gotten into Coffee lately (cliché, I know) and I wanted to summarize my setup in a post.</p><p><strong>If you're one of the people who asked me about my Coffee setup, this is for you!</strong></p><blockquote><p>Disclaimer: I am not a Coffee expert. The info I'm sharing in this post is based on my own research and experimentation. Taste is subjective!</p></blockquote><h2>Coffee Beans</h2><p>I have a pretty standard set of practices for buying and preparing my coffee beans. I typically try to buy beans that are from local specialty shops, and that list their farms / origins explicitly. See <a href=\"https://www.youtube.com/watch?v=O9YnLFrM7Fs\">James Hoffman's video on how to buy beans</a> for more info here.</p><h3>Where I Get My Beans</h3><p>I typically get beans from one of the following roasters:</p><ol><li><a href=\"https://smokingguncoffee.com/\">Smoking Gun Coffee</a></li><li><a href=\"https://monogramcoffee.com/\">Monogram Coffee Co</a></li></ol><p>They list where their beans are purchased from (even listing the farms at times), and generally support sustainable coffee growing which is a plus!</p><h3>How I Grind My Beans</h3><p>I ended up buying <a href=\"https://www.bodum.com/ca/en/11750-01us-bistro\">a budget-priced Bodum burr grinder</a>. I chose it because it had metal burrs, and decent reviews.</p><p>Based on what I've read so far, porcelain burrs tend to get dull and chip; meaning you'll have to replace them relatively frequently. Metal burrs tend to last a bit longer, and be more maintainable over time.</p><p>I have the exact grind settings I use in the <a href=\"#pour-over\">Pour Over</a> and <a href=\"#aeropress\">AeroPress</a> Sections below.</p><h2>Brewing Methods</h2><p>With the gear I have at home right now, I can brew either pour over, or aerorpess coffee. That said, I've picked up some things that you'll need regardless of brewing method.</p><ol><li><a href=\"https://www.amazon.ca/gp/product/B07L2KV94G/ref=ppx_yo_dt_b_asin_title_o02_s00?ie=UTF8&amp;psc=1\">Digital Scale with timer</a>: any scale with .1g accuracy, and a tare feature should work (a timer is a plus).</li><li><a href=\"https://www.amazon.ca/POLIVIAR-Electric-Gooseneck-Kettle-JX2020-EKG/dp/B08XGN42YT/?th=1\">POLIVIAR Electric Gooseneck Kettle</a>: if you're not doing pour over, you can stick with a cheaper standard kettle</li></ol><h3>Pour Over</h3><p>I started out with Pour Over ~2 months ago, and have kept with it since. That said, I'm pretty meticulous in the steps I take, and I'm sure there are much simpler ways to do things.</p><h4>Equipment</h4><ol><li>HARIO V60 - 02 Acrylic Dripper</li><li>HARIO V60 - 02 Filter Papers</li><li>HARIO V60 - 01 Glass Range Server</li></ol><h4>Brew Instructions</h4><ol><li>measure out 28g of coffee</li><li>measure out 550g of water into your kettle</li><li>start boiling water (90 C for light/medium roasts, 95 C for dark roasts)</li><li>grind coffee to 1 setting past “pour over” on my grinder</li><li>place your V60 dripper onto your server, and place a paper filter inside</li><li>with some warm tap water, gently “rinse” the filter in the dripper. when done empty the server</li><li>pour your grounds into the setup and level them out, dig a slight hole in the middle with your fingers</li><li>place your setup onto the scale and tare it</li><li>once your water is to temperature, start the timer and pour in 60g of water; this starts the “resting” phase.</li><li>place the kettle down, and “swirl” the setup until you see all of the grounds are wet</li><li>let it sit until the timer hits 45 seconds</li><li>slowly pour water into the setup in a circular motion until the scale reads ~448g of water</li><li>let the dripper work a little bit, and then give the entire setup another swirl to clear some of the grounds of the filter walls</li><li>wait until the setup completes draw down (should take around 3, to 3 ½ minutes.</li><li>Enjoy your coffee! ☕</li></ol><h3>AeroPress</h3><p>I just got my AeroPress last week, and oh boy! I think it might replace my Pour Over setup for a daily brew. In my experience, a Pour Over brew is super easy to mess up (this could just be  a symptom of my inexperience), and I've yet to have a bad cup of coffee with my AeroPress.</p><h4>Equipment</h4><ol><li>AeroPress GO: all in one kit that includes a drinking cup, stir stick, coffee scoop, 350 filters, and an AeroPress!</li></ol><h4>Brew Instructions</h4><ol><li>measure out 11g of coffee</li><li>measure out at least 275g of water</li><li>start boiling water (90 C for light/medium roasts, 95 C for dark roasts)</li><li>grind coffee to 2 settings past pour over on my grinder</li><li>place a filter in your AeroPress, pour your coffee in, place your AeroPress on your digital scale, and tare it.</li><li>once your water is to temperature, start the timer, and pour in 200g of water in a circular motion.</li><li>place the plunger on top of the AeroPress (just enough to get a seal) and take it off the scale</li><li>when your timer reaches 1:30, swirl the AeroPress (make sure you're holding the cup and the AeroPress)</li><li>when your timer reaches 2:00 slowly push down on the plunger and extract your coffee</li><li>Enjoy your coffee! ☕</li></ol>"}},{"id":"docker-rocks","url":"https://mykalmachon.com/posts/docker-rocks","meta":{"title":"Docker Rocks 🐳","pubDate":"2022-04-27T00:00:00.000Z","type":"post","draft":false,"tags":["devops"],"description":"Once Docker and Docker-compose clicks, it makes dev ➡️ launch so much easier","heroImage":{"src":"/_astro/docker-rocks.S50ISAGS.avif","width":1170,"height":780,"format":"avif"}},"content":{"md":"Docker is such an amazing piece of technology.\n\nI’ve been doing a bunch with it at work lately and once things “click” (docker + docker-compose) the pipeline from development, to deployment can feel seamless, and a lot easier to be confident in.\n\nYou can have a local \"dev\" environment (DB, App, Redis, etc) that mirrors your production environment up and running with one command.\nyou can make all your changes, and test them in an environment that is extremely close to production, in _one command_.\n\nFrom there, deploying your app is as simple as commiting your code, merging a pull request, pulling things down on your production server, and restarting the app!\n\n> If you're in a cloud environment that supports git-based CI/CD, this is probably automated for you already! 🎉\n\nWe've even started to \"containerize\" off-the-shelf apps we use at work to simplify deployment. docker-compose is so useful for \"composing\" services that it can take deployments down from multi-hour efforts to a few commands. Previously monolothic all-day-long deploys can literally be as simple as:\n\n```bash\ndocker-compose build && docker-compose up\n```\n\nThe next logical step for me in all of this is Kubernetes, which seems infinitely more daunting somehow.\n\n### What does this look like?\n\nHere's [an, admittedly extremely abstracted, example](https://github.com/MykalMachon/Caeser) based on one of my recent work projects.\n\nDespite the code being a lot simpler, the basic \"docker\" structure we use is on full display here, and could pretty easily be forked out to fit other projects.","html":"<p>Docker is such an amazing piece of technology.</p><p>I’ve been doing a bunch with it at work lately and once things “click” (docker + docker-compose) the pipeline from development, to deployment can feel seamless, and a lot easier to be confident in.</p><p>You can have a local &quot;dev&quot; environment (DB, App, Redis, etc) that mirrors your production environment up and running with one command.you can make all your changes, and test them in an environment that is extremely close to production, in <em>one command</em>.</p><p>From there, deploying your app is as simple as commiting your code, merging a pull request, pulling things down on your production server, and restarting the app!</p><blockquote><p>If you're in a cloud environment that supports git-based CI/CD, this is probably automated for you already! 🎉</p></blockquote><p>We've even started to &quot;containerize&quot; off-the-shelf apps we use at work to simplify deployment. docker-compose is so useful for &quot;composing&quot; services that it can take deployments down from multi-hour efforts to a few commands. Previously monolothic all-day-long deploys can literally be as simple as:</p><pre><code class=\"language-bash\">docker-compose build &amp;&amp; docker-compose up</code></pre><p>The next logical step for me in all of this is Kubernetes, which seems infinitely more daunting somehow.</p><h3>What does this look like?</h3><p>Here's <a href=\"https://github.com/MykalMachon/Caeser\">an, admittedly extremely abstracted, example</a> based on one of my recent work projects.</p><p>Despite the code being a lot simpler, the basic &quot;docker&quot; structure we use is on full display here, and could pretty easily be forked out to fit other projects.</p>"}},{"id":"autosave-forms","url":"https://mykalmachon.com/posts/autosave-forms","meta":{"title":"Autosave on the Web","pubDate":"2022-02-15T00:00:00.000Z","type":"post","draft":false,"tags":["webdev"],"description":"Using native browser APIs to autosave HTML forms","heroImage":{"src":"/_astro/autosave-forms.Cfxwx7yC.avif","width":1171,"height":781,"format":"avif"}},"content":{"md":"Forms on the web are awesome:\n\nThey're one of the first instances of web interactivity. Before we had Jquery, JavaScript, or Flash, we had forms, and post requests.\n\n> Forms walked, so 1.2MB JavaScript bundles could run\n>\n> _Mykal Machon, 2022_\n\nForms are 100% native to the web and are a tried-and-true way to collect input from users: whether you stick to the originally intended POST-data-to-action on submit, or something javascript _enhanced_ like submitting the data via fetch, they sort of _just work_.\n\nThat said, forms don't have all the bells and whistles out of the box,\n_One of those missing bells and whistles is autosave_\n\n## Do we need Autosave, though?\n\nWell, not always!\n\nUntil recently, it hadn't occurred to me that you really would need autosave in your forms; there are many other ways to persist user input.\n\nTo name a few:\n\n1. Write user data to a database in real-time: similar to how apps like Notion, or Google docs handle things.\n2. Keep forms short: users shouldn't spend a significant amount of time filling a form out\n\nAnd those seem like valid, wholistic strategies until you think about:\n\n### Working Offline\n\nI'm working on a web app for one of my consulting partners. The app is typically used on phones in locations with _less than ideal_ wireless connections.\n\nPicture this:\n\n- You're working on-site, filling out a web-based inspection form.\n- You fill out a few inputs, but you lose signal halfway through; you don't notice.\n- You open your phone 5 minutes later and notice that your browser has reloaded the page in the background.\n- You just lost all the data in your form! 😔\n\nAdmittedly I never even considered this when building the app. I just assumed the best. In cases like these, autosave can be a lifesaver!\n\n## Solving Autosave!\n\nLuckily, browsers and modern JavaScript make autosave pretty easy to implement 🥳\n\nThe solution I ended up going with uses something called [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API): which is a fancy key-value database, that's designed to be very fast, with lots of data.\n\nI created a little NPM package called [FormStore](https://github.com/MykalMachon/FormStore) to make it easy!\n\n> Hey 👋🏻\n>\n> I'm working on a more technical walk-through of this now; I just wanted to get my thought process out first.\n> If you're seeing this and read the whole way through, thanks!","html":"<p>Forms on the web are awesome:</p><p>They're one of the first instances of web interactivity. Before we had Jquery, JavaScript, or Flash, we had forms, and post requests.</p><blockquote><p>Forms walked, so 1.2MB JavaScript bundles could run</p><p><em>Mykal Machon, 2022</em></p></blockquote><p>Forms are 100% native to the web and are a tried-and-true way to collect input from users: whether you stick to the originally intended POST-data-to-action on submit, or something javascript <em>enhanced</em> like submitting the data via fetch, they sort of <em>just work</em>.</p><p>That said, forms don't have all the bells and whistles out of the box,<em>One of those missing bells and whistles is autosave</em></p><h2>Do we need Autosave, though?</h2><p>Well, not always!</p><p>Until recently, it hadn't occurred to me that you really would need autosave in your forms; there are many other ways to persist user input.</p><p>To name a few:</p><ol><li>Write user data to a database in real-time: similar to how apps like Notion, or Google docs handle things.</li><li>Keep forms short: users shouldn't spend a significant amount of time filling a form out</li></ol><p>And those seem like valid, wholistic strategies until you think about:</p><h3>Working Offline</h3><p>I'm working on a web app for one of my consulting partners. The app is typically used on phones in locations with <em>less than ideal</em> wireless connections.</p><p>Picture this:</p><ul><li>You're working on-site, filling out a web-based inspection form.</li><li>You fill out a few inputs, but you lose signal halfway through; you don't notice.</li><li>You open your phone 5 minutes later and notice that your browser has reloaded the page in the background.</li><li>You just lost all the data in your form! 😔</li></ul><p>Admittedly I never even considered this when building the app. I just assumed the best. In cases like these, autosave can be a lifesaver!</p><h2>Solving Autosave!</h2><p>Luckily, browsers and modern JavaScript make autosave pretty easy to implement 🥳</p><p>The solution I ended up going with uses something called <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API\">IndexedDB</a>: which is a fancy key-value database, that's designed to be very fast, with lots of data.</p><p>I created a little NPM package called <a href=\"https://github.com/MykalMachon/FormStore\">FormStore</a> to make it easy!</p><blockquote><p>Hey 👋🏻</p><p>I'm working on a more technical walk-through of this now; I just wanted to get my thought process out first.If you're seeing this and read the whole way through, thanks!</p></blockquote>"}},{"id":"mykal-social","url":"https://mykalmachon.com/posts/mykal-social","meta":{"title":"mykal.social","pubDate":"2022-01-15T00:00:00.000Z","type":"link","draft":false,"tags":["personal"],"url":"https://mykal.social","description":""},"content":{"md":"My new ActivityPub server setup with [GoToSocial](https://github.com/superseriousbusiness/gotosocial). I'm a fan of it so far.\nI'll probably set up a few experiments there.","html":"<p>My new ActivityPub server setup with <a href=\"https://github.com/superseriousbusiness/gotosocial\">GoToSocial</a>. I'm a fan of it so far.I'll probably set up a few experiments there.</p>"}},{"id":"digital-gardens","url":"https://mykalmachon.com/posts/digital-gardens","meta":{"title":"Digital Gardens","pubDate":"2022-01-01T00:00:00.000Z","type":"note","draft":false,"tags":["personal","opinion"],"stage":"seed","description":"my thoughts on building digital gardens"},"content":{"md":"I've been thinking a lot about owning your own data indie-web style. Digital gardens seem like the best, most mentally friendly way to achieve this.","html":"<p>I've been thinking a lot about owning your own data indie-web style. Digital gardens seem like the best, most mentally friendly way to achieve this.</p>"}},{"id":"web-dev-resources","url":"https://mykalmachon.com/posts/web-dev-resources","meta":{"title":"Web Dev Resources","pubDate":"2021-08-26T00:00:00.000Z","type":"post","draft":false,"tags":["webdev"],"description":"What I used, and am still using, to learn web dev","heroImage":{"src":"/_astro/web-dev-resources.DaB5_eWe.avif","width":900,"height":604,"format":"avif"}},"content":{"md":"I was talking with a friend yesterday about how I learned web dev.\n\nThey're just starting out in University, and were surprised to hear that I don't really attribute a lot of my technical knowledge to university classes.\n\nWith them in mind, I made them a quick list of the most notable web development resources I used to learn.\n\n> ℹ I'm still working on this one- it'll be updated over time.\n>\n> I may re-organize things to be more focused on the level of complexity than the format as it probably makes more sense for quick referencing.\n\n## Video Courses 📺\n\nThis is honestly where I've learned most of what I know in a \"traditional\" sense.\n\n- [Wes Bos's Learn Node](https://learnnode.com): Comprehensive course that goes over what Node is, how it works, HTML templating, basic JS bundling, etc.\n- [Wes Bos's Advanced React & GraphQL](https://advancedreact.com/): If you have some basic React knowledge, this tutorial takes it to the next level with Next.js, GraphQL, and friends!\n- [Level Up Tutorials](https://leveluptutorials.com): React, Svelte, etc: All of Scott's content on Level Up Tuts is great!\n- [Free Code Camp](https://freecodecamp.org): lots of really great courses (mostly on their YouTube channel) and even certificate programs!\n- [Kent C. Dodd's Epic React](https://epicreact.dev/): an extremely comprehensive course on react.js that takes you all the way up from fundamentals to advanced concepts.\n- [Kent C. Dodd's Testing JavaScript](https://testingjavascript.com/): a practical guide on how to test JavaScript applications that takes you all the way from static analysis, to unit and integration tests.\n\n## Podcasts 🎤\n\nI listen to podcasts to keep up on what's new & upcoming in web dev, as well as a way to nerd out about existing tech I haven't used:\n\n- [Shop Talk Show](https://shoptalkshow.com/): Just! build! WEBSITES! Dave Rupert and Chris Coyier talk about web dev stuff from a down-to-earth perspective.\n- [Syntax FM](https://syntax.fm): A relatively beginner oriented podcast hosted by Scott Tolinski, and Wes Bos.\n- [JS Party](https://changelog.com/jsparty): Talking about Javascript and the web with a \"party\" of rotating hosts.\n- [Remotely Interesting](https://www.netlify.com/blog/2020/09/01/announcing-a-remotely-interesting-podcast/): The Netlify devrel team's podcast, they're typically talking about JAMStack and a number of other webdevy things.\n\n## Projects 👩🏻‍💻\n\nThis is where most of my practical knowledge came from- and where I recommend people spend most of their time!\n\n- [Supastack](https://github.com/MykalMachon/Supastack): A mix between SubStack + Tumblr. It's definitely not complete but gave me time to learn SupaBase & Postgres.\n- [Smolcart.js](https://github.com/TinyboxSoftware/Smolcart.js): A semi-finished library used for adding Shopify products to cart via Javascript\n- [Mykal.codes](https://github.com/MykalMachon/Mykal.Codes): This website! a blog- it's been rebuilt 4-5 times in different frameworks (Next.js, Gatsby, 11ty, now Astro)\n\n## Books 📚\n\nI use books more as a reference, than anything.\n\n- [Flask Web Development](https://www.oreilly.com/library/view/flask-web-development/9781491991725/): I recently read this to learn about flask for work- it's great! Walks you through creating a modern-ish web app with Flask.\n- [Humble Bundle Books](https://www.humblebundle.com/books): Humble bundle usually puts together some sort of developer book bundle. I've got around 60 books from publishers like O'Reilly through their bundles at _amazing_ prices.\n- [Increment Magazine](https://increment.com): A _very_ high quality magazine about software development. Lots of varied, and well done articles from different disciplines of software (edit: rest in peace)\n\n## Miscellaneous ✨\n\nOther stuff I use on a regular basis\n\n- [Mozilla Developer Network](https://mdn.org): have you ever wanted docs for like every web API ever? this is it!","html":"<p>I was talking with a friend yesterday about how I learned web dev.</p><p>They're just starting out in University, and were surprised to hear that I don't really attribute a lot of my technical knowledge to university classes.</p><p>With them in mind, I made them a quick list of the most notable web development resources I used to learn.</p><blockquote><p>ℹ I'm still working on this one- it'll be updated over time.</p><p>I may re-organize things to be more focused on the level of complexity than the format as it probably makes more sense for quick referencing.</p></blockquote><h2>Video Courses 📺</h2><p>This is honestly where I've learned most of what I know in a &quot;traditional&quot; sense.</p><ul><li><a href=\"https://learnnode.com\">Wes Bos's Learn Node</a>: Comprehensive course that goes over what Node is, how it works, HTML templating, basic JS bundling, etc.</li><li><a href=\"https://advancedreact.com/\">Wes Bos's Advanced React &amp; GraphQL</a>: If you have some basic React knowledge, this tutorial takes it to the next level with Next.js, GraphQL, and friends!</li><li><a href=\"https://leveluptutorials.com\">Level Up Tutorials</a>: React, Svelte, etc: All of Scott's content on Level Up Tuts is great!</li><li><a href=\"https://freecodecamp.org\">Free Code Camp</a>: lots of really great courses (mostly on their YouTube channel) and even certificate programs!</li><li><a href=\"https://epicreact.dev/\">Kent C. Dodd's Epic React</a>: an extremely comprehensive course on react.js that takes you all the way up from fundamentals to advanced concepts.</li><li><a href=\"https://testingjavascript.com/\">Kent C. Dodd's Testing JavaScript</a>: a practical guide on how to test JavaScript applications that takes you all the way from static analysis, to unit and integration tests.</li></ul><h2>Podcasts 🎤</h2><p>I listen to podcasts to keep up on what's new &amp; upcoming in web dev, as well as a way to nerd out about existing tech I haven't used:</p><ul><li><a href=\"https://shoptalkshow.com/\">Shop Talk Show</a>: Just! build! WEBSITES! Dave Rupert and Chris Coyier talk about web dev stuff from a down-to-earth perspective.</li><li><a href=\"https://syntax.fm\">Syntax FM</a>: A relatively beginner oriented podcast hosted by Scott Tolinski, and Wes Bos.</li><li><a href=\"https://changelog.com/jsparty\">JS Party</a>: Talking about Javascript and the web with a &quot;party&quot; of rotating hosts.</li><li><a href=\"https://www.netlify.com/blog/2020/09/01/announcing-a-remotely-interesting-podcast/\">Remotely Interesting</a>: The Netlify devrel team's podcast, they're typically talking about JAMStack and a number of other webdevy things.</li></ul><h2>Projects 👩🏻‍💻</h2><p>This is where most of my practical knowledge came from- and where I recommend people spend most of their time!</p><ul><li><a href=\"https://github.com/MykalMachon/Supastack\">Supastack</a>: A mix between SubStack + Tumblr. It's definitely not complete but gave me time to learn SupaBase &amp; Postgres.</li><li><a href=\"https://github.com/TinyboxSoftware/Smolcart.js\">Smolcart.js</a>: A semi-finished library used for adding Shopify products to cart via Javascript</li><li><a href=\"https://github.com/MykalMachon/Mykal.Codes\">Mykal.codes</a>: This website! a blog- it's been rebuilt 4-5 times in different frameworks (Next.js, Gatsby, 11ty, now Astro)</li></ul><h2>Books 📚</h2><p>I use books more as a reference, than anything.</p><ul><li><a href=\"https://www.oreilly.com/library/view/flask-web-development/9781491991725/\">Flask Web Development</a>: I recently read this to learn about flask for work- it's great! Walks you through creating a modern-ish web app with Flask.</li><li><a href=\"https://www.humblebundle.com/books\">Humble Bundle Books</a>: Humble bundle usually puts together some sort of developer book bundle. I've got around 60 books from publishers like O'Reilly through their bundles at <em>amazing</em> prices.</li><li><a href=\"https://increment.com\">Increment Magazine</a>: A <em>very</em> high quality magazine about software development. Lots of varied, and well done articles from different disciplines of software (edit: rest in peace)</li></ul><h2>Miscellaneous ✨</h2><p>Other stuff I use on a regular basis</p><ul><li><a href=\"https://mdn.org\">Mozilla Developer Network</a>: have you ever wanted docs for like every web API ever? this is it!</li></ul>"}},{"id":"starting-at-ufv","url":"https://mykalmachon.com/posts/starting-at-ufv","meta":{"title":"New Job at UFV!","pubDate":"2021-05-10T00:00:00.000Z","type":"post","draft":false,"tags":["personal","career"],"description":"Moving from web developer, to systems analyst","heroImage":{"src":"/_astro/starting-at-ufv.fpMLa2iS.avif","width":1200,"height":675,"format":"avif"}},"content":{"md":"I'm starting as a Systems Analyst at the [University of the Fraser Valley (UFV)!](https://ufv.ca) 🎉\n\nnot-so-coincidentally, I'll also be graduating from UFV this winter with a Bachelor's Degree in Computer Information Systems (BCIS)\n\nI'm looking forward to being part of a team of other developers, hopefully some challenging and interesting problems, and the stability that comes from\na fulltime non-freelancing position!\n\nMore to come on those challenging and interesting problems!\nI'm sure I'll be blogging about them one way or another...","html":"<p>I'm starting as a Systems Analyst at the <a href=\"https://ufv.ca\">University of the Fraser Valley (UFV)!</a> 🎉</p><p>not-so-coincidentally, I'll also be graduating from UFV this winter with a Bachelor's Degree in Computer Information Systems (BCIS)</p><p>I'm looking forward to being part of a team of other developers, hopefully some challenging and interesting problems, and the stability that comes froma fulltime non-freelancing position!</p><p>More to come on those challenging and interesting problems!I'm sure I'll be blogging about them one way or another...</p>"}},{"id":"scaling-down","url":"https://mykalmachon.com/posts/scaling-down","meta":{"title":"Scaling Down","pubDate":"2021-05-01T00:00:00.000Z","type":"post","draft":false,"tags":["personal","career"],"description":"Why I'm scaling down my freelancing business, Tinybox","heroImage":{"src":"/_astro/scaling-down.C7j7Ht6V.avif","width":1050,"height":700,"format":"avif"}},"content":{"md":"In May of 2019, during the first few months of the Coronavirus pandemic I made the decision to start an e-commerce / web development freelancing business, Tinybox Software Development.\n\nThis was the first time I'd ever been fully self employed. I had a tPonne of fun, launched a bunch of sites, and learned _a lot_ along the way.\n\n### Why Scale Down Then?\n\n1. **Growth:** I want to keep learning and growing technically, and without challenging, weird problems for me to solve I've been feeling stagnant.\n2. **Stability:** It's extremely hard to get approved to rent an appartment without a boss someone can call. Freelancing can pay great, but it's hard to prove stability unless you've been doing it for awhile.\n3. **Burnout & Stress:** This is heavily related to No. 1, and 2 on the list above. That said, Juggling work is hard; On average I was working on 3 projects at the same time, and felt like the pace I was working at wasn't sustainable.\n\n### Is This the End?\n\n**No! I still plan to work on freelance projects, just at a much smaller scale.**\n\nI genuinely enjoy doing the work when it's not something I'm working on 25/8- so I'll likely keep doing it.\n\n[Check out the Tinybox website!](https://tinybox.dev)","html":"<p>In May of 2019, during the first few months of the Coronavirus pandemic I made the decision to start an e-commerce / web development freelancing business, Tinybox Software Development.</p><p>This was the first time I'd ever been fully self employed. I had a tPonne of fun, launched a bunch of sites, and learned <em>a lot</em> along the way.</p><h3>Why Scale Down Then?</h3><ol><li><strong>Growth:</strong> I want to keep learning and growing technically, and without challenging, weird problems for me to solve I've been feeling stagnant.</li><li><strong>Stability:</strong> It's extremely hard to get approved to rent an appartment without a boss someone can call. Freelancing can pay great, but it's hard to prove stability unless you've been doing it for awhile.</li><li><strong>Burnout &amp; Stress:</strong> This is heavily related to No. 1, and 2 on the list above. That said, Juggling work is hard; On average I was working on 3 projects at the same time, and felt like the pace I was working at wasn't sustainable.</li></ol><h3>Is This the End?</h3><p><strong>No! I still plan to work on freelance projects, just at a much smaller scale.</strong></p><p>I genuinely enjoy doing the work when it's not something I'm working on 25/8- so I'll likely keep doing it.</p><p><a href=\"https://tinybox.dev\">Check out the Tinybox website!</a></p>"}},{"id":"hidden-content-in-an-unpublished-shopify-theme","url":"https://mykalmachon.com/posts/hidden-content-in-an-unpublished-shopify-theme","meta":{"title":"Previewing hidden content in an unpublished Shopify theme","pubDate":"2020-07-08T00:00:00.000Z","type":"post","draft":false,"tags":["webdev"],"description":"When you want to preview a hidden product/post on a hidden theme things get tricky."},"content":{"md":"## The Issue\n\nYou're working on a brand new product for your Shopify store, it's set to unavailable and you & the team are working on a launch for it later this month.You're also working on a new theme that will launch alongside your product, **how are you supposed to see how that new product looks with your new theme?**\n\nYou can preview the new product, but it opens in the published theme, not the new theme you're working on...\n\n**So do you have to make your new product available and just hope no one notices?** 😥\n\n## The Solution\n\nThankfully, there's a way we can get around this!\n\n1. Open your Shopify admin to your hidden product, and click \"Preview\" at the top of the page\n2. From the product's preview page, copy the part of the URL that looks like this: `/products_preview?preview_key=your_key_goes_here`\n3. In a new tab, open the new theme you're working on as a preview as well.\n4. In the URL bar, paste the portion of the product URL we copied earlier. your new URL should look something like this `https://mystore.com/products_preview?preview_key=your_key_goes_here`.\n5. Hit enter and you should be able to preview your hidden product in your new theme ✨🎉\n\n## Not just for Products\n\nThis will work for hidden pages, and articles as well! Which is great when you're working on a landing page or a fancy new blog template!\n\nThis has been a huge pain for me when working on themes, and I just recently found this quick fix so I thought I'd share! Hopefully it helped you as well.","html":"<h2>The Issue</h2><p>You're working on a brand new product for your Shopify store, it's set to unavailable and you &amp; the team are working on a launch for it later this month.You're also working on a new theme that will launch alongside your product, <strong>how are you supposed to see how that new product looks with your new theme?</strong></p><p>You can preview the new product, but it opens in the published theme, not the new theme you're working on...</p><p><strong>So do you have to make your new product available and just hope no one notices?</strong> 😥</p><h2>The Solution</h2><p>Thankfully, there's a way we can get around this!</p><ol><li>Open your Shopify admin to your hidden product, and click &quot;Preview&quot; at the top of the page</li><li>From the product's preview page, copy the part of the URL that looks like this: <code>/products_preview?preview_key=your_key_goes_here</code></li><li>In a new tab, open the new theme you're working on as a preview as well.</li><li>In the URL bar, paste the portion of the product URL we copied earlier. your new URL should look something like this <code>https://mystore.com/products_preview?preview_key=your_key_goes_here</code>.</li><li>Hit enter and you should be able to preview your hidden product in your new theme ✨🎉</li></ol><h2>Not just for Products</h2><p>This will work for hidden pages, and articles as well! Which is great when you're working on a landing page or a fancy new blog template!</p><p>This has been a huge pain for me when working on themes, and I just recently found this quick fix so I thought I'd share! Hopefully it helped you as well.</p>"}},{"id":"tech-im-learning-this-summer-2019","url":"https://mykalmachon.com/posts/tech-im-learning-this-summer-2019","meta":{"title":"Tech I'm learning this summer","pubDate":"2019-05-13T00:00:00.000Z","type":"post","draft":false,"tags":["personal"],"description":"The tech that I want to learn in the summer of 2019"},"content":{"md":"As of April 28th, 2019, I finished my third year at University! 🎉\n\nWhile I am going back to work full time, I should still have some more time on evenings and weekends. In true student fashion, I'm planning to fill some of this time learning and sharpening my tech skills. I've got a few technologies in particular that I would like to get to know a little better.\n\n## Things I Want To Learn\n\n1. **React Hooks** : While I realize that the traditional component model isn't leaving react anytime soon, I still feel like learning hooks is a good step forward and could benefit my overall understanding of react. Overall, from what I have seen anyways, Hooks likes like a pretty fundamental shift in structure so it should be interesting to see how I adapt to that.\n\n2. **Typescript** : This one has been on the backburner for me _forever_. I work primarily with heavily typed OOP languages in University (Mostly Java) and am interested in how javascript will feel within a more strongly typed environment with interfaces & generics etc. I'll also be interested to see how typescript will end up fitting into React (especially with hooks).\n\n3. **Integration Testing** : I've tried to pick up testing libraries over and over again over the last year but just haven't been able to commit to putting the time in and learning what I need to make testing natural for me. I'm particularly interested in integration testing with jest but I feel that I should probably try and pick something up to test node apps as well.\n\n4. **VIM** : VIM is the tech I'm most nervous to attempt to learn but also the most excited by because it requires _so much buy in_. From what I've heard, to really get the speed gains you need to make vim worth it, you really need to immerse yourself in it. That said, until I get the proficiency I'll need to be faster in VIM, I'll probably be slow and inaccurate which will drive me insane. I'm also concerned about losing my snippets and prettier support that I have in VScode so. I'm not 100% sure on that level either. Maybe there is some way I can get all of those things into VIM but I'm so uneducated at this point I'll just have to see.\n\n5. **CSS Grid :** I feel really behind the curve on this one. I've seen how easy it is to get some really awesome layouts together with CSS grid once you put the time in but I'm really having trouble finding the motivation when I've been using flexbox for so long. however, I hope that I will be able to finally start with grid this summer.\n\n## Strategies For Learning\n\nI learn best by doing so I have a few resources and strategies that I'll be using to make sure I learn this tech the best I can.\n\n1. **Tutorials & Talks :** I usually use tutorials or tech talks as an intro into a subject and go from there. In the past, I've used tutorials from Wes Bos (which are usually more comprehensive and long-form. Good for going in-depth) or Scott Tolinski (Most tutorials can be completed in a day or two and give you a really strong understanding of concepts overall). Scott has some courses on Typescript, Testing and React Hooks that I will most likely be taking on weekends to get a good basic understanding.\n\n2. **Building Things** : As i said, I always learn best by building things so I will probably end up building some sort of basic application using a mixture of the tech listed above (maybe a simple budgeting app? a style guide for my website/projects? either way I'm sure I will post about it here). Honestly, this strategy will probably account for most of my learning; it usually does anyway.\n\nHopefully, between the tutorials and whatever little project I decide to put together, I should be able to master these over the summer. I'll probably do a few updates here throughout the next 4 months to keep track of my progress and be a little bit more accountable for it.\n\nIf any of you have any tips, resources, advice, or just want to talk about the tech mentioned here comment down below! I'd love to talk about any of the topics in this article!","html":"<p>As of April 28th, 2019, I finished my third year at University! 🎉</p><p>While I am going back to work full time, I should still have some more time on evenings and weekends. In true student fashion, I'm planning to fill some of this time learning and sharpening my tech skills. I've got a few technologies in particular that I would like to get to know a little better.</p><h2>Things I Want To Learn</h2><ol><li><p><strong>React Hooks</strong> : While I realize that the traditional component model isn't leaving react anytime soon, I still feel like learning hooks is a good step forward and could benefit my overall understanding of react. Overall, from what I have seen anyways, Hooks likes like a pretty fundamental shift in structure so it should be interesting to see how I adapt to that.</p></li><li><p><strong>Typescript</strong> : This one has been on the backburner for me <em>forever</em>. I work primarily with heavily typed OOP languages in University (Mostly Java) and am interested in how javascript will feel within a more strongly typed environment with interfaces &amp; generics etc. I'll also be interested to see how typescript will end up fitting into React (especially with hooks).</p></li><li><p><strong>Integration Testing</strong> : I've tried to pick up testing libraries over and over again over the last year but just haven't been able to commit to putting the time in and learning what I need to make testing natural for me. I'm particularly interested in integration testing with jest but I feel that I should probably try and pick something up to test node apps as well.</p></li><li><p><strong>VIM</strong> : VIM is the tech I'm most nervous to attempt to learn but also the most excited by because it requires <em>so much buy in</em>. From what I've heard, to really get the speed gains you need to make vim worth it, you really need to immerse yourself in it. That said, until I get the proficiency I'll need to be faster in VIM, I'll probably be slow and inaccurate which will drive me insane. I'm also concerned about losing my snippets and prettier support that I have in VScode so. I'm not 100% sure on that level either. Maybe there is some way I can get all of those things into VIM but I'm so uneducated at this point I'll just have to see.</p></li><li><p><strong>CSS Grid :</strong> I feel really behind the curve on this one. I've seen how easy it is to get some really awesome layouts together with CSS grid once you put the time in but I'm really having trouble finding the motivation when I've been using flexbox for so long. however, I hope that I will be able to finally start with grid this summer.</p></li></ol><h2>Strategies For Learning</h2><p>I learn best by doing so I have a few resources and strategies that I'll be using to make sure I learn this tech the best I can.</p><ol><li><p><strong>Tutorials &amp; Talks :</strong> I usually use tutorials or tech talks as an intro into a subject and go from there. In the past, I've used tutorials from Wes Bos (which are usually more comprehensive and long-form. Good for going in-depth) or Scott Tolinski (Most tutorials can be completed in a day or two and give you a really strong understanding of concepts overall). Scott has some courses on Typescript, Testing and React Hooks that I will most likely be taking on weekends to get a good basic understanding.</p></li><li><p><strong>Building Things</strong> : As i said, I always learn best by building things so I will probably end up building some sort of basic application using a mixture of the tech listed above (maybe a simple budgeting app? a style guide for my website/projects? either way I'm sure I will post about it here). Honestly, this strategy will probably account for most of my learning; it usually does anyway.</p></li></ol><p>Hopefully, between the tutorials and whatever little project I decide to put together, I should be able to master these over the summer. I'll probably do a few updates here throughout the next 4 months to keep track of my progress and be a little bit more accountable for it.</p><p>If any of you have any tips, resources, advice, or just want to talk about the tech mentioned here comment down below! I'd love to talk about any of the topics in this article!</p>"}},{"id":"preparing-for-battlesnake","url":"https://mykalmachon.com/posts/preparing-for-battlesnake","meta":{"title":"Preparing for Battlesnake","pubDate":"2019-02-18T00:00:00.000Z","type":"post","draft":false,"tags":["tech"],"description":"Lets review my goals for this years Battlesnake competition.","heroImage":{"src":"/_astro/preparing-for-battlesnake.kcK2JxOT.avif","width":652,"height":653,"format":"avif"}},"content":{"md":"This year's Battlesnake competition takes place on March 2nd 2019 at the Victoria Convention Center and I've got quite a bit of preparation to do before then.\n\n## But wait a second... what's Battlesnake?\n\nIn the organizer's words:\n\n> Battlesnake is a fun way to practice programming and learn new skills. Groups and individuals build AI bots to play the game Snake using modern, real-world technology. Read the docs, deploy your snake to a live web server, and join other programmers trying be the last snake slithering.\n>\n> - ([Battlesnake.io](https://battlesnake.io))\n\nIf you've ever played the classic game snake on your phone or computer, you know exactly what a Battlesnake arena looks like.\n\nJust imagine instead of one snake on the screen, there are 4-5 of them. All fighting for the same food and space with the best programmed snake taking home a cash prize and the esteemed title of Battlesnake Champ.\n\nSounds fun right? I thought so! If you're still not convinced, [here's a video](https://www.youtube.com/watch?v=ygvQds0C1X8)\n\n## My goals for Battlesnake this year\n\nSince I've got the fundamentals down, I'm taking Battlesnake as an opportunity to spend some time sharpening my path finding skills and programming a snake that can at least sort of stand on its own two, metaphorical, legs.\n\nWhile, in a perfect world, I'd be spending hours on this a night, I've got midterms the week of and the week after Battlesnake so I'm going to have to prioritize a little bit.\n\n### Priority #1: Self Sustaining\n\nThis is the very bare minimum I'd like to get my snake AI to. At this level my snake should be able to:\n\n1. Not kill itself by running into its own body / other snakes / walls...\n2. Be able to find a route to food in the area and determine whether it can do so before any other snakes.\n3. As a result of 1 + 2, be able to be self-sustaining if it is the only snake in an arena.\n\n### Priority #2: Basic Path finding\n\nThis is where I realistically feel that I should be able to get the snake to before the competition.\nAt this level my snake should be able to:\n\n1. Do everything that a self-sustaining snake could do\n2. Have some sort of intrinsic goal. For example, always be on the move for food, or always be trying to trap other snakes.\n3. Have a basic weighting system where the snake can determine which move would be best and act on that.\n\n### Priority #3: Getting Fancy\n\nThese are the things I think would be really cool to play around with, but doubt I will actually get to implement. At this level my snake should be able to:\n\n1. Do everything a basic path finding snake could do.\n2. Be operating off a trained neural network. Most likely something built through OpenAI or Clarifai\n3. Capability to autonomously improve or \"learn\" after each match through the use of machine learning\n\nI've never used any AI tech beyond using some very simple pre-trained facial recognition models. This would be a tonne of fun to play around with, but may be a little too far out of my wheelhouse to be achievable in two weeks.\n\n## Closing thoughts\n\nThis is actually my second year attending BattleSnake. I first attended in 2017, my first year of university. I don't think my team and I could even get the snake to change directions. We were all pretty inexperienced developers at the time and had never worked with, or even heard of, tools like Node.js or Heroku.\n\nDespite our lack of knowledge, We had a tonne of fun! The event's atmosphere was awesome and I learned more about real-world development in that 5-6 hour event than I had all year in class. I remember feeling so proud that we even got our snake to respond with a 200 to the server.\n\nIt's crazy to think that just two years ago I knew absolutely nothing about any of the technology I use on an almost daily basis today. I sometimes feel like I'm constantly losing a race with technology; Like I can never learn enough, fast enough, to feel caught up and knowledgeable.\n\nThat said, Being able to look back on my skills in 2017 and contrast them to my skills today through the lens of something like Battlesnake has really inspired me to prove to myself just how far I have come in that time. I can't wait to start this years snake!\n\n> Update post-Battlesnake🐍\n>\n> My snake AI went made it into the second round of the beginner's bracket 🎉! I’ve definitely made progress and I’m looking forward to making even more come next year’s Battlesnake.","html":"<p>This year's Battlesnake competition takes place on March 2nd 2019 at the Victoria Convention Center and I've got quite a bit of preparation to do before then.</p><h2>But wait a second... what's Battlesnake?</h2><p>In the organizer's words:</p><blockquote><p>Battlesnake is a fun way to practice programming and learn new skills. Groups and individuals build AI bots to play the game Snake using modern, real-world technology. Read the docs, deploy your snake to a live web server, and join other programmers trying be the last snake slithering.</p><ul><li>(<a href=\"https://battlesnake.io\">Battlesnake.io</a>)</li></ul></blockquote><p>If you've ever played the classic game snake on your phone or computer, you know exactly what a Battlesnake arena looks like.</p><p>Just imagine instead of one snake on the screen, there are 4-5 of them. All fighting for the same food and space with the best programmed snake taking home a cash prize and the esteemed title of Battlesnake Champ.</p><p>Sounds fun right? I thought so! If you're still not convinced, <a href=\"https://www.youtube.com/watch?v=ygvQds0C1X8\">here's a video</a></p><h2>My goals for Battlesnake this year</h2><p>Since I've got the fundamentals down, I'm taking Battlesnake as an opportunity to spend some time sharpening my path finding skills and programming a snake that can at least sort of stand on its own two, metaphorical, legs.</p><p>While, in a perfect world, I'd be spending hours on this a night, I've got midterms the week of and the week after Battlesnake so I'm going to have to prioritize a little bit.</p><h3>Priority #1: Self Sustaining</h3><p>This is the very bare minimum I'd like to get my snake AI to. At this level my snake should be able to:</p><ol><li>Not kill itself by running into its own body / other snakes / walls...</li><li>Be able to find a route to food in the area and determine whether it can do so before any other snakes.</li><li>As a result of 1 + 2, be able to be self-sustaining if it is the only snake in an arena.</li></ol><h3>Priority #2: Basic Path finding</h3><p>This is where I realistically feel that I should be able to get the snake to before the competition.At this level my snake should be able to:</p><ol><li>Do everything that a self-sustaining snake could do</li><li>Have some sort of intrinsic goal. For example, always be on the move for food, or always be trying to trap other snakes.</li><li>Have a basic weighting system where the snake can determine which move would be best and act on that.</li></ol><h3>Priority #3: Getting Fancy</h3><p>These are the things I think would be really cool to play around with, but doubt I will actually get to implement. At this level my snake should be able to:</p><ol><li>Do everything a basic path finding snake could do.</li><li>Be operating off a trained neural network. Most likely something built through OpenAI or Clarifai</li><li>Capability to autonomously improve or &quot;learn&quot; after each match through the use of machine learning</li></ol><p>I've never used any AI tech beyond using some very simple pre-trained facial recognition models. This would be a tonne of fun to play around with, but may be a little too far out of my wheelhouse to be achievable in two weeks.</p><h2>Closing thoughts</h2><p>This is actually my second year attending BattleSnake. I first attended in 2017, my first year of university. I don't think my team and I could even get the snake to change directions. We were all pretty inexperienced developers at the time and had never worked with, or even heard of, tools like Node.js or Heroku.</p><p>Despite our lack of knowledge, We had a tonne of fun! The event's atmosphere was awesome and I learned more about real-world development in that 5-6 hour event than I had all year in class. I remember feeling so proud that we even got our snake to respond with a 200 to the server.</p><p>It's crazy to think that just two years ago I knew absolutely nothing about any of the technology I use on an almost daily basis today. I sometimes feel like I'm constantly losing a race with technology; Like I can never learn enough, fast enough, to feel caught up and knowledgeable.</p><p>That said, Being able to look back on my skills in 2017 and contrast them to my skills today through the lens of something like Battlesnake has really inspired me to prove to myself just how far I have come in that time. I can't wait to start this years snake!</p><blockquote><p>Update post-Battlesnake🐍</p><p>My snake AI went made it into the second round of the beginner's bracket 🎉! I’ve definitely made progress and I’m looking forward to making even more come next year’s Battlesnake.</p></blockquote>"}}]