about summary refs log tree commit diff
path: root/users/aspen/web
diff options
context:
space:
mode:
Diffstat (limited to 'users/aspen/web')
-rw-r--r--users/aspen/web/.envrc1
-rw-r--r--users/aspen/web/.gitignore3
-rw-r--r--users/aspen/web/Makefile37
-rw-r--r--users/aspen/web/config.el6
-rw-r--r--users/aspen/web/default.nix62
-rw-r--r--users/aspen/web/index.org47
-rw-r--r--users/aspen/web/main.css139
-rw-r--r--users/aspen/web/orgExportHTML.nix67
-rw-r--r--users/aspen/web/pubkey.gpg206
-rw-r--r--users/aspen/web/recipes/tomato-sauce.org102
-rw-r--r--users/aspen/web/shell.nix9
-rw-r--r--users/aspen/web/site.nix12
12 files changed, 691 insertions, 0 deletions
diff --git a/users/aspen/web/.envrc b/users/aspen/web/.envrc
new file mode 100644
index 000000000000..be81feddb1a5
--- /dev/null
+++ b/users/aspen/web/.envrc
@@ -0,0 +1 @@
+eval "$(lorri direnv)"
\ No newline at end of file
diff --git a/users/aspen/web/.gitignore b/users/aspen/web/.gitignore
new file mode 100644
index 000000000000..2b72eaed2988
--- /dev/null
+++ b/users/aspen/web/.gitignore
@@ -0,0 +1,3 @@
+result
+letsencrypt
+index.html
diff --git a/users/aspen/web/Makefile b/users/aspen/web/Makefile
new file mode 100644
index 000000000000..cf3da73c91ea
--- /dev/null
+++ b/users/aspen/web/Makefile
@@ -0,0 +1,37 @@
+.PHONY: deploy purge_cf do_deploy renew backup open
+
+deploy: do_deploy purge_cf
+
+purge_cf:
+	@$(shell nix-build `git rev-parse --show-toplevel` -A 'users.aspen.web.purge-cf')/bin/purge-cf.sh
+
+do_deploy:
+	@$(shell nix-build `git rev-parse --show-toplevel` -A 'users.aspen.web')/bin/deploy.sh
+
+
+renew:
+	@echo Renewing...
+	@certbot certonly \
+		--manual \
+		--domain www.gws.fyi \
+		--preferred-challenges dns \
+		--server https://acme-v02.api.letsencrypt.org/directory \
+		--agree-tos \
+		--work-dir $(shell pwd)/letsencrypt/work \
+		--logs-dir $(shell pwd)/letsencrypt/logs \
+		--config-dir $(shell pwd)/letsencrypt/config
+	@echo "Reimporting certificate"
+	@aws acm import-certificate \
+	    --profile personal \
+	    --certificate file://letsencrypt/config/live/www.gws.fyi/cert.pem \
+	    --certificate-chain file://letsencrypt/config/live/www.gws.fyi/fullchain.pem \
+	    --private-key file://letsencrypt/config/live/www.gws.fyi/privkey.pem \
+	    --certificate-arn arn:aws:acm:us-east-1:797089351721:certificate/628e54f3-55f9-49c0-811a-eba516b68e30 \
+		--region us-east-1
+
+backup:
+	@tarsnap -cf $(shell uname -n)-letsencrypt-$(shell date +%Y-%m-%d_%H-%M-%S) \
+		letsencrypt/
+
+open:
+	$$BROWSER "https://www.gws.fyi"
diff --git a/users/aspen/web/config.el b/users/aspen/web/config.el
new file mode 100644
index 000000000000..b05d897d3ddb
--- /dev/null
+++ b/users/aspen/web/config.el
@@ -0,0 +1,6 @@
+(require 'org)
+
+(setq org-html-postamble nil)
+
+(defadvice org-export-grab-title-from-buffer
+    (around org-export-grab-title-from-buffer-disable activate))
diff --git a/users/aspen/web/default.nix b/users/aspen/web/default.nix
new file mode 100644
index 000000000000..a16cd16c065a
--- /dev/null
+++ b/users/aspen/web/default.nix
@@ -0,0 +1,62 @@
+args@{ pkgs, depot, ... }:
+with pkgs;
+let
+  site = import ./site.nix args;
+  resume = import ../resume args;
+  bucket = "s3://gws.fyi";
+  distributionID = "E2ST43JNBH8C64";
+
+  css = runCommand "main.css"
+    {
+      buildInputs = [ pkgs.minify ];
+    } ''
+    minify --type css < ${./main.css} > $out
+  '';
+
+  keys = runCommand "ssh-keys" { } ''
+    touch $out
+    echo "${depot.users.aspen.keys.main}" >> $out
+  '';
+
+  website =
+    runCommand "gws.fyi" { } ''
+      mkdir -p $out
+      cp ${css} $out/main.css
+      cp ${site.index} $out/index.html
+      cp -r ${site.recipes} $out/recipes
+      cp ${resume} $out/resume.pdf
+      cp ${keys} $out/keys
+      cp ${./pubkey.gpg} $out/pubkey.gpg
+    '';
+
+  purge-cf = writeShellApplication {
+    name = "purge-cf.sh";
+    runtimeInputs = [ httpie jq pass ];
+    text = ''
+      cfapi() {
+        http \
+          "https://api.cloudflare.com/client/v4/$1" \
+          X-Auth-Email:root@gws.fyi \
+          "X-Auth-Key: $(pass cloudflare-api-key)" \
+          "''${@:2}"
+      }
+
+      zone_id=$(
+        cfapi zones \
+          | jq -r '.result[] | select(.name == "gws.fyi") | .id'
+      )
+
+      cfapi "zones/$zone_id/purge_cache" purge_everything:=true
+    '';
+  };
+in
+(writeShellApplication {
+  name = "deploy.sh";
+  runtimeInputs = [ awscli2 ];
+  text = ''
+    aws --profile personal s3 sync ${website}/ ${bucket}
+    echo "Deployed to http://gws.fyi"
+  '';
+}).overrideAttrs {
+  passthru = { inherit website site purge-cf; };
+}
diff --git a/users/aspen/web/index.org b/users/aspen/web/index.org
new file mode 100644
index 000000000000..14d214df16fd
--- /dev/null
+++ b/users/aspen/web/index.org
@@ -0,0 +1,47 @@
+#+OPTIONS: title:nil toc:nil num:nil
+#+HTML_HEAD: <title>aspen smith</title>
+#+HTML_HEAD: <link rel="stylesheet" href="./main.css">
+
+my name is aspen smith and i'm a software engineer and musician.
+
+* code
+
+- [[https://github.com/glittershark/][github]]
+- [[https://cs.tvl.fyi/depot/-/tree/users/aspen][my directory in the tvl monorepo]]
+
+* work
+
+i am currently looking for a job! ideally i'd like to do something involving
+compilers or database query planners, or other low-level systems work. i'm
+pretty language-agnostic generally, but i'm quite fond of rust and prefer not to
+work in go. i'm based in new york city, but strongly prefer to work in
+remote-first teams. if you're interested, send me an [[mailto:web@gws.fyi][email]]. you can also see my
+resume [[https://gws.fyi/resume.pdf][here]]
+
+most recently, i worked on database internals at [[https://readyset.io/][readyset]], an incrementally
+maintained, partially stateful materialized view maintenance system for sql
+that's wire-compatible with postgresql and mysql, based on [[https://github.com/mit-pdos/noria][noria]].
+
+* projects
+
+- [[https://windtunnel.ci/][windtunnel]], a continuous benchmarking software-as-a-service currently accepting early alpha users (send me an email if you want to try it out!)
+- [[https://cs.tvl.fyi/depot/-/tree/users/aspen/achilles][achilles]], a compiler for (what I plan to become) a dependently typed, low-level functional programming language targeting LLVM
+- [[https://github.com/glittershark/org-clubhouse][org-clubhouse]], an emacs package for lightweight integration between [[https://orgmode.org/][org-mode]] and [[https://clubhouse.io/][the clubhouse project management tool]]
+- [[https://cs.tvl.fyi/depot/-/tree/users/aspen/xanthous][xanthous]], a terminal roguelike in haskell that I work on intermittently and exclusively for fun
+
+* music
+
+- https://sacrosanct.bandcamp.com/, a post-rock project with a [[https://bandcamp.com/h34rken][friend of mine]]
+- [[https://soundcloud.com/missingggg][my current soundcloud]], releasing instrumental music under the name *missing*
+- i play bass in [[https://www.instagram.com/goodcryband_bk/][good cry]], a rock band based in brooklyn
+- you can also find a log of all the music I listen to [[https://www.last.fm/user/wildgriffin45][on last.fm]]
+
+* contact
+
+- [[mailto:web@gws.fyi][web@gws.fyi]]
+- [[https://twitter.com/glittershark1][twitter]]
+- [[https://bsky.app/profile/gws.fyi][bluesky]]
+- https://keybase.io/glittershark
+- aspen on IRC (hackint or libera.chat)
+- [[https://gws.fyi/pubkey.gpg][gpg key: 0F11A989879E8BBBFDC1E23644EF5B5E861C09A7]]
+- signal / telegram / discord available upon request
diff --git a/users/aspen/web/main.css b/users/aspen/web/main.css
new file mode 100644
index 000000000000..cdcd440766ca
--- /dev/null
+++ b/users/aspen/web/main.css
@@ -0,0 +1,139 @@
+@import url(https://fonts.googleapis.com/css?family=Inconsolata|Inter&display=swap);
+
+body {
+  margin-top: 40px;
+  max-width: 900px;
+  line-height: 1.6;
+  font-size: 16px;
+  background: #f8f3ff;
+  color: #3a1616;
+  padding: 0 10px;
+  font-family: Inter, sans-serif;
+}
+
+@media (min-width: 1050px) {
+  body {
+    margin-left: 150px;
+  }
+}
+
+@media (min-width: 2000px) {
+  body {
+    margin-left: 300px;
+  }
+}
+
+input {
+  padding: 10px 16px;
+  margin: 2px 0;
+  box-sizing: border-box;
+  border: 2px solid #dabebe;
+  border-radius: 6px;
+  background: #f8f3ff;
+  color: #3a1616;
+  font-size: 16px;
+  -webkit-transition: 0.5s;
+  transition: 0.5s;
+  outline: 0;
+}
+
+input:focus {
+  border: 2px solid #3a1616;
+}
+
+.button {
+  background-color: #f8f3ff;
+  border: none;
+  color: #000;
+  padding: 6px 14px;
+  text-align: center;
+  text-decoration: none;
+  display: inline-block;
+  font-size: 16px;
+  margin: 4px 2px;
+  transition-duration: 0.4s;
+  cursor: pointer;
+  border: 2px solid #3a1616;
+  border-radius: 6px;
+}
+
+.button:hover {
+  background-color: #3a1616;
+  color: #fff;
+}
+
+.isa_error,
+.isa_info,
+.isa_success,
+.isa_warning {
+  width: 90%;
+  margin: 10px 0;
+  padding: 12px;
+}
+
+.isa_info {
+  color: #00529b;
+  background-color: #bde5f8;
+}
+
+.isa_success {
+  color: #4f8a10;
+  background-color: #dff2bf;
+}
+
+.isa_warning {
+  color: #9f6000;
+  background-color: #feefb3;
+}
+
+.isa_error {
+  color: #d8000c;
+  background-color: #ffd2d2;
+}
+
+h1,
+h2,
+h3 {
+  line-height: 1.2;
+  font-family: Inter, sans-serif;
+}
+
+h1.title,
+h2.title,
+h3.title {
+  text-align: left;
+  margin-bottom: 1.5em;
+}
+
+h2 {
+  font-size: 18px;
+}
+
+img {
+  max-width: 750px;
+  border-radius: 10px;
+}
+
+a {
+  cursor: pointer;
+  color: #217ab7;
+  line-height: inherit;
+}
+
+a:hover {
+  background-color: #e3d6ff;
+}
+
+a:visited {
+  color: #43458b;
+  border-color: #43458b;
+}
+
+pre {
+  font-family: Inconsolata, monospace;
+}
+
+::selection {
+  color: #fff;
+  background: #ff4081;
+}
diff --git a/users/aspen/web/orgExportHTML.nix b/users/aspen/web/orgExportHTML.nix
new file mode 100644
index 000000000000..aac4e32e7ac5
--- /dev/null
+++ b/users/aspen/web/orgExportHTML.nix
@@ -0,0 +1,67 @@
+{ pkgs, depot, ... }:
+
+with pkgs;
+with lib;
+
+let
+
+  emacs = pkgs.emacs28;
+
+in
+
+opts:
+
+let
+  src = if isAttrs opts then opts.src else opts;
+  headline = if isAttrs opts then opts.headline else null;
+
+  bn = builtins.baseNameOf src;
+  filename = elemAt (splitString "." bn) 0;
+
+  outName =
+    if isNull headline
+    then
+      let
+        bn = builtins.baseNameOf src;
+        filename = elemAt (splitString "." bn) 0;
+      in
+      if depot.nix.utils.isDirectory src
+      then filename
+      else filename + ".html"
+    else "${filename}-${replaceStrings [" "] ["-"] filename}.html";
+
+  escapeDoubleQuotes = replaceStrings [ "\"" ] [ "\\\"" ];
+
+  navToHeadline = optionalString (! isNull headline) ''
+    (search-forward "${escapeDoubleQuotes headline}")
+    (org-narrow-to-subtree)
+  '';
+
+in
+
+runCommand outName { inherit src; } ''
+  buildFile() {
+    cp "$1" file.org
+    ${emacs}/bin/emacs --batch \
+      --load ${./config.el} \
+      --visit file.org \
+      --eval "(progn
+        ${escapeDoubleQuotes navToHeadline}
+        (org-html-export-to-html))" \
+      --kill
+    rm file.org
+    substitute file.html "$2" \
+      --replace '<title>&lrm;</title>' ""
+    rm file.html
+  }
+
+  if [ -d $src ]; then
+    for file in $src/*; do
+      result=''${file/$src/$out}
+      mkdir -p $(dirname $result)
+      buildFile $file ''${result/.org/.html}
+    done
+  else
+    buildFile $src $out
+  fi
+''
diff --git a/users/aspen/web/pubkey.gpg b/users/aspen/web/pubkey.gpg
new file mode 100644
index 000000000000..2a7e744cc340
--- /dev/null
+++ b/users/aspen/web/pubkey.gpg
@@ -0,0 +1,206 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+R/QKwpd4hf8=
+=qj6y
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/users/aspen/web/recipes/tomato-sauce.org b/users/aspen/web/recipes/tomato-sauce.org
new file mode 100644
index 000000000000..dec7468ac271
--- /dev/null
+++ b/users/aspen/web/recipes/tomato-sauce.org
@@ -0,0 +1,102 @@
+#+TITLE: Tomato Sauce
+#+OPTIONS: toc:nil num:nil
+#+HTML_HEAD: <link rel="stylesheet" href="../main.css">
+
+This is a general, all-purpose framework for turning some form of tomatoes into
+some form of sauce. You can use fresh tomatoes or canned (the latter are really
+quite surprisingly good sometimes), and include or omit garlic, basil, or other
+add-ins. The only real non-negotiable ingredients are tomatoes (duh), onion, and
+some kind of fat (I prefer butter).
+
+* Sauce
+
+1. *Prep*. If starting with canned tomatoes, skip this step. if starting with
+   whole tomatoes (which you should really only ever do if you grew them
+   yourself or got them fresh at a farmers market, grocery store tomatoes are
+   kinda sad), first, peel the tomatoes. The easiest way to do this is to score
+   them with an X pattern cut as shallow as possible while still breaking the
+   skin, trying to cover the whole surface area of the tomato, blanch them
+   briefly in boiling water, then dunk into an ice bath. After this, the skins
+   will slip right off.  After peeling, cut out the stem, core, and any green or
+   brown bits, and go to the next step
+
+2. *Base layer*. Couple of variables here, though a perfectly good (in fact, my
+   usual go-to) tomato sauce can also skip this entire step:
+   - If you want meat with your sauce (pancetta/guanciale/bacon for an
+     amatriciana, ground beef or pork for a bolognese) you'll start out by
+     sautéing that in some sort of fat (probably olive oil), less fat for meat
+     with a lot of fat already in it, to brown and render out fat from the meat
+   - If you want onion in the final sauce, you'll chop them finely and sauté
+     them with whatever fat you've got (either from the meat, or olive oil or
+     butter if you're not making a meat sauce). Remember to always add a *bit*
+     of salt when sautéing onion like this, not for flavor but to draw out the
+     moisture. If you just want onion flavor but not bits of onion in the final
+     sauce, it's added whole later (so ignore this bullet point).
+   - If you feel like it (sometimes I do, usually I don't) you can also mince
+     garlic here and sauté that in with everything else. Add a little after the
+     onion, as garlic cooks faster than onion, unless you want something
+     roastier (usually you don't for tomato sauce)
+   - The traditional (so I'm told) thing to do with amatriciana, but also nice
+     with all variations, is to add in a little crushed red pepper with the
+     fat to flavor it slightly, but do this late so it doesn't burn
+   - If you have tomato paste on hand and feel like using it, it's also nice to
+     fry that in the oil for a little bit - usually I'd do that around the same
+     time as the garlic
+
+   If you're making tomato *paste* from your sauce, skip all of this - paste is
+   an ingredient, not a sauce on its own, so imo should be as neutral as
+   possible (i.e. just tomato).
+
+3. *Tomato layer*. Not a whole lot to do here, just add all of your tomatoes -
+   either your peeled and de-cored tomatoes from step 1 if you're using whole
+   tomatoes, or an entire can of whole, peeled san marzano tomatoes, including
+   the juice in the can - to a pot over medium-high heat. If you need more fat
+   or if you skipped step 2, this is where you'd add it - a classic and my
+   personal favorite is like 2/3rds to 3/4ths of a stick of butter, but you can
+   also go with olive oil. If you skipped the onion in step 2, add that here
+   too - usually that'd just be a fist-sized amount of onion or so peeled but
+   left with the stem on so you can fish it out from your final sauce later (and
+   snack on it!). Also salt here, again not to taste but primarily to draw out
+   moisture from the various ingredients.
+
+4. You can cook that for a wide variety of times, especially depending on how
+   hot you make your stove - there ends up being *lot* of liquid in there, so
+   you can go (in my experience) a reasonable amount hotter than you expect
+   without burning the sauce, though obviously your mileage may vary. The main
+   thing you're looking for is the whole chunks of tomato to break down, and the
+   whole sauce to get a texture that looks like it'll end up sticking to pasta
+   nicely. In all versions of this, stir pretty regularly with a wooden spoon,
+   and use the spoon to crush the big chunks of tomato occasionally.
+
+5. *Final layer*. Usually I don't do anything here - but if you feel
+   like it, usually right as you take stuff off the heat is where you'd add
+   basil, if you're using it. You can also add sugar to balance out too much
+   acidity from an especially acidic tomato here - I'm not going to tell anyone.
+   Also salt, but make sure to account for the extra salt you're gonna get from
+   the pasta water (see step 6)
+
+6. *Pasta*. You know how to cook pasta, I'm not going to tell you that. But,
+   like, salt your water until it tastes too salty, and remember to move the
+   pasta itself *directly* into the sauce pot from the pasta pot before it's
+   completely done cooking and without straining, bringing along some of the
+   pasta water (and a little extra for good measure) then finishing the pasta in
+   the sauce. You know, the thing you do for pasta. Remember the pasta water
+   will have salt in it, so adjust for that when salting the sauce overall (I
+   have made this mistake and ended up with too-salty pasta sauce).
+
+* Paste
+
+Start with the above recipe for tomato sauce, noting especially that (in my
+opinion) you should skip step 2 entirely. Keep cooking the sauce until it's
+*too* thick for pasta sauce (but don't burn it!), then spread it out across some
+sort of lined sheet pan (like a silpat, if you've got one) and bake in the oven
+at like 250-300 degrees for a *hell* of a long time - I've seen this take like
+10 hours, for an especially juicy batch of tomatoes, but obviously keep a close
+eye on it because it *definitely will burn* eventually. You're looking for the
+end result to be the texture of tomato paste, because that's what the recipe is
+for.  Especially if you're using garden-grown or otherwise fresh tomatoes,
+you'll notice quite a few seeds in the final product - don't worry too much
+about those, they've never bothered me. Once everything's done and cooled down,
+store in a jar in a fridge, topped with olive oil to seal things off and prevent
+oxidation. Use in all your future endeavors, including the tomato sauce recipe
+above itself. Tomato sauce is a beautiful oroborous.
diff --git a/users/aspen/web/shell.nix b/users/aspen/web/shell.nix
new file mode 100644
index 000000000000..7e7fccdc939c
--- /dev/null
+++ b/users/aspen/web/shell.nix
@@ -0,0 +1,9 @@
+with import <nixpkgs> { config.allowUnfree = true; };
+mkShell {
+  buildInputs = [
+    awscli
+    gnumake
+    tarsnap
+    certbot
+  ];
+}
diff --git a/users/aspen/web/site.nix b/users/aspen/web/site.nix
new file mode 100644
index 000000000000..057c4d3ee698
--- /dev/null
+++ b/users/aspen/web/site.nix
@@ -0,0 +1,12 @@
+args@{ pkgs ? import <nixpkgs> { }, ... }:
+
+let
+
+  orgExportHTML = import ./orgExportHTML.nix args;
+
+in
+
+{
+  index = orgExportHTML ./index.org;
+  recipes = orgExportHTML ./recipes;
+}