forked from cadey/xesite
Compare commits
21 Commits
nix-flakes
...
main
Author | SHA1 | Date |
---|---|---|
Cadey Ratio | f590fc71d1 | |
Cadey Ratio | 2e539512b7 | |
Cadey Ratio | f51752ed3c | |
dependabot[bot] | e825b1b904 | |
Cadey Ratio | 20aeb35890 | |
Cadey Ratio | 449ddabce1 | |
Cadey Ratio | 6b771b5503 | |
Cadey Ratio | ea8e1e045a | |
Cadey Ratio | 3a4827c887 | |
Cadey Ratio | fd6ac469a6 | |
Cadey Ratio | fa2ada9747 | |
Cadey Ratio | 3a5c7adc42 | |
Cadey Ratio | e5ee825c0a | |
Cadey Ratio | e665412345 | |
kjain | 828a5f277e | |
Cadey Ratio | 7c90296bf0 | |
Cadey Ratio | 1c8c3396a7 | |
Cadey Ratio | 0c0c5875e6 | |
Cadey Ratio | 7fdae76543 | |
Cadey Ratio | 1bedcb6a25 | |
Martin Schwaighofer | 66574582f2 |
|
@ -6,3 +6,5 @@ cw.tar
|
||||||
/result
|
/result
|
||||||
.#*
|
.#*
|
||||||
/target
|
/target
|
||||||
|
.patreon.json
|
||||||
|
.direnv
|
||||||
|
|
|
@ -130,9 +130,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum"
|
name = "axum"
|
||||||
version = "0.4.8"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c9f346c92c1e9a71d14fe4aaf7c2a5d9932cc4e5e48d8fb6641524416eb79ddd"
|
checksum = "5611d4977882c5af1c0f7a34d51b5d87f784f86912bb543986b014ea4995ef93"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum-core",
|
"axum-core",
|
||||||
|
@ -142,6 +142,7 @@ dependencies = [
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
"itoa",
|
||||||
"matchit",
|
"matchit",
|
||||||
"memchr",
|
"memchr",
|
||||||
"mime",
|
"mime",
|
||||||
|
@ -160,9 +161,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-core"
|
name = "axum-core"
|
||||||
version = "0.1.2"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6dbcda393bef9c87572779cb8ef916f12d77750b27535dd6819fa86591627a51"
|
checksum = "95cd109b3e93c9541dcce5b0219dcf89169dcc58c1bebed65082808324258afb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -174,9 +175,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-extra"
|
name = "axum-extra"
|
||||||
version = "0.1.5"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5b6d79bc9c2975821d39c7df31ea766026beb9efe28c076a48cfd7d50f34f18"
|
checksum = "ff3819ded1be91d7ee2cd9f0466aa345cc70ba0b0035ed47e3eac6427f83b81a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -191,9 +192,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-macros"
|
name = "axum-macros"
|
||||||
version = "0.1.2"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c5b2a9133b2658e684c8ea04157a8bd48dac7906a2eb884ffebfb051af123394"
|
checksum = "63bcb0d395bc5dd286e61aada9fc48201eb70e232f006f9d6c330c9db2f256f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
@ -1190,9 +1191,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchit"
|
name = "matchit"
|
||||||
version = "0.4.6"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9376a4f0340565ad675d11fc1419227faf5f60cd7ac9cb2e7185a471f30af833"
|
checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "md5"
|
name = "md5"
|
||||||
|
@ -1514,6 +1515,7 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-futures",
|
"tracing-futures",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2467,9 +2469,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-subscriber"
|
name = "tracing-subscriber"
|
||||||
version = "0.3.9"
|
version = "0.3.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce"
|
checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
"ansi_term",
|
||||||
"sharded-slab",
|
"sharded-slab",
|
||||||
|
|
|
@ -9,9 +9,9 @@ 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 = "0.4"
|
axum = "0.5"
|
||||||
axum-macros = "0.1"
|
axum-macros = "0.2"
|
||||||
axum-extra = "0.1"
|
axum-extra = "0.2"
|
||||||
color-eyre = "0.6"
|
color-eyre = "0.6"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
comrak = "0.12.1"
|
comrak = "0.12.1"
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
---
|
||||||
|
title: Compiling Code to Matter in My Living Room
|
||||||
|
date: 2022-03-28
|
||||||
|
tags:
|
||||||
|
- openscad
|
||||||
|
- 3dprinting
|
||||||
|
---
|
||||||
|
|
||||||
|
In a moment of weakness, my husband and I got a 3d printer. It's mostly been sitting around and not doing much since we got it, but recently I found a great use for it: I wanted a controller stand for my Valve Index controllers and VR full body trackers.
|
||||||
|
|
||||||
|
After doing some digging on Thingiverse, I found [this stand](https://www.thingiverse.com/thing:4587097) that looked like it had promise. So I downloaded the model, sliced it and then sent it over to Kyubey:
|
||||||
|
|
||||||
|
<blockquote class="twitter-tweet"><p lang="tl" dir="ltr">Kyuubey is happy <a href="https://t.co/atTLN8MSgc">pic.twitter.com/atTLN8MSgc</a></p>— Xe Iaso (@theprincessxena) <a href="https://twitter.com/theprincessxena/status/1507485129907871747?ref_src=twsrc%5Etfw">March 25, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
||||||
|
|
||||||
|
[Kyubey's name is a reference to <a href="https://madoka.fandom.com/wiki/Kyubey">Kyubey</a> from Puella Magi Madoka Magika</a>.](conversation://Mara/hacker)
|
||||||
|
|
||||||
|
Once it was done I ended up with a stand that I could feed [these cables I got from Amazon](https://www.amazon.ca/gp/product/B09LSF8XL9/) through. The tracker holes worked great, but the controller holes were just barely too small.
|
||||||
|
|
||||||
|
This was kinda frustrating and I almost gave up on the project, but then I remembered that [OpenSCAD](https://openscad.org) existed. OpenSCAD is a weird programming environment / 3D modeling hybrid program that I've seen used on Thingiverse. It works by letting you position platonic solids into a 3d environment, and from there you can create anything you want.
|
||||||
|
|
||||||
|
One of the primitives that OpenSCAD offers is a cylinder. So I wondered if I could use one of those to widen the hole in the index stand and then reprint the part with the wider hole.
|
||||||
|
|
||||||
|
[Wait, you're using a CAD program to fix your 3D print by modifying the model instead of using, I don't know, a drill and 5 minutes to make it fit that way?](conversation://Numa/dismay)
|
||||||
|
|
||||||
|
[There's no doing like overdoing!](conversation://Cadey/enby)
|
||||||
|
|
||||||
|
After some finangling, I managed to get the cylinders in the right place with this OpenSCAD code:
|
||||||
|
|
||||||
|
```scad
|
||||||
|
//difference() {
|
||||||
|
color("magenta") translate([0, 0, 0]) import("./assets/ValveTrackerDeckEditedByInugoro.stl");
|
||||||
|
// bores for controller holders
|
||||||
|
color([0, 1, 0]) translate([63, 44, 0]) cylinder(h = 55, r = 4.75);
|
||||||
|
color([0, 1, 0]) translate([-63, 44, 0]) cylinder(h = 55, r = 4.75);
|
||||||
|
//}
|
||||||
|
```
|
||||||
|
|
||||||
|
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Some finagling required <a href="https://t.co/7T0R6x1XoP">pic.twitter.com/7T0R6x1XoP</a></p>— Xe Iaso (@theprincessxena) <a href="https://twitter.com/theprincessxena/status/1508566854926745614?ref_src=twsrc%5Etfw">March 28, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
||||||
|
|
||||||
|
And when I uncommented out the `difference()` block, it ends up looking good enough:
|
||||||
|
|
||||||
|
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="und" dir="ltr"><a href="https://t.co/fiShvlN8QH">pic.twitter.com/fiShvlN8QH</a></p>— Xe Iaso (@theprincessxena) <a href="https://twitter.com/theprincessxena/status/1508567556759728141?ref_src=twsrc%5Etfw">March 28, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
||||||
|
|
||||||
|
So then I took a good solid look at the rest of the 3D printed part to see if I could improve on anything else before I sent it to another round of the printer. The last stand took _14 hours_ to print and used a lot of material. I want to avoid waste.
|
||||||
|
|
||||||
|
Something I noticed is that the front of the print where all the cables come out was a bit too thin. All 5 of the cables wouldn't fit in there (my braided cables must have been thicker than the ones that the original modeler used). So again I grabbed a few platonic solids and managed to make it work out:
|
||||||
|
|
||||||
|
```scad
|
||||||
|
// widen the paths
|
||||||
|
color("green") translate([0, -16, 1.3]) rotate([0, 0, 90]) cube([10, 57, 7.8], center = true);
|
||||||
|
color("green") translate([0, 0, 1.7]) rotate([0, 0, 0]) cube([25, 30, 7], center = true);
|
||||||
|
```
|
||||||
|
|
||||||
|
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="und" dir="ltr"><a href="https://t.co/pKAVtiPfDS">pic.twitter.com/pKAVtiPfDS</a></p>— Xe Iaso (@theprincessxena) <a href="https://twitter.com/theprincessxena/status/1508568858650685440?ref_src=twsrc%5Etfw">March 28, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
||||||
|
|
||||||
|
Then I wanted to add some wedges into the underside of the part to help me get the print off the bed. Most people have a problem with bed adhesion being too little. I have too much bed adhesion. So I added some angled rectangles:
|
||||||
|
|
||||||
|
```scad
|
||||||
|
// wedges to help get the print off the bed
|
||||||
|
color([1, 1, 0]) translate([-120, 0, 0]) rotate([15, 0, 90]) cube([10, 11, 2], center = true); // right
|
||||||
|
color([1, 1, 0]) translate([120, 0, 0]) rotate([-15, 0, 90]) cube([10, 11, 2], center = true); // left
|
||||||
|
color([1, 1, 0]) translate([0, -85, 0]) rotate([0, 15, 90]) cube([10, 11, 2], center = true); // back
|
||||||
|
color([1, 1, 0]) translate([60, 56, 1]) rotate([0, -15, 90]) cube([10, 11, 2], center = true); // front left
|
||||||
|
color([1, 1, 0]) translate([-60, 56, 1]) rotate([0, -15, 90]) cube([10, 11, 2], center = true); // front right
|
||||||
|
color([1, 1, 0]) translate([32.5, 41, 1]) rotate([0, -15, 130]) cube([10, 11, 2], center = true); // front left inner
|
||||||
|
color([1, 1, 0]) translate([-32.5, 41, 1]) rotate([0, -15, 60]) cube([10, 11, 2], center = true); // front right inner
|
||||||
|
```
|
||||||
|
|
||||||
|
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="und" dir="ltr"><a href="https://t.co/XUQ9ZeYk1H">pic.twitter.com/XUQ9ZeYk1H</a></p>— Xe Iaso (@theprincessxena) <a href="https://twitter.com/theprincessxena/status/1508569796253827077?ref_src=twsrc%5Etfw">March 28, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
||||||
|
|
||||||
|
And then once I spun it around for a bit and thought it was good, I sliced it in PrusaSlicer and sent it off to Kyubey. It was going to take 14 hours, so I went off to do other things, ate dinner and then went to bed while the printer continued.
|
||||||
|
|
||||||
|
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="fr" dir="ltr">Diligent bean <a href="https://t.co/yPgnJA0ZdW">pic.twitter.com/yPgnJA0ZdW</a></p>— Xe Iaso (@theprincessxena) <a href="https://twitter.com/theprincessxena/status/1508397506031460352?ref_src=twsrc%5Etfw">March 28, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
||||||
|
|
||||||
|
Then when I woke up, Kyubey was done:
|
||||||
|
|
||||||
|
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="und" dir="ltr"><a href="https://t.co/2E1IS810EH">pic.twitter.com/2E1IS810EH</a></p>— Xe Iaso (@theprincessxena) <a href="https://twitter.com/theprincessxena/status/1508407046995156992?ref_src=twsrc%5Etfw">March 28, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
||||||
|
|
||||||
|
I was excited and chiseled the print off the bed (the wedges helped a little, but it ended up making the print look kinda weird so I don't know if I will do that again), but the hole for the middle tracker didn't fit perfectly. Everything else did though.
|
||||||
|
|
||||||
|
[If you want to get prints off your printer easier, see this video for the method we're starting to use: <br /><br /><iframe width="560" height="315" src="https://www.youtube.com/embed/VCCbzCvtRzU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>](conversation://Mara/hacker)
|
||||||
|
|
||||||
|
I looked on my desk and found that a random pen that I had sitting around for months was about the right size, so I pushed it into and out of the hole a few times and then the cables fit perfectly. I assume some plastic was in a weird state or something.
|
||||||
|
|
||||||
|
Then I set everything up and I had my Index controller stand:
|
||||||
|
|
||||||
|
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">Victory! <a href="https://t.co/A3aCtQMQt5">pic.twitter.com/A3aCtQMQt5</a></p>— Xe Iaso (@theprincessxena) <a href="https://twitter.com/theprincessxena/status/1508426229464064001?ref_src=twsrc%5Etfw">March 28, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
||||||
|
|
||||||
|
[I really need to get a table or something for this.](conversation://Cadey/facepalm)
|
||||||
|
|
||||||
|
I've uploaded my modified version to [Thingiverse](https://www.thingiverse.com/thing:5332988). If you want to see the OpenSCAD code, you can check it out on GitHub [here](https://github.com/Xe/3dstuff/blob/main/index_stand_hack.scad). I'm really liking OpenSCAD so far. It's very weird but it lets you do whatever you want by chaining together basic shapes to build up to what you want. I imagine I will be using it a lot in the future, especially once my husband's new sim racing gear comes in.
|
||||||
|
|
||||||
|
Having a 3D printer around is like having a very weird superpower on standby. You can compile matter in your living room, but you need a very pedantic description of what that should look like. You also can have any material you like as long as it's plastic. However when it's useful, it's a lifesaver. You can make something to fit a gap or mend something broken or even add functionality to something that lacked it. The cloud's the limit!
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: "</kubernetes>"
|
title: "Goodbye Kubernetes"
|
||||||
date: 2021-01-03
|
date: 2021-01-03
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,10 @@ Then you can look at `flake.nix` to see what's up:
|
||||||
pkgs = nixpkgsFor.${system};
|
pkgs = nixpkgsFor.${system};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
go-hello = pkgs.buildGoModule {
|
# The default package for 'nix build'. This makes sense if the
|
||||||
|
# flake provides only one package or there is a clear "main"
|
||||||
|
# package.
|
||||||
|
default = pkgs.buildGoModule {
|
||||||
pname = "go-hello";
|
pname = "go-hello";
|
||||||
inherit version;
|
inherit version;
|
||||||
# In 'nix develop', we don't need a copy of the source tree
|
# In 'nix develop', we don't need a copy of the source tree
|
||||||
|
@ -156,11 +159,6 @@ Then you can look at `flake.nix` to see what's up:
|
||||||
vendorSha256 = "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=";
|
vendorSha256 = "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=";
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
# The default package for 'nix build'. This makes sense if the
|
|
||||||
# flake provides only one package or there is a clear "main"
|
|
||||||
# package.
|
|
||||||
defaultPackage = forAllSystems (system: self.packages.${system}.go-hello);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -197,7 +195,6 @@ Let's take a closer look at the higher level things in the flake:
|
||||||
|
|
||||||
outputs = { self, nixpkgs }: {
|
outputs = { self, nixpkgs }: {
|
||||||
packages = { ... };
|
packages = { ... };
|
||||||
defaultPackage = { ... };
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -418,7 +415,7 @@ world. To use a private repo, your flake input URL should look something like
|
||||||
this:
|
this:
|
||||||
|
|
||||||
```
|
```
|
||||||
ssh+git://git@github.com:user/repo
|
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
|
||||||
|
@ -462,7 +459,7 @@ nixosModules.bot = { config, lib, ... }: {
|
||||||
Group = "mara-bot";
|
Group = "mara-bot";
|
||||||
Restart = "always";
|
Restart = "always";
|
||||||
WorkingDirectory = "/var/lib/mara-bot";
|
WorkingDirectory = "/var/lib/mara-bot";
|
||||||
ExecStart = "${self.defaultPackage."${system}"}/bin/mara";
|
ExecStart = "${self.packages."${system}".default}/bin/mara";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,6 +19,10 @@ chance of bitrotting. I will make every attempt to update it if things change,
|
||||||
however flakes have been fairly consistent for a few years
|
however flakes have been fairly consistent for a few years
|
||||||
now.](conversation://Cadey/coffee)
|
now.](conversation://Cadey/coffee)
|
||||||
|
|
||||||
|
[EDIT(20220327 14:13): A previous version of this article said to use
|
||||||
|
`defaultPackage` for the default package. This is deprecated and you should use
|
||||||
|
`packages.default` instead.](conversation://Cadey/coffee)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
[What is a package? I've seen this term thrown around with phrases like "Nix is a
|
[What is a package? I've seen this term thrown around with phrases like "Nix is a
|
||||||
|
@ -180,10 +184,10 @@ web server template by defining another package:
|
||||||
```nix
|
```nix
|
||||||
# flake.nix
|
# flake.nix
|
||||||
|
|
||||||
# after defaultPackage
|
|
||||||
packages = {
|
packages = {
|
||||||
|
default = ...;
|
||||||
docker = let
|
docker = let
|
||||||
web = self.defaultPackage.${system};
|
web = self.packages.${system}.default;
|
||||||
in pkgs.dockerTools.buildLayeredImage {
|
in pkgs.dockerTools.buildLayeredImage {
|
||||||
name = web.pname;
|
name = web.pname;
|
||||||
tag = web.version;
|
tag = web.version;
|
||||||
|
@ -373,7 +377,7 @@ the systemd unit:
|
||||||
web-service = pkgs.substituteAll {
|
web-service = pkgs.substituteAll {
|
||||||
name = "web-server.service";
|
name = "web-server.service";
|
||||||
src = ./systemd/web-server.service.in;
|
src = ./systemd/web-server.service.in;
|
||||||
web = self.defaultPackage.${system};
|
web = self.packages.${system}.default;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -399,7 +403,7 @@ Then you can add the bit that builds the portable service:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
portable = let
|
portable = let
|
||||||
web = self.defaultPackage.${system};
|
web = self.packages.${system}.default;
|
||||||
in pkgs.portableService {
|
in pkgs.portableService {
|
||||||
inherit (web) version;
|
inherit (web) version;
|
||||||
name = web.pname;
|
name = web.pname;
|
||||||
|
|
|
@ -0,0 +1,443 @@
|
||||||
|
---
|
||||||
|
title: "Nix Flakes: Exposing and using NixOS Modules"
|
||||||
|
date: 2022-04-07
|
||||||
|
series: nix-flakes
|
||||||
|
tags:
|
||||||
|
- nixos
|
||||||
|
vod:
|
||||||
|
twitch: https://www.twitch.tv/videos/1437346416
|
||||||
|
youtube: https://youtu.be/wCZ9SwmgSck
|
||||||
|
---
|
||||||
|
|
||||||
|
Nix flakes allow you to expose NixOS modules. NixOS modules are templates for
|
||||||
|
system configuration and they are the basis of how you configure NixOS. Today
|
||||||
|
we're going to take our Nix flake [from the last
|
||||||
|
article](/blog/nix-flakes-2-2022-02-27) and write a NixOS module for it so that
|
||||||
|
we can deploy it to a container running locally. In the next post we will deploy
|
||||||
|
this to a server.
|
||||||
|
|
||||||
|
[If you haven't read <a href="/blog/series/nix-flakes">the other articles in
|
||||||
|
this series</a>, you probably should. This article builds upon the previous
|
||||||
|
ones.](conversation://Mara/hacker)
|
||||||
|
|
||||||
|
NixOS modules are building blocks that let you configure NixOS servers. Modules
|
||||||
|
expose customizable options that expand out into system configuration.
|
||||||
|
Individually, each module is fairly standalone and self-contained, but they
|
||||||
|
build up together into your server configuration like a bunch of legos build
|
||||||
|
into a house. Each module describes a subset of your desired system
|
||||||
|
configuration and any options relevant to that configuration.
|
||||||
|
|
||||||
|
[You can think about them like Ansible playbooks, but NixOS modules describe the
|
||||||
|
desired end state instead of the steps you need to get to that end
|
||||||
|
state. It's the end result of evaluating all of your options against all of the
|
||||||
|
modules that you use in your configuration.](conversation://Mara/hacker)
|
||||||
|
|
||||||
|
NixOS modules are functions that take in the current state of the system and
|
||||||
|
then return things to add to the state of the system. Here is a basic NixOS
|
||||||
|
module that enables [nginx](https://nginx.org/):
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
services.nginx.enable = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This function takes in the state of the world and returns additions to the state
|
||||||
|
of the world. This will use the nginx module that ships with NixOS to give you a
|
||||||
|
basic nginx setup that has the upstream default configuration in it.
|
||||||
|
|
||||||
|
NixOS has a way to run other instances of NixOS with [NixOS
|
||||||
|
containers](https://nixos.org/manual/nixos/stable/index.html#ch-containers). We
|
||||||
|
can use them to test our NixOS module as we write it.
|
||||||
|
|
||||||
|
[This probably won't work on a non-NixOS machine. You will need to
|
||||||
|
install NixOS in order to test this. For an easy way to do this, see <a
|
||||||
|
href="https://github.com/elitak/nixos-infect">nixos-infect</a>, a script you can
|
||||||
|
put into a cloudconfig when spinning up a new server. You can also <a
|
||||||
|
href="https://nixos.org/manual/nixos/stable/index.html#sec-installation">install
|
||||||
|
NixOS manually</a> in a VM, but for now it may be better to use a cloud server
|
||||||
|
as the path of least resistance. Installing NixOS with a flake will be a part of
|
||||||
|
a future article in this series.](conversation://Mara/hacker)
|
||||||
|
|
||||||
|
In Nix you can merge two attribute sets using the `//` operator. This allows you
|
||||||
|
to add two attribute sets into one larger one, such as like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
nix-repl> { foo = 1; } // { bar = 2; }
|
||||||
|
{ bar = 2; foo = 1; }
|
||||||
|
```
|
||||||
|
|
||||||
|
<xeblog-conv name="Mara" mood="hacker">
|
||||||
|
Important pro tip: the merge operator is NOT recursive. If you try to do
|
||||||
|
something like:
|
||||||
|
|
||||||
|
```
|
||||||
|
nix-repl> foo = { bar = { baz = "foo"; }; }
|
||||||
|
nix-repl> (foo // { bar = { spam = "eggs"; }; }).bar
|
||||||
|
```
|
||||||
|
|
||||||
|
You will get:
|
||||||
|
|
||||||
|
```
|
||||||
|
{ spam = "eggs"; }
|
||||||
|
```
|
||||||
|
|
||||||
|
And not:
|
||||||
|
|
||||||
|
```
|
||||||
|
{ baz = "foo"; spam = "eggs"; }
|
||||||
|
```
|
||||||
|
|
||||||
|
This is because the `//` operator prefers things in the right hand side over the
|
||||||
|
left hand side if both conflict. To recursively merge two attribute sets (using
|
||||||
|
all elements from both sides), use
|
||||||
|
[lib.recursiveUpdate](https://nixos.org/manual/nixpkgs/stable/#function-library-lib.attrsets.recursiveUpdate):
|
||||||
|
|
||||||
|
```
|
||||||
|
nix-repl> (pkgs.lib.recursiveUpdate foo bar).bar
|
||||||
|
{ baz = "foo"; spam = "eggs"; }
|
||||||
|
```
|
||||||
|
|
||||||
|
</xeblog-conv>
|
||||||
|
|
||||||
|
We will use this to add the container configuration to the flake at the end of
|
||||||
|
the flake.nix file. We need to do this because the upper part of the flake with
|
||||||
|
the `forAllSystems` call will generate a bunch of system-specific attributes for
|
||||||
|
each system we support. NixOS configurations don't support this level of
|
||||||
|
granularity.
|
||||||
|
|
||||||
|
At the end of your flake.nix (just before the final closing `}`), there should
|
||||||
|
be a line that looks like this:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
This is what terminates the `outputs` declaration from all the way at the top.
|
||||||
|
In order to add the container configuration, you should change this to look like
|
||||||
|
this:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
}) // {
|
||||||
|
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Then we can add the container configuration to the flake:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
}) // {
|
||||||
|
nixosConfigurations.container = nixpkgs.lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
modules = [
|
||||||
|
({pkgs, ...}: {
|
||||||
|
# Only allow this to boot as a container
|
||||||
|
boot.isContainer = true;
|
||||||
|
networking.hostName = "gohello";
|
||||||
|
|
||||||
|
# Allow nginx through the firewall
|
||||||
|
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||||
|
|
||||||
|
services.nginx.enable = true;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create a container (with the hostname "gohello") that starts nginx and
|
||||||
|
allows traffic to go to nginx on TCP port 80. You can start up the container
|
||||||
|
with the `nixos-container` command:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ sudo nixos-container create gohello --flake .#container
|
||||||
|
host IP is 10.233.1.1, container IP is 10.233.1.2
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can start the container with this command:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ sudo nixos-container start gohello
|
||||||
|
```
|
||||||
|
|
||||||
|
And then we can try to connect to nginx to see if it's working:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ curl http://10.233.1.2
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Welcome to nginx!</title>
|
||||||
|
<style>
|
||||||
|
body {}
|
||||||
|
width: 35em;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-family: Tahoma, Verdana, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Welcome to nginx!</h1>
|
||||||
|
<p>If you see this page, the nginx web server is successfully installed and
|
||||||
|
working. Further configuration is required.</p>
|
||||||
|
|
||||||
|
<p>For online documentation and support please refer to
|
||||||
|
<a href="http://nginx.org/">nginx.org</a>.<br/>
|
||||||
|
Commercial support is available at
|
||||||
|
<a href="http://nginx.com/">nginx.com</a>.</p>
|
||||||
|
|
||||||
|
<p><em>Thank you for using nginx.</em></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
We have nginx!
|
||||||
|
|
||||||
|
Now that we have our container to test with, let's write the configuration for
|
||||||
|
the service. At a basic level we need the following things:
|
||||||
|
|
||||||
|
- A systemd unit for orchestrating the HTTP server process
|
||||||
|
- nginx configuration to reverse proxy to that HTTP server
|
||||||
|
|
||||||
|
Above the container definition, add this basic NixOS module template:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nixosModule = { config, lib, pkgs, ... }:
|
||||||
|
with lib;
|
||||||
|
let cfg = config.xeserv.services.gohello;
|
||||||
|
in {
|
||||||
|
options.xeserv.services.gohello = {
|
||||||
|
enable = mkEnableOption "Enables the gohello HTTP service";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create a NixOS module that will only be enabled when the configuration
|
||||||
|
setting `xeserv.services.gohello.enable` is set to `true`. Everything else we do
|
||||||
|
here will build on this.
|
||||||
|
|
||||||
|
[You can and probably do want to change the namespace `xeserv` here, it is a
|
||||||
|
placeholder that is not likely to conflict with anything
|
||||||
|
else.](conversation://Mara/happy)
|
||||||
|
|
||||||
|
Create a basic systemd service with this template:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
systemd.services."xeserv.gohello" = {
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|
||||||
|
serviceConfig = let pkg = self.packages.${system}.default;
|
||||||
|
in {
|
||||||
|
Restart = "on-failure";
|
||||||
|
ExecStart = "${pkg}/bin/web-server";
|
||||||
|
DynamicUser = "yes";
|
||||||
|
RuntimeDirectory = "xeserv.gohello";
|
||||||
|
RuntimeDirectoryMode = "0755";
|
||||||
|
StateDirectory = "xeserv.gohello";
|
||||||
|
StateDirectoryMode = "0700";
|
||||||
|
CacheDirectory = "xeserv.gohello";
|
||||||
|
CacheDirectoryMode = "0750";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
<xeblog-conv name="Mara" mood="hacker">
|
||||||
|
NOTE: If you have been following along since before this article was published,
|
||||||
|
you will want to be sure to do the following things to your copy of gohello:
|
||||||
|
|
||||||
|
* Move the definition of `defaultPackage` into the `packages` attribute set with
|
||||||
|
the name `default`
|
||||||
|
* Update `defaultApp` and the other entries to point to
|
||||||
|
`self.packages.${system}.default` instead of `self.defaultPackage.${system}`
|
||||||
|
|
||||||
|
We have updated previous articles and the template accordingly. Annoyingly it
|
||||||
|
seems that this change is new enough that it isn't totally documented on the
|
||||||
|
NixOS wiki. We are working on fixing this.
|
||||||
|
|
||||||
|
</xeblog-conv>
|
||||||
|
|
||||||
|
This will do the following things:
|
||||||
|
|
||||||
|
- Start the service on boot (`multi-user.target` fires once the system is "fully
|
||||||
|
booted" and the network is active)
|
||||||
|
- Automatically restarts the service when it crashes
|
||||||
|
- Starts our `web-server` binary when running the service
|
||||||
|
- Creates a random, unique user account for the service (see
|
||||||
|
[here](http://0pointer.net/blog/dynamic-users-with-systemd.html) for more
|
||||||
|
information on how/why this works)
|
||||||
|
- Creates temporary, home and cache directories for the service, makes sure that
|
||||||
|
random user has permission to use them (with the specified directory modes
|
||||||
|
too)
|
||||||
|
- Enables the service automatically
|
||||||
|
|
||||||
|
Then you need to add the nginx configuration. We want this application to have
|
||||||
|
its own virtual host, so we will need to add that as a configuration option
|
||||||
|
under the `enable` option:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
domain = mkOption rec {
|
||||||
|
type = types.str;
|
||||||
|
default = "gohello.local.cetacean.club";
|
||||||
|
example = default;
|
||||||
|
description = "The domain name for gohello";
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
[Pro tip: `anything.local.cetacean.club` points to `127.0.0.1`. You can use this
|
||||||
|
when testing things.](conversation://Mara/happy)
|
||||||
|
|
||||||
|
And then we can add the nginx configuration under the systemd service definition:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
services.nginx.virtualHosts.${cfg.domain} = {
|
||||||
|
locations."/" = { proxyPass = "http://127.0.0.1:3031"; };
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Your module should look like this:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nixosModule = { config, lib, pkgs, ... }:
|
||||||
|
with lib;
|
||||||
|
let cfg = config.xeserv.services.gohello;
|
||||||
|
in {
|
||||||
|
options.xeserv.services.gohello = {
|
||||||
|
enable = mkEnableOption "Enables the gohello HTTP service";
|
||||||
|
|
||||||
|
domain = mkOption rec {
|
||||||
|
type = types.str;
|
||||||
|
default = "gohello.local.cetacean.club";
|
||||||
|
example = default;
|
||||||
|
description = "The domain name for gohello";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
systemd.services."xeserv.gohello" = {
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|
||||||
|
serviceConfig = let pkg = self.packages.${pkgs.system}.default;
|
||||||
|
in {
|
||||||
|
Restart = "on-failure";
|
||||||
|
ExecStart = "${pkg}/bin/web-server";
|
||||||
|
DynamicUser = "yes";
|
||||||
|
RuntimeDirectory = "xeserv.gohello";
|
||||||
|
RuntimeDirectoryMode = "0755";
|
||||||
|
StateDirectory = "xeserv.gohello";
|
||||||
|
StateDirectoryMode = "0700";
|
||||||
|
CacheDirectory = "xeserv.gohello";
|
||||||
|
CacheDirectoryMode = "0750";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx.virtualHosts.${cfg.domain} = {
|
||||||
|
locations."/" = { proxyPass = "http://127.0.0.1:3031"; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
[The service name is overly defensive. It's intended to avoid conflicting with
|
||||||
|
any other unit on the system named `gohello.service`. Feel free to remove this
|
||||||
|
part, it is really just defensive devops by design to avoid name
|
||||||
|
conflicts.](conversation://Mara/hacker)
|
||||||
|
|
||||||
|
Then you can add it to the container by importing our new module in its
|
||||||
|
configuration and activating the gohello service:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nixosConfigurations.container = nixpkgs.lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
modules = [
|
||||||
|
self.nixosModule
|
||||||
|
({ pkgs, ... }: {
|
||||||
|
# Only allow this to boot as a container
|
||||||
|
boot.isContainer = true;
|
||||||
|
|
||||||
|
# Allow nginx through the firewall
|
||||||
|
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||||
|
|
||||||
|
services.nginx.enable = true;
|
||||||
|
|
||||||
|
xeserv.services.gohello.enable = true;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can update the container's configuration with this command:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ sudo nixos-container update gohello --flake .#container
|
||||||
|
reloading container...
|
||||||
|
```
|
||||||
|
|
||||||
|
And finally make a request to the gohello service running in that container:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ curl http://10.233.1.2 -H "Host: gohello.local.cetacean.club"
|
||||||
|
hello world :)
|
||||||
|
```
|
||||||
|
|
||||||
|
<xeblog-conv name="Mara" mood="hacker">
|
||||||
|
Exercises for the reader:
|
||||||
|
|
||||||
|
Try adding a [nixos
|
||||||
|
option](https://nixos.org/manual/nixos/stable/index.html#sec-writing-modules)
|
||||||
|
that correlates to the `--bind` flag that `gohello` uses as the TCP
|
||||||
|
address to serve HTTP from. You will want to have the type be
|
||||||
|
`types.port`. If you are stuck, see
|
||||||
|
[here](https://github.com/Xe/nixos-configs/tree/master/common/services) for inspiration.
|
||||||
|
|
||||||
|
Also try adding `AmbientCapabilities = "CAP_NET_BIND_SERVICE"` and
|
||||||
|
`CapabilityBoundingSet = "CAP_NET_BIND_SERVICE"` to your `serviceConfig` and
|
||||||
|
bind `gohello` to port 80 without nginx involved at all.
|
||||||
|
|
||||||
|
</xeblog-conv>
|
||||||
|
|
||||||
|
You can delete this container with `sudo nixos-container destroy gohello` when
|
||||||
|
you are done with it.
|
||||||
|
|
||||||
|
These are the basics on how to use NixOS modules. Everything else you can do
|
||||||
|
with them builds off of these fundamental ideas. Modules are templates that
|
||||||
|
coordinate packages and configuration into your desired system state. Containers
|
||||||
|
can let you test out modules without having to add them to your currently
|
||||||
|
running system. Modules declare options and emit configuration based on those
|
||||||
|
options.
|
||||||
|
|
||||||
|
You can also consume NixOS modules from flakes using the input system, however I
|
||||||
|
will go into more details about this at a later date. If you want more examples
|
||||||
|
of NixOS modules, I would suggest checking out my
|
||||||
|
[nixos-configs](https://github.com/Xe/nixos-configs) repository. I have nearly
|
||||||
|
everything neatly modularized and configurable. If you see anything in there
|
||||||
|
that is confusing to you, please [reach out](/contact) and ask. I am happy to
|
||||||
|
answer your questions and your feedback will help me write future posts in this
|
||||||
|
series.
|
||||||
|
|
||||||
|
I also have my "next generation" flakes-based configuration experiments
|
||||||
|
[here](https://tulpa.dev/cadey/nixos-configs) if you want to read through those.
|
||||||
|
I have still been porting over things piecemeal, so it is not a complete replica
|
||||||
|
of my existing configuration.
|
||||||
|
|
||||||
|
Next time I will cover how to install NixOS to a server and deploy system
|
||||||
|
configurations using [deploy-rs](https://github.com/serokell/deploy-rs). This
|
||||||
|
will allow you to have your workstation build configuration for your servers and
|
||||||
|
push out all the changes from there.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Many thanks to Open Skies for being my fearless editor that helps make these
|
||||||
|
things shine.
|
||||||
|
|
||||||
|
In part of this post I use my new Xeact-powered HTML component for some of the
|
||||||
|
conversation fragments, but the sizing was off on my iPhone when I tested it. If
|
||||||
|
you know what I am doing wrong, please [get in touch](/contact).
|
|
@ -0,0 +1,53 @@
|
||||||
|
---
|
||||||
|
title: Stop Using Politics As A Cudgel To Discourage Experimentation
|
||||||
|
date: 2022-04-21
|
||||||
|
tags:
|
||||||
|
- rant
|
||||||
|
- systemd
|
||||||
|
- communityhealth
|
||||||
|
---
|
||||||
|
|
||||||
|
So let's say you get bored one day and you decide you want to do things that god
|
||||||
|
and man have decreed impossible. Let's also say that this exact thing involves a
|
||||||
|
tool that just happens to rustle all of the jimmies (for reasons that are not
|
||||||
|
entirely clear). Then you get it all to a point where you want to submit it
|
||||||
|
upstream so you can get help experimenting with this tool.
|
||||||
|
|
||||||
|
So you submit it to upstream in the experimental branch, expecting very little
|
||||||
|
pushback so you can get help tinkering with things. But once you submit it
|
||||||
|
upstream, [all hell breaks
|
||||||
|
loose](https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/33329).
|
||||||
|
|
||||||
|
Stop using politics as a cudgel to discourage experimentation. Yes it involves
|
||||||
|
systemd. Just because you think that the tool is overcomplicated doesn't mean
|
||||||
|
that other people don't find it useful. Trying to shut down experimentation is
|
||||||
|
how you get people to leave the community or give up participating in open
|
||||||
|
source altogether.
|
||||||
|
|
||||||
|
The reactions in that thread are both disappointing and somewhat to be expected.
|
||||||
|
I don't know why people have such a negative reaction to systemd. It's just an
|
||||||
|
init system, not a religion. It wouldn't have become a good choice for so much
|
||||||
|
of the Linux ecosystem without it having solid technical merits. If it is really
|
||||||
|
that bad then the mantle of responsibility is on you for coming up with a better
|
||||||
|
option.
|
||||||
|
|
||||||
|
[No, OpenRC is not that option. It can be PART OF an option, but it is not a
|
||||||
|
competitor by itself.](conversation://Cadey/coffee)
|
||||||
|
|
||||||
|
I know I said I'd stop ranting on this blog as much, but really this stuff
|
||||||
|
grinds my gears and I feel that I should use my platform for good in this
|
||||||
|
regard. This is inexcusable. I want to reiterate that I have _no_ power in this
|
||||||
|
regard. I am just some random person on a blog that got frustrated at the
|
||||||
|
reactions to this contribution. Some pushback is acceptable. Accusing a
|
||||||
|
contributor of ignorance is inexcusable. Comments like this have no place in
|
||||||
|
open source contributions:
|
||||||
|
|
||||||
|
> SysTemD is the STD of operating systems. There is no "one little poke", you
|
||||||
|
> can't be a little bit pregnant.
|
||||||
|
|
||||||
|
Jake, if you're out there reading this: keep doing this thing. It is a fantastic
|
||||||
|
creation that I thought was impossible. You may have to soft-fork the
|
||||||
|
distribution to get this to work reliably, but I really want to see where this
|
||||||
|
rabbit-hole goes.
|
||||||
|
|
||||||
|
Keep hacking.
|
|
@ -0,0 +1,118 @@
|
||||||
|
---
|
||||||
|
title: "What To Do As A Recruiter When A Gender-diverse Person Asks You To Update Their Name"
|
||||||
|
date: 2022-04-01
|
||||||
|
---
|
||||||
|
|
||||||
|
[I really wish this was an April Fool's post. I had a few ideas planned, but
|
||||||
|
maybe you will get to see them next year.<br /><br />As a reminder, I am
|
||||||
|
speaking for myself and not for my employer.](conversation://Cadey/coffee)
|
||||||
|
|
||||||
|
This post is directed at all of the recruiters that are reading this blog. This
|
||||||
|
is a scenario that many of you may not have dealt with. After having an example
|
||||||
|
of this with a recruiter recently I figure it's a teaching moment.
|
||||||
|
|
||||||
|
I am speaking up about this because I know many others who have gone through the
|
||||||
|
same kinds of problems and have not felt safe to speak up about them. I am not
|
||||||
|
speaking for those people in this post, but I want to use my platform as a
|
||||||
|
blogger to amplify the sentiment of what I have heard over the years.
|
||||||
|
|
||||||
|
## To Recruiters
|
||||||
|
|
||||||
|
As a recruiter, if you are cold-emailing someone, please do the research to get
|
||||||
|
their name correct. If you do not and someone asks you to correct it, do it.
|
||||||
|
|
||||||
|
When gender-diverse people like me get an email that references an out of date
|
||||||
|
name, it is seen as a sign that the person sending that email has not done their
|
||||||
|
research before sending that email out into the void.
|
||||||
|
|
||||||
|
When you correct that name in your system also make sure to cancel all outgoing
|
||||||
|
automated emails to that person. The caching layer of the recruiting system may
|
||||||
|
have already drafted those emails based on a template. If they go out, this will
|
||||||
|
be seen as a _massive sign of disrespect_. It will also make the person
|
||||||
|
receiving that email question if you _actually corrected_ the name in that
|
||||||
|
system or not. It may make the recipient also question if you are just giving
|
||||||
|
them lip service to save face instead of making a genuine effort to ensure that
|
||||||
|
the recruiting system has accurate information in it.
|
||||||
|
|
||||||
|
This is not a good way to foster the kind of trust needed for a gender-diverse
|
||||||
|
person to want to choose your employer as the single point of failure for access
|
||||||
|
to medication, food and regular medical checkups. For many gender-diverse
|
||||||
|
people, changing jobs can mean an interruption of access to life-saving
|
||||||
|
medication.
|
||||||
|
|
||||||
|
You may get a slightly angry reply if you send out emails with incorrect
|
||||||
|
information. This can happen because gender-diverse people are likely to feel
|
||||||
|
like society really doesn't care about them and that they are not being
|
||||||
|
respected to have agency over their identity. To some this is a fact and not
|
||||||
|
a feeling. And with
|
||||||
|
[all](https://www.theguardian.com/us-news/2022/mar/10/idaho-bill-trans-youth-treatment-ban-passes-house)
|
||||||
|
[of](https://www.washingtonpost.com/dc-md-va/2022/03/17/texas-trans-child-abuse-investigations/)
|
||||||
|
[the](https://www.nbcnews.com/nbc-out/out-politics-and-policy/alabama-bill-seeks-ban-hormone-treatments-trans-youth-rcna18512)
|
||||||
|
[actions](https://www.hrc.org/press-releases/breaking-2021-becomes-record-year-for-anti-transgender-legislation)
|
||||||
|
governments have been taking to directly attack the freedoms and rights to
|
||||||
|
self-determination that gender-diverse people like me rely on, you can't blame
|
||||||
|
them for being fed up with the situation. It is not fun to feel like your very
|
||||||
|
existence is made out to be some black mark of doom on Western civilization. It
|
||||||
|
is even less fun to be reminded of that when reading your email inbox. Please
|
||||||
|
understand that we mean well, society is just broken in general.
|
||||||
|
|
||||||
|
The least you can do is ensure that you do _any amount of research_ to ensure
|
||||||
|
that you are using the correct name. It may be a good idea to add the following
|
||||||
|
text to your recruiting emails (before you brag about fundraising is probably
|
||||||
|
best, I tune out about then):
|
||||||
|
|
||||||
|
> If I got your name incorrect, please let me know what name/pronouns you would
|
||||||
|
> like me to update our system to use. I got this name from $SOURCE.
|
||||||
|
|
||||||
|
Adding the source of where you got that name from can help make this less
|
||||||
|
stressful for gender-diverse people. People's names are spattered everywhere
|
||||||
|
across the internet. Letting people know where you got that information from can
|
||||||
|
help them know what to fix if a fix is needed.
|
||||||
|
|
||||||
|
Some chosen names may seem "weird" due to societal biases that serve to ensure
|
||||||
|
that the primary way that people use to refer to eachother in particular are not
|
||||||
|
chosen by the people being referred to. Trust that the person on the other end
|
||||||
|
is being honest about their identity. The truth requires no belief.
|
||||||
|
|
||||||
|
If they ask you to update their pronouns, respect that and ensure you use them
|
||||||
|
without failure. Using the wrong pronouns can be seen as an even worse
|
||||||
|
disrespect than using the wrong name. You do not want this to happen if your
|
||||||
|
goal is to find people to hire.
|
||||||
|
|
||||||
|
## To Gender-diverse People
|
||||||
|
|
||||||
|
Yeah, this situation sucks. I can't disagree. You really do need to assume good
|
||||||
|
faith as much as you can. Most of these recruiter systems rely on ["data
|
||||||
|
enrichment" APIs](https://clearbit.com/) and potentially outdated mass scraping
|
||||||
|
of LinkedIn and people's blogs.
|
||||||
|
|
||||||
|
It can help if you make publicly available posts like
|
||||||
|
[this](/blog/xe-2021-08-07) that unambiguously say what you want people to call
|
||||||
|
you by. Keep it updated in case journalists decide to compare your chosen name
|
||||||
|
to mercenary groups.
|
||||||
|
|
||||||
|
Try to be as polite and direct as possible. Here is an example of how I have
|
||||||
|
asked recruiters to update their information in the past:
|
||||||
|
|
||||||
|
> Please update your files with the name "Xe Iaso" (capital I). I am
|
||||||
|
> slowly moving away from "Christine Dodrill" as the name I use to
|
||||||
|
> represent myself professionally.
|
||||||
|
|
||||||
|
If you are moving away from a "dead name", you may want to use something like
|
||||||
|
this:
|
||||||
|
|
||||||
|
> 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
|
||||||
|
> information about me: https://christine.website.
|
||||||
|
|
||||||
|
Throw in your pronouns too to be safe.
|
||||||
|
|
||||||
|
[I really need to change this blog's domain, but I have such amazing SEO that I
|
||||||
|
really don't want to break it.](conversation://Cadey/coffee)
|
||||||
|
|
||||||
|
Also consider deleting the email and not replying to them. That's totally valid
|
||||||
|
too unless you are in desperate need for a new employer.
|
||||||
|
|
||||||
|
You do not need to justify speaking up about an employer having the wrong name
|
||||||
|
for you. The truth requires no belief. Speaking the truth to power is the
|
||||||
|
essence of valor, which is one of the highest forms of love.
|
13
config.dhall
13
config.dhall
|
@ -1,7 +1,16 @@
|
||||||
let Person =
|
let Person =
|
||||||
{ Type = { name : Text, tags : List Text, gitLink : Text, twitter : Text }
|
{ Type =
|
||||||
|
{ name : Text
|
||||||
|
, tags : List Text
|
||||||
|
, gitLink : Optional Text
|
||||||
|
, twitter : Optional Text
|
||||||
|
}
|
||||||
, default =
|
, default =
|
||||||
{ name = "", tags = [] : List Text, gitLink = "", twitter = "" }
|
{ name = ""
|
||||||
|
, tags = [] : List Text
|
||||||
|
, gitLink = None Text
|
||||||
|
, twitter = None Text
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let Author =
|
let Author =
|
||||||
|
|
|
@ -36,6 +36,7 @@ img {
|
||||||
|
|
||||||
.conversation-chat {
|
.conversation-chat {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gruvbox-dark pre, pre {
|
.gruvbox-dark pre, pre {
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1649676176,
|
||||||
|
"narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"naersk": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1650265945,
|
||||||
|
"narHash": "sha256-SO8+1db4jTOjnwP++29vVgImLIfETSXyoz0FuLkiikE=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "naersk",
|
||||||
|
"rev": "e8f9f8d037774becd82fce2781e1abdb7836d7df",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "naersk",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1650647760,
|
||||||
|
"narHash": "sha256-Ng8CGYLSTxeI+oEux0x+tSRA6K7ydoyfJNQf56ld+Uo=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "b80f570a92d04e8ace67ff09c34aa48708a5c88c",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1650161686,
|
||||||
|
"narHash": "sha256-70ZWAlOQ9nAZ08OU6WY7n4Ij2kOO199dLfNlvO/+pf8=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "1ffba9f2f683063c2b14c9f4d12c55ad5f4ed887",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"naersk": "naersk",
|
||||||
|
"nixpkgs": "nixpkgs_2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
|
@ -0,0 +1,215 @@
|
||||||
|
{
|
||||||
|
description = "A very basic flake";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
naersk.url = "github:nix-community/naersk";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils, naersk }:
|
||||||
|
flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ] (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
naersk-lib = naersk.lib."${system}";
|
||||||
|
src = ./.;
|
||||||
|
in rec {
|
||||||
|
packages = rec {
|
||||||
|
bin = naersk-lib.buildPackage {
|
||||||
|
pname = "xesite-bin";
|
||||||
|
root = src;
|
||||||
|
buildInputs = with pkgs; [ pkg-config openssl git ];
|
||||||
|
};
|
||||||
|
|
||||||
|
config = pkgs.stdenv.mkDerivation {
|
||||||
|
pname = "xesite-config";
|
||||||
|
inherit (bin) version;
|
||||||
|
inherit src;
|
||||||
|
buildInputs = with pkgs; [ dhall ];
|
||||||
|
|
||||||
|
phases = "installPhase";
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
cd $src
|
||||||
|
mkdir -p $out
|
||||||
|
dhall resolve < $src/config.dhall >> $out/config.dhall
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
static = pkgs.stdenv.mkDerivation {
|
||||||
|
pname = "xesite-static";
|
||||||
|
inherit (bin) version;
|
||||||
|
inherit src;
|
||||||
|
|
||||||
|
phases = "installPhase";
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out
|
||||||
|
cp -vrf $src/static $out
|
||||||
|
cp -vrf $src/css $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
posts = pkgs.stdenv.mkDerivation {
|
||||||
|
pname = "xesite-posts";
|
||||||
|
inherit (bin) version;
|
||||||
|
inherit src;
|
||||||
|
|
||||||
|
phases = "installPhase";
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out
|
||||||
|
cp -vrf $src/blog $out
|
||||||
|
cp -vrf $src/gallery $out
|
||||||
|
cp -vrf $src/talks $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
default = pkgs.symlinkJoin {
|
||||||
|
name = "xesite-${bin.version}";
|
||||||
|
paths = [ config posts static bin ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
devShell = pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
# Rust
|
||||||
|
rustc
|
||||||
|
cargo
|
||||||
|
rust-analyzer
|
||||||
|
cargo-watch
|
||||||
|
|
||||||
|
# system dependencies
|
||||||
|
openssl
|
||||||
|
pkg-config
|
||||||
|
|
||||||
|
# kubernetes deployment
|
||||||
|
dhall
|
||||||
|
dhall-json
|
||||||
|
|
||||||
|
# dependency manager
|
||||||
|
niv
|
||||||
|
|
||||||
|
# tools
|
||||||
|
ispell
|
||||||
|
];
|
||||||
|
|
||||||
|
SITE_PREFIX = "devel.";
|
||||||
|
CLACK_SET = "Ashlynn,Terry Davis,Dennis Ritchie";
|
||||||
|
RUST_LOG = "debug";
|
||||||
|
RUST_BACKTRACE = "1";
|
||||||
|
GITHUB_SHA = "devel";
|
||||||
|
};
|
||||||
|
|
||||||
|
nixosModules.bot = { config, lib, ... }:
|
||||||
|
with lib;
|
||||||
|
let cfg = config.xeserv.services.xesite;
|
||||||
|
in {
|
||||||
|
options.within.services.xesite = {
|
||||||
|
enable = mkEnableOption "Activates my personal website";
|
||||||
|
useACME = mkEnableOption "Enables ACME for cert stuff";
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 32837;
|
||||||
|
example = 9001;
|
||||||
|
description =
|
||||||
|
"The port number xesite should listen on for HTTP traffic";
|
||||||
|
};
|
||||||
|
|
||||||
|
domain = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "xesite.akua";
|
||||||
|
example = "christine.website";
|
||||||
|
description =
|
||||||
|
"The domain name that nginx should check against for HTTP hostnames";
|
||||||
|
};
|
||||||
|
|
||||||
|
sockPath = mkOption rec {
|
||||||
|
type = types.str;
|
||||||
|
default = "/srv/within/run/xesite.sock";
|
||||||
|
example = default;
|
||||||
|
description =
|
||||||
|
"The unix domain socket that xesite should listen on";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
users.users.xesite = {
|
||||||
|
createHome = true;
|
||||||
|
description = "github.com/Xe/site";
|
||||||
|
isSystemUser = true;
|
||||||
|
group = "within";
|
||||||
|
home = "/srv/within/xesite";
|
||||||
|
extraGroups = [ "keys" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.xesite = {
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
User = "xesite";
|
||||||
|
Group = "within";
|
||||||
|
Restart = "on-failure";
|
||||||
|
WorkingDirectory = "/srv/within/xesite";
|
||||||
|
RestartSec = "30s";
|
||||||
|
Type = "notify";
|
||||||
|
|
||||||
|
# Security
|
||||||
|
CapabilityBoundingSet = "";
|
||||||
|
DeviceAllow = [ ];
|
||||||
|
NoNewPrivileges = "true";
|
||||||
|
ProtectControlGroups = "true";
|
||||||
|
ProtectClock = "true";
|
||||||
|
PrivateDevices = "true";
|
||||||
|
PrivateUsers = "true";
|
||||||
|
ProtectHome = "true";
|
||||||
|
ProtectHostname = "true";
|
||||||
|
ProtectKernelLogs = "true";
|
||||||
|
ProtectKernelModules = "true";
|
||||||
|
ProtectKernelTunables = "true";
|
||||||
|
ProtectSystem = "true";
|
||||||
|
ProtectProc = "invisible";
|
||||||
|
RemoveIPC = "true";
|
||||||
|
RestrictSUIDSGID = "true";
|
||||||
|
RestrictRealtime = "true";
|
||||||
|
SystemCallArchitectures = "native";
|
||||||
|
SystemCallFilter = [
|
||||||
|
"~@reboot"
|
||||||
|
"~@module"
|
||||||
|
"~@mount"
|
||||||
|
"~@swap"
|
||||||
|
"~@resources"
|
||||||
|
"~@cpu-emulation"
|
||||||
|
"~@obsolete"
|
||||||
|
"~@debug"
|
||||||
|
"~@privileged"
|
||||||
|
];
|
||||||
|
UMask = "007";
|
||||||
|
};
|
||||||
|
|
||||||
|
script = let site = packages.default;
|
||||||
|
in ''
|
||||||
|
export SOCKPATH=${cfg.sockPath}
|
||||||
|
export DOMAIN=${toString cfg.domain}
|
||||||
|
cd ${site}
|
||||||
|
exec ${site}/bin/xesite
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx.virtualHosts."xesite" = {
|
||||||
|
serverName = "${cfg.domain}";
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://unix:${toString cfg.sockPath}";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
};
|
||||||
|
forceSSL = cfg.useACME;
|
||||||
|
useACMEHost = "christine.website";
|
||||||
|
extraConfig = ''
|
||||||
|
access_log /var/log/nginx/xesite.access.log;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ serde = { version = "1", features = ["derive"] }
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-futures = "0.2"
|
tracing-futures = "0.2"
|
||||||
|
url = "2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
use std::{fs, io, path::Path};
|
||||||
|
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::{debug, error, instrument};
|
use tracing::{debug, error, instrument};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
pub type Campaigns = Vec<Object<Campaign>>;
|
pub type Campaigns = Vec<Object<Campaign>>;
|
||||||
pub type Pledges = Vec<Object<Pledge>>;
|
pub type Pledges = Vec<Object<Pledge>>;
|
||||||
|
@ -61,17 +64,33 @@ pub struct User {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct RefreshGrant {
|
||||||
|
pub access_token: String,
|
||||||
|
pub refresh_token: String,
|
||||||
|
pub expires_in: serde_json::Value,
|
||||||
|
pub scope: serde_json::Value,
|
||||||
|
pub token_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("json error: {0:?}")]
|
#[error("json error: {0}")]
|
||||||
Json(#[from] serde_json::Error),
|
Json(#[from] serde_json::Error),
|
||||||
#[error("request error: {0:?}")]
|
|
||||||
|
#[error("request error: {0}")]
|
||||||
Request(#[from] reqwest::Error),
|
Request(#[from] reqwest::Error),
|
||||||
|
|
||||||
|
#[error("{0}")]
|
||||||
|
IO(#[from] io::Error),
|
||||||
|
|
||||||
|
#[error("url parse error: {0}")]
|
||||||
|
URLParse(#[from] url::ParseError),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default, Eq, PartialEq)]
|
||||||
pub struct Credentials {
|
pub struct Credentials {
|
||||||
pub client_id: String,
|
pub client_id: String,
|
||||||
pub client_secret: String,
|
pub client_secret: String,
|
||||||
|
@ -105,12 +124,18 @@ pub struct Links {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub fn new(creds: Credentials) -> Self {
|
pub fn new() -> Result<Self> {
|
||||||
Self {
|
let mut creds = Credentials::default();
|
||||||
|
|
||||||
|
let p = Path::new(".patreon.json");
|
||||||
|
let config = fs::read_to_string(p)?;
|
||||||
|
creds = serde_json::from_str(&config)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
cli: reqwest::Client::new(),
|
cli: reqwest::Client::new(),
|
||||||
base_url: "https://api.patreon.com".into(),
|
base_url: "https://api.patreon.com".into(),
|
||||||
creds: creds,
|
creds: creds,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
|
@ -157,4 +182,57 @@ impl Client {
|
||||||
let data: Data<Vec<Object<Pledge>>, Object<User>> = serde_json::from_str(&data)?;
|
let data: Data<Vec<Object<Pledge>>, Object<User>> = serde_json::from_str(&data)?;
|
||||||
Ok(data.included.unwrap())
|
Ok(data.included.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
POST www.patreon.com/api/oauth2/token
|
||||||
|
?grant_type=refresh_token
|
||||||
|
&refresh_token=<the user‘s refresh_token>
|
||||||
|
&client_id=<your client id>
|
||||||
|
&client_secret=<your client secret>
|
||||||
|
|
||||||
|
1. grab new creds
|
||||||
|
2. serialize new creds to disk
|
||||||
|
3. reload current creds in ram
|
||||||
|
4. ???
|
||||||
|
5. profit!
|
||||||
|
*/
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
pub async fn refresh_token(&mut self) -> Result<()> {
|
||||||
|
let mut u = Url::parse(&self.base_url)?;
|
||||||
|
u.set_path("/api/oauth2/token");
|
||||||
|
u.query_pairs_mut()
|
||||||
|
.append_pair("grant_type", "refresh_token")
|
||||||
|
.append_pair("refresh_token", &self.creds.refresh_token)
|
||||||
|
.append_pair("client_id", &self.creds.client_id)
|
||||||
|
.append_pair("client_secret", &self.creds.client_secret);
|
||||||
|
|
||||||
|
let rg: RefreshGrant = self
|
||||||
|
.cli
|
||||||
|
.post(&u.to_string())
|
||||||
|
.header(
|
||||||
|
"Authorization",
|
||||||
|
format!("Bearer {}", self.creds.access_token),
|
||||||
|
)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?
|
||||||
|
.json()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut creds = self.creds.clone();
|
||||||
|
|
||||||
|
creds.access_token = rg.access_token;
|
||||||
|
creds.refresh_token = rg.refresh_token;
|
||||||
|
|
||||||
|
let p = Path::new(".patreon.json");
|
||||||
|
if p.exists() {
|
||||||
|
fs::remove_file(p)?;
|
||||||
|
}
|
||||||
|
let mut fout = fs::File::create(p)?;
|
||||||
|
serde_json::to_writer(&mut fout, &creds)?;
|
||||||
|
|
||||||
|
self.creds = creds;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
40
shell.nix
40
shell.nix
|
@ -1,40 +0,0 @@
|
||||||
let
|
|
||||||
sources = import ./nix/sources.nix;
|
|
||||||
pkgs =
|
|
||||||
import sources.nixpkgs { overlays = [ (import sources.nixpkgs-mozilla) ]; };
|
|
||||||
dhallpkgs = import sources.easy-dhall-nix { inherit pkgs; };
|
|
||||||
dhall-yaml = dhallpkgs.dhall-yaml-simple;
|
|
||||||
dhall = dhallpkgs.dhall-simple;
|
|
||||||
xepkgs = import sources.xepkgs { inherit pkgs; };
|
|
||||||
rust = pkgs.callPackage ./nix/rust.nix { };
|
|
||||||
in with pkgs;
|
|
||||||
with xepkgs;
|
|
||||||
mkShell {
|
|
||||||
buildInputs = [
|
|
||||||
# Rust
|
|
||||||
rust
|
|
||||||
cargo-watch
|
|
||||||
|
|
||||||
# system dependencies
|
|
||||||
openssl
|
|
||||||
pkg-config
|
|
||||||
|
|
||||||
# kubernetes deployment
|
|
||||||
dhall
|
|
||||||
dhall-yaml
|
|
||||||
|
|
||||||
# dependency manager
|
|
||||||
niv
|
|
||||||
|
|
||||||
# tools
|
|
||||||
ispell
|
|
||||||
];
|
|
||||||
|
|
||||||
SITE_PREFIX = "devel.";
|
|
||||||
CLACK_SET = "Ashlynn,Terry Davis,Dennis Ritchie";
|
|
||||||
RUST_LOG = "debug";
|
|
||||||
RUST_BACKTRACE = "1";
|
|
||||||
RUST_SRC_PATH =
|
|
||||||
"${pkgs.latest.rustChannels.nightly.rust-src}/lib/rustlib/src/rust/library";
|
|
||||||
GITHUB_SHA = "devel";
|
|
||||||
}
|
|
|
@ -1,7 +1,16 @@
|
||||||
let Person =
|
let Person =
|
||||||
{ Type = { name : Text, tags : List Text, gitLink : Text, twitter : Text }
|
{ Type =
|
||||||
|
{ name : Text
|
||||||
|
, tags : List Text
|
||||||
|
, gitLink : Optional Text
|
||||||
|
, twitter : Optional Text
|
||||||
|
}
|
||||||
, default =
|
, default =
|
||||||
{ name = "", tags = [] : List Text, gitLink = "", twitter = "" }
|
{ name = ""
|
||||||
|
, tags = [] : List Text
|
||||||
|
, gitLink = None Text
|
||||||
|
, twitter = None Text
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
in [ Person::{
|
in [ Person::{
|
||||||
|
@ -20,8 +29,8 @@ in [ Person::{
|
||||||
, "istio"
|
, "istio"
|
||||||
, "typescript"
|
, "typescript"
|
||||||
]
|
]
|
||||||
, gitLink = "https://github.com/euforic"
|
, gitLink = Some "https://github.com/euforic"
|
||||||
, twitter = "https://twitter.com/euforic"
|
, twitter = Some "https://twitter.com/euforic"
|
||||||
}
|
}
|
||||||
, Person::{
|
, Person::{
|
||||||
, name = "David Roberts"
|
, name = "David Roberts"
|
||||||
|
@ -41,8 +50,8 @@ in [ Person::{
|
||||||
, "embedded"
|
, "embedded"
|
||||||
, "sql"
|
, "sql"
|
||||||
]
|
]
|
||||||
, gitLink = "https://github.com/ddr0"
|
, gitLink = Some "https://github.com/ddr0"
|
||||||
, twitter = "https://twitter.com/DDR_4"
|
, twitter = Some "https://twitter.com/DDR_4"
|
||||||
}
|
}
|
||||||
, Person::{
|
, Person::{
|
||||||
, name = "Faizan Jamil"
|
, name = "Faizan Jamil"
|
||||||
|
@ -65,8 +74,7 @@ in [ Person::{
|
||||||
, "full-stack"
|
, "full-stack"
|
||||||
, "linux"
|
, "linux"
|
||||||
]
|
]
|
||||||
, gitLink = "https://github.com/faizjamil"
|
, gitLink = Some "https://github.com/faizjamil"
|
||||||
, twitter = "N/A"
|
|
||||||
}
|
}
|
||||||
, Person::{
|
, Person::{
|
||||||
, name = "Joseph Crawley"
|
, name = "Joseph Crawley"
|
||||||
|
@ -80,8 +88,8 @@ in [ Person::{
|
||||||
, "bash"
|
, "bash"
|
||||||
, "linux"
|
, "linux"
|
||||||
]
|
]
|
||||||
, gitLink = "https://github.com/espe-on"
|
, gitLink = Some "https://github.com/espe-on"
|
||||||
, twitter = "https://twitter.com/espe_on_"
|
, twitter = Some "https://twitter.com/espe_on_"
|
||||||
}
|
}
|
||||||
, Person::{
|
, Person::{
|
||||||
, name = "nicoo"
|
, name = "nicoo"
|
||||||
|
@ -96,7 +104,7 @@ in [ Person::{
|
||||||
, "security"
|
, "security"
|
||||||
, "SDR"
|
, "SDR"
|
||||||
]
|
]
|
||||||
, gitLink = "https://github.com/nbraud"
|
, gitLink = Some "https://github.com/nbraud"
|
||||||
}
|
}
|
||||||
, Person::{
|
, Person::{
|
||||||
, name = "Prajjwal Singh"
|
, name = "Prajjwal Singh"
|
||||||
|
@ -112,8 +120,8 @@ in [ Person::{
|
||||||
, "google-cloud"
|
, "google-cloud"
|
||||||
, "typescript"
|
, "typescript"
|
||||||
]
|
]
|
||||||
, gitLink = "https://github.com/Prajjwal"
|
, gitLink = Some "https://github.com/Prajjwal"
|
||||||
, twitter = "https://twitter.com/prajjwalsin"
|
, twitter = Some "https://twitter.com/prajjwalsin"
|
||||||
}
|
}
|
||||||
, Person::{
|
, Person::{
|
||||||
, name = "Piyushh Bhutoria"
|
, name = "Piyushh Bhutoria"
|
||||||
|
@ -125,8 +133,8 @@ in [ Person::{
|
||||||
, "php"
|
, "php"
|
||||||
, "google-cloud"
|
, "google-cloud"
|
||||||
]
|
]
|
||||||
, gitLink = "https://github.com/Piyushhbhutoria"
|
, gitLink = Some "https://github.com/Piyushhbhutoria"
|
||||||
, twitter = "https://twitter.com/PiyushhB"
|
, twitter = Some "https://twitter.com/PiyushhB"
|
||||||
}
|
}
|
||||||
, Person::{
|
, Person::{
|
||||||
, name = "Ryan Casalino"
|
, name = "Ryan Casalino"
|
||||||
|
@ -143,8 +151,7 @@ in [ Person::{
|
||||||
, "flask"
|
, "flask"
|
||||||
, "unix"
|
, "unix"
|
||||||
]
|
]
|
||||||
, gitLink = "https://github.com/rjpcasalino"
|
, gitLink = Some "https://github.com/rjpcasalino"
|
||||||
, twitter = "N/A"
|
|
||||||
}
|
}
|
||||||
, Person::{
|
, Person::{
|
||||||
, name = "Jeremy White"
|
, name = "Jeremy White"
|
||||||
|
@ -163,8 +170,8 @@ in [ Person::{
|
||||||
, "google-cloud"
|
, "google-cloud"
|
||||||
, "azure"
|
, "azure"
|
||||||
]
|
]
|
||||||
, gitLink = "https://github.com/dudymas"
|
, gitLink = Some "https://github.com/dudymas"
|
||||||
, twitter = "https://twitter.com/dudymas"
|
, twitter = Some "https://twitter.com/dudymas"
|
||||||
}
|
}
|
||||||
, Person::{
|
, Person::{
|
||||||
, name = "Zachary McKee"
|
, name = "Zachary McKee"
|
||||||
|
@ -181,14 +188,12 @@ in [ Person::{
|
||||||
, "nginx"
|
, "nginx"
|
||||||
, "gunicorn"
|
, "gunicorn"
|
||||||
]
|
]
|
||||||
, gitLink = "https://github.com/ZacharyRMcKee"
|
, gitLink = Some "https://github.com/ZacharyRMcKee"
|
||||||
, twitter = "N/A"
|
|
||||||
}
|
}
|
||||||
, Person::{
|
, Person::{
|
||||||
, name = "Muazzam Kazmi"
|
, name = "Muazzam Kazmi"
|
||||||
, tags = [ "Rust", "C++", "x86assembly", "WinAPI", "Node.js", "React.js" ]
|
, tags = [ "Rust", "C++", "x86assembly", "WinAPI", "Node.js", "React.js" ]
|
||||||
, gitLink = "https://github.com/muazzamalikazmi"
|
, gitLink = Some "https://github.com/muazzamalikazmi"
|
||||||
, twitter = "N/A"
|
|
||||||
}
|
}
|
||||||
, Person::{
|
, Person::{
|
||||||
, name = "Jeffin Mathew"
|
, name = "Jeffin Mathew"
|
||||||
|
@ -202,8 +207,8 @@ in [ Person::{
|
||||||
, "javascript"
|
, "javascript"
|
||||||
, "iot"
|
, "iot"
|
||||||
]
|
]
|
||||||
, gitLink = "https://github.com/mjeffin"
|
, gitLink = Some "https://github.com/mjeffin"
|
||||||
, twitter = "https://twitter.com/mpjeffin"
|
, twitter = Some "https://twitter.com/mpjeffin"
|
||||||
}
|
}
|
||||||
, Person::{
|
, Person::{
|
||||||
, name = "Nasir Hussain"
|
, name = "Nasir Hussain"
|
||||||
|
@ -218,24 +223,17 @@ in [ Person::{
|
||||||
, "golang"
|
, "golang"
|
||||||
, "rpm packaging"
|
, "rpm packaging"
|
||||||
]
|
]
|
||||||
, gitLink = "https://github.com/nasirhm"
|
, gitLink = Some "https://github.com/nasirhm"
|
||||||
, twitter = "https://twitter.com/_nasirhm_"
|
, twitter = Some "https://twitter.com/_nasirhm_"
|
||||||
}
|
}
|
||||||
, Person::{
|
, Person::{
|
||||||
, name = "Avi Parshan"
|
, name = "Avi Parshan"
|
||||||
, tags =
|
, tags =
|
||||||
[ "python"
|
[ "python", "windows", "javascript", "html", "android", "java", "C#" ]
|
||||||
, "windows"
|
, gitLink = Some "https://github.com/avipars"
|
||||||
, "javascript"
|
, twitter = Some "https://twitter.com/aviinfinity"
|
||||||
, "html"
|
|
||||||
, "android"
|
|
||||||
, "java"
|
|
||||||
, "C#"
|
|
||||||
]
|
|
||||||
, gitLink = "https://github.com/avipars"
|
|
||||||
, twitter = "https://twitter.com/aviinfinity"
|
|
||||||
}
|
}
|
||||||
, Person:: {
|
, Person::{
|
||||||
, name = "Tommy Nguyen"
|
, name = "Tommy Nguyen"
|
||||||
, tags =
|
, tags =
|
||||||
[ "c++"
|
[ "c++"
|
||||||
|
@ -246,6 +244,13 @@ in [ Person::{
|
||||||
, "web"
|
, "web"
|
||||||
, "google-cloud-platform"
|
, "google-cloud-platform"
|
||||||
]
|
]
|
||||||
, gitLink = "https://github.com/remyabel"
|
, gitLink = Some "https://github.com/remyabel"
|
||||||
|
}
|
||||||
|
, Person::{
|
||||||
|
, name = "Krish Jain"
|
||||||
|
, tags =
|
||||||
|
[ "c++", "linux", "c", "python", "ios", "nlp", "machine learning" ]
|
||||||
|
, gitLink = Some "https://github.com/Krish-sysadmin"
|
||||||
|
, twitter = Some "https://twitter.com/krishjain02"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use crate::{post::Post, signalboost::Person};
|
use crate::{post::Post, signalboost::Person};
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{fs, path::PathBuf};
|
use std::{
|
||||||
|
fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
use tracing::{error, instrument};
|
use tracing::{error, instrument};
|
||||||
|
|
||||||
pub mod markdown;
|
pub mod markdown;
|
||||||
|
@ -9,25 +12,26 @@ pub mod poke;
|
||||||
|
|
||||||
#[derive(Clone, Deserialize)]
|
#[derive(Clone, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[serde(rename = "clackSet")]
|
|
||||||
pub(crate) clack_set: Vec<String>,
|
|
||||||
pub(crate) signalboost: Vec<Person>,
|
pub(crate) signalboost: Vec<Person>,
|
||||||
pub(crate) port: u16,
|
|
||||||
#[serde(rename = "resumeFname")]
|
#[serde(rename = "resumeFname")]
|
||||||
pub(crate) resume_fname: PathBuf,
|
pub(crate) resume_fname: PathBuf,
|
||||||
#[serde(rename = "webMentionEndpoint")]
|
|
||||||
pub(crate) webmention_url: String,
|
|
||||||
#[serde(rename = "miToken")]
|
#[serde(rename = "miToken")]
|
||||||
pub(crate) mi_token: String,
|
pub(crate) mi_token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
async fn patrons() -> Result<Option<patreon::Users>> {
|
async fn patrons() -> Result<Option<patreon::Users>> {
|
||||||
use patreon::*;
|
let p = Path::new(".patreon.json");
|
||||||
let creds: Credentials = envy::prefixed("PATREON_")
|
if !p.exists() {
|
||||||
.from_env()
|
info!("{:?} does not exist", p);
|
||||||
.unwrap_or(Credentials::default());
|
return Ok(None);
|
||||||
let cli = Client::new(creds);
|
}
|
||||||
|
|
||||||
|
let mut cli = patreon::Client::new()?;
|
||||||
|
|
||||||
|
if let Err(why) = cli.refresh_token().await {
|
||||||
|
error!("error getting refresh token: {}", why);
|
||||||
|
}
|
||||||
|
|
||||||
match cli.campaign().await {
|
match cli.campaign().await {
|
||||||
Ok(camp) => {
|
Ok(camp) => {
|
||||||
|
|
|
@ -232,7 +232,7 @@ async fn main() -> Result<()> {
|
||||||
let _ = std::fs::remove_file(&sockpath);
|
let _ = std::fs::remove_file(&sockpath);
|
||||||
let uds = UnixListener::bind(&sockpath)?;
|
let uds = UnixListener::bind(&sockpath)?;
|
||||||
axum::Server::builder(ServerAccept { uds })
|
axum::Server::builder(ServerAccept { uds })
|
||||||
.serve(app.into_make_service_with_connect_info::<UdsConnectInfo, _>())
|
.serve(app.into_make_service_with_connect_info::<UdsConnectInfo>())
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
|
|
@ -36,7 +36,7 @@ impl Into<jsonfeed::Item> for Post {
|
||||||
.date_published(self.date.to_rfc3339())
|
.date_published(self.date.to_rfc3339())
|
||||||
.author(
|
.author(
|
||||||
jsonfeed::Author::new()
|
jsonfeed::Author::new()
|
||||||
.name("Christine Dodrill")
|
.name("Xe Iaso")
|
||||||
.url("https://christine.website")
|
.url("https://christine.website")
|
||||||
.avatar("https://christine.website/static/img/avatar.png"),
|
.avatar("https://christine.website/static/img/avatar.png"),
|
||||||
);
|
);
|
||||||
|
@ -83,8 +83,7 @@ impl Post {
|
||||||
|
|
||||||
async fn read_post(dir: &str, fname: PathBuf, cli: &Option<mi::Client>) -> Result<Post> {
|
async fn read_post(dir: &str, fname: PathBuf, cli: &Option<mi::Client>) -> Result<Post> {
|
||||||
debug!(
|
debug!(
|
||||||
"loading {}/{}",
|
"loading {}",
|
||||||
dir,
|
|
||||||
fname.clone().into_os_string().into_string().unwrap()
|
fname.clone().into_os_string().into_string().unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,9 @@ pub struct Person {
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
|
|
||||||
#[serde(rename = "gitLink")]
|
#[serde(rename = "gitLink")]
|
||||||
pub git_link: String,
|
pub git_link: Option<String>,
|
||||||
|
|
||||||
pub twitter: String,
|
pub twitter: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -21,7 +21,12 @@
|
||||||
<div class="cell -4of12 content">
|
<div class="cell -4of12 content">
|
||||||
<big>@person.name</big>
|
<big>@person.name</big>
|
||||||
<p>@for tag in person.tags { @tag }</p>
|
<p>@for tag in person.tags { @tag }</p>
|
||||||
<a href="@person.git_link">GitHub</a> - <a href="@person.twitter">Twitter</a>
|
@if person.git_link.is_some() {
|
||||||
|
<a href="@person.git_link.unwrap()">GitHub</a>
|
||||||
|
}
|
||||||
|
@if person.twitter.is_some() {
|
||||||
|
<a href="@person.twitter.unwrap()">Twitter</a>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue