about summary refs log tree commit diff
path: root/users/wpcarro/website/blog
diff options
context:
space:
mode:
Diffstat (limited to 'users/wpcarro/website/blog')
-rw-r--r--users/wpcarro/website/blog/.skip-subtree1
-rw-r--r--users/wpcarro/website/blog/default.nix47
-rw-r--r--users/wpcarro/website/blog/fragments/.skip-subtree0
-rw-r--r--users/wpcarro/website/blog/fragments/post.html8
-rw-r--r--users/wpcarro/website/blog/fragments/posts.html10
-rw-r--r--users/wpcarro/website/blog/posts.nix116
-rw-r--r--users/wpcarro/website/blog/posts/auto-reboot-nixos.md40
-rw-r--r--users/wpcarro/website/blog/posts/cell-phone-experiment.md274
-rw-r--r--users/wpcarro/website/blog/posts/csharp-unused-variables.md38
-rw-r--r--users/wpcarro/website/blog/posts/git-filter-repo-note.md59
-rw-r--r--users/wpcarro/website/blog/posts/git-rev-refs.md85
-rw-r--r--users/wpcarro/website/blog/posts/importing-subtrees.md147
-rw-r--r--users/wpcarro/website/blog/posts/nginx-curl-note.md5
-rw-r--r--users/wpcarro/website/blog/posts/nix-env-note.md33
-rw-r--r--users/wpcarro/website/blog/posts/nix-shell-note.md50
-rw-r--r--users/wpcarro/website/blog/posts/nixos-disk-full-note.md113
-rw-r--r--users/wpcarro/website/blog/posts/quassel-google-vm.md34
-rw-r--r--users/wpcarro/website/blog/posts/restic.md91
-rw-r--r--users/wpcarro/website/blog/posts/send-mail-as-2fa.md43
-rw-r--r--users/wpcarro/website/blog/posts/ssh-oddities.md59
-rw-r--r--users/wpcarro/website/blog/posts/tcp-tunneling-note.md63
-rw-r--r--users/wpcarro/website/blog/posts/tee-time.md16
22 files changed, 1332 insertions, 0 deletions
diff --git a/users/wpcarro/website/blog/.skip-subtree b/users/wpcarro/website/blog/.skip-subtree
new file mode 100644
index 0000000000..3a9dbd4d2b
--- /dev/null
+++ b/users/wpcarro/website/blog/.skip-subtree
@@ -0,0 +1 @@
+Subdirectories contain blog posts and static assets only
\ No newline at end of file
diff --git a/users/wpcarro/website/blog/default.nix b/users/wpcarro/website/blog/default.nix
new file mode 100644
index 0000000000..27541b0f39
--- /dev/null
+++ b/users/wpcarro/website/blog/default.nix
@@ -0,0 +1,47 @@
+{ depot, lib, pkgs, ... }:
+
+with depot.nix.yants;
+
+let
+  inherit (builtins) hasAttr filter readFile;
+  inherit (depot.web.blog) post includePost renderPost;
+  inherit (depot.users.wpcarro.website) domain renderTemplate withBrand;
+  inherit (lib.lists) sort;
+
+  config = {
+    name = "bill and his blog";
+    baseUrl = "https://${domain}/blog";
+    staticUrl = "https://static.tvl.fyi/latest";
+    footer = "";
+  };
+
+  posts = sort (x: y: x.date > y.date)
+    (filter includePost (list post (import ./posts.nix)));
+
+  rendered = pkgs.runCommand "blog-posts" { } ''
+    mkdir -p $out
+
+    ${lib.concatStringsSep "\n" (map (post:
+      "cp ${renderPost config post} $out/${post.key}.html"
+    ) posts)}
+  '';
+
+  formatDate = date: readFile (pkgs.runCommand "date" { } ''
+    date --date='@${toString date}' '+%B %e, %Y' > $out
+  '');
+
+  postsHtml = renderTemplate ./fragments/posts.html {
+    postsHtml = lib.concatStringsSep "\n" (map toPostHtml posts);
+  };
+
+  toPostHtml = post: readFile (renderTemplate ./fragments/post.html {
+    postUrl = "${config.baseUrl}/posts/${post.key}.html";
+    postTitle = post.title;
+    postDate = formatDate post.date;
+  });
+in
+pkgs.runCommand "blog" { } ''
+  mkdir -p $out
+  cp ${withBrand (readFile postsHtml)} $out/index.html
+  cp -r ${rendered} $out/posts
+''
diff --git a/users/wpcarro/website/blog/fragments/.skip-subtree b/users/wpcarro/website/blog/fragments/.skip-subtree
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/users/wpcarro/website/blog/fragments/.skip-subtree
diff --git a/users/wpcarro/website/blog/fragments/post.html b/users/wpcarro/website/blog/fragments/post.html
new file mode 100644
index 0000000000..2741292aa9
--- /dev/null
+++ b/users/wpcarro/website/blog/fragments/post.html
@@ -0,0 +1,8 @@
+<li class="pb-6 md:pb-10 text-md md:text-xl">
+  <h2 class="text-bold">
+    <a class="font-bold text-blue-600 hover:underline" href="@postUrl@">
+      @postTitle@
+    </a>
+  </h2>
+  <p class="text-gray-500">@postDate@</p>
+</li>
diff --git a/users/wpcarro/website/blog/fragments/posts.html b/users/wpcarro/website/blog/fragments/posts.html
new file mode 100644
index 0000000000..a85a4b7110
--- /dev/null
+++ b/users/wpcarro/website/blog/fragments/posts.html
@@ -0,0 +1,10 @@
+<div class="max-w-sm md:max-w-prose mx-auto">
+  <section class="pt-8 pb-14">
+    <p class="font-bold pb-3 text-xl">
+      Personal blog by <a class="font-bold text-blue-600 hover:underline" href="@homepage@">Bill</a>.
+    </p>
+    <p class="text-gray-500">&gt; Half-baked musings lossily encoded.</p>
+    <p class="text-gray-500">&gt; - misc reviewer</p>
+  </section>
+  <ul>@postsHtml@</ul>
+</div>
diff --git a/users/wpcarro/website/blog/posts.nix b/users/wpcarro/website/blog/posts.nix
new file mode 100644
index 0000000000..31fb0c83d8
--- /dev/null
+++ b/users/wpcarro/website/blog/posts.nix
@@ -0,0 +1,116 @@
+# To format the date, run:
+#   $ date -d "today" +%s
+[
+  {
+    key = "cell-phone-experiment";
+    title = "Cell Phone Experiment";
+    date = 1585800000;
+    content = ./posts/cell-phone-experiment.md;
+    draft = false;
+  }
+  {
+    key = "quassel-google-vm";
+    title = "IRC, GCP, and NixOS";
+    date = 1640404800;
+    content = ./posts/quassel-google-vm.md;
+    draft = true;
+  }
+  {
+    key = "send-mail-as-2fa";
+    title = "2FA and Gmail's \"Send mail as\"";
+    date = 1641497483;
+    content = ./posts/send-mail-as-2fa.md;
+    draft = false;
+  }
+  {
+    key = "auto-reboot-nixos";
+    title = "Automatically Reboot NixOS";
+    date = 1643666914;
+    content = ./posts/auto-reboot-nixos.md;
+    draft = false;
+  }
+  {
+    key = "csharp-unused-variables";
+    title = "Unused Variables Broke Prod";
+    date = 1655840877;
+    content = ./posts/csharp-unused-variables.md;
+    draft = false;
+  }
+  {
+    key = "restic-field-guide";
+    title = "Beginner's Field Guide to restic";
+    date = 1656645093;
+    content = ./posts/restic.md;
+    draft = false;
+  }
+  {
+    key = "tee-time";
+    title = "tee time";
+    date = 1657597870;
+    content = ./posts/tee-time.md;
+    draft = false;
+  }
+  {
+    key = "ssh-oddities";
+    title = "SSH Oddities";
+    date = 1657647994;
+    content = ./posts/ssh-oddities.md;
+    draft = false;
+  }
+  {
+    key = "nix-shell";
+    title = "nix-shell (note to self)";
+    date = 1664902186;
+    content = ./posts/nix-shell-note.md;
+    draft = false;
+  }
+  {
+    key = "git-filter-repo-note";
+    title = "git-filter-repo (note to self)";
+    date = 1665163559;
+    content = ./posts/git-filter-repo-note.md;
+    draft = false;
+  }
+  {
+    key = "nixos-disk-full-note";
+    title = "disk full (note to self)";
+    date = 1666801882;
+    content = ./posts/nixos-disk-full-note.md;
+    draft = false;
+  }
+  {
+    key = "git-rev-refs";
+    title = "git revision numbers as refs (note to self)";
+    date = 1666823030;
+    content = ./posts/git-rev-refs.md;
+    draft = false;
+  }
+  {
+    key = "import-subtree-checklist";
+    title = "Checklist for Importing Subtrees";
+    date = 1666903846;
+    content = ./posts/importing-subtrees.md;
+    draft = false;
+  }
+  {
+    key = "nix-env-note";
+    title = "nix-env (note to self)";
+    date = 1667343279;
+    content = ./posts/nix-env-note.md;
+    draft = false;
+  }
+  {
+    key = "nginx-virtual-host-note";
+    title = "Nginx Virtual Host (note to self)";
+    date = 1668448541;
+    content = ./posts/nginx-curl-note.md;
+    draft = false;
+  }
+  {
+    key = "tcp-tunneling-note";
+    title = "TCP Tunneling (note to self)";
+    date = 1668709613;
+    content = ./posts/tcp-tunneling-note.md;
+    draft = false;
+  }
+]
diff --git a/users/wpcarro/website/blog/posts/auto-reboot-nixos.md b/users/wpcarro/website/blog/posts/auto-reboot-nixos.md
new file mode 100644
index 0000000000..24474e6dfe
--- /dev/null
+++ b/users/wpcarro/website/blog/posts/auto-reboot-nixos.md
@@ -0,0 +1,40 @@
+## Show me the codes
+
+Regularly rebooting machines can be a useful, hygienic practice, but quite
+frankly I cannot be relied on to remember to regularly reboot my machine.
+
+Let's free-up some wetware-RAM by automating this with Nix. The following
+addition to your `configuration.nix` will schedule daily reboots at `03:00`:
+
+```nix
+systemd.timers.auto-reboot = {
+  wantedBy = [ "timers.target" ];
+  timerConfig = {
+    OnCalendar = "*-*-* 03:00:00";
+    Unit = "reboot.target";
+  };
+};
+```
+
+If you want to fiddle with the date format, `systemd-analyze` is your friend:
+
+```shell
+λ systemd-analyze calendar '*-*-* 03:00:00'
+Normalized form: *-*-* 03:00:00
+    Next elapse: Tue 2022-02-01 03:00:00 PST
+       (in UTC): Tue 2022-02-01 11:00:00 UTC
+       From now: 12h left
+```
+
+After calling `nixos-rebuild switch`, you can verify that `systemd` started the
+timer with:
+
+```shell
+λ systemctl list-timers auto-reboot
+#  output omitted because I'm writing this from a different machine
+```
+
+## That's all, folks!
+
+I wanted to keep this post short-and-sweet, to build the habit of posting more
+regularly. Hopefully someone out there found this useful.
diff --git a/users/wpcarro/website/blog/posts/cell-phone-experiment.md b/users/wpcarro/website/blog/posts/cell-phone-experiment.md
new file mode 100644
index 0000000000..f781a60873
--- /dev/null
+++ b/users/wpcarro/website/blog/posts/cell-phone-experiment.md
@@ -0,0 +1,274 @@
+### TL;DR
+
+I will not use my cell phone during March to learn more about how much I depend
+on it.
+
+### Explore/Exploit
+
+Ever since I read Charles Duhigg's book, [The Power of Habit][poh], I try to
+habituate as many aspects of my life as I can.
+
+Making my bed every morning is an example of a habit -- so too is flossing at
+night before bed.
+
+The *exploit* axis of the [explore/exploit tradeoff][exp-exp] endows habits with
+their power. Brian Christian and Tom Griffiths explain this concept more clearly
+than I can in Chapter 2 of their exceptional book, [Algorithms to Live
+By][algos].
+
+Habits are powerful, but if I overly exploit an activity, I may settle on a
+local optimum in lieu of settling on a global optimum; these are the opportunity
+costs of exploiting (i.e. habits) versus exploring (i.e. spontaneity).
+
+But what if it was possible to habituate exploration?
+
+### Monthly challenges
+
+Every month since October 2018, I challenge myself to try something new. In the
+past, monthly challenges have been things like:
+- sign up and take Brazilian Jiu Jitsu classes
+- buy a guitar and learn [Freight Train](https://www.youtube.com/watch?v=IUK8emiWabU)
+- study Italian
+- learn a handstand
+
+Typically for an activity to qualify as a challenge, I must spend *at least
+fifteen minutes* working on it *at least five days* each week.
+
+This month (i.e. March) I'm challenging myself to avoid using my cell phone.
+
+My parents gave me a cell phone when when I was a freshman in High School; I was
+14 years old. I am now 28, so I have been using a cell phone semi-daily for over
+a decade.
+
+While I enjoy the convenience that my cell phone provides me, I am curious to
+suspend my usage to more clearly understand how much I depend on it...
+
+### April
+
+Now it is early April, and I completed March's challenge. So how was it?
+
+Below I outline the parts of using a cell phone that I missed and the parts that
+I surprisingly did not miss. I will also mention the two times that I used my
+cell phone and why.
+
+The first three things that I missed all relate to time.
+
+#### Timekeeping
+
+On the first day I realized that unless I was near a computer, I did not know
+what time it was.
+
+I exclusively use my cell phone as my watch; I do not wear a watch. To adapt, I
+started looking for clocks around my office and while I was taking public
+transportation. Thankfully London posts the current time on the digital train
+schedules. This oriented me while I was traveling, which was also when I needed
+to know the time the most.
+
+Most of the month, however, I never precisely knew what time it was.
+
+#### Alarm clocks
+
+While I anticipated living without an alarm clock prior to the experiment, I
+decided against buying a substitute. Prior to this month, I theorized that
+morning alarms probably disrupt the quality of my sleep. If I'm tired, shouldn't
+I keep sleeping?
+
+As the month progressed and my 24 hour day morphed into a 25 hour day, I learned
+that I would prefer waking up at a set time every day and synchronize my
+schedule with the rest of my timezone.
+
+I am still unsure if alarm clocks are helpful in the long-term. I would have
+slept with the curtains drawn to allow the morning sun to wake me
+up. Unfortunately, I live on the ground floor nearby a brightly shining street
+lamp that spills into my bedroom.
+
+If I lived somewhere more remote (perhaps even a suburb would do) I would like
+to repeat an experiment where I live for a month without an alarm clock.
+
+For now, I must return to the Temple of Chronology and supplicate until Father
+Time restores my sanity.
+
+#### Timers
+
+Using timers motivates me to do a bunch of short tasks like cleaning my flat for
+fifteen minutes, stretching, or reading before bed. Thankfully, I already owned
+a physical timer that I keep in my kitchen. This replaced the timer on my phone
+without disrupting my routine.
+
+#### Maps
+
+Speaking of being disoriented, what about living without maps software?  On the
+few occasions where I traveled somewhere that was unfamiliar to me, I had to
+memorize the directions from my computer before I departed.
+
+At least I didn't need to visit gas stations or museums to buy trifold tourist
+maps...
+
+I once left my office mistakenly assuming that I would download the directions
+to my destination while commuting. As I awaited the office elevator, I realized
+that I had no clue where I was heading.
+
+Thankfully I wasn't far from the safety, comfort, and familiarity of my desktop
+computer -- with its fatty WiFi connection. In no time I was studying Google
+Maps in my web browser and memorizing the directions.
+
+Overall this was hardly an inconvenience, and I think I even enjoyed
+stress-testing my memory: a job that I so often outsource to hardware.
+
+#### Rendezvouses
+
+A couple of times I met friends in various parts of the city. Organizing these
+particular rendezvouses was a novel (read: anachronistic) experience. For all
+you young whippersnappers reading, take out your stone tablets and chisels. I'm
+going to explain how this works:
+
+First I would tell my friends where and when to meet me. I emphasized that I
+would be quite helpless to any changes they might make to the plans once I began
+commuting, which made the commitments unusually more binding.
+
+On one occasion my friend -- who is characteristically prompt, and even chides
+me for when I'm late -- was twenty minutes late for our engagement. My friend is
+German, so I figured I should do my civic duty of alerting the German embassy
+that my friend had broken German code, is obscenely late, and should therefore
+hand-in his passport and renounce his citizenship. After awhile my conscience
+advised me to reconsider.
+
+It was fortunate for both of us that I did not fully understand how late he was.
+Remember: I didn't know what time it was.
+
+I decided this would be a useful opportunity to test my patience, so I loitered
+for twenty minutes outside of our meeting point. He couldn't text me to tell me
+that he was late. I couldn't listen to music, call family or friends, or partake
+in any of the other rituals that modern-day loiterers observe to pass the
+time. In the end he showed up, and it was scarcely a big deal.
+
+This experience made me wonder what the policy for abandoning plans is when
+someone is running late. Before smart phones, how long did people wait? Maybe
+the proper etiquette is to wait long enough for you to absolve yourself of the
+guilt of flaking in the unlikely event that your friend arrives shortly after
+you leave.
+
+So... thirty minutes? I'll call my grandma tomorrow and ask her.
+
+#### Boredom
+
+My phone couldn't entertain me while I queued at the grocery store. Same too
+when I commuted.
+
+I also found myself listening to less music than I usually do. I decided to read
+to occupy the void when I could; this helped me progress towards completing this
+year's [GoodReads challenge][gr-annual].
+
+### Cheating
+
+I used my phone twice during March.
+
+1. Once to use my bank's mobile app to internationally transfer money from my
+   U.K. account to my U.S. account. I could have used [TransferWise's][tw]
+   website, but I didn't.
+2. Another time I used my phone to take pictures of an item that I wanted to
+   sell on [CraigsList][cl]. I could have and perhaps should have used my laptop's
+   webcam, but at the time, I didn't want to. I am accustomed to using my phone
+   to take pictures, and I wanted to sell something.
+
+In both of these cases, prior habits eroded my resolve to stay the course. These
+are useful reminders that habits don't distinguish between helpful and hurtful;
+they just exist.
+
+In total I would estimate that I spent somewhere around fifteen minutes using
+my phone in March. While not perfect:
+
+> Better a diamond with a flaw than a pebble without (Confucius)
+
+### Substitution = Dilution
+
+While the explicit goal of this challenge was to avoid using my cell phone for a
+month, the implicit goal was to disengage from many of the
+[nonessential][essentialism] activities that compete for my attention.
+
+There were some activities that I didn't miss while living without a cell
+phone. This wasn't because I don't value these activities, but rather because I
+can adequately replace them with alternatives.
+
+For texting and making phone calls, I used [Telegram][wtf-telegram]. Telegram
+helped me sustain a healthy relationship with my girlfriend while still honoring
+the constraints of the challenge.
+
+While I appreciated the convenience Telegram provided, I felt that I remained
+about as [available][wtf-availability] during March as I was in February. If I
+ever experiment with drastically reducing my availability, I will be more
+explicit about my objectives.
+
+### Distraction displacement (whack-a-mole)
+
+Because cell phones and other electronics have conditioned my behavior, I
+habitually avoid boredom and seek entertainment. On its face this may not sound
+like a harmful practice. My generation drills the aphorism "you only live once",
+suggesting that we may want to embrace a Hedonistic lifestyle.
+
+Hedonism may or may not be a wise way to play the game of Life. All I know is
+that living a life in which I am often stimulated but proportionately distracted
+appeals increasingly less to me as time progresses.
+
+During March I noticed that once I freed my attention from sending/receiving
+texts, my brain quickly reassigned my attention to maintaining a vigil over the
+other social media outposts that I maintain.
+
+I should also admit that I habitually checked Telegram now that it served as my
+new cell phone. Didn't see that coming...
+
+In another case, once I discovered that I could use Instagram in a web browser
+instead of on my phone, I filled my newfound time and attention on
+[Instagram.com][ig] (don't click!): displacing the time that I spent on an app
+on my phone to time that I spent on a website in a web browser.
+
+Holy whack-a-mole!
+
+Halfway through the month, I wrote a [program to block websites][url-blocker] on
+my computer. Surprisingly this worked and forced me to more deliberately fill
+this hard-fought, foreign time with other activities.
+
+### Easy come, easy go?
+
+As the saying for making friends goes, "easy come, easy go", implying that
+friendships that you easily form can just as easily be destroyed.
+
+Habits invert this creation/destruction relationship. In my experience "easy
+come" implies "difficult to go".
+
+For example, I could easily form the habit of eating chocolate around 15:00 at
+work; curbing this habit would require more effort. When I compare this to the
+difficulty I experienced habituating a meditation practice, and how easily I
+can dislodge my meditation practice, it seems to me that the laws of habits
+dictate "easy come, difficult go; difficult come, easy go".
+
+I suspect that while my cravings for using a cell phone have temporarily ceased,
+they will return shortly after I start using my cell phone. And as if nothing
+happened, I return to where I was at the end of February just before I decided
+to curb my cell phone usage.
+
+Because of this, I'm planning on keeping my cell phone in my closet where I
+stored it during the month of March. As noted, enough substitutes exist for me
+to live a mostly normal life: one where I am not unnecessarily straining the
+relationships of my friends and my family. After all these are the people who
+matter most to me and those who drive me to explore new ways to improve.
+
+I recognize that the "self" in self-experimentation is a misnomer. Can you truly
+conduct an [N of 1 trial][nof1]? My decisions impact the people in my life, and
+I want to thank everyone who tolerates my eccentric and oftentimes annoying
+experimentation.
+
+Thank you for reading.
+
+[pod]: https://www.goodreads.com/book/show/12609433-the-power-of-habit
+[exp-exp]: https://en.wikipedia.org/wiki/Multi-armed_bandit
+[algos]: https://www.goodreads.com/book/show/25666050-algorithms-to-live-by
+[gr-annual]: https://www.goodreads.com/user_challenges/19737920
+[cl]: http://craigslist.com
+[tw]: https://transferwise.com
+[url-blocker]: https://github.com/wpcarro/url-blocker
+[wtf-telegram]: https://telegram.org
+[wtf-availability]: https://landing.google.com/sre/sre-book/chapters/availability-table
+[essentialism]: https://www.goodreads.com/book/show/18077875-essentialism
+[ig]: https://instagram.com
+[nof1]: https://en.wikipedia.org/wiki/N_of_1_trial
diff --git a/users/wpcarro/website/blog/posts/csharp-unused-variables.md b/users/wpcarro/website/blog/posts/csharp-unused-variables.md
new file mode 100644
index 0000000000..a5b62647bc
--- /dev/null
+++ b/users/wpcarro/website/blog/posts/csharp-unused-variables.md
@@ -0,0 +1,38 @@
+**Problem**: This morning we broke production because (believe it or not) an
+unused variable went undetected.
+
+**Solution**: Consume the variable in the relevant codepath.
+
+**Further AI**: Treat unused variables as errors (which will block CI).
+
+## Warning/Disclaimer
+
+I am not a C# programmer. I know close to nothing about C#. But at `$WORK`, one
+of our codebases is written in C#, so occasionally I interface with it.
+
+## Treating Unused Variables as Errors
+
+C# uses `.csproj` files to configure projects. The following changes to our
+`.csproj` file WAI'd:
+
+```diff
++    <!-- IDE0059: Remove unnecessary value assignment -->
++    <WarningsAsErrors>IDE0059</WarningsAsErrors>
++    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
+```
+
+However, supporting this turned out to be a ~1h adventure... Why was this
+unexpectedly difficult? As it turns out, there are the 3x promising compiler
+warnings that I had to discover/try:
+
+- `CS0219`: doesn't WAI (see "Note" here: https://docs.microsoft.com/en-us/dotnet/csharp/misc/cs0219)
+- `CA1804`: silently unsupported (replaced by `IDE0059`)
+- `IDE0059`: WAIs
+
+Legend:
+- `CS`: stands for C#
+- `CA`: stands for Code Analysis (I *think* a Visual Studio concept)
+- `IDE`: stands for IDE (I think *also* a Visual Studio concept)
+
+For `CA` and `IDE` prefixed warnings, `EnforceCodeStyleInBuild` must also be
+enabled.
diff --git a/users/wpcarro/website/blog/posts/git-filter-repo-note.md b/users/wpcarro/website/blog/posts/git-filter-repo-note.md
new file mode 100644
index 0000000000..e5fbb05f5c
--- /dev/null
+++ b/users/wpcarro/website/blog/posts/git-filter-repo-note.md
@@ -0,0 +1,59 @@
+## Background
+
+- I recently used `git-filter-repo` to scrub cleartext secrets from a
+  repository.
+- We pin some services' deployments to commit SHAs.
+- These commit SHAs are no longer reachable from `origin/main`.
+
+## Problem
+
+If `git` garbage-collects any of the commits to which services are pinned, and
+that service attempts to redeploy, the deployment will fail.
+
+`git for-each-ref --contains $SHA` will report all of the refs that can reach
+some commit, `$SHA`. This may report things like:
+- `refs/replace` (i.e. `git-filter-repo` artifacts)
+- `refs/stash`
+- some local branches
+- some remote branches
+
+One solution might involve creating references to avoid garbage-collection. But
+if any of our pinned commits contains sensitive cleartext we *want* to ensure
+that `git` purges these.
+
+Instead let's find the SHAs of the new, rewritten commits and replace the pinned
+versions with those.
+
+## Solution
+
+Essentially we want to find a commit with the same *tree* state as the currently
+pinned commit. Here are two ways to get that info...
+
+This way is indirect, but provides more context about the change:
+
+```shell
+λ git cat-file -p $SHA
+tree d011a1dd4a3c5c4c6455ab3592fa2bf71d551d22 # <-- copy this tree info
+parent ba88bbf8de61be932184631244d2ec0ec8205cb8
+author William Carroll <wpcarro@gmail.com> 1664993052 -0700
+committer William Carroll <wpcarro@gmail.com> 1665116042 -0700
+
+feat(florp): Florp can now flarp
+
+You're welcome :)
+```
+
+This way is more direct (read: code-golf-friendly):
+
+```shell
+λ git log -1 --format=%T $SHA
+```
+
+Now that we have the SHA of the desired tree state, let's use it to query `git`
+for commits with the same tree SHA.
+
+```shell
+λ git log --format='%H %T' | grep $(git log --format=%T -1 $SHA) | awk '{ print $1 }'
+```
+
+Hopefully this helps!
diff --git a/users/wpcarro/website/blog/posts/git-rev-refs.md b/users/wpcarro/website/blog/posts/git-rev-refs.md
new file mode 100644
index 0000000000..fdc0aaf5cc
--- /dev/null
+++ b/users/wpcarro/website/blog/posts/git-rev-refs.md
@@ -0,0 +1,85 @@
+## Credit
+
+Credit goes to `tazjin@` for this idea :)
+
+## Background
+
+Using `git` revisions to pin versions is nice, but git SHAs aren't very
+human-friendly:
+
+- They're difficult to type.
+- They're difficult to say in conversation.
+- They're difficult to compare. e.g. Which is newer? `2911fcd` or `db6ac90`?
+
+## Solution
+
+Let's assign monotonically increasing natural numbers to each of
+our repo's mainline commits and create `git` refs so we can use references like
+`r/123` instead of `2911fcd`.
+
+- They're easy to type: `r/123`
+- They're easy to say in conversion: "Check-out rev one-twenty-three."
+- They're easy to compare: `r/123` is an earlier version than `r/147`.
+
+## Backfilling
+
+Let's start-off by assigning "revision numbers" as refs for each of the mainline
+commits:
+
+```shell
+for commit in $(git rev-list --first-parent HEAD); do
+  git update-ref "refs/r/$(git rev-list --count --first-parent $commit)" $commit
+done
+```
+
+We can verify with:
+
+```shell
+λ git log --first-parent --oneline
+```
+
+If everything looks good, we can publish the refs to the remote:
+
+```shell
+λ git push origin 'refs/r/*:refs/r/*'
+```
+
+## Staying Current
+
+In order to make sure that any newly merged commits have an associated revision
+number as a ref, add something like the following to your CI system to run on
+the builds of your repo's mainline branch:
+
+```shell
+λ git push origin "HEAD:refs/r/$(git rev-list --count --first-parent HEAD)"
+```
+
+## Summary
+
+To verify that the remote now has the expected refs, we can use:
+
+```shell
+λ git ls-remote origin | less # grep for refs/r
+```
+
+If that looks good, you should now be able to *manually* fetch the refs with:
+
+```shell
+λ git fetch origin 'refs/r/*:refs/r/*'
+```
+
+Or you can use `git config` to automate this:
+
+```shell
+λ git config --add remote.origin.fetch '+refs/r/*:refs/r/*'
+λ git fetch origin
+```
+
+Now you can run fun commands like:
+
+```shell
+λ git show r/1234
+λ git diff r/123{4,8} # see changes from 1234 -> 1238
+```
+
+Thanks for reading!
diff --git a/users/wpcarro/website/blog/posts/importing-subtrees.md b/users/wpcarro/website/blog/posts/importing-subtrees.md
new file mode 100644
index 0000000000..e1070fc3b9
--- /dev/null
+++ b/users/wpcarro/website/blog/posts/importing-subtrees.md
@@ -0,0 +1,147 @@
+## Background
+
+Sometimes you need to merge one Git repo into another. This is a common task
+when administrating a monorepo.
+
+Here's a checklist that I follow:
+
+1. Detect leaked secrets.
+1. Rotate leaked secrets.
+1. Purge leaked secrets from repo history.
+1. Create mainline references to branches (for deployments).
+1. Subtree-merge into the target repo.
+1. Format the code.
+1. Celebrate!
+
+## Secrets
+
+**Note:** If you notice any leaked secrets, first and foremost rotate them
+before moving on...
+
+`gitleaks` supports `gitleaks protect`, but that doesn't seem to work for `WRN`
+level leaks, which in my experience often contain sensitive cleartext. We can
+use `git-filter-repo` to purge the cleartext from our repo history.
+
+Let's make a `secrets.txt` file that we can feed `git-filter-repo`:
+
+```shell
+λ gitleaks detect -r /tmp/secrets.json
+λ jq -r 'map_values(.Secret) | .[]' /tmp/secrets.txt
+```
+
+Now for the redacting...
+
+```shell
+λ git-filter-repo --force --replace-text /tmp/secrets.txt
+```
+
+Verify that the secrets were removed.
+
+```shell
+λ rg --hidden '\*\*\*REMOVED\*\*\*'
+λ gitleaks detect -v
+```
+
+Looks good! Let's move on to support the adopted repo's deploy strategy.
+
+## Supporting Deploys
+
+While deploying services when someone pushes to a given branch is a common
+deployment strategy, branch-based deployment don't make a whole lot of sense in
+a monorepo.
+
+When adopting another repo, you'll typically encounter a Github Action
+configuration that contains a section like this:
+
+```yaml
+on:
+  push:
+    - staging
+    - production
+```
+
+In our monorepo, `staging` and `production` don't exist. And I don't think we
+want to support them either. `staging` and `production` are ambiguous in a
+monorepo that hosts multiple services each of which likely having its own notion
+of `staging` and `production`.
+
+Doing "pinned releases" where a service is deployed from a `git` revision from
+the mainline branch works well in these scenarios. In order to support this we
+need to make sure the adopted repo has references to
+
+`git subtree add` asks us to define which branch it should use when grafting the
+repository onto our monorepo. We'll use `main` (or whatever the mainline branch
+is).
+
+In order to support the *current* deployments while migrating to a pinned
+release strategy, we have to ensure that `main` has a commit containing the same
+tree state as `staging` *and* another commit containing the same tree state as
+`production`. Let's do that!
+
+```shell
+λ git checkout main # ensure you're on the main branch
+λ git diff main staging >/tmp/main-to-staging.patch
+λ git diff main production >/tmp/main-to-production.patch
+```
+
+### staging
+
+```shell
+λ git apply /tmp/main-to-staging.patch
+λ git add . && git commit # chore: main -> staging
+λ git revert HEAD
+λ git commit --amend # revert: staging -> main
+```
+
+### production
+
+```shell
+λ git apply /tmp/main-to-production.patch
+λ git add . && git commit # chore: main -> production
+λ git revert HEAD
+λ git commit --amend # revert: production -> main
+```
+
+Now let's check our work:
+
+```shell
+λ git log --oneline
+38f4422 revert: production -> main
+f071a9f chore: main -> production
+02ea731 revert: staging -> main
+308ed90 chore: main -> staging
+```
+
+When we go to support pinned releases we can do something like so:
+
+```json
+{
+  "staging": "308ed90",
+  "production": "f071a9f"
+}
+```
+
+## Subtree Merge
+
+Now the repo is ready to be merged.
+
+```shell
+λ git subtree add --prefix=foo/bar/baz path/to/baz main
+λ git commit --amend # subtree: Dock baz into monorepo!
+```
+
+## Formatting
+
+Some CI enforces code formatting standards, so you may need to run that:
+
+```shell
+λ repofmt
+λ git add . && git commit # chore(fmt): Format the codes
+```
+
+Lastly, if you need the latest monorepo code from `origin/main` before opening a
+pull request, the following should work:
+
+```shell
+λ git fetch origin main && git rebase origin/main --rebase-merges --strategy=subtree
+```
diff --git a/users/wpcarro/website/blog/posts/nginx-curl-note.md b/users/wpcarro/website/blog/posts/nginx-curl-note.md
new file mode 100644
index 0000000000..e2f4341f54
--- /dev/null
+++ b/users/wpcarro/website/blog/posts/nginx-curl-note.md
@@ -0,0 +1,5 @@
+Use the following to make requests to Nginx virtual hosts from the host itself:
+
+```shell
+$ curl -H 'Host: trace.website.internal' localhost:8000
+```
diff --git a/users/wpcarro/website/blog/posts/nix-env-note.md b/users/wpcarro/website/blog/posts/nix-env-note.md
new file mode 100644
index 0000000000..8683c52e8f
--- /dev/null
+++ b/users/wpcarro/website/blog/posts/nix-env-note.md
@@ -0,0 +1,33 @@
+## Background
+
+Much in the same vain as my [nix-shell (note to self)][nix-shell-note], I'm
+going to leave a note to my future self on how to install packages using
+`nix-env`, which is something I do once in a blue moon.
+
+## Solution
+
+```shell
+λ nix-env -iA tvix.eval -f /depot
+```
+
+Looks like I was forgetting the `-f /depot` option all this time:
+
+> --file / -f path
+>     Specifies the Nix expression (designated below as the active Nix
+>     expression) used by the --install, --upgrade, and --query --available
+>     operations to obtain derivations. The default is ~/.nix-defexpr.
+> - `man nix-env`
+
+## Failed Attempts (don't try these at home)
+
+This section is brought to you by my shell's `Ctrl-r`!
+
+```shell
+λ nix-env -I depot=/depot -iA depot.tvix.eval
+λ NIX_PATH=depot=/depot nix-env -iA depot.tvix.eval
+λ nix-env -iE '(import /depot {}).tvix.eval'
+```
+
+Thanks for reading!
+
+[nix-shell-note]: https://billandhiscomputer.com/blog/posts/nix-shell.html
diff --git a/users/wpcarro/website/blog/posts/nix-shell-note.md b/users/wpcarro/website/blog/posts/nix-shell-note.md
new file mode 100644
index 0000000000..da33c846ce
--- /dev/null
+++ b/users/wpcarro/website/blog/posts/nix-shell-note.md
@@ -0,0 +1,50 @@
+## Background
+
+I rarely use `nix-shell` for its originally intended purpose of "reproducing the
+environment of a derivation for development". Instead, I often use it to put
+some executable on my `PATH` for some ad hoc task.
+
+What's `nix-shell`'s "intended purpose"? Let's ask The Man (`man nix-shell`):
+
+> The command nix-shell will build the dependencies of the specified derivation,
+> but not the derivation itself. It will then start an interactive shell in
+> which all environment variables defined by the derivation path have been set
+> to their corresponding values, and the script $stdenv/setup has been
+> sourced. This is useful for reproducing the environment of a derivation for
+> development.
+
+Because I'm abusing `nix-shell` in this way, I'm liable to forget that
+`nix-shell` puts `buildInputs` on `PATH` and *not* the derivation itself. But I
+often only want the derivation!
+
+## Solution
+
+Pass the Nix expression to `nix-shell -p`:
+
+```shell
+λ nix-shell -p '(import /depot {}).tvix.eval'
+```
+
+## Explanation
+
+This works because Nix forwards the arguments passed to `-p` (i.e. `--packages`)
+and interpolates them into this expression here: [source][nix-src]
+
+```nix
+{ ... }@args:
+
+with import <nixpkgs> args;
+
+(pkgs.runCommandCC or pkgs.runCommand) "shell" {
+  buildInputs = [
+    # --packages go here
+  ];
+}
+```
+
+So really you can pass-in *any* valid Nix expression that produces a derivation
+and `nix-shell` will put its outputs on your `PATH`.
+
+Enjoy!
+
+[nix-src]: https://sourcegraph.com/github.com/NixOS/nix@3ae9467d57188f9db41f85b0e5c41c0c9d141955/-/blob/src/nix-build/nix-build.cc?L266
diff --git a/users/wpcarro/website/blog/posts/nixos-disk-full-note.md b/users/wpcarro/website/blog/posts/nixos-disk-full-note.md
new file mode 100644
index 0000000000..4bbd3f58e2
--- /dev/null
+++ b/users/wpcarro/website/blog/posts/nixos-disk-full-note.md
@@ -0,0 +1,113 @@
+## Background
+
+Every now and then NixOS hosts runs out of disk space. This happened to my IRC
+server recently...
+
+> No problem. Let's free-up some space with Nix's garbage-collection:
+> - me
+
+```shell
+λ nix-collect-garbage -d # failed due lack of disk space
+```
+
+Ironically Nix needs to do an SQLite transaction before deleting stuff and
+SQLite can't do that if there's no space. This is especially funny because the
+SQLite is probably a `DELETE`.
+
+## Solution
+
+First let's verify that our disk is indeed at capacity:
+
+```shell
+λ df -h
+Filesystem                Size  Used Avail Use% Mounted on
+devtmpfs                  399M     0  399M   0% /dev
+tmpfs                     3.9G     0  3.9G   0% /dev/shm
+tmpfs                     2.0G  3.7M  2.0G   1% /run
+tmpfs                     3.9G  408K  3.9G   1% /run/wrappers
+/dev/disk/by-label/nixos  9.9G  9.9G    0G 100% /
+tmpfs                     4.0M     0  4.0M   0% /sys/fs/cgroup
+tmpfs                     797M     0  797M   0% /run/user/0
+```
+
+Looks like `/dev/disk/by-label/nixos` is at `100%`. Now let's find some easy
+targets to free-up space so that we can run `nix-collect-garbage -d`...
+
+```shell
+λ du -hs /* 2>/dev/null
+8.0K    /bin
+12M     /boot
+0       /dev
+200K    /etc
+68K     /home
+16K     /lost+found
+9.0G    /nix
+0       /proc
+1.2M    /root
+2.9M    /run
+4.0K    /srv
+0       /sys
+44K     /tmp
+12K     /usr
+1.2G    /var
+```
+
+Okay: `/var` looks like an easy candidate. Let's recurse into that directory:
+
+```shell
+λ du -hs /var/*
+40K     /var/cache
+12K     /var/db
+4.0K    /var/empty
+4.0K    /var/google-users.d
+211M    /var/lib
+0       /var/lock
+918M    /var/log
+0       /var/run
+4.0K    /var/spool
+44K     /var/tmp
+λ du -hs /var/log/* # /var/log looks promising
+60M     /var/log/btmp
+82M     /var/log/btmp.1
+776M    /var/log/journal # ah-ha! journald. Let's clean-up some logs
+8.0K    /var/log/lastlog
+1.1M    /var/log/nginx
+4.0K    /var/log/private
+12K     /var/log/wtmp
+```
+
+To retain at most 1w's worth of logs:
+
+```shell
+λ journalctl --vacuum-time=1w
+```
+
+...or if you'd prefer to retain only 100M's worth of logs:
+
+```shell
+λ journalctl --vacuum-size=100M
+```
+
+Now Nix should be able to garbage-collect!
+
+```shell
+λ nix-collect-garbage -d
+```
+
+And lastly verify that it WAI'd:
+
+```
+λ df -h
+Filesystem                Size  Used Avail Use% Mounted on
+devtmpfs                  399M     0  399M   0% /dev
+tmpfs                     3.9G     0  3.9G   0% /dev/shm
+tmpfs                     2.0G  3.7M  2.0G   1% /run
+tmpfs                     3.9G  408K  3.9G   1% /run/wrappers
+/dev/disk/by-label/nixos  9.9G  5.1G  4.3G  55% /
+tmpfs                     4.0M     0  4.0M   0% /sys/fs/cgroup
+tmpfs                     797M     0  797M   0% /run/user/0
+```
+
+## Closing Thoughts
+
+Why doesn't Nix just reserve enough space to be able to GC itself? Not sure...
diff --git a/users/wpcarro/website/blog/posts/quassel-google-vm.md b/users/wpcarro/website/blog/posts/quassel-google-vm.md
new file mode 100644
index 0000000000..dd74387f8b
--- /dev/null
+++ b/users/wpcarro/website/blog/posts/quassel-google-vm.md
@@ -0,0 +1,34 @@
+# IRC, GCP, and NixOS
+
+- "cannot read /var/lib/acme/wpcarro.dev/full.pem"
+- `sudo stat /var/lib/acme/wpcarro.dev/full.pem` exists
+- `sudo -i`
+- `su quassel` # denied
+- `sudo --user=quassel stat /var/lib/acme/wpcarro.dev/full.pem` exists
+- `groups quassel` quassel
+- `usermod -a -G nginx quassel` exists
+- `groups quassel` quassel, nginx
+- `sudo --user=quassel cat /var/lib/acme/wpcarro.dev/full.pem` exists
+
+# Firewall
+
+- `nmap localhost`
+- `nmap wpcarro.dev`
+- Update `configuration.nix` firewall
+- `nmap localhost`
+- `nmap wpcarro.dev`
+- Edit cloud.google.com Configuration (VPC > Firewall > 6697)
+
+# Quassel
+
+- Test connecting, disconnecting, persisted logs?
+- Change `~quassel@253.253.209.35.bc.googleusercontent.com` -> `~quassel@wpcarro.dev`
+  - cloaking?
+  - rDNS?
+    - `dig wpcarro.dev`       -> `35.209.253.253`
+    - `dig -x 35.209.253.253` -> `253.253.209.35.bc.googleusercontent.com`
+    - From within GCP https://stackoverflow.com/a/47060002 (create the PTR record)
+- `/msg hostserv take hackint/user/$account` add cloaking
+- disconnect/connect from hackint for changes to take affect
+- `/msg hostserv drop` remove cloaking
+- Test can I log-in from another machine?
diff --git a/users/wpcarro/website/blog/posts/restic.md b/users/wpcarro/website/blog/posts/restic.md
new file mode 100644
index 0000000000..4af1fab368
--- /dev/null
+++ b/users/wpcarro/website/blog/posts/restic.md
@@ -0,0 +1,91 @@
+Continuing along the trend that [Profpatsch][2] recently inspired in me: writing
+short notes to myself instead of fully fledged blog posts aimed at some
+unknowable audience. Today we're looking at how I burned myself by only
+*partially* RTFD.
+
+## Background
+
+I recently started using [restic][4] and NixOS thanks to the help of [TVL's
+`restic.nix` module][1]. I setup `1x/h` backups to [MinIO][3] (S3-compatible
+storage) for just a handful of `/var/lib` directories (`~9GiB` total), but after
+a few days MinIO reported that my bucket size was `O(100GiB)`!
+
+> What's going on?
+> -- me
+
+```shell
+$ restic stats
+repository 763bfe37 opened successfully, password is correct
+scanning...
+Stats in restore-size mode:
+Snapshots processed:   175
+   Total File Count:   31369384
+         Total Size:   21.027 GiB
+```
+
+> Wait: 20GiB... wat?
+> -- me (moments later)
+
+Maybe we're snapshotting our MinIO buckets, and that's contributing to our
+bucket size. Checking the logs proved that `restic` was backing-up `1.5GiB/h`,
+which supported MinIO's reports.
+
+> Ah maybe `restic stats` isn't reporting what I *think* it's reporting...
+> -- me (again)
+
+Let's consult Le Docs:
+
+```shell
+$ restic stats -h
+
+The "stats" command walks one or multiple snapshots in a repository
+and accumulates statistics about the data stored therein. It reports
+on the number of unique files and their sizes, according to one of
+the counting modes as given by the --mode flag.
+
+It operates on all snapshots matching the selection criteria or all
+snapshots if nothing is specified. The special snapshot ID "latest"
+is also supported. Some modes make more sense over
+just a single snapshot, while others are useful across all snapshots,
+depending on what you are trying to calculate.
+
+[to be continued]
+```
+
+This is where I stopped reading (the first time). But then I returned a second
+time as I was running low on theories...
+
+```shell
+[continued]
+
+The modes are:
+
+* restore-size: (default) Counts the size of the restored files.
+* files-by-contents: Counts total size of files, where a file is
+   considered unique if it has unique contents.
+* raw-data: Counts the size of blobs in the repository, regardless of
+  how many files reference them.
+* blobs-per-file: A combination of files-by-contents and raw-data.
+```
+
+Bingo: `--mode=raw-data` **not** `--mode=restore-size`.
+
+## Solution
+
+```shell
+$ restic stats --mode=raw-data
+repository 763bfe37 opened successfully, password is correct
+scanning...
+Stats in raw-data mode:
+Snapshots processed:   175
+   Total Blob Count:   710988
+         Total Size:   303.216 GiB
+```
+
+> Ah... the world agrees again.
+> -- me
+
+[1]: https://cs.tvl.fyi/depot@2ec0d3611960b163a7052e8554ba065f3c89a8cc/-/blob/ops/modules/restic.nix?L9
+[2]: https://github.com/profpatsch
+[3]: https://min.io/
+[4]: https://restic.net/
diff --git a/users/wpcarro/website/blog/posts/send-mail-as-2fa.md b/users/wpcarro/website/blog/posts/send-mail-as-2fa.md
new file mode 100644
index 0000000000..5d18935c7a
--- /dev/null
+++ b/users/wpcarro/website/blog/posts/send-mail-as-2fa.md
@@ -0,0 +1,43 @@
+## Prelude
+
+This is a short story about how I configured myself out of my own email. Posting
+this as an exercise in humility, a tutorial for my future self in case of
+amnesia, and penance for my sins.
+
+## Background
+
+-   I have 2x Gmail accounts: **work** and **personal**.
+-   I configure **work** to send emails as **personal**.
+-   I configure **personal** to forward incoming emails to **work**.
+
+This allows me to use **work** and manage both of my inboxes as one. I recently
+added two-factor authentication (2FA) to **personal**, forgot about it, and
+spent a few days unable to send **personal** emails from any **work** device.
+
+## Symptoms
+
+Whenever I tried to send emails on behalf of **personal**, I'd receive the
+following error message as a reply:
+
+> You're sending this from a different address using the 'Send mail as' feature.
+> The settings for your 'Send mail as' account are misconfigured or out of date.
+> Check those settings and try resending.
+
+Useful error message if you ask me (especially in retrospect), but because I had
+*forgotten* that I setup 2FA for **personal**, I naively assumed this issue
+might magically disappear given enough time... kind of how restarting your
+device resets the state and causes the symptoms of a certain class of bugs to
+disappear.
+
+After a few days of mounting frustration, I decided to take a closer look...
+
+## Solution
+
+-   Create an "App Password" for **personal**:
+    [instructions](https://support.google.com/accounts/answer/185833?hl=en).
+-   Login to **work** and delete **personal** from `Settings > Accounts > Send
+    mail as`.
+-   `Add another email address` for **personal** using the "App Password" you
+    just created.
+
+And now I'm back in business!
diff --git a/users/wpcarro/website/blog/posts/ssh-oddities.md b/users/wpcarro/website/blog/posts/ssh-oddities.md
new file mode 100644
index 0000000000..ae0bd5c9f5
--- /dev/null
+++ b/users/wpcarro/website/blog/posts/ssh-oddities.md
@@ -0,0 +1,59 @@
+## Background
+
+I was trying to debug a service over `ssh` that offered password-only
+authentication, but I couldn't seem to get the `ssh` client to prompt me for the
+password.
+
+It looked something like this (skip ahead to the conclusion if you're pressed
+for time):
+
+## Troubleshooting
+
+```shell
+λ ssh root@[redacted]
+Unable to negotiate with [redacted] port 22: no matching host key type found. Their offer: ssh-rsa
+```
+
+But the same command was working just fine for my coworker.
+
+I took a closer look with `ssh -v root@[redacted]`, but nothing jumped-out at
+me. Maybe it's something with *my* `ssh` configuration; let's remove that
+variable:
+
+```shell
+λ ssh -F /dev/null root@[redacted]
+Unable to negotiate with [redacted] port 22: no matching host key type found. Their offer: ssh-rsa
+```
+
+> Ah it looks like there's a way to set my preferred authentication method...
+> -- me
+
+```shell
+λ ssh -F /dev/null -o PreferredAuthentications=password root@[redacted]
+Unable to negotiate with [redacted] port 22: no matching host key type found. Their offer: ssh-rsa
+```
+
+## Conclusion
+
+Well it turns-out that newer SSH clients disable the `ssh-rsa` public key
+signature algorithm because it depends on SHA-1, which is considered insecure.
+
+```shell
+λ ssh -V
+OpenSSH_9.0p1, OpenSSL 1.1.1p  21 Jun 2022
+```
+
+...and according to the `ssh -v` output, the server is running a pre-COVID(!!!)
+version of `ssh`:
+
+```
+debug1: Remote protocol version 2.0, remote software version dropbear_2018.76
+```
+
+So if you don't have time to upgrade the SSH server, and you just want to
+connect, the following should work because we're *opting-into* the less secure
+option:
+
+```shell
+λ ssh -o HostKeyAlgorithms=+ssh-rsa root@[redacted]
+```
diff --git a/users/wpcarro/website/blog/posts/tcp-tunneling-note.md b/users/wpcarro/website/blog/posts/tcp-tunneling-note.md
new file mode 100644
index 0000000000..06f6469aff
--- /dev/null
+++ b/users/wpcarro/website/blog/posts/tcp-tunneling-note.md
@@ -0,0 +1,63 @@
+## Background
+
+Let's say we'd like to debug a remote machine but use some of the debugging
+tools we have on our local machine like wireshark.
+
+You *can* run `tcpdump` on the remote and then `scp` the file to your local
+machine to analyze the traffic, but after doing that a few times you may want a
+workflow with a tighter feedback loop. For this we'll forward traffic from a
+remote machine to our local machine.
+
+**Note:** There's also `termshark`, which is a `wireshark` TUI that you can run
+on the remote. It's quite cool!
+
+## Local
+
+Run the following on your local machine to forward your remote's traffic:
+
+```shell
+λ ssh -R 4317:127.0.0.1:4317 -N -f user@remote
+```
+
+Here is an abridged explanation of the flags we're passing from `man ssh`:
+
+```
+-N     Do  not  execute  a remote command.  This is useful for just forwarding ports.
+-f     Requests ssh to go to background just before command execution.
+```
+
+**Note:** I couldn't find a good explanation for the `-R` option, so I tried
+removing it and re-running the command, but that results in a resolution error:
+
+```
+ssh: Could not resolve hostname 4317:127.0.0.1:4317: Name or service not known
+```
+
+The remote should now be forwarding traffic from port `4317` to our
+machine.
+
+## Testing
+
+Let's generate some traffic on the remote:
+
+```shell
+λ telnet localhost 4317
+Trying ::1...
+Connected to localhost.
+Escape character is '^]'.
+hello
+world
+```
+
+Locally you should see:
+
+```shell
+λ nc -l 4317 -k # run this *before* running the above command
+hello
+world
+```
+
+You should now be able to `tcpdump -i lo port 4317` or just use `wireshark`
+locally.
+
+Happy debugging!
diff --git a/users/wpcarro/website/blog/posts/tee-time.md b/users/wpcarro/website/blog/posts/tee-time.md
new file mode 100644
index 0000000000..c8107fcded
--- /dev/null
+++ b/users/wpcarro/website/blog/posts/tee-time.md
@@ -0,0 +1,16 @@
+I encountered this fun TIL while troubleshooting Linux write permissions
+issues...
+
+## TL;DR
+
+Don't do this (unless you want misleading test results):
+
+```shell
+λ sudo -u node-exporter echo 'Hello, world' >/var/lib/textfile-exporter/test.prom
+```
+
+Do this:
+
+```shell
+λ echo 'Hello, world' | sudo -u node-exporter tee /var/lib/textfile-exporter/test.prom
+```