Compare commits

..

3 Commits

Author SHA1 Message Date
Cadey Ratio 2208703487 more edits
Signed-off-by: Xe Iaso <me@christine.website>
2022-04-30 16:44:38 -04:00
Cadey Ratio f535bac942 edits
Signed-off-by: Xe Iaso <me@christine.website>
2022-04-30 16:38:28 -04:00
Cadey Ratio ae78412edd nix flakes 4: WSL
Signed-off-by: Xe Iaso <me@christine.website>
2022-04-30 16:38:28 -04:00
148 changed files with 934 additions and 5356 deletions

1053
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -9,14 +9,13 @@ repository = "https://github.com/Xe/site"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
axum = { version = "0.5", features = ["headers"] } axum = "0.5"
axum-macros = "0.2" axum-macros = "0.2"
axum-extra = "0.3" axum-extra = "0.3"
color-eyre = "0.6" color-eyre = "0.6"
chrono = "0.4" chrono = "0.4"
comrak = "0.14.0" comrak = "0.12.1"
derive_more = "0.99" derive_more = "0.99"
dirs = "4"
envy = "0.4" envy = "0.4"
estimated_read_time = "1" estimated_read_time = "1"
futures = "0.3" futures = "0.3"
@ -27,14 +26,11 @@ hyper = "0.14"
kankyo = "0.3" kankyo = "0.3"
lazy_static = "1.4" lazy_static = "1.4"
log = "0.4" log = "0.4"
lol_html = "0.3"
maud = { version = "0.23.0", features = ["axum"] }
mime = "0.3.0" mime = "0.3.0"
prometheus = { version = "0.13", default-features = false, features = ["process"] } prometheus = { version = "0.13", default-features = false, features = ["process"] }
rand = "0" rand = "0"
regex = "1"
reqwest = { version = "0.11", features = ["json"] } reqwest = { version = "0.11", features = ["json"] }
serde_dhall = "0.11.2" serde_dhall = "0.11.0"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_yaml = "0.8" serde_yaml = "0.8"
sitemap = "0.4" sitemap = "0.4"
@ -48,11 +44,9 @@ xml-rs = "0.8"
url = "2" url = "2"
uuid = { version = "0.8", features = ["serde", "v4"] } uuid = { version = "0.8", features = ["serde", "v4"] }
xesite_types = { path = "./lib/xesite_types" }
# workspace dependencies # workspace dependencies
cfcache = { path = "./lib/cfcache" } cfcache = { path = "./lib/cfcache" }
xe_jsonfeed = { path = "./lib/jsonfeed" } jsonfeed = { path = "./lib/jsonfeed" }
mi = { path = "./lib/mi" } mi = { path = "./lib/mi" }
patreon = { path = "./lib/patreon" } patreon = { path = "./lib/patreon" }

View File

@ -7,7 +7,7 @@ tags:
--- ---
[*Last time in the christine dot website cinematic [*Last time in the christine dot website cinematic
universe:*](https://xeiaso.net/blog/unix-domain-sockets-2021-04-01) universe:*](https://christine.website/blog/unix-domain-sockets-2021-04-01)
*Unix sockets started to be used to grace the cluster. Things were at peace. *Unix sockets started to be used to grace the cluster. Things were at peace.
Then, a realization came through:* Then, a realization came through:*

View File

@ -1,65 +0,0 @@
---
title: "My Stance on Toxicity About Programming Languages"
date: 2022-05-23
tags:
- toxicity
- culture
---
I have been toxic and hateful in the past about programming language choice. I
now realize this is a mistake. I am sorry if my being toxic about programming
languages has harmed you.
By toxic, I mean doing things or saying things that imply people are lesser for
having different experience and preferences about programming languages. I have
seen people imply that using languages like PHP or Node.js means that they are
idiots or similar. This is toxic behavior and I do not want to be a part of it
in the future.
I am trying to not be toxic about programming languages in the future. Each
programming language is made to solve the tasks it was designed to solve and
being toxic about it helps nobody. By being toxic about programming languages
like this, I only serve to spread toxicity and then see it be repeated as the
people that look up to me as a role model will then strive to repeat my
behavior. This cannot continue. I do not want my passion projects to become
synonymous with toxicity and vitriol. I do not want to be known as the person
that hates $PROGRAMMING_LANGUAGE. I want to break the cycle.
With this, I want to confirm that I will not write any more attack articles
about programming languages. I am doing my best to ensure that this will also
spread to my social media actions, conference talks and as many other things as
I can.
I challenge all of you readers to take this challenge too. Don't spread toxicity
about programming languages. All of the PHP hate out there is a classic example
of this. PHP is a viable programming language that is used by a large percentage
of the internet. By insinuating that everyone using PHP is inferior (or worse)
you only serve to push people away and worst case cause them to be toxic about
the things you like. Toxicity breeds toxicity and the best way to stop it is to
be the one to break the cycle and have others follow in your footsteps.
I have been incredibly toxic about PHP in the past. PHP is one of if not the
most widely used programming languages for writing applications that run on a
web server. Its design makes it dead simple to understand how incoming HTTP
requests relate to files on the disk. There is no compile step. The steps to
make a change are to open the file on the server, make the change you want to
see and press F5. This is a developer experience that is unparalleled in most
HTTP frameworks that I've seen in other programming environments. PHP users
deserve better than to be hated on. PHP is an incredibly valid choice and I'm
sure that with the right linters and human review in the mix it can be as secure
as "properly written" services in Go, Java and Rust.
<xeblog-conv name="Cadey" mood="enby">Take the "don't be toxic about programming
languages" challenge! Just stop with the hate, toxicity and vitriol. Our jobs
are complicated enough already. Being toxic to eachother about how we decide to
solve problems is a horrible life decision at the least and actively harmful to
people's careers at most. Just stop.</xeblog-conv>
---
<xeblog-conv name="Mara" mood="hacker">This post is not intended as a sub-blog.
If you feel that this post is calling you out, please don't take this
personally. There is a lot of toxicity out there and it will take a long time to
totally disarm it, even with people dedicated to doing it. This is an adaptation
of [this twitter
thread](https://twitter.com/theprincessxena/status/1527765025561186304).</xeblog-conv>

View File

@ -1,175 +0,0 @@
---
title: "Anbernic Win600 First Impressions"
date: 2022-07-14
series: reviews
---
Right now PC gaming is largely a monopoly centered around Microsoft Windows.
Many PC games only support Windows and a large fraction of them use technical
means to prevent gamers on other platforms from playing those games. In 2021,
Valve introduced the [Steam Deck](https://www.steamdeck.com/en/) as an
alternative to that monopoly. There's always been a small, underground market
for handheld gaming PCs that let you play PC games on the go, but it's always
been a very niche market dominated by a few big players that charge a lot of
money relative to the game experience they deliver. The Steam Deck radically
changed this equation and it's still on backorder to this day. This has made
other manufacturers take notice and one of them was Anbernic.
[Anbernic](https://anbernic.com/) is a company that specializes in making retro
emulation handheld gaming consoles. Recently they released their
[Win600](https://anbernic.com/products/new-anbernic-win600) handheld. It has a
Radeon Silver 3020e or a Radeon Silver 3050e and today I am going to give you my
first impressions of it. I have the 3050e version.
<xeblog-conv name="Mara" mood="happy">A review of the Steam Deck is coming up
soon!</xeblog-conv>
One of the real standout features of this device is that Anbernic has been
working with Valve to allow people to run SteamOS on it! This makes us all one
step closer to having a viable competitor to Windows for gaming. SteamOS is
fantastic and has revolutionized gaming on Linux. It's good to see it coming to
more devices.
## Out of the box
I ordered my Win600 about 3 hours after sales opened. It arrived in a week and
came in one of those china-spec packages made out of insulation. If you've ever
ordered things from AliExpress you know what I'm talking about. It's just a
solid mass of insulation.
[![The console siting on my desk with its charging brick and included cable](https://cdn.xeiaso.net/file/christine-static/img/FXozAEjUsAQEg9d-smol.jpeg)](https://cdn.xeiaso.net/file/christine-static/img/FXozAEjUsAQEg9d.jpeg)
The unboxing experience was pretty great. The console came with:
* The console
* A box containing the charger and cable
* A slip of paper telling you how to set up windows without a wifi connection
* A screen protector and cleaning cloth (my screen protector was broken in the
box, so much for all that insulation lol)
* A user manual that points out obvious things about your device
One of the weirder things about this device is the mouse/gamepad slider on the
side. It changes the USB devices on the system and makes the gamepad either act
like an xinput joypad or a mouse and keyboard. The mouse and keyboard controls
are strange. Here are the controls I have discovered so far:
* R1 is right click
* L1 is left click
* A is enter
* The right stick very slowly skitters the mouse around the screen
* The left stick is a super aggressive scroll wheel
Figuring out these controls on the fly without any help from the manual meant
that I had taken long enough in the setup screen that [Cortana started to pipe
up](https://youtu.be/yn6bSm9HXFg) and guided me through the setup process. This
was not fun. I had to connect an external keyboard to finish setup.
<xeblog-conv name="Cadey" mood="coffee">This is probably not Anbernic's fault.
Windows is NOT made for smaller devices like this and oh god it
shows.</xeblog-conv>
## Windows 10 "fun"
There is also a keyboard button on the side. When you are using windows this
button summons a soft keyboard. Not the nice to use modern soft keyboard though,
the legacy terrible soft keyboard that Microsoft has had for forever and never
really updated. Using it is grating, like rubbing sandpaper all over your hands.
This made entering in my Wi-Fi password an adventure. It took my husband and I
15 minutes to get the device to connect to Wi-Fi. 15 minutes to connect to
Wi-Fi.
Once it was connected to Wi-Fi, I tried to update the system to the latest
version of windows. The settings update crashed. Windows Update's service also
crashed. Windows Update also randomly got stuck trying to start the installation
process for updates. Once updates worked and finished installing, I rebooted.
I tried to clean up the taskbar by disabling all of the random icons that
product managers at Microsoft want you to see. The Cortana button was stuck on
and I was unable to disable it. Trying to hide the Windows meet icon crashed
explorer.exe. I don't know what part of this is Windows going out of its way to
mess with me (I'm cursed) and what part of it is Windows really not being
optimized for this hardware in any sense of the way.
Windows is really painful on this device. It's obvious that Windows was not made
with this device in mind. There are buttons to hack around this (but not as far
as the task manager button I've seen on other handhelds), but overall trying to
use Windows with a game console is like trying to saw a log with a pencil
sharpener. It's just the wrong tool for the job. Sure you _can_ do it, but _can_
and _should_ are different words in English.
Another weird thing about Windows on this device is that the screen only reports
a single display mode: 1280x720. It has no support for lower resolutions to run
older games that only work on those lower resolutions. In most cases this will
be not an issue, but if you want to lower the resolution of a game to squeeze
more performance out then you may have issues.
## Steam
In a moment of weakness, I decided to start up Steam. Steam defaulted to Big
Picture mode and its first-time-user-experience made me set up Wi-Fi again.
There was no way to bypass it. I got out my moonlander again and typed in my
Wi-Fi password again, and then I downloaded Sonic Adventure 2 as a test for how
games feel on it. Sonic Adventure 2 is a very lightweight game (you can play it
for like 6.5 hours on a full charge of the Steam Deck) and I've played it to
_death_ over the years. I know how the game _should_ feel.
[![The starting screen of City Escape in Sonic Adventure 2, with the console propped up with a Steam Controller](https://cdn.xeiaso.net/file/christine-static/img/FXpIjhwUIAUEujg-smol.jpeg)](https://cdn.xeiaso.net/file/christine-static/img/FXpIjhwUIAUEujg.jpeg)
City Escape ran at a perfect 60 FPS at the device's native resolution. The main
thing I noticed though was the position of the analog sticks. Based on the
design of the device, I'm pretty sure they were going for something with a
PlayStation DualShock 4 layout with the action buttons on the top and the sticks
on the bottom. The sticks are too far down on the device. Playing Sonic
Adventure 2 was kind of painful.
## SteamOS
So I installed SteamOS on the device. Besides a weird issue with 5 GHZ Wi-Fi not
working and updates requiring me to reboot the device IMMEDIATELY after
connecting to Wi-Fi, it works great. I can install games and they run. The DPI
for SteamOS is quite wrong though. All the UI elements are painfully small. For
comparison, I put my Steam Deck on the same screen as I had on the Win600. The
Steam Deck is on top and the Win600 is on the bottom.
[![The game overview for Sonic Adventure 2 on both the Steam Deck and Anbernic Win600](https://f001.backblazeb2.com/file/christine-static/img/FXqSz_tVsAAU0wf-smol.jpeg)](https://f001.backblazeb2.com/file/christine-static/img/FXqSz_tVsAAU0wf.jpeg)
Yeah. It leaves things to be desired.
When I had SteamOS set up, I did find something that makes the Win600 slightly
better than the Steam Deck. When you are adding games to Steam with Emulation
Station you need to close the Steam client to edit the leveldb files that Steam
uses to track what games you can launch. On the Steam Deck, the Steam client
also enables the built-in controllers to act as a keyboard and mouse. This means
that you need to poke around and pray with the touchscreen to get EmuDeck games
up and running. The mouse/controller switch on the Win600 makes this slightly
more convenient because the controllers can always poorly act as a mouse and
keyboard.
When you are in KDE on the Win600, you don't get a soft keyboard at all. This is
mildly inconvenient, but can be fixed with the moonlander yet again. Here's a
screenshot of what my KDE desktop on the Win600 looks like:
[![My SteamOS desktop on the Win600, showing Crossette from Xenoblade Chronicles 2 center frame](https://f001.backblazeb2.com/file/christine-static/img/Screenshot_20220714_144655-smol.jpg)](https://f001.backblazeb2.com/file/christine-static/img/Screenshot_20220714_144655.png)
Overall, SteamOS is a lot more ergonomic in my opinion and will let you play
games to your heart's content.
The D-pad feels really good. I love how it responds. When I did a little bit of
Sonic Mania I never felt like I was inaccurate. There were some weird audio
hitches on Sonic Mania though where the music would cut out randomly. Not sure
what's going on with that. I could play through entire Pokemon games with that
D-pad.
## Conclusions for now
Overall I'm getting the feeling that this device is _okay_. It's not great, it's
not terrible, but it's okay. I need to get some more experience with it, but so
far it seems that this device really does have a weight class and oh god if you
play a game outside its weight class your UX goes to shit instantly. The battery
life leaves _a lot_ to be desired so far. However it does work. It's hard to not
compare this to the Steam Deck, but it's so much less in comparison to the Steam
Deck.
I don't know how I feel about this device. I'm not sure it's worth the money. I
need to get more experience with it. I'll have a better sense of all this when I
write my full review. Stay tuned for that!

View File

@ -21,7 +21,7 @@ up being the _worst_ experience that I have using an aarch64 MacBook.
[This website](https://github.com/Xe/site) is a fairly complicated webapp [This website](https://github.com/Xe/site) is a fairly complicated webapp
written in Rust. As such it makes for a fairly decent compile stress test. I'm written in Rust. As such it makes for a fairly decent compile stress test. I'm
going to do a compile test against my [Ryzen going to do a compile test against my [Ryzen
3600](https://xeiaso.net/blog/nixos-desktop-flow-2020-04-25) with this M1 3600](https://christine.website/blog/nixos-desktop-flow-2020-04-25) with this M1
MacBook Air. MacBook Air.
My tower is running this version of Rust: My tower is running this version of Rust:

View File

@ -181,7 +181,7 @@ server, my kubernetes cluster and my dokku server:
- hlang -> https://h.christine.website - hlang -> https://h.christine.website
- mi -> https://mi.within.website - mi -> https://mi.within.website
- printerfacts -> https://printerfacts.cetacean.club - printerfacts -> https://printerfacts.cetacean.club
- xesite -> https://xeiaso.net - xesite -> https://christine.website
- graphviz -> https://graphviz.christine.website - graphviz -> https://graphviz.christine.website
- idp -> https://idp.christine.website - idp -> https://idp.christine.website
- oragono -> ircs://irc.within.website:6697/ - oragono -> ircs://irc.within.website:6697/

View File

@ -1,125 +0,0 @@
---
title: "Site Update: The Big Domain Move To xeiaso.net"
date: 2022-05-28
tags:
- dns
---
Hello all!
If you take a look in the URL bar of your browser (or on the article URL section
of your feed reader), you should see that there is a new domain name! Welcome to
[xeiaso.net](https://xeiaso.net)!
Hopefully nothing broke in the process of moving things over, I tried to make
sure that everything would forward over and today I'm going to explain how I did
that.
I have really good SEO on my NixOS articles, and for my blog in general. I did
not want to risk tanking that SEO when I moved domain names, so I have been
putting this off for the better part of a year. As for why now? I got tired of
internets complaning that the URL was "christine dot website" when I wanted to
be called "Xe". Now you have no excuse.
So the first step was to be sure that everything got forwarded over to the new
domain. After buying the domain name and setting everything up in Cloudflare
(including moving my paid plan over), I pointed the new domain at my server and
then set up a new NixOS configuration block to have that domain name point to my
site binary:
```nix
services.nginx.virtualHosts."xeiaso.net" = {
locations."/" = {
proxyPass = "http://unix:${toString cfg.sockPath}";
proxyWebsockets = true;
};
forceSSL = cfg.useACME;
useACMEHost = "xeiaso.net";
extraConfig = ''
access_log /var/log/nginx/xesite.access.log;
'';
};
```
After that was working, I then got a list of all the things that probably
shouldn't be redirected from. In most cases, most HTTP clients should do the
right thing when getting a permanent redirect to a new URL. However, we live in
a fallen world where we cannot expect clients to do the right thing. Especially
RSS feed readers.
So I made a list of all the things that I was afraid to make permanent redirects
for and here it is:
* `/jsonfeed` - a JSONFeed package for Go
([docs](https://pkg.go.dev/christine.website/jsonfeed)), I didn't want to
break builds by issuing a permanent redirect that would not match the
[go.mod](https://tulpa.dev/Xe/jsonfeed/src/branch/master/go.mod) file.
* `/.within/health` - the healthcheck route used by monitoring. I didn't want to
find out if NodePing blew up on a 301.
* `/.within/website.within.xesite/new_post` - the URL used by the [Android
app](https://play.google.com/store/apps/details?id=website.christine.xesite)
widget to let you know when a new post is published. I didn't want to find out
if Android's HTTP library handles redirects properly or not.
* `/blog.rss` - RSS feed readers are badly implemented. I didn't want to find
out if it would break people's feeds entirely. I actually care about people
that read this blog over RSS and I'm sad that poorly written feed readers
punish this server so much.
* `/blog.atom` - See above.
* `/blog.json` - See above.
Now that I have the list of URLs to not forward, I can finally write the small
bit of Nginx config that will set up permanent forwards (HTTP status code 301)
for every link pointing to the old domain. It will look something like this:
```nginx
location / {
return 301 https://xeiaso.net$request_uri;
}
```
<xeblog-conv name="Mara" mood="hacker">Note that it's using `$request_uri` and
not just `$uri`. If you use `$uri` you run the risk of [CRLF
injection](https://reversebrain.github.io/2021/03/29/The-story-of-Nginx-and-uri-variable/),
which will allow any random attacker to inject HTTP headers into incoming
requests. This is not a good thing to have happen, to say the
least.</xeblog-conv>
So I wrote a little bit of NixOS config that automatically bridges the gap:
```nix
services.nginx.virtualHosts."christine.website" = let proxyOld = {
proxyPass = "http://unix:${toString cfg.sockPath}";
proxyWebsockets = true;
}; in {
locations."/jsonfeed" = proxyOld;
locations."/.within/health" = proxyOld;
locations."/.within/website.within.xesite/new_post" = proxyOld;
locations."/blog.rss" = proxyOld;
locations."/blog.atom" = proxyOld;
locations."/blog.json" = proxyOld;
locations."/".extraConfig = ''
return 301 https://xeiaso.net$request_uri;
'';
forceSSL = cfg.useACME;
useACMEHost = "christine.website";
extraConfig = ''
access_log /var/log/nginx/xesite_old.access.log;
'';
};
```
This will point all the scary paths to the site itself and have
`https://christine.website/whatever` get forwarded to
`https://xeiaso.net/whatever`, this makes sure that every single link that
anyone has ever posted will get properly forwarded. This makes link rot
literally impossible, and helps ensure that I keep my hard-earned SEO.
I also renamed my email address to `me@xeiaso.net`. Please update your address
books and spam filters accordingly. Also update my name to `Xe Iaso` if you
haven't already.
I've got some projects in the back burner that will make this blog even better!
Stay tuned and stay frosty.
What was formerly known as the "christine dot website cinematic universe" is now
known as the "xeiaso dot net cinematic universe".

View File

@ -78,7 +78,7 @@ for this:
``` ```
Xe Iaso (zi ai-uh-so) Xe Iaso (zi ai-uh-so)
https://xeiaso.net https://christine.website
.i la budza pu cusku lu .i la budza pu cusku lu
<<.i ko snura .i ko kanro <<.i ko snura .i ko kanro

View File

@ -36,7 +36,7 @@ Your website should include at least the following things:
- Links to or words about projects of yours that you are proud of - Links to or words about projects of yours that you are proud of
- Some contact information (an email address is a good idea too) - Some contact information (an email address is a good idea too)
If you feel comfortable doing so, I'd also suggest putting your [resume](https://xeiaso.net/resume) If you feel comfortable doing so, I'd also suggest putting your [resume](https://christine.website/resume)
on this site too. Even if it's just got your foodservice jobs or education on this site too. Even if it's just got your foodservice jobs or education
history (including your high school diploma if need be). history (including your high school diploma if need be).
@ -47,7 +47,7 @@ not.
## Make a Tech Blog On That Site ## Make a Tech Blog On That Site
This has been the single biggest thing to help me grow professionally. I regularly This has been the single biggest thing to help me grow professionally. I regularly
put [articles](https://xeiaso.net/blog) on my blog, sometimes not even about put [articles](https://christine.website/blog) on my blog, sometimes not even about
technology topics. Even if you are writing about your take on something people have technology topics. Even if you are writing about your take on something people have
already written about, it's still good practice. Your early posts are going to be already written about, it's still good practice. Your early posts are going to be
rough. It's normal to not be an expert when starting out in a new skill. rough. It's normal to not be an expert when starting out in a new skill.

View File

@ -54,7 +54,7 @@ by it. That attempt to come out failed and I was put into Christian
writing down my thoughts in a journal to this day. writing down my thoughts in a journal to this day.
So that day I hit "send" on [the So that day I hit "send" on [the
email](https://xeiaso.net/blog/coming-out-2015-12-01) was mortally email](https://christine.website/blog/coming-out-2015-12-01) was mortally
terrifying. All that fear from so long ago came raging up to the surface and I terrifying. All that fear from so long ago came raging up to the surface and I
was left in a crying and vulnerable state. However it ended up being a good kind was left in a crying and vulnerable state. However it ended up being a good kind
of cry, the healing kind. of cry, the healing kind.

View File

@ -21,7 +21,7 @@ named [dyson][dyson] in order to help me manage Terraform as well as create
Kubernetes manifests from [a template][template]. This works for the majority of Kubernetes manifests from [a template][template]. This works for the majority of
my apps, but it is difficult to extend at this point for a few reasons: my apps, but it is difficult to extend at this point for a few reasons:
[cultk8s]: https://xeiaso.net/blog/the-cult-of-kubernetes-2019-09-07 [cultk8s]: https://christine.website/blog/the-cult-of-kubernetes-2019-09-07
[dyson]: https://github.com/Xe/within-terraform/tree/master/dyson [dyson]: https://github.com/Xe/within-terraform/tree/master/dyson
[template]: https://github.com/Xe/within-terraform/blob/master/dyson/src/dysonPkg/deployment_with_ingress.yaml [template]: https://github.com/Xe/within-terraform/blob/master/dyson/src/dysonPkg/deployment_with_ingress.yaml

View File

@ -1,205 +0,0 @@
---
title: Writing Coherently At Scale
date: 2022-06-29
tags:
- writing
vod:
youtube: https://youtu.be/pDOoqqu06-8
twitch: https://www.twitch.tv/videos/1513874389
---
As someone who does a lot of writing, I have been asked how to write about
things. I have been asked about it enough that I am documenting this here so you
can all understand my process. This is not a prescriptive system that you must
do in order to make Quality Content™️, this is what I do.
<xeblog-conv name="Cadey" mood="coffee">I honestly have no idea if this is a
"correct" way of doing things, but it seems to work well enough. Especially so
if you are reading this.</xeblog-conv>
<xeblog-hero file="great-wave-cyberpunk" prompt="the great wave off of kanagawa, cyberpunk, hanzi inscription"></xeblog-hero>
## The Planning Phase
To start out the process of writing about something, I usually like to start
with the end goal in mind. If I am writing about an event or technology thing,
I'll start out with a goal that looks something like this:
> Explain a split DNS setup and its advantages and weaknesses so that people can
> make more informed decisions about their technical setups.
It doesn't have to be very complicated or intricate. Most of the complexity
comes up naturally during the process of writing the intermediate steps. Think
about the end goal or what you want people to gain from reading the article.
I've also found it helps to think about the target audience and assumed skills
of the reader. I'll usually list out the kind of person that would benefit from
this the most and how it will help them. Here's an example:
> The reader is assumed to have some context about what DNS is and wants to help
> make their production environment more secure, but isn't totally clear on how
> it helps and what tradeoffs are made.
State what the reader is to you and how the post benefits them. Underthink it.
It's tempting to overthink this, but really don't. You can overthink the
explanations later.
### The Outline
Once I have an end goal and the target audience in mind, then I make an outline
of what I want the post to contain. This outline will have top level items for
generic parts of the article or major concepts/steps and then I will go in and
add more detail inside each top level item. Here is an example set of top level
items for that split DNS post:
```markdown
- Introduction
- Define split DNS
- How split DNS is different
- Where you can use split DNS
- Advantages of split DNS
- Tradeoffs of split DNS
- Conclusion
```
Each step should build on the last and help you reach towards the end goal.
After I write the top level outline, I start drilling down into more detail. As
I drill down into more detail about a thing, the bullet points get nested
deeper, but when topics change then I go down a line. Here's an example:
```markdown
- Introduction
- What is DNS?
- Domain Name Service
- Maps names to IP addresses
- Sometimes it does other things, but we're not worrying about that today
- Distributed system
- Intended to have the same data everywhere in the world
- It can take time for records to be usable from everywhere
```
Then I will go in and start filling in the bullet tree with links and references
to each major concept or other opinions that people have had about the topic.
For example:
```markdown
- Introduction
- What is DNS?
- Domain Name Service
- https://datatracker.ietf.org/doc/html/rfc1035
- Maps names to IP addresses
- Sometimes it does other things, but we're not worrying about that today
- Distributed system
- Intended to have the same data everywhere in the world
- It can take time for records to be usable from everywhere
- https://jvns.ca/blog/2021/12/06/dns-doesn-t-propagate/
```
These help me write about the topic and give me links to add to the post so that
people can understand more if they want to. You should spend most of your time
writing the outline. The rest is really just restating the outline in sentences.
## Writing The Post
After each top level item is fleshed out enough, I usually pick somewhere to
start and add some space after a top level item. Then I just start writing. Each
top level item usually maps to a few paragraphs / a section of the post. I
usually like to have each section have its own little goal / context to it so
that readers start out from not understanding something and end up understanding
it better. Here's an example:
> If you have used a computer in the last few decades or so, you have probably
> used the Domain Name Service (DNS). DNS maps human-readable names (like
> `google.com`) to machine-readable IP addresses (like `182.48.247.12`). Because
> of this, DNS is one of the bedrock protocols of the modern internet and it
> usually is the cause of most failures in big companies.
>
> DNS is a globally distributed system without any authentication or way to
> ensure that only authorized parties can query IP addresses for specific domain
> names. As a consequence of this, this means that anyone can get the IP address
> of a given server if they have the DNS name for it. This also means that
> updating a DNS record can take a nontrivial amount of time to be visible from
> everywhere in the world.
>
> Instead of using public DNS records for internal services, you can set up a
> split DNS configuration so that you run an internal DNS server that has your
> internal service IP addresses obscured away from the public internet. This
> means that attackers can't get their hands on the IP addresses of your
> services so that they are harder to attack. In this article, I'm going to
> spell out how this works, the advantages of this setup, the tradeoffs made in
> the process and how you can implement something like this for yourself.
In the process of writing, I will find gaps in the outline and just fix it by
writing more words than the outline suggested. This is okay, and somewhat
normal. Go with the flow.
I expand each major thing into its component paragraphs and will break things up
into sections with markdown headers if there is a huge change in topics. Adding
section breaks can also help people stay engaged with the post. Giant walls of
text are hard to read and can make people lose focus easily.
Another trick I use to avoid my posts being giant walls of text is what I call
"conversation snippets". These look like this:
<xeblog-conv name="Mara" mood="hacker">These are words and I am saying
them!</xeblog-conv>
I use them for both creating [Socratic
dialogue](https://en.wikipedia.org/wiki/Socratic_dialogue) and to add prose
flair to my writing. I am more of a prose writer [by
nature](https://xeiaso.net/blog/the-oasis), and I find that this mix allows me
to keep both technical and artistic writing up to snuff.
<xeblog-conv name="Cadey" mood="enby">Amusingly, I get asked if the characters
in my blog are separate people all giving their input into things. They are
characters, nothing more. If you ever got an impression otherwise, then I have
done my job as a writer _incredibly well_.</xeblog-conv>
Just flesh things out and progressively delete parts of the outline as you go.
It gets easier.
### Writing The Conclusion
I have to admit, I really suck at writing conclusions. They are annoying for me
to write because I usually don't know what to put there. Sometimes I won't even
write a conclusion at all and just end the article there. This doesn't always
work though.
A lot of the time when I am describing how to do things I will end the article
with a "call to action". This is a few sentences that encourages the reader to
try the thing that I've been writing about out for themselves. If I was turning
that split DNS article from earlier into a full article, the conclusion
could look something like this:
> ---
>
> If you want an easy way to try out a split DNS configuration, install
> [Tailscale](https://tailscale.com/) on a couple virtual machines and enable
> [MagicDNS](https://tailscale.com/kb/1081/magicdns/). This will set up a split
> DNS configuration with a domain that won't resolve globally, such as
> `hostname.example.com.beta.tailscale.net`, or just `hostname` for short.
>
> I use this in my own infrastructure constantly. It has gotten to the point
> where I regularly forget that Tailscale is involved at all, and become
> surprised when I can't just access machines by name.
>
> A split DNS setup isn't a security feature (if anything, it's more of an
> obscurity feature), but you can use it to help administrate your systems by
> making your life easier. You can update records on your own schedule and you
> don't have to worry about outside attackers getting the IP addresses of your
> services.
I don't like giving the conclusion a heading, so I'll usually use a [horizontal
rule (`---` or `<hr
/>`)](https://www.coffeecup.com/help/articles/what-is-a-horizontal-rule/) to
break it off.
---
This is how I write about things. Do you have a topic in mind that you have
wanted to write about for a while? Try this system out! If you get something
that you like and want feedback on how to make it shine, email me at
`iwroteanarticle at xeserv dot us` with either a link to it or the draft
somehow. I'll be sure to read it and reply back with both what I liked and some
advice on how to make it even better.

View File

@ -82,7 +82,7 @@ terrible idea. Microservices architectures are not planned. They are an
evolutionary result, not a fully anticipated feature. evolutionary result, not a fully anticipated feature.
Finally, don’t “design for the future”. The future [hasn’t happened Finally, don’t “design for the future”. The future [hasn’t happened
yet](https://xeiaso.net/blog/all-there-is-is-now-2019-05-25). Nobody yet](https://christine.website/blog/all-there-is-is-now-2019-05-25). Nobody
knows how it’s going to turn out. The future is going to happen, and you can knows how it’s going to turn out. The future is going to happen, and you can
either adapt to it as it happens in the Now or fail to. Don’t make things overly either adapt to it as it happens in the Now or fail to. Don’t make things overly
modular, that leads to insane things like dynamically linking parts of an modular, that leads to insane things like dynamically linking parts of an

View File

@ -279,7 +279,7 @@ step.
The deploy step does two small things. First, it installs The deploy step does two small things. First, it installs
[dhall-yaml](https://github.com/dhall-lang/dhall-haskell/tree/master/dhall-yaml) [dhall-yaml](https://github.com/dhall-lang/dhall-haskell/tree/master/dhall-yaml)
for generating the Kubernetes manifest (see for generating the Kubernetes manifest (see
[here](https://xeiaso.net/blog/dhall-kubernetes-2020-01-25)) and then [here](https://christine.website/blog/dhall-kubernetes-2020-01-25)) and then
runs runs
[`scripts/release.sh`](https://tulpa.dev/cadey/printerfacts/src/branch/master/scripts/release.sh): [`scripts/release.sh`](https://tulpa.dev/cadey/printerfacts/src/branch/master/scripts/release.sh):

View File

@ -67,7 +67,7 @@ Hopefully Valve can improve the state of VR on Linux with the "deckard".
2021 has had some banger releases. Halo Infinite finally dropped. Final Fantasy 2021 has had some banger releases. Halo Infinite finally dropped. Final Fantasy
7 Remake came to PC. [Metroid 7 Remake came to PC. [Metroid
Dread](https://xeiaso.net/blog/metroid-dread-review-2021-10-10) finally Dread](https://christine.website/blog/metroid-dread-review-2021-10-10) finally
came out after being rumored for more than half of my lifetime. Forza Horizon 5 came out after being rumored for more than half of my lifetime. Forza Horizon 5
raced out into the hearts of millions. Overall, it was a pretty good year to be raced out into the hearts of millions. Overall, it was a pretty good year to be
a gamer. a gamer.

View File

@ -1,95 +0,0 @@
---
title: "Fly.io: the Reclaimer of Heroku's Magic"
date: 2022-05-15
tags:
- flyio
- heroku
vod:
twitch: https://www.twitch.tv/videos/1484123245
youtube: https://youtu.be/BAgzkKpLVt4
---
Heroku was catalytic to my career. It's been hard to watch the fall from grace.
Don't get me wrong, Heroku still _works_, but it's obviously been in maintenance
mode for years. When I worked there, there was a goal that just kind of grew in
scope over and over without reaching an end state: the Dogwood stack.
In Heroku each "stack" is the substrate the dynos run on. It encompasses the AWS
runtime, the HTTP router, the logging pipeline and a bunch of the other
infrastructure like the slug builder and the deployment infrastructure. The
three stacks Heroku has used are named after trees: Aspen, Bamboo and Cedar.
Every Heroku app today runs on the Cedar stack, and compared to Bamboo it was a
generational leap in capability. Cedar was what introduced buildpacks and
support for any language under the sun. Prior stacks railroaded you into Ruby on
Rails (Heroku used to be a web IDE for making Rails apps). However there were
always plans to improve with another generational leap. This ended up being
called the "Dogwood stack", but Dogwood never totally materialized because it
was too ambitious for Heroku to handle post-acquisition. Parts of Dogwood's
roadmap ended up being used in the implementation of Private Spaces, but as a
whole I don't expect Dogwood to materialize in Heroku in the way we all had
hoped.
However, I can confidently say that [fly.io](https://fly.io) seems like a viable
inheritor of the mantle of responsibility that Heroku has left into the hands of
the cloud. fly.io is a Platform-as-a-Service that hosts your applications on top
of physical dedicated servers run all over the world instead of being a reseller
of AWS. This allows them to get your app running in multiple regions for a lot
less than it would cost to run it on Heroku. They also use anycasting to allow
your app to use the same IP address globally. The internet itself will load
balance users to the nearest instance using BGP as the load balancing
substrate.
<xeblog-conv name="Cadey" mood="enby">People have been asking me what I would
suggest using instead of Heroku. I have been unable to give a good option until
now. If you are dissatisfied with the neglect of Heroku in the wake of the
Salesforce acquisition, take a look at fly.io. Its free tier is super generous.
I worked at Heroku and I am beyond satisfied with it. I'm considering using it
for hosting some personal services that don't need something like
NixOS.</xeblog-conv>
Applications can be built either using [cloud native
buildpacks](https://fly.io/docs/reference/builders/), Dockerfiles or arbitrary
docker images that you generated with something like Nix's
`pkgs.dockerTools.buildLayeredImage`. This gives you freedom to do whatever you
want like the Cedar stack, but at a fraction of the cost. Its default instance
size is likely good enough to run the blog you are reading right now and would
be able to do that for $2 a month plus bandwidth costs (I'd probably estimate
that to be about $3-5, depending on how many times I get on the front page of
Hacker News).
You can have persistent storage in the form of volumes, poke the internal DNS
server fly.io uses for service discovery, run apps that use arbitrary TCP/UDP
ports (even a DNS server!), connect to your internal network over WireGuard, ssh
into your containers, and import Heroku apps into fly.io without having to
rebuild them. This is what the Dogwood stack should have been. This represents a
generational leap in the capabilities of what a Platform as a Service can do.
The stream VOD in the footer of this post contains my first impressions using
fly.io to try and deploy an app written with [Deno](https://deno.land) to the
cloud. I ended up creating a terrible CRUD app on stream using SQLite that
worked perfectly beyond expectations. I was able to _restart the app_ and my
SQLite database didn't get blown away. I could easily imagine myself combining
something like [litestream](https://litestream.io) into my docker images to
automate offsite backups of SQLite databases like this. It was magical.
<xeblog-conv name="Mara" mood="happy">If you've never really used Heroku, for
context each dyno has a mutable filesystem. However that filesystem gets blown
away every time a dyno reboots. Having something that is mutable and persistent
is mind-blowing.</xeblog-conv>
Everything else you expect out of Heroku works like you'd expect in fly.io. The
only things I can see missing are automated Redis hosting by the platform
(however this seems intentional as fly.io is generic enough [to just run redis
directly for you](https://fly.io/docs/reference/redis/)) and the marketplace.
The marketplace being absent is super reasonable, seeing as Heroku's marketplace
only really started existing as a result of them being the main game in town
with all the mindshare. fly.io is a voice among a chorus, so it's understandable
that it wouldn't have the same treatment.
Overall, I would rate fly.io as a worthy inheritor of Heroku's mantle as the
platform as a service that is just _magic_. It Just Works™️. There was no
fighting it at a platform level, it just worked. Give it a try.
<xeblog-conv name="Cadey" mood="enby">Don't worry
[@tqbf](https://twitter.com/tqbf), fly.io put in a good showing. I still wanna
meet you at some conference.</xeblog-conv>

View File

@ -8,7 +8,7 @@ series: conlangs
`h` is a conlang project that I have been working off and on for years. It is infinitely simply teachable, trivial to master and can be used to represent the entire scope of all meaning in any facet of the word. All with a single character. `h` is a conlang project that I have been working off and on for years. It is infinitely simply teachable, trivial to master and can be used to represent the entire scope of all meaning in any facet of the word. All with a single character.
This is a continuation from [this post](https://xeiaso.net/blog/the-origin-of-h-2015-12-14). If this post makes sense to you, please let me know and/or schedule a psychologist appointment just to be safe. This is a continuation from [this post](https://christine.website/blog/the-origin-of-h-2015-12-14). If this post makes sense to you, please let me know and/or schedule a psychologist appointment just to be safe.
## Phonology ## Phonology

View File

@ -363,14 +363,14 @@ my blog's [JSONFeed](/blog.json):
#!/usr/bin/env bash #!/usr/bin/env bash
# xeblog-post.sh # xeblog-post.sh
curl -s https://xeiaso.net/blog.json | jq -r '.items[0] | "\(.title) \(.url)"' curl -s https://christine.website/blog.json | jq -r '.items[0] | "\(.title) \(.url)"'
``` ```
At the time of writing this post, here is the output I get from this command: At the time of writing this post, here is the output I get from this command:
``` ```
$ ./xeblog-post.sh $ ./xeblog-post.sh
Anbernic RG280M Review https://xeiaso.net/blog/rg280m-review Anbernic RG280M Review https://christine.website/blog/rg280m-review
``` ```
What else could you do with pipes and redirection? The cloud's the limit! What else could you do with pipes and redirection? The cloud's the limit!

View File

@ -16,7 +16,7 @@ it. This is a sort of spiritual successor to my old
ecosystem since then, as well as my understanding of the language. ecosystem since then, as well as my understanding of the language.
[go]: https://golang.org [go]: https://golang.org
[gswg]: https://xeiaso.net/blog/getting-started-with-go-2015-01-28 [gswg]: https://christine.website/blog/getting-started-with-go-2015-01-28
Like always, feedback is very welcome. Any feedback I get will be used to help Like always, feedback is very welcome. Any feedback I get will be used to help
make this book even better. make this book even better.

View File

@ -20,16 +20,16 @@ for browsers, but I've been using it for server-side tasks.
I have written more about/with WebAssembly in the past in these posts: I have written more about/with WebAssembly in the past in these posts:
- https://xeiaso.net/talks/webassembly-on-the-server-system-calls-2019-05-31 - https://christine.website/talks/webassembly-on-the-server-system-calls-2019-05-31
- https://xeiaso.net/blog/olin-1-why-09-1-2018 - https://christine.website/blog/olin-1-why-09-1-2018
- https://xeiaso.net/blog/olin-2-the-future-09-5-2018 - https://christine.website/blog/olin-2-the-future-09-5-2018
- https://xeiaso.net/blog/land-1-syscalls-file-io-2018-06-18 - https://christine.website/blog/land-1-syscalls-file-io-2018-06-18
- https://xeiaso.net/blog/templeos-2-god-the-rng-2019-05-30 - https://christine.website/blog/templeos-2-god-the-rng-2019-05-30
This is a continuation of the following two posts: This is a continuation of the following two posts:
- https://xeiaso.net/blog/the-origin-of-h-2015-12-14 - https://christine.website/blog/the-origin-of-h-2015-12-14
- https://xeiaso.net/blog/formal-grammar-of-h-2019-05-19 - https://christine.website/blog/formal-grammar-of-h-2019-05-19
All of the relevant code for h is [here](https://github.com/Xe/x/tree/master/cmd/h). All of the relevant code for h is [here](https://github.com/Xe/x/tree/master/cmd/h).

View File

@ -1,102 +0,0 @@
---
title: "I Miss Heroku's DevEx"
date: 2022-05-12
---
If you've never really experienced it before, it's gonna sound really weird.
Basically the main way that Heroku worked is that they would set up a git remote
for each "app" it hosted. Each "app" had its source code in a git repo and a
"Procfile" that told Heroku what to do with it. So when it came time to deploy
that app, you'd just `git push heroku main` and then Heroku would just go off
and build that app and run it _somewhere_ in the cloud. You got back a HTTPS URL
and then bam you have a website.
The developer experience didn't stop there. Most of how Heroku apps are
configured are via environment variables, and there were addons that let you
tell Heroku things like "hi yes I would like one (1) postgres please" and the
platform would spin up a database somewhere and drop a config variable into the
app's config. It was magic. Things just worked and it left you free to go do
what made you money.
Heroku's free tier got me the in I needed to make my career really start. If I
didn't have something like Heroku in my life I doubt that my career would be the
same or even I would be the same person I am today. It's really hard to describe
what having access to a platform that lets you turn ideas into production
quality code does to your output ability. I even ended up reinventing Heroku a
few times in my career (working for Deis and later reinventing most of the core
of Heroku as a project between jobs), but nothing really hit that same level of
wonder/magic that Heroku did.
I ended up working there and when I did I understood why Heroku had fallen so
much. Heroku is owned by Salesforce and Salesforce doesn't really understand
what they had acquired with Heroku. Heroku had resisted integration into the
larger Salesforce organization and as a result was really really starved for
headcount. I had to have a come-to-jesus meeting with the CTO of Heroku where I
spelled out my medical needs and how the insurance that the contracting agency
they were using was insufficent (showing comparisons between bills for blood
draws where paying with the insurance ended up costing me more than not using
it). I got hired and then that was just in time for Salesforce to really start
pulling Heroku into the fold.
The really great part about working at Heroku was that setting up a new service
was so easy that the majority of the productionalization checklist was just
enabling hidden feature flags to lock down the app. I'm surprised that didn't
get streamlined.
The Heroku I joined no longer exists. I joined Heroku but I left Salesforce. I
can't blame any of my coworkers from Heroku from fleeing the sinking ship. The
ship has been sinking for years but the culture of Heroku really stuck around
long enough that it was hard to realize the ship was sinking.
It can really be seen with how long it's taken Heroku to react to [that one
horrible security event](https://status.heroku.com/incidents/2413) they've been
dealing with. Based on what I remember about the internal architecture (it was a
microservices tire fire unlike you have ever seen, it's part of the inspiration
that lead me to write [this post](/blog/make-microservices-cluster-2022-01-27))
and the notes that have been put on the public facing status page, I'm guessing
that most of Heroku is "legacy" code (IE: nobody on the team that made this
service works here anymore) at this point. When I was there most of the services
on my team were "legacy" code that was production-facing, load-bearing and
overall critical to the company succeeding; but it was built to be reliable
enough that we could overall ignore it until it was actually falling over. But
then because of the ways that things were chorded together it could take a very
long time to actually fix issues because the symptoms were all over the place.
Don't get me wrong, I loved working there but it was mostly for the people. That
and the ability to say that I helped make Heroku better for the next generation.
If you've ever used the metrics tab on Heroku, chances are that you've
encountered my code indirectly. If you've ever done Heroku threshold autoscaling
or response time alerting, you've dealt with code I helped write. The body of
Heroku remains but the soul has long since fled.
At the few points of my career that I have tried to reinvent Heroku (be it on my
own or working for a company doing that), there has mostly been this weird
realization that in order to have a thing like Heroku exist it really needs to
be hosted by someone else in the cloud. One of the places I worked for was
selling self-hosted Heroku on top of CoreOS and fleetd (remember fleetd? that
was magical) and while it did have a lot of the same developer experience, it
never really had the same magic feeling. I had the same problem with my own
implementation. Sure you can get the app hosting part of Heroku fairly easily
(and with Docker being as mature as it was at that point yeah it was fairly
easy). But when it comes to the real experience of addons and the whole
ecosystem there, you really need either to get very lucky or become an industry
standard. Realistically though, you aren't going to be either lucky or an
industry standard and then you need to also reinvent the next 80% of Heroku from
scratch on hardware that you don't control. It's no wonder that ultimately
failed (even though one of them was bought out by Microsoft after doing a weird
Kubernetes pivot).
There was something really magical about the whole thing that I really miss to
this day. Heroku was at least a decade ahead of its time as far as developer
experience goes. Things Just Worked in ways that would probably put a lot of us
out of jobs if they really took off. I miss the process for putting something on
the internet to just be a `git push` and trust that the machine will just take
care of it. I wonder if we'll ever really have something like that on top of Nix
or NixOS.
---
If you're reading this before the 12th, welcome to an experiment! I've been
wondering about how to make some of my posts Patreon exclusive for a week. This
post was published for my patrons on the 5th of May. Please don't share this
link around on social media until the 12th, but privately sharing it is okay.

View File

@ -14,7 +14,7 @@ goes into hitting enter on christine.website and this website being loaded.
## Beginnings ## Beginnings
The user types in `https://xeiaso.net` into the address bar and hits The user types in `https://christine.website` into the address bar and hits
enter on the keyboard. This sends a signal over USB to the computer and the enter on the keyboard. This sends a signal over USB to the computer and the
kernel polls the USB controller for a new message. It's recognized as from the kernel polls the USB controller for a new message. It's recognized as from the
keyboard. The input is then sent to the browser through an input driver talking keyboard. The input is then sent to the browser through an input driver talking

View File

@ -46,7 +46,7 @@ the Rust compiler.
[nixos]: https://nixos.org/nixos/ [nixos]: https://nixos.org/nixos/
[nix]: https://nixos.org/nix/ [nix]: https://nixos.org/nix/
[howistartnix]: https://xeiaso.net/blog/how-i-start-nix-2020-03-08 [howistartnix]: https://christine.website/blog/how-i-start-nix-2020-03-08
## A new project ## A new project

View File

@ -10,7 +10,7 @@ From time to time, I am outright wrong on my blog. This is one of those times.
In my [last post about Nix][nixpost], I didn't see the light yet. I think I do In my [last post about Nix][nixpost], I didn't see the light yet. I think I do
now, and I'm going to attempt to clarify below. now, and I'm going to attempt to clarify below.
[nixpost]: https://xeiaso.net/blog/thoughts-on-nix-2020-01-28 [nixpost]: https://christine.website/blog/thoughts-on-nix-2020-01-28
Let's talk about a more simple scenario: writing a service in Go. This service Let's talk about a more simple scenario: writing a service in Go. This service
will depend on at least the following: will depend on at least the following:

View File

@ -453,7 +453,7 @@ module. Here's how I do it:
You can add this to your `imports` in your server's `configuration.nix` using You can add this to your `imports` in your server's `configuration.nix` using
[the layout I described in this [the layout I described in this
post](https://xeiaso.net/blog/morph-setup-2021-04-25). This would go in post](https://christine.website/blog/morph-setup-2021-04-25). This would go in
the host-specific configuration folder. the host-specific configuration folder.
Once you've deployed this to a server, try to open the page in your browser: Once you've deployed this to a server, try to open the page in your browser:

View File

@ -308,19 +308,19 @@ And then you can register it in your `network.nix` like this:
This should help you get your servers wrangled into a somewhat consistent state. This should help you get your servers wrangled into a somewhat consistent state.
From here the following articles may be useful to give you ideas: From here the following articles may be useful to give you ideas:
- [Borg Backup Config](https://xeiaso.net/blog/borg-backup-2021-01-09) - [Borg Backup Config](https://christine.website/blog/borg-backup-2021-01-09)
- [Nixops Services On Your Home - [Nixops Services On Your Home
Network](https://xeiaso.net/blog/nixops-services-2020-11-09) (just be Network](https://christine.website/blog/nixops-services-2020-11-09) (just be
sure to ignore the part where it mentions `deployment.keys`, you can replace sure to ignore the part where it mentions `deployment.keys`, you can replace
it with the semantically identical it with the semantically identical
[`deployment.secrets`](https://github.com/DBCDK/morph/blob/master/examples/secrets.nix) [`deployment.secrets`](https://github.com/DBCDK/morph/blob/master/examples/secrets.nix)
as described in the morph documentation) as described in the morph documentation)
- [Prometheus and - [Prometheus and
Aegis](https://xeiaso.net/blog/aegis-prometheus-2021-04-05) Aegis](https://christine.website/blog/aegis-prometheus-2021-04-05)
- [My Automagic NixOS Wireguard - [My Automagic NixOS Wireguard
Setup](https://xeiaso.net/blog/my-wireguard-setup-2021-02-06) Setup](https://christine.website/blog/my-wireguard-setup-2021-02-06)
- [Encrypted Secrets with - [Encrypted Secrets with
NixOS](https://xeiaso.net/blog/nixos-encrypted-secrets-2021-01-20) NixOS](https://christine.website/blog/nixos-encrypted-secrets-2021-01-20)
Also feel free to dig around [the `common` folder of my `nixos-configs` Also feel free to dig around [the `common` folder of my `nixos-configs`
repo](https://github.com/Xe/nixos-configs/tree/master/common). There's a bunch repo](https://github.com/Xe/nixos-configs/tree/master/common). There's a bunch

View File

@ -3,12 +3,6 @@ title: My Career So Far in Dates/Titles/Salaries
date: 2019-03-14 date: 2019-03-14
--- ---
<div class="warning"><xeblog-conv name="Cadey" mood="coffee">This post is
outdated, see <a href="/salary-transparency">here</a> for more context on why
this data is made public. The table on this page will be automatically updated
to contain the data on my salary transparency page, but you should prefer that
page over this one when possible.</xeblog-conv></div>
Let this be inspiration to whoever is afraid of trying, failing and being fired. Let this be inspiration to whoever is afraid of trying, failing and being fired.
Every single one of these jobs has taught me lessons I've used daily in my Every single one of these jobs has taught me lessons I've used daily in my
career. career.
@ -32,7 +26,20 @@ might not want.
The following table is a history of my software career by title, date and salary The following table is a history of my software career by title, date and salary
(company names are omitted). (company names are omitted).
<xeblog-salary-history></xeblog-salary-history> | Title | Start Date | End Date | Days Worked | Days Between Jobs | Salary | How I Left |
|:----- |:---------- |:-------- |:----------- |:----------------- |:------ |:---------- |
| Junior Systems Administrator | November 11, 2013 | January 06, 2014 | 56 days | n/a | $50,000/year | Terminated |
| Software Engineering Intern | July 14, 2014 | August 27, 2014 | 44 days | 189 days | $35,000/year | Terminated |
| Consultant | September 17, 2014 | October 15, 2014 | 28 days | 21 days | $90/hour | Contract Lapsed |
| Consultant | October 27, 2014 | Feburary 9, 2015 | 105 days | 12 days | $90/hour | Contract Lapsed |
| Site Reliability Engineer | March 30, 2015 | March 7, 2016 | 343 days | 49 days | $125,000/year | Demoted |
| Systems Administrator | March 8, 2016 | April 1, 2016 | 24 days | 1 day | $105,000/year | Bad terms |
| Member of Technical Staff | April 4, 2016 | August 3, 2016 | 121 days | 3 days | $135,000/year | Bad terms |
| Software Engineer | August 24, 2016 | November 22, 2016 | 90 days | 21 days | $105,000/year | Terminated |
| Consultant | Feburary 13, 2017 | November 13, 2017 | 273 days | 83 days | don't remember | Hired |
| Senior Software Engineer | November 13, 2017 | March 8, 2019 | 480 days | 0 days | $150,000/year | Voulntary quit |
| Senior Site Reliability Expert | May 6, 2019 | October 27, 2020 | 540 days | 48 days | CAD$115,000/year (about USD$ 80k and change) | Voluntary quit |
| Software Designer | December 14, 2020 | *current* | n/a | n/a | CAD$135,000/year (about USD$ 105k and change) | n/a |
Even though I've been fired three times, I don't regret my career as it's been Even though I've been fired three times, I don't regret my career as it's been
thus far. I've been able to work on experimental technology integrating into thus far. I've been able to work on experimental technology integrating into

View File

@ -17,7 +17,7 @@ One thing that I do a lot is run virtual machines. Some of these stick around, a
lot of them are very ephemeral. I also like being able to get into these VMs lot of them are very ephemeral. I also like being able to get into these VMs
quickly if I want to mess around with a given distribution or OS. Normally I'd quickly if I want to mess around with a given distribution or OS. Normally I'd
run these on [my gaming run these on [my gaming
tower](https://xeiaso.net/blog/nixos-desktop-flow-2020-04-25), however tower](https://christine.website/blog/nixos-desktop-flow-2020-04-25), however
this makes my tower very load-bearing. I also want to play games sometimes on my this makes my tower very load-bearing. I also want to play games sometimes on my
tower, and even though there have been many strides in getting games to run well tower, and even though there have been many strides in getting games to run well
on Linux it's still not as good as I'd like it to be. on Linux it's still not as good as I'd like it to be.

View File

@ -58,7 +58,7 @@ la budza pu cusku lu
> May you be at peace. May you be happy. > May you be at peace. May you be happy.
- Buddha - Buddha
I will be reachable on the internet. See https://xeiaso.net/contact to I will be reachable on the internet. See https://christine.website/contact to
see contact information that will help you reach out to me. If you can, please see contact information that will help you reach out to me. If you can, please
direct replies to me@christine.website, that way I can read them after this direct replies to me@christine.website, that way I can read them after this
account gets disabled. account gets disabled.
@ -70,7 +70,7 @@ From my world to yours,
-- --
Christine Dodrill Christine Dodrill
https://xeiaso.net https://christine.website
``` ```
la budza pu cusku lu la budza pu cusku lu

View File

@ -3,7 +3,7 @@ title: New Site
date: 2016-12-18 date: 2016-12-18
--- ---
This post is now being brought to you by the new and improved [https://xeiaso.net](https://xeiaso.net). This post is now being brought to you by the new and improved [https://christine.website](https://christine.website).
This content is [markdown](/api/blog/post?name=new-site-2016-12-18) rendered by This content is [markdown](/api/blog/post?name=new-site-2016-12-18) rendered by
[Purescript](http://www.purescript.org/). The old [site](https://github.com/Xe/christine.website) [Purescript](http://www.purescript.org/). The old [site](https://github.com/Xe/christine.website)
is now being retired in favor of [this one](https://github.com/Xe/site). The old is now being retired in favor of [this one](https://github.com/Xe/site). The old

View File

@ -207,12 +207,12 @@ Let's take a closer look at the higher level things in the flake:
your `flake.nix`. Ditto with "flake input" referring to the `inputs` attribute your `flake.nix`. Ditto with "flake input" referring to the `inputs` attribute
of your `flake.nix`.](conversation://Cadey/enby) of your `flake.nix`.](conversation://Cadey/enby)
When you ran `nix build` earlier, it defaulted to building the `default` entry When you ran `nix build` earlier, it defaulted to building the package in
in `packages`. You can also build the `default` package by running this `defaultPackage`. You can also build the `go-hello` package by running this
command: command:
```console ```console
$ nix build .#default $ nix build .#go-hello
``` ```
And if you want to build the copy I made for this post: And if you want to build the copy I made for this post:
@ -234,13 +234,11 @@ simplify that above `nix build` and `./result/bin/go-hello` cycle into a single
`go-hello` to be the default app: `go-hello` to be the default app:
```nix ```nix
# below packages # below defaultPackage
apps = forAllSystems (system: { defaultApp = forAllSystems (system: {
default = { type = "app";
type = "app"; program = "${self.packages.${system}.go-hello}/bin/go-hello";
program = "${self.packages.${system}.default}/bin/go-hello";
};
}); });
``` ```
@ -275,18 +273,16 @@ project is using the same tools.
project folder I do not have any development tools project folder I do not have any development tools
available.](conversation://Cadey/enby) available.](conversation://Cadey/enby)
Flakes has the ability to specify this using the `devShells` flake output. You Flakes has the ability to specify this using the `devShell` flake output. You
can add it to your `flake.nix` using this: can add it to your `flake.nix` using this:
```nix ```nix
# after apps # after defaultApp
devShells = forAllSystems (system: devShell = forAllSystems (system:
let pkgs = nixpkgsFor.${system}; let pkgs = nixpkgsFor.${system};
in { in pkgs.mkShell {
default = pkgs.mkShell { buildInputs = with pkgs; [ go gopls goimports go-tools ];
buildInputs = with pkgs; [ go gopls gotools go-tools ];
};
}); });
``` ```
@ -422,7 +418,7 @@ world. To use a private repo, your flake input URL should look something like
this: this:
``` ```
git+ssh://git@github.com/user/repo?ref=main git+ssh://git@github.com:user/repo
``` ```
[I'm pretty sure you could use private git repos outside of flakes, however it [I'm pretty sure you could use private git repos outside of flakes, however it

View File

@ -72,7 +72,7 @@ Everything else we'll cover today will build on top of this.
Let's look back at the Go [example Let's look back at the Go [example
package](https://github.com/Xe/gohello/blob/caf54cdff7d8dd9bd9df4b3b783a72fe75c9a11e/flake.nix#L31-L54) package](https://github.com/Xe/gohello/blob/caf54cdff7d8dd9bd9df4b3b783a72fe75c9a11e/flake.nix#L31-L54)
I walked us through in [the last I walked us through in [the last
post](https://xeiaso.net/blog/nix-flakes-1-2022-02-21): post](https://christine.website/blog/nix-flakes-1-2022-02-21):
```nix ```nix
# ... # ...

View File

@ -140,5 +140,5 @@ for more information.
--- ---
Also happy December! My site has the [snow Also happy December! My site has the [snow
CSS](https://xeiaso.net/blog/let-it-snow-2018-12-17) loaded for the CSS](https://christine.website/blog/let-it-snow-2018-12-17) loaded for the
month. Enjoy! month. Enjoy!

View File

@ -6,7 +6,7 @@ author: ectamorphic
Recently I got a new VR setup that uses my tower directly instead of the [wifi Recently I got a new VR setup that uses my tower directly instead of the [wifi
streaming streaming
catastrophe](https://xeiaso.net/blog/convoluted-vrchat-gchat-setup-2021-02-24). catastrophe](https://christine.website/blog/convoluted-vrchat-gchat-setup-2021-02-24).
I have a [Valve Index](https://store.steampowered.com/valveindex) and an [AMD I have a [Valve Index](https://store.steampowered.com/valveindex) and an [AMD
RX6700XT](https://www.amd.com/en/products/graphics/amd-radeon-rx-6700-xt) GPU. RX6700XT](https://www.amd.com/en/products/graphics/amd-radeon-rx-6700-xt) GPU.
Some huge advantages of this setup include: Some huge advantages of this setup include:

View File

@ -5,10 +5,10 @@ date: 2019-01-17
I found an old backup that contained a few articles from my old [Medium](https://medium.com/@theprincessxena) blog. I have converted them to markdown and added them to the blog archives: I found an old backup that contained a few articles from my old [Medium](https://medium.com/@theprincessxena) blog. I have converted them to markdown and added them to the blog archives:
- 2014-11-28 - [Web Application Development with Beego](https://xeiaso.net/blog/beego-2014-11-28) - 2014-11-28 - [Web Application Development with Beego](https://christine.website/blog/beego-2014-11-28)
- 2014-11-20 - [Dependency Hell](https://xeiaso.net/blog/dependency-hell-2014-11-20) - 2014-11-20 - [Dependency Hell](https://christine.website/blog/dependency-hell-2014-11-20)
- 2014-11-18 - [My Experience with Atom as A Vim User](https://xeiaso.net/blog/atom-as-vim-2014-11-18) - 2014-11-18 - [My Experience with Atom as A Vim User](https://christine.website/blog/atom-as-vim-2014-11-18)
- 2014-10-24 - [Instant Development Environments in Docker](https://xeiaso.net/blog/dev-2014-10-24) - 2014-10-24 - [Instant Development Environments in Docker](https://christine.website/blog/dev-2014-10-24)
- 2014-10-20 - [MPD Via Docker](https://xeiaso.net/blog/mpd-docker-2014-10-20) - 2014-10-20 - [MPD Via Docker](https://christine.website/blog/mpd-docker-2014-10-20)
I hope these are at all useful. I hope these are at all useful.

View File

@ -4,7 +4,7 @@ date: 2018-09-05
series: olin series: olin
--- ---
This post is a continuation of [this post](https://xeiaso.net/blog/olin-1-why-09-1-2018). This post is a continuation of [this post](https://christine.website/blog/olin-1-why-09-1-2018).
Suppose you are given the chance to throw out the world and start from scratch Suppose you are given the chance to throw out the world and start from scratch
in a minimal environment. You can then work up from nothing and build the world in a minimal environment. You can then work up from nothing and build the world

View File

@ -13,7 +13,7 @@ tags:
Over the last week or so I've been doing a _lot_ of improvements to [Olin][olin] in order to make it ready to be the kernel for the minimum viable product of [wasmcloud][wasmcloud-hello-world]. Here's an overview of the big things that have happened from version [0.1.1][olin-0.1.1] to version [0.4.0][olin-0.4.0]. Over the last week or so I've been doing a _lot_ of improvements to [Olin][olin] in order to make it ready to be the kernel for the minimum viable product of [wasmcloud][wasmcloud-hello-world]. Here's an overview of the big things that have happened from version [0.1.1][olin-0.1.1] to version [0.4.0][olin-0.4.0].
[olin]: https://github.com/Xe/olin [olin]: https://github.com/Xe/olin
[wasmcloud-hello-world]: https://xeiaso.net/blog/wasmcloud-progress-2019-12-08 [wasmcloud-hello-world]: https://christine.website/blog/wasmcloud-progress-2019-12-08
[olin-0.1.1]: https://github.com/Xe/olin/releases/tag/v0.1.1 [olin-0.1.1]: https://github.com/Xe/olin/releases/tag/v0.1.1
[olin-0.4.0]: https://github.com/Xe/olin/releases/tag/v0.4.0 [olin-0.4.0]: https://github.com/Xe/olin/releases/tag/v0.4.0
@ -31,7 +31,7 @@ As Olin is just a kernel, it needs some work in order to really shine as a true
Here is what has been done since the [last Olin post][last-olin-post]: Here is what has been done since the [last Olin post][last-olin-post]:
[last-olin-post]: https://xeiaso.net/blog/olin-2-the-future-09-5-2018 [last-olin-post]: https://christine.website/blog/olin-2-the-future-09-5-2018
* An official, automated build of the example Olin components has been published to the Docker Hub * An official, automated build of the example Olin components has been published to the Docker Hub
* The Go ABI has been deprecated for the moment * The Go ABI has been deprecated for the moment

View File

@ -13,7 +13,7 @@ In my [last post][pahihelloworld] I mentioned that pa'i was faster than Olin's
cwa binary written in go without giving any benchmarks. I've been working on new cwa binary written in go without giving any benchmarks. I've been working on new
ways to gather and visualize these benchmarks, and here they are. ways to gather and visualize these benchmarks, and here they are.
[pahihelloworld]: https://xeiaso.net/blog/pahi-hello-world-2020-02-22 [pahihelloworld]: https://christine.website/blog/pahi-hello-world-2020-02-22
Benchmarking WebAssembly implementations is slightly hard. A lot of existing Benchmarking WebAssembly implementations is slightly hard. A lot of existing
benchmark tools simply do not run in WebAssembly as is, not to mention inside benchmark tools simply do not run in WebAssembly as is, not to mention inside

View File

@ -115,7 +115,7 @@ production-facing servers should probably only be able to be connected to over a
VPN of some kind. VPN of some kind.
If you want to see more about how to set up WireGuard on NixOS, see If you want to see more about how to set up WireGuard on NixOS, see
[here](https://xeiaso.net/blog/my-wireguard-setup-2021-02-06) for more [here](https://christine.website/blog/my-wireguard-setup-2021-02-06) for more
information. information.
## Locking Down the Hatches ## Locking Down the Hatches
@ -130,7 +130,7 @@ I am going to use the word "service" annoyingly vague here. In this world, a
"service" is a human-oriented view of "computer does the thing I want it to do". "service" is a human-oriented view of "computer does the thing I want it to do".
This website you're reading this post on could be one service, and it should This website you're reading this post on could be one service, and it should
have a separate account from other services. See have a separate account from other services. See
[here](https://xeiaso.net/blog/nixops-services-2020-11-09) for more [here](https://christine.website/blog/nixops-services-2020-11-09) for more
information on how to set this up. information on how to set this up.
### Lock Down Services Within Systemd ### Lock Down Services Within Systemd
@ -433,7 +433,7 @@ where I show you how to automatically create an ISO that does all this for you.
### Repeatable Base Image with an ISO ### Repeatable Base Image with an ISO
Using the setup I mentioned [in a past Using the setup I mentioned [in a past
post](https://xeiaso.net/blog/my-homelab-2021-06-08), you can create an post](https://christine.website/blog/my-homelab-2021-06-08), you can create an
automatic install ISO that will take a blank disk to a state where you can SSH automatic install ISO that will take a blank disk to a state where you can SSH
into it and configure it further using a tool like into it and configure it further using a tool like
[morph](https://github.com/DBCDK/morph). Take a look at [this [morph](https://github.com/DBCDK/morph). Take a look at [this

View File

@ -9,7 +9,7 @@ tags:
- r13y - r13y
--- ---
In [the last post](https://xeiaso.net/blog/paranoid-nixos-2021-07-18) we In [the last post](https://christine.website/blog/paranoid-nixos-2021-07-18) we
covered a lot of the base groundwork involved in making a paranoid NixOS setup. covered a lot of the base groundwork involved in making a paranoid NixOS setup.
Today we're gonna throw this into prod by making a base NixOS image with it. Today we're gonna throw this into prod by making a base NixOS image with it.

View File

@ -37,7 +37,7 @@ closest friends that I can talk about anything with, even what would normally
violate an NDA. My closest friends are so close that language isn't even as much violate an NDA. My closest friends are so close that language isn't even as much
of a barrier as it would be otherwise. of a barrier as it would be otherwise.
As I've mentioned in the past, [I have tulpas](https://xeiaso.net/blog/what-its-like-to-be-me-2018-06-14). As I've mentioned in the past, [I have tulpas](https://christine.website/blog/what-its-like-to-be-me-2018-06-14).
They are people that live with me like roommates inside my body. It really does They are people that live with me like roommates inside my body. It really does
sound strange or psychotic; but you'll just have to trust me when I say they sound strange or psychotic; but you'll just have to trust me when I say they
fundamentally help me live my life, do my job and do other things people fundamentally help me live my life, do my job and do other things people

View File

@ -14,7 +14,7 @@ My work laptop uses KDE, so I tried out
really liked this. I think one of the major differences between how I've been really liked this. I think one of the major differences between how I've been
failing at pomodoro in the past and why it's been working now is that I've failing at pomodoro in the past and why it's been working now is that I've
worked it into my [daily note-taking/TODO worked it into my [daily note-taking/TODO
workflow](https://xeiaso.net/blog/gtd-on-paper-2021-06-13). I label each workflow](https://christine.website/blog/gtd-on-paper-2021-06-13). I label each
pomodoro (my notes call them "Pom" because that isn't something I write often in pomodoro (my notes call them "Pom" because that isn't something I write often in
them) as a section in my notes and then include a few TODO items under it. I'll them) as a section in my notes and then include a few TODO items under it. I'll
also add some notes to the pom in case I need them later. also add some notes to the pom in case I need them later.

View File

@ -49,12 +49,12 @@ Here is an example web app manifest [from my portfolio site](https://github.com/
"background_color": "#fa99ca", "background_color": "#fa99ca",
"display": "standalone", "display": "standalone",
"scope": "/", "scope": "/",
"start_url": "https://xeiaso.net/", "start_url": "https://christine.website/",
"description": "Blog and Resume for Christine Dodrill", "description": "Blog and Resume for Christine Dodrill",
"orientation": "any", "orientation": "any",
"icons": [ "icons": [
{ {
"src": "https://xeiaso.net/static/img/avatar.png", "src": "https://christine.website/static/img/avatar.png",
"sizes": "1024x1024" "sizes": "1024x1024"
} }
] ]
@ -65,7 +65,7 @@ If you just want to create a manifest quickly, check out [this](https://app-mani
## Add Manifest to Your Base HTML Template ## Add Manifest to Your Base HTML Template
I suggest adding the HTML link for the manifest to the most base HTML template you can, or in the case of a purely client side web app its main `index.html` file, as it needs to be as visible by the client trying to install the app. Adding this is [simple](https://developer.mozilla.org/en-US/docs/Web/Apps/Progressive/Installable_PWAs), assuming you are hosting this manifest on [/static/manifest.json](https://xeiaso.net/static/manifest.json) – simply add it to the <head> section: I suggest adding the HTML link for the manifest to the most base HTML template you can, or in the case of a purely client side web app its main `index.html` file, as it needs to be as visible by the client trying to install the app. Adding this is [simple](https://developer.mozilla.org/en-US/docs/Web/Apps/Progressive/Installable_PWAs), assuming you are hosting this manifest on [/static/manifest.json](https://christine.website/static/manifest.json) – simply add it to the <head> section:
```html ```html
<link rel="manifest" href="/static/manifest.json"> <link rel="manifest" href="/static/manifest.json">
@ -96,7 +96,7 @@ At a high level, consider what assets and pages you want users of your website t
* Contact information for the person, company or service running the progressive web app * Contact information for the person, company or service running the progressive web app
* Any other pages or information you might find useful for users of your website * Any other pages or information you might find useful for users of your website
For example, I have the following precached for [my portfolio site](https://xeiaso.net): For example, I have the following precached for [my portfolio site](https://christine.website):
* My homepage (implicitly includes all of the CSS on the site) `/` * My homepage (implicitly includes all of the CSS on the site) `/`
* My blog index `/blog/` * My blog index `/blog/`

View File

@ -14,7 +14,7 @@ language help people understand where the boundaries between syllables are. I
will then describe my plans for the L'ewa orthography and how L'ewa is will then describe my plans for the L'ewa orthography and how L'ewa is
romanized. This is a response to the prompt made [here][rclm2prompt]. romanized. This is a response to the prompt made [here][rclm2prompt].
[rclm1]: https://xeiaso.net/blog/reconlangmo-1-name-ctx-history-2020-05-05 [rclm1]: https://christine.website/blog/reconlangmo-1-name-ctx-history-2020-05-05
[rclm2prompt]: https://www.reddit.com/r/conlangs/comments/gfp3hw/reconlangmo_2_phonology_writing/ [rclm2prompt]: https://www.reddit.com/r/conlangs/comments/gfp3hw/reconlangmo_2_phonology_writing/
## Phonology ## Phonology

View File

@ -15,7 +15,7 @@ making the vocabulary for L'ewa and I'll include an entire table of the
dictionary words. This answers [this dictionary words. This answers [this
prompt](https://www.reddit.com/r/conlangs/comments/gojncp/reconlangmo_6_lexicon/). prompt](https://www.reddit.com/r/conlangs/comments/gojncp/reconlangmo_6_lexicon/).
[reconlangmo]: https://xeiaso.net/blog/series/reconlangmo [reconlangmo]: https://christine.website/blog/series/reconlangmo
## Word Distinctions ## Word Distinctions

View File

@ -15,7 +15,7 @@ post will start to cover a lot of the softer skills behind L'ewa as well as
cover some other changes I'm making under the hood. This is a response to [this cover some other changes I'm making under the hood. This is a response to [this
prompt][rclm7]. prompt][rclm7].
[reconlangmo]: https://xeiaso.net/blog/series/reconlangmo [reconlangmo]: https://christine.website/blog/series/reconlangmo
[rclm7]: https://www.reddit.com/r/conlangs/comments/gqo8jn/reconlangmo_7_discourse/ [rclm7]: https://www.reddit.com/r/conlangs/comments/gqo8jn/reconlangmo_7_discourse/
## Information Structure ## Information Structure

View File

@ -103,7 +103,7 @@ this:
> I have no record of a "Christine Dodrill" at this email address. You may want > I have no record of a "Christine Dodrill" at this email address. You may want
> to look elsewhere. If you would like to proceed with me instead, here is > to look elsewhere. If you would like to proceed with me instead, here is
> information about me: https://xeiaso.net. > information about me: https://christine.website.
Throw in your pronouns too to be safe. Throw in your pronouns too to be safe.

View File

@ -10,7 +10,7 @@ As of [a recent commit](https://github.com/Xe/site/commit/b89387f6bbb010907dfa85
to this site's code, it now generates RSS and Atom feeds for future posts on my to this site's code, it now generates RSS and Atom feeds for future posts on my
blog. blog.
For RSS: `https://xeiaso.net/blog.rss` For RSS: `https://christine.website/blog.rss`
For Atom: `https://christine.webiste/blog.atom` For Atom: `https://christine.webiste/blog.atom`

View File

@ -38,7 +38,7 @@ my RTMP server. This means I could set it up to ingest via my [WireGuard
VPN][sts-wireguard] with very little work. Here is the docker command I run on VPN][sts-wireguard] with very little work. Here is the docker command I run on
my VPN host: my VPN host:
[sts-wireguard]: https://xeiaso.net/blog/series/site-to-site-wireguard [sts-wireguard]: https://christine.website/blog/series/site-to-site-wireguard
```console ```console
$ docker run \ $ docker run \

View File

@ -198,7 +198,7 @@ describes why functions fail to do what they intend. Rust has the [`Error`
trait](https://doc.rust-lang.org/std/error/trait.Error.html) which lets you also trait](https://doc.rust-lang.org/std/error/trait.Error.html) which lets you also
create a type that describes why functions fail to do what they intend. create a type that describes why functions fail to do what they intend.
In [my last post](https://xeiaso.net/blog/TLDR-rust-2020-09-19) I In [my last post](https://christine.website/blog/TLDR-rust-2020-09-19) I
described [`eyre`](https://docs.rs/eyre) and the Result type. However, this time described [`eyre`](https://docs.rs/eyre) and the Result type. However, this time
we're going to dive into [`thiserror`](https://docs.rs/thiserror) for making our we're going to dive into [`thiserror`](https://docs.rs/thiserror) for making our
own error type. Let's add `thiserror` to our crate: own error type. Let's add `thiserror` to our crate:

View File

@ -9,9 +9,9 @@ In this blogpost series I'm going to go over how I created a [site to site](http
This series is going to be broken up into multiple posts about as follows: This series is going to be broken up into multiple posts about as follows:
- Part 1 - Names and Numbers (this post) - Part 1 - Names and Numbers (this post)
- [Part 2 - DNS](https://xeiaso.net/blog/site-to-site-wireguard-part-2-2019-04-07) - [Part 2 - DNS](https://christine.website/blog/site-to-site-wireguard-part-2-2019-04-07)
- [Part 3 - Custom TLS Certificate Authority](https://xeiaso.net/blog/site-to-site-wireguard-part-3-2019-04-11) - [Part 3 - Custom TLS Certificate Authority](https://christine.website/blog/site-to-site-wireguard-part-3-2019-04-11)
- [Part 4 - HTTPS](https://xeiaso.net/blog/site-to-site-wireguard-part-4-2019-04-16) - [Part 4 - HTTPS](https://christine.website/blog/site-to-site-wireguard-part-4-2019-04-16)
- Setting up additional iOS, macOS, Android and Linux clients - Setting up additional iOS, macOS, Android and Linux clients
- Other future fun things (seamless tor2web routing, etc) - Other future fun things (seamless tor2web routing, etc)

View File

@ -6,10 +6,10 @@ series: site-to-site-wireguard
This is the second in my Site to Site WireGuard VPN series. You can read the other articles here: This is the second in my Site to Site WireGuard VPN series. You can read the other articles here:
- [Part 1 - Names and Numbers](https://xeiaso.net/blog/site-to-site-wireguard-part-1-2019-04-02) - [Part 1 - Names and Numbers](https://christine.website/blog/site-to-site-wireguard-part-1-2019-04-02)
- Part 2 - DNS (this post) - Part 2 - DNS (this post)
- [Part 3 - Custom TLS Certificate Authority](https://xeiaso.net/blog/site-to-site-wireguard-part-3-2019-04-11) - [Part 3 - Custom TLS Certificate Authority](https://christine.website/blog/site-to-site-wireguard-part-3-2019-04-11)
- [Part 4 - HTTPS](https://xeiaso.net/blog/site-to-site-wireguard-part-4-2019-04-16) - [Part 4 - HTTPS](https://christine.website/blog/site-to-site-wireguard-part-4-2019-04-16)
- Setting up additional iOS, macOS, Android and Linux clients - Setting up additional iOS, macOS, Android and Linux clients
- Other future fun things (seamless tor2web routing, etc) - Other future fun things (seamless tor2web routing, etc)
@ -230,7 +230,7 @@ $ dig @127.0.0.1 -x 10.55.0.1
### Using With the iOS WireGuard App ### Using With the iOS WireGuard App
In order to configure [iOS WireGuard clients](https://itunes.apple.com/us/app/wireguard/id1441195209?mt=8) to use this DNS server, open the WireGuard app and tap the name of the configuration we created in the [last post](https://xeiaso.net/blog/site-to-site-wireguard-part-1-2019-04-02). Hit "Edit" in the upper right hand corner and select the "DNS Servers" box. Put `10.55.0.1` in it and hit "Save". Be sure to confirm the VPN is active, then open [LibTerm](https://itunes.apple.com/us/app/libterm/id1380911705?mt=8) and enter in the following: In order to configure [iOS WireGuard clients](https://itunes.apple.com/us/app/wireguard/id1441195209?mt=8) to use this DNS server, open the WireGuard app and tap the name of the configuration we created in the [last post](https://christine.website/blog/site-to-site-wireguard-part-1-2019-04-02). Hit "Edit" in the upper right hand corner and select the "DNS Servers" box. Put `10.55.0.1` in it and hit "Save". Be sure to confirm the VPN is active, then open [LibTerm](https://itunes.apple.com/us/app/libterm/id1380911705?mt=8) and enter in the following:
``` ```
$ dig oho.pele $ dig oho.pele

View File

@ -6,10 +6,10 @@ series: site-to-site-wireguard
This is the third in my Site to Site WireGuard VPN series. You can read the other articles here: This is the third in my Site to Site WireGuard VPN series. You can read the other articles here:
- [Part 1 - Names and Numbers](https://xeiaso.net/blog/site-to-site-wireguard-part-1-2019-04-02) - [Part 1 - Names and Numbers](https://christine.website/blog/site-to-site-wireguard-part-1-2019-04-02)
- [Part 2 - DNS](https://xeiaso.net/blog/site-to-site-wireguard-part-2-2019-04-07) - [Part 2 - DNS](https://christine.website/blog/site-to-site-wireguard-part-2-2019-04-07)
- Part 3 - Custom TLS Certificate Authority (this post) - Part 3 - Custom TLS Certificate Authority (this post)
- [Part 4 - HTTPS](https://xeiaso.net/blog/site-to-site-wireguard-part-4-2019-04-16) - [Part 4 - HTTPS](https://christine.website/blog/site-to-site-wireguard-part-4-2019-04-16)
- Setting up additional iOS, macOS, Android and Linux clients - Setting up additional iOS, macOS, Android and Linux clients
- Other future fun things (seamless tor2web routing, etc) - Other future fun things (seamless tor2web routing, etc)
@ -26,7 +26,7 @@ A TLS Certificate Authority is a certificate that is allowed to issue other cert
### Why Should I Create One? ### Why Should I Create One?
Generally, it is useful to create a custom TLS certificate authority when there are custom DNS domains being used. This allows you to create `https://` links for your internal services (which can then act as [Progressive Web Apps](https://xeiaso.net/blog/progressive-webapp-conversion-2019-01-26)). This will also fully prevent the ["Not Secure"](https://versprite.com/blog/http-labeled-not-secure/) blurb from showing up in the URL bar. Generally, it is useful to create a custom TLS certificate authority when there are custom DNS domains being used. This allows you to create `https://` links for your internal services (which can then act as [Progressive Web Apps](https://christine.website/blog/progressive-webapp-conversion-2019-01-26)). This will also fully prevent the ["Not Secure"](https://versprite.com/blog/http-labeled-not-secure/) blurb from showing up in the URL bar.
Sometimes your needs may involve needing to see what an application is doing over TLS traffic. Having a custom TLS certificate authority already set up makes this a much faster thing to do. Sometimes your needs may involve needing to see what an application is doing over TLS traffic. Having a custom TLS certificate authority already set up makes this a much faster thing to do.

View File

@ -6,9 +6,9 @@ series: site-to-site-wireguard
This is the fourth post in my Site to Site WireGuard VPN series. You can read the other articles here: This is the fourth post in my Site to Site WireGuard VPN series. You can read the other articles here:
- [Part 1 - Names and Numbers](https://xeiaso.net/blog/site-to-site-wireguard-part-1-2019-04-02) - [Part 1 - Names and Numbers](https://christine.website/blog/site-to-site-wireguard-part-1-2019-04-02)
- [Part 2 - DNS](https://xeiaso.net/blog/site-to-site-wireguard-part-2-2019-04-07) - [Part 2 - DNS](https://christine.website/blog/site-to-site-wireguard-part-2-2019-04-07)
- [Part 3 - Custom TLS Certificate Authority](https://xeiaso.net/blog/site-to-site-wireguard-part-3-2019-04-11) - [Part 3 - Custom TLS Certificate Authority](https://christine.website/blog/site-to-site-wireguard-part-3-2019-04-11)
- Part 4 - HTTPS (this post) - Part 4 - HTTPS (this post)
- Setting up additional iOS, macOS, Android and Linux clients - Setting up additional iOS, macOS, Android and Linux clients
- Other future fun things (seamless tor2web routing, etc) - Other future fun things (seamless tor2web routing, etc)
@ -85,7 +85,7 @@ This will allow only Caddy and root to manage certificates in that folder.
### Custom CA Certificate Permissions ### Custom CA Certificate Permissions
In the [last post](https://xeiaso.net/blog/site-to-site-wireguard-part-3-2019-04-11), custom certificates were created at `/srv/within/certs`. Caddy is going to need to have the correct permissions in order to be able to read them. In the [last post](https://christine.website/blog/site-to-site-wireguard-part-3-2019-04-11), custom certificates were created at `/srv/within/certs`. Caddy is going to need to have the correct permissions in order to be able to read them.
```shell ```shell
#!/bin/sh #!/bin/sh

View File

@ -58,7 +58,7 @@ me](mailto:me@christine.website) and let me know them.
</noscript> </noscript>
I want to use [Xeact](https://xeiaso.net/blog/xeact-0.0.69-2021-11-18) I want to use [Xeact](https://christine.website/blog/xeact-0.0.69-2021-11-18)
more in my website. I am trying to hit a balance of avoiding structural more in my website. I am trying to hit a balance of avoiding structural
JavaScript while also allowing me to experiment with new and interesting ways of JavaScript while also allowing me to experiment with new and interesting ways of
doing things. To this end I have created a custom HTML element that allows me to doing things. To this end I have created a custom HTML element that allows me to

View File

@ -1,101 +0,0 @@
---
title: "Site Update: Hero Images"
date: 2022-06-08
---
For a while I've been wondering how I can add dramatic flair to my website with
so-called "hero images". These images are tools that let you describe the mood a
website wants to evoke. I've been unsure how to best implement these on my
website for a while, but with the advent of MidJourney and other image
generation APIs/algorithms I think I have found a way to create these without
too much effort on my part and the results are pretty fantastic:
<xeblog-hero file="secret-to-life" prompt="the secret to life, the universe and everything, concept art"></xeblog-hero>
I have generated a bunch of other images that I'm going to use for my other
posts. I'll give out a desktop wallpaper sized version of each of these images
on my [Patreon](https://patreon.com/cadey).
Under the hood this is powered by
[lol_html](https://github.com/cloudflare/lol-html) and
[Maud](https://maud.lambda.xyz/). The magic is mostly contained in a function
that generates a `<figure>` HTML element (which I just learned exists today). I
use a function that looks like this for generating the `<xeblog-hero>` snippets:
```rust
pub fn xeblog_hero(file: String, prompt: Option<String>) -> Markup {
html! {
figure.hero style="margin:0" {
picture style="margin:0" {
source type="image/avif" srcset={"https://cdn.xeiaso.net/file/christine-static/hero/" (file) ".avif"};
source type="image/webp" srcset={"https://cdn.xeiaso.net/file/christine-static/hero/" (file) ".webp"};
img style="padding:0" alt={"hero image " (file)} src={"https://cdn.xeiaso.net/file/christine-static/hero/" (file) "-smol.png"};
}
figcaption { "Image generated by MidJourney" @if let Some(prompt) = prompt { " -- " (prompt) } }
}
}
}
```
I have it wired up with lol_html like this:
```rust
lol_html::element!("xeblog-hero", |el| {
let file = el.get_attribute("file").expect("wanted xeblog-hero to contain file");
el.replace(&crate::tmpl::xeblog_hero(file, el.get_attribute("prompt")).0, ContentType::Html);
Ok(())
})
```
The result is that I can declare hero images with HTML fragments like this:
```html
<xeblog-hero file="miku-dark-souls" prompt="hatsune miku, elden ring, dark souls, concept art, crowbar"></xeblog-hero>
```
And I get this:
<xeblog-hero file="miku-dark-souls" prompt="hatsune miku, elden ring, dark souls, concept art, crowbar"></xeblog-hero>
<xeblog-conv name="Mara" mood="hacker">This is powered by the
[`<figure>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure)
tag, which is a new discovery to us. This is probably one of the most useful
tags we never knew about and removed the need to write a bunch of annoying CSS
and HTML.</xeblog-conv>
The webp and AVIF versions of the hero images have a higher resolution version
so that it looks nicer on retina screens. However, the png versions of these are
locked to a resolution of 800x356 pixels because I was unable to crush them
below a size of half a megabyte at full resolution. Realistically, this should
only affect older browsers on slower hardware, so I don't expect this to have
too much impact on most users.
<xeblog-conv name="Cadey" mood="coffee">If you don't want to see these hero
images, you can remove them with a userstyle like this:
```css
figure.hero {
display: none;
}
```
</xeblog-conv>
I'm likely going to convert over most of my website templates to use Maud. I'm
very happy with it and I think it is incredibly useful to express your HTML in
Rust instead of something that has to be compiled to Rust. In practice it
reminds me of the Nim library [emerald](http://flyx.github.io/emerald/), which
lets you write HTML using Nim functions similar to how you use Maud.
Here's a few more examples of hero images I have generated:
<xeblog-hero file="the-forbidden-shape" prompt="the forbidden shape"></xeblog-hero>
<xeblog-hero file="great-wave-cyberpunk" prompt="the great wave off of kanagawa, cyberpunk, hanzi inscription"></xeblog-hero>
Normally I will only have one image per post and it will usually be after the
introduction paragraph. The prompt will usually be related to the article topic,
but sometimes I will take artistic liberty. If you have suggestions for prompts,
please [contact me](/contact) with those ideas.
I hope these updates on how I've been messing with my site are interesting. I'm
trying to capture the spirit of how I'm implementing these changes as well as
details of how everything fits together.

View File

@ -1,117 +0,0 @@
---
title: "Site Update: I Fixed the Patron Page"
date: 2022-05-18
---
So I fixed [the patron page](https://xeiaso.net/patrons) and the
underlying issue was stupid enough that I feel like explaining it so you all can
learn from my mistake.
<xeblog-conv name="Numa" mood="delet">For those of you playing the christine dot
website home game, look
[here](https://github.com/Xe/site/commit/e2b9f384bf4033eddf321b5b5020ac4847609b37)
to see the fix and play along!</xeblog-conv>
My blog is basically a thin wrapper around two basic things:
1. Markdown files (such as for this article you are reading right now)
2. Static files (such as for the CSS that is making this article look nice)
When I create a package out of my blog's code, I have a layout that resembles
the directory structure in my git repo:
```console
$ ls -l /nix/store/crc94hqyb546w3w9fzdyr8zvz3xf3p1j-xesite-2.4.0
total 64
dr-xr-xr-x 2 root root 4096 Dec 31 1969 bin/
dr-xr-xr-x 2 root root 20480 Dec 31 1969 blog/
-r--r--r-- 24 root root 8663 Dec 31 1969 config.dhall
dr-xr-xr-x 2 root root 4096 Dec 31 1969 css/
dr-xr-xr-x 2 root root 4096 Dec 31 1969 gallery/
-r--r--r-- 52 root root 5902 Dec 31 1969 signalboost.dhall
dr-xr-xr-x 12 root root 4096 Dec 31 1969 static/
dr-xr-xr-x 2 root root 4096 Dec 31 1969 talks/
```
Here is my git repo for comparison:
```console
$ ls -l
total 188
drwxr-xr-x 2 cadey users 20480 May 18 20:21 blog/
-rw-r--r-- 1 cadey users 77521 May 18 20:15 Cargo.lock
-rw-r--r-- 1 cadey users 1795 May 18 20:15 Cargo.toml
-rw-r--r-- 1 cadey users 198 Oct 30 2020 CHANGELOG.md
-rw-r--r-- 1 cadey users 2779 Apr 5 20:32 config.dhall
drwxr-xr-x 2 cadey users 4096 Apr 16 11:56 css/
-rw-r--r-- 1 cadey users 1325 Jan 15 2021 default.nix
drwxr-xr-x 2 cadey users 4096 Mar 15 2020 docs/
drwxr-xr-x 2 cadey users 4096 Mar 21 20:23 examples/
-rw-r--r-- 1 cadey users 1882 Apr 30 16:13 flake.lock
-rw-r--r-- 1 cadey users 6547 Apr 24 20:35 flake.nix
drwxr-xr-x 2 cadey users 4096 Jun 17 2020 gallery/
drwxr-xr-x 6 cadey users 4096 Mar 21 20:23 lib/
-rw-r--r-- 1 cadey users 887 Jan 1 2021 LICENSE
drwxr-xr-x 2 cadey users 4096 Dec 18 00:06 nix/
-rw-r--r-- 1 cadey users 1467 Feb 21 20:39 README.md
drwxr-xr-x 2 cadey users 4096 Mar 21 21:21 scripts/
-rw-r--r-- 1 cadey users 5902 May 18 16:44 signalboost.dhall
drwxr-xr-x 5 cadey users 4096 Apr 5 20:32 src/
drwxr-xr-x 12 cadey users 4096 Jan 10 17:22 static/
drwxr-xr-x 2 cadey users 4096 Nov 10 2021 talks/
drwxr-xr-x 4 cadey users 4096 Apr 16 09:56 target/
drwxr-xr-x 2 cadey users 4096 May 15 07:59 templates/
```
The main problem is that my site expects all of this to be in the current
working directory. In my site's systemd unit I have a launch script that looks
like this:
```nix
script = let site = packages.default;
in ''
export SOCKPATH=${cfg.sockPath}
export DOMAIN=${toString cfg.domain}
cd ${site}
exec ${site}/bin/xesite
'';
```
However the Nix store isn't writable by user code. My patreon API client looked
for its credentials in the current working directory. When I set it up on the
target server I put the credentials in `/srv/within/xesite/.patreon.json`,
thinking that the `WorkingDirectory` setting would make it Just Work:
```nix
WorkingDirectory = "/srv/within/xesite";
```
But this was immediately blown away by the `cd` command on line 4 of the script.
I have fixed this by making my Patreon client put its credentials in the home
directory explicitly with this fragment of code:
```rust
let mut p = dirs::home_dir().unwrap_or(".".into());
p.push(".patreon.json");
```
This will make the Patreon credentials get properly stored in the service's home
directory (which is writable). This will also make the patrons page work
persistently without having to manually rotate secrets every month.
Here's a good lesson for you all, make sure to print out the absolute path of
everything in error messages. For the longest time I had to debug this from this
error message:
```
patrons: xesite::app: ".patreon.json" does not exist
```
I was looking at the directory `/srv/within/xesite` and I saw it existing right
in front of my eyes. This made me feel like I was going crazy and I've been
putting off fixing it because of that. However, it's a simple fix and I was
blind.
<xeblog-conv name="Cadey"
mood="coffee">aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</xeblog-conv>

View File

@ -1,42 +0,0 @@
---
title: "Site Update: Salary Transparency Page Added"
date: 2022-06-14
author: Sephie
---
<xeblog-hero file="miku-dark-souls" prompt="hatsune miku, elden ring, dark souls, concept art, crowbar"></xeblog-hero>
I have added a [salary transparency
page](https://xeiaso.net/salary-transparency) to the blog. This page lists my
salary for every job I've had in tech. I have had this data open to the public
for years, but I feel this should be more prominently displayed on my website.
As someone who has seen pay discrimination work in action first-hand, data is
one of the ways that we can end this pointless hiding of information that leads
to people being uninformed and hurt by their lack of knowledge. By laying my
hand out in the open like this, I hope to ensure that people are better informed
about how much money they can make, so that they can be paid equally for equal
work.
Raw, machine processable data (including employer names) is available at
`/api/salary_transparency.json`. The JSON format is not stable. Do not treat it as
such. I reserve the right to change the formatting or semantics of the JSON
format at any time without warning. The raw data is in `/dhall/jobHistory.dhall`
in my site's git repository.
I have also taken the time to make sure that the [old
post](https://xeiaso.net/blog/my-career-in-dates-titles-salaries-2019-03-14)
maintains an up-to-date list. I do not want to break semantics on my website
without a very good reason. By leaving the old post un-updated, I feel it would
be doing a disservice to the community.
Please consider publishing your salary data like this as well. By open,
voluntary transparency we can help to end stigmas around discussing pay and help
ensure that the next generations of people in tech are treated fairly. Stigmas
thrive in darkness but die in the light of day. You can help end the stigma by
playing your cards out in the open like this.
It can be scary to do this; however every person that does it will make it that
much more easy for the next person to do it.
Don't be afraid.

View File

@ -8,13 +8,13 @@ tags:
--- ---
I made a little interactive fiction story! You can find it I made a little interactive fiction story! You can find it
[here](https://xeiaso.net/static/stories/spaceship.html). This was [here](https://christine.website/static/stories/spaceship.html). This was
written as a result of a terrible idea I had after watching some QuakeCon written as a result of a terrible idea I had after watching some QuakeCon
announcements. announcements.
I wonder if I can get away with using an `<iframe>` in 2021: I wonder if I can get away with using an `<iframe>` in 2021:
<iframe src="https://xeiaso.net/static/stories/spaceship.html" width="100%" height=500></iframe> <iframe src="https://christine.website/static/stories/spaceship.html" width="100%" height=500></iframe>
This is adapted from [a twitter This is adapted from [a twitter
thread](https://twitter.com/theprincessxena/status/1428479144699088903). thread](https://twitter.com/theprincessxena/status/1428479144699088903).

View File

@ -1,68 +0,0 @@
---
title: "Spearphishing: it can happen to you too"
date: 2022-07-09
tags:
- linkedin
- infosec
---
<xeblog-hero file="the-fool" prompt="The Fool in a woodcut tarot card style"></xeblog-hero>
For some reason, LinkedIn has become the de-facto social network for
professionals. It is viewed as a powerful networking and marketing site that
lets professionals communicate, find new opportunities and source talent at
eye-watering speed and rates. However, at the same time this also means that
LinkedIn becomes a treasure trove of data to enable spearphising attacks.
Let's consider [this attack against popular "play to earn" game Axie
Infinity](https://www.theblock.co/post/156038/how-a-fake-job-offer-took-down-the-worlds-most-popular-crypto-game).
The attackers had PDF based malware that allowed them to get access to a target
computer, so they needed someone to open a PDF to trigger the exploit chain that
let them gain a foothold. But they specifically wanted people that likely had
access to the crypto wallets that enable control of the blockchain. LinkedIn let
them filter by employees at the company behind Axie Infinity that were
developers and likely started spearphishing by role and seniority. The details
of the attack spell out that the attackers had set up a whole fake interview
process to convince the marks that the process was legitimate and they put the
malware in the offer letter. The attackers later gained access to the validator
wallets and then they were able to make off with over half a billion dollars
worth of cryptocurrency.
<xeblog-conv name="Numa" mood="delet">Maybe, just maybe you shouldn't store a
majority of the keys required to validate something on _the same computer_.
Especially if those keypairs control assets worth close to _half a billion
dollars_. Holy heck.</xeblog-conv>
The malware was in the offer letter. This is the kind of social engineering
attack that I bet any one of you reading this article could fall for. Hell, I'd
probably fall for this. This may be the wrong kind of take to have, but I'm
really starting to wonder if using LinkedIn so much is actually bad for
security. It's not just recruiters reading through LinkedIn anymore, it's also
threat actors that are trying to break in and do God knows what. Maybe we as an
industry should stop feeding all of that data into LinkedIn. Not only would it
give you less recruiter spam, maybe it'll make spearphishing attacks more
difficult too.
<xeblog-conv name="Cadey" mood="coffee">Also, yes we can't trust PDFs anymore,
especially after exploits like
[FORCEDENTRY](https://googleprojectzero.blogspot.com/2021/12/a-deep-dive-into-nso-zero-click.html)
became a thing.</xeblog-conv>
Either way, I may end up getting a disposable machine for dealing with reading
PDFs from unknown sources in the future. I could use a virtual machine for this,
but if my threat model includes PDFs having exploits in them then I probably
can't trust a virtual machine to be a reasonable security barrier. I don't know.
It sucks that we can't trust people anymore.
I kinda wish we could.
---
<xeblog-conv name="Mara" mood="hacker">Fun fact: the tarot card "The Fool"
doesn't actually imply idiocy in a malicious way. The major arcana of the tarot
is a bunch of memes that describe the story of The Fool's journey through magick
and learning how the world works. The Fool is not an idiot, The Fool is just
someone that is unaware of the difficulties they are going to face in life and
treats things optimistically. Think a free spirit as opposed to someone that is
foolhardy (though foolhardiness is the meaning of The Fool when the card is
inverted).</xeblog-conv>

View File

@ -26,7 +26,7 @@ problems that require deep thought and consideration.
I was originally gonna release this by the end of the year as a cohesive novel, I was originally gonna release this by the end of the year as a cohesive novel,
however it looks like the cards aren't falling that way. I want to instead shift however it looks like the cards aren't falling that way. I want to instead shift
[Spellblade](https://xeiaso.net/blog/spellblade-plans-2021-08-16) into a [Spellblade](https://christine.website/blog/spellblade-plans-2021-08-16) into a
web novel, which I am defining as something that I'll release in big chunks like web novel, which I am defining as something that I'll release in big chunks like
this every month or so. I don't want to compromise any of the artistic vision or this every month or so. I don't want to compromise any of the artistic vision or
whatever, I just want each "chunk" to be a lot more finely scoped than "the whatever, I just want each "chunk" to be a lot more finely scoped than "the

View File

@ -64,7 +64,7 @@ def get_feed(feed_url):
con.commit() con.commit()
print("got feed %s" % (feed_url)) print("got feed %s" % (feed_url))
get_feed("https://xeiaso.net/blog.json") get_feed("https://christine.website/blog.json")
``` ```
So now let's play with the data! Let's load the database schema in with the So now let's play with the data! Let's load the database schema in with the
@ -76,14 +76,14 @@ $ sqlite3 data.db < schema.sql
[The less-than symbol there is a redirect, it loads the data from `schema.sql` [The less-than symbol there is a redirect, it loads the data from `schema.sql`
as standard input to the `sqlite` command. See <a as standard input to the `sqlite` command. See <a
href="https://xeiaso.net/blog/fun-with-redirection-2021-09-22">here</a> href="https://christine.website/blog/fun-with-redirection-2021-09-22">here</a>
for more information on redirections.](conversation://Mara/hacker) for more information on redirections.](conversation://Mara/hacker)
Then run that python script to populate the database: Then run that python script to populate the database:
```console ```console
$ python ./jsonfeedfetch.py $ python ./jsonfeedfetch.py
got feed https://xeiaso.net/blog.json got feed https://christine.website/blog.json
``` ```
Then open up the SQLite command line: Then open up the SQLite command line:
@ -179,7 +179,7 @@ And run that python script again, then the data should automatically show up:
``` ```
sqlite3> SELECT * FROM jsonfeed_metadata; sqlite3> SELECT * FROM jsonfeed_metadata;
https://xeiaso.net/blog.json|Xe's Blog|My blog posts and rants about various technology things.|https://xeiaso.net|2022-01-04 https://christine.website/blog.json|Xe's Blog|My blog posts and rants about various technology things.|https://christine.website|2022-01-04
``` ```
It's like magic! It's like magic!

View File

@ -1,106 +0,0 @@
---
title: "The Stanley Parable: Ultra Deluxe Review"
date: 2022-07-25
series: reviews
---
Every so often a game comes around that is genuinely hard to review. Especially
when you are trying to avoid spoiling the magic of the game in that review. This
is a game that is even harder to review than normal because it's an absolute
philosophical document. This game absolutely riffs at the games industry super
hard and it really shows. I'm going to try to avoid spoilers in this article,
except for a few I made up.
<xeblog-conv name="Cadey" mood="coffee">I was going to include screenshots in
this article, but it's difficult for me to get them without spoiling the subtle
comedy at hand, so I'm going to leave this as a text-only review.</xeblog-conv>
The Stanley Parable: Ultra Deluxe is either the second or third game in the
series. At first this game was a Half Life 2 mod that came out of nowhere and
was one of the most beloved mods ever released. Then they made it a proper game
on the Source engine and expanded it a bit. After a while they wanted to
continue the parable and expand it even more, but they weren't able to get it on
consoles with it still being a Source engine game. So they ported it to Unity
and the end result is The Stanley Parable: Ultra Deluxe. It is one of my
favorite games of all time.
It is a deeply limited game, you only can move around and interact with things.
The story is about an office drone named Stanley that pushes buttons based on
instructions from his computer. The big thing that this game does though is make
you realize the inherent paradoxes in its own design.
<xeblog-conv name="Mara" mood="happy">Being mechanically limited like this is
not actually a bad thing like the phrasing might imply. This means that the main
focus of the gameplay is not on the micro actions the player can take. In this
case the main focus is on how the player interacts with the story and not how
the player interacts with their controller or puzzles or tactics. Additionally,
the mechanical limitations of the gameplay are thematically aligned with the
story's premise of being an office drone in ways it can play with. Think
dramatic irony taken to its logical conclusion.</xeblog-conv>
Endings that make you look like you had exercised your free will actually boil
down to your actions being controlled by following the narrator's voices. This
is absolutely taking the piss out of how most modern AAA game design works,
guiding you with an invisible hand and making it _seem_ like you had the free
will to choose what was going on when in fact you were really just following the
invisible guidance the whole time.
However I think one of the best examples of how The Stanley Parable riffs at
mainstream game design is via the Adventure Line™️ that shows up in one branch of
the game. The Line™️ is an obvious riff on games like Dead Space where you can
summon a line to tell you where to go at any time. It shows how _boring_ modern
game design is by making you _see_ the consequences of it. If you follow the
narrator's voice, you get boring endings.
In many modern AAA games, you have the free will to choose to follow the main
story and finish all the quests or whatever, but not much else. Consider Call of
Duty or Battlefield. You are John America and you have to kill the enemies to
death before they kill you to death by throwing bullets at you. You get to the
end of the level and blow up the brown people some more or something and then
it's suddenly a victory for America. But what did you really accomplish? You
just followed the line. Walk outside of the intended playable area? 10 second
timer until the game kills you. Shoot a person with the wrong skin color? The
game kills you.
<xeblog-conv name="Numa" mood="delet">If you manage to clip out of bounds in the
escape ending, the screen will fade to black and you will be transported to a
temperate climate. Then a t-posing model in terrible armor will tell you that it
used to be an adventurer until they took an arrow to the knee. Hope that's not a
marriage proposal!</xeblog-conv>
However in The Stanley Parable you can defy the narrator and that's where the
game really opens up. It's great to get in the area where the game is unfinished
and then have the narrator complain about deadlines, scheduling delays, investor
funding and them wanting to avoid having to stuff it to the gills with
microtransactions. You can legitimately glitch your way out of bounds and then
the game will reward you with a new ending you didn't know was possible. The
game takes the concept of the illusion of free will and plays with it.
The game makes you think about what games _can_ be. It makes you wonder if the
potted plant soliloquy after the broom closet ending speaks to the mental state
of the author more than anything. Of all of the artistic endeavors that games as
a medium _can_ have, we end up seeing very few or none of them in mainstream
gaming. Sure you get your occasional 4k120fps robot killer waifu with a bow and
a whacky stick, but none of it really _revolutionizes_ video games as an art
form. It's all just derivative of the generic "unalive bad guy and save earth"
trope.
<xeblog-conv name="Mara" mood="hacker">If you want some games that really
revolutionize what games can be, check out
[Celeste](https://mattmakesgames.itch.io/celeste), [Secret Little
Haven](https://ristar.itch.io/secret-little-haven), [Baba Is
You](https://hempuli.itch.io/baba), and [Glittermitten
Grove](https://twinbeard.itch.io/glittermitten-grove). All of these games really
challenge what games can be and experiment with radically different kinds of
art. You never will see mainstream games be as risk-taking as this because art
is fundamentally risky and capitalism wants line to go up, so they go out of
their way to make sure that mainstream games are as safe and likely to sell many
copies as possible.</xeblog-conv>
I made up the thing about the potted plant, but if you had played the game then
you'd probably have started the game up to look for it just to see what was
there. I wonder if I made someone stand at that potted plant for like 5 minutes
or something. This game sparks creativity in ways that other mainstream games
just fundamentally don't. If you've been looking for something different in your
video game diet, I really suggest you give it a try. Go in as blind as possible.
I'm not paid in any way to say this, I genuinely think this is really good.

View File

@ -4,7 +4,7 @@ date: 2019-05-30
series: templeos series: templeos
--- ---
The [last post](https://xeiaso.net/blog/templeos-1-installation-and-basic-use-2019-05-20) covered a lot of the basic usage of TempleOS. This post is going to be significantly different, as I'm going to be porting part of the TempleOS kernel to WebAssembly as a live demo. The [last post](https://christine.website/blog/templeos-1-installation-and-basic-use-2019-05-20) covered a lot of the basic usage of TempleOS. This post is going to be significantly different, as I'm going to be porting part of the TempleOS kernel to WebAssembly as a live demo.
This post may contain words used in ways and places that look blasphemous at first glance. No blasphemy is intended, though it is an unfortunate requirement for covering this part of TempleOS' kernel. It's worth noting that Terry Davis [legitimately believed that TempleOS is a temple of the Lord Yahweh](https://templeos.holyc.xyz/Wb/Doc/Charter.html): This post may contain words used in ways and places that look blasphemous at first glance. No blasphemy is intended, though it is an unfortunate requirement for covering this part of TempleOS' kernel. It's worth noting that Terry Davis [legitimately believed that TempleOS is a temple of the Lord Yahweh](https://templeos.holyc.xyz/Wb/Doc/Charter.html):

View File

@ -1,344 +0,0 @@
---
title: The Oasis
date: 2022-06-03
series: malto
tags:
- furry
---
Tosen did a final check of his backpack. It was a very hot day in Tashei, but
the river radiated an aura of cool air that protected everyone from the heat of
the harsh sun. He had his backup cloak, a hydroflask and some fish jerkey, not
to mention the package for the client. *Not exactly the best equipment but this
will work. Riltash is a half day away at worst,* Tosen thought to himself. He
squatted down and fit his arms into the pack's straps. The pack easily weighed a
quarter of what he did, but as he regained his catlike balance he secured the
waistband and got ready to head out.
The oracle predicted that there would be a sandstorm late in the evening, but it
wasn't even noon yet. He pulled out his compass, let it settle and then set out
to the southeast.
Walking in the desert always has its own unique rhythm to it. With the
unrelenting heat of the sun pounding down on the sand, the ground itself can
feel like a million angry daggers with every step. Tosen thought ahead of this
issue. He got himself a pair of sandshoes from the fancy magic item store. The
only downside was that his main connection to the earth was significantly
weakened. Chee paws are some of the most finely tuned sensory organs on Malto
(second only to Snep paws), and they were his main warning about sandstorms.
*The oracle isn't wrong most of the time. I'm fine, I'm fine. I can't feel the
desert but I'm fine.*
He kept walking past all different kinds of cacti. His favorite ones were the
ones that were made up of a bunch of spiky ovals built on top of eachother. He'd
never want to get stung by one and risk the wrath of the serrated needles, but
he'd always thought that they had such a unique look. *If I had a house of my
own, I'd grow one of them.*
As he continued walking he started to focus on the patterns of walking. Every
step was taken one after the other. With every step, his foot slid to the side
ever so slightly. The sand wrapped around his shoes and warmed his feet. The
worst part of the sandshoes was when sand trickled into the back of them. This
required him to stop every so often to purge the sand out of his shoes, because
otherwise it would hurt a lot.
This continued for what felt like hours. He checked his compass every so often
and made sure he was on the right path. It started to move a bit weird compared
to normal. It was taking longer to find north. Normally this would be concerning
to him, but the desert had entranced him. Left, right, left, right, left, right.
Each step bringing him closer to his destination.
Then the sky changed color. The brilliant blue started to get stained with a
light brown that worked its way across what Tosen could see. Tosen instantly
noticed this change and pulled over his face veil. The sandstorm had started
early. The oracle was wrong.
Tosen looked around for some kind of shelter but all he could see was the
remains of a broken wagon that looked like its better days had seen better days.
It was barely enough protection. With his spare robe to patch over the biggest
holes just enough to ride out the storm. He took the leap and hunkered down.
*It's only a level 2 storm. It'll be over in an hour. I'll make it to Riltash
today. Everything will work out.*
As he sat down he reached for his compass and couldn't find it. He reached into
the pocket that his compass normally lives in and felt it conspicuously empty.
He looked up towards the path he walked in on and saw a golden glint in the
sand. It was so close. If he could get to it, he'd know where to go. He'd find
his way to Riltash.
But the sandstorm started to kick up. The sky started fading towards darker and
darker shades of brown and he could feel the sand beat against his makeshift
shelter. The hot sand was whipped up and he could hear it pitter and patter the
wooden and cloth walls.
After an hour, the sandstorm started showing signs of slowing down. *This is
nothing close to a level 2*, Tosen thought to himself. His spare robe was
totally ruined, but he survived. As things died down, he remembered his compass
and tore down enough of his shelter to be able to find it. It wasn't where it
was before. *Okay, it's made of gold, it can't have gone that far*. He grabbed
his pack, almost fell over from the sudden weight and started to scan around him
in 360 degrees. He saw the familiar glint of its knob and walked over to its
resting site. The looking glass was cracked. Rotating it did nothing. The
compass was broken.
He was lost.
It took every ounce of strength Tosen had to avoid shouting out in anger. He
needed to conserve the water. Miau was huge. He needs to extend his supplies to
last as long as possible.
He couldn't resist the urge. He shouted out in anger for an instant before
realizing what he did and covering his mouth.
It took a while for Tosen to regain his senses. The shock of the event wasn't
sitting well with him. His mother's compass was destroyed. His rendezvous time
with the client was surely shot. At least it wasn't the solar apex anymore.
*Okay, I can deal with this. I should stay put until sunset. The sun sets to the
east. I can go diagonally into the sunset to get to Riltash*.
He more confidently went back to his makeshift shelter. It was in worse
condition after the storm, but at least it would give him shade. The sun was on
its way down, but it was still a deadly laser that he needed to worry about.
*It's just me and you, buddy.*
Some time passed and the sun very visibly was in the eastern portion of the sky.
Tosen grabbed for his hydroflask and took a sip. *It was still cold. At least
that oracle was good for SOMETHING.* He stood up and grabbed his pack. He left a
bit of red cloth as a flag on the southeast side of his makeshift shelter to
tell anyone looking for him where he went.
Then it was back to the rhythms of the desert. The desert felt confusing without
the comforting pulse of nature under his paws. But, he continued taking steps
and continued walking forwards.
Left. Right. Left. Right. Left. Right. Things felt more deliberate this time.
There was a frustration to his walking. He was so frustrated at the whole
situation. As he walked, he felt his emotions fuming over this whole debacle.
He walked and walked. The sunset had started to peek out its head and show Tosen
a display of fantastic colors as he continued to walk. *This isn't right. I
should have reached Riltash by now. It's only a few miles from Tashei.* He took
another swig of his hydroflask and felt it notably lighter than it should be. He
was low on water. This was especially dangerous out here. *This is going to be a
long night, isn't it.*
The sunset continued and the colors gradually started to fade to the black night
sky. Starts started to peek out without the sun to hide them. Tosen scanned over
the constellations and found the North Star. From there he worked out that he
was going to the southeast like he thought he was. He looked around and found a
few miserable bushes to use for firewood, but they were in a sea of thorns. He
had talents in fire magick, but he didn't trust using it with so many dry thorns
nearby. *You know what, what's the worst that can happen? I get warm? It's going
to be so cold soon, I need to do something.*
He held out his hand and mentally started to trace out the triangles like he
learned from school. Each triangle stacked on top of each other and then built
up into a viable casting circle glowing a brilliant orange in front of his hand.
The area around him was illuminated from the magickal force, the thorns casting
long evil shadows against cacti and other miserable little bushes.
"Toor sha!"
A weak puff of flame came out of his hand and tickled one of the thorny vines.
There wasn't much of a response and it looked like the fire was going to go out
so he cast a fireball in its place. The triangles shifted into squares and a
baseball sized orb of energy started to form in his hand.
"Toor shaltel!"
The fireball formed around his fingers and he chucked it right into the pit of
thorns. They were all set on fire simultaneously. After a brilliant blaze, the
fire petered out into nothing as fast as it started. He looked over to see if
the firewood was still there, his spell circle was still active as a flashlight
but that kindling was nowhere to be seen. It was incinerated with everything
else.
He had to resist shouting out in anger again. *Okay, okay, calm down. I set off
a massive signal fire. That should alert someone. I can't keep this spell circle
up, I'll mind down and then I'll be in worse trouble.* He killed off the spell
with a flick of the wrist and the darkness crept in. He was alone. I need to
keep walking. So he started walking, not realizing that he changed direction
after the incident with the thorns.
Left! Right! Left! Right! Left! Right! Each step felt angry and defiant. Tosen
started to feel legitimate anger at the desert. It was normally his home, he
grew up in the sands of Miau, but tonight it was his enemy. He defiantly marched
towards where he thought Riltash was, but got no closer.
It was a very long night walking towards town. It was a desperate, angry march.
He stopped a few times to take a bite of jerkey and swig it down with the bare
minimum of water he could get away with. He thought it would be colder, but it
turns out all that fur ended up going towards something.
The night continued and was broken by the inklings of a sunrise. Tosen looked up
in dismay. He had walked all night and he was nowhere closer to his destination.
An overwhelming feeling of sadness blanketed him and he broke down to start
crying.
He looked forward and saw something different. He saw what looked like the faint
outline of Riltash's signal statue. His sadness was instantly transmuted to a
mixture of relief and joy and his second wind started to hit. He trudged forward
towards that statue. Towards his salvation. Towards his client. Towards his
paycheck. Towards the next step to move out to Zhalram with his friends. Towards
his future.
He kept up his pace and got closer. The statue looked wrong. Riltash has the
visage of one of the water goddesses in the region. This looked different,
almost like a Chee. He wasn't aware of any local Chee deities. He looked down
and saw shimmers. It almost looked like a mirage, but then he remembered
something. There were rumors of an oasis south of Riltash. Could this be that?
Could there be water?
His second wind became a third and then a fourth wind. He got close enough to
take a better look at everything and it was that oasis!
*Water!*
His walk became a sprint and the sand started to be diluted with grass. As he
walked on the grass he suddenly felt an overwhelming sense of calm. It was as if
all of the anger, all the vitriol, all the hatred towards the desert vanished in
an instant. He paused for a moment but then continued on. The promise of fresh
water was too great. He was so thirsty.
He put his pack down, took off his shoes and tested the water with a paw. It was
cool to the touch, about 10 degrees celsius. It was the real deal. It was water.
He took off his robe, folded it haphazardly next to his pack and grabbed his
hydroflask. He opened it and shoved it under so it could be filled. Once he was
satisfied that it was full, he bent over and started to lap up the water
greedily.
A figure vaguely resembling the statue was watching from a nearby house. The
figure chuckled to themselves. They decided they should intervene. They donned a
white robe and walked out to the weary traveler.
Tosen was enamoured by the water. His exhaustion had finally caught up to him.
He looked back at his pack and saw a figure walking towards him from some kind
of house. He instantly jumped to alertness, but didn't feel the fear that
generally came with being startled like that. The figure felt familiar yet alien
somehow.
The figure looked at his pack and his visibly broken compass. They looked right
into Tosen's eyes. "Rough day?"
Tosen stammered a few times and eventually managed to come up with a reply:
"Y...yeah. I was caught in that sandstorm yesterday. I hid from it in a broken
wagon."
The figure reached out a hand to him. "Come with me. You need a rest. I'll come
back to take care of your things. I have a spare bed for travelers like you."
Tosen didn't have enough energy to argue with the stranger's offer of
hospitality and followed them into their house. They guided Tosen to the guest
room and sat on one of the chairs. Tosen collapsed on the most comfortable bed
he had ever felt in his life. All he could get out was a weak "thank....youuuu"
before his lost sleep caught up and he was out like a light. The figure pulled a
blanket over him and closed the shades to make the room nice and dark.
Tosen was asleep until the late afternoon. The figure had moved his stuff
inside, done his laundry, mended a hole in the pack and was lounging in a chair
for a nap of their own.
Tosen woke up, stretched out and yawned loudly. He looked up at the ceiling and
realized how unfamiliar it was. That angel in his dream was real. Had he
actually walked through the night? The figure knocked at the door. "Hey, come
and have a meal. You must be starving." He was. Tosen stood up and opened the
door. The figure was wearing a white robe and a golden necklace. They looked
like the archetypal vision of Chee beauty. Tosen noted that he was unable to
refer to that figure with any pronoun but "they". *That's weird...*
The figure started to speak: "I am Shal'tash. I saw you hurting and I decided to
intervene and help you. Come, I have some food almost ready." Shal'tash started
to walk towards the kitchen and Tosen followed. He made his way to a rather
ordinary looking wooden table and took a seat. His stuff was near the table and
he was grateful for his host's gratuity.
They were making pancakes. The batter was being poured into a metal pan in
little groups. Tosen noticed that the stove seemed to be powered by its own
magic circle, a non-organic magic emitter was being used to create the fire
needed for cooking. It was a weak burner, but it was enough for Shal'tash to
cook with.
Tosen was befuddled. He had never seen such a thing in action. He got up and
looked at it closely. Shal'tash looked back and smiled, "Never seen a stove
burner before?"
"Not like that no, it looks like it's casting a weak fire spell, but
constantly."
"This is a lot more efficient than the coal burning stoves you have. This lets
you use the energy equivalent of a fireball to get a half hour of cooking heat,
or an hour or two of torchlight. I'm surprised you didn't know about this."
Tosen looked confused. "You mean you can use the square level spell to
supercharge the triangle level spell? No, they never taught us this. But how is
the burner even working?"
Shal'tash laughed. "It's nothing special. I just rooted the circle under the pan
instead of on my hand. Here, you try it." They flicked their wrist and banished
the magic circle. "Now cast a fireball but focus on the pan instead of your
hand, let it simmer a bit, and then kick off create fire."
Tosen was confused but nodded and tried to comply. After a moment Shal'tash
piped in: "no, don't think about where the pan is relative to your hand. Think
about where the pan is relative to the pan. You're so close. I know you can do
it."
Tosen nodded and started over. The circle started to be inscribed below the pan
and Shal'tash's face lit up like a Christmas tree. "Toor sha!"
The burner was lit. The fire was continuously burning and Tosen didn't feel the
sting of a continuous cast. "Perfect. See how easy this is? Spend the mana on
the fireball, then use it for the weaker spell. No need to waste any."
Tosen was astounded. It normally took him ages to learn magical skills, but here
he was on the second try with this person and their vague instruction and he did
two things he thought was impossible. It was like magic was all new all over
again. Can I use this to make a bunch of fireballs when casting a firestorm? How
far does this go?
"Be careful with this, you could really hurt someone if you do displacement
foolishly. They must have stopped teaching it for a reason." Shal'tash finished
the stack of pancakes and put the plate in the middle of the table. "Now let's
eat!"
They shared a meal. It was just what Tosen needed.
The meal was finished. Shal'tash looked over to Tosen. "So where are you headed?
I can point you in the right direction."
"Riltash, I have a delivery that I'm incredibly late for by now."
Shal'tash chuckled and pointed towards the statue. "The statue points towards
Riltash. Just go straight north and you'll get there in 20 minutes."
Tosen looked incredulous. "I was really that close?"
"Yeah, though it looked like you needed to get lost. It can be good for you."
He didn't understand what they meant by that, but he didn't think he needed to.
Shal'tash walked with Tosen to the north side of the oasis. Tosen looked towards
his saviour and was suddenly overcome with emotion. "Thank you so much. You
saved me."
"You are welcome. I saved you because I was in a situation worse than yours when
I found this oasis. I don't want anyone to experience the pain that I have felt,
so I saved you before it could get that bad."
"What can I do to repay you?"
"You don't need to do anything right now. Just save someone else when you can.
If you want, come back here and give me a visit. It'd be fun to catch up,
Tosen."
"Thanks again! I'll be back!"
Tosen walked off towards his payday. He looked back every so often and the oasis
became more distant and then faded completely from sight into the rest of the
sands. He was alone again, but not in spirit.
He never noticed that they knew his name without him telling them his name.
Shal'tash walked back towards their house and stood by their cactus. They
watched as Tosen faded into the sands and then headed inside. Their job was
complete.

View File

@ -3,7 +3,7 @@ title: The Origin of h
date: 2015-12-14 date: 2015-12-14
--- ---
NOTE: There is a [second part](https://xeiaso.net/blog/formal-grammar-of-h-2019-05-19) to this article now with a formal grammar. NOTE: There is a [second part](https://christine.website/blog/formal-grammar-of-h-2019-05-19) to this article now with a formal grammar.
For a while I have been pepetuating a small joke between my friends, co-workers and community members of various communities (whether or not this has been beneficial or harmful is out of the scope of this post). The whole "joke" is that someone says "h", another person says "h" back. For a while I have been pepetuating a small joke between my friends, co-workers and community members of various communities (whether or not this has been beneficial or harmful is out of the scope of this post). The whole "joke" is that someone says "h", another person says "h" back.

View File

@ -8,7 +8,7 @@ tags:
--- ---
EDIT(M02 20 2020): I've written a bit of a rebuttal to my own post EDIT(M02 20 2020): I've written a bit of a rebuttal to my own post
[here](https://xeiaso.net/blog/i-was-wrong-about-nix-2020-02-10). I am [here](https://christine.website/blog/i-was-wrong-about-nix-2020-02-10). I am
keeping this post up for posterity. keeping this post up for posterity.
I don't really know how I feel about [Nix][nix]. It's a functional package I don't really know how I feel about [Nix][nix]. It's a functional package

View File

@ -42,7 +42,7 @@ how things changed:
As of the time of writing this post, it is January third, 2020 and the roadmap As of the time of writing this post, it is January third, 2020 and the roadmap
is apparently to release V 0.2 this month. is apparently to release V 0.2 this month.
Let's see what's been fixed since [my last article](https://xeiaso.net/blog/v-vaporware-2019-06-23). Let's see what's been fixed since [my last article](https://christine.website/blog/v-vaporware-2019-06-23).
## Compile Speed ## Compile Speed

View File

@ -125,7 +125,7 @@ designing this, but I think the next character in my blog is going to be an
anthro snow leopard named Alicia. I want Alicia to be a beginner that is very anthro snow leopard named Alicia. I want Alicia to be a beginner that is very
new to computer programming and other topics, which would then make Mara into new to computer programming and other topics, which would then make Mara into
more of a teacher type. I may also introduce my own OC Cadey (the orca looking more of a teacher type. I may also introduce my own OC Cadey (the orca looking
thing you can see [here](https://xeiaso.net/static/img/avatar_large.png) thing you can see [here](https://christine.website/static/img/avatar_large.png)
or in the favicon of my site) into the mix to reply to these questions in or in the favicon of my site) into the mix to reply to these questions in
something more close to the Socratic method. something more close to the Socratic method.

View File

@ -37,7 +37,7 @@ Be well.
--- ---
Every so often I like to check in on the [V Programming Language][vlang]. It's been Every so often I like to check in on the [V Programming Language][vlang]. It's been
about six months since [my last post](https://xeiaso.net/blog/v-vvork-in-progress-2020-01-03), about six months since [my last post](https://christine.website/blog/v-vvork-in-progress-2020-01-03),
so I thought I'd take another look at it and see what progress has been done in six so I thought I'd take another look at it and see what progress has been done in six
months. months.

View File

@ -51,10 +51,10 @@ job. TLS configuration is not its job. Its job is to run your code. Everything
else should just be provided by the system. else should just be provided by the system.
I wrote a I wrote a
[blogpost](https://xeiaso.net/blog/land-1-syscalls-file-io-2018-06-18) [blogpost](https://christine.website/blog/land-1-syscalls-file-io-2018-06-18)
about this work and even did a about this work and even did a
[talk at GoCon [talk at GoCon
Canada](https://xeiaso.net/talks/webassembly-on-the-server-system-calls-2019-05-31) Canada](https://christine.website/talks/webassembly-on-the-server-system-calls-2019-05-31)
about it. about it.
And this worked for several months as I learned WebAssembly and started to And this worked for several months as I learned WebAssembly and started to
@ -93,8 +93,8 @@ people understand low-level operating system development.
I've even written a few blogposts about Olin: I've even written a few blogposts about Olin:
- [Olin: Why](https://xeiaso.net/blog/olin-1-why-09-1-2018) - [Olin: Why](https://christine.website/blog/olin-1-why-09-1-2018)
- [Olin: The Future](https://xeiaso.net/blog/olin-2-the-future-09-5-2018) - [Olin: The Future](https://christine.website/blog/olin-2-the-future-09-5-2018)
But, this was great for running stuff interactively and via the command line. It But, this was great for running stuff interactively and via the command line. It
left me wanting more. I wanted to have that mythical functions as a service left me wanting more. I wanted to have that mythical functions as a service
@ -230,5 +230,5 @@ keep the dream alive!
[olincwa]: https://github.com/Xe/olin/tree/master/docs/cwa-spec [olincwa]: https://github.com/Xe/olin/tree/master/docs/cwa-spec
[olincwarust]: https://github.com/Xe/olin/tree/master/cwa/olin [olincwarust]: https://github.com/Xe/olin/tree/master/cwa/olin
[olincwatest]: https://github.com/Xe/olin/blob/master/cwa/tests/src/main.rs [olincwatest]: https://github.com/Xe/olin/blob/master/cwa/tests/src/main.rs
[olintempleos]: https://xeiaso.net/blog/templeos-2-god-the-rng-2019-05-30 [olintempleos]: https://christine.website/blog/templeos-2-god-the-rng-2019-05-30
[wasmcloud]: https://tulpa.dev/within/wasmcloud [wasmcloud]: https://tulpa.dev/within/wasmcloud

View File

@ -1,902 +0,0 @@
---
title: We Already Have Go 2
date: 2022-05-25
tags:
- golang
- generics
- context
- modules
---
I've been using Go since Go 1.4. Since I started using Go then (2014-2015 ish),
I’ve seen the language evolve significantly. The Go I write today is roughly the
same Go as the Go I wrote back when I was still learning the language, but the
toolchain has changed in ways that make it so much nicer in practice. Here are
the biggest things that changed how I use Go on a regular basis:
* The compiler rewrite in Go
* Go modules
* The context package
* Generics
This is a good thing. Go has had a lot of people use it. My career would not
exist in its current form without Go. My time in the Go community has been
_catalytic_ to my career goals and it’s made me into the professional I am
today. Without having met the people I did in the Go slack, I would probably not
have gotten as lucky as I have as consistently as I have.
Releasing a "Go 2" has become a philosophical and political challenge due to the
forces that be. "Go 2" has kind of gotten the feeling of "this is never going to
happen, is it?" with how the political forces within and without the Go team are
functioning. They seem to have been incrementally releasing new features and
using version gating in `go.mod` to make it easier on people instead of a big
release with breaking changes all over the standard library.
This is pretty great and I am well in favour of this approach, but with all of
the changes that have built up there really should be a Go 2 by this point. If
only to make no significant changes and tag what we have today as Go 2.
<xeblog-conv name="Cadey" mood="coffee">Take everything I say here with a grain
of salt the size of east Texas. I am not an expert in programming language
design and I do not pretend to be one on TV. I am also not a member of the Go
team nor do I pretend to be one or see myself becoming one in the
future.<br /><br />If you are on the Go team and think that something I said
here is demonstrably wrong, please [contact me](/contact) so I can correct it. I
have tried to contain my personal feelings or observations about things to these
conversation snippets.</xeblog-conv>
This is a look back at the huge progress that has been made since Go 1 released
and what I'd consider to be the headline features of Go 2.
This is a whirlwind tour of the huge progress in improvement to the Go compiler,
toolchain, and standard library, including what I'd consider to be the headline
features of Go 2. I highly encourage you read this fairly large post in chunks
because it will feel like _a lot_ if you read it all at once.
## The Compiler Rewrite in Go
When the Go compiler was first written, it was written in C because the core Go
team has a background in Plan 9 and C was its lingua franca. However as a result
of either it being written in C or the design around all the tools it was
shelling out to, it wasn’t easy to cross compile Go programs. If you were
building windows programs on a Mac you needed to do a separate install of Go
from source with other targets enabled. This worked, but it wasn’t the default
and eventually the Go compiler rewrite in Go changed this so that Go could cross
compile natively with no extra effort required.
<xeblog-conv name="Cadey" mood="enby">This has been such an amazingly productive
part of the Go toolchain that I was shocked that Go didn’t have this out of the
gate at version 1. Most people that use Go today don’t know that there was a
point where Go didn’t have the easy to use cross-compiling superpower it
currently has, and I think that is a more sure marker of success than anything
else.</xeblog-conv>
<xeblog-conv name="Mara" mood="happy">The cross compliation powers are why
Tailscale uses Go so extensively throughout its core product. Every Tailscale
client is built on the same Go source tree and everything is in lockstep with
eachother, provided people actually update their apps. This kind of thing would
be at the least impossible or at the most very difficult in other languages like
Rust or C++.</xeblog-conv>
This one feature is probably at the heart of more CI flows, debian package
releases and other workflows than we can know. It's really hard to understate
how simple this kind of thing makes distributing software for other
architectures, especially given that macOS has just switched over to aarch64
CPUs.
Having the compiler be self-hosting does end up causing a minor amount of
grief for people wanting to bootstrap a Go compiler from absolute source code
on a new Linux distribtion (and slightly more after the minimum Go compiler
version to compile Go will be raised to Go 1.17 with the release of Go 1.19
in about 6 months from the time of this post being written). This isn't too
big of a practical issue given how fast the compiler builds, but it is a
nonzero amount of work. The bootstrapping can be made simpler with
[gccgo](https://gcc.gnu.org/onlinedocs/gccgo/), a GCC frontend that is mostly
compatible with the semantics and user experience of the Go compiler that
Google makes.
Another key thing porting the compiler to Go unlocks is the ability to compile
Go packages in parallel. Back when the compiler was written in C, the main point
of parallelism was the fact that each Go package was compiled in parallel. This
lead to people splitting up bigger packages into smaller sub-packages in order
to speedhack the compiler. Having the compiler be written in Go means that the
compiler can take advantage of Go features like its dead-simple concurrency
primitives to spread the load out across all the cores on the machine.
<xeblog-conv name="Mara" mood="hacker">The Go compiler is fast sure, but
over a certain point having each package be compiled in a single-threaded manner
adds up and can make build times slow. This was a lot worse when things like the
AWS, GCP and Kubernetes client libraries had everything in one big package.
Building those packages could take minutes, which is very long in Go
time.</xeblog-conv>
## Go Modules
In Go's dependency model, you have a folder that contains all your Go code
called the `GOPATH`. The `GOPATH` has a few top level folders that have a
well-known meaning in the Go ecosystem:
* bin: binary files made by `go install` or `go get` go here
* pkg: intermediate compiler state goes here
* src: Go packages go here
`GOPATH` has one major advantage: it is ruthlessly easy to understand the
correlation between the packages you import in your code to their locations on
disk.
If you need to see what `within.website/ln` is doing, you go to
`GOPATH/src/within.website/ln`. The files you are looking for are somewhere in
there. You don’t have to really understand how the package manager works (mostly
because there isn’t one). If you want to hack something up you just go to the
folder and add the changes you want to see.
You can delete all of the intermediate compiler state easily in one fell swoop.
Just delete the `pkg` folder and poof, it’s all gone. This was great when you
needed to free up a bunch of disk space really quickly because over months the
small amount of incremental compiler state can really add up.
The Go compiler would fetch any missing packages from the internet at build time
so things Just Worked™️. This makes it utterly trivial to check out a project and
then build/run it. That combined with `go get` to automatically just figure
things out and install them made installing programs written in Go so easy that
it’s almost magic. This combined with Go's preference for making static binaries
as much as possible meant that even if the user didn't have Go installed you could
easily make a package to hand off to your users.
The GOPATH was conceptually simple to reason about. Go code goes in the GOPATH. The
best place for it was in the GOPATH. There's no reason to put it anywhere else.
Everything was organized into its place and it was lovely.
This wasn’t perfect though. There were notable flaws in this setup that were
easy to run into in practice:
* There wasn't a good way to make sure that everyone was using the _same copies_
of every library. People did add vendoring tools later to check that everyone
was using the same copies of every package, but this also introduced problems
when one project used one version of a dependency and another project used
another in ways that were mutually incompatible.
* The process to get the newest version of a dependency was to grab the latest
commit off of the default branch of that git repo. There was support for SVN,
mercurial and fossil, but in practice Git was the most used one so it’s almost
not worth mentioning the other version control systems. This also left you at
the mercy of other random people having good code security sense and required
you to audit your dependencies, but this is fairly standard across ecosystems.
* Dependency names were case sensitive on Linux but not on Windows or macOS.
Arguably this is a "Windows and macOS are broken for backwards compatibility
reasons" thing, but this did bite me at random times without warning.
* If the wrong random people deleted their GitHub repos, there's a chance your
builds could break unless your GOPATH had the packages in it already. Then you
could share that with your coworkers or the build machine somehow, maybe even
upload those packages to a git repository to soft-fork it.
* The default location for the GOPATH created a folder in your home directory.
<xeblog-conv name="Cadey" mood="coffee">Yeah, yeah, this default was added later
but still people complained about having to put the GOPATH somewhere at first.
Having to choose a place to put all the Go code they would use seemed like a big
choice that people really wanted solid guidance and defaults on. After a while
they changed this to default to `~/go` (with an easy to use command to influence
the defaults without having to set an environment variable). I don't personally
understand the arguments people have for wanting to keep their home directory
"clean", but their preferences are valid regardless.</xeblog-conv>
Overall I think GOPATH was a net good thing for Go. It had its downsides, but as
far as these things go it was a very opinionated place to start from. This is
something typical to Go (much to people's arguments), but the main thing that it
focused on was making Go conceptually simple. There's not a lot going on there.
You have code in the folder and then that's where the Go compiler looks for
other code. It's a very lightweight approach to things that a lot of other
languages could learn a lot from. It's great for monorepos because it basically
treats all your Go code as one big monorepo. So many other languages don’t
really translate well to working in a monorepo context like Go does.
### Vendoring
That making sure everyone had the same versions of everything problem ended up
becoming a big problem in practice. I'm assuming that the original intent of the
GOPATH was to be similar to how Google's internal monorepo worked, where
everyone clones and deals with the entire GOPATH in source control. You'd then
have to do GOPATH juggling between monorepos, but the intent was to have
everything in one big monorepo anyways, so this wasn't thought of as much of a
big deal in practice. It turns out that people in fact did not want to treat Go
code this way, in practice this conflicted with the dependency model that Go
encouraged people to use with how people consume libraries from GitHub or other
such repository hosting sites.
The main disconnect between importing from a GOPATH monorepo and a Go library
off of GitHub is that when you import from a monorepo with a GOPATH in it, you
need to be sure to import the repository path and not the path used inside the
repository. This sounds weird but this means you'd import
`github.com/Xe/x/src/github.com/Xe/x/markov` instead of
`github.com/Xe/x/markov`. This means that things need to be extracted _out of_
monorepos and reformatted into "flat" repos so that you can only grab the one
package you need. This became tedious in practice.
In Go 1.5 (the one where they rewrote the compiler in Go) they added support for
[vendoring code into your
repo](https://medium.com/@freeformz/go-1-5-s-vendor-experiment-fd3e830f52c3).
The idea here was to make it easy to get closer to the model that the Go authors
envisioned for how people should use Go. Go code should all be in one big happy
repo and everything should have its place in your GOPATH. This combined with
other tools people made allowed you to vendor all of your dependencies into a
`vendor` folder and then you could do whatever you wanted from there.
One of the big advantages of the `vendor` folder was that you could clone your
git repo, create a new process namespace and then run tests without a network
stack. Everything would work offline and you wouldn't have to worry about
external state leaking in. Not to mention removing the angle of someone deleting
their GitHub repos causing a huge problem for your builds.
<xeblog-conv name="Mara" mood="happy">Save tests that require internet access or
a database engine!</xeblog-conv>
This worked for a very long time. People were able to vendor their code into
their repos and everything was better for people using Go. However the most
critical oversight with the `vendor` folder approach was that the Go team didn't
create an official tool to manage that `vendor` folder. They wanted to let tools
like `godep` and `glide` handle that. This is kind of a reasonable take, Go
comes from a very Google culture where this kind of problem doesn't happen, so
as a result they probably won't be able to come up with something that meets the
needs of the outside world very easily.
<xeblog-conv name="Cadey" mood="enby">I can't speak for how `godep` or `glide`
works, I never really used them enough to have a solid opinion. I do remember
using [`vendor`](https://github.com/bmizerany/vendor) in my own projects though.
That had no real dependency resolution algorithm to speak of because it assumed
that you had everything working locally when you vendored the code.</xeblog-conv>
### `dep`
After a while the Go team worked with people in the community to come up with an
"official experiment" in tracking dependencies called `dep`. `dep` was a tool
that used some more fancy computer science maths to help developers declare
dependencies for projects in a way like you do in other ecosystems. When `dep`
was done thinking, it emitted a bunch of files in `vendor` and a lockfile in
your repository. This worked really well and when I was working at Heroku this
was basically our butter and bread for how to deal with Go code.
<xeblog-conv name="Cadey" mood="enby">It probably helped that my manager was on
the team that wrote `dep`.</xeblog-conv>
One of the biggest advantages of `dep` over other tools was the way that it
solved versioning. It worked by having each package declare
[constraints](https://golang.github.io/dep/docs/the-solver.html) in the ranges
of versions that everything requires. This allowed it to do some fancy
dependency resolution math similar to how the solvers in `npm` or `cargo` work.
This worked fantastically in the 99% case. There were some fairly easy to
accidentally get yourself in cases where you could make the solver loop
infinitely though, as well as ending up in a state where you have mutually
incompatible transient dependencies without any real way around it.
<xeblog-conv name="Mara" mood="hacker">`npm` and `cargo` work around this by
letting you use multiple versions of a single dependency in a
project.</xeblog-conv>
However these cases were really really rare, only appearing in much, much larger
repositories. I don't think I practically ran into this, but I'm sure someone
reading this right now found themselves in `dep` hell and probably has a hell of
a war story around it.
### vgo and Modules
This lead the Go team to come up with a middle path between the unrestricted
madness of GOPATH and something more maximal like `dep`. They eventually called
this Go modules and the core reasons for it are outlined in [this series of
technical posts](https://research.swtch.com/vgo).
<xeblog-conv name="Mara" mood="hacker">These posts are a very good read and I'd
highly suggest reading them if you've never seem then before. It outlines the
problem space and the justification for the choices that Go modules ended up
using. I don't agree with all of what is said there, but overall it's well
worth reading at least once if you want to get an idea of the inspirations
that lead to Go modules.</xeblog-conv>
Apparently the development of Go modules came out as a complete surprise,
even to the core developer team of `dep`. I'm fairly sure this lead my
manager to take up woodworking as his main non work side hobby, I can only
wonder about the kind of resentment this created for other parts of the
`dep` team. They were under the impression that `dep` was going to be the
future of the ecosystem (likely under the subcommand `go dep`) and then had
the rug pulled out from under their feet.
<xeblog-conv name="Cadey" mood="coffee">The `dep` team was as close as we've
gotten for having people in the _actual industry_ using Go _in production_
outside of Google having a real voice in how Go is used in the real world. I
fear that we will never have this kind of thing happen again.<br /><br />It's
also worth noting that the fallout of this lead to the core `dep` team leaving
the Go community.</xeblog-conv>
<xeblog-conv name="Mara" mood="hmm">Well, Google has to be using Go modules in
their monorepo, right? If that's the official build system for Go it makes sense
that they'd be dogfooding it hard enough that they'd need to use the tool in the
same way that everyone else did.</xeblog-conv>
<xeblog-conv name="Numa" mood="delet">lol nope. They use an overcomplicated
bazel/blaze abomination that has developed in parallel to their NIH'd source
control server. Google doesn't have to deal with the downsides of Go modules
unless it's in a project like Kubernetes. It's easy to imagine that they just
don't have the same problems that everyone else does due to how weird Google
prod is. Google only has problems that Google has, and statistically your
company is NOT Google.</xeblog-conv>
Go modules does solve one very critical problem for the Go ecosystem though: it
allows you to have the equivalent of the GOPATH but with multiple versions of
dependencies in it. It allows you to have `within.website/ln@v0.7` and
`within.website/ln@0.9` as dependencies for _two different projects_ without
having to vendor source code or do advanced GOPATH manipulation between
projects. It also adds cryptographic checksumming for each Go module that you
download from the internet, so that you can be sure the code wasn't tampered
with in-flight. They also created a cryptographic checksum comparison server so
that you could ask a third party to validate what it thinks the checksum is so
you can be sure that the code isn't tampered with on the maintainer's side. This
also allows you to avoid having to shell out to `git` every time you fetch a
module that someone else has fetched before. Companies could run their own Go
module proxy and then use that to provide offline access to Go code fetched from
the internet.
<xeblog-conv name="Mara" mood="hmm">Wait, couldn't this allow Google to see the
source code of all of your Go dependencies? How would this intersect with
private repositories that shouldn't ever be on anything but work
machines?</xeblog-conv>
<xeblog-conv name="Cadey" mood="coffee">Yeah, this was one of the big privacy
disadvantages out of the gate with Go modules. I think that in practice the
disadvantages are limited, but still the fact that it defaults to phoning home
to Google every time you run a Go build without all the dependencies present
locally is kind of questionable. They did make up for this with the checksum
verification database a little, but it's still kinda sus.<br /><br />I'm not
aware of any companies I've worked at running their own internal Go module
caching servers, but I ran my own for a very long time.</xeblog-conv>
The earliest version of Go modules basically was a glorified `vendor` folder
manager named `vgo`. This worked out amazingly well and probably made
prototyping this a hell of a lot easier. This worked well enough that we used
this in production for many services at Heroku. We had no real issues with it
and most of the friction was with the fact that most of the existing ecosystem
had already been using `dep` or `glide`.
<xeblog-conv name="Mara" mood="hacker">There was a bit of interoperability glue
that allowed `vgo` to parse the dependency definitions in `dep`, `godep` and
`glide`. This still exists today and helps `go mod init` tell what dependencies
to import into the Go module to aid migration.</xeblog-conv>
If they had shipped this in prod, it probably would have been a huge success. It
would also let people continue to use `dep`, `glide` and `godep`, but just doing
that would also leave the ecosystem kinda fragmented. You’d need to have code
for all 4 version management systems to parse their configuration files and
implement algorithms that would be compatible with the semantics of all of them.
It would work and the Go team is definitely smart enough to do it, but in
practice it would be a huge mess.
This also solved the case-insensitive filesystem problem with
[bang-casing](https://go.dev/ref/mod#goproxy-protocol). This allows them to
encode the capital letters in a path in a way that works on macOS and Windows
without having to worry about horrifying hacks that are only really in place for
Photoshop to keep working.
### The Subtle Problem of `v2`
However one of the bigger downsides that came with Go modules is what I've been
calling the "v2 landmine" that Semantic Import Versioning gives you. One of the
very earliest bits of Go advice was to make the import paths for version 1 of a
project and version 2 of a project different so that people can mix the two to
allow more graceful upgrading across a larger project. Semantic Import
Versioning enforces this at the toolchain level, which means that it can be the
gate between compiling your code or not.
<xeblog-conv name="Cadey" mood="coffee">Many people have been telling me that
I’m kind of off base for thinking that this is a landmine for people, but I am
using the term “landmine” to talk about this because I feel like it reflects the
rough edges of unexpectedly encountering this in the wild. It kinda feels like
you stepped on a landmine.</xeblog-conv>
<xeblog-conv name="Numa" mood="delet">It's also worth noting that the protobuf
team didn't use major version 2 when making an API breaking change. They
defended this by saying that they are changing the import path away from GitHub,
but it feels like they wanted to avoid the v2 problem.</xeblog-conv>
The core of this is that when you create major version 2 of a Go project, you
need to adjust all your import paths everywhere in that project to import the
`v2` of that package or you will silently import the `v1` version of that
package. This can end up making large projects create circular dependencies on
themselves, which is quite confusing in practice. When consumers are aware of
this, then they can use that to more gradually upgrade larger codebases to the
next major version of a Go module, which will allow for smaller refactors.
This also applies to consumers. Given that this kind of thing is something that
you only do in Go it can come out of left field. The go router
[github.com/go-chi/chi](https://github.com/go-chi/chi/issues/462) tried doing
modules in the past and found that it lead to confusing users. Conveniently they
only really found this out after the Go modules design was considered final and
Semantic Import Versioning has always been a part of Go modules and the Go team
is now refusing to budge on this.
<xeblog-conv name="Cadey" mood="coffee">My suggestion to people is to never
release a version `1.x.x` of a Go project to avoid the "v2 landmine". The Go
team claims that the right bit of tooling can help ease the pain, but this
tooling never really made it out into the public. I bet it works great inside
Google's internal monorepo though!</xeblog-conv>
When you were upgrading a Go project that already hit major version 2 or
higher to Go modules, adopting Go modules forced maintainers to make another
major version bump because it would break all of the import paths for every
package in the module. This caused some maintainers to meet Go modules with
resistance to avoid confusing their consumers. The workarounds for people that
still used GOPATH using upstream code with Semantic Import Versioning in it
were also kind of annoying at first until the Go team added "minimal module
awareness" to GOPATH mode. Then it was fine.
<xeblog-conv name="Mara" mood="hmm">It feels like you are overly focusing on the
`v2` problem. It can't really be that bad, can it? `grpc-gateway` updated to v2
without any major issues. What's a real-world example of this?</xeblog-conv>
<xeblog-conv name="Numa" mood="delet">The situation with
[github.com/gofrs/uuid](https://github.com/gofrs/uuid/issues/61) was heckin'
bad. Arguably it's a teething issue as the ecosystem was still moving to the new
modules situation, but it was especially bad for projects that were already at
major version 2 or higher because adding Go modules support meant that they
needed to update the major version just for Go modules. This was a tough sell
and rightly so.<br /><br />This was claimed to be made a non-issue by the right
application of tooling on the side, but this tooling was either never developed
or not released to us mere mortals outside of Google. Even with automated
tooling this can still lead to massive diffs that are a huge pain to review,
even if the only thing that is changed is the version number in every import of
every package in that module. This was even worse for things that have C
dependencies, as if you didn't update it everywhere in your dependency chain you
could have two versions of the same C functions try to be linked in and this
really just does not work.</xeblog-conv>
Overall though, Go modules has been a net positive for the community and for
people wanting to create reliable software in Go. It’s just such a big semantics
break in how the toolchain works that I almost think it would have been easier
for the to accept if _that_ was Go 2. Especially since the semantic of how the
toolchain worked changed so much.
<xeblog-conv name="Mara" mood="hmm">Wait, doesn’t the Go compiler have a
backwards compatibility promise that any code built with Go 1.x works on go
1.(x+1)?</xeblog-conv>
<xeblog-conv name="Cadey" mood="coffee">Yes, but that only applies to _code you
write_, not _semantics of the toolchain_ itself. On one hand this makes a lot of
sense and on the other it feels like a cop-out. The changes in how `go get` now
refers to adding dependencies to a project and `go install` now installs a
binary to the system have made an entire half decade of tool installation
documentation obsolete. It’s understandable why they want to make that change,
but the way that it broke people’s muscle memory is [quite frustrating for
users](https://github.com/golang/go/issues/40276#issuecomment-1109797059) that
aren’t keeping on top of every single change in semantics of toolchains (this
bites me constantly when I need to quick and dirty grab something outside of a
Nix package). I understand _why_ this isn’t a breaking change as far as the
compatibility promise but this feels like a cop-out in my subjective
opinion.</xeblog-conv>
## Contexts
One of Go’s major features is its co-operative threading system that it calls
goroutines. Goroutines are kinda like coroutines that are scheduled by the
scheduler. However there is no easy way to "kill" a goroutine. You have to add
something to the invocation of the goroutine that lets you signal it to stop and
then opt-in the goroutine to stop.
Without contexts you would need to do all of this legwork manually. Every
project from the time before contexts still shows signs of this. The best
practice was to make a "stop" channel like this:
```go
stop := make(chan struct{})
```
And then you'd send a cancellation signal like this:
```go
stop <- struct{}{}
```
<xeblog-conv name="Mara" mood="hacker">The type `struct{}` is an anonymous
structure value that takes 0 bytes in ram. It was suggested to use this as your
stopping signal to avoid unneeded memory allocations. A `bool` needs one whole
machine word, which can be up to 64 bits of ram. In practice the compiler can
smoosh multiple bools in a struct together into one place in ram, but when
sending these values over a channel like this you can't really cheat that
way.</xeblog-conv>
This did work and was the heart of many event loops, but the main problem with
it is that the signal was only sent _once_. Many other people also followed up
the stop signal by closing the channel:
```go
close(stop)
```
However with naĂŻve stopping logic the closed channel would successfully fire a
zero value of the event. So code like this would still work the way you wanted:
```go
select {
case <- stop:
haltAndCatchFire()
}
```
### Package `context`
However if your stop channel was a `chan bool` and you relied on the `bool`
value being `true`, this would fail because the value would be `false`. This
was a bit too brittle for comfortable widespread production use and we ended
up with the [context](https://pkg.go.dev/context) package in the standard
library. A Go context lets you more easily and uniformly handle timeouts and
giving up when there is no more work to be done.
<xeblog-conv name="Mara" mood="hacker">This started as something that existed
inside the Google monorepo that escaped out into the world. They also claim to
have an internal tool that makes
[`context.TODO()`](https://pkg.go.dev/context#TODO) useful (probably by showing
you the callsites above that function?), but they never released that tool as
open source so it’s difficult to know where to use it without that added
context.</xeblog-conv>
One of the most basic examples of using contexts comes when you are trying to
stop something from continuing. If you have something that constantly writes
data to clients such as a pub-sub queue, you probably want to stop writing data
to them when the client disconnects. If you have a large number of HTTP requests
to do and only so many workers can make outstanding requests at once, you
want to be able to set a timeout so that after a certain amount of time it gives
up.
Here's an example of using a context in an event processing loop (of course while
pretending that fetching the current time is anything else that isn't a contrived
example to show this concept off):
```go
t := time.NewTicker(30 * time.Second)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for {
select {
case <- ctx.Done():
log.Printf("not doing anything more: %v", ctx.Err())
return
case data := <- t.C:
log.Printf("got data: %s", data)
}
}
```
This will have the Go runtime select between two channels, one of them will
emit the current time every 30 seconds and the other will fire when the
`cancel` function is called.
<xeblog-conv name="Mara" mood="happy">Don't worry, you can call the `cancel()`
function multiple times without any issues. Any additional calls will not do
anything special.</xeblog-conv>
If you want to set a timeout on this (so that the function only tries to run
for 5 minutes), you'd want to change the second line of that example to this:
```go
ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Minute)
defer cancel()
```
<xeblog-conv name="Mara" mood="happy">You should always `defer cancel()` unless
you can prove that it is called elsewhere. If you don't do this you can leak
goroutines that will dutifully try to do their job potentially forever without
any ability to stop them.</xeblog-conv>
The context will be automatically cancelled after 5 minutes. You can cancel it
sooner by calling the `cancel()` function should you need to. Anything else in
the stack that is context-aware will automatically cancel as well as the
cancellation signal percolates down the stack and across goroutines.
You can attach this to an HTTP request by using
[`http.NewRequestWithContext`](https://pkg.go.dev/net/http#NewRequestWithContext):
```go
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://xeiaso.net/.within/health", nil)
```
And then when you execute the request (such as with `http.DefaultClient.Do(req)`)
the context will automatically be cancelled if it takes too long to fetch the
response.
You can also wire this up to the `Control-c` signal using a bit of code
[like this](https://medium.com/@matryer/make-ctrl-c-cancel-the-context-context-bd006a8ad6ff).
Context cancellation propagates upwards, so you can use this to ensure that things
get stopped properly.
<xeblog-conv name="Mara" mood="hacker">Be sure to avoid creating a "god context"
across your entire app. This is a known anti-pattern and this pattern should only
be used for small command line tools that have an expected run time in the minutes
at worst, not hours like production bearing services.</xeblog-conv>
This is a huge benefit to the language because of how disjointed the process of
doing this before contexts was. Because this wasn’t in the core of the language,
every single implementation was different and required learning what the library
did. Not to mention adapting between libraries could be brittle at best and
confusing at worst.
I understand why they put data into the context type, but in practice I really
wish they didn’t do that. This feature has been abused a lot in my experience.
At Heroku a few of our production load bearing services used contexts as a
dependency injection framework. This did work, but it turned a lot of things
that would normally be compile time errors into runtime errors.
<xeblog-conv name="Cadey" mood="coffee">I say this as someone who maintains a
library that uses contexts to store [contextually relevant log
fields](https://pkg.go.dev/within.website/ln) as a way to make logs easier to
correlate between.<br /><br />Arguably you could make the case that people are misusing the
tool and of course this is what will happen when you do that but I don't know if
this is really the right thing to tell people.</xeblog-conv>
I wish contexts were in the core of the language from the beginning. I know that
it is difficult to do this in practice (especially on all the targets that Go
supports), but having cancellable syscalls would be so cool. It would also be
really neat if contexts could be goroutine-level globals so you didn’t have to
"pollute" the callsites of every function with them.
<xeblog-conv name="Cadey" mood="coffee">At the time contexts were introduced,
one of the major arguments I remember hearing against them was that contexts
"polluted" their function definitions and callsites. I can't disagree with this
sentiment, at some level it really does look like contexts propagate "virally"
throughout a codebase.<br /><br />I think that the net improvements to
reliability and understandability of how things get stopped do make up for this
though. Instead of a bunch of separate ways to cancel work in each individual
library you have the best practice in the standard library. Having contexts
around makes it a lot harder to "leak" goroutines on accident.</xeblog-conv>
## Generics
One of the biggest ticket items that Go has added is "generic types", or being
able to accept types as parameters for other types. This is really a huge ticket
item and I feel that in order to understand _why_ this is a huge change I need
to cover the context behind what you had before generics were added to the
language.
One of the major standout features of Go is interface types. They are like Rust
Traits, Java Interfaces, or Haskell Typeclasses; but the main difference is that
interface types are _implicit_ rather than explicit. When you want to meet the
signature of an interface, all you need to do is implement the contract that the
interface spells out. So if you have an interface like this:
```go
type Quacker interface {
Quack()
}
```
You can make a type like `Duck` a `Quacker` by defining the `Duck` type and a
`Quack` method like this:
```go
type Duck struct{}
func (Duck) Quack() { fmt.Println("Quack!") }
```
But this is not limited to just `Ducks`, you could easily make a `Sheep` a
`Quacker` fairly easily:
```go
type Sheep struct{}
func (Sheep) Quack() { fmt.Println("*confused sheep noises*") }
```
This allows you to deal with expected _behaviors_ of types rather than having to
have versions of functions for every concrete implementation of them. If you
want to read from a file, network socket, `tar` archive, `zip` archive, the
decrypted form of an encrypted stream, a TLS socket, or a HTTP/2 stream they're
all [`io.Reader`](https://pkg.go.dev/io#Reader) instances. With the example
above we can make a function that takes a `Quacker` and then does something with
it:
```go
func main() {
duck := Duck{}
sheep := Sheep{}
doSomething(duck)
doSomething(sheep)
}
func doSomething(q Quacker) {
q.Quack()
}
```
<xeblog-conv name="Mara" mood="hacker">If you want to play with this example,
check it out on the Go playground [here](https://go.dev/play/p/INK8O2O-D01). Try
to make a slice of Quackers and pass it to `doSomething`!</xeblog-conv>
You can also embed interfaces into other interfaces, which will let you create
composite interfaces that assert multiple behaviours at once. For example,
consider [`io.ReadWriteCloser`](https://pkg.go.dev/io#ReadWriteCloser). Any
value that matches an `io.Reader`, `io.Writer` and an `io.Closer` will be able
to be treated as an `io.ReadWriteCloser`. This allows you to assert a lot of
behaviour about types even though the actual underlying types are opaque to you.
This means it’s easy to split up a [`net.Conn`](https://pkg.go.dev/net#Conn)
into its reader half and its writer half without really thinking about
it:
```go
conn, _ := net.Dial("tcp", "127.0.0.1:42069")
var reader io.Reader = conn
var writer io.Writer = conn
```
And then you can pass the writer side off to one function and the reader side
off to another.
There’s also a bunch of room for "type-level middleware" like
[`io.LimitReader`](https://pkg.go.dev/io#LimitReader). This allows you to set
constraints or details around an interface type while still meeting the contract
for that interface, such as an `io.Reader` that doesn’t let you read too much,
an `io.Writer` that automatically encrypts everything you feed It with TLS, or
even something like sending data over a Unix socket instead of a TCP one. If it
fits the shape of the interface, it Just Works.
However, this falls apart when you want to deal with a collection of _only one_
type that meets an interface at once. When you create a slice of `Quacker`s and
pass it to a function, you can put both `Duck`s and `Sheep` into that slice:
```go
quackers := []Quacker{
Duck{},
Sheep{},
}
doSomething(quackers)
```
If you want to assert that every `Quacker` is the same type, you have to do some
fairly brittle things that step around Go's type safety like this:
```go
func doSomething(qs []Quacker) error {
// Store the name of the type of first Quacker.
// We have to use the name `typ` because `type` is
// a reserved keyword.
typ := fmt.Sprintf("%T", qs[0])
for i, q := range qs {
if qType := fmt.Sprintf("%T", q); qType != typ {
return fmt.Errorf("slice value %d was type %s, wanted: %s", qType, typ)
}
q.Quack()
}
return nil
}
```
This would explode at runtime. This same kind of weakness is basically the main
reason why the Go standard library package [`container`](https://pkg.go.dev/container)
is mostly unused. Everything in the `container` package deals with
`interface{}`/`any` values, which is Go for "literally anything". This means
that without careful wrapper code you need to either make interfaces around
everything in your lists (and then pay the cost of boxing everything in an
interface, which adds up a lot in practice in more ways than you'd think) or
have to type-assert anything going into or coming out of the list, combined
with having to pay super close attention to anything touching that code
during reviews.
<xeblog-conv name="Cadey" mood="enby">Don't get me wrong, interface types
are an _amazing_ standout feature of Go. They are one of the main reasons that
Go code is so easy to reason about and work with. You don't have to worry
about the entire tree of stuff that a value is made out of, you can just
assert that values have behaviors and then you're off to the races. I end up
missing the brutal simplicity of Go interfaces in other languages like Rust.
</xeblog-conv>
### Introducing Go Generics
In Go 1.18, support for adding types as parameters to other types was added.
This allows you to define constraints on what types are accepted by a function,
so that you can reuse the same logic for multiple different kinds of underlying
types.
That `doSomething` function from above could be rewritten like this with
generics:
```go
func doSomething[T Quacker](qs []T) {
for i, q := range qs {
q.Quack()
}
}
```
However this doesn't currently let you avoid mixing types of `Quacker`s at
compile time like I assumed while I was writing the first version of this
article. This does however let you write code like this:
```go
doSomething([]Duck{{}, {}, {}})
doSomething([]Sheep{{}, {}, {}})
```
And then this will reject anything that _is not a `Quacker`_ at compile time:
```go
doSomething([]string{"hi there this won't work"})
```
```
./prog.go:20:13: string does not implement Quacker (missing Quack method)
```
### Unions
This also lets you create untagged union types, or types that can be a range of
other types. These are typically useful when writing parsers or other similar
things.
<xeblog-conv name="Numa" mood="delet">It's frankly kind of fascinating that
something made by Google would even let you _think_ about the word "union" when
using it.</xeblog-conv>
Here's an example of a union type of several different kinds of values that you
could realistically see in a parser for a language like [LOLCODE](http://www.lolcode.org/):
```go
// Value can hold any LOLCODE value as defined by the LOLCODE 1.2 spec[1].
//
// [1]: https://github.com/justinmeza/lolcode-spec/blob/master/v1.2/lolcode-spec-v1.2.md#types
type Value interface {
int64 // NUMBR
float64 // NUMBAR
string // YARN
bool // TROOF
struct{} // NOOB
}
```
This is similar to making something like an
[`enum`](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html) in Rust,
except that there isn't any tag for what the data could be. You still have to do
a type-assertion over every value it _could_ be, but you can do it with only the
subset of values listed in the interface vs any possible type ever made. This
makes it easier to constrain what values can be so you can focus more on your
parsing code and less on defensively programming around variable types.
This adds up to a huge improvement to the language, making things that were
previously very tedious and difficult very easy. You can make your own
generic collections (such as a B-Tree) and take advantages of packages like
[`golang.org/x/exp/slices`](https://pkg.go.dev/golang.org/x/exp/slices) to avoid
the repetition of having to define utility functions for every single type you
use in a program.
<xeblog-conv name="Cadey" mood="enby">I'm barely scratching the surface with
generics here, please see the [type parameters proposal
document](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md)
for a lot more information on how generics work. This is a well-written thing
and I highly suggest reading this at least once before you try to use generics
in your Go code. I've been watching this all develop from afar and I'm very
happy with what we have so far (the only things I'd want would be a bit more
ability to be precise about what you are allowing with slices and maps as
function arguments).</xeblog-conv>
---
In conclusion, I believe that we already have Go 2. It’s just called Go 1.18 for
some reason. It’s got so many improvements and fundamental changes that I
believe that this is already Go 2 in spirit. There are so many other things that
I'm not covering here (mostly because this post is so long already) like
fuzzing, RISC-V support, binary/octal/hexadecimal/imaginary number literals,
WebAssembly support, so many garbage collector improvements and more. This has
added up to make Go a fantastic choice for developing server-side applications.
I, as some random person on the internet that is not associated with the Go
team, think that if there was sufficient political will that they could probably
label what we have as Go 2, but I don’t think that is going to happen any time
soon. Until then, we still have a very great set of building blocks that allow
you to make easy to maintain production quality services, and I don’t see that
changing any time soon.
---
<xeblog-conv name="Mara" mood="happy">If you had subscribed to the
[Patreon](https://patreon.com/cadey) you could have read this a week
ago!</xeblog-conv>

View File

@ -15,4 +15,4 @@ of that talk has been posted.
I hope you enjoy! I have some more blogposts in the queue but I've been sleeping horribly lately. Here's hoping that clears up. I hope you enjoy! I have some more blogposts in the queue but I've been sleeping horribly lately. Here's hoping that clears up.
[goconcanada]: https://gocon.ca/ [goconcanada]: https://gocon.ca/
[talklink]: https://xeiaso.net/talks/webassembly-on-the-server-system-calls-2019-05-31 [talklink]: https://christine.website/talks/webassembly-on-the-server-system-calls-2019-05-31

View File

@ -31,7 +31,7 @@ $ curl https://mi.within.website/api/webmention/01ERGGEG7DCKRH3R7DH4BXZ6R9 | jq
{ {
"id": "01ERGGEG7DCKRH3R7DH4BXZ6R9", "id": "01ERGGEG7DCKRH3R7DH4BXZ6R9",
"source_url": "https://maya.land/responses/2020/12/01/i-think-this-blog-post-might-have-been.html", "source_url": "https://maya.land/responses/2020/12/01/i-think-this-blog-post-might-have-been.html",
"target_url": "https://xeiaso.net/blog/toast-sandwich-recipe-2019-12-02", "target_url": "https://christine.website/blog/toast-sandwich-recipe-2019-12-02",
"title": null "title": null
} }
``` ```

View File

@ -80,7 +80,7 @@ in one of a few ways:
Some concepts are pulled in from various documents and ideas in a slightly Some concepts are pulled in from various documents and ideas in a slightly
[kasmakfa](https://write.as/excerpts/practical-kasmakfa) manner, but overall the [kasmakfa](https://write.as/excerpts/practical-kasmakfa) manner, but overall the
most "confusing" thing to new readers is going to be related to this comment in most "confusing" thing to new readers is going to be related to this comment in
the [anapana](https://xeiaso.net/blog/when-then-zen-anapana-2018-08-15) the [anapana](https://christine.website/blog/when-then-zen-anapana-2018-08-15)
feature: feature:
> Note: "the body" means the sack of meat and bone that you are currently living inside. For the purposes of explanation of this technique, please consider what makes you yourself separate from the body you live in. > Note: "the body" means the sack of meat and bone that you are currently living inside. For the purposes of explanation of this technique, please consider what makes you yourself separate from the body you live in.

View File

@ -32,7 +32,7 @@ This article is a more verbose version of [the correlating feature from when-the
The When Then Zen project aims to describe the finer points of meditative concepts in plain English. As such, we start assuming just about nothing and build fractally on top of concepts derived from common or plain English usage of the terms. Some of these techniques may be easier for people with a more intensive meditative background, but try things and see what works best for you. Meditation in general works a lot better when you have a curious and playful attitude about figuring things out. The When Then Zen project aims to describe the finer points of meditative concepts in plain English. As such, we start assuming just about nothing and build fractally on top of concepts derived from common or plain English usage of the terms. Some of these techniques may be easier for people with a more intensive meditative background, but try things and see what works best for you. Meditation in general works a lot better when you have a curious and playful attitude about figuring things out.
I'm not perfect. I don't know what will work best for you. A lot of this is documenting both my practice and what parts of what books helped me "get it". If this works for you, [please let me know](https://xeiaso.net/contact). If this doesn't work for you, [please let me know](https://xeiaso.net/contact). I will use this information for making direct improvements to these documents. I'm not perfect. I don't know what will work best for you. A lot of this is documenting both my practice and what parts of what books helped me "get it". If this works for you, [please let me know](https://christine.website/contact). If this doesn't work for you, [please let me know](https://christine.website/contact). I will use this information for making direct improvements to these documents.
As for your practice, twist the rules into circles and scrape out the parts that don't work if it helps you. Find out how to integrate it into your life in the best manner and go with it. As for your practice, twist the rules into circles and scrape out the parts that don't work if it helps you. Find out how to integrate it into your life in the best manner and go with it.

View File

@ -8,7 +8,7 @@ tags:
--- ---
This website has been a progressive web app [for a long This website has been a progressive web app [for a long
time](https://xeiaso.net/blog/progressive-webapp-conversion-2019-01-26). time](https://christine.website/blog/progressive-webapp-conversion-2019-01-26).
This means that you can install my blog to your phone as if it was a normal app This means that you can install my blog to your phone as if it was a normal app
via the share menu in Safari on iOS or via other native prompts on other via the share menu in Safari on iOS or via other native prompts on other
browsers. However, this is not enough. In the constant pursuit of advancement I browsers. However, this is not enough. In the constant pursuit of advancement I

View File

@ -1,64 +0,0 @@
---
title: How to Store an SSH Key on a Yubikey
date: 2022-05-27
series: howto
tags:
- yubikey
- security
---
SSH keys suck. They are a file on the disk and you can easily move it to other
machines instead of storing them in hardware where they can't be exfiltrated.
Using a password to encrypt the private key is a viable option, but the UX for
that is hot garbage. It's allegedly the future, so surely we MUST have some way
to make this all better, right?
<xeblog-conv name="Numa" mood="delet">\>implying there is a way to make anything
security related better</xeblog-conv>
Luckily, there is actually something we can do for this! As of [OpenSSH
8.2](https://www.openssh.com/releasenotes.html#8.2) (Feburary 14, 2020) you are
able to store an SSH private key on a yubikey! Here's how to do it.
<xeblog-conv name="Mara" mood="hacker">This should work on other FIDO keys like
Google's Titan, but we don't have access to one over here and as such haven't
tested it. Your mileage may vary. We are told that it works with the Google
Titan key that is handed out to Go contributors.</xeblog-conv>
First install `yubikey-manager` (see
[here](https://www.yubico.com/support/download/yubikey-manager/) for more
information, or run `nix-shell -p yubikey-manager` to run it without installing
it on NixOS), plug in your yubikey and run `ykman list`:
```console
$ ykman list
YubiKey 5C NFC (5.4.3) [OTP+FIDO+CCID] Serial: 4206942069
```
If you haven't set a PIN for the yubikey yet, follow
[this](https://docs.yubico.com/software/yubikey/tools/ykman/FIDO_Commands.html#ykman-fido-access-change-pin-options)
to set a PIN of your choice. Once you do this, you can generate a new SSH key
with the following command:
```
ssh-keygen -t ed25519-sk -O resident
```
<xeblog-conv name="Mara" mood="hacker">If that fails, try `ecdsa-sk`
instead! Some hardware keys may not support storing the key on the key
itself.</xeblog-conv>
Then enter in a super secret password (such as the Tongues you received as a kid
when you were forced into learning the bible against your will) twice and then
add that key to your agent with `ssh-add -K`. Then you can list your keys with
`ssh-add -L`:
```console
$ ssh-add -L
sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIKgGePSwpBuHUhrFCRLch9Usqi7L0fKtgTRnh6F/R+ruAAAABHNzaDo= cadey@shachi
```
Then you can copy this public key to GitHub or whatever and authenticate as
normal. The private key is stored on your yubikey directly and you can add it
with `ssh-add -K`. You can delete the ssh key stub at `~/.ssh/id_ed25519_sk` and
then your yubikey will be the only thing holding that key.

View File

@ -8,7 +8,7 @@ tags:
--- ---
As I mentioned As I mentioned
[before](https://xeiaso.net/blog/colemak-layout-2020-08-15), I ordered a [before](https://christine.website/blog/colemak-layout-2020-08-15), I ordered a
[ZSA Moonlander](https://zsa.io/moonlander) and it has finally arrived. I am [ZSA Moonlander](https://zsa.io/moonlander) and it has finally arrived. I am
writing this post from my Moonlander, and as such I may do a few more typos writing this post from my Moonlander, and as such I may do a few more typos
than normal, I'm still getting used to this. than normal, I'm still getting used to this.

View File

@ -22,7 +22,7 @@ no influence pushing me either way on this keyboard.
desk](https://cdn.christine.website/file/christine-static/img/keeb/Elm3dN8XUAAYHws.jpg) desk](https://cdn.christine.website/file/christine-static/img/keeb/Elm3dN8XUAAYHws.jpg)
[That 3d printed brain is built from the 3D model that was made as a part of <a [That 3d printed brain is built from the 3D model that was made as a part of <a
href="https://xeiaso.net/blog/brain-fmri-to-3d-model-2019-08-23">this href="https://christine.website/blog/brain-fmri-to-3d-model-2019-08-23">this
blogpost</a>.](conversation://Mara/hacker) blogpost</a>.](conversation://Mara/hacker)
## tl;dr ## tl;dr
@ -131,7 +131,7 @@ standard [Colemak](https://Colemak.com/) layout and it is currently the layer I
type the fastest on. I have the RGB configured so that it is mostly pink with type the fastest on. I have the RGB configured so that it is mostly pink with
the homerow using a lighter shade of pink. The color codes come from my logo the homerow using a lighter shade of pink. The color codes come from my logo
that you can see in the favicon [or here for a larger that you can see in the favicon [or here for a larger
version](https://xeiaso.net/static/img/avatar_large.png). version](https://christine.website/static/img/avatar_large.png).
I also have a qwerty layer for gaming. Most games expect qwerty keyboards and I also have a qwerty layer for gaming. Most games expect qwerty keyboards and
this is an excellent stopgap to avoid having to rebind every game that I want to this is an excellent stopgap to avoid having to rebind every game that I want to

View File

@ -1,11 +1,96 @@
let xesite = ./dhall/types/package.dhall let Person =
{ Type =
{ name : Text
, tags : List Text
, gitLink : Optional Text
, twitter : Optional Text
}
, default =
{ name = ""
, tags = [] : List Text
, gitLink = None Text
, twitter = None Text
}
}
let Config = xesite.Config let Author =
{ Type =
{ name : Text
, handle : Text
, picUrl : Optional Text
, link : Optional Text
, twitter : Optional Text
, default : Bool
, inSystem : Bool
}
, default =
{ name = ""
, handle = ""
, picUrl = None Text
, link = None Text
, twitter = None Text
, default = False
, inSystem = False
}
}
let defaultPort = env:PORT ? 3030
let defaultWebMentionEndpoint =
env:WEBMENTION_ENDPOINT
? "https://mi.within.website/api/webmention/accept"
let Config =
{ Type =
{ signalboost : List Person.Type
, authors : List Author.Type
, port : Natural
, clackSet : List Text
, resumeFname : Text
, webMentionEndpoint : Text
, miToken : Text
}
, default =
{ signalboost = [] : List Person.Type
, authors =
[ Author::{
, name = "Xe Iaso"
, handle = "xe"
, picUrl = Some "/static/img/avatar.png"
, link = Some "https://christine.website"
, twitter = Some "theprincessxena"
, default = True
, inSystem = True
}
, Author::{
, name = "Jessie"
, handle = "Heartmender"
, picUrl = Some
"https://cdn.christine.website/file/christine-static/img/UPRcp1pO_400x400.jpg"
, link = Some "https://heartmender.writeas.com"
, twitter = Some "BeJustFine"
, inSystem = True
}
, Author::{
, name = "Ashe"
, handle = "ectamorphic"
, picUrl = Some
"https://cdn.christine.website/file/christine-static/img/FFVV1InX0AkDX3f_cropped_smol.jpg"
, inSystem = True
}
, Author::{ name = "Nicole", handle = "Twi", inSystem = True }
, Author::{ name = "Mai", handle = "Mai", inSystem = True }
]
, port = defaultPort
, clackSet = [ "Ashlynn" ]
, resumeFname = "./static/resume/resume.md"
, webMentionEndpoint = defaultWebMentionEndpoint
, miToken = "${env:MI_TOKEN as Text ? ""}"
}
}
in Config::{ in Config::{
, signalboost = ./dhall/signalboost.dhall , signalboost = ./signalboost.dhall
, authors = ./dhall/authors.dhall
, clackSet = , clackSet =
[ "Ashlynn", "Terry Davis", "Dennis Ritchie", "Steven Hawking" ] [ "Ashlynn", "Terry Davis", "Dennis Ritchie", "Steven Hawking" ]
, jobHistory = ./dhall/jobHistory.dhall
} }

View File

@ -44,6 +44,17 @@ img {
padding-right: 1em; padding-right: 1em;
} }
/* xeblog-conv:not(:defined) { */
/* display: block; */
/* border-left: 0.25ch solid green; */
/* padding-left: 1.75ch; */
/* } */
/* xeblog-conv:before:not(:defined) { */
/* content: "<"attr(name)">"; */
/* font-weight: bold; */
/* } */
.warning { .warning {
background-color: #282828; background-color: #282828;
} }

View File

@ -48,6 +48,7 @@ in pkgs.stdenv.mkDerivation {
cp -rf $src/blog $out/blog cp -rf $src/blog $out/blog
cp -rf $src/css $out/css cp -rf $src/css $out/css
cp -rf $src/gallery $out/gallery cp -rf $src/gallery $out/gallery
cp -rf $src/signalboost.dhall $out/signalboost.dhall
cp -rf $src/static $out/static cp -rf $src/static $out/static
cp -rf $src/talks $out/talks cp -rf $src/talks $out/talks

View File

@ -1,30 +0,0 @@
let Author = ./types/Author.dhall
in [ Author::{
, name = "Xe Iaso"
, handle = "xe"
, picUrl = Some "/static/img/avatar.png"
, link = Some "https://christine.website"
, twitter = Some "theprincessxena"
, default = True
, inSystem = True
}
, Author::{
, name = "Jessie"
, handle = "Heartmender"
, picUrl = Some
"https://cdn.christine.website/file/christine-static/img/UPRcp1pO_400x400.jpg"
, twitter = Some "BeJustFine"
, inSystem = True
}
, Author::{
, name = "Ashe"
, handle = "ectamorphic"
, picUrl = Some
"https://cdn.christine.website/file/christine-static/img/FFVV1InX0AkDX3f_cropped_smol.jpg"
, inSystem = True
}
, Author::{ name = "Nicole", handle = "Twi", inSystem = True }
, Author::{ name = "Mai", handle = "Mai", inSystem = True }
, Author::{ name = "Sephira", handle = "Sephie", inSystem = True }
]

View File

@ -1,346 +0,0 @@
let xesite = ./types/package.dhall
let Job = xesite.Job
let Salary = xesite.Salary
let Stock = xesite.Stock
let StockKind = xesite.StockKind
let Company = xesite.Company
let Location = xesite.Location
let annual = \(rate : Natural) -> Salary::{ amount = rate }
let hourly = \(rate : Natural) -> Salary::{ amount = rate, per = "hour" }
let annualCAD = \(rate : Natural) -> Salary::{ amount = rate, currency = "CAD" }
let mercerIsland =
Location::{
, city = "Mercer Island"
, stateOrProvince = "WA"
, country = "USA"
}
let bellevue = mercerIsland // { city = "Bellevue" }
let mountainView =
Location::{
, city = "Mountain View"
, stateOrProvince = "CA"
, country = "USA"
, remote = False
}
let sf = mountainView // { city = "San Fransisco" }
let montreal =
Location::{
, city = "Montreal"
, stateOrProvince = "QC"
, country = "CAN"
, remote = False
}
let ottawa =
Location::{ city = "Ottawa", stateOrProvince = "ON", country = "CAN" }
let imvu =
Company::{
, name = "IMVU"
, url = Some "https://imvu.com"
, tagline =
"a company whose mission is to help people find and communicate with eachother. Their main product is a 3D avatar-based chat client and its surrounding infrastructure allowing creators to make content for the avatars to wear."
, location = mountainView // { city = "Redwood City" }
}
let tailscale =
Company::{
, name = "Tailscale"
, url = Some "https://tailscale.com"
, tagline =
"a zero config VPN for building secure networks. Install on any device in minutes. Remote access from any network or physical location."
, location = ottawa // { city = "Toronto" }
}
in [ Job::{
, company = Company::{
, name = "Symplicity"
, tagline =
"a company that provides students with the tools and connections they need to enhance their employability while preparing to succeed in today's job market."
, url = Some "https://www.symplicity.com"
, location = Location::{
, city = "Arlington"
, stateOrProvince = "VA"
, country = "USA"
, remote = False
}
}
, title = "Junior Systems Administrator"
, startDate = "2013-11-11"
, endDate = Some "2014-01-06"
, daysWorked = Some 56
, salary = annual 50000
, leaveReason = Some "terminated"
, locations =
[ Location::{
, city = "Arlington"
, stateOrProvince = "VA"
, country = "USA"
, remote = False
}
]
, highlights = [ "Python message queue processing" ]
, hideFromResume = True
}
, Job::{
, company = Company::{
, name = "OpDemand"
, defunct = True
, tagline =
"the company behind the open source project Deis, a distributed platform-as-a-service (PaaS) designed from the ground up to emulate Heroku but on privately owned servers."
, location = Location::{
, city = "Boulder"
, stateOrProvince = "CO"
, country = "USA"
}
}
, title = "Software Engineering Intern"
, startDate = "2014-07-14"
, endDate = Some "2014-08-27"
, daysWorked = Some 44
, daysBetween = Some 189
, salary = annual 35000
, leaveReason = Some "terminated"
, locations = [ mercerIsland ]
, highlights =
[ "Built new base image for Deis components"
, "Research and development on a new builder component"
]
, hideFromResume = True
}
, Job::{
, company = Company::{
, name = "Appen"
, url = Some "https://appen.com/"
, tagline =
"is a company that uses crowdsourcing to have its customers submit tasks to be done, similar to Amazon's Mechanical Turk."
, location = mountainView // { city = "San Francisco", remote = True }
}
, title = "Consultant"
, contract = True
, startDate = "2014-09-17"
, endDate = Some "2014-10-15"
, daysWorked = Some 28
, daysBetween = Some 21
, salary = hourly 90
, leaveReason = Some "contract not renewed"
, locations = [ mercerIsland ]
, highlights =
[ "Research and development on scalable Linux deployments on AWS via CoreOS and Docker"
, "Development of in-house tools to speed instance creation"
, "Laid groundwork on the creation and use of better tools for managing large clusters of CoreOS and Fleet machines"
]
}
, Job::{
, company = Company::{
, name = "VTCSecure"
, url = Some "https://www.vtcsecure.com/"
, tagline =
"a company dedicated to helping with custom and standard audio/video conferencing solutions. They specialize in helping the deaf and blind communicate over today's infrastructure without any trouble on their end."
, location = Location::{
, city = "Clearwater"
, stateOrProvince = "FL"
, country = "USA"
}
}
, title = "Consultant"
, contract = True
, startDate = "2014-10-27"
, endDate = Some "2015-02-09"
, daysWorked = Some 105
, daysBetween = Some 12
, salary = hourly 90
, leaveReason = Some "contract not renewed"
, locations = [ mercerIsland ]
, highlights =
[ "Started groundwork for a dynamically scalable infrastructure on a project for helping the blind see things"
, "Developed a prototype of a new website for VTCSecure"
, "Education on best practices using Docker and CoreOS"
, "Learning Freeswitch"
]
}
, Job::{
, company = imvu
, title = "Site Reliability Engineer"
, startDate = "2015-03-30"
, endDate = Some "2016-03-07"
, daysWorked = Some 343
, daysBetween = Some 49
, salary = annual 125000 // { stock = Some Stock::{ amount = 20000 } }
, leaveReason = Some "demoted"
, locations = [ mountainView ]
, highlights =
[ "Wrote up technical designs"
, "Implemented technical designs on an over 800 machine cluster"
, "Continuous learning of a lot of very powerful systems and improving upon them when it is needed"
]
}
, Job::{
, company = imvu
, title = "Systems Administrator"
, startDate = "2016-03-08"
, endDate = Some "2016-04-01"
, daysWorked = Some 24
, daysBetween = Some 1
, salary = annual 105000
, leaveReason = Some "quit"
, locations = [ mountainView // { city = "Redwood City" } ]
}
, Job::{
, company = Company::{
, name = "Pure Storage"
, url = Some "https://www.purestorage.com/"
, tagline =
"a Mountain View, California-based enterprise data flash storage company founded in 2009. It is traded on the NYSE (PSTG)."
, location = mountainView
}
, title = "Member of Technical Staff"
, startDate = "2016-04-04"
, endDate = Some "2016-08-03"
, daysWorked = Some 121
, daysBetween = Some 3
, salary =
annual 135000
// { stock = Some Stock::{
, amount = 5000
, liquid = True
, kind = StockKind.Grant
}
}
, leaveReason = Some "quit"
, locations = [ mountainView ]
, highlights = [ "Python 2 code maintenance", "Working with Foone" ]
}
, Job::{
, company = Company::{
, name = "Backplane.io"
, defunct = True
, location = sf
}
, title = "Software Engineer"
, startDate = "2016-08-24"
, endDate = Some "2016-11-22"
, daysWorked = Some 90
, daysBetween = Some 21
, salary = annual 105000 // { stock = Some Stock::{ amount = 85000 } }
, leaveReason = Some "terminated"
, locations = [ sf ]
, highlights =
[ "Performance monitoring of production servers"
, "Continuous deployment and development in Go"
, "Learning a lot about HTTP/2 and load balancing"
]
}
, Job::{
, company = Company::{
, name = "MBO Partners (Heroku)"
, tagline = "a staffing agency used to contract me for Heroku."
, location = Location::{
, city = "Herndon"
, stateOrProvince = "VA"
, country = "USA"
}
}
, title = "Consultant"
, contract = True
, startDate = "2017-02-13"
, endDate = Some "2017-11-13"
, daysWorked = Some 273
, daysBetween = Some 83
, salary = hourly 120
, leaveReason = Some "hired"
, locations = [ mountainView ]
}
, Job::{
, company = Company::{
, name = "Heroku"
, url = Some "https://heroku.com"
, tagline =
"a cloud Platform-as-a-Service (PaaS) that created the term 'platform as a service'. Heroku currently supports several programming languages that are commonly used on the web. Heroku, one of the first cloud platforms, has been in development since June 2007, when it supported only the Ruby programming language, but now supports Java, Node.js, Scala, Clojure, Python, PHP, and Go."
, location = sf
}
, title = "Senior Software Engineer"
, startDate = "2017-11-13"
, endDate = Some "2019-03-08"
, daysWorked = Some 480
, daysBetween = Some 0
, salary = annual 150000
, leaveReason = Some "quit"
, locations = [ mountainView, bellevue ]
, highlights =
[ "JVM Application Metrics"
, "Go Runtime Metrics Agent"
, "Other backend fixes and improvements on Threshold Autoscaling and Threshold Alerting"
, "Public-facing blogpost writing"
]
}
, Job::{
, company = Company::{
, name = "Lightspeed POS"
, url = Some "https://lightspeedhq.com"
, tagline =
"a provider of retail, ecommerce and point-of-sale solutions for small and medium scale businesses."
, location = montreal
}
, title = "Expert principal en fiabilité du site"
, startDate = "2019-05-06"
, endDate = Some "2020-11-27"
, daysWorked = Some 540
, daysBetween = Some 48
, salary =
annualCAD 115000
// { stock = Some Stock::{ amount = 7500, liquid = True } }
, leaveReason = Some "quit"
, locations = [ montreal ]
, highlights =
[ "Migration from cloud to cloud"
, "Work on the cloud platform initiative"
, "Crafting reliable infrastructure for clients of customers"
, "Creation of an internally consistent and extensible command line interface for internal tooling"
]
}
, Job::{
, company = tailscale
, title = "Software Designer"
, startDate = "2020-12-14"
, endDate = Some "2022-03-01"
, daysWorked = Some 442
, daysBetween = Some 0
, salary = annualCAD 135000
, leaveReason = Some "raise"
, locations = [ montreal // { remote = True }, ottawa ]
, highlights =
[ "Go programming"
, "SQL integrations"
, "Public-facing content writing"
, "Customer support"
]
}
, Job::{
, company = tailscale
, title = "Archmage of Infrastructure"
, startDate = "2022-03-01"
, salary = annualCAD 147150
, locations = [ ottawa ]
, highlights =
[ "The first developer relations person at Tailscale"
, "Public-facing content writing"
, "Public speaking"
, "Developing custom integration solutions and supporting them"
]
}
]

View File

@ -1,38 +0,0 @@
let xesite = ./types/package.dhall
let Resume = xesite.Resume
let Link = xesite.Link
in Resume::{
, hnLinks =
[ Link::{
, url = "https://news.ycombinator.com/item?id=29522941"
, title = "'Open Source' is Broken"
}
, Link::{
, url = "https://news.ycombinator.com/item?id=29167560"
, title = "The Surreal Horror of PAM"
}
, Link::{
, url = "https://news.ycombinator.com/item?id=27175960"
, title = "Systemd: The Good Parts"
}
, Link::{
, url = "https://news.ycombinator.com/item?id=26845355"
, title = "I Implemented /dev/printerfact in Rust"
}
, Link::{
, url = "https://news.ycombinator.com/item?id=25978511"
, title = "A Model for Identity in Software"
}
, Link::{
, url = "https://news.ycombinator.com/item?id=31390506"
, title = "Fly.io: The reclaimer of Heroku's magic"
}
, Link::{
, url = "https://news.ycombinator.com/item?id=31149801"
, title = "Crimes with Go Generics"
}
]
}

View File

@ -1,19 +0,0 @@
{ Type =
{ name : Text
, handle : Text
, picUrl : Optional Text
, link : Optional Text
, twitter : Optional Text
, default : Bool
, inSystem : Bool
}
, default =
{ name = ""
, handle = ""
, picUrl = None Text
, link = None Text
, twitter = None Text
, default = False
, inSystem = False
}
}

View File

@ -1,17 +0,0 @@
let Location = ./Location.dhall
in { Type =
{ name : Text
, url : Optional Text
, tagline : Text
, location : Location.Type
, defunct : Bool
}
, default =
{ name = ""
, url = None Text
, tagline = ""
, location = Location::{=}
, defunct = False
}
}

View File

@ -1,33 +0,0 @@
let Person = ./Person.dhall
let Author = ./Author.dhall
let Job = ./Job.dhall
let defaultPort = env:PORT ? 3030
let defaultWebMentionEndpoint =
env:WEBMENTION_ENDPOINT
? "https://mi.within.website/api/webmention/accept"
in { Type =
{ signalboost : List Person.Type
, authors : List Author.Type
, port : Natural
, clackSet : List Text
, resumeFname : Text
, webMentionEndpoint : Text
, miToken : Text
, jobHistory : List Job.Type
}
, default =
{ signalboost = [] : List Person.Type
, authors = [] : List Author.Type
, port = defaultPort
, clackSet = [ "Ashlynn" ]
, resumeFname = "./static/resume/resume.md"
, webMentionEndpoint = defaultWebMentionEndpoint
, miToken = "${env:MI_TOKEN as Text ? ""}"
, jobHistory = [] : List Job.Type
}
}

View File

@ -1,35 +0,0 @@
let Company = ./Company.dhall
let Salary = ./Salary.dhall
let Location = ./Location.dhall
in { Type =
{ company : Company.Type
, title : Text
, contract : Bool
, startDate : Text
, endDate : Optional Text
, daysWorked : Optional Natural
, daysBetween : Optional Natural
, salary : Salary.Type
, leaveReason : Optional Text
, locations : List Location.Type
, highlights : List Text
, hideFromResume : Bool
}
, default =
{ company = Company::{=}
, title = "Unknown"
, contract = False
, startDate = "0000-01-01"
, endDate = None Text
, daysWorked = None Natural
, daysBetween = None Natural
, salary = Salary::{=}
, leaveReason = None Text
, locations = [] : List Location.Type
, highlights = [] : List Text
, hideFromResume = False
}
}

View File

@ -1 +0,0 @@
{ Type = { url : Text, title : Text }, default = { url = "", title = "" } }

View File

@ -1,3 +0,0 @@
{ Type = { city : Text, stateOrProvince : Text, country : Text, remote : Bool }
, default = { remote = True, city = "", stateOrProvince = "", country = "CAN" }
}

View File

@ -1,9 +0,0 @@
{ Type =
{ name : Text
, tags : List Text
, gitLink : Optional Text
, twitter : Optional Text
}
, default =
{ name = "", tags = [] : List Text, gitLink = None Text, twitter = None Text }
}

View File

@ -1,21 +0,0 @@
let Location = ./Location.dhall
let Link = ./Link.dhall
in { Type =
{ name : Text
, tagline : Text
, location : Location.Type
, hnLinks : List Link.Type
}
, default =
{ name = "Xe Iaso"
, tagline = "Archmage of Infrastructure"
, location = Location::{
, city = "Ottawa"
, stateOrProvince = "ON"
, country = "CAN"
}
, hnLinks = [] : List Link.Type
}
}

View File

@ -1,11 +0,0 @@
let Stock = ./Stock.dhall
in { Type =
{ amount : Natural
, currency : Text
, per : Text
, stock : Optional Stock.Type
}
, default =
{ amount = 0, currency = "USD", per = "year", stock = None Stock.Type }
}

View File

@ -1,17 +0,0 @@
let StockKind = ./StockKind.dhall
in { Type =
{ kind : StockKind
, amount : Natural
, liquid : Bool
, vestingYears : Natural
, cliffYears : Natural
}
, default =
{ kind = StockKind.Options
, amount = 0
, liquid = False
, vestingYears = 4
, cliffYears = 1
}
}

View File

@ -1 +0,0 @@
< Grant | Options >

View File

@ -1,12 +0,0 @@
{ Author = ./Author.dhall
, Company = ./Company.dhall
, Config = ./Config.dhall
, Job = ./Job.dhall
, Link = ./Link.dhall
, Location = ./Location.dhall
, Person = ./Person.dhall
, Resume = ./Resume.dhall
, Salary = ./Salary.dhall
, Stock = ./Stock.dhall
, StockKind = ./StockKind.dhall
}

View File

@ -1,27 +0,0 @@
# JSON Feed Extensions
Here is the documentation of all of my JSON Feed extensions. I have created
these JSON Feed extensions in order to give users more metadata about my
articles and talks.
## `_xesite_frontmatter`
This extension is added to [JSON Feed
Items](https://www.jsonfeed.org/version/1.1/#items-a-name-items-a) and gives
readers a copy of the frontmatter data that I annotate my posts with. The
contents of this will vary by post, but will have any of the following fields:
* `about` (required, string) is a link to this documentation. It gives readers
of the JSON Feed information about what this extension does. This is for
informational purposes only and can safely be ignored by programs.
* `series` (optional, string) is the optional blogpost series name that this
item belongs to. When I post multiple posts about the same topic, I will
usually set the `series` to the same value so that it is more discoverable [on
my series index page](https://xeiaso.net/blog/series).
* `slides_link` (optional, string) is a link to the PDF containing the slides
for a given talk. This is always set on talks, but is technically optional
because not everything I do is a talk.
* `vod` (optional, string) is an object that describes where you can watch the
Video On Demand (vod) for the writing process of a post. This is an object
that always contains the fields `twitch` and `youtube`. These will be URLs to
the videos so that you can watch them on demand.

Some files were not shown because too many files have changed in this diff Show More