diff options
Diffstat (limited to 'users/wpcarro/website/blog')
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 000000000000..3a9dbd4d2b60 --- /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 000000000000..27541b0f39b4 --- /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 000000000000..e69de29bb2d1 --- /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 000000000000..2741292aa930 --- /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 000000000000..a85a4b71101e --- /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">> Half-baked musings lossily encoded.</p> + <p class="text-gray-500">> - 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 000000000000..31fb0c83d8f0 --- /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 000000000000..24474e6dfe48 --- /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 000000000000..f781a60873c7 --- /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 000000000000..a5b62647bcde --- /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 000000000000..e5fbb05f5cd2 --- /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 000000000000..fdc0aaf5cc9f --- /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 000000000000..e1070fc3b922 --- /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 000000000000..e2f4341f5400 --- /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 000000000000..8683c52e8fec --- /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 000000000000..da33c846ceaa --- /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 000000000000..4bbd3f58e2fb --- /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 000000000000..dd74387f8bf0 --- /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 000000000000..4af1fab3682f --- /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 000000000000..5d18935c7aef --- /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 000000000000..ae0bd5c9f507 --- /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 000000000000..06f6469aff3d --- /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 000000000000..c8107fcded69 --- /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 +``` |